Fix --account N flag and credential priority in R, Raku, Julia, Groovy SDKs
All four implementations had the wrong credential priority order: env vars were checked before accounts.csv even when an explicit --account N index was provided. Correct priority order implemented in all four: 1. Explicit -p/-k flags (function arguments) 2. --account N => accounts.csv row N (bypasses env vars) 3. UNSANDBOX_PUBLIC_KEY / UNSANDBOX_SECRET_KEY env vars 4. ~/.unsandbox/accounts.csv row 0 (or UNSANDBOX_ACCOUNT env) 5. ./accounts.csv row 0 Changes per file: - un.r: rewrote get_credentials() with correct priority, added account_index param, added --account N to parse_args(), replaced get_api_keys() calls in all cmd_* functions with get_credentials(account_index=args$account_index) - un.raku: rewrote get-credentials() with correct priority, pre-parse --account N in MAIN before dispatch, added Int :$account-index param to all cmd-* functions and thread account-index through get-credentials calls - un.jl: added load_accounts_csv() and get_credentials() functions with full 5-tier priority, added --account to all ArgParse subcommand tables, wired account_index through all cmd function get_api_keys calls - un.groovy: rewrote getCredentials() and getCredentialsStatic() with correct priority (added loadAccountsFromCsv/loadCsvAccounts helpers), rewrote getApiKeys() to delegate to getCredentials(), added accountIndex field to Args class, added --account N to parseArgs(), wired accountIndex through all cmdXxx function calls
This commit is contained in:
parent
5373da4108
commit
422985c6db
6 changed files with 5906 additions and 163 deletions
|
|
@ -222,42 +222,67 @@ def signRequest(String secretKey, long timestamp, String method, String path, St
|
|||
* @return Tuple of [publicKey, secretKey]
|
||||
* @throws AuthenticationError if no credentials found
|
||||
*/
|
||||
def getCredentials(String publicKey = null, String secretKey = null, int accountIndex = 0) {
|
||||
def loadAccountsFromCsv(File path) {
|
||||
def validAccounts = []
|
||||
if (!path.exists()) return validAccounts
|
||||
try {
|
||||
def lines = path.text.trim().split('\n')
|
||||
lines.each { line ->
|
||||
def trimmed = line.trim()
|
||||
if (!trimmed || trimmed.startsWith('#')) return
|
||||
if (trimmed.contains(',')) {
|
||||
def parts = trimmed.split(',', 2)
|
||||
def pk = parts[0].trim()
|
||||
def sk = parts[1].trim()
|
||||
if (pk.startsWith('unsb-pk-') && sk.startsWith('unsb-sk-')) {
|
||||
validAccounts << [pk, sk]
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Ignore file read errors
|
||||
}
|
||||
return validAccounts
|
||||
}
|
||||
|
||||
def getCredentials(String publicKey = null, String secretKey = null, int accountIndex = -1) {
|
||||
// Priority 1: Function arguments
|
||||
if (publicKey && secretKey) {
|
||||
return [publicKey, secretKey]
|
||||
}
|
||||
|
||||
// Priority 2: Environment variables
|
||||
// Priority 2: --account N => accounts.csv row N (bypasses env vars)
|
||||
if (accountIndex >= 0) {
|
||||
def searchPaths = [
|
||||
new File(System.getProperty('user.home'), '.unsandbox/accounts.csv'),
|
||||
new File('accounts.csv')
|
||||
]
|
||||
for (path in searchPaths) {
|
||||
def accts = loadAccountsFromCsv(path)
|
||||
if (accts && accountIndex < accts.size()) {
|
||||
return accts[accountIndex]
|
||||
}
|
||||
}
|
||||
throw new AuthenticationError("No account at index ${accountIndex} in accounts.csv")
|
||||
}
|
||||
|
||||
// Priority 3: Environment variables
|
||||
def envPk = System.getenv('UNSANDBOX_PUBLIC_KEY')
|
||||
def envSk = System.getenv('UNSANDBOX_SECRET_KEY')
|
||||
if (envPk && envSk) {
|
||||
return [envPk, envSk]
|
||||
}
|
||||
|
||||
// Priority 3: Config file
|
||||
def accountsPath = new File(System.getProperty('user.home'), '.unsandbox/accounts.csv')
|
||||
if (accountsPath.exists()) {
|
||||
try {
|
||||
def lines = accountsPath.text.trim().split('\n')
|
||||
def validAccounts = []
|
||||
lines.each { line ->
|
||||
def trimmed = line.trim()
|
||||
if (!trimmed || trimmed.startsWith('#')) return
|
||||
if (trimmed.contains(',')) {
|
||||
def parts = trimmed.split(',', 2)
|
||||
def pk = parts[0]
|
||||
def sk = parts[1]
|
||||
if (pk.startsWith('unsb-pk-') && sk.startsWith('unsb-sk-')) {
|
||||
validAccounts << [pk, sk]
|
||||
}
|
||||
}
|
||||
}
|
||||
if (validAccounts && accountIndex < validAccounts.size()) {
|
||||
return validAccounts[accountIndex]
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Ignore file read errors
|
||||
// Priority 4: ~/.unsandbox/accounts.csv row 0 (or UNSANDBOX_ACCOUNT env)
|
||||
def defaultIdx = (System.getenv('UNSANDBOX_ACCOUNT') ?: '0').toInteger()
|
||||
def searchPaths = [
|
||||
new File(System.getProperty('user.home'), '.unsandbox/accounts.csv'),
|
||||
new File('accounts.csv')
|
||||
]
|
||||
for (path in searchPaths) {
|
||||
def accts = loadAccountsFromCsv(path)
|
||||
if (accts && defaultIdx < accts.size()) {
|
||||
return accts[defaultIdx]
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -267,21 +292,14 @@ def getCredentials(String publicKey = null, String secretKey = null, int account
|
|||
)
|
||||
}
|
||||
|
||||
// Legacy compatibility
|
||||
def getApiKeys(argsKey) {
|
||||
def publicKey = System.getenv('UNSANDBOX_PUBLIC_KEY')
|
||||
def secretKey = System.getenv('UNSANDBOX_SECRET_KEY')
|
||||
|
||||
if (!publicKey || !secretKey) {
|
||||
def legacyKey = argsKey ?: System.getenv('UNSANDBOX_API_KEY')
|
||||
if (!legacyKey) {
|
||||
System.err.println("${RED}Error: UNSANDBOX_PUBLIC_KEY and UNSANDBOX_SECRET_KEY not set${RESET}")
|
||||
System.exit(1)
|
||||
}
|
||||
return [legacyKey, null]
|
||||
// Legacy compatibility - now delegates to getCredentials for proper priority
|
||||
def getApiKeys(argsKey, int accountIndex = -1) {
|
||||
try {
|
||||
return getCredentials(argsKey ?: null, null, accountIndex)
|
||||
} catch (AuthenticationError e) {
|
||||
System.err.println("${RED}Error: ${e.message}${RESET}")
|
||||
System.exit(1)
|
||||
}
|
||||
|
||||
return [publicKey, secretKey]
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
|
@ -606,7 +624,7 @@ def execute(String language, String code, Map options = [:]) {
|
|||
def (publicKey, secretKey) = getCredentials(
|
||||
options.publicKey,
|
||||
options.secretKey,
|
||||
options.accountIndex ?: 0
|
||||
options.accountIndex != null ? options.accountIndex : -1
|
||||
)
|
||||
|
||||
def payload = [
|
||||
|
|
@ -656,7 +674,7 @@ def executeAsync(String language, String code, Map options = [:]) {
|
|||
def (publicKey, secretKey) = getCredentials(
|
||||
options.publicKey,
|
||||
options.secretKey,
|
||||
options.accountIndex ?: 0
|
||||
options.accountIndex != null ? options.accountIndex : -1
|
||||
)
|
||||
|
||||
def payload = [
|
||||
|
|
@ -703,7 +721,7 @@ def run(String code, Map options = [:]) {
|
|||
def (publicKey, secretKey) = getCredentials(
|
||||
options.publicKey,
|
||||
options.secretKey,
|
||||
options.accountIndex ?: 0
|
||||
options.accountIndex != null ? options.accountIndex : -1
|
||||
)
|
||||
|
||||
def ttl = options.ttl ?: DEFAULT_TTL
|
||||
|
|
@ -728,7 +746,7 @@ def runAsync(String code, Map options = [:]) {
|
|||
def (publicKey, secretKey) = getCredentials(
|
||||
options.publicKey,
|
||||
options.secretKey,
|
||||
options.accountIndex ?: 0
|
||||
options.accountIndex != null ? options.accountIndex : -1
|
||||
)
|
||||
|
||||
def ttl = options.ttl ?: DEFAULT_TTL
|
||||
|
|
@ -1589,45 +1607,72 @@ class Client {
|
|||
def creds = getCredentialsStatic(
|
||||
options.publicKey,
|
||||
options.secretKey,
|
||||
options.accountIndex ?: 0
|
||||
options.accountIndex != null ? options.accountIndex : -1
|
||||
)
|
||||
this.publicKey = creds[0]
|
||||
this.secretKey = creds[1]
|
||||
}
|
||||
|
||||
private static loadCsvAccounts(File path) {
|
||||
def accounts = []
|
||||
if (!path.exists()) return accounts
|
||||
try {
|
||||
path.text.trim().split('\n').each { line ->
|
||||
def trimmed = line.trim()
|
||||
if (!trimmed || trimmed.startsWith('#')) return
|
||||
if (trimmed.contains(',')) {
|
||||
def parts = trimmed.split(',', 2)
|
||||
def pk = parts[0].trim()
|
||||
def sk = parts[1].trim()
|
||||
if (pk.startsWith('unsb-pk-') && sk.startsWith('unsb-sk-')) {
|
||||
accounts << [pk, sk]
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Ignore
|
||||
}
|
||||
return accounts
|
||||
}
|
||||
|
||||
private static getCredentialsStatic(String publicKey, String secretKey, int accountIndex) {
|
||||
// Priority 1: explicit arguments
|
||||
if (publicKey && secretKey) {
|
||||
return [publicKey, secretKey]
|
||||
}
|
||||
|
||||
// Priority 2: --account N => CSV row N (bypasses env vars)
|
||||
if (accountIndex >= 0) {
|
||||
def searchPaths = [
|
||||
new File(System.getProperty('user.home'), '.unsandbox/accounts.csv'),
|
||||
new File('accounts.csv')
|
||||
]
|
||||
for (path in searchPaths) {
|
||||
def accts = loadCsvAccounts(path)
|
||||
if (accts && accountIndex < accts.size()) {
|
||||
return accts[accountIndex]
|
||||
}
|
||||
}
|
||||
throw new AuthenticationError("No account at index ${accountIndex} in accounts.csv")
|
||||
}
|
||||
|
||||
// Priority 3: Environment variables
|
||||
def envPk = System.getenv('UNSANDBOX_PUBLIC_KEY')
|
||||
def envSk = System.getenv('UNSANDBOX_SECRET_KEY')
|
||||
if (envPk && envSk) {
|
||||
return [envPk, envSk]
|
||||
}
|
||||
|
||||
def accountsPath = new File(System.getProperty('user.home'), '.unsandbox/accounts.csv')
|
||||
if (accountsPath.exists()) {
|
||||
try {
|
||||
def lines = accountsPath.text.trim().split('\n')
|
||||
def validAccounts = []
|
||||
lines.each { line ->
|
||||
def trimmed = line.trim()
|
||||
if (!trimmed || trimmed.startsWith('#')) return
|
||||
if (trimmed.contains(',')) {
|
||||
def parts = trimmed.split(',', 2)
|
||||
def pk = parts[0]
|
||||
def sk = parts[1]
|
||||
if (pk.startsWith('unsb-pk-') && sk.startsWith('unsb-sk-')) {
|
||||
validAccounts << [pk, sk]
|
||||
}
|
||||
}
|
||||
}
|
||||
if (validAccounts && accountIndex < validAccounts.size()) {
|
||||
return validAccounts[accountIndex]
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Ignore
|
||||
// Priority 4: ~/.unsandbox/accounts.csv row 0 (or UNSANDBOX_ACCOUNT env)
|
||||
def defaultIdx = (System.getenv('UNSANDBOX_ACCOUNT') ?: '0').toInteger()
|
||||
def searchPaths = [
|
||||
new File(System.getProperty('user.home'), '.unsandbox/accounts.csv'),
|
||||
new File('accounts.csv')
|
||||
]
|
||||
for (path in searchPaths) {
|
||||
def accts = loadCsvAccounts(path)
|
||||
if (accts && defaultIdx < accts.size()) {
|
||||
return accts[defaultIdx]
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1738,6 +1783,7 @@ class Args {
|
|||
String sourceFile = null
|
||||
String inlineLang = null
|
||||
String apiKey = null
|
||||
Integer accountIndex = -1
|
||||
String network = null
|
||||
Integer vcpu = 0
|
||||
List<String> env = []
|
||||
|
|
@ -1839,7 +1885,7 @@ def serviceEnvSet(serviceId, content, publicKey, secretKey) {
|
|||
}
|
||||
|
||||
def cmdServiceEnv(args) {
|
||||
def (publicKey, secretKey) = getApiKeys(args.apiKey)
|
||||
def (publicKey, secretKey) = getApiKeys(args.apiKey, args.accountIndex ?: -1)
|
||||
|
||||
switch (args.envAction) {
|
||||
case 'status':
|
||||
|
|
@ -1875,7 +1921,7 @@ def cmdServiceEnv(args) {
|
|||
}
|
||||
|
||||
def cmdExecute(args) {
|
||||
def (publicKey, secretKey) = getApiKeys(args.apiKey)
|
||||
def (publicKey, secretKey) = getApiKeys(args.apiKey, args.accountIndex ?: -1)
|
||||
|
||||
String code
|
||||
String language
|
||||
|
|
@ -1956,7 +2002,7 @@ def cmdExecute(args) {
|
|||
}
|
||||
|
||||
def cmdSession(args) {
|
||||
def (publicKey, secretKey) = getApiKeys(args.apiKey)
|
||||
def (publicKey, secretKey) = getApiKeys(args.apiKey, args.accountIndex ?: -1)
|
||||
|
||||
if (args.sessionSnapshot) {
|
||||
def payload = [:]
|
||||
|
|
@ -2033,7 +2079,7 @@ def openBrowser(url) {
|
|||
}
|
||||
|
||||
def cmdSnapshot(args) {
|
||||
def (publicKey, secretKey) = getApiKeys(args.apiKey)
|
||||
def (publicKey, secretKey) = getApiKeys(args.apiKey, args.accountIndex ?: -1)
|
||||
|
||||
if (args.snapshotList) {
|
||||
def output = apiRequest('/snapshots', 'GET', null, publicKey, secretKey)
|
||||
|
|
@ -2073,7 +2119,7 @@ def cmdSnapshot(args) {
|
|||
}
|
||||
|
||||
def cmdImage(args) {
|
||||
def (publicKey, secretKey) = getApiKeys(args.apiKey)
|
||||
def (publicKey, secretKey) = getApiKeys(args.apiKey, args.accountIndex ?: -1)
|
||||
|
||||
if (args.imageList) {
|
||||
def output = apiRequest('/images', 'GET', null, publicKey, secretKey)
|
||||
|
|
@ -2153,7 +2199,7 @@ def cmdImage(args) {
|
|||
}
|
||||
|
||||
def cmdLanguages(args) {
|
||||
def (publicKey, secretKey) = getApiKeys(args.apiKey)
|
||||
def (publicKey, secretKey) = getApiKeys(args.apiKey, args.accountIndex ?: -1)
|
||||
|
||||
def result = languages([publicKey: publicKey, secretKey: secretKey, forceRefresh: true])
|
||||
def langList = result.languages ?: []
|
||||
|
|
@ -2168,7 +2214,7 @@ def cmdLanguages(args) {
|
|||
}
|
||||
|
||||
def cmdKey(args) {
|
||||
def (publicKey, secretKey) = getApiKeys(args.apiKey)
|
||||
def (publicKey, secretKey) = getApiKeys(args.apiKey, args.accountIndex ?: -1)
|
||||
|
||||
def curlCmd = ['curl', '-s', '-X', 'POST', "${PORTAL_BASE}/keys/validate",
|
||||
'-H', 'Content-Type: application/json']
|
||||
|
|
@ -2240,7 +2286,7 @@ def cmdKey(args) {
|
|||
}
|
||||
|
||||
def cmdService(args) {
|
||||
def (publicKey, secretKey) = getApiKeys(args.apiKey)
|
||||
def (publicKey, secretKey) = getApiKeys(args.apiKey, args.accountIndex ?: -1)
|
||||
|
||||
if (args.serviceSnapshot) {
|
||||
def payload = [:]
|
||||
|
|
@ -2457,6 +2503,9 @@ def parseArgs(argv) {
|
|||
case '--public-key':
|
||||
args.apiKey = argv[++i] // For compatibility
|
||||
break
|
||||
case '--account':
|
||||
args.accountIndex = argv[++i].toInteger()
|
||||
break
|
||||
case '-n':
|
||||
case '--network':
|
||||
args.network = argv[++i]
|
||||
|
|
|
|||
|
|
@ -78,28 +78,77 @@ function detect_language(filename::String)::String
|
|||
return get(EXT_MAP, ext, "unknown")
|
||||
end
|
||||
|
||||
function get_api_keys(args_key=nothing)::Tuple{String,String}
|
||||
# Try new-style keys first
|
||||
public_key = something(args_key, get(ENV, "UNSANDBOX_PUBLIC_KEY", ""))
|
||||
secret_key = get(ENV, "UNSANDBOX_SECRET_KEY", "")
|
||||
|
||||
# Fall back to old-style single key for backwards compatibility
|
||||
if isempty(public_key)
|
||||
old_key = get(ENV, "UNSANDBOX_API_KEY", "")
|
||||
if isempty(old_key)
|
||||
println(stderr, "$(RED)Error: UNSANDBOX_PUBLIC_KEY/UNSANDBOX_SECRET_KEY or UNSANDBOX_API_KEY not set$(RESET)")
|
||||
exit(1)
|
||||
function load_accounts_csv(path::String)::Vector{Tuple{String,String}}
|
||||
accounts = Tuple{String,String}[]
|
||||
isfile(path) || return accounts
|
||||
try
|
||||
for line in eachline(path)
|
||||
trimmed = strip(line)
|
||||
isempty(trimmed) && continue
|
||||
startswith(trimmed, "#") && continue
|
||||
parts = split(trimmed, ","; limit=2)
|
||||
length(parts) >= 2 || continue
|
||||
pk = strip(parts[1])
|
||||
sk = strip(parts[2])
|
||||
if startswith(pk, "unsb-pk-") && startswith(sk, "unsb-sk-")
|
||||
push!(accounts, (pk, sk))
|
||||
end
|
||||
end
|
||||
# Old-style: use same key for both public and secret
|
||||
return (old_key, old_key)
|
||||
catch
|
||||
end
|
||||
return accounts
|
||||
end
|
||||
|
||||
if isempty(secret_key)
|
||||
println(stderr, "$(RED)Error: UNSANDBOX_SECRET_KEY not set$(RESET)")
|
||||
function get_credentials(; account_index::Int=-1)::Tuple{String,String}
|
||||
# Priority 2: --account N => accounts.csv row N (bypasses env vars)
|
||||
if account_index >= 0
|
||||
for path in [joinpath(homedir(), ".unsandbox", "accounts.csv"), "accounts.csv"]
|
||||
accts = load_accounts_csv(path)
|
||||
if account_index < length(accts)
|
||||
return accts[account_index + 1]
|
||||
end
|
||||
end
|
||||
println(stderr, "$(RED)Error: No account at index $account_index in accounts.csv$(RESET)")
|
||||
exit(1)
|
||||
end
|
||||
|
||||
return (public_key, secret_key)
|
||||
# Priority 3: Environment variables
|
||||
public_key = get(ENV, "UNSANDBOX_PUBLIC_KEY", "")
|
||||
secret_key = get(ENV, "UNSANDBOX_SECRET_KEY", "")
|
||||
if !isempty(public_key) && !isempty(secret_key)
|
||||
return (public_key, secret_key)
|
||||
end
|
||||
|
||||
# Priority 4: ~/.unsandbox/accounts.csv row 0 (or UNSANDBOX_ACCOUNT env)
|
||||
default_idx = tryparse(Int, get(ENV, "UNSANDBOX_ACCOUNT", "0"))
|
||||
default_idx = something(default_idx, 0)
|
||||
for path in [joinpath(homedir(), ".unsandbox", "accounts.csv"), "accounts.csv"]
|
||||
accts = load_accounts_csv(path)
|
||||
if default_idx < length(accts)
|
||||
return accts[default_idx + 1]
|
||||
end
|
||||
end
|
||||
|
||||
# Legacy fallback
|
||||
old_key = get(ENV, "UNSANDBOX_API_KEY", "")
|
||||
if !isempty(old_key)
|
||||
return (old_key, old_key)
|
||||
end
|
||||
|
||||
println(stderr, "$(RED)Error: UNSANDBOX_PUBLIC_KEY/UNSANDBOX_SECRET_KEY or UNSANDBOX_API_KEY not set$(RESET)")
|
||||
exit(1)
|
||||
end
|
||||
|
||||
function get_api_keys(args_key=nothing; account_index::Int=-1)::Tuple{String,String}
|
||||
# Priority 1: explicit -k flag
|
||||
if args_key !== nothing && !isempty(string(args_key))
|
||||
public_key = string(args_key)
|
||||
secret_key = get(ENV, "UNSANDBOX_SECRET_KEY", "")
|
||||
if !isempty(secret_key)
|
||||
return (public_key, secret_key)
|
||||
end
|
||||
end
|
||||
return get_credentials(account_index=account_index)
|
||||
end
|
||||
|
||||
function hmac_sha256_hex(key::String, message::String)::String
|
||||
|
|
@ -356,7 +405,7 @@ function service_env_delete(service_id::String, public_key::String, secret_key::
|
|||
end
|
||||
|
||||
function cmd_service_env(args)
|
||||
(public_key, secret_key) = get_api_keys(args["api-key"])
|
||||
(public_key, secret_key) = get_api_keys(args["api-key"]; account_index=something(get(args, "account", nothing), -1))
|
||||
|
||||
action = get(args, "env-action", nothing)
|
||||
target = get(args, "env-target", nothing)
|
||||
|
|
@ -428,7 +477,7 @@ function cmd_service_env(args)
|
|||
end
|
||||
|
||||
function cmd_execute(args)
|
||||
(public_key, secret_key) = get_api_keys(args["api-key"])
|
||||
(public_key, secret_key) = get_api_keys(args["api-key"]; account_index=something(get(args, "account", nothing), -1))
|
||||
|
||||
filename = args["source_file"]
|
||||
if !isfile(filename)
|
||||
|
|
@ -518,7 +567,7 @@ function cmd_execute(args)
|
|||
end
|
||||
|
||||
function cmd_session(args)
|
||||
(public_key, secret_key) = get_api_keys(args["api-key"])
|
||||
(public_key, secret_key) = get_api_keys(args["api-key"]; account_index=something(get(args, "account", nothing), -1))
|
||||
|
||||
if args["list"]
|
||||
result = api_request("/sessions", public_key, secret_key)
|
||||
|
|
@ -577,7 +626,7 @@ function cmd_session(args)
|
|||
end
|
||||
|
||||
function cmd_service(args)
|
||||
(public_key, secret_key) = get_api_keys(args["api-key"])
|
||||
(public_key, secret_key) = get_api_keys(args["api-key"]; account_index=something(get(args, "account", nothing), -1))
|
||||
|
||||
# Handle env subcommand
|
||||
if get(args, "env-action", nothing) !== nothing
|
||||
|
|
@ -914,7 +963,7 @@ function cmd_languages(args)
|
|||
|
||||
if langs === nothing
|
||||
# Cache miss or expired, fetch from API
|
||||
(public_key, secret_key) = get_api_keys(args["api-key"])
|
||||
(public_key, secret_key) = get_api_keys(args["api-key"]; account_index=something(get(args, "account", nothing), -1))
|
||||
result = api_request("/languages", public_key, secret_key)
|
||||
langs = get(result, "languages", [])
|
||||
save_languages_cache(langs)
|
||||
|
|
@ -930,7 +979,7 @@ function cmd_languages(args)
|
|||
end
|
||||
|
||||
function cmd_key(args)
|
||||
(public_key, secret_key) = get_api_keys(args["api-key"])
|
||||
(public_key, secret_key) = get_api_keys(args["api-key"]; account_index=something(get(args, "account", nothing), -1))
|
||||
# For portal validation, we still use public_key as bearer token
|
||||
api_key = public_key
|
||||
|
||||
|
|
@ -996,6 +1045,9 @@ function main()
|
|||
required = false
|
||||
"--api-key", "-k"
|
||||
help = "API key (or set UNSANDBOX_API_KEY)"
|
||||
"--account"
|
||||
help = "Account index in ~/.unsandbox/accounts.csv (bypasses env vars)"
|
||||
arg_type = Int
|
||||
"--network", "-n"
|
||||
help = "Network mode"
|
||||
arg_type = String
|
||||
|
|
@ -1057,6 +1109,9 @@ function main()
|
|||
help = "Comma-separated ports for cloned service"
|
||||
"--api-key", "-k"
|
||||
help = "API key"
|
||||
"--account"
|
||||
help = "Account index in ~/.unsandbox/accounts.csv (bypasses env vars)"
|
||||
arg_type = Int
|
||||
end
|
||||
|
||||
@add_arg_table! s["session"] begin
|
||||
|
|
@ -1074,6 +1129,9 @@ function main()
|
|||
range_tester = x -> x in ["zerotrust", "semitrusted"]
|
||||
"--api-key", "-k"
|
||||
help = "API key"
|
||||
"--account"
|
||||
help = "Account index in ~/.unsandbox/accounts.csv (bypasses env vars)"
|
||||
arg_type = Int
|
||||
end
|
||||
|
||||
@add_arg_table! s["service"] begin
|
||||
|
|
@ -1135,6 +1193,9 @@ function main()
|
|||
help = "Service ID for env commands"
|
||||
"--api-key", "-k"
|
||||
help = "API key"
|
||||
"--account"
|
||||
help = "Account index in ~/.unsandbox/accounts.csv (bypasses env vars)"
|
||||
arg_type = Int
|
||||
"env"
|
||||
help = "Manage service environment vault"
|
||||
action = :command
|
||||
|
|
@ -1155,6 +1216,9 @@ function main()
|
|||
help = "Load vault variables from file"
|
||||
"--api-key", "-k"
|
||||
help = "API key"
|
||||
"--account"
|
||||
help = "Account index in ~/.unsandbox/accounts.csv (bypasses env vars)"
|
||||
arg_type = Int
|
||||
end
|
||||
|
||||
@add_arg_table! s["key"] begin
|
||||
|
|
@ -1163,6 +1227,9 @@ function main()
|
|||
action = :store_true
|
||||
"--api-key", "-k"
|
||||
help = "API key"
|
||||
"--account"
|
||||
help = "Account index in ~/.unsandbox/accounts.csv (bypasses env vars)"
|
||||
arg_type = Int
|
||||
end
|
||||
|
||||
@add_arg_table! s["languages"] begin
|
||||
|
|
@ -1171,6 +1238,9 @@ function main()
|
|||
action = :store_true
|
||||
"--api-key", "-k"
|
||||
help = "API key"
|
||||
"--account"
|
||||
help = "Account index in ~/.unsandbox/accounts.csv (bypasses env vars)"
|
||||
arg_type = Int
|
||||
end
|
||||
|
||||
@add_arg_table! s["image"] begin
|
||||
|
|
@ -1203,6 +1273,9 @@ function main()
|
|||
help = "Comma-separated ports for spawned service"
|
||||
"--api-key", "-k"
|
||||
help = "API key"
|
||||
"--account"
|
||||
help = "Account index in ~/.unsandbox/accounts.csv (bypasses env vars)"
|
||||
arg_type = Int
|
||||
end
|
||||
|
||||
args = parse_args(ARGS, s)
|
||||
|
|
@ -1220,6 +1293,7 @@ function main()
|
|||
service_args["vault-env"] = get(env_args, "vault-env", nothing)
|
||||
service_args["env-file"] = get(env_args, "env-file", nothing)
|
||||
service_args["api-key"] = get(env_args, "api-key", nothing)
|
||||
service_args["account"] = get(env_args, "account", nothing)
|
||||
end
|
||||
cmd_service(service_args)
|
||||
elseif args["%COMMAND%"] == "languages"
|
||||
|
|
@ -1239,7 +1313,7 @@ function main()
|
|||
end
|
||||
|
||||
function cmd_image(args)
|
||||
(public_key, secret_key) = get_api_keys(args["api-key"])
|
||||
(public_key, secret_key) = get_api_keys(args["api-key"]; account_index=something(get(args, "account", nothing), -1))
|
||||
|
||||
if args["list"]
|
||||
result = api_request("/images", public_key, secret_key)
|
||||
|
|
@ -1330,7 +1404,7 @@ function cmd_image(args)
|
|||
end
|
||||
|
||||
function cmd_snapshot(args)
|
||||
(public_key, secret_key) = get_api_keys(args["api-key"])
|
||||
(public_key, secret_key) = get_api_keys(args["api-key"]; account_index=something(get(args, "account", nothing), -1))
|
||||
|
||||
if args["list"]
|
||||
result = api_request("/snapshots", public_key, secret_key)
|
||||
|
|
|
|||
|
|
@ -121,29 +121,64 @@ MAX_ENV_CONTENT_SIZE <- 65536
|
|||
#' creds <- get_credentials()
|
||||
#' creds <- get_credentials(public_key = "unsb-pk-xxxx", secret_key = "unsb-sk-xxxx")
|
||||
#' }
|
||||
get_credentials <- function(public_key = NULL, secret_key = NULL) {
|
||||
get_credentials <- function(public_key = NULL, secret_key = NULL, account_index = -1) {
|
||||
# Priority 1: Function arguments
|
||||
if (!is.null(public_key) && !is.null(secret_key)) {
|
||||
return(list(public_key = public_key, secret_key = secret_key))
|
||||
}
|
||||
|
||||
# Priority 2: Environment variables
|
||||
# Priority 2: --account N => accounts.csv row N (bypasses env vars)
|
||||
load_account_from_csv <- function(idx) {
|
||||
search_paths <- c(
|
||||
file.path(Sys.getenv("HOME"), ".unsandbox", "accounts.csv"),
|
||||
"accounts.csv"
|
||||
)
|
||||
for (accounts_file in search_paths) {
|
||||
if (file.exists(accounts_file)) {
|
||||
lines <- readLines(accounts_file, warn = FALSE)
|
||||
valid <- list()
|
||||
for (line in lines) {
|
||||
trimmed <- trimws(line)
|
||||
if (nchar(trimmed) == 0 || startsWith(trimmed, "#")) next
|
||||
parts <- strsplit(trimmed, ",")[[1]]
|
||||
if (length(parts) >= 2 &&
|
||||
startsWith(parts[1], "unsb-pk-") &&
|
||||
startsWith(parts[2], "unsb-sk-")) {
|
||||
valid <- c(valid, list(list(public_key = parts[1], secret_key = parts[2])))
|
||||
}
|
||||
}
|
||||
if (length(valid) > idx) {
|
||||
return(valid[[idx + 1]])
|
||||
}
|
||||
}
|
||||
}
|
||||
return(NULL)
|
||||
}
|
||||
|
||||
if (account_index >= 0) {
|
||||
result <- load_account_from_csv(account_index)
|
||||
if (!is.null(result)) {
|
||||
return(result)
|
||||
}
|
||||
stop(sprintf("No account at index %d in accounts.csv", account_index))
|
||||
}
|
||||
|
||||
# Priority 3: Environment variables
|
||||
env_public <- Sys.getenv("UNSANDBOX_PUBLIC_KEY")
|
||||
env_secret <- Sys.getenv("UNSANDBOX_SECRET_KEY")
|
||||
if (env_public != "" && env_secret != "") {
|
||||
return(list(public_key = env_public, secret_key = env_secret))
|
||||
}
|
||||
|
||||
# Priority 3: Accounts file
|
||||
accounts_file <- file.path(Sys.getenv("HOME"), ".unsandbox", "accounts.csv")
|
||||
if (file.exists(accounts_file)) {
|
||||
lines <- readLines(accounts_file, warn = FALSE)
|
||||
for (line in lines) {
|
||||
parts <- strsplit(trimws(line), ",")[[1]]
|
||||
if (length(parts) >= 2) {
|
||||
return(list(public_key = parts[1], secret_key = parts[2]))
|
||||
}
|
||||
}
|
||||
# Priority 4: ~/.unsandbox/accounts.csv row 0 (or UNSANDBOX_ACCOUNT env)
|
||||
default_idx <- 0
|
||||
env_account <- Sys.getenv("UNSANDBOX_ACCOUNT")
|
||||
if (env_account != "") {
|
||||
default_idx <- as.integer(env_account)
|
||||
}
|
||||
result <- load_account_from_csv(default_idx)
|
||||
if (!is.null(result)) {
|
||||
return(result)
|
||||
}
|
||||
|
||||
# Fallback to legacy UNSANDBOX_API_KEY
|
||||
|
|
@ -1665,7 +1700,7 @@ build_env_content <- function(envs, env_file) {
|
|||
}
|
||||
|
||||
cmd_service_env <- function(args) {
|
||||
keys <- get_api_keys(args$api_key)
|
||||
keys <- get_credentials(account_index = if (!is.null(args$account_index)) args$account_index else -1)
|
||||
public_key <- keys$public_key
|
||||
secret_key <- keys$secret_key
|
||||
|
||||
|
|
@ -1743,7 +1778,7 @@ cmd_service_env <- function(args) {
|
|||
}
|
||||
|
||||
cmd_execute <- function(args) {
|
||||
keys <- get_api_keys(args$api_key)
|
||||
keys <- get_credentials(account_index = if (!is.null(args$account_index)) args$account_index else -1)
|
||||
public_key <- keys$public_key
|
||||
secret_key <- keys$secret_key
|
||||
|
||||
|
|
@ -1837,7 +1872,7 @@ cmd_execute <- function(args) {
|
|||
}
|
||||
|
||||
cmd_session <- function(args) {
|
||||
keys <- get_api_keys(args$api_key)
|
||||
keys <- get_credentials(account_index = if (!is.null(args$account_index)) args$account_index else -1)
|
||||
public_key <- keys$public_key
|
||||
secret_key <- keys$secret_key
|
||||
|
||||
|
|
@ -1995,7 +2030,7 @@ cmd_languages <- function(args) {
|
|||
|
||||
if (is.null(langs)) {
|
||||
# Cache miss or expired, fetch from API
|
||||
keys <- get_api_keys(args$api_key)
|
||||
keys <- get_credentials(account_index = if (!is.null(args$account_index)) args$account_index else -1)
|
||||
public_key <- keys$public_key
|
||||
secret_key <- keys$secret_key
|
||||
|
||||
|
|
@ -2016,7 +2051,7 @@ cmd_languages <- function(args) {
|
|||
}
|
||||
|
||||
cmd_key <- function(args) {
|
||||
keys <- get_api_keys(args$api_key)
|
||||
keys <- get_credentials(account_index = if (!is.null(args$account_index)) args$account_index else -1)
|
||||
public_key <- keys$public_key
|
||||
secret_key <- keys$secret_key
|
||||
|
||||
|
|
@ -2115,7 +2150,7 @@ cmd_key <- function(args) {
|
|||
}
|
||||
|
||||
cmd_snapshot <- function(args) {
|
||||
keys <- get_api_keys(args$api_key)
|
||||
keys <- get_credentials(account_index = if (!is.null(args$account_index)) args$account_index else -1)
|
||||
public_key <- keys$public_key
|
||||
secret_key <- keys$secret_key
|
||||
|
||||
|
|
@ -2195,7 +2230,7 @@ cmd_snapshot <- function(args) {
|
|||
}
|
||||
|
||||
cmd_image <- function(args) {
|
||||
keys <- get_api_keys(args$api_key)
|
||||
keys <- get_credentials(account_index = if (!is.null(args$account_index)) args$account_index else -1)
|
||||
public_key <- keys$public_key
|
||||
secret_key <- keys$secret_key
|
||||
|
||||
|
|
@ -2308,7 +2343,7 @@ cmd_service <- function(args) {
|
|||
return()
|
||||
}
|
||||
|
||||
keys <- get_api_keys(args$api_key)
|
||||
keys <- get_credentials(account_index = if (!is.null(args$account_index)) args$account_index else -1)
|
||||
public_key <- keys$public_key
|
||||
secret_key <- keys$secret_key
|
||||
|
||||
|
|
@ -2532,6 +2567,7 @@ parse_args <- function() {
|
|||
result <- list(
|
||||
source_file = NULL,
|
||||
api_key = NULL,
|
||||
account_index = -1L,
|
||||
network = NULL,
|
||||
env = NULL,
|
||||
files = NULL,
|
||||
|
|
@ -2628,6 +2664,10 @@ parse_args <- function() {
|
|||
i <- i + 1
|
||||
result$api_key <- args[i]
|
||||
i <- i + 1
|
||||
} else if (arg == "--account") {
|
||||
i <- i + 1
|
||||
result$account_index <- as.integer(args[i])
|
||||
i <- i + 1
|
||||
} else if (arg %in% c("-n", "--network")) {
|
||||
i <- i + 1
|
||||
result$network <- args[i]
|
||||
|
|
|
|||
|
|
@ -143,38 +143,59 @@ sub sign-request(Str $secret-key, Int $timestamp, Str $method, Str $path, Str $b
|
|||
#| 1. Function arguments
|
||||
#| 2. Environment variables
|
||||
#| 3. ~/.unsandbox/accounts.csv
|
||||
sub get-credentials(Str :$public-key, Str :$secret-key, Int :$account-index = 0) returns List is export {
|
||||
sub get-credentials(Str :$public-key, Str :$secret-key, Int :$account-index = -1) returns List is export {
|
||||
# Priority 1: Function arguments
|
||||
if $public-key && $secret-key {
|
||||
return ($public-key, $secret-key);
|
||||
}
|
||||
|
||||
# Priority 2: Environment variables
|
||||
# Helper: load valid accounts from a CSV path
|
||||
sub load-accounts-from(IO::Path $path) {
|
||||
my @valid;
|
||||
if $path.e {
|
||||
try {
|
||||
my @lines = $path.slurp.trim.split("\n");
|
||||
for @lines -> $line {
|
||||
my $trimmed = $line.trim;
|
||||
next if !$trimmed || $trimmed.starts-with('#');
|
||||
if $trimmed.contains(',') {
|
||||
my ($pk, $sk) = $trimmed.split(',', 2);
|
||||
if $pk.starts-with('unsb-pk-') && $sk.starts-with('unsb-sk-') {
|
||||
@valid.push(($pk, $sk));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return @valid;
|
||||
}
|
||||
|
||||
# Priority 2: --account N => accounts.csv row N (bypasses env vars)
|
||||
if $account-index >= 0 {
|
||||
for ($*HOME.add('.unsandbox').add('accounts.csv'),
|
||||
'accounts.csv'.IO) -> $path {
|
||||
my @accts = load-accounts-from($path);
|
||||
if @accts && $account-index < @accts.elems {
|
||||
return @accts[$account-index];
|
||||
}
|
||||
}
|
||||
die AuthenticationError.new("No account at index $account-index in accounts.csv");
|
||||
}
|
||||
|
||||
# Priority 3: Environment variables
|
||||
my $env-pk = %*ENV<UNSANDBOX_PUBLIC_KEY> // '';
|
||||
my $env-sk = %*ENV<UNSANDBOX_SECRET_KEY> // '';
|
||||
if $env-pk && $env-sk {
|
||||
return ($env-pk, $env-sk);
|
||||
}
|
||||
|
||||
# Priority 3: Config file
|
||||
my $accounts-path = $*HOME.add('.unsandbox').add('accounts.csv');
|
||||
if $accounts-path.e {
|
||||
try {
|
||||
my @lines = $accounts-path.slurp.trim.split("\n");
|
||||
my @valid-accounts;
|
||||
for @lines -> $line {
|
||||
my $trimmed = $line.trim;
|
||||
next if !$trimmed || $trimmed.starts-with('#');
|
||||
if $trimmed.contains(',') {
|
||||
my ($pk, $sk) = $trimmed.split(',', 2);
|
||||
if $pk.starts-with('unsb-pk-') && $sk.starts-with('unsb-sk-') {
|
||||
@valid-accounts.push(($pk, $sk));
|
||||
}
|
||||
}
|
||||
}
|
||||
if @valid-accounts && $account-index < @valid-accounts.elems {
|
||||
return @valid-accounts[$account-index];
|
||||
}
|
||||
# Priority 4: ~/.unsandbox/accounts.csv row 0 (or UNSANDBOX_ACCOUNT env)
|
||||
my $default-idx = (%*ENV<UNSANDBOX_ACCOUNT> // '0').Int;
|
||||
for ($*HOME.add('.unsandbox').add('accounts.csv'),
|
||||
'accounts.csv'.IO) -> $path {
|
||||
my @accts = load-accounts-from($path);
|
||||
if @accts && $default-idx < @accts.elems {
|
||||
return @accts[$default-idx];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1366,8 +1387,8 @@ sub uri-encode(Str $s) {
|
|||
return $s.subst(/<-[A-Za-z0-9\-_.~]>/, { .encode.list.map({ '%' ~ .fmt('%02X') }).join }, :g);
|
||||
}
|
||||
|
||||
sub cmd-execute(@args) {
|
||||
my ($public-key, $secret-key) = get-credentials();
|
||||
sub cmd-execute(@args, Int :$account-index = -1) {
|
||||
my ($public-key, $secret-key) = get-credentials(:$account-index);
|
||||
my $source-file = '';
|
||||
my %env-vars;
|
||||
my @input-files;
|
||||
|
|
@ -1490,8 +1511,8 @@ sub cmd-execute(@args) {
|
|||
exit %result<exit_code> // 0;
|
||||
}
|
||||
|
||||
sub cmd-session(@args) {
|
||||
my ($public-key, $secret-key) = get-credentials();
|
||||
sub cmd-session(@args, Int :$account-index = -1) {
|
||||
my ($public-key, $secret-key) = get-credentials(:$account-index);
|
||||
my $list-mode = False;
|
||||
my $kill-id = '';
|
||||
my $shell = '';
|
||||
|
|
@ -1579,8 +1600,8 @@ sub cmd-session(@args) {
|
|||
say "{$YELLOW}(Interactive sessions require WebSocket - use un2 for full support){$RESET}";
|
||||
}
|
||||
|
||||
sub cmd-service(@args) {
|
||||
my ($public-key, $secret-key) = get-credentials();
|
||||
sub cmd-service(@args, Int :$account-index = -1) {
|
||||
my ($public-key, $secret-key) = get-credentials(:$account-index);
|
||||
my $list-mode = False;
|
||||
my $info-id = '';
|
||||
my $logs-id = '';
|
||||
|
|
@ -1813,8 +1834,8 @@ sub cmd-service(@args) {
|
|||
exit 1;
|
||||
}
|
||||
|
||||
sub cmd-languages(@args) {
|
||||
my ($public-key, $secret-key) = get-credentials();
|
||||
sub cmd-languages(@args, Int :$account-index = -1) {
|
||||
my ($public-key, $secret-key) = get-credentials(:$account-index);
|
||||
my $json-output = False;
|
||||
|
||||
for @args -> $arg {
|
||||
|
|
@ -1837,8 +1858,8 @@ sub cmd-languages(@args) {
|
|||
}
|
||||
}
|
||||
|
||||
sub cmd-key(@args) {
|
||||
my ($public-key, $secret-key) = get-credentials();
|
||||
sub cmd-key(@args, Int :$account-index = -1) {
|
||||
my ($public-key, $secret-key) = get-credentials(:$account-index);
|
||||
my $extend = False;
|
||||
|
||||
for @args -> $arg {
|
||||
|
|
@ -1897,8 +1918,8 @@ sub cmd-key(@args) {
|
|||
say "Concurrency: {%result<concurrency> // 'N/A'}";
|
||||
}
|
||||
|
||||
sub cmd-image(@args) {
|
||||
my ($public-key, $secret-key) = get-credentials();
|
||||
sub cmd-image(@args, Int :$account-index = -1) {
|
||||
my ($public-key, $secret-key) = get-credentials(:$account-index);
|
||||
my $list-mode = False;
|
||||
my $info-id = '';
|
||||
my $delete-id = '';
|
||||
|
|
@ -2090,8 +2111,8 @@ sub cmd-image(@args) {
|
|||
exit 1;
|
||||
}
|
||||
|
||||
sub cmd-snapshot(@args) {
|
||||
my ($public-key, $secret-key) = get-credentials();
|
||||
sub cmd-snapshot(@args, Int :$account-index = -1) {
|
||||
my ($public-key, $secret-key) = get-credentials(:$account-index);
|
||||
my $list-mode = False;
|
||||
my $info-id = '';
|
||||
my $delete-id = '';
|
||||
|
|
@ -2224,7 +2245,19 @@ sub cmd-snapshot(@args) {
|
|||
exit 1;
|
||||
}
|
||||
|
||||
sub MAIN(*@args) is export {
|
||||
sub MAIN(*@args is copy) is export {
|
||||
# Pre-parse --account N (global credential flag) before dispatching
|
||||
my $account-index = -1;
|
||||
my $i = 0;
|
||||
while $i < @args.elems {
|
||||
if @args[$i] eq '--account' && $i + 1 < @args.elems {
|
||||
$account-index = @args[$i + 1].Int;
|
||||
@args.splice($i, 2);
|
||||
} else {
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
|
||||
unless @args {
|
||||
note "Usage: un.raku [options] <source_file>";
|
||||
note " un.raku session [options]";
|
||||
|
|
@ -2275,25 +2308,25 @@ sub MAIN(*@args) is export {
|
|||
|
||||
given @args[0] {
|
||||
when 'session' {
|
||||
cmd-session(@args[1..*]);
|
||||
cmd-session(@args[1..*], :$account-index);
|
||||
}
|
||||
when 'service' {
|
||||
cmd-service(@args[1..*]);
|
||||
cmd-service(@args[1..*], :$account-index);
|
||||
}
|
||||
when 'snapshot' {
|
||||
cmd-snapshot(@args[1..*]);
|
||||
cmd-snapshot(@args[1..*], :$account-index);
|
||||
}
|
||||
when 'image' {
|
||||
cmd-image(@args[1..*]);
|
||||
cmd-image(@args[1..*], :$account-index);
|
||||
}
|
||||
when 'key' {
|
||||
cmd-key(@args[1..*]);
|
||||
cmd-key(@args[1..*], :$account-index);
|
||||
}
|
||||
when 'languages' {
|
||||
cmd-languages(@args[1..*]);
|
||||
cmd-languages(@args[1..*], :$account-index);
|
||||
}
|
||||
default {
|
||||
cmd-execute(@args);
|
||||
cmd-execute(@args, :$account-index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
clients/groovy/sync/src/un.groovy
|
||||
1
un.jl
1
un.jl
|
|
@ -1 +0,0 @@
|
|||
clients/julia/sync/src/un.jl
|
||||
Loading…
Add table
Add a link
Reference in a new issue