Update public backend API surface
Private source commit: ba3f7ce2b6d9
This commit is contained in:
parent
784463622c
commit
26fdcb9d84
34 changed files with 5473 additions and 72 deletions
183
crates/clusterflux-client/tests/client_contract.rs
Normal file
183
crates/clusterflux-client/tests/client_contract.rs
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
use std::net::TcpListener;
|
||||
|
||||
use clusterflux_client::{
|
||||
ApiErrorCategory, ApiErrorCode, ArtifactDownloadPoll, ArtifactId, ClientError,
|
||||
ClusterfluxClient, MockTransport, ProjectId, SessionCredential, TenantId, UserId,
|
||||
CLIENT_API_VERSION,
|
||||
};
|
||||
use clusterflux_control::CONTROL_API_PATH;
|
||||
use clusterflux_coordinator::service::CoordinatorService;
|
||||
use serde_json::{json, Value};
|
||||
|
||||
#[tokio::test]
|
||||
async fn mock_transport_exercises_typed_envelope_and_session_plumbing() {
|
||||
let transport = MockTransport::from_json_responses([json!({
|
||||
"type": "projects",
|
||||
"projects": [{
|
||||
"id": "project-one",
|
||||
"tenant": "tenant-one",
|
||||
"name": "Project one"
|
||||
}],
|
||||
"actor": "user-one"
|
||||
})
|
||||
.to_string()]);
|
||||
let client = ClusterfluxClient::with_transport(transport.clone())
|
||||
.with_session_credential(&SessionCredential::from_secret("test-session-secret"));
|
||||
|
||||
let projects = client.list_projects().await.unwrap();
|
||||
assert_eq!(projects.len(), 1);
|
||||
assert_eq!(projects[0].id, ProjectId::from("project-one"));
|
||||
|
||||
let requests = transport.requests();
|
||||
assert_eq!(requests.len(), 1);
|
||||
assert_eq!(requests[0].api_path, CONTROL_API_PATH);
|
||||
let envelope: Value = serde_json::from_slice(&requests[0].body).unwrap();
|
||||
assert_eq!(envelope["protocol_version"], CLIENT_API_VERSION);
|
||||
assert_eq!(envelope["request_id"], "client-1");
|
||||
assert_eq!(envelope["operation"], "authenticated");
|
||||
assert_eq!(envelope["payload"]["type"], "authenticated");
|
||||
assert_eq!(envelope["payload"]["request"]["type"], "list_projects");
|
||||
assert_eq!(envelope["payload"]["session_secret"], "test-session-secret");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn structured_errors_retain_machine_fields_and_originating_request_id() {
|
||||
let transport = MockTransport::from_json_responses([json!({
|
||||
"type": "error",
|
||||
"code": "account_suspended",
|
||||
"category": "authorization",
|
||||
"message": "account access is suspended",
|
||||
"retryable": false,
|
||||
"request_id": "client-1"
|
||||
})
|
||||
.to_string()]);
|
||||
let client = ClusterfluxClient::with_transport(transport)
|
||||
.with_session_credential(&SessionCredential::from_secret("test-session-secret"));
|
||||
|
||||
let ClientError::Api(error) = client.account_status().await.unwrap_err() else {
|
||||
panic!("expected typed API error");
|
||||
};
|
||||
assert_eq!(error.code, ApiErrorCode::AccountSuspended);
|
||||
assert_eq!(error.category, ApiErrorCategory::Authorization);
|
||||
assert_eq!(error.request_id, "client-1");
|
||||
assert!(!error.retryable);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn a_mismatched_error_request_id_is_rejected_as_a_protocol_error() {
|
||||
let transport = MockTransport::from_json_responses([json!({
|
||||
"type": "error",
|
||||
"code": "validation_error",
|
||||
"category": "validation",
|
||||
"message": "bad request",
|
||||
"retryable": false,
|
||||
"request_id": "another-request"
|
||||
})
|
||||
.to_string()]);
|
||||
let client = ClusterfluxClient::with_transport(transport)
|
||||
.with_session_credential(&SessionCredential::from_secret("test-session-secret"));
|
||||
|
||||
let ClientError::Protocol(message) = client.list_projects().await.unwrap_err() else {
|
||||
panic!("expected protocol error");
|
||||
};
|
||||
assert!(message.contains("does not match client-1"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn session_credentials_are_redacted_from_debug_output() {
|
||||
let credential = SessionCredential::from_secret("must-not-appear");
|
||||
assert_eq!(format!("{credential:?}"), "SessionCredential([REDACTED])");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn artifact_download_is_a_typed_bounded_chunk_stream() {
|
||||
let link = json!({
|
||||
"artifact": "artifact-one",
|
||||
"artifact_digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
"artifact_size_bytes": 5,
|
||||
"source": { "RetainedNode": "node-one" },
|
||||
"url_path": "/artifacts/tenant-one/project-one/process-one/artifact-one",
|
||||
"scoped_token_digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
|
||||
"expires_at_epoch_seconds": 1000,
|
||||
"tenant": "tenant-one",
|
||||
"project": "project-one",
|
||||
"process": "process-one",
|
||||
"actor": { "User": "user-one" },
|
||||
"max_bytes": 1024,
|
||||
"policy_context_digest": "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
|
||||
});
|
||||
let transport = MockTransport::from_json_responses([
|
||||
json!({ "type": "artifact_download_link", "link": link.clone() }).to_string(),
|
||||
json!({
|
||||
"type": "artifact_download_stream",
|
||||
"link": link.clone(),
|
||||
"streamed_bytes": 0,
|
||||
"charged_download_bytes": 0,
|
||||
"content_bytes_available": false,
|
||||
"content_eof": false
|
||||
})
|
||||
.to_string(),
|
||||
json!({
|
||||
"type": "artifact_download_stream",
|
||||
"link": link,
|
||||
"streamed_bytes": 5,
|
||||
"charged_download_bytes": 5,
|
||||
"content_bytes_available": true,
|
||||
"content_offset": 0,
|
||||
"content_eof": true,
|
||||
"content_base64": "aGVsbG8="
|
||||
})
|
||||
.to_string(),
|
||||
]);
|
||||
let client = ClusterfluxClient::with_transport(transport)
|
||||
.with_session_credential(&SessionCredential::from_secret("test-session-secret"));
|
||||
let mut download = client
|
||||
.begin_artifact_download(ArtifactId::from("artifact-one"), 1024, 60, 64)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
download.next_chunk().await.unwrap(),
|
||||
ArtifactDownloadPoll::Pending
|
||||
);
|
||||
assert_eq!(
|
||||
download.next_chunk().await.unwrap(),
|
||||
ArtifactDownloadPoll::Chunk {
|
||||
offset: 0,
|
||||
bytes: b"hello".to_vec(),
|
||||
eof: true,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn typed_client_runs_against_the_real_strict_control_endpoint() {
|
||||
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
|
||||
let address = listener.local_addr().unwrap();
|
||||
let server = std::thread::spawn(move || {
|
||||
let mut service = CoordinatorService::new(7);
|
||||
service
|
||||
.issue_cli_session(
|
||||
TenantId::from("tenant-one"),
|
||||
ProjectId::from("project-one"),
|
||||
UserId::from("user-one"),
|
||||
"real-endpoint-session",
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
let (stream, _) = listener.accept().unwrap();
|
||||
service.handle_stream(stream).unwrap();
|
||||
});
|
||||
|
||||
let client = ClusterfluxClient::connect(format!("clusterflux+tcp://{address}"))
|
||||
.unwrap()
|
||||
.with_session_credential(&SessionCredential::from_secret("real-endpoint-session"));
|
||||
let projects = client.list_projects().await.unwrap();
|
||||
assert_eq!(projects.len(), 1);
|
||||
assert_eq!(projects[0].id, ProjectId::from("project-one"));
|
||||
let status = client.account_status().await.unwrap();
|
||||
assert!(status.authenticated);
|
||||
assert_eq!(status.actor, UserId::from("user-one"));
|
||||
|
||||
drop(client);
|
||||
server.join().unwrap();
|
||||
}
|
||||
194
crates/clusterflux-client/tests/fixtures/web_operations.json
vendored
Normal file
194
crates/clusterflux-client/tests/fixtures/web_operations.json
vendored
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
[
|
||||
{
|
||||
"operation": "begin_web_browser_login",
|
||||
"boundary": "login",
|
||||
"request": { "type": "begin_web_browser_login" },
|
||||
"response": { "type": "web_browser_login_started", "transaction_id": "login-transaction", "authorization_url": "https://auth.clusterflux.lesstuff.com/authorize?state=opaque", "expires_at_epoch_seconds": 1000 }
|
||||
},
|
||||
{
|
||||
"operation": "exchange_web_login_handoff",
|
||||
"boundary": "login",
|
||||
"request": { "type": "exchange_web_login_handoff", "transaction_id": "login-transaction", "handoff_code": "one-time-handoff" },
|
||||
"response": { "type": "web_browser_session", "session": { "tenant": "tenant-one", "project": "project-one", "user": "user-one", "session_secret": "server-side-session", "expires_at_epoch_seconds": 2000 } }
|
||||
},
|
||||
{
|
||||
"operation": "auth_status",
|
||||
"boundary": "control",
|
||||
"request": { "type": "auth_status" },
|
||||
"response": { "type": "auth_status", "tenant": "tenant-one", "project": "project-one", "actor": "user-one", "authenticated": true, "account_status": "active", "suspended": false, "disabled": false, "deleted": false, "manual_review": false, "sanitized_reason": null, "next_actions": [] }
|
||||
},
|
||||
{
|
||||
"operation": "revoke_cli_session",
|
||||
"boundary": "control",
|
||||
"request": { "type": "revoke_cli_session" },
|
||||
"response": { "type": "cli_session_revoked" }
|
||||
},
|
||||
{
|
||||
"operation": "create_project",
|
||||
"boundary": "control",
|
||||
"request": { "type": "create_project", "project": "project-new", "name": "New project" },
|
||||
"response": { "type": "project_created", "project": { "id": "project-new", "tenant": "tenant-one", "name": "New project" } }
|
||||
},
|
||||
{
|
||||
"operation": "select_project",
|
||||
"boundary": "control",
|
||||
"request": { "type": "select_project", "project": "project-selected" },
|
||||
"response": { "type": "project_selected", "project": { "id": "project-selected", "tenant": "tenant-one", "name": "Selected project" } }
|
||||
},
|
||||
{
|
||||
"operation": "list_projects",
|
||||
"boundary": "control",
|
||||
"request": { "type": "list_projects" },
|
||||
"response": { "type": "projects", "projects": [{ "id": "project-one", "tenant": "tenant-one", "name": "Project one" }] }
|
||||
},
|
||||
{
|
||||
"operation": "register_agent_public_key",
|
||||
"boundary": "control",
|
||||
"request": { "type": "register_agent_public_key", "agent": "agent-browser", "public_key": "ed25519:test-public-key" },
|
||||
"response": { "type": "agent_public_key", "record": { "tenant": "tenant-one", "project": "project-one", "user": "user-one", "agent": "agent-browser", "public_key": "ed25519:test-public-key", "public_key_fingerprint": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "version": 1, "revoked": false, "scopes": ["project"], "human_account_creation_privilege": false, "browser_interaction_required_each_run": false } }
|
||||
},
|
||||
{
|
||||
"operation": "list_agent_public_keys",
|
||||
"boundary": "control",
|
||||
"request": { "type": "list_agent_public_keys" },
|
||||
"response": { "type": "agent_public_keys", "records": [] }
|
||||
},
|
||||
{
|
||||
"operation": "rotate_agent_public_key",
|
||||
"boundary": "control",
|
||||
"request": { "type": "rotate_agent_public_key", "agent": "agent-browser", "public_key": "ed25519:rotated-public-key" },
|
||||
"response": { "type": "agent_public_key", "record": { "tenant": "tenant-one", "project": "project-one", "user": "user-one", "agent": "agent-browser", "public_key": "ed25519:rotated-public-key", "public_key_fingerprint": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", "version": 2, "revoked": false, "scopes": ["project"], "human_account_creation_privilege": false, "browser_interaction_required_each_run": false } }
|
||||
},
|
||||
{
|
||||
"operation": "revoke_agent_public_key",
|
||||
"boundary": "control",
|
||||
"request": { "type": "revoke_agent_public_key", "agent": "agent-browser" },
|
||||
"response": { "type": "agent_public_key", "record": { "tenant": "tenant-one", "project": "project-one", "user": "user-one", "agent": "agent-browser", "public_key": "ed25519:rotated-public-key", "public_key_fingerprint": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", "version": 3, "revoked": true, "scopes": ["project"], "human_account_creation_privilege": false, "browser_interaction_required_each_run": false } }
|
||||
},
|
||||
{
|
||||
"operation": "create_node_enrollment_grant",
|
||||
"boundary": "control",
|
||||
"request": { "type": "create_node_enrollment_grant", "ttl_seconds": 300 },
|
||||
"response": { "type": "node_enrollment_grant_created", "tenant": "tenant-one", "project": "project-one", "grant": "one-time-grant", "scope": "tenant-one/project-one", "expires_at_epoch_seconds": 1000 }
|
||||
},
|
||||
{
|
||||
"operation": "list_node_summaries",
|
||||
"boundary": "control",
|
||||
"request": { "type": "list_node_summaries", "cursor": null, "limit": 200 },
|
||||
"response": { "type": "node_summaries", "nodes": [], "next_cursor": null }
|
||||
},
|
||||
{
|
||||
"operation": "revoke_node_credential",
|
||||
"boundary": "control",
|
||||
"request": { "type": "revoke_node_credential", "node": "node-one" },
|
||||
"response": { "type": "node_credential_revoked", "node": "node-one", "tenant": "tenant-one", "project": "project-one", "actor": "user-one", "descriptor_removed": true, "queued_assignments_removed": 0 }
|
||||
},
|
||||
{
|
||||
"operation": "list_process_summaries",
|
||||
"boundary": "control",
|
||||
"request": { "type": "list_process_summaries", "cursor": "process:1", "limit": 20 },
|
||||
"response": { "type": "process_summaries", "processes": [], "next_cursor": null }
|
||||
},
|
||||
{
|
||||
"operation": "cancel_process",
|
||||
"boundary": "control",
|
||||
"request": { "type": "cancel_process", "process": "process-one" },
|
||||
"response": { "type": "process_cancellation_requested", "process": "process-one", "cancelled_tasks": [], "affected_nodes": [] }
|
||||
},
|
||||
{
|
||||
"operation": "abort_process",
|
||||
"boundary": "control",
|
||||
"request": { "type": "abort_process", "process": "process-one", "launch_attempt": null },
|
||||
"response": { "type": "process_aborted", "process": "process-one", "aborted_tasks": [], "affected_nodes": [] }
|
||||
},
|
||||
{
|
||||
"operation": "quota_status",
|
||||
"boundary": "control",
|
||||
"request": { "type": "quota_status" },
|
||||
"response": { "type": "quota_status", "tenant": "tenant-one", "project": "project-one", "actor": "user-one", "policy_label": "community tier", "limits": { "limits": { "ApiCall": 10000 } }, "window_seconds": { "ApiCall": 60 }, "usage": { "ApiCall": 12 }, "window_started_epoch_seconds": { "ApiCall": 900 } }
|
||||
},
|
||||
{
|
||||
"operation": "list_task_events",
|
||||
"boundary": "control",
|
||||
"request": { "type": "list_task_events", "process": "process-one" },
|
||||
"response": { "type": "task_events", "events": [] }
|
||||
},
|
||||
{
|
||||
"operation": "list_task_snapshots",
|
||||
"boundary": "control",
|
||||
"request": { "type": "list_task_snapshots", "process": "process-one" },
|
||||
"response": { "type": "task_snapshots", "snapshots": [] }
|
||||
},
|
||||
{
|
||||
"operation": "list_recent_logs",
|
||||
"boundary": "control",
|
||||
"request": { "type": "list_recent_logs", "process": "process-one", "task": "task-one", "after_sequence": 7, "limit": 100 },
|
||||
"response": { "type": "recent_logs", "entries": [{ "sequence": 1, "process": "process-one", "task": "task-one", "stream": "stdout", "text": "building", "server_timestamp_epoch_seconds": 1000, "truncated": false }], "next_sequence": 1, "history_truncated": false }
|
||||
},
|
||||
{
|
||||
"operation": "restart_task",
|
||||
"boundary": "control",
|
||||
"request": { "type": "restart_task", "process": "process-one", "task": "task-one", "replacement_bundle": null },
|
||||
"response": { "type": "task_restart", "process": "process-one", "task": "task-one", "restarted_task_instance": "task-one-retry", "restarted_attempt_id": "attempt-2", "actor": "user-one", "accepted": true, "clean_boundary_available": true, "active_task": false, "completed_event_observed": true, "requires_whole_process_restart": false, "message": "task restart accepted", "audit_event": { "tenant": "tenant-one", "project": "project-one", "process": "process-one", "task": "task-one", "actor": "user-one", "operation": "restart_task", "allowed": true, "reason": "authorized", "charged_debug_read_bytes": 0, "used_debug_read_bytes": 0 } }
|
||||
},
|
||||
{
|
||||
"operation": "resolve_task_failure",
|
||||
"boundary": "control",
|
||||
"request": { "type": "resolve_task_failure", "process": "process-one", "task": "task-one", "resolution": "accept_failure" },
|
||||
"response": { "type": "task_failure_resolved", "process": "process-one", "task": "task-one", "attempt_id": "attempt-1", "resolution": "accept_failure" }
|
||||
},
|
||||
{
|
||||
"operation": "debug_attach",
|
||||
"boundary": "control",
|
||||
"request": { "type": "debug_attach", "process": "process-one" },
|
||||
"response": { "type": "debug_attach", "process": "process-one", "actor": "user-one", "authorization": { "allowed": true, "reason": "authorized" }, "audit_event": { "tenant": "tenant-one", "project": "project-one", "process": "process-one", "task": null, "actor": "user-one", "operation": "debug_attach", "allowed": true, "reason": "authorized", "charged_debug_read_bytes": 0, "used_debug_read_bytes": 0 } }
|
||||
},
|
||||
{
|
||||
"operation": "create_debug_epoch",
|
||||
"boundary": "control",
|
||||
"request": { "type": "create_debug_epoch", "process": "process-one", "stopped_task": "task-one", "reason": "breakpoint" },
|
||||
"response": { "type": "debug_epoch", "process": "process-one", "actor": "user-one", "epoch": 3, "command": "freeze", "affected_tasks": [], "all_stop_requested": true, "audit_event": { "tenant": "tenant-one", "project": "project-one", "process": "process-one", "task": "task-one", "actor": "user-one", "operation": "create_debug_epoch", "allowed": true, "reason": "authorized", "charged_debug_read_bytes": 0, "used_debug_read_bytes": 0 } }
|
||||
},
|
||||
{
|
||||
"operation": "resume_debug_epoch",
|
||||
"boundary": "control",
|
||||
"request": { "type": "resume_debug_epoch", "process": "process-one", "epoch": 3 },
|
||||
"response": { "type": "debug_epoch", "process": "process-one", "actor": "user-one", "epoch": 3, "command": "resume", "affected_tasks": [], "all_stop_requested": false, "audit_event": { "tenant": "tenant-one", "project": "project-one", "process": "process-one", "task": null, "actor": "user-one", "operation": "resume_debug_epoch", "allowed": true, "reason": "authorized", "charged_debug_read_bytes": 0, "used_debug_read_bytes": 0 } }
|
||||
},
|
||||
{
|
||||
"operation": "inspect_debug_epoch",
|
||||
"boundary": "control",
|
||||
"request": { "type": "inspect_debug_epoch", "process": "process-one", "epoch": 3 },
|
||||
"response": { "type": "debug_epoch_status", "process": "process-one", "actor": "user-one", "epoch": 3, "command": "freeze", "expected_tasks": [], "acknowledgements": [], "fully_frozen": false, "partially_frozen": true, "fully_resumed": false, "failed": false, "failure_messages": [], "audit_event": { "tenant": "tenant-one", "project": "project-one", "process": "process-one", "task": null, "actor": "user-one", "operation": "inspect_debug_epoch", "allowed": true, "reason": "authorized", "charged_debug_read_bytes": 0, "used_debug_read_bytes": 0 } }
|
||||
},
|
||||
{
|
||||
"operation": "list_artifacts",
|
||||
"boundary": "control",
|
||||
"request": { "type": "list_artifacts", "process": "process-one", "cursor": "artifact:1", "limit": 50 },
|
||||
"response": { "type": "artifacts", "artifacts": [], "next_cursor": null }
|
||||
},
|
||||
{
|
||||
"operation": "get_artifact",
|
||||
"boundary": "control",
|
||||
"request": { "type": "get_artifact", "artifact": "artifact-one" },
|
||||
"response": { "type": "artifact", "artifact": { "id": "artifact-one", "display_path": "/out/artifact-one", "display_name": "artifact-one", "process": "process-one", "producer_task": "task-one", "safe_node": "node-one", "digest": "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc", "size_bytes": 5, "availability": "available", "downloadable_now": true, "retention_state": "node_retained", "explicit_storage": false, "order_cursor": "artifact:1" } }
|
||||
},
|
||||
{
|
||||
"operation": "create_artifact_download_link",
|
||||
"boundary": "control",
|
||||
"request": { "type": "create_artifact_download_link", "artifact": "artifact-one", "max_bytes": 1048576, "ttl_seconds": 120 },
|
||||
"response": { "type": "artifact_download_link", "link": { "artifact": "artifact-one", "artifact_digest": "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc", "artifact_size_bytes": 5, "source": { "RetainedNode": "node-one" }, "url_path": "/artifacts/tenant-one/project-one/process-one/artifact-one", "scoped_token_digest": "sha256:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd", "expires_at_epoch_seconds": 1100, "tenant": "tenant-one", "project": "project-one", "process": "process-one", "actor": { "User": "user-one" }, "max_bytes": 1048576, "policy_context_digest": "sha256:eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" } }
|
||||
},
|
||||
{
|
||||
"operation": "open_artifact_download_stream",
|
||||
"boundary": "control",
|
||||
"request": { "type": "open_artifact_download_stream", "artifact": "artifact-one", "max_bytes": 1048576, "token_digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "chunk_bytes": 65536 },
|
||||
"response": { "type": "artifact_download_stream", "link": { "artifact": "artifact-one", "artifact_digest": "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc", "artifact_size_bytes": 5, "source": { "RetainedNode": "node-one" }, "url_path": "/artifacts/tenant-one/project-one/process-one/artifact-one", "scoped_token_digest": "sha256:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd", "expires_at_epoch_seconds": 1100, "tenant": "tenant-one", "project": "project-one", "process": "process-one", "actor": { "User": "user-one" }, "max_bytes": 1048576, "policy_context_digest": "sha256:eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" }, "streamed_bytes": 5, "charged_download_bytes": 5, "content_bytes_available": true, "content_offset": 0, "content_eof": true, "content_base64": "aGVsbG8=" }
|
||||
},
|
||||
{
|
||||
"operation": "revoke_artifact_download_link",
|
||||
"boundary": "control",
|
||||
"request": { "type": "revoke_artifact_download_link", "artifact": "artifact-one", "token_digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" },
|
||||
"response": { "type": "artifact_download_link_revoked" }
|
||||
}
|
||||
]
|
||||
Loading…
Add table
Add a link
Reference in a new issue