feat: full feature parity for all 42 SDKs + comprehensive tests
All SDKs now implement 58+ functions matching C reference (un.h): - Execution (8): execute, execute_async, wait_job, get_job, cancel_job, list_jobs, get_languages, detect_language - Sessions (9): list, get, create, destroy, freeze, unfreeze, boost, unboost, execute - Services (17): list, get, create, destroy, freeze, unfreeze, lock, unlock, set_unfreeze_on_demand, redeploy, logs, execute, env_get/set/delete/export, resize - Snapshots (9): list, get, session, service, restore, delete, lock, unlock, clone - Images (13): list, get, publish, delete, lock, unlock, set_visibility, grant/revoke_access, list_trusted, transfer, spawn, clone - PaaS Logs (2): fetch, stream - Utilities (5): validate_keys, hmac_sign, health_check, version, last_error Test suites created for all SDKs with unit, integration, and functional tests. Languages: AWK, Bash, C++, C#, Clojure, COBOL, Crystal, D, Dart, .NET, Elixir, Erlang, F#, Forth, Fortran, Go, Groovy, Haskell, Java, JavaScript, Julia, Kotlin, Lisp, Lua, Nim, Objective-C, OCaml, Perl, PHP, PowerShell, Prolog, Python, R, Raku, Ruby, Rust, Scheme, Swift, Tcl, TypeScript, V, Zig
This commit is contained in:
parent
a5155aed4f
commit
6e746ace44
90 changed files with 28955 additions and 3028 deletions
|
|
@ -573,12 +573,57 @@ def cmd_session(args)
|
|||
return
|
||||
end
|
||||
|
||||
if info_id = args[:session_info]?.as?(String)
|
||||
result = api_request("/sessions/#{info_id}", public_key, secret_key)
|
||||
puts result.to_pretty_json
|
||||
return
|
||||
end
|
||||
|
||||
if kill_id = args[:kill]?.as?(String)
|
||||
api_request("/sessions/#{kill_id}", public_key, secret_key, method: "DELETE")
|
||||
puts "#{GREEN}Session terminated: #{kill_id}#{RESET}"
|
||||
return
|
||||
end
|
||||
|
||||
if freeze_id = args[:session_freeze]?.as?(String)
|
||||
api_request("/sessions/#{freeze_id}/freeze", public_key, secret_key, method: "POST")
|
||||
puts "#{GREEN}Session frozen: #{freeze_id}#{RESET}"
|
||||
return
|
||||
end
|
||||
|
||||
if unfreeze_id = args[:session_unfreeze]?.as?(String)
|
||||
api_request("/sessions/#{unfreeze_id}/unfreeze", public_key, secret_key, method: "POST")
|
||||
puts "#{GREEN}Session unfreezing: #{unfreeze_id}#{RESET}"
|
||||
return
|
||||
end
|
||||
|
||||
if boost_id = args[:session_boost]?.as?(String)
|
||||
vcpu = args[:vcpu]?.as?(Int32) || 2
|
||||
payload = JSON.parse({vcpu: vcpu}.to_json)
|
||||
api_request("/sessions/#{boost_id}/boost", public_key, secret_key, method: "POST", data: payload)
|
||||
puts "#{GREEN}Session boosted to #{vcpu} vCPU: #{boost_id}#{RESET}"
|
||||
return
|
||||
end
|
||||
|
||||
if unboost_id = args[:session_unboost]?.as?(String)
|
||||
api_request("/sessions/#{unboost_id}/unboost", public_key, secret_key, method: "POST")
|
||||
puts "#{GREEN}Session unboosted: #{unboost_id}#{RESET}"
|
||||
return
|
||||
end
|
||||
|
||||
if execute_id = args[:session_execute]?.as?(String)
|
||||
command = args[:command]?.as?(String) || ""
|
||||
payload = JSON.parse({command: command}.to_json)
|
||||
result = api_request("/sessions/#{execute_id}/execute", public_key, secret_key, method: "POST", data: payload)
|
||||
if stdout = result["stdout"]?.try(&.as_s?)
|
||||
print BLUE, stdout, RESET
|
||||
end
|
||||
if stderr = result["stderr"]?.try(&.as_s?)
|
||||
print RED, stderr, RESET
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
# Create new session
|
||||
payload = JSON.parse({shell: "bash"}.to_json)
|
||||
|
||||
|
|
@ -586,6 +631,10 @@ def cmd_session(args)
|
|||
payload.as_h["network"] = JSON::Any.new(network)
|
||||
end
|
||||
|
||||
if shell = args[:shell]?.as?(String)
|
||||
payload.as_h["shell"] = JSON::Any.new(shell)
|
||||
end
|
||||
|
||||
# Add input files
|
||||
if files = args[:files]?.as?(Array(String))
|
||||
input_files = [] of JSON::Any
|
||||
|
|
@ -844,11 +893,291 @@ def cmd_image(args)
|
|||
return
|
||||
end
|
||||
|
||||
if grant_id = args[:image_grant]?.as?(String)
|
||||
trusted_key = args[:image_trusted_key]?.as?(String)
|
||||
if trusted_key.nil? || trusted_key.empty?
|
||||
STDERR.puts "#{RED}Error: --grant requires --trusted-key#{RESET}"
|
||||
exit 1
|
||||
end
|
||||
payload = JSON.parse({trusted_api_key: trusted_key}.to_json)
|
||||
api_request("/images/#{grant_id}/grant", public_key, secret_key, method: "POST", data: payload)
|
||||
puts "#{GREEN}Access granted to #{trusted_key}#{RESET}"
|
||||
return
|
||||
end
|
||||
|
||||
if revoke_id = args[:image_revoke]?.as?(String)
|
||||
trusted_key = args[:image_trusted_key]?.as?(String)
|
||||
if trusted_key.nil? || trusted_key.empty?
|
||||
STDERR.puts "#{RED}Error: --revoke requires --trusted-key#{RESET}"
|
||||
exit 1
|
||||
end
|
||||
payload = JSON.parse({trusted_api_key: trusted_key}.to_json)
|
||||
api_request("/images/#{revoke_id}/revoke", public_key, secret_key, method: "POST", data: payload)
|
||||
puts "#{GREEN}Access revoked from #{trusted_key}#{RESET}"
|
||||
return
|
||||
end
|
||||
|
||||
if trusted_id = args[:image_trusted]?.as?(String)
|
||||
result = api_request("/images/#{trusted_id}/trusted", public_key, secret_key)
|
||||
puts result.to_pretty_json
|
||||
return
|
||||
end
|
||||
|
||||
if transfer_id = args[:image_transfer]?.as?(String)
|
||||
to_key = args[:image_to_key]?.as?(String)
|
||||
if to_key.nil? || to_key.empty?
|
||||
STDERR.puts "#{RED}Error: --transfer requires --to-key#{RESET}"
|
||||
exit 1
|
||||
end
|
||||
payload = JSON.parse({to_api_key: to_key}.to_json)
|
||||
status_code, response = api_request_with_sudo("/images/#{transfer_id}/transfer", public_key, secret_key, method: "POST", data: payload)
|
||||
if status_code == 428
|
||||
handle_sudo_challenge(response.to_json, public_key, secret_key, "POST", "/images/#{transfer_id}/transfer", payload.to_json)
|
||||
elsif status_code >= 200 && status_code < 300
|
||||
puts "#{GREEN}Image transferred to #{to_key}#{RESET}"
|
||||
else
|
||||
STDERR.puts "#{RED}Error: HTTP #{status_code}#{RESET}"
|
||||
STDERR.puts response.to_json
|
||||
exit 1
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
# Default: list images
|
||||
result = api_request("/images", public_key, secret_key)
|
||||
puts result.to_pretty_json
|
||||
end
|
||||
|
||||
def cmd_snapshot(args)
|
||||
public_key, secret_key = get_api_keys(args[:api_key]?)
|
||||
|
||||
if args[:list]?.as?(Bool)
|
||||
result = api_request("/snapshots", public_key, secret_key)
|
||||
snapshots = result["snapshots"]?.try(&.as_a?) || [] of JSON::Any
|
||||
if snapshots.empty?
|
||||
puts "No snapshots"
|
||||
else
|
||||
printf "%-20s %-20s %-10s %-10s %-10s %s\n", "ID", "Name", "Type", "Hot", "Locked", "Created"
|
||||
snapshots.each do |s|
|
||||
printf "%-20s %-20s %-10s %-10s %-10s %s\n",
|
||||
s["id"]?.try(&.as_s?) || "N/A",
|
||||
s["name"]?.try(&.as_s?) || "N/A",
|
||||
s["type"]?.try(&.as_s?) || "N/A",
|
||||
s["hot"]?.try(&.as_bool?) ? "yes" : "no",
|
||||
s["locked"]?.try(&.as_bool?) ? "yes" : "no",
|
||||
s["created_at"]?.try(&.as_s?) || "N/A"
|
||||
end
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
if info_id = args[:snapshot_info]?.as?(String)
|
||||
result = api_request("/snapshots/#{info_id}", public_key, secret_key)
|
||||
puts result.to_pretty_json
|
||||
return
|
||||
end
|
||||
|
||||
if session_id = args[:snapshot_session]?.as?(String)
|
||||
payload = JSON.parse({}.to_json)
|
||||
if name = args[:snapshot_name]?.as?(String)
|
||||
payload.as_h["name"] = JSON::Any.new(name)
|
||||
end
|
||||
if args[:snapshot_hot]?.as?(Bool)
|
||||
payload.as_h["hot"] = JSON::Any.new(true)
|
||||
end
|
||||
result = api_request("/sessions/#{session_id}/snapshot", public_key, secret_key, method: "POST", data: payload)
|
||||
puts "#{GREEN}Snapshot created#{RESET}"
|
||||
puts result.to_pretty_json
|
||||
return
|
||||
end
|
||||
|
||||
if service_id = args[:snapshot_service]?.as?(String)
|
||||
payload = JSON.parse({}.to_json)
|
||||
if name = args[:snapshot_name]?.as?(String)
|
||||
payload.as_h["name"] = JSON::Any.new(name)
|
||||
end
|
||||
if args[:snapshot_hot]?.as?(Bool)
|
||||
payload.as_h["hot"] = JSON::Any.new(true)
|
||||
end
|
||||
result = api_request("/services/#{service_id}/snapshot", public_key, secret_key, method: "POST", data: payload)
|
||||
puts "#{GREEN}Snapshot created#{RESET}"
|
||||
puts result.to_pretty_json
|
||||
return
|
||||
end
|
||||
|
||||
if restore_id = args[:snapshot_restore]?.as?(String)
|
||||
payload = JSON.parse({}.to_json)
|
||||
result = api_request("/snapshots/#{restore_id}/restore", public_key, secret_key, method: "POST", data: payload)
|
||||
puts "#{GREEN}Snapshot restored#{RESET}"
|
||||
puts result.to_pretty_json
|
||||
return
|
||||
end
|
||||
|
||||
if del_id = args[:snapshot_delete]?.as?(String)
|
||||
status_code, response = api_request_with_sudo("/snapshots/#{del_id}", public_key, secret_key, method: "DELETE")
|
||||
if status_code == 428
|
||||
handle_sudo_challenge(response.to_json, public_key, secret_key, "DELETE", "/snapshots/#{del_id}", nil)
|
||||
elsif status_code >= 200 && status_code < 300
|
||||
puts "#{GREEN}Snapshot deleted: #{del_id}#{RESET}"
|
||||
else
|
||||
STDERR.puts "#{RED}Error: HTTP #{status_code}#{RESET}"
|
||||
STDERR.puts response.to_json
|
||||
exit 1
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
if lock_id = args[:snapshot_lock]?.as?(String)
|
||||
payload = JSON.parse({}.to_json)
|
||||
api_request("/snapshots/#{lock_id}/lock", public_key, secret_key, method: "POST", data: payload)
|
||||
puts "#{GREEN}Snapshot locked: #{lock_id}#{RESET}"
|
||||
return
|
||||
end
|
||||
|
||||
if unlock_id = args[:snapshot_unlock]?.as?(String)
|
||||
payload = JSON.parse({}.to_json)
|
||||
body = "{}"
|
||||
status_code, response = api_request_with_sudo("/snapshots/#{unlock_id}/unlock", public_key, secret_key, method: "POST", data: payload)
|
||||
if status_code == 428
|
||||
handle_sudo_challenge(response.to_json, public_key, secret_key, "POST", "/snapshots/#{unlock_id}/unlock", body)
|
||||
elsif status_code >= 200 && status_code < 300
|
||||
puts "#{GREEN}Snapshot unlocked: #{unlock_id}#{RESET}"
|
||||
else
|
||||
STDERR.puts "#{RED}Error: HTTP #{status_code}#{RESET}"
|
||||
STDERR.puts response.to_json
|
||||
exit 1
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
if clone_id = args[:snapshot_clone]?.as?(String)
|
||||
clone_type = args[:snapshot_clone_type]?.as?(String) || "session"
|
||||
payload = JSON.parse({clone_type: clone_type}.to_json)
|
||||
if name = args[:snapshot_name]?.as?(String)
|
||||
payload.as_h["name"] = JSON::Any.new(name)
|
||||
end
|
||||
if ports_str = args[:snapshot_ports]?.as?(String)
|
||||
ports = ports_str.split(',').map(&.to_i)
|
||||
payload.as_h["ports"] = JSON.parse(ports.to_json)
|
||||
end
|
||||
result = api_request("/snapshots/#{clone_id}/clone", public_key, secret_key, method: "POST", data: payload)
|
||||
puts "#{GREEN}Snapshot cloned#{RESET}"
|
||||
puts result.to_pretty_json
|
||||
return
|
||||
end
|
||||
|
||||
# Default: list snapshots
|
||||
result = api_request("/snapshots", public_key, secret_key)
|
||||
puts result.to_pretty_json
|
||||
end
|
||||
|
||||
def cmd_logs(args)
|
||||
public_key, secret_key = get_api_keys(args[:api_key]?)
|
||||
|
||||
source = args[:logs_source]?.as?(String) || "all"
|
||||
lines = args[:logs_lines]?.as?(Int32) || 100
|
||||
since = args[:logs_since]?.as?(String) || "1h"
|
||||
grep_pattern = args[:logs_grep]?.as?(String)
|
||||
|
||||
endpoint = "/paas/logs?source=#{source}&lines=#{lines}&since=#{since}"
|
||||
if grep_pattern && !grep_pattern.empty?
|
||||
endpoint += "&grep=#{URI.encode_path(grep_pattern)}"
|
||||
end
|
||||
|
||||
if args[:logs_follow]?.as?(Bool)
|
||||
# Streaming logs via SSE
|
||||
stream_endpoint = "/paas/logs/stream?source=#{source}"
|
||||
if grep_pattern && !grep_pattern.empty?
|
||||
stream_endpoint += "&grep=#{URI.encode_path(grep_pattern)}"
|
||||
end
|
||||
|
||||
url = URI.parse(PORTAL_BASE + stream_endpoint)
|
||||
headers = HTTP::Headers{
|
||||
"Accept" => "text/event-stream"
|
||||
}
|
||||
|
||||
# Add HMAC authentication headers
|
||||
if secret_key && !secret_key.empty?
|
||||
timestamp = Time.utc.to_unix.to_s
|
||||
message = "#{timestamp}:GET:#{stream_endpoint}:"
|
||||
signature = OpenSSL::HMAC.hexdigest(:sha256, secret_key, message)
|
||||
headers["Authorization"] = "Bearer #{public_key}"
|
||||
headers["X-Timestamp"] = timestamp
|
||||
headers["X-Signature"] = signature
|
||||
else
|
||||
headers["Authorization"] = "Bearer #{public_key}"
|
||||
end
|
||||
|
||||
begin
|
||||
HTTP::Client.get(url, headers: headers) do |response|
|
||||
if response.status_code == 200
|
||||
response.body_io.each_line do |line|
|
||||
if line.starts_with?("data: ")
|
||||
data = line[6..]
|
||||
begin
|
||||
parsed = JSON.parse(data)
|
||||
src = parsed["source"]?.try(&.as_s?) || "unknown"
|
||||
msg = parsed["line"]?.try(&.as_s?) || data
|
||||
puts "[#{src}] #{msg}"
|
||||
rescue
|
||||
puts line
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
STDERR.puts "#{RED}Error: HTTP #{response.status_code}#{RESET}"
|
||||
STDERR.puts response.body_io.gets_to_end
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
rescue ex
|
||||
STDERR.puts "#{RED}Error: #{ex.message}#{RESET}"
|
||||
exit 1
|
||||
end
|
||||
else
|
||||
# Batch fetch
|
||||
result = api_request(endpoint, public_key, secret_key)
|
||||
if logs = result["logs"]?.try(&.as_a?)
|
||||
logs.each do |log|
|
||||
src = log["source"]?.try(&.as_s?) || "unknown"
|
||||
msg = log["line"]?.try(&.as_s?) || log.to_json
|
||||
ts = log["timestamp"]?.try(&.as_s?) || ""
|
||||
if ts.empty?
|
||||
puts "[#{src}] #{msg}"
|
||||
else
|
||||
puts "[#{ts}] [#{src}] #{msg}"
|
||||
end
|
||||
end
|
||||
else
|
||||
puts result.to_pretty_json
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def cmd_health(args)
|
||||
begin
|
||||
url = URI.parse(API_BASE + "/health")
|
||||
response = HTTP::Client.get(url)
|
||||
if response.status_code == 200
|
||||
puts "#{GREEN}API is healthy#{RESET}"
|
||||
result = JSON.parse(response.body)
|
||||
puts result.to_pretty_json
|
||||
else
|
||||
puts "#{RED}API is unhealthy: HTTP #{response.status_code}#{RESET}"
|
||||
exit 1
|
||||
end
|
||||
rescue ex
|
||||
puts "#{RED}API is unreachable: #{ex.message}#{RESET}"
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
|
||||
def cmd_version(args)
|
||||
puts "un.cr version 1.0.0"
|
||||
puts "API: #{API_BASE}"
|
||||
puts "Portal: #{PORTAL_BASE}"
|
||||
end
|
||||
|
||||
def cmd_service(args)
|
||||
# Handle env subcommand
|
||||
if env_action = args[:env_action]?.as?(String)
|
||||
|
|
@ -973,6 +1302,48 @@ def cmd_service(args)
|
|||
return
|
||||
end
|
||||
|
||||
if lock_id = args[:service_lock]?.as?(String)
|
||||
payload = JSON.parse({}.to_json)
|
||||
api_request("/services/#{lock_id}/lock", public_key, secret_key, method: "POST", data: payload)
|
||||
puts "#{GREEN}Service locked: #{lock_id}#{RESET}"
|
||||
return
|
||||
end
|
||||
|
||||
if unlock_id = args[:service_unlock]?.as?(String)
|
||||
payload = JSON.parse({}.to_json)
|
||||
body = "{}"
|
||||
status_code, response = api_request_with_sudo("/services/#{unlock_id}/unlock", public_key, secret_key, method: "POST", data: payload)
|
||||
if status_code == 428
|
||||
handle_sudo_challenge(response.to_json, public_key, secret_key, "POST", "/services/#{unlock_id}/unlock", body)
|
||||
elsif status_code >= 200 && status_code < 300
|
||||
puts "#{GREEN}Service unlocked: #{unlock_id}#{RESET}"
|
||||
else
|
||||
STDERR.puts "#{RED}Error: HTTP #{status_code}#{RESET}"
|
||||
STDERR.puts response.to_json
|
||||
exit 1
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
if redeploy_id = args[:redeploy]?.as?(String)
|
||||
payload = JSON.parse({}.to_json)
|
||||
if bootstrap = args[:bootstrap]?.as?(String)
|
||||
payload.as_h["bootstrap"] = JSON::Any.new(bootstrap)
|
||||
end
|
||||
if bootstrap_file = args[:bootstrap_file]?.as?(String)
|
||||
if File.exists?(bootstrap_file)
|
||||
payload.as_h["bootstrap_content"] = JSON::Any.new(File.read(bootstrap_file))
|
||||
else
|
||||
STDERR.puts "#{RED}Error: Bootstrap file not found: #{bootstrap_file}#{RESET}"
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
result = api_request("/services/#{redeploy_id}/redeploy", public_key, secret_key, method: "POST", data: payload)
|
||||
puts "#{GREEN}Service redeployed: #{redeploy_id}#{RESET}"
|
||||
puts result.to_pretty_json
|
||||
return
|
||||
end
|
||||
|
||||
# Create new service
|
||||
if name = args[:name]?.as?(String)
|
||||
payload = JSON.parse({name: name}.to_json)
|
||||
|
|
@ -1103,6 +1474,19 @@ def main
|
|||
env_action: nil,
|
||||
env_target: nil,
|
||||
json: false,
|
||||
shell: nil,
|
||||
# Session options
|
||||
session_info: nil,
|
||||
session_freeze: nil,
|
||||
session_unfreeze: nil,
|
||||
session_boost: nil,
|
||||
session_unboost: nil,
|
||||
session_execute: nil,
|
||||
# Service options
|
||||
service_lock: nil,
|
||||
service_unlock: nil,
|
||||
redeploy: nil,
|
||||
# Image options
|
||||
image_info: nil,
|
||||
image_delete: nil,
|
||||
image_lock: nil,
|
||||
|
|
@ -1114,11 +1498,36 @@ def main
|
|||
image_spawn: nil,
|
||||
image_clone: nil,
|
||||
image_name: nil,
|
||||
image_ports: nil
|
||||
} of Symbol => (String | Array(String) | Bool | Nil)
|
||||
image_ports: nil,
|
||||
image_grant: nil,
|
||||
image_revoke: nil,
|
||||
image_trusted: nil,
|
||||
image_trusted_key: nil,
|
||||
image_transfer: nil,
|
||||
image_to_key: nil,
|
||||
# Snapshot options
|
||||
snapshot_info: nil,
|
||||
snapshot_session: nil,
|
||||
snapshot_service: nil,
|
||||
snapshot_restore: nil,
|
||||
snapshot_delete: nil,
|
||||
snapshot_lock: nil,
|
||||
snapshot_unlock: nil,
|
||||
snapshot_clone: nil,
|
||||
snapshot_clone_type: nil,
|
||||
snapshot_name: nil,
|
||||
snapshot_hot: false,
|
||||
snapshot_ports: nil,
|
||||
# Logs options
|
||||
logs_source: nil,
|
||||
logs_lines: nil,
|
||||
logs_since: nil,
|
||||
logs_grep: nil,
|
||||
logs_follow: false
|
||||
} of Symbol => (String | Array(String) | Bool | Int32 | Nil)
|
||||
|
||||
parser = OptionParser.new do |opts|
|
||||
opts.banner = "Usage: un.cr [options] <source_file>\n un.cr languages [--json]\n un.cr session [options]\n un.cr service [options]\n un.cr service env <action> <service_id> [options]\n un.cr key [options]\n\nService env commands:\n env status <id> Show vault status\n env set <id> Set vault (-e KEY=VALUE or --env-file FILE)\n env export <id> Export vault contents\n env delete <id> Delete vault"
|
||||
opts.banner = "Usage: un.cr [options] <source_file>\n un.cr languages [--json]\n un.cr session [options]\n un.cr service [options]\n un.cr service env <action> <service_id> [options]\n un.cr snapshot [options]\n un.cr image [options]\n un.cr logs [options]\n un.cr key [options]\n un.cr health\n un.cr version\n\nService env commands:\n env status <id> Show vault status\n env set <id> Set vault (-e KEY=VALUE or --env-file FILE)\n env export <id> Export vault contents\n env delete <id> Delete vault"
|
||||
|
||||
opts.on("-k API_KEY", "--api-key=API_KEY", "API key") { |k| args[:api_key] = k }
|
||||
opts.on("-n NETWORK", "--network=NETWORK", "Network mode") { |n| args[:network] = n }
|
||||
|
|
@ -1131,21 +1540,21 @@ def main
|
|||
opts.on("-o DIR", "--output-dir=DIR", "Output directory") { |d| args[:output_dir] = d }
|
||||
opts.on("-l", "--list", "List items") { args[:list] = true }
|
||||
opts.on("--kill=ID", "Kill session") { |id| args[:kill] = id }
|
||||
opts.on("--info=ID", "Get service info") { |id| args[:info] = id }
|
||||
opts.on("--info=ID", "Get service/session info") { |id| args[:info] = id }
|
||||
opts.on("--logs=ID", "Get service logs") { |id| args[:logs] = id }
|
||||
opts.on("--freeze=ID", "Sleep service") { |id| args[:sleep] = id }
|
||||
opts.on("--unfreeze=ID", "Wake service") { |id| args[:wake] = id }
|
||||
opts.on("--freeze=ID", "Freeze service/session") { |id| args[:sleep] = id }
|
||||
opts.on("--unfreeze=ID", "Unfreeze service/session") { |id| args[:wake] = id }
|
||||
opts.on("--unfreeze-on-demand=ID", "Set unfreeze-on-demand for service") { |id| args[:unfreeze_on_demand] = id }
|
||||
opts.on("--unfreeze-on-demand-enabled=BOOL", "Enable/disable unfreeze-on-demand (default: true)") { |b| args[:unfreeze_on_demand_enabled] = b.downcase == "true" }
|
||||
opts.on("--with-unfreeze-on-demand", "Enable unfreeze-on-demand when creating service") { args[:create_unfreeze_on_demand] = true }
|
||||
opts.on("--destroy=ID", "Destroy service") { |id| args[:destroy] = id }
|
||||
opts.on("--execute=ID", "Execute command in service") { |id| args[:execute] = id }
|
||||
opts.on("--execute=ID", "Execute command in service/session") { |id| args[:execute] = id }
|
||||
opts.on("--command=CMD", "Command to execute (with --execute)") { |cmd| args[:command] = cmd }
|
||||
opts.on("--dump-bootstrap=ID", "Dump bootstrap script") { |id| args[:dump_bootstrap] = id }
|
||||
opts.on("--dump-file=FILE", "File to save bootstrap (with --dump-bootstrap)") { |file| args[:dump_file] = file }
|
||||
opts.on("--resize=ID", "Resize service vCPU") { |id| args[:resize] = id }
|
||||
opts.on("-v VCPU", "--vcpu=VCPU", "vCPU count (1-8) for resize") { |v| args[:vcpu] = v.to_i }
|
||||
opts.on("--name=NAME", "Service name") { |n| args[:name] = n }
|
||||
opts.on("-v VCPU", "--vcpu=VCPU", "vCPU count (1-8) for resize/boost") { |v| args[:vcpu] = v.to_i }
|
||||
opts.on("--name=NAME", "Service/snapshot name") { |n| args[:name] = n }
|
||||
opts.on("--ports=PORTS", "Comma-separated ports") { |p| args[:ports] = p }
|
||||
opts.on("--domains=DOMAINS", "Comma-separated domains") { |d| args[:domains] = d }
|
||||
opts.on("--type=TYPE", "Service type for SRV records") { |t| args[:service_type] = t }
|
||||
|
|
@ -1154,12 +1563,99 @@ def main
|
|||
opts.on("--env-file=FILE", "Load env vars from file (for vault)") { |f| args[:svc_env_file] = f }
|
||||
opts.on("--extend", "Open browser to extend/renew key") { args[:extend] = true }
|
||||
opts.on("--json", "Output as JSON array (for languages command)") { args[:json] = true }
|
||||
opts.on("--shell=SHELL", "Shell for session (bash, python3, etc.)") { |s| args[:shell] = s }
|
||||
opts.on("--lock=ID", "Lock service/snapshot/image") { |id| args[:service_lock] = id }
|
||||
opts.on("--unlock=ID", "Unlock service/snapshot/image") { |id| args[:service_unlock] = id }
|
||||
opts.on("--redeploy=ID", "Redeploy service") { |id| args[:redeploy] = id }
|
||||
opts.on("--boost=ID", "Boost session vCPU") { |id| args[:session_boost] = id }
|
||||
opts.on("--unboost=ID", "Unboost session") { |id| args[:session_unboost] = id }
|
||||
# Snapshot options
|
||||
opts.on("--snapshot-session=ID", "Create snapshot from session") { |id| args[:snapshot_session] = id }
|
||||
opts.on("--snapshot-service=ID", "Create snapshot from service") { |id| args[:snapshot_service] = id }
|
||||
opts.on("--restore=ID", "Restore snapshot") { |id| args[:snapshot_restore] = id }
|
||||
opts.on("--delete=ID", "Delete snapshot") { |id| args[:snapshot_delete] = id }
|
||||
opts.on("--clone=ID", "Clone snapshot") { |id| args[:snapshot_clone] = id }
|
||||
opts.on("--clone-type=TYPE", "Clone type (session or service)") { |t| args[:snapshot_clone_type] = t }
|
||||
opts.on("--hot", "Create hot snapshot") { args[:snapshot_hot] = true }
|
||||
# Logs options
|
||||
opts.on("--source=SOURCE", "Log source (all, api, portal, pool/cammy, pool/ai)") { |s| args[:logs_source] = s }
|
||||
opts.on("--lines=N", "Number of log lines") { |n| args[:logs_lines] = n.to_i }
|
||||
opts.on("--since=TIME", "Time window (1m, 5m, 1h, 1d)") { |t| args[:logs_since] = t }
|
||||
opts.on("--grep=PATTERN", "Filter pattern") { |p| args[:logs_grep] = p }
|
||||
opts.on("--follow", "Follow log stream") { args[:logs_follow] = true }
|
||||
# Image access options
|
||||
opts.on("--grant=ID", "Grant image access") { |id| args[:image_grant] = id }
|
||||
opts.on("--revoke=ID", "Revoke image access") { |id| args[:image_revoke] = id }
|
||||
opts.on("--trusted=ID", "List trusted keys for image") { |id| args[:image_trusted] = id }
|
||||
opts.on("--trusted-key=KEY", "API key to grant/revoke access") { |k| args[:image_trusted_key] = k }
|
||||
opts.on("--transfer=ID", "Transfer image ownership") { |id| args[:image_transfer] = id }
|
||||
opts.on("--to-key=KEY", "Target API key for transfer") { |k| args[:image_to_key] = k }
|
||||
|
||||
opts.unknown_args do |before, after|
|
||||
if before.size > 0
|
||||
case before[0]
|
||||
when "session"
|
||||
args[:command] = "session"
|
||||
# Parse session subcommand options
|
||||
i = 1
|
||||
while i < before.size
|
||||
case before[i]
|
||||
when "--list", "-l"
|
||||
args[:list] = true
|
||||
i += 1
|
||||
when "--info"
|
||||
if i + 1 < before.size
|
||||
args[:session_info] = before[i + 1]
|
||||
i += 2
|
||||
else
|
||||
i += 1
|
||||
end
|
||||
when "--freeze"
|
||||
if i + 1 < before.size
|
||||
args[:session_freeze] = before[i + 1]
|
||||
i += 2
|
||||
else
|
||||
i += 1
|
||||
end
|
||||
when "--unfreeze"
|
||||
if i + 1 < before.size
|
||||
args[:session_unfreeze] = before[i + 1]
|
||||
i += 2
|
||||
else
|
||||
i += 1
|
||||
end
|
||||
when "--boost"
|
||||
if i + 1 < before.size
|
||||
args[:session_boost] = before[i + 1]
|
||||
i += 2
|
||||
else
|
||||
i += 1
|
||||
end
|
||||
when "--unboost"
|
||||
if i + 1 < before.size
|
||||
args[:session_unboost] = before[i + 1]
|
||||
i += 2
|
||||
else
|
||||
i += 1
|
||||
end
|
||||
when "--execute"
|
||||
if i + 1 < before.size
|
||||
args[:session_execute] = before[i + 1]
|
||||
i += 2
|
||||
else
|
||||
i += 1
|
||||
end
|
||||
when "--command"
|
||||
if i + 1 < before.size
|
||||
args[:command] = before[i + 1]
|
||||
i += 2
|
||||
else
|
||||
i += 1
|
||||
end
|
||||
else
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
when "service"
|
||||
args[:command] = "service"
|
||||
# Check for env subcommand
|
||||
|
|
@ -1180,11 +1676,179 @@ def main
|
|||
i += 1
|
||||
end
|
||||
end
|
||||
else
|
||||
# Parse service subcommand options
|
||||
i = 1
|
||||
while i < before.size
|
||||
case before[i]
|
||||
when "--lock"
|
||||
if i + 1 < before.size
|
||||
args[:service_lock] = before[i + 1]
|
||||
i += 2
|
||||
else
|
||||
i += 1
|
||||
end
|
||||
when "--unlock"
|
||||
if i + 1 < before.size
|
||||
args[:service_unlock] = before[i + 1]
|
||||
i += 2
|
||||
else
|
||||
i += 1
|
||||
end
|
||||
when "--redeploy"
|
||||
if i + 1 < before.size
|
||||
args[:redeploy] = before[i + 1]
|
||||
i += 2
|
||||
else
|
||||
i += 1
|
||||
end
|
||||
else
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
end
|
||||
when "key"
|
||||
args[:command] = "key"
|
||||
when "languages"
|
||||
args[:command] = "languages"
|
||||
when "snapshot"
|
||||
args[:command] = "snapshot"
|
||||
# Parse snapshot subcommand options
|
||||
i = 1
|
||||
while i < before.size
|
||||
case before[i]
|
||||
when "--list", "-l"
|
||||
args[:list] = true
|
||||
i += 1
|
||||
when "--info"
|
||||
if i + 1 < before.size
|
||||
args[:snapshot_info] = before[i + 1]
|
||||
i += 2
|
||||
else
|
||||
i += 1
|
||||
end
|
||||
when "--session"
|
||||
if i + 1 < before.size
|
||||
args[:snapshot_session] = before[i + 1]
|
||||
i += 2
|
||||
else
|
||||
i += 1
|
||||
end
|
||||
when "--service"
|
||||
if i + 1 < before.size
|
||||
args[:snapshot_service] = before[i + 1]
|
||||
i += 2
|
||||
else
|
||||
i += 1
|
||||
end
|
||||
when "--restore"
|
||||
if i + 1 < before.size
|
||||
args[:snapshot_restore] = before[i + 1]
|
||||
i += 2
|
||||
else
|
||||
i += 1
|
||||
end
|
||||
when "--delete"
|
||||
if i + 1 < before.size
|
||||
args[:snapshot_delete] = before[i + 1]
|
||||
i += 2
|
||||
else
|
||||
i += 1
|
||||
end
|
||||
when "--lock"
|
||||
if i + 1 < before.size
|
||||
args[:snapshot_lock] = before[i + 1]
|
||||
i += 2
|
||||
else
|
||||
i += 1
|
||||
end
|
||||
when "--unlock"
|
||||
if i + 1 < before.size
|
||||
args[:snapshot_unlock] = before[i + 1]
|
||||
i += 2
|
||||
else
|
||||
i += 1
|
||||
end
|
||||
when "--clone"
|
||||
if i + 1 < before.size
|
||||
args[:snapshot_clone] = before[i + 1]
|
||||
i += 2
|
||||
else
|
||||
i += 1
|
||||
end
|
||||
when "--clone-type"
|
||||
if i + 1 < before.size
|
||||
args[:snapshot_clone_type] = before[i + 1]
|
||||
i += 2
|
||||
else
|
||||
i += 1
|
||||
end
|
||||
when "--name"
|
||||
if i + 1 < before.size
|
||||
args[:snapshot_name] = before[i + 1]
|
||||
i += 2
|
||||
else
|
||||
i += 1
|
||||
end
|
||||
when "--hot"
|
||||
args[:snapshot_hot] = true
|
||||
i += 1
|
||||
when "--ports"
|
||||
if i + 1 < before.size
|
||||
args[:snapshot_ports] = before[i + 1]
|
||||
i += 2
|
||||
else
|
||||
i += 1
|
||||
end
|
||||
else
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
when "logs"
|
||||
args[:command] = "logs"
|
||||
# Parse logs subcommand options
|
||||
i = 1
|
||||
while i < before.size
|
||||
case before[i]
|
||||
when "--source"
|
||||
if i + 1 < before.size
|
||||
args[:logs_source] = before[i + 1]
|
||||
i += 2
|
||||
else
|
||||
i += 1
|
||||
end
|
||||
when "--lines"
|
||||
if i + 1 < before.size
|
||||
args[:logs_lines] = before[i + 1].to_i
|
||||
i += 2
|
||||
else
|
||||
i += 1
|
||||
end
|
||||
when "--since"
|
||||
if i + 1 < before.size
|
||||
args[:logs_since] = before[i + 1]
|
||||
i += 2
|
||||
else
|
||||
i += 1
|
||||
end
|
||||
when "--grep"
|
||||
if i + 1 < before.size
|
||||
args[:logs_grep] = before[i + 1]
|
||||
i += 2
|
||||
else
|
||||
i += 1
|
||||
end
|
||||
when "--follow", "-f"
|
||||
args[:logs_follow] = true
|
||||
i += 1
|
||||
else
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
when "health"
|
||||
args[:command] = "health"
|
||||
when "version"
|
||||
args[:command] = "version"
|
||||
when "image"
|
||||
args[:command] = "image"
|
||||
# Parse image subcommand options
|
||||
|
|
@ -1272,6 +1936,48 @@ def main
|
|||
else
|
||||
i += 1
|
||||
end
|
||||
when "--grant"
|
||||
if i + 1 < before.size
|
||||
args[:image_grant] = before[i + 1]
|
||||
i += 2
|
||||
else
|
||||
i += 1
|
||||
end
|
||||
when "--revoke"
|
||||
if i + 1 < before.size
|
||||
args[:image_revoke] = before[i + 1]
|
||||
i += 2
|
||||
else
|
||||
i += 1
|
||||
end
|
||||
when "--trusted"
|
||||
if i + 1 < before.size
|
||||
args[:image_trusted] = before[i + 1]
|
||||
i += 2
|
||||
else
|
||||
i += 1
|
||||
end
|
||||
when "--trusted-key"
|
||||
if i + 1 < before.size
|
||||
args[:image_trusted_key] = before[i + 1]
|
||||
i += 2
|
||||
else
|
||||
i += 1
|
||||
end
|
||||
when "--transfer"
|
||||
if i + 1 < before.size
|
||||
args[:image_transfer] = before[i + 1]
|
||||
i += 2
|
||||
else
|
||||
i += 1
|
||||
end
|
||||
when "--to-key"
|
||||
if i + 1 < before.size
|
||||
args[:image_to_key] = before[i + 1]
|
||||
i += 2
|
||||
else
|
||||
i += 1
|
||||
end
|
||||
else
|
||||
i += 1
|
||||
end
|
||||
|
|
@ -1300,6 +2006,14 @@ def main
|
|||
cmd_languages(args)
|
||||
elsif args[:command] == "image"
|
||||
cmd_image(args)
|
||||
elsif args[:command] == "snapshot"
|
||||
cmd_snapshot(args)
|
||||
elsif args[:command] == "logs"
|
||||
cmd_logs(args)
|
||||
elsif args[:command] == "health"
|
||||
cmd_health(args)
|
||||
elsif args[:command] == "version"
|
||||
cmd_version(args)
|
||||
elsif args[:source_file]
|
||||
cmd_execute(args)
|
||||
else
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue