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
|
|
@ -922,6 +922,597 @@ def languages(Map options = [:]) {
|
|||
return result
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Utility Functions
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Get SDK version string.
|
||||
*/
|
||||
def version() {
|
||||
return "4.2.0"
|
||||
}
|
||||
|
||||
/**
|
||||
* Check API health status.
|
||||
*/
|
||||
def healthCheck() {
|
||||
try {
|
||||
def url = new URL("${API_BASE}/health")
|
||||
def connection = url.openConnection() as java.net.HttpURLConnection
|
||||
connection.requestMethod = "GET"
|
||||
connection.connectTimeout = 5000
|
||||
connection.readTimeout = 5000
|
||||
return connection.responseCode == 200
|
||||
} catch (Exception e) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate HMAC-SHA256 signature.
|
||||
*/
|
||||
def hmacSign(String secretKey, String message) {
|
||||
def mac = Mac.getInstance("HmacSHA256")
|
||||
mac.init(new SecretKeySpec(secretKey.getBytes("UTF-8"), "HmacSHA256"))
|
||||
return mac.doFinal(message.getBytes("UTF-8")).encodeHex().toString()
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Session Functions
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* List all sessions.
|
||||
*/
|
||||
def sessionList(Map options = [:]) {
|
||||
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
|
||||
def result = apiRequest('/sessions', 'GET', null, publicKey, secretKey)
|
||||
return result.sessions ?: []
|
||||
}
|
||||
|
||||
/**
|
||||
* Get session details.
|
||||
*/
|
||||
def sessionGet(String sessionId, Map options = [:]) {
|
||||
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
|
||||
return apiRequest("/sessions/${sessionId}", 'GET', null, publicKey, secretKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new session.
|
||||
*/
|
||||
def sessionCreate(Map options = [:]) {
|
||||
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
|
||||
def payload = [
|
||||
network_mode: options.networkMode ?: 'zerotrust',
|
||||
shell: options.shell ?: 'bash'
|
||||
]
|
||||
if (options.vcpu) payload.vcpu = options.vcpu
|
||||
return apiRequest('/sessions', 'POST', payload, publicKey, secretKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy a session.
|
||||
*/
|
||||
def sessionDestroy(String sessionId, Map options = [:]) {
|
||||
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
|
||||
return apiRequest("/sessions/${sessionId}", 'DELETE', null, publicKey, secretKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Freeze a session.
|
||||
*/
|
||||
def sessionFreeze(String sessionId, Map options = [:]) {
|
||||
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
|
||||
return apiRequest("/sessions/${sessionId}/freeze", 'POST', null, publicKey, secretKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Unfreeze a session.
|
||||
*/
|
||||
def sessionUnfreeze(String sessionId, Map options = [:]) {
|
||||
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
|
||||
return apiRequest("/sessions/${sessionId}/unfreeze", 'POST', null, publicKey, secretKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Boost a session.
|
||||
*/
|
||||
def sessionBoost(String sessionId, int vcpu = 2, Map options = [:]) {
|
||||
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
|
||||
return apiRequest("/sessions/${sessionId}/boost", 'POST', [vcpu: vcpu], publicKey, secretKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Unboost a session.
|
||||
*/
|
||||
def sessionUnboost(String sessionId, Map options = [:]) {
|
||||
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
|
||||
return apiRequest("/sessions/${sessionId}/unboost", 'POST', null, publicKey, secretKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute command in a session.
|
||||
*/
|
||||
def sessionExecute(String sessionId, String command, Map options = [:]) {
|
||||
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
|
||||
return apiRequest("/sessions/${sessionId}/shell", 'POST', [command: command], publicKey, secretKey)
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Service Functions
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* List all services.
|
||||
*/
|
||||
def serviceList(Map options = [:]) {
|
||||
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
|
||||
def result = apiRequest('/services', 'GET', null, publicKey, secretKey)
|
||||
return result.services ?: []
|
||||
}
|
||||
|
||||
/**
|
||||
* Get service details.
|
||||
*/
|
||||
def serviceGet(String serviceId, Map options = [:]) {
|
||||
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
|
||||
return apiRequest("/services/${serviceId}", 'GET', null, publicKey, secretKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new service.
|
||||
*/
|
||||
def serviceCreate(String name, Map options = [:]) {
|
||||
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
|
||||
def payload = [name: name]
|
||||
if (options.ports) payload.ports = options.ports.split(',').collect { it.trim().toInteger() }
|
||||
if (options.domains) payload.domains = options.domains
|
||||
if (options.bootstrap) payload.bootstrap = options.bootstrap
|
||||
if (options.networkMode) payload.network_mode = options.networkMode
|
||||
def result = apiRequest('/services', 'POST', payload, publicKey, secretKey)
|
||||
return result.id
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy a service.
|
||||
*/
|
||||
def serviceDestroy(String serviceId, Map options = [:]) {
|
||||
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
|
||||
return executeDestructive("/services/${serviceId}", 'DELETE', null, publicKey, secretKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Freeze a service.
|
||||
*/
|
||||
def serviceFreeze(String serviceId, Map options = [:]) {
|
||||
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
|
||||
return apiRequest("/services/${serviceId}/freeze", 'POST', null, publicKey, secretKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Unfreeze a service.
|
||||
*/
|
||||
def serviceUnfreeze(String serviceId, Map options = [:]) {
|
||||
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
|
||||
return apiRequest("/services/${serviceId}/unfreeze", 'POST', null, publicKey, secretKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Lock a service.
|
||||
*/
|
||||
def serviceLock(String serviceId, Map options = [:]) {
|
||||
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
|
||||
return apiRequest("/services/${serviceId}/lock", 'POST', null, publicKey, secretKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlock a service.
|
||||
*/
|
||||
def serviceUnlock(String serviceId, Map options = [:]) {
|
||||
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
|
||||
return executeDestructive("/services/${serviceId}/unlock", 'POST', null, publicKey, secretKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Set unfreeze on demand for a service.
|
||||
*/
|
||||
def serviceSetUnfreezeOnDemand(String serviceId, boolean enabled, Map options = [:]) {
|
||||
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
|
||||
return apiRequestPatch("/services/${serviceId}", [unfreeze_on_demand: enabled], publicKey, secretKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Redeploy a service.
|
||||
*/
|
||||
def serviceRedeploy(String serviceId, String bootstrap = null, Map options = [:]) {
|
||||
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
|
||||
def payload = bootstrap ? [bootstrap: bootstrap] : [:]
|
||||
return apiRequest("/services/${serviceId}/redeploy", 'POST', payload, publicKey, secretKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get service logs.
|
||||
*/
|
||||
def serviceLogs(String serviceId, boolean allLogs = false, Map options = [:]) {
|
||||
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
|
||||
def path = allLogs ? "/services/${serviceId}/logs?all=true" : "/services/${serviceId}/logs"
|
||||
def result = apiRequest(path, 'GET', null, publicKey, secretKey)
|
||||
return result.logs
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute command in a service.
|
||||
*/
|
||||
def serviceExecute(String serviceId, String command, int timeoutMs = 0, Map options = [:]) {
|
||||
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
|
||||
def payload = [command: command]
|
||||
if (timeoutMs > 0) payload.timeout = timeoutMs
|
||||
return apiRequest("/services/${serviceId}/execute", 'POST', payload, publicKey, secretKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get service environment vault status.
|
||||
*/
|
||||
def serviceEnvGet(String serviceId, Map options = [:]) {
|
||||
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
|
||||
return apiRequest("/services/${serviceId}/env", 'GET', null, publicKey, secretKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Set service environment vault.
|
||||
*/
|
||||
def serviceEnvSet(String serviceId, String envContent, Map options = [:]) {
|
||||
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
|
||||
return apiRequestText("/services/${serviceId}/env", 'PUT', envContent, publicKey, secretKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete service environment vault.
|
||||
*/
|
||||
def serviceEnvDelete(String serviceId, Map options = [:]) {
|
||||
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
|
||||
return apiRequest("/services/${serviceId}/env", 'DELETE', null, publicKey, secretKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Export service environment vault.
|
||||
*/
|
||||
def serviceEnvExport(String serviceId, Map options = [:]) {
|
||||
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
|
||||
return apiRequest("/services/${serviceId}/env/export", 'POST', [:], publicKey, secretKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Resize a service.
|
||||
*/
|
||||
def serviceResize(String serviceId, int vcpu, Map options = [:]) {
|
||||
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
|
||||
return apiRequestPatch("/services/${serviceId}", [vcpu: vcpu], publicKey, secretKey)
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Snapshot Functions
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* List all snapshots.
|
||||
*/
|
||||
def snapshotList(Map options = [:]) {
|
||||
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
|
||||
def result = apiRequest('/snapshots', 'GET', null, publicKey, secretKey)
|
||||
return result.snapshots ?: []
|
||||
}
|
||||
|
||||
/**
|
||||
* Get snapshot details.
|
||||
*/
|
||||
def snapshotGet(String snapshotId, Map options = [:]) {
|
||||
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
|
||||
return apiRequest("/snapshots/${snapshotId}", 'GET', null, publicKey, secretKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create snapshot from session.
|
||||
*/
|
||||
def snapshotSession(String sessionId, String name = null, boolean hot = false, Map options = [:]) {
|
||||
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
|
||||
def payload = [session_id: sessionId, hot: hot]
|
||||
if (name) payload.name = name
|
||||
def result = apiRequest('/snapshots', 'POST', payload, publicKey, secretKey)
|
||||
return result.snapshot_id
|
||||
}
|
||||
|
||||
/**
|
||||
* Create snapshot from service.
|
||||
*/
|
||||
def snapshotService(String serviceId, String name = null, boolean hot = false, Map options = [:]) {
|
||||
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
|
||||
def payload = [service_id: serviceId, hot: hot]
|
||||
if (name) payload.name = name
|
||||
def result = apiRequest('/snapshots', 'POST', payload, publicKey, secretKey)
|
||||
return result.snapshot_id
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore a snapshot.
|
||||
*/
|
||||
def snapshotRestore(String snapshotId, Map options = [:]) {
|
||||
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
|
||||
return apiRequest("/snapshots/${snapshotId}/restore", 'POST', [:], publicKey, secretKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a snapshot.
|
||||
*/
|
||||
def snapshotDelete(String snapshotId, Map options = [:]) {
|
||||
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
|
||||
return executeDestructive("/snapshots/${snapshotId}", 'DELETE', null, publicKey, secretKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Lock a snapshot.
|
||||
*/
|
||||
def snapshotLock(String snapshotId, Map options = [:]) {
|
||||
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
|
||||
return apiRequest("/snapshots/${snapshotId}/lock", 'POST', null, publicKey, secretKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlock a snapshot.
|
||||
*/
|
||||
def snapshotUnlock(String snapshotId, Map options = [:]) {
|
||||
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
|
||||
return executeDestructive("/snapshots/${snapshotId}/unlock", 'POST', null, publicKey, secretKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone a snapshot.
|
||||
*/
|
||||
def snapshotClone(String snapshotId, String cloneType, String name = null, String ports = null, String shell = null, Map options = [:]) {
|
||||
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
|
||||
def payload = [type: cloneType]
|
||||
if (name) payload.name = name
|
||||
if (ports) payload.ports = ports.split(',').collect { it.trim().toInteger() }
|
||||
if (shell) payload.shell = shell
|
||||
def result = apiRequest("/snapshots/${snapshotId}/clone", 'POST', payload, publicKey, secretKey)
|
||||
return result.session_id ?: result.service_id
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Image Functions
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* List all images.
|
||||
*/
|
||||
def imageList(String filter = null, Map options = [:]) {
|
||||
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
|
||||
def path = filter ? "/images/${filter}" : '/images'
|
||||
def result = apiRequest(path, 'GET', null, publicKey, secretKey)
|
||||
return result.images ?: []
|
||||
}
|
||||
|
||||
/**
|
||||
* Get image details.
|
||||
*/
|
||||
def imageGet(String imageId, Map options = [:]) {
|
||||
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
|
||||
return apiRequest("/images/${imageId}", 'GET', null, publicKey, secretKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Publish an image.
|
||||
*/
|
||||
def imagePublish(String sourceType, String sourceId, String name = null, String description = null, Map options = [:]) {
|
||||
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
|
||||
def payload = [source_type: sourceType, source_id: sourceId]
|
||||
if (name) payload.name = name
|
||||
if (description) payload.description = description
|
||||
def result = apiRequest('/images', 'POST', payload, publicKey, secretKey)
|
||||
return result.image_id
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an image.
|
||||
*/
|
||||
def imageDelete(String imageId, Map options = [:]) {
|
||||
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
|
||||
return executeDestructive("/images/${imageId}", 'DELETE', null, publicKey, secretKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Lock an image.
|
||||
*/
|
||||
def imageLock(String imageId, Map options = [:]) {
|
||||
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
|
||||
return apiRequest("/images/${imageId}/lock", 'POST', null, publicKey, secretKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlock an image.
|
||||
*/
|
||||
def imageUnlock(String imageId, Map options = [:]) {
|
||||
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
|
||||
return executeDestructive("/images/${imageId}/unlock", 'POST', null, publicKey, secretKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Set image visibility.
|
||||
*/
|
||||
def imageSetVisibility(String imageId, String visibility, Map options = [:]) {
|
||||
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
|
||||
return apiRequest("/images/${imageId}/visibility", 'POST', [visibility: visibility], publicKey, secretKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Grant access to an image.
|
||||
*/
|
||||
def imageGrantAccess(String imageId, String trustedApiKey, Map options = [:]) {
|
||||
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
|
||||
return apiRequest("/images/${imageId}/grant", 'POST', [trusted_api_key: trustedApiKey], publicKey, secretKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Revoke access to an image.
|
||||
*/
|
||||
def imageRevokeAccess(String imageId, String trustedApiKey, Map options = [:]) {
|
||||
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
|
||||
return apiRequest("/images/${imageId}/revoke", 'POST', [trusted_api_key: trustedApiKey], publicKey, secretKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* List trusted keys for an image.
|
||||
*/
|
||||
def imageListTrusted(String imageId, Map options = [:]) {
|
||||
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
|
||||
def result = apiRequest("/images/${imageId}/trusted", 'GET', null, publicKey, secretKey)
|
||||
return result.trusted ?: []
|
||||
}
|
||||
|
||||
/**
|
||||
* Transfer image ownership.
|
||||
*/
|
||||
def imageTransfer(String imageId, String toApiKey, Map options = [:]) {
|
||||
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
|
||||
return apiRequest("/images/${imageId}/transfer", 'POST', [to_api_key: toApiKey], publicKey, secretKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Spawn a service from an image.
|
||||
*/
|
||||
def imageSpawn(String imageId, String name = null, String ports = null, String bootstrap = null, String networkMode = null, Map options = [:]) {
|
||||
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
|
||||
def payload = [:]
|
||||
if (name) payload.name = name
|
||||
if (ports) payload.ports = ports.split(',').collect { it.trim().toInteger() }
|
||||
if (bootstrap) payload.bootstrap = bootstrap
|
||||
if (networkMode) payload.network_mode = networkMode
|
||||
def result = apiRequest("/images/${imageId}/spawn", 'POST', payload, publicKey, secretKey)
|
||||
return result.service_id
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone an image.
|
||||
*/
|
||||
def imageClone(String imageId, String name = null, String description = null, Map options = [:]) {
|
||||
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
|
||||
def payload = [:]
|
||||
if (name) payload.name = name
|
||||
if (description) payload.description = description
|
||||
def result = apiRequest("/images/${imageId}/clone", 'POST', payload, publicKey, secretKey)
|
||||
return result.image_id
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// PaaS Logs Functions
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Fetch batch logs.
|
||||
*/
|
||||
def logsFetch(String source = 'all', int lines = 100, String since = null, String grep = null, Map options = [:]) {
|
||||
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
|
||||
def params = ["source=${source}", "lines=${lines}"]
|
||||
if (since) params << "since=${since}"
|
||||
if (grep) params << "grep=${URLEncoder.encode(grep, 'UTF-8')}"
|
||||
return apiRequest("/paas/logs?${params.join('&')}", 'GET', null, publicKey, secretKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback interface for log streaming.
|
||||
*/
|
||||
interface LogCallback {
|
||||
void onLogLine(String source, String line)
|
||||
}
|
||||
|
||||
/**
|
||||
* Stream logs via SSE. Blocks until interrupted or server closes.
|
||||
*
|
||||
* @param source Log source ('all', 'api', 'portal', 'pool/cammy', 'pool/ai')
|
||||
* @param grep Optional filter pattern
|
||||
* @param callback Callback for each log line
|
||||
* @param options Optional parameters (publicKey, secretKey)
|
||||
* @return true on clean shutdown, false on error
|
||||
*/
|
||||
def logsStream(String source = 'all', String grep = null, LogCallback callback, Map options = [:]) {
|
||||
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
|
||||
|
||||
def path = "/paas/logs/stream?source=${source ?: 'all'}"
|
||||
if (grep) {
|
||||
path += "&grep=${URLEncoder.encode(grep, 'UTF-8')}"
|
||||
}
|
||||
|
||||
def timestamp = (System.currentTimeMillis() / 1000) as long
|
||||
def signature = signRequest(secretKey, timestamp, 'GET', path, '')
|
||||
|
||||
def url = new URL("${API_BASE}${path}")
|
||||
def connection = url.openConnection() as java.net.HttpURLConnection
|
||||
|
||||
connection.requestMethod = 'GET'
|
||||
connection.setRequestProperty('Authorization', "Bearer ${publicKey}")
|
||||
connection.setRequestProperty('X-Timestamp', timestamp.toString())
|
||||
connection.setRequestProperty('X-Signature', signature)
|
||||
connection.setRequestProperty('Accept', 'text/event-stream')
|
||||
connection.connectTimeout = 30000
|
||||
connection.readTimeout = 0 // No timeout for streaming
|
||||
|
||||
if (connection.responseCode != 200) {
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
def reader = new BufferedReader(new InputStreamReader(connection.inputStream, 'UTF-8'))
|
||||
def currentSource = source ?: 'all'
|
||||
def line
|
||||
|
||||
while ((line = reader.readLine()) != null) {
|
||||
if (line.startsWith('data: ')) {
|
||||
def data = line.substring(6)
|
||||
if (callback) {
|
||||
callback.onLogLine(currentSource, data)
|
||||
}
|
||||
} else if (line.startsWith('event: ')) {
|
||||
currentSource = line.substring(7)
|
||||
}
|
||||
}
|
||||
return true
|
||||
} catch (Exception e) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate API keys.
|
||||
*/
|
||||
def validateKeys(Map options = [:]) {
|
||||
def (publicKey, secretKey) = getCredentials(options.publicKey, options.secretKey)
|
||||
|
||||
def timestamp = (System.currentTimeMillis() / 1000) as long
|
||||
def message = "${timestamp}:POST:/keys/validate:{}"
|
||||
def signature = signRequest(secretKey, timestamp, 'POST', '/keys/validate', '{}')
|
||||
|
||||
def url = new URL("${PORTAL_BASE}/keys/validate")
|
||||
def connection = url.openConnection() as java.net.HttpURLConnection
|
||||
|
||||
connection.requestMethod = 'POST'
|
||||
connection.setRequestProperty('Authorization', "Bearer ${publicKey}")
|
||||
connection.setRequestProperty('X-Timestamp', timestamp.toString())
|
||||
connection.setRequestProperty('X-Signature', signature)
|
||||
connection.setRequestProperty('Content-Type', 'application/json')
|
||||
connection.connectTimeout = 30000
|
||||
connection.readTimeout = 30000
|
||||
connection.doOutput = true
|
||||
connection.outputStream.withWriter { it.write('{}') }
|
||||
|
||||
if (connection.responseCode !in 200..299) {
|
||||
throw new APIError("HTTP ${connection.responseCode}")
|
||||
}
|
||||
|
||||
return new JsonSlurper().parseText(connection.inputStream.text)
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect programming language from file extension or shebang.
|
||||
*
|
||||
|
|
|
|||
453
clients/groovy/sync/tests/UnTest.groovy
Normal file
453
clients/groovy/sync/tests/UnTest.groovy
Normal file
|
|
@ -0,0 +1,453 @@
|
|||
#!/usr/bin/env groovy
|
||||
// PUBLIC DOMAIN - NO LICENSE, NO WARRANTY
|
||||
// Unit tests for Un SDK - Groovy Synchronous client
|
||||
|
||||
import groovy.test.GroovyTestCase
|
||||
|
||||
/**
|
||||
* Test suite for the Unsandbox Groovy SDK.
|
||||
*
|
||||
* Run with: groovy UnTest.groovy
|
||||
*
|
||||
* Integration tests require UNSANDBOX_PUBLIC_KEY and UNSANDBOX_SECRET_KEY
|
||||
* environment variables to be set.
|
||||
*/
|
||||
class UnTest extends GroovyTestCase {
|
||||
|
||||
// Load the SDK
|
||||
static {
|
||||
def sdkPath = new File(UnTest.class.protectionDomain.codeSource.location.path).parentFile.parentFile
|
||||
evaluate(new File(sdkPath, 'src/un.groovy'))
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// Language Detection Tests
|
||||
// ========================================================================
|
||||
|
||||
void testDetectPython() {
|
||||
assertEquals("python", detectLanguage("script.py"))
|
||||
assertEquals("python", detectLanguage("path/to/script.py"))
|
||||
}
|
||||
|
||||
void testDetectJavaScript() {
|
||||
assertEquals("javascript", detectLanguage("app.js"))
|
||||
}
|
||||
|
||||
void testDetectTypeScript() {
|
||||
assertEquals("typescript", detectLanguage("app.ts"))
|
||||
}
|
||||
|
||||
void testDetectGo() {
|
||||
assertEquals("go", detectLanguage("main.go"))
|
||||
}
|
||||
|
||||
void testDetectRust() {
|
||||
assertEquals("rust", detectLanguage("lib.rs"))
|
||||
}
|
||||
|
||||
void testDetectJava() {
|
||||
assertEquals("java", detectLanguage("Main.java"))
|
||||
}
|
||||
|
||||
void testDetectKotlin() {
|
||||
assertEquals("kotlin", detectLanguage("Main.kt"))
|
||||
}
|
||||
|
||||
void testDetectGroovy() {
|
||||
assertEquals("groovy", detectLanguage("script.groovy"))
|
||||
}
|
||||
|
||||
void testDetectCpp() {
|
||||
assertEquals("cpp", detectLanguage("main.cpp"))
|
||||
}
|
||||
|
||||
void testDetectC() {
|
||||
assertEquals("c", detectLanguage("main.c"))
|
||||
}
|
||||
|
||||
void testDetectRuby() {
|
||||
assertEquals("ruby", detectLanguage("script.rb"))
|
||||
}
|
||||
|
||||
void testDetectPhp() {
|
||||
assertEquals("php", detectLanguage("index.php"))
|
||||
}
|
||||
|
||||
void testDetectUnknown() {
|
||||
assertNull(detectLanguage("file.unknown"))
|
||||
assertNull(detectLanguage("noextension"))
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// Utility Function Tests
|
||||
// ========================================================================
|
||||
|
||||
void testVersionString() {
|
||||
def ver = version()
|
||||
assertNotNull(ver)
|
||||
assertTrue("Version should be in X.Y.Z format", ver ==~ /\d+\.\d+\.\d+/)
|
||||
}
|
||||
|
||||
void testHmacSignature() {
|
||||
def signature = hmacSign("secret", "message")
|
||||
assertNotNull(signature)
|
||||
assertEquals("HMAC-SHA256 should produce 64 hex chars", 64, signature.length())
|
||||
assertTrue("Signature should be lowercase hex", signature ==~ /[0-9a-f]+/)
|
||||
}
|
||||
|
||||
void testHmacConsistent() {
|
||||
def sig1 = hmacSign("key", "data")
|
||||
def sig2 = hmacSign("key", "data")
|
||||
assertEquals("Same inputs should produce same signature", sig1, sig2)
|
||||
}
|
||||
|
||||
void testHmacDifferent() {
|
||||
def sig1 = hmacSign("key1", "data")
|
||||
def sig2 = hmacSign("key2", "data")
|
||||
assertFalse("Different keys should produce different signatures", sig1 == sig2)
|
||||
}
|
||||
|
||||
void testHmacKnownValue() {
|
||||
// HMAC-SHA256("key", "The quick brown fox jumps over the lazy dog")
|
||||
// Known value from various implementations
|
||||
def signature = hmacSign("key", "The quick brown fox jumps over the lazy dog")
|
||||
assertEquals("f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8", signature)
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// Health Check Tests
|
||||
// ========================================================================
|
||||
|
||||
void testHealthCheckReturnsBoolean() {
|
||||
def healthy = healthCheck()
|
||||
// We just verify it returns a boolean without throwing
|
||||
assertTrue(healthy instanceof Boolean)
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// Exception Tests
|
||||
// ========================================================================
|
||||
|
||||
void testUnsandboxError() {
|
||||
def error = new UnsandboxError("Test message")
|
||||
assertEquals("Test message", error.message)
|
||||
}
|
||||
|
||||
void testAuthenticationError() {
|
||||
def error = new AuthenticationError("Auth failed")
|
||||
assertEquals("Auth failed", error.message)
|
||||
assertTrue(error instanceof UnsandboxError)
|
||||
}
|
||||
|
||||
void testExecutionError() {
|
||||
def error = new ExecutionError("Exec failed", 1, "stderr output")
|
||||
assertEquals("Exec failed", error.message)
|
||||
assertEquals(1, error.exitCode)
|
||||
assertEquals("stderr output", error.stderr)
|
||||
}
|
||||
|
||||
void testAPIError() {
|
||||
def error = new APIError("API failed", 500, '{"error": "internal"}')
|
||||
assertEquals("API failed", error.message)
|
||||
assertEquals(500, error.statusCode)
|
||||
assertEquals('{"error": "internal"}', error.response)
|
||||
}
|
||||
|
||||
void testTimeoutError() {
|
||||
def error = new TimeoutError("Operation timed out")
|
||||
assertEquals("Operation timed out", error.message)
|
||||
assertTrue(error instanceof UnsandboxError)
|
||||
}
|
||||
|
||||
void testSudoChallengeError() {
|
||||
def error = new SudoChallengeError("challenge-123", '{"challenge_id": "challenge-123"}')
|
||||
assertEquals("challenge-123", error.challengeId)
|
||||
assertEquals('{"challenge_id": "challenge-123"}', error.responseBody)
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// Extension Map Tests
|
||||
// ========================================================================
|
||||
|
||||
void testExtensionMapComplete() {
|
||||
// Verify the EXT_MAP has all expected extensions
|
||||
assertNotNull(EXT_MAP['.py'])
|
||||
assertNotNull(EXT_MAP['.js'])
|
||||
assertNotNull(EXT_MAP['.ts'])
|
||||
assertNotNull(EXT_MAP['.go'])
|
||||
assertNotNull(EXT_MAP['.rs'])
|
||||
assertNotNull(EXT_MAP['.java'])
|
||||
assertNotNull(EXT_MAP['.kt'])
|
||||
assertNotNull(EXT_MAP['.groovy'])
|
||||
assertNotNull(EXT_MAP['.rb'])
|
||||
assertNotNull(EXT_MAP['.php'])
|
||||
assertNotNull(EXT_MAP['.c'])
|
||||
assertNotNull(EXT_MAP['.cpp'])
|
||||
assertNotNull(EXT_MAP['.sh'])
|
||||
assertNotNull(EXT_MAP['.lua'])
|
||||
assertNotNull(EXT_MAP['.pl'])
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// Integration Tests (requires credentials)
|
||||
// ========================================================================
|
||||
|
||||
void testExecutePythonCode() {
|
||||
def publicKey = System.getenv("UNSANDBOX_PUBLIC_KEY")
|
||||
def secretKey = System.getenv("UNSANDBOX_SECRET_KEY")
|
||||
|
||||
if (!publicKey || !secretKey) {
|
||||
println "Skipping integration test - credentials not set"
|
||||
return
|
||||
}
|
||||
|
||||
def result = execute("python", 'print("Hello, World!")', [
|
||||
publicKey: publicKey,
|
||||
secretKey: secretKey
|
||||
])
|
||||
|
||||
assertNotNull(result)
|
||||
assertTrue(result.stdout?.contains("Hello, World!") ?: false)
|
||||
}
|
||||
|
||||
void testExecuteJavaScriptCode() {
|
||||
def publicKey = System.getenv("UNSANDBOX_PUBLIC_KEY")
|
||||
def secretKey = System.getenv("UNSANDBOX_SECRET_KEY")
|
||||
|
||||
if (!publicKey || !secretKey) {
|
||||
println "Skipping integration test - credentials not set"
|
||||
return
|
||||
}
|
||||
|
||||
def result = execute("javascript", 'console.log("Hello from JS")', [
|
||||
publicKey: publicKey,
|
||||
secretKey: secretKey
|
||||
])
|
||||
|
||||
assertNotNull(result)
|
||||
assertTrue(result.stdout?.contains("Hello from JS") ?: false)
|
||||
}
|
||||
|
||||
void testGetLanguages() {
|
||||
def publicKey = System.getenv("UNSANDBOX_PUBLIC_KEY")
|
||||
def secretKey = System.getenv("UNSANDBOX_SECRET_KEY")
|
||||
|
||||
if (!publicKey || !secretKey) {
|
||||
println "Skipping integration test - credentials not set"
|
||||
return
|
||||
}
|
||||
|
||||
def result = languages([
|
||||
publicKey: publicKey,
|
||||
secretKey: secretKey,
|
||||
forceRefresh: true
|
||||
])
|
||||
|
||||
assertNotNull(result)
|
||||
assertNotNull(result.languages)
|
||||
assertTrue(result.languages.size() > 0)
|
||||
assertTrue(result.languages.contains("python"))
|
||||
assertTrue(result.languages.contains("javascript"))
|
||||
}
|
||||
|
||||
void testListJobs() {
|
||||
def publicKey = System.getenv("UNSANDBOX_PUBLIC_KEY")
|
||||
def secretKey = System.getenv("UNSANDBOX_SECRET_KEY")
|
||||
|
||||
if (!publicKey || !secretKey) {
|
||||
println "Skipping integration test - credentials not set"
|
||||
return
|
||||
}
|
||||
|
||||
def jobs = listJobs([
|
||||
publicKey: publicKey,
|
||||
secretKey: secretKey
|
||||
])
|
||||
|
||||
assertNotNull(jobs)
|
||||
// Jobs list can be empty if no jobs are running
|
||||
}
|
||||
|
||||
void testValidateKeys() {
|
||||
def publicKey = System.getenv("UNSANDBOX_PUBLIC_KEY")
|
||||
def secretKey = System.getenv("UNSANDBOX_SECRET_KEY")
|
||||
|
||||
if (!publicKey || !secretKey) {
|
||||
println "Skipping integration test - credentials not set"
|
||||
return
|
||||
}
|
||||
|
||||
def result = validateKeys([
|
||||
publicKey: publicKey,
|
||||
secretKey: secretKey
|
||||
])
|
||||
|
||||
assertNotNull(result)
|
||||
}
|
||||
|
||||
void testListSessions() {
|
||||
def publicKey = System.getenv("UNSANDBOX_PUBLIC_KEY")
|
||||
def secretKey = System.getenv("UNSANDBOX_SECRET_KEY")
|
||||
|
||||
if (!publicKey || !secretKey) {
|
||||
println "Skipping integration test - credentials not set"
|
||||
return
|
||||
}
|
||||
|
||||
def sessions = sessionList([
|
||||
publicKey: publicKey,
|
||||
secretKey: secretKey
|
||||
])
|
||||
|
||||
assertNotNull(sessions)
|
||||
}
|
||||
|
||||
void testListServices() {
|
||||
def publicKey = System.getenv("UNSANDBOX_PUBLIC_KEY")
|
||||
def secretKey = System.getenv("UNSANDBOX_SECRET_KEY")
|
||||
|
||||
if (!publicKey || !secretKey) {
|
||||
println "Skipping integration test - credentials not set"
|
||||
return
|
||||
}
|
||||
|
||||
def services = serviceList([
|
||||
publicKey: publicKey,
|
||||
secretKey: secretKey
|
||||
])
|
||||
|
||||
assertNotNull(services)
|
||||
}
|
||||
|
||||
void testListSnapshots() {
|
||||
def publicKey = System.getenv("UNSANDBOX_PUBLIC_KEY")
|
||||
def secretKey = System.getenv("UNSANDBOX_SECRET_KEY")
|
||||
|
||||
if (!publicKey || !secretKey) {
|
||||
println "Skipping integration test - credentials not set"
|
||||
return
|
||||
}
|
||||
|
||||
def snapshots = snapshotList([
|
||||
publicKey: publicKey,
|
||||
secretKey: secretKey
|
||||
])
|
||||
|
||||
assertNotNull(snapshots)
|
||||
}
|
||||
|
||||
void testListImages() {
|
||||
def publicKey = System.getenv("UNSANDBOX_PUBLIC_KEY")
|
||||
def secretKey = System.getenv("UNSANDBOX_SECRET_KEY")
|
||||
|
||||
if (!publicKey || !secretKey) {
|
||||
println "Skipping integration test - credentials not set"
|
||||
return
|
||||
}
|
||||
|
||||
def images = imageList(null, [
|
||||
publicKey: publicKey,
|
||||
secretKey: secretKey
|
||||
])
|
||||
|
||||
assertNotNull(images)
|
||||
}
|
||||
|
||||
void testAsyncExecution() {
|
||||
def publicKey = System.getenv("UNSANDBOX_PUBLIC_KEY")
|
||||
def secretKey = System.getenv("UNSANDBOX_SECRET_KEY")
|
||||
|
||||
if (!publicKey || !secretKey) {
|
||||
println "Skipping integration test - credentials not set"
|
||||
return
|
||||
}
|
||||
|
||||
def job = executeAsync("python", 'print("Async test")', [
|
||||
publicKey: publicKey,
|
||||
secretKey: secretKey
|
||||
])
|
||||
|
||||
assertNotNull(job)
|
||||
assertNotNull(job.job_id)
|
||||
|
||||
// Wait for completion
|
||||
def result = wait(job.job_id, [
|
||||
publicKey: publicKey,
|
||||
secretKey: secretKey,
|
||||
maxPolls: 30
|
||||
])
|
||||
|
||||
assertNotNull(result)
|
||||
assertTrue(result.stdout?.contains("Async test") ?: (result.result?.stdout?.contains("Async test") ?: false))
|
||||
}
|
||||
|
||||
void testLogsFetch() {
|
||||
def publicKey = System.getenv("UNSANDBOX_PUBLIC_KEY")
|
||||
def secretKey = System.getenv("UNSANDBOX_SECRET_KEY")
|
||||
|
||||
if (!publicKey || !secretKey) {
|
||||
println "Skipping integration test - credentials not set"
|
||||
return
|
||||
}
|
||||
|
||||
def result = logsFetch('all', 10, null, null, [
|
||||
publicKey: publicKey,
|
||||
secretKey: secretKey
|
||||
])
|
||||
|
||||
assertNotNull(result)
|
||||
}
|
||||
|
||||
void testLogCallbackInterface() {
|
||||
// Verify LogCallback interface exists and can be implemented
|
||||
def callback = { source, line ->
|
||||
assertNotNull(source)
|
||||
assertNotNull(line)
|
||||
} as LogCallback
|
||||
|
||||
assertNotNull(callback)
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// Run all tests
|
||||
// ========================================================================
|
||||
|
||||
static void main(String[] args) {
|
||||
println "Running Unsandbox Groovy SDK Tests..."
|
||||
println "=" * 60
|
||||
|
||||
def test = new UnTest()
|
||||
def methods = UnTest.class.declaredMethods.findAll {
|
||||
it.name.startsWith('test') && it.parameterCount == 0
|
||||
}
|
||||
|
||||
int passed = 0
|
||||
int failed = 0
|
||||
int skipped = 0
|
||||
|
||||
methods.each { method ->
|
||||
print "Testing ${method.name}... "
|
||||
try {
|
||||
method.invoke(test)
|
||||
println "PASS"
|
||||
passed++
|
||||
} catch (Exception e) {
|
||||
def cause = e.cause ?: e
|
||||
if (cause.message?.contains("Skipping")) {
|
||||
println "SKIP"
|
||||
skipped++
|
||||
} else {
|
||||
println "FAIL: ${cause.message}"
|
||||
failed++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println "=" * 60
|
||||
println "Results: ${passed} passed, ${failed} failed, ${skipped} skipped"
|
||||
|
||||
if (failed > 0) {
|
||||
System.exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue