Add --account N flag and fix credential resolution priority in un.m, un.f90, un.zig, un.nim
Implements correct 5-tier credential priority in all four implementations: 1. Explicit -p/-k flags 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 var) 5. ./accounts.csv row 0 un.m (Objective-C): adds UNLoadCredentialsFromCSV helper, updates UNGetCredentials to use g_accountIndex global, parses --account N in main() pre-scan. un.f90 (Fortran): adds load_csv_row subroutine, updates get_credentials with optional account_index parameter, parses --account N in main program pre-scan, passes account_index via host association to all handle_* subroutines. un.zig (Zig): adds loadCsvRow and resolveCredentials functions, parses --account N in main() pre-scan, updates execute-mode arg loop to skip known flags. un.nim (Nim): adds loadCredentialsFromCsv and resolveCredentials procs, parses --account N in main() pre-scan, updates execute-mode loop to skip --account.
This commit is contained in:
parent
13f15c8abc
commit
5373da4108
4 changed files with 375 additions and 103 deletions
|
|
@ -64,9 +64,11 @@
|
|||
! ./un key [--extend]
|
||||
!
|
||||
! Authentication (in priority order):
|
||||
! 1. Environment variables: UNSANDBOX_PUBLIC_KEY + UNSANDBOX_SECRET_KEY
|
||||
! 2. Config file: ~/.unsandbox/accounts.csv (public_key,secret_key per line)
|
||||
! 3. Legacy: UNSANDBOX_API_KEY (deprecated)
|
||||
! 1. --account N flag -> accounts.csv row N (bypasses env vars)
|
||||
! 2. Environment variables: UNSANDBOX_PUBLIC_KEY + UNSANDBOX_SECRET_KEY
|
||||
! 3. Config file: ~/.unsandbox/accounts.csv row 0 (or UNSANDBOX_ACCOUNT)
|
||||
! 4. ./accounts.csv row 0
|
||||
! 5. Legacy: UNSANDBOX_API_KEY (deprecated)
|
||||
!
|
||||
! Compile:
|
||||
! gfortran -o un un.f90
|
||||
|
|
@ -190,32 +192,97 @@ module unsandbox_sdk
|
|||
|
||||
contains
|
||||
|
||||
!--------------------------------------------------------------------------
|
||||
! Subroutine: load_csv_row
|
||||
! Description: Load public_key,secret_key from a CSV file at row_index
|
||||
! (0-based, skipping blank lines and '#' comments).
|
||||
!
|
||||
! Arguments:
|
||||
! csv_path - Path to CSV file
|
||||
! row_index - Zero-based data row to read
|
||||
! public_key - Output: public key (empty if not found)
|
||||
! secret_key - Output: secret key (empty if not found)
|
||||
!--------------------------------------------------------------------------
|
||||
subroutine load_csv_row(csv_path, row_index, public_key, secret_key)
|
||||
character(len=*), intent(in) :: csv_path
|
||||
integer, intent(in) :: row_index
|
||||
character(len=*), intent(out) :: public_key, secret_key
|
||||
character(len=1024) :: line
|
||||
integer :: unit_num, ios, data_index
|
||||
logical :: file_exists
|
||||
|
||||
public_key = ''
|
||||
secret_key = ''
|
||||
data_index = 0
|
||||
|
||||
inquire(file=trim(csv_path), exist=file_exists)
|
||||
if (.not. file_exists) return
|
||||
|
||||
open(newunit=unit_num, file=trim(csv_path), status='old', action='read', iostat=ios)
|
||||
if (ios /= 0) return
|
||||
|
||||
do
|
||||
read(unit_num, '(A)', iostat=ios) line
|
||||
if (ios /= 0) exit
|
||||
line = adjustl(line)
|
||||
if (len_trim(line) == 0) cycle
|
||||
if (line(1:1) == '#') cycle
|
||||
if (data_index == row_index) then
|
||||
call parse_csv_line(line, public_key, secret_key)
|
||||
close(unit_num)
|
||||
return
|
||||
end if
|
||||
data_index = data_index + 1
|
||||
end do
|
||||
close(unit_num)
|
||||
end subroutine load_csv_row
|
||||
|
||||
!--------------------------------------------------------------------------
|
||||
! Subroutine: get_credentials
|
||||
! Description: Get API credentials from environment or config file
|
||||
!
|
||||
! Priority order:
|
||||
! 1. Environment variables (UNSANDBOX_PUBLIC_KEY, UNSANDBOX_SECRET_KEY)
|
||||
! 2. Config file (~/.unsandbox/accounts.csv)
|
||||
! 3. Legacy UNSANDBOX_API_KEY (deprecated)
|
||||
! 1. account_index >= 0 -> accounts.csv row N (bypasses env vars)
|
||||
! 2. Environment variables (UNSANDBOX_PUBLIC_KEY, UNSANDBOX_SECRET_KEY)
|
||||
! 3. Config file (~/.unsandbox/accounts.csv row 0 or UNSANDBOX_ACCOUNT)
|
||||
! 4. ./accounts.csv row 0
|
||||
! 5. Legacy UNSANDBOX_API_KEY (deprecated)
|
||||
!
|
||||
! Arguments:
|
||||
! public_key - Output: API public key
|
||||
! secret_key - Output: API secret key
|
||||
! status - Output: 0 on success, non-zero on error
|
||||
! public_key - Output: API public key
|
||||
! secret_key - Output: API secret key
|
||||
! status - Output: 0 on success, non-zero on error
|
||||
! account_index - Optional input: if >= 0, load that CSV row directly
|
||||
!--------------------------------------------------------------------------
|
||||
subroutine get_credentials(public_key, secret_key, status)
|
||||
subroutine get_credentials(public_key, secret_key, status, account_index)
|
||||
character(len=*), intent(out) :: public_key, secret_key
|
||||
integer, intent(out) :: status
|
||||
character(len=1024) :: home_dir, accounts_path, line, api_key
|
||||
integer :: unit_num, ios
|
||||
logical :: file_exists
|
||||
integer, intent(in), optional :: account_index
|
||||
character(len=1024) :: home_dir, accounts_path, api_key, acct_env
|
||||
integer :: ios, acct_idx, default_index
|
||||
|
||||
status = 0
|
||||
public_key = ''
|
||||
secret_key = ''
|
||||
|
||||
! Priority 1: Environment variables
|
||||
! Priority 1: account_index >= 0 -> load that CSV row (bypasses env vars)
|
||||
if (present(account_index)) then
|
||||
if (account_index >= 0) then
|
||||
acct_idx = account_index
|
||||
call get_environment_variable('HOME', home_dir, status=ios)
|
||||
if (ios == 0) then
|
||||
accounts_path = trim(home_dir) // '/.unsandbox/accounts.csv'
|
||||
call load_csv_row(accounts_path, acct_idx, public_key, secret_key)
|
||||
if (len_trim(public_key) > 0) return
|
||||
end if
|
||||
call load_csv_row('accounts.csv', acct_idx, public_key, secret_key)
|
||||
if (len_trim(public_key) > 0) return
|
||||
status = 1
|
||||
return
|
||||
end if
|
||||
end if
|
||||
|
||||
! Priority 2: Environment variables
|
||||
call get_environment_variable('UNSANDBOX_PUBLIC_KEY', public_key, status=ios)
|
||||
if (ios == 0 .and. len_trim(public_key) > 0) then
|
||||
call get_environment_variable('UNSANDBOX_SECRET_KEY', secret_key, status=ios)
|
||||
|
|
@ -224,37 +291,27 @@ contains
|
|||
end if
|
||||
end if
|
||||
|
||||
! Priority 2: Config file
|
||||
! Priority 3: ~/.unsandbox/accounts.csv (default row)
|
||||
call get_environment_variable('UNSANDBOX_ACCOUNT', acct_env, status=ios)
|
||||
if (ios == 0 .and. len_trim(acct_env) > 0) then
|
||||
read(acct_env, *, iostat=ios) default_index
|
||||
if (ios /= 0) default_index = 0
|
||||
else
|
||||
default_index = 0
|
||||
end if
|
||||
|
||||
call get_environment_variable('HOME', home_dir, status=ios)
|
||||
if (ios == 0) then
|
||||
accounts_path = trim(home_dir) // '/.unsandbox/accounts.csv'
|
||||
inquire(file=trim(accounts_path), exist=file_exists)
|
||||
if (file_exists) then
|
||||
open(newunit=unit_num, file=trim(accounts_path), status='old', &
|
||||
action='read', iostat=ios)
|
||||
if (ios == 0) then
|
||||
do
|
||||
read(unit_num, '(A)', iostat=ios) line
|
||||
if (ios /= 0) exit
|
||||
line = adjustl(line)
|
||||
if (len_trim(line) == 0) cycle
|
||||
if (line(1:1) == '#') cycle
|
||||
! Parse CSV: public_key,secret_key
|
||||
call parse_csv_line(line, public_key, secret_key)
|
||||
if (len_trim(public_key) > 0 .and. len_trim(secret_key) > 0) then
|
||||
if (public_key(1:8) == 'unsb-pk-' .and. &
|
||||
secret_key(1:8) == 'unsb-sk-') then
|
||||
close(unit_num)
|
||||
return
|
||||
end if
|
||||
end if
|
||||
end do
|
||||
close(unit_num)
|
||||
end if
|
||||
end if
|
||||
call load_csv_row(accounts_path, default_index, public_key, secret_key)
|
||||
if (len_trim(public_key) > 0) return
|
||||
end if
|
||||
|
||||
! Priority 3: Legacy API key
|
||||
! Priority 4: ./accounts.csv
|
||||
call load_csv_row('accounts.csv', default_index, public_key, secret_key)
|
||||
if (len_trim(public_key) > 0) return
|
||||
|
||||
! Priority 5: Legacy API key
|
||||
call get_environment_variable('UNSANDBOX_API_KEY', api_key, status=ios)
|
||||
if (ios == 0 .and. len_trim(api_key) > 0) then
|
||||
public_key = api_key
|
||||
|
|
@ -850,6 +907,7 @@ program unsandbox_cli
|
|||
character(len=1024) :: filename, language, api_key, ext, arg, subcommand
|
||||
character(len=256) :: session_id, service_id
|
||||
integer :: stat, i, nargs, dot_pos
|
||||
integer :: account_index ! -1 = not set; >= 0 means use that CSV row
|
||||
logical :: list_flag, is_session, is_service, is_key
|
||||
|
||||
! Initialize
|
||||
|
|
@ -860,6 +918,7 @@ program unsandbox_cli
|
|||
is_key = .false.
|
||||
session_id = ''
|
||||
service_id = ''
|
||||
account_index = -1
|
||||
|
||||
! Get command line arguments count
|
||||
nargs = command_argument_count()
|
||||
|
|
@ -868,6 +927,17 @@ program unsandbox_cli
|
|||
stop 1
|
||||
end if
|
||||
|
||||
! Pre-scan all arguments for --account N
|
||||
do i = 1, nargs - 1
|
||||
call get_command_argument(i, arg)
|
||||
if (trim(arg) == '--account') then
|
||||
call get_command_argument(i + 1, arg)
|
||||
read(arg, *, iostat=stat) account_index
|
||||
if (stat /= 0) account_index = -1
|
||||
exit
|
||||
end if
|
||||
end do
|
||||
|
||||
! Check for subcommands
|
||||
call get_command_argument(1, arg, status=stat)
|
||||
if (trim(arg) == '-h' .or. trim(arg) == '--help') then
|
||||
|
|
@ -974,6 +1044,9 @@ contains
|
|||
write(*, '(A)') 'Languages options:'
|
||||
write(*, '(A)') ' --json Output as JSON array'
|
||||
write(*, '(A)') ''
|
||||
write(*, '(A)') 'Credential options (global):'
|
||||
write(*, '(A)') ' --account N Use row N from accounts.csv (bypasses env vars)'
|
||||
write(*, '(A)') ''
|
||||
write(*, '(A)') 'Library Usage:'
|
||||
write(*, '(A)') ' use unsandbox_sdk'
|
||||
write(*, '(A)') ' type(unsandbox_client) :: client'
|
||||
|
|
@ -1003,7 +1076,7 @@ contains
|
|||
end if
|
||||
|
||||
! Get API keys
|
||||
call get_credentials(public_key, secret_key, stat)
|
||||
call get_credentials(public_key, secret_key, stat, account_index)
|
||||
if (stat /= 0) then
|
||||
write(0, '(A)') 'Error: No credentials found. Set UNSANDBOX_PUBLIC_KEY and UNSANDBOX_SECRET_KEY'
|
||||
stop 1
|
||||
|
|
@ -1074,6 +1147,8 @@ contains
|
|||
input_files = trim(arg)
|
||||
end if
|
||||
end if
|
||||
else if (trim(arg) == '--account') then
|
||||
! already processed in main pre-scan; skip this token and its value
|
||||
else
|
||||
if (len_trim(arg) > 0) then
|
||||
if (arg(1:1) == '-') then
|
||||
|
|
@ -1086,7 +1161,7 @@ contains
|
|||
end do
|
||||
|
||||
! Get API keys
|
||||
call get_credentials(public_key, secret_key, stat)
|
||||
call get_credentials(public_key, secret_key, stat, account_index)
|
||||
if (stat /= 0) then
|
||||
write(0, '(A)') 'Error: No credentials found'
|
||||
stop 1
|
||||
|
|
@ -1289,7 +1364,7 @@ contains
|
|||
end do
|
||||
|
||||
! Get API keys
|
||||
call get_credentials(public_key, secret_key, stat)
|
||||
call get_credentials(public_key, secret_key, stat, account_index)
|
||||
if (stat /= 0) then
|
||||
write(0, '(A)') 'Error: No credentials found'
|
||||
stop 1
|
||||
|
|
@ -1617,7 +1692,7 @@ contains
|
|||
end do
|
||||
|
||||
! Get credentials
|
||||
call get_credentials(public_key, secret_key, stat)
|
||||
call get_credentials(public_key, secret_key, stat, account_index)
|
||||
if (stat /= 0) then
|
||||
write(0, '(A)') 'Error: No credentials found'
|
||||
stop 1
|
||||
|
|
@ -1771,7 +1846,7 @@ contains
|
|||
list_mode = .false.
|
||||
|
||||
! Get credentials
|
||||
call get_credentials(public_key, secret_key, stat)
|
||||
call get_credentials(public_key, secret_key, stat, account_index)
|
||||
if (stat /= 0) then
|
||||
write(0, '(A)') 'Error: No credentials found'
|
||||
stop 1
|
||||
|
|
@ -2046,7 +2121,7 @@ contains
|
|||
end do
|
||||
|
||||
! Get API key
|
||||
call get_credentials(public_key, secret_key, stat)
|
||||
call get_credentials(public_key, secret_key, stat, account_index)
|
||||
if (stat /= 0) then
|
||||
write(0, '(A)') 'Error: No credentials found'
|
||||
stop 1
|
||||
|
|
@ -2143,7 +2218,7 @@ contains
|
|||
end do
|
||||
|
||||
! Get API keys
|
||||
call get_credentials(public_key, secret_key, stat)
|
||||
call get_credentials(public_key, secret_key, stat, account_index)
|
||||
if (stat /= 0) then
|
||||
write(0, '(A)') 'Error: No credentials found'
|
||||
stop 1
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@
|
|||
# un.nim session --list
|
||||
# un.nim service --name web --ports 8080
|
||||
|
||||
import os, strutils, osproc, strformat, times, sequtils
|
||||
import os, strutils, osproc, strformat, times
|
||||
|
||||
const
|
||||
API_BASE = "https://api.unsandbox.com"
|
||||
|
|
@ -1252,15 +1252,15 @@ proc resolveCredentials(argPk: string, argSk: string, accountIndex: int): tuple[
|
|||
if argPk != "" and argSk != "":
|
||||
return (argPk, argSk)
|
||||
# Tier 2: --account N bypasses env vars
|
||||
let homeDir = getHomeDir()
|
||||
if accountIndex >= 0:
|
||||
let homeDir = getHomeDir()
|
||||
let homeCsv = homeDir / ".unsandbox" / "accounts.csv"
|
||||
var creds = loadCredentialsFromCsv(homeCsv, accountIndex)
|
||||
if creds.pk != "":
|
||||
return creds
|
||||
creds = loadCredentialsFromCsv("accounts.csv", accountIndex)
|
||||
if creds.pk != "":
|
||||
return creds
|
||||
let homeCsv2 = homeDir / ".unsandbox" / "accounts.csv"
|
||||
var creds2 = loadCredentialsFromCsv(homeCsv2, accountIndex)
|
||||
if creds2.pk != "":
|
||||
return creds2
|
||||
creds2 = loadCredentialsFromCsv("accounts.csv", accountIndex)
|
||||
if creds2.pk != "":
|
||||
return creds2
|
||||
stderr.writeLine(RED & fmt"Error: No credentials found for account index {accountIndex} in accounts.csv" & RESET)
|
||||
quit(1)
|
||||
# Tier 3: env vars
|
||||
|
|
@ -1270,7 +1270,6 @@ proc resolveCredentials(argPk: string, argSk: string, accountIndex: int): tuple[
|
|||
return (envPk, envSk)
|
||||
# Tier 4: ~/.unsandbox/accounts.csv (default row or UNSANDBOX_ACCOUNT)
|
||||
let defaultIndex = parseInt(getEnv("UNSANDBOX_ACCOUNT", "0"))
|
||||
let homeDir = getHomeDir()
|
||||
let homeCsv = homeDir / ".unsandbox" / "accounts.csv"
|
||||
var creds = loadCredentialsFromCsv(homeCsv, defaultIndex)
|
||||
if creds.pk != "":
|
||||
|
|
@ -1296,7 +1295,10 @@ proc main() =
|
|||
var i = 0
|
||||
while i < rawArgs.len:
|
||||
if rawArgs[i] == "--account" and i + 1 < rawArgs.len:
|
||||
accountIndex = parseInt(rawArgs[i + 1])
|
||||
try:
|
||||
accountIndex = parseInt(rawArgs[i + 1])
|
||||
except ValueError:
|
||||
discard
|
||||
i.inc
|
||||
elif rawArgs[i] == "-p" and i + 1 < rawArgs.len:
|
||||
argPublicKey = rawArgs[i + 1]
|
||||
|
|
|
|||
|
|
@ -50,8 +50,10 @@
|
|||
//
|
||||
// Authentication (in priority order):
|
||||
// 1. UNClient initWithPublicKey:secretKey: constructor arguments
|
||||
// 2. Environment variables: UNSANDBOX_PUBLIC_KEY + UNSANDBOX_SECRET_KEY
|
||||
// 3. Config file: ~/.unsandbox/accounts.csv (public_key,secret_key per line)
|
||||
// 2. --account N flag -> accounts.csv row N (bypasses env vars)
|
||||
// 3. Environment variables: UNSANDBOX_PUBLIC_KEY + UNSANDBOX_SECRET_KEY
|
||||
// 4. Config file: ~/.unsandbox/accounts.csv row 0 (or UNSANDBOX_ACCOUNT)
|
||||
// 5. ./accounts.csv row 0
|
||||
|
||||
#!/usr/bin/env -S clang -x objective-c -framework Foundation -o /tmp/un_objc && /tmp/un_objc
|
||||
|
||||
|
|
@ -207,9 +209,54 @@ NSString* UNComputeSignature(NSString* secretKey, long timestamp, NSString* meth
|
|||
// Credentials Loading
|
||||
// ============================================================================
|
||||
|
||||
// Global account index: -1 means not set (use env vars / default CSV row).
|
||||
// Set by main() when --account N is parsed.
|
||||
static NSInteger g_accountIndex = -1;
|
||||
|
||||
/**
|
||||
* Load public_key,secret_key from a CSV file at the given row index (0-based,
|
||||
* skipping blank lines and comment lines starting with '#').
|
||||
*
|
||||
* @param csvPath Path to the CSV file
|
||||
* @param rowIndex Zero-based data row to read
|
||||
* @param outPk Output: public key string (nil if not found)
|
||||
* @param outSk Output: secret key string (nil if not found)
|
||||
*/
|
||||
void UNLoadCredentialsFromCSV(NSString* csvPath, NSInteger rowIndex, NSString** outPk, NSString** outSk) {
|
||||
*outPk = nil;
|
||||
*outSk = nil;
|
||||
NSFileManager* fm = [NSFileManager defaultManager];
|
||||
if (![fm fileExistsAtPath:csvPath]) return;
|
||||
|
||||
NSString* content = [NSString stringWithContentsOfFile:csvPath encoding:NSUTF8StringEncoding error:nil];
|
||||
if (!content) return;
|
||||
|
||||
NSArray* lines = [content componentsSeparatedByString:@"\n"];
|
||||
NSInteger dataIndex = 0;
|
||||
for (NSString* line in lines) {
|
||||
NSString* trimmed = [line stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
||||
if ([trimmed length] == 0 || [trimmed hasPrefix:@"#"]) continue;
|
||||
if (dataIndex == rowIndex) {
|
||||
NSArray* parts = [trimmed componentsSeparatedByString:@","];
|
||||
if ([parts count] >= 2) {
|
||||
*outPk = [parts[0] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
|
||||
*outSk = [parts[1] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
|
||||
}
|
||||
return;
|
||||
}
|
||||
dataIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get API credentials from environment or config file.
|
||||
* Priority: 1. Arguments, 2. Environment vars, 3. ~/.unsandbox/accounts.csv
|
||||
*
|
||||
* Priority order:
|
||||
* 1. Function arguments (argPublicKey / argSecretKey)
|
||||
* 2. g_accountIndex >= 0 -> accounts.csv row N (bypasses env vars)
|
||||
* 3. Environment variables: UNSANDBOX_PUBLIC_KEY + UNSANDBOX_SECRET_KEY
|
||||
* 4. ~/.unsandbox/accounts.csv row 0 (or UNSANDBOX_ACCOUNT env var)
|
||||
* 5. ./accounts.csv row 0
|
||||
*
|
||||
* @param publicKey Output public key
|
||||
* @param secretKey Output secret key
|
||||
|
|
@ -226,7 +273,22 @@ BOOL UNGetCredentials(NSString** publicKey, NSString** secretKey, NSString* argP
|
|||
return YES;
|
||||
}
|
||||
|
||||
// Priority 2: Environment variables
|
||||
// Priority 2: --account N -> accounts.csv row N (bypasses env vars)
|
||||
if (g_accountIndex >= 0) {
|
||||
NSString* home = NSHomeDirectory();
|
||||
NSString* homeCsv = [home stringByAppendingPathComponent:@".unsandbox/accounts.csv"];
|
||||
UNLoadCredentialsFromCSV(homeCsv, g_accountIndex, publicKey, secretKey);
|
||||
if (*publicKey && [*publicKey length] > 0) return YES;
|
||||
UNLoadCredentialsFromCSV(@"accounts.csv", g_accountIndex, publicKey, secretKey);
|
||||
if (*publicKey && [*publicKey length] > 0) return YES;
|
||||
if (error) {
|
||||
*error = [UNAuthenticationError errorWithMessage:
|
||||
[NSString stringWithFormat:@"No credentials found for account index %ld in accounts.csv", (long)g_accountIndex]];
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
// Priority 3: Environment variables
|
||||
*publicKey = [[[NSProcessInfo processInfo] environment] objectForKey:@"UNSANDBOX_PUBLIC_KEY"];
|
||||
*secretKey = [[[NSProcessInfo processInfo] environment] objectForKey:@"UNSANDBOX_SECRET_KEY"];
|
||||
|
||||
|
|
@ -242,32 +304,17 @@ BOOL UNGetCredentials(NSString** publicKey, NSString** secretKey, NSString* argP
|
|||
return YES;
|
||||
}
|
||||
|
||||
// Priority 3: Config file ~/.unsandbox/accounts.csv
|
||||
// Priority 4: Config file ~/.unsandbox/accounts.csv (default row)
|
||||
NSString* home = NSHomeDirectory();
|
||||
NSString* accountsPath = [home stringByAppendingPathComponent:@".unsandbox/accounts.csv"];
|
||||
NSFileManager* fm = [NSFileManager defaultManager];
|
||||
NSString* accountIndexStr = [[[NSProcessInfo processInfo] environment] objectForKey:@"UNSANDBOX_ACCOUNT"];
|
||||
NSInteger defaultIndex = accountIndexStr ? [accountIndexStr integerValue] : 0;
|
||||
NSString* homeCsv = [home stringByAppendingPathComponent:@".unsandbox/accounts.csv"];
|
||||
UNLoadCredentialsFromCSV(homeCsv, defaultIndex, publicKey, secretKey);
|
||||
if (*publicKey && [*publicKey length] > 0) return YES;
|
||||
|
||||
if ([fm fileExistsAtPath:accountsPath]) {
|
||||
NSString* content = [NSString stringWithContentsOfFile:accountsPath encoding:NSUTF8StringEncoding error:nil];
|
||||
if (content) {
|
||||
NSArray* lines = [content componentsSeparatedByString:@"\n"];
|
||||
for (NSString* line in lines) {
|
||||
NSString* trimmed = [line stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
||||
if ([trimmed length] == 0 || [trimmed hasPrefix:@"#"]) continue;
|
||||
|
||||
NSArray* parts = [trimmed componentsSeparatedByString:@","];
|
||||
if ([parts count] >= 2) {
|
||||
NSString* pk = [parts[0] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
|
||||
NSString* sk = [parts[1] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
|
||||
if ([pk hasPrefix:@"unsb-pk-"] && [sk hasPrefix:@"unsb-sk-"]) {
|
||||
*publicKey = pk;
|
||||
*secretKey = sk;
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Priority 5: ./accounts.csv
|
||||
UNLoadCredentialsFromCSV(@"accounts.csv", defaultIndex, publicKey, secretKey);
|
||||
if (*publicKey && [*publicKey length] > 0) return YES;
|
||||
|
||||
if (error) {
|
||||
*error = [UNAuthenticationError errorWithMessage:
|
||||
|
|
@ -2378,6 +2425,14 @@ int main(int argc, const char* argv[]) {
|
|||
[args addObject:[NSString stringWithUTF8String:argv[i]]];
|
||||
}
|
||||
|
||||
// Pre-scan for --account N before subcommand dispatch
|
||||
for (NSUInteger i = 0; i < [args count]; i++) {
|
||||
if ([args[i] isEqualToString:@"--account"] && i + 1 < [args count]) {
|
||||
g_accountIndex = [args[i + 1] integerValue];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
NSString* firstArg = args[0];
|
||||
|
||||
if ([firstArg isEqualToString:@"--help"] || [firstArg isEqualToString:@"-h"]) {
|
||||
|
|
|
|||
|
|
@ -806,6 +806,116 @@ fn buildInputFilesJson(allocator: std.mem.Allocator, files: std.ArrayList([]cons
|
|||
return list.toOwnedSlice();
|
||||
}
|
||||
|
||||
/// Load credentials from a CSV file at the given 0-based row index,
|
||||
/// skipping blank lines and lines starting with '#'.
|
||||
/// Returns allocated pk and sk slices, or null if not found.
|
||||
fn loadCsvRow(allocator: std.mem.Allocator, csv_path: []const u8, row_index: usize) !?struct { pk: []const u8, sk: []const u8 } {
|
||||
const file = fs.cwd().openFile(csv_path, .{}) catch return null;
|
||||
defer file.close();
|
||||
|
||||
var buf_reader = std.io.bufferedReader(file.reader());
|
||||
var reader = buf_reader.reader();
|
||||
var line_buf: [4096]u8 = undefined;
|
||||
var data_index: usize = 0;
|
||||
|
||||
while (true) {
|
||||
const line = reader.readUntilDelimiterOrEof(&line_buf, '\n') catch break orelse break;
|
||||
const trimmed = std.mem.trim(u8, line, " \t\r\n");
|
||||
if (trimmed.len == 0 or trimmed[0] == '#') continue;
|
||||
if (data_index == row_index) {
|
||||
if (std.mem.indexOfScalar(u8, trimmed, ',')) |comma_pos| {
|
||||
const pk = std.mem.trim(u8, trimmed[0..comma_pos], " \t");
|
||||
const sk = std.mem.trim(u8, trimmed[comma_pos + 1 ..], " \t");
|
||||
if (pk.len > 0 and sk.len > 0) {
|
||||
return .{ .pk = try allocator.dupe(u8, pk), .sk = try allocator.dupe(u8, sk) };
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
data_index += 1;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Resolve credentials using 5-tier priority:
|
||||
/// 1. arg_pk / arg_sk (explicit -p/-k flags) if both non-empty
|
||||
/// 2. account_index >= 0 -> 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 var)
|
||||
/// 5. ./accounts.csv row 0
|
||||
/// Returns allocated pk and sk slices.
|
||||
fn resolveCredentials(allocator: std.mem.Allocator, arg_pk: []const u8, arg_sk: []const u8, account_index: i64) !struct { pk: []u8, sk: []u8 } {
|
||||
// Tier 1: explicit key flags
|
||||
if (arg_pk.len > 0 and arg_sk.len > 0) {
|
||||
return .{ .pk = try allocator.dupe(u8, arg_pk), .sk = try allocator.dupe(u8, arg_sk) };
|
||||
}
|
||||
|
||||
// Tier 2: --account N bypasses env vars
|
||||
if (account_index >= 0) {
|
||||
const acct_idx: usize = @intCast(account_index);
|
||||
// try ~/.unsandbox/accounts.csv
|
||||
const home_opt = process.getEnvVarOwned(allocator, "HOME") catch null;
|
||||
if (home_opt) |home| {
|
||||
defer allocator.free(home);
|
||||
const home_csv = try std.fmt.allocPrint(allocator, "{s}/.unsandbox/accounts.csv", .{home});
|
||||
defer allocator.free(home_csv);
|
||||
if (try loadCsvRow(allocator, home_csv, acct_idx)) |creds| {
|
||||
return .{ .pk = @constCast(creds.pk), .sk = @constCast(creds.sk) };
|
||||
}
|
||||
}
|
||||
// try ./accounts.csv
|
||||
if (try loadCsvRow(allocator, "accounts.csv", acct_idx)) |creds| {
|
||||
return .{ .pk = @constCast(creds.pk), .sk = @constCast(creds.sk) };
|
||||
}
|
||||
const stderr = std.io.getStdErr().writer();
|
||||
try stderr.print("{s}Error: No credentials found for account index {d} in accounts.csv{s}\n", .{ RED, account_index, RESET });
|
||||
std.process.exit(1);
|
||||
}
|
||||
|
||||
// Tier 3: env vars
|
||||
const env_pk = process.getEnvVarOwned(allocator, "UNSANDBOX_PUBLIC_KEY") catch blk: {
|
||||
break :blk process.getEnvVarOwned(allocator, "UNSANDBOX_API_KEY") catch try allocator.dupe(u8, "");
|
||||
};
|
||||
const env_sk = process.getEnvVarOwned(allocator, "UNSANDBOX_SECRET_KEY") catch try allocator.dupe(u8, "");
|
||||
if (env_pk.len > 0 and env_sk.len > 0) {
|
||||
return .{ .pk = env_pk, .sk = env_sk };
|
||||
}
|
||||
|
||||
// Tier 4: ~/.unsandbox/accounts.csv (default row)
|
||||
const default_index_str = process.getEnvVarOwned(allocator, "UNSANDBOX_ACCOUNT") catch try allocator.dupe(u8, "0");
|
||||
defer allocator.free(default_index_str);
|
||||
const default_index = std.fmt.parseInt(usize, std.mem.trim(u8, default_index_str, " \t"), 10) catch 0;
|
||||
|
||||
const home_opt = process.getEnvVarOwned(allocator, "HOME") catch null;
|
||||
if (home_opt) |home| {
|
||||
defer allocator.free(home);
|
||||
const home_csv = try std.fmt.allocPrint(allocator, "{s}/.unsandbox/accounts.csv", .{home});
|
||||
defer allocator.free(home_csv);
|
||||
if (try loadCsvRow(allocator, home_csv, default_index)) |creds| {
|
||||
allocator.free(env_pk);
|
||||
allocator.free(env_sk);
|
||||
return .{ .pk = @constCast(creds.pk), .sk = @constCast(creds.sk) };
|
||||
}
|
||||
}
|
||||
|
||||
// Tier 5: ./accounts.csv
|
||||
if (try loadCsvRow(allocator, "accounts.csv", default_index)) |creds| {
|
||||
allocator.free(env_pk);
|
||||
allocator.free(env_sk);
|
||||
return .{ .pk = @constCast(creds.pk), .sk = @constCast(creds.sk) };
|
||||
}
|
||||
|
||||
if (env_pk.len > 0) {
|
||||
return .{ .pk = env_pk, .sk = env_sk };
|
||||
}
|
||||
|
||||
allocator.free(env_pk);
|
||||
allocator.free(env_sk);
|
||||
const stderr = std.io.getStdErr().writer();
|
||||
try stderr.print("{s}Error: No credentials found. Set UNSANDBOX_PUBLIC_KEY and UNSANDBOX_SECRET_KEY{s}\n", .{ RED, RESET });
|
||||
std.process.exit(1);
|
||||
}
|
||||
|
||||
pub fn main() !u8 {
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
defer _ = gpa.deinit();
|
||||
|
|
@ -837,18 +947,37 @@ pub fn main() !u8 {
|
|||
std.debug.print(" image --visibility ID MODE Set visibility (private/unlisted/public)\n", .{});
|
||||
std.debug.print(" image --spawn ID --name NAME Spawn service from image\n", .{});
|
||||
std.debug.print(" image --clone ID --name NAME Clone an image\n", .{});
|
||||
std.debug.print("\nCredential options (global):\n", .{});
|
||||
std.debug.print(" -p PUBLIC_KEY Explicit public key\n", .{});
|
||||
std.debug.print(" -k SECRET_KEY Explicit secret key\n", .{});
|
||||
std.debug.print(" --account N Use row N from accounts.csv (bypasses env vars)\n", .{});
|
||||
return 1;
|
||||
}
|
||||
|
||||
var public_key = std.process.getEnvVarOwned(allocator, "UNSANDBOX_PUBLIC_KEY") catch blk: {
|
||||
// Fall back to UNSANDBOX_API_KEY for backwards compatibility
|
||||
break :blk std.process.getEnvVarOwned(allocator, "UNSANDBOX_API_KEY") catch try allocator.dupe(u8, "");
|
||||
};
|
||||
defer allocator.free(public_key);
|
||||
// Pre-scan for --account N, -p, and -k before subcommand dispatch
|
||||
var account_index: i64 = -1;
|
||||
var arg_pk: []const u8 = "";
|
||||
var arg_sk: []const u8 = "";
|
||||
{
|
||||
var scan_i: usize = 1;
|
||||
while (scan_i < args.len) : (scan_i += 1) {
|
||||
if (mem.eql(u8, args[scan_i], "--account") and scan_i + 1 < args.len) {
|
||||
scan_i += 1;
|
||||
account_index = std.fmt.parseInt(i64, args[scan_i], 10) catch -1;
|
||||
} else if (mem.eql(u8, args[scan_i], "-p") and scan_i + 1 < args.len) {
|
||||
scan_i += 1;
|
||||
arg_pk = args[scan_i];
|
||||
} else if (mem.eql(u8, args[scan_i], "-k") and scan_i + 1 < args.len) {
|
||||
scan_i += 1;
|
||||
arg_sk = args[scan_i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const secret_key = std.process.getEnvVarOwned(allocator, "UNSANDBOX_SECRET_KEY") catch blk: {
|
||||
break :blk try allocator.dupe(u8, "");
|
||||
};
|
||||
const creds = try resolveCredentials(allocator, arg_pk, arg_sk, account_index);
|
||||
var public_key = creds.pk;
|
||||
defer allocator.free(public_key);
|
||||
const secret_key = creds.sk;
|
||||
defer allocator.free(secret_key);
|
||||
|
||||
// Handle session command
|
||||
|
|
@ -1727,16 +1856,27 @@ pub fn main() !u8 {
|
|||
return 0;
|
||||
}
|
||||
|
||||
// Execute mode - find source file
|
||||
// Execute mode - find source file (skip known flags and their values)
|
||||
var source_file: ?[]const u8 = null;
|
||||
for (args[1..]) |arg| {
|
||||
if (mem.startsWith(u8, arg, "-")) {
|
||||
const stderr = std.io.getStdErr().writer();
|
||||
stderr.print("{s}Unknown option: {s}{s}\n", .{ RED, arg, RESET }) catch {};
|
||||
std.os.exit(1);
|
||||
} else {
|
||||
source_file = arg;
|
||||
break;
|
||||
{
|
||||
var exec_i: usize = 1;
|
||||
while (exec_i < args.len) : (exec_i += 1) {
|
||||
const arg = args[exec_i];
|
||||
if (mem.eql(u8, arg, "--account") or mem.eql(u8, arg, "-p") or
|
||||
mem.eql(u8, arg, "-k") or mem.eql(u8, arg, "-e") or
|
||||
mem.eql(u8, arg, "-n") or mem.eql(u8, arg, "-v"))
|
||||
{
|
||||
exec_i += 1; // skip value
|
||||
} else if (mem.eql(u8, arg, "-a")) {
|
||||
// no value
|
||||
} else if (mem.startsWith(u8, arg, "-")) {
|
||||
const stderr = std.io.getStdErr().writer();
|
||||
stderr.print("{s}Unknown option: {s}{s}\n", .{ RED, arg, RESET }) catch {};
|
||||
std.os.exit(1);
|
||||
} else {
|
||||
source_file = arg;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue