Add --account N flag and CSV credential resolution to Clojure, Common Lisp, Scheme, PowerShell
Each implementation gains: - load-accounts-csv function parsing public_key,secret_key rows, skipping # comments and blanks - Full 5-tier credential resolution: -p/-k flags > --account N > env vars > ~/.unsandbox/accounts.csv > ./accounts.csv - --account N CLI flag (pre-parsed and stripped before main arg dispatch) - UNSANDBOX_ACCOUNT env var support for default row selection in csv fallback
This commit is contained in:
parent
ce327b35e9
commit
59eb474995
4 changed files with 276 additions and 37 deletions
|
|
@ -118,17 +118,51 @@
|
|||
(or (second (re-find pattern-str json-str))
|
||||
(second (re-find pattern-num json-str)))))
|
||||
|
||||
(def ^:dynamic *account-index* nil)
|
||||
|
||||
(defn load-accounts-csv [path index]
|
||||
(when (.exists (io/file path))
|
||||
(try
|
||||
(let [lines (str/split-lines (slurp path))
|
||||
rows (->> lines
|
||||
(filter #(and (> (count %) 0)
|
||||
(not (str/starts-with? % "#"))))
|
||||
(map #(str/split % #"," 2))
|
||||
(filter #(>= (count %) 2)))]
|
||||
(when (< index (count rows))
|
||||
(let [row (nth rows index)]
|
||||
[(str/trim (first row)) (str/trim (second row))])))
|
||||
(catch Exception _ nil))))
|
||||
|
||||
(defn get-api-keys []
|
||||
(let [public-key (System/getenv "UNSANDBOX_PUBLIC_KEY")
|
||||
secret-key (System/getenv "UNSANDBOX_SECRET_KEY")
|
||||
api-key (System/getenv "UNSANDBOX_API_KEY")]
|
||||
api-key (System/getenv "UNSANDBOX_API_KEY")
|
||||
home (System/getenv "HOME")
|
||||
account-idx (or *account-index* 0)]
|
||||
(cond
|
||||
;; --account N: load row N from accounts.csv, bypasses env vars
|
||||
(some? *account-index*)
|
||||
(or (load-accounts-csv (str home "/.unsandbox/accounts.csv") account-idx)
|
||||
(load-accounts-csv "./accounts.csv" account-idx)
|
||||
(do
|
||||
(binding [*out* *err*]
|
||||
(println (str "Error: account " account-idx " not found in accounts.csv")))
|
||||
(System/exit 1)))
|
||||
;; env vars
|
||||
(and public-key secret-key) [public-key secret-key]
|
||||
api-key [api-key nil]
|
||||
:else (do
|
||||
;; accounts.csv fallback (row 0 or UNSANDBOX_ACCOUNT)
|
||||
:else
|
||||
(let [row-idx (if-let [acc (System/getenv "UNSANDBOX_ACCOUNT")]
|
||||
(try (Integer/parseInt acc) (catch Exception _ 0))
|
||||
0)]
|
||||
(or (load-accounts-csv (str home "/.unsandbox/accounts.csv") row-idx)
|
||||
(load-accounts-csv "./accounts.csv" row-idx)
|
||||
(do
|
||||
(binding [*out* *err*]
|
||||
(println "Error: UNSANDBOX_PUBLIC_KEY and UNSANDBOX_SECRET_KEY not set (or UNSANDBOX_API_KEY for backwards compat)"))
|
||||
(System/exit 1)))))
|
||||
(System/exit 1)))))))
|
||||
|
||||
(defn get-api-key []
|
||||
(first (get-api-keys)))
|
||||
|
|
@ -1216,4 +1250,14 @@
|
|||
(recur (rest args) file env-vars artifacts out-dir network vcpu session-action session-id session-shell session-input-files
|
||||
service-action service-id service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file service-unfreeze-on-demand key-extend image-action image-id image-source-type image-visibility image-name image-ports mode))))
|
||||
|
||||
(parse-args *command-line-args*)
|
||||
(let [raw-args *command-line-args*
|
||||
account-val (second (drop-while #(not= % "--account") raw-args))
|
||||
account-idx (when account-val
|
||||
(try (Integer/parseInt account-val) (catch Exception _ nil)))
|
||||
filtered-args (loop [in raw-args out []]
|
||||
(cond
|
||||
(empty? in) out
|
||||
(= (first in) "--account") (recur (drop 2 in) out)
|
||||
:else (recur (rest in) (conj out (first in)))))]
|
||||
(binding [*account-index* account-idx]
|
||||
(parse-args filtered-args)))
|
||||
|
|
|
|||
|
|
@ -343,16 +343,63 @@
|
|||
(curl-delete api-key (format nil "/services/~a/env" service-id))
|
||||
(format t "~aVault deleted: ~a~a~%" *green* service-id *reset*))
|
||||
|
||||
(defparameter *account-index* nil)
|
||||
|
||||
(defun load-accounts-csv (path index)
|
||||
"Load row INDEX from a CSV file of public_key,secret_key pairs.
|
||||
Skips blank lines and lines starting with #. Returns (list pk sk) or nil."
|
||||
(when (probe-file path)
|
||||
(handler-case
|
||||
(with-open-file (stream path)
|
||||
(let ((rows nil))
|
||||
(loop for line = (read-line stream nil nil)
|
||||
while line do
|
||||
(let ((trimmed (string-trim '(#\Space #\Tab #\Return) line)))
|
||||
(when (and (> (length trimmed) 0)
|
||||
(not (char= (char trimmed 0) #\#)))
|
||||
(let ((comma-pos (position #\, trimmed)))
|
||||
(when comma-pos
|
||||
(push (list (string-trim '(#\Space #\Tab) (subseq trimmed 0 comma-pos))
|
||||
(string-trim '(#\Space #\Tab) (subseq trimmed (1+ comma-pos))))
|
||||
rows))))))
|
||||
(let ((rows (nreverse rows)))
|
||||
(when (< index (length rows))
|
||||
(nth index rows)))))
|
||||
(error () nil))))
|
||||
|
||||
(defun get-api-keys ()
|
||||
(let ((public-key (uiop:getenv "UNSANDBOX_PUBLIC_KEY"))
|
||||
(secret-key (uiop:getenv "UNSANDBOX_SECRET_KEY"))
|
||||
(api-key (uiop:getenv "UNSANDBOX_API_KEY")))
|
||||
(api-key (uiop:getenv "UNSANDBOX_API_KEY"))
|
||||
(home (uiop:getenv "HOME")))
|
||||
(cond
|
||||
;; --account N: load row N from accounts.csv, bypasses env vars
|
||||
((not (null *account-index*))
|
||||
(let ((result (or (load-accounts-csv
|
||||
(format nil "~a/.unsandbox/accounts.csv" home)
|
||||
*account-index*)
|
||||
(load-accounts-csv "./accounts.csv" *account-index*))))
|
||||
(or result
|
||||
(progn
|
||||
(format *error-output* "Error: account ~a not found in accounts.csv~%" *account-index*)
|
||||
(uiop:quit 1)))))
|
||||
;; env vars
|
||||
((and public-key secret-key) (list public-key secret-key))
|
||||
(api-key (list api-key nil))
|
||||
(t (progn
|
||||
(format t "Error: UNSANDBOX_PUBLIC_KEY and UNSANDBOX_SECRET_KEY not set (or UNSANDBOX_API_KEY for backwards compat)~%")
|
||||
(uiop:quit 1))))))
|
||||
;; accounts.csv fallback (row 0 or UNSANDBOX_ACCOUNT env var)
|
||||
(t
|
||||
(let* ((acc-env (uiop:getenv "UNSANDBOX_ACCOUNT"))
|
||||
(row-idx (if acc-env
|
||||
(handler-case (parse-integer acc-env) (error () 0))
|
||||
0))
|
||||
(result (or (load-accounts-csv
|
||||
(format nil "~a/.unsandbox/accounts.csv" home)
|
||||
row-idx)
|
||||
(load-accounts-csv "./accounts.csv" row-idx))))
|
||||
(or result
|
||||
(progn
|
||||
(format *error-output* "Error: UNSANDBOX_PUBLIC_KEY and UNSANDBOX_SECRET_KEY not set (or UNSANDBOX_API_KEY for backwards compat)~%")
|
||||
(uiop:quit 1))))))))
|
||||
|
||||
(defun get-api-key ()
|
||||
(first (get-api-keys)))
|
||||
|
|
@ -912,8 +959,26 @@
|
|||
do (format t "~a~%" (subseq array-content (1+ start) end))))
|
||||
(format t "")))))
|
||||
|
||||
(defun strip-account-flag (args)
|
||||
"Remove --account N from args, set *account-index* as side-effect. Returns filtered args."
|
||||
(let ((result nil)
|
||||
(i 0)
|
||||
(vec (coerce args 'vector)))
|
||||
(loop while (< i (length vec)) do
|
||||
(cond
|
||||
((string= (aref vec i) "--account")
|
||||
(when (< (+ i 1) (length vec))
|
||||
(setf *account-index*
|
||||
(handler-case (parse-integer (aref vec (+ i 1))) (error () nil))))
|
||||
(incf i 2))
|
||||
(t
|
||||
(push (aref vec i) result)
|
||||
(incf i))))
|
||||
(nreverse result)))
|
||||
|
||||
(defun main ()
|
||||
(let ((args (uiop:command-line-arguments)))
|
||||
(let* ((raw-args (uiop:command-line-arguments))
|
||||
(args (strip-account-flag raw-args)))
|
||||
(if (null args)
|
||||
(progn
|
||||
(format t "Usage: un.lisp [options] <source_file>~%")
|
||||
|
|
|
|||
|
|
@ -61,7 +61,43 @@ $EXT_MAP = @{
|
|||
".raku" = "raku"; ".m" = "objc"; ".awk" = "awk"
|
||||
}
|
||||
|
||||
$script:AccountIndex = $null
|
||||
|
||||
function Load-AccountsCSV {
|
||||
param($Path, $Index)
|
||||
if (-not (Test-Path $Path)) { return $null }
|
||||
try {
|
||||
$rows = @()
|
||||
Get-Content $Path | Where-Object {
|
||||
$_.Trim() -ne "" -and -not $_.TrimStart().StartsWith("#")
|
||||
} | ForEach-Object {
|
||||
$parts = $_ -split ",", 2
|
||||
if ($parts.Count -ge 2) {
|
||||
$rows += ,@($parts[0].Trim(), $parts[1].Trim())
|
||||
}
|
||||
}
|
||||
if ($Index -lt $rows.Count) {
|
||||
return $rows[$Index]
|
||||
}
|
||||
} catch {}
|
||||
return $null
|
||||
}
|
||||
|
||||
function Get-ApiKeys {
|
||||
$home = $env:HOME
|
||||
if (-not $home) { $home = $env:USERPROFILE }
|
||||
|
||||
# --account N: load row N from accounts.csv, bypasses env vars
|
||||
if ($null -ne $script:AccountIndex) {
|
||||
$result = Load-AccountsCSV -Path "$home/.unsandbox/accounts.csv" -Index $script:AccountIndex
|
||||
if (-not $result) {
|
||||
$result = Load-AccountsCSV -Path "./accounts.csv" -Index $script:AccountIndex
|
||||
}
|
||||
if ($result) { return $result }
|
||||
Write-Error "Error: account $($script:AccountIndex) not found in accounts.csv"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$publicKey = $env:UNSANDBOX_PUBLIC_KEY
|
||||
$secretKey = $env:UNSANDBOX_SECRET_KEY
|
||||
|
||||
|
|
@ -71,11 +107,27 @@ function Get-ApiKeys {
|
|||
$secretKey = ""
|
||||
}
|
||||
|
||||
if (-not $publicKey) {
|
||||
Write-Error "Error: UNSANDBOX_PUBLIC_KEY or UNSANDBOX_API_KEY not set"
|
||||
exit 1
|
||||
if ($publicKey -and $secretKey) {
|
||||
return @($publicKey, $secretKey)
|
||||
}
|
||||
return @($publicKey, $secretKey)
|
||||
|
||||
if ($publicKey) {
|
||||
return @($publicKey, "")
|
||||
}
|
||||
|
||||
# accounts.csv fallback (row 0 or UNSANDBOX_ACCOUNT env var)
|
||||
$rowIdx = 0
|
||||
if ($env:UNSANDBOX_ACCOUNT) {
|
||||
try { $rowIdx = [int]$env:UNSANDBOX_ACCOUNT } catch {}
|
||||
}
|
||||
$result = Load-AccountsCSV -Path "$home/.unsandbox/accounts.csv" -Index $rowIdx
|
||||
if (-not $result) {
|
||||
$result = Load-AccountsCSV -Path "./accounts.csv" -Index $rowIdx
|
||||
}
|
||||
if ($result) { return $result }
|
||||
|
||||
Write-Error "Error: UNSANDBOX_PUBLIC_KEY or UNSANDBOX_API_KEY not set"
|
||||
exit 1
|
||||
}
|
||||
|
||||
function Invoke-Api {
|
||||
|
|
@ -1179,38 +1231,49 @@ Key options:
|
|||
exit 0
|
||||
}
|
||||
|
||||
if ($args[0] -eq "session") {
|
||||
Invoke-Session -Args $args[1..($args.Count-1)]
|
||||
} elseif ($args[0] -eq "service") {
|
||||
Invoke-Service -Args $args[1..($args.Count-1)]
|
||||
} elseif ($args[0] -eq "snapshot") {
|
||||
Invoke-Snapshot -Args $args[1..($args.Count-1)]
|
||||
} elseif ($args[0] -eq "image") {
|
||||
Invoke-Image -Args $args[1..($args.Count-1)]
|
||||
} elseif ($args[0] -eq "languages") {
|
||||
Invoke-Languages -Args $args[1..($args.Count-1)]
|
||||
} elseif ($args[0] -eq "key") {
|
||||
Invoke-Key -Args $args[1..($args.Count-1)]
|
||||
# Pre-parse --account N from args, strip from effective arg list
|
||||
$effectiveArgs = @()
|
||||
for ($i = 0; $i -lt $args.Count; $i++) {
|
||||
if ($args[$i] -eq "--account" -and ($i + 1) -lt $args.Count) {
|
||||
try { $script:AccountIndex = [int]$args[$i + 1] } catch {}
|
||||
$i++
|
||||
} else {
|
||||
$effectiveArgs += $args[$i]
|
||||
}
|
||||
}
|
||||
|
||||
if ($effectiveArgs[0] -eq "session") {
|
||||
Invoke-Session -Args $effectiveArgs[1..($effectiveArgs.Count-1)]
|
||||
} elseif ($effectiveArgs[0] -eq "service") {
|
||||
Invoke-Service -Args $effectiveArgs[1..($effectiveArgs.Count-1)]
|
||||
} elseif ($effectiveArgs[0] -eq "snapshot") {
|
||||
Invoke-Snapshot -Args $effectiveArgs[1..($effectiveArgs.Count-1)]
|
||||
} elseif ($effectiveArgs[0] -eq "image") {
|
||||
Invoke-Image -Args $effectiveArgs[1..($effectiveArgs.Count-1)]
|
||||
} elseif ($effectiveArgs[0] -eq "languages") {
|
||||
Invoke-Languages -Args $effectiveArgs[1..($effectiveArgs.Count-1)]
|
||||
} elseif ($effectiveArgs[0] -eq "key") {
|
||||
Invoke-Key -Args $effectiveArgs[1..($effectiveArgs.Count-1)]
|
||||
} else {
|
||||
# Parse execute args
|
||||
$sourceFile = $null
|
||||
$envVars = @{}
|
||||
$network = $null
|
||||
|
||||
for ($i = 0; $i -lt $args.Count; $i++) {
|
||||
switch ($args[$i]) {
|
||||
for ($i = 0; $i -lt $effectiveArgs.Count; $i++) {
|
||||
switch ($effectiveArgs[$i]) {
|
||||
"-e" {
|
||||
$kv = $args[$i+1] -split "=", 2
|
||||
$kv = $effectiveArgs[$i+1] -split "=", 2
|
||||
$envVars[$kv[0]] = $kv[1]
|
||||
$i++
|
||||
}
|
||||
"-n" { $network = $args[$i+1]; $i++ }
|
||||
"-n" { $network = $effectiveArgs[$i+1]; $i++ }
|
||||
default {
|
||||
if ($args[$i].StartsWith("-")) {
|
||||
Write-Error "${RED}Unknown option: $($args[$i])${RESET}"
|
||||
if ($effectiveArgs[$i].StartsWith("-")) {
|
||||
Write-Error "${RED}Unknown option: $($effectiveArgs[$i])${RESET}"
|
||||
exit 1
|
||||
} else {
|
||||
$sourceFile = $args[$i]
|
||||
$sourceFile = $effectiveArgs[$i]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -445,16 +445,72 @@
|
|||
(curl-delete api-key (format #f "/services/~a/env" service-id))
|
||||
(format #t "~aVault deleted for: ~a~a\n" green service-id reset))
|
||||
|
||||
(define *account-index* #f)
|
||||
|
||||
(define (load-accounts-csv path index)
|
||||
"Load row INDEX from a CSV file of public_key,secret_key pairs.
|
||||
Skips blank lines and lines starting with #. Returns (list pk sk) or #f."
|
||||
(if (not (access? path R_OK))
|
||||
#f
|
||||
(catch #t
|
||||
(lambda ()
|
||||
(let ((port (open-input-file path))
|
||||
(rows '()))
|
||||
(let loop ()
|
||||
(let ((line (read-line port)))
|
||||
(when (not (eof-object? line))
|
||||
(let ((trimmed (string-trim-both line)))
|
||||
(when (and (> (string-length trimmed) 0)
|
||||
(not (char=? (string-ref trimmed 0) #\#)))
|
||||
(let ((comma-pos (string-index trimmed #\,)))
|
||||
(when comma-pos
|
||||
(set! rows (append rows
|
||||
(list (list
|
||||
(string-trim-both (substring trimmed 0 comma-pos))
|
||||
(string-trim-both (substring trimmed (1+ comma-pos)))))))))))
|
||||
(loop))))
|
||||
(close-input-port port)
|
||||
(if (< index (length rows))
|
||||
(list-ref rows index)
|
||||
#f)))
|
||||
(lambda (key . args) #f))))
|
||||
|
||||
(define (get-api-keys)
|
||||
(let ((public-key (getenv "UNSANDBOX_PUBLIC_KEY"))
|
||||
(secret-key (getenv "UNSANDBOX_SECRET_KEY"))
|
||||
(api-key (getenv "UNSANDBOX_API_KEY")))
|
||||
(api-key (getenv "UNSANDBOX_API_KEY"))
|
||||
(home (getenv "HOME")))
|
||||
(cond
|
||||
;; --account N: load row N from accounts.csv, bypasses env vars
|
||||
((not (eq? *account-index* #f))
|
||||
(let ((result (or (load-accounts-csv
|
||||
(string-append home "/.unsandbox/accounts.csv")
|
||||
*account-index*)
|
||||
(load-accounts-csv "./accounts.csv" *account-index*))))
|
||||
(or result
|
||||
(begin
|
||||
(format (current-error-port) "Error: account ~a not found in accounts.csv\n" *account-index*)
|
||||
(exit 1)))))
|
||||
;; env vars
|
||||
((and public-key secret-key) (list public-key secret-key))
|
||||
(api-key (list api-key #f))
|
||||
(else (begin
|
||||
(display "Error: UNSANDBOX_PUBLIC_KEY and UNSANDBOX_SECRET_KEY not set (or UNSANDBOX_API_KEY for backwards compat)\n" (current-error-port))
|
||||
(exit 1))))))
|
||||
;; accounts.csv fallback (row 0 or UNSANDBOX_ACCOUNT env var)
|
||||
(else
|
||||
(let* ((acc-env (getenv "UNSANDBOX_ACCOUNT"))
|
||||
(row-idx (if acc-env
|
||||
(catch #t
|
||||
(lambda () (string->number acc-env))
|
||||
(lambda (key . args) 0))
|
||||
0))
|
||||
(row-idx (if (number? row-idx) (inexact->exact row-idx) 0))
|
||||
(result (or (load-accounts-csv
|
||||
(string-append home "/.unsandbox/accounts.csv")
|
||||
row-idx)
|
||||
(load-accounts-csv "./accounts.csv" row-idx))))
|
||||
(or result
|
||||
(begin
|
||||
(display "Error: UNSANDBOX_PUBLIC_KEY and UNSANDBOX_SECRET_KEY not set (or UNSANDBOX_API_KEY for backwards compat)\n" (current-error-port))
|
||||
(exit 1)))))))
|
||||
|
||||
(define (get-api-key)
|
||||
(car (get-api-keys)))
|
||||
|
|
@ -1286,4 +1342,15 @@
|
|||
(else
|
||||
(execute-cmd (car args))))))
|
||||
|
||||
(main (cdr (command-line)))
|
||||
(define (strip-account-flag args)
|
||||
"Remove --account N from args list, setting *account-index* as side-effect."
|
||||
(let loop ((in args) (out '()))
|
||||
(cond
|
||||
((null? in) (reverse out))
|
||||
((and (equal? (car in) "--account") (pair? (cdr in)))
|
||||
(let ((n (string->number (cadr in))))
|
||||
(set! *account-index* (if (number? n) (inexact->exact n) #f))
|
||||
(loop (cddr in) out)))
|
||||
(else (loop (cdr in) (cons (car in) out))))))
|
||||
|
||||
(main (strip-account-flag (cdr (command-line))))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue