Files
substrate/pkg/proto/ateapipb/ateapi.proto
T
shrutiyam-glitch 704eeb4376 validation: resolve validation todo in ateapi.proto (#1600)
Fixes a few todos in the `ateapi.proto`

* Adds custom validation for `create_time` and `update_time` fields.
* Adds better validation method for the `Container.image` field.

- [ ] Tests pass
- [ ] Appropriate changes to documentation are included in the PR
2026-09-14 11:34:48 -04:00

2017 lines
65 KiB
Protocol Buffer

// Copyright 2026 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
syntax = "proto3";
package ateapi;
import "google/protobuf/empty.proto";
import "google/protobuf/timestamp.proto";
option go_package = "github.com/agent-substrate/substrate/pkg/proto/ateapipb";
// Control is the primary RPC interface for Agentic Substrate.
service Control {
// Get an Actor.
rpc GetActor(GetActorRequest) returns (Actor) {}
// Create a new Actor deriving from a given ActorTemplate.
rpc CreateActor(CreateActorRequest) returns (Actor) {}
// Update mutable fields on an existing Actor.
rpc UpdateActor(UpdateActorRequest) returns (Actor) {}
// Suspend a given actor to a new snapshot. A running actor is checkpointed
// on its worker; a paused actor's node-local snapshot is uploaded, narrowed
// to the template's commit scope where required (Full capture, Data commit).
rpc SuspendActor(SuspendActorRequest) returns (SuspendActorResponse) {}
// Pause a given actor and keep its snapshots on node VM.
rpc PauseActor(PauseActorRequest) returns (PauseActorResponse) {}
// Resume an actor from its latest snapshot.
rpc ResumeActor(ResumeActorRequest) returns (ResumeActorResponse) {}
// Delete an actor. Only suspended actors can be deleted.
rpc DeleteActor(DeleteActorRequest) returns (Actor) {}
// Get the egress policy resource nested under an Actor.
rpc GetActorEgressPolicy(GetActorEgressPolicyRequest) returns (EgressPolicy) {}
// Create the egress policy resource nested under an Actor.
rpc CreateActorEgressPolicy(CreateActorEgressPolicyRequest) returns (EgressPolicy) {}
// Replace the egress policy resource nested under an Actor.
rpc UpdateActorEgressPolicy(UpdateActorEgressPolicyRequest) returns (EgressPolicy) {}
// Delete the egress policy resource nested under an Actor.
rpc DeleteActorEgressPolicy(DeleteActorEgressPolicyRequest) returns (EgressPolicy) {}
// Create a Substrate-issued JWT asserting the actor identity.
//
// * Called by the egress gateway when actor JWT injection is configured for outbound requests.
rpc MintActorJWT(MintActorJWTRequest) returns (MintActorJWTResponse) {}
// Create a Substrate-issued SPIFFE certificate asserting the actor identity.
//
// * Called by atelet to provision an atunnel with a certificate for
// communication with the egress gateway. TODO(identity): Migrate this use
// case to a distinct certificate to prevent actor/atunnel confusion.
// * Called by the egress gateway when actor client certificate injection is
// configured for outbound requests.
rpc MintActorCertificate(MintActorCertificateRequest) returns (MintActorCertificateResponse) {}
// Tag the external snapshot a suspended Actor holds. The tag gets its own
// copy of that snapshot, so suspending or deleting the Actor afterwards
// cannot collect it.
rpc CreateTag(CreateTagRequest) returns (Tag) {}
// Get a Tag.
rpc GetTag(GetTagRequest) returns (Tag) {}
// List Tags.
rpc ListTags(ListTagsRequest) returns (ListTagsResponse) {}
// Publish or unpublish a Tag without changing its address.
rpc UpdateTag(UpdateTagRequest) returns (Tag) {}
// Delete a Tag and the external snapshot it owns. Actors created from the
// tag that have not yet been suspended still point at that external snapshot
// and become unrecoverable, so do not delete a tag while such Actors exist.
rpc DeleteTag(DeleteTagRequest) returns (Tag) {}
// List Workers.
rpc ListWorkers(ListWorkersRequest) returns (ListWorkersResponse) {}
// Get a Worker.
rpc GetWorker(GetWorkerRequest) returns (Worker) {}
// Register a Worker. Called once its Pod is Ready and has an IP.
rpc CreateWorker(CreateWorkerRequest) returns (Worker) {}
// Update observed pool state on a Worker.
rpc UpdateWorker(UpdateWorkerRequest) returns (Worker) {}
// Deregister a Worker. Does not cascade: the caller is responsible for
// cleaning up related resources first.
rpc DeleteWorker(DeleteWorkerRequest) returns (Worker) {}
// Mark a Worker as terminating so the scheduler stops routing new Actors to
// it. Idempotent; one-way. Deliberately leaves any bound Actor alone.
// Returns ABORTED if another write lands on the Worker first; retry.
rpc DrainWorker(DrainWorkerRequest) returns (Worker) {}
// List the Actors hosted by a given Worker.
rpc ListWorkerActorAssignments(ListWorkerActorAssignmentsRequest) returns (ListWorkerActorAssignmentsResponse) {}
// List Actors.
rpc ListActors(ListActorsRequest) returns (ListActorsResponse) {}
// Create a new Atespace. Substrate-native, stored in database.
rpc CreateAtespace(CreateAtespaceRequest) returns (Atespace) {}
// Get an Atespace by name.
rpc GetAtespace(GetAtespaceRequest) returns (Atespace) {}
// List Atespaces.
rpc ListAtespaces(ListAtespacesRequest) returns (ListAtespacesResponse) {}
// Delete an empty Atespace. Rejects (FailedPrecondition) if any Actors or
// Tags remain.
rpc DeleteAtespace(DeleteAtespaceRequest) returns (Atespace) {}
rpc CreateActorTemplate(CreateActorTemplateRequest) returns (ActorTemplate) {}
rpc GetActorTemplate(GetActorTemplateRequest) returns (ActorTemplate) {}
rpc ListActorTemplates(ListActorTemplatesRequest) returns (ListActorTemplatesResponse) {}
// Delete an ActorTemplate together with its golden actor and golden
// snapshot in the ActorTemplate's namespace.
rpc DeleteActorTemplate(DeleteActorTemplateRequest) returns (ActorTemplate) {}
}
// ExternalSnapshot addresses a snapshot held in object storage.
message ExternalSnapshot {
// snapshot_uri addresses the snapshot's prefix in object storage.
//
// +k8s:required
string snapshot_uri = 1;
// content_scope is what the snapshot captured.
//
// +k8s:optional
// +k8s:minimum=1
// +k8s:maximum=2 # keep this in sync with the SnapshotContentScope enum
SnapshotContentScope content_scope = 2;
}
message LocalSnapshotInfo {
// The name of the local checkpoint on each of the nodes below. Checkpoint
// names are server-generated UUIDs, but any resource name is valid here.
//
// +k8s:optional
// +k8s:format=k8s-short-name
string snapshot_name = 1;
// Node VMs that have local snapshots for this actor, while it's PAUSED.
// Each node appears at most once.
//
// TODO: revisit this design; a snapshot propagated to every node would
// grow this list with the fleet, and the bound below is provisional.
//
// +k8s:optional
// +k8s:maxItems=256
// +k8s:listType=set
// +k8s:eachVal=+k8s:format=k8s-long-name
repeated string node_vms_with_local_snapshots = 2;
// Scope the pause checkpoint captured (the template's onPause at pause
// time). UNSPECIFIED is tolerated for compatibility and reads as FULL.
//
// +k8s:optional
// +k8s:minimum=1
// +k8s:maximum=2 # keep this in sync with the SnapshotContentScope enum
SnapshotContentScope content_scope = 3;
}
enum SnapshotContentScope {
// Defaults to FULL for compatibility with existing snapshot configuration.
SNAPSHOT_CONTENT_SCOPE_UNSPECIFIED = 0;
// Captures process memory, root filesystem changes, and durable data.
SNAPSHOT_CONTENT_SCOPE_FULL = 1;
// Captures durable data without process memory or root filesystem changes.
SNAPSHOT_CONTENT_SCOPE_DATA = 2;
// Keep this in sync with the maximums on fields of this type.
}
enum TagScope {
// Not set and rejected wherever a client supplies a scope
TAG_SCOPE_UNSPECIFIED = 0;
// May initialize Actors only in the tag's owning Atespace.
TAG_SCOPE_ATESPACE = 1;
// Published for use by Actors in any Atespace. The tag remains addressed
// through its owning Atespace.
TAG_SCOPE_PUBLISHED = 2;
}
// Selector matches worker pools by label.
// Only equality-based matching is supported.
message Selector {
// match_labels selects by exact equality on a set of labels.
//
// +k8s:optional
// +k8s:unionMember
// +k8s:minProperties=1
// +k8s:maxProperties=10
// +k8s:eachKey=+k8s:format=k8s-label-key
// +k8s:eachVal=+k8s:format=k8s-label-value
map<string, string> match_labels = 1;
}
// ResourceMetadata holds the common fields carried by every Substrate resource.
//
// +k8s:customValidation # timestamps must be valid, and update_time must not precede create_time
message ResourceMetadata {
// atespace is the namespace the resource belongs to. Empty for global-scoped
// resources. Caller-specified at creation and immutable thereafter.
//
// Atespaced resources should add `+k8s:subfield(atespace)=+k8s:required`
// Non-atespaced resources should add `+k8s:subfield(atespace)=+k8s:forbidden`
// +k8s:optional
// +k8s:format=k8s-short-name
// +k8s:immutable
string atespace = 1;
// name is the resource's name, unique within its atespace (or globally, for
// global-scoped resources). Caller-specified at creation and immutable thereafter.
//
// +k8s:required
// +k8s:format=k8s-short-name
// +k8s:immutable
string name = 2;
// uid is a server-assigned, globally unique identifier for this resource.
// Immutable throughout the lifecycle of the resource.
//
// This field is ignored during create operations and used as precondition
// for update operations.
//
// +k8s:optional
// +k8s:format=k8s-uuid
// +k8s:immutable
string uid = 3;
// version is increased on every mutation.
//
// This field is ignored during create operations and used as precondition
// for update operations.
//
// +k8s:optional
// +k8s:alpha(since: "0.0")=+k8s:monotonic # TODO: get rid of alpha prefix
// +k8s:minimum=1
// +k8s:update=NoUnset
int64 version = 4;
// create_time is the time the resource was created.
//
// This field is ignored on input.
//
// +k8s:optional
// +k8s:immutable
google.protobuf.Timestamp create_time = 5;
// update_time is the time the resource was last updated.
//
// This field is ignored on input.
//
// +k8s:optional
// +k8s:update=NoUnset
google.protobuf.Timestamp update_time = 6;
}
message ExternalVolume {
// Name of the volume specified in the actor template. Template volume
// names are DNS labels.
//
// +k8s:required
// +k8s:format=k8s-short-name
// +k8s:update=NoModify
// +k8s:update=NoUnset # set-once; immutable would reject the create
// ratchet's nil->set when the final object is validated as an update
string volume_name = 1;
// The globally unique volume_id returned from the storage system.
// This will be initially empty during volume creation. Its format is the
// storage system's own, so it is only bounded and checked for control
// characters (U+0000-U+0008, U+000B, U+000C, U+000E-U+001F, U+007F-U+009F).
// The CSI spec caps IDs at 128 bytes but does not require it, so allow
// some slack. Set once when provisioning completes, then never changed.
//
// +k8s:optional
// +k8s:maxLength=256
// +k8s:update=NoModify
// +k8s:update=NoUnset
// +k8s:customValidation # no control characters
string storage_volume_id = 2;
// Internal volume plugin name or CSI driver name, from the StorageClass
// provisioner. Must be a DNS subdomain, optionally prefixed with
// "substrate.io/" (per the CSI spec's driver-name syntax).
//
// +k8s:required
// +k8s:maxLength=253
// +k8s:update=NoModify
// +k8s:update=NoUnset # set-once, like volume_name above
// +k8s:customValidation
string volume_type = 3;
enum Status {
STATUS_UNSPECIFIED = 0;
// Volume creation pending in the storage system.
STATUS_PENDING = 1;
// Volume successfully created in the storage system.
STATUS_CREATED = 2;
// Volume being deleted from the storage system.
STATUS_DELETING = 3;
// Keep this in sync with ExternalVolume.status's maximum.
}
// +k8s:optional
// +k8s:minimum=1
// +k8s:maximum=3 # keep this in sync with the Status enum
Status status = 4;
// volume_context contains metadata returned by the CSI driver during volume
// provisioning, needed by the node plugin for mounting (e.g. attachment
// info). Keys and values are the driver's own, so they are only bounded,
// not validated.
//
// +k8s:optional
// +k8s:maxProperties=32
// +k8s:eachKey=+k8s:maxLength=128
// +k8s:eachVal=+k8s:maxLength=256
map<string, string> volume_context = 5;
}
message Actor {
// Common resource metadata: atespace, name, uid, version, timestamps.
//
// +k8s:required
// +k8s:subfield(atespace)=+k8s:required
ResourceMetadata metadata = 1;
// TODO: replace with full actor_template spec if we decide to make each Actor self-contained.
// +k8s:required
// +k8s:subfield(atespace)=+k8s:required
// +k8s:mutable
ObjectRef actor_template = 4;
// worker_selector is the per-actor placement constraint. The scheduler
// evaluates the AND of this selector and the template's workerSelector to
// find eligible pools.
// Changes take effect on the next ResumeActor call.
//
// +k8s:optional
Selector worker_selector = 5;
// The tag specified by the caller at CreateActor to seed this Actor from an
// ActorSnapshot. Unset if the Actor was not created from a snapshot. Set
// once at creation and immutable afterward.
//
// +k8s:optional
// +k8s:subfield(atespace)=+k8s:required
// +k8s:immutable
ObjectRef source_tag = 6;
// status is the system-managed state of the Actor. It is updated by the
// server and ignored on input. It is always present in output.
//
// +k8s:optional
ActorStatus status = 7;
}
// EgressPolicy is an egress policy resource nested under an Actor. An Actor has
// at most one egress policy resource, named "default".
//
message EgressPolicy {
// Standard resource metadata. Atespace must match the parent Actor and name
// must be "default". Both are caller-specified on create and immutable.
// UID, version, create_time, and update_time are server-managed.
//
// +k8s:required
// +k8s:subfield(atespace)=+k8s:required
// +k8s:customValidation # name must be "default"
ResourceMetadata metadata = 1;
// Rules are evaluated in order. The first matching rule authorizes the
// request, only that rule's effects are applied, and evaluation stops even if
// later rules would also match. A request is denied when no rule matches.
//
// +k8s:optional
// +k8s:maxItems=256
// +k8s:listType=atomic # rule order matters
repeated EgressRule rules = 2;
}
// EgressRule authorizes a request using exactly one destination matcher.
message EgressRule {
// Matches when the request hostname matches any configured pattern. Effects
// are applied once when the rule matches.
//
// +k8s:optional
// +k8s:unionMember
HostnameRule hostnames = 1;
// Matches when the original destination IP belongs to any configured prefix.
//
// +k8s:optional
// +k8s:unionMember
CIDRRule cidrs = 2;
// Matches every destination.
//
// +k8s:optional
// +k8s:unionMember
google.protobuf.Empty all = 3;
}
// HostnameRule matches requests by destination hostname.
message HostnameRule {
// DNS names, or DNS names with a wildcard "*" in the leftmost label. The
// rule matches when any pattern matches.
//
// The DNS name must conform to RFC 1034 and RFC 1123. It must be lowercase
// and must not have a trailing dot.
//
// A wildcard replaces the complete leftmost label. "*.example.com" matches
// exactly one non-empty label, such as "api.example.com". It does not match
// "example.com" or "nested.api.example.com". No other wildcard syntax is
// accepted. A pattern without a wildcard matches only the complete normalized
// name.
//
// Internationalized names must use their IDNA A-label (punycode) form; no
// Unicode conversion is performed. URLs, IP addresses, names with ports,
// malformed DNS names (for example, "foo..example.com"), and more than one
// trailing dot are invalid.
//
// +k8s:required
// +k8s:maxItems=256
// +k8s:listType=set
// +k8s:customValidation # format
repeated string patterns = 1;
// Effects do not authorize traffic. They are applied only when this is the
// first matching rule.
//
// +k8s:optional
EgressRuleEffects effects = 2;
}
// CIDRRule matches requests by original destination IP address.
message CIDRRule {
// Canonical IPv4 or IPv6 CIDR prefixes. IPv4 uses dotted-decimal notation,
// such as "192.0.2.0/24". IPv6 uses lowercase compressed notation, such as
// "2001:db8::/32". Bits after the prefix length must be zero. The rule
// matches when the original destination IP belongs to any prefix.
//
// +k8s:required
// +k8s:maxItems=256
// +k8s:listType=set
// +k8s:customValidation # format
repeated string cidrs = 1;
}
// EgressRuleEffects contains effects applied by a matching hostname rule.
//
// +k8s:customValidation # for "at least one" and duplicate headers
message EgressRuleEffects {
// Injects values retrieved from credential providers into request headers.
// Header names must be unique case-insensitively.
//
// +k8s:optional
// +k8s:maxItems=16
// +k8s:listType=map
// +k8s:listMapKey=header
// +k8s:customUnique # case-insensitive
// +k8s:customValidation # for duplicate headers
repeated CredentialHeaderInjection inject_static_headers = 1;
}
// CredentialHeaderInjection injects credential-provider material into an HTTP
// request header.
message CredentialHeaderInjection {
// The case-insensitive HTTP request header to inject.
//
// +k8s:required
// +k8s:customValidation # format
string header = 1;
// Prepended verbatim to the credential value. Include any desired separator;
// for example, use "Bearer " (including the space) for Authorization.
//
// +k8s:optional
// +k8s:customValidation # format
string prefix = 2;
// Source-agnostic reference interpreted by a registered credential provider:
// substrate-secret://<provider-class>/<provider-name>/<provider-specific-tail>
//
// +k8s:required
// +k8s:customValidation # format
string credential_uri = 3;
}
enum ActorState {
ACTOR_STATE_UNSPECIFIED = 0;
ACTOR_STATE_RESUMING = 1;
ACTOR_STATE_RUNNING = 2;
ACTOR_STATE_SUSPENDING = 3;
ACTOR_STATE_SUSPENDED = 4;
ACTOR_STATE_PAUSING = 5;
ACTOR_STATE_PAUSED = 6;
ACTOR_STATE_CRASHED = 7;
ACTOR_STATE_DELETING = 8;
// Keep this in sync with ActorStatus.state's maximum.
}
message ActorStatus {
// state is the Actor's current lifecycle state.
//
// +k8s:required
// +k8s:minimum=1
// +k8s:maximum=8 # keep this in sync with the ActorState enum
ActorState state = 1;
// worker_assignment points at the worker currently hosting this Actor.
// Unset whenever the Actor has no worker (SUSPENDED, PAUSED, CRASHED).
//
// +k8s:optional
// +k8s:update=NoModify # can be set and cleared, but not changed in place
WorkerAssignment worker_assignment = 2;
// The name the in-progress durable snapshot will be stored under. Snapshot
// names are server-generated UUIDs, but any resource name is valid here.
//
// +k8s:optional
// +k8s:format=k8s-short-name
string in_progress_snapshot_name = 3;
// external_snapshot is the Actor's current external snapshot.
// If the Actor was created from a Tag this is the tag's snapshot, borrowed
// until the Actor's first suspend writes one of its own. Otherwise it is
// unset until the Actor is first suspended.
// +k8s:optional
ExternalSnapshot external_snapshot = 4;
// Node-local state used only while the Actor is paused.
//
// +k8s:optional
LocalSnapshotInfo local_snapshot_info = 5;
// Volumes attached to the actor. These volumes only live as long as the actor.
// They are deleted when the actor is deleted. Each template volume
// appears at most once.
//
// TODO: consider a literal map keyed by volume_name instead of a list-map.
//
// +k8s:optional
// +k8s:maxItems=32 # matches the template's volumes bound
// +k8s:listType=map
// +k8s:listMapKey=volume_name
repeated ExternalVolume actor_volumes = 7;
// The name of the in-progress node-local checkpoint. Like durable snapshot
// names, these are server-generated UUIDs.
//
// +k8s:optional
// +k8s:format=k8s-short-name
string in_progress_local_snapshot_name = 8;
// The UID of the actor template the Actor's guest state is currently built
// on: the one its latest sprint booted with, recorded when the resume (or
// first boot) commits RUNNING, or at creation for an Actor seeded from a tag,
// since that Actor borrows guest state that was already built. Empty means
// the Actor has no guest state yet.
// Diverges from the UID of the Actor's actor_template ref when a suspended
// Actor is repointed at a replacement template, which forces the next
// restore to data-only.
//
// +k8s:optional
string current_actor_template_uid = 11;
}
// WorkerAssignment points at the Worker currently hosting an Actor.
//
// This is a denormalized snapshot, not merely a reference: atenet reads
// worker_pod_ip on the request-routing path and must not need a second lookup,
// and kubectl-ate displays the pod name. The copies stay valid for the life of
// the assignment because a Worker's identity fields are immutable.
//
// Pass `worker` to GetWorker; none of the denormalized fields identify the
// Worker to the API.
message WorkerAssignment {
// worker references the assigned Worker. This is the field to look the Worker
// up by; the worker_* fields below are a denormalized copy kept so that
// readers on a hot path do not have to fetch the Worker at all.
//
// Workers are global-scoped, so this carries no atespace.
//
// +k8s:beta(since: "0.0")=+k8s:subfield(atespace)=+k8s:forbidden # TODO: get rid of beta prefix
// +k8s:required
ObjectRef worker = 6;
// worker_namespace is the Kubernetes namespace of the WorkerPool and worker Pod.
//
// +k8s:required
// +k8s:format=k8s-short-name
string worker_namespace = 1;
// worker_pool is the name of the WorkerPool the worker belongs to.
//
// +k8s:required
// +k8s:format=k8s-long-name
string worker_pool = 2;
// worker_pod is the name of the Kubernetes pod hosting the Actor.
//
// +k8s:required
// +k8s:format=k8s-long-name
string worker_pod = 3;
// worker_pod_uid is the Kubernetes UID of worker_pod.
//
// +k8s:required
// +k8s:format=k8s-uuid
string worker_pod_uid = 4;
// worker_pod_ip is the IP of worker_pod.
//
// +k8s:required
// +k8s:customValidation # until `format=k8s-ip` is supported
string worker_pod_ip = 5;
}
// TagStatus is the server-owned state of a Tag.
// The tag is fixed to one external snapshot once its creation completes, so
// the rest is immutable provenance.
message TagStatus {
// snapshot is the external snapshot this tag points at. Unset while the tag
// is still being created, and immutable once set: a tag never moves between
// snapshots. A tag is usable only once this is set.
//
// +k8s:optional
ExternalSnapshot snapshot = 1;
// UID of the ActorTemplate the snapshot was taken under. Actors can only be
// seeded from a tag under the same template.
string actor_template_uid = 2;
// storage_location is the base object-storage URI for this tag's snapshot.
// Set by the server from the source actor's template when the tag is created
// and immutable thereafter. The full snapshot URI is available in
// snapshot.snapshot_uri once tag creation completes.
string storage_location = 3;
// source_actor_uid is the UID of the Actor this tag's snapshot was copied
// from.
string source_actor_uid = 4;
}
// Tag is an Atespace-owned, stable name for one external snapshot. The
// external snapshot pointed by a tag is a copy of an actor snapshot. The
// snapshot is copied when the tag is created, and deleted when the tag is. Its
// owning Atespace cannot be deleted until the tag is removed.
message Tag {
// Common resource metadata: atespace, name, uid, version, timestamps.
//
// +k8s:required
// +k8s:subfield(atespace)=+k8s:required
ResourceMetadata metadata = 1;
// status is set by the control plane; it is ignored on write.
//
// +k8s:optional
TagStatus status = 2;
// scope controls who may create an Actor from this tag.
//
// +k8s:required
// +k8s:minimum=1
// +k8s:maximum=2 # keep this in sync with the TagScope enum
TagScope scope = 3;
// source_actor is the suspended Actor whose external snapshot this tag
// captures. The tag lives in this Actor's Atespace. Set once at creation and
// immutable afterward.
//
// +k8s:required
// +k8s:subfield(atespace)=+k8s:required
// +k8s:immutable
ObjectRef source_actor = 4;
}
// Atespace is the isolation boundary an Actor is created into. Global-scoped:
// metadata.atespace is always empty; the atespace's identity is metadata.name.
message Atespace {
// Common resource metadata: name, uid, version, timestamps.
//
// +k8s:required
// +k8s:beta(since: "0.0")=+k8s:subfield(atespace)=+k8s:forbidden # TODO: get rid of beta prefix
ResourceMetadata metadata = 1;
}
// ObjectRef references a Substrate resource by its atespace and name.
message ObjectRef {
// The atespace of the referenced resource. This field should be empty if the
// target resource is global-scoped, but references to objects in the same
// atespace as the referrer must still populate this field.
//
// References to atespaced resources should add `+k8s:subfield(atespace)=+k8s:required`
// References to non-atespaced resources should add `+k8s:subfield(atespace)=+k8s:forbidden`
// +k8s:optional
// +k8s:format=k8s-short-name
string atespace = 1;
// The name of the referenced resource. This field is required.
//
// +k8s:required
// +k8s:format=k8s-short-name
string name = 2;
}
// SandboxClass selects the sandbox runtime family. Snapshots are not portable
// across classes.
enum SandboxClass {
SANDBOX_CLASS_UNSPECIFIED = 0;
SANDBOX_CLASS_GVISOR = 1;
SANDBOX_CLASS_MICROVM = 2;
// Keep this in sync with SandboxConfig.sandbox_class's maximum.
}
message ActorTemplate {
// Common resource metadata: atespace, name, uid, version, timestamps.
//
// +k8s:required
// +k8s:subfield(atespace)=+k8s:required
ResourceMetadata metadata = 1;
// worker_selector restricts which worker pools actors from this template
// may use.
//
// +k8s:optional
Selector worker_selector = 2;
// +k8s:required # at least one container
// +k8s:maxItems=10
// +k8s:listType=map
// +k8s:listMapKey=name
repeated Container containers = 3;
// +k8s:optional
// +k8s:maxItems=32
// +k8s:listType=map
// +k8s:listMapKey=name
repeated Volume volumes = 4;
// +k8s:required
SnapshotsConfig snapshots_config = 5;
// sandbox_config selects the sandbox runtime this version's actors run on.
//
// +k8s:required
SandboxConfig sandbox_config = 6;
// Resource usage configuration.
//
// +k8s:optional
Resources resources = 7;
// +k8s:optional
ActorTemplateStatus status = 8;
}
message Resources {
// limits is the maximum amount of compute resources allowed. Only "cpu"
// and "memory" are supported, each at most once, and each quantity must be
// greater than zero; the cpu limit must be less than 1000 cores.
//
// +k8s:optional
// +k8s:maxItems=2
// +k8s:listType=map
// +k8s:listMapKey=name
// +k8s:customValidation # names, quantity parse/positivity, cpu bound
repeated Limits limits = 1;
}
message Limits {
// +k8s:required
// +k8s:maxLength=16 # the hook on Resources.limits restricts values to cpu/memory
string name = 1;
// quantity is in Kubernetes resource.Quantity string form (e.g. "500m",
// "2Gi").
//
// +k8s:required
// +k8s:maxLength=32 # the hook on Resources.limits requires a parseable quantity
string quantity = 2;
}
message GoldenSnapshotStatus {
// golden_snapshot is the external snapshot built for this version by
// ate-api, taken from the golden Actor in the reserved ate-golden system
// atespace. Set once state is READY. The golden Actor owns it.
//
// +k8s:optional
ExternalSnapshot golden_snapshot = 1;
// take_golden_snapshot_at is when the golden-actor warmup ends and the
// golden snapshot may be taken.
google.protobuf.Timestamp take_golden_snapshot_at = 2;
string error_message = 3;
}
message ActorTemplateStatus {
// +k8s:optional
GoldenSnapshotStatus golden_snapshot_status = 1;
}
// SandboxConfig selects the sandbox runtime for an ActorTemplate.
message SandboxConfig {
// sandbox_class selects the sandbox runtime family.
//
// +k8s:required
// +k8s:minimum=1
// +k8s:maximum=2 # keep this in sync with the SandboxClass enum
SandboxClass sandbox_class = 1;
// config_name names the cluster-scoped SandboxConfig Kubernetes object
// supplying the sandbox binaries. Required; must match sandbox_class.
//
// +k8s:required
// +k8s:format=k8s-long-name
string config_name = 2;
}
// +k8s:customValidation # on_commit must be a subset of on_pause
message SnapshotsConfig {
// on_pause selects what is captured during pause actor. UNSPECIFIED is
// tolerated for compatibility and reads as FULL.
//
// +k8s:optional
// +k8s:minimum=1
// +k8s:maximum=2 # keep this in sync with the SnapshotContentScope enum
SnapshotContentScope on_pause = 1;
// on_commit selects what captures.
// Must be a subset of on_pause: FULL allows FULL or DATA, DATA allows DATA.
//
// +k8s:optional
// +k8s:minimum=1
// +k8s:maximum=2 # keep this in sync with the SnapshotContentScope enum
SnapshotContentScope on_commit = 2;
// on_resume selects, per snapshot situation, what supplies the guest state
// at resume. Unset means the defaults documented on OnResumeConfig.
//
// +k8s:optional
OnResumeConfig on_resume = 3;
// storage_location is the base object-storage URI snapshots of actors on
// this version are stored under. Required.
//
// +k8s:required
// +k8s:maxLength=1024
// +k8s:customValidation # Validate URI
string storage_location = 4;
}
// ResumeSource selects what supplies the guest state when an actor is resumed
// from one of the snapshot situations named by OnResumeConfig's fields.
enum ResumeSource {
RESUME_SOURCE_UNSPECIFIED = 0;
// Starts the actor's containers afresh from the OCI image, with the
// durable-dir volumes pre-populated from the snapshot.
RESUME_SOURCE_COLD_BOOT = 1;
// Restores with the version's golden snapshot and the actor's own
// durable data.
RESUME_SOURCE_GOLDEN = 2;
// Keep this in sync with OnResumeConfig.from_data's maximum.
}
// OnResumeConfig selects, per snapshot situation, what supplies the guest
// state at resume. Each field names what is being resumed FROM; the value
// names the boot source. Full snapshots that are still valid always restore
// from their own content and are not configurable here.
message OnResumeConfig {
// from_data applies when the resume uses a DATA-scope snapshot (from
// on_pause or on_commit). UNSPECIFIED selects the documented default.
//
// +k8s:optional
// +k8s:minimum=1
// +k8s:maximum=2 # keep this in sync with the ResumeSource enum
ResumeSource from_data = 1;
}
// Container is a single application container of an ActorTemplate.
message Container {
// +k8s:required
// +k8s:format=k8s-short-name
string name = 1;
// image is the OCI image reference the container runs:
// [registry/]repository[:tag]@digest. Must be pinned by digest
// (e.g. "name@sha256:...").
//
// +k8s:required
// +k8s:maxLength=512 # matches ImageVolumeSource.reference's bound
// +k8s:customValidation # must be a well-formed image reference, pinned by digest
string image = 2;
// Entrypoint array; when set, the image's ENTRYPOINT and CMD are both
// ignored and the process argv is command + args. Unlike Kubernetes,
// $(VAR_NAME) references are NOT expanded.
//
// +k8s:optional
// +k8s:maxItems=64
// +k8s:listType=atomic
// +k8s:eachVal=+k8s:maxLength=4096 # argv strings; guardrail, not a contract
repeated string command = 3;
// Arguments to the entrypoint; the image's CMD is used if unset (unless
// command is set, which discards the image's CMD).
//
// +k8s:optional
// +k8s:maxItems=64
// +k8s:listType=atomic
// +k8s:eachVal=+k8s:maxLength=4096 # argv strings; guardrail, not a contract
repeated string args = 4;
// Env variables to set in the container's process environment. Unlike
// Kubernetes, $(VAR_NAME) references are NOT expanded in this field.
//
// +k8s:optional
// +k8s:maxItems=32
// +k8s:listType=map # each variable is set at most once
// +k8s:listMapKey=name
repeated EnvVar env = 5;
// readyz is an optional HTTP readiness probe; when set the actor is not
// ready until the endpoint returns 200.
//
// +k8s:optional
ContainerReadyz readyz = 6;
// TODO: Kubernetes permits mounting a single volume at multiple paths
// (which requires keying by mountPath). We restrict it to one mount per
// volume (keyed by name).
//
// +k8s:optional
// +k8s:maxItems=32
// +k8s:listType=map
// +k8s:listMapKey=name
// +k8s:customValidation # mount_path must be unique within the container
repeated VolumeMount volume_mounts = 7;
// security_context adjusts the container's security settings. Unset leaves
// the default capability set.
//
// +k8s:optional
SecurityContext security_context = 8;
// resources carries this container's compute limits, enforced inside the
// actor's sandbox. Only cpu and memory limits are supported.
//
// +k8s:optional
Resources resources = 9;
}
// SecurityContext holds security settings for a container's process,
// modeling a subset of the Kubernetes container securityContext.
message SecurityContext {
// capabilities adjusts the Linux capabilities of the container's process.
//
// +k8s:optional
Capabilities capabilities = 1;
}
// Capabilities adjusts a container's Linux capabilities relative to the
// default set. Drop applies first, then add, so a capability in both is
// granted. Capabilities are named without the "CAP_" prefix; "ALL" in drop
// removes the whole default set.
message Capabilities {
// add lists capabilities to grant on top of the default set. "ALL" is
// rejected: name the individual capabilities the container needs.
//
// +k8s:optional
// +k8s:maxItems=64
// +k8s:listType=set
// +k8s:customValidation # capability grammar; "ALL" not accepted
repeated string add = 1;
// drop lists capabilities to remove from the default set. "ALL" drops the
// whole set, so drop+add expresses an exact set rather than a relative one.
//
// +k8s:optional
// +k8s:maxItems=64
// +k8s:listType=set
// +k8s:customValidation # capability grammar
repeated string drop = 2;
}
// EnvVar supplies one environment variable to a container. Values are not
// expanded with Kubernetes-style $(VAR) references.
message EnvVar {
// name may be any printable ASCII character except '='.
//
// +k8s:required
// +k8s:maxLength=256 # guardrail;
// +k8s:customValidation # printable ASCII except '='
string name = 1;
// value is the literal value.
//
// +k8s:optional
// +k8s:maxLength=32768 # guardrail;
string value = 2;
}
// ContainerReadyz configures the readiness signal for a container.
message ContainerReadyz {
// http_get specifies the HTTP request to perform. Required.
//
// +k8s:required
HTTPGetAction http_get = 1;
// timeout_seconds bounds how long to poll http_get before failing the
// actor start. 0 means the server-applied default (30s).
//
// +k8s:optional
// +k8s:minimum=1
// +k8s:maximum=3600
int32 timeout_seconds = 2;
}
// HTTPGetAction describes an HTTP GET against the container's interior IP.
message HTTPGetAction {
// path defaults to "/readyz". Must be a URL path starting with "/", using
// only RFC 3986 path-segment characters, without query or fragment.
//
// +k8s:optional
// +k8s:maxLength=1024
// +k8s:customValidation # RFC 3986 path shape; no regex/pattern tag exists
string path = 1;
// +k8s:required
// +k8s:minimum=1
// +k8s:maximum=65535
int32 port = 2;
}
message Volume {
// name of the volume. Must be a DNS label.
//
// +k8s:required
// +k8s:format=k8s-short-name
string name = 1;
// Exactly one of durable_dir / external_volume_template / image /
// system_info must be set.
//
// +k8s:optional
// +k8s:unionMember
DurableDirVolumeSource durable_dir = 2;
// +k8s:optional
// +k8s:unionMember
ExternalVolumeTemplate external_volume_template = 3;
// +k8s:optional
// +k8s:unionMember
SystemInfoVolumeSource system_info = 5;
// image mounts the contents of an OCI image, read-only.
//
// +k8s:optional
// +k8s:unionMember
ImageVolumeSource image = 6;
}
// ImageVolumeSource mounts the contents of an OCI image, read-only. The
// reference must include a digest: changing the image invalidates
// snapshots.
message ImageVolumeSource {
// reference is the OCI image reference. Must include a digest
// (e.g. "name@sha256:...").
//
// +k8s:required
// +k8s:maxLength=512
// +k8s:customValidation # must be a well-formed image reference, pinned by digest
string reference = 1;
}
// DurableDirVolumeSource is a durable directory on rootfs that persists
// across resumes and participates in snapshots.
message DurableDirVolumeSource {}
// ExternalVolumeTemplate provisions an external volume per actor; the volume
// lives only as long as the actor. Not supported with SANDBOX_CLASS_MICROVM.
message ExternalVolumeTemplate {
// capacity of the volume to create, in Kubernetes resource.Quantity string
// form (e.g. "10Gi"). Required.
//
// +k8s:required
// +k8s:customValidation # must parse as a resource.Quantity
string capacity = 1;
// storage_class_name names the cluster-scoped Kubernetes StorageClass to
// create the volume from. Required.
//
// +k8s:required
// +k8s:format=k8s-long-name
string storage_class_name = 2;
}
// SystemInfoVolumeSource is a read-only volume of substrate-generated
// per-actor files (identity fields, projected trust bundles), regenerated by
// atelet on every Run/Restore.
message SystemInfoVolumeSource {
// data_sources lists the projections to place within the volume. At most
// one actor_metadata entry may appear, and file paths must be unique
// across all entries.
//
// +k8s:optional
// +k8s:maxItems=8
// +k8s:listType=atomic
// +k8s:customValidation # paths unique across entries
repeated SystemInfoDataSource data_sources = 1;
}
message SystemInfoDataSource {
// Exactly one of actor_metadata / trust_bundle must be set.
//
// +k8s:optional
// +k8s:unionMember
ActorMetadataDataSource actor_metadata = 1;
// +k8s:optional
// +k8s:unionMember
TrustBundleDataSource trust_bundle = 2;
}
// ActorMetadataDataSource projects the actor's identity fields to files, one
// per item.
message ActorMetadataDataSource {
// items must not project the same field twice, and item paths must be
// unique.
//
// +k8s:required
// +k8s:minItems=1
// +k8s:maxItems=8
// +k8s:listType=atomic
// +k8s:unique=map
// +k8s:listMapKey=field
repeated ActorMetadataItem items = 1;
}
// ActorMetadataItem projects one actor identity field to a file at path,
// a clean relative Unix path from the root of the volume.
message ActorMetadataItem {
// +k8s:required
// +k8s:minimum=1
// +k8s:maximum=3 # keep this in sync with the ActorMetadataField enum
ActorMetadataField field = 1;
// path must be a clean relative Unix path: at most 16 '/'-separated
// segments, none of them empty, '.' or '..', and no NUL byte.
//
// +k8s:required
// +k8s:minLength=1
// +k8s:maxLength=255
// +k8s:customValidation # projected-path rule shared with atelet, see internal/volumepath
string path = 2;
}
// ActorMetadataField selects one identity field of the actor.
enum ActorMetadataField {
ACTOR_METADATA_FIELD_UNSPECIFIED = 0;
ACTOR_METADATA_FIELD_NAME = 1;
ACTOR_METADATA_FIELD_ATESPACE = 2;
ACTOR_METADATA_FIELD_UID = 3;
// Keep this in sync with ActorMetadataItem.field's maximum.
}
// TrustBundleDataSource projects the trust anchors of the named trust bundle
// to a PEM file at path. Supported names are allowlisted in atelet.
message TrustBundleDataSource {
// +k8s:required
// +k8s:minLength=1
// +k8s:maxLength=253
string name = 1;
// path must be a clean relative Unix path: at most 16 '/'-separated
// segments, none of them empty, '.' or '..', and no NUL byte.
//
// +k8s:required
// +k8s:minLength=1
// +k8s:maxLength=255
// +k8s:customValidation # projected-path rule shared with atelet, see internal/volumepath
string path = 2;
}
// VolumeMount mounts a named Volume into a container.
message VolumeMount {
// name must match the name of a Volume.
//
// +k8s:required
// +k8s:format=k8s-short-name
string name = 1;
// mount_path within the container. Must be a clean absolute Unix path:
// must start with '/', not be '/', and contain no ':', '..', '.', '//',
// trailing '/', or control characters.
//
// +k8s:required
// +k8s:maxLength=4096
// +k8s:customValidation # clean-absolute-path shape; no regex/pattern tag exists
string mount_path = 2;
}
message CreateAtespaceRequest {
// The atespace to create.
//
// +k8s:required
Atespace atespace = 1;
}
message GetAtespaceRequest {
// +k8s:required
// +k8s:beta(since: "0.0")=+k8s:subfield(atespace)=+k8s:forbidden # TODO: get rid of beta prefix
ObjectRef atespace = 1;
}
message ListAtespacesRequest {
// Requested page size; the server may return fewer, or occasionally
// slightly more. If unspecified, defaults to a server-chosen value;
// values above 1000 are coerced to 1000.
//
// +k8s:optional
// +k8s:minimum=1
int32 page_size = 1;
// Pagination token from a previous ListAtespaces response.
// Omit or leave empty for the first request.
//
// +k8s:optional
// +k8s:maxLength=256
string page_token = 2;
}
message ListAtespacesResponse {
// The page of atespaces. This list may be empty even if there are more results.
repeated Atespace atespaces = 1;
// Pagination token for the next page. Empty if this is the last page.
string next_page_token = 2;
}
message DeleteAtespaceRequest {
// +k8s:required
// +k8s:beta(since: "0.0")=+k8s:subfield(atespace)=+k8s:forbidden # TODO: get rid of beta prefix
ObjectRef atespace = 1;
}
message CreateActorTemplateRequest {
// The actor template version to create. Server-assigned metadata (uid,
// version, timestamps) is ignored, as are the status fields.
//
// +k8s:required
// +k8s:customValidation # volume_mounts must reference declared volumes
ActorTemplate actor_template = 1;
}
message GetActorTemplateRequest {
// +k8s:required
// +k8s:subfield(atespace)=+k8s:required
ObjectRef actor_template = 1;
}
message ListActorTemplatesRequest {
// The atespace to list actor templates from. Empty lists across all
// atespaces.
//
// +k8s:optional
// +k8s:format=k8s-short-name
string atespace = 1;
// Requested page size; the server may return fewer, or occasionally
// slightly more. If unspecified, defaults to a server-chosen value;
// values above 1000 are coerced to 1000.
//
// +k8s:optional
// +k8s:minimum=1
int32 page_size = 2;
// Pagination token from a previous ListActorTemplates response.
// Omit or leave empty for the first request.
//
// +k8s:optional
// +k8s:maxLength=256
string page_token = 3;
}
message ListActorTemplatesResponse {
// The page of actor template versions. This list may be empty even if
// there are more results.
repeated ActorTemplate actor_templates = 1;
// Pagination token for the next page. Empty if this is the last page.
string next_page_token = 2;
}
message DeleteActorTemplateRequest {
// +k8s:required
// +k8s:subfield(atespace)=+k8s:required
ObjectRef actor_template = 1;
}
message GetActorRequest {
// +k8s:required
// +k8s:subfield(atespace)=+k8s:required
ObjectRef actor = 1;
}
// Request to create a new Actor.
message CreateActorRequest {
// The actor to create.
//
// +k8s:required
Actor actor = 1;
}
// Request to update mutable fields on an existing Actor.
// May be called regardless of the actor's current state.
// Changes take effect on the next ResumeActor call.
message UpdateActorRequest {
// The actor to update.
// actor.metadata.atespace and actor.metadata.name identify which resource to
// update.
// actor.metadata.version and actor.metadata.uid are required preconditions.
//
// +k8s:required
// +k8s:opaqueType # updates are handled in 2 steps, do not descend
// +k8s:subfield(metadata)=+k8s:required
// +k8s:customValidation # TODO: when we get nested subfields, require metadata.atespace
Actor actor = 1;
}
message SuspendActorRequest {
// +k8s:required
// +k8s:subfield(atespace)=+k8s:required
ObjectRef actor = 1;
}
message SuspendActorResponse {
Actor actor = 1;
}
message PauseActorRequest {
// +k8s:required
// +k8s:subfield(atespace)=+k8s:required
ObjectRef actor = 1;
}
message PauseActorResponse {
Actor actor = 1;
}
message ResumeActorRequest {
// +k8s:required
// +k8s:subfield(atespace)=+k8s:required
ObjectRef actor = 1;
}
message ResumeActorResponse {
Actor actor = 1;
// True if a resume workflow was executed to activate the actor.
// False if the actor was already RUNNING.
bool resumed = 2;
}
message DeleteActorRequest {
// +k8s:required
// +k8s:subfield(atespace)=+k8s:required
ObjectRef actor = 1;
// If true, delete the actor regardless of its state.
//
// +k8s:optional
bool any_state = 2;
}
// GetActorEgressPolicyRequest identifies an Actor's egress policy.
message GetActorEgressPolicyRequest {
// The parent Actor of the egress policy resource to retrieve.
//
// +k8s:required
// +k8s:subfield(atespace)=+k8s:required
ObjectRef actor = 1;
}
// CreateActorEgressPolicyRequest creates an egress policy for an Actor.
//
// +k8s:customValidation # atespaces must match
message CreateActorEgressPolicyRequest {
// Parent Actor under which to create the policy resource.
//
// +k8s:required
// +k8s:subfield(atespace)=+k8s:required
ObjectRef actor = 1;
// The policy to create. metadata.name must be "default" and
// metadata.atespace must match actor.atespace.
//
// +k8s:required
EgressPolicy egress_policy = 2;
}
// UpdateActorEgressPolicyRequest replaces an Actor's egress policy.
//
// +k8s:customValidation
message UpdateActorEgressPolicyRequest {
// Parent Actor under which to update the policy resource.
//
// +k8s:required
// +k8s:subfield(atespace)=+k8s:required
ObjectRef actor = 1;
// Full replacement. Metadata UID and version are required preconditions;
// atespace and name are immutable and must match the existing policy.
//
// +k8s:required
EgressPolicy egress_policy = 2;
}
// DeleteActorEgressPolicyRequest identifies an Actor's egress policy.
message DeleteActorEgressPolicyRequest {
// The parent Actor of the egress policy resource to delete.
//
// +k8s:required
// +k8s:subfield(atespace)=+k8s:required
ObjectRef actor = 1;
}
message GetTagRequest {
// +k8s:required
// +k8s:subfield(atespace)=+k8s:required
ObjectRef tag = 1;
}
// Request message for MintActorJWT.
message MintActorJWTRequest {
// The actor for which the JWT should be issued.
//
// Must be a valid actor that currently exists according to the actor store.
//
// +k8s:required
ObjectRef actor = 5;
// The UID of the actor --- used to guard against deletion and recreation of
// an actor with the same name.
//
// +k8s:required
// +k8s:format=k8s-uuid
string actor_uid = 7;
// The audiences the minted JWT is bound to. Tokens are only issued with
// audience bindings, so at least one is required.
//
// +k8s:required
// +k8s:maxItems=16 # guardrail; tokens realistically bind a handful of audiences
// +k8s:listType=set
// +k8s:eachVal=+k8s:maxLength=512 # audiences are caller-defined URIs; bound only
repeated string audience = 1;
}
// Response message for MintActorJWT
//
// TODO(identity): check why k8s do ":" and not "/" as a seprator for the Subject format
//
// TODO(identity): whats the right format for the subject? kubernetes follow "system:serviceaccount:<namespace>:<name>".
message MintActorJWTResponse {
// Actor JWT. An OIDC Discovery-compatible JWT
//
// Claims:
//
// * iss: Issuer - a valid URL where a relying party can fetch the OIDC
// discovery documents.
// * sub: Subject - a string expressing the identity carried in the
// credential. Format
// `atespaces:${atespace}:actors:${actorname}`.
// * aud: Audience - a string identifying the service this token will be used
// to authenticate to.
// * nbf: Not Before - a numeric unix timestamp
// * exp: Expiration - a numeric unix timestamp
// * iat: Issued At - a numeric unix timestamp
// * `ate.dev`: Ate/Substrate Extension - JSON object
// * atespace: (string) The atespace the actor belongs to
// * actorName: (string) The actor's name, unique within its atespace
string actor_jwt = 1;
}
// Request message for MintActorCertificate.
message MintActorCertificateRequest {
// The actor for which the certificate should be issued.
//
// Must be a valid actor that currently exists according to the actor store.
//
// +k8s:required
ObjectRef actor = 6;
// The UID of the actor --- used to guard against deletion and recreation of
// an actor with the same name.
//
// +k8s:required
// +k8s:format=k8s-uuid
string actor_uid = 7;
// Request contains DER encoded bytes of a x509 certificate signing request.
// The signer will ignore the contents of the CSR except to extract the
// subject public key.
//
// +k8s:required
// +k8s:maxBytes=16384
bytes certificate_signing_request = 2;
// Purpose of the certificate. Used to distinguish between system components
// acting on behalf of an actor, and the actor itself taking action.
//
// +k8s:required
// +k8s:minimum=1
// +k8s:maximum=1 # keep this in sync with the ActorCertificatePurpose enum
ActorCertificatePurpose purpose = 4;
}
// Purpose of the certificate. Used to distinguish between system components
// acting on behalf of an actor, and the actor itself taking action.
//
// TODO(identity): This should be removed once ateom/atunnel certs are separated
// from plain actor certs.
enum ActorCertificatePurpose {
ACTOR_CERTIFICATE_PURPOSE_UNSPECIFIED = 0;
ACTOR_CERTIFICATE_PURPOSE_ATUNNEL = 1;
// Keep this in sync with MintCertRequest.purpose's maximum.
}
// Response message for MintActorCertificate.
message MintActorCertificateResponse {
// Response contains a list of DER encoded certificates. The first entry is the
// leaf certificate, and any remaining entries are intermediates in
// leaf-to-root order.
repeated bytes actor_certificates = 1;
}
message GetActorSnapshotRequest {
// +k8s:opaqueType
ObjectRef actor_snapshot = 1;
}
message GetActorSnapshotTagRequest {
// +k8s:opaqueType
ObjectRef actor_snapshot_tag = 1;
}
message ListTagsRequest {
// The atespace to list tags from. Empty lists across all atespaces.
//
// +k8s:optional
// +k8s:format=k8s-short-name
string atespace = 1;
// Requested page size; the server may return fewer, or occasionally
// slightly more. If unspecified, defaults to a server-chosen value;
// values above 1000 are coerced to 1000.
//
// +k8s:optional
// +k8s:minimum=1
int32 page_size = 2;
// Pagination token from a previous ListTags response.
// Omit or leave empty for the first request.
//
// +k8s:optional
// +k8s:maxLength=256
string page_token = 3;
}
message ListTagsResponse {
repeated Tag tags = 1;
string next_page_token = 2;
}
// Request to tag the external snapshot a suspended Actor holds.
//
// The tag captures whichever snapshot the Actor holds when the call runs. An
// Actor keeps no snapshot history — each suspend replaces the last — so a
// suspend that lands between taking a snapshot and tagging it moves what gets
// tagged. That race is inherent and accepted: the tag means "the Actor as of
// this call", not "the snapshot some earlier suspend returned".
//
// The tag is given its own copy of that snapshot, so it survives the Actor
// being suspended again or deleted.
// To retry a create that failed or timed out, delete the tag first,
// which collects whatever that attempt stranded, and create it again.
//
// +k8s:customValidation # metadata.atespace must match source_actor.atespace
message CreateTagRequest {
// The tag to create. metadata.atespace, metadata.name, scope and source_actor
// are honored; metadata.atespace must match source_actor's, and status is
// ignored on write.
//
// +k8s:required
Tag tag = 1;
}
// Request to update mutable fields on an existing Tag.
// The tag keeps its address: the snapshot it points at cannot be changed.
message UpdateTagRequest {
// The tag to update.
// tag.metadata.atespace and tag.metadata.name identify which resource to
// update.
// tag.metadata.version and tag.metadata.uid are required preconditions
//
// +k8s:required
// +k8s:opaqueType # updates are handled in 2 steps, do not descend
// +k8s:subfield(metadata)=+k8s:required
// +k8s:customValidation # TODO: when we get nested subfields, require metadata.atespace
Tag tag = 1;
}
message DeleteTagRequest {
// +k8s:required
// +k8s:subfield(atespace)=+k8s:required
ObjectRef tag = 1;
}
// DeleteOptions carries per-delete controls. Today it holds optional
// preconditions that guard against acting on a resource that is not in the
// state the caller expects. Future delete-specific controls (e.g. dry-run)
// should be added here.
//
// Intended to be reused across every Delete<Type>Request; only
// DeleteWorkerRequest carries it so far.
message DeleteOptions {
// If non-zero, delete only if the server's current version matches.
//
// +k8s:optional
// +k8s:minimum=1
int64 version = 1;
// If non-empty, delete only if the server's current uid matches. Guards
// against name reuse across lifecycles.
//
// +k8s:optional
// +k8s:format=k8s-uuid
string uid = 2;
}
// ListWorkerActorAssignmentsRequest asks for a page of the Actors hosted by a
// given Worker.
message ListWorkerActorAssignmentsRequest {
// The Worker under which to list Actors.
//
// +k8s:required
// +k8s:beta(since: "0.0")=+k8s:subfield(atespace)=+k8s:forbidden # TODO: get rid of beta prefix
ObjectRef worker = 1;
// Requested page size; the server may return fewer. If unspecified, defaults
// to a server-chosen value; values above 1000 are coerced to 1000.
//
// +k8s:optional
// +k8s:minimum=1
int32 page_size = 2;
// Pagination token from a previous ListWorkerActorAssignments response.
// Omit or leave empty for the first request.
//
// +k8s:optional
// +k8s:maxLength=256
string page_token = 3;
}
// ListWorkerActorAssignmentsResponse is one page of a Worker's Actors.
message ListWorkerActorAssignmentsResponse {
// The Actors this page of the listing covers.
repeated ActorAssignment actor_assignments = 1;
// Pagination token for the next page. Empty if this is the last page.
string next_page_token = 2;
}
message ListWorkersRequest {
// Requested page size; the server may return fewer, or occasionally
// slightly more. If unspecified, defaults to a server-chosen value;
// values above 1000 are coerced to 1000.
//
// +k8s:optional
// +k8s:minimum=1
int32 page_size = 1;
// Pagination token from a previous ListWorkers response.
// Omit or leave empty for the first request.
//
// +k8s:optional
// +k8s:maxLength=256
string page_token = 2;
}
message ListWorkersResponse {
// The page of workers. This list may be empty even if there are more results.
repeated Worker workers = 1;
// Pagination token for the next page. Empty if this is the last page.
string next_page_token = 2;
}
// TODO: Workers are still created, updated, and deleted by writing directly to
// the store from the WorkerPoolSyncer and the actor workflows; migrating those
// callers onto the RPCs below lands in a follow-up change.
message GetWorkerRequest {
// The Worker to fetch. atespace is always empty; Workers are global-scoped.
//
// +k8s:required
// +k8s:beta(since: "0.0")=+k8s:subfield(atespace)=+k8s:forbidden # TODO: get rid of beta prefix
ObjectRef worker = 1;
}
message CreateWorkerRequest {
// The Worker to register.
//
// +k8s:required
Worker worker = 1;
}
message UpdateWorkerRequest {
// The Worker to update.
// worker.metadata.name identifies which resource to update. atespace is
// always empty; Workers are global-scoped.
// worker.metadata.version and worker.metadata.uid are required preconditions.
//
// sandbox_class and labels are the only fields an update may change. Every
// other field is replaced with what the request carries, and a field left
// unset is cleared — so read the Worker, change what you mean to change, and
// send the whole thing back. A request that alters an immutable field, by
// changing it or by omitting it, returns INVALID_ARGUMENT naming the field.
// status is output-only and whatever it carries is ignored.
//
// +k8s:required
// +k8s:opaqueType # updates are handled in 2 steps, do not descend
// +k8s:subfield(metadata)=+k8s:required
// +k8s:customValidation # TODO: when we get nested subfields, forbid metadata.atespace
Worker worker = 1;
}
message DeleteWorkerRequest {
// The Worker to deregister.
//
// +k8s:required
// +k8s:beta(since: "0.0")=+k8s:subfield(atespace)=+k8s:forbidden # TODO: get rid of beta prefix
ObjectRef worker = 1;
// Optional per-delete preconditions.
//
// +k8s:optional
DeleteOptions options = 2;
}
message DrainWorkerRequest {
// The Worker to mark as terminating.
//
// +k8s:required
// +k8s:beta(since: "0.0")=+k8s:subfield(atespace)=+k8s:forbidden # TODO: get rid of beta prefix
ObjectRef worker = 1;
}
// List actors with pagination. Provides "soft" guarantees: actors may be
// missed or duplicated if the system state changes during pagination.
message ListActorsRequest {
// The atespace to list actors from. Empty lists across all atespaces.
//
// +k8s:optional
// +k8s:format=k8s-short-name
string atespace = 1;
// Requested page size; the server may return fewer, or occasionally
// slightly more. If unspecified, defaults to a server-chosen value;
// values above 1000 are coerced to 1000.
//
// +k8s:optional
// +k8s:minimum=1
int32 page_size = 2;
// Pagination token from a previous ListActors response.
// Omit or leave empty for the first request.
//
// +k8s:optional
// +k8s:maxLength=256
string page_token = 3;
}
message ListActorsResponse {
// The page of actors. This list may be empty even if there are more results.
repeated Actor actors = 1;
// Pagination token for the next page. Empty if this is the last page.
string next_page_token = 2;
}
// Worker is a schedulable worker pod.
//
// Global-scoped: metadata.atespace is always empty. metadata.name is assigned
// by the control plane and is opaque to clients — never parse it or derive it
// from anything else; read pod identity from the named fields below.
//
// sandbox_class and labels are the only mutable fields; every other field is
// either immutable after creation or output-only. UpdateWorker replaces the
// whole resource, so an immutable field that a request changes — including by
// omitting it, which would clear it — is rejected with INVALID_ARGUMENT.
message Worker {
// Output-only: name, uid, version and timestamps are all server-assigned.
// uid and version are echoed back on UpdateWorker as preconditions.
//
// +k8s:required
// +k8s:beta(since: "0.0")=+k8s:subfield(atespace)=+k8s:forbidden # TODO: get rid of beta prefix
ResourceMetadata metadata = 1;
// Kubernetes coordinates. Immutable, set at creation.
//
// +k8s:required
// +k8s:format=k8s-short-name
// +k8s:immutable
string worker_namespace = 2;
// +k8s:required
// +k8s:format=k8s-long-name
// +k8s:immutable
string worker_pool = 3;
// +k8s:required
// +k8s:format=k8s-long-name
// +k8s:immutable
string worker_pod = 4;
// +k8s:required
// +k8s:format=k8s-uuid
// +k8s:immutable
string worker_pod_uid = 5;
// +k8s:required
// +k8s:format=k8s-long-name
// +k8s:immutable
string node_name = 6;
// +k8s:required
// +k8s:customValidation # until `format=k8s-ip` is supported
// +k8s:immutable
string ip = 7;
// sandbox_class mirrors the WorkerPool's sandboxClass; its values are the
// CRD's own vocabulary, so it is only bounded, not validated. Mutable.
//
// +k8s:optional
// +k8s:maxLength=63
string sandbox_class = 8;
// labels mirror the WorkerPool object's Kubernetes labels, which selectors
// match against.
// Kubernetes does not cap an object's label count, so the bound below is
// a generous guardrail, not a mirror of an upstream limit.
//
// +k8s:optional
// +k8s:maxProperties=64
// +k8s:eachKey=+k8s:format=k8s-label-key
// +k8s:eachVal=+k8s:format=k8s-label-value
map<string, string> labels = 9;
// Output-only server-managed state. Absent from Create/Update request
// payloads; whatever a request carries here is ignored. DrainWorker is the
// only way a client moves state, and the assignments are the scheduler's.
//
// +k8s:optional
WorkerStatus status = 11;
}
enum WorkerState {
WORKER_STATE_UNSPECIFIED = 0;
// Ready; schedulable.
WORKER_STATE_ACTIVE = 1;
// Pod terminating. Not schedulable.
WORKER_STATE_DRAINING = 2;
// Keep this in sync with WorkerStatus.state's maximum.
}
message WorkerStatus {
// +k8s:required
// +k8s:minimum=1
// +k8s:maximum=2 # keep this in sync with the WorkerState enum
WorkerState state = 1;
// The total resource capacity of the Worker.
//
// A Worker should report every dimension of resources it has available to be
// consumed by Actors. Any resource not specified here is assumed to be 0. An
// Actor which requests a resource which is not present in a Worker's
// capacity will prevent that Actor from running on that Worker.
//
// Shrinking capacity below allocated prevents new placements but does not
// evicts running Actors.
//
// +k8s:optional
WorkerResources capacity = 2;
// The allocated resources of the Worker. This is the sum of all the
// resources consumed by the Actors currently assigned to this Worker.
//
// +k8s:optional
WorkerResources allocated = 3;
}
// WorkerResources represents a set of resources.
message WorkerResources {
// Resources match the Actor's definition of resources.
// They are matched by name and used for placement.
//
// +k8s:optional
Resources resources = 1;
// A count of Actors. Workers can use this to limit the number of Actors
// that can be placed on them, constraining the costs of other things that
// are not captured in the resources above, but which still have costs -
// netns, mounts, file descriptors, etc.
//
// +k8s:optional
// +k8s:minimum=1
int32 actors = 2;
}
// ActorAssignment binds an Actor to a Worker. This is a subresource of the
// Worker.
message ActorAssignment {
// Metadata about the assignment.
//
// The atespace field is always empty, since it corresponds to a Worker, and
// Workers are not atespaced. The name field is the Actor's UID, so the Actor
// that caused an assignment is what addresses it.
//
// +k8s:required
// +k8s:beta(since: "0.0")=+k8s:subfield(atespace)=+k8s:forbidden # TODO: get rid of beta prefix
ResourceMetadata metadata = 6;
// actor is the Actor that is assigned to the Worker.
//
// +k8s:required
// +k8s:subfield(atespace)=+k8s:required
ObjectRef actor = 2;
// +k8s:required
// +k8s:format=k8s-uuid
string actor_uid = 3;
// actor_template_ref names the ActorTemplate resource the assigned Actor
// uses.
//
// +k8s:required
// +k8s:subfield(atespace)=+k8s:required
ObjectRef actor_template_ref = 4;
// The resources allocated to the Actor when it was assigned.
//
// +k8s:optional
Resources resources = 5;
}
// WorkerService is how a Worker tells the control plane about itself. It is
// separate from Control because the two have different callers and different
// authorization: Control is the client-facing API, while these RPCs are served
// only to an atelet, and only for the Workers on its own node.
service WorkerService {
// SetWorkerCapacity records what a Worker can hold. Capacity is the Worker's
// to report rather than the control plane's to infer: it is what the ateom
// can actually supply, only its node can observe it, and a fleet may run
// mixed ateom versions.
//
// atelet calls this with its own client certificate, as it does for
// MintCert. Idempotent: re-sending the same capacity is not a write.
rpc SetWorkerCapacity(SetWorkerCapacityRequest) returns (SetWorkerCapacityResponse);
}
message SetWorkerCapacityRequest {
// The Worker being reported on. atespace is always empty; Workers are
// global-scoped.
//
// +k8s:required
// +k8s:beta(since: "0.0")=+k8s:subfield(atespace)=+k8s:forbidden # TODO: get rid of beta prefix
ObjectRef worker = 1;
// Everything the Worker can hold. This replaces what is recorded rather than
// merging into it: a dimension left out is one the Worker no longer supplies,
// and an Actor asking for that dimension will not be placed here.
//
// +k8s:required
WorkerResources capacity = 2;
}
message SetWorkerCapacityResponse {
// The Worker as recorded, so a caller sees what its report resolved to.
Worker worker = 1;
}