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]
|
* @return Tuple of [publicKey, secretKey]
|
||||||
* @throws AuthenticationError if no credentials found
|
* @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
|
// Priority 1: Function arguments
|
||||||
if (publicKey && secretKey) {
|
if (publicKey && secretKey) {
|
||||||
return [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 envPk = System.getenv('UNSANDBOX_PUBLIC_KEY')
|
||||||
def envSk = System.getenv('UNSANDBOX_SECRET_KEY')
|
def envSk = System.getenv('UNSANDBOX_SECRET_KEY')
|
||||||
if (envPk && envSk) {
|
if (envPk && envSk) {
|
||||||
return [envPk, envSk]
|
return [envPk, envSk]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Priority 3: Config file
|
// Priority 4: ~/.unsandbox/accounts.csv row 0 (or UNSANDBOX_ACCOUNT env)
|
||||||
def accountsPath = new File(System.getProperty('user.home'), '.unsandbox/accounts.csv')
|
def defaultIdx = (System.getenv('UNSANDBOX_ACCOUNT') ?: '0').toInteger()
|
||||||
if (accountsPath.exists()) {
|
def searchPaths = [
|
||||||
try {
|
new File(System.getProperty('user.home'), '.unsandbox/accounts.csv'),
|
||||||
def lines = accountsPath.text.trim().split('\n')
|
new File('accounts.csv')
|
||||||
def validAccounts = []
|
]
|
||||||
lines.each { line ->
|
for (path in searchPaths) {
|
||||||
def trimmed = line.trim()
|
def accts = loadAccountsFromCsv(path)
|
||||||
if (!trimmed || trimmed.startsWith('#')) return
|
if (accts && defaultIdx < accts.size()) {
|
||||||
if (trimmed.contains(',')) {
|
return accts[defaultIdx]
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -267,21 +292,14 @@ def getCredentials(String publicKey = null, String secretKey = null, int account
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Legacy compatibility
|
// Legacy compatibility - now delegates to getCredentials for proper priority
|
||||||
def getApiKeys(argsKey) {
|
def getApiKeys(argsKey, int accountIndex = -1) {
|
||||||
def publicKey = System.getenv('UNSANDBOX_PUBLIC_KEY')
|
try {
|
||||||
def secretKey = System.getenv('UNSANDBOX_SECRET_KEY')
|
return getCredentials(argsKey ?: null, null, accountIndex)
|
||||||
|
} catch (AuthenticationError e) {
|
||||||
if (!publicKey || !secretKey) {
|
System.err.println("${RED}Error: ${e.message}${RESET}")
|
||||||
def legacyKey = argsKey ?: System.getenv('UNSANDBOX_API_KEY')
|
System.exit(1)
|
||||||
if (!legacyKey) {
|
|
||||||
System.err.println("${RED}Error: UNSANDBOX_PUBLIC_KEY and UNSANDBOX_SECRET_KEY not set${RESET}")
|
|
||||||
System.exit(1)
|
|
||||||
}
|
|
||||||
return [legacyKey, null]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return [publicKey, secretKey]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
@ -606,7 +624,7 @@ def execute(String language, String code, Map options = [:]) {
|
||||||
def (publicKey, secretKey) = getCredentials(
|
def (publicKey, secretKey) = getCredentials(
|
||||||
options.publicKey,
|
options.publicKey,
|
||||||
options.secretKey,
|
options.secretKey,
|
||||||
options.accountIndex ?: 0
|
options.accountIndex != null ? options.accountIndex : -1
|
||||||
)
|
)
|
||||||
|
|
||||||
def payload = [
|
def payload = [
|
||||||
|
|
@ -656,7 +674,7 @@ def executeAsync(String language, String code, Map options = [:]) {
|
||||||
def (publicKey, secretKey) = getCredentials(
|
def (publicKey, secretKey) = getCredentials(
|
||||||
options.publicKey,
|
options.publicKey,
|
||||||
options.secretKey,
|
options.secretKey,
|
||||||
options.accountIndex ?: 0
|
options.accountIndex != null ? options.accountIndex : -1
|
||||||
)
|
)
|
||||||
|
|
||||||
def payload = [
|
def payload = [
|
||||||
|
|
@ -703,7 +721,7 @@ def run(String code, Map options = [:]) {
|
||||||
def (publicKey, secretKey) = getCredentials(
|
def (publicKey, secretKey) = getCredentials(
|
||||||
options.publicKey,
|
options.publicKey,
|
||||||
options.secretKey,
|
options.secretKey,
|
||||||
options.accountIndex ?: 0
|
options.accountIndex != null ? options.accountIndex : -1
|
||||||
)
|
)
|
||||||
|
|
||||||
def ttl = options.ttl ?: DEFAULT_TTL
|
def ttl = options.ttl ?: DEFAULT_TTL
|
||||||
|
|
@ -728,7 +746,7 @@ def runAsync(String code, Map options = [:]) {
|
||||||
def (publicKey, secretKey) = getCredentials(
|
def (publicKey, secretKey) = getCredentials(
|
||||||
options.publicKey,
|
options.publicKey,
|
||||||
options.secretKey,
|
options.secretKey,
|
||||||
options.accountIndex ?: 0
|
options.accountIndex != null ? options.accountIndex : -1
|
||||||
)
|
)
|
||||||
|
|
||||||
def ttl = options.ttl ?: DEFAULT_TTL
|
def ttl = options.ttl ?: DEFAULT_TTL
|
||||||
|
|
@ -1589,45 +1607,72 @@ class Client {
|
||||||
def creds = getCredentialsStatic(
|
def creds = getCredentialsStatic(
|
||||||
options.publicKey,
|
options.publicKey,
|
||||||
options.secretKey,
|
options.secretKey,
|
||||||
options.accountIndex ?: 0
|
options.accountIndex != null ? options.accountIndex : -1
|
||||||
)
|
)
|
||||||
this.publicKey = creds[0]
|
this.publicKey = creds[0]
|
||||||
this.secretKey = creds[1]
|
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) {
|
private static getCredentialsStatic(String publicKey, String secretKey, int accountIndex) {
|
||||||
|
// Priority 1: explicit arguments
|
||||||
if (publicKey && secretKey) {
|
if (publicKey && secretKey) {
|
||||||
return [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 envPk = System.getenv('UNSANDBOX_PUBLIC_KEY')
|
||||||
def envSk = System.getenv('UNSANDBOX_SECRET_KEY')
|
def envSk = System.getenv('UNSANDBOX_SECRET_KEY')
|
||||||
if (envPk && envSk) {
|
if (envPk && envSk) {
|
||||||
return [envPk, envSk]
|
return [envPk, envSk]
|
||||||
}
|
}
|
||||||
|
|
||||||
def accountsPath = new File(System.getProperty('user.home'), '.unsandbox/accounts.csv')
|
// Priority 4: ~/.unsandbox/accounts.csv row 0 (or UNSANDBOX_ACCOUNT env)
|
||||||
if (accountsPath.exists()) {
|
def defaultIdx = (System.getenv('UNSANDBOX_ACCOUNT') ?: '0').toInteger()
|
||||||
try {
|
def searchPaths = [
|
||||||
def lines = accountsPath.text.trim().split('\n')
|
new File(System.getProperty('user.home'), '.unsandbox/accounts.csv'),
|
||||||
def validAccounts = []
|
new File('accounts.csv')
|
||||||
lines.each { line ->
|
]
|
||||||
def trimmed = line.trim()
|
for (path in searchPaths) {
|
||||||
if (!trimmed || trimmed.startsWith('#')) return
|
def accts = loadCsvAccounts(path)
|
||||||
if (trimmed.contains(',')) {
|
if (accts && defaultIdx < accts.size()) {
|
||||||
def parts = trimmed.split(',', 2)
|
return accts[defaultIdx]
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1738,6 +1783,7 @@ class Args {
|
||||||
String sourceFile = null
|
String sourceFile = null
|
||||||
String inlineLang = null
|
String inlineLang = null
|
||||||
String apiKey = null
|
String apiKey = null
|
||||||
|
Integer accountIndex = -1
|
||||||
String network = null
|
String network = null
|
||||||
Integer vcpu = 0
|
Integer vcpu = 0
|
||||||
List<String> env = []
|
List<String> env = []
|
||||||
|
|
@ -1839,7 +1885,7 @@ def serviceEnvSet(serviceId, content, publicKey, secretKey) {
|
||||||
}
|
}
|
||||||
|
|
||||||
def cmdServiceEnv(args) {
|
def cmdServiceEnv(args) {
|
||||||
def (publicKey, secretKey) = getApiKeys(args.apiKey)
|
def (publicKey, secretKey) = getApiKeys(args.apiKey, args.accountIndex ?: -1)
|
||||||
|
|
||||||
switch (args.envAction) {
|
switch (args.envAction) {
|
||||||
case 'status':
|
case 'status':
|
||||||
|
|
@ -1875,7 +1921,7 @@ def cmdServiceEnv(args) {
|
||||||
}
|
}
|
||||||
|
|
||||||
def cmdExecute(args) {
|
def cmdExecute(args) {
|
||||||
def (publicKey, secretKey) = getApiKeys(args.apiKey)
|
def (publicKey, secretKey) = getApiKeys(args.apiKey, args.accountIndex ?: -1)
|
||||||
|
|
||||||
String code
|
String code
|
||||||
String language
|
String language
|
||||||
|
|
@ -1956,7 +2002,7 @@ def cmdExecute(args) {
|
||||||
}
|
}
|
||||||
|
|
||||||
def cmdSession(args) {
|
def cmdSession(args) {
|
||||||
def (publicKey, secretKey) = getApiKeys(args.apiKey)
|
def (publicKey, secretKey) = getApiKeys(args.apiKey, args.accountIndex ?: -1)
|
||||||
|
|
||||||
if (args.sessionSnapshot) {
|
if (args.sessionSnapshot) {
|
||||||
def payload = [:]
|
def payload = [:]
|
||||||
|
|
@ -2033,7 +2079,7 @@ def openBrowser(url) {
|
||||||
}
|
}
|
||||||
|
|
||||||
def cmdSnapshot(args) {
|
def cmdSnapshot(args) {
|
||||||
def (publicKey, secretKey) = getApiKeys(args.apiKey)
|
def (publicKey, secretKey) = getApiKeys(args.apiKey, args.accountIndex ?: -1)
|
||||||
|
|
||||||
if (args.snapshotList) {
|
if (args.snapshotList) {
|
||||||
def output = apiRequest('/snapshots', 'GET', null, publicKey, secretKey)
|
def output = apiRequest('/snapshots', 'GET', null, publicKey, secretKey)
|
||||||
|
|
@ -2073,7 +2119,7 @@ def cmdSnapshot(args) {
|
||||||
}
|
}
|
||||||
|
|
||||||
def cmdImage(args) {
|
def cmdImage(args) {
|
||||||
def (publicKey, secretKey) = getApiKeys(args.apiKey)
|
def (publicKey, secretKey) = getApiKeys(args.apiKey, args.accountIndex ?: -1)
|
||||||
|
|
||||||
if (args.imageList) {
|
if (args.imageList) {
|
||||||
def output = apiRequest('/images', 'GET', null, publicKey, secretKey)
|
def output = apiRequest('/images', 'GET', null, publicKey, secretKey)
|
||||||
|
|
@ -2153,7 +2199,7 @@ def cmdImage(args) {
|
||||||
}
|
}
|
||||||
|
|
||||||
def cmdLanguages(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 result = languages([publicKey: publicKey, secretKey: secretKey, forceRefresh: true])
|
||||||
def langList = result.languages ?: []
|
def langList = result.languages ?: []
|
||||||
|
|
@ -2168,7 +2214,7 @@ def cmdLanguages(args) {
|
||||||
}
|
}
|
||||||
|
|
||||||
def cmdKey(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",
|
def curlCmd = ['curl', '-s', '-X', 'POST', "${PORTAL_BASE}/keys/validate",
|
||||||
'-H', 'Content-Type: application/json']
|
'-H', 'Content-Type: application/json']
|
||||||
|
|
@ -2240,7 +2286,7 @@ def cmdKey(args) {
|
||||||
}
|
}
|
||||||
|
|
||||||
def cmdService(args) {
|
def cmdService(args) {
|
||||||
def (publicKey, secretKey) = getApiKeys(args.apiKey)
|
def (publicKey, secretKey) = getApiKeys(args.apiKey, args.accountIndex ?: -1)
|
||||||
|
|
||||||
if (args.serviceSnapshot) {
|
if (args.serviceSnapshot) {
|
||||||
def payload = [:]
|
def payload = [:]
|
||||||
|
|
@ -2457,6 +2503,9 @@ def parseArgs(argv) {
|
||||||
case '--public-key':
|
case '--public-key':
|
||||||
args.apiKey = argv[++i] // For compatibility
|
args.apiKey = argv[++i] // For compatibility
|
||||||
break
|
break
|
||||||
|
case '--account':
|
||||||
|
args.accountIndex = argv[++i].toInteger()
|
||||||
|
break
|
||||||
case '-n':
|
case '-n':
|
||||||
case '--network':
|
case '--network':
|
||||||
args.network = argv[++i]
|
args.network = argv[++i]
|
||||||
|
|
|
||||||
|
|
@ -78,28 +78,77 @@ function detect_language(filename::String)::String
|
||||||
return get(EXT_MAP, ext, "unknown")
|
return get(EXT_MAP, ext, "unknown")
|
||||||
end
|
end
|
||||||
|
|
||||||
function get_api_keys(args_key=nothing)::Tuple{String,String}
|
function load_accounts_csv(path::String)::Vector{Tuple{String,String}}
|
||||||
# Try new-style keys first
|
accounts = Tuple{String,String}[]
|
||||||
public_key = something(args_key, get(ENV, "UNSANDBOX_PUBLIC_KEY", ""))
|
isfile(path) || return accounts
|
||||||
secret_key = get(ENV, "UNSANDBOX_SECRET_KEY", "")
|
try
|
||||||
|
for line in eachline(path)
|
||||||
# Fall back to old-style single key for backwards compatibility
|
trimmed = strip(line)
|
||||||
if isempty(public_key)
|
isempty(trimmed) && continue
|
||||||
old_key = get(ENV, "UNSANDBOX_API_KEY", "")
|
startswith(trimmed, "#") && continue
|
||||||
if isempty(old_key)
|
parts = split(trimmed, ","; limit=2)
|
||||||
println(stderr, "$(RED)Error: UNSANDBOX_PUBLIC_KEY/UNSANDBOX_SECRET_KEY or UNSANDBOX_API_KEY not set$(RESET)")
|
length(parts) >= 2 || continue
|
||||||
exit(1)
|
pk = strip(parts[1])
|
||||||
|
sk = strip(parts[2])
|
||||||
|
if startswith(pk, "unsb-pk-") && startswith(sk, "unsb-sk-")
|
||||||
|
push!(accounts, (pk, sk))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
# Old-style: use same key for both public and secret
|
catch
|
||||||
return (old_key, old_key)
|
|
||||||
end
|
end
|
||||||
|
return accounts
|
||||||
|
end
|
||||||
|
|
||||||
if isempty(secret_key)
|
function get_credentials(; account_index::Int=-1)::Tuple{String,String}
|
||||||
println(stderr, "$(RED)Error: UNSANDBOX_SECRET_KEY not set$(RESET)")
|
# 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)
|
exit(1)
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
function hmac_sha256_hex(key::String, message::String)::String
|
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
|
end
|
||||||
|
|
||||||
function cmd_service_env(args)
|
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)
|
action = get(args, "env-action", nothing)
|
||||||
target = get(args, "env-target", nothing)
|
target = get(args, "env-target", nothing)
|
||||||
|
|
@ -428,7 +477,7 @@ function cmd_service_env(args)
|
||||||
end
|
end
|
||||||
|
|
||||||
function cmd_execute(args)
|
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"]
|
filename = args["source_file"]
|
||||||
if !isfile(filename)
|
if !isfile(filename)
|
||||||
|
|
@ -518,7 +567,7 @@ function cmd_execute(args)
|
||||||
end
|
end
|
||||||
|
|
||||||
function cmd_session(args)
|
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"]
|
if args["list"]
|
||||||
result = api_request("/sessions", public_key, secret_key)
|
result = api_request("/sessions", public_key, secret_key)
|
||||||
|
|
@ -577,7 +626,7 @@ function cmd_session(args)
|
||||||
end
|
end
|
||||||
|
|
||||||
function cmd_service(args)
|
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
|
# Handle env subcommand
|
||||||
if get(args, "env-action", nothing) !== nothing
|
if get(args, "env-action", nothing) !== nothing
|
||||||
|
|
@ -914,7 +963,7 @@ function cmd_languages(args)
|
||||||
|
|
||||||
if langs === nothing
|
if langs === nothing
|
||||||
# Cache miss or expired, fetch from API
|
# 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)
|
result = api_request("/languages", public_key, secret_key)
|
||||||
langs = get(result, "languages", [])
|
langs = get(result, "languages", [])
|
||||||
save_languages_cache(langs)
|
save_languages_cache(langs)
|
||||||
|
|
@ -930,7 +979,7 @@ function cmd_languages(args)
|
||||||
end
|
end
|
||||||
|
|
||||||
function cmd_key(args)
|
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
|
# For portal validation, we still use public_key as bearer token
|
||||||
api_key = public_key
|
api_key = public_key
|
||||||
|
|
||||||
|
|
@ -996,6 +1045,9 @@ function main()
|
||||||
required = false
|
required = false
|
||||||
"--api-key", "-k"
|
"--api-key", "-k"
|
||||||
help = "API key (or set UNSANDBOX_API_KEY)"
|
help = "API key (or set UNSANDBOX_API_KEY)"
|
||||||
|
"--account"
|
||||||
|
help = "Account index in ~/.unsandbox/accounts.csv (bypasses env vars)"
|
||||||
|
arg_type = Int
|
||||||
"--network", "-n"
|
"--network", "-n"
|
||||||
help = "Network mode"
|
help = "Network mode"
|
||||||
arg_type = String
|
arg_type = String
|
||||||
|
|
@ -1057,6 +1109,9 @@ function main()
|
||||||
help = "Comma-separated ports for cloned service"
|
help = "Comma-separated ports for cloned service"
|
||||||
"--api-key", "-k"
|
"--api-key", "-k"
|
||||||
help = "API key"
|
help = "API key"
|
||||||
|
"--account"
|
||||||
|
help = "Account index in ~/.unsandbox/accounts.csv (bypasses env vars)"
|
||||||
|
arg_type = Int
|
||||||
end
|
end
|
||||||
|
|
||||||
@add_arg_table! s["session"] begin
|
@add_arg_table! s["session"] begin
|
||||||
|
|
@ -1074,6 +1129,9 @@ function main()
|
||||||
range_tester = x -> x in ["zerotrust", "semitrusted"]
|
range_tester = x -> x in ["zerotrust", "semitrusted"]
|
||||||
"--api-key", "-k"
|
"--api-key", "-k"
|
||||||
help = "API key"
|
help = "API key"
|
||||||
|
"--account"
|
||||||
|
help = "Account index in ~/.unsandbox/accounts.csv (bypasses env vars)"
|
||||||
|
arg_type = Int
|
||||||
end
|
end
|
||||||
|
|
||||||
@add_arg_table! s["service"] begin
|
@add_arg_table! s["service"] begin
|
||||||
|
|
@ -1135,6 +1193,9 @@ function main()
|
||||||
help = "Service ID for env commands"
|
help = "Service ID for env commands"
|
||||||
"--api-key", "-k"
|
"--api-key", "-k"
|
||||||
help = "API key"
|
help = "API key"
|
||||||
|
"--account"
|
||||||
|
help = "Account index in ~/.unsandbox/accounts.csv (bypasses env vars)"
|
||||||
|
arg_type = Int
|
||||||
"env"
|
"env"
|
||||||
help = "Manage service environment vault"
|
help = "Manage service environment vault"
|
||||||
action = :command
|
action = :command
|
||||||
|
|
@ -1155,6 +1216,9 @@ function main()
|
||||||
help = "Load vault variables from file"
|
help = "Load vault variables from file"
|
||||||
"--api-key", "-k"
|
"--api-key", "-k"
|
||||||
help = "API key"
|
help = "API key"
|
||||||
|
"--account"
|
||||||
|
help = "Account index in ~/.unsandbox/accounts.csv (bypasses env vars)"
|
||||||
|
arg_type = Int
|
||||||
end
|
end
|
||||||
|
|
||||||
@add_arg_table! s["key"] begin
|
@add_arg_table! s["key"] begin
|
||||||
|
|
@ -1163,6 +1227,9 @@ function main()
|
||||||
action = :store_true
|
action = :store_true
|
||||||
"--api-key", "-k"
|
"--api-key", "-k"
|
||||||
help = "API key"
|
help = "API key"
|
||||||
|
"--account"
|
||||||
|
help = "Account index in ~/.unsandbox/accounts.csv (bypasses env vars)"
|
||||||
|
arg_type = Int
|
||||||
end
|
end
|
||||||
|
|
||||||
@add_arg_table! s["languages"] begin
|
@add_arg_table! s["languages"] begin
|
||||||
|
|
@ -1171,6 +1238,9 @@ function main()
|
||||||
action = :store_true
|
action = :store_true
|
||||||
"--api-key", "-k"
|
"--api-key", "-k"
|
||||||
help = "API key"
|
help = "API key"
|
||||||
|
"--account"
|
||||||
|
help = "Account index in ~/.unsandbox/accounts.csv (bypasses env vars)"
|
||||||
|
arg_type = Int
|
||||||
end
|
end
|
||||||
|
|
||||||
@add_arg_table! s["image"] begin
|
@add_arg_table! s["image"] begin
|
||||||
|
|
@ -1203,6 +1273,9 @@ function main()
|
||||||
help = "Comma-separated ports for spawned service"
|
help = "Comma-separated ports for spawned service"
|
||||||
"--api-key", "-k"
|
"--api-key", "-k"
|
||||||
help = "API key"
|
help = "API key"
|
||||||
|
"--account"
|
||||||
|
help = "Account index in ~/.unsandbox/accounts.csv (bypasses env vars)"
|
||||||
|
arg_type = Int
|
||||||
end
|
end
|
||||||
|
|
||||||
args = parse_args(ARGS, s)
|
args = parse_args(ARGS, s)
|
||||||
|
|
@ -1220,6 +1293,7 @@ function main()
|
||||||
service_args["vault-env"] = get(env_args, "vault-env", nothing)
|
service_args["vault-env"] = get(env_args, "vault-env", nothing)
|
||||||
service_args["env-file"] = get(env_args, "env-file", nothing)
|
service_args["env-file"] = get(env_args, "env-file", nothing)
|
||||||
service_args["api-key"] = get(env_args, "api-key", nothing)
|
service_args["api-key"] = get(env_args, "api-key", nothing)
|
||||||
|
service_args["account"] = get(env_args, "account", nothing)
|
||||||
end
|
end
|
||||||
cmd_service(service_args)
|
cmd_service(service_args)
|
||||||
elseif args["%COMMAND%"] == "languages"
|
elseif args["%COMMAND%"] == "languages"
|
||||||
|
|
@ -1239,7 +1313,7 @@ function main()
|
||||||
end
|
end
|
||||||
|
|
||||||
function cmd_image(args)
|
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"]
|
if args["list"]
|
||||||
result = api_request("/images", public_key, secret_key)
|
result = api_request("/images", public_key, secret_key)
|
||||||
|
|
@ -1330,7 +1404,7 @@ function cmd_image(args)
|
||||||
end
|
end
|
||||||
|
|
||||||
function cmd_snapshot(args)
|
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"]
|
if args["list"]
|
||||||
result = api_request("/snapshots", public_key, secret_key)
|
result = api_request("/snapshots", public_key, secret_key)
|
||||||
|
|
|
||||||
|
|
@ -121,29 +121,64 @@ MAX_ENV_CONTENT_SIZE <- 65536
|
||||||
#' creds <- get_credentials()
|
#' creds <- get_credentials()
|
||||||
#' creds <- get_credentials(public_key = "unsb-pk-xxxx", secret_key = "unsb-sk-xxxx")
|
#' 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
|
# Priority 1: Function arguments
|
||||||
if (!is.null(public_key) && !is.null(secret_key)) {
|
if (!is.null(public_key) && !is.null(secret_key)) {
|
||||||
return(list(public_key = public_key, secret_key = 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_public <- Sys.getenv("UNSANDBOX_PUBLIC_KEY")
|
||||||
env_secret <- Sys.getenv("UNSANDBOX_SECRET_KEY")
|
env_secret <- Sys.getenv("UNSANDBOX_SECRET_KEY")
|
||||||
if (env_public != "" && env_secret != "") {
|
if (env_public != "" && env_secret != "") {
|
||||||
return(list(public_key = env_public, secret_key = env_secret))
|
return(list(public_key = env_public, secret_key = env_secret))
|
||||||
}
|
}
|
||||||
|
|
||||||
# Priority 3: Accounts file
|
# Priority 4: ~/.unsandbox/accounts.csv row 0 (or UNSANDBOX_ACCOUNT env)
|
||||||
accounts_file <- file.path(Sys.getenv("HOME"), ".unsandbox", "accounts.csv")
|
default_idx <- 0
|
||||||
if (file.exists(accounts_file)) {
|
env_account <- Sys.getenv("UNSANDBOX_ACCOUNT")
|
||||||
lines <- readLines(accounts_file, warn = FALSE)
|
if (env_account != "") {
|
||||||
for (line in lines) {
|
default_idx <- as.integer(env_account)
|
||||||
parts <- strsplit(trimws(line), ",")[[1]]
|
}
|
||||||
if (length(parts) >= 2) {
|
result <- load_account_from_csv(default_idx)
|
||||||
return(list(public_key = parts[1], secret_key = parts[2]))
|
if (!is.null(result)) {
|
||||||
}
|
return(result)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Fallback to legacy UNSANDBOX_API_KEY
|
# Fallback to legacy UNSANDBOX_API_KEY
|
||||||
|
|
@ -1665,7 +1700,7 @@ build_env_content <- function(envs, env_file) {
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd_service_env <- function(args) {
|
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
|
public_key <- keys$public_key
|
||||||
secret_key <- keys$secret_key
|
secret_key <- keys$secret_key
|
||||||
|
|
||||||
|
|
@ -1743,7 +1778,7 @@ cmd_service_env <- function(args) {
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd_execute <- 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
|
public_key <- keys$public_key
|
||||||
secret_key <- keys$secret_key
|
secret_key <- keys$secret_key
|
||||||
|
|
||||||
|
|
@ -1837,7 +1872,7 @@ cmd_execute <- function(args) {
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd_session <- 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
|
public_key <- keys$public_key
|
||||||
secret_key <- keys$secret_key
|
secret_key <- keys$secret_key
|
||||||
|
|
||||||
|
|
@ -1995,7 +2030,7 @@ cmd_languages <- function(args) {
|
||||||
|
|
||||||
if (is.null(langs)) {
|
if (is.null(langs)) {
|
||||||
# Cache miss or expired, fetch from API
|
# 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
|
public_key <- keys$public_key
|
||||||
secret_key <- keys$secret_key
|
secret_key <- keys$secret_key
|
||||||
|
|
||||||
|
|
@ -2016,7 +2051,7 @@ cmd_languages <- function(args) {
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd_key <- 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
|
public_key <- keys$public_key
|
||||||
secret_key <- keys$secret_key
|
secret_key <- keys$secret_key
|
||||||
|
|
||||||
|
|
@ -2115,7 +2150,7 @@ cmd_key <- function(args) {
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd_snapshot <- 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
|
public_key <- keys$public_key
|
||||||
secret_key <- keys$secret_key
|
secret_key <- keys$secret_key
|
||||||
|
|
||||||
|
|
@ -2195,7 +2230,7 @@ cmd_snapshot <- function(args) {
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd_image <- 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
|
public_key <- keys$public_key
|
||||||
secret_key <- keys$secret_key
|
secret_key <- keys$secret_key
|
||||||
|
|
||||||
|
|
@ -2308,7 +2343,7 @@ cmd_service <- function(args) {
|
||||||
return()
|
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
|
public_key <- keys$public_key
|
||||||
secret_key <- keys$secret_key
|
secret_key <- keys$secret_key
|
||||||
|
|
||||||
|
|
@ -2532,6 +2567,7 @@ parse_args <- function() {
|
||||||
result <- list(
|
result <- list(
|
||||||
source_file = NULL,
|
source_file = NULL,
|
||||||
api_key = NULL,
|
api_key = NULL,
|
||||||
|
account_index = -1L,
|
||||||
network = NULL,
|
network = NULL,
|
||||||
env = NULL,
|
env = NULL,
|
||||||
files = NULL,
|
files = NULL,
|
||||||
|
|
@ -2628,6 +2664,10 @@ parse_args <- function() {
|
||||||
i <- i + 1
|
i <- i + 1
|
||||||
result$api_key <- args[i]
|
result$api_key <- args[i]
|
||||||
i <- i + 1
|
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")) {
|
} else if (arg %in% c("-n", "--network")) {
|
||||||
i <- i + 1
|
i <- i + 1
|
||||||
result$network <- args[i]
|
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
|
#| 1. Function arguments
|
||||||
#| 2. Environment variables
|
#| 2. Environment variables
|
||||||
#| 3. ~/.unsandbox/accounts.csv
|
#| 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
|
# Priority 1: Function arguments
|
||||||
if $public-key && $secret-key {
|
if $public-key && $secret-key {
|
||||||
return ($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-pk = %*ENV<UNSANDBOX_PUBLIC_KEY> // '';
|
||||||
my $env-sk = %*ENV<UNSANDBOX_SECRET_KEY> // '';
|
my $env-sk = %*ENV<UNSANDBOX_SECRET_KEY> // '';
|
||||||
if $env-pk && $env-sk {
|
if $env-pk && $env-sk {
|
||||||
return ($env-pk, $env-sk);
|
return ($env-pk, $env-sk);
|
||||||
}
|
}
|
||||||
|
|
||||||
# Priority 3: Config file
|
# Priority 4: ~/.unsandbox/accounts.csv row 0 (or UNSANDBOX_ACCOUNT env)
|
||||||
my $accounts-path = $*HOME.add('.unsandbox').add('accounts.csv');
|
my $default-idx = (%*ENV<UNSANDBOX_ACCOUNT> // '0').Int;
|
||||||
if $accounts-path.e {
|
for ($*HOME.add('.unsandbox').add('accounts.csv'),
|
||||||
try {
|
'accounts.csv'.IO) -> $path {
|
||||||
my @lines = $accounts-path.slurp.trim.split("\n");
|
my @accts = load-accounts-from($path);
|
||||||
my @valid-accounts;
|
if @accts && $default-idx < @accts.elems {
|
||||||
for @lines -> $line {
|
return @accts[$default-idx];
|
||||||
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];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1366,8 +1387,8 @@ sub uri-encode(Str $s) {
|
||||||
return $s.subst(/<-[A-Za-z0-9\-_.~]>/, { .encode.list.map({ '%' ~ .fmt('%02X') }).join }, :g);
|
return $s.subst(/<-[A-Za-z0-9\-_.~]>/, { .encode.list.map({ '%' ~ .fmt('%02X') }).join }, :g);
|
||||||
}
|
}
|
||||||
|
|
||||||
sub cmd-execute(@args) {
|
sub cmd-execute(@args, Int :$account-index = -1) {
|
||||||
my ($public-key, $secret-key) = get-credentials();
|
my ($public-key, $secret-key) = get-credentials(:$account-index);
|
||||||
my $source-file = '';
|
my $source-file = '';
|
||||||
my %env-vars;
|
my %env-vars;
|
||||||
my @input-files;
|
my @input-files;
|
||||||
|
|
@ -1490,8 +1511,8 @@ sub cmd-execute(@args) {
|
||||||
exit %result<exit_code> // 0;
|
exit %result<exit_code> // 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
sub cmd-session(@args) {
|
sub cmd-session(@args, Int :$account-index = -1) {
|
||||||
my ($public-key, $secret-key) = get-credentials();
|
my ($public-key, $secret-key) = get-credentials(:$account-index);
|
||||||
my $list-mode = False;
|
my $list-mode = False;
|
||||||
my $kill-id = '';
|
my $kill-id = '';
|
||||||
my $shell = '';
|
my $shell = '';
|
||||||
|
|
@ -1579,8 +1600,8 @@ sub cmd-session(@args) {
|
||||||
say "{$YELLOW}(Interactive sessions require WebSocket - use un2 for full support){$RESET}";
|
say "{$YELLOW}(Interactive sessions require WebSocket - use un2 for full support){$RESET}";
|
||||||
}
|
}
|
||||||
|
|
||||||
sub cmd-service(@args) {
|
sub cmd-service(@args, Int :$account-index = -1) {
|
||||||
my ($public-key, $secret-key) = get-credentials();
|
my ($public-key, $secret-key) = get-credentials(:$account-index);
|
||||||
my $list-mode = False;
|
my $list-mode = False;
|
||||||
my $info-id = '';
|
my $info-id = '';
|
||||||
my $logs-id = '';
|
my $logs-id = '';
|
||||||
|
|
@ -1813,8 +1834,8 @@ sub cmd-service(@args) {
|
||||||
exit 1;
|
exit 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
sub cmd-languages(@args) {
|
sub cmd-languages(@args, Int :$account-index = -1) {
|
||||||
my ($public-key, $secret-key) = get-credentials();
|
my ($public-key, $secret-key) = get-credentials(:$account-index);
|
||||||
my $json-output = False;
|
my $json-output = False;
|
||||||
|
|
||||||
for @args -> $arg {
|
for @args -> $arg {
|
||||||
|
|
@ -1837,8 +1858,8 @@ sub cmd-languages(@args) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sub cmd-key(@args) {
|
sub cmd-key(@args, Int :$account-index = -1) {
|
||||||
my ($public-key, $secret-key) = get-credentials();
|
my ($public-key, $secret-key) = get-credentials(:$account-index);
|
||||||
my $extend = False;
|
my $extend = False;
|
||||||
|
|
||||||
for @args -> $arg {
|
for @args -> $arg {
|
||||||
|
|
@ -1897,8 +1918,8 @@ sub cmd-key(@args) {
|
||||||
say "Concurrency: {%result<concurrency> // 'N/A'}";
|
say "Concurrency: {%result<concurrency> // 'N/A'}";
|
||||||
}
|
}
|
||||||
|
|
||||||
sub cmd-image(@args) {
|
sub cmd-image(@args, Int :$account-index = -1) {
|
||||||
my ($public-key, $secret-key) = get-credentials();
|
my ($public-key, $secret-key) = get-credentials(:$account-index);
|
||||||
my $list-mode = False;
|
my $list-mode = False;
|
||||||
my $info-id = '';
|
my $info-id = '';
|
||||||
my $delete-id = '';
|
my $delete-id = '';
|
||||||
|
|
@ -2090,8 +2111,8 @@ sub cmd-image(@args) {
|
||||||
exit 1;
|
exit 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
sub cmd-snapshot(@args) {
|
sub cmd-snapshot(@args, Int :$account-index = -1) {
|
||||||
my ($public-key, $secret-key) = get-credentials();
|
my ($public-key, $secret-key) = get-credentials(:$account-index);
|
||||||
my $list-mode = False;
|
my $list-mode = False;
|
||||||
my $info-id = '';
|
my $info-id = '';
|
||||||
my $delete-id = '';
|
my $delete-id = '';
|
||||||
|
|
@ -2224,7 +2245,19 @@ sub cmd-snapshot(@args) {
|
||||||
exit 1;
|
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 {
|
unless @args {
|
||||||
note "Usage: un.raku [options] <source_file>";
|
note "Usage: un.raku [options] <source_file>";
|
||||||
note " un.raku session [options]";
|
note " un.raku session [options]";
|
||||||
|
|
@ -2275,25 +2308,25 @@ sub MAIN(*@args) is export {
|
||||||
|
|
||||||
given @args[0] {
|
given @args[0] {
|
||||||
when 'session' {
|
when 'session' {
|
||||||
cmd-session(@args[1..*]);
|
cmd-session(@args[1..*], :$account-index);
|
||||||
}
|
}
|
||||||
when 'service' {
|
when 'service' {
|
||||||
cmd-service(@args[1..*]);
|
cmd-service(@args[1..*], :$account-index);
|
||||||
}
|
}
|
||||||
when 'snapshot' {
|
when 'snapshot' {
|
||||||
cmd-snapshot(@args[1..*]);
|
cmd-snapshot(@args[1..*], :$account-index);
|
||||||
}
|
}
|
||||||
when 'image' {
|
when 'image' {
|
||||||
cmd-image(@args[1..*]);
|
cmd-image(@args[1..*], :$account-index);
|
||||||
}
|
}
|
||||||
when 'key' {
|
when 'key' {
|
||||||
cmd-key(@args[1..*]);
|
cmd-key(@args[1..*], :$account-index);
|
||||||
}
|
}
|
||||||
when 'languages' {
|
when 'languages' {
|
||||||
cmd-languages(@args[1..*]);
|
cmd-languages(@args[1..*], :$account-index);
|
||||||
}
|
}
|
||||||
default {
|
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