From 6044bb7a7b103c2447fbbc2273c2c4d9727b3a7e Mon Sep 17 00:00:00 2001 From: Peter Jausovec Date: Thu, 9 Apr 2026 10:00:42 +0200 Subject: [PATCH 01/13] feat: SandboxAgent CRD and sandbox runtime integration Add SandboxAgent kind with reconciliation via SandboxTemplate/SandboxClaim, translator runInSandbox flag, HTTP/UI support. Signed-off-by: Peter Jausovec --- .../crd/bases/kagent.dev_sandboxagents.yaml | 8196 +++++++++++++++++ go/api/httpapi/types.go | 2 + go/api/v1alpha2/agent_types.go | 26 + go/api/v1alpha2/sandboxagent_types.go | 47 + go/api/v1alpha2/zz_generated.deepcopy.go | 94 + go/core/cmd/controller/main.go | 8 +- go/core/internal/a2a/a2a_registrar.go | 47 + .../internal/controller/agent_controller.go | 9 + .../mcp_server_tool_controller_test.go | 4 + .../reconciler/mcp_server_reconciler_test.go | 2 + .../controller/reconciler/reconciler.go | 228 +- .../reconciler/sandboxagent_sandbox_sync.go | 81 + .../sandboxagent_sandbox_sync_test.go | 70 + .../reconciler/utils/reconciler_utils.go | 4 +- .../controller/sandboxagent_controller.go | 328 + .../controller/service_controller_test.go | 4 + .../translator/agent/adk_api_translator.go | 176 +- .../agent/adk_api_translator_test.go | 168 +- .../agent/adk_translator_golden_test.go | 2 +- .../translator/agent/git_skills_test.go | 12 +- .../translator/agent/mcp_validation_test.go | 18 +- .../controller/translator/agent/proxy_test.go | 15 +- .../translator/agent/runtime_test.go | 16 +- .../translator/agent/sandbox_agent_view.go | 33 + .../translator/agent/security_context_test.go | 20 +- .../controller/translator/agent/utils.go | 3 +- .../internal/controller/translator/mutate.go | 23 + .../translator/mutate_sandbox_test.go | 53 + .../internal/httpserver/handlers/agents.go | 257 +- .../httpserver/handlers/agents_test.go | 82 + .../internal/httpserver/handlers/handlers.go | 5 +- .../internal/httpserver/handlers/sessions.go | 31 + .../httpserver/handlers/sessions_test.go | 6 +- .../httpserver/handlers/test_helpers_test.go | 6 + go/core/internal/httpserver/server.go | 8 +- go/core/pkg/app/app.go | 18 + .../sandboxbackend/agentsxk8s/agentsxk8s.go | 138 + .../agentsxk8s/agentsxk8s_test.go | 46 + go/core/pkg/sandboxbackend/apis_available.go | 38 + go/core/pkg/sandboxbackend/backend.go | 31 + go/go.mod | 27 +- go/go.sum | 56 +- ...ensions.agents.x-k8s.io_sandboxclaims.yaml | 103 + ...ions.agents.x-k8s.io_sandboxtemplates.yaml | 4019 ++++++++ .../templates/kagent.dev_sandboxagents.yaml | 8196 +++++++++++++++++ helm/kagent/templates/rbac/getter-role.yaml | 20 + helm/kagent/templates/rbac/writer-role.yaml | 21 + ui/src/app/actions/agents.ts | 211 +- .../agents/[namespace]/[name]/chat/page.tsx | 83 +- ui/src/app/agents/new/page.tsx | 56 +- ui/src/components/AgentCard.tsx | 2 +- ui/src/components/AgentsProvider.tsx | 18 +- ui/src/components/chat/ChatAgentContext.tsx | 37 + ui/src/components/chat/ChatInterface.tsx | 32 + ui/src/components/chat/ChatLayoutUI.tsx | 13 +- .../sidebars/AgentDetailsSidebar.tsx | 10 +- ui/src/components/sidebars/AgentSwitcher.tsx | 5 +- ui/src/components/sidebars/ChatItem.tsx | 6 +- ui/src/components/sidebars/EmptyState.tsx | 32 +- ui/src/components/sidebars/GroupedChats.tsx | 16 +- ui/src/components/sidebars/SessionGroup.tsx | 5 +- .../components/sidebars/SessionsSidebar.tsx | 8 +- ui/src/lib/a2aBackendFetch.ts | 41 + ui/src/lib/a2aClient.ts | 3 - ui/src/lib/agentFormLayout.ts | 33 + ui/src/types/index.ts | 31 +- 66 files changed, 23177 insertions(+), 262 deletions(-) create mode 100644 go/api/config/crd/bases/kagent.dev_sandboxagents.yaml create mode 100644 go/api/v1alpha2/sandboxagent_types.go create mode 100644 go/core/internal/controller/reconciler/sandboxagent_sandbox_sync.go create mode 100644 go/core/internal/controller/reconciler/sandboxagent_sandbox_sync_test.go create mode 100644 go/core/internal/controller/sandboxagent_controller.go create mode 100644 go/core/internal/controller/translator/agent/sandbox_agent_view.go create mode 100644 go/core/internal/controller/translator/mutate_sandbox_test.go create mode 100644 go/core/pkg/sandboxbackend/agentsxk8s/agentsxk8s.go create mode 100644 go/core/pkg/sandboxbackend/agentsxk8s/agentsxk8s_test.go create mode 100644 go/core/pkg/sandboxbackend/apis_available.go create mode 100644 go/core/pkg/sandboxbackend/backend.go create mode 100644 helm/kagent-crds/templates/extensions.agents.x-k8s.io_sandboxclaims.yaml create mode 100644 helm/kagent-crds/templates/extensions.agents.x-k8s.io_sandboxtemplates.yaml create mode 100644 helm/kagent-crds/templates/kagent.dev_sandboxagents.yaml create mode 100644 ui/src/components/chat/ChatAgentContext.tsx create mode 100644 ui/src/lib/a2aBackendFetch.ts create mode 100644 ui/src/lib/agentFormLayout.ts diff --git a/go/api/config/crd/bases/kagent.dev_sandboxagents.yaml b/go/api/config/crd/bases/kagent.dev_sandboxagents.yaml new file mode 100644 index 000000000..142d92c41 --- /dev/null +++ b/go/api/config/crd/bases/kagent.dev_sandboxagents.yaml @@ -0,0 +1,8196 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + name: sandboxagents.kagent.dev +spec: + group: kagent.dev + names: + kind: SandboxAgent + listKind: SandboxAgentList + plural: sandboxagents + singular: sandboxagent + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: Whether the sandbox workload is ready. + jsonPath: .status.conditions[?(@.type=='Ready')].status + name: Ready + type: string + - description: Whether configuration was accepted. + jsonPath: .status.conditions[?(@.type=='Accepted')].status + name: Accepted + type: string + name: v1alpha2 + schema: + openAPIV3Schema: + description: SandboxAgent declares an agent that runs in an isolated sandbox + (agent-sandbox SandboxTemplate+SandboxClaim). + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: |- + SandboxAgentSpec defines the desired state of a SandboxAgent. Workload fields match Agent.spec + (Declarative or BYO). The kind selects sandbox reconciliation (SandboxTemplate + SandboxClaim) instead of a Deployment. + properties: + allowedNamespaces: + description: |- + AllowedNamespaces defines which namespaces are allowed to reference this resource. + This mechanism provides a bidirectional handshake for cross-namespace references, + following the pattern used by Gateway API for cross-namespace route attachments. + + By default (when not specified), only references from the same namespace are allowed. + properties: + from: + default: Same + description: |- + From indicates where references to this resource can originate. + Possible values are: + * All: References from all namespaces are allowed. + * Same: Only references from the same namespace are allowed (default). + * Selector: References from namespaces matching the selector are allowed. + enum: + - All + - Same + - Selector + type: string + selector: + description: |- + Selector is a label selector for namespaces that are allowed to reference this resource. + Only used when From is set to "Selector". + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + x-kubernetes-validations: + - message: selector must be specified when from is Selector + rule: '!(self.from == ''Selector'' && !has(self.selector))' + byo: + properties: + deployment: + description: Trust relationship to the agent. + properties: + affinity: + description: Affinity is a group of affinity scheduling rules. + properties: + nodeAffinity: + description: Describes node affinity scheduling rules + for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: A node selector term, associated + with the corresponding weight. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the + selector applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the + selector applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: Weight associated with matching + the corresponding nodeSelectorTerm, in the + range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: Required. A list of node selector + terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the + selector applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the + selector applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: Describes pod affinity scheduling rules (e.g. + co-locate this pod in the same node, zone, etc. as some + other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, + associated with the corresponding weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The + requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The + requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: Describes pod anti-affinity scheduling rules + (e.g. avoid putting this pod in the same node, zone, + etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, + associated with the corresponding weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The + requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The + requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + annotations: + additionalProperties: + type: string + type: object + args: + items: + type: string + type: array + cmd: + type: string + env: + items: + description: EnvVar represents an environment variable present + in a Container. + properties: + name: + description: |- + Name of the environment variable. + May consist of any printable ASCII characters except '='. + type: string + value: + description: |- + Variable references $(VAR_NAME) are expanded + using the previously defined environment variables in the container and + any service environment variables. If a variable cannot be resolved, + the reference in the input string will be unchanged. Double $$ are reduced + to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. + "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". + Escaped references will never be expanded, regardless of whether the variable + exists or not. + Defaults to "". + type: string + valueFrom: + description: Source for the environment variable's value. + Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the ConfigMap or + its key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + description: |- + Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['']`, `metadata.annotations['']`, + spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs. + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in + the specified API version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + fileKeyRef: + description: |- + FileKeyRef selects a key of the env file. + Requires the EnvFiles feature gate to be enabled. + properties: + key: + description: |- + The key within the env file. An invalid key will prevent the pod from starting. + The keys defined within a source may consist of any printable ASCII characters except '='. + During Alpha stage of the EnvFiles feature gate, the key size is limited to 128 characters. + type: string + optional: + default: false + description: |- + Specify whether the file or its key must be defined. If the file or key + does not exist, then the env var is not published. + If optional is set to true and the specified key does not exist, + the environment variable will not be set in the Pod's containers. + + If optional is set to false and the specified key does not exist, + an error will be returned during Pod creation. + type: boolean + path: + description: |- + The path within the volume from which to select the file. + Must be relative and may not contain the '..' path or start with '..'. + type: string + volumeName: + description: The name of the volume mount containing + the env file. + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported. + properties: + containerName: + description: 'Container name: required for volumes, + optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of + the exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + description: Selects a key of a secret in the pod's + namespace + properties: + key: + description: The key of the secret to select + from. Must be a valid secret key. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the Secret or its + key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + required: + - name + type: object + type: array + image: + minLength: 1 + type: string + imagePullPolicy: + description: PullPolicy describes a policy for if/when to + pull a container image + type: string + imagePullSecrets: + items: + description: |- + LocalObjectReference contains enough information to let you locate the + referenced object inside the same namespace. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + type: array + labels: + additionalProperties: + type: string + type: object + nodeSelector: + additionalProperties: + type: string + type: object + podSecurityContext: + description: |- + PodSecurityContext holds pod-level security attributes and common container settings. + Some fields are also present in container.securityContext. Field values of + container.securityContext take precedence over field values of PodSecurityContext. + properties: + appArmorProfile: + description: |- + appArmorProfile is the AppArmor options to use by the containers in this pod. + Note that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: |- + localhostProfile indicates a profile loaded on the node that should be used. + The profile must be preconfigured on the node to work. + Must match the loaded name of the profile. + Must be set if and only if type is "Localhost". + type: string + type: + description: |- + type indicates which kind of AppArmor profile will be applied. + Valid options are: + Localhost - a profile pre-loaded on the node. + RuntimeDefault - the container runtime's default profile. + Unconfined - no AppArmor enforcement. + type: string + required: + - type + type: object + fsGroup: + description: |- + A special supplemental group that applies to all containers in a pod. + Some volume types allow the Kubelet to change the ownership of that volume + to be owned by the pod: + + 1. The owning GID will be the FSGroup + 2. The setgid bit is set (new files created in the volume will be owned by FSGroup) + 3. The permission bits are OR'd with rw-rw---- + + If unset, the Kubelet will not modify the ownership and permissions of any volume. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + fsGroupChangePolicy: + description: |- + fsGroupChangePolicy defines behavior of changing ownership and permission of the volume + before being exposed inside Pod. This field will only apply to + volume types which support fsGroup based ownership(and permissions). + It will have no effect on ephemeral volume types such as: secret, configmaps + and emptydir. + Valid values are "OnRootMismatch" and "Always". If not specified, "Always" is used. + Note that this field cannot be set when spec.os.name is windows. + type: string + runAsGroup: + description: |- + The GID to run the entrypoint of the container process. + Uses runtime default if unset. + May also be set in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence + for that container. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: |- + Indicates that the container must run as a non-root user. + If true, the Kubelet will validate the image at runtime to ensure that it + does not run as UID 0 (root) and fail to start the container if it does. + If unset or false, no such validation will be performed. + May also be set in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: |- + The UID to run the entrypoint of the container process. + Defaults to user specified in image metadata if unspecified. + May also be set in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence + for that container. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + seLinuxChangePolicy: + description: |- + seLinuxChangePolicy defines how the container's SELinux label is applied to all volumes used by the Pod. + It has no effect on nodes that do not support SELinux or to volumes does not support SELinux. + Valid values are "MountOption" and "Recursive". + + "Recursive" means relabeling of all files on all Pod volumes by the container runtime. + This may be slow for large volumes, but allows mixing privileged and unprivileged Pods sharing the same volume on the same node. + + "MountOption" mounts all eligible Pod volumes with `-o context` mount option. + This requires all Pods that share the same volume to use the same SELinux label. + It is not possible to share the same volume among privileged and unprivileged Pods. + Eligible volumes are in-tree FibreChannel and iSCSI volumes, and all CSI volumes + whose CSI driver announces SELinux support by setting spec.seLinuxMount: true in their + CSIDriver instance. Other volumes are always re-labelled recursively. + "MountOption" value is allowed only when SELinuxMount feature gate is enabled. + + If not specified and SELinuxMount feature gate is enabled, "MountOption" is used. + If not specified and SELinuxMount feature gate is disabled, "MountOption" is used for ReadWriteOncePod volumes + and "Recursive" for all other volumes. + + This field affects only Pods that have SELinux label set, either in PodSecurityContext or in SecurityContext of all containers. + + All Pods that use the same volume should use the same seLinuxChangePolicy, otherwise some pods can get stuck in ContainerCreating state. + Note that this field cannot be set when spec.os.name is windows. + type: string + seLinuxOptions: + description: |- + The SELinux context to be applied to all containers. + If unspecified, the container runtime will allocate a random SELinux context for each + container. May also be set in SecurityContext. If set in + both SecurityContext and PodSecurityContext, the value specified in SecurityContext + takes precedence for that container. + Note that this field cannot be set when spec.os.name is windows. + properties: + level: + description: Level is SELinux level label that applies + to the container. + type: string + role: + description: Role is a SELinux role label that applies + to the container. + type: string + type: + description: Type is a SELinux type label that applies + to the container. + type: string + user: + description: User is a SELinux user label that applies + to the container. + type: string + type: object + seccompProfile: + description: |- + The seccomp options to use by the containers in this pod. + Note that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: |- + localhostProfile indicates a profile defined in a file on the node should be used. + The profile must be preconfigured on the node to work. + Must be a descending path, relative to the kubelet's configured seccomp profile location. + Must be set if type is "Localhost". Must NOT be set for any other type. + type: string + type: + description: |- + type indicates which kind of seccomp profile will be applied. + Valid options are: + + Localhost - a profile defined in a file on the node should be used. + RuntimeDefault - the container runtime default profile should be used. + Unconfined - no profile should be applied. + type: string + required: + - type + type: object + supplementalGroups: + description: |- + A list of groups applied to the first process run in each container, in + addition to the container's primary GID and fsGroup (if specified). If + the SupplementalGroupsPolicy feature is enabled, the + supplementalGroupsPolicy field determines whether these are in addition + to or instead of any group memberships defined in the container image. + If unspecified, no additional groups are added, though group memberships + defined in the container image may still be used, depending on the + supplementalGroupsPolicy field. + Note that this field cannot be set when spec.os.name is windows. + items: + format: int64 + type: integer + type: array + x-kubernetes-list-type: atomic + supplementalGroupsPolicy: + description: |- + Defines how supplemental groups of the first container processes are calculated. + Valid values are "Merge" and "Strict". If not specified, "Merge" is used. + (Alpha) Using the field requires the SupplementalGroupsPolicy feature gate to be enabled + and the container runtime must implement support for this feature. + Note that this field cannot be set when spec.os.name is windows. + type: string + sysctls: + description: |- + Sysctls hold a list of namespaced sysctls used for the pod. Pods with unsupported + sysctls (by the container runtime) might fail to launch. + Note that this field cannot be set when spec.os.name is windows. + items: + description: Sysctl defines a kernel parameter to be + set + properties: + name: + description: Name of a property to set + type: string + value: + description: Value of a property to set + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + windowsOptions: + description: |- + The Windows specific settings applied to all containers. + If unspecified, the options within a container's SecurityContext will be used. + If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is linux. + properties: + gmsaCredentialSpec: + description: |- + GMSACredentialSpec is where the GMSA admission webhook + (https://github.com/kubernetes-sigs/windows-gmsa) inlines the contents of the + GMSA credential spec named by the GMSACredentialSpecName field. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the name of + the GMSA credential spec to use. + type: string + hostProcess: + description: |- + HostProcess determines if a container should be run as a 'Host Process' container. + All of a Pod's containers must have the same effective HostProcess value + (it is not allowed to have a mix of HostProcess containers and non-HostProcess containers). + In addition, if HostProcess is true then HostNetwork must also be set to true. + type: boolean + runAsUserName: + description: |- + The UserName in Windows to run the entrypoint of the container process. + Defaults to the user specified in image metadata if unspecified. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + type: string + type: object + type: object + replicas: + format: int32 + type: integer + resources: + description: ResourceRequirements describes the compute resource + requirements. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + + This field depends on the + DynamicResourceAllocation feature gate. + + This field is immutable. It can only be set for containers. + items: + description: ResourceClaim references one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + securityContext: + description: |- + SecurityContext holds security configuration that will be applied to a container. + Some fields are present in both SecurityContext and PodSecurityContext. When both + are set, the values in SecurityContext take precedence. + properties: + allowPrivilegeEscalation: + description: |- + AllowPrivilegeEscalation controls whether a process can gain more + privileges than its parent process. This bool directly controls if + the no_new_privs flag will be set on the container process. + AllowPrivilegeEscalation is true always when the container is: + 1) run as Privileged + 2) has CAP_SYS_ADMIN + Note that this field cannot be set when spec.os.name is windows. + type: boolean + appArmorProfile: + description: |- + appArmorProfile is the AppArmor options to use by this container. If set, this profile + overrides the pod's appArmorProfile. + Note that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: |- + localhostProfile indicates a profile loaded on the node that should be used. + The profile must be preconfigured on the node to work. + Must match the loaded name of the profile. + Must be set if and only if type is "Localhost". + type: string + type: + description: |- + type indicates which kind of AppArmor profile will be applied. + Valid options are: + Localhost - a profile pre-loaded on the node. + RuntimeDefault - the container runtime's default profile. + Unconfined - no AppArmor enforcement. + type: string + required: + - type + type: object + capabilities: + description: |- + The capabilities to add/drop when running containers. + Defaults to the default set of capabilities granted by the container runtime. + Note that this field cannot be set when spec.os.name is windows. + properties: + add: + description: Added capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + x-kubernetes-list-type: atomic + drop: + description: Removed capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + x-kubernetes-list-type: atomic + type: object + privileged: + description: |- + Run container in privileged mode. + Processes in privileged containers are essentially equivalent to root on the host. + Defaults to false. + Note that this field cannot be set when spec.os.name is windows. + type: boolean + procMount: + description: |- + procMount denotes the type of proc mount to use for the containers. + The default value is Default which uses the container runtime defaults for + readonly paths and masked paths. + This requires the ProcMountType feature flag to be enabled. + Note that this field cannot be set when spec.os.name is windows. + type: string + readOnlyRootFilesystem: + description: |- + Whether this container has a read-only root filesystem. + Default is false. + Note that this field cannot be set when spec.os.name is windows. + type: boolean + runAsGroup: + description: |- + The GID to run the entrypoint of the container process. + Uses runtime default if unset. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: |- + Indicates that the container must run as a non-root user. + If true, the Kubelet will validate the image at runtime to ensure that it + does not run as UID 0 (root) and fail to start the container if it does. + If unset or false, no such validation will be performed. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: |- + The UID to run the entrypoint of the container process. + Defaults to user specified in image metadata if unspecified. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + seLinuxOptions: + description: |- + The SELinux context to be applied to the container. + If unspecified, the container runtime will allocate a random SELinux context for each + container. May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is windows. + properties: + level: + description: Level is SELinux level label that applies + to the container. + type: string + role: + description: Role is a SELinux role label that applies + to the container. + type: string + type: + description: Type is a SELinux type label that applies + to the container. + type: string + user: + description: User is a SELinux user label that applies + to the container. + type: string + type: object + seccompProfile: + description: |- + The seccomp options to use by this container. If seccomp options are + provided at both the pod & container level, the container options + override the pod options. + Note that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: |- + localhostProfile indicates a profile defined in a file on the node should be used. + The profile must be preconfigured on the node to work. + Must be a descending path, relative to the kubelet's configured seccomp profile location. + Must be set if type is "Localhost". Must NOT be set for any other type. + type: string + type: + description: |- + type indicates which kind of seccomp profile will be applied. + Valid options are: + + Localhost - a profile defined in a file on the node should be used. + RuntimeDefault - the container runtime default profile should be used. + Unconfined - no profile should be applied. + type: string + required: + - type + type: object + windowsOptions: + description: |- + The Windows specific settings applied to all containers. + If unspecified, the options from the PodSecurityContext will be used. + If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is linux. + properties: + gmsaCredentialSpec: + description: |- + GMSACredentialSpec is where the GMSA admission webhook + (https://github.com/kubernetes-sigs/windows-gmsa) inlines the contents of the + GMSA credential spec named by the GMSACredentialSpecName field. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the name of + the GMSA credential spec to use. + type: string + hostProcess: + description: |- + HostProcess determines if a container should be run as a 'Host Process' container. + All of a Pod's containers must have the same effective HostProcess value + (it is not allowed to have a mix of HostProcess containers and non-HostProcess containers). + In addition, if HostProcess is true then HostNetwork must also be set to true. + type: boolean + runAsUserName: + description: |- + The UserName in Windows to run the entrypoint of the container process. + Defaults to the user specified in image metadata if unspecified. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + type: string + type: object + type: object + serviceAccountConfig: + description: |- + ServiceAccountConfig configures the ServiceAccount created by the Agent controller. + This field can only be used when ServiceAccountName is not set. + If ServiceAccountName is not set, a default ServiceAccount (named after the agent) + is created, and this config will be applied to it. + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + serviceAccountName: + description: |- + ServiceAccountName specifies the name of an existing ServiceAccount to use. + If this field is set, the Agent controller will not create a ServiceAccount for the agent. + This field is mutually exclusive with ServiceAccountConfig. + type: string + tolerations: + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists, Equal, Lt, and Gt. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + Lt and Gt perform numeric comparisons (requires feature gate TaintTolerationComparisonOperators). + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + volumeMounts: + items: + description: VolumeMount describes a mounting of a Volume + within a container. + properties: + mountPath: + description: |- + Path within the container at which the volume should be mounted. Must + not contain ':'. + type: string + mountPropagation: + description: |- + mountPropagation determines how mounts are propagated from the host + to container and the other way around. + When not set, MountPropagationNone is used. + This field is beta in 1.10. + When RecursiveReadOnly is set to IfPossible or to Enabled, MountPropagation must be None or unspecified + (which defaults to None). + type: string + name: + description: This must match the Name of a Volume. + type: string + readOnly: + description: |- + Mounted read-only if true, read-write otherwise (false or unspecified). + Defaults to false. + type: boolean + recursiveReadOnly: + description: |- + RecursiveReadOnly specifies whether read-only mounts should be handled + recursively. + + If ReadOnly is false, this field has no meaning and must be unspecified. + + If ReadOnly is true, and this field is set to Disabled, the mount is not made + recursively read-only. If this field is set to IfPossible, the mount is made + recursively read-only, if it is supported by the container runtime. If this + field is set to Enabled, the mount is made recursively read-only if it is + supported by the container runtime, otherwise the pod will not be started and + an error will be generated to indicate the reason. + + If this field is set to IfPossible or Enabled, MountPropagation must be set to + None (or be unspecified, which defaults to None). + + If this field is not specified, it is treated as an equivalent of Disabled. + type: string + subPath: + description: |- + Path within the volume from which the container's volume should be mounted. + Defaults to "" (volume's root). + type: string + subPathExpr: + description: |- + Expanded path within the volume from which the container's volume should be mounted. + Behaves similarly to SubPath but environment variable references $(VAR_NAME) are expanded using the container's environment. + Defaults to "" (volume's root). + SubPathExpr and SubPath are mutually exclusive. + type: string + required: + - mountPath + - name + type: object + type: array + volumes: + items: + description: Volume represents a named volume in a pod that + may be accessed by any container in the pod. + properties: + awsElasticBlockStore: + description: |- + awsElasticBlockStore represents an AWS Disk resource that is attached to a + kubelet's host machine and then exposed to the pod. + Deprecated: AWSElasticBlockStore is deprecated. All operations for the in-tree + awsElasticBlockStore type are redirected to the ebs.csi.aws.com CSI driver. + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + properties: + fsType: + description: |- + fsType is the filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + type: string + partition: + description: |- + partition is the partition in the volume that you want to mount. + If omitted, the default is to mount by volume name. + Examples: For volume /dev/sda1, you specify the partition as "1". + Similarly, the volume partition for /dev/sda is "0" (or you can leave the property empty). + format: int32 + type: integer + readOnly: + description: |- + readOnly value true will force the readOnly setting in VolumeMounts. + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + type: boolean + volumeID: + description: |- + volumeID is unique ID of the persistent disk resource in AWS (Amazon EBS volume). + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + type: string + required: + - volumeID + type: object + azureDisk: + description: |- + azureDisk represents an Azure Data Disk mount on the host and bind mount to the pod. + Deprecated: AzureDisk is deprecated. All operations for the in-tree azureDisk type + are redirected to the disk.csi.azure.com CSI driver. + properties: + cachingMode: + description: 'cachingMode is the Host Caching mode: + None, Read Only, Read Write.' + type: string + diskName: + description: diskName is the Name of the data disk + in the blob storage + type: string + diskURI: + description: diskURI is the URI of data disk in + the blob storage + type: string + fsType: + default: ext4 + description: |- + fsType is Filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + kind: + description: 'kind expected values are Shared: multiple + blob disks per storage account Dedicated: single + blob disk per storage account Managed: azure + managed data disk (only in managed availability + set). defaults to shared' + type: string + readOnly: + default: false + description: |- + readOnly Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + required: + - diskName + - diskURI + type: object + azureFile: + description: |- + azureFile represents an Azure File Service mount on the host and bind mount to the pod. + Deprecated: AzureFile is deprecated. All operations for the in-tree azureFile type + are redirected to the file.csi.azure.com CSI driver. + properties: + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretName: + description: secretName is the name of secret that + contains Azure Storage Account Name and Key + type: string + shareName: + description: shareName is the azure share Name + type: string + required: + - secretName + - shareName + type: object + cephfs: + description: |- + cephFS represents a Ceph FS mount on the host that shares a pod's lifetime. + Deprecated: CephFS is deprecated and the in-tree cephfs type is no longer supported. + properties: + monitors: + description: |- + monitors is Required: Monitors is a collection of Ceph monitors + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + items: + type: string + type: array + x-kubernetes-list-type: atomic + path: + description: 'path is Optional: Used as the mounted + root, rather than the full Ceph tree, default + is /' + type: string + readOnly: + description: |- + readOnly is Optional: Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + type: boolean + secretFile: + description: |- + secretFile is Optional: SecretFile is the path to key ring for User, default is /etc/ceph/user.secret + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + type: string + secretRef: + description: |- + secretRef is Optional: SecretRef is reference to the authentication secret for User, default is empty. + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + user: + description: |- + user is optional: User is the rados user name, default is admin + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + type: string + required: + - monitors + type: object + cinder: + description: |- + cinder represents a cinder volume attached and mounted on kubelets host machine. + Deprecated: Cinder is deprecated. All operations for the in-tree cinder type + are redirected to the cinder.csi.openstack.org CSI driver. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + type: string + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + type: boolean + secretRef: + description: |- + secretRef is optional: points to a secret object containing parameters used to connect + to OpenStack. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + volumeID: + description: |- + volumeID used to identify the volume in cinder. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + type: string + required: + - volumeID + type: object + configMap: + description: configMap represents a configMap that should + populate this volume + properties: + defaultMode: + description: |- + defaultMode is optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + ConfigMap will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the ConfigMap, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key to a path within + a volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: optional specify whether the ConfigMap + or its keys must be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + csi: + description: csi (Container Storage Interface) represents + ephemeral storage that is handled by certain external + CSI drivers. + properties: + driver: + description: |- + driver is the name of the CSI driver that handles this volume. + Consult with your admin for the correct name as registered in the cluster. + type: string + fsType: + description: |- + fsType to mount. Ex. "ext4", "xfs", "ntfs". + If not provided, the empty value is passed to the associated CSI driver + which will determine the default filesystem to apply. + type: string + nodePublishSecretRef: + description: |- + nodePublishSecretRef is a reference to the secret object containing + sensitive information to pass to the CSI driver to complete the CSI + NodePublishVolume and NodeUnpublishVolume calls. + This field is optional, and may be empty if no secret is required. If the + secret object contains more than one secret, all secret references are passed. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + readOnly: + description: |- + readOnly specifies a read-only configuration for the volume. + Defaults to false (read/write). + type: boolean + volumeAttributes: + additionalProperties: + type: string + description: |- + volumeAttributes stores driver-specific properties that are passed to the CSI + driver. Consult your driver's documentation for supported values. + type: object + required: + - driver + type: object + downwardAPI: + description: downwardAPI represents downward API about + the pod that should populate this volume + properties: + defaultMode: + description: |- + Optional: mode bits to use on created files by default. Must be a + Optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + items: + description: Items is a list of downward API volume + file + items: + description: DownwardAPIVolumeFile represents + information to create the file containing the + pod field + properties: + fieldRef: + description: 'Required: Selects a field of + the pod: only annotations, labels, name, + namespace and uid are supported.' + properties: + apiVersion: + description: Version of the schema the + FieldPath is written in terms of, defaults + to "v1". + type: string + fieldPath: + description: Path of the field to select + in the specified API version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + description: |- + Optional: mode bits used to set permissions on this file, must be an octal value + between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: 'Required: Path is the relative + path name of the file to be created. Must + not be absolute or contain the ''..'' path. + Must be utf-8 encoded. The first item of + the relative path must not start with ''..''' + type: string + resourceFieldRef: + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, requests.cpu and requests.memory) are currently supported. + properties: + containerName: + description: 'Container name: required + for volumes, optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format + of the exposed resources, defaults to + "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + required: + - path + type: object + type: array + x-kubernetes-list-type: atomic + type: object + emptyDir: + description: |- + emptyDir represents a temporary directory that shares a pod's lifetime. + More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir + properties: + medium: + description: |- + medium represents what type of storage medium should back this directory. + The default is "" which means to use the node's default medium. + Must be an empty string (default) or Memory. + More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir + type: string + sizeLimit: + anyOf: + - type: integer + - type: string + description: |- + sizeLimit is the total amount of local storage required for this EmptyDir volume. + The size limit is also applicable for memory medium. + The maximum usage on memory medium EmptyDir would be the minimum value between + the SizeLimit specified here and the sum of memory limits of all containers in a pod. + The default is nil which means that the limit is undefined. + More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + ephemeral: + description: |- + ephemeral represents a volume that is handled by a cluster storage driver. + The volume's lifecycle is tied to the pod that defines it - it will be created before the pod starts, + and deleted when the pod is removed. + + Use this if: + a) the volume is only needed while the pod runs, + b) features of normal volumes like restoring from snapshot or capacity + tracking are needed, + c) the storage driver is specified through a storage class, and + d) the storage driver supports dynamic volume provisioning through + a PersistentVolumeClaim (see EphemeralVolumeSource for more + information on the connection between this volume type + and PersistentVolumeClaim). + + Use PersistentVolumeClaim or one of the vendor-specific + APIs for volumes that persist for longer than the lifecycle + of an individual pod. + + Use CSI for light-weight local ephemeral volumes if the CSI driver is meant to + be used that way - see the documentation of the driver for + more information. + + A pod can use both types of ephemeral volumes and + persistent volumes at the same time. + properties: + volumeClaimTemplate: + description: |- + Will be used to create a stand-alone PVC to provision the volume. + The pod in which this EphemeralVolumeSource is embedded will be the + owner of the PVC, i.e. the PVC will be deleted together with the + pod. The name of the PVC will be `-` where + `` is the name from the `PodSpec.Volumes` array + entry. Pod validation will reject the pod if the concatenated name + is not valid for a PVC (for example, too long). + + An existing PVC with that name that is not owned by the pod + will *not* be used for the pod to avoid using an unrelated + volume by mistake. Starting the pod is then blocked until + the unrelated PVC is removed. If such a pre-created PVC is + meant to be used by the pod, the PVC has to updated with an + owner reference to the pod once the pod exists. Normally + this should not be necessary, but it may be useful when + manually reconstructing a broken cluster. + + This field is read-only and no changes will be made by Kubernetes + to the PVC after it has been created. + + Required, must not be nil. + properties: + metadata: + description: |- + May contain labels and annotations that will be copied into the PVC + when creating it. No other fields are allowed and will be rejected during + validation. + type: object + spec: + description: |- + The specification for the PersistentVolumeClaim. The entire content is + copied unchanged into the PVC that gets created from this + template. The same fields as in a PersistentVolumeClaim + are also valid here. + properties: + accessModes: + description: |- + accessModes contains the desired access modes the volume should have. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1 + items: + type: string + type: array + x-kubernetes-list-type: atomic + dataSource: + description: |- + dataSource field can be used to specify either: + * An existing VolumeSnapshot object (snapshot.storage.k8s.io/VolumeSnapshot) + * An existing PVC (PersistentVolumeClaim) + If the provisioner or an external controller can support the specified data source, + it will create a new volume based on the contents of the specified data source. + When the AnyVolumeDataSource feature gate is enabled, dataSource contents will be copied to dataSourceRef, + and dataSourceRef contents will be copied to dataSource when dataSourceRef.namespace is not specified. + If the namespace is specified, then dataSourceRef will not be copied to dataSource. + properties: + apiGroup: + description: |- + APIGroup is the group for the resource being referenced. + If APIGroup is not specified, the specified Kind must be in the core API group. + For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource + being referenced + type: string + name: + description: Name is the name of resource + being referenced + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + dataSourceRef: + description: |- + dataSourceRef specifies the object from which to populate the volume with data, if a non-empty + volume is desired. This may be any object from a non-empty API group (non + core object) or a PersistentVolumeClaim object. + When this field is specified, volume binding will only succeed if the type of + the specified object matches some installed volume populator or dynamic + provisioner. + This field will replace the functionality of the dataSource field and as such + if both fields are non-empty, they must have the same value. For backwards + compatibility, when namespace isn't specified in dataSourceRef, + both fields (dataSource and dataSourceRef) will be set to the same + value automatically if one of them is empty and the other is non-empty. + When namespace is specified in dataSourceRef, + dataSource isn't set to the same value and must be empty. + There are three important differences between dataSource and dataSourceRef: + * While dataSource only allows two specific types of objects, dataSourceRef + allows any non-core object, as well as PersistentVolumeClaim objects. + * While dataSource ignores disallowed values (dropping them), dataSourceRef + preserves all values, and generates an error if a disallowed value is + specified. + * While dataSource only allows local objects, dataSourceRef allows objects + in any namespaces. + (Beta) Using this field requires the AnyVolumeDataSource feature gate to be enabled. + (Alpha) Using the namespace field of dataSourceRef requires the CrossNamespaceVolumeDataSource feature gate to be enabled. + properties: + apiGroup: + description: |- + APIGroup is the group for the resource being referenced. + If APIGroup is not specified, the specified Kind must be in the core API group. + For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource + being referenced + type: string + name: + description: Name is the name of resource + being referenced + type: string + namespace: + description: |- + Namespace is the namespace of resource being referenced + Note that when a namespace is specified, a gateway.networking.k8s.io/ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. + (Alpha) This field requires the CrossNamespaceVolumeDataSource feature gate to be enabled. + type: string + required: + - kind + - name + type: object + resources: + description: |- + resources represents the minimum resources the volume should have. + Users are allowed to specify resource requirements + that are lower than previous value but must still be higher than capacity recorded in the + status field of the claim. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + selector: + description: selector is a label query over + volumes to consider for binding. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The + requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + storageClassName: + description: |- + storageClassName is the name of the StorageClass required by the claim. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1 + type: string + volumeAttributesClassName: + description: |- + volumeAttributesClassName may be used to set the VolumeAttributesClass used by this claim. + If specified, the CSI driver will create or update the volume with the attributes defined + in the corresponding VolumeAttributesClass. This has a different purpose than storageClassName, + it can be changed after the claim is created. An empty string or nil value indicates that no + VolumeAttributesClass will be applied to the claim. If the claim enters an Infeasible error state, + this field can be reset to its previous value (including nil) to cancel the modification. + If the resource referred to by volumeAttributesClass does not exist, this PersistentVolumeClaim will be + set to a Pending state, as reflected by the modifyVolumeStatus field, until such as a resource + exists. + More info: https://kubernetes.io/docs/concepts/storage/volume-attributes-classes/ + type: string + volumeMode: + description: |- + volumeMode defines what type of volume is required by the claim. + Value of Filesystem is implied when not included in claim spec. + type: string + volumeName: + description: volumeName is the binding reference + to the PersistentVolume backing this claim. + type: string + type: object + required: + - spec + type: object + type: object + fc: + description: fc represents a Fibre Channel resource + that is attached to a kubelet's host machine and then + exposed to the pod. + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + lun: + description: 'lun is Optional: FC target lun number' + format: int32 + type: integer + readOnly: + description: |- + readOnly is Optional: Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + targetWWNs: + description: 'targetWWNs is Optional: FC target + worldwide names (WWNs)' + items: + type: string + type: array + x-kubernetes-list-type: atomic + wwids: + description: |- + wwids Optional: FC volume world wide identifiers (wwids) + Either wwids or combination of targetWWNs and lun must be set, but not both simultaneously. + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + flexVolume: + description: |- + flexVolume represents a generic volume resource that is + provisioned/attached using an exec based plugin. + Deprecated: FlexVolume is deprecated. Consider using a CSIDriver instead. + properties: + driver: + description: driver is the name of the driver to + use for this volume. + type: string + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". The default filesystem depends on FlexVolume script. + type: string + options: + additionalProperties: + type: string + description: 'options is Optional: this field holds + extra command options if any.' + type: object + readOnly: + description: |- + readOnly is Optional: defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: |- + secretRef is Optional: secretRef is reference to the secret object containing + sensitive information to pass to the plugin scripts. This may be + empty if no secret object is specified. If the secret object + contains more than one secret, all secrets are passed to the plugin + scripts. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + required: + - driver + type: object + flocker: + description: |- + flocker represents a Flocker volume attached to a kubelet's host machine. This depends on the Flocker control service being running. + Deprecated: Flocker is deprecated and the in-tree flocker type is no longer supported. + properties: + datasetName: + description: |- + datasetName is Name of the dataset stored as metadata -> name on the dataset for Flocker + should be considered as deprecated + type: string + datasetUUID: + description: datasetUUID is the UUID of the dataset. + This is unique identifier of a Flocker dataset + type: string + type: object + gcePersistentDisk: + description: |- + gcePersistentDisk represents a GCE Disk resource that is attached to a + kubelet's host machine and then exposed to the pod. + Deprecated: GCEPersistentDisk is deprecated. All operations for the in-tree + gcePersistentDisk type are redirected to the pd.csi.storage.gke.io CSI driver. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + properties: + fsType: + description: |- + fsType is filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + type: string + partition: + description: |- + partition is the partition in the volume that you want to mount. + If omitted, the default is to mount by volume name. + Examples: For volume /dev/sda1, you specify the partition as "1". + Similarly, the volume partition for /dev/sda is "0" (or you can leave the property empty). + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + format: int32 + type: integer + pdName: + description: |- + pdName is unique name of the PD resource in GCE. Used to identify the disk in GCE. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + type: string + readOnly: + description: |- + readOnly here will force the ReadOnly setting in VolumeMounts. + Defaults to false. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + type: boolean + required: + - pdName + type: object + gitRepo: + description: |- + gitRepo represents a git repository at a particular revision. + Deprecated: GitRepo is deprecated. To provision a container with a git repo, mount an + EmptyDir into an InitContainer that clones the repo using git, then mount the EmptyDir + into the Pod's container. + properties: + directory: + description: |- + directory is the target directory name. + Must not contain or start with '..'. If '.' is supplied, the volume directory will be the + git repository. Otherwise, if specified, the volume will contain the git repository in + the subdirectory with the given name. + type: string + repository: + description: repository is the URL + type: string + revision: + description: revision is the commit hash for the + specified revision. + type: string + required: + - repository + type: object + glusterfs: + description: |- + glusterfs represents a Glusterfs mount on the host that shares a pod's lifetime. + Deprecated: Glusterfs is deprecated and the in-tree glusterfs type is no longer supported. + properties: + endpoints: + description: endpoints is the endpoint name that + details Glusterfs topology. + type: string + path: + description: |- + path is the Glusterfs volume path. + More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod + type: string + readOnly: + description: |- + readOnly here will force the Glusterfs volume to be mounted with read-only permissions. + Defaults to false. + More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod + type: boolean + required: + - endpoints + - path + type: object + hostPath: + description: |- + hostPath represents a pre-existing file or directory on the host + machine that is directly exposed to the container. This is generally + used for system agents or other privileged things that are allowed + to see the host machine. Most containers will NOT need this. + More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + properties: + path: + description: |- + path of the directory on the host. + If the path is a symlink, it will follow the link to the real path. + More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + type: string + type: + description: |- + type for HostPath Volume + Defaults to "" + More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + type: string + required: + - path + type: object + image: + description: |- + image represents an OCI object (a container image or artifact) pulled and mounted on the kubelet's host machine. + The volume is resolved at pod startup depending on which PullPolicy value is provided: + + - Always: the kubelet always attempts to pull the reference. Container creation will fail If the pull fails. + - Never: the kubelet never pulls the reference and only uses a local image or artifact. Container creation will fail if the reference isn't present. + - IfNotPresent: the kubelet pulls if the reference isn't already present on disk. Container creation will fail if the reference isn't present and the pull fails. + + The volume gets re-resolved if the pod gets deleted and recreated, which means that new remote content will become available on pod recreation. + A failure to resolve or pull the image during pod startup will block containers from starting and may add significant latency. Failures will be retried using normal volume backoff and will be reported on the pod reason and message. + The types of objects that may be mounted by this volume are defined by the container runtime implementation on a host machine and at minimum must include all valid types supported by the container image field. + The OCI object gets mounted in a single directory (spec.containers[*].volumeMounts.mountPath) by merging the manifest layers in the same way as for container images. + The volume will be mounted read-only (ro) and non-executable files (noexec). + Sub path mounts for containers are not supported (spec.containers[*].volumeMounts.subpath) before 1.33. + The field spec.securityContext.fsGroupChangePolicy has no effect on this volume type. + properties: + pullPolicy: + description: |- + Policy for pulling OCI objects. Possible values are: + Always: the kubelet always attempts to pull the reference. Container creation will fail If the pull fails. + Never: the kubelet never pulls the reference and only uses a local image or artifact. Container creation will fail if the reference isn't present. + IfNotPresent: the kubelet pulls if the reference isn't already present on disk. Container creation will fail if the reference isn't present and the pull fails. + Defaults to Always if :latest tag is specified, or IfNotPresent otherwise. + type: string + reference: + description: |- + Required: Image or artifact reference to be used. + Behaves in the same way as pod.spec.containers[*].image. + Pull secrets will be assembled in the same way as for the container image by looking up node credentials, SA image pull secrets, and pod spec image pull secrets. + More info: https://kubernetes.io/docs/concepts/containers/images + This field is optional to allow higher level config management to default or override + container images in workload controllers like Deployments and StatefulSets. + type: string + type: object + iscsi: + description: |- + iscsi represents an ISCSI Disk resource that is attached to a + kubelet's host machine and then exposed to the pod. + More info: https://kubernetes.io/docs/concepts/storage/volumes/#iscsi + properties: + chapAuthDiscovery: + description: chapAuthDiscovery defines whether support + iSCSI Discovery CHAP authentication + type: boolean + chapAuthSession: + description: chapAuthSession defines whether support + iSCSI Session CHAP authentication + type: boolean + fsType: + description: |- + fsType is the filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#iscsi + type: string + initiatorName: + description: |- + initiatorName is the custom iSCSI Initiator Name. + If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface + : will be created for the connection. + type: string + iqn: + description: iqn is the target iSCSI Qualified Name. + type: string + iscsiInterface: + default: default + description: |- + iscsiInterface is the interface Name that uses an iSCSI transport. + Defaults to 'default' (tcp). + type: string + lun: + description: lun represents iSCSI Target Lun number. + format: int32 + type: integer + portals: + description: |- + portals is the iSCSI Target Portal List. The portal is either an IP or ip_addr:port if the port + is other than default (typically TCP ports 860 and 3260). + items: + type: string + type: array + x-kubernetes-list-type: atomic + readOnly: + description: |- + readOnly here will force the ReadOnly setting in VolumeMounts. + Defaults to false. + type: boolean + secretRef: + description: secretRef is the CHAP Secret for iSCSI + target and initiator authentication + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + targetPortal: + description: |- + targetPortal is iSCSI Target Portal. The Portal is either an IP or ip_addr:port if the port + is other than default (typically TCP ports 860 and 3260). + type: string + required: + - iqn + - lun + - targetPortal + type: object + name: + description: |- + name of the volume. + Must be a DNS_LABEL and unique within the pod. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + nfs: + description: |- + nfs represents an NFS mount on the host that shares a pod's lifetime + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + properties: + path: + description: |- + path that is exported by the NFS server. + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + type: string + readOnly: + description: |- + readOnly here will force the NFS export to be mounted with read-only permissions. + Defaults to false. + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + type: boolean + server: + description: |- + server is the hostname or IP address of the NFS server. + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + type: string + required: + - path + - server + type: object + persistentVolumeClaim: + description: |- + persistentVolumeClaimVolumeSource represents a reference to a + PersistentVolumeClaim in the same namespace. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims + properties: + claimName: + description: |- + claimName is the name of a PersistentVolumeClaim in the same namespace as the pod using this volume. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims + type: string + readOnly: + description: |- + readOnly Will force the ReadOnly setting in VolumeMounts. + Default false. + type: boolean + required: + - claimName + type: object + photonPersistentDisk: + description: |- + photonPersistentDisk represents a PhotonController persistent disk attached and mounted on kubelets host machine. + Deprecated: PhotonPersistentDisk is deprecated and the in-tree photonPersistentDisk type is no longer supported. + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + pdID: + description: pdID is the ID that identifies Photon + Controller persistent disk + type: string + required: + - pdID + type: object + portworxVolume: + description: |- + portworxVolume represents a portworx volume attached and mounted on kubelets host machine. + Deprecated: PortworxVolume is deprecated. All operations for the in-tree portworxVolume type + are redirected to the pxd.portworx.com CSI driver when the CSIMigrationPortworx feature-gate + is on. + properties: + fsType: + description: |- + fSType represents the filesystem type to mount + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs". Implicitly inferred to be "ext4" if unspecified. + type: string + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + volumeID: + description: volumeID uniquely identifies a Portworx + volume + type: string + required: + - volumeID + type: object + projected: + description: projected items for all in one resources + secrets, configmaps, and downward API + properties: + defaultMode: + description: |- + defaultMode are the mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + sources: + description: |- + sources is the list of volume projections. Each entry in this list + handles one source. + items: + description: |- + Projection that may be projected along with other supported volume types. + Exactly one of these fields must be set. + properties: + clusterTrustBundle: + description: |- + ClusterTrustBundle allows a pod to access the `.spec.trustBundle` field + of ClusterTrustBundle objects in an auto-updating file. + + Alpha, gated by the ClusterTrustBundleProjection feature gate. + + ClusterTrustBundle objects can either be selected by name, or by the + combination of signer name and a label selector. + + Kubelet performs aggressive normalization of the PEM contents written + into the pod filesystem. Esoteric PEM features such as inter-block + comments and block headers are stripped. Certificates are deduplicated. + The ordering of certificates within the file is arbitrary, and Kubelet + may change the order over time. + properties: + labelSelector: + description: |- + Select all ClusterTrustBundles that match this label selector. Only has + effect if signerName is set. Mutually-exclusive with name. If unset, + interpreted as "match nothing". If set but empty, interpreted as "match + everything". + properties: + matchExpressions: + description: matchExpressions is a + list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + name: + description: |- + Select a single ClusterTrustBundle by object name. Mutually-exclusive + with signerName and labelSelector. + type: string + optional: + description: |- + If true, don't block pod startup if the referenced ClusterTrustBundle(s) + aren't available. If using name, then the named ClusterTrustBundle is + allowed not to exist. If using signerName, then the combination of + signerName and labelSelector is allowed to match zero + ClusterTrustBundles. + type: boolean + path: + description: Relative path from the volume + root to write the bundle. + type: string + signerName: + description: |- + Select all ClusterTrustBundles that match this signer name. + Mutually-exclusive with name. The contents of all selected + ClusterTrustBundles will be unified and deduplicated. + type: string + required: + - path + type: object + configMap: + description: configMap information about the + configMap data to project + properties: + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + ConfigMap will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the ConfigMap, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key to a + path within a volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: optional specify whether + the ConfigMap or its keys must be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + downwardAPI: + description: downwardAPI information about + the downwardAPI data to project + properties: + items: + description: Items is a list of DownwardAPIVolume + file + items: + description: DownwardAPIVolumeFile represents + information to create the file containing + the pod field + properties: + fieldRef: + description: 'Required: Selects + a field of the pod: only annotations, + labels, name, namespace and uid + are supported.' + properties: + apiVersion: + description: Version of the + schema the FieldPath is written + in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field + to select in the specified + API version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + description: |- + Optional: mode bits used to set permissions on this file, must be an octal value + between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: 'Required: Path is the + relative path name of the file + to be created. Must not be absolute + or contain the ''..'' path. Must + be utf-8 encoded. The first item + of the relative path must not + start with ''..''' + type: string + resourceFieldRef: + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, requests.cpu and requests.memory) are currently supported. + properties: + containerName: + description: 'Container name: + required for volumes, optional + for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output + format of the exposed resources, + defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource + to select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + required: + - path + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podCertificate: + description: |- + Projects an auto-rotating credential bundle (private key and certificate + chain) that the pod can use either as a TLS client or server. + + Kubelet generates a private key and uses it to send a + PodCertificateRequest to the named signer. Once the signer approves the + request and issues a certificate chain, Kubelet writes the key and + certificate chain to the pod filesystem. The pod does not start until + certificates have been issued for each podCertificate projected volume + source in its spec. + + Kubelet will begin trying to rotate the certificate at the time indicated + by the signer using the PodCertificateRequest.Status.BeginRefreshAt + timestamp. + + Kubelet can write a single file, indicated by the credentialBundlePath + field, or separate files, indicated by the keyPath and + certificateChainPath fields. + + The credential bundle is a single file in PEM format. The first PEM + entry is the private key (in PKCS#8 format), and the remaining PEM + entries are the certificate chain issued by the signer (typically, + signers will return their certificate chain in leaf-to-root order). + + Prefer using the credential bundle format, since your application code + can read it atomically. If you use keyPath and certificateChainPath, + your application must make two separate file reads. If these coincide + with a certificate rotation, it is possible that the private key and leaf + certificate you read may not correspond to each other. Your application + will need to check for this condition, and re-read until they are + consistent. + + The named signer controls chooses the format of the certificate it + issues; consult the signer implementation's documentation to learn how to + use the certificates it issues. + properties: + certificateChainPath: + description: |- + Write the certificate chain at this path in the projected volume. + + Most applications should use credentialBundlePath. When using keyPath + and certificateChainPath, your application needs to check that the key + and leaf certificate are consistent, because it is possible to read the + files mid-rotation. + type: string + credentialBundlePath: + description: |- + Write the credential bundle at this path in the projected volume. + + The credential bundle is a single file that contains multiple PEM blocks. + The first PEM block is a PRIVATE KEY block, containing a PKCS#8 private + key. + + The remaining blocks are CERTIFICATE blocks, containing the issued + certificate chain from the signer (leaf and any intermediates). + + Using credentialBundlePath lets your Pod's application code make a single + atomic read that retrieves a consistent key and certificate chain. If you + project them to separate files, your application code will need to + additionally check that the leaf certificate was issued to the key. + type: string + keyPath: + description: |- + Write the key at this path in the projected volume. + + Most applications should use credentialBundlePath. When using keyPath + and certificateChainPath, your application needs to check that the key + and leaf certificate are consistent, because it is possible to read the + files mid-rotation. + type: string + keyType: + description: |- + The type of keypair Kubelet will generate for the pod. + + Valid values are "RSA3072", "RSA4096", "ECDSAP256", "ECDSAP384", + "ECDSAP521", and "ED25519". + type: string + maxExpirationSeconds: + description: |- + maxExpirationSeconds is the maximum lifetime permitted for the + certificate. + + Kubelet copies this value verbatim into the PodCertificateRequests it + generates for this projection. + + If omitted, kube-apiserver will set it to 86400(24 hours). kube-apiserver + will reject values shorter than 3600 (1 hour). The maximum allowable + value is 7862400 (91 days). + + The signer implementation is then free to issue a certificate with any + lifetime *shorter* than MaxExpirationSeconds, but no shorter than 3600 + seconds (1 hour). This constraint is enforced by kube-apiserver. + `kubernetes.io` signers will never issue certificates with a lifetime + longer than 24 hours. + format: int32 + type: integer + signerName: + description: Kubelet's generated CSRs + will be addressed to this signer. + type: string + userAnnotations: + additionalProperties: + type: string + description: |- + userAnnotations allow pod authors to pass additional information to + the signer implementation. Kubernetes does not restrict or validate this + metadata in any way. + + These values are copied verbatim into the `spec.unverifiedUserAnnotations` field of + the PodCertificateRequest objects that Kubelet creates. + + Entries are subject to the same validation as object metadata annotations, + with the addition that all keys must be domain-prefixed. No restrictions + are placed on values, except an overall size limitation on the entire field. + + Signers should document the keys and values they support. Signers should + deny requests that contain keys they do not recognize. + type: object + required: + - keyType + - signerName + type: object + secret: + description: secret information about the + secret data to project + properties: + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + Secret will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the Secret, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key to a + path within a volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: optional field specify whether + the Secret or its key must be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + serviceAccountToken: + description: serviceAccountToken is information + about the serviceAccountToken data to project + properties: + audience: + description: |- + audience is the intended audience of the token. A recipient of a token + must identify itself with an identifier specified in the audience of the + token, and otherwise should reject the token. The audience defaults to the + identifier of the apiserver. + type: string + expirationSeconds: + description: |- + expirationSeconds is the requested duration of validity of the service + account token. As the token approaches expiration, the kubelet volume + plugin will proactively rotate the service account token. The kubelet will + start trying to rotate the token if the token is older than 80 percent of + its time to live or if the token is older than 24 hours.Defaults to 1 hour + and must be at least 10 minutes. + format: int64 + type: integer + path: + description: |- + path is the path relative to the mount point of the file to project the + token into. + type: string + required: + - path + type: object + type: object + type: array + x-kubernetes-list-type: atomic + type: object + quobyte: + description: |- + quobyte represents a Quobyte mount on the host that shares a pod's lifetime. + Deprecated: Quobyte is deprecated and the in-tree quobyte type is no longer supported. + properties: + group: + description: |- + group to map volume access to + Default is no group + type: string + readOnly: + description: |- + readOnly here will force the Quobyte volume to be mounted with read-only permissions. + Defaults to false. + type: boolean + registry: + description: |- + registry represents a single or multiple Quobyte Registry services + specified as a string as host:port pair (multiple entries are separated with commas) + which acts as the central registry for volumes + type: string + tenant: + description: |- + tenant owning the given Quobyte volume in the Backend + Used with dynamically provisioned Quobyte volumes, value is set by the plugin + type: string + user: + description: |- + user to map volume access to + Defaults to serivceaccount user + type: string + volume: + description: volume is a string that references + an already created Quobyte volume by name. + type: string + required: + - registry + - volume + type: object + rbd: + description: |- + rbd represents a Rados Block Device mount on the host that shares a pod's lifetime. + Deprecated: RBD is deprecated and the in-tree rbd type is no longer supported. + properties: + fsType: + description: |- + fsType is the filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#rbd + type: string + image: + description: |- + image is the rados image name. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + keyring: + default: /etc/ceph/keyring + description: |- + keyring is the path to key ring for RBDUser. + Default is /etc/ceph/keyring. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + monitors: + description: |- + monitors is a collection of Ceph monitors. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + items: + type: string + type: array + x-kubernetes-list-type: atomic + pool: + default: rbd + description: |- + pool is the rados pool name. + Default is rbd. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + readOnly: + description: |- + readOnly here will force the ReadOnly setting in VolumeMounts. + Defaults to false. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: boolean + secretRef: + description: |- + secretRef is name of the authentication secret for RBDUser. If provided + overrides keyring. + Default is nil. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + user: + default: admin + description: |- + user is the rados user name. + Default is admin. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + required: + - image + - monitors + type: object + scaleIO: + description: |- + scaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes. + Deprecated: ScaleIO is deprecated and the in-tree scaleIO type is no longer supported. + properties: + fsType: + default: xfs + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". + Default is "xfs". + type: string + gateway: + description: gateway is the host address of the + ScaleIO API Gateway. + type: string + protectionDomain: + description: protectionDomain is the name of the + ScaleIO Protection Domain for the configured storage. + type: string + readOnly: + description: |- + readOnly Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: |- + secretRef references to the secret for ScaleIO user and other + sensitive information. If this is not provided, Login operation will fail. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + sslEnabled: + description: sslEnabled Flag enable/disable SSL + communication with Gateway, default false + type: boolean + storageMode: + default: ThinProvisioned + description: |- + storageMode indicates whether the storage for a volume should be ThickProvisioned or ThinProvisioned. + Default is ThinProvisioned. + type: string + storagePool: + description: storagePool is the ScaleIO Storage + Pool associated with the protection domain. + type: string + system: + description: system is the name of the storage system + as configured in ScaleIO. + type: string + volumeName: + description: |- + volumeName is the name of a volume already created in the ScaleIO system + that is associated with this volume source. + type: string + required: + - gateway + - secretRef + - system + type: object + secret: + description: |- + secret represents a secret that should populate this volume. + More info: https://kubernetes.io/docs/concepts/storage/volumes#secret + properties: + defaultMode: + description: |- + defaultMode is Optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values + for mode bits. Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + items: + description: |- + items If unspecified, each key-value pair in the Data field of the referenced + Secret will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the Secret, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key to a path within + a volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + optional: + description: optional field specify whether the + Secret or its keys must be defined + type: boolean + secretName: + description: |- + secretName is the name of the secret in the pod's namespace to use. + More info: https://kubernetes.io/docs/concepts/storage/volumes#secret + type: string + type: object + storageos: + description: |- + storageOS represents a StorageOS volume attached and mounted on Kubernetes nodes. + Deprecated: StorageOS is deprecated and the in-tree storageos type is no longer supported. + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: |- + secretRef specifies the secret to use for obtaining the StorageOS API + credentials. If not specified, default values will be attempted. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + volumeName: + description: |- + volumeName is the human-readable name of the StorageOS volume. Volume + names are only unique within a namespace. + type: string + volumeNamespace: + description: |- + volumeNamespace specifies the scope of the volume within StorageOS. If no + namespace is specified then the Pod's namespace will be used. This allows the + Kubernetes name scoping to be mirrored within StorageOS for tighter integration. + Set VolumeName to any name to override the default behaviour. + Set to "default" if you are not using namespaces within StorageOS. + Namespaces that do not pre-exist within StorageOS will be created. + type: string + type: object + vsphereVolume: + description: |- + vsphereVolume represents a vSphere volume attached and mounted on kubelets host machine. + Deprecated: VsphereVolume is deprecated. All operations for the in-tree vsphereVolume type + are redirected to the csi.vsphere.vmware.com CSI driver. + properties: + fsType: + description: |- + fsType is filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + storagePolicyID: + description: storagePolicyID is the storage Policy + Based Management (SPBM) profile ID associated + with the StoragePolicyName. + type: string + storagePolicyName: + description: storagePolicyName is the storage Policy + Based Management (SPBM) profile name. + type: string + volumePath: + description: volumePath is the path that identifies + vSphere volume vmdk + type: string + required: + - volumePath + type: object + required: + - name + type: object + type: array + type: object + x-kubernetes-validations: + - message: serviceAccountName and serviceAccountConfig are mutually + exclusive + rule: '!(has(self.serviceAccountName) && has(self.serviceAccountConfig))' + type: object + declarative: + properties: + a2aConfig: + description: |- + A2AConfig instantiates an A2A server for this agent, + served on the HTTP port of the kagent kubernetes + controller (default 8083). + The A2A server URL will be served at + :8083/api/a2a// + Read more about the A2A protocol here: https://github.com/google/A2A + properties: + skills: + items: + description: AgentSkill describes a specific capability + or function of the agent. + properties: + description: + description: Description is an optional detailed description + of the skill. + type: string + examples: + description: Examples are optional usage examples. + items: + type: string + type: array + id: + description: ID is the unique identifier for the skill. + type: string + inputModes: + description: InputModes are the supported input data + modes/types. + items: + type: string + type: array + name: + description: Name is the human-readable name of the + skill. + type: string + outputModes: + description: OutputModes are the supported output data + modes/types. + items: + type: string + type: array + tags: + description: Tags are optional tags for categorization. + items: + type: string + type: array + required: + - id + - name + - tags + type: object + minItems: 1 + type: array + type: object + context: + description: |- + Context configures context management for this agent. + This includes event compaction (compression) and context caching. + properties: + compaction: + description: |- + Compaction configures event history compaction. + When enabled, older events in the conversation are compacted (compressed/summarized) + to reduce context size while preserving key information. + properties: + compactionInterval: + default: 5 + description: The number of *new* user-initiated invocations + that, once fully represented in the session's events, + will trigger a compaction. + minimum: 1 + type: integer + eventRetentionSize: + description: EventRetentionSize is the number of most + recent events to always retain. + type: integer + overlapSize: + default: 2 + description: The number of preceding invocations to include + from the end of the last compacted range. This creates + an overlap between consecutive compacted summaries, + maintaining context. + minimum: 0 + type: integer + summarizer: + description: |- + Summarizer configures an LLM-based summarizer for event compaction. + If not specified, compacted events are dropped from the context without summarization. + properties: + modelConfig: + description: |- + ModelConfig is the name of a ModelConfig resource to use for summarization. + Must be in the same namespace as the Agent. + If not specified, uses the agent's own model. + type: string + promptTemplate: + description: |- + PromptTemplate is a custom prompt template for the summarizer. + See the ADK LlmEventSummarizer for template details: + https://github.com/google/adk-python/blob/main/src/google/adk/apps/llm_event_summarizer.py + type: string + type: object + tokenThreshold: + description: |- + Post-invocation token threshold trigger. If set, ADK will attempt a post-invocation compaction when the most recently + observed prompt token count meets or exceeds this threshold. + type: integer + type: object + type: object + deployment: + properties: + affinity: + description: Affinity is a group of affinity scheduling rules. + properties: + nodeAffinity: + description: Describes node affinity scheduling rules + for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: A node selector term, associated + with the corresponding weight. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the + selector applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the + selector applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: Weight associated with matching + the corresponding nodeSelectorTerm, in the + range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: Required. A list of node selector + terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the + selector applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the + selector applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: Describes pod affinity scheduling rules (e.g. + co-locate this pod in the same node, zone, etc. as some + other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, + associated with the corresponding weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The + requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The + requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: Describes pod anti-affinity scheduling rules + (e.g. avoid putting this pod in the same node, zone, + etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, + associated with the corresponding weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The + requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The + requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + annotations: + additionalProperties: + type: string + type: object + env: + items: + description: EnvVar represents an environment variable present + in a Container. + properties: + name: + description: |- + Name of the environment variable. + May consist of any printable ASCII characters except '='. + type: string + value: + description: |- + Variable references $(VAR_NAME) are expanded + using the previously defined environment variables in the container and + any service environment variables. If a variable cannot be resolved, + the reference in the input string will be unchanged. Double $$ are reduced + to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. + "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". + Escaped references will never be expanded, regardless of whether the variable + exists or not. + Defaults to "". + type: string + valueFrom: + description: Source for the environment variable's value. + Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the ConfigMap or + its key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + description: |- + Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['']`, `metadata.annotations['']`, + spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs. + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in + the specified API version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + fileKeyRef: + description: |- + FileKeyRef selects a key of the env file. + Requires the EnvFiles feature gate to be enabled. + properties: + key: + description: |- + The key within the env file. An invalid key will prevent the pod from starting. + The keys defined within a source may consist of any printable ASCII characters except '='. + During Alpha stage of the EnvFiles feature gate, the key size is limited to 128 characters. + type: string + optional: + default: false + description: |- + Specify whether the file or its key must be defined. If the file or key + does not exist, then the env var is not published. + If optional is set to true and the specified key does not exist, + the environment variable will not be set in the Pod's containers. + + If optional is set to false and the specified key does not exist, + an error will be returned during Pod creation. + type: boolean + path: + description: |- + The path within the volume from which to select the file. + Must be relative and may not contain the '..' path or start with '..'. + type: string + volumeName: + description: The name of the volume mount containing + the env file. + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported. + properties: + containerName: + description: 'Container name: required for volumes, + optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of + the exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + description: Selects a key of a secret in the pod's + namespace + properties: + key: + description: The key of the secret to select + from. Must be a valid secret key. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the Secret or its + key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + required: + - name + type: object + type: array + imagePullPolicy: + description: PullPolicy describes a policy for if/when to + pull a container image + type: string + imagePullSecrets: + items: + description: |- + LocalObjectReference contains enough information to let you locate the + referenced object inside the same namespace. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + type: array + imageRegistry: + type: string + labels: + additionalProperties: + type: string + type: object + nodeSelector: + additionalProperties: + type: string + type: object + podSecurityContext: + description: |- + PodSecurityContext holds pod-level security attributes and common container settings. + Some fields are also present in container.securityContext. Field values of + container.securityContext take precedence over field values of PodSecurityContext. + properties: + appArmorProfile: + description: |- + appArmorProfile is the AppArmor options to use by the containers in this pod. + Note that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: |- + localhostProfile indicates a profile loaded on the node that should be used. + The profile must be preconfigured on the node to work. + Must match the loaded name of the profile. + Must be set if and only if type is "Localhost". + type: string + type: + description: |- + type indicates which kind of AppArmor profile will be applied. + Valid options are: + Localhost - a profile pre-loaded on the node. + RuntimeDefault - the container runtime's default profile. + Unconfined - no AppArmor enforcement. + type: string + required: + - type + type: object + fsGroup: + description: |- + A special supplemental group that applies to all containers in a pod. + Some volume types allow the Kubelet to change the ownership of that volume + to be owned by the pod: + + 1. The owning GID will be the FSGroup + 2. The setgid bit is set (new files created in the volume will be owned by FSGroup) + 3. The permission bits are OR'd with rw-rw---- + + If unset, the Kubelet will not modify the ownership and permissions of any volume. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + fsGroupChangePolicy: + description: |- + fsGroupChangePolicy defines behavior of changing ownership and permission of the volume + before being exposed inside Pod. This field will only apply to + volume types which support fsGroup based ownership(and permissions). + It will have no effect on ephemeral volume types such as: secret, configmaps + and emptydir. + Valid values are "OnRootMismatch" and "Always". If not specified, "Always" is used. + Note that this field cannot be set when spec.os.name is windows. + type: string + runAsGroup: + description: |- + The GID to run the entrypoint of the container process. + Uses runtime default if unset. + May also be set in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence + for that container. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: |- + Indicates that the container must run as a non-root user. + If true, the Kubelet will validate the image at runtime to ensure that it + does not run as UID 0 (root) and fail to start the container if it does. + If unset or false, no such validation will be performed. + May also be set in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: |- + The UID to run the entrypoint of the container process. + Defaults to user specified in image metadata if unspecified. + May also be set in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence + for that container. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + seLinuxChangePolicy: + description: |- + seLinuxChangePolicy defines how the container's SELinux label is applied to all volumes used by the Pod. + It has no effect on nodes that do not support SELinux or to volumes does not support SELinux. + Valid values are "MountOption" and "Recursive". + + "Recursive" means relabeling of all files on all Pod volumes by the container runtime. + This may be slow for large volumes, but allows mixing privileged and unprivileged Pods sharing the same volume on the same node. + + "MountOption" mounts all eligible Pod volumes with `-o context` mount option. + This requires all Pods that share the same volume to use the same SELinux label. + It is not possible to share the same volume among privileged and unprivileged Pods. + Eligible volumes are in-tree FibreChannel and iSCSI volumes, and all CSI volumes + whose CSI driver announces SELinux support by setting spec.seLinuxMount: true in their + CSIDriver instance. Other volumes are always re-labelled recursively. + "MountOption" value is allowed only when SELinuxMount feature gate is enabled. + + If not specified and SELinuxMount feature gate is enabled, "MountOption" is used. + If not specified and SELinuxMount feature gate is disabled, "MountOption" is used for ReadWriteOncePod volumes + and "Recursive" for all other volumes. + + This field affects only Pods that have SELinux label set, either in PodSecurityContext or in SecurityContext of all containers. + + All Pods that use the same volume should use the same seLinuxChangePolicy, otherwise some pods can get stuck in ContainerCreating state. + Note that this field cannot be set when spec.os.name is windows. + type: string + seLinuxOptions: + description: |- + The SELinux context to be applied to all containers. + If unspecified, the container runtime will allocate a random SELinux context for each + container. May also be set in SecurityContext. If set in + both SecurityContext and PodSecurityContext, the value specified in SecurityContext + takes precedence for that container. + Note that this field cannot be set when spec.os.name is windows. + properties: + level: + description: Level is SELinux level label that applies + to the container. + type: string + role: + description: Role is a SELinux role label that applies + to the container. + type: string + type: + description: Type is a SELinux type label that applies + to the container. + type: string + user: + description: User is a SELinux user label that applies + to the container. + type: string + type: object + seccompProfile: + description: |- + The seccomp options to use by the containers in this pod. + Note that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: |- + localhostProfile indicates a profile defined in a file on the node should be used. + The profile must be preconfigured on the node to work. + Must be a descending path, relative to the kubelet's configured seccomp profile location. + Must be set if type is "Localhost". Must NOT be set for any other type. + type: string + type: + description: |- + type indicates which kind of seccomp profile will be applied. + Valid options are: + + Localhost - a profile defined in a file on the node should be used. + RuntimeDefault - the container runtime default profile should be used. + Unconfined - no profile should be applied. + type: string + required: + - type + type: object + supplementalGroups: + description: |- + A list of groups applied to the first process run in each container, in + addition to the container's primary GID and fsGroup (if specified). If + the SupplementalGroupsPolicy feature is enabled, the + supplementalGroupsPolicy field determines whether these are in addition + to or instead of any group memberships defined in the container image. + If unspecified, no additional groups are added, though group memberships + defined in the container image may still be used, depending on the + supplementalGroupsPolicy field. + Note that this field cannot be set when spec.os.name is windows. + items: + format: int64 + type: integer + type: array + x-kubernetes-list-type: atomic + supplementalGroupsPolicy: + description: |- + Defines how supplemental groups of the first container processes are calculated. + Valid values are "Merge" and "Strict". If not specified, "Merge" is used. + (Alpha) Using the field requires the SupplementalGroupsPolicy feature gate to be enabled + and the container runtime must implement support for this feature. + Note that this field cannot be set when spec.os.name is windows. + type: string + sysctls: + description: |- + Sysctls hold a list of namespaced sysctls used for the pod. Pods with unsupported + sysctls (by the container runtime) might fail to launch. + Note that this field cannot be set when spec.os.name is windows. + items: + description: Sysctl defines a kernel parameter to be + set + properties: + name: + description: Name of a property to set + type: string + value: + description: Value of a property to set + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + windowsOptions: + description: |- + The Windows specific settings applied to all containers. + If unspecified, the options within a container's SecurityContext will be used. + If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is linux. + properties: + gmsaCredentialSpec: + description: |- + GMSACredentialSpec is where the GMSA admission webhook + (https://github.com/kubernetes-sigs/windows-gmsa) inlines the contents of the + GMSA credential spec named by the GMSACredentialSpecName field. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the name of + the GMSA credential spec to use. + type: string + hostProcess: + description: |- + HostProcess determines if a container should be run as a 'Host Process' container. + All of a Pod's containers must have the same effective HostProcess value + (it is not allowed to have a mix of HostProcess containers and non-HostProcess containers). + In addition, if HostProcess is true then HostNetwork must also be set to true. + type: boolean + runAsUserName: + description: |- + The UserName in Windows to run the entrypoint of the container process. + Defaults to the user specified in image metadata if unspecified. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + type: string + type: object + type: object + replicas: + format: int32 + type: integer + resources: + description: ResourceRequirements describes the compute resource + requirements. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + + This field depends on the + DynamicResourceAllocation feature gate. + + This field is immutable. It can only be set for containers. + items: + description: ResourceClaim references one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + securityContext: + description: |- + SecurityContext holds security configuration that will be applied to a container. + Some fields are present in both SecurityContext and PodSecurityContext. When both + are set, the values in SecurityContext take precedence. + properties: + allowPrivilegeEscalation: + description: |- + AllowPrivilegeEscalation controls whether a process can gain more + privileges than its parent process. This bool directly controls if + the no_new_privs flag will be set on the container process. + AllowPrivilegeEscalation is true always when the container is: + 1) run as Privileged + 2) has CAP_SYS_ADMIN + Note that this field cannot be set when spec.os.name is windows. + type: boolean + appArmorProfile: + description: |- + appArmorProfile is the AppArmor options to use by this container. If set, this profile + overrides the pod's appArmorProfile. + Note that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: |- + localhostProfile indicates a profile loaded on the node that should be used. + The profile must be preconfigured on the node to work. + Must match the loaded name of the profile. + Must be set if and only if type is "Localhost". + type: string + type: + description: |- + type indicates which kind of AppArmor profile will be applied. + Valid options are: + Localhost - a profile pre-loaded on the node. + RuntimeDefault - the container runtime's default profile. + Unconfined - no AppArmor enforcement. + type: string + required: + - type + type: object + capabilities: + description: |- + The capabilities to add/drop when running containers. + Defaults to the default set of capabilities granted by the container runtime. + Note that this field cannot be set when spec.os.name is windows. + properties: + add: + description: Added capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + x-kubernetes-list-type: atomic + drop: + description: Removed capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + x-kubernetes-list-type: atomic + type: object + privileged: + description: |- + Run container in privileged mode. + Processes in privileged containers are essentially equivalent to root on the host. + Defaults to false. + Note that this field cannot be set when spec.os.name is windows. + type: boolean + procMount: + description: |- + procMount denotes the type of proc mount to use for the containers. + The default value is Default which uses the container runtime defaults for + readonly paths and masked paths. + This requires the ProcMountType feature flag to be enabled. + Note that this field cannot be set when spec.os.name is windows. + type: string + readOnlyRootFilesystem: + description: |- + Whether this container has a read-only root filesystem. + Default is false. + Note that this field cannot be set when spec.os.name is windows. + type: boolean + runAsGroup: + description: |- + The GID to run the entrypoint of the container process. + Uses runtime default if unset. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: |- + Indicates that the container must run as a non-root user. + If true, the Kubelet will validate the image at runtime to ensure that it + does not run as UID 0 (root) and fail to start the container if it does. + If unset or false, no such validation will be performed. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: |- + The UID to run the entrypoint of the container process. + Defaults to user specified in image metadata if unspecified. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + seLinuxOptions: + description: |- + The SELinux context to be applied to the container. + If unspecified, the container runtime will allocate a random SELinux context for each + container. May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is windows. + properties: + level: + description: Level is SELinux level label that applies + to the container. + type: string + role: + description: Role is a SELinux role label that applies + to the container. + type: string + type: + description: Type is a SELinux type label that applies + to the container. + type: string + user: + description: User is a SELinux user label that applies + to the container. + type: string + type: object + seccompProfile: + description: |- + The seccomp options to use by this container. If seccomp options are + provided at both the pod & container level, the container options + override the pod options. + Note that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: |- + localhostProfile indicates a profile defined in a file on the node should be used. + The profile must be preconfigured on the node to work. + Must be a descending path, relative to the kubelet's configured seccomp profile location. + Must be set if type is "Localhost". Must NOT be set for any other type. + type: string + type: + description: |- + type indicates which kind of seccomp profile will be applied. + Valid options are: + + Localhost - a profile defined in a file on the node should be used. + RuntimeDefault - the container runtime default profile should be used. + Unconfined - no profile should be applied. + type: string + required: + - type + type: object + windowsOptions: + description: |- + The Windows specific settings applied to all containers. + If unspecified, the options from the PodSecurityContext will be used. + If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is linux. + properties: + gmsaCredentialSpec: + description: |- + GMSACredentialSpec is where the GMSA admission webhook + (https://github.com/kubernetes-sigs/windows-gmsa) inlines the contents of the + GMSA credential spec named by the GMSACredentialSpecName field. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the name of + the GMSA credential spec to use. + type: string + hostProcess: + description: |- + HostProcess determines if a container should be run as a 'Host Process' container. + All of a Pod's containers must have the same effective HostProcess value + (it is not allowed to have a mix of HostProcess containers and non-HostProcess containers). + In addition, if HostProcess is true then HostNetwork must also be set to true. + type: boolean + runAsUserName: + description: |- + The UserName in Windows to run the entrypoint of the container process. + Defaults to the user specified in image metadata if unspecified. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + type: string + type: object + type: object + serviceAccountConfig: + description: |- + ServiceAccountConfig configures the ServiceAccount created by the Agent controller. + This field can only be used when ServiceAccountName is not set. + If ServiceAccountName is not set, a default ServiceAccount (named after the agent) + is created, and this config will be applied to it. + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + serviceAccountName: + description: |- + ServiceAccountName specifies the name of an existing ServiceAccount to use. + If this field is set, the Agent controller will not create a ServiceAccount for the agent. + This field is mutually exclusive with ServiceAccountConfig. + type: string + tolerations: + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists, Equal, Lt, and Gt. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + Lt and Gt perform numeric comparisons (requires feature gate TaintTolerationComparisonOperators). + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + volumeMounts: + items: + description: VolumeMount describes a mounting of a Volume + within a container. + properties: + mountPath: + description: |- + Path within the container at which the volume should be mounted. Must + not contain ':'. + type: string + mountPropagation: + description: |- + mountPropagation determines how mounts are propagated from the host + to container and the other way around. + When not set, MountPropagationNone is used. + This field is beta in 1.10. + When RecursiveReadOnly is set to IfPossible or to Enabled, MountPropagation must be None or unspecified + (which defaults to None). + type: string + name: + description: This must match the Name of a Volume. + type: string + readOnly: + description: |- + Mounted read-only if true, read-write otherwise (false or unspecified). + Defaults to false. + type: boolean + recursiveReadOnly: + description: |- + RecursiveReadOnly specifies whether read-only mounts should be handled + recursively. + + If ReadOnly is false, this field has no meaning and must be unspecified. + + If ReadOnly is true, and this field is set to Disabled, the mount is not made + recursively read-only. If this field is set to IfPossible, the mount is made + recursively read-only, if it is supported by the container runtime. If this + field is set to Enabled, the mount is made recursively read-only if it is + supported by the container runtime, otherwise the pod will not be started and + an error will be generated to indicate the reason. + + If this field is set to IfPossible or Enabled, MountPropagation must be set to + None (or be unspecified, which defaults to None). + + If this field is not specified, it is treated as an equivalent of Disabled. + type: string + subPath: + description: |- + Path within the volume from which the container's volume should be mounted. + Defaults to "" (volume's root). + type: string + subPathExpr: + description: |- + Expanded path within the volume from which the container's volume should be mounted. + Behaves similarly to SubPath but environment variable references $(VAR_NAME) are expanded using the container's environment. + Defaults to "" (volume's root). + SubPathExpr and SubPath are mutually exclusive. + type: string + required: + - mountPath + - name + type: object + type: array + volumes: + items: + description: Volume represents a named volume in a pod that + may be accessed by any container in the pod. + properties: + awsElasticBlockStore: + description: |- + awsElasticBlockStore represents an AWS Disk resource that is attached to a + kubelet's host machine and then exposed to the pod. + Deprecated: AWSElasticBlockStore is deprecated. All operations for the in-tree + awsElasticBlockStore type are redirected to the ebs.csi.aws.com CSI driver. + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + properties: + fsType: + description: |- + fsType is the filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + type: string + partition: + description: |- + partition is the partition in the volume that you want to mount. + If omitted, the default is to mount by volume name. + Examples: For volume /dev/sda1, you specify the partition as "1". + Similarly, the volume partition for /dev/sda is "0" (or you can leave the property empty). + format: int32 + type: integer + readOnly: + description: |- + readOnly value true will force the readOnly setting in VolumeMounts. + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + type: boolean + volumeID: + description: |- + volumeID is unique ID of the persistent disk resource in AWS (Amazon EBS volume). + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + type: string + required: + - volumeID + type: object + azureDisk: + description: |- + azureDisk represents an Azure Data Disk mount on the host and bind mount to the pod. + Deprecated: AzureDisk is deprecated. All operations for the in-tree azureDisk type + are redirected to the disk.csi.azure.com CSI driver. + properties: + cachingMode: + description: 'cachingMode is the Host Caching mode: + None, Read Only, Read Write.' + type: string + diskName: + description: diskName is the Name of the data disk + in the blob storage + type: string + diskURI: + description: diskURI is the URI of data disk in + the blob storage + type: string + fsType: + default: ext4 + description: |- + fsType is Filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + kind: + description: 'kind expected values are Shared: multiple + blob disks per storage account Dedicated: single + blob disk per storage account Managed: azure + managed data disk (only in managed availability + set). defaults to shared' + type: string + readOnly: + default: false + description: |- + readOnly Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + required: + - diskName + - diskURI + type: object + azureFile: + description: |- + azureFile represents an Azure File Service mount on the host and bind mount to the pod. + Deprecated: AzureFile is deprecated. All operations for the in-tree azureFile type + are redirected to the file.csi.azure.com CSI driver. + properties: + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretName: + description: secretName is the name of secret that + contains Azure Storage Account Name and Key + type: string + shareName: + description: shareName is the azure share Name + type: string + required: + - secretName + - shareName + type: object + cephfs: + description: |- + cephFS represents a Ceph FS mount on the host that shares a pod's lifetime. + Deprecated: CephFS is deprecated and the in-tree cephfs type is no longer supported. + properties: + monitors: + description: |- + monitors is Required: Monitors is a collection of Ceph monitors + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + items: + type: string + type: array + x-kubernetes-list-type: atomic + path: + description: 'path is Optional: Used as the mounted + root, rather than the full Ceph tree, default + is /' + type: string + readOnly: + description: |- + readOnly is Optional: Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + type: boolean + secretFile: + description: |- + secretFile is Optional: SecretFile is the path to key ring for User, default is /etc/ceph/user.secret + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + type: string + secretRef: + description: |- + secretRef is Optional: SecretRef is reference to the authentication secret for User, default is empty. + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + user: + description: |- + user is optional: User is the rados user name, default is admin + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + type: string + required: + - monitors + type: object + cinder: + description: |- + cinder represents a cinder volume attached and mounted on kubelets host machine. + Deprecated: Cinder is deprecated. All operations for the in-tree cinder type + are redirected to the cinder.csi.openstack.org CSI driver. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + type: string + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + type: boolean + secretRef: + description: |- + secretRef is optional: points to a secret object containing parameters used to connect + to OpenStack. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + volumeID: + description: |- + volumeID used to identify the volume in cinder. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + type: string + required: + - volumeID + type: object + configMap: + description: configMap represents a configMap that should + populate this volume + properties: + defaultMode: + description: |- + defaultMode is optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + ConfigMap will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the ConfigMap, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key to a path within + a volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: optional specify whether the ConfigMap + or its keys must be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + csi: + description: csi (Container Storage Interface) represents + ephemeral storage that is handled by certain external + CSI drivers. + properties: + driver: + description: |- + driver is the name of the CSI driver that handles this volume. + Consult with your admin for the correct name as registered in the cluster. + type: string + fsType: + description: |- + fsType to mount. Ex. "ext4", "xfs", "ntfs". + If not provided, the empty value is passed to the associated CSI driver + which will determine the default filesystem to apply. + type: string + nodePublishSecretRef: + description: |- + nodePublishSecretRef is a reference to the secret object containing + sensitive information to pass to the CSI driver to complete the CSI + NodePublishVolume and NodeUnpublishVolume calls. + This field is optional, and may be empty if no secret is required. If the + secret object contains more than one secret, all secret references are passed. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + readOnly: + description: |- + readOnly specifies a read-only configuration for the volume. + Defaults to false (read/write). + type: boolean + volumeAttributes: + additionalProperties: + type: string + description: |- + volumeAttributes stores driver-specific properties that are passed to the CSI + driver. Consult your driver's documentation for supported values. + type: object + required: + - driver + type: object + downwardAPI: + description: downwardAPI represents downward API about + the pod that should populate this volume + properties: + defaultMode: + description: |- + Optional: mode bits to use on created files by default. Must be a + Optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + items: + description: Items is a list of downward API volume + file + items: + description: DownwardAPIVolumeFile represents + information to create the file containing the + pod field + properties: + fieldRef: + description: 'Required: Selects a field of + the pod: only annotations, labels, name, + namespace and uid are supported.' + properties: + apiVersion: + description: Version of the schema the + FieldPath is written in terms of, defaults + to "v1". + type: string + fieldPath: + description: Path of the field to select + in the specified API version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + description: |- + Optional: mode bits used to set permissions on this file, must be an octal value + between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: 'Required: Path is the relative + path name of the file to be created. Must + not be absolute or contain the ''..'' path. + Must be utf-8 encoded. The first item of + the relative path must not start with ''..''' + type: string + resourceFieldRef: + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, requests.cpu and requests.memory) are currently supported. + properties: + containerName: + description: 'Container name: required + for volumes, optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format + of the exposed resources, defaults to + "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + required: + - path + type: object + type: array + x-kubernetes-list-type: atomic + type: object + emptyDir: + description: |- + emptyDir represents a temporary directory that shares a pod's lifetime. + More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir + properties: + medium: + description: |- + medium represents what type of storage medium should back this directory. + The default is "" which means to use the node's default medium. + Must be an empty string (default) or Memory. + More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir + type: string + sizeLimit: + anyOf: + - type: integer + - type: string + description: |- + sizeLimit is the total amount of local storage required for this EmptyDir volume. + The size limit is also applicable for memory medium. + The maximum usage on memory medium EmptyDir would be the minimum value between + the SizeLimit specified here and the sum of memory limits of all containers in a pod. + The default is nil which means that the limit is undefined. + More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + ephemeral: + description: |- + ephemeral represents a volume that is handled by a cluster storage driver. + The volume's lifecycle is tied to the pod that defines it - it will be created before the pod starts, + and deleted when the pod is removed. + + Use this if: + a) the volume is only needed while the pod runs, + b) features of normal volumes like restoring from snapshot or capacity + tracking are needed, + c) the storage driver is specified through a storage class, and + d) the storage driver supports dynamic volume provisioning through + a PersistentVolumeClaim (see EphemeralVolumeSource for more + information on the connection between this volume type + and PersistentVolumeClaim). + + Use PersistentVolumeClaim or one of the vendor-specific + APIs for volumes that persist for longer than the lifecycle + of an individual pod. + + Use CSI for light-weight local ephemeral volumes if the CSI driver is meant to + be used that way - see the documentation of the driver for + more information. + + A pod can use both types of ephemeral volumes and + persistent volumes at the same time. + properties: + volumeClaimTemplate: + description: |- + Will be used to create a stand-alone PVC to provision the volume. + The pod in which this EphemeralVolumeSource is embedded will be the + owner of the PVC, i.e. the PVC will be deleted together with the + pod. The name of the PVC will be `-` where + `` is the name from the `PodSpec.Volumes` array + entry. Pod validation will reject the pod if the concatenated name + is not valid for a PVC (for example, too long). + + An existing PVC with that name that is not owned by the pod + will *not* be used for the pod to avoid using an unrelated + volume by mistake. Starting the pod is then blocked until + the unrelated PVC is removed. If such a pre-created PVC is + meant to be used by the pod, the PVC has to updated with an + owner reference to the pod once the pod exists. Normally + this should not be necessary, but it may be useful when + manually reconstructing a broken cluster. + + This field is read-only and no changes will be made by Kubernetes + to the PVC after it has been created. + + Required, must not be nil. + properties: + metadata: + description: |- + May contain labels and annotations that will be copied into the PVC + when creating it. No other fields are allowed and will be rejected during + validation. + type: object + spec: + description: |- + The specification for the PersistentVolumeClaim. The entire content is + copied unchanged into the PVC that gets created from this + template. The same fields as in a PersistentVolumeClaim + are also valid here. + properties: + accessModes: + description: |- + accessModes contains the desired access modes the volume should have. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1 + items: + type: string + type: array + x-kubernetes-list-type: atomic + dataSource: + description: |- + dataSource field can be used to specify either: + * An existing VolumeSnapshot object (snapshot.storage.k8s.io/VolumeSnapshot) + * An existing PVC (PersistentVolumeClaim) + If the provisioner or an external controller can support the specified data source, + it will create a new volume based on the contents of the specified data source. + When the AnyVolumeDataSource feature gate is enabled, dataSource contents will be copied to dataSourceRef, + and dataSourceRef contents will be copied to dataSource when dataSourceRef.namespace is not specified. + If the namespace is specified, then dataSourceRef will not be copied to dataSource. + properties: + apiGroup: + description: |- + APIGroup is the group for the resource being referenced. + If APIGroup is not specified, the specified Kind must be in the core API group. + For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource + being referenced + type: string + name: + description: Name is the name of resource + being referenced + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + dataSourceRef: + description: |- + dataSourceRef specifies the object from which to populate the volume with data, if a non-empty + volume is desired. This may be any object from a non-empty API group (non + core object) or a PersistentVolumeClaim object. + When this field is specified, volume binding will only succeed if the type of + the specified object matches some installed volume populator or dynamic + provisioner. + This field will replace the functionality of the dataSource field and as such + if both fields are non-empty, they must have the same value. For backwards + compatibility, when namespace isn't specified in dataSourceRef, + both fields (dataSource and dataSourceRef) will be set to the same + value automatically if one of them is empty and the other is non-empty. + When namespace is specified in dataSourceRef, + dataSource isn't set to the same value and must be empty. + There are three important differences between dataSource and dataSourceRef: + * While dataSource only allows two specific types of objects, dataSourceRef + allows any non-core object, as well as PersistentVolumeClaim objects. + * While dataSource ignores disallowed values (dropping them), dataSourceRef + preserves all values, and generates an error if a disallowed value is + specified. + * While dataSource only allows local objects, dataSourceRef allows objects + in any namespaces. + (Beta) Using this field requires the AnyVolumeDataSource feature gate to be enabled. + (Alpha) Using the namespace field of dataSourceRef requires the CrossNamespaceVolumeDataSource feature gate to be enabled. + properties: + apiGroup: + description: |- + APIGroup is the group for the resource being referenced. + If APIGroup is not specified, the specified Kind must be in the core API group. + For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource + being referenced + type: string + name: + description: Name is the name of resource + being referenced + type: string + namespace: + description: |- + Namespace is the namespace of resource being referenced + Note that when a namespace is specified, a gateway.networking.k8s.io/ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. + (Alpha) This field requires the CrossNamespaceVolumeDataSource feature gate to be enabled. + type: string + required: + - kind + - name + type: object + resources: + description: |- + resources represents the minimum resources the volume should have. + Users are allowed to specify resource requirements + that are lower than previous value but must still be higher than capacity recorded in the + status field of the claim. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + selector: + description: selector is a label query over + volumes to consider for binding. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The + requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + storageClassName: + description: |- + storageClassName is the name of the StorageClass required by the claim. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1 + type: string + volumeAttributesClassName: + description: |- + volumeAttributesClassName may be used to set the VolumeAttributesClass used by this claim. + If specified, the CSI driver will create or update the volume with the attributes defined + in the corresponding VolumeAttributesClass. This has a different purpose than storageClassName, + it can be changed after the claim is created. An empty string or nil value indicates that no + VolumeAttributesClass will be applied to the claim. If the claim enters an Infeasible error state, + this field can be reset to its previous value (including nil) to cancel the modification. + If the resource referred to by volumeAttributesClass does not exist, this PersistentVolumeClaim will be + set to a Pending state, as reflected by the modifyVolumeStatus field, until such as a resource + exists. + More info: https://kubernetes.io/docs/concepts/storage/volume-attributes-classes/ + type: string + volumeMode: + description: |- + volumeMode defines what type of volume is required by the claim. + Value of Filesystem is implied when not included in claim spec. + type: string + volumeName: + description: volumeName is the binding reference + to the PersistentVolume backing this claim. + type: string + type: object + required: + - spec + type: object + type: object + fc: + description: fc represents a Fibre Channel resource + that is attached to a kubelet's host machine and then + exposed to the pod. + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + lun: + description: 'lun is Optional: FC target lun number' + format: int32 + type: integer + readOnly: + description: |- + readOnly is Optional: Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + targetWWNs: + description: 'targetWWNs is Optional: FC target + worldwide names (WWNs)' + items: + type: string + type: array + x-kubernetes-list-type: atomic + wwids: + description: |- + wwids Optional: FC volume world wide identifiers (wwids) + Either wwids or combination of targetWWNs and lun must be set, but not both simultaneously. + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + flexVolume: + description: |- + flexVolume represents a generic volume resource that is + provisioned/attached using an exec based plugin. + Deprecated: FlexVolume is deprecated. Consider using a CSIDriver instead. + properties: + driver: + description: driver is the name of the driver to + use for this volume. + type: string + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". The default filesystem depends on FlexVolume script. + type: string + options: + additionalProperties: + type: string + description: 'options is Optional: this field holds + extra command options if any.' + type: object + readOnly: + description: |- + readOnly is Optional: defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: |- + secretRef is Optional: secretRef is reference to the secret object containing + sensitive information to pass to the plugin scripts. This may be + empty if no secret object is specified. If the secret object + contains more than one secret, all secrets are passed to the plugin + scripts. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + required: + - driver + type: object + flocker: + description: |- + flocker represents a Flocker volume attached to a kubelet's host machine. This depends on the Flocker control service being running. + Deprecated: Flocker is deprecated and the in-tree flocker type is no longer supported. + properties: + datasetName: + description: |- + datasetName is Name of the dataset stored as metadata -> name on the dataset for Flocker + should be considered as deprecated + type: string + datasetUUID: + description: datasetUUID is the UUID of the dataset. + This is unique identifier of a Flocker dataset + type: string + type: object + gcePersistentDisk: + description: |- + gcePersistentDisk represents a GCE Disk resource that is attached to a + kubelet's host machine and then exposed to the pod. + Deprecated: GCEPersistentDisk is deprecated. All operations for the in-tree + gcePersistentDisk type are redirected to the pd.csi.storage.gke.io CSI driver. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + properties: + fsType: + description: |- + fsType is filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + type: string + partition: + description: |- + partition is the partition in the volume that you want to mount. + If omitted, the default is to mount by volume name. + Examples: For volume /dev/sda1, you specify the partition as "1". + Similarly, the volume partition for /dev/sda is "0" (or you can leave the property empty). + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + format: int32 + type: integer + pdName: + description: |- + pdName is unique name of the PD resource in GCE. Used to identify the disk in GCE. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + type: string + readOnly: + description: |- + readOnly here will force the ReadOnly setting in VolumeMounts. + Defaults to false. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + type: boolean + required: + - pdName + type: object + gitRepo: + description: |- + gitRepo represents a git repository at a particular revision. + Deprecated: GitRepo is deprecated. To provision a container with a git repo, mount an + EmptyDir into an InitContainer that clones the repo using git, then mount the EmptyDir + into the Pod's container. + properties: + directory: + description: |- + directory is the target directory name. + Must not contain or start with '..'. If '.' is supplied, the volume directory will be the + git repository. Otherwise, if specified, the volume will contain the git repository in + the subdirectory with the given name. + type: string + repository: + description: repository is the URL + type: string + revision: + description: revision is the commit hash for the + specified revision. + type: string + required: + - repository + type: object + glusterfs: + description: |- + glusterfs represents a Glusterfs mount on the host that shares a pod's lifetime. + Deprecated: Glusterfs is deprecated and the in-tree glusterfs type is no longer supported. + properties: + endpoints: + description: endpoints is the endpoint name that + details Glusterfs topology. + type: string + path: + description: |- + path is the Glusterfs volume path. + More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod + type: string + readOnly: + description: |- + readOnly here will force the Glusterfs volume to be mounted with read-only permissions. + Defaults to false. + More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod + type: boolean + required: + - endpoints + - path + type: object + hostPath: + description: |- + hostPath represents a pre-existing file or directory on the host + machine that is directly exposed to the container. This is generally + used for system agents or other privileged things that are allowed + to see the host machine. Most containers will NOT need this. + More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + properties: + path: + description: |- + path of the directory on the host. + If the path is a symlink, it will follow the link to the real path. + More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + type: string + type: + description: |- + type for HostPath Volume + Defaults to "" + More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + type: string + required: + - path + type: object + image: + description: |- + image represents an OCI object (a container image or artifact) pulled and mounted on the kubelet's host machine. + The volume is resolved at pod startup depending on which PullPolicy value is provided: + + - Always: the kubelet always attempts to pull the reference. Container creation will fail If the pull fails. + - Never: the kubelet never pulls the reference and only uses a local image or artifact. Container creation will fail if the reference isn't present. + - IfNotPresent: the kubelet pulls if the reference isn't already present on disk. Container creation will fail if the reference isn't present and the pull fails. + + The volume gets re-resolved if the pod gets deleted and recreated, which means that new remote content will become available on pod recreation. + A failure to resolve or pull the image during pod startup will block containers from starting and may add significant latency. Failures will be retried using normal volume backoff and will be reported on the pod reason and message. + The types of objects that may be mounted by this volume are defined by the container runtime implementation on a host machine and at minimum must include all valid types supported by the container image field. + The OCI object gets mounted in a single directory (spec.containers[*].volumeMounts.mountPath) by merging the manifest layers in the same way as for container images. + The volume will be mounted read-only (ro) and non-executable files (noexec). + Sub path mounts for containers are not supported (spec.containers[*].volumeMounts.subpath) before 1.33. + The field spec.securityContext.fsGroupChangePolicy has no effect on this volume type. + properties: + pullPolicy: + description: |- + Policy for pulling OCI objects. Possible values are: + Always: the kubelet always attempts to pull the reference. Container creation will fail If the pull fails. + Never: the kubelet never pulls the reference and only uses a local image or artifact. Container creation will fail if the reference isn't present. + IfNotPresent: the kubelet pulls if the reference isn't already present on disk. Container creation will fail if the reference isn't present and the pull fails. + Defaults to Always if :latest tag is specified, or IfNotPresent otherwise. + type: string + reference: + description: |- + Required: Image or artifact reference to be used. + Behaves in the same way as pod.spec.containers[*].image. + Pull secrets will be assembled in the same way as for the container image by looking up node credentials, SA image pull secrets, and pod spec image pull secrets. + More info: https://kubernetes.io/docs/concepts/containers/images + This field is optional to allow higher level config management to default or override + container images in workload controllers like Deployments and StatefulSets. + type: string + type: object + iscsi: + description: |- + iscsi represents an ISCSI Disk resource that is attached to a + kubelet's host machine and then exposed to the pod. + More info: https://kubernetes.io/docs/concepts/storage/volumes/#iscsi + properties: + chapAuthDiscovery: + description: chapAuthDiscovery defines whether support + iSCSI Discovery CHAP authentication + type: boolean + chapAuthSession: + description: chapAuthSession defines whether support + iSCSI Session CHAP authentication + type: boolean + fsType: + description: |- + fsType is the filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#iscsi + type: string + initiatorName: + description: |- + initiatorName is the custom iSCSI Initiator Name. + If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface + : will be created for the connection. + type: string + iqn: + description: iqn is the target iSCSI Qualified Name. + type: string + iscsiInterface: + default: default + description: |- + iscsiInterface is the interface Name that uses an iSCSI transport. + Defaults to 'default' (tcp). + type: string + lun: + description: lun represents iSCSI Target Lun number. + format: int32 + type: integer + portals: + description: |- + portals is the iSCSI Target Portal List. The portal is either an IP or ip_addr:port if the port + is other than default (typically TCP ports 860 and 3260). + items: + type: string + type: array + x-kubernetes-list-type: atomic + readOnly: + description: |- + readOnly here will force the ReadOnly setting in VolumeMounts. + Defaults to false. + type: boolean + secretRef: + description: secretRef is the CHAP Secret for iSCSI + target and initiator authentication + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + targetPortal: + description: |- + targetPortal is iSCSI Target Portal. The Portal is either an IP or ip_addr:port if the port + is other than default (typically TCP ports 860 and 3260). + type: string + required: + - iqn + - lun + - targetPortal + type: object + name: + description: |- + name of the volume. + Must be a DNS_LABEL and unique within the pod. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + nfs: + description: |- + nfs represents an NFS mount on the host that shares a pod's lifetime + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + properties: + path: + description: |- + path that is exported by the NFS server. + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + type: string + readOnly: + description: |- + readOnly here will force the NFS export to be mounted with read-only permissions. + Defaults to false. + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + type: boolean + server: + description: |- + server is the hostname or IP address of the NFS server. + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + type: string + required: + - path + - server + type: object + persistentVolumeClaim: + description: |- + persistentVolumeClaimVolumeSource represents a reference to a + PersistentVolumeClaim in the same namespace. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims + properties: + claimName: + description: |- + claimName is the name of a PersistentVolumeClaim in the same namespace as the pod using this volume. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims + type: string + readOnly: + description: |- + readOnly Will force the ReadOnly setting in VolumeMounts. + Default false. + type: boolean + required: + - claimName + type: object + photonPersistentDisk: + description: |- + photonPersistentDisk represents a PhotonController persistent disk attached and mounted on kubelets host machine. + Deprecated: PhotonPersistentDisk is deprecated and the in-tree photonPersistentDisk type is no longer supported. + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + pdID: + description: pdID is the ID that identifies Photon + Controller persistent disk + type: string + required: + - pdID + type: object + portworxVolume: + description: |- + portworxVolume represents a portworx volume attached and mounted on kubelets host machine. + Deprecated: PortworxVolume is deprecated. All operations for the in-tree portworxVolume type + are redirected to the pxd.portworx.com CSI driver when the CSIMigrationPortworx feature-gate + is on. + properties: + fsType: + description: |- + fSType represents the filesystem type to mount + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs". Implicitly inferred to be "ext4" if unspecified. + type: string + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + volumeID: + description: volumeID uniquely identifies a Portworx + volume + type: string + required: + - volumeID + type: object + projected: + description: projected items for all in one resources + secrets, configmaps, and downward API + properties: + defaultMode: + description: |- + defaultMode are the mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + sources: + description: |- + sources is the list of volume projections. Each entry in this list + handles one source. + items: + description: |- + Projection that may be projected along with other supported volume types. + Exactly one of these fields must be set. + properties: + clusterTrustBundle: + description: |- + ClusterTrustBundle allows a pod to access the `.spec.trustBundle` field + of ClusterTrustBundle objects in an auto-updating file. + + Alpha, gated by the ClusterTrustBundleProjection feature gate. + + ClusterTrustBundle objects can either be selected by name, or by the + combination of signer name and a label selector. + + Kubelet performs aggressive normalization of the PEM contents written + into the pod filesystem. Esoteric PEM features such as inter-block + comments and block headers are stripped. Certificates are deduplicated. + The ordering of certificates within the file is arbitrary, and Kubelet + may change the order over time. + properties: + labelSelector: + description: |- + Select all ClusterTrustBundles that match this label selector. Only has + effect if signerName is set. Mutually-exclusive with name. If unset, + interpreted as "match nothing". If set but empty, interpreted as "match + everything". + properties: + matchExpressions: + description: matchExpressions is a + list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + name: + description: |- + Select a single ClusterTrustBundle by object name. Mutually-exclusive + with signerName and labelSelector. + type: string + optional: + description: |- + If true, don't block pod startup if the referenced ClusterTrustBundle(s) + aren't available. If using name, then the named ClusterTrustBundle is + allowed not to exist. If using signerName, then the combination of + signerName and labelSelector is allowed to match zero + ClusterTrustBundles. + type: boolean + path: + description: Relative path from the volume + root to write the bundle. + type: string + signerName: + description: |- + Select all ClusterTrustBundles that match this signer name. + Mutually-exclusive with name. The contents of all selected + ClusterTrustBundles will be unified and deduplicated. + type: string + required: + - path + type: object + configMap: + description: configMap information about the + configMap data to project + properties: + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + ConfigMap will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the ConfigMap, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key to a + path within a volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: optional specify whether + the ConfigMap or its keys must be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + downwardAPI: + description: downwardAPI information about + the downwardAPI data to project + properties: + items: + description: Items is a list of DownwardAPIVolume + file + items: + description: DownwardAPIVolumeFile represents + information to create the file containing + the pod field + properties: + fieldRef: + description: 'Required: Selects + a field of the pod: only annotations, + labels, name, namespace and uid + are supported.' + properties: + apiVersion: + description: Version of the + schema the FieldPath is written + in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field + to select in the specified + API version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + description: |- + Optional: mode bits used to set permissions on this file, must be an octal value + between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: 'Required: Path is the + relative path name of the file + to be created. Must not be absolute + or contain the ''..'' path. Must + be utf-8 encoded. The first item + of the relative path must not + start with ''..''' + type: string + resourceFieldRef: + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, requests.cpu and requests.memory) are currently supported. + properties: + containerName: + description: 'Container name: + required for volumes, optional + for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output + format of the exposed resources, + defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource + to select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + required: + - path + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podCertificate: + description: |- + Projects an auto-rotating credential bundle (private key and certificate + chain) that the pod can use either as a TLS client or server. + + Kubelet generates a private key and uses it to send a + PodCertificateRequest to the named signer. Once the signer approves the + request and issues a certificate chain, Kubelet writes the key and + certificate chain to the pod filesystem. The pod does not start until + certificates have been issued for each podCertificate projected volume + source in its spec. + + Kubelet will begin trying to rotate the certificate at the time indicated + by the signer using the PodCertificateRequest.Status.BeginRefreshAt + timestamp. + + Kubelet can write a single file, indicated by the credentialBundlePath + field, or separate files, indicated by the keyPath and + certificateChainPath fields. + + The credential bundle is a single file in PEM format. The first PEM + entry is the private key (in PKCS#8 format), and the remaining PEM + entries are the certificate chain issued by the signer (typically, + signers will return their certificate chain in leaf-to-root order). + + Prefer using the credential bundle format, since your application code + can read it atomically. If you use keyPath and certificateChainPath, + your application must make two separate file reads. If these coincide + with a certificate rotation, it is possible that the private key and leaf + certificate you read may not correspond to each other. Your application + will need to check for this condition, and re-read until they are + consistent. + + The named signer controls chooses the format of the certificate it + issues; consult the signer implementation's documentation to learn how to + use the certificates it issues. + properties: + certificateChainPath: + description: |- + Write the certificate chain at this path in the projected volume. + + Most applications should use credentialBundlePath. When using keyPath + and certificateChainPath, your application needs to check that the key + and leaf certificate are consistent, because it is possible to read the + files mid-rotation. + type: string + credentialBundlePath: + description: |- + Write the credential bundle at this path in the projected volume. + + The credential bundle is a single file that contains multiple PEM blocks. + The first PEM block is a PRIVATE KEY block, containing a PKCS#8 private + key. + + The remaining blocks are CERTIFICATE blocks, containing the issued + certificate chain from the signer (leaf and any intermediates). + + Using credentialBundlePath lets your Pod's application code make a single + atomic read that retrieves a consistent key and certificate chain. If you + project them to separate files, your application code will need to + additionally check that the leaf certificate was issued to the key. + type: string + keyPath: + description: |- + Write the key at this path in the projected volume. + + Most applications should use credentialBundlePath. When using keyPath + and certificateChainPath, your application needs to check that the key + and leaf certificate are consistent, because it is possible to read the + files mid-rotation. + type: string + keyType: + description: |- + The type of keypair Kubelet will generate for the pod. + + Valid values are "RSA3072", "RSA4096", "ECDSAP256", "ECDSAP384", + "ECDSAP521", and "ED25519". + type: string + maxExpirationSeconds: + description: |- + maxExpirationSeconds is the maximum lifetime permitted for the + certificate. + + Kubelet copies this value verbatim into the PodCertificateRequests it + generates for this projection. + + If omitted, kube-apiserver will set it to 86400(24 hours). kube-apiserver + will reject values shorter than 3600 (1 hour). The maximum allowable + value is 7862400 (91 days). + + The signer implementation is then free to issue a certificate with any + lifetime *shorter* than MaxExpirationSeconds, but no shorter than 3600 + seconds (1 hour). This constraint is enforced by kube-apiserver. + `kubernetes.io` signers will never issue certificates with a lifetime + longer than 24 hours. + format: int32 + type: integer + signerName: + description: Kubelet's generated CSRs + will be addressed to this signer. + type: string + userAnnotations: + additionalProperties: + type: string + description: |- + userAnnotations allow pod authors to pass additional information to + the signer implementation. Kubernetes does not restrict or validate this + metadata in any way. + + These values are copied verbatim into the `spec.unverifiedUserAnnotations` field of + the PodCertificateRequest objects that Kubelet creates. + + Entries are subject to the same validation as object metadata annotations, + with the addition that all keys must be domain-prefixed. No restrictions + are placed on values, except an overall size limitation on the entire field. + + Signers should document the keys and values they support. Signers should + deny requests that contain keys they do not recognize. + type: object + required: + - keyType + - signerName + type: object + secret: + description: secret information about the + secret data to project + properties: + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + Secret will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the Secret, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key to a + path within a volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: optional field specify whether + the Secret or its key must be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + serviceAccountToken: + description: serviceAccountToken is information + about the serviceAccountToken data to project + properties: + audience: + description: |- + audience is the intended audience of the token. A recipient of a token + must identify itself with an identifier specified in the audience of the + token, and otherwise should reject the token. The audience defaults to the + identifier of the apiserver. + type: string + expirationSeconds: + description: |- + expirationSeconds is the requested duration of validity of the service + account token. As the token approaches expiration, the kubelet volume + plugin will proactively rotate the service account token. The kubelet will + start trying to rotate the token if the token is older than 80 percent of + its time to live or if the token is older than 24 hours.Defaults to 1 hour + and must be at least 10 minutes. + format: int64 + type: integer + path: + description: |- + path is the path relative to the mount point of the file to project the + token into. + type: string + required: + - path + type: object + type: object + type: array + x-kubernetes-list-type: atomic + type: object + quobyte: + description: |- + quobyte represents a Quobyte mount on the host that shares a pod's lifetime. + Deprecated: Quobyte is deprecated and the in-tree quobyte type is no longer supported. + properties: + group: + description: |- + group to map volume access to + Default is no group + type: string + readOnly: + description: |- + readOnly here will force the Quobyte volume to be mounted with read-only permissions. + Defaults to false. + type: boolean + registry: + description: |- + registry represents a single or multiple Quobyte Registry services + specified as a string as host:port pair (multiple entries are separated with commas) + which acts as the central registry for volumes + type: string + tenant: + description: |- + tenant owning the given Quobyte volume in the Backend + Used with dynamically provisioned Quobyte volumes, value is set by the plugin + type: string + user: + description: |- + user to map volume access to + Defaults to serivceaccount user + type: string + volume: + description: volume is a string that references + an already created Quobyte volume by name. + type: string + required: + - registry + - volume + type: object + rbd: + description: |- + rbd represents a Rados Block Device mount on the host that shares a pod's lifetime. + Deprecated: RBD is deprecated and the in-tree rbd type is no longer supported. + properties: + fsType: + description: |- + fsType is the filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#rbd + type: string + image: + description: |- + image is the rados image name. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + keyring: + default: /etc/ceph/keyring + description: |- + keyring is the path to key ring for RBDUser. + Default is /etc/ceph/keyring. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + monitors: + description: |- + monitors is a collection of Ceph monitors. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + items: + type: string + type: array + x-kubernetes-list-type: atomic + pool: + default: rbd + description: |- + pool is the rados pool name. + Default is rbd. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + readOnly: + description: |- + readOnly here will force the ReadOnly setting in VolumeMounts. + Defaults to false. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: boolean + secretRef: + description: |- + secretRef is name of the authentication secret for RBDUser. If provided + overrides keyring. + Default is nil. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + user: + default: admin + description: |- + user is the rados user name. + Default is admin. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + required: + - image + - monitors + type: object + scaleIO: + description: |- + scaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes. + Deprecated: ScaleIO is deprecated and the in-tree scaleIO type is no longer supported. + properties: + fsType: + default: xfs + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". + Default is "xfs". + type: string + gateway: + description: gateway is the host address of the + ScaleIO API Gateway. + type: string + protectionDomain: + description: protectionDomain is the name of the + ScaleIO Protection Domain for the configured storage. + type: string + readOnly: + description: |- + readOnly Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: |- + secretRef references to the secret for ScaleIO user and other + sensitive information. If this is not provided, Login operation will fail. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + sslEnabled: + description: sslEnabled Flag enable/disable SSL + communication with Gateway, default false + type: boolean + storageMode: + default: ThinProvisioned + description: |- + storageMode indicates whether the storage for a volume should be ThickProvisioned or ThinProvisioned. + Default is ThinProvisioned. + type: string + storagePool: + description: storagePool is the ScaleIO Storage + Pool associated with the protection domain. + type: string + system: + description: system is the name of the storage system + as configured in ScaleIO. + type: string + volumeName: + description: |- + volumeName is the name of a volume already created in the ScaleIO system + that is associated with this volume source. + type: string + required: + - gateway + - secretRef + - system + type: object + secret: + description: |- + secret represents a secret that should populate this volume. + More info: https://kubernetes.io/docs/concepts/storage/volumes#secret + properties: + defaultMode: + description: |- + defaultMode is Optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values + for mode bits. Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + items: + description: |- + items If unspecified, each key-value pair in the Data field of the referenced + Secret will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the Secret, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key to a path within + a volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + optional: + description: optional field specify whether the + Secret or its keys must be defined + type: boolean + secretName: + description: |- + secretName is the name of the secret in the pod's namespace to use. + More info: https://kubernetes.io/docs/concepts/storage/volumes#secret + type: string + type: object + storageos: + description: |- + storageOS represents a StorageOS volume attached and mounted on Kubernetes nodes. + Deprecated: StorageOS is deprecated and the in-tree storageos type is no longer supported. + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: |- + secretRef specifies the secret to use for obtaining the StorageOS API + credentials. If not specified, default values will be attempted. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + volumeName: + description: |- + volumeName is the human-readable name of the StorageOS volume. Volume + names are only unique within a namespace. + type: string + volumeNamespace: + description: |- + volumeNamespace specifies the scope of the volume within StorageOS. If no + namespace is specified then the Pod's namespace will be used. This allows the + Kubernetes name scoping to be mirrored within StorageOS for tighter integration. + Set VolumeName to any name to override the default behaviour. + Set to "default" if you are not using namespaces within StorageOS. + Namespaces that do not pre-exist within StorageOS will be created. + type: string + type: object + vsphereVolume: + description: |- + vsphereVolume represents a vSphere volume attached and mounted on kubelets host machine. + Deprecated: VsphereVolume is deprecated. All operations for the in-tree vsphereVolume type + are redirected to the csi.vsphere.vmware.com CSI driver. + properties: + fsType: + description: |- + fsType is filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + storagePolicyID: + description: storagePolicyID is the storage Policy + Based Management (SPBM) profile ID associated + with the StoragePolicyName. + type: string + storagePolicyName: + description: storagePolicyName is the storage Policy + Based Management (SPBM) profile name. + type: string + volumePath: + description: volumePath is the path that identifies + vSphere volume vmdk + type: string + required: + - volumePath + type: object + required: + - name + type: object + type: array + type: object + x-kubernetes-validations: + - message: serviceAccountName and serviceAccountConfig are mutually + exclusive + rule: '!(has(self.serviceAccountName) && has(self.serviceAccountConfig))' + executeCodeBlocks: + description: |- + Allow code execution for python code blocks with this agent. + If true, the agent will automatically execute python code blocks in the LLM responses. + Code will be executed in a sandboxed environment. + due to a bug in adk (https://github.com/google/adk-python/issues/3921), this field is ignored for now. + type: boolean + memory: + description: Memory configuration for the agent. + properties: + modelConfig: + description: |- + ModelConfig is the name of the ModelConfig object whose embedding + provider will be used to generate memory vectors. + type: string + ttlDays: + description: |- + TTLDays controls how many days a stored memory entry remains valid before + it is eligible for pruning. Defaults to 15 days when unset or zero. + minimum: 1 + type: integer + required: + - modelConfig + type: object + modelConfig: + description: |- + The name of the model config to use. + If not specified, the default value is "default-model-config". + Must be in the same namespace as the Agent. + type: string + promptTemplate: + description: |- + PromptTemplate enables Go text/template processing on the systemMessage field. + When set, systemMessage is treated as a Go template with access to the include function + and agent context variables. + properties: + dataSources: + description: |- + DataSources defines the ConfigMaps whose keys can be included in the systemMessage + using Go template syntax, e.g. include("alias/key") or include("name/key"). + items: + description: |- + PromptSource references a ConfigMap whose keys are available as prompt fragments. + In systemMessage templates, use include("alias/key") (or include("name/key") if no alias is set) + to insert the value of a specific key from this source. + properties: + alias: + description: |- + Alias is an optional short identifier for use in include directives. + If set, use include("alias/key") instead of include("name/key"). + type: string + apiGroup: + type: string + kind: + type: string + name: + type: string + required: + - name + type: object + maxItems: 20 + type: array + type: object + runtime: + allOf: + - enum: + - python + - go + - enum: + - python + - go + default: python + description: |- + Runtime specifies which ADK implementation to use for this agent. + - "python": Uses the Python ADK (default, slower startup, full feature set) + - "go": Uses the Go ADK (faster startup, most features supported) + The runtime determines both the container image and readiness probe configuration. + type: string + stream: + description: |- + Whether to stream the response from the model. + If not specified, the default value is false. + type: boolean + systemMessage: + description: |- + SystemMessage is a string specifying the system message for the agent. + When PromptTemplate is set, this field is treated as a Go text/template + with access to an include("source/key") function and agent context variables + such as .AgentName, .AgentNamespace, .Description, .ToolNames, and .SkillNames. + type: string + systemMessageFrom: + description: |- + SystemMessageFrom is a reference to a ConfigMap or Secret containing the system message. + When PromptTemplate is set, the resolved value is treated as a Go text/template. + properties: + key: + description: The key of the ConfigMap or Secret. + type: string + name: + description: The name of the ConfigMap or Secret. + type: string + type: + enum: + - ConfigMap + - Secret + type: string + required: + - key + - name + - type + type: object + tools: + items: + properties: + agent: + properties: + apiGroup: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + required: + - name + type: object + headersFrom: + description: |- + HeadersFrom specifies a list of configuration values to be added as + headers to requests sent to the Tool from this agent. The value of + each header is resolved from either a Secret or ConfigMap in the same + namespace as the Agent. Headers specified here will override any + headers of the same name/key specified on the tool. + items: + description: ValueRef represents a configuration value + properties: + name: + type: string + value: + type: string + valueFrom: + description: ValueSource defines a source for configuration + values from a Secret or ConfigMap + properties: + key: + description: The key of the ConfigMap or Secret. + type: string + name: + description: The name of the ConfigMap or Secret. + type: string + type: + enum: + - ConfigMap + - Secret + type: string + required: + - key + - name + - type + type: object + required: + - name + type: object + x-kubernetes-validations: + - message: Exactly one of value or valueFrom must be specified + rule: (has(self.value) && !has(self.valueFrom)) || (!has(self.value) + && has(self.valueFrom)) + type: array + mcpServer: + properties: + allowedHeaders: + description: |- + AllowedHeaders specifies which headers from the A2A request should be + propagated to MCP tool calls. Header names are case-insensitive. + + Authorization header behavior: + - Authorization headers CAN be propagated if explicitly listed in allowedHeaders + - When STS token propagation is enabled, STS-generated Authorization headers + will take precedence and replace any Authorization header from the A2A request + - This is a security measure to prevent request headers from overwriting + authentication tokens generated by the STS integration + + Example: ["x-user-email", "x-tenant-id"] + items: + type: string + type: array + apiGroup: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + requireApproval: + description: |- + RequireApproval lists tool names that require human approval before + execution. Each name must also appear in ToolNames. When a tool in + this list is invoked by the agent, execution pauses and the user is + prompted to approve or reject the call. + items: + type: string + maxItems: 50 + type: array + toolNames: + description: |- + The names of the tools to be provided by the ToolServer + For a list of all the tools provided by the server, + the client can query the status of the ToolServer object after it has been created + items: + type: string + maxItems: 50 + type: array + required: + - name + type: object + x-kubernetes-validations: + - message: each RequireApproval entry must also appear in + ToolNames + rule: '!has(self.requireApproval) || self.requireApproval.all(x, + has(self.toolNames) && x in self.toolNames)' + type: + allOf: + - enum: + - McpServer + - Agent + - enum: + - McpServer + - Agent + description: ToolProviderType represents the tool provider + type + type: string + type: object + x-kubernetes-validations: + - message: type.mcpServer must be nil if the type is not McpServer + rule: '!(has(self.mcpServer) && self.type != ''McpServer'')' + - message: type.mcpServer must be specified for McpServer filter.type + rule: '!(!has(self.mcpServer) && self.type == ''McpServer'')' + - message: type.agent must be nil if the type is not Agent + rule: '!(has(self.agent) && self.type != ''Agent'')' + - message: type.agent must be specified for Agent filter.type + rule: '!(!has(self.agent) && self.type == ''Agent'')' + maxItems: 20 + type: array + type: object + x-kubernetes-validations: + - message: systemMessage and systemMessageFrom are mutually exclusive + rule: '!has(self.systemMessage) || !has(self.systemMessageFrom)' + description: + type: string + skills: + properties: + gitAuthSecretRef: + description: |- + Reference to a Secret containing git credentials. + Applied to all gitRefs entries. + The secret should contain a `token` key for HTTPS auth, + or `ssh-privatekey` for SSH auth. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + gitRefs: + description: Git repositories to fetch skills from. + items: + description: GitRepo specifies a single Git repository to fetch + skills from. + properties: + name: + description: Name for the skill directory under /skills. + Defaults to the repo name. + type: string + path: + description: Subdirectory within the repo to use as the + skill root. + type: string + ref: + default: main + description: 'Git reference: branch name, tag, or commit + SHA.' + type: string + url: + description: URL of the git repository (HTTPS or SSH). + type: string + required: + - url + type: object + maxItems: 20 + minItems: 1 + type: array + initContainer: + description: Configuration for the skills-init init container. + properties: + env: + description: Additional environment variables for the skills-init + init container. + items: + description: EnvVar represents an environment variable present + in a Container. + properties: + name: + description: |- + Name of the environment variable. + May consist of any printable ASCII characters except '='. + type: string + value: + description: |- + Variable references $(VAR_NAME) are expanded + using the previously defined environment variables in the container and + any service environment variables. If a variable cannot be resolved, + the reference in the input string will be unchanged. Double $$ are reduced + to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. + "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". + Escaped references will never be expanded, regardless of whether the variable + exists or not. + Defaults to "". + type: string + valueFrom: + description: Source for the environment variable's value. + Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the ConfigMap or + its key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + description: |- + Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['']`, `metadata.annotations['']`, + spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs. + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in + the specified API version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + fileKeyRef: + description: |- + FileKeyRef selects a key of the env file. + Requires the EnvFiles feature gate to be enabled. + properties: + key: + description: |- + The key within the env file. An invalid key will prevent the pod from starting. + The keys defined within a source may consist of any printable ASCII characters except '='. + During Alpha stage of the EnvFiles feature gate, the key size is limited to 128 characters. + type: string + optional: + default: false + description: |- + Specify whether the file or its key must be defined. If the file or key + does not exist, then the env var is not published. + If optional is set to true and the specified key does not exist, + the environment variable will not be set in the Pod's containers. + + If optional is set to false and the specified key does not exist, + an error will be returned during Pod creation. + type: boolean + path: + description: |- + The path within the volume from which to select the file. + Must be relative and may not contain the '..' path or start with '..'. + type: string + volumeName: + description: The name of the volume mount containing + the env file. + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported. + properties: + containerName: + description: 'Container name: required for volumes, + optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of + the exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + description: Selects a key of a secret in the pod's + namespace + properties: + key: + description: The key of the secret to select + from. Must be a valid secret key. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the Secret or its + key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + required: + - name + type: object + type: array + resources: + description: Resource requirements for the skills-init init + container. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + + This field depends on the + DynamicResourceAllocation feature gate. + + This field is immutable. It can only be set for containers. + items: + description: ResourceClaim references one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + type: object + insecureSkipVerify: + description: |- + Fetch images insecurely from registries (allowing HTTP and skipping TLS verification). + Meant for development and testing purposes only. + type: boolean + refs: + description: The list of skill images to fetch. + items: + type: string + maxItems: 20 + minItems: 1 + type: array + type: object + type: + allOf: + - enum: + - Declarative + - BYO + - enum: + - Declarative + - BYO + default: Declarative + description: AgentType represents the agent type + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: type must be specified + rule: has(self.type) + - message: type must be either Declarative or BYO + rule: self.type == 'Declarative' || self.type == 'BYO' + - message: declarative or byo must match type + rule: (self.type == 'Declarative' && has(self.declarative)) || (self.type + == 'BYO' && has(self.byo)) + status: + description: AgentStatus defines the observed state of Agent. + properties: + conditions: + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + observedGeneration: + format: int64 + type: integer + required: + - observedGeneration + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/go/api/httpapi/types.go b/go/api/httpapi/types.go index 5cc45cda9..4c5ef5088 100644 --- a/go/api/httpapi/types.go +++ b/go/api/httpapi/types.go @@ -94,6 +94,8 @@ type AgentResponse struct { Tools []*v1alpha2.Tool `json:"tools"` DeploymentReady bool `json:"deploymentReady"` Accepted bool `json:"accepted"` + // RunInSandbox is true when the workload is reconciled as a SandboxAgent (SandboxTemplate/SandboxClaim), not a Deployment. + RunInSandbox bool `json:"runInSandbox,omitempty"` } // Session types diff --git a/go/api/v1alpha2/agent_types.go b/go/api/v1alpha2/agent_types.go index e41ab979b..4e4b1e460 100644 --- a/go/api/v1alpha2/agent_types.go +++ b/go/api/v1alpha2/agent_types.go @@ -78,6 +78,32 @@ type AgentSpec struct { AllowedNamespaces *AllowedNamespaces `json:"allowedNamespaces,omitempty"` } +// SandboxAgentSpec defines the desired state of a SandboxAgent. Workload fields match Agent.spec +// (Declarative or BYO). The kind selects sandbox reconciliation (SandboxTemplate + SandboxClaim) instead of a Deployment. +// +// +kubebuilder:validation:XValidation:message="type must be specified",rule="has(self.type)" +// +kubebuilder:validation:XValidation:message="type must be either Declarative or BYO",rule="self.type == 'Declarative' || self.type == 'BYO'" +// +kubebuilder:validation:XValidation:message="declarative or byo must match type",rule="(self.type == 'Declarative' && has(self.declarative)) || (self.type == 'BYO' && has(self.byo))" +type SandboxAgentSpec struct { + // +kubebuilder:validation:Enum=Declarative;BYO + // +kubebuilder:default=Declarative + Type AgentType `json:"type"` + + // +optional + BYO *BYOAgentSpec `json:"byo,omitempty"` + // +optional + Declarative *DeclarativeAgentSpec `json:"declarative,omitempty"` + + // +optional + Description string `json:"description,omitempty"` + + // +optional + Skills *SkillForAgent `json:"skills,omitempty"` + + // +optional + AllowedNamespaces *AllowedNamespaces `json:"allowedNamespaces,omitempty"` +} + // +kubebuilder:validation:AtLeastOneOf=refs,gitRefs type SkillForAgent struct { // Fetch images insecurely from registries (allowing HTTP and skipping TLS verification). diff --git a/go/api/v1alpha2/sandboxagent_types.go b/go/api/v1alpha2/sandboxagent_types.go new file mode 100644 index 000000000..cf988586e --- /dev/null +++ b/go/api/v1alpha2/sandboxagent_types.go @@ -0,0 +1,47 @@ +/* +Copyright 2025. + +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. +*/ + +package v1alpha2 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].status",description="Whether the sandbox workload is ready." +// +kubebuilder:printcolumn:name="Accepted",type="string",JSONPath=".status.conditions[?(@.type=='Accepted')].status",description="Whether configuration was accepted." +// SandboxAgent declares an agent that runs in an isolated sandbox (agent-sandbox SandboxTemplate+SandboxClaim). +type SandboxAgent struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec SandboxAgentSpec `json:"spec,omitempty"` + Status AgentStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// SandboxAgentList contains a list of SandboxAgent. +type SandboxAgentList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []SandboxAgent `json:"items"` +} + +func init() { + SchemeBuilder.Register(&SandboxAgent{}, &SandboxAgentList{}) +} diff --git a/go/api/v1alpha2/zz_generated.deepcopy.go b/go/api/v1alpha2/zz_generated.deepcopy.go index 56ff1153e..fe085fde2 100644 --- a/go/api/v1alpha2/zz_generated.deepcopy.go +++ b/go/api/v1alpha2/zz_generated.deepcopy.go @@ -1110,6 +1110,100 @@ func (in *RemoteMCPServerStatus) DeepCopy() *RemoteMCPServerStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SandboxAgent) DeepCopyInto(out *SandboxAgent) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SandboxAgent. +func (in *SandboxAgent) DeepCopy() *SandboxAgent { + if in == nil { + return nil + } + out := new(SandboxAgent) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *SandboxAgent) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SandboxAgentList) DeepCopyInto(out *SandboxAgentList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]SandboxAgent, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SandboxAgentList. +func (in *SandboxAgentList) DeepCopy() *SandboxAgentList { + if in == nil { + return nil + } + out := new(SandboxAgentList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *SandboxAgentList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SandboxAgentSpec) DeepCopyInto(out *SandboxAgentSpec) { + *out = *in + if in.BYO != nil { + in, out := &in.BYO, &out.BYO + *out = new(BYOAgentSpec) + (*in).DeepCopyInto(*out) + } + if in.Declarative != nil { + in, out := &in.Declarative, &out.Declarative + *out = new(DeclarativeAgentSpec) + (*in).DeepCopyInto(*out) + } + if in.Skills != nil { + in, out := &in.Skills, &out.Skills + *out = new(SkillForAgent) + (*in).DeepCopyInto(*out) + } + if in.AllowedNamespaces != nil { + in, out := &in.AllowedNamespaces, &out.AllowedNamespaces + *out = new(AllowedNamespaces) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SandboxAgentSpec. +func (in *SandboxAgentSpec) DeepCopy() *SandboxAgentSpec { + if in == nil { + return nil + } + out := new(SandboxAgentSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SecretReference) DeepCopyInto(out *SecretReference) { *out = *in diff --git a/go/core/cmd/controller/main.go b/go/core/cmd/controller/main.go index ff47ba86b..576dee94a 100644 --- a/go/core/cmd/controller/main.go +++ b/go/core/cmd/controller/main.go @@ -20,6 +20,7 @@ import ( "github.com/kagent-dev/kagent/go/core/internal/httpserver/auth" "github.com/kagent-dev/kagent/go/core/pkg/app" pkgauth "github.com/kagent-dev/kagent/go/core/pkg/auth" + "github.com/kagent-dev/kagent/go/core/pkg/sandboxbackend/agentsxk8s" // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) // to ensure that exec-entrypoint and run can make use of them. @@ -32,9 +33,10 @@ func main() { app.Start(func(bootstrap app.BootstrapConfig) (*app.ExtensionConfig, error) { authenticator := getAuthenticator(bootstrap.Config.Auth) return &app.ExtensionConfig{ - Authenticator: authenticator, - Authorizer: authorizer, - AgentPlugins: nil, + Authenticator: authenticator, + Authorizer: authorizer, + AgentPlugins: nil, + SandboxBackend: agentsxk8s.New(), }, nil }, nil) } diff --git a/go/core/internal/a2a/a2a_registrar.go b/go/core/internal/a2a/a2a_registrar.go index aa26b0d1e..6c699e842 100644 --- a/go/core/internal/a2a/a2a_registrar.go +++ b/go/core/internal/a2a/a2a_registrar.go @@ -112,6 +112,53 @@ func (a *A2ARegistrar) Start(ctx context.Context) error { return fmt.Errorf("failed to add informer event handler: %w", err) } + sandboxInformer, err := a.cache.GetInformer(ctx, &v1alpha2.SandboxAgent{}) + if err != nil { + return fmt.Errorf("failed to get sandboxagent cache informer: %w", err) + } + + if _, err := sandboxInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: func(obj any) { + if sa, ok := obj.(*v1alpha2.SandboxAgent); ok { + agentView := agent_translator.AgentViewFromSandboxAgent(sa) + if err := a.upsertAgentHandler(ctx, agentView, log); err != nil { + log.Error(err, "failed to upsert A2A handler", "sandboxagent", common.GetObjectRef(sa)) + } + } + }, + UpdateFunc: func(oldObj, newObj any) { + oldSA, ok1 := oldObj.(*v1alpha2.SandboxAgent) + newSA, ok2 := newObj.(*v1alpha2.SandboxAgent) + if !ok1 || !ok2 { + return + } + if oldSA.Generation != newSA.Generation || !reflect.DeepEqual(oldSA.Spec, newSA.Spec) { + agentView := agent_translator.AgentViewFromSandboxAgent(newSA) + if err := a.upsertAgentHandler(ctx, agentView, log); err != nil { + log.Error(err, "failed to upsert A2A handler", "sandboxagent", common.GetObjectRef(newSA)) + } + } + }, + DeleteFunc: func(obj any) { + sa, ok := obj.(*v1alpha2.SandboxAgent) + if !ok { + if tombstone, ok := obj.(cache.DeletedFinalStateUnknown); ok { + if s2, ok := tombstone.Obj.(*v1alpha2.SandboxAgent); ok { + sa = s2 + } + } + } + if sa == nil { + return + } + ref := common.GetObjectRef(sa) + a.handlerMux.RemoveAgentHandler(ref) + log.V(1).Info("removed A2A handler", "sandboxagent", ref) + }, + }); err != nil { + return fmt.Errorf("failed to add sandboxagent informer event handler: %w", err) + } + if ok := a.cache.WaitForCacheSync(ctx); !ok { return fmt.Errorf("cache sync failed") } diff --git a/go/core/internal/controller/agent_controller.go b/go/core/internal/controller/agent_controller.go index 4c048290b..c2c01a6bc 100644 --- a/go/core/internal/controller/agent_controller.go +++ b/go/core/internal/controller/agent_controller.go @@ -57,6 +57,15 @@ type AgentController struct { // +kubebuilder:rbac:groups=core,resources=serviceaccounts,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=core,resources=configmaps,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=agents.x-k8s.io,resources=sandboxes,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=agents.x-k8s.io,resources=sandboxes/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=agents.x-k8s.io,resources=sandboxes/finalizers,verbs=update +// +kubebuilder:rbac:groups=extensions.agents.x-k8s.io,resources=sandboxtemplates,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=extensions.agents.x-k8s.io,resources=sandboxtemplates/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=extensions.agents.x-k8s.io,resources=sandboxtemplates/finalizers,verbs=update +// +kubebuilder:rbac:groups=extensions.agents.x-k8s.io,resources=sandboxclaims,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=extensions.agents.x-k8s.io,resources=sandboxclaims/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=extensions.agents.x-k8s.io,resources=sandboxclaims/finalizers,verbs=update func (r *AgentController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { _ = log.FromContext(ctx) diff --git a/go/core/internal/controller/mcp_server_tool_controller_test.go b/go/core/internal/controller/mcp_server_tool_controller_test.go index 3c7a7a4bf..a74b1559c 100644 --- a/go/core/internal/controller/mcp_server_tool_controller_test.go +++ b/go/core/internal/controller/mcp_server_tool_controller_test.go @@ -31,6 +31,10 @@ func (f *fakeReconciler) ReconcileKagentAgent(ctx context.Context, req ctrl.Requ return nil } +func (f *fakeReconciler) ReconcileKagentSandboxAgent(ctx context.Context, req ctrl.Request) error { + return nil +} + func (f *fakeReconciler) ReconcileKagentModelConfig(ctx context.Context, req ctrl.Request) error { return nil } diff --git a/go/core/internal/controller/reconciler/mcp_server_reconciler_test.go b/go/core/internal/controller/reconciler/mcp_server_reconciler_test.go index 15ba5bd62..70dbeeef2 100644 --- a/go/core/internal/controller/reconciler/mcp_server_reconciler_test.go +++ b/go/core/internal/controller/reconciler/mcp_server_reconciler_test.go @@ -102,6 +102,7 @@ func TestReconcileKagentMCPServer_ErrorPropagation(t *testing.T) { types.NamespacedName{Namespace: "test", Name: "default-model"}, nil, "", + nil, ) reconciler := NewKagentReconciler( translator, @@ -109,6 +110,7 @@ func TestReconcileKagentMCPServer_ErrorPropagation(t *testing.T) { dbClient, types.NamespacedName{Namespace: "test", Name: "default-model"}, []string{}, // No namespace restrictions for tests + nil, ) // Call ReconcileKagentMCPServer diff --git a/go/core/internal/controller/reconciler/reconciler.go b/go/core/internal/controller/reconciler/reconciler.go index 0e854a567..e54785a89 100644 --- a/go/core/internal/controller/reconciler/reconciler.go +++ b/go/core/internal/controller/reconciler/reconciler.go @@ -14,6 +14,7 @@ import ( "github.com/hashicorp/go-multierror" reconcilerutils "github.com/kagent-dev/kagent/go/core/internal/controller/reconciler/utils" "github.com/kagent-dev/kagent/go/core/internal/controller/translator" + "github.com/kagent-dev/kagent/go/core/pkg/sandboxbackend" "github.com/kagent-dev/kmcp/api/v1alpha1" appsv1 "k8s.io/api/apps/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -40,8 +41,15 @@ var ( reconcileLog = ctrl.Log.WithName("reconciler") ) +// Reasons for Agent status condition type Ready. +const ( + AgentReadyReasonDeploymentReady = "DeploymentReady" + AgentReadyReasonWorkloadReady = "WorkloadReady" +) + type KagentReconciler interface { ReconcileKagentAgent(ctx context.Context, req ctrl.Request) error + ReconcileKagentSandboxAgent(ctx context.Context, req ctrl.Request) error ReconcileKagentModelConfig(ctx context.Context, req ctrl.Request) error ReconcileKagentRemoteMCPServer(ctx context.Context, req ctrl.Request) error ReconcileKagentMCPService(ctx context.Context, req ctrl.Request) error @@ -62,6 +70,8 @@ type kagentReconciler struct { // watchedNamespaces is the list of namespaces the controller watches. // An empty list means watching all namespaces. watchedNamespaces []string + + sandboxBackend sandboxbackend.Backend } func NewKagentReconciler( @@ -70,6 +80,7 @@ func NewKagentReconciler( dbClient database.Client, defaultModelConfig types.NamespacedName, watchedNamespaces []string, + sandboxBackend sandboxbackend.Backend, ) KagentReconciler { return &kagentReconciler{ adkTranslator: translator, @@ -77,6 +88,7 @@ func NewKagentReconciler( dbClient: dbClient, defaultModelConfig: defaultModelConfig, watchedNamespaces: watchedNamespaces, + sandboxBackend: sandboxBackend, } } @@ -110,6 +122,159 @@ func (a *kagentReconciler) handleAgentDeletion(ctx context.Context, req ctrl.Req return nil } +func (a *kagentReconciler) ReconcileKagentSandboxAgent(ctx context.Context, req ctrl.Request) error { + sa := &v1alpha2.SandboxAgent{} + if err := a.kube.Get(ctx, req.NamespacedName, sa); err != nil { + if apierrors.IsNotFound(err) { + return a.handleSandboxAgentDeletion(ctx, req) + } + return fmt.Errorf("failed to get sandboxagent %s: %w", req.NamespacedName, err) + } + + err := a.reconcileSandboxAgent(ctx, sa) + if err != nil { + reconcileLog.Error(err, "failed to reconcile sandboxagent", "sandboxagent", req.NamespacedName) + } + + return a.reconcileSandboxAgentStatus(ctx, sa, err) +} + +func (a *kagentReconciler) handleSandboxAgentDeletion(ctx context.Context, req ctrl.Request) error { + id := utils.ConvertToPythonIdentifier(req.String()) + if err := a.dbClient.DeleteAgent(ctx, id); err != nil { + return fmt.Errorf("failed to delete sandbox agent %s from db: %w", req.String(), err) + } + + reconcileLog.Info("SandboxAgent was deleted", "namespace", req.Namespace, "name", req.Name) + return nil +} + +func (a *kagentReconciler) reassignManifestOwnershipToSandboxAgent(sa *v1alpha2.SandboxAgent, manifest []client.Object) error { + for _, obj := range manifest { + obj.SetOwnerReferences(nil) + if err := controllerutil.SetControllerReference(sa, obj, a.kube.Scheme()); err != nil { + return fmt.Errorf("set controller reference for %s %s/%s: %w", obj.GetObjectKind().GroupVersionKind().Kind, obj.GetNamespace(), obj.GetName(), err) + } + } + return nil +} + +func (a *kagentReconciler) reconcileSandboxAgent(ctx context.Context, sa *v1alpha2.SandboxAgent) error { + if a.sandboxBackend != nil { + if err := sandboxbackend.EnsureAgentSandboxAPIsRegistered(ctx, a.kube); err != nil { + return err + } + } + + agentView := agent_translator.AgentViewFromSandboxAgent(sa) + if err := a.validateCrossNamespaceReferences(ctx, agentView); err != nil { + return err + } + + agentOutputs, err := a.adkTranslator.TranslateAgent(ctx, agentView, true) + if err != nil { + return fmt.Errorf("failed to translate sandboxagent %s/%s: %w", sa.Namespace, sa.Name, err) + } + + if err := a.reassignManifestOwnershipToSandboxAgent(sa, agentOutputs.Manifest); err != nil { + return err + } + + ownedObjects, err := reconcilerutils.FindOwnedObjects(ctx, a.kube, sa.UID, sa.Namespace, a.adkTranslator.GetOwnedResourceTypes()) + if err != nil { + return err + } + + if err := a.reconcileDesiredObjects(ctx, sa, agentOutputs.Manifest, ownedObjects); err != nil { + return fmt.Errorf("failed to reconcile owned objects: %w", err) + } + + if err := a.resyncAgentSandboxWorkload(ctx, sa); err != nil { + return err + } + + if err := a.upsertAgent(ctx, agentView, agentOutputs, true); err != nil { + return fmt.Errorf("failed to upsert agent %s/%s: %w", sa.Namespace, sa.Name, err) + } + + return nil +} + +func (a *kagentReconciler) reconcileSandboxAgentStatus(ctx context.Context, sa *v1alpha2.SandboxAgent, reconcileErr error) error { + agentView := agent_translator.AgentViewFromSandboxAgent(sa) + var ( + status metav1.ConditionStatus + message string + reason string + ) + if reconcileErr != nil { + status = metav1.ConditionFalse + message = reconcileErr.Error() + reason = "ReconcileFailed" + } else { + status = metav1.ConditionTrue + reason = "Reconciled" + message = "SandboxAgent configuration accepted" + } + + conditionChanged := meta.SetStatusCondition(&sa.Status.Conditions, metav1.Condition{ + Type: v1alpha2.AgentConditionTypeAccepted, + Status: status, + Reason: reason, + Message: message, + ObservedGeneration: sa.Generation, + }) + + if warning := a.validateRuntimeFeatures(agentView); warning != "" { + conditionChanged = conditionChanged || meta.SetStatusCondition(&sa.Status.Conditions, metav1.Condition{ + Type: v1alpha2.AgentConditionTypeUnsupportedFeatures, + Status: metav1.ConditionTrue, + Reason: "UnsupportedFeatures", + Message: warning, + ObservedGeneration: sa.Generation, + }) + } else { + for i, cond := range sa.Status.Conditions { + if cond.Type == v1alpha2.AgentConditionTypeUnsupportedFeatures && cond.Reason == "UnsupportedFeatures" { + sa.Status.Conditions = append(sa.Status.Conditions[:i], sa.Status.Conditions[i+1:]...) + conditionChanged = true + break + } + } + } + + deployedCondition := metav1.Condition{ + Type: v1alpha2.AgentConditionTypeReady, + Status: metav1.ConditionUnknown, + ObservedGeneration: sa.Generation, + } + + if a.sandboxBackend == nil { + deployedCondition.Status = metav1.ConditionUnknown + deployedCondition.Reason = "SandboxBackendNotConfigured" + deployedCondition.Message = "Sandbox backend is not configured" + } else { + st, reason, msg := a.sandboxBackend.ComputeReady(ctx, a.kube, types.NamespacedName{Namespace: sa.Namespace, Name: sa.Name}) + deployedCondition.Status = st + deployedCondition.Reason = reason + deployedCondition.Message = msg + if st == metav1.ConditionTrue { + deployedCondition.Reason = AgentReadyReasonWorkloadReady + } + } + + conditionChanged = conditionChanged || meta.SetStatusCondition(&sa.Status.Conditions, deployedCondition) + + if conditionChanged || sa.Status.ObservedGeneration != sa.Generation { + sa.Status.ObservedGeneration = sa.Generation + if err := a.kube.Status().Update(ctx, sa); err != nil { + return fmt.Errorf("failed to update sandboxagent status: %w", err) + } + } + + return nil +} + func (a *kagentReconciler) reconcileAgentStatus(ctx context.Context, agent *v1alpha2.Agent, err error) error { var ( status metav1.ConditionStatus @@ -161,25 +326,28 @@ func (a *kagentReconciler) reconcileAgentStatus(ctx context.Context, agent *v1al ObservedGeneration: agent.Generation, } - // Check if the deployment exists - deployment := &appsv1.Deployment{} - if err := a.kube.Get(ctx, types.NamespacedName{Namespace: agent.Namespace, Name: agent.Name}, deployment); err != nil { - deployedCondition.Status = metav1.ConditionUnknown - deployedCondition.Reason = "DeploymentNotFound" - deployedCondition.Message = err.Error() - } else { - replicas := int32(1) - if deployment.Spec.Replicas != nil { - replicas = *deployment.Spec.Replicas - } - if deployment.Status.AvailableReplicas >= replicas { - deployedCondition.Status = metav1.ConditionTrue - deployedCondition.Reason = "DeploymentReady" - deployedCondition.Message = "Deployment is ready" + switch agent.Spec.Type { + default: + // Check if the deployment exists + deployment := &appsv1.Deployment{} + if err := a.kube.Get(ctx, types.NamespacedName{Namespace: agent.Namespace, Name: agent.Name}, deployment); err != nil { + deployedCondition.Status = metav1.ConditionUnknown + deployedCondition.Reason = "DeploymentNotFound" + deployedCondition.Message = err.Error() } else { - deployedCondition.Status = metav1.ConditionFalse - deployedCondition.Reason = "DeploymentNotReady" - deployedCondition.Message = fmt.Sprintf("Deployment is not ready, %d/%d pods are ready", deployment.Status.AvailableReplicas, replicas) + replicas := int32(1) + if deployment.Spec.Replicas != nil { + replicas = *deployment.Spec.Replicas + } + if deployment.Status.AvailableReplicas >= replicas { + deployedCondition.Status = metav1.ConditionTrue + deployedCondition.Reason = AgentReadyReasonDeploymentReady + deployedCondition.Message = "Deployment is ready" + } else { + deployedCondition.Status = metav1.ConditionFalse + deployedCondition.Reason = "DeploymentNotReady" + deployedCondition.Message = fmt.Sprintf("Deployment is not ready, %d/%d pods are ready", deployment.Status.AvailableReplicas, replicas) + } } } @@ -526,8 +694,9 @@ func (a *kagentReconciler) validateCrossNamespaceReferences(ctx context.Context, if agent.Spec.Type != v1alpha2.AgentType_Declarative || agent.Spec.Declarative == nil { return nil } + decl := agent.Spec.Declarative - for _, tool := range agent.Spec.Declarative.Tools { + for _, tool := range decl.Tools { switch { case tool.McpServer != nil: if err := a.validateMcpServerReference(ctx, agent.Namespace, tool.McpServer); err != nil { @@ -644,7 +813,7 @@ func (a *kagentReconciler) reconcileAgent(ctx context.Context, agent *v1alpha2.A return err } - agentOutputs, err := a.adkTranslator.TranslateAgent(ctx, agent) + agentOutputs, err := a.adkTranslator.TranslateAgent(ctx, agent, false) if err != nil { return fmt.Errorf("failed to translate agent %s/%s: %w", agent.Namespace, agent.Name, err) } @@ -658,7 +827,7 @@ func (a *kagentReconciler) reconcileAgent(ctx context.Context, agent *v1alpha2.A return fmt.Errorf("failed to reconcile owned objects: %w", err) } - if err := a.upsertAgent(ctx, agent, agentOutputs); err != nil { + if err := a.upsertAgent(ctx, agent, agentOutputs, false); err != nil { return fmt.Errorf("failed to upsert agent %s/%s: %w", agent.Namespace, agent.Name, err) } @@ -669,12 +838,13 @@ func (a *kagentReconciler) reconcileAgent(ctx context.Context, agent *v1alpha2.A // Returns a warning message if unsupported features are detected, empty string otherwise. // This implements soft validation - warns but doesn't fail reconciliation. func (a *kagentReconciler) validateRuntimeFeatures(agent *v1alpha2.Agent) string { - if agent.Spec.Declarative == nil { + if agent.Spec.Type != v1alpha2.AgentType_Declarative || agent.Spec.Declarative == nil { return "" } + decl := agent.Spec.Declarative // Get runtime (defaults to python) - runtime := agent.Spec.Declarative.Runtime + runtime := decl.Runtime if runtime == "" { runtime = v1alpha2.DeclarativeRuntime_Python } @@ -688,13 +858,13 @@ func (a *kagentReconciler) validateRuntimeFeatures(agent *v1alpha2.Agent) string var unsupported []string // ExecuteCodeBlocks: deprecated, not implementing in Go - if agent.Spec.Declarative.ExecuteCodeBlocks != nil && *agent.Spec.Declarative.ExecuteCodeBlocks { + if decl.ExecuteCodeBlocks != nil && *decl.ExecuteCodeBlocks { unsupported = append(unsupported, "code execution (executeCodeBlocks is deprecated)") } // Memory: ✅ Supported in Go as of PR #1444 // Context compression: Not yet implemented in Go runtime - if agent.Spec.Declarative.Context != nil && agent.Spec.Declarative.Context.Compaction != nil { + if decl.Context != nil && decl.Context.Compaction != nil { unsupported = append(unsupported, "context compression/compaction (not implemented in Go runtime)") } @@ -827,11 +997,15 @@ func (a *kagentReconciler) deleteObjects(ctx context.Context, objects map[types. return errors.Join(pruneErrs...) } -func (a *kagentReconciler) upsertAgent(ctx context.Context, agent *v1alpha2.Agent, agentOutputs *agent_translator.AgentOutputs) error { +func (a *kagentReconciler) upsertAgent(ctx context.Context, agent *v1alpha2.Agent, agentOutputs *agent_translator.AgentOutputs, runInSandbox bool) error { id := utils.ConvertToPythonIdentifier(utils.GetObjectRef(agent)) + dbType := string(agent.Spec.Type) + if runInSandbox { + dbType = "SandboxAgent" + } dbAgent := &database.Agent{ ID: id, - Type: string(agent.Spec.Type), + Type: dbType, Config: agentOutputs.Config, } diff --git a/go/core/internal/controller/reconciler/sandboxagent_sandbox_sync.go b/go/core/internal/controller/reconciler/sandboxagent_sandbox_sync.go new file mode 100644 index 000000000..f2dbff191 --- /dev/null +++ b/go/core/internal/controller/reconciler/sandboxagent_sandbox_sync.go @@ -0,0 +1,81 @@ +package reconciler + +import ( + "context" + "fmt" + + "github.com/kagent-dev/kagent/go/api/v1alpha2" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/util/retry" + agentsandboxv1 "sigs.k8s.io/agent-sandbox/api/v1alpha1" + extensionsv1alpha1 "sigs.k8s.io/agent-sandbox/extensions/api/v1alpha1" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// sandboxTemplateResourceVersionAnnotation records the SandboxTemplate.metadata.resourceVersion +// last applied to the live Sandbox workload. agent-sandbox's SandboxClaim controller only copies +// PodTemplate from SandboxTemplate when it creates the Sandbox; it does not update an existing +// Sandbox when the template changes. The core Sandbox controller likewise leaves an existing Pod +// spec untouched—see upstream TODO: https://github.com/kubernetes-sigs/agent-sandbox/blob/059575c213eec95641c9229da8fd4525cb919617/controllers/sandbox_controller.go#L601 +// Once/if the upstream fixes/implements it, then we can remove this resync logic. +const sandboxTemplateResourceVersionAnnotation = "kagent.dev/last-synced-sandbox-template-resource-version" + +func sandboxTemplateNameForAgent(agentName string) string { + return fmt.Sprintf("kagent-%s", agentName) +} + +func (a *kagentReconciler) resyncAgentSandboxWorkload(ctx context.Context, sa *v1alpha2.SandboxAgent) error { + if a.sandboxBackend == nil { + return nil + } + + tmplKey := types.NamespacedName{Namespace: sa.Namespace, Name: sandboxTemplateNameForAgent(sa.Name)} + tmpl := &extensionsv1alpha1.SandboxTemplate{} + if err := a.kube.Get(ctx, tmplKey, tmpl); err != nil { + if apierrors.IsNotFound(err) { + return nil + } + return fmt.Errorf("get SandboxTemplate for workload resync: %w", err) + } + wantRV := tmpl.ResourceVersion + + claimKey := types.NamespacedName{Namespace: sa.Namespace, Name: sa.Name} + claim := &extensionsv1alpha1.SandboxClaim{} + if err := a.kube.Get(ctx, claimKey, claim); err != nil { + if apierrors.IsNotFound(err) { + return nil + } + return fmt.Errorf("get SandboxClaim for workload resync: %w", err) + } + + stored := "" + if claim.Annotations != nil { + stored = claim.Annotations[sandboxTemplateResourceVersionAnnotation] + } + if wantRV == stored { + return nil + } + + sb := &agentsandboxv1.Sandbox{} + if err := a.kube.Get(ctx, claimKey, sb); err == nil { + if err := a.kube.Delete(ctx, sb); err != nil { + return fmt.Errorf("delete Sandbox %s/%s to apply updated SandboxTemplate: %w", sb.Namespace, sb.Name, err) + } + } else if !apierrors.IsNotFound(err) { + return fmt.Errorf("get Sandbox for workload resync: %w", err) + } + + return retry.RetryOnConflict(retry.DefaultRetry, func() error { + fresh := &extensionsv1alpha1.SandboxClaim{} + if err := a.kube.Get(ctx, claimKey, fresh); err != nil { + return fmt.Errorf("get SandboxClaim for sync annotation: %w", err) + } + base := fresh.DeepCopy() + if fresh.Annotations == nil { + fresh.Annotations = make(map[string]string) + } + fresh.Annotations[sandboxTemplateResourceVersionAnnotation] = wantRV + return a.kube.Patch(ctx, fresh, client.MergeFrom(base)) + }) +} diff --git a/go/core/internal/controller/reconciler/sandboxagent_sandbox_sync_test.go b/go/core/internal/controller/reconciler/sandboxagent_sandbox_sync_test.go new file mode 100644 index 000000000..95424b3cf --- /dev/null +++ b/go/core/internal/controller/reconciler/sandboxagent_sandbox_sync_test.go @@ -0,0 +1,70 @@ +package reconciler + +import ( + "context" + "testing" + + "github.com/kagent-dev/kagent/go/api/v1alpha2" + "github.com/kagent-dev/kagent/go/core/pkg/sandboxbackend/agentsxk8s" + "github.com/stretchr/testify/require" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + + agentsandboxv1 "sigs.k8s.io/agent-sandbox/api/v1alpha1" + extensionsv1alpha1 "sigs.k8s.io/agent-sandbox/extensions/api/v1alpha1" +) + +func TestResyncAgentSandboxWorkload_deletesSandboxWhenTemplateRVChanges(t *testing.T) { + scheme := runtime.NewScheme() + require.NoError(t, extensionsv1alpha1.AddToScheme(scheme)) + require.NoError(t, agentsandboxv1.AddToScheme(scheme)) + require.NoError(t, v1alpha2.AddToScheme(scheme)) + + ns := "ns1" + agName := "myagent" + tmplName := sandboxTemplateNameForAgent(agName) + + tmpl := &extensionsv1alpha1.SandboxTemplate{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: ns, + Name: tmplName, + ResourceVersion: "rv-2", + }, + Spec: extensionsv1alpha1.SandboxTemplateSpec{}, + } + claim := &extensionsv1alpha1.SandboxClaim{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: ns, + Name: agName, + Annotations: map[string]string{ + sandboxTemplateResourceVersionAnnotation: "rv-1", + }, + }, + Spec: extensionsv1alpha1.SandboxClaimSpec{ + TemplateRef: extensionsv1alpha1.SandboxTemplateRef{Name: tmplName}, + }, + } + sb := &agentsandboxv1.Sandbox{ + ObjectMeta: metav1.ObjectMeta{Namespace: ns, Name: agName}, + Spec: agentsandboxv1.SandboxSpec{}, + } + + cl := fake.NewClientBuilder().WithScheme(scheme).WithObjects(tmpl, claim, sb).Build() + + r := &kagentReconciler{kube: cl, sandboxBackend: agentsxk8s.New()} + sa := &v1alpha2.SandboxAgent{ + ObjectMeta: metav1.ObjectMeta{Namespace: ns, Name: agName}, + } + + require.NoError(t, r.resyncAgentSandboxWorkload(context.Background(), sa)) + + err := cl.Get(context.Background(), client.ObjectKeyFromObject(sb), sb) + require.True(t, apierrors.IsNotFound(err)) + + var updatedClaim extensionsv1alpha1.SandboxClaim + require.NoError(t, cl.Get(context.Background(), client.ObjectKeyFromObject(claim), &updatedClaim)) + require.Equal(t, "rv-2", updatedClaim.Annotations[sandboxTemplateResourceVersionAnnotation]) +} diff --git a/go/core/internal/controller/reconciler/utils/reconciler_utils.go b/go/core/internal/controller/reconciler/utils/reconciler_utils.go index 4178537b4..70377df69 100644 --- a/go/core/internal/controller/reconciler/utils/reconciler_utils.go +++ b/go/core/internal/controller/reconciler/utils/reconciler_utils.go @@ -149,9 +149,9 @@ func SetupOwnerIndexes(mgr ctrl.Manager, ownedTypes []client.Object) error { } // This is an optimisation to avoid indexing every owned object, - // only those owned by an Agent will be indexed. It may need to be + // only those owned by Agent or SandboxAgent will be indexed. It may need to be // adjusted in future if other controllers start owning resources. - if owner.Kind != "Agent" { + if owner.Kind != "Agent" && owner.Kind != "SandboxAgent" { return nil } diff --git a/go/core/internal/controller/sandboxagent_controller.go b/go/core/internal/controller/sandboxagent_controller.go new file mode 100644 index 000000000..c9abf538e --- /dev/null +++ b/go/core/internal/controller/sandboxagent_controller.go @@ -0,0 +1,328 @@ +/* +Copyright 2025. + +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" 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. +*/ + +package controller + +import ( + "context" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + "github.com/kagent-dev/kagent/go/api/v1alpha2" + "github.com/kagent-dev/kagent/go/core/internal/controller/reconciler" + agent_translator "github.com/kagent-dev/kagent/go/core/internal/controller/translator/agent" + "github.com/kagent-dev/kmcp/api/v1alpha1" +) + +var ( + sandboxAgentControllerLog = ctrl.Log.WithName("sandboxagent-controller") +) + +// SandboxAgentController reconciles SandboxAgent objects. +type SandboxAgentController struct { + Scheme *runtime.Scheme + Reconciler reconciler.KagentReconciler + AdkTranslator agent_translator.AdkApiTranslator +} + +// +kubebuilder:rbac:groups=kagent.dev,resources=sandboxagents,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=kagent.dev,resources=sandboxagents/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=kagent.dev,resources=sandboxagents/finalizers,verbs=update +// +kubebuilder:rbac:groups=core,resources=secrets,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=core,resources=serviceaccounts,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=core,resources=configmaps,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=agents.x-k8s.io,resources=sandboxes,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=agents.x-k8s.io,resources=sandboxes/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=agents.x-k8s.io,resources=sandboxes/finalizers,verbs=update +// +kubebuilder:rbac:groups=extensions.agents.x-k8s.io,resources=sandboxtemplates,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=extensions.agents.x-k8s.io,resources=sandboxtemplates/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=extensions.agents.x-k8s.io,resources=sandboxtemplates/finalizers,verbs=update +// +kubebuilder:rbac:groups=extensions.agents.x-k8s.io,resources=sandboxclaims,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=extensions.agents.x-k8s.io,resources=sandboxclaims/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=extensions.agents.x-k8s.io,resources=sandboxclaims/finalizers,verbs=update + +func (r *SandboxAgentController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + _ = ctrl.LoggerFrom(ctx) + return ctrl.Result{}, r.Reconciler.ReconcileKagentSandboxAgent(ctx, req) +} + +// SetupWithManager sets up the controller with the Manager. +func (r *SandboxAgentController) SetupWithManager(mgr ctrl.Manager) error { + build := ctrl.NewControllerManagedBy(mgr). + WithOptions(controller.Options{ + NeedLeaderElection: new(true), + }). + For(&v1alpha2.SandboxAgent{}, builder.WithPredicates(predicate.Or(predicate.GenerationChangedPredicate{}, predicate.LabelChangedPredicate{}))) + + for _, ownedType := range r.AdkTranslator.GetOwnedResourceTypes() { + build = build.Owns(ownedType, builder.WithPredicates(ownedObjectPredicate{}, predicate.ResourceVersionChangedPredicate{})) + } + + build = build.Watches( + &v1alpha2.ModelConfig{}, + handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []reconcile.Request { + var requests []reconcile.Request + for _, sa := range r.findSandboxAgentsUsingModelConfig(ctx, mgr.GetClient(), types.NamespacedName{ + Name: obj.GetName(), + Namespace: obj.GetNamespace(), + }) { + requests = append(requests, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: sa.Name, + Namespace: sa.Namespace, + }, + }) + } + return requests + }), + builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}), + ). + Watches( + &v1alpha2.RemoteMCPServer{}, + handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []reconcile.Request { + var requests []reconcile.Request + for _, sa := range r.findSandboxAgentsUsingRemoteMCPServer(ctx, mgr.GetClient(), types.NamespacedName{ + Name: obj.GetName(), + Namespace: obj.GetNamespace(), + }) { + requests = append(requests, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: sa.Name, + Namespace: sa.Namespace, + }, + }) + } + return requests + }), + ). + Watches( + &corev1.Service{}, + handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []reconcile.Request { + var requests []reconcile.Request + for _, sa := range r.findSandboxAgentsUsingMCPService(ctx, mgr.GetClient(), types.NamespacedName{ + Name: obj.GetName(), + Namespace: obj.GetNamespace(), + }) { + requests = append(requests, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: sa.Name, + Namespace: sa.Namespace, + }, + }) + } + return requests + }), + builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}), + ). + Watches( + &corev1.ConfigMap{}, + handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []reconcile.Request { + var requests []reconcile.Request + for _, sa := range r.findSandboxAgentsReferencingConfigMap(ctx, mgr.GetClient(), types.NamespacedName{ + Name: obj.GetName(), + Namespace: obj.GetNamespace(), + }) { + requests = append(requests, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: sa.Name, + Namespace: sa.Namespace, + }, + }) + } + return requests + }), + builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}), + ) + + if _, err := mgr.GetRESTMapper().RESTMapping(mcpServerGK); err == nil { + build = build.Watches( + &v1alpha1.MCPServer{}, + handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []reconcile.Request { + var requests []reconcile.Request + for _, sa := range r.findSandboxAgentsUsingMCPServer(ctx, mgr.GetClient(), types.NamespacedName{ + Name: obj.GetName(), + Namespace: obj.GetNamespace(), + }) { + requests = append(requests, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: sa.Name, + Namespace: sa.Namespace, + }, + }) + } + return requests + }), + builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}), + ) + } + + return build.Named("sandboxagent").Complete(r) +} + +func (r *SandboxAgentController) findSandboxAgentsUsingMCPServer(ctx context.Context, cl client.Client, obj types.NamespacedName) []*v1alpha2.SandboxAgent { + var list v1alpha2.SandboxAgentList + if err := cl.List(ctx, &list); err != nil { + sandboxAgentControllerLog.Error(err, "failed to list sandboxagents for MCPServer watch") + return nil + } + + var out []*v1alpha2.SandboxAgent + for i := range list.Items { + sa := &list.Items[i] + decl := sa.Spec.Declarative + if decl == nil { + continue + } + for _, tool := range decl.Tools { + if tool.McpServer == nil { + continue + } + if tool.McpServer.ApiGroup != "kagent.dev" || tool.McpServer.Kind != "MCPServer" { + continue + } + if tool.McpServer.NamespacedName(sa.Namespace) == obj { + out = append(out, sa) + } + } + } + return out +} + +func (r *SandboxAgentController) findSandboxAgentsUsingRemoteMCPServer(ctx context.Context, cl client.Client, obj types.NamespacedName) []*v1alpha2.SandboxAgent { + var out []*v1alpha2.SandboxAgent + + var list v1alpha2.SandboxAgentList + if err := cl.List(ctx, &list); err != nil { + sandboxAgentControllerLog.Error(err, "failed to list sandboxagents for RemoteMCPServer watch") + return out + } + + for i := range list.Items { + sa := &list.Items[i] + decl := sa.Spec.Declarative + if decl == nil { + continue + } + for _, tool := range decl.Tools { + if tool.McpServer == nil { + continue + } + mcpServerRef := tool.McpServer.NamespacedName(sa.Namespace) + if mcpServerRef == obj { + out = append(out, sa) + break + } + } + } + return out +} + +func (r *SandboxAgentController) findSandboxAgentsUsingMCPService(ctx context.Context, cl client.Client, obj types.NamespacedName) []*v1alpha2.SandboxAgent { + var list v1alpha2.SandboxAgentList + if err := cl.List(ctx, &list); err != nil { + sandboxAgentControllerLog.Error(err, "failed to list sandboxagents for Service watch") + return nil + } + + var out []*v1alpha2.SandboxAgent + for i := range list.Items { + sa := &list.Items[i] + decl := sa.Spec.Declarative + if decl == nil { + continue + } + for _, tool := range decl.Tools { + if tool.McpServer == nil { + continue + } + if tool.McpServer.ApiGroup != "" || tool.McpServer.Kind != "Service" { + continue + } + if tool.McpServer.NamespacedName(sa.Namespace) == obj { + out = append(out, sa) + } + } + } + return out +} + +func (r *SandboxAgentController) findSandboxAgentsUsingModelConfig(ctx context.Context, cl client.Client, obj types.NamespacedName) []*v1alpha2.SandboxAgent { + var list v1alpha2.SandboxAgentList + if err := cl.List(ctx, &list); err != nil { + sandboxAgentControllerLog.Error(err, "failed to list sandboxagents for ModelConfig watch") + return nil + } + + var out []*v1alpha2.SandboxAgent + for i := range list.Items { + sa := &list.Items[i] + if sa.Namespace != obj.Namespace { + continue + } + if sa.Spec.Declarative != nil && sa.Spec.Declarative.ModelConfig == obj.Name { + out = append(out, sa) + } + } + return out +} + +func (r *SandboxAgentController) findSandboxAgentsReferencingConfigMap(ctx context.Context, cl client.Client, obj types.NamespacedName) []*v1alpha2.SandboxAgent { + var list v1alpha2.SandboxAgentList + if err := cl.List(ctx, &list); err != nil { + sandboxAgentControllerLog.Error(err, "failed to list sandboxagents for ConfigMap watch") + return nil + } + + var out []*v1alpha2.SandboxAgent + for i := range list.Items { + sa := &list.Items[i] + if sa.Namespace != obj.Namespace { + continue + } + decl := sa.Spec.Declarative + if decl == nil { + continue + } + + if ref := decl.SystemMessageFrom; ref != nil { + if ref.Type == v1alpha2.ConfigMapValueSource && ref.Name == obj.Name { + out = append(out, sa) + continue + } + } + + if pt := decl.PromptTemplate; pt != nil { + for _, ds := range pt.DataSources { + if ds.Name == obj.Name { + out = append(out, sa) + break + } + } + } + } + + return out +} diff --git a/go/core/internal/controller/service_controller_test.go b/go/core/internal/controller/service_controller_test.go index 8f708769e..b5cb81e18 100644 --- a/go/core/internal/controller/service_controller_test.go +++ b/go/core/internal/controller/service_controller_test.go @@ -31,6 +31,10 @@ func (f *fakeServiceReconciler) ReconcileKagentAgent(ctx context.Context, req ct return nil } +func (f *fakeServiceReconciler) ReconcileKagentSandboxAgent(ctx context.Context, req ctrl.Request) error { + return nil +} + func (f *fakeServiceReconciler) ReconcileKagentModelConfig(ctx context.Context, req ctrl.Request) error { return nil } diff --git a/go/core/internal/controller/translator/agent/adk_api_translator.go b/go/core/internal/controller/translator/agent/adk_api_translator.go index 0b2ea9de1..2849f5095 100644 --- a/go/core/internal/controller/translator/agent/adk_api_translator.go +++ b/go/core/internal/controller/translator/agent/adk_api_translator.go @@ -26,6 +26,7 @@ import ( "github.com/kagent-dev/kagent/go/core/internal/utils" "github.com/kagent-dev/kagent/go/core/internal/version" "github.com/kagent-dev/kagent/go/core/pkg/env" + "github.com/kagent-dev/kagent/go/core/pkg/sandboxbackend" "github.com/kagent-dev/kagent/go/core/pkg/translator" "github.com/kagent-dev/kmcp/api/v1alpha1" appsv1 "k8s.io/api/apps/v1" @@ -122,6 +123,7 @@ type AdkApiTranslator interface { TranslateAgent( ctx context.Context, agent *v1alpha2.Agent, + runInSandbox bool, ) (*AgentOutputs, error) GetOwnedResourceTypes() []client.Object } @@ -160,12 +162,13 @@ func getRuntimeProbeConfig(runtime v1alpha2.DeclarativeRuntime) probeConfig { type TranslatorPlugin = translator.TranslatorPlugin -func NewAdkApiTranslator(kube client.Client, defaultModelConfig types.NamespacedName, plugins []TranslatorPlugin, globalProxyURL string) AdkApiTranslator { +func NewAdkApiTranslator(kube client.Client, defaultModelConfig types.NamespacedName, plugins []TranslatorPlugin, globalProxyURL string, sandboxBackend sandboxbackend.Backend) AdkApiTranslator { return &adkApiTranslator{ kube: kube, defaultModelConfig: defaultModelConfig, plugins: plugins, globalProxyURL: globalProxyURL, + sandboxBackend: sandboxBackend, } } @@ -174,6 +177,7 @@ type adkApiTranslator struct { defaultModelConfig types.NamespacedName plugins []TranslatorPlugin globalProxyURL string + sandboxBackend sandboxbackend.Backend } const MAX_DEPTH = 10 @@ -204,6 +208,7 @@ func (t *tState) isVisited(agentName string) bool { func (a *adkApiTranslator) TranslateAgent( ctx context.Context, agent *v1alpha2.Agent, + runInSandbox bool, ) (*AgentOutputs, error) { err := a.validateAgent(ctx, agent, &tState{}) if err != nil { @@ -237,9 +242,13 @@ func (a *adkApiTranslator) TranslateAgent( return nil, fmt.Errorf("unknown agent type: %s", agent.Spec.Type) } + if runInSandbox && a.sandboxBackend == nil { + return nil, fmt.Errorf("sandbox backend is not configured") + } + card := GetA2AAgentCard(agent) - return a.buildManifest(ctx, agent, dep, cfg, card, secretHashBytes) + return a.buildManifest(ctx, agent, dep, cfg, card, secretHashBytes, runInSandbox) } // GetOwnedResourceTypes returns all the resource types that may be created for an agent. @@ -258,6 +267,10 @@ func (r *adkApiTranslator) GetOwnedResourceTypes() []client.Object { ownedResources = append(ownedResources, plugin.GetOwnedResourceTypes()...) } + if r.sandboxBackend != nil { + ownedResources = append(ownedResources, r.sandboxBackend.GetOwnedResourceTypes()...) + } + return ownedResources } @@ -313,6 +326,7 @@ func (a *adkApiTranslator) buildManifest( cfg *adk.AgentConfig, // nil for BYO card *server.AgentCard, // nil for BYO modelConfigSecretHashBytes []byte, // nil for BYO + runInSandbox bool, ) (*AgentOutputs, error) { outputs := &AgentOutputs{} @@ -439,10 +453,10 @@ func (a *adkApiTranslator) buildManifest( } hasSkills := len(skills) > 0 || len(gitRefs) > 0 - // Build Deployment + // Build workload pod (Deployment or pluggable Sandbox CRD) volumes := append(secretVol, dep.Volumes...) volumeMounts := append(secretMounts, dep.VolumeMounts...) - needSandbox := cfg != nil && cfg.GetExecuteCode() + needCodeExecIsolation := cfg != nil && cfg.GetExecuteCode() var initContainers []corev1.Container @@ -453,10 +467,10 @@ func (a *adkApiTranslator) buildManifest( Value: "/skills", } // Skills use the BashTool which calls srt (Anthropic Sandbox Runtime) → bubblewrap. - // Mark that a sandbox is needed so Privileged is set when possible. + // Mark that code-exec isolation is needed so Privileged is set when possible. // Exception: if the user explicitly set AllowPrivilegeEscalation=false (PSS Restricted), // we respect their security context and let srt fall back to user-namespace sandboxing. - needSandbox = true + needCodeExecIsolation = true volumes = append(volumes, corev1.Volume{ Name: "kagent-skills", VolumeSource: corev1.VolumeSource{ @@ -534,16 +548,16 @@ func (a *adkApiTranslator) buildManifest( // When the user explicitly sets AllowPrivilegeEscalation=false (PSS Restricted namespace), // we respect their choice: srt will use unprivileged user-namespace sandboxing instead. // On modern kernels (EKS, GKE) unprivileged_userns_clone is enabled by default. - if needSandbox && !allowPrivilegeEscalationExplicitlyFalse(securityContext) { + if needCodeExecIsolation && !allowPrivilegeEscalationExplicitlyFalse(securityContext) { securityContext.Privileged = new(true) } - } else if needSandbox { - // No user-provided securityContext: create one with Privileged for full sandbox + } else if needCodeExecIsolation { + // No user-provided securityContext: create one with Privileged for code execution securityContext = &corev1.SecurityContext{ Privileged: new(true), } } - // If neither user-provided securityContext nor sandbox is needed, securityContext remains nil + // If neither user-provided securityContext nor code-exec isolation is needed, securityContext remains nil // Determine runtime for probe configuration runtime := v1alpha2.DeclarativeRuntime_Python @@ -552,70 +566,94 @@ func (a *adkApiTranslator) buildManifest( } probeConf := getRuntimeProbeConfig(runtime) - deployment := &appsv1.Deployment{ - TypeMeta: metav1.TypeMeta{APIVersion: "apps/v1", Kind: "Deployment"}, - ObjectMeta: objMeta(), - Spec: appsv1.DeploymentSpec{ - Replicas: dep.Replicas, - Strategy: appsv1.DeploymentStrategy{ - Type: appsv1.RollingUpdateDeploymentStrategyType, - RollingUpdate: &appsv1.RollingUpdateDeployment{ - MaxUnavailable: &intstr.IntOrString{Type: intstr.Int, IntVal: 0}, - MaxSurge: &intstr.IntOrString{Type: intstr.Int, IntVal: 1}, + podTemplate := corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{Labels: podLabels(), Annotations: podTemplateAnnotations}, + Spec: corev1.PodSpec{ + ServiceAccountName: *dep.ServiceAccountName, + ImagePullSecrets: dep.ImagePullSecrets, + SecurityContext: dep.PodSecurityContext, + InitContainers: initContainers, + Containers: []corev1.Container{{ + Name: "kagent", + Image: dep.Image, + ImagePullPolicy: dep.ImagePullPolicy, + Command: cmd, + Args: dep.Args, + Ports: []corev1.ContainerPort{{Name: "http", ContainerPort: dep.Port}}, + Resources: dep.Resources, + Env: env, + ReadinessProbe: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{Path: "/.well-known/agent-card.json", Port: intstr.FromString("http")}, + }, + InitialDelaySeconds: probeConf.InitialDelaySeconds, + TimeoutSeconds: probeConf.TimeoutSeconds, + PeriodSeconds: probeConf.PeriodSeconds, }, - }, - Selector: &metav1.LabelSelector{MatchLabels: selectorLabels}, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{Labels: podLabels(), Annotations: podTemplateAnnotations}, - Spec: corev1.PodSpec{ - ServiceAccountName: *dep.ServiceAccountName, - ImagePullSecrets: dep.ImagePullSecrets, - SecurityContext: dep.PodSecurityContext, - InitContainers: initContainers, - Containers: []corev1.Container{{ - Name: "kagent", - Image: dep.Image, - ImagePullPolicy: dep.ImagePullPolicy, - Command: cmd, - Args: dep.Args, - Ports: []corev1.ContainerPort{{Name: "http", ContainerPort: dep.Port}}, - Resources: dep.Resources, - Env: env, - ReadinessProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{Path: "/.well-known/agent-card.json", Port: intstr.FromString("http")}, - }, - InitialDelaySeconds: probeConf.InitialDelaySeconds, - TimeoutSeconds: probeConf.TimeoutSeconds, - PeriodSeconds: probeConf.PeriodSeconds, - }, - SecurityContext: securityContext, - VolumeMounts: volumeMounts, - }}, - Volumes: volumes, - Tolerations: dep.Tolerations, - Affinity: dep.Affinity, - NodeSelector: dep.NodeSelector, + SecurityContext: securityContext, + VolumeMounts: volumeMounts, + }}, + Volumes: volumes, + Tolerations: dep.Tolerations, + Affinity: dep.Affinity, + NodeSelector: dep.NodeSelector, + }, + } + + var workloadObj client.Object + if runInSandbox { + templateName := fmt.Sprintf("kagent-%s", agent.Name) + sbObjs, err := a.sandboxBackend.BuildSandbox(ctx, sandboxbackend.BuildInput{ + Agent: agent, + PodTemplate: podTemplate, + TemplateName: templateName, + }) + if err != nil { + return nil, fmt.Errorf("build sandbox workload: %w", err) + } + outputs.Manifest = append(outputs.Manifest, sbObjs...) + workloadObj = nil + } else { + deployment := &appsv1.Deployment{ + TypeMeta: metav1.TypeMeta{APIVersion: "apps/v1", Kind: "Deployment"}, + ObjectMeta: objMeta(), + Spec: appsv1.DeploymentSpec{ + Replicas: dep.Replicas, + Strategy: appsv1.DeploymentStrategy{ + Type: appsv1.RollingUpdateDeploymentStrategyType, + RollingUpdate: &appsv1.RollingUpdateDeployment{ + MaxUnavailable: &intstr.IntOrString{Type: intstr.Int, IntVal: 0}, + MaxSurge: &intstr.IntOrString{Type: intstr.Int, IntVal: 1}, + }, }, + Selector: &metav1.LabelSelector{MatchLabels: selectorLabels}, + Template: podTemplate, }, - }, + } + workloadObj = deployment + } + if workloadObj != nil { + outputs.Manifest = append(outputs.Manifest, workloadObj) } - outputs.Manifest = append(outputs.Manifest, deployment) - // Service - outputs.Manifest = append(outputs.Manifest, &corev1.Service{ - TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: "Service"}, - ObjectMeta: objMeta(), - Spec: corev1.ServiceSpec{ - Selector: selectorLabels, - Ports: []corev1.ServicePort{{ - Name: "http", - Port: dep.Port, - TargetPort: intstr.FromInt(int(dep.Port)), - }}, - Type: corev1.ServiceTypeClusterIP, - }, - }) + // Service: Sandbox workloads use SandboxTemplate + + // SandboxClaim; the agent-sandbox Sandbox controller creates and owns the Service with the + // same name as the claim, so we only need to create a service for non-sandboxed agents. + if !runInSandbox { + outputs.Manifest = append(outputs.Manifest, &corev1.Service{ + TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: "Service"}, + ObjectMeta: objMeta(), + Spec: corev1.ServiceSpec{ + Selector: selectorLabels, + Ports: []corev1.ServicePort{{ + Name: "http", + Port: dep.Port, + TargetPort: intstr.FromInt(int(dep.Port)), + }}, + Type: corev1.ServiceTypeClusterIP, + }, + }) + } // Owner refs for _, obj := range outputs.Manifest { diff --git a/go/core/internal/controller/translator/agent/adk_api_translator_test.go b/go/core/internal/controller/translator/agent/adk_api_translator_test.go index 4344f68b2..3988fed4c 100644 --- a/go/core/internal/controller/translator/agent/adk_api_translator_test.go +++ b/go/core/internal/controller/translator/agent/adk_api_translator_test.go @@ -11,6 +11,7 @@ import ( "github.com/kagent-dev/kagent/go/api/adk" "github.com/kagent-dev/kagent/go/api/v1alpha2" translator "github.com/kagent-dev/kagent/go/core/internal/controller/translator/agent" + "github.com/kagent-dev/kagent/go/core/pkg/sandboxbackend/agentsxk8s" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -18,6 +19,9 @@ import ( schemev1 "k8s.io/client-go/kubernetes/scheme" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" + + agentsandboxv1 "sigs.k8s.io/agent-sandbox/api/v1alpha1" + extensionsv1alpha1 "sigs.k8s.io/agent-sandbox/extensions/api/v1alpha1" ) // Test_AdkApiTranslator_CrossNamespaceAgentTool tests that the translator can @@ -176,9 +180,9 @@ func Test_AdkApiTranslator_CrossNamespaceAgentTool(t *testing.T) { Name: "test-model", } - trans := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "") + trans := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil) - _, err := trans.TranslateAgent(context.Background(), tt.sourceAgent) + _, err := trans.TranslateAgent(context.Background(), tt.sourceAgent, false) if tt.wantErr { require.Error(t, err) @@ -339,9 +343,9 @@ func Test_AdkApiTranslator_CrossNamespaceRemoteMCPServer(t *testing.T) { Name: "test-model", } - trans := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "") + trans := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil) - _, err := trans.TranslateAgent(context.Background(), tt.agent) + _, err := trans.TranslateAgent(context.Background(), tt.agent, false) if tt.wantErr { require.Error(t, err) @@ -413,9 +417,9 @@ func Test_AdkApiTranslator_OllamaOptions(t *testing.T) { Name: modelName, } - trans := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "") + trans := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil) - outputs, err := trans.TranslateAgent(context.Background(), agent) + outputs, err := trans.TranslateAgent(context.Background(), agent, false) require.NoError(t, err) require.NotNil(t, outputs) require.NotNil(t, outputs.Config) @@ -532,9 +536,9 @@ func Test_AdkApiTranslator_ServiceAccountNameOverride(t *testing.T) { Name: "test-model", } - trans := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "") + trans := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil) - outputs, err := trans.TranslateAgent(context.Background(), tt.agent) + outputs, err := trans.TranslateAgent(context.Background(), tt.agent, false) require.NoError(t, err) require.NotNil(t, outputs) @@ -680,8 +684,8 @@ func Test_AdkApiTranslator_RecursionDepthTracking(t *testing.T) { } kubeClient := builder.Build() - trans := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "") - _, err := trans.TranslateAgent(context.Background(), root) + trans := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil) + _, err := trans.TranslateAgent(context.Background(), root, false) require.NoError(t, err, "flat list of 12 agent tools should not hit recursion limit") }) @@ -724,8 +728,8 @@ func Test_AdkApiTranslator_RecursionDepthTracking(t *testing.T) { } kubeClient := builder.Build() - trans := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "") - _, err := trans.TranslateAgent(context.Background(), agents[0]) + trans := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil) + _, err := trans.TranslateAgent(context.Background(), agents[0], false) require.NoError(t, err, "deep nesting of 10 levels should pass") }) @@ -767,8 +771,8 @@ func Test_AdkApiTranslator_RecursionDepthTracking(t *testing.T) { } kubeClient := builder.Build() - trans := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "") - _, err := trans.TranslateAgent(context.Background(), agents[0]) + trans := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil) + _, err := trans.TranslateAgent(context.Background(), agents[0], false) require.Error(t, err, "deep nesting of 12 levels should fail") assert.Contains(t, err.Error(), "recursion limit reached") }) @@ -822,8 +826,8 @@ func Test_AdkApiTranslator_RecursionDepthTracking(t *testing.T) { kubeClient := fake.NewClientBuilder().WithScheme(scheme). WithObjects(modelConfig, agentA, agentB).Build() - trans := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "") - _, err := trans.TranslateAgent(context.Background(), agentA) + trans := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil) + _, err := trans.TranslateAgent(context.Background(), agentA, false) require.Error(t, err, "cycle A->B->A should be detected") assert.Contains(t, err.Error(), "cycle detected") }) @@ -908,8 +912,8 @@ func Test_AdkApiTranslator_RecursionDepthTracking(t *testing.T) { kubeClient := fake.NewClientBuilder().WithScheme(scheme). WithObjects(modelConfig, agentA, agentB, agentC, agentD).Build() - trans := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "") - _, err := trans.TranslateAgent(context.Background(), agentA) + trans := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil) + _, err := trans.TranslateAgent(context.Background(), agentA, false) require.NoError(t, err, "diamond pattern should pass — D is not a cycle, just shared") }) } @@ -1114,8 +1118,8 @@ func Test_AdkApiTranslator_MergeDeploymentData(t *testing.T) { Build() defaultModel := types.NamespacedName{Namespace: "default", Name: tt.agentModel.Name} - trans := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "") - outputs, err := trans.TranslateAgent(context.Background(), makeAgent(tt.agentModel.Name, tt.summModel.Name)) + trans := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil) + outputs, err := trans.TranslateAgent(context.Background(), makeAgent(tt.agentModel.Name, tt.summModel.Name), false) require.NoError(t, err) require.NotNil(t, outputs) @@ -1266,8 +1270,8 @@ func Test_AdkApiTranslator_ContextConfig(t *testing.T) { Build() defaultModel := types.NamespacedName{Namespace: "default", Name: "test-model"} - trans := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "") - outputs, err := trans.TranslateAgent(context.Background(), tt.agent) + trans := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil) + outputs, err := trans.TranslateAgent(context.Background(), tt.agent, false) if tt.wantErr { require.Error(t, err) @@ -1286,3 +1290,123 @@ func Test_AdkApiTranslator_ContextConfig(t *testing.T) { }) } } + +func Test_AdkApiTranslator_SandboxAgent_defaultUsesSandboxTemplateAndClaim(t *testing.T) { + ctx := context.Background() + scheme := schemev1.Scheme + require.NoError(t, v1alpha2.AddToScheme(scheme)) + require.NoError(t, agentsandboxv1.AddToScheme(scheme)) + require.NoError(t, extensionsv1alpha1.AddToScheme(scheme)) + + ns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "sandbox-ns"}} + modelConfig := &v1alpha2.ModelConfig{ + ObjectMeta: metav1.ObjectMeta{Name: "m1", Namespace: "sandbox-ns"}, + Spec: v1alpha2.ModelConfigSpec{ + Model: "gpt-4", + Provider: v1alpha2.ModelProviderOpenAI, + }, + } + agent := &v1alpha2.Agent{ + ObjectMeta: metav1.ObjectMeta{Name: "ag1", Namespace: "sandbox-ns"}, + Spec: v1alpha2.AgentSpec{ + Type: v1alpha2.AgentType_Declarative, + Declarative: &v1alpha2.DeclarativeAgentSpec{ + SystemMessage: "You are a sandboxed agent", + ModelConfig: "m1", + }, + }, + } + kubeClient := fake.NewClientBuilder(). + WithScheme(scheme). + WithObjects(ns, modelConfig, agent). + Build() + + trans := translator.NewAdkApiTranslator( + kubeClient, + types.NamespacedName{Namespace: "sandbox-ns", Name: "m1"}, + nil, + "", + agentsxk8s.New(), + ) + outputs, err := trans.TranslateAgent(ctx, agent, true) + require.NoError(t, err) + require.NotNil(t, outputs) + + var sawTemplate, sawClaim, sawSandbox, sawDeploy, sawService bool + for _, o := range outputs.Manifest { + switch o.(type) { + case *extensionsv1alpha1.SandboxTemplate: + sawTemplate = true + case *extensionsv1alpha1.SandboxClaim: + sawClaim = true + case *agentsandboxv1.Sandbox: + sawSandbox = true + case *appsv1.Deployment: + sawDeploy = true + case *corev1.Service: + sawService = true + } + } + require.True(t, sawTemplate, "sandbox runtime should include SandboxTemplate") + require.True(t, sawClaim, "sandbox runtime should include SandboxClaim") + require.False(t, sawSandbox, "default should not emit a direct Sandbox CR") + require.False(t, sawDeploy, "manifest should not include Deployment when runInSandbox is true") + require.False(t, sawService, "sandbox runtime must not include Service; agent-sandbox owns it") +} + +func Test_AdkApiTranslator_SandboxAgentView_BYOUsesSandboxTemplateAndClaim(t *testing.T) { + ctx := context.Background() + scheme := schemev1.Scheme + require.NoError(t, v1alpha2.AddToScheme(scheme)) + require.NoError(t, agentsandboxv1.AddToScheme(scheme)) + require.NoError(t, extensionsv1alpha1.AddToScheme(scheme)) + + ns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "sandbox-ns"}} + cmd := "/app/run" + sa := &v1alpha2.SandboxAgent{ + ObjectMeta: metav1.ObjectMeta{Name: "byo-sb", Namespace: "sandbox-ns"}, + Spec: v1alpha2.SandboxAgentSpec{ + Type: v1alpha2.AgentType_BYO, + BYO: &v1alpha2.BYOAgentSpec{ + Deployment: &v1alpha2.ByoDeploymentSpec{ + Image: "example.com/agent:1", + Cmd: &cmd, + }, + }, + }, + } + agentView := translator.AgentViewFromSandboxAgent(sa) + kubeClient := fake.NewClientBuilder(). + WithScheme(scheme). + WithObjects(ns). + Build() + + trans := translator.NewAdkApiTranslator( + kubeClient, + types.NamespacedName{Namespace: "sandbox-ns", Name: "default"}, + nil, + "", + agentsxk8s.New(), + ) + outputs, err := trans.TranslateAgent(ctx, agentView, true) + require.NoError(t, err) + require.NotNil(t, outputs) + + var sawTemplate, sawClaim, sawDeploy, sawService bool + for _, o := range outputs.Manifest { + switch o.(type) { + case *extensionsv1alpha1.SandboxTemplate: + sawTemplate = true + case *extensionsv1alpha1.SandboxClaim: + sawClaim = true + case *appsv1.Deployment: + sawDeploy = true + case *corev1.Service: + sawService = true + } + } + require.True(t, sawTemplate) + require.True(t, sawClaim) + require.False(t, sawDeploy) + require.False(t, sawService, "sandbox runtime must not include Service; agent-sandbox owns it") +} diff --git a/go/core/internal/controller/translator/agent/adk_translator_golden_test.go b/go/core/internal/controller/translator/agent/adk_translator_golden_test.go index eb3e7f80a..64d8ebc09 100644 --- a/go/core/internal/controller/translator/agent/adk_translator_golden_test.go +++ b/go/core/internal/controller/translator/agent/adk_translator_golden_test.go @@ -178,7 +178,7 @@ func runGoldenTest(t *testing.T, inputFile, outputsDir, testName string, updateG // Use proxy URL from test input if provided proxyURL := testInput.ProxyURL - result, err = translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, proxyURL).TranslateAgent(ctx, agent) + result, err = translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, proxyURL, nil).TranslateAgent(ctx, agent, false) require.NoError(t, err) default: diff --git a/go/core/internal/controller/translator/agent/git_skills_test.go b/go/core/internal/controller/translator/agent/git_skills_test.go index 366ba8076..75e38dbe2 100644 --- a/go/core/internal/controller/translator/agent/git_skills_test.go +++ b/go/core/internal/controller/translator/agent/git_skills_test.go @@ -299,9 +299,9 @@ func Test_AdkApiTranslator_Skills(t *testing.T) { WithObjects(modelConfig, tt.agent). Build() - trans := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "") + trans := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil) - outputs, err := trans.TranslateAgent(context.Background(), tt.agent) + outputs, err := trans.TranslateAgent(context.Background(), tt.agent, false) require.NoError(t, err) require.NotNil(t, outputs) @@ -485,8 +485,8 @@ func Test_AdkApiTranslator_SkillsConfigurableImage(t *testing.T) { Name: modelName, } - trans := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "") - outputs, err := trans.TranslateAgent(context.Background(), agent) + trans := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil) + outputs, err := trans.TranslateAgent(context.Background(), agent, false) require.NoError(t, err) var deployment *appsv1.Deployment @@ -682,8 +682,8 @@ func Test_AdkApiTranslator_SkillsInitContainer(t *testing.T) { WithObjects(modelConfig, tt.agent). Build() - trans := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "") - outputs, err := trans.TranslateAgent(context.Background(), tt.agent) + trans := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil) + outputs, err := trans.TranslateAgent(context.Background(), tt.agent, false) require.NoError(t, err) var deployment *appsv1.Deployment diff --git a/go/core/internal/controller/translator/agent/mcp_validation_test.go b/go/core/internal/controller/translator/agent/mcp_validation_test.go index 9f857a6b7..275285bf6 100644 --- a/go/core/internal/controller/translator/agent/mcp_validation_test.go +++ b/go/core/internal/controller/translator/agent/mcp_validation_test.go @@ -94,10 +94,11 @@ func TestMCPServerValidation_InvalidPort(t *testing.T) { types.NamespacedName{Namespace: "test", Name: "default-model"}, nil, "", + nil, ) // TranslateAgent should fail with error about invalid port - _, err = translator.TranslateAgent(ctx, agent) + _, err = translator.TranslateAgent(ctx, agent, false) require.Error(t, err) assert.Contains(t, err.Error(), "cannot determine port") assert.Contains(t, err.Error(), "test-mcp-server") @@ -179,10 +180,11 @@ func TestMCPServerValidation_ValidPort(t *testing.T) { types.NamespacedName{Namespace: "test", Name: "default-model"}, nil, "", + nil, ) // TranslateAgent should succeed - outputs, err := translator.TranslateAgent(ctx, agent) + outputs, err := translator.TranslateAgent(ctx, agent, false) require.NoError(t, err) assert.NotNil(t, outputs) assert.NotNil(t, outputs.Config) @@ -249,10 +251,11 @@ func TestMCPServerValidation_NotFound(t *testing.T) { types.NamespacedName{Namespace: "test", Name: "default-model"}, nil, "", + nil, ) // TranslateAgent should fail with not found error - _, err = translator.TranslateAgent(ctx, agent) + _, err = translator.TranslateAgent(ctx, agent, false) require.Error(t, err) assert.True(t, apierrors.IsNotFound(err)) } @@ -310,10 +313,11 @@ func TestMCPServerValidation_NoMCPServerReference(t *testing.T) { types.NamespacedName{Namespace: "test", Name: "default-model"}, nil, "", + nil, ) // TranslateAgent should fail with provider or tool server error - _, err = translator.TranslateAgent(ctx, agent) + _, err = translator.TranslateAgent(ctx, agent, false) require.Error(t, err) assert.Contains(t, err.Error(), "tool must have a provider or tool server") } @@ -388,10 +392,11 @@ func TestMCPServerValidation_RemoteMCPServer(t *testing.T) { types.NamespacedName{Namespace: "test", Name: "default-model"}, nil, "", + nil, ) // TranslateAgent should succeed - RemoteMCPServer doesn't have port validation - outputs, err := translator.TranslateAgent(ctx, agent) + outputs, err := translator.TranslateAgent(ctx, agent, false) require.NoError(t, err) assert.NotNil(t, outputs) assert.NotNil(t, outputs.Config) @@ -546,10 +551,11 @@ func TestMCPServerValidation_MultipleTools(t *testing.T) { types.NamespacedName{Namespace: "test", Name: "default-model"}, nil, "", + nil, ) // TranslateAgent should fail because one of the MCPServers is invalid - _, err = translator.TranslateAgent(ctx, agent) + _, err = translator.TranslateAgent(ctx, agent, false) require.Error(t, err) assert.Contains(t, err.Error(), "cannot determine port") assert.Contains(t, err.Error(), "invalid-mcp-server") diff --git a/go/core/internal/controller/translator/agent/proxy_test.go b/go/core/internal/controller/translator/agent/proxy_test.go index 9ef3e8240..73889074f 100644 --- a/go/core/internal/controller/translator/agent/proxy_test.go +++ b/go/core/internal/controller/translator/agent/proxy_test.go @@ -116,9 +116,10 @@ func TestProxyConfiguration_ThroughTranslateAgent(t *testing.T) { types.NamespacedName{Name: "default-model", Namespace: "test"}, nil, "http://proxy.kagent.svc.cluster.local:8080", + nil, ) - result, err := translator.TranslateAgent(ctx, agent) + result, err := translator.TranslateAgent(ctx, agent, false) require.NoError(t, err) require.NotNil(t, result) require.NotNil(t, result.Config) @@ -145,9 +146,10 @@ func TestProxyConfiguration_ThroughTranslateAgent(t *testing.T) { types.NamespacedName{Name: "default-model", Namespace: "test"}, nil, "", // No proxy + nil, ) - result, err := translator.TranslateAgent(ctx, agent) + result, err := translator.TranslateAgent(ctx, agent, false) require.NoError(t, err) require.NotNil(t, result) require.NotNil(t, result.Config) @@ -246,9 +248,10 @@ func TestProxyConfiguration_RemoteMCPServer_ExternalURL(t *testing.T) { types.NamespacedName{Name: "default-model", Namespace: "test"}, nil, "http://proxy.kagent.svc.cluster.local:8080", + nil, ) - result, err := translator.TranslateAgent(ctx, agent) + result, err := translator.TranslateAgent(ctx, agent, false) require.NoError(t, err) require.NotNil(t, result) require.NotNil(t, result.Config) @@ -338,9 +341,10 @@ func TestProxyConfiguration_MCPServer(t *testing.T) { types.NamespacedName{Name: "default-model", Namespace: "test"}, nil, "http://proxy.kagent.svc.cluster.local:8080", + nil, ) - result, err := translator.TranslateAgent(ctx, agent) + result, err := translator.TranslateAgent(ctx, agent, false) require.NoError(t, err) require.NotNil(t, result) require.NotNil(t, result.Config) @@ -435,9 +439,10 @@ func TestProxyConfiguration_Service(t *testing.T) { types.NamespacedName{Name: "default-model", Namespace: "test"}, nil, "http://proxy.kagent.svc.cluster.local:8080", + nil, ) - result, err := translator.TranslateAgent(ctx, agent) + result, err := translator.TranslateAgent(ctx, agent, false) require.NoError(t, err) require.NotNil(t, result) require.NotNil(t, result.Config) diff --git a/go/core/internal/controller/translator/agent/runtime_test.go b/go/core/internal/controller/translator/agent/runtime_test.go index 55e5d42cb..b0870cd6d 100644 --- a/go/core/internal/controller/translator/agent/runtime_test.go +++ b/go/core/internal/controller/translator/agent/runtime_test.go @@ -62,10 +62,10 @@ func TestRuntime_GoRuntime(t *testing.T) { Namespace: "test", Name: "test-model", } - translatorInstance := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "") + translatorInstance := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil) // Translate agent - result, err := translatorInstance.TranslateAgent(ctx, agent) + result, err := translatorInstance.TranslateAgent(ctx, agent, false) require.NoError(t, err) require.NotNil(t, result) @@ -137,10 +137,10 @@ func TestRuntime_PythonRuntime(t *testing.T) { Namespace: "test", Name: "test-model", } - translatorInstance := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "") + translatorInstance := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil) // Translate agent - result, err := translatorInstance.TranslateAgent(ctx, agent) + result, err := translatorInstance.TranslateAgent(ctx, agent, false) require.NoError(t, err) require.NotNil(t, result) @@ -212,10 +212,10 @@ func TestRuntime_DefaultToPython(t *testing.T) { Namespace: "test", Name: "test-model", } - translatorInstance := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "") + translatorInstance := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil) // Translate agent - result, err := translatorInstance.TranslateAgent(ctx, agent) + result, err := translatorInstance.TranslateAgent(ctx, agent, false) require.NoError(t, err) require.NotNil(t, result) @@ -296,10 +296,10 @@ func TestRuntime_CustomRepositoryPath(t *testing.T) { Namespace: "test", Name: "test-model", } - translatorInstance := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "") + translatorInstance := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil) // Translate agent - result, err := translatorInstance.TranslateAgent(ctx, agent) + result, err := translatorInstance.TranslateAgent(ctx, agent, false) require.NoError(t, err) require.NotNil(t, result) diff --git a/go/core/internal/controller/translator/agent/sandbox_agent_view.go b/go/core/internal/controller/translator/agent/sandbox_agent_view.go new file mode 100644 index 000000000..d7ced6c28 --- /dev/null +++ b/go/core/internal/controller/translator/agent/sandbox_agent_view.go @@ -0,0 +1,33 @@ +package agent + +import ( + "github.com/kagent-dev/kagent/go/api/v1alpha2" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// AgentViewFromSandboxAgent returns an in-memory Agent with the same spec, metadata, and status +// as the SandboxAgent. Use with TranslateAgent(ctx, view, true) so the translator emits sandbox +// workload objects; the returned value is not persisted as an Agent resource. +func AgentViewFromSandboxAgent(sa *v1alpha2.SandboxAgent) *v1alpha2.Agent { + if sa == nil { + return nil + } + spec := v1alpha2.AgentSpec{ + Type: sa.Spec.Type, + Declarative: sa.Spec.Declarative, + BYO: sa.Spec.BYO, + Description: sa.Spec.Description, + Skills: sa.Spec.Skills, + AllowedNamespaces: sa.Spec.AllowedNamespaces, + } + a := &v1alpha2.Agent{ + TypeMeta: metav1.TypeMeta{ + APIVersion: v1alpha2.GroupVersion.String(), + Kind: "Agent", + }, + ObjectMeta: sa.ObjectMeta, + Spec: spec, + } + sa.Status.DeepCopyInto(&a.Status) + return a +} diff --git a/go/core/internal/controller/translator/agent/security_context_test.go b/go/core/internal/controller/translator/agent/security_context_test.go index 791c71d39..ae2e8ddd8 100644 --- a/go/core/internal/controller/translator/agent/security_context_test.go +++ b/go/core/internal/controller/translator/agent/security_context_test.go @@ -83,10 +83,10 @@ func TestSecurityContext_AppliedToPodSpec(t *testing.T) { Namespace: "test", Name: "test-model", } - translatorInstance := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "") + translatorInstance := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil) // Translate agent - result, err := translatorInstance.TranslateAgent(ctx, agent) + result, err := translatorInstance.TranslateAgent(ctx, agent, false) require.NoError(t, err) require.NotNil(t, result) @@ -174,9 +174,9 @@ func TestSecurityContext_OnlyPodSecurityContext(t *testing.T) { Namespace: "test", Name: "test-model", } - translatorInstance := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "") + translatorInstance := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil) - result, err := translatorInstance.TranslateAgent(ctx, agent) + result, err := translatorInstance.TranslateAgent(ctx, agent, false) require.NoError(t, err) var deployment *appsv1.Deployment @@ -249,9 +249,9 @@ func TestSecurityContext_OnlyContainerSecurityContext(t *testing.T) { Namespace: "test", Name: "test-model", } - translatorInstance := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "") + translatorInstance := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil) - result, err := translatorInstance.TranslateAgent(ctx, agent) + result, err := translatorInstance.TranslateAgent(ctx, agent, false) require.NoError(t, err) var deployment *appsv1.Deployment @@ -323,9 +323,9 @@ func TestSecurityContext_SkillsDefaultPrivilegedSandbox(t *testing.T) { Namespace: "test", Name: "test-model", } - translatorInstance := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "") + translatorInstance := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil) - result, err := translatorInstance.TranslateAgent(ctx, agent) + result, err := translatorInstance.TranslateAgent(ctx, agent, false) require.NoError(t, err) var deployment *appsv1.Deployment @@ -407,9 +407,9 @@ func TestSecurityContext_SkillsPSSRestricted(t *testing.T) { Namespace: "test", Name: "test-model", } - translatorInstance := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "") + translatorInstance := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil) - result, err := translatorInstance.TranslateAgent(ctx, agent) + result, err := translatorInstance.TranslateAgent(ctx, agent, false) require.NoError(t, err) var deployment *appsv1.Deployment diff --git a/go/core/internal/controller/translator/agent/utils.go b/go/core/internal/controller/translator/agent/utils.go index a9dda8e5b..85ca1e172 100644 --- a/go/core/internal/controller/translator/agent/utils.go +++ b/go/core/internal/controller/translator/agent/utils.go @@ -26,7 +26,8 @@ func GetA2AAgentCard(agent *v1alpha2.Agent) *server.AgentCard { DefaultOutputModes: []string{"text"}, } if agent.Spec.Type == v1alpha2.AgentType_Declarative && agent.Spec.Declarative != nil && agent.Spec.Declarative.A2AConfig != nil { - card.Skills = slices.Collect(utils.Map(slices.Values(agent.Spec.Declarative.A2AConfig.Skills), func(skill v1alpha2.AgentSkill) server.AgentSkill { + decl := agent.Spec.Declarative + card.Skills = slices.Collect(utils.Map(slices.Values(decl.A2AConfig.Skills), func(skill v1alpha2.AgentSkill) server.AgentSkill { return server.AgentSkill(skill) })) } diff --git a/go/core/internal/controller/translator/mutate.go b/go/core/internal/controller/translator/mutate.go index b6dcc0c73..07f04e196 100644 --- a/go/core/internal/controller/translator/mutate.go +++ b/go/core/internal/controller/translator/mutate.go @@ -8,6 +8,8 @@ import ( corev1 "k8s.io/api/core/v1" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + extensionsv1alpha1 "sigs.k8s.io/agent-sandbox/extensions/api/v1alpha1" ) func MutateFuncFor(existing, desired client.Object) controllerutil.MutateFn { @@ -58,6 +60,16 @@ func MutateFuncFor(existing, desired client.Object) controllerutil.MutateFn { wantDpl := desired.(*appsv1.Deployment) return mutateDeployment(dpl, wantDpl) + case *extensionsv1alpha1.SandboxTemplate: + st := existing.(*extensionsv1alpha1.SandboxTemplate) + want := desired.(*extensionsv1alpha1.SandboxTemplate) + mutateSandboxTemplate(st, want) + + case *extensionsv1alpha1.SandboxClaim: + sc := existing.(*extensionsv1alpha1.SandboxClaim) + want := desired.(*extensionsv1alpha1.SandboxClaim) + mutateSandboxClaim(sc, want) + default: return mergeWithOverride(existing, desired) } @@ -119,3 +131,14 @@ func mutatePodTemplate(existing, desired *corev1.PodTemplateSpec) error { return nil } + +// mutateSandboxTemplate replaces the template spec wholesale. The default mergo path does not +// reliably replace slice fields (containers, volumes, env), so SandboxAgent updates would not +// roll pods until we assign spec explicitly (same idea as mutatePodTemplate / Deployment). +func mutateSandboxTemplate(existing, desired *extensionsv1alpha1.SandboxTemplate) { + existing.Spec = desired.Spec +} + +func mutateSandboxClaim(existing, desired *extensionsv1alpha1.SandboxClaim) { + existing.Spec = desired.Spec +} diff --git a/go/core/internal/controller/translator/mutate_sandbox_test.go b/go/core/internal/controller/translator/mutate_sandbox_test.go new file mode 100644 index 000000000..d9062d771 --- /dev/null +++ b/go/core/internal/controller/translator/mutate_sandbox_test.go @@ -0,0 +1,53 @@ +package translator + +import ( + "testing" + + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + agentsandboxv1 "sigs.k8s.io/agent-sandbox/api/v1alpha1" + extensionsv1alpha1 "sigs.k8s.io/agent-sandbox/extensions/api/v1alpha1" +) + +func TestMutateFuncFor_SandboxTemplate_replacesPodTemplateSlices(t *testing.T) { + oldImg := "registry.example/old:v1" + newImg := "registry.example/new:v2" + + existing := &extensionsv1alpha1.SandboxTemplate{ + ObjectMeta: metav1.ObjectMeta{Name: "kagent-a", Namespace: "ns"}, + Spec: extensionsv1alpha1.SandboxTemplateSpec{ + PodTemplate: agentsandboxv1.PodTemplate{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{Name: "agent", Image: oldImg}}, + }, + }, + }, + } + desired := existing.DeepCopy() + desired.Spec.PodTemplate.Spec.Containers[0].Image = newImg + + f := MutateFuncFor(existing, desired) + require.NoError(t, f()) + require.Equal(t, newImg, existing.Spec.PodTemplate.Spec.Containers[0].Image) +} + +func TestMutateFuncFor_SandboxClaim_replacesSpec(t *testing.T) { + existing := &extensionsv1alpha1.SandboxClaim{ + ObjectMeta: metav1.ObjectMeta{Name: "a", Namespace: "ns"}, + Spec: extensionsv1alpha1.SandboxClaimSpec{ + TemplateRef: extensionsv1alpha1.SandboxTemplateRef{Name: "t-old"}, + }, + } + desired := &extensionsv1alpha1.SandboxClaim{ + ObjectMeta: metav1.ObjectMeta{Name: "a", Namespace: "ns"}, + Spec: extensionsv1alpha1.SandboxClaimSpec{ + TemplateRef: extensionsv1alpha1.SandboxTemplateRef{Name: "t-new"}, + }, + } + + f := MutateFuncFor(existing, desired) + require.NoError(t, f()) + require.Equal(t, "t-new", existing.Spec.TemplateRef.Name) +} diff --git a/go/core/internal/httpserver/handlers/agents.go b/go/core/internal/httpserver/handlers/agents.go index 011c97bc3..6209bed5b 100644 --- a/go/core/internal/httpserver/handlers/agents.go +++ b/go/core/internal/httpserver/handlers/agents.go @@ -7,10 +7,12 @@ import ( "github.com/go-logr/logr" api "github.com/kagent-dev/kagent/go/api/httpapi" "github.com/kagent-dev/kagent/go/api/v1alpha2" + "github.com/kagent-dev/kagent/go/core/internal/controller/reconciler" agent_translator "github.com/kagent-dev/kagent/go/core/internal/controller/translator/agent" "github.com/kagent-dev/kagent/go/core/internal/httpserver/errors" "github.com/kagent-dev/kagent/go/core/internal/utils" "github.com/kagent-dev/kagent/go/core/pkg/auth" + "github.com/kagent-dev/kagent/go/core/pkg/sandboxbackend" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" @@ -42,6 +44,12 @@ func (h *AgentsHandler) HandleListAgents(w ErrorResponseWriter, r *http.Request) return } + sandboxAgentList := &v1alpha2.SandboxAgentList{} + if err := h.KubeClient.List(r.Context(), sandboxAgentList); err != nil { + w.RespondWithError(errors.NewInternalServerError("Failed to list SandboxAgents from Kubernetes", err)) + return + } + agentsWithID := make([]api.AgentResponse, 0) for _, agent := range agentList.Items { agentRef := utils.GetObjectRef(&agent) @@ -54,6 +62,13 @@ func (h *AgentsHandler) HandleListAgents(w ErrorResponseWriter, r *http.Request) agentsWithID = append(agentsWithID, agentResponse) } + for _, sa := range sandboxAgentList.Items { + agentRef := utils.GetObjectRef(&sa) + log.V(1).Info("Processing SandboxAgent", "agentRef", agentRef) + agentResponse, _ := h.getSandboxAgentResponse(r.Context(), log, &sa) + agentsWithID = append(agentsWithID, agentResponse) + } + log.Info("Successfully listed agents", "count", len(agentsWithID)) data := api.NewResponse(agentsWithID, "Successfully listed agents", false) RespondWithJSON(w, http.StatusOK, data) @@ -65,9 +80,11 @@ func (h *AgentsHandler) getAgentResponse(ctx context.Context, log logr.Logger, a deploymentReady := false for _, condition := range agent.Status.Conditions { - if condition.Type == "Ready" && condition.Reason == "DeploymentReady" && condition.Status == "True" { - deploymentReady = true - break + if condition.Type == "Ready" && condition.Status == "True" { + if condition.Reason == reconciler.AgentReadyReasonDeploymentReady || condition.Reason == reconciler.AgentReadyReasonWorkloadReady { + deploymentReady = true + break + } } } @@ -115,6 +132,16 @@ func (h *AgentsHandler) getAgentResponse(ctx context.Context, log logr.Logger, a return response, nil } +func (h *AgentsHandler) getSandboxAgentResponse(ctx context.Context, log logr.Logger, sa *v1alpha2.SandboxAgent) (api.AgentResponse, error) { + agentView := agent_translator.AgentViewFromSandboxAgent(sa) + resp, err := h.getAgentResponse(ctx, log, agentView) + if err != nil { + return resp, err + } + resp.RunInSandbox = true + return resp, nil +} + // HandleGetAgent handles GET /api/agents/{namespace}/{name} requests using database func (h *AgentsHandler) HandleGetAgent(w ErrorResponseWriter, r *http.Request) { log := ctrllog.FromContext(r.Context()).WithName("agents-handler").WithValues("operation", "get-db") @@ -138,14 +165,31 @@ func (h *AgentsHandler) HandleGetAgent(w ErrorResponseWriter, r *http.Request) { return } agent := &v1alpha2.Agent{} - if err := h.KubeClient.Get( + err = h.KubeClient.Get( r.Context(), client.ObjectKey{ Namespace: agentNamespace, Name: agentName, }, agent, - ); err != nil { + ) + if err != nil { + if apierrors.IsNotFound(err) { + sa := &v1alpha2.SandboxAgent{} + if err2 := h.KubeClient.Get(r.Context(), client.ObjectKey{Namespace: agentNamespace, Name: agentName}, sa); err2 != nil { + w.RespondWithError(errors.NewNotFoundError("Agent not found", err2)) + return + } + agentResponse, err3 := h.getSandboxAgentResponse(r.Context(), log, sa) + if err3 != nil { + w.RespondWithError(err3) + return + } + log.Info("Successfully retrieved sandbox agent") + data := api.NewResponse(agentResponse, "Successfully retrieved agent", false) + RespondWithJSON(w, http.StatusOK, data) + return + } w.RespondWithError(errors.NewNotFoundError("Agent not found", err)) return } @@ -178,6 +222,7 @@ func (h *AgentsHandler) HandleCreateAgent(w ErrorResponseWriter, r *http.Request agentRef, err := utils.ParseRefString(agentReq.Name, agentReq.Namespace) if err != nil { w.RespondWithError(errors.NewBadRequestError("Invalid agent metadata", err)) + return } log = log.WithValues( @@ -201,10 +246,11 @@ func (h *AgentsHandler) HandleCreateAgent(w ErrorResponseWriter, r *http.Request h.DefaultModelConfig, nil, h.ProxyURL, + h.SandboxBackend, ) log.V(1).Info("Translating Agent to ADK format") - _, err = apiTranslator.TranslateAgent(r.Context(), &agentReq) + _, err = apiTranslator.TranslateAgent(r.Context(), &agentReq, false) if err != nil { w.RespondWithError(errors.NewInternalServerError("Failed to translate Agent to ADK format", err)) return @@ -320,8 +366,25 @@ func (h *AgentsHandler) HandleDeleteAgent(w ErrorResponseWriter, r *http.Request ) if err != nil { if apierrors.IsNotFound(err) { - log.Info("Agent not found") - w.RespondWithError(errors.NewNotFoundError("Agent not found", nil)) + sa := &v1alpha2.SandboxAgent{} + if err2 := h.KubeClient.Get(r.Context(), client.ObjectKey{Namespace: agentNamespace, Name: agentName}, sa); err2 != nil { + if apierrors.IsNotFound(err2) { + log.Info("Agent not found") + w.RespondWithError(errors.NewNotFoundError("Agent not found", nil)) + return + } + log.Error(err2, "Failed to get SandboxAgent") + w.RespondWithError(errors.NewInternalServerError("Failed to get SandboxAgent", err2)) + return + } + log.V(1).Info("Deleting SandboxAgent from Kubernetes") + if err := h.KubeClient.Delete(r.Context(), sa); err != nil { + w.RespondWithError(errors.NewInternalServerError("Failed to delete SandboxAgent", err)) + return + } + log.Info("Successfully deleted sandbox agent") + data := api.NewResponse(struct{}{}, "Successfully deleted agent", false) + RespondWithJSON(w, http.StatusOK, data) return } log.Error(err, "Failed to get Agent") @@ -339,3 +402,181 @@ func (h *AgentsHandler) HandleDeleteAgent(w ErrorResponseWriter, r *http.Request data := api.NewResponse(struct{}{}, "Successfully deleted agent", false) RespondWithJSON(w, http.StatusOK, data) } + +func normalizeSandboxAgentForAPI(sa *v1alpha2.SandboxAgent) { + if sa == nil { + return + } + if sa.Spec.Type == "" { + sa.Spec.Type = v1alpha2.AgentType_Declarative + } +} + +// HandleCreateSandboxAgent handles POST /api/sandboxagents requests. +func (h *AgentsHandler) HandleCreateSandboxAgent(w ErrorResponseWriter, r *http.Request) { + log := ctrllog.FromContext(r.Context()).WithName("agents-handler").WithValues("operation", "create-sandboxagent") + + var saReq v1alpha2.SandboxAgent + if err := DecodeJSONBody(r, &saReq); err != nil { + w.RespondWithError(errors.NewBadRequestError("Invalid request body", err)) + return + } + normalizeSandboxAgentForAPI(&saReq) + if saReq.Namespace == "" { + saReq.Namespace = utils.GetResourceNamespace() + log.V(4).Info("Namespace not provided in request. Creating in controller installation namespace", + "namespace", saReq.Namespace) + } + agentRef, err := utils.ParseRefString(saReq.Name, saReq.Namespace) + if err != nil { + w.RespondWithError(errors.NewBadRequestError("Invalid sandboxagent metadata", err)) + return + } + + log = log.WithValues( + "agentNamespace", agentRef.Namespace, + "agentName", agentRef.Name, + ) + + if err := Check(h.Authorizer, r, auth.Resource{Type: "Agent", Name: agentRef.String()}); err != nil { + w.RespondWithError(err) + return + } + + if h.SandboxBackend != nil { + if err := sandboxbackend.EnsureAgentSandboxAPIsRegistered(r.Context(), h.KubeClient); err != nil { + w.RespondWithError(errors.NewBadRequestError(err.Error(), err)) + return + } + } + + kubeClientWrapper := utils.NewKubeClientWrapper(h.KubeClient) + if err := kubeClientWrapper.AddInMemory(&saReq); err != nil { + w.RespondWithError(errors.NewInternalServerError("Failed to add SandboxAgent to Kubernetes wrapper", err)) + return + } + + apiTranslator := agent_translator.NewAdkApiTranslator( + kubeClientWrapper, + h.DefaultModelConfig, + nil, + h.ProxyURL, + h.SandboxBackend, + ) + + agentView := agent_translator.AgentViewFromSandboxAgent(&saReq) + log.V(1).Info("Translating SandboxAgent to ADK format") + if _, err := apiTranslator.TranslateAgent(r.Context(), agentView, true); err != nil { + w.RespondWithError(errors.NewInternalServerError("Failed to translate SandboxAgent to ADK format", err)) + return + } + + if err := h.KubeClient.Create(r.Context(), &saReq); err != nil { + w.RespondWithError(errors.NewInternalServerError("Failed to create SandboxAgent in Kubernetes", err)) + return + } + + agentResponse, err := h.getSandboxAgentResponse(r.Context(), log, &saReq) + if err != nil { + w.RespondWithError(err) + return + } + + log.Info("Successfully created sandbox agent", "agentRef", agentRef) + data := api.NewResponse(agentResponse, "Successfully created sandbox agent", false) + RespondWithJSON(w, http.StatusCreated, data) +} + +// HandleUpdateSandboxAgent handles PUT /api/sandboxagents/{namespace}/{name} requests. +func (h *AgentsHandler) HandleUpdateSandboxAgent(w ErrorResponseWriter, r *http.Request) { + log := ctrllog.FromContext(r.Context()).WithName("agents-handler").WithValues("operation", "update-sandboxagent") + + var saReq v1alpha2.SandboxAgent + if err := DecodeJSONBody(r, &saReq); err != nil { + w.RespondWithError(errors.NewBadRequestError("Invalid request body", err)) + return + } + normalizeSandboxAgentForAPI(&saReq) + if saReq.Namespace == "" { + saReq.Namespace = utils.GetResourceNamespace() + } + agentRef, err := utils.ParseRefString(saReq.Name, saReq.Namespace) + if err != nil { + w.RespondWithError(errors.NewBadRequestError("Invalid SandboxAgent metadata", err)) + return + } + + agentNamespace, err := GetPathParam(r, "namespace") + if err != nil { + w.RespondWithError(errors.NewBadRequestError("Failed to get namespace from path", err)) + return + } + agentName, err := GetPathParam(r, "name") + if err != nil { + w.RespondWithError(errors.NewBadRequestError("Failed to get name from path", err)) + return + } + + if agentRef.Namespace != agentNamespace || agentRef.Name != agentName { + w.RespondWithError(errors.NewBadRequestError("Path does not match request body metadata", nil)) + return + } + + if err := Check(h.Authorizer, r, auth.Resource{Type: "Agent", Name: agentRef.String()}); err != nil { + w.RespondWithError(err) + return + } + + if h.SandboxBackend != nil { + if err := sandboxbackend.EnsureAgentSandboxAPIsRegistered(r.Context(), h.KubeClient); err != nil { + w.RespondWithError(errors.NewBadRequestError(err.Error(), err)) + return + } + } + + existing := &v1alpha2.SandboxAgent{} + if err := h.KubeClient.Get(r.Context(), agentRef, existing); err != nil { + if apierrors.IsNotFound(err) { + w.RespondWithError(errors.NewNotFoundError("SandboxAgent not found", nil)) + return + } + w.RespondWithError(errors.NewInternalServerError("Failed to get SandboxAgent", err)) + return + } + + existing.Spec = saReq.Spec + + kubeClientWrapper := utils.NewKubeClientWrapper(h.KubeClient) + if err := kubeClientWrapper.AddInMemory(existing); err != nil { + w.RespondWithError(errors.NewInternalServerError("Failed to add SandboxAgent to Kubernetes wrapper", err)) + return + } + + apiTranslator := agent_translator.NewAdkApiTranslator( + kubeClientWrapper, + h.DefaultModelConfig, + nil, + h.ProxyURL, + h.SandboxBackend, + ) + agentView := agent_translator.AgentViewFromSandboxAgent(existing) + if _, err := apiTranslator.TranslateAgent(r.Context(), agentView, true); err != nil { + w.RespondWithError(errors.NewInternalServerError("Failed to translate SandboxAgent to ADK format", err)) + return + } + + if err := h.KubeClient.Update(r.Context(), existing); err != nil { + w.RespondWithError(errors.NewInternalServerError("Failed to update SandboxAgent", err)) + return + } + + agentResponse, err := h.getSandboxAgentResponse(r.Context(), log, existing) + if err != nil { + w.RespondWithError(err) + return + } + + log.Info("Successfully updated sandbox agent", "agentRef", agentRef) + data := api.NewResponse(agentResponse, "Successfully updated sandbox agent", false) + RespondWithJSON(w, http.StatusOK, data) +} diff --git a/go/core/internal/httpserver/handlers/agents_test.go b/go/core/internal/httpserver/handlers/agents_test.go index 6ad712485..add6c3d96 100644 --- a/go/core/internal/httpserver/handlers/agents_test.go +++ b/go/core/internal/httpserver/handlers/agents_test.go @@ -62,6 +62,24 @@ func createTestAgentWithStatus(name string, modelConfig *v1alpha2.ModelConfig, c return agent } +func createTestSandboxAgentCRD(name string, modelConfig *v1alpha2.ModelConfig, conditions []metav1.Condition) *v1alpha2.SandboxAgent { + return &v1alpha2.SandboxAgent{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: "default", + }, + Spec: v1alpha2.SandboxAgentSpec{ + Type: v1alpha2.AgentType_Declarative, + Declarative: &v1alpha2.DeclarativeAgentSpec{ + ModelConfig: modelConfig.Name, + }, + }, + Status: v1alpha2.AgentStatus{ + Conditions: conditions, + }, + } +} + func setupTestHandler(objects ...client.Object) (*handlers.AgentsHandler, string) { kubeClient := fake.NewClientBuilder(). WithScheme(setupScheme()). @@ -213,6 +231,42 @@ func TestHandleGetAgent(t *testing.T) { require.False(t, response.Data.DeploymentReady) }) + t.Run("SandboxAgent CRD gets Accepted and DeploymentReady from status (GET falls back after Agent missing)", func(t *testing.T) { + modelConfig := createTestModelConfig() + conditions := []metav1.Condition{ + { + Type: "Accepted", + Status: "True", + Reason: "AgentReconciled", + }, + { + Type: "Ready", + Status: "True", + Reason: "WorkloadReady", + }, + } + sa := createTestSandboxAgentCRD("sandbox-accepted", modelConfig, conditions) + + handler, _ := setupTestHandler(sa, modelConfig) + + req := httptest.NewRequest("GET", "/api/agents/default/sandbox-accepted", nil) + req = mux.SetURLVars(req, map[string]string{"namespace": "default", "name": "sandbox-accepted"}) + req = setUser(req, "test-user") + w := httptest.NewRecorder() + + handler.HandleGetAgent(&testErrorResponseWriter{w}, req) + + require.Equal(t, http.StatusOK, w.Code) + + var response api.StandardResponse[api.AgentResponse] + err := json.Unmarshal(w.Body.Bytes(), &response) + require.NoError(t, err) + require.True(t, response.Data.Accepted) + require.True(t, response.Data.DeploymentReady) + require.Equal(t, v1alpha2.AgentType_Declarative, response.Data.Agent.Spec.Type) + require.True(t, response.Data.RunInSandbox) + }) + t.Run("returns 404 for missing agent", func(t *testing.T) { handler, _ := setupTestHandler() @@ -334,6 +388,34 @@ func TestHandleListAgents(t *testing.T) { require.Equal(t, false, response.Data[0].Accepted) require.Equal(t, true, response.Data[0].DeploymentReady) }) + + t.Run("lists SandboxAgent CRD with Accepted and Ready from status", func(t *testing.T) { + modelConfig := createTestModelConfig() + conditions := []metav1.Condition{ + {Type: "Accepted", Status: "True", Reason: "Reconciled"}, + {Type: "Ready", Status: "True", Reason: "WorkloadReady"}, + } + sa := createTestSandboxAgentCRD("mysandbox", modelConfig, conditions) + handler, _ := setupTestHandler(sa, modelConfig) + + req := httptest.NewRequest("GET", "/api/agents", nil) + req = setUser(req, "test-user") + w := httptest.NewRecorder() + + handler.HandleListAgents(&testErrorResponseWriter{w}, req) + + require.Equal(t, http.StatusOK, w.Code) + + var response api.StandardResponse[[]api.AgentResponse] + err := json.Unmarshal(w.Body.Bytes(), &response) + require.NoError(t, err) + require.Len(t, response.Data, 1) + require.Equal(t, "mysandbox", response.Data[0].Agent.Name) + require.True(t, response.Data[0].Accepted) + require.True(t, response.Data[0].DeploymentReady) + require.Equal(t, v1alpha2.AgentType_Declarative, response.Data[0].Agent.Spec.Type) + require.True(t, response.Data[0].RunInSandbox) + }) } func TestHandleUpdateAgent(t *testing.T) { diff --git a/go/core/internal/httpserver/handlers/handlers.go b/go/core/internal/httpserver/handlers/handlers.go index 00351d37f..b8173027b 100644 --- a/go/core/internal/httpserver/handlers/handlers.go +++ b/go/core/internal/httpserver/handlers/handlers.go @@ -7,6 +7,7 @@ import ( "github.com/kagent-dev/kagent/go/api/database" "github.com/kagent-dev/kagent/go/core/internal/controller/reconciler" "github.com/kagent-dev/kagent/go/core/pkg/auth" + "github.com/kagent-dev/kagent/go/core/pkg/sandboxbackend" ) // Handlers holds all the HTTP handler components @@ -36,16 +37,18 @@ type Base struct { DatabaseService database.Client Authorizer auth.Authorizer // Interface for authorization checks ProxyURL string + SandboxBackend sandboxbackend.Backend } // NewHandlers creates a new Handlers instance with all handler components. -func NewHandlers(kubeClient client.Client, defaultModelConfig types.NamespacedName, dbService database.Client, watchedNamespaces []string, authorizer auth.Authorizer, proxyURL string, rcnclr reconciler.KagentReconciler) *Handlers { +func NewHandlers(kubeClient client.Client, defaultModelConfig types.NamespacedName, dbService database.Client, watchedNamespaces []string, authorizer auth.Authorizer, proxyURL string, rcnclr reconciler.KagentReconciler, sandboxBackend sandboxbackend.Backend) *Handlers { base := &Base{ KubeClient: kubeClient, DefaultModelConfig: defaultModelConfig, DatabaseService: dbService, Authorizer: authorizer, ProxyURL: proxyURL, + SandboxBackend: sandboxBackend, } return &Handlers{ diff --git a/go/core/internal/httpserver/handlers/sessions.go b/go/core/internal/httpserver/handlers/sessions.go index eb26e6714..cf9472f6c 100644 --- a/go/core/internal/httpserver/handlers/sessions.go +++ b/go/core/internal/httpserver/handlers/sessions.go @@ -1,6 +1,7 @@ package handlers import ( + "context" "fmt" "net/http" "strconv" @@ -8,8 +9,11 @@ import ( "github.com/kagent-dev/kagent/go/api/database" api "github.com/kagent-dev/kagent/go/api/httpapi" + "github.com/kagent-dev/kagent/go/api/v1alpha2" "github.com/kagent-dev/kagent/go/core/internal/httpserver/errors" "github.com/kagent-dev/kagent/go/core/internal/utils" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "sigs.k8s.io/controller-runtime/pkg/client" ctrllog "sigs.k8s.io/controller-runtime/pkg/log" "trpc.group/trpc-go/trpc-a2a-go/protocol" ) @@ -24,6 +28,18 @@ func NewSessionsHandler(base *Base) *SessionsHandler { return &SessionsHandler{Base: base} } +func (h *SessionsHandler) isSandboxWorkload(ctx context.Context, namespace, name string) (bool, error) { + sa := &v1alpha2.SandboxAgent{} + err := h.KubeClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: name}, sa) + if err != nil { + if apierrors.IsNotFound(err) { + return false, nil + } + return false, err + } + return true, nil +} + // RunRequest represents a run creation request type RunRequest struct { Task string `json:"task"` @@ -131,6 +147,21 @@ func (h *SessionsHandler) HandleCreateSession(w ErrorResponseWriter, r *http.Req return } + nn, perr := utils.ParseRefString(*sessionRequest.AgentRef, "") + if perr == nil { + if isSandboxWorkload, err := h.isSandboxWorkload(r.Context(), nn.Namespace, nn.Name); err == nil && isSandboxWorkload { + existing, lerr := h.DatabaseService.ListSessionsForAgent(r.Context(), agent.ID, userID) + if lerr != nil { + w.RespondWithError(errors.NewInternalServerError("Failed to list sessions for agent", lerr)) + return + } + if len(existing) > 0 { + w.RespondWithError(errors.NewConflictError("Sandbox agents support only one chat session per user", fmt.Errorf("a session already exists for this agent"))) + return + } + } + } + session := &database.Session{ ID: id, Name: sessionRequest.Name, diff --git a/go/core/internal/httpserver/handlers/sessions_test.go b/go/core/internal/httpserver/handlers/sessions_test.go index feb21fd30..0346394bb 100644 --- a/go/core/internal/httpserver/handlers/sessions_test.go +++ b/go/core/internal/httpserver/handlers/sessions_test.go @@ -393,7 +393,11 @@ func TestSessionsHandler(t *testing.T) { userID := "test-user" sessionID := "test-session" - // Create test session + // Session.AgentID must resolve via GetAgent (non-Sandbox: delete allowed). + require.NoError(t, dbClient.StoreAgent(context.Background(), &database.Agent{ + ID: "1", + Type: "Declarative", + })) agentID := "1" createTestSession(dbClient, sessionID, userID, agentID) diff --git a/go/core/internal/httpserver/handlers/test_helpers_test.go b/go/core/internal/httpserver/handlers/test_helpers_test.go index 0777cc264..d668cc137 100644 --- a/go/core/internal/httpserver/handlers/test_helpers_test.go +++ b/go/core/internal/httpserver/handlers/test_helpers_test.go @@ -22,7 +22,13 @@ func setupScheme() *runtime.Scheme { &v1alpha2.ModelConfigList{}, ) + s.AddKnownTypes(v1alpha2.GroupVersion, + &v1alpha2.SandboxAgent{}, + &v1alpha2.SandboxAgentList{}, + ) + metav1.AddToGroupVersion(s, schema.GroupVersion{Group: "kagent.dev", Version: "v1alpha1"}) + metav1.AddToGroupVersion(s, v1alpha2.GroupVersion) return s } diff --git a/go/core/internal/httpserver/server.go b/go/core/internal/httpserver/server.go index f58b0186d..ea85b2e32 100644 --- a/go/core/internal/httpserver/server.go +++ b/go/core/internal/httpserver/server.go @@ -15,6 +15,7 @@ import ( common "github.com/kagent-dev/kagent/go/core/internal/utils" "github.com/kagent-dev/kagent/go/core/internal/version" "github.com/kagent-dev/kagent/go/core/pkg/auth" + "github.com/kagent-dev/kagent/go/core/pkg/sandboxbackend" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" "k8s.io/apimachinery/pkg/types" ctrl_client "sigs.k8s.io/controller-runtime/pkg/client" @@ -34,6 +35,7 @@ const ( APIPathToolServers = "/api/toolservers" APIPathToolServerTypes = "/api/toolservertypes" APIPathAgents = "/api/agents" + APIPathSandboxAgents = "/api/sandboxagents" APIPathModelProviderConfigs = "/api/modelproviderconfigs" APIPathModels = "/api/models" APIPathMemories = "/api/memories" @@ -63,6 +65,7 @@ type ServerConfig struct { Authorizer auth.Authorizer ProxyURL string Reconciler reconciler.KagentReconciler + SandboxBackend sandboxbackend.Backend } // HTTPServer is the structure that manages the HTTP server @@ -81,7 +84,7 @@ func NewHTTPServer(config ServerConfig) (*HTTPServer, error) { return &HTTPServer{ config: config, router: config.Router, - handlers: handlers.NewHandlers(config.KubeClient, defaultModelConfig, config.DbClient, config.WatchedNamespaces, config.Authorizer, config.ProxyURL, config.Reconciler), + handlers: handlers.NewHandlers(config.KubeClient, defaultModelConfig, config.DbClient, config.WatchedNamespaces, config.Authorizer, config.ProxyURL, config.Reconciler, config.SandboxBackend), authenticator: config.Authenticator, }, nil } @@ -242,6 +245,9 @@ func (s *HTTPServer) setupRoutes() { s.router.HandleFunc(APIPathAgents+"/{namespace}/{name}", adaptHandler(s.handlers.Agents.HandleGetAgent)).Methods(http.MethodGet) s.router.HandleFunc(APIPathAgents+"/{namespace}/{name}", adaptHandler(s.handlers.Agents.HandleDeleteAgent)).Methods(http.MethodDelete) + s.router.HandleFunc(APIPathSandboxAgents, adaptHandler(s.handlers.Agents.HandleCreateSandboxAgent)).Methods(http.MethodPost) + s.router.HandleFunc(APIPathSandboxAgents+"/{namespace}/{name}", adaptHandler(s.handlers.Agents.HandleUpdateSandboxAgent)).Methods(http.MethodPut) + // Model Provider Configs s.router.HandleFunc(APIPathModelProviderConfigs+"/models", adaptHandler(s.handlers.ModelProviderConfig.HandleListSupportedModelProviders)).Methods(http.MethodGet) s.router.HandleFunc(APIPathModelProviderConfigs+"/memories", adaptHandler(s.handlers.ModelProviderConfig.HandleListSupportedMemoryProviders)).Methods(http.MethodGet) diff --git a/go/core/pkg/app/app.go b/go/core/pkg/app/app.go index c9dcaf7fb..c0a262ce0 100644 --- a/go/core/pkg/app/app.go +++ b/go/core/pkg/app/app.go @@ -56,6 +56,7 @@ import ( dbpkg "github.com/kagent-dev/kagent/go/api/database" "github.com/kagent-dev/kagent/go/core/pkg/auth" "github.com/kagent-dev/kagent/go/core/pkg/migrations" + "github.com/kagent-dev/kagent/go/core/pkg/sandboxbackend" "github.com/kagent-dev/kagent/go/core/pkg/translator" "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" @@ -75,6 +76,8 @@ import ( "github.com/kagent-dev/kagent/go/core/internal/controller" "github.com/kagent-dev/kagent/go/core/internal/goruntime" "github.com/kagent-dev/kmcp/api/v1alpha1" + agentsandboxv1 "sigs.k8s.io/agent-sandbox/api/v1alpha1" + extensionsagentsandboxv1 "sigs.k8s.io/agent-sandbox/extensions/api/v1alpha1" // +kubebuilder:scaffold:imports ) @@ -94,6 +97,8 @@ func init() { utilruntime.Must(v1alpha1.AddToScheme(scheme)) utilruntime.Must(v1alpha2.AddToScheme(scheme)) + utilruntime.Must(agentsandboxv1.AddToScheme(scheme)) + utilruntime.Must(extensionsagentsandboxv1.AddToScheme(scheme)) // +kubebuilder:scaffold:scheme } @@ -267,6 +272,7 @@ type ExtensionConfig struct { Authorizer auth.Authorizer AgentPlugins []agent_translator.TranslatorPlugin MCPServerPlugins []translator.MCPTranslatorPlugin + SandboxBackend sandboxbackend.Backend } type GetExtensionConfig func(bootstrap BootstrapConfig) (*ExtensionConfig, error) @@ -482,6 +488,7 @@ func Start(getExtensionConfig GetExtensionConfig, migrationRunner MigrationRunne cfg.DefaultModelConfig, extensionCfg.AgentPlugins, cfg.Proxy.URL, + extensionCfg.SandboxBackend, ) rcnclr := reconciler.NewKagentReconciler( @@ -490,6 +497,7 @@ func Start(getExtensionConfig GetExtensionConfig, migrationRunner MigrationRunne dbClient, cfg.DefaultModelConfig, watchNamespacesList, + extensionCfg.SandboxBackend, ) if err := (&controller.ServiceController{ @@ -517,6 +525,15 @@ func Start(getExtensionConfig GetExtensionConfig, migrationRunner MigrationRunne os.Exit(1) } + if err = (&controller.SandboxAgentController{ + Scheme: mgr.GetScheme(), + Reconciler: rcnclr, + AdkTranslator: apiTranslator, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "SandboxAgent") + os.Exit(1) + } + if err = (&controller.ModelConfigController{ Scheme: mgr.GetScheme(), Reconciler: rcnclr, @@ -618,6 +635,7 @@ func Start(getExtensionConfig GetExtensionConfig, migrationRunner MigrationRunne Authenticator: extensionCfg.Authenticator, ProxyURL: cfg.Proxy.URL, Reconciler: rcnclr, + SandboxBackend: extensionCfg.SandboxBackend, }) if err != nil { setupLog.Error(err, "unable to create HTTP server") diff --git a/go/core/pkg/sandboxbackend/agentsxk8s/agentsxk8s.go b/go/core/pkg/sandboxbackend/agentsxk8s/agentsxk8s.go new file mode 100644 index 000000000..dacc173e1 --- /dev/null +++ b/go/core/pkg/sandboxbackend/agentsxk8s/agentsxk8s.go @@ -0,0 +1,138 @@ +package agentsxk8s + +import ( + "context" + "fmt" + "maps" + + "github.com/kagent-dev/kagent/go/core/pkg/sandboxbackend" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + + agentsandboxv1 "sigs.k8s.io/agent-sandbox/api/v1alpha1" + extensionsv1alpha1 "sigs.k8s.io/agent-sandbox/extensions/api/v1alpha1" +) + +// Backend builds kubernetes-sigs/agent-sandbox SandboxTemplate + SandboxClaim resources. +type Backend struct{} + +var _ sandboxbackend.Backend = (*Backend)(nil) + +// New returns the agent-sandbox backend. +func New() *Backend { + return &Backend{} +} + +func (b *Backend) GetOwnedResourceTypes() []client.Object { + return []client.Object{ + &extensionsv1alpha1.SandboxTemplate{}, + &extensionsv1alpha1.SandboxClaim{}, + } +} + +func (b *Backend) BuildSandbox(_ context.Context, in sandboxbackend.BuildInput) ([]client.Object, error) { + if in.Agent == nil { + return nil, fmt.Errorf("agent is required") + } + name := in.Agent.Name + if in.WorkloadName != "" { + name = in.WorkloadName + } + podLabels := in.PodTemplate.Labels + if len(in.ExtraLabels) > 0 { + podLabels = mapsUnion(podLabels, in.ExtraLabels) + } + + return b.buildSandboxTemplateAndClaim(in, name, podLabels) +} + +func (b *Backend) buildSandboxTemplateAndClaim(in sandboxbackend.BuildInput, claimName string, podLabels map[string]string) ([]client.Object, error) { + tmplName := in.TemplateName + if tmplName == "" { + return nil, fmt.Errorf("template name is required") + } + + pt := agentsandboxv1.PodTemplate{ + Spec: in.PodTemplate.Spec, + ObjectMeta: agentsandboxv1.PodMetadata{ + Labels: podLabels, + Annotations: in.PodTemplate.Annotations, + }, + } + + labelUnion := mapsUnion(podLabels, in.Agent.Labels) + + tmplSpec := extensionsv1alpha1.SandboxTemplateSpec{ + PodTemplate: pt, + // Unmanaged allows kagent to reach the agent Service in-cluster without agent-sandbox-managed NetworkPolicies. + NetworkPolicyManagement: extensionsv1alpha1.NetworkPolicyManagementUnmanaged, + } + + st := &extensionsv1alpha1.SandboxTemplate{ + TypeMeta: metav1.TypeMeta{ + APIVersion: extensionsv1alpha1.GroupVersion.String(), + Kind: "SandboxTemplate", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: tmplName, + Namespace: in.Agent.Namespace, + Annotations: in.Agent.Annotations, + Labels: labelUnion, + }, + Spec: tmplSpec, + } + out := []client.Object{st} + + claim := &extensionsv1alpha1.SandboxClaim{ + TypeMeta: metav1.TypeMeta{ + APIVersion: extensionsv1alpha1.GroupVersion.String(), + Kind: "SandboxClaim", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: claimName, + Namespace: in.Agent.Namespace, + Annotations: in.Agent.Annotations, + Labels: labelUnion, + }, + Spec: extensionsv1alpha1.SandboxClaimSpec{ + TemplateRef: extensionsv1alpha1.SandboxTemplateRef{ + Name: tmplName, + }, + }, + } + out = append(out, claim) + return out, nil +} + +func mapsUnion(podLabels map[string]string, agentLabels map[string]string) map[string]string { + if len(podLabels) == 0 && len(agentLabels) == 0 { + return nil + } + out := make(map[string]string, len(podLabels)+len(agentLabels)) + maps.Copy(out, podLabels) + for k, v := range agentLabels { + if _, ok := out[k]; !ok { + out[k] = v + } + } + return out +} + +func (b *Backend) ComputeReady(ctx context.Context, cl client.Client, nn types.NamespacedName) (metav1.ConditionStatus, string, string) { + sb := &agentsandboxv1.Sandbox{} + if err := cl.Get(ctx, nn, sb); err != nil { + if apierrors.IsNotFound(err) { + return metav1.ConditionUnknown, "SandboxNotFound", err.Error() + } + return metav1.ConditionUnknown, "SandboxGetFailed", err.Error() + } + for i := range sb.Status.Conditions { + c := sb.Status.Conditions[i] + if c.Type == string(agentsandboxv1.SandboxConditionReady) { + return c.Status, c.Reason, c.Message + } + } + return metav1.ConditionUnknown, "SandboxReadyPending", "Sandbox Ready condition not yet reported" +} diff --git a/go/core/pkg/sandboxbackend/agentsxk8s/agentsxk8s_test.go b/go/core/pkg/sandboxbackend/agentsxk8s/agentsxk8s_test.go new file mode 100644 index 000000000..74c9a9958 --- /dev/null +++ b/go/core/pkg/sandboxbackend/agentsxk8s/agentsxk8s_test.go @@ -0,0 +1,46 @@ +package agentsxk8s + +import ( + "context" + "testing" + + "github.com/kagent-dev/kagent/go/api/v1alpha2" + "github.com/kagent-dev/kagent/go/core/pkg/sandboxbackend" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + extensionsv1alpha1 "sigs.k8s.io/agent-sandbox/extensions/api/v1alpha1" +) + +func TestBackend_BuildSandbox_templateAndClaim(t *testing.T) { + b := New() + agent := &v1alpha2.Agent{ + ObjectMeta: metav1.ObjectMeta{Name: "a1", Namespace: "ns1", Labels: map[string]string{"app": "x"}}, + } + pt := corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"kagent": "a1"}}, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{Name: "kagent", Image: "img:v1"}}, + }, + } + objs, err := b.BuildSandbox(context.Background(), sandboxbackend.BuildInput{ + Agent: agent, + PodTemplate: pt, + TemplateName: "kagent-a1", + }) + require.NoError(t, err) + require.Len(t, objs, 2) + + st, ok := objs[0].(*extensionsv1alpha1.SandboxTemplate) + require.True(t, ok) + require.Equal(t, "kagent-a1", st.Name) + require.Equal(t, "img:v1", st.Spec.PodTemplate.Spec.Containers[0].Image) + require.Equal(t, extensionsv1alpha1.NetworkPolicyManagementUnmanaged, st.Spec.NetworkPolicyManagement) + require.Nil(t, st.Spec.NetworkPolicy) + + claim, ok := objs[1].(*extensionsv1alpha1.SandboxClaim) + require.True(t, ok) + require.Equal(t, "a1", claim.Name) + require.Equal(t, "kagent-a1", claim.Spec.TemplateRef.Name) +} diff --git a/go/core/pkg/sandboxbackend/apis_available.go b/go/core/pkg/sandboxbackend/apis_available.go new file mode 100644 index 000000000..e1016fad5 --- /dev/null +++ b/go/core/pkg/sandboxbackend/apis_available.go @@ -0,0 +1,38 @@ +package sandboxbackend + +import ( + "context" + "fmt" + + "k8s.io/apimachinery/pkg/api/meta" + "sigs.k8s.io/controller-runtime/pkg/client" + + agentsandboxv1 "sigs.k8s.io/agent-sandbox/api/v1alpha1" + extensionsv1alpha1 "sigs.k8s.io/agent-sandbox/extensions/api/v1alpha1" +) + +// EnsureAgentSandboxAPIsRegistered checks that the apiserver exposes the agent-sandbox +// resources kagent needs (SandboxTemplate, SandboxClaim, Sandbox). Call this before +// creating or reconciling SandboxAgent when a sandbox backend is configured. +// +// When CRDs are missing, the apiserver returns a *meta.NoKindMatchError (or similar); +// that surfaces as a clear prerequisite error instead of a late reconcile failure. +func EnsureAgentSandboxAPIsRegistered(ctx context.Context, c client.Client) error { + checks := []struct { + list client.ObjectList + kind string + }{ + {&extensionsv1alpha1.SandboxTemplateList{}, "SandboxTemplate (extensions.agents.x-k8s.io/v1alpha1)"}, + {&extensionsv1alpha1.SandboxClaimList{}, "SandboxClaim (extensions.agents.x-k8s.io/v1alpha1)"}, + {&agentsandboxv1.SandboxList{}, "Sandbox (agents.x-k8s.io/v1alpha1)"}, + } + for _, ch := range checks { + if err := c.List(ctx, ch.list, client.Limit(1)); err != nil { + if meta.IsNoMatchError(err) { + return fmt.Errorf("agent-sandbox API %s is not available on this cluster; install the agent-sandbox CRDs and controller before using SandboxAgent: %w", ch.kind, err) + } + return fmt.Errorf("could not reach agent-sandbox API %s (check RBAC and apiserver connectivity): %w", ch.kind, err) + } + } + return nil +} diff --git a/go/core/pkg/sandboxbackend/backend.go b/go/core/pkg/sandboxbackend/backend.go new file mode 100644 index 000000000..aaa9cad8e --- /dev/null +++ b/go/core/pkg/sandboxbackend/backend.go @@ -0,0 +1,31 @@ +package sandboxbackend + +import ( + "context" + + "github.com/kagent-dev/kagent/go/api/v1alpha2" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// BuildInput carries the pod template to embed in SandboxTemplate and reference from SandboxClaim. +type BuildInput struct { + Agent *v1alpha2.Agent + PodTemplate corev1.PodTemplateSpec + WorkloadName string + ExtraLabels map[string]string + + // TemplateName is the SandboxTemplate metadata.name (required). + TemplateName string +} + +// Backend builds sandbox CRD objects and evaluates their readiness. +type Backend interface { + BuildSandbox(ctx context.Context, in BuildInput) ([]client.Object, error) + GetOwnedResourceTypes() []client.Object + + // ComputeReady reflects implementation-specific status into condition pieces for Agent.status. + ComputeReady(ctx context.Context, cl client.Client, nn types.NamespacedName) (status metav1.ConditionStatus, reason, message string) +} diff --git a/go/go.mod b/go/go.mod index 0d9bd0609..a51a57b50 100644 --- a/go/go.mod +++ b/go/go.mod @@ -53,6 +53,7 @@ require ( k8s.io/api v0.35.3 k8s.io/apimachinery v0.35.3 k8s.io/client-go v0.35.3 + sigs.k8s.io/agent-sandbox v0.2.1 sigs.k8s.io/controller-runtime v0.23.3 sigs.k8s.io/yaml v1.6.0 trpc.group/trpc-go/trpc-a2a-go v0.2.5 @@ -65,6 +66,7 @@ require ( go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0 go.opentelemetry.io/otel/sdk/log v0.18.0 + k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 ) require ( @@ -120,7 +122,7 @@ require ( github.com/docker/go-units v0.5.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/ebitengine/purego v0.10.0 // indirect - github.com/emicklei/go-restful/v3 v3.12.2 // indirect + github.com/emicklei/go-restful/v3 v3.13.0 // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect github.com/evanphx/json-patch/v5 v5.9.11 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect @@ -129,9 +131,20 @@ require ( github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.2.6 // indirect - github.com/go-openapi/jsonpointer v0.21.0 // indirect - github.com/go-openapi/jsonreference v0.20.2 // indirect - github.com/go-openapi/swag v0.23.0 // indirect + github.com/go-openapi/jsonpointer v0.22.1 // indirect + github.com/go-openapi/jsonreference v0.21.2 // indirect + github.com/go-openapi/swag v0.25.1 // indirect + github.com/go-openapi/swag/cmdutils v0.25.1 // indirect + github.com/go-openapi/swag/conv v0.25.1 // indirect + github.com/go-openapi/swag/fileutils v0.25.1 // indirect + github.com/go-openapi/swag/jsonname v0.25.1 // indirect + github.com/go-openapi/swag/jsonutils v0.25.1 // indirect + github.com/go-openapi/swag/loading v0.25.1 // indirect + github.com/go-openapi/swag/mangling v0.25.1 // indirect + github.com/go-openapi/swag/netutils v0.25.1 // indirect + github.com/go-openapi/swag/stringutils v0.25.1 // indirect + github.com/go-openapi/swag/typeutils v0.25.1 // indirect + github.com/go-openapi/swag/yamlutils v0.25.1 // indirect github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/goccy/go-json v0.10.3 // indirect github.com/golang-jwt/jwt/v5 v5.3.0 // indirect @@ -153,6 +166,8 @@ require ( github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/josharian/intern v1.0.0 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.18.2 // indirect github.com/lestrrat-go/blackmagic v1.0.2 // indirect @@ -164,7 +179,6 @@ require ( github.com/lucasb-eyer/go-colorful v1.3.0 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.10 // indirect - github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-localereader v0.0.1 // indirect @@ -241,7 +255,7 @@ require ( golang.org/x/sys v0.42.0 // indirect golang.org/x/term v0.41.0 // indirect golang.org/x/time v0.14.0 // indirect - gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect + gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect google.golang.org/api v0.252.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect @@ -253,7 +267,6 @@ require ( k8s.io/component-base v0.35.0 // indirect k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect - k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 // indirect rsc.io/omap v1.2.0 // indirect rsc.io/ordered v1.1.1 // indirect sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect diff --git a/go/go.sum b/go/go.sum index 59e34a0fc..135724336 100644 --- a/go/go.sum +++ b/go/go.sum @@ -123,7 +123,6 @@ github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7np github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA= github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -148,8 +147,8 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU= github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= -github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU= -github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= +github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA= github.com/envoyproxy/go-control-plane/envoy v1.36.0 h1:yg/JjO5E7ubRyKX3m07GF3reDNEnfOboJ0QySbH736g= github.com/envoyproxy/go-control-plane/envoy v1.36.0/go.mod h1:ty89S1YCCVruQAm9OtKeEkQLTb+Lkz0k8v9W0Oxsv98= @@ -183,14 +182,36 @@ github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= -github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= -github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= -github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= -github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= -github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= -github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= -github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= -github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-openapi/jsonpointer v0.22.1 h1:sHYI1He3b9NqJ4wXLoJDKmUmHkWy/L7rtEo92JUxBNk= +github.com/go-openapi/jsonpointer v0.22.1/go.mod h1:pQT9OsLkfz1yWoMgYFy4x3U5GY5nUlsOn1qSBH5MkCM= +github.com/go-openapi/jsonreference v0.21.2 h1:Wxjda4M/BBQllegefXrY/9aq1fxBA8sI5M/lFU6tSWU= +github.com/go-openapi/jsonreference v0.21.2/go.mod h1:pp3PEjIsJ9CZDGCNOyXIQxsNuroxm8FAJ/+quA0yKzQ= +github.com/go-openapi/swag v0.25.1 h1:6uwVsx+/OuvFVPqfQmOOPsqTcm5/GkBhNwLqIR916n8= +github.com/go-openapi/swag v0.25.1/go.mod h1:bzONdGlT0fkStgGPd3bhZf1MnuPkf2YAys6h+jZipOo= +github.com/go-openapi/swag/cmdutils v0.25.1 h1:nDke3nAFDArAa631aitksFGj2omusks88GF1VwdYqPY= +github.com/go-openapi/swag/cmdutils v0.25.1/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0= +github.com/go-openapi/swag/conv v0.25.1 h1:+9o8YUg6QuqqBM5X6rYL/p1dpWeZRhoIt9x7CCP+he0= +github.com/go-openapi/swag/conv v0.25.1/go.mod h1:Z1mFEGPfyIKPu0806khI3zF+/EUXde+fdeksUl2NiDs= +github.com/go-openapi/swag/fileutils v0.25.1 h1:rSRXapjQequt7kqalKXdcpIegIShhTPXx7yw0kek2uU= +github.com/go-openapi/swag/fileutils v0.25.1/go.mod h1:+NXtt5xNZZqmpIpjqcujqojGFek9/w55b3ecmOdtg8M= +github.com/go-openapi/swag/jsonname v0.25.1 h1:Sgx+qbwa4ej6AomWC6pEfXrA6uP2RkaNjA9BR8a1RJU= +github.com/go-openapi/swag/jsonname v0.25.1/go.mod h1:71Tekow6UOLBD3wS7XhdT98g5J5GR13NOTQ9/6Q11Zo= +github.com/go-openapi/swag/jsonutils v0.25.1 h1:AihLHaD0brrkJoMqEZOBNzTLnk81Kg9cWr+SPtxtgl8= +github.com/go-openapi/swag/jsonutils v0.25.1/go.mod h1:JpEkAjxQXpiaHmRO04N1zE4qbUEg3b7Udll7AMGTNOo= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.1 h1:DSQGcdB6G0N9c/KhtpYc71PzzGEIc/fZ1no35x4/XBY= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.1/go.mod h1:kjmweouyPwRUEYMSrbAidoLMGeJ5p6zdHi9BgZiqmsg= +github.com/go-openapi/swag/loading v0.25.1 h1:6OruqzjWoJyanZOim58iG2vj934TysYVptyaoXS24kw= +github.com/go-openapi/swag/loading v0.25.1/go.mod h1:xoIe2EG32NOYYbqxvXgPzne989bWvSNoWoyQVWEZicc= +github.com/go-openapi/swag/mangling v0.25.1 h1:XzILnLzhZPZNtmxKaz/2xIGPQsBsvmCjrJOWGNz/ync= +github.com/go-openapi/swag/mangling v0.25.1/go.mod h1:CdiMQ6pnfAgyQGSOIYnZkXvqhnnwOn997uXZMAd/7mQ= +github.com/go-openapi/swag/netutils v0.25.1 h1:2wFLYahe40tDUHfKT1GRC4rfa5T1B4GWZ+msEFA4Fl4= +github.com/go-openapi/swag/netutils v0.25.1/go.mod h1:CAkkvqnUJX8NV96tNhEQvKz8SQo2KF0f7LleiJwIeRE= +github.com/go-openapi/swag/stringutils v0.25.1 h1:Xasqgjvk30eUe8VKdmyzKtjkVjeiXx1Iz0zDfMNpPbw= +github.com/go-openapi/swag/stringutils v0.25.1/go.mod h1:JLdSAq5169HaiDUbTvArA2yQxmgn4D6h4A+4HqVvAYg= +github.com/go-openapi/swag/typeutils v0.25.1 h1:rD/9HsEQieewNt6/k+JBwkxuAHktFtH3I3ysiFZqukA= +github.com/go-openapi/swag/typeutils v0.25.1/go.mod h1:9McMC/oCdS4BKwk2shEB7x17P6HmMmA6dQRtAkSnNb8= +github.com/go-openapi/swag/yamlutils v0.25.1 h1:mry5ez8joJwzvMbaTGLhw8pXUnhDK91oSJLDPF1bmGk= +github.com/go-openapi/swag/yamlutils v0.25.1/go.mod h1:cm9ywbzncy3y6uPm/97ysW8+wZ09qsks+9RS8fLWKqg= github.com/go-pg/pg/v10 v10.11.0 h1:CMKJqLgTrfpE/aOVeLdybezR2om071Vh38OLZjsyMI0= github.com/go-pg/pg/v10 v10.11.0/go.mod h1:4BpHRoxE61y4Onpof3x1a2SQvi9c+q1dJnrNdMjsroA= github.com/go-pg/zerochecker v0.2.0 h1:pp7f72c3DobMWOb2ErtZsnrPaSvHd2W4o9//8HtF4mU= @@ -263,8 +284,6 @@ github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= -github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= -github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kagent-dev/kmcp v0.2.8 h1:X03WYFUQsFLtMGZ1sFKUhtdNKaFmMytLK6EQUfmaEXM= @@ -273,11 +292,8 @@ github.com/kagent-dev/mockllm v0.0.5 h1:mm9Ml3NH6/E/YKVMgMwWYMNsNGkDze6I6TC0ppHZ github.com/kagent-dev/mockllm v0.0.5/go.mod h1:tDLemRsTZa1NdHaDbg3sgFk9cT1QWvMPlBtLVD6I2mA= github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk= github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= @@ -302,8 +318,6 @@ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= @@ -578,8 +592,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= -gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= +gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0= +gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= google.golang.org/adk v1.0.0 h1:DcJGKH9YweOdsAvE5Hu9UhhLoVYcNEVKzvOPS+B49lQ= @@ -638,6 +652,8 @@ rsc.io/omap v1.2.0 h1:c1M8jchnHbzmJALzGLclfH3xDWXrPxSUHXzH5C+8Kdw= rsc.io/omap v1.2.0/go.mod h1:C8pkI0AWexHopQtZX+qiUeJGzvc8HkdgnsWK4/mAa00= rsc.io/ordered v1.1.1 h1:1kZM6RkTmceJgsFH/8DLQvkCVEYomVDJfBRLT595Uak= rsc.io/ordered v1.1.1/go.mod h1:evAi8739bWVBRG9aaufsjVc202+6okf8u2QeVL84BCM= +sigs.k8s.io/agent-sandbox v0.2.1 h1:BRrZzBkmoXjzSyCLnv7a2F804uSPQbbinOKtRbKe/94= +sigs.k8s.io/agent-sandbox v0.2.1/go.mod h1:S2A4dLvKUUp7nk7uTXmzzwI2N9/4NabxjQ4McJXrRWU= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 h1:jpcvIRr3GLoUoEKRkHKSmGjxb6lWwrBlJsXc+eUYQHM= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= sigs.k8s.io/controller-runtime v0.23.3 h1:VjB/vhoPoA9l1kEKZHBMnQF33tdCLQKJtydy4iqwZ80= diff --git a/helm/kagent-crds/templates/extensions.agents.x-k8s.io_sandboxclaims.yaml b/helm/kagent-crds/templates/extensions.agents.x-k8s.io_sandboxclaims.yaml new file mode 100644 index 000000000..69efd1906 --- /dev/null +++ b/helm/kagent-crds/templates/extensions.agents.x-k8s.io_sandboxclaims.yaml @@ -0,0 +1,103 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + name: sandboxclaims.extensions.agents.x-k8s.io +spec: + group: extensions.agents.x-k8s.io + names: + kind: SandboxClaim + listKind: SandboxClaimList + plural: sandboxclaims + shortNames: + - sandboxclaim + singular: sandboxclaim + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + lifecycle: + properties: + shutdownPolicy: + default: Retain + enum: + - Delete + - Retain + type: string + shutdownTime: + format: date-time + type: string + type: object + sandboxTemplateRef: + properties: + name: + type: string + required: + - name + type: object + required: + - sandboxTemplateRef + type: object + status: + properties: + conditions: + items: + properties: + lastTransitionTime: + format: date-time + type: string + message: + maxLength: 32768 + type: string + observedGeneration: + format: int64 + minimum: 0 + type: integer + reason: + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + enum: + - "True" + - "False" + - Unknown + type: string + type: + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + sandbox: + properties: + Name: + type: string + type: object + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/helm/kagent-crds/templates/extensions.agents.x-k8s.io_sandboxtemplates.yaml b/helm/kagent-crds/templates/extensions.agents.x-k8s.io_sandboxtemplates.yaml new file mode 100644 index 000000000..0f9fc3316 --- /dev/null +++ b/helm/kagent-crds/templates/extensions.agents.x-k8s.io_sandboxtemplates.yaml @@ -0,0 +1,4019 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + name: sandboxtemplates.extensions.agents.x-k8s.io +spec: + group: extensions.agents.x-k8s.io + names: + kind: SandboxTemplate + listKind: SandboxTemplateList + plural: sandboxtemplates + shortNames: + - sandboxtemplate + singular: sandboxtemplate + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + networkPolicy: + properties: + egress: + items: + properties: + ports: + items: + properties: + endPort: + format: int32 + type: integer + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + protocol: + type: string + type: object + type: array + x-kubernetes-list-type: atomic + to: + items: + properties: + ipBlock: + properties: + cidr: + type: string + except: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - cidr + type: object + namespaceSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + type: object + type: object + x-kubernetes-map-type: atomic + podSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + type: object + type: object + x-kubernetes-map-type: atomic + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: array + ingress: + items: + properties: + from: + items: + properties: + ipBlock: + properties: + cidr: + type: string + except: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - cidr + type: object + namespaceSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + type: object + type: object + x-kubernetes-map-type: atomic + podSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + type: object + type: object + x-kubernetes-map-type: atomic + type: object + type: array + x-kubernetes-list-type: atomic + ports: + items: + properties: + endPort: + format: int32 + type: integer + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + protocol: + type: string + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: array + type: object + networkPolicyManagement: + default: Managed + enum: + - Managed + - Unmanaged + type: string + podTemplate: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + spec: + properties: + activeDeadlineSeconds: + format: int64 + type: integer + affinity: + properties: + nodeAffinity: + properties: + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + preference: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + properties: + nodeSelectorTerms: + items: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + properties: + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + podAffinityTerm: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + type: string + required: + - topologyKey + type: object + weight: + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + items: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + properties: + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + podAffinityTerm: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + type: string + required: + - topologyKey + type: object + weight: + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + items: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + automountServiceAccountToken: + type: boolean + containers: + items: + properties: + args: + items: + type: string + type: array + x-kubernetes-list-type: atomic + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + env: + items: + properties: + name: + type: string + value: + type: string + valueFrom: + properties: + configMapKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + fileKeyRef: + properties: + key: + type: string + optional: + default: false + type: boolean + path: + type: string + volumeName: + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + envFrom: + items: + properties: + configMapRef: + properties: + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + prefix: + type: string + secretRef: + properties: + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + type: object + type: array + x-kubernetes-list-type: atomic + image: + type: string + imagePullPolicy: + type: string + lifecycle: + properties: + postStart: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + sleep: + properties: + seconds: + format: int64 + type: integer + required: + - seconds + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + preStop: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + sleep: + properties: + seconds: + format: int64 + type: integer + required: + - seconds + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + stopSignal: + type: string + type: object + livenessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + name: + type: string + ports: + items: + properties: + containerPort: + format: int32 + type: integer + hostIP: + type: string + hostPort: + format: int32 + type: integer + name: + type: string + protocol: + default: TCP + type: string + required: + - containerPort + type: object + type: array + x-kubernetes-list-map-keys: + - containerPort + - protocol + x-kubernetes-list-type: map + readinessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + resizePolicy: + items: + properties: + resourceName: + type: string + restartPolicy: + type: string + required: + - resourceName + - restartPolicy + type: object + type: array + x-kubernetes-list-type: atomic + resources: + properties: + claims: + items: + properties: + name: + type: string + request: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + restartPolicy: + type: string + restartPolicyRules: + items: + properties: + action: + type: string + exitCodes: + properties: + operator: + type: string + values: + items: + format: int32 + type: integer + type: array + x-kubernetes-list-type: set + required: + - operator + type: object + required: + - action + type: object + type: array + x-kubernetes-list-type: atomic + securityContext: + properties: + allowPrivilegeEscalation: + type: boolean + appArmorProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + capabilities: + properties: + add: + items: + type: string + type: array + x-kubernetes-list-type: atomic + drop: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + privileged: + type: boolean + procMount: + type: string + readOnlyRootFilesystem: + type: boolean + runAsGroup: + format: int64 + type: integer + runAsNonRoot: + type: boolean + runAsUser: + format: int64 + type: integer + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string + type: object + seccompProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + windowsOptions: + properties: + gmsaCredentialSpec: + type: string + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + runAsUserName: + type: string + type: object + type: object + startupProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + stdin: + type: boolean + stdinOnce: + type: boolean + terminationMessagePath: + type: string + terminationMessagePolicy: + type: string + tty: + type: boolean + volumeDevices: + items: + properties: + devicePath: + type: string + name: + type: string + required: + - devicePath + - name + type: object + type: array + x-kubernetes-list-map-keys: + - devicePath + x-kubernetes-list-type: map + volumeMounts: + items: + properties: + mountPath: + type: string + mountPropagation: + type: string + name: + type: string + readOnly: + type: boolean + recursiveReadOnly: + type: string + subPath: + type: string + subPathExpr: + type: string + required: + - mountPath + - name + type: object + type: array + x-kubernetes-list-map-keys: + - mountPath + x-kubernetes-list-type: map + workingDir: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + dnsConfig: + properties: + nameservers: + items: + type: string + type: array + x-kubernetes-list-type: atomic + options: + items: + properties: + name: + type: string + value: + type: string + type: object + type: array + x-kubernetes-list-type: atomic + searches: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + dnsPolicy: + type: string + enableServiceLinks: + type: boolean + ephemeralContainers: + items: + properties: + args: + items: + type: string + type: array + x-kubernetes-list-type: atomic + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + env: + items: + properties: + name: + type: string + value: + type: string + valueFrom: + properties: + configMapKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + fileKeyRef: + properties: + key: + type: string + optional: + default: false + type: boolean + path: + type: string + volumeName: + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + envFrom: + items: + properties: + configMapRef: + properties: + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + prefix: + type: string + secretRef: + properties: + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + type: object + type: array + x-kubernetes-list-type: atomic + image: + type: string + imagePullPolicy: + type: string + lifecycle: + properties: + postStart: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + sleep: + properties: + seconds: + format: int64 + type: integer + required: + - seconds + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + preStop: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + sleep: + properties: + seconds: + format: int64 + type: integer + required: + - seconds + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + stopSignal: + type: string + type: object + livenessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + name: + type: string + ports: + items: + properties: + containerPort: + format: int32 + type: integer + hostIP: + type: string + hostPort: + format: int32 + type: integer + name: + type: string + protocol: + default: TCP + type: string + required: + - containerPort + type: object + type: array + x-kubernetes-list-map-keys: + - containerPort + - protocol + x-kubernetes-list-type: map + readinessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + resizePolicy: + items: + properties: + resourceName: + type: string + restartPolicy: + type: string + required: + - resourceName + - restartPolicy + type: object + type: array + x-kubernetes-list-type: atomic + resources: + properties: + claims: + items: + properties: + name: + type: string + request: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + restartPolicy: + type: string + restartPolicyRules: + items: + properties: + action: + type: string + exitCodes: + properties: + operator: + type: string + values: + items: + format: int32 + type: integer + type: array + x-kubernetes-list-type: set + required: + - operator + type: object + required: + - action + type: object + type: array + x-kubernetes-list-type: atomic + securityContext: + properties: + allowPrivilegeEscalation: + type: boolean + appArmorProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + capabilities: + properties: + add: + items: + type: string + type: array + x-kubernetes-list-type: atomic + drop: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + privileged: + type: boolean + procMount: + type: string + readOnlyRootFilesystem: + type: boolean + runAsGroup: + format: int64 + type: integer + runAsNonRoot: + type: boolean + runAsUser: + format: int64 + type: integer + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string + type: object + seccompProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + windowsOptions: + properties: + gmsaCredentialSpec: + type: string + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + runAsUserName: + type: string + type: object + type: object + startupProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + stdin: + type: boolean + stdinOnce: + type: boolean + targetContainerName: + type: string + terminationMessagePath: + type: string + terminationMessagePolicy: + type: string + tty: + type: boolean + volumeDevices: + items: + properties: + devicePath: + type: string + name: + type: string + required: + - devicePath + - name + type: object + type: array + x-kubernetes-list-map-keys: + - devicePath + x-kubernetes-list-type: map + volumeMounts: + items: + properties: + mountPath: + type: string + mountPropagation: + type: string + name: + type: string + readOnly: + type: boolean + recursiveReadOnly: + type: string + subPath: + type: string + subPathExpr: + type: string + required: + - mountPath + - name + type: object + type: array + x-kubernetes-list-map-keys: + - mountPath + x-kubernetes-list-type: map + workingDir: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + hostAliases: + items: + properties: + hostnames: + items: + type: string + type: array + x-kubernetes-list-type: atomic + ip: + type: string + required: + - ip + type: object + type: array + x-kubernetes-list-map-keys: + - ip + x-kubernetes-list-type: map + hostIPC: + type: boolean + hostNetwork: + type: boolean + hostPID: + type: boolean + hostUsers: + type: boolean + hostname: + type: string + hostnameOverride: + type: string + imagePullSecrets: + items: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + initContainers: + items: + properties: + args: + items: + type: string + type: array + x-kubernetes-list-type: atomic + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + env: + items: + properties: + name: + type: string + value: + type: string + valueFrom: + properties: + configMapKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + fileKeyRef: + properties: + key: + type: string + optional: + default: false + type: boolean + path: + type: string + volumeName: + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + envFrom: + items: + properties: + configMapRef: + properties: + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + prefix: + type: string + secretRef: + properties: + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + type: object + type: array + x-kubernetes-list-type: atomic + image: + type: string + imagePullPolicy: + type: string + lifecycle: + properties: + postStart: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + sleep: + properties: + seconds: + format: int64 + type: integer + required: + - seconds + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + preStop: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + sleep: + properties: + seconds: + format: int64 + type: integer + required: + - seconds + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + stopSignal: + type: string + type: object + livenessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + name: + type: string + ports: + items: + properties: + containerPort: + format: int32 + type: integer + hostIP: + type: string + hostPort: + format: int32 + type: integer + name: + type: string + protocol: + default: TCP + type: string + required: + - containerPort + type: object + type: array + x-kubernetes-list-map-keys: + - containerPort + - protocol + x-kubernetes-list-type: map + readinessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + resizePolicy: + items: + properties: + resourceName: + type: string + restartPolicy: + type: string + required: + - resourceName + - restartPolicy + type: object + type: array + x-kubernetes-list-type: atomic + resources: + properties: + claims: + items: + properties: + name: + type: string + request: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + restartPolicy: + type: string + restartPolicyRules: + items: + properties: + action: + type: string + exitCodes: + properties: + operator: + type: string + values: + items: + format: int32 + type: integer + type: array + x-kubernetes-list-type: set + required: + - operator + type: object + required: + - action + type: object + type: array + x-kubernetes-list-type: atomic + securityContext: + properties: + allowPrivilegeEscalation: + type: boolean + appArmorProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + capabilities: + properties: + add: + items: + type: string + type: array + x-kubernetes-list-type: atomic + drop: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + privileged: + type: boolean + procMount: + type: string + readOnlyRootFilesystem: + type: boolean + runAsGroup: + format: int64 + type: integer + runAsNonRoot: + type: boolean + runAsUser: + format: int64 + type: integer + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string + type: object + seccompProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + windowsOptions: + properties: + gmsaCredentialSpec: + type: string + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + runAsUserName: + type: string + type: object + type: object + startupProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + stdin: + type: boolean + stdinOnce: + type: boolean + terminationMessagePath: + type: string + terminationMessagePolicy: + type: string + tty: + type: boolean + volumeDevices: + items: + properties: + devicePath: + type: string + name: + type: string + required: + - devicePath + - name + type: object + type: array + x-kubernetes-list-map-keys: + - devicePath + x-kubernetes-list-type: map + volumeMounts: + items: + properties: + mountPath: + type: string + mountPropagation: + type: string + name: + type: string + readOnly: + type: boolean + recursiveReadOnly: + type: string + subPath: + type: string + subPathExpr: + type: string + required: + - mountPath + - name + type: object + type: array + x-kubernetes-list-map-keys: + - mountPath + x-kubernetes-list-type: map + workingDir: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + nodeName: + type: string + nodeSelector: + additionalProperties: + type: string + type: object + x-kubernetes-map-type: atomic + os: + properties: + name: + type: string + required: + - name + type: object + overhead: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + preemptionPolicy: + type: string + priority: + format: int32 + type: integer + priorityClassName: + type: string + readinessGates: + items: + properties: + conditionType: + type: string + required: + - conditionType + type: object + type: array + x-kubernetes-list-type: atomic + resourceClaims: + items: + properties: + name: + type: string + resourceClaimName: + type: string + resourceClaimTemplateName: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + resources: + properties: + claims: + items: + properties: + name: + type: string + request: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + restartPolicy: + type: string + runtimeClassName: + type: string + schedulerName: + type: string + schedulingGates: + items: + properties: + name: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + securityContext: + properties: + appArmorProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + fsGroup: + format: int64 + type: integer + fsGroupChangePolicy: + type: string + runAsGroup: + format: int64 + type: integer + runAsNonRoot: + type: boolean + runAsUser: + format: int64 + type: integer + seLinuxChangePolicy: + type: string + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string + type: object + seccompProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + supplementalGroups: + items: + format: int64 + type: integer + type: array + x-kubernetes-list-type: atomic + supplementalGroupsPolicy: + type: string + sysctls: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + windowsOptions: + properties: + gmsaCredentialSpec: + type: string + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + runAsUserName: + type: string + type: object + type: object + serviceAccount: + type: string + serviceAccountName: + type: string + setHostnameAsFQDN: + type: boolean + shareProcessNamespace: + type: boolean + subdomain: + type: string + terminationGracePeriodSeconds: + format: int64 + type: integer + tolerations: + items: + properties: + effect: + type: string + key: + type: string + operator: + type: string + tolerationSeconds: + format: int64 + type: integer + value: + type: string + type: object + type: array + x-kubernetes-list-type: atomic + topologySpreadConstraints: + items: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + items: + type: string + type: array + x-kubernetes-list-type: atomic + maxSkew: + format: int32 + type: integer + minDomains: + format: int32 + type: integer + nodeAffinityPolicy: + type: string + nodeTaintsPolicy: + type: string + topologyKey: + type: string + whenUnsatisfiable: + type: string + required: + - maxSkew + - topologyKey + - whenUnsatisfiable + type: object + type: array + x-kubernetes-list-map-keys: + - topologyKey + - whenUnsatisfiable + x-kubernetes-list-type: map + volumes: + items: + properties: + awsElasticBlockStore: + properties: + fsType: + type: string + partition: + format: int32 + type: integer + readOnly: + type: boolean + volumeID: + type: string + required: + - volumeID + type: object + azureDisk: + properties: + cachingMode: + type: string + diskName: + type: string + diskURI: + type: string + fsType: + default: ext4 + type: string + kind: + type: string + readOnly: + default: false + type: boolean + required: + - diskName + - diskURI + type: object + azureFile: + properties: + readOnly: + type: boolean + secretName: + type: string + shareName: + type: string + required: + - secretName + - shareName + type: object + cephfs: + properties: + monitors: + items: + type: string + type: array + x-kubernetes-list-type: atomic + path: + type: string + readOnly: + type: boolean + secretFile: + type: string + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + user: + type: string + required: + - monitors + type: object + cinder: + properties: + fsType: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + volumeID: + type: string + required: + - volumeID + type: object + configMap: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + csi: + properties: + driver: + type: string + fsType: + type: string + nodePublishSecretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + readOnly: + type: boolean + volumeAttributes: + additionalProperties: + type: string + type: object + required: + - driver + type: object + downwardAPI: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + format: int32 + type: integer + path: + type: string + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + required: + - path + type: object + type: array + x-kubernetes-list-type: atomic + type: object + emptyDir: + properties: + medium: + type: string + sizeLimit: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + ephemeral: + properties: + volumeClaimTemplate: + properties: + metadata: + type: object + spec: + properties: + accessModes: + items: + type: string + type: array + x-kubernetes-list-type: atomic + dataSource: + properties: + apiGroup: + type: string + kind: + type: string + name: + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + dataSourceRef: + properties: + apiGroup: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + required: + - kind + - name + type: object + resources: + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + selector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + type: object + type: object + x-kubernetes-map-type: atomic + storageClassName: + type: string + volumeAttributesClassName: + type: string + volumeMode: + type: string + volumeName: + type: string + type: object + required: + - spec + type: object + type: object + fc: + properties: + fsType: + type: string + lun: + format: int32 + type: integer + readOnly: + type: boolean + targetWWNs: + items: + type: string + type: array + x-kubernetes-list-type: atomic + wwids: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + flexVolume: + properties: + driver: + type: string + fsType: + type: string + options: + additionalProperties: + type: string + type: object + readOnly: + type: boolean + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + required: + - driver + type: object + flocker: + properties: + datasetName: + type: string + datasetUUID: + type: string + type: object + gcePersistentDisk: + properties: + fsType: + type: string + partition: + format: int32 + type: integer + pdName: + type: string + readOnly: + type: boolean + required: + - pdName + type: object + gitRepo: + properties: + directory: + type: string + repository: + type: string + revision: + type: string + required: + - repository + type: object + glusterfs: + properties: + endpoints: + type: string + path: + type: string + readOnly: + type: boolean + required: + - endpoints + - path + type: object + hostPath: + properties: + path: + type: string + type: + type: string + required: + - path + type: object + image: + properties: + pullPolicy: + type: string + reference: + type: string + type: object + iscsi: + properties: + chapAuthDiscovery: + type: boolean + chapAuthSession: + type: boolean + fsType: + type: string + initiatorName: + type: string + iqn: + type: string + iscsiInterface: + default: default + type: string + lun: + format: int32 + type: integer + portals: + items: + type: string + type: array + x-kubernetes-list-type: atomic + readOnly: + type: boolean + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + targetPortal: + type: string + required: + - iqn + - lun + - targetPortal + type: object + name: + type: string + nfs: + properties: + path: + type: string + readOnly: + type: boolean + server: + type: string + required: + - path + - server + type: object + persistentVolumeClaim: + properties: + claimName: + type: string + readOnly: + type: boolean + required: + - claimName + type: object + photonPersistentDisk: + properties: + fsType: + type: string + pdID: + type: string + required: + - pdID + type: object + portworxVolume: + properties: + fsType: + type: string + readOnly: + type: boolean + volumeID: + type: string + required: + - volumeID + type: object + projected: + properties: + defaultMode: + format: int32 + type: integer + sources: + items: + properties: + clusterTrustBundle: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + type: object + type: object + x-kubernetes-map-type: atomic + name: + type: string + optional: + type: boolean + path: + type: string + signerName: + type: string + required: + - path + type: object + configMap: + properties: + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + downwardAPI: + properties: + items: + items: + properties: + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + format: int32 + type: integer + path: + type: string + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + required: + - path + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podCertificate: + properties: + certificateChainPath: + type: string + credentialBundlePath: + type: string + keyPath: + type: string + keyType: + type: string + maxExpirationSeconds: + format: int32 + type: integer + signerName: + type: string + required: + - keyType + - signerName + type: object + secret: + properties: + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + serviceAccountToken: + properties: + audience: + type: string + expirationSeconds: + format: int64 + type: integer + path: + type: string + required: + - path + type: object + type: object + type: array + x-kubernetes-list-type: atomic + type: object + quobyte: + properties: + group: + type: string + readOnly: + type: boolean + registry: + type: string + tenant: + type: string + user: + type: string + volume: + type: string + required: + - registry + - volume + type: object + rbd: + properties: + fsType: + type: string + image: + type: string + keyring: + default: /etc/ceph/keyring + type: string + monitors: + items: + type: string + type: array + x-kubernetes-list-type: atomic + pool: + default: rbd + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + user: + default: admin + type: string + required: + - image + - monitors + type: object + scaleIO: + properties: + fsType: + default: xfs + type: string + gateway: + type: string + protectionDomain: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + sslEnabled: + type: boolean + storageMode: + default: ThinProvisioned + type: string + storagePool: + type: string + system: + type: string + volumeName: + type: string + required: + - gateway + - secretRef + - system + type: object + secret: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + optional: + type: boolean + secretName: + type: string + type: object + storageos: + properties: + fsType: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + volumeName: + type: string + volumeNamespace: + type: string + type: object + vsphereVolume: + properties: + fsType: + type: string + storagePolicyID: + type: string + storagePolicyName: + type: string + volumePath: + type: string + required: + - volumePath + type: object + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + required: + - containers + type: object + required: + - spec + type: object + required: + - podTemplate + type: object + status: + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/helm/kagent-crds/templates/kagent.dev_sandboxagents.yaml b/helm/kagent-crds/templates/kagent.dev_sandboxagents.yaml new file mode 100644 index 000000000..142d92c41 --- /dev/null +++ b/helm/kagent-crds/templates/kagent.dev_sandboxagents.yaml @@ -0,0 +1,8196 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + name: sandboxagents.kagent.dev +spec: + group: kagent.dev + names: + kind: SandboxAgent + listKind: SandboxAgentList + plural: sandboxagents + singular: sandboxagent + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: Whether the sandbox workload is ready. + jsonPath: .status.conditions[?(@.type=='Ready')].status + name: Ready + type: string + - description: Whether configuration was accepted. + jsonPath: .status.conditions[?(@.type=='Accepted')].status + name: Accepted + type: string + name: v1alpha2 + schema: + openAPIV3Schema: + description: SandboxAgent declares an agent that runs in an isolated sandbox + (agent-sandbox SandboxTemplate+SandboxClaim). + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: |- + SandboxAgentSpec defines the desired state of a SandboxAgent. Workload fields match Agent.spec + (Declarative or BYO). The kind selects sandbox reconciliation (SandboxTemplate + SandboxClaim) instead of a Deployment. + properties: + allowedNamespaces: + description: |- + AllowedNamespaces defines which namespaces are allowed to reference this resource. + This mechanism provides a bidirectional handshake for cross-namespace references, + following the pattern used by Gateway API for cross-namespace route attachments. + + By default (when not specified), only references from the same namespace are allowed. + properties: + from: + default: Same + description: |- + From indicates where references to this resource can originate. + Possible values are: + * All: References from all namespaces are allowed. + * Same: Only references from the same namespace are allowed (default). + * Selector: References from namespaces matching the selector are allowed. + enum: + - All + - Same + - Selector + type: string + selector: + description: |- + Selector is a label selector for namespaces that are allowed to reference this resource. + Only used when From is set to "Selector". + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + x-kubernetes-validations: + - message: selector must be specified when from is Selector + rule: '!(self.from == ''Selector'' && !has(self.selector))' + byo: + properties: + deployment: + description: Trust relationship to the agent. + properties: + affinity: + description: Affinity is a group of affinity scheduling rules. + properties: + nodeAffinity: + description: Describes node affinity scheduling rules + for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: A node selector term, associated + with the corresponding weight. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the + selector applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the + selector applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: Weight associated with matching + the corresponding nodeSelectorTerm, in the + range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: Required. A list of node selector + terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the + selector applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the + selector applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: Describes pod affinity scheduling rules (e.g. + co-locate this pod in the same node, zone, etc. as some + other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, + associated with the corresponding weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The + requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The + requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: Describes pod anti-affinity scheduling rules + (e.g. avoid putting this pod in the same node, zone, + etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, + associated with the corresponding weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The + requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The + requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + annotations: + additionalProperties: + type: string + type: object + args: + items: + type: string + type: array + cmd: + type: string + env: + items: + description: EnvVar represents an environment variable present + in a Container. + properties: + name: + description: |- + Name of the environment variable. + May consist of any printable ASCII characters except '='. + type: string + value: + description: |- + Variable references $(VAR_NAME) are expanded + using the previously defined environment variables in the container and + any service environment variables. If a variable cannot be resolved, + the reference in the input string will be unchanged. Double $$ are reduced + to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. + "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". + Escaped references will never be expanded, regardless of whether the variable + exists or not. + Defaults to "". + type: string + valueFrom: + description: Source for the environment variable's value. + Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the ConfigMap or + its key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + description: |- + Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['']`, `metadata.annotations['']`, + spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs. + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in + the specified API version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + fileKeyRef: + description: |- + FileKeyRef selects a key of the env file. + Requires the EnvFiles feature gate to be enabled. + properties: + key: + description: |- + The key within the env file. An invalid key will prevent the pod from starting. + The keys defined within a source may consist of any printable ASCII characters except '='. + During Alpha stage of the EnvFiles feature gate, the key size is limited to 128 characters. + type: string + optional: + default: false + description: |- + Specify whether the file or its key must be defined. If the file or key + does not exist, then the env var is not published. + If optional is set to true and the specified key does not exist, + the environment variable will not be set in the Pod's containers. + + If optional is set to false and the specified key does not exist, + an error will be returned during Pod creation. + type: boolean + path: + description: |- + The path within the volume from which to select the file. + Must be relative and may not contain the '..' path or start with '..'. + type: string + volumeName: + description: The name of the volume mount containing + the env file. + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported. + properties: + containerName: + description: 'Container name: required for volumes, + optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of + the exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + description: Selects a key of a secret in the pod's + namespace + properties: + key: + description: The key of the secret to select + from. Must be a valid secret key. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the Secret or its + key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + required: + - name + type: object + type: array + image: + minLength: 1 + type: string + imagePullPolicy: + description: PullPolicy describes a policy for if/when to + pull a container image + type: string + imagePullSecrets: + items: + description: |- + LocalObjectReference contains enough information to let you locate the + referenced object inside the same namespace. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + type: array + labels: + additionalProperties: + type: string + type: object + nodeSelector: + additionalProperties: + type: string + type: object + podSecurityContext: + description: |- + PodSecurityContext holds pod-level security attributes and common container settings. + Some fields are also present in container.securityContext. Field values of + container.securityContext take precedence over field values of PodSecurityContext. + properties: + appArmorProfile: + description: |- + appArmorProfile is the AppArmor options to use by the containers in this pod. + Note that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: |- + localhostProfile indicates a profile loaded on the node that should be used. + The profile must be preconfigured on the node to work. + Must match the loaded name of the profile. + Must be set if and only if type is "Localhost". + type: string + type: + description: |- + type indicates which kind of AppArmor profile will be applied. + Valid options are: + Localhost - a profile pre-loaded on the node. + RuntimeDefault - the container runtime's default profile. + Unconfined - no AppArmor enforcement. + type: string + required: + - type + type: object + fsGroup: + description: |- + A special supplemental group that applies to all containers in a pod. + Some volume types allow the Kubelet to change the ownership of that volume + to be owned by the pod: + + 1. The owning GID will be the FSGroup + 2. The setgid bit is set (new files created in the volume will be owned by FSGroup) + 3. The permission bits are OR'd with rw-rw---- + + If unset, the Kubelet will not modify the ownership and permissions of any volume. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + fsGroupChangePolicy: + description: |- + fsGroupChangePolicy defines behavior of changing ownership and permission of the volume + before being exposed inside Pod. This field will only apply to + volume types which support fsGroup based ownership(and permissions). + It will have no effect on ephemeral volume types such as: secret, configmaps + and emptydir. + Valid values are "OnRootMismatch" and "Always". If not specified, "Always" is used. + Note that this field cannot be set when spec.os.name is windows. + type: string + runAsGroup: + description: |- + The GID to run the entrypoint of the container process. + Uses runtime default if unset. + May also be set in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence + for that container. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: |- + Indicates that the container must run as a non-root user. + If true, the Kubelet will validate the image at runtime to ensure that it + does not run as UID 0 (root) and fail to start the container if it does. + If unset or false, no such validation will be performed. + May also be set in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: |- + The UID to run the entrypoint of the container process. + Defaults to user specified in image metadata if unspecified. + May also be set in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence + for that container. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + seLinuxChangePolicy: + description: |- + seLinuxChangePolicy defines how the container's SELinux label is applied to all volumes used by the Pod. + It has no effect on nodes that do not support SELinux or to volumes does not support SELinux. + Valid values are "MountOption" and "Recursive". + + "Recursive" means relabeling of all files on all Pod volumes by the container runtime. + This may be slow for large volumes, but allows mixing privileged and unprivileged Pods sharing the same volume on the same node. + + "MountOption" mounts all eligible Pod volumes with `-o context` mount option. + This requires all Pods that share the same volume to use the same SELinux label. + It is not possible to share the same volume among privileged and unprivileged Pods. + Eligible volumes are in-tree FibreChannel and iSCSI volumes, and all CSI volumes + whose CSI driver announces SELinux support by setting spec.seLinuxMount: true in their + CSIDriver instance. Other volumes are always re-labelled recursively. + "MountOption" value is allowed only when SELinuxMount feature gate is enabled. + + If not specified and SELinuxMount feature gate is enabled, "MountOption" is used. + If not specified and SELinuxMount feature gate is disabled, "MountOption" is used for ReadWriteOncePod volumes + and "Recursive" for all other volumes. + + This field affects only Pods that have SELinux label set, either in PodSecurityContext or in SecurityContext of all containers. + + All Pods that use the same volume should use the same seLinuxChangePolicy, otherwise some pods can get stuck in ContainerCreating state. + Note that this field cannot be set when spec.os.name is windows. + type: string + seLinuxOptions: + description: |- + The SELinux context to be applied to all containers. + If unspecified, the container runtime will allocate a random SELinux context for each + container. May also be set in SecurityContext. If set in + both SecurityContext and PodSecurityContext, the value specified in SecurityContext + takes precedence for that container. + Note that this field cannot be set when spec.os.name is windows. + properties: + level: + description: Level is SELinux level label that applies + to the container. + type: string + role: + description: Role is a SELinux role label that applies + to the container. + type: string + type: + description: Type is a SELinux type label that applies + to the container. + type: string + user: + description: User is a SELinux user label that applies + to the container. + type: string + type: object + seccompProfile: + description: |- + The seccomp options to use by the containers in this pod. + Note that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: |- + localhostProfile indicates a profile defined in a file on the node should be used. + The profile must be preconfigured on the node to work. + Must be a descending path, relative to the kubelet's configured seccomp profile location. + Must be set if type is "Localhost". Must NOT be set for any other type. + type: string + type: + description: |- + type indicates which kind of seccomp profile will be applied. + Valid options are: + + Localhost - a profile defined in a file on the node should be used. + RuntimeDefault - the container runtime default profile should be used. + Unconfined - no profile should be applied. + type: string + required: + - type + type: object + supplementalGroups: + description: |- + A list of groups applied to the first process run in each container, in + addition to the container's primary GID and fsGroup (if specified). If + the SupplementalGroupsPolicy feature is enabled, the + supplementalGroupsPolicy field determines whether these are in addition + to or instead of any group memberships defined in the container image. + If unspecified, no additional groups are added, though group memberships + defined in the container image may still be used, depending on the + supplementalGroupsPolicy field. + Note that this field cannot be set when spec.os.name is windows. + items: + format: int64 + type: integer + type: array + x-kubernetes-list-type: atomic + supplementalGroupsPolicy: + description: |- + Defines how supplemental groups of the first container processes are calculated. + Valid values are "Merge" and "Strict". If not specified, "Merge" is used. + (Alpha) Using the field requires the SupplementalGroupsPolicy feature gate to be enabled + and the container runtime must implement support for this feature. + Note that this field cannot be set when spec.os.name is windows. + type: string + sysctls: + description: |- + Sysctls hold a list of namespaced sysctls used for the pod. Pods with unsupported + sysctls (by the container runtime) might fail to launch. + Note that this field cannot be set when spec.os.name is windows. + items: + description: Sysctl defines a kernel parameter to be + set + properties: + name: + description: Name of a property to set + type: string + value: + description: Value of a property to set + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + windowsOptions: + description: |- + The Windows specific settings applied to all containers. + If unspecified, the options within a container's SecurityContext will be used. + If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is linux. + properties: + gmsaCredentialSpec: + description: |- + GMSACredentialSpec is where the GMSA admission webhook + (https://github.com/kubernetes-sigs/windows-gmsa) inlines the contents of the + GMSA credential spec named by the GMSACredentialSpecName field. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the name of + the GMSA credential spec to use. + type: string + hostProcess: + description: |- + HostProcess determines if a container should be run as a 'Host Process' container. + All of a Pod's containers must have the same effective HostProcess value + (it is not allowed to have a mix of HostProcess containers and non-HostProcess containers). + In addition, if HostProcess is true then HostNetwork must also be set to true. + type: boolean + runAsUserName: + description: |- + The UserName in Windows to run the entrypoint of the container process. + Defaults to the user specified in image metadata if unspecified. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + type: string + type: object + type: object + replicas: + format: int32 + type: integer + resources: + description: ResourceRequirements describes the compute resource + requirements. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + + This field depends on the + DynamicResourceAllocation feature gate. + + This field is immutable. It can only be set for containers. + items: + description: ResourceClaim references one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + securityContext: + description: |- + SecurityContext holds security configuration that will be applied to a container. + Some fields are present in both SecurityContext and PodSecurityContext. When both + are set, the values in SecurityContext take precedence. + properties: + allowPrivilegeEscalation: + description: |- + AllowPrivilegeEscalation controls whether a process can gain more + privileges than its parent process. This bool directly controls if + the no_new_privs flag will be set on the container process. + AllowPrivilegeEscalation is true always when the container is: + 1) run as Privileged + 2) has CAP_SYS_ADMIN + Note that this field cannot be set when spec.os.name is windows. + type: boolean + appArmorProfile: + description: |- + appArmorProfile is the AppArmor options to use by this container. If set, this profile + overrides the pod's appArmorProfile. + Note that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: |- + localhostProfile indicates a profile loaded on the node that should be used. + The profile must be preconfigured on the node to work. + Must match the loaded name of the profile. + Must be set if and only if type is "Localhost". + type: string + type: + description: |- + type indicates which kind of AppArmor profile will be applied. + Valid options are: + Localhost - a profile pre-loaded on the node. + RuntimeDefault - the container runtime's default profile. + Unconfined - no AppArmor enforcement. + type: string + required: + - type + type: object + capabilities: + description: |- + The capabilities to add/drop when running containers. + Defaults to the default set of capabilities granted by the container runtime. + Note that this field cannot be set when spec.os.name is windows. + properties: + add: + description: Added capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + x-kubernetes-list-type: atomic + drop: + description: Removed capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + x-kubernetes-list-type: atomic + type: object + privileged: + description: |- + Run container in privileged mode. + Processes in privileged containers are essentially equivalent to root on the host. + Defaults to false. + Note that this field cannot be set when spec.os.name is windows. + type: boolean + procMount: + description: |- + procMount denotes the type of proc mount to use for the containers. + The default value is Default which uses the container runtime defaults for + readonly paths and masked paths. + This requires the ProcMountType feature flag to be enabled. + Note that this field cannot be set when spec.os.name is windows. + type: string + readOnlyRootFilesystem: + description: |- + Whether this container has a read-only root filesystem. + Default is false. + Note that this field cannot be set when spec.os.name is windows. + type: boolean + runAsGroup: + description: |- + The GID to run the entrypoint of the container process. + Uses runtime default if unset. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: |- + Indicates that the container must run as a non-root user. + If true, the Kubelet will validate the image at runtime to ensure that it + does not run as UID 0 (root) and fail to start the container if it does. + If unset or false, no such validation will be performed. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: |- + The UID to run the entrypoint of the container process. + Defaults to user specified in image metadata if unspecified. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + seLinuxOptions: + description: |- + The SELinux context to be applied to the container. + If unspecified, the container runtime will allocate a random SELinux context for each + container. May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is windows. + properties: + level: + description: Level is SELinux level label that applies + to the container. + type: string + role: + description: Role is a SELinux role label that applies + to the container. + type: string + type: + description: Type is a SELinux type label that applies + to the container. + type: string + user: + description: User is a SELinux user label that applies + to the container. + type: string + type: object + seccompProfile: + description: |- + The seccomp options to use by this container. If seccomp options are + provided at both the pod & container level, the container options + override the pod options. + Note that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: |- + localhostProfile indicates a profile defined in a file on the node should be used. + The profile must be preconfigured on the node to work. + Must be a descending path, relative to the kubelet's configured seccomp profile location. + Must be set if type is "Localhost". Must NOT be set for any other type. + type: string + type: + description: |- + type indicates which kind of seccomp profile will be applied. + Valid options are: + + Localhost - a profile defined in a file on the node should be used. + RuntimeDefault - the container runtime default profile should be used. + Unconfined - no profile should be applied. + type: string + required: + - type + type: object + windowsOptions: + description: |- + The Windows specific settings applied to all containers. + If unspecified, the options from the PodSecurityContext will be used. + If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is linux. + properties: + gmsaCredentialSpec: + description: |- + GMSACredentialSpec is where the GMSA admission webhook + (https://github.com/kubernetes-sigs/windows-gmsa) inlines the contents of the + GMSA credential spec named by the GMSACredentialSpecName field. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the name of + the GMSA credential spec to use. + type: string + hostProcess: + description: |- + HostProcess determines if a container should be run as a 'Host Process' container. + All of a Pod's containers must have the same effective HostProcess value + (it is not allowed to have a mix of HostProcess containers and non-HostProcess containers). + In addition, if HostProcess is true then HostNetwork must also be set to true. + type: boolean + runAsUserName: + description: |- + The UserName in Windows to run the entrypoint of the container process. + Defaults to the user specified in image metadata if unspecified. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + type: string + type: object + type: object + serviceAccountConfig: + description: |- + ServiceAccountConfig configures the ServiceAccount created by the Agent controller. + This field can only be used when ServiceAccountName is not set. + If ServiceAccountName is not set, a default ServiceAccount (named after the agent) + is created, and this config will be applied to it. + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + serviceAccountName: + description: |- + ServiceAccountName specifies the name of an existing ServiceAccount to use. + If this field is set, the Agent controller will not create a ServiceAccount for the agent. + This field is mutually exclusive with ServiceAccountConfig. + type: string + tolerations: + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists, Equal, Lt, and Gt. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + Lt and Gt perform numeric comparisons (requires feature gate TaintTolerationComparisonOperators). + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + volumeMounts: + items: + description: VolumeMount describes a mounting of a Volume + within a container. + properties: + mountPath: + description: |- + Path within the container at which the volume should be mounted. Must + not contain ':'. + type: string + mountPropagation: + description: |- + mountPropagation determines how mounts are propagated from the host + to container and the other way around. + When not set, MountPropagationNone is used. + This field is beta in 1.10. + When RecursiveReadOnly is set to IfPossible or to Enabled, MountPropagation must be None or unspecified + (which defaults to None). + type: string + name: + description: This must match the Name of a Volume. + type: string + readOnly: + description: |- + Mounted read-only if true, read-write otherwise (false or unspecified). + Defaults to false. + type: boolean + recursiveReadOnly: + description: |- + RecursiveReadOnly specifies whether read-only mounts should be handled + recursively. + + If ReadOnly is false, this field has no meaning and must be unspecified. + + If ReadOnly is true, and this field is set to Disabled, the mount is not made + recursively read-only. If this field is set to IfPossible, the mount is made + recursively read-only, if it is supported by the container runtime. If this + field is set to Enabled, the mount is made recursively read-only if it is + supported by the container runtime, otherwise the pod will not be started and + an error will be generated to indicate the reason. + + If this field is set to IfPossible or Enabled, MountPropagation must be set to + None (or be unspecified, which defaults to None). + + If this field is not specified, it is treated as an equivalent of Disabled. + type: string + subPath: + description: |- + Path within the volume from which the container's volume should be mounted. + Defaults to "" (volume's root). + type: string + subPathExpr: + description: |- + Expanded path within the volume from which the container's volume should be mounted. + Behaves similarly to SubPath but environment variable references $(VAR_NAME) are expanded using the container's environment. + Defaults to "" (volume's root). + SubPathExpr and SubPath are mutually exclusive. + type: string + required: + - mountPath + - name + type: object + type: array + volumes: + items: + description: Volume represents a named volume in a pod that + may be accessed by any container in the pod. + properties: + awsElasticBlockStore: + description: |- + awsElasticBlockStore represents an AWS Disk resource that is attached to a + kubelet's host machine and then exposed to the pod. + Deprecated: AWSElasticBlockStore is deprecated. All operations for the in-tree + awsElasticBlockStore type are redirected to the ebs.csi.aws.com CSI driver. + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + properties: + fsType: + description: |- + fsType is the filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + type: string + partition: + description: |- + partition is the partition in the volume that you want to mount. + If omitted, the default is to mount by volume name. + Examples: For volume /dev/sda1, you specify the partition as "1". + Similarly, the volume partition for /dev/sda is "0" (or you can leave the property empty). + format: int32 + type: integer + readOnly: + description: |- + readOnly value true will force the readOnly setting in VolumeMounts. + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + type: boolean + volumeID: + description: |- + volumeID is unique ID of the persistent disk resource in AWS (Amazon EBS volume). + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + type: string + required: + - volumeID + type: object + azureDisk: + description: |- + azureDisk represents an Azure Data Disk mount on the host and bind mount to the pod. + Deprecated: AzureDisk is deprecated. All operations for the in-tree azureDisk type + are redirected to the disk.csi.azure.com CSI driver. + properties: + cachingMode: + description: 'cachingMode is the Host Caching mode: + None, Read Only, Read Write.' + type: string + diskName: + description: diskName is the Name of the data disk + in the blob storage + type: string + diskURI: + description: diskURI is the URI of data disk in + the blob storage + type: string + fsType: + default: ext4 + description: |- + fsType is Filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + kind: + description: 'kind expected values are Shared: multiple + blob disks per storage account Dedicated: single + blob disk per storage account Managed: azure + managed data disk (only in managed availability + set). defaults to shared' + type: string + readOnly: + default: false + description: |- + readOnly Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + required: + - diskName + - diskURI + type: object + azureFile: + description: |- + azureFile represents an Azure File Service mount on the host and bind mount to the pod. + Deprecated: AzureFile is deprecated. All operations for the in-tree azureFile type + are redirected to the file.csi.azure.com CSI driver. + properties: + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretName: + description: secretName is the name of secret that + contains Azure Storage Account Name and Key + type: string + shareName: + description: shareName is the azure share Name + type: string + required: + - secretName + - shareName + type: object + cephfs: + description: |- + cephFS represents a Ceph FS mount on the host that shares a pod's lifetime. + Deprecated: CephFS is deprecated and the in-tree cephfs type is no longer supported. + properties: + monitors: + description: |- + monitors is Required: Monitors is a collection of Ceph monitors + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + items: + type: string + type: array + x-kubernetes-list-type: atomic + path: + description: 'path is Optional: Used as the mounted + root, rather than the full Ceph tree, default + is /' + type: string + readOnly: + description: |- + readOnly is Optional: Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + type: boolean + secretFile: + description: |- + secretFile is Optional: SecretFile is the path to key ring for User, default is /etc/ceph/user.secret + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + type: string + secretRef: + description: |- + secretRef is Optional: SecretRef is reference to the authentication secret for User, default is empty. + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + user: + description: |- + user is optional: User is the rados user name, default is admin + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + type: string + required: + - monitors + type: object + cinder: + description: |- + cinder represents a cinder volume attached and mounted on kubelets host machine. + Deprecated: Cinder is deprecated. All operations for the in-tree cinder type + are redirected to the cinder.csi.openstack.org CSI driver. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + type: string + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + type: boolean + secretRef: + description: |- + secretRef is optional: points to a secret object containing parameters used to connect + to OpenStack. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + volumeID: + description: |- + volumeID used to identify the volume in cinder. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + type: string + required: + - volumeID + type: object + configMap: + description: configMap represents a configMap that should + populate this volume + properties: + defaultMode: + description: |- + defaultMode is optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + ConfigMap will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the ConfigMap, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key to a path within + a volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: optional specify whether the ConfigMap + or its keys must be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + csi: + description: csi (Container Storage Interface) represents + ephemeral storage that is handled by certain external + CSI drivers. + properties: + driver: + description: |- + driver is the name of the CSI driver that handles this volume. + Consult with your admin for the correct name as registered in the cluster. + type: string + fsType: + description: |- + fsType to mount. Ex. "ext4", "xfs", "ntfs". + If not provided, the empty value is passed to the associated CSI driver + which will determine the default filesystem to apply. + type: string + nodePublishSecretRef: + description: |- + nodePublishSecretRef is a reference to the secret object containing + sensitive information to pass to the CSI driver to complete the CSI + NodePublishVolume and NodeUnpublishVolume calls. + This field is optional, and may be empty if no secret is required. If the + secret object contains more than one secret, all secret references are passed. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + readOnly: + description: |- + readOnly specifies a read-only configuration for the volume. + Defaults to false (read/write). + type: boolean + volumeAttributes: + additionalProperties: + type: string + description: |- + volumeAttributes stores driver-specific properties that are passed to the CSI + driver. Consult your driver's documentation for supported values. + type: object + required: + - driver + type: object + downwardAPI: + description: downwardAPI represents downward API about + the pod that should populate this volume + properties: + defaultMode: + description: |- + Optional: mode bits to use on created files by default. Must be a + Optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + items: + description: Items is a list of downward API volume + file + items: + description: DownwardAPIVolumeFile represents + information to create the file containing the + pod field + properties: + fieldRef: + description: 'Required: Selects a field of + the pod: only annotations, labels, name, + namespace and uid are supported.' + properties: + apiVersion: + description: Version of the schema the + FieldPath is written in terms of, defaults + to "v1". + type: string + fieldPath: + description: Path of the field to select + in the specified API version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + description: |- + Optional: mode bits used to set permissions on this file, must be an octal value + between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: 'Required: Path is the relative + path name of the file to be created. Must + not be absolute or contain the ''..'' path. + Must be utf-8 encoded. The first item of + the relative path must not start with ''..''' + type: string + resourceFieldRef: + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, requests.cpu and requests.memory) are currently supported. + properties: + containerName: + description: 'Container name: required + for volumes, optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format + of the exposed resources, defaults to + "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + required: + - path + type: object + type: array + x-kubernetes-list-type: atomic + type: object + emptyDir: + description: |- + emptyDir represents a temporary directory that shares a pod's lifetime. + More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir + properties: + medium: + description: |- + medium represents what type of storage medium should back this directory. + The default is "" which means to use the node's default medium. + Must be an empty string (default) or Memory. + More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir + type: string + sizeLimit: + anyOf: + - type: integer + - type: string + description: |- + sizeLimit is the total amount of local storage required for this EmptyDir volume. + The size limit is also applicable for memory medium. + The maximum usage on memory medium EmptyDir would be the minimum value between + the SizeLimit specified here and the sum of memory limits of all containers in a pod. + The default is nil which means that the limit is undefined. + More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + ephemeral: + description: |- + ephemeral represents a volume that is handled by a cluster storage driver. + The volume's lifecycle is tied to the pod that defines it - it will be created before the pod starts, + and deleted when the pod is removed. + + Use this if: + a) the volume is only needed while the pod runs, + b) features of normal volumes like restoring from snapshot or capacity + tracking are needed, + c) the storage driver is specified through a storage class, and + d) the storage driver supports dynamic volume provisioning through + a PersistentVolumeClaim (see EphemeralVolumeSource for more + information on the connection between this volume type + and PersistentVolumeClaim). + + Use PersistentVolumeClaim or one of the vendor-specific + APIs for volumes that persist for longer than the lifecycle + of an individual pod. + + Use CSI for light-weight local ephemeral volumes if the CSI driver is meant to + be used that way - see the documentation of the driver for + more information. + + A pod can use both types of ephemeral volumes and + persistent volumes at the same time. + properties: + volumeClaimTemplate: + description: |- + Will be used to create a stand-alone PVC to provision the volume. + The pod in which this EphemeralVolumeSource is embedded will be the + owner of the PVC, i.e. the PVC will be deleted together with the + pod. The name of the PVC will be `-` where + `` is the name from the `PodSpec.Volumes` array + entry. Pod validation will reject the pod if the concatenated name + is not valid for a PVC (for example, too long). + + An existing PVC with that name that is not owned by the pod + will *not* be used for the pod to avoid using an unrelated + volume by mistake. Starting the pod is then blocked until + the unrelated PVC is removed. If such a pre-created PVC is + meant to be used by the pod, the PVC has to updated with an + owner reference to the pod once the pod exists. Normally + this should not be necessary, but it may be useful when + manually reconstructing a broken cluster. + + This field is read-only and no changes will be made by Kubernetes + to the PVC after it has been created. + + Required, must not be nil. + properties: + metadata: + description: |- + May contain labels and annotations that will be copied into the PVC + when creating it. No other fields are allowed and will be rejected during + validation. + type: object + spec: + description: |- + The specification for the PersistentVolumeClaim. The entire content is + copied unchanged into the PVC that gets created from this + template. The same fields as in a PersistentVolumeClaim + are also valid here. + properties: + accessModes: + description: |- + accessModes contains the desired access modes the volume should have. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1 + items: + type: string + type: array + x-kubernetes-list-type: atomic + dataSource: + description: |- + dataSource field can be used to specify either: + * An existing VolumeSnapshot object (snapshot.storage.k8s.io/VolumeSnapshot) + * An existing PVC (PersistentVolumeClaim) + If the provisioner or an external controller can support the specified data source, + it will create a new volume based on the contents of the specified data source. + When the AnyVolumeDataSource feature gate is enabled, dataSource contents will be copied to dataSourceRef, + and dataSourceRef contents will be copied to dataSource when dataSourceRef.namespace is not specified. + If the namespace is specified, then dataSourceRef will not be copied to dataSource. + properties: + apiGroup: + description: |- + APIGroup is the group for the resource being referenced. + If APIGroup is not specified, the specified Kind must be in the core API group. + For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource + being referenced + type: string + name: + description: Name is the name of resource + being referenced + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + dataSourceRef: + description: |- + dataSourceRef specifies the object from which to populate the volume with data, if a non-empty + volume is desired. This may be any object from a non-empty API group (non + core object) or a PersistentVolumeClaim object. + When this field is specified, volume binding will only succeed if the type of + the specified object matches some installed volume populator or dynamic + provisioner. + This field will replace the functionality of the dataSource field and as such + if both fields are non-empty, they must have the same value. For backwards + compatibility, when namespace isn't specified in dataSourceRef, + both fields (dataSource and dataSourceRef) will be set to the same + value automatically if one of them is empty and the other is non-empty. + When namespace is specified in dataSourceRef, + dataSource isn't set to the same value and must be empty. + There are three important differences between dataSource and dataSourceRef: + * While dataSource only allows two specific types of objects, dataSourceRef + allows any non-core object, as well as PersistentVolumeClaim objects. + * While dataSource ignores disallowed values (dropping them), dataSourceRef + preserves all values, and generates an error if a disallowed value is + specified. + * While dataSource only allows local objects, dataSourceRef allows objects + in any namespaces. + (Beta) Using this field requires the AnyVolumeDataSource feature gate to be enabled. + (Alpha) Using the namespace field of dataSourceRef requires the CrossNamespaceVolumeDataSource feature gate to be enabled. + properties: + apiGroup: + description: |- + APIGroup is the group for the resource being referenced. + If APIGroup is not specified, the specified Kind must be in the core API group. + For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource + being referenced + type: string + name: + description: Name is the name of resource + being referenced + type: string + namespace: + description: |- + Namespace is the namespace of resource being referenced + Note that when a namespace is specified, a gateway.networking.k8s.io/ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. + (Alpha) This field requires the CrossNamespaceVolumeDataSource feature gate to be enabled. + type: string + required: + - kind + - name + type: object + resources: + description: |- + resources represents the minimum resources the volume should have. + Users are allowed to specify resource requirements + that are lower than previous value but must still be higher than capacity recorded in the + status field of the claim. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + selector: + description: selector is a label query over + volumes to consider for binding. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The + requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + storageClassName: + description: |- + storageClassName is the name of the StorageClass required by the claim. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1 + type: string + volumeAttributesClassName: + description: |- + volumeAttributesClassName may be used to set the VolumeAttributesClass used by this claim. + If specified, the CSI driver will create or update the volume with the attributes defined + in the corresponding VolumeAttributesClass. This has a different purpose than storageClassName, + it can be changed after the claim is created. An empty string or nil value indicates that no + VolumeAttributesClass will be applied to the claim. If the claim enters an Infeasible error state, + this field can be reset to its previous value (including nil) to cancel the modification. + If the resource referred to by volumeAttributesClass does not exist, this PersistentVolumeClaim will be + set to a Pending state, as reflected by the modifyVolumeStatus field, until such as a resource + exists. + More info: https://kubernetes.io/docs/concepts/storage/volume-attributes-classes/ + type: string + volumeMode: + description: |- + volumeMode defines what type of volume is required by the claim. + Value of Filesystem is implied when not included in claim spec. + type: string + volumeName: + description: volumeName is the binding reference + to the PersistentVolume backing this claim. + type: string + type: object + required: + - spec + type: object + type: object + fc: + description: fc represents a Fibre Channel resource + that is attached to a kubelet's host machine and then + exposed to the pod. + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + lun: + description: 'lun is Optional: FC target lun number' + format: int32 + type: integer + readOnly: + description: |- + readOnly is Optional: Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + targetWWNs: + description: 'targetWWNs is Optional: FC target + worldwide names (WWNs)' + items: + type: string + type: array + x-kubernetes-list-type: atomic + wwids: + description: |- + wwids Optional: FC volume world wide identifiers (wwids) + Either wwids or combination of targetWWNs and lun must be set, but not both simultaneously. + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + flexVolume: + description: |- + flexVolume represents a generic volume resource that is + provisioned/attached using an exec based plugin. + Deprecated: FlexVolume is deprecated. Consider using a CSIDriver instead. + properties: + driver: + description: driver is the name of the driver to + use for this volume. + type: string + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". The default filesystem depends on FlexVolume script. + type: string + options: + additionalProperties: + type: string + description: 'options is Optional: this field holds + extra command options if any.' + type: object + readOnly: + description: |- + readOnly is Optional: defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: |- + secretRef is Optional: secretRef is reference to the secret object containing + sensitive information to pass to the plugin scripts. This may be + empty if no secret object is specified. If the secret object + contains more than one secret, all secrets are passed to the plugin + scripts. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + required: + - driver + type: object + flocker: + description: |- + flocker represents a Flocker volume attached to a kubelet's host machine. This depends on the Flocker control service being running. + Deprecated: Flocker is deprecated and the in-tree flocker type is no longer supported. + properties: + datasetName: + description: |- + datasetName is Name of the dataset stored as metadata -> name on the dataset for Flocker + should be considered as deprecated + type: string + datasetUUID: + description: datasetUUID is the UUID of the dataset. + This is unique identifier of a Flocker dataset + type: string + type: object + gcePersistentDisk: + description: |- + gcePersistentDisk represents a GCE Disk resource that is attached to a + kubelet's host machine and then exposed to the pod. + Deprecated: GCEPersistentDisk is deprecated. All operations for the in-tree + gcePersistentDisk type are redirected to the pd.csi.storage.gke.io CSI driver. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + properties: + fsType: + description: |- + fsType is filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + type: string + partition: + description: |- + partition is the partition in the volume that you want to mount. + If omitted, the default is to mount by volume name. + Examples: For volume /dev/sda1, you specify the partition as "1". + Similarly, the volume partition for /dev/sda is "0" (or you can leave the property empty). + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + format: int32 + type: integer + pdName: + description: |- + pdName is unique name of the PD resource in GCE. Used to identify the disk in GCE. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + type: string + readOnly: + description: |- + readOnly here will force the ReadOnly setting in VolumeMounts. + Defaults to false. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + type: boolean + required: + - pdName + type: object + gitRepo: + description: |- + gitRepo represents a git repository at a particular revision. + Deprecated: GitRepo is deprecated. To provision a container with a git repo, mount an + EmptyDir into an InitContainer that clones the repo using git, then mount the EmptyDir + into the Pod's container. + properties: + directory: + description: |- + directory is the target directory name. + Must not contain or start with '..'. If '.' is supplied, the volume directory will be the + git repository. Otherwise, if specified, the volume will contain the git repository in + the subdirectory with the given name. + type: string + repository: + description: repository is the URL + type: string + revision: + description: revision is the commit hash for the + specified revision. + type: string + required: + - repository + type: object + glusterfs: + description: |- + glusterfs represents a Glusterfs mount on the host that shares a pod's lifetime. + Deprecated: Glusterfs is deprecated and the in-tree glusterfs type is no longer supported. + properties: + endpoints: + description: endpoints is the endpoint name that + details Glusterfs topology. + type: string + path: + description: |- + path is the Glusterfs volume path. + More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod + type: string + readOnly: + description: |- + readOnly here will force the Glusterfs volume to be mounted with read-only permissions. + Defaults to false. + More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod + type: boolean + required: + - endpoints + - path + type: object + hostPath: + description: |- + hostPath represents a pre-existing file or directory on the host + machine that is directly exposed to the container. This is generally + used for system agents or other privileged things that are allowed + to see the host machine. Most containers will NOT need this. + More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + properties: + path: + description: |- + path of the directory on the host. + If the path is a symlink, it will follow the link to the real path. + More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + type: string + type: + description: |- + type for HostPath Volume + Defaults to "" + More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + type: string + required: + - path + type: object + image: + description: |- + image represents an OCI object (a container image or artifact) pulled and mounted on the kubelet's host machine. + The volume is resolved at pod startup depending on which PullPolicy value is provided: + + - Always: the kubelet always attempts to pull the reference. Container creation will fail If the pull fails. + - Never: the kubelet never pulls the reference and only uses a local image or artifact. Container creation will fail if the reference isn't present. + - IfNotPresent: the kubelet pulls if the reference isn't already present on disk. Container creation will fail if the reference isn't present and the pull fails. + + The volume gets re-resolved if the pod gets deleted and recreated, which means that new remote content will become available on pod recreation. + A failure to resolve or pull the image during pod startup will block containers from starting and may add significant latency. Failures will be retried using normal volume backoff and will be reported on the pod reason and message. + The types of objects that may be mounted by this volume are defined by the container runtime implementation on a host machine and at minimum must include all valid types supported by the container image field. + The OCI object gets mounted in a single directory (spec.containers[*].volumeMounts.mountPath) by merging the manifest layers in the same way as for container images. + The volume will be mounted read-only (ro) and non-executable files (noexec). + Sub path mounts for containers are not supported (spec.containers[*].volumeMounts.subpath) before 1.33. + The field spec.securityContext.fsGroupChangePolicy has no effect on this volume type. + properties: + pullPolicy: + description: |- + Policy for pulling OCI objects. Possible values are: + Always: the kubelet always attempts to pull the reference. Container creation will fail If the pull fails. + Never: the kubelet never pulls the reference and only uses a local image or artifact. Container creation will fail if the reference isn't present. + IfNotPresent: the kubelet pulls if the reference isn't already present on disk. Container creation will fail if the reference isn't present and the pull fails. + Defaults to Always if :latest tag is specified, or IfNotPresent otherwise. + type: string + reference: + description: |- + Required: Image or artifact reference to be used. + Behaves in the same way as pod.spec.containers[*].image. + Pull secrets will be assembled in the same way as for the container image by looking up node credentials, SA image pull secrets, and pod spec image pull secrets. + More info: https://kubernetes.io/docs/concepts/containers/images + This field is optional to allow higher level config management to default or override + container images in workload controllers like Deployments and StatefulSets. + type: string + type: object + iscsi: + description: |- + iscsi represents an ISCSI Disk resource that is attached to a + kubelet's host machine and then exposed to the pod. + More info: https://kubernetes.io/docs/concepts/storage/volumes/#iscsi + properties: + chapAuthDiscovery: + description: chapAuthDiscovery defines whether support + iSCSI Discovery CHAP authentication + type: boolean + chapAuthSession: + description: chapAuthSession defines whether support + iSCSI Session CHAP authentication + type: boolean + fsType: + description: |- + fsType is the filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#iscsi + type: string + initiatorName: + description: |- + initiatorName is the custom iSCSI Initiator Name. + If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface + : will be created for the connection. + type: string + iqn: + description: iqn is the target iSCSI Qualified Name. + type: string + iscsiInterface: + default: default + description: |- + iscsiInterface is the interface Name that uses an iSCSI transport. + Defaults to 'default' (tcp). + type: string + lun: + description: lun represents iSCSI Target Lun number. + format: int32 + type: integer + portals: + description: |- + portals is the iSCSI Target Portal List. The portal is either an IP or ip_addr:port if the port + is other than default (typically TCP ports 860 and 3260). + items: + type: string + type: array + x-kubernetes-list-type: atomic + readOnly: + description: |- + readOnly here will force the ReadOnly setting in VolumeMounts. + Defaults to false. + type: boolean + secretRef: + description: secretRef is the CHAP Secret for iSCSI + target and initiator authentication + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + targetPortal: + description: |- + targetPortal is iSCSI Target Portal. The Portal is either an IP or ip_addr:port if the port + is other than default (typically TCP ports 860 and 3260). + type: string + required: + - iqn + - lun + - targetPortal + type: object + name: + description: |- + name of the volume. + Must be a DNS_LABEL and unique within the pod. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + nfs: + description: |- + nfs represents an NFS mount on the host that shares a pod's lifetime + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + properties: + path: + description: |- + path that is exported by the NFS server. + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + type: string + readOnly: + description: |- + readOnly here will force the NFS export to be mounted with read-only permissions. + Defaults to false. + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + type: boolean + server: + description: |- + server is the hostname or IP address of the NFS server. + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + type: string + required: + - path + - server + type: object + persistentVolumeClaim: + description: |- + persistentVolumeClaimVolumeSource represents a reference to a + PersistentVolumeClaim in the same namespace. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims + properties: + claimName: + description: |- + claimName is the name of a PersistentVolumeClaim in the same namespace as the pod using this volume. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims + type: string + readOnly: + description: |- + readOnly Will force the ReadOnly setting in VolumeMounts. + Default false. + type: boolean + required: + - claimName + type: object + photonPersistentDisk: + description: |- + photonPersistentDisk represents a PhotonController persistent disk attached and mounted on kubelets host machine. + Deprecated: PhotonPersistentDisk is deprecated and the in-tree photonPersistentDisk type is no longer supported. + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + pdID: + description: pdID is the ID that identifies Photon + Controller persistent disk + type: string + required: + - pdID + type: object + portworxVolume: + description: |- + portworxVolume represents a portworx volume attached and mounted on kubelets host machine. + Deprecated: PortworxVolume is deprecated. All operations for the in-tree portworxVolume type + are redirected to the pxd.portworx.com CSI driver when the CSIMigrationPortworx feature-gate + is on. + properties: + fsType: + description: |- + fSType represents the filesystem type to mount + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs". Implicitly inferred to be "ext4" if unspecified. + type: string + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + volumeID: + description: volumeID uniquely identifies a Portworx + volume + type: string + required: + - volumeID + type: object + projected: + description: projected items for all in one resources + secrets, configmaps, and downward API + properties: + defaultMode: + description: |- + defaultMode are the mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + sources: + description: |- + sources is the list of volume projections. Each entry in this list + handles one source. + items: + description: |- + Projection that may be projected along with other supported volume types. + Exactly one of these fields must be set. + properties: + clusterTrustBundle: + description: |- + ClusterTrustBundle allows a pod to access the `.spec.trustBundle` field + of ClusterTrustBundle objects in an auto-updating file. + + Alpha, gated by the ClusterTrustBundleProjection feature gate. + + ClusterTrustBundle objects can either be selected by name, or by the + combination of signer name and a label selector. + + Kubelet performs aggressive normalization of the PEM contents written + into the pod filesystem. Esoteric PEM features such as inter-block + comments and block headers are stripped. Certificates are deduplicated. + The ordering of certificates within the file is arbitrary, and Kubelet + may change the order over time. + properties: + labelSelector: + description: |- + Select all ClusterTrustBundles that match this label selector. Only has + effect if signerName is set. Mutually-exclusive with name. If unset, + interpreted as "match nothing". If set but empty, interpreted as "match + everything". + properties: + matchExpressions: + description: matchExpressions is a + list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + name: + description: |- + Select a single ClusterTrustBundle by object name. Mutually-exclusive + with signerName and labelSelector. + type: string + optional: + description: |- + If true, don't block pod startup if the referenced ClusterTrustBundle(s) + aren't available. If using name, then the named ClusterTrustBundle is + allowed not to exist. If using signerName, then the combination of + signerName and labelSelector is allowed to match zero + ClusterTrustBundles. + type: boolean + path: + description: Relative path from the volume + root to write the bundle. + type: string + signerName: + description: |- + Select all ClusterTrustBundles that match this signer name. + Mutually-exclusive with name. The contents of all selected + ClusterTrustBundles will be unified and deduplicated. + type: string + required: + - path + type: object + configMap: + description: configMap information about the + configMap data to project + properties: + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + ConfigMap will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the ConfigMap, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key to a + path within a volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: optional specify whether + the ConfigMap or its keys must be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + downwardAPI: + description: downwardAPI information about + the downwardAPI data to project + properties: + items: + description: Items is a list of DownwardAPIVolume + file + items: + description: DownwardAPIVolumeFile represents + information to create the file containing + the pod field + properties: + fieldRef: + description: 'Required: Selects + a field of the pod: only annotations, + labels, name, namespace and uid + are supported.' + properties: + apiVersion: + description: Version of the + schema the FieldPath is written + in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field + to select in the specified + API version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + description: |- + Optional: mode bits used to set permissions on this file, must be an octal value + between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: 'Required: Path is the + relative path name of the file + to be created. Must not be absolute + or contain the ''..'' path. Must + be utf-8 encoded. The first item + of the relative path must not + start with ''..''' + type: string + resourceFieldRef: + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, requests.cpu and requests.memory) are currently supported. + properties: + containerName: + description: 'Container name: + required for volumes, optional + for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output + format of the exposed resources, + defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource + to select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + required: + - path + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podCertificate: + description: |- + Projects an auto-rotating credential bundle (private key and certificate + chain) that the pod can use either as a TLS client or server. + + Kubelet generates a private key and uses it to send a + PodCertificateRequest to the named signer. Once the signer approves the + request and issues a certificate chain, Kubelet writes the key and + certificate chain to the pod filesystem. The pod does not start until + certificates have been issued for each podCertificate projected volume + source in its spec. + + Kubelet will begin trying to rotate the certificate at the time indicated + by the signer using the PodCertificateRequest.Status.BeginRefreshAt + timestamp. + + Kubelet can write a single file, indicated by the credentialBundlePath + field, or separate files, indicated by the keyPath and + certificateChainPath fields. + + The credential bundle is a single file in PEM format. The first PEM + entry is the private key (in PKCS#8 format), and the remaining PEM + entries are the certificate chain issued by the signer (typically, + signers will return their certificate chain in leaf-to-root order). + + Prefer using the credential bundle format, since your application code + can read it atomically. If you use keyPath and certificateChainPath, + your application must make two separate file reads. If these coincide + with a certificate rotation, it is possible that the private key and leaf + certificate you read may not correspond to each other. Your application + will need to check for this condition, and re-read until they are + consistent. + + The named signer controls chooses the format of the certificate it + issues; consult the signer implementation's documentation to learn how to + use the certificates it issues. + properties: + certificateChainPath: + description: |- + Write the certificate chain at this path in the projected volume. + + Most applications should use credentialBundlePath. When using keyPath + and certificateChainPath, your application needs to check that the key + and leaf certificate are consistent, because it is possible to read the + files mid-rotation. + type: string + credentialBundlePath: + description: |- + Write the credential bundle at this path in the projected volume. + + The credential bundle is a single file that contains multiple PEM blocks. + The first PEM block is a PRIVATE KEY block, containing a PKCS#8 private + key. + + The remaining blocks are CERTIFICATE blocks, containing the issued + certificate chain from the signer (leaf and any intermediates). + + Using credentialBundlePath lets your Pod's application code make a single + atomic read that retrieves a consistent key and certificate chain. If you + project them to separate files, your application code will need to + additionally check that the leaf certificate was issued to the key. + type: string + keyPath: + description: |- + Write the key at this path in the projected volume. + + Most applications should use credentialBundlePath. When using keyPath + and certificateChainPath, your application needs to check that the key + and leaf certificate are consistent, because it is possible to read the + files mid-rotation. + type: string + keyType: + description: |- + The type of keypair Kubelet will generate for the pod. + + Valid values are "RSA3072", "RSA4096", "ECDSAP256", "ECDSAP384", + "ECDSAP521", and "ED25519". + type: string + maxExpirationSeconds: + description: |- + maxExpirationSeconds is the maximum lifetime permitted for the + certificate. + + Kubelet copies this value verbatim into the PodCertificateRequests it + generates for this projection. + + If omitted, kube-apiserver will set it to 86400(24 hours). kube-apiserver + will reject values shorter than 3600 (1 hour). The maximum allowable + value is 7862400 (91 days). + + The signer implementation is then free to issue a certificate with any + lifetime *shorter* than MaxExpirationSeconds, but no shorter than 3600 + seconds (1 hour). This constraint is enforced by kube-apiserver. + `kubernetes.io` signers will never issue certificates with a lifetime + longer than 24 hours. + format: int32 + type: integer + signerName: + description: Kubelet's generated CSRs + will be addressed to this signer. + type: string + userAnnotations: + additionalProperties: + type: string + description: |- + userAnnotations allow pod authors to pass additional information to + the signer implementation. Kubernetes does not restrict or validate this + metadata in any way. + + These values are copied verbatim into the `spec.unverifiedUserAnnotations` field of + the PodCertificateRequest objects that Kubelet creates. + + Entries are subject to the same validation as object metadata annotations, + with the addition that all keys must be domain-prefixed. No restrictions + are placed on values, except an overall size limitation on the entire field. + + Signers should document the keys and values they support. Signers should + deny requests that contain keys they do not recognize. + type: object + required: + - keyType + - signerName + type: object + secret: + description: secret information about the + secret data to project + properties: + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + Secret will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the Secret, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key to a + path within a volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: optional field specify whether + the Secret or its key must be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + serviceAccountToken: + description: serviceAccountToken is information + about the serviceAccountToken data to project + properties: + audience: + description: |- + audience is the intended audience of the token. A recipient of a token + must identify itself with an identifier specified in the audience of the + token, and otherwise should reject the token. The audience defaults to the + identifier of the apiserver. + type: string + expirationSeconds: + description: |- + expirationSeconds is the requested duration of validity of the service + account token. As the token approaches expiration, the kubelet volume + plugin will proactively rotate the service account token. The kubelet will + start trying to rotate the token if the token is older than 80 percent of + its time to live or if the token is older than 24 hours.Defaults to 1 hour + and must be at least 10 minutes. + format: int64 + type: integer + path: + description: |- + path is the path relative to the mount point of the file to project the + token into. + type: string + required: + - path + type: object + type: object + type: array + x-kubernetes-list-type: atomic + type: object + quobyte: + description: |- + quobyte represents a Quobyte mount on the host that shares a pod's lifetime. + Deprecated: Quobyte is deprecated and the in-tree quobyte type is no longer supported. + properties: + group: + description: |- + group to map volume access to + Default is no group + type: string + readOnly: + description: |- + readOnly here will force the Quobyte volume to be mounted with read-only permissions. + Defaults to false. + type: boolean + registry: + description: |- + registry represents a single or multiple Quobyte Registry services + specified as a string as host:port pair (multiple entries are separated with commas) + which acts as the central registry for volumes + type: string + tenant: + description: |- + tenant owning the given Quobyte volume in the Backend + Used with dynamically provisioned Quobyte volumes, value is set by the plugin + type: string + user: + description: |- + user to map volume access to + Defaults to serivceaccount user + type: string + volume: + description: volume is a string that references + an already created Quobyte volume by name. + type: string + required: + - registry + - volume + type: object + rbd: + description: |- + rbd represents a Rados Block Device mount on the host that shares a pod's lifetime. + Deprecated: RBD is deprecated and the in-tree rbd type is no longer supported. + properties: + fsType: + description: |- + fsType is the filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#rbd + type: string + image: + description: |- + image is the rados image name. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + keyring: + default: /etc/ceph/keyring + description: |- + keyring is the path to key ring for RBDUser. + Default is /etc/ceph/keyring. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + monitors: + description: |- + monitors is a collection of Ceph monitors. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + items: + type: string + type: array + x-kubernetes-list-type: atomic + pool: + default: rbd + description: |- + pool is the rados pool name. + Default is rbd. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + readOnly: + description: |- + readOnly here will force the ReadOnly setting in VolumeMounts. + Defaults to false. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: boolean + secretRef: + description: |- + secretRef is name of the authentication secret for RBDUser. If provided + overrides keyring. + Default is nil. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + user: + default: admin + description: |- + user is the rados user name. + Default is admin. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + required: + - image + - monitors + type: object + scaleIO: + description: |- + scaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes. + Deprecated: ScaleIO is deprecated and the in-tree scaleIO type is no longer supported. + properties: + fsType: + default: xfs + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". + Default is "xfs". + type: string + gateway: + description: gateway is the host address of the + ScaleIO API Gateway. + type: string + protectionDomain: + description: protectionDomain is the name of the + ScaleIO Protection Domain for the configured storage. + type: string + readOnly: + description: |- + readOnly Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: |- + secretRef references to the secret for ScaleIO user and other + sensitive information. If this is not provided, Login operation will fail. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + sslEnabled: + description: sslEnabled Flag enable/disable SSL + communication with Gateway, default false + type: boolean + storageMode: + default: ThinProvisioned + description: |- + storageMode indicates whether the storage for a volume should be ThickProvisioned or ThinProvisioned. + Default is ThinProvisioned. + type: string + storagePool: + description: storagePool is the ScaleIO Storage + Pool associated with the protection domain. + type: string + system: + description: system is the name of the storage system + as configured in ScaleIO. + type: string + volumeName: + description: |- + volumeName is the name of a volume already created in the ScaleIO system + that is associated with this volume source. + type: string + required: + - gateway + - secretRef + - system + type: object + secret: + description: |- + secret represents a secret that should populate this volume. + More info: https://kubernetes.io/docs/concepts/storage/volumes#secret + properties: + defaultMode: + description: |- + defaultMode is Optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values + for mode bits. Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + items: + description: |- + items If unspecified, each key-value pair in the Data field of the referenced + Secret will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the Secret, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key to a path within + a volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + optional: + description: optional field specify whether the + Secret or its keys must be defined + type: boolean + secretName: + description: |- + secretName is the name of the secret in the pod's namespace to use. + More info: https://kubernetes.io/docs/concepts/storage/volumes#secret + type: string + type: object + storageos: + description: |- + storageOS represents a StorageOS volume attached and mounted on Kubernetes nodes. + Deprecated: StorageOS is deprecated and the in-tree storageos type is no longer supported. + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: |- + secretRef specifies the secret to use for obtaining the StorageOS API + credentials. If not specified, default values will be attempted. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + volumeName: + description: |- + volumeName is the human-readable name of the StorageOS volume. Volume + names are only unique within a namespace. + type: string + volumeNamespace: + description: |- + volumeNamespace specifies the scope of the volume within StorageOS. If no + namespace is specified then the Pod's namespace will be used. This allows the + Kubernetes name scoping to be mirrored within StorageOS for tighter integration. + Set VolumeName to any name to override the default behaviour. + Set to "default" if you are not using namespaces within StorageOS. + Namespaces that do not pre-exist within StorageOS will be created. + type: string + type: object + vsphereVolume: + description: |- + vsphereVolume represents a vSphere volume attached and mounted on kubelets host machine. + Deprecated: VsphereVolume is deprecated. All operations for the in-tree vsphereVolume type + are redirected to the csi.vsphere.vmware.com CSI driver. + properties: + fsType: + description: |- + fsType is filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + storagePolicyID: + description: storagePolicyID is the storage Policy + Based Management (SPBM) profile ID associated + with the StoragePolicyName. + type: string + storagePolicyName: + description: storagePolicyName is the storage Policy + Based Management (SPBM) profile name. + type: string + volumePath: + description: volumePath is the path that identifies + vSphere volume vmdk + type: string + required: + - volumePath + type: object + required: + - name + type: object + type: array + type: object + x-kubernetes-validations: + - message: serviceAccountName and serviceAccountConfig are mutually + exclusive + rule: '!(has(self.serviceAccountName) && has(self.serviceAccountConfig))' + type: object + declarative: + properties: + a2aConfig: + description: |- + A2AConfig instantiates an A2A server for this agent, + served on the HTTP port of the kagent kubernetes + controller (default 8083). + The A2A server URL will be served at + :8083/api/a2a// + Read more about the A2A protocol here: https://github.com/google/A2A + properties: + skills: + items: + description: AgentSkill describes a specific capability + or function of the agent. + properties: + description: + description: Description is an optional detailed description + of the skill. + type: string + examples: + description: Examples are optional usage examples. + items: + type: string + type: array + id: + description: ID is the unique identifier for the skill. + type: string + inputModes: + description: InputModes are the supported input data + modes/types. + items: + type: string + type: array + name: + description: Name is the human-readable name of the + skill. + type: string + outputModes: + description: OutputModes are the supported output data + modes/types. + items: + type: string + type: array + tags: + description: Tags are optional tags for categorization. + items: + type: string + type: array + required: + - id + - name + - tags + type: object + minItems: 1 + type: array + type: object + context: + description: |- + Context configures context management for this agent. + This includes event compaction (compression) and context caching. + properties: + compaction: + description: |- + Compaction configures event history compaction. + When enabled, older events in the conversation are compacted (compressed/summarized) + to reduce context size while preserving key information. + properties: + compactionInterval: + default: 5 + description: The number of *new* user-initiated invocations + that, once fully represented in the session's events, + will trigger a compaction. + minimum: 1 + type: integer + eventRetentionSize: + description: EventRetentionSize is the number of most + recent events to always retain. + type: integer + overlapSize: + default: 2 + description: The number of preceding invocations to include + from the end of the last compacted range. This creates + an overlap between consecutive compacted summaries, + maintaining context. + minimum: 0 + type: integer + summarizer: + description: |- + Summarizer configures an LLM-based summarizer for event compaction. + If not specified, compacted events are dropped from the context without summarization. + properties: + modelConfig: + description: |- + ModelConfig is the name of a ModelConfig resource to use for summarization. + Must be in the same namespace as the Agent. + If not specified, uses the agent's own model. + type: string + promptTemplate: + description: |- + PromptTemplate is a custom prompt template for the summarizer. + See the ADK LlmEventSummarizer for template details: + https://github.com/google/adk-python/blob/main/src/google/adk/apps/llm_event_summarizer.py + type: string + type: object + tokenThreshold: + description: |- + Post-invocation token threshold trigger. If set, ADK will attempt a post-invocation compaction when the most recently + observed prompt token count meets or exceeds this threshold. + type: integer + type: object + type: object + deployment: + properties: + affinity: + description: Affinity is a group of affinity scheduling rules. + properties: + nodeAffinity: + description: Describes node affinity scheduling rules + for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: A node selector term, associated + with the corresponding weight. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the + selector applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the + selector applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: Weight associated with matching + the corresponding nodeSelectorTerm, in the + range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: Required. A list of node selector + terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the + selector applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the + selector applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: Describes pod affinity scheduling rules (e.g. + co-locate this pod in the same node, zone, etc. as some + other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, + associated with the corresponding weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The + requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The + requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: Describes pod anti-affinity scheduling rules + (e.g. avoid putting this pod in the same node, zone, + etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, + associated with the corresponding weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The + requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The + requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + annotations: + additionalProperties: + type: string + type: object + env: + items: + description: EnvVar represents an environment variable present + in a Container. + properties: + name: + description: |- + Name of the environment variable. + May consist of any printable ASCII characters except '='. + type: string + value: + description: |- + Variable references $(VAR_NAME) are expanded + using the previously defined environment variables in the container and + any service environment variables. If a variable cannot be resolved, + the reference in the input string will be unchanged. Double $$ are reduced + to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. + "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". + Escaped references will never be expanded, regardless of whether the variable + exists or not. + Defaults to "". + type: string + valueFrom: + description: Source for the environment variable's value. + Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the ConfigMap or + its key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + description: |- + Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['']`, `metadata.annotations['']`, + spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs. + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in + the specified API version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + fileKeyRef: + description: |- + FileKeyRef selects a key of the env file. + Requires the EnvFiles feature gate to be enabled. + properties: + key: + description: |- + The key within the env file. An invalid key will prevent the pod from starting. + The keys defined within a source may consist of any printable ASCII characters except '='. + During Alpha stage of the EnvFiles feature gate, the key size is limited to 128 characters. + type: string + optional: + default: false + description: |- + Specify whether the file or its key must be defined. If the file or key + does not exist, then the env var is not published. + If optional is set to true and the specified key does not exist, + the environment variable will not be set in the Pod's containers. + + If optional is set to false and the specified key does not exist, + an error will be returned during Pod creation. + type: boolean + path: + description: |- + The path within the volume from which to select the file. + Must be relative and may not contain the '..' path or start with '..'. + type: string + volumeName: + description: The name of the volume mount containing + the env file. + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported. + properties: + containerName: + description: 'Container name: required for volumes, + optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of + the exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + description: Selects a key of a secret in the pod's + namespace + properties: + key: + description: The key of the secret to select + from. Must be a valid secret key. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the Secret or its + key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + required: + - name + type: object + type: array + imagePullPolicy: + description: PullPolicy describes a policy for if/when to + pull a container image + type: string + imagePullSecrets: + items: + description: |- + LocalObjectReference contains enough information to let you locate the + referenced object inside the same namespace. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + type: array + imageRegistry: + type: string + labels: + additionalProperties: + type: string + type: object + nodeSelector: + additionalProperties: + type: string + type: object + podSecurityContext: + description: |- + PodSecurityContext holds pod-level security attributes and common container settings. + Some fields are also present in container.securityContext. Field values of + container.securityContext take precedence over field values of PodSecurityContext. + properties: + appArmorProfile: + description: |- + appArmorProfile is the AppArmor options to use by the containers in this pod. + Note that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: |- + localhostProfile indicates a profile loaded on the node that should be used. + The profile must be preconfigured on the node to work. + Must match the loaded name of the profile. + Must be set if and only if type is "Localhost". + type: string + type: + description: |- + type indicates which kind of AppArmor profile will be applied. + Valid options are: + Localhost - a profile pre-loaded on the node. + RuntimeDefault - the container runtime's default profile. + Unconfined - no AppArmor enforcement. + type: string + required: + - type + type: object + fsGroup: + description: |- + A special supplemental group that applies to all containers in a pod. + Some volume types allow the Kubelet to change the ownership of that volume + to be owned by the pod: + + 1. The owning GID will be the FSGroup + 2. The setgid bit is set (new files created in the volume will be owned by FSGroup) + 3. The permission bits are OR'd with rw-rw---- + + If unset, the Kubelet will not modify the ownership and permissions of any volume. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + fsGroupChangePolicy: + description: |- + fsGroupChangePolicy defines behavior of changing ownership and permission of the volume + before being exposed inside Pod. This field will only apply to + volume types which support fsGroup based ownership(and permissions). + It will have no effect on ephemeral volume types such as: secret, configmaps + and emptydir. + Valid values are "OnRootMismatch" and "Always". If not specified, "Always" is used. + Note that this field cannot be set when spec.os.name is windows. + type: string + runAsGroup: + description: |- + The GID to run the entrypoint of the container process. + Uses runtime default if unset. + May also be set in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence + for that container. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: |- + Indicates that the container must run as a non-root user. + If true, the Kubelet will validate the image at runtime to ensure that it + does not run as UID 0 (root) and fail to start the container if it does. + If unset or false, no such validation will be performed. + May also be set in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: |- + The UID to run the entrypoint of the container process. + Defaults to user specified in image metadata if unspecified. + May also be set in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence + for that container. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + seLinuxChangePolicy: + description: |- + seLinuxChangePolicy defines how the container's SELinux label is applied to all volumes used by the Pod. + It has no effect on nodes that do not support SELinux or to volumes does not support SELinux. + Valid values are "MountOption" and "Recursive". + + "Recursive" means relabeling of all files on all Pod volumes by the container runtime. + This may be slow for large volumes, but allows mixing privileged and unprivileged Pods sharing the same volume on the same node. + + "MountOption" mounts all eligible Pod volumes with `-o context` mount option. + This requires all Pods that share the same volume to use the same SELinux label. + It is not possible to share the same volume among privileged and unprivileged Pods. + Eligible volumes are in-tree FibreChannel and iSCSI volumes, and all CSI volumes + whose CSI driver announces SELinux support by setting spec.seLinuxMount: true in their + CSIDriver instance. Other volumes are always re-labelled recursively. + "MountOption" value is allowed only when SELinuxMount feature gate is enabled. + + If not specified and SELinuxMount feature gate is enabled, "MountOption" is used. + If not specified and SELinuxMount feature gate is disabled, "MountOption" is used for ReadWriteOncePod volumes + and "Recursive" for all other volumes. + + This field affects only Pods that have SELinux label set, either in PodSecurityContext or in SecurityContext of all containers. + + All Pods that use the same volume should use the same seLinuxChangePolicy, otherwise some pods can get stuck in ContainerCreating state. + Note that this field cannot be set when spec.os.name is windows. + type: string + seLinuxOptions: + description: |- + The SELinux context to be applied to all containers. + If unspecified, the container runtime will allocate a random SELinux context for each + container. May also be set in SecurityContext. If set in + both SecurityContext and PodSecurityContext, the value specified in SecurityContext + takes precedence for that container. + Note that this field cannot be set when spec.os.name is windows. + properties: + level: + description: Level is SELinux level label that applies + to the container. + type: string + role: + description: Role is a SELinux role label that applies + to the container. + type: string + type: + description: Type is a SELinux type label that applies + to the container. + type: string + user: + description: User is a SELinux user label that applies + to the container. + type: string + type: object + seccompProfile: + description: |- + The seccomp options to use by the containers in this pod. + Note that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: |- + localhostProfile indicates a profile defined in a file on the node should be used. + The profile must be preconfigured on the node to work. + Must be a descending path, relative to the kubelet's configured seccomp profile location. + Must be set if type is "Localhost". Must NOT be set for any other type. + type: string + type: + description: |- + type indicates which kind of seccomp profile will be applied. + Valid options are: + + Localhost - a profile defined in a file on the node should be used. + RuntimeDefault - the container runtime default profile should be used. + Unconfined - no profile should be applied. + type: string + required: + - type + type: object + supplementalGroups: + description: |- + A list of groups applied to the first process run in each container, in + addition to the container's primary GID and fsGroup (if specified). If + the SupplementalGroupsPolicy feature is enabled, the + supplementalGroupsPolicy field determines whether these are in addition + to or instead of any group memberships defined in the container image. + If unspecified, no additional groups are added, though group memberships + defined in the container image may still be used, depending on the + supplementalGroupsPolicy field. + Note that this field cannot be set when spec.os.name is windows. + items: + format: int64 + type: integer + type: array + x-kubernetes-list-type: atomic + supplementalGroupsPolicy: + description: |- + Defines how supplemental groups of the first container processes are calculated. + Valid values are "Merge" and "Strict". If not specified, "Merge" is used. + (Alpha) Using the field requires the SupplementalGroupsPolicy feature gate to be enabled + and the container runtime must implement support for this feature. + Note that this field cannot be set when spec.os.name is windows. + type: string + sysctls: + description: |- + Sysctls hold a list of namespaced sysctls used for the pod. Pods with unsupported + sysctls (by the container runtime) might fail to launch. + Note that this field cannot be set when spec.os.name is windows. + items: + description: Sysctl defines a kernel parameter to be + set + properties: + name: + description: Name of a property to set + type: string + value: + description: Value of a property to set + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + windowsOptions: + description: |- + The Windows specific settings applied to all containers. + If unspecified, the options within a container's SecurityContext will be used. + If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is linux. + properties: + gmsaCredentialSpec: + description: |- + GMSACredentialSpec is where the GMSA admission webhook + (https://github.com/kubernetes-sigs/windows-gmsa) inlines the contents of the + GMSA credential spec named by the GMSACredentialSpecName field. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the name of + the GMSA credential spec to use. + type: string + hostProcess: + description: |- + HostProcess determines if a container should be run as a 'Host Process' container. + All of a Pod's containers must have the same effective HostProcess value + (it is not allowed to have a mix of HostProcess containers and non-HostProcess containers). + In addition, if HostProcess is true then HostNetwork must also be set to true. + type: boolean + runAsUserName: + description: |- + The UserName in Windows to run the entrypoint of the container process. + Defaults to the user specified in image metadata if unspecified. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + type: string + type: object + type: object + replicas: + format: int32 + type: integer + resources: + description: ResourceRequirements describes the compute resource + requirements. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + + This field depends on the + DynamicResourceAllocation feature gate. + + This field is immutable. It can only be set for containers. + items: + description: ResourceClaim references one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + securityContext: + description: |- + SecurityContext holds security configuration that will be applied to a container. + Some fields are present in both SecurityContext and PodSecurityContext. When both + are set, the values in SecurityContext take precedence. + properties: + allowPrivilegeEscalation: + description: |- + AllowPrivilegeEscalation controls whether a process can gain more + privileges than its parent process. This bool directly controls if + the no_new_privs flag will be set on the container process. + AllowPrivilegeEscalation is true always when the container is: + 1) run as Privileged + 2) has CAP_SYS_ADMIN + Note that this field cannot be set when spec.os.name is windows. + type: boolean + appArmorProfile: + description: |- + appArmorProfile is the AppArmor options to use by this container. If set, this profile + overrides the pod's appArmorProfile. + Note that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: |- + localhostProfile indicates a profile loaded on the node that should be used. + The profile must be preconfigured on the node to work. + Must match the loaded name of the profile. + Must be set if and only if type is "Localhost". + type: string + type: + description: |- + type indicates which kind of AppArmor profile will be applied. + Valid options are: + Localhost - a profile pre-loaded on the node. + RuntimeDefault - the container runtime's default profile. + Unconfined - no AppArmor enforcement. + type: string + required: + - type + type: object + capabilities: + description: |- + The capabilities to add/drop when running containers. + Defaults to the default set of capabilities granted by the container runtime. + Note that this field cannot be set when spec.os.name is windows. + properties: + add: + description: Added capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + x-kubernetes-list-type: atomic + drop: + description: Removed capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + x-kubernetes-list-type: atomic + type: object + privileged: + description: |- + Run container in privileged mode. + Processes in privileged containers are essentially equivalent to root on the host. + Defaults to false. + Note that this field cannot be set when spec.os.name is windows. + type: boolean + procMount: + description: |- + procMount denotes the type of proc mount to use for the containers. + The default value is Default which uses the container runtime defaults for + readonly paths and masked paths. + This requires the ProcMountType feature flag to be enabled. + Note that this field cannot be set when spec.os.name is windows. + type: string + readOnlyRootFilesystem: + description: |- + Whether this container has a read-only root filesystem. + Default is false. + Note that this field cannot be set when spec.os.name is windows. + type: boolean + runAsGroup: + description: |- + The GID to run the entrypoint of the container process. + Uses runtime default if unset. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: |- + Indicates that the container must run as a non-root user. + If true, the Kubelet will validate the image at runtime to ensure that it + does not run as UID 0 (root) and fail to start the container if it does. + If unset or false, no such validation will be performed. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: |- + The UID to run the entrypoint of the container process. + Defaults to user specified in image metadata if unspecified. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + seLinuxOptions: + description: |- + The SELinux context to be applied to the container. + If unspecified, the container runtime will allocate a random SELinux context for each + container. May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is windows. + properties: + level: + description: Level is SELinux level label that applies + to the container. + type: string + role: + description: Role is a SELinux role label that applies + to the container. + type: string + type: + description: Type is a SELinux type label that applies + to the container. + type: string + user: + description: User is a SELinux user label that applies + to the container. + type: string + type: object + seccompProfile: + description: |- + The seccomp options to use by this container. If seccomp options are + provided at both the pod & container level, the container options + override the pod options. + Note that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: |- + localhostProfile indicates a profile defined in a file on the node should be used. + The profile must be preconfigured on the node to work. + Must be a descending path, relative to the kubelet's configured seccomp profile location. + Must be set if type is "Localhost". Must NOT be set for any other type. + type: string + type: + description: |- + type indicates which kind of seccomp profile will be applied. + Valid options are: + + Localhost - a profile defined in a file on the node should be used. + RuntimeDefault - the container runtime default profile should be used. + Unconfined - no profile should be applied. + type: string + required: + - type + type: object + windowsOptions: + description: |- + The Windows specific settings applied to all containers. + If unspecified, the options from the PodSecurityContext will be used. + If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is linux. + properties: + gmsaCredentialSpec: + description: |- + GMSACredentialSpec is where the GMSA admission webhook + (https://github.com/kubernetes-sigs/windows-gmsa) inlines the contents of the + GMSA credential spec named by the GMSACredentialSpecName field. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the name of + the GMSA credential spec to use. + type: string + hostProcess: + description: |- + HostProcess determines if a container should be run as a 'Host Process' container. + All of a Pod's containers must have the same effective HostProcess value + (it is not allowed to have a mix of HostProcess containers and non-HostProcess containers). + In addition, if HostProcess is true then HostNetwork must also be set to true. + type: boolean + runAsUserName: + description: |- + The UserName in Windows to run the entrypoint of the container process. + Defaults to the user specified in image metadata if unspecified. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + type: string + type: object + type: object + serviceAccountConfig: + description: |- + ServiceAccountConfig configures the ServiceAccount created by the Agent controller. + This field can only be used when ServiceAccountName is not set. + If ServiceAccountName is not set, a default ServiceAccount (named after the agent) + is created, and this config will be applied to it. + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + serviceAccountName: + description: |- + ServiceAccountName specifies the name of an existing ServiceAccount to use. + If this field is set, the Agent controller will not create a ServiceAccount for the agent. + This field is mutually exclusive with ServiceAccountConfig. + type: string + tolerations: + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists, Equal, Lt, and Gt. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + Lt and Gt perform numeric comparisons (requires feature gate TaintTolerationComparisonOperators). + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + volumeMounts: + items: + description: VolumeMount describes a mounting of a Volume + within a container. + properties: + mountPath: + description: |- + Path within the container at which the volume should be mounted. Must + not contain ':'. + type: string + mountPropagation: + description: |- + mountPropagation determines how mounts are propagated from the host + to container and the other way around. + When not set, MountPropagationNone is used. + This field is beta in 1.10. + When RecursiveReadOnly is set to IfPossible or to Enabled, MountPropagation must be None or unspecified + (which defaults to None). + type: string + name: + description: This must match the Name of a Volume. + type: string + readOnly: + description: |- + Mounted read-only if true, read-write otherwise (false or unspecified). + Defaults to false. + type: boolean + recursiveReadOnly: + description: |- + RecursiveReadOnly specifies whether read-only mounts should be handled + recursively. + + If ReadOnly is false, this field has no meaning and must be unspecified. + + If ReadOnly is true, and this field is set to Disabled, the mount is not made + recursively read-only. If this field is set to IfPossible, the mount is made + recursively read-only, if it is supported by the container runtime. If this + field is set to Enabled, the mount is made recursively read-only if it is + supported by the container runtime, otherwise the pod will not be started and + an error will be generated to indicate the reason. + + If this field is set to IfPossible or Enabled, MountPropagation must be set to + None (or be unspecified, which defaults to None). + + If this field is not specified, it is treated as an equivalent of Disabled. + type: string + subPath: + description: |- + Path within the volume from which the container's volume should be mounted. + Defaults to "" (volume's root). + type: string + subPathExpr: + description: |- + Expanded path within the volume from which the container's volume should be mounted. + Behaves similarly to SubPath but environment variable references $(VAR_NAME) are expanded using the container's environment. + Defaults to "" (volume's root). + SubPathExpr and SubPath are mutually exclusive. + type: string + required: + - mountPath + - name + type: object + type: array + volumes: + items: + description: Volume represents a named volume in a pod that + may be accessed by any container in the pod. + properties: + awsElasticBlockStore: + description: |- + awsElasticBlockStore represents an AWS Disk resource that is attached to a + kubelet's host machine and then exposed to the pod. + Deprecated: AWSElasticBlockStore is deprecated. All operations for the in-tree + awsElasticBlockStore type are redirected to the ebs.csi.aws.com CSI driver. + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + properties: + fsType: + description: |- + fsType is the filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + type: string + partition: + description: |- + partition is the partition in the volume that you want to mount. + If omitted, the default is to mount by volume name. + Examples: For volume /dev/sda1, you specify the partition as "1". + Similarly, the volume partition for /dev/sda is "0" (or you can leave the property empty). + format: int32 + type: integer + readOnly: + description: |- + readOnly value true will force the readOnly setting in VolumeMounts. + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + type: boolean + volumeID: + description: |- + volumeID is unique ID of the persistent disk resource in AWS (Amazon EBS volume). + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + type: string + required: + - volumeID + type: object + azureDisk: + description: |- + azureDisk represents an Azure Data Disk mount on the host and bind mount to the pod. + Deprecated: AzureDisk is deprecated. All operations for the in-tree azureDisk type + are redirected to the disk.csi.azure.com CSI driver. + properties: + cachingMode: + description: 'cachingMode is the Host Caching mode: + None, Read Only, Read Write.' + type: string + diskName: + description: diskName is the Name of the data disk + in the blob storage + type: string + diskURI: + description: diskURI is the URI of data disk in + the blob storage + type: string + fsType: + default: ext4 + description: |- + fsType is Filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + kind: + description: 'kind expected values are Shared: multiple + blob disks per storage account Dedicated: single + blob disk per storage account Managed: azure + managed data disk (only in managed availability + set). defaults to shared' + type: string + readOnly: + default: false + description: |- + readOnly Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + required: + - diskName + - diskURI + type: object + azureFile: + description: |- + azureFile represents an Azure File Service mount on the host and bind mount to the pod. + Deprecated: AzureFile is deprecated. All operations for the in-tree azureFile type + are redirected to the file.csi.azure.com CSI driver. + properties: + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretName: + description: secretName is the name of secret that + contains Azure Storage Account Name and Key + type: string + shareName: + description: shareName is the azure share Name + type: string + required: + - secretName + - shareName + type: object + cephfs: + description: |- + cephFS represents a Ceph FS mount on the host that shares a pod's lifetime. + Deprecated: CephFS is deprecated and the in-tree cephfs type is no longer supported. + properties: + monitors: + description: |- + monitors is Required: Monitors is a collection of Ceph monitors + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + items: + type: string + type: array + x-kubernetes-list-type: atomic + path: + description: 'path is Optional: Used as the mounted + root, rather than the full Ceph tree, default + is /' + type: string + readOnly: + description: |- + readOnly is Optional: Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + type: boolean + secretFile: + description: |- + secretFile is Optional: SecretFile is the path to key ring for User, default is /etc/ceph/user.secret + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + type: string + secretRef: + description: |- + secretRef is Optional: SecretRef is reference to the authentication secret for User, default is empty. + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + user: + description: |- + user is optional: User is the rados user name, default is admin + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + type: string + required: + - monitors + type: object + cinder: + description: |- + cinder represents a cinder volume attached and mounted on kubelets host machine. + Deprecated: Cinder is deprecated. All operations for the in-tree cinder type + are redirected to the cinder.csi.openstack.org CSI driver. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + type: string + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + type: boolean + secretRef: + description: |- + secretRef is optional: points to a secret object containing parameters used to connect + to OpenStack. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + volumeID: + description: |- + volumeID used to identify the volume in cinder. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + type: string + required: + - volumeID + type: object + configMap: + description: configMap represents a configMap that should + populate this volume + properties: + defaultMode: + description: |- + defaultMode is optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + ConfigMap will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the ConfigMap, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key to a path within + a volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: optional specify whether the ConfigMap + or its keys must be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + csi: + description: csi (Container Storage Interface) represents + ephemeral storage that is handled by certain external + CSI drivers. + properties: + driver: + description: |- + driver is the name of the CSI driver that handles this volume. + Consult with your admin for the correct name as registered in the cluster. + type: string + fsType: + description: |- + fsType to mount. Ex. "ext4", "xfs", "ntfs". + If not provided, the empty value is passed to the associated CSI driver + which will determine the default filesystem to apply. + type: string + nodePublishSecretRef: + description: |- + nodePublishSecretRef is a reference to the secret object containing + sensitive information to pass to the CSI driver to complete the CSI + NodePublishVolume and NodeUnpublishVolume calls. + This field is optional, and may be empty if no secret is required. If the + secret object contains more than one secret, all secret references are passed. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + readOnly: + description: |- + readOnly specifies a read-only configuration for the volume. + Defaults to false (read/write). + type: boolean + volumeAttributes: + additionalProperties: + type: string + description: |- + volumeAttributes stores driver-specific properties that are passed to the CSI + driver. Consult your driver's documentation for supported values. + type: object + required: + - driver + type: object + downwardAPI: + description: downwardAPI represents downward API about + the pod that should populate this volume + properties: + defaultMode: + description: |- + Optional: mode bits to use on created files by default. Must be a + Optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + items: + description: Items is a list of downward API volume + file + items: + description: DownwardAPIVolumeFile represents + information to create the file containing the + pod field + properties: + fieldRef: + description: 'Required: Selects a field of + the pod: only annotations, labels, name, + namespace and uid are supported.' + properties: + apiVersion: + description: Version of the schema the + FieldPath is written in terms of, defaults + to "v1". + type: string + fieldPath: + description: Path of the field to select + in the specified API version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + description: |- + Optional: mode bits used to set permissions on this file, must be an octal value + between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: 'Required: Path is the relative + path name of the file to be created. Must + not be absolute or contain the ''..'' path. + Must be utf-8 encoded. The first item of + the relative path must not start with ''..''' + type: string + resourceFieldRef: + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, requests.cpu and requests.memory) are currently supported. + properties: + containerName: + description: 'Container name: required + for volumes, optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format + of the exposed resources, defaults to + "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + required: + - path + type: object + type: array + x-kubernetes-list-type: atomic + type: object + emptyDir: + description: |- + emptyDir represents a temporary directory that shares a pod's lifetime. + More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir + properties: + medium: + description: |- + medium represents what type of storage medium should back this directory. + The default is "" which means to use the node's default medium. + Must be an empty string (default) or Memory. + More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir + type: string + sizeLimit: + anyOf: + - type: integer + - type: string + description: |- + sizeLimit is the total amount of local storage required for this EmptyDir volume. + The size limit is also applicable for memory medium. + The maximum usage on memory medium EmptyDir would be the minimum value between + the SizeLimit specified here and the sum of memory limits of all containers in a pod. + The default is nil which means that the limit is undefined. + More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + ephemeral: + description: |- + ephemeral represents a volume that is handled by a cluster storage driver. + The volume's lifecycle is tied to the pod that defines it - it will be created before the pod starts, + and deleted when the pod is removed. + + Use this if: + a) the volume is only needed while the pod runs, + b) features of normal volumes like restoring from snapshot or capacity + tracking are needed, + c) the storage driver is specified through a storage class, and + d) the storage driver supports dynamic volume provisioning through + a PersistentVolumeClaim (see EphemeralVolumeSource for more + information on the connection between this volume type + and PersistentVolumeClaim). + + Use PersistentVolumeClaim or one of the vendor-specific + APIs for volumes that persist for longer than the lifecycle + of an individual pod. + + Use CSI for light-weight local ephemeral volumes if the CSI driver is meant to + be used that way - see the documentation of the driver for + more information. + + A pod can use both types of ephemeral volumes and + persistent volumes at the same time. + properties: + volumeClaimTemplate: + description: |- + Will be used to create a stand-alone PVC to provision the volume. + The pod in which this EphemeralVolumeSource is embedded will be the + owner of the PVC, i.e. the PVC will be deleted together with the + pod. The name of the PVC will be `-` where + `` is the name from the `PodSpec.Volumes` array + entry. Pod validation will reject the pod if the concatenated name + is not valid for a PVC (for example, too long). + + An existing PVC with that name that is not owned by the pod + will *not* be used for the pod to avoid using an unrelated + volume by mistake. Starting the pod is then blocked until + the unrelated PVC is removed. If such a pre-created PVC is + meant to be used by the pod, the PVC has to updated with an + owner reference to the pod once the pod exists. Normally + this should not be necessary, but it may be useful when + manually reconstructing a broken cluster. + + This field is read-only and no changes will be made by Kubernetes + to the PVC after it has been created. + + Required, must not be nil. + properties: + metadata: + description: |- + May contain labels and annotations that will be copied into the PVC + when creating it. No other fields are allowed and will be rejected during + validation. + type: object + spec: + description: |- + The specification for the PersistentVolumeClaim. The entire content is + copied unchanged into the PVC that gets created from this + template. The same fields as in a PersistentVolumeClaim + are also valid here. + properties: + accessModes: + description: |- + accessModes contains the desired access modes the volume should have. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1 + items: + type: string + type: array + x-kubernetes-list-type: atomic + dataSource: + description: |- + dataSource field can be used to specify either: + * An existing VolumeSnapshot object (snapshot.storage.k8s.io/VolumeSnapshot) + * An existing PVC (PersistentVolumeClaim) + If the provisioner or an external controller can support the specified data source, + it will create a new volume based on the contents of the specified data source. + When the AnyVolumeDataSource feature gate is enabled, dataSource contents will be copied to dataSourceRef, + and dataSourceRef contents will be copied to dataSource when dataSourceRef.namespace is not specified. + If the namespace is specified, then dataSourceRef will not be copied to dataSource. + properties: + apiGroup: + description: |- + APIGroup is the group for the resource being referenced. + If APIGroup is not specified, the specified Kind must be in the core API group. + For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource + being referenced + type: string + name: + description: Name is the name of resource + being referenced + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + dataSourceRef: + description: |- + dataSourceRef specifies the object from which to populate the volume with data, if a non-empty + volume is desired. This may be any object from a non-empty API group (non + core object) or a PersistentVolumeClaim object. + When this field is specified, volume binding will only succeed if the type of + the specified object matches some installed volume populator or dynamic + provisioner. + This field will replace the functionality of the dataSource field and as such + if both fields are non-empty, they must have the same value. For backwards + compatibility, when namespace isn't specified in dataSourceRef, + both fields (dataSource and dataSourceRef) will be set to the same + value automatically if one of them is empty and the other is non-empty. + When namespace is specified in dataSourceRef, + dataSource isn't set to the same value and must be empty. + There are three important differences between dataSource and dataSourceRef: + * While dataSource only allows two specific types of objects, dataSourceRef + allows any non-core object, as well as PersistentVolumeClaim objects. + * While dataSource ignores disallowed values (dropping them), dataSourceRef + preserves all values, and generates an error if a disallowed value is + specified. + * While dataSource only allows local objects, dataSourceRef allows objects + in any namespaces. + (Beta) Using this field requires the AnyVolumeDataSource feature gate to be enabled. + (Alpha) Using the namespace field of dataSourceRef requires the CrossNamespaceVolumeDataSource feature gate to be enabled. + properties: + apiGroup: + description: |- + APIGroup is the group for the resource being referenced. + If APIGroup is not specified, the specified Kind must be in the core API group. + For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource + being referenced + type: string + name: + description: Name is the name of resource + being referenced + type: string + namespace: + description: |- + Namespace is the namespace of resource being referenced + Note that when a namespace is specified, a gateway.networking.k8s.io/ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. + (Alpha) This field requires the CrossNamespaceVolumeDataSource feature gate to be enabled. + type: string + required: + - kind + - name + type: object + resources: + description: |- + resources represents the minimum resources the volume should have. + Users are allowed to specify resource requirements + that are lower than previous value but must still be higher than capacity recorded in the + status field of the claim. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + selector: + description: selector is a label query over + volumes to consider for binding. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The + requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + storageClassName: + description: |- + storageClassName is the name of the StorageClass required by the claim. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1 + type: string + volumeAttributesClassName: + description: |- + volumeAttributesClassName may be used to set the VolumeAttributesClass used by this claim. + If specified, the CSI driver will create or update the volume with the attributes defined + in the corresponding VolumeAttributesClass. This has a different purpose than storageClassName, + it can be changed after the claim is created. An empty string or nil value indicates that no + VolumeAttributesClass will be applied to the claim. If the claim enters an Infeasible error state, + this field can be reset to its previous value (including nil) to cancel the modification. + If the resource referred to by volumeAttributesClass does not exist, this PersistentVolumeClaim will be + set to a Pending state, as reflected by the modifyVolumeStatus field, until such as a resource + exists. + More info: https://kubernetes.io/docs/concepts/storage/volume-attributes-classes/ + type: string + volumeMode: + description: |- + volumeMode defines what type of volume is required by the claim. + Value of Filesystem is implied when not included in claim spec. + type: string + volumeName: + description: volumeName is the binding reference + to the PersistentVolume backing this claim. + type: string + type: object + required: + - spec + type: object + type: object + fc: + description: fc represents a Fibre Channel resource + that is attached to a kubelet's host machine and then + exposed to the pod. + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + lun: + description: 'lun is Optional: FC target lun number' + format: int32 + type: integer + readOnly: + description: |- + readOnly is Optional: Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + targetWWNs: + description: 'targetWWNs is Optional: FC target + worldwide names (WWNs)' + items: + type: string + type: array + x-kubernetes-list-type: atomic + wwids: + description: |- + wwids Optional: FC volume world wide identifiers (wwids) + Either wwids or combination of targetWWNs and lun must be set, but not both simultaneously. + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + flexVolume: + description: |- + flexVolume represents a generic volume resource that is + provisioned/attached using an exec based plugin. + Deprecated: FlexVolume is deprecated. Consider using a CSIDriver instead. + properties: + driver: + description: driver is the name of the driver to + use for this volume. + type: string + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". The default filesystem depends on FlexVolume script. + type: string + options: + additionalProperties: + type: string + description: 'options is Optional: this field holds + extra command options if any.' + type: object + readOnly: + description: |- + readOnly is Optional: defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: |- + secretRef is Optional: secretRef is reference to the secret object containing + sensitive information to pass to the plugin scripts. This may be + empty if no secret object is specified. If the secret object + contains more than one secret, all secrets are passed to the plugin + scripts. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + required: + - driver + type: object + flocker: + description: |- + flocker represents a Flocker volume attached to a kubelet's host machine. This depends on the Flocker control service being running. + Deprecated: Flocker is deprecated and the in-tree flocker type is no longer supported. + properties: + datasetName: + description: |- + datasetName is Name of the dataset stored as metadata -> name on the dataset for Flocker + should be considered as deprecated + type: string + datasetUUID: + description: datasetUUID is the UUID of the dataset. + This is unique identifier of a Flocker dataset + type: string + type: object + gcePersistentDisk: + description: |- + gcePersistentDisk represents a GCE Disk resource that is attached to a + kubelet's host machine and then exposed to the pod. + Deprecated: GCEPersistentDisk is deprecated. All operations for the in-tree + gcePersistentDisk type are redirected to the pd.csi.storage.gke.io CSI driver. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + properties: + fsType: + description: |- + fsType is filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + type: string + partition: + description: |- + partition is the partition in the volume that you want to mount. + If omitted, the default is to mount by volume name. + Examples: For volume /dev/sda1, you specify the partition as "1". + Similarly, the volume partition for /dev/sda is "0" (or you can leave the property empty). + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + format: int32 + type: integer + pdName: + description: |- + pdName is unique name of the PD resource in GCE. Used to identify the disk in GCE. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + type: string + readOnly: + description: |- + readOnly here will force the ReadOnly setting in VolumeMounts. + Defaults to false. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + type: boolean + required: + - pdName + type: object + gitRepo: + description: |- + gitRepo represents a git repository at a particular revision. + Deprecated: GitRepo is deprecated. To provision a container with a git repo, mount an + EmptyDir into an InitContainer that clones the repo using git, then mount the EmptyDir + into the Pod's container. + properties: + directory: + description: |- + directory is the target directory name. + Must not contain or start with '..'. If '.' is supplied, the volume directory will be the + git repository. Otherwise, if specified, the volume will contain the git repository in + the subdirectory with the given name. + type: string + repository: + description: repository is the URL + type: string + revision: + description: revision is the commit hash for the + specified revision. + type: string + required: + - repository + type: object + glusterfs: + description: |- + glusterfs represents a Glusterfs mount on the host that shares a pod's lifetime. + Deprecated: Glusterfs is deprecated and the in-tree glusterfs type is no longer supported. + properties: + endpoints: + description: endpoints is the endpoint name that + details Glusterfs topology. + type: string + path: + description: |- + path is the Glusterfs volume path. + More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod + type: string + readOnly: + description: |- + readOnly here will force the Glusterfs volume to be mounted with read-only permissions. + Defaults to false. + More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod + type: boolean + required: + - endpoints + - path + type: object + hostPath: + description: |- + hostPath represents a pre-existing file or directory on the host + machine that is directly exposed to the container. This is generally + used for system agents or other privileged things that are allowed + to see the host machine. Most containers will NOT need this. + More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + properties: + path: + description: |- + path of the directory on the host. + If the path is a symlink, it will follow the link to the real path. + More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + type: string + type: + description: |- + type for HostPath Volume + Defaults to "" + More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + type: string + required: + - path + type: object + image: + description: |- + image represents an OCI object (a container image or artifact) pulled and mounted on the kubelet's host machine. + The volume is resolved at pod startup depending on which PullPolicy value is provided: + + - Always: the kubelet always attempts to pull the reference. Container creation will fail If the pull fails. + - Never: the kubelet never pulls the reference and only uses a local image or artifact. Container creation will fail if the reference isn't present. + - IfNotPresent: the kubelet pulls if the reference isn't already present on disk. Container creation will fail if the reference isn't present and the pull fails. + + The volume gets re-resolved if the pod gets deleted and recreated, which means that new remote content will become available on pod recreation. + A failure to resolve or pull the image during pod startup will block containers from starting and may add significant latency. Failures will be retried using normal volume backoff and will be reported on the pod reason and message. + The types of objects that may be mounted by this volume are defined by the container runtime implementation on a host machine and at minimum must include all valid types supported by the container image field. + The OCI object gets mounted in a single directory (spec.containers[*].volumeMounts.mountPath) by merging the manifest layers in the same way as for container images. + The volume will be mounted read-only (ro) and non-executable files (noexec). + Sub path mounts for containers are not supported (spec.containers[*].volumeMounts.subpath) before 1.33. + The field spec.securityContext.fsGroupChangePolicy has no effect on this volume type. + properties: + pullPolicy: + description: |- + Policy for pulling OCI objects. Possible values are: + Always: the kubelet always attempts to pull the reference. Container creation will fail If the pull fails. + Never: the kubelet never pulls the reference and only uses a local image or artifact. Container creation will fail if the reference isn't present. + IfNotPresent: the kubelet pulls if the reference isn't already present on disk. Container creation will fail if the reference isn't present and the pull fails. + Defaults to Always if :latest tag is specified, or IfNotPresent otherwise. + type: string + reference: + description: |- + Required: Image or artifact reference to be used. + Behaves in the same way as pod.spec.containers[*].image. + Pull secrets will be assembled in the same way as for the container image by looking up node credentials, SA image pull secrets, and pod spec image pull secrets. + More info: https://kubernetes.io/docs/concepts/containers/images + This field is optional to allow higher level config management to default or override + container images in workload controllers like Deployments and StatefulSets. + type: string + type: object + iscsi: + description: |- + iscsi represents an ISCSI Disk resource that is attached to a + kubelet's host machine and then exposed to the pod. + More info: https://kubernetes.io/docs/concepts/storage/volumes/#iscsi + properties: + chapAuthDiscovery: + description: chapAuthDiscovery defines whether support + iSCSI Discovery CHAP authentication + type: boolean + chapAuthSession: + description: chapAuthSession defines whether support + iSCSI Session CHAP authentication + type: boolean + fsType: + description: |- + fsType is the filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#iscsi + type: string + initiatorName: + description: |- + initiatorName is the custom iSCSI Initiator Name. + If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface + : will be created for the connection. + type: string + iqn: + description: iqn is the target iSCSI Qualified Name. + type: string + iscsiInterface: + default: default + description: |- + iscsiInterface is the interface Name that uses an iSCSI transport. + Defaults to 'default' (tcp). + type: string + lun: + description: lun represents iSCSI Target Lun number. + format: int32 + type: integer + portals: + description: |- + portals is the iSCSI Target Portal List. The portal is either an IP or ip_addr:port if the port + is other than default (typically TCP ports 860 and 3260). + items: + type: string + type: array + x-kubernetes-list-type: atomic + readOnly: + description: |- + readOnly here will force the ReadOnly setting in VolumeMounts. + Defaults to false. + type: boolean + secretRef: + description: secretRef is the CHAP Secret for iSCSI + target and initiator authentication + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + targetPortal: + description: |- + targetPortal is iSCSI Target Portal. The Portal is either an IP or ip_addr:port if the port + is other than default (typically TCP ports 860 and 3260). + type: string + required: + - iqn + - lun + - targetPortal + type: object + name: + description: |- + name of the volume. + Must be a DNS_LABEL and unique within the pod. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + nfs: + description: |- + nfs represents an NFS mount on the host that shares a pod's lifetime + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + properties: + path: + description: |- + path that is exported by the NFS server. + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + type: string + readOnly: + description: |- + readOnly here will force the NFS export to be mounted with read-only permissions. + Defaults to false. + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + type: boolean + server: + description: |- + server is the hostname or IP address of the NFS server. + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + type: string + required: + - path + - server + type: object + persistentVolumeClaim: + description: |- + persistentVolumeClaimVolumeSource represents a reference to a + PersistentVolumeClaim in the same namespace. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims + properties: + claimName: + description: |- + claimName is the name of a PersistentVolumeClaim in the same namespace as the pod using this volume. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims + type: string + readOnly: + description: |- + readOnly Will force the ReadOnly setting in VolumeMounts. + Default false. + type: boolean + required: + - claimName + type: object + photonPersistentDisk: + description: |- + photonPersistentDisk represents a PhotonController persistent disk attached and mounted on kubelets host machine. + Deprecated: PhotonPersistentDisk is deprecated and the in-tree photonPersistentDisk type is no longer supported. + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + pdID: + description: pdID is the ID that identifies Photon + Controller persistent disk + type: string + required: + - pdID + type: object + portworxVolume: + description: |- + portworxVolume represents a portworx volume attached and mounted on kubelets host machine. + Deprecated: PortworxVolume is deprecated. All operations for the in-tree portworxVolume type + are redirected to the pxd.portworx.com CSI driver when the CSIMigrationPortworx feature-gate + is on. + properties: + fsType: + description: |- + fSType represents the filesystem type to mount + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs". Implicitly inferred to be "ext4" if unspecified. + type: string + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + volumeID: + description: volumeID uniquely identifies a Portworx + volume + type: string + required: + - volumeID + type: object + projected: + description: projected items for all in one resources + secrets, configmaps, and downward API + properties: + defaultMode: + description: |- + defaultMode are the mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + sources: + description: |- + sources is the list of volume projections. Each entry in this list + handles one source. + items: + description: |- + Projection that may be projected along with other supported volume types. + Exactly one of these fields must be set. + properties: + clusterTrustBundle: + description: |- + ClusterTrustBundle allows a pod to access the `.spec.trustBundle` field + of ClusterTrustBundle objects in an auto-updating file. + + Alpha, gated by the ClusterTrustBundleProjection feature gate. + + ClusterTrustBundle objects can either be selected by name, or by the + combination of signer name and a label selector. + + Kubelet performs aggressive normalization of the PEM contents written + into the pod filesystem. Esoteric PEM features such as inter-block + comments and block headers are stripped. Certificates are deduplicated. + The ordering of certificates within the file is arbitrary, and Kubelet + may change the order over time. + properties: + labelSelector: + description: |- + Select all ClusterTrustBundles that match this label selector. Only has + effect if signerName is set. Mutually-exclusive with name. If unset, + interpreted as "match nothing". If set but empty, interpreted as "match + everything". + properties: + matchExpressions: + description: matchExpressions is a + list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + name: + description: |- + Select a single ClusterTrustBundle by object name. Mutually-exclusive + with signerName and labelSelector. + type: string + optional: + description: |- + If true, don't block pod startup if the referenced ClusterTrustBundle(s) + aren't available. If using name, then the named ClusterTrustBundle is + allowed not to exist. If using signerName, then the combination of + signerName and labelSelector is allowed to match zero + ClusterTrustBundles. + type: boolean + path: + description: Relative path from the volume + root to write the bundle. + type: string + signerName: + description: |- + Select all ClusterTrustBundles that match this signer name. + Mutually-exclusive with name. The contents of all selected + ClusterTrustBundles will be unified and deduplicated. + type: string + required: + - path + type: object + configMap: + description: configMap information about the + configMap data to project + properties: + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + ConfigMap will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the ConfigMap, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key to a + path within a volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: optional specify whether + the ConfigMap or its keys must be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + downwardAPI: + description: downwardAPI information about + the downwardAPI data to project + properties: + items: + description: Items is a list of DownwardAPIVolume + file + items: + description: DownwardAPIVolumeFile represents + information to create the file containing + the pod field + properties: + fieldRef: + description: 'Required: Selects + a field of the pod: only annotations, + labels, name, namespace and uid + are supported.' + properties: + apiVersion: + description: Version of the + schema the FieldPath is written + in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field + to select in the specified + API version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + description: |- + Optional: mode bits used to set permissions on this file, must be an octal value + between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: 'Required: Path is the + relative path name of the file + to be created. Must not be absolute + or contain the ''..'' path. Must + be utf-8 encoded. The first item + of the relative path must not + start with ''..''' + type: string + resourceFieldRef: + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, requests.cpu and requests.memory) are currently supported. + properties: + containerName: + description: 'Container name: + required for volumes, optional + for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output + format of the exposed resources, + defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource + to select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + required: + - path + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podCertificate: + description: |- + Projects an auto-rotating credential bundle (private key and certificate + chain) that the pod can use either as a TLS client or server. + + Kubelet generates a private key and uses it to send a + PodCertificateRequest to the named signer. Once the signer approves the + request and issues a certificate chain, Kubelet writes the key and + certificate chain to the pod filesystem. The pod does not start until + certificates have been issued for each podCertificate projected volume + source in its spec. + + Kubelet will begin trying to rotate the certificate at the time indicated + by the signer using the PodCertificateRequest.Status.BeginRefreshAt + timestamp. + + Kubelet can write a single file, indicated by the credentialBundlePath + field, or separate files, indicated by the keyPath and + certificateChainPath fields. + + The credential bundle is a single file in PEM format. The first PEM + entry is the private key (in PKCS#8 format), and the remaining PEM + entries are the certificate chain issued by the signer (typically, + signers will return their certificate chain in leaf-to-root order). + + Prefer using the credential bundle format, since your application code + can read it atomically. If you use keyPath and certificateChainPath, + your application must make two separate file reads. If these coincide + with a certificate rotation, it is possible that the private key and leaf + certificate you read may not correspond to each other. Your application + will need to check for this condition, and re-read until they are + consistent. + + The named signer controls chooses the format of the certificate it + issues; consult the signer implementation's documentation to learn how to + use the certificates it issues. + properties: + certificateChainPath: + description: |- + Write the certificate chain at this path in the projected volume. + + Most applications should use credentialBundlePath. When using keyPath + and certificateChainPath, your application needs to check that the key + and leaf certificate are consistent, because it is possible to read the + files mid-rotation. + type: string + credentialBundlePath: + description: |- + Write the credential bundle at this path in the projected volume. + + The credential bundle is a single file that contains multiple PEM blocks. + The first PEM block is a PRIVATE KEY block, containing a PKCS#8 private + key. + + The remaining blocks are CERTIFICATE blocks, containing the issued + certificate chain from the signer (leaf and any intermediates). + + Using credentialBundlePath lets your Pod's application code make a single + atomic read that retrieves a consistent key and certificate chain. If you + project them to separate files, your application code will need to + additionally check that the leaf certificate was issued to the key. + type: string + keyPath: + description: |- + Write the key at this path in the projected volume. + + Most applications should use credentialBundlePath. When using keyPath + and certificateChainPath, your application needs to check that the key + and leaf certificate are consistent, because it is possible to read the + files mid-rotation. + type: string + keyType: + description: |- + The type of keypair Kubelet will generate for the pod. + + Valid values are "RSA3072", "RSA4096", "ECDSAP256", "ECDSAP384", + "ECDSAP521", and "ED25519". + type: string + maxExpirationSeconds: + description: |- + maxExpirationSeconds is the maximum lifetime permitted for the + certificate. + + Kubelet copies this value verbatim into the PodCertificateRequests it + generates for this projection. + + If omitted, kube-apiserver will set it to 86400(24 hours). kube-apiserver + will reject values shorter than 3600 (1 hour). The maximum allowable + value is 7862400 (91 days). + + The signer implementation is then free to issue a certificate with any + lifetime *shorter* than MaxExpirationSeconds, but no shorter than 3600 + seconds (1 hour). This constraint is enforced by kube-apiserver. + `kubernetes.io` signers will never issue certificates with a lifetime + longer than 24 hours. + format: int32 + type: integer + signerName: + description: Kubelet's generated CSRs + will be addressed to this signer. + type: string + userAnnotations: + additionalProperties: + type: string + description: |- + userAnnotations allow pod authors to pass additional information to + the signer implementation. Kubernetes does not restrict or validate this + metadata in any way. + + These values are copied verbatim into the `spec.unverifiedUserAnnotations` field of + the PodCertificateRequest objects that Kubelet creates. + + Entries are subject to the same validation as object metadata annotations, + with the addition that all keys must be domain-prefixed. No restrictions + are placed on values, except an overall size limitation on the entire field. + + Signers should document the keys and values they support. Signers should + deny requests that contain keys they do not recognize. + type: object + required: + - keyType + - signerName + type: object + secret: + description: secret information about the + secret data to project + properties: + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + Secret will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the Secret, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key to a + path within a volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: optional field specify whether + the Secret or its key must be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + serviceAccountToken: + description: serviceAccountToken is information + about the serviceAccountToken data to project + properties: + audience: + description: |- + audience is the intended audience of the token. A recipient of a token + must identify itself with an identifier specified in the audience of the + token, and otherwise should reject the token. The audience defaults to the + identifier of the apiserver. + type: string + expirationSeconds: + description: |- + expirationSeconds is the requested duration of validity of the service + account token. As the token approaches expiration, the kubelet volume + plugin will proactively rotate the service account token. The kubelet will + start trying to rotate the token if the token is older than 80 percent of + its time to live or if the token is older than 24 hours.Defaults to 1 hour + and must be at least 10 minutes. + format: int64 + type: integer + path: + description: |- + path is the path relative to the mount point of the file to project the + token into. + type: string + required: + - path + type: object + type: object + type: array + x-kubernetes-list-type: atomic + type: object + quobyte: + description: |- + quobyte represents a Quobyte mount on the host that shares a pod's lifetime. + Deprecated: Quobyte is deprecated and the in-tree quobyte type is no longer supported. + properties: + group: + description: |- + group to map volume access to + Default is no group + type: string + readOnly: + description: |- + readOnly here will force the Quobyte volume to be mounted with read-only permissions. + Defaults to false. + type: boolean + registry: + description: |- + registry represents a single or multiple Quobyte Registry services + specified as a string as host:port pair (multiple entries are separated with commas) + which acts as the central registry for volumes + type: string + tenant: + description: |- + tenant owning the given Quobyte volume in the Backend + Used with dynamically provisioned Quobyte volumes, value is set by the plugin + type: string + user: + description: |- + user to map volume access to + Defaults to serivceaccount user + type: string + volume: + description: volume is a string that references + an already created Quobyte volume by name. + type: string + required: + - registry + - volume + type: object + rbd: + description: |- + rbd represents a Rados Block Device mount on the host that shares a pod's lifetime. + Deprecated: RBD is deprecated and the in-tree rbd type is no longer supported. + properties: + fsType: + description: |- + fsType is the filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#rbd + type: string + image: + description: |- + image is the rados image name. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + keyring: + default: /etc/ceph/keyring + description: |- + keyring is the path to key ring for RBDUser. + Default is /etc/ceph/keyring. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + monitors: + description: |- + monitors is a collection of Ceph monitors. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + items: + type: string + type: array + x-kubernetes-list-type: atomic + pool: + default: rbd + description: |- + pool is the rados pool name. + Default is rbd. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + readOnly: + description: |- + readOnly here will force the ReadOnly setting in VolumeMounts. + Defaults to false. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: boolean + secretRef: + description: |- + secretRef is name of the authentication secret for RBDUser. If provided + overrides keyring. + Default is nil. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + user: + default: admin + description: |- + user is the rados user name. + Default is admin. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + required: + - image + - monitors + type: object + scaleIO: + description: |- + scaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes. + Deprecated: ScaleIO is deprecated and the in-tree scaleIO type is no longer supported. + properties: + fsType: + default: xfs + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". + Default is "xfs". + type: string + gateway: + description: gateway is the host address of the + ScaleIO API Gateway. + type: string + protectionDomain: + description: protectionDomain is the name of the + ScaleIO Protection Domain for the configured storage. + type: string + readOnly: + description: |- + readOnly Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: |- + secretRef references to the secret for ScaleIO user and other + sensitive information. If this is not provided, Login operation will fail. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + sslEnabled: + description: sslEnabled Flag enable/disable SSL + communication with Gateway, default false + type: boolean + storageMode: + default: ThinProvisioned + description: |- + storageMode indicates whether the storage for a volume should be ThickProvisioned or ThinProvisioned. + Default is ThinProvisioned. + type: string + storagePool: + description: storagePool is the ScaleIO Storage + Pool associated with the protection domain. + type: string + system: + description: system is the name of the storage system + as configured in ScaleIO. + type: string + volumeName: + description: |- + volumeName is the name of a volume already created in the ScaleIO system + that is associated with this volume source. + type: string + required: + - gateway + - secretRef + - system + type: object + secret: + description: |- + secret represents a secret that should populate this volume. + More info: https://kubernetes.io/docs/concepts/storage/volumes#secret + properties: + defaultMode: + description: |- + defaultMode is Optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values + for mode bits. Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + items: + description: |- + items If unspecified, each key-value pair in the Data field of the referenced + Secret will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the Secret, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key to a path within + a volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + optional: + description: optional field specify whether the + Secret or its keys must be defined + type: boolean + secretName: + description: |- + secretName is the name of the secret in the pod's namespace to use. + More info: https://kubernetes.io/docs/concepts/storage/volumes#secret + type: string + type: object + storageos: + description: |- + storageOS represents a StorageOS volume attached and mounted on Kubernetes nodes. + Deprecated: StorageOS is deprecated and the in-tree storageos type is no longer supported. + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: |- + secretRef specifies the secret to use for obtaining the StorageOS API + credentials. If not specified, default values will be attempted. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + volumeName: + description: |- + volumeName is the human-readable name of the StorageOS volume. Volume + names are only unique within a namespace. + type: string + volumeNamespace: + description: |- + volumeNamespace specifies the scope of the volume within StorageOS. If no + namespace is specified then the Pod's namespace will be used. This allows the + Kubernetes name scoping to be mirrored within StorageOS for tighter integration. + Set VolumeName to any name to override the default behaviour. + Set to "default" if you are not using namespaces within StorageOS. + Namespaces that do not pre-exist within StorageOS will be created. + type: string + type: object + vsphereVolume: + description: |- + vsphereVolume represents a vSphere volume attached and mounted on kubelets host machine. + Deprecated: VsphereVolume is deprecated. All operations for the in-tree vsphereVolume type + are redirected to the csi.vsphere.vmware.com CSI driver. + properties: + fsType: + description: |- + fsType is filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + storagePolicyID: + description: storagePolicyID is the storage Policy + Based Management (SPBM) profile ID associated + with the StoragePolicyName. + type: string + storagePolicyName: + description: storagePolicyName is the storage Policy + Based Management (SPBM) profile name. + type: string + volumePath: + description: volumePath is the path that identifies + vSphere volume vmdk + type: string + required: + - volumePath + type: object + required: + - name + type: object + type: array + type: object + x-kubernetes-validations: + - message: serviceAccountName and serviceAccountConfig are mutually + exclusive + rule: '!(has(self.serviceAccountName) && has(self.serviceAccountConfig))' + executeCodeBlocks: + description: |- + Allow code execution for python code blocks with this agent. + If true, the agent will automatically execute python code blocks in the LLM responses. + Code will be executed in a sandboxed environment. + due to a bug in adk (https://github.com/google/adk-python/issues/3921), this field is ignored for now. + type: boolean + memory: + description: Memory configuration for the agent. + properties: + modelConfig: + description: |- + ModelConfig is the name of the ModelConfig object whose embedding + provider will be used to generate memory vectors. + type: string + ttlDays: + description: |- + TTLDays controls how many days a stored memory entry remains valid before + it is eligible for pruning. Defaults to 15 days when unset or zero. + minimum: 1 + type: integer + required: + - modelConfig + type: object + modelConfig: + description: |- + The name of the model config to use. + If not specified, the default value is "default-model-config". + Must be in the same namespace as the Agent. + type: string + promptTemplate: + description: |- + PromptTemplate enables Go text/template processing on the systemMessage field. + When set, systemMessage is treated as a Go template with access to the include function + and agent context variables. + properties: + dataSources: + description: |- + DataSources defines the ConfigMaps whose keys can be included in the systemMessage + using Go template syntax, e.g. include("alias/key") or include("name/key"). + items: + description: |- + PromptSource references a ConfigMap whose keys are available as prompt fragments. + In systemMessage templates, use include("alias/key") (or include("name/key") if no alias is set) + to insert the value of a specific key from this source. + properties: + alias: + description: |- + Alias is an optional short identifier for use in include directives. + If set, use include("alias/key") instead of include("name/key"). + type: string + apiGroup: + type: string + kind: + type: string + name: + type: string + required: + - name + type: object + maxItems: 20 + type: array + type: object + runtime: + allOf: + - enum: + - python + - go + - enum: + - python + - go + default: python + description: |- + Runtime specifies which ADK implementation to use for this agent. + - "python": Uses the Python ADK (default, slower startup, full feature set) + - "go": Uses the Go ADK (faster startup, most features supported) + The runtime determines both the container image and readiness probe configuration. + type: string + stream: + description: |- + Whether to stream the response from the model. + If not specified, the default value is false. + type: boolean + systemMessage: + description: |- + SystemMessage is a string specifying the system message for the agent. + When PromptTemplate is set, this field is treated as a Go text/template + with access to an include("source/key") function and agent context variables + such as .AgentName, .AgentNamespace, .Description, .ToolNames, and .SkillNames. + type: string + systemMessageFrom: + description: |- + SystemMessageFrom is a reference to a ConfigMap or Secret containing the system message. + When PromptTemplate is set, the resolved value is treated as a Go text/template. + properties: + key: + description: The key of the ConfigMap or Secret. + type: string + name: + description: The name of the ConfigMap or Secret. + type: string + type: + enum: + - ConfigMap + - Secret + type: string + required: + - key + - name + - type + type: object + tools: + items: + properties: + agent: + properties: + apiGroup: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + required: + - name + type: object + headersFrom: + description: |- + HeadersFrom specifies a list of configuration values to be added as + headers to requests sent to the Tool from this agent. The value of + each header is resolved from either a Secret or ConfigMap in the same + namespace as the Agent. Headers specified here will override any + headers of the same name/key specified on the tool. + items: + description: ValueRef represents a configuration value + properties: + name: + type: string + value: + type: string + valueFrom: + description: ValueSource defines a source for configuration + values from a Secret or ConfigMap + properties: + key: + description: The key of the ConfigMap or Secret. + type: string + name: + description: The name of the ConfigMap or Secret. + type: string + type: + enum: + - ConfigMap + - Secret + type: string + required: + - key + - name + - type + type: object + required: + - name + type: object + x-kubernetes-validations: + - message: Exactly one of value or valueFrom must be specified + rule: (has(self.value) && !has(self.valueFrom)) || (!has(self.value) + && has(self.valueFrom)) + type: array + mcpServer: + properties: + allowedHeaders: + description: |- + AllowedHeaders specifies which headers from the A2A request should be + propagated to MCP tool calls. Header names are case-insensitive. + + Authorization header behavior: + - Authorization headers CAN be propagated if explicitly listed in allowedHeaders + - When STS token propagation is enabled, STS-generated Authorization headers + will take precedence and replace any Authorization header from the A2A request + - This is a security measure to prevent request headers from overwriting + authentication tokens generated by the STS integration + + Example: ["x-user-email", "x-tenant-id"] + items: + type: string + type: array + apiGroup: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + requireApproval: + description: |- + RequireApproval lists tool names that require human approval before + execution. Each name must also appear in ToolNames. When a tool in + this list is invoked by the agent, execution pauses and the user is + prompted to approve or reject the call. + items: + type: string + maxItems: 50 + type: array + toolNames: + description: |- + The names of the tools to be provided by the ToolServer + For a list of all the tools provided by the server, + the client can query the status of the ToolServer object after it has been created + items: + type: string + maxItems: 50 + type: array + required: + - name + type: object + x-kubernetes-validations: + - message: each RequireApproval entry must also appear in + ToolNames + rule: '!has(self.requireApproval) || self.requireApproval.all(x, + has(self.toolNames) && x in self.toolNames)' + type: + allOf: + - enum: + - McpServer + - Agent + - enum: + - McpServer + - Agent + description: ToolProviderType represents the tool provider + type + type: string + type: object + x-kubernetes-validations: + - message: type.mcpServer must be nil if the type is not McpServer + rule: '!(has(self.mcpServer) && self.type != ''McpServer'')' + - message: type.mcpServer must be specified for McpServer filter.type + rule: '!(!has(self.mcpServer) && self.type == ''McpServer'')' + - message: type.agent must be nil if the type is not Agent + rule: '!(has(self.agent) && self.type != ''Agent'')' + - message: type.agent must be specified for Agent filter.type + rule: '!(!has(self.agent) && self.type == ''Agent'')' + maxItems: 20 + type: array + type: object + x-kubernetes-validations: + - message: systemMessage and systemMessageFrom are mutually exclusive + rule: '!has(self.systemMessage) || !has(self.systemMessageFrom)' + description: + type: string + skills: + properties: + gitAuthSecretRef: + description: |- + Reference to a Secret containing git credentials. + Applied to all gitRefs entries. + The secret should contain a `token` key for HTTPS auth, + or `ssh-privatekey` for SSH auth. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + gitRefs: + description: Git repositories to fetch skills from. + items: + description: GitRepo specifies a single Git repository to fetch + skills from. + properties: + name: + description: Name for the skill directory under /skills. + Defaults to the repo name. + type: string + path: + description: Subdirectory within the repo to use as the + skill root. + type: string + ref: + default: main + description: 'Git reference: branch name, tag, or commit + SHA.' + type: string + url: + description: URL of the git repository (HTTPS or SSH). + type: string + required: + - url + type: object + maxItems: 20 + minItems: 1 + type: array + initContainer: + description: Configuration for the skills-init init container. + properties: + env: + description: Additional environment variables for the skills-init + init container. + items: + description: EnvVar represents an environment variable present + in a Container. + properties: + name: + description: |- + Name of the environment variable. + May consist of any printable ASCII characters except '='. + type: string + value: + description: |- + Variable references $(VAR_NAME) are expanded + using the previously defined environment variables in the container and + any service environment variables. If a variable cannot be resolved, + the reference in the input string will be unchanged. Double $$ are reduced + to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. + "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". + Escaped references will never be expanded, regardless of whether the variable + exists or not. + Defaults to "". + type: string + valueFrom: + description: Source for the environment variable's value. + Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the ConfigMap or + its key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + description: |- + Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['']`, `metadata.annotations['']`, + spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs. + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in + the specified API version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + fileKeyRef: + description: |- + FileKeyRef selects a key of the env file. + Requires the EnvFiles feature gate to be enabled. + properties: + key: + description: |- + The key within the env file. An invalid key will prevent the pod from starting. + The keys defined within a source may consist of any printable ASCII characters except '='. + During Alpha stage of the EnvFiles feature gate, the key size is limited to 128 characters. + type: string + optional: + default: false + description: |- + Specify whether the file or its key must be defined. If the file or key + does not exist, then the env var is not published. + If optional is set to true and the specified key does not exist, + the environment variable will not be set in the Pod's containers. + + If optional is set to false and the specified key does not exist, + an error will be returned during Pod creation. + type: boolean + path: + description: |- + The path within the volume from which to select the file. + Must be relative and may not contain the '..' path or start with '..'. + type: string + volumeName: + description: The name of the volume mount containing + the env file. + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported. + properties: + containerName: + description: 'Container name: required for volumes, + optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of + the exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + description: Selects a key of a secret in the pod's + namespace + properties: + key: + description: The key of the secret to select + from. Must be a valid secret key. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the Secret or its + key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + required: + - name + type: object + type: array + resources: + description: Resource requirements for the skills-init init + container. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + + This field depends on the + DynamicResourceAllocation feature gate. + + This field is immutable. It can only be set for containers. + items: + description: ResourceClaim references one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + type: object + insecureSkipVerify: + description: |- + Fetch images insecurely from registries (allowing HTTP and skipping TLS verification). + Meant for development and testing purposes only. + type: boolean + refs: + description: The list of skill images to fetch. + items: + type: string + maxItems: 20 + minItems: 1 + type: array + type: object + type: + allOf: + - enum: + - Declarative + - BYO + - enum: + - Declarative + - BYO + default: Declarative + description: AgentType represents the agent type + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: type must be specified + rule: has(self.type) + - message: type must be either Declarative or BYO + rule: self.type == 'Declarative' || self.type == 'BYO' + - message: declarative or byo must match type + rule: (self.type == 'Declarative' && has(self.declarative)) || (self.type + == 'BYO' && has(self.byo)) + status: + description: AgentStatus defines the observed state of Agent. + properties: + conditions: + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + observedGeneration: + format: int64 + type: integer + required: + - observedGeneration + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/helm/kagent/templates/rbac/getter-role.yaml b/helm/kagent/templates/rbac/getter-role.yaml index eb395461c..78b46f7bb 100644 --- a/helm/kagent/templates/rbac/getter-role.yaml +++ b/helm/kagent/templates/rbac/getter-role.yaml @@ -3,6 +3,7 @@ - kagent.dev resources: - agents + - sandboxagents - modelconfigs - modelproviderconfigs - toolservers @@ -17,6 +18,7 @@ - kagent.dev resources: - agents/finalizers + - sandboxagents/finalizers - modelconfigs/finalizers - modelproviderconfigs/finalizers - toolservers/finalizers @@ -29,6 +31,7 @@ - kagent.dev resources: - agents/status + - sandboxagents/status - modelconfigs/status - modelproviderconfigs/status - toolservers/status @@ -79,6 +82,23 @@ - get - list - watch +- apiGroups: + - agents.x-k8s.io + resources: + - sandboxes + verbs: + - get + - list + - watch +- apiGroups: + - extensions.agents.x-k8s.io + resources: + - sandboxtemplates + - sandboxclaims + verbs: + - get + - list + - watch {{- end -}} {{- if .Values.rbac.clusterScoped }} diff --git a/helm/kagent/templates/rbac/writer-role.yaml b/helm/kagent/templates/rbac/writer-role.yaml index cbc5b4387..9cfd33732 100644 --- a/helm/kagent/templates/rbac/writer-role.yaml +++ b/helm/kagent/templates/rbac/writer-role.yaml @@ -3,6 +3,7 @@ - kagent.dev resources: - agents + - sandboxagents - modelconfigs - modelproviderconfigs - toolservers @@ -18,6 +19,7 @@ - kagent.dev resources: - agents/finalizers + - sandboxagents/finalizers - modelconfigs/finalizers - modelproviderconfigs/finalizers - toolservers/finalizers @@ -62,6 +64,25 @@ - update - patch - delete +- apiGroups: + - agents.x-k8s.io + resources: + - sandboxes + verbs: + - create + - update + - patch + - delete +- apiGroups: + - extensions.agents.x-k8s.io + resources: + - sandboxtemplates + - sandboxclaims + verbs: + - create + - update + - patch + - delete {{- end -}} {{- if .Values.rbac.clusterScoped }} diff --git a/ui/src/app/actions/agents.ts b/ui/src/app/actions/agents.ts index f3389f7fa..3e60dcd1c 100644 --- a/ui/src/app/actions/agents.ts +++ b/ui/src/app/actions/agents.ts @@ -1,6 +1,6 @@ "use server"; -import { AgentSpec, BaseResponse } from "@/types"; +import { AgentSpec, BaseResponse, DeclarativeAgentSpec, SandboxAgent, SandboxAgentSpec } from "@/types"; import { Agent, AgentResponse, Tool } from "@/types"; import { revalidatePath } from "next/cache"; import { fetchApi, createErrorResponse } from "./utils"; @@ -159,6 +159,162 @@ function fromAgentFormDataToAgent(agentFormData: AgentFormData): Agent { return base as Agent; } +function fromAgentFormDataToSandboxAgent(agentFormData: AgentFormData): SandboxAgent { + if (agentFormData.byoImage?.trim()) { + return { + apiVersion: "kagent.dev/v1alpha2", + kind: "SandboxAgent", + metadata: { + name: agentFormData.name, + namespace: agentFormData.namespace || "", + }, + spec: { + type: "BYO", + description: agentFormData.description, + byo: { + deployment: { + image: agentFormData.byoImage || "", + cmd: agentFormData.byoCmd, + args: agentFormData.byoArgs, + replicas: agentFormData.replicas, + imagePullSecrets: agentFormData.imagePullSecrets, + volumes: agentFormData.volumes, + volumeMounts: agentFormData.volumeMounts, + labels: agentFormData.labels, + annotations: agentFormData.annotations, + env: agentFormData.env, + imagePullPolicy: agentFormData.imagePullPolicy, + serviceAccountName: agentFormData.serviceAccountName, + }, + }, + }, + }; + } + + const modelConfigName = agentFormData.modelName?.includes("/") + ? agentFormData.modelName.split("/").pop() || "" + : agentFormData.modelName; + + const agentNamespace = agentFormData.namespace || ""; + + const convertTools = (tools: Tool[]) => + tools.map((tool) => { + if (isMcpTool(tool)) { + const mcpServer = tool.mcpServer; + if (!mcpServer) { + throw new Error("MCP server not found"); + } + + let name = mcpServer.name; + let namespace: string | undefined = mcpServer.namespace; + + if (k8sRefUtils.isValidRef(mcpServer.name)) { + const parsed = k8sRefUtils.fromRef(mcpServer.name); + name = parsed.name; + } + + if (!namespace) { + namespace = agentNamespace; + } + + return { + type: "McpServer", + mcpServer: { + name, + namespace, + kind: mcpServer.kind, + apiGroup: mcpServer.apiGroup, + toolNames: mcpServer.toolNames, + }, + } as Tool; + } + + if (tool.type === "Agent") { + const ag = tool.agent; + if (!ag) { + throw new Error("Agent not found"); + } + + let name = ag.name; + let namespace: string | undefined = ag.namespace; + + if (k8sRefUtils.isValidRef(name)) { + const parsed = k8sRefUtils.fromRef(name); + name = parsed.name; + } + + if (!namespace) { + namespace = agentNamespace; + } + + return { + type: "Agent", + agent: { + name, + namespace, + kind: ag.kind || "Agent", + apiGroup: ag.apiGroup || "kagent.dev", + }, + } as Tool; + } + + console.warn("Unknown tool type:", tool); + return tool as Tool; + }); + + const decl: DeclarativeAgentSpec = { + systemMessage: agentFormData.systemPrompt || "", + modelConfig: modelConfigName || "", + stream: agentFormData.stream ?? true, + tools: convertTools(agentFormData.tools || []), + }; + + if (agentFormData.memory?.modelConfig) { + const memoryModel = agentFormData.memory.modelConfig; + const memoryModelName = k8sRefUtils.isValidRef(memoryModel) + ? k8sRefUtils.fromRef(memoryModel).name + : memoryModel; + decl.memory = { + modelConfig: memoryModelName, + ttlDays: agentFormData.memory.ttlDays, + }; + } + + if (agentFormData.context) { + decl.context = agentFormData.context; + } + + const trimmedSA = agentFormData.serviceAccountName?.trim(); + if (trimmedSA) { + decl.deployment = { + ...decl.deployment, + serviceAccountName: trimmedSA, + }; + } + + const spec: SandboxAgentSpec = { + type: "Declarative", + declarative: decl, + description: agentFormData.description, + }; + + if (agentFormData.skillRefs && agentFormData.skillRefs.length > 0) { + spec.skills = { + refs: agentFormData.skillRefs, + }; + } + + return { + apiVersion: "kagent.dev/v1alpha2", + kind: "SandboxAgent", + metadata: { + name: agentFormData.name, + namespace: agentFormData.namespace || "", + }, + spec, + }; +} + export async function getAgent(agentName: string, namespace: string): Promise> { try { const agentData = await fetchApi>(`/agents/${namespace}/${agentName}`); @@ -168,6 +324,34 @@ export async function getAgent(agentName: string, namespace: string): Promise { + const timeoutMs = opts?.timeoutMs ?? 120_000; + const intervalMs = opts?.intervalMs ?? 1500; + const deadline = Date.now() + timeoutMs; + + while (Date.now() < deadline) { + const res = await getAgent(agentName, namespace); + if (!res.data) { + return { ok: false, error: res.message || "Agent not found" }; + } + if (res.data.deploymentReady === true) { + return { ok: true }; + } + await new Promise((r) => setTimeout(r, intervalMs)); + } + return { + ok: false, + error: "Timed out waiting for sandbox agent to become ready", + }; +} + /** * Deletes a agent * @param agentName The agent name @@ -205,6 +389,31 @@ export async function createAgent(agentConfig: AgentFormData, update: boolean = } } + if (agentConfig.type === "Sandbox") { + const sandboxPayload = fromAgentFormDataToSandboxAgent(agentConfig); + const ns = sandboxPayload.metadata.namespace || ""; + const name = sandboxPayload.metadata.name; + const path = update ? `/sandboxagents/${ns}/${name}` : `/sandboxagents`; + const response = await fetchApi>(path, { + method: update ? "PUT" : "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(sandboxPayload), + }); + + const agent = response.data?.agent; + if (!agent) { + throw new Error("Failed to create sandbox agent"); + } + + const agentRef = k8sRefUtils.toRef(agent.metadata.namespace || "", agent.metadata.name); + + revalidatePath("/agents"); + revalidatePath(`/agents/${agentRef}/chat`); + return { message: response.message || "Successfully created agent", data: agent }; + } + const agentPayload = fromAgentFormDataToAgent(agentConfig); const response = await fetchApi>(`/agents`, { method: update ? "PUT" : "POST", diff --git a/ui/src/app/agents/[namespace]/[name]/chat/page.tsx b/ui/src/app/agents/[namespace]/[name]/chat/page.tsx index 78620d94e..1f03a7c80 100644 --- a/ui/src/app/agents/[namespace]/[name]/chat/page.tsx +++ b/ui/src/app/agents/[namespace]/[name]/chat/page.tsx @@ -1,8 +1,83 @@ +"use client"; + +import { use, useEffect, useState } from "react"; +import { useRouter } from "next/navigation"; import ChatInterface from "@/components/chat/ChatInterface"; -import { use } from 'react'; +import { getAgent } from "@/app/actions/agents"; +import { getSessionsForAgent, createSession } from "@/app/actions/sessions"; +import { getCurrentUserId } from "@/app/actions/utils"; +import { Loader2 } from "lucide-react"; +import type { Session } from "@/types"; + +function notifySidebarSession(agentRef: string, session: Session) { + if (typeof window === "undefined") return; + window.dispatchEvent( + new CustomEvent("new-session-created", { + detail: { agentRef, session }, + }) + ); +} -// This page component receives props (like params) from the Layout -export default function ChatAgentPage({ params }: { params: Promise<{ name: string, namespace: string }> }) { +export default function ChatAgentPage({ params }: { params: Promise<{ name: string; namespace: string }> }) { const { name, namespace } = use(params); + const router = useRouter(); + const [gate, setGate] = useState<"loading" | "ready">("loading"); + + useEffect(() => { + let cancelled = false; + (async () => { + try { + const agentRes = await getAgent(name, namespace); + if (cancelled) return; + if (agentRes.error || !agentRes.data) { + setGate("ready"); + return; + } + if (!agentRes.data.runInSandbox) { + setGate("ready"); + return; + } + const sessRes = await getSessionsForAgent(namespace, name); + if (cancelled) return; + if (sessRes.error || !sessRes.data) { + setGate("ready"); + return; + } + const list = sessRes.data; + const agentRef = `${namespace}/${name}`; + if (list.length >= 1) { + notifySidebarSession(agentRef, list[0]); + router.replace(`/agents/${namespace}/${name}/chat/${list[0].id}`); + return; + } + const created = await createSession({ + user_id: await getCurrentUserId(), + agent_ref: agentRef, + name: "Chat", + }); + if (cancelled) return; + if (!created.error && created.data) { + notifySidebarSession(agentRef, created.data); + router.replace(`/agents/${namespace}/${name}/chat/${created.data.id}`); + return; + } + } catch { + /* fall through to chat */ + } + setGate("ready"); + })(); + return () => { + cancelled = true; + }; + }, [name, namespace, router]); + + if (gate === "loading") { + return ( +
+ +
+ ); + } + return ; -} \ No newline at end of file +} diff --git a/ui/src/app/agents/new/page.tsx b/ui/src/app/agents/new/page.tsx index 93f257ea6..5ec7ff2cc 100644 --- a/ui/src/app/agents/new/page.tsx +++ b/ui/src/app/agents/new/page.tsx @@ -4,7 +4,8 @@ import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import { Textarea } from "@/components/ui/textarea"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { Brain, Loader2, Settings2, PlusCircle, Trash2, Layers } from "lucide-react"; +import { Brain, Info, Loader2, Settings2, PlusCircle, Trash2, Layers } from "lucide-react"; +import { formAgentTypeFromApi, formUsesByoSections, formUsesDeclarativeSections } from "@/lib/agentFormLayout"; import { ModelConfig, AgentType, ContextConfig } from "@/types"; import { SystemPromptSection } from "@/components/create/SystemPromptSection"; import { ModelSelectionSection } from "@/components/create/ModelSelectionSection"; @@ -23,7 +24,7 @@ import { NamespaceCombobox } from "@/components/NamespaceCombobox"; import { Label } from "@/components/ui/label"; import { Checkbox } from "@/components/ui/checkbox"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; - +import { Alert, AlertDescription } from "@/components/ui/alert"; interface ValidationErrors { name?: string; namespace?: string; @@ -117,6 +118,9 @@ function AgentPageContent({ isEditMode, agentName, agentNamespace }: AgentPageCo errors: {}, }); + const useDeclarativeAgentFields = formUsesDeclarativeSections(state.agentType, state.byoImage); + const showByoFields = formUsesByoSections(state.agentType, state.byoImage); + // Fetch existing agent data if in edit mode useEffect(() => { const fetchAgentData = async () => { @@ -138,26 +142,27 @@ function AgentPageContent({ isEditMode, agentName, agentNamespace }: AgentPageCo name: agent.metadata.name || "", namespace: agent.metadata.namespace || "", description: agent.spec?.description || "", - agentType: agent.spec.type, + agentType: formAgentTypeFromApi(agent.spec.type, agentResponse.runInSandbox), }; - // v1alpha2: read type and split specs - if (agent.spec.type === "Declarative") { - const memorySpec = agent.spec?.memory; + const useDeclarativeForm = agent.spec.type === "Declarative"; + if (useDeclarativeForm) { + const decl = agent.spec?.declarative; + const memorySpec = decl?.memory ?? agent.spec?.memory; const memoryModelConfig = memorySpec?.modelConfig ? `${agent.metadata.namespace}/${memorySpec.modelConfig}` : ""; setState(prev => ({ ...prev, ...baseUpdates, - systemPrompt: agent.spec?.declarative?.systemMessage || "", - selectedTools: (agent.spec?.declarative?.tools && agentResponse.tools) ? agentResponse.tools : [], + systemPrompt: decl?.systemMessage || "", + selectedTools: (decl?.tools && agentResponse.tools) ? agentResponse.tools : [], selectedModel: agentResponse.modelConfigRef ? { model: agentResponse.model || "default-model-config", ref: agentResponse.modelConfigRef } : null, skillRefs: (agent.spec?.skills?.refs && agent.spec.skills.refs.length > 0) ? agent.spec.skills.refs : [""], - stream: agent.spec?.declarative?.stream ?? false, + stream: decl?.stream ?? false, selectedMemoryModel: memoryModelConfig ? { model: memorySpec?.modelConfig || "", ref: memoryModelConfig } : null, memoryTtlDays: memorySpec?.ttlDays ? String(memorySpec.ttlDays) : "", - contextConfig: agent.spec?.declarative?.context, - serviceAccountName: agent.spec?.declarative?.deployment?.serviceAccountName || "", + contextConfig: decl?.context, + serviceAccountName: decl?.deployment?.serviceAccountName || "", byoImage: "", byoCmd: "", byoArgs: "", @@ -235,7 +240,7 @@ function AgentPageContent({ isEditMode, agentName, agentNamespace }: AgentPageCo const newErrors = validateAgentData(formData); - if (state.agentType === "Declarative" && state.skillRefs && state.skillRefs.length > 0) { + if (useDeclarativeAgentFields && state.skillRefs && state.skillRefs.length > 0) { // Filter out empty/whitespace entries first - if all are empty, treat as "no skills" const nonEmptyRefs = state.skillRefs.filter(ref => ref.trim()); @@ -318,8 +323,8 @@ function AgentPageContent({ isEditMode, agentName, agentNamespace }: AgentPageCo try { setState(prev => ({ ...prev, isSubmitting: true })); - if (state.agentType === "Declarative" && !state.selectedModel) { - throw new Error("Model is required to create a declarative agent."); + if (useDeclarativeAgentFields && !state.selectedModel) { + throw new Error("Model is required for this agent type."); } const memoryEnabled = !!(state.selectedMemoryModel?.ref || state.memoryTtlDays); @@ -333,14 +338,14 @@ function AgentPageContent({ isEditMode, agentName, agentNamespace }: AgentPageCo modelName: state.selectedModel?.ref || "", stream: state.stream, tools: state.selectedTools, - skillRefs: state.agentType === "Declarative" ? (state.skillRefs || []).filter(ref => ref.trim()) : undefined, - memory: state.agentType === "Declarative" && memoryEnabled + skillRefs: useDeclarativeAgentFields ? (state.skillRefs || []).filter(ref => ref.trim()) : undefined, + memory: useDeclarativeAgentFields && memoryEnabled ? { modelConfig: state.selectedMemoryModel?.ref || "", ttlDays: state.memoryTtlDays ? parseInt(state.memoryTtlDays, 10) : undefined, } : undefined, - context: state.agentType === "Declarative" ? state.contextConfig : undefined, + context: useDeclarativeAgentFields ? state.contextConfig : undefined, // BYO byoImage: state.byoImage, byoCmd: state.byoCmd || undefined, @@ -454,13 +459,17 @@ function AgentPageContent({ isEditMode, agentName, agentNamespace }: AgentPageCo

- Choose declarative (uses a model) or BYO (bring your own containerized agent). + Declarative or Sandbox: model, tools, and prompts below. BYO: your own container image.

@@ -490,7 +500,7 @@ function AgentPageContent({ isEditMode, agentName, agentNamespace }: AgentPageCo {state.errors.description &&

{state.errors.description}

}
- {state.agentType === "Declarative" && ( + {useDeclarativeAgentFields && ( <> )} - {state.agentType === "BYO" && ( + {showByoFields && (
@@ -704,7 +714,7 @@ function AgentPageContent({ isEditMode, agentName, agentNamespace }: AgentPageCo )} - {state.agentType === "Declarative" && ( + {useDeclarativeAgentFields && ( <> diff --git a/ui/src/components/AgentCard.tsx b/ui/src/components/AgentCard.tsx index e773306af..738ad0bc9 100644 --- a/ui/src/components/AgentCard.tsx +++ b/ui/src/components/AgentCard.tsx @@ -36,7 +36,7 @@ export function AgentCard({ agentResponse: { agent, model, modelProvider, deploy const isBYO = agent.spec?.type === "BYO"; const byoImage = isBYO ? agent.spec?.byo?.deployment?.image : undefined; - const isReady = deploymentReady && accepted; + const isReady = accepted && deploymentReady; const handleEditClick = (e: React.MouseEvent) => { e.preventDefault(); diff --git a/ui/src/components/AgentsProvider.tsx b/ui/src/components/AgentsProvider.tsx index f812e1d4b..fd3d7ed2b 100644 --- a/ui/src/components/AgentsProvider.tsx +++ b/ui/src/components/AgentsProvider.tsx @@ -3,8 +3,19 @@ import React, { createContext, useContext, useState, useEffect, ReactNode, useCallback } from "react"; import { getAgent as getAgentAction, createAgent, getAgents } from "@/app/actions/agents"; import { getTools } from "@/app/actions/tools"; -import type { Agent, Tool, AgentResponse, BaseResponse, ModelConfig, ToolsResponse, AgentType, EnvVar, ContextConfig } from "@/types"; +import type { + Agent, + Tool, + AgentResponse, + BaseResponse, + ModelConfig, + ToolsResponse, + AgentType, + EnvVar, + ContextConfig, +} from "@/types"; import { getModelConfigs } from "@/app/actions/modelConfigs"; +import { formUsesByoSections, formUsesDeclarativeSections } from "@/lib/agentFormLayout"; import { isResourceNameValid } from "@/lib/utils"; export interface ValidationErrors { @@ -167,7 +178,8 @@ export function AgentsProvider({ children }: AgentsProviderProps) { } const type = data.type || "Declarative"; - if (type === "Declarative") { + const byoImage = data.byoImage; + if (formUsesDeclarativeSections(type, byoImage)) { if (data.systemPrompt !== undefined && !data.systemPrompt.trim()) { errors.systemPrompt = "Agent instructions are required"; } @@ -183,7 +195,7 @@ export function AgentsProvider({ children }: AgentsProviderProps) { errors.memoryTtl = "TTL must be at least 1 day"; } } - } else if (type === "BYO") { + } else if (formUsesByoSections(type, byoImage)) { if (!data.byoImage || data.byoImage.trim() === "") { errors.model = "Container image is required"; } diff --git a/ui/src/components/chat/ChatAgentContext.tsx b/ui/src/components/chat/ChatAgentContext.tsx new file mode 100644 index 000000000..6e4cb8d45 --- /dev/null +++ b/ui/src/components/chat/ChatAgentContext.tsx @@ -0,0 +1,37 @@ +"use client"; + +import { createContext, useContext, type ReactNode } from "react"; +import type { AgentType } from "@/types"; + +type ChatAgentRuntimeContextValue = { + agentType: AgentType; + runInSandbox: boolean; +}; + +const ChatAgentRuntimeContext = createContext(undefined); + +export function ChatAgentProvider({ + agentType, + runInSandbox = false, + children, +}: { + agentType: AgentType; + runInSandbox?: boolean; + children: ReactNode; +}) { + return ( + + {children} + + ); +} + +/** Agent type for the current chat route (from layout). Undefined outside provider. */ +export function useChatAgentType(): AgentType | undefined { + return useContext(ChatAgentRuntimeContext)?.agentType; +} + +/** SandboxAgent workloads (API `runInSandbox`). */ +export function useChatRunInSandbox(): boolean { + return useContext(ChatAgentRuntimeContext)?.runInSandbox ?? false; +} diff --git a/ui/src/components/chat/ChatInterface.tsx b/ui/src/components/chat/ChatInterface.tsx index 9730f0e2a..22282c378 100644 --- a/ui/src/components/chat/ChatInterface.tsx +++ b/ui/src/components/chat/ChatInterface.tsx @@ -19,10 +19,12 @@ import SessionTokenStatsDisplay from "@/components/chat/TokenStats"; import type { TokenStats, Session, ChatStatus, ToolDecision } from "@/types"; import StatusDisplay from "./StatusDisplay"; import { createSession, getSessionTasks, checkSessionExists } from "@/app/actions/sessions"; +import { waitForSandboxAgentReady } from "@/app/actions/agents"; import { toast } from "sonner"; import { useRouter } from "next/navigation"; import { createMessageHandlers, extractMessagesFromTasks, extractApprovalMessagesFromTasks, extractTokenStatsFromTasks, createMessage, ADKMetadata, ProcessedToolCallData } from "@/lib/messageHandlers"; import { kagentA2AClient } from "@/lib/a2aClient"; +import { useChatAgentType, useChatRunInSandbox } from "@/components/chat/ChatAgentContext"; import { v4 as uuidv4 } from "uuid"; import { getStatusPlaceholder } from "@/lib/statusUtils"; import { Message, DataPart } from "@a2a-js/sdk"; @@ -35,6 +37,8 @@ interface ChatInterfaceProps { } export default function ChatInterface({ selectedAgentName, selectedNamespace, selectedSession, sessionId }: ChatInterfaceProps) { + const agentType = useChatAgentType(); + const runInSandbox = useChatRunInSandbox(); const router = useRouter(); const containerRef = useRef(null); const [currentInputMessage, setCurrentInputMessage] = useState(""); @@ -277,6 +281,7 @@ export default function ChatInterface({ selectedAgentName, selectedNamespace, se await streamA2AMessage(a2aMessage, { errorLabel: "Streaming failed", onError: () => setCurrentInputMessage(userMessageText), + sessionIdForWait: currentSessionId, }); } catch (error) { console.error("Error sending message or creating session:", error); @@ -297,12 +302,37 @@ export default function ChatInterface({ selectedAgentName, selectedNamespace, se errorLabel?: string; onError?: () => void; onFinally?: () => void; + /** Session id for readiness polling when React state may lag. */ + sessionIdForWait?: string; }, ) => { abortControllerRef.current = new AbortController(); isFirstAssistantChunkRef.current = true; try { + const sid = opts?.sessionIdForWait ?? session?.id ?? sessionId; + if (runInSandbox && !sid) { + throw new Error("Session is required before messaging a Sandbox agent"); + } + if (runInSandbox && sid) { + let loadingToast: string | number | undefined; + const slowToast = setTimeout(() => { + loadingToast = toast.loading("Starting sandbox workload…"); + }, 600); + try { + const ready = await waitForSandboxAgentReady(selectedAgentName, selectedNamespace); + clearTimeout(slowToast); + if (loadingToast !== undefined) toast.dismiss(loadingToast); + if (!ready.ok) { + throw new Error(ready.error ?? "Sandbox workload not ready"); + } + } catch (waitErr) { + clearTimeout(slowToast); + if (loadingToast !== undefined) toast.dismiss(loadingToast); + throw waitErr; + } + } + isCreatingSessionRef.current = false; const sendParams = { message: a2aMessage, metadata: {} }; const stream = await kagentA2AClient.sendMessageStream( selectedNamespace, @@ -449,6 +479,7 @@ export default function ChatInterface({ selectedAgentName, selectedNamespace, se await streamA2AMessage(a2aMessage, { errorLabel: "Approval failed", + sessionIdForWait: currentSessionId, onFinally: () => { // Ensure chat state resets after approval stream ends setIsStreaming(false); @@ -587,6 +618,7 @@ export default function ChatInterface({ selectedAgentName, selectedNamespace, se streamA2AMessage(a2aMessage, { errorLabel: "Ask user response failed", + sessionIdForWait: currentSessionId, onFinally: () => { setIsStreaming(false); setStreamingContent(""); diff --git a/ui/src/components/chat/ChatLayoutUI.tsx b/ui/src/components/chat/ChatLayoutUI.tsx index 10561dfcb..26756f4a5 100644 --- a/ui/src/components/chat/ChatLayoutUI.tsx +++ b/ui/src/components/chat/ChatLayoutUI.tsx @@ -1,11 +1,13 @@ "use client"; import React, { useState, useEffect, useMemo } from "react"; +import { usePathname } from "next/navigation"; import SessionsSidebar from "@/components/sidebars/SessionsSidebar"; import { AgentDetailsSidebar } from "@/components/sidebars/AgentDetailsSidebar"; import { getSessionsForAgent } from "@/app/actions/sessions"; import { AgentResponse, Session, RemoteMCPServerResponse, ToolsResponse } from "@/types"; import { toast } from "sonner"; +import { ChatAgentProvider } from "@/components/chat/ChatAgentContext"; interface ChatLayoutUIProps { agentName: string; @@ -24,6 +26,7 @@ export default function ChatLayoutUI({ allTools, children }: ChatLayoutUIProps) { + const pathname = usePathname(); const [sessions, setSessions] = useState([]); const [isLoadingSessions, setIsLoadingSessions] = useState(true); @@ -66,7 +69,8 @@ export default function ChatLayoutUI({ } }; refreshSessions(); - }, [agentName, namespace]); + // Same agent may navigate /chat → /chat/:id (e.g. Sandbox); refetch so the sidebar lists the session. + }, [agentName, namespace, pathname]); useEffect(() => { // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -102,7 +106,12 @@ export default function ChatLayoutUI({ isLoadingSessions={isLoadingSessions} />
- {children} + + {children} +
@@ -258,14 +258,14 @@ export function AgentDetailsSidebar({ selectedAgentName, currentAgent, allTools

{selectedTeam?.agent.spec.description}

- {isDeclarativeAgent &&( + {isDeclarativeLikeAgent && ( Tools & Agents {selectedTeam && renderAgentTools(selectedTeam.tools)} )} - {isDeclarativeAgent && selectedTeam?.agent.spec?.skills?.refs && selectedTeam.agent.spec.skills.refs.length > 0 && ( + {isDeclarativeLikeAgent && selectedTeam?.agent.spec?.skills?.refs && selectedTeam.agent.spec.skills.refs.length > 0 && (
Skills diff --git a/ui/src/components/sidebars/AgentSwitcher.tsx b/ui/src/components/sidebars/AgentSwitcher.tsx index 4856cdaca..7516bf7db 100644 --- a/ui/src/components/sidebars/AgentSwitcher.tsx +++ b/ui/src/components/sidebars/AgentSwitcher.tsx @@ -31,8 +31,9 @@ export function AgentSwitcher({ currentAgent, allAgents }: AgentSwitcherProps) { selectedAgent.agent.metadata.name ); - // We don't want to show agents that are not ready or accepted - const filteredAgentResponses = agentResponses.filter(({ deploymentReady, accepted }) => deploymentReady && accepted); + const filteredAgentResponses = agentResponses.filter( + ({ deploymentReady, accepted }) => accepted && deploymentReady + ); return ( diff --git a/ui/src/components/sidebars/ChatItem.tsx b/ui/src/components/sidebars/ChatItem.tsx index 6b01371ed..76b7ebdfe 100644 --- a/ui/src/components/sidebars/ChatItem.tsx +++ b/ui/src/components/sidebars/ChatItem.tsx @@ -23,9 +23,11 @@ interface ChatItemProps { sessionName?: string; onDownload?: (sessionId: string) => Promise; createdAt?: string; + /** When true, omit delete (e.g. Sandbox single-session agents). */ + hideDelete?: boolean; } -const ChatItem = ({ sessionId, agentName, agentNamespace, onDelete, sessionName, onDownload, createdAt }: ChatItemProps) => { +const ChatItem = ({ sessionId, agentName, agentNamespace, onDelete, sessionName, onDownload, createdAt, hideDelete }: ChatItemProps) => { const title = sessionName || "Untitled"; // Format timestamp based on how recent it is @@ -81,6 +83,7 @@ const ChatItem = ({ sessionId, agentName, agentNamespace, onDelete, sessionName, Download + {!hideDelete && ( e.preventDefault()} className="p-0"> @@ -104,6 +107,7 @@ const ChatItem = ({ sessionId, agentName, agentNamespace, onDelete, sessionName, + )} diff --git a/ui/src/components/sidebars/EmptyState.tsx b/ui/src/components/sidebars/EmptyState.tsx index 193aa86f6..5cfec26bc 100644 --- a/ui/src/components/sidebars/EmptyState.tsx +++ b/ui/src/components/sidebars/EmptyState.tsx @@ -2,16 +2,30 @@ import { MessageCircleMore, MessageSquare } from "lucide-react"; import { SidebarMenuButton } from "../ui/sidebar"; import Link from "next/link"; -const EmptyState = () => ( -
-
- +export interface EmptyStateProps { + /** Sandbox agents: one persistent chat; hide “new chat” framing. */ + variant?: "default" | "singleChat"; +} + +const EmptyState = ({ variant = "default" }: EmptyStateProps) => { + const isSingle = variant === "singleChat"; + return ( +
+
+ +
+

+ {isSingle ? "Your conversation" : "No chats yet"} +

+

+ {isSingle + ? "This agent keeps a single chat. Messages and history appear here after you send something in the panel." + : "Start a new conversation to begin using kagent"} +

+ {!isSingle && }
-

No chats yet

-

{"Start a new conversation to begin using kagent"}

- -
-); + ); +}; interface ActionButtonsProps { hasSessions?: boolean; diff --git a/ui/src/components/sidebars/GroupedChats.tsx b/ui/src/components/sidebars/GroupedChats.tsx index 6a11ca824..6e63a72f0 100644 --- a/ui/src/components/sidebars/GroupedChats.tsx +++ b/ui/src/components/sidebars/GroupedChats.tsx @@ -13,9 +13,13 @@ interface GroupedChatsProps { agentName: string; agentNamespace: string; sessions: Session[]; + /** Sandbox agents use a single persistent chat; hide "New Chat". */ + hideNewChat?: boolean; + /** Sandbox agents cannot delete their only session from the UI. */ + hideSessionDelete?: boolean; } -export default function GroupedChats({ agentName, agentNamespace, sessions }: GroupedChatsProps) { +export default function GroupedChats({ agentName, agentNamespace, sessions, hideNewChat, hideSessionDelete }: GroupedChatsProps) { // Local state to manage sessions for immediate UI updates const [localSessions, setLocalSessions] = useState(sessions); @@ -106,6 +110,7 @@ export default function GroupedChats({ agentName, agentNamespace, sessions }: Gr return ( <> + {!hideNewChat && (
+ )} {hasNoSessions || localSessions.length === 0 ? ( - + ) : ( <> - {groupedChats.today.length > 0 && onDeleteClick(sessionId)} onDownloadSession={(sessionId) => onDownloadClick(sessionId)} />} + {groupedChats.today.length > 0 && onDeleteClick(sessionId)} onDownloadSession={(sessionId) => onDownloadClick(sessionId)} hideSessionDelete={hideSessionDelete} />} {groupedChats.yesterday.length > 0 && ( - onDeleteClick(sessionId)} onDownloadSession={(sessionId) => onDownloadClick(sessionId)} /> + onDeleteClick(sessionId)} onDownloadSession={(sessionId) => onDownloadClick(sessionId)} hideSessionDelete={hideSessionDelete} /> )} - {groupedChats.older.length > 0 && onDeleteClick(sessionId)} onDownloadSession={(sessionId) => onDownloadClick(sessionId)} />} + {groupedChats.older.length > 0 && onDeleteClick(sessionId)} onDownloadSession={(sessionId) => onDownloadClick(sessionId)} hideSessionDelete={hideSessionDelete} />} )} diff --git a/ui/src/components/sidebars/SessionGroup.tsx b/ui/src/components/sidebars/SessionGroup.tsx index 6a952ab6d..e997f3260 100644 --- a/ui/src/components/sidebars/SessionGroup.tsx +++ b/ui/src/components/sidebars/SessionGroup.tsx @@ -12,10 +12,11 @@ interface ChatGroupProps { onDownloadSession: (sessionId: string) => Promise; agentName: string; agentNamespace: string; + hideSessionDelete?: boolean; } // The sessions are grouped by today, yesterday, and older -const ChatGroup = ({ title, sessions, onDeleteSession, onDownloadSession, agentName, agentNamespace }: ChatGroupProps) => { +const ChatGroup = ({ title, sessions, onDeleteSession, onDownloadSession, agentName, agentNamespace, hideSessionDelete }: ChatGroupProps) => { return ( @@ -29,7 +30,7 @@ const ChatGroup = ({ title, sessions, onDeleteSession, onDownloadSession, agentN {sessions.map((session) => ( - + ))} diff --git a/ui/src/components/sidebars/SessionsSidebar.tsx b/ui/src/components/sidebars/SessionsSidebar.tsx index c64eb655e..ddd57a62e 100644 --- a/ui/src/components/sidebars/SessionsSidebar.tsx +++ b/ui/src/components/sidebars/SessionsSidebar.tsx @@ -37,7 +37,13 @@ export default function SessionsSidebar({ Loading sessions...
) : ( - + )} diff --git a/ui/src/lib/a2aBackendFetch.ts b/ui/src/lib/a2aBackendFetch.ts new file mode 100644 index 000000000..84123373f --- /dev/null +++ b/ui/src/lib/a2aBackendFetch.ts @@ -0,0 +1,41 @@ +import type { NextRequest } from "next/server"; +import { getBackendUrl } from "@/lib/utils"; +import { getCurrentUserId } from "@/app/actions/utils"; + +/** + * Server-side fetch to kagent /api/a2a/... Must mirror {@link fetchApi}: same user_id + * query param and forwarded auth so DB session lookups match session creation. + */ +export async function fetchA2ABackend( + request: NextRequest, + pathAfterApi: string, + body: unknown +): Promise { + const base = getBackendUrl().replace(/\/$/, ""); + const path = pathAfterApi.startsWith("/") ? pathAfterApi : `/${pathAfterApi}`; + const url = new URL(`${base}${path}`); + const userId = await getCurrentUserId(); + if (!url.searchParams.has("user_id")) { + url.searchParams.set("user_id", userId); + } + + const headers: Record = { + "Content-Type": "application/json", + Accept: "text/event-stream", + "Cache-Control": "no-cache", + Connection: "keep-alive", + "User-Agent": "kagent-ui", + }; + const auth = request.headers.get("authorization"); + if (auth) headers.Authorization = auth; + const cookie = request.headers.get("cookie"); + if (cookie) headers.Cookie = cookie; + const xUser = request.headers.get("x-user-id"); + if (xUser) headers["X-User-Id"] = xUser; + + return fetch(url.toString(), { + method: "POST", + headers, + body: JSON.stringify(body), + }); +} diff --git a/ui/src/lib/a2aClient.ts b/ui/src/lib/a2aClient.ts index 1942f4840..95848dcd0 100644 --- a/ui/src/lib/a2aClient.ts +++ b/ui/src/lib/a2aClient.ts @@ -47,9 +47,6 @@ export class KagentA2AClient { signal?: AbortSignal ): Promise> { const request = this.createStreamingRequest(params); - // This redirects to the Next.js API route - // Note that this route CAN'T be the same - // as the routes on the backend. const proxyUrl = `/a2a/${namespace}/${agentName}`; const response = await fetch(proxyUrl, { diff --git a/ui/src/lib/agentFormLayout.ts b/ui/src/lib/agentFormLayout.ts new file mode 100644 index 000000000..5ea9e4258 --- /dev/null +++ b/ui/src/lib/agentFormLayout.ts @@ -0,0 +1,33 @@ +import type { AgentType } from "@/types"; + +/** Sandbox create flow + non-empty image → BYO-shaped sandbox workload. */ +export function isSandboxByoImageChosen( + agentType: AgentType, + byoImage: string | undefined | null, +): boolean { + return agentType === "Sandbox" && !!byoImage?.trim(); +} + +/** Model, tools, prompts (and related) sections. */ +export function formUsesDeclarativeSections( + agentType: AgentType, + byoImage: string | undefined | null, +): boolean { + return agentType === "Declarative" || (agentType === "Sandbox" && !isSandboxByoImageChosen(agentType, byoImage)); +} + +/** Container image and deployment-style fields. */ +export function formUsesByoSections( + agentType: AgentType, + byoImage: string | undefined | null, +): boolean { + return agentType === "BYO" || isSandboxByoImageChosen(agentType, byoImage); +} + +/** Create-agent form type from GET /agents response (SandboxAgent → form "Sandbox"). */ +export function formAgentTypeFromApi( + specType: AgentType, + runInSandbox: boolean | undefined, +): AgentType { + return runInSandbox ? "Sandbox" : specType; +} diff --git a/ui/src/types/index.ts b/ui/src/types/index.ts index e9eee4201..1c7240eb2 100644 --- a/ui/src/types/index.ts +++ b/ui/src/types/index.ts @@ -229,13 +229,36 @@ export interface McpServerTool extends TypedLocalReference { requireApproval?: string[]; } -export type AgentType = "Declarative" | "BYO"; +export type AgentType = "Declarative" | "BYO" | "Sandbox"; export interface SkillForAgent { insecureSkipVerify?: boolean; refs?: string[]; } +/** Workload discriminator for SandboxAgent; same as Agent except Sandbox (the kind implies sandbox runtime). */ +export type SandboxAgentWorkloadType = "Declarative" | "BYO"; + +/** + * Spec for a SandboxAgent: same workload fields as Agent.spec (Declarative or BYO). + * Isolation comes from the SandboxAgent kind (SandboxTemplate + SandboxClaim), not from extra fields here. + */ +export interface SandboxAgentSpec { + type: SandboxAgentWorkloadType; + declarative?: DeclarativeAgentSpec; + byo?: BYOAgentSpec; + description?: string; + skills?: SkillForAgent; +} + +/** Kubernetes SandboxAgent CRD (kagent.dev/v1alpha2). */ +export interface SandboxAgent { + apiVersion?: string; + kind?: string; + metadata: ResourceMetadata; + spec: SandboxAgentSpec; +} + export interface AgentSpec { type: AgentType; declarative?: DeclarativeAgentSpec; @@ -258,6 +281,8 @@ export interface DeclarativeAgentSpec { a2aConfig?: A2AConfig; context?: ContextConfig; deployment?: DeclarativeDeploymentSpec; + /** Long-term memory (same shape as Kubernetes declarative spec). */ + memory?: MemorySpec; } export interface ContextConfig { @@ -332,6 +357,8 @@ export interface AgentResponse { tools: Tool[]; deploymentReady: boolean; accepted: boolean; + /** Set when the workload is reconciled as a SandboxAgent. */ + runInSandbox?: boolean; } export interface RemoteMCPServer { @@ -451,7 +478,7 @@ export interface AgentMemory { // --------------------------------------------------------------------------- // HITL (Human-in-the-Loop) types // -// These mirror the Python models in kagent-core/a2a/_hitl_utils.py and describe the +// These mirror the Python models in kagent-core/a2a/_hitl_utils.py and describe the // A2A - UI wire format for request and decision paths in HITL flow. // --------------------------------------------------------------------------- From 1b82760488104518e031e21a6e150933587c6dc4 Mon Sep 17 00:00:00 2001 From: Peter Jausovec Date: Thu, 9 Apr 2026 14:14:12 +0200 Subject: [PATCH 02/13] reuse AgentSpec Signed-off-by: Peter Jausovec --- .../crd/bases/kagent.dev_sandboxagents.yaml | 20 ++++++----- go/api/v1alpha2/agent_types.go | 26 -------------- go/api/v1alpha2/sandboxagent_types.go | 4 +-- go/api/v1alpha2/zz_generated.deepcopy.go | 35 ------------------- .../agent/adk_api_translator_test.go | 2 +- .../translator/agent/sandbox_agent_view.go | 9 +---- .../httpserver/handlers/agents_test.go | 2 +- .../templates/kagent.dev_sandboxagents.yaml | 20 ++++++----- ui/src/app/actions/agents.ts | 4 +-- ui/src/types/index.ts | 19 ++-------- 10 files changed, 31 insertions(+), 110 deletions(-) diff --git a/go/api/config/crd/bases/kagent.dev_sandboxagents.yaml b/go/api/config/crd/bases/kagent.dev_sandboxagents.yaml index 142d92c41..f8903d9af 100644 --- a/go/api/config/crd/bases/kagent.dev_sandboxagents.yaml +++ b/go/api/config/crd/bases/kagent.dev_sandboxagents.yaml @@ -47,17 +47,15 @@ spec: metadata: type: object spec: - description: |- - SandboxAgentSpec defines the desired state of a SandboxAgent. Workload fields match Agent.spec - (Declarative or BYO). The kind selects sandbox reconciliation (SandboxTemplate + SandboxClaim) instead of a Deployment. + description: AgentSpec defines the desired state of Agent. properties: allowedNamespaces: description: |- - AllowedNamespaces defines which namespaces are allowed to reference this resource. - This mechanism provides a bidirectional handshake for cross-namespace references, - following the pattern used by Gateway API for cross-namespace route attachments. - - By default (when not specified), only references from the same namespace are allowed. + AllowedNamespaces defines which namespaces are allowed to reference this Agent as a tool. + This follows the Gateway API pattern for cross-namespace route attachments. + If not specified, only Agents in the same namespace can reference this Agent as a tool. + This field only applies when this Agent is used as a tool by another Agent. + See: https://gateway-api.sigs.k8s.io/guides/multiple-ns/#cross-namespace-routing properties: from: default: Same @@ -7819,6 +7817,9 @@ spec: description: type: string skills: + description: |- + Skills to load into the agent. They will be pulled from the specified container images. + and made available to the agent under the `/skills` folder. properties: gitAuthSecretRef: description: |- @@ -8121,7 +8122,8 @@ spec: rule: has(self.type) - message: type must be either Declarative or BYO rule: self.type == 'Declarative' || self.type == 'BYO' - - message: declarative or byo must match type + - message: declarative must be specified if type is Declarative, or byo + must be specified if type is BYO rule: (self.type == 'Declarative' && has(self.declarative)) || (self.type == 'BYO' && has(self.byo)) status: diff --git a/go/api/v1alpha2/agent_types.go b/go/api/v1alpha2/agent_types.go index 4e4b1e460..e41ab979b 100644 --- a/go/api/v1alpha2/agent_types.go +++ b/go/api/v1alpha2/agent_types.go @@ -78,32 +78,6 @@ type AgentSpec struct { AllowedNamespaces *AllowedNamespaces `json:"allowedNamespaces,omitempty"` } -// SandboxAgentSpec defines the desired state of a SandboxAgent. Workload fields match Agent.spec -// (Declarative or BYO). The kind selects sandbox reconciliation (SandboxTemplate + SandboxClaim) instead of a Deployment. -// -// +kubebuilder:validation:XValidation:message="type must be specified",rule="has(self.type)" -// +kubebuilder:validation:XValidation:message="type must be either Declarative or BYO",rule="self.type == 'Declarative' || self.type == 'BYO'" -// +kubebuilder:validation:XValidation:message="declarative or byo must match type",rule="(self.type == 'Declarative' && has(self.declarative)) || (self.type == 'BYO' && has(self.byo))" -type SandboxAgentSpec struct { - // +kubebuilder:validation:Enum=Declarative;BYO - // +kubebuilder:default=Declarative - Type AgentType `json:"type"` - - // +optional - BYO *BYOAgentSpec `json:"byo,omitempty"` - // +optional - Declarative *DeclarativeAgentSpec `json:"declarative,omitempty"` - - // +optional - Description string `json:"description,omitempty"` - - // +optional - Skills *SkillForAgent `json:"skills,omitempty"` - - // +optional - AllowedNamespaces *AllowedNamespaces `json:"allowedNamespaces,omitempty"` -} - // +kubebuilder:validation:AtLeastOneOf=refs,gitRefs type SkillForAgent struct { // Fetch images insecurely from registries (allowing HTTP and skipping TLS verification). diff --git a/go/api/v1alpha2/sandboxagent_types.go b/go/api/v1alpha2/sandboxagent_types.go index cf988586e..29646469d 100644 --- a/go/api/v1alpha2/sandboxagent_types.go +++ b/go/api/v1alpha2/sandboxagent_types.go @@ -29,8 +29,8 @@ type SandboxAgent struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - Spec SandboxAgentSpec `json:"spec,omitempty"` - Status AgentStatus `json:"status,omitempty"` + Spec AgentSpec `json:"spec,omitempty"` + Status AgentStatus `json:"status,omitempty"` } // +kubebuilder:object:root=true diff --git a/go/api/v1alpha2/zz_generated.deepcopy.go b/go/api/v1alpha2/zz_generated.deepcopy.go index fe085fde2..ed186ec34 100644 --- a/go/api/v1alpha2/zz_generated.deepcopy.go +++ b/go/api/v1alpha2/zz_generated.deepcopy.go @@ -1169,41 +1169,6 @@ func (in *SandboxAgentList) DeepCopyObject() runtime.Object { return nil } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *SandboxAgentSpec) DeepCopyInto(out *SandboxAgentSpec) { - *out = *in - if in.BYO != nil { - in, out := &in.BYO, &out.BYO - *out = new(BYOAgentSpec) - (*in).DeepCopyInto(*out) - } - if in.Declarative != nil { - in, out := &in.Declarative, &out.Declarative - *out = new(DeclarativeAgentSpec) - (*in).DeepCopyInto(*out) - } - if in.Skills != nil { - in, out := &in.Skills, &out.Skills - *out = new(SkillForAgent) - (*in).DeepCopyInto(*out) - } - if in.AllowedNamespaces != nil { - in, out := &in.AllowedNamespaces, &out.AllowedNamespaces - *out = new(AllowedNamespaces) - (*in).DeepCopyInto(*out) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SandboxAgentSpec. -func (in *SandboxAgentSpec) DeepCopy() *SandboxAgentSpec { - if in == nil { - return nil - } - out := new(SandboxAgentSpec) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SecretReference) DeepCopyInto(out *SecretReference) { *out = *in diff --git a/go/core/internal/controller/translator/agent/adk_api_translator_test.go b/go/core/internal/controller/translator/agent/adk_api_translator_test.go index 3988fed4c..888bacda1 100644 --- a/go/core/internal/controller/translator/agent/adk_api_translator_test.go +++ b/go/core/internal/controller/translator/agent/adk_api_translator_test.go @@ -1365,7 +1365,7 @@ func Test_AdkApiTranslator_SandboxAgentView_BYOUsesSandboxTemplateAndClaim(t *te cmd := "/app/run" sa := &v1alpha2.SandboxAgent{ ObjectMeta: metav1.ObjectMeta{Name: "byo-sb", Namespace: "sandbox-ns"}, - Spec: v1alpha2.SandboxAgentSpec{ + Spec: v1alpha2.AgentSpec{ Type: v1alpha2.AgentType_BYO, BYO: &v1alpha2.BYOAgentSpec{ Deployment: &v1alpha2.ByoDeploymentSpec{ diff --git a/go/core/internal/controller/translator/agent/sandbox_agent_view.go b/go/core/internal/controller/translator/agent/sandbox_agent_view.go index d7ced6c28..cebfc10fd 100644 --- a/go/core/internal/controller/translator/agent/sandbox_agent_view.go +++ b/go/core/internal/controller/translator/agent/sandbox_agent_view.go @@ -12,14 +12,7 @@ func AgentViewFromSandboxAgent(sa *v1alpha2.SandboxAgent) *v1alpha2.Agent { if sa == nil { return nil } - spec := v1alpha2.AgentSpec{ - Type: sa.Spec.Type, - Declarative: sa.Spec.Declarative, - BYO: sa.Spec.BYO, - Description: sa.Spec.Description, - Skills: sa.Spec.Skills, - AllowedNamespaces: sa.Spec.AllowedNamespaces, - } + spec := sa.Spec a := &v1alpha2.Agent{ TypeMeta: metav1.TypeMeta{ APIVersion: v1alpha2.GroupVersion.String(), diff --git a/go/core/internal/httpserver/handlers/agents_test.go b/go/core/internal/httpserver/handlers/agents_test.go index add6c3d96..547293508 100644 --- a/go/core/internal/httpserver/handlers/agents_test.go +++ b/go/core/internal/httpserver/handlers/agents_test.go @@ -68,7 +68,7 @@ func createTestSandboxAgentCRD(name string, modelConfig *v1alpha2.ModelConfig, c Name: name, Namespace: "default", }, - Spec: v1alpha2.SandboxAgentSpec{ + Spec: v1alpha2.AgentSpec{ Type: v1alpha2.AgentType_Declarative, Declarative: &v1alpha2.DeclarativeAgentSpec{ ModelConfig: modelConfig.Name, diff --git a/helm/kagent-crds/templates/kagent.dev_sandboxagents.yaml b/helm/kagent-crds/templates/kagent.dev_sandboxagents.yaml index 142d92c41..f8903d9af 100644 --- a/helm/kagent-crds/templates/kagent.dev_sandboxagents.yaml +++ b/helm/kagent-crds/templates/kagent.dev_sandboxagents.yaml @@ -47,17 +47,15 @@ spec: metadata: type: object spec: - description: |- - SandboxAgentSpec defines the desired state of a SandboxAgent. Workload fields match Agent.spec - (Declarative or BYO). The kind selects sandbox reconciliation (SandboxTemplate + SandboxClaim) instead of a Deployment. + description: AgentSpec defines the desired state of Agent. properties: allowedNamespaces: description: |- - AllowedNamespaces defines which namespaces are allowed to reference this resource. - This mechanism provides a bidirectional handshake for cross-namespace references, - following the pattern used by Gateway API for cross-namespace route attachments. - - By default (when not specified), only references from the same namespace are allowed. + AllowedNamespaces defines which namespaces are allowed to reference this Agent as a tool. + This follows the Gateway API pattern for cross-namespace route attachments. + If not specified, only Agents in the same namespace can reference this Agent as a tool. + This field only applies when this Agent is used as a tool by another Agent. + See: https://gateway-api.sigs.k8s.io/guides/multiple-ns/#cross-namespace-routing properties: from: default: Same @@ -7819,6 +7817,9 @@ spec: description: type: string skills: + description: |- + Skills to load into the agent. They will be pulled from the specified container images. + and made available to the agent under the `/skills` folder. properties: gitAuthSecretRef: description: |- @@ -8121,7 +8122,8 @@ spec: rule: has(self.type) - message: type must be either Declarative or BYO rule: self.type == 'Declarative' || self.type == 'BYO' - - message: declarative or byo must match type + - message: declarative must be specified if type is Declarative, or byo + must be specified if type is BYO rule: (self.type == 'Declarative' && has(self.declarative)) || (self.type == 'BYO' && has(self.byo)) status: diff --git a/ui/src/app/actions/agents.ts b/ui/src/app/actions/agents.ts index 3e60dcd1c..abd7845d2 100644 --- a/ui/src/app/actions/agents.ts +++ b/ui/src/app/actions/agents.ts @@ -1,6 +1,6 @@ "use server"; -import { AgentSpec, BaseResponse, DeclarativeAgentSpec, SandboxAgent, SandboxAgentSpec } from "@/types"; +import { AgentSpec, BaseResponse, DeclarativeAgentSpec, SandboxAgent } from "@/types"; import { Agent, AgentResponse, Tool } from "@/types"; import { revalidatePath } from "next/cache"; import { fetchApi, createErrorResponse } from "./utils"; @@ -292,7 +292,7 @@ function fromAgentFormDataToSandboxAgent(agentFormData: AgentFormData): SandboxA }; } - const spec: SandboxAgentSpec = { + const spec: AgentSpec = { type: "Declarative", declarative: decl, description: agentFormData.description, diff --git a/ui/src/types/index.ts b/ui/src/types/index.ts index 1c7240eb2..81b9ef971 100644 --- a/ui/src/types/index.ts +++ b/ui/src/types/index.ts @@ -236,27 +236,12 @@ export interface SkillForAgent { refs?: string[]; } -/** Workload discriminator for SandboxAgent; same as Agent except Sandbox (the kind implies sandbox runtime). */ -export type SandboxAgentWorkloadType = "Declarative" | "BYO"; - -/** - * Spec for a SandboxAgent: same workload fields as Agent.spec (Declarative or BYO). - * Isolation comes from the SandboxAgent kind (SandboxTemplate + SandboxClaim), not from extra fields here. - */ -export interface SandboxAgentSpec { - type: SandboxAgentWorkloadType; - declarative?: DeclarativeAgentSpec; - byo?: BYOAgentSpec; - description?: string; - skills?: SkillForAgent; -} - -/** Kubernetes SandboxAgent CRD (kagent.dev/v1alpha2). */ +/** Kubernetes SandboxAgent CRD (kagent.dev/v1alpha2). Spec matches Agent.spec (AgentSpec). */ export interface SandboxAgent { apiVersion?: string; kind?: string; metadata: ResourceMetadata; - spec: SandboxAgentSpec; + spec: AgentSpec; } export interface AgentSpec { From 605280a321348300960c6062576efa7247d6de3c Mon Sep 17 00:00:00 2001 From: Peter Jausovec Date: Thu, 9 Apr 2026 14:38:56 +0200 Subject: [PATCH 03/13] remove sandboxtemplate/claim Signed-off-by: Peter Jausovec --- .../crd/bases/kagent.dev_sandboxagents.yaml | 2 +- go/api/httpapi/types.go | 2 +- go/api/v1alpha2/sandboxagent_types.go | 2 +- .../internal/controller/agent_controller.go | 6 - .../controller/reconciler/reconciler.go | 4 - .../reconciler/sandboxagent_sandbox_sync.go | 81 - .../sandboxagent_sandbox_sync_test.go | 70 - .../controller/sandboxagent_controller.go | 6 - .../translator/agent/adk_api_translator.go | 11 +- .../agent/adk_api_translator_test.go | 28 +- .../internal/controller/translator/mutate.go | 29 +- .../translator/mutate_sandbox_test.go | 28 +- go/core/pkg/app/app.go | 2 - .../sandboxbackend/agentsxk8s/agentsxk8s.go | 56 +- .../agentsxk8s/agentsxk8s_test.go | 27 +- go/core/pkg/sandboxbackend/backend.go | 9 +- ...ensions.agents.x-k8s.io_sandboxclaims.yaml | 103 - ...ions.agents.x-k8s.io_sandboxtemplates.yaml | 4019 ----------------- .../templates/kagent.dev_sandboxagents.yaml | 2 +- helm/kagent/templates/rbac/getter-role.yaml | 9 - helm/kagent/templates/rbac/writer-role.yaml | 10 - 21 files changed, 56 insertions(+), 4450 deletions(-) delete mode 100644 go/core/internal/controller/reconciler/sandboxagent_sandbox_sync.go delete mode 100644 go/core/internal/controller/reconciler/sandboxagent_sandbox_sync_test.go delete mode 100644 helm/kagent-crds/templates/extensions.agents.x-k8s.io_sandboxclaims.yaml delete mode 100644 helm/kagent-crds/templates/extensions.agents.x-k8s.io_sandboxtemplates.yaml diff --git a/go/api/config/crd/bases/kagent.dev_sandboxagents.yaml b/go/api/config/crd/bases/kagent.dev_sandboxagents.yaml index f8903d9af..35d6b08ec 100644 --- a/go/api/config/crd/bases/kagent.dev_sandboxagents.yaml +++ b/go/api/config/crd/bases/kagent.dev_sandboxagents.yaml @@ -27,7 +27,7 @@ spec: schema: openAPIV3Schema: description: SandboxAgent declares an agent that runs in an isolated sandbox - (agent-sandbox SandboxTemplate+SandboxClaim). + (agent-sandbox Sandbox CR). properties: apiVersion: description: |- diff --git a/go/api/httpapi/types.go b/go/api/httpapi/types.go index 4c5ef5088..c2b13dfaa 100644 --- a/go/api/httpapi/types.go +++ b/go/api/httpapi/types.go @@ -94,7 +94,7 @@ type AgentResponse struct { Tools []*v1alpha2.Tool `json:"tools"` DeploymentReady bool `json:"deploymentReady"` Accepted bool `json:"accepted"` - // RunInSandbox is true when the workload is reconciled as a SandboxAgent (SandboxTemplate/SandboxClaim), not a Deployment. + // RunInSandbox is true when the workload is reconciled as a SandboxAgent (agent-sandbox Sandbox CR), not a Deployment. RunInSandbox bool `json:"runInSandbox,omitempty"` } diff --git a/go/api/v1alpha2/sandboxagent_types.go b/go/api/v1alpha2/sandboxagent_types.go index 29646469d..9938088b7 100644 --- a/go/api/v1alpha2/sandboxagent_types.go +++ b/go/api/v1alpha2/sandboxagent_types.go @@ -24,7 +24,7 @@ import ( // +kubebuilder:subresource:status // +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].status",description="Whether the sandbox workload is ready." // +kubebuilder:printcolumn:name="Accepted",type="string",JSONPath=".status.conditions[?(@.type=='Accepted')].status",description="Whether configuration was accepted." -// SandboxAgent declares an agent that runs in an isolated sandbox (agent-sandbox SandboxTemplate+SandboxClaim). +// SandboxAgent declares an agent that runs in an isolated sandbox (agent-sandbox Sandbox CR). type SandboxAgent struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` diff --git a/go/core/internal/controller/agent_controller.go b/go/core/internal/controller/agent_controller.go index c2c01a6bc..6742a23c3 100644 --- a/go/core/internal/controller/agent_controller.go +++ b/go/core/internal/controller/agent_controller.go @@ -60,12 +60,6 @@ type AgentController struct { // +kubebuilder:rbac:groups=agents.x-k8s.io,resources=sandboxes,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=agents.x-k8s.io,resources=sandboxes/status,verbs=get;update;patch // +kubebuilder:rbac:groups=agents.x-k8s.io,resources=sandboxes/finalizers,verbs=update -// +kubebuilder:rbac:groups=extensions.agents.x-k8s.io,resources=sandboxtemplates,verbs=get;list;watch;create;update;patch;delete -// +kubebuilder:rbac:groups=extensions.agents.x-k8s.io,resources=sandboxtemplates/status,verbs=get;update;patch -// +kubebuilder:rbac:groups=extensions.agents.x-k8s.io,resources=sandboxtemplates/finalizers,verbs=update -// +kubebuilder:rbac:groups=extensions.agents.x-k8s.io,resources=sandboxclaims,verbs=get;list;watch;create;update;patch;delete -// +kubebuilder:rbac:groups=extensions.agents.x-k8s.io,resources=sandboxclaims/status,verbs=get;update;patch -// +kubebuilder:rbac:groups=extensions.agents.x-k8s.io,resources=sandboxclaims/finalizers,verbs=update func (r *AgentController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { _ = log.FromContext(ctx) diff --git a/go/core/internal/controller/reconciler/reconciler.go b/go/core/internal/controller/reconciler/reconciler.go index e54785a89..03f70577d 100644 --- a/go/core/internal/controller/reconciler/reconciler.go +++ b/go/core/internal/controller/reconciler/reconciler.go @@ -189,10 +189,6 @@ func (a *kagentReconciler) reconcileSandboxAgent(ctx context.Context, sa *v1alph return fmt.Errorf("failed to reconcile owned objects: %w", err) } - if err := a.resyncAgentSandboxWorkload(ctx, sa); err != nil { - return err - } - if err := a.upsertAgent(ctx, agentView, agentOutputs, true); err != nil { return fmt.Errorf("failed to upsert agent %s/%s: %w", sa.Namespace, sa.Name, err) } diff --git a/go/core/internal/controller/reconciler/sandboxagent_sandbox_sync.go b/go/core/internal/controller/reconciler/sandboxagent_sandbox_sync.go deleted file mode 100644 index f2dbff191..000000000 --- a/go/core/internal/controller/reconciler/sandboxagent_sandbox_sync.go +++ /dev/null @@ -1,81 +0,0 @@ -package reconciler - -import ( - "context" - "fmt" - - "github.com/kagent-dev/kagent/go/api/v1alpha2" - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/util/retry" - agentsandboxv1 "sigs.k8s.io/agent-sandbox/api/v1alpha1" - extensionsv1alpha1 "sigs.k8s.io/agent-sandbox/extensions/api/v1alpha1" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -// sandboxTemplateResourceVersionAnnotation records the SandboxTemplate.metadata.resourceVersion -// last applied to the live Sandbox workload. agent-sandbox's SandboxClaim controller only copies -// PodTemplate from SandboxTemplate when it creates the Sandbox; it does not update an existing -// Sandbox when the template changes. The core Sandbox controller likewise leaves an existing Pod -// spec untouched—see upstream TODO: https://github.com/kubernetes-sigs/agent-sandbox/blob/059575c213eec95641c9229da8fd4525cb919617/controllers/sandbox_controller.go#L601 -// Once/if the upstream fixes/implements it, then we can remove this resync logic. -const sandboxTemplateResourceVersionAnnotation = "kagent.dev/last-synced-sandbox-template-resource-version" - -func sandboxTemplateNameForAgent(agentName string) string { - return fmt.Sprintf("kagent-%s", agentName) -} - -func (a *kagentReconciler) resyncAgentSandboxWorkload(ctx context.Context, sa *v1alpha2.SandboxAgent) error { - if a.sandboxBackend == nil { - return nil - } - - tmplKey := types.NamespacedName{Namespace: sa.Namespace, Name: sandboxTemplateNameForAgent(sa.Name)} - tmpl := &extensionsv1alpha1.SandboxTemplate{} - if err := a.kube.Get(ctx, tmplKey, tmpl); err != nil { - if apierrors.IsNotFound(err) { - return nil - } - return fmt.Errorf("get SandboxTemplate for workload resync: %w", err) - } - wantRV := tmpl.ResourceVersion - - claimKey := types.NamespacedName{Namespace: sa.Namespace, Name: sa.Name} - claim := &extensionsv1alpha1.SandboxClaim{} - if err := a.kube.Get(ctx, claimKey, claim); err != nil { - if apierrors.IsNotFound(err) { - return nil - } - return fmt.Errorf("get SandboxClaim for workload resync: %w", err) - } - - stored := "" - if claim.Annotations != nil { - stored = claim.Annotations[sandboxTemplateResourceVersionAnnotation] - } - if wantRV == stored { - return nil - } - - sb := &agentsandboxv1.Sandbox{} - if err := a.kube.Get(ctx, claimKey, sb); err == nil { - if err := a.kube.Delete(ctx, sb); err != nil { - return fmt.Errorf("delete Sandbox %s/%s to apply updated SandboxTemplate: %w", sb.Namespace, sb.Name, err) - } - } else if !apierrors.IsNotFound(err) { - return fmt.Errorf("get Sandbox for workload resync: %w", err) - } - - return retry.RetryOnConflict(retry.DefaultRetry, func() error { - fresh := &extensionsv1alpha1.SandboxClaim{} - if err := a.kube.Get(ctx, claimKey, fresh); err != nil { - return fmt.Errorf("get SandboxClaim for sync annotation: %w", err) - } - base := fresh.DeepCopy() - if fresh.Annotations == nil { - fresh.Annotations = make(map[string]string) - } - fresh.Annotations[sandboxTemplateResourceVersionAnnotation] = wantRV - return a.kube.Patch(ctx, fresh, client.MergeFrom(base)) - }) -} diff --git a/go/core/internal/controller/reconciler/sandboxagent_sandbox_sync_test.go b/go/core/internal/controller/reconciler/sandboxagent_sandbox_sync_test.go deleted file mode 100644 index 95424b3cf..000000000 --- a/go/core/internal/controller/reconciler/sandboxagent_sandbox_sync_test.go +++ /dev/null @@ -1,70 +0,0 @@ -package reconciler - -import ( - "context" - "testing" - - "github.com/kagent-dev/kagent/go/api/v1alpha2" - "github.com/kagent-dev/kagent/go/core/pkg/sandboxbackend/agentsxk8s" - "github.com/stretchr/testify/require" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/client/fake" - - agentsandboxv1 "sigs.k8s.io/agent-sandbox/api/v1alpha1" - extensionsv1alpha1 "sigs.k8s.io/agent-sandbox/extensions/api/v1alpha1" -) - -func TestResyncAgentSandboxWorkload_deletesSandboxWhenTemplateRVChanges(t *testing.T) { - scheme := runtime.NewScheme() - require.NoError(t, extensionsv1alpha1.AddToScheme(scheme)) - require.NoError(t, agentsandboxv1.AddToScheme(scheme)) - require.NoError(t, v1alpha2.AddToScheme(scheme)) - - ns := "ns1" - agName := "myagent" - tmplName := sandboxTemplateNameForAgent(agName) - - tmpl := &extensionsv1alpha1.SandboxTemplate{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: ns, - Name: tmplName, - ResourceVersion: "rv-2", - }, - Spec: extensionsv1alpha1.SandboxTemplateSpec{}, - } - claim := &extensionsv1alpha1.SandboxClaim{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: ns, - Name: agName, - Annotations: map[string]string{ - sandboxTemplateResourceVersionAnnotation: "rv-1", - }, - }, - Spec: extensionsv1alpha1.SandboxClaimSpec{ - TemplateRef: extensionsv1alpha1.SandboxTemplateRef{Name: tmplName}, - }, - } - sb := &agentsandboxv1.Sandbox{ - ObjectMeta: metav1.ObjectMeta{Namespace: ns, Name: agName}, - Spec: agentsandboxv1.SandboxSpec{}, - } - - cl := fake.NewClientBuilder().WithScheme(scheme).WithObjects(tmpl, claim, sb).Build() - - r := &kagentReconciler{kube: cl, sandboxBackend: agentsxk8s.New()} - sa := &v1alpha2.SandboxAgent{ - ObjectMeta: metav1.ObjectMeta{Namespace: ns, Name: agName}, - } - - require.NoError(t, r.resyncAgentSandboxWorkload(context.Background(), sa)) - - err := cl.Get(context.Background(), client.ObjectKeyFromObject(sb), sb) - require.True(t, apierrors.IsNotFound(err)) - - var updatedClaim extensionsv1alpha1.SandboxClaim - require.NoError(t, cl.Get(context.Background(), client.ObjectKeyFromObject(claim), &updatedClaim)) - require.Equal(t, "rv-2", updatedClaim.Annotations[sandboxTemplateResourceVersionAnnotation]) -} diff --git a/go/core/internal/controller/sandboxagent_controller.go b/go/core/internal/controller/sandboxagent_controller.go index c9abf538e..9f4dd5445 100644 --- a/go/core/internal/controller/sandboxagent_controller.go +++ b/go/core/internal/controller/sandboxagent_controller.go @@ -57,12 +57,6 @@ type SandboxAgentController struct { // +kubebuilder:rbac:groups=agents.x-k8s.io,resources=sandboxes,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=agents.x-k8s.io,resources=sandboxes/status,verbs=get;update;patch // +kubebuilder:rbac:groups=agents.x-k8s.io,resources=sandboxes/finalizers,verbs=update -// +kubebuilder:rbac:groups=extensions.agents.x-k8s.io,resources=sandboxtemplates,verbs=get;list;watch;create;update;patch;delete -// +kubebuilder:rbac:groups=extensions.agents.x-k8s.io,resources=sandboxtemplates/status,verbs=get;update;patch -// +kubebuilder:rbac:groups=extensions.agents.x-k8s.io,resources=sandboxtemplates/finalizers,verbs=update -// +kubebuilder:rbac:groups=extensions.agents.x-k8s.io,resources=sandboxclaims,verbs=get;list;watch;create;update;patch;delete -// +kubebuilder:rbac:groups=extensions.agents.x-k8s.io,resources=sandboxclaims/status,verbs=get;update;patch -// +kubebuilder:rbac:groups=extensions.agents.x-k8s.io,resources=sandboxclaims/finalizers,verbs=update func (r *SandboxAgentController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { _ = ctrl.LoggerFrom(ctx) diff --git a/go/core/internal/controller/translator/agent/adk_api_translator.go b/go/core/internal/controller/translator/agent/adk_api_translator.go index 2849f5095..22909a895 100644 --- a/go/core/internal/controller/translator/agent/adk_api_translator.go +++ b/go/core/internal/controller/translator/agent/adk_api_translator.go @@ -602,11 +602,9 @@ func (a *adkApiTranslator) buildManifest( var workloadObj client.Object if runInSandbox { - templateName := fmt.Sprintf("kagent-%s", agent.Name) sbObjs, err := a.sandboxBackend.BuildSandbox(ctx, sandboxbackend.BuildInput{ - Agent: agent, - PodTemplate: podTemplate, - TemplateName: templateName, + Agent: agent, + PodTemplate: podTemplate, }) if err != nil { return nil, fmt.Errorf("build sandbox workload: %w", err) @@ -636,9 +634,8 @@ func (a *adkApiTranslator) buildManifest( outputs.Manifest = append(outputs.Manifest, workloadObj) } - // Service: Sandbox workloads use SandboxTemplate + - // SandboxClaim; the agent-sandbox Sandbox controller creates and owns the Service with the - // same name as the claim, so we only need to create a service for non-sandboxed agents. + // Service: for Sandbox workloads the agent-sandbox controller creates and owns the Service + // (same name as the Sandbox), so we only create a Service for non-sandboxed agents. if !runInSandbox { outputs.Manifest = append(outputs.Manifest, &corev1.Service{ TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: "Service"}, diff --git a/go/core/internal/controller/translator/agent/adk_api_translator_test.go b/go/core/internal/controller/translator/agent/adk_api_translator_test.go index 888bacda1..be2c1aaf0 100644 --- a/go/core/internal/controller/translator/agent/adk_api_translator_test.go +++ b/go/core/internal/controller/translator/agent/adk_api_translator_test.go @@ -21,7 +21,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client/fake" agentsandboxv1 "sigs.k8s.io/agent-sandbox/api/v1alpha1" - extensionsv1alpha1 "sigs.k8s.io/agent-sandbox/extensions/api/v1alpha1" ) // Test_AdkApiTranslator_CrossNamespaceAgentTool tests that the translator can @@ -1291,12 +1290,11 @@ func Test_AdkApiTranslator_ContextConfig(t *testing.T) { } } -func Test_AdkApiTranslator_SandboxAgent_defaultUsesSandboxTemplateAndClaim(t *testing.T) { +func Test_AdkApiTranslator_SandboxAgent_defaultEmitsSandbox(t *testing.T) { ctx := context.Background() scheme := schemev1.Scheme require.NoError(t, v1alpha2.AddToScheme(scheme)) require.NoError(t, agentsandboxv1.AddToScheme(scheme)) - require.NoError(t, extensionsv1alpha1.AddToScheme(scheme)) ns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "sandbox-ns"}} modelConfig := &v1alpha2.ModelConfig{ @@ -1332,13 +1330,9 @@ func Test_AdkApiTranslator_SandboxAgent_defaultUsesSandboxTemplateAndClaim(t *te require.NoError(t, err) require.NotNil(t, outputs) - var sawTemplate, sawClaim, sawSandbox, sawDeploy, sawService bool + var sawSandbox, sawDeploy, sawService bool for _, o := range outputs.Manifest { switch o.(type) { - case *extensionsv1alpha1.SandboxTemplate: - sawTemplate = true - case *extensionsv1alpha1.SandboxClaim: - sawClaim = true case *agentsandboxv1.Sandbox: sawSandbox = true case *appsv1.Deployment: @@ -1347,19 +1341,16 @@ func Test_AdkApiTranslator_SandboxAgent_defaultUsesSandboxTemplateAndClaim(t *te sawService = true } } - require.True(t, sawTemplate, "sandbox runtime should include SandboxTemplate") - require.True(t, sawClaim, "sandbox runtime should include SandboxClaim") - require.False(t, sawSandbox, "default should not emit a direct Sandbox CR") + require.True(t, sawSandbox, "sandbox runtime should emit a Sandbox CR") require.False(t, sawDeploy, "manifest should not include Deployment when runInSandbox is true") require.False(t, sawService, "sandbox runtime must not include Service; agent-sandbox owns it") } -func Test_AdkApiTranslator_SandboxAgentView_BYOUsesSandboxTemplateAndClaim(t *testing.T) { +func Test_AdkApiTranslator_SandboxAgentView_BYOEmitsSandbox(t *testing.T) { ctx := context.Background() scheme := schemev1.Scheme require.NoError(t, v1alpha2.AddToScheme(scheme)) require.NoError(t, agentsandboxv1.AddToScheme(scheme)) - require.NoError(t, extensionsv1alpha1.AddToScheme(scheme)) ns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "sandbox-ns"}} cmd := "/app/run" @@ -1392,21 +1383,18 @@ func Test_AdkApiTranslator_SandboxAgentView_BYOUsesSandboxTemplateAndClaim(t *te require.NoError(t, err) require.NotNil(t, outputs) - var sawTemplate, sawClaim, sawDeploy, sawService bool + var sawSandbox, sawDeploy, sawService bool for _, o := range outputs.Manifest { switch o.(type) { - case *extensionsv1alpha1.SandboxTemplate: - sawTemplate = true - case *extensionsv1alpha1.SandboxClaim: - sawClaim = true + case *agentsandboxv1.Sandbox: + sawSandbox = true case *appsv1.Deployment: sawDeploy = true case *corev1.Service: sawService = true } } - require.True(t, sawTemplate) - require.True(t, sawClaim) + require.True(t, sawSandbox) require.False(t, sawDeploy) require.False(t, sawService, "sandbox runtime must not include Service; agent-sandbox owns it") } diff --git a/go/core/internal/controller/translator/mutate.go b/go/core/internal/controller/translator/mutate.go index 07f04e196..485975919 100644 --- a/go/core/internal/controller/translator/mutate.go +++ b/go/core/internal/controller/translator/mutate.go @@ -6,10 +6,10 @@ import ( "dario.cat/mergo" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" + + agentsandboxv1 "sigs.k8s.io/agent-sandbox/api/v1alpha1" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - - extensionsv1alpha1 "sigs.k8s.io/agent-sandbox/extensions/api/v1alpha1" ) func MutateFuncFor(existing, desired client.Object) controllerutil.MutateFn { @@ -60,15 +60,10 @@ func MutateFuncFor(existing, desired client.Object) controllerutil.MutateFn { wantDpl := desired.(*appsv1.Deployment) return mutateDeployment(dpl, wantDpl) - case *extensionsv1alpha1.SandboxTemplate: - st := existing.(*extensionsv1alpha1.SandboxTemplate) - want := desired.(*extensionsv1alpha1.SandboxTemplate) - mutateSandboxTemplate(st, want) - - case *extensionsv1alpha1.SandboxClaim: - sc := existing.(*extensionsv1alpha1.SandboxClaim) - want := desired.(*extensionsv1alpha1.SandboxClaim) - mutateSandboxClaim(sc, want) + case *agentsandboxv1.Sandbox: + sb := existing.(*agentsandboxv1.Sandbox) + want := desired.(*agentsandboxv1.Sandbox) + mutateSandbox(sb, want) default: return mergeWithOverride(existing, desired) @@ -132,13 +127,9 @@ func mutatePodTemplate(existing, desired *corev1.PodTemplateSpec) error { return nil } -// mutateSandboxTemplate replaces the template spec wholesale. The default mergo path does not -// reliably replace slice fields (containers, volumes, env), so SandboxAgent updates would not -// roll pods until we assign spec explicitly (same idea as mutatePodTemplate / Deployment). -func mutateSandboxTemplate(existing, desired *extensionsv1alpha1.SandboxTemplate) { - existing.Spec = desired.Spec -} - -func mutateSandboxClaim(existing, desired *extensionsv1alpha1.SandboxClaim) { +// mutateSandbox replaces the spec wholesale. The default mergo path does not reliably replace +// slice fields (containers, volumes, env), so SandboxAgent updates would not roll pods until we +// assign spec explicitly (same idea as mutatePodTemplate / Deployment). +func mutateSandbox(existing, desired *agentsandboxv1.Sandbox) { existing.Spec = desired.Spec } diff --git a/go/core/internal/controller/translator/mutate_sandbox_test.go b/go/core/internal/controller/translator/mutate_sandbox_test.go index d9062d771..275ec084d 100644 --- a/go/core/internal/controller/translator/mutate_sandbox_test.go +++ b/go/core/internal/controller/translator/mutate_sandbox_test.go @@ -8,16 +8,15 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" agentsandboxv1 "sigs.k8s.io/agent-sandbox/api/v1alpha1" - extensionsv1alpha1 "sigs.k8s.io/agent-sandbox/extensions/api/v1alpha1" ) -func TestMutateFuncFor_SandboxTemplate_replacesPodTemplateSlices(t *testing.T) { +func TestMutateFuncFor_Sandbox_replacesPodTemplateSlices(t *testing.T) { oldImg := "registry.example/old:v1" newImg := "registry.example/new:v2" - existing := &extensionsv1alpha1.SandboxTemplate{ - ObjectMeta: metav1.ObjectMeta{Name: "kagent-a", Namespace: "ns"}, - Spec: extensionsv1alpha1.SandboxTemplateSpec{ + existing := &agentsandboxv1.Sandbox{ + ObjectMeta: metav1.ObjectMeta{Name: "a", Namespace: "ns"}, + Spec: agentsandboxv1.SandboxSpec{ PodTemplate: agentsandboxv1.PodTemplate{ Spec: corev1.PodSpec{ Containers: []corev1.Container{{Name: "agent", Image: oldImg}}, @@ -32,22 +31,3 @@ func TestMutateFuncFor_SandboxTemplate_replacesPodTemplateSlices(t *testing.T) { require.NoError(t, f()) require.Equal(t, newImg, existing.Spec.PodTemplate.Spec.Containers[0].Image) } - -func TestMutateFuncFor_SandboxClaim_replacesSpec(t *testing.T) { - existing := &extensionsv1alpha1.SandboxClaim{ - ObjectMeta: metav1.ObjectMeta{Name: "a", Namespace: "ns"}, - Spec: extensionsv1alpha1.SandboxClaimSpec{ - TemplateRef: extensionsv1alpha1.SandboxTemplateRef{Name: "t-old"}, - }, - } - desired := &extensionsv1alpha1.SandboxClaim{ - ObjectMeta: metav1.ObjectMeta{Name: "a", Namespace: "ns"}, - Spec: extensionsv1alpha1.SandboxClaimSpec{ - TemplateRef: extensionsv1alpha1.SandboxTemplateRef{Name: "t-new"}, - }, - } - - f := MutateFuncFor(existing, desired) - require.NoError(t, f()) - require.Equal(t, "t-new", existing.Spec.TemplateRef.Name) -} diff --git a/go/core/pkg/app/app.go b/go/core/pkg/app/app.go index c0a262ce0..f6735bdbd 100644 --- a/go/core/pkg/app/app.go +++ b/go/core/pkg/app/app.go @@ -77,7 +77,6 @@ import ( "github.com/kagent-dev/kagent/go/core/internal/goruntime" "github.com/kagent-dev/kmcp/api/v1alpha1" agentsandboxv1 "sigs.k8s.io/agent-sandbox/api/v1alpha1" - extensionsagentsandboxv1 "sigs.k8s.io/agent-sandbox/extensions/api/v1alpha1" // +kubebuilder:scaffold:imports ) @@ -98,7 +97,6 @@ func init() { utilruntime.Must(v1alpha1.AddToScheme(scheme)) utilruntime.Must(v1alpha2.AddToScheme(scheme)) utilruntime.Must(agentsandboxv1.AddToScheme(scheme)) - utilruntime.Must(extensionsagentsandboxv1.AddToScheme(scheme)) // +kubebuilder:scaffold:scheme } diff --git a/go/core/pkg/sandboxbackend/agentsxk8s/agentsxk8s.go b/go/core/pkg/sandboxbackend/agentsxk8s/agentsxk8s.go index dacc173e1..05643d816 100644 --- a/go/core/pkg/sandboxbackend/agentsxk8s/agentsxk8s.go +++ b/go/core/pkg/sandboxbackend/agentsxk8s/agentsxk8s.go @@ -12,10 +12,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" agentsandboxv1 "sigs.k8s.io/agent-sandbox/api/v1alpha1" - extensionsv1alpha1 "sigs.k8s.io/agent-sandbox/extensions/api/v1alpha1" ) -// Backend builds kubernetes-sigs/agent-sandbox SandboxTemplate + SandboxClaim resources. +// Backend builds kubernetes-sigs/agent-sandbox Sandbox CRs directly (no SandboxTemplate/SandboxClaim). type Backend struct{} var _ sandboxbackend.Backend = (*Backend)(nil) @@ -27,8 +26,7 @@ func New() *Backend { func (b *Backend) GetOwnedResourceTypes() []client.Object { return []client.Object{ - &extensionsv1alpha1.SandboxTemplate{}, - &extensionsv1alpha1.SandboxClaim{}, + &agentsandboxv1.Sandbox{}, } } @@ -45,15 +43,6 @@ func (b *Backend) BuildSandbox(_ context.Context, in sandboxbackend.BuildInput) podLabels = mapsUnion(podLabels, in.ExtraLabels) } - return b.buildSandboxTemplateAndClaim(in, name, podLabels) -} - -func (b *Backend) buildSandboxTemplateAndClaim(in sandboxbackend.BuildInput, claimName string, podLabels map[string]string) ([]client.Object, error) { - tmplName := in.TemplateName - if tmplName == "" { - return nil, fmt.Errorf("template name is required") - } - pt := agentsandboxv1.PodTemplate{ Spec: in.PodTemplate.Spec, ObjectMeta: agentsandboxv1.PodMetadata{ @@ -64,46 +53,25 @@ func (b *Backend) buildSandboxTemplateAndClaim(in sandboxbackend.BuildInput, cla labelUnion := mapsUnion(podLabels, in.Agent.Labels) - tmplSpec := extensionsv1alpha1.SandboxTemplateSpec{ - PodTemplate: pt, - // Unmanaged allows kagent to reach the agent Service in-cluster without agent-sandbox-managed NetworkPolicies. - NetworkPolicyManagement: extensionsv1alpha1.NetworkPolicyManagementUnmanaged, - } - - st := &extensionsv1alpha1.SandboxTemplate{ - TypeMeta: metav1.TypeMeta{ - APIVersion: extensionsv1alpha1.GroupVersion.String(), - Kind: "SandboxTemplate", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: tmplName, - Namespace: in.Agent.Namespace, - Annotations: in.Agent.Annotations, - Labels: labelUnion, - }, - Spec: tmplSpec, - } - out := []client.Object{st} - - claim := &extensionsv1alpha1.SandboxClaim{ + replicas := int32(1) + sb := &agentsandboxv1.Sandbox{ TypeMeta: metav1.TypeMeta{ - APIVersion: extensionsv1alpha1.GroupVersion.String(), - Kind: "SandboxClaim", + APIVersion: agentsandboxv1.GroupVersion.String(), + Kind: "Sandbox", }, ObjectMeta: metav1.ObjectMeta{ - Name: claimName, + Name: name, Namespace: in.Agent.Namespace, Annotations: in.Agent.Annotations, Labels: labelUnion, }, - Spec: extensionsv1alpha1.SandboxClaimSpec{ - TemplateRef: extensionsv1alpha1.SandboxTemplateRef{ - Name: tmplName, - }, + Spec: agentsandboxv1.SandboxSpec{ + PodTemplate: pt, + Replicas: &replicas, }, } - out = append(out, claim) - return out, nil + + return []client.Object{sb}, nil } func mapsUnion(podLabels map[string]string, agentLabels map[string]string) map[string]string { diff --git a/go/core/pkg/sandboxbackend/agentsxk8s/agentsxk8s_test.go b/go/core/pkg/sandboxbackend/agentsxk8s/agentsxk8s_test.go index 74c9a9958..965bbe883 100644 --- a/go/core/pkg/sandboxbackend/agentsxk8s/agentsxk8s_test.go +++ b/go/core/pkg/sandboxbackend/agentsxk8s/agentsxk8s_test.go @@ -10,10 +10,10 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - extensionsv1alpha1 "sigs.k8s.io/agent-sandbox/extensions/api/v1alpha1" + agentsandboxv1 "sigs.k8s.io/agent-sandbox/api/v1alpha1" ) -func TestBackend_BuildSandbox_templateAndClaim(t *testing.T) { +func TestBackend_BuildSandbox_emitsSandbox(t *testing.T) { b := New() agent := &v1alpha2.Agent{ ObjectMeta: metav1.ObjectMeta{Name: "a1", Namespace: "ns1", Labels: map[string]string{"app": "x"}}, @@ -25,22 +25,17 @@ func TestBackend_BuildSandbox_templateAndClaim(t *testing.T) { }, } objs, err := b.BuildSandbox(context.Background(), sandboxbackend.BuildInput{ - Agent: agent, - PodTemplate: pt, - TemplateName: "kagent-a1", + Agent: agent, + PodTemplate: pt, }) require.NoError(t, err) - require.Len(t, objs, 2) + require.Len(t, objs, 1) - st, ok := objs[0].(*extensionsv1alpha1.SandboxTemplate) + sb, ok := objs[0].(*agentsandboxv1.Sandbox) require.True(t, ok) - require.Equal(t, "kagent-a1", st.Name) - require.Equal(t, "img:v1", st.Spec.PodTemplate.Spec.Containers[0].Image) - require.Equal(t, extensionsv1alpha1.NetworkPolicyManagementUnmanaged, st.Spec.NetworkPolicyManagement) - require.Nil(t, st.Spec.NetworkPolicy) - - claim, ok := objs[1].(*extensionsv1alpha1.SandboxClaim) - require.True(t, ok) - require.Equal(t, "a1", claim.Name) - require.Equal(t, "kagent-a1", claim.Spec.TemplateRef.Name) + require.Equal(t, "a1", sb.Name) + require.Equal(t, "ns1", sb.Namespace) + require.Equal(t, "img:v1", sb.Spec.PodTemplate.Spec.Containers[0].Image) + require.NotNil(t, sb.Spec.Replicas) + require.Equal(t, int32(1), *sb.Spec.Replicas) } diff --git a/go/core/pkg/sandboxbackend/backend.go b/go/core/pkg/sandboxbackend/backend.go index aaa9cad8e..d0c39c167 100644 --- a/go/core/pkg/sandboxbackend/backend.go +++ b/go/core/pkg/sandboxbackend/backend.go @@ -10,15 +10,12 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" ) -// BuildInput carries the pod template to embed in SandboxTemplate and reference from SandboxClaim. +// BuildInput carries the pod template for a Sandbox workload (agents.x-k8s.io Sandbox). type BuildInput struct { - Agent *v1alpha2.Agent - PodTemplate corev1.PodTemplateSpec + Agent *v1alpha2.Agent + PodTemplate corev1.PodTemplateSpec WorkloadName string ExtraLabels map[string]string - - // TemplateName is the SandboxTemplate metadata.name (required). - TemplateName string } // Backend builds sandbox CRD objects and evaluates their readiness. diff --git a/helm/kagent-crds/templates/extensions.agents.x-k8s.io_sandboxclaims.yaml b/helm/kagent-crds/templates/extensions.agents.x-k8s.io_sandboxclaims.yaml deleted file mode 100644 index 69efd1906..000000000 --- a/helm/kagent-crds/templates/extensions.agents.x-k8s.io_sandboxclaims.yaml +++ /dev/null @@ -1,103 +0,0 @@ ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.19.0 - name: sandboxclaims.extensions.agents.x-k8s.io -spec: - group: extensions.agents.x-k8s.io - names: - kind: SandboxClaim - listKind: SandboxClaimList - plural: sandboxclaims - shortNames: - - sandboxclaim - singular: sandboxclaim - scope: Namespaced - versions: - - name: v1alpha1 - schema: - openAPIV3Schema: - properties: - apiVersion: - type: string - kind: - type: string - metadata: - type: object - spec: - properties: - lifecycle: - properties: - shutdownPolicy: - default: Retain - enum: - - Delete - - Retain - type: string - shutdownTime: - format: date-time - type: string - type: object - sandboxTemplateRef: - properties: - name: - type: string - required: - - name - type: object - required: - - sandboxTemplateRef - type: object - status: - properties: - conditions: - items: - properties: - lastTransitionTime: - format: date-time - type: string - message: - maxLength: 32768 - type: string - observedGeneration: - format: int64 - minimum: 0 - type: integer - reason: - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - enum: - - "True" - - "False" - - Unknown - type: string - type: - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array - sandbox: - properties: - Name: - type: string - type: object - type: object - required: - - spec - type: object - served: true - storage: true - subresources: - status: {} diff --git a/helm/kagent-crds/templates/extensions.agents.x-k8s.io_sandboxtemplates.yaml b/helm/kagent-crds/templates/extensions.agents.x-k8s.io_sandboxtemplates.yaml deleted file mode 100644 index 0f9fc3316..000000000 --- a/helm/kagent-crds/templates/extensions.agents.x-k8s.io_sandboxtemplates.yaml +++ /dev/null @@ -1,4019 +0,0 @@ ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.19.0 - name: sandboxtemplates.extensions.agents.x-k8s.io -spec: - group: extensions.agents.x-k8s.io - names: - kind: SandboxTemplate - listKind: SandboxTemplateList - plural: sandboxtemplates - shortNames: - - sandboxtemplate - singular: sandboxtemplate - scope: Namespaced - versions: - - name: v1alpha1 - schema: - openAPIV3Schema: - properties: - apiVersion: - type: string - kind: - type: string - metadata: - type: object - spec: - properties: - networkPolicy: - properties: - egress: - items: - properties: - ports: - items: - properties: - endPort: - format: int32 - type: integer - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - protocol: - type: string - type: object - type: array - x-kubernetes-list-type: atomic - to: - items: - properties: - ipBlock: - properties: - cidr: - type: string - except: - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - cidr - type: object - namespaceSelector: - properties: - matchExpressions: - items: - properties: - key: - type: string - operator: - type: string - values: - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - type: object - type: object - x-kubernetes-map-type: atomic - podSelector: - properties: - matchExpressions: - items: - properties: - key: - type: string - operator: - type: string - values: - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - type: object - type: object - x-kubernetes-map-type: atomic - type: object - type: array - x-kubernetes-list-type: atomic - type: object - type: array - ingress: - items: - properties: - from: - items: - properties: - ipBlock: - properties: - cidr: - type: string - except: - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - cidr - type: object - namespaceSelector: - properties: - matchExpressions: - items: - properties: - key: - type: string - operator: - type: string - values: - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - type: object - type: object - x-kubernetes-map-type: atomic - podSelector: - properties: - matchExpressions: - items: - properties: - key: - type: string - operator: - type: string - values: - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - type: object - type: object - x-kubernetes-map-type: atomic - type: object - type: array - x-kubernetes-list-type: atomic - ports: - items: - properties: - endPort: - format: int32 - type: integer - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - protocol: - type: string - type: object - type: array - x-kubernetes-list-type: atomic - type: object - type: array - type: object - networkPolicyManagement: - default: Managed - enum: - - Managed - - Unmanaged - type: string - podTemplate: - properties: - metadata: - properties: - annotations: - additionalProperties: - type: string - type: object - labels: - additionalProperties: - type: string - type: object - type: object - spec: - properties: - activeDeadlineSeconds: - format: int64 - type: integer - affinity: - properties: - nodeAffinity: - properties: - preferredDuringSchedulingIgnoredDuringExecution: - items: - properties: - preference: - properties: - matchExpressions: - items: - properties: - key: - type: string - operator: - type: string - values: - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchFields: - items: - properties: - key: - type: string - operator: - type: string - values: - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - type: object - x-kubernetes-map-type: atomic - weight: - format: int32 - type: integer - required: - - preference - - weight - type: object - type: array - x-kubernetes-list-type: atomic - requiredDuringSchedulingIgnoredDuringExecution: - properties: - nodeSelectorTerms: - items: - properties: - matchExpressions: - items: - properties: - key: - type: string - operator: - type: string - values: - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchFields: - items: - properties: - key: - type: string - operator: - type: string - values: - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - type: object - x-kubernetes-map-type: atomic - type: array - x-kubernetes-list-type: atomic - required: - - nodeSelectorTerms - type: object - x-kubernetes-map-type: atomic - type: object - podAffinity: - properties: - preferredDuringSchedulingIgnoredDuringExecution: - items: - properties: - podAffinityTerm: - properties: - labelSelector: - properties: - matchExpressions: - items: - properties: - key: - type: string - operator: - type: string - values: - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - type: object - type: object - x-kubernetes-map-type: atomic - matchLabelKeys: - items: - type: string - type: array - x-kubernetes-list-type: atomic - mismatchLabelKeys: - items: - type: string - type: array - x-kubernetes-list-type: atomic - namespaceSelector: - properties: - matchExpressions: - items: - properties: - key: - type: string - operator: - type: string - values: - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - type: object - type: object - x-kubernetes-map-type: atomic - namespaces: - items: - type: string - type: array - x-kubernetes-list-type: atomic - topologyKey: - type: string - required: - - topologyKey - type: object - weight: - format: int32 - type: integer - required: - - podAffinityTerm - - weight - type: object - type: array - x-kubernetes-list-type: atomic - requiredDuringSchedulingIgnoredDuringExecution: - items: - properties: - labelSelector: - properties: - matchExpressions: - items: - properties: - key: - type: string - operator: - type: string - values: - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - type: object - type: object - x-kubernetes-map-type: atomic - matchLabelKeys: - items: - type: string - type: array - x-kubernetes-list-type: atomic - mismatchLabelKeys: - items: - type: string - type: array - x-kubernetes-list-type: atomic - namespaceSelector: - properties: - matchExpressions: - items: - properties: - key: - type: string - operator: - type: string - values: - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - type: object - type: object - x-kubernetes-map-type: atomic - namespaces: - items: - type: string - type: array - x-kubernetes-list-type: atomic - topologyKey: - type: string - required: - - topologyKey - type: object - type: array - x-kubernetes-list-type: atomic - type: object - podAntiAffinity: - properties: - preferredDuringSchedulingIgnoredDuringExecution: - items: - properties: - podAffinityTerm: - properties: - labelSelector: - properties: - matchExpressions: - items: - properties: - key: - type: string - operator: - type: string - values: - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - type: object - type: object - x-kubernetes-map-type: atomic - matchLabelKeys: - items: - type: string - type: array - x-kubernetes-list-type: atomic - mismatchLabelKeys: - items: - type: string - type: array - x-kubernetes-list-type: atomic - namespaceSelector: - properties: - matchExpressions: - items: - properties: - key: - type: string - operator: - type: string - values: - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - type: object - type: object - x-kubernetes-map-type: atomic - namespaces: - items: - type: string - type: array - x-kubernetes-list-type: atomic - topologyKey: - type: string - required: - - topologyKey - type: object - weight: - format: int32 - type: integer - required: - - podAffinityTerm - - weight - type: object - type: array - x-kubernetes-list-type: atomic - requiredDuringSchedulingIgnoredDuringExecution: - items: - properties: - labelSelector: - properties: - matchExpressions: - items: - properties: - key: - type: string - operator: - type: string - values: - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - type: object - type: object - x-kubernetes-map-type: atomic - matchLabelKeys: - items: - type: string - type: array - x-kubernetes-list-type: atomic - mismatchLabelKeys: - items: - type: string - type: array - x-kubernetes-list-type: atomic - namespaceSelector: - properties: - matchExpressions: - items: - properties: - key: - type: string - operator: - type: string - values: - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - type: object - type: object - x-kubernetes-map-type: atomic - namespaces: - items: - type: string - type: array - x-kubernetes-list-type: atomic - topologyKey: - type: string - required: - - topologyKey - type: object - type: array - x-kubernetes-list-type: atomic - type: object - type: object - automountServiceAccountToken: - type: boolean - containers: - items: - properties: - args: - items: - type: string - type: array - x-kubernetes-list-type: atomic - command: - items: - type: string - type: array - x-kubernetes-list-type: atomic - env: - items: - properties: - name: - type: string - value: - type: string - valueFrom: - properties: - configMapKeyRef: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - fieldRef: - properties: - apiVersion: - type: string - fieldPath: - type: string - required: - - fieldPath - type: object - x-kubernetes-map-type: atomic - fileKeyRef: - properties: - key: - type: string - optional: - default: false - type: boolean - path: - type: string - volumeName: - type: string - required: - - key - - path - - volumeName - type: object - x-kubernetes-map-type: atomic - resourceFieldRef: - properties: - containerName: - type: string - divisor: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - resource: - type: string - required: - - resource - type: object - x-kubernetes-map-type: atomic - secretKeyRef: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - required: - - name - type: object - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - envFrom: - items: - properties: - configMapRef: - properties: - name: - default: "" - type: string - optional: - type: boolean - type: object - x-kubernetes-map-type: atomic - prefix: - type: string - secretRef: - properties: - name: - default: "" - type: string - optional: - type: boolean - type: object - x-kubernetes-map-type: atomic - type: object - type: array - x-kubernetes-list-type: atomic - image: - type: string - imagePullPolicy: - type: string - lifecycle: - properties: - postStart: - properties: - exec: - properties: - command: - items: - type: string - type: array - x-kubernetes-list-type: atomic - type: object - httpGet: - properties: - host: - type: string - httpHeaders: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - x-kubernetes-list-type: atomic - path: - type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - scheme: - type: string - required: - - port - type: object - sleep: - properties: - seconds: - format: int64 - type: integer - required: - - seconds - type: object - tcpSocket: - properties: - host: - type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - required: - - port - type: object - type: object - preStop: - properties: - exec: - properties: - command: - items: - type: string - type: array - x-kubernetes-list-type: atomic - type: object - httpGet: - properties: - host: - type: string - httpHeaders: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - x-kubernetes-list-type: atomic - path: - type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - scheme: - type: string - required: - - port - type: object - sleep: - properties: - seconds: - format: int64 - type: integer - required: - - seconds - type: object - tcpSocket: - properties: - host: - type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - required: - - port - type: object - type: object - stopSignal: - type: string - type: object - livenessProbe: - properties: - exec: - properties: - command: - items: - type: string - type: array - x-kubernetes-list-type: atomic - type: object - failureThreshold: - format: int32 - type: integer - grpc: - properties: - port: - format: int32 - type: integer - service: - default: "" - type: string - required: - - port - type: object - httpGet: - properties: - host: - type: string - httpHeaders: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - x-kubernetes-list-type: atomic - path: - type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - scheme: - type: string - required: - - port - type: object - initialDelaySeconds: - format: int32 - type: integer - periodSeconds: - format: int32 - type: integer - successThreshold: - format: int32 - type: integer - tcpSocket: - properties: - host: - type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - required: - - port - type: object - terminationGracePeriodSeconds: - format: int64 - type: integer - timeoutSeconds: - format: int32 - type: integer - type: object - name: - type: string - ports: - items: - properties: - containerPort: - format: int32 - type: integer - hostIP: - type: string - hostPort: - format: int32 - type: integer - name: - type: string - protocol: - default: TCP - type: string - required: - - containerPort - type: object - type: array - x-kubernetes-list-map-keys: - - containerPort - - protocol - x-kubernetes-list-type: map - readinessProbe: - properties: - exec: - properties: - command: - items: - type: string - type: array - x-kubernetes-list-type: atomic - type: object - failureThreshold: - format: int32 - type: integer - grpc: - properties: - port: - format: int32 - type: integer - service: - default: "" - type: string - required: - - port - type: object - httpGet: - properties: - host: - type: string - httpHeaders: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - x-kubernetes-list-type: atomic - path: - type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - scheme: - type: string - required: - - port - type: object - initialDelaySeconds: - format: int32 - type: integer - periodSeconds: - format: int32 - type: integer - successThreshold: - format: int32 - type: integer - tcpSocket: - properties: - host: - type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - required: - - port - type: object - terminationGracePeriodSeconds: - format: int64 - type: integer - timeoutSeconds: - format: int32 - type: integer - type: object - resizePolicy: - items: - properties: - resourceName: - type: string - restartPolicy: - type: string - required: - - resourceName - - restartPolicy - type: object - type: array - x-kubernetes-list-type: atomic - resources: - properties: - claims: - items: - properties: - name: - type: string - request: - type: string - required: - - name - type: object - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - limits: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - type: object - requests: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - type: object - type: object - restartPolicy: - type: string - restartPolicyRules: - items: - properties: - action: - type: string - exitCodes: - properties: - operator: - type: string - values: - items: - format: int32 - type: integer - type: array - x-kubernetes-list-type: set - required: - - operator - type: object - required: - - action - type: object - type: array - x-kubernetes-list-type: atomic - securityContext: - properties: - allowPrivilegeEscalation: - type: boolean - appArmorProfile: - properties: - localhostProfile: - type: string - type: - type: string - required: - - type - type: object - capabilities: - properties: - add: - items: - type: string - type: array - x-kubernetes-list-type: atomic - drop: - items: - type: string - type: array - x-kubernetes-list-type: atomic - type: object - privileged: - type: boolean - procMount: - type: string - readOnlyRootFilesystem: - type: boolean - runAsGroup: - format: int64 - type: integer - runAsNonRoot: - type: boolean - runAsUser: - format: int64 - type: integer - seLinuxOptions: - properties: - level: - type: string - role: - type: string - type: - type: string - user: - type: string - type: object - seccompProfile: - properties: - localhostProfile: - type: string - type: - type: string - required: - - type - type: object - windowsOptions: - properties: - gmsaCredentialSpec: - type: string - gmsaCredentialSpecName: - type: string - hostProcess: - type: boolean - runAsUserName: - type: string - type: object - type: object - startupProbe: - properties: - exec: - properties: - command: - items: - type: string - type: array - x-kubernetes-list-type: atomic - type: object - failureThreshold: - format: int32 - type: integer - grpc: - properties: - port: - format: int32 - type: integer - service: - default: "" - type: string - required: - - port - type: object - httpGet: - properties: - host: - type: string - httpHeaders: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - x-kubernetes-list-type: atomic - path: - type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - scheme: - type: string - required: - - port - type: object - initialDelaySeconds: - format: int32 - type: integer - periodSeconds: - format: int32 - type: integer - successThreshold: - format: int32 - type: integer - tcpSocket: - properties: - host: - type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - required: - - port - type: object - terminationGracePeriodSeconds: - format: int64 - type: integer - timeoutSeconds: - format: int32 - type: integer - type: object - stdin: - type: boolean - stdinOnce: - type: boolean - terminationMessagePath: - type: string - terminationMessagePolicy: - type: string - tty: - type: boolean - volumeDevices: - items: - properties: - devicePath: - type: string - name: - type: string - required: - - devicePath - - name - type: object - type: array - x-kubernetes-list-map-keys: - - devicePath - x-kubernetes-list-type: map - volumeMounts: - items: - properties: - mountPath: - type: string - mountPropagation: - type: string - name: - type: string - readOnly: - type: boolean - recursiveReadOnly: - type: string - subPath: - type: string - subPathExpr: - type: string - required: - - mountPath - - name - type: object - type: array - x-kubernetes-list-map-keys: - - mountPath - x-kubernetes-list-type: map - workingDir: - type: string - required: - - name - type: object - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - dnsConfig: - properties: - nameservers: - items: - type: string - type: array - x-kubernetes-list-type: atomic - options: - items: - properties: - name: - type: string - value: - type: string - type: object - type: array - x-kubernetes-list-type: atomic - searches: - items: - type: string - type: array - x-kubernetes-list-type: atomic - type: object - dnsPolicy: - type: string - enableServiceLinks: - type: boolean - ephemeralContainers: - items: - properties: - args: - items: - type: string - type: array - x-kubernetes-list-type: atomic - command: - items: - type: string - type: array - x-kubernetes-list-type: atomic - env: - items: - properties: - name: - type: string - value: - type: string - valueFrom: - properties: - configMapKeyRef: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - fieldRef: - properties: - apiVersion: - type: string - fieldPath: - type: string - required: - - fieldPath - type: object - x-kubernetes-map-type: atomic - fileKeyRef: - properties: - key: - type: string - optional: - default: false - type: boolean - path: - type: string - volumeName: - type: string - required: - - key - - path - - volumeName - type: object - x-kubernetes-map-type: atomic - resourceFieldRef: - properties: - containerName: - type: string - divisor: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - resource: - type: string - required: - - resource - type: object - x-kubernetes-map-type: atomic - secretKeyRef: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - required: - - name - type: object - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - envFrom: - items: - properties: - configMapRef: - properties: - name: - default: "" - type: string - optional: - type: boolean - type: object - x-kubernetes-map-type: atomic - prefix: - type: string - secretRef: - properties: - name: - default: "" - type: string - optional: - type: boolean - type: object - x-kubernetes-map-type: atomic - type: object - type: array - x-kubernetes-list-type: atomic - image: - type: string - imagePullPolicy: - type: string - lifecycle: - properties: - postStart: - properties: - exec: - properties: - command: - items: - type: string - type: array - x-kubernetes-list-type: atomic - type: object - httpGet: - properties: - host: - type: string - httpHeaders: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - x-kubernetes-list-type: atomic - path: - type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - scheme: - type: string - required: - - port - type: object - sleep: - properties: - seconds: - format: int64 - type: integer - required: - - seconds - type: object - tcpSocket: - properties: - host: - type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - required: - - port - type: object - type: object - preStop: - properties: - exec: - properties: - command: - items: - type: string - type: array - x-kubernetes-list-type: atomic - type: object - httpGet: - properties: - host: - type: string - httpHeaders: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - x-kubernetes-list-type: atomic - path: - type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - scheme: - type: string - required: - - port - type: object - sleep: - properties: - seconds: - format: int64 - type: integer - required: - - seconds - type: object - tcpSocket: - properties: - host: - type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - required: - - port - type: object - type: object - stopSignal: - type: string - type: object - livenessProbe: - properties: - exec: - properties: - command: - items: - type: string - type: array - x-kubernetes-list-type: atomic - type: object - failureThreshold: - format: int32 - type: integer - grpc: - properties: - port: - format: int32 - type: integer - service: - default: "" - type: string - required: - - port - type: object - httpGet: - properties: - host: - type: string - httpHeaders: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - x-kubernetes-list-type: atomic - path: - type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - scheme: - type: string - required: - - port - type: object - initialDelaySeconds: - format: int32 - type: integer - periodSeconds: - format: int32 - type: integer - successThreshold: - format: int32 - type: integer - tcpSocket: - properties: - host: - type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - required: - - port - type: object - terminationGracePeriodSeconds: - format: int64 - type: integer - timeoutSeconds: - format: int32 - type: integer - type: object - name: - type: string - ports: - items: - properties: - containerPort: - format: int32 - type: integer - hostIP: - type: string - hostPort: - format: int32 - type: integer - name: - type: string - protocol: - default: TCP - type: string - required: - - containerPort - type: object - type: array - x-kubernetes-list-map-keys: - - containerPort - - protocol - x-kubernetes-list-type: map - readinessProbe: - properties: - exec: - properties: - command: - items: - type: string - type: array - x-kubernetes-list-type: atomic - type: object - failureThreshold: - format: int32 - type: integer - grpc: - properties: - port: - format: int32 - type: integer - service: - default: "" - type: string - required: - - port - type: object - httpGet: - properties: - host: - type: string - httpHeaders: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - x-kubernetes-list-type: atomic - path: - type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - scheme: - type: string - required: - - port - type: object - initialDelaySeconds: - format: int32 - type: integer - periodSeconds: - format: int32 - type: integer - successThreshold: - format: int32 - type: integer - tcpSocket: - properties: - host: - type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - required: - - port - type: object - terminationGracePeriodSeconds: - format: int64 - type: integer - timeoutSeconds: - format: int32 - type: integer - type: object - resizePolicy: - items: - properties: - resourceName: - type: string - restartPolicy: - type: string - required: - - resourceName - - restartPolicy - type: object - type: array - x-kubernetes-list-type: atomic - resources: - properties: - claims: - items: - properties: - name: - type: string - request: - type: string - required: - - name - type: object - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - limits: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - type: object - requests: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - type: object - type: object - restartPolicy: - type: string - restartPolicyRules: - items: - properties: - action: - type: string - exitCodes: - properties: - operator: - type: string - values: - items: - format: int32 - type: integer - type: array - x-kubernetes-list-type: set - required: - - operator - type: object - required: - - action - type: object - type: array - x-kubernetes-list-type: atomic - securityContext: - properties: - allowPrivilegeEscalation: - type: boolean - appArmorProfile: - properties: - localhostProfile: - type: string - type: - type: string - required: - - type - type: object - capabilities: - properties: - add: - items: - type: string - type: array - x-kubernetes-list-type: atomic - drop: - items: - type: string - type: array - x-kubernetes-list-type: atomic - type: object - privileged: - type: boolean - procMount: - type: string - readOnlyRootFilesystem: - type: boolean - runAsGroup: - format: int64 - type: integer - runAsNonRoot: - type: boolean - runAsUser: - format: int64 - type: integer - seLinuxOptions: - properties: - level: - type: string - role: - type: string - type: - type: string - user: - type: string - type: object - seccompProfile: - properties: - localhostProfile: - type: string - type: - type: string - required: - - type - type: object - windowsOptions: - properties: - gmsaCredentialSpec: - type: string - gmsaCredentialSpecName: - type: string - hostProcess: - type: boolean - runAsUserName: - type: string - type: object - type: object - startupProbe: - properties: - exec: - properties: - command: - items: - type: string - type: array - x-kubernetes-list-type: atomic - type: object - failureThreshold: - format: int32 - type: integer - grpc: - properties: - port: - format: int32 - type: integer - service: - default: "" - type: string - required: - - port - type: object - httpGet: - properties: - host: - type: string - httpHeaders: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - x-kubernetes-list-type: atomic - path: - type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - scheme: - type: string - required: - - port - type: object - initialDelaySeconds: - format: int32 - type: integer - periodSeconds: - format: int32 - type: integer - successThreshold: - format: int32 - type: integer - tcpSocket: - properties: - host: - type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - required: - - port - type: object - terminationGracePeriodSeconds: - format: int64 - type: integer - timeoutSeconds: - format: int32 - type: integer - type: object - stdin: - type: boolean - stdinOnce: - type: boolean - targetContainerName: - type: string - terminationMessagePath: - type: string - terminationMessagePolicy: - type: string - tty: - type: boolean - volumeDevices: - items: - properties: - devicePath: - type: string - name: - type: string - required: - - devicePath - - name - type: object - type: array - x-kubernetes-list-map-keys: - - devicePath - x-kubernetes-list-type: map - volumeMounts: - items: - properties: - mountPath: - type: string - mountPropagation: - type: string - name: - type: string - readOnly: - type: boolean - recursiveReadOnly: - type: string - subPath: - type: string - subPathExpr: - type: string - required: - - mountPath - - name - type: object - type: array - x-kubernetes-list-map-keys: - - mountPath - x-kubernetes-list-type: map - workingDir: - type: string - required: - - name - type: object - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - hostAliases: - items: - properties: - hostnames: - items: - type: string - type: array - x-kubernetes-list-type: atomic - ip: - type: string - required: - - ip - type: object - type: array - x-kubernetes-list-map-keys: - - ip - x-kubernetes-list-type: map - hostIPC: - type: boolean - hostNetwork: - type: boolean - hostPID: - type: boolean - hostUsers: - type: boolean - hostname: - type: string - hostnameOverride: - type: string - imagePullSecrets: - items: - properties: - name: - default: "" - type: string - type: object - x-kubernetes-map-type: atomic - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - initContainers: - items: - properties: - args: - items: - type: string - type: array - x-kubernetes-list-type: atomic - command: - items: - type: string - type: array - x-kubernetes-list-type: atomic - env: - items: - properties: - name: - type: string - value: - type: string - valueFrom: - properties: - configMapKeyRef: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - fieldRef: - properties: - apiVersion: - type: string - fieldPath: - type: string - required: - - fieldPath - type: object - x-kubernetes-map-type: atomic - fileKeyRef: - properties: - key: - type: string - optional: - default: false - type: boolean - path: - type: string - volumeName: - type: string - required: - - key - - path - - volumeName - type: object - x-kubernetes-map-type: atomic - resourceFieldRef: - properties: - containerName: - type: string - divisor: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - resource: - type: string - required: - - resource - type: object - x-kubernetes-map-type: atomic - secretKeyRef: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - required: - - name - type: object - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - envFrom: - items: - properties: - configMapRef: - properties: - name: - default: "" - type: string - optional: - type: boolean - type: object - x-kubernetes-map-type: atomic - prefix: - type: string - secretRef: - properties: - name: - default: "" - type: string - optional: - type: boolean - type: object - x-kubernetes-map-type: atomic - type: object - type: array - x-kubernetes-list-type: atomic - image: - type: string - imagePullPolicy: - type: string - lifecycle: - properties: - postStart: - properties: - exec: - properties: - command: - items: - type: string - type: array - x-kubernetes-list-type: atomic - type: object - httpGet: - properties: - host: - type: string - httpHeaders: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - x-kubernetes-list-type: atomic - path: - type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - scheme: - type: string - required: - - port - type: object - sleep: - properties: - seconds: - format: int64 - type: integer - required: - - seconds - type: object - tcpSocket: - properties: - host: - type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - required: - - port - type: object - type: object - preStop: - properties: - exec: - properties: - command: - items: - type: string - type: array - x-kubernetes-list-type: atomic - type: object - httpGet: - properties: - host: - type: string - httpHeaders: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - x-kubernetes-list-type: atomic - path: - type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - scheme: - type: string - required: - - port - type: object - sleep: - properties: - seconds: - format: int64 - type: integer - required: - - seconds - type: object - tcpSocket: - properties: - host: - type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - required: - - port - type: object - type: object - stopSignal: - type: string - type: object - livenessProbe: - properties: - exec: - properties: - command: - items: - type: string - type: array - x-kubernetes-list-type: atomic - type: object - failureThreshold: - format: int32 - type: integer - grpc: - properties: - port: - format: int32 - type: integer - service: - default: "" - type: string - required: - - port - type: object - httpGet: - properties: - host: - type: string - httpHeaders: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - x-kubernetes-list-type: atomic - path: - type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - scheme: - type: string - required: - - port - type: object - initialDelaySeconds: - format: int32 - type: integer - periodSeconds: - format: int32 - type: integer - successThreshold: - format: int32 - type: integer - tcpSocket: - properties: - host: - type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - required: - - port - type: object - terminationGracePeriodSeconds: - format: int64 - type: integer - timeoutSeconds: - format: int32 - type: integer - type: object - name: - type: string - ports: - items: - properties: - containerPort: - format: int32 - type: integer - hostIP: - type: string - hostPort: - format: int32 - type: integer - name: - type: string - protocol: - default: TCP - type: string - required: - - containerPort - type: object - type: array - x-kubernetes-list-map-keys: - - containerPort - - protocol - x-kubernetes-list-type: map - readinessProbe: - properties: - exec: - properties: - command: - items: - type: string - type: array - x-kubernetes-list-type: atomic - type: object - failureThreshold: - format: int32 - type: integer - grpc: - properties: - port: - format: int32 - type: integer - service: - default: "" - type: string - required: - - port - type: object - httpGet: - properties: - host: - type: string - httpHeaders: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - x-kubernetes-list-type: atomic - path: - type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - scheme: - type: string - required: - - port - type: object - initialDelaySeconds: - format: int32 - type: integer - periodSeconds: - format: int32 - type: integer - successThreshold: - format: int32 - type: integer - tcpSocket: - properties: - host: - type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - required: - - port - type: object - terminationGracePeriodSeconds: - format: int64 - type: integer - timeoutSeconds: - format: int32 - type: integer - type: object - resizePolicy: - items: - properties: - resourceName: - type: string - restartPolicy: - type: string - required: - - resourceName - - restartPolicy - type: object - type: array - x-kubernetes-list-type: atomic - resources: - properties: - claims: - items: - properties: - name: - type: string - request: - type: string - required: - - name - type: object - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - limits: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - type: object - requests: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - type: object - type: object - restartPolicy: - type: string - restartPolicyRules: - items: - properties: - action: - type: string - exitCodes: - properties: - operator: - type: string - values: - items: - format: int32 - type: integer - type: array - x-kubernetes-list-type: set - required: - - operator - type: object - required: - - action - type: object - type: array - x-kubernetes-list-type: atomic - securityContext: - properties: - allowPrivilegeEscalation: - type: boolean - appArmorProfile: - properties: - localhostProfile: - type: string - type: - type: string - required: - - type - type: object - capabilities: - properties: - add: - items: - type: string - type: array - x-kubernetes-list-type: atomic - drop: - items: - type: string - type: array - x-kubernetes-list-type: atomic - type: object - privileged: - type: boolean - procMount: - type: string - readOnlyRootFilesystem: - type: boolean - runAsGroup: - format: int64 - type: integer - runAsNonRoot: - type: boolean - runAsUser: - format: int64 - type: integer - seLinuxOptions: - properties: - level: - type: string - role: - type: string - type: - type: string - user: - type: string - type: object - seccompProfile: - properties: - localhostProfile: - type: string - type: - type: string - required: - - type - type: object - windowsOptions: - properties: - gmsaCredentialSpec: - type: string - gmsaCredentialSpecName: - type: string - hostProcess: - type: boolean - runAsUserName: - type: string - type: object - type: object - startupProbe: - properties: - exec: - properties: - command: - items: - type: string - type: array - x-kubernetes-list-type: atomic - type: object - failureThreshold: - format: int32 - type: integer - grpc: - properties: - port: - format: int32 - type: integer - service: - default: "" - type: string - required: - - port - type: object - httpGet: - properties: - host: - type: string - httpHeaders: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - x-kubernetes-list-type: atomic - path: - type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - scheme: - type: string - required: - - port - type: object - initialDelaySeconds: - format: int32 - type: integer - periodSeconds: - format: int32 - type: integer - successThreshold: - format: int32 - type: integer - tcpSocket: - properties: - host: - type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - required: - - port - type: object - terminationGracePeriodSeconds: - format: int64 - type: integer - timeoutSeconds: - format: int32 - type: integer - type: object - stdin: - type: boolean - stdinOnce: - type: boolean - terminationMessagePath: - type: string - terminationMessagePolicy: - type: string - tty: - type: boolean - volumeDevices: - items: - properties: - devicePath: - type: string - name: - type: string - required: - - devicePath - - name - type: object - type: array - x-kubernetes-list-map-keys: - - devicePath - x-kubernetes-list-type: map - volumeMounts: - items: - properties: - mountPath: - type: string - mountPropagation: - type: string - name: - type: string - readOnly: - type: boolean - recursiveReadOnly: - type: string - subPath: - type: string - subPathExpr: - type: string - required: - - mountPath - - name - type: object - type: array - x-kubernetes-list-map-keys: - - mountPath - x-kubernetes-list-type: map - workingDir: - type: string - required: - - name - type: object - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - nodeName: - type: string - nodeSelector: - additionalProperties: - type: string - type: object - x-kubernetes-map-type: atomic - os: - properties: - name: - type: string - required: - - name - type: object - overhead: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - type: object - preemptionPolicy: - type: string - priority: - format: int32 - type: integer - priorityClassName: - type: string - readinessGates: - items: - properties: - conditionType: - type: string - required: - - conditionType - type: object - type: array - x-kubernetes-list-type: atomic - resourceClaims: - items: - properties: - name: - type: string - resourceClaimName: - type: string - resourceClaimTemplateName: - type: string - required: - - name - type: object - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - resources: - properties: - claims: - items: - properties: - name: - type: string - request: - type: string - required: - - name - type: object - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - limits: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - type: object - requests: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - type: object - type: object - restartPolicy: - type: string - runtimeClassName: - type: string - schedulerName: - type: string - schedulingGates: - items: - properties: - name: - type: string - required: - - name - type: object - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - securityContext: - properties: - appArmorProfile: - properties: - localhostProfile: - type: string - type: - type: string - required: - - type - type: object - fsGroup: - format: int64 - type: integer - fsGroupChangePolicy: - type: string - runAsGroup: - format: int64 - type: integer - runAsNonRoot: - type: boolean - runAsUser: - format: int64 - type: integer - seLinuxChangePolicy: - type: string - seLinuxOptions: - properties: - level: - type: string - role: - type: string - type: - type: string - user: - type: string - type: object - seccompProfile: - properties: - localhostProfile: - type: string - type: - type: string - required: - - type - type: object - supplementalGroups: - items: - format: int64 - type: integer - type: array - x-kubernetes-list-type: atomic - supplementalGroupsPolicy: - type: string - sysctls: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - x-kubernetes-list-type: atomic - windowsOptions: - properties: - gmsaCredentialSpec: - type: string - gmsaCredentialSpecName: - type: string - hostProcess: - type: boolean - runAsUserName: - type: string - type: object - type: object - serviceAccount: - type: string - serviceAccountName: - type: string - setHostnameAsFQDN: - type: boolean - shareProcessNamespace: - type: boolean - subdomain: - type: string - terminationGracePeriodSeconds: - format: int64 - type: integer - tolerations: - items: - properties: - effect: - type: string - key: - type: string - operator: - type: string - tolerationSeconds: - format: int64 - type: integer - value: - type: string - type: object - type: array - x-kubernetes-list-type: atomic - topologySpreadConstraints: - items: - properties: - labelSelector: - properties: - matchExpressions: - items: - properties: - key: - type: string - operator: - type: string - values: - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - type: object - type: object - x-kubernetes-map-type: atomic - matchLabelKeys: - items: - type: string - type: array - x-kubernetes-list-type: atomic - maxSkew: - format: int32 - type: integer - minDomains: - format: int32 - type: integer - nodeAffinityPolicy: - type: string - nodeTaintsPolicy: - type: string - topologyKey: - type: string - whenUnsatisfiable: - type: string - required: - - maxSkew - - topologyKey - - whenUnsatisfiable - type: object - type: array - x-kubernetes-list-map-keys: - - topologyKey - - whenUnsatisfiable - x-kubernetes-list-type: map - volumes: - items: - properties: - awsElasticBlockStore: - properties: - fsType: - type: string - partition: - format: int32 - type: integer - readOnly: - type: boolean - volumeID: - type: string - required: - - volumeID - type: object - azureDisk: - properties: - cachingMode: - type: string - diskName: - type: string - diskURI: - type: string - fsType: - default: ext4 - type: string - kind: - type: string - readOnly: - default: false - type: boolean - required: - - diskName - - diskURI - type: object - azureFile: - properties: - readOnly: - type: boolean - secretName: - type: string - shareName: - type: string - required: - - secretName - - shareName - type: object - cephfs: - properties: - monitors: - items: - type: string - type: array - x-kubernetes-list-type: atomic - path: - type: string - readOnly: - type: boolean - secretFile: - type: string - secretRef: - properties: - name: - default: "" - type: string - type: object - x-kubernetes-map-type: atomic - user: - type: string - required: - - monitors - type: object - cinder: - properties: - fsType: - type: string - readOnly: - type: boolean - secretRef: - properties: - name: - default: "" - type: string - type: object - x-kubernetes-map-type: atomic - volumeID: - type: string - required: - - volumeID - type: object - configMap: - properties: - defaultMode: - format: int32 - type: integer - items: - items: - properties: - key: - type: string - mode: - format: int32 - type: integer - path: - type: string - required: - - key - - path - type: object - type: array - x-kubernetes-list-type: atomic - name: - default: "" - type: string - optional: - type: boolean - type: object - x-kubernetes-map-type: atomic - csi: - properties: - driver: - type: string - fsType: - type: string - nodePublishSecretRef: - properties: - name: - default: "" - type: string - type: object - x-kubernetes-map-type: atomic - readOnly: - type: boolean - volumeAttributes: - additionalProperties: - type: string - type: object - required: - - driver - type: object - downwardAPI: - properties: - defaultMode: - format: int32 - type: integer - items: - items: - properties: - fieldRef: - properties: - apiVersion: - type: string - fieldPath: - type: string - required: - - fieldPath - type: object - x-kubernetes-map-type: atomic - mode: - format: int32 - type: integer - path: - type: string - resourceFieldRef: - properties: - containerName: - type: string - divisor: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - resource: - type: string - required: - - resource - type: object - x-kubernetes-map-type: atomic - required: - - path - type: object - type: array - x-kubernetes-list-type: atomic - type: object - emptyDir: - properties: - medium: - type: string - sizeLimit: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - type: object - ephemeral: - properties: - volumeClaimTemplate: - properties: - metadata: - type: object - spec: - properties: - accessModes: - items: - type: string - type: array - x-kubernetes-list-type: atomic - dataSource: - properties: - apiGroup: - type: string - kind: - type: string - name: - type: string - required: - - kind - - name - type: object - x-kubernetes-map-type: atomic - dataSourceRef: - properties: - apiGroup: - type: string - kind: - type: string - name: - type: string - namespace: - type: string - required: - - kind - - name - type: object - resources: - properties: - limits: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - type: object - requests: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - type: object - type: object - selector: - properties: - matchExpressions: - items: - properties: - key: - type: string - operator: - type: string - values: - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - type: object - type: object - x-kubernetes-map-type: atomic - storageClassName: - type: string - volumeAttributesClassName: - type: string - volumeMode: - type: string - volumeName: - type: string - type: object - required: - - spec - type: object - type: object - fc: - properties: - fsType: - type: string - lun: - format: int32 - type: integer - readOnly: - type: boolean - targetWWNs: - items: - type: string - type: array - x-kubernetes-list-type: atomic - wwids: - items: - type: string - type: array - x-kubernetes-list-type: atomic - type: object - flexVolume: - properties: - driver: - type: string - fsType: - type: string - options: - additionalProperties: - type: string - type: object - readOnly: - type: boolean - secretRef: - properties: - name: - default: "" - type: string - type: object - x-kubernetes-map-type: atomic - required: - - driver - type: object - flocker: - properties: - datasetName: - type: string - datasetUUID: - type: string - type: object - gcePersistentDisk: - properties: - fsType: - type: string - partition: - format: int32 - type: integer - pdName: - type: string - readOnly: - type: boolean - required: - - pdName - type: object - gitRepo: - properties: - directory: - type: string - repository: - type: string - revision: - type: string - required: - - repository - type: object - glusterfs: - properties: - endpoints: - type: string - path: - type: string - readOnly: - type: boolean - required: - - endpoints - - path - type: object - hostPath: - properties: - path: - type: string - type: - type: string - required: - - path - type: object - image: - properties: - pullPolicy: - type: string - reference: - type: string - type: object - iscsi: - properties: - chapAuthDiscovery: - type: boolean - chapAuthSession: - type: boolean - fsType: - type: string - initiatorName: - type: string - iqn: - type: string - iscsiInterface: - default: default - type: string - lun: - format: int32 - type: integer - portals: - items: - type: string - type: array - x-kubernetes-list-type: atomic - readOnly: - type: boolean - secretRef: - properties: - name: - default: "" - type: string - type: object - x-kubernetes-map-type: atomic - targetPortal: - type: string - required: - - iqn - - lun - - targetPortal - type: object - name: - type: string - nfs: - properties: - path: - type: string - readOnly: - type: boolean - server: - type: string - required: - - path - - server - type: object - persistentVolumeClaim: - properties: - claimName: - type: string - readOnly: - type: boolean - required: - - claimName - type: object - photonPersistentDisk: - properties: - fsType: - type: string - pdID: - type: string - required: - - pdID - type: object - portworxVolume: - properties: - fsType: - type: string - readOnly: - type: boolean - volumeID: - type: string - required: - - volumeID - type: object - projected: - properties: - defaultMode: - format: int32 - type: integer - sources: - items: - properties: - clusterTrustBundle: - properties: - labelSelector: - properties: - matchExpressions: - items: - properties: - key: - type: string - operator: - type: string - values: - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - type: object - type: object - x-kubernetes-map-type: atomic - name: - type: string - optional: - type: boolean - path: - type: string - signerName: - type: string - required: - - path - type: object - configMap: - properties: - items: - items: - properties: - key: - type: string - mode: - format: int32 - type: integer - path: - type: string - required: - - key - - path - type: object - type: array - x-kubernetes-list-type: atomic - name: - default: "" - type: string - optional: - type: boolean - type: object - x-kubernetes-map-type: atomic - downwardAPI: - properties: - items: - items: - properties: - fieldRef: - properties: - apiVersion: - type: string - fieldPath: - type: string - required: - - fieldPath - type: object - x-kubernetes-map-type: atomic - mode: - format: int32 - type: integer - path: - type: string - resourceFieldRef: - properties: - containerName: - type: string - divisor: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - resource: - type: string - required: - - resource - type: object - x-kubernetes-map-type: atomic - required: - - path - type: object - type: array - x-kubernetes-list-type: atomic - type: object - podCertificate: - properties: - certificateChainPath: - type: string - credentialBundlePath: - type: string - keyPath: - type: string - keyType: - type: string - maxExpirationSeconds: - format: int32 - type: integer - signerName: - type: string - required: - - keyType - - signerName - type: object - secret: - properties: - items: - items: - properties: - key: - type: string - mode: - format: int32 - type: integer - path: - type: string - required: - - key - - path - type: object - type: array - x-kubernetes-list-type: atomic - name: - default: "" - type: string - optional: - type: boolean - type: object - x-kubernetes-map-type: atomic - serviceAccountToken: - properties: - audience: - type: string - expirationSeconds: - format: int64 - type: integer - path: - type: string - required: - - path - type: object - type: object - type: array - x-kubernetes-list-type: atomic - type: object - quobyte: - properties: - group: - type: string - readOnly: - type: boolean - registry: - type: string - tenant: - type: string - user: - type: string - volume: - type: string - required: - - registry - - volume - type: object - rbd: - properties: - fsType: - type: string - image: - type: string - keyring: - default: /etc/ceph/keyring - type: string - monitors: - items: - type: string - type: array - x-kubernetes-list-type: atomic - pool: - default: rbd - type: string - readOnly: - type: boolean - secretRef: - properties: - name: - default: "" - type: string - type: object - x-kubernetes-map-type: atomic - user: - default: admin - type: string - required: - - image - - monitors - type: object - scaleIO: - properties: - fsType: - default: xfs - type: string - gateway: - type: string - protectionDomain: - type: string - readOnly: - type: boolean - secretRef: - properties: - name: - default: "" - type: string - type: object - x-kubernetes-map-type: atomic - sslEnabled: - type: boolean - storageMode: - default: ThinProvisioned - type: string - storagePool: - type: string - system: - type: string - volumeName: - type: string - required: - - gateway - - secretRef - - system - type: object - secret: - properties: - defaultMode: - format: int32 - type: integer - items: - items: - properties: - key: - type: string - mode: - format: int32 - type: integer - path: - type: string - required: - - key - - path - type: object - type: array - x-kubernetes-list-type: atomic - optional: - type: boolean - secretName: - type: string - type: object - storageos: - properties: - fsType: - type: string - readOnly: - type: boolean - secretRef: - properties: - name: - default: "" - type: string - type: object - x-kubernetes-map-type: atomic - volumeName: - type: string - volumeNamespace: - type: string - type: object - vsphereVolume: - properties: - fsType: - type: string - storagePolicyID: - type: string - storagePolicyName: - type: string - volumePath: - type: string - required: - - volumePath - type: object - required: - - name - type: object - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - required: - - containers - type: object - required: - - spec - type: object - required: - - podTemplate - type: object - status: - type: object - required: - - spec - type: object - served: true - storage: true - subresources: - status: {} diff --git a/helm/kagent-crds/templates/kagent.dev_sandboxagents.yaml b/helm/kagent-crds/templates/kagent.dev_sandboxagents.yaml index f8903d9af..35d6b08ec 100644 --- a/helm/kagent-crds/templates/kagent.dev_sandboxagents.yaml +++ b/helm/kagent-crds/templates/kagent.dev_sandboxagents.yaml @@ -27,7 +27,7 @@ spec: schema: openAPIV3Schema: description: SandboxAgent declares an agent that runs in an isolated sandbox - (agent-sandbox SandboxTemplate+SandboxClaim). + (agent-sandbox Sandbox CR). properties: apiVersion: description: |- diff --git a/helm/kagent/templates/rbac/getter-role.yaml b/helm/kagent/templates/rbac/getter-role.yaml index 78b46f7bb..b5e317017 100644 --- a/helm/kagent/templates/rbac/getter-role.yaml +++ b/helm/kagent/templates/rbac/getter-role.yaml @@ -90,15 +90,6 @@ - get - list - watch -- apiGroups: - - extensions.agents.x-k8s.io - resources: - - sandboxtemplates - - sandboxclaims - verbs: - - get - - list - - watch {{- end -}} {{- if .Values.rbac.clusterScoped }} diff --git a/helm/kagent/templates/rbac/writer-role.yaml b/helm/kagent/templates/rbac/writer-role.yaml index 9cfd33732..941334e31 100644 --- a/helm/kagent/templates/rbac/writer-role.yaml +++ b/helm/kagent/templates/rbac/writer-role.yaml @@ -73,16 +73,6 @@ - update - patch - delete -- apiGroups: - - extensions.agents.x-k8s.io - resources: - - sandboxtemplates - - sandboxclaims - verbs: - - create - - update - - patch - - delete {{- end -}} {{- if .Values.rbac.clusterScoped }} From 6b235d522035000fc2ecd3320f4676eecd9e74fe Mon Sep 17 00:00:00 2001 From: Peter Jausovec Date: Thu, 9 Apr 2026 15:04:48 +0200 Subject: [PATCH 04/13] fix lint issues Signed-off-by: Peter Jausovec --- go/core/pkg/sandboxbackend/backend.go | 4 ++-- ui/src/app/agents/new/page.tsx | 3 +-- ui/src/components/chat/ChatInterface.tsx | 1 - 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/go/core/pkg/sandboxbackend/backend.go b/go/core/pkg/sandboxbackend/backend.go index d0c39c167..163b18fb5 100644 --- a/go/core/pkg/sandboxbackend/backend.go +++ b/go/core/pkg/sandboxbackend/backend.go @@ -12,8 +12,8 @@ import ( // BuildInput carries the pod template for a Sandbox workload (agents.x-k8s.io Sandbox). type BuildInput struct { - Agent *v1alpha2.Agent - PodTemplate corev1.PodTemplateSpec + Agent *v1alpha2.Agent + PodTemplate corev1.PodTemplateSpec WorkloadName string ExtraLabels map[string]string } diff --git a/ui/src/app/agents/new/page.tsx b/ui/src/app/agents/new/page.tsx index 5ec7ff2cc..5bc7b0bc6 100644 --- a/ui/src/app/agents/new/page.tsx +++ b/ui/src/app/agents/new/page.tsx @@ -4,7 +4,7 @@ import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import { Textarea } from "@/components/ui/textarea"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { Brain, Info, Loader2, Settings2, PlusCircle, Trash2, Layers } from "lucide-react"; +import { Brain, Loader2, Settings2, PlusCircle, Trash2, Layers } from "lucide-react"; import { formAgentTypeFromApi, formUsesByoSections, formUsesDeclarativeSections } from "@/lib/agentFormLayout"; import { ModelConfig, AgentType, ContextConfig } from "@/types"; import { SystemPromptSection } from "@/components/create/SystemPromptSection"; @@ -24,7 +24,6 @@ import { NamespaceCombobox } from "@/components/NamespaceCombobox"; import { Label } from "@/components/ui/label"; import { Checkbox } from "@/components/ui/checkbox"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; -import { Alert, AlertDescription } from "@/components/ui/alert"; interface ValidationErrors { name?: string; namespace?: string; diff --git a/ui/src/components/chat/ChatInterface.tsx b/ui/src/components/chat/ChatInterface.tsx index 22282c378..d03bd5290 100644 --- a/ui/src/components/chat/ChatInterface.tsx +++ b/ui/src/components/chat/ChatInterface.tsx @@ -37,7 +37,6 @@ interface ChatInterfaceProps { } export default function ChatInterface({ selectedAgentName, selectedNamespace, selectedSession, sessionId }: ChatInterfaceProps) { - const agentType = useChatAgentType(); const runInSandbox = useChatRunInSandbox(); const router = useRouter(); const containerRef = useRef(null); From ef71b3a7ac16420b3de47b0e9838b3c74b4ff802 Mon Sep 17 00:00:00 2001 From: Peter Jausovec Date: Thu, 9 Apr 2026 15:11:47 +0200 Subject: [PATCH 05/13] cleanup Signed-off-by: Peter Jausovec --- .../agents/[namespace]/[name]/chat/page.tsx | 2 - ui/src/lib/a2aBackendFetch.ts | 41 ------------------- 2 files changed, 43 deletions(-) delete mode 100644 ui/src/lib/a2aBackendFetch.ts diff --git a/ui/src/app/agents/[namespace]/[name]/chat/page.tsx b/ui/src/app/agents/[namespace]/[name]/chat/page.tsx index 1f03a7c80..bd30e0aa3 100644 --- a/ui/src/app/agents/[namespace]/[name]/chat/page.tsx +++ b/ui/src/app/agents/[namespace]/[name]/chat/page.tsx @@ -5,7 +5,6 @@ import { useRouter } from "next/navigation"; import ChatInterface from "@/components/chat/ChatInterface"; import { getAgent } from "@/app/actions/agents"; import { getSessionsForAgent, createSession } from "@/app/actions/sessions"; -import { getCurrentUserId } from "@/app/actions/utils"; import { Loader2 } from "lucide-react"; import type { Session } from "@/types"; @@ -51,7 +50,6 @@ export default function ChatAgentPage({ params }: { params: Promise<{ name: stri return; } const created = await createSession({ - user_id: await getCurrentUserId(), agent_ref: agentRef, name: "Chat", }); diff --git a/ui/src/lib/a2aBackendFetch.ts b/ui/src/lib/a2aBackendFetch.ts deleted file mode 100644 index 84123373f..000000000 --- a/ui/src/lib/a2aBackendFetch.ts +++ /dev/null @@ -1,41 +0,0 @@ -import type { NextRequest } from "next/server"; -import { getBackendUrl } from "@/lib/utils"; -import { getCurrentUserId } from "@/app/actions/utils"; - -/** - * Server-side fetch to kagent /api/a2a/... Must mirror {@link fetchApi}: same user_id - * query param and forwarded auth so DB session lookups match session creation. - */ -export async function fetchA2ABackend( - request: NextRequest, - pathAfterApi: string, - body: unknown -): Promise { - const base = getBackendUrl().replace(/\/$/, ""); - const path = pathAfterApi.startsWith("/") ? pathAfterApi : `/${pathAfterApi}`; - const url = new URL(`${base}${path}`); - const userId = await getCurrentUserId(); - if (!url.searchParams.has("user_id")) { - url.searchParams.set("user_id", userId); - } - - const headers: Record = { - "Content-Type": "application/json", - Accept: "text/event-stream", - "Cache-Control": "no-cache", - Connection: "keep-alive", - "User-Agent": "kagent-ui", - }; - const auth = request.headers.get("authorization"); - if (auth) headers.Authorization = auth; - const cookie = request.headers.get("cookie"); - if (cookie) headers.Cookie = cookie; - const xUser = request.headers.get("x-user-id"); - if (xUser) headers["X-User-Id"] = xUser; - - return fetch(url.toString(), { - method: "POST", - headers, - body: JSON.stringify(body), - }); -} From 800aacd24b5b887ade2f5c57a28f6c9cf6af520f Mon Sep 17 00:00:00 2001 From: Peter Jausovec Date: Thu, 9 Apr 2026 15:17:39 +0200 Subject: [PATCH 06/13] only check for sandbox crd Signed-off-by: Peter Jausovec --- go/core/pkg/sandboxbackend/apis_available.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/go/core/pkg/sandboxbackend/apis_available.go b/go/core/pkg/sandboxbackend/apis_available.go index e1016fad5..3e485d4c8 100644 --- a/go/core/pkg/sandboxbackend/apis_available.go +++ b/go/core/pkg/sandboxbackend/apis_available.go @@ -8,7 +8,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" agentsandboxv1 "sigs.k8s.io/agent-sandbox/api/v1alpha1" - extensionsv1alpha1 "sigs.k8s.io/agent-sandbox/extensions/api/v1alpha1" ) // EnsureAgentSandboxAPIsRegistered checks that the apiserver exposes the agent-sandbox @@ -22,8 +21,6 @@ func EnsureAgentSandboxAPIsRegistered(ctx context.Context, c client.Client) erro list client.ObjectList kind string }{ - {&extensionsv1alpha1.SandboxTemplateList{}, "SandboxTemplate (extensions.agents.x-k8s.io/v1alpha1)"}, - {&extensionsv1alpha1.SandboxClaimList{}, "SandboxClaim (extensions.agents.x-k8s.io/v1alpha1)"}, {&agentsandboxv1.SandboxList{}, "Sandbox (agents.x-k8s.io/v1alpha1)"}, } for _, ch := range checks { From d1dadc9c387bea43a590ed13df181cd3f060c352 Mon Sep 17 00:00:00 2001 From: Peter Jausovec Date: Thu, 9 Apr 2026 15:42:26 +0200 Subject: [PATCH 07/13] ensure sandbox CRDs are registered before appending owned resources Signed-off-by: Peter Jausovec --- .../controller/translator/agent/adk_api_translator.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/go/core/internal/controller/translator/agent/adk_api_translator.go b/go/core/internal/controller/translator/agent/adk_api_translator.go index 22909a895..34b8e2709 100644 --- a/go/core/internal/controller/translator/agent/adk_api_translator.go +++ b/go/core/internal/controller/translator/agent/adk_api_translator.go @@ -255,6 +255,7 @@ func (a *adkApiTranslator) TranslateAgent( // Even though this method returns an array of client.Object, these are (empty) // example structs rather than actual resources. func (r *adkApiTranslator) GetOwnedResourceTypes() []client.Object { + ctx := context.Background() ownedResources := []client.Object{ &appsv1.Deployment{}, &corev1.ConfigMap{}, @@ -267,7 +268,8 @@ func (r *adkApiTranslator) GetOwnedResourceTypes() []client.Object { ownedResources = append(ownedResources, plugin.GetOwnedResourceTypes()...) } - if r.sandboxBackend != nil { + // Only set this up if the sandbox CRDs are available + if err := sandboxbackend.EnsureAgentSandboxAPIsRegistered(ctx, r.kube); err == nil { ownedResources = append(ownedResources, r.sandboxBackend.GetOwnedResourceTypes()...) } From 62ad14a7381ed15d7cd6b9ee999b9a7516992ce5 Mon Sep 17 00:00:00 2001 From: Eitan Yarmush Date: Thu, 9 Apr 2026 20:26:23 +0000 Subject: [PATCH 08/13] Refactor sandbox agent integration and add e2e coverage Signed-off-by: Eitan Yarmush --- .github/workflows/ci.yaml | 19 +- go/api/database/client.go | 1 + go/api/database/models.go | 6 +- go/api/httpapi/types.go | 67 +- go/api/v1alpha2/agentobject.go | 56 ++ go/core/cli/internal/cli/agent/get.go | 4 +- go/core/cli/internal/cli/agent/invoke.go | 18 +- .../cli/internal/tui/dialogs/agent_chooser.go | 4 +- go/core/cli/internal/tui/workspace.go | 11 +- .../cli/internal/tui/workspace_simple_test.go | 4 +- go/core/internal/a2a/a2a_handler_mux.go | 32 +- go/core/internal/a2a/a2a_registrar.go | 157 ++-- go/core/internal/a2a/trace.go | 7 +- .../internal/controller/agent_controller.go | 314 +------- .../controller/agentobject_helpers.go | 111 +++ .../controller/reconciler/reconciler.go | 277 +++---- .../reconciler/utils/reconciler_utils.go | 11 + .../controller/sandboxagent_controller.go | 265 +------ .../translator/agent/adk_api_translator.go | 660 +---------------- .../agent/adk_api_translator_test.go | 33 +- .../agent/adk_translator_golden_test.go | 2 +- .../controller/translator/agent/compiler.go | 319 ++++++++ .../translator/agent/deployments.go | 28 +- .../translator/agent/git_skills_test.go | 6 +- .../translator/agent/manifest_builder.go | 497 +++++++++++++ .../translator/agent/mcp_validation_test.go | 12 +- .../controller/translator/agent/proxy_test.go | 10 +- .../translator/agent/runtime_test.go | 8 +- .../translator/agent/sandbox_agent_view.go | 26 - .../translator/agent/security_context_test.go | 10 +- .../controller/translator/agent/template.go | 15 +- .../controller/translator/agent/utils.go | 13 +- go/core/internal/controller/watch_helpers.go | 102 +++ go/core/internal/database/client_postgres.go | 32 +- go/core/internal/database/fake/client.go | 19 + go/core/internal/database/gen/agents.sql.go | 25 +- go/core/internal/database/gen/models.go | 13 +- go/core/internal/database/gen/querier.go | 1 + go/core/internal/database/gen/sessions.sql.go | 36 + go/core/internal/database/queries/agents.sql | 5 +- .../internal/database/queries/sessions.sql | 6 + .../internal/httpserver/handlers/agents.go | 699 +++++++++--------- .../httpserver/handlers/agents_test.go | 232 +++++- .../internal/httpserver/handlers/sessions.go | 36 +- .../httpserver/handlers/sessions_test.go | 33 +- go/core/internal/httpserver/server.go | 5 + go/core/pkg/app/app.go | 4 +- .../core/000003_agent_workload_type.down.sql | 1 + .../core/000003_agent_workload_type.up.sql | 3 + .../sandboxbackend/agentsxk8s/agentsxk8s.go | 8 +- go/core/pkg/sandboxbackend/backend.go | 2 +- .../translator/adk_api_translator_types.go | 2 +- go/core/test/e2e/invoke_api_test.go | 111 ++- ui/public/mockServiceWorker.js | 2 +- .../[namespace]/[agentName]/route.ts | 144 ++++ .../agents/[namespace]/[name]/chat/page.tsx | 2 +- ui/src/app/agents/new/page.tsx | 2 +- ui/src/components/chat/ChatInterface.tsx | 3 +- ui/src/components/chat/ChatLayoutUI.tsx | 4 +- .../components/sidebars/SessionsSidebar.tsx | 4 +- ui/src/lib/a2aClient.ts | 14 +- ui/src/lib/agentFormLayout.ts | 4 +- ui/src/types/index.ts | 16 +- 63 files changed, 2584 insertions(+), 1989 deletions(-) create mode 100644 go/api/v1alpha2/agentobject.go create mode 100644 go/core/internal/controller/agentobject_helpers.go create mode 100644 go/core/internal/controller/translator/agent/compiler.go create mode 100644 go/core/internal/controller/translator/agent/manifest_builder.go delete mode 100644 go/core/internal/controller/translator/agent/sandbox_agent_view.go create mode 100644 go/core/internal/controller/watch_helpers.go create mode 100644 go/core/pkg/migrations/core/000003_agent_workload_type.down.sql create mode 100644 go/core/pkg/migrations/core/000003_agent_workload_type.up.sql create mode 100644 ui/src/app/a2a-sandboxes/[namespace]/[agentName]/route.ts diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 940660461..a56e77114 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -15,6 +15,7 @@ env: # Cache key components for better organization CACHE_KEY_PREFIX: kagent-v2 BRANCH_CACHE_KEY: ${{ github.head_ref || github.ref_name }} + AGENT_SANDBOX_VERSION: v0.2.1 # Consistent builder configuration BUILDX_BUILDER_NAME: kagent-builder-v0.23.0 BUILDX_VERSION: v0.23.0 @@ -66,6 +67,17 @@ jobs: with: install_only: true + - name: Create Kind cluster + run: | + make create-kind-cluster + + - name: Install agent-sandbox + run: | + kubectl apply -f "https://github.com/kubernetes-sigs/agent-sandbox/releases/download/${AGENT_SANDBOX_VERSION}/manifest.yaml" + kubectl wait --for=condition=Established crd/sandboxes.agents.x-k8s.io --timeout=90s + kubectl rollout status statefulset/agent-sandbox-controller -n agent-sandbox-system --timeout=120s + kubectl wait --for=condition=Ready pod -l app=agent-sandbox-controller -n agent-sandbox-system --timeout=120s + - name: Install Kagent id: install-kagent env: @@ -79,10 +91,11 @@ jobs: --platform=linux/amd64 --push run: | - make create-kind-cluster echo "Cache key: ${{ needs.setup.outputs.cache-key }}" make helm-install make push-test-agent push-test-skill + kubectl rollout status deployment/kagent-controller -n kagent --timeout=120s + kubectl wait --for=condition=Ready pod -l app.kubernetes.io/component=controller -n kagent --timeout=120s kubectl wait --for=condition=Ready agents.kagent.dev -n kagent --all --timeout=60s || kubectl get po -n kagent -o wide ||: kubectl wait --for=condition=Ready agents.kagent.dev -n kagent --all --timeout=60s @@ -131,6 +144,10 @@ jobs: echo "::error::Failed to run e2e tests" echo "::error::Kubectl get pods -n kagent" kubectl describe pods -n kagent + echo "::error::Kubectl get pods -n agent-sandbox-system" + kubectl get pods -n agent-sandbox-system -o wide || true + echo "::error::Kubectl logs -n agent-sandbox-system statefulset/agent-sandbox-controller" + kubectl logs -n agent-sandbox-system statefulset/agent-sandbox-controller || true echo "::error::Kubectl get events -n kagent" kubectl get events -n kagent echo "::error::Kubectl get agents -n kagent" diff --git a/go/api/database/client.go b/go/api/database/client.go index 6f3bfedde..43943da67 100644 --- a/go/api/database/client.go +++ b/go/api/database/client.go @@ -51,6 +51,7 @@ type Client interface { ListTasksForSession(ctx context.Context, sessionID string) ([]*protocol.Task, error) ListSessions(ctx context.Context, userID string) ([]Session, error) ListSessionsForAgent(ctx context.Context, agentID string, userID string) ([]Session, error) + ListSessionsForAgentAllUsers(ctx context.Context, agentID string) ([]Session, error) ListAgents(ctx context.Context) ([]Agent, error) ListToolServers(ctx context.Context) ([]ToolServer, error) ListToolsForServer(ctx context.Context, serverName string, groupKind string) ([]Tool, error) diff --git a/go/api/database/models.go b/go/api/database/models.go index 23648accd..7bb7be9da 100644 --- a/go/api/database/models.go +++ b/go/api/database/models.go @@ -5,6 +5,7 @@ import ( "time" "github.com/kagent-dev/kagent/go/api/adk" + "github.com/kagent-dev/kagent/go/api/v1alpha2" "github.com/pgvector/pgvector-go" "trpc.group/trpc-go/trpc-a2a-go/protocol" ) @@ -15,8 +16,9 @@ type Agent struct { UpdatedAt time.Time `json:"updated_at"` DeletedAt *time.Time `json:"deleted_at,omitempty"` - Type string `json:"type"` - Config *adk.AgentConfig `json:"config"` + Type string `json:"type"` + WorkloadType v1alpha2.WorkloadMode `json:"workload_type"` + Config *adk.AgentConfig `json:"config"` } type Event struct { diff --git a/go/api/httpapi/types.go b/go/api/httpapi/types.go index c2b13dfaa..2e9ee9f76 100644 --- a/go/api/httpapi/types.go +++ b/go/api/httpapi/types.go @@ -4,6 +4,7 @@ import ( "github.com/kagent-dev/kagent/go/api/database" "github.com/kagent-dev/kagent/go/api/v1alpha1" "github.com/kagent-dev/kagent/go/api/v1alpha2" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // Common types @@ -83,9 +84,68 @@ type UpdateModelConfigRequest struct { // Agent types +type AgentResource struct { + APIVersion string `json:"apiVersion,omitempty"` + Kind string `json:"kind,omitempty"` + Metadata metav1.ObjectMeta `json:"metadata,omitempty"` + Spec v1alpha2.AgentSpec `json:"spec,omitempty"` + Status v1alpha2.AgentStatus `json:"status,omitempty"` +} + +func AgentResourceFrom(agent v1alpha2.AgentObject) *AgentResource { + if agent == nil { + return nil + } + + spec := agent.GetAgentSpec() + status := agent.GetAgentStatus() + gvk := agent.GetObjectKind().GroupVersionKind() + apiVersion := gvk.GroupVersion().String() + kind := gvk.Kind + var metadata metav1.ObjectMeta + if apiVersion == "" { + apiVersion = v1alpha2.GroupVersion.String() + } + if kind == "" { + if agent.GetWorkloadMode() == v1alpha2.WorkloadModeSandbox { + kind = "SandboxAgent" + } else { + kind = "Agent" + } + } + switch typed := agent.(type) { + case *v1alpha2.Agent: + metadata = *typed.ObjectMeta.DeepCopy() + case *v1alpha2.SandboxAgent: + metadata = *typed.ObjectMeta.DeepCopy() + default: + metadata = metav1.ObjectMeta{ + Name: agent.GetName(), + Namespace: agent.GetNamespace(), + Labels: agent.GetLabels(), + Annotations: agent.GetAnnotations(), + ResourceVersion: agent.GetResourceVersion(), + Generation: agent.GetGeneration(), + } + } + + res := &AgentResource{ + APIVersion: apiVersion, + Kind: kind, + Metadata: metadata, + } + if spec != nil { + res.Spec = *spec.DeepCopy() + } + if status != nil { + res.Status = *status.DeepCopy() + } + return res +} + type AgentResponse struct { - ID string `json:"id"` - Agent *v1alpha2.Agent `json:"agent"` + ID string `json:"id"` + Agent *AgentResource `json:"agent"` // Config *adk.AgentConfig `json:"config"` ModelProvider v1alpha2.ModelProvider `json:"modelProvider"` Model string `json:"model"` @@ -94,8 +154,7 @@ type AgentResponse struct { Tools []*v1alpha2.Tool `json:"tools"` DeploymentReady bool `json:"deploymentReady"` Accepted bool `json:"accepted"` - // RunInSandbox is true when the workload is reconciled as a SandboxAgent (agent-sandbox Sandbox CR), not a Deployment. - RunInSandbox bool `json:"runInSandbox,omitempty"` + WorkloadMode v1alpha2.WorkloadMode `json:"workloadMode,omitempty"` } // Session types diff --git a/go/api/v1alpha2/agentobject.go b/go/api/v1alpha2/agentobject.go new file mode 100644 index 000000000..e22e2edd8 --- /dev/null +++ b/go/api/v1alpha2/agentobject.go @@ -0,0 +1,56 @@ +package v1alpha2 + +import "sigs.k8s.io/controller-runtime/pkg/client" + +type WorkloadMode string + +const ( + WorkloadModeDeployment WorkloadMode = "deployment" + WorkloadModeSandbox WorkloadMode = "sandbox" +) + +// AgentObject is the shared shape implemented by agent-style CRDs that expose the +// same Spec/Status model but reconcile to different workload types. +// +kubebuilder:object:generate=false +type AgentObject interface { + client.Object + GetAgentSpec() *AgentSpec + GetAgentStatus() *AgentStatus + GetWorkloadMode() WorkloadMode +} + +func (a *Agent) GetAgentSpec() *AgentSpec { + if a == nil { + return nil + } + return &a.Spec +} + +func (a *Agent) GetAgentStatus() *AgentStatus { + if a == nil { + return nil + } + return &a.Status +} + +func (a *Agent) GetWorkloadMode() WorkloadMode { + return WorkloadModeDeployment +} + +func (a *SandboxAgent) GetAgentSpec() *AgentSpec { + if a == nil { + return nil + } + return &a.Spec +} + +func (a *SandboxAgent) GetAgentStatus() *AgentStatus { + if a == nil { + return nil + } + return &a.Status +} + +func (a *SandboxAgent) GetWorkloadMode() WorkloadMode { + return WorkloadModeSandbox +} diff --git a/go/core/cli/internal/cli/agent/get.go b/go/core/cli/internal/cli/agent/get.go index d19630fce..080b9e47c 100644 --- a/go/core/cli/internal/cli/agent/get.go +++ b/go/core/cli/internal/cli/agent/get.go @@ -109,8 +109,8 @@ func printAgents(agents []api.AgentResponse) error { for i, agent := range agents { rows[i] = []string{ strconv.Itoa(i + 1), - utils.GetObjectRef(agent.Agent), - agent.Agent.CreationTimestamp.Format(time.RFC3339), + utils.ResourceRefString(agent.Agent.Metadata.Namespace, agent.Agent.Metadata.Name), + agent.Agent.Metadata.CreationTimestamp.Format(time.RFC3339), strconv.FormatBool(agent.DeploymentReady), strconv.FormatBool(agent.Accepted), } diff --git a/go/core/cli/internal/cli/agent/invoke.go b/go/core/cli/internal/cli/agent/invoke.go index a444a67c7..067e6c817 100644 --- a/go/core/cli/internal/cli/agent/invoke.go +++ b/go/core/cli/internal/cli/agent/invoke.go @@ -9,6 +9,8 @@ import ( "strings" "time" + api "github.com/kagent-dev/kagent/go/api/httpapi" + "github.com/kagent-dev/kagent/go/api/v1alpha2" "github.com/kagent-dev/kagent/go/core/cli/internal/config" a2aclient "trpc.group/trpc-go/trpc-a2a-go/client" "trpc.group/trpc-go/trpc-a2a-go/protocol" @@ -110,7 +112,13 @@ func InvokeCmd(ctx context.Context, cfg *InvokeCfg) { return } - a2aURL := fmt.Sprintf("%s/api/a2a/%s/%s", cfg.Config.KAgentURL, cfg.Config.Namespace, cfg.Agent) + agentResponse, err := clientSet.Agent.GetAgent(ctx, fmt.Sprintf("%s/%s", cfg.Config.Namespace, cfg.Agent)) + if err != nil { + fmt.Fprintf(os.Stderr, "Error getting agent metadata: %v\n", err) + return + } + + a2aURL := buildA2AURL(cfg.Config.KAgentURL, cfg.Config.Namespace, cfg.Agent, agentResponse.Data) a2aClient, err = a2aclient.NewA2AClient(a2aURL, a2aClientOpts...) if err != nil { fmt.Fprintf(os.Stderr, "Error creating A2A client: %v\n", err) @@ -167,3 +175,11 @@ func InvokeCmd(ctx context.Context, cfg *InvokeCfg) { fmt.Fprintf(os.Stdout, "%+v\n", string(jsn)) } } + +func buildA2AURL(baseURL, namespace, agent string, agentResponse *api.AgentResponse) string { + a2aPath := "api/a2a" + if agentResponse != nil && agentResponse.WorkloadMode == v1alpha2.WorkloadModeSandbox { + a2aPath = "api/a2a-sandboxes" + } + return fmt.Sprintf("%s/%s/%s/%s", baseURL, a2aPath, namespace, agent) +} diff --git a/go/core/cli/internal/tui/dialogs/agent_chooser.go b/go/core/cli/internal/tui/dialogs/agent_chooser.go index 90e12dfc3..5ed58bf11 100644 --- a/go/core/cli/internal/tui/dialogs/agent_chooser.go +++ b/go/core/cli/internal/tui/dialogs/agent_chooser.go @@ -11,8 +11,8 @@ import ( type AgentItem struct{ api.AgentResponse } -func (i AgentItem) Title() string { return i.Agent.Name } -func (i AgentItem) Namespace() string { return i.Agent.Namespace } +func (i AgentItem) Title() string { return i.Agent.Metadata.Name } +func (i AgentItem) Namespace() string { return i.Agent.Metadata.Namespace } func (i AgentItem) Description() string { return i.Agent.Spec.Description } func (i AgentItem) FilterValue() string { return i.ID } diff --git a/go/core/cli/internal/tui/workspace.go b/go/core/cli/internal/tui/workspace.go index be7510b8c..073dcc63d 100644 --- a/go/core/cli/internal/tui/workspace.go +++ b/go/core/cli/internal/tui/workspace.go @@ -222,7 +222,10 @@ func (m *workspaceModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } // Sort and store agents for later; do not auto-open chooser or auto-select. slices.SortStableFunc(msg.agents, func(a, b api.AgentResponse) int { - return strings.Compare(utils.GetObjectRef(a.Agent), utils.GetObjectRef(a.Agent)) + return strings.Compare( + utils.ResourceRefString(a.Agent.Metadata.Namespace, a.Agent.Metadata.Name), + utils.ResourceRefString(b.Agent.Metadata.Namespace, b.Agent.Metadata.Name), + ) }) m.agents = msg.agents // Keep welcome screen visible until user presses Ctrl+A @@ -459,7 +462,11 @@ func (m *workspaceModel) startChat(loadHistory bool) tea.Cmd { if m.agent == nil || m.current == nil { return nil } - a2aURL := fmt.Sprintf("%s/api/a2a/%s", m.cfg.KAgentURL, m.agentRef) + a2aPath := "api/a2a" + if m.agent != nil && m.agent.WorkloadMode == v1alpha2.WorkloadModeSandbox { + a2aPath = "api/a2a-sandboxes" + } + a2aURL := fmt.Sprintf("%s/%s/%s", m.cfg.KAgentURL, a2aPath, m.agentRef) client, err := a2aclient.NewA2AClient(a2aURL, a2aclient.WithTimeout(m.cfg.Timeout), ) diff --git a/go/core/cli/internal/tui/workspace_simple_test.go b/go/core/cli/internal/tui/workspace_simple_test.go index ec6337767..2b977b2ba 100644 --- a/go/core/cli/internal/tui/workspace_simple_test.go +++ b/go/core/cli/internal/tui/workspace_simple_test.go @@ -190,11 +190,11 @@ func TestWorkspaceModel_AgentsLoadedUpdate(t *testing.T) { agents := []api.AgentResponse{ { ID: "agent-1", - Agent: testutil.CreateTestAgent("default", "agent-1"), + Agent: api.AgentResourceFrom(testutil.CreateTestAgent("default", "agent-1")), }, { ID: "agent-2", - Agent: testutil.CreateTestAgent("default", "agent-2"), + Agent: api.AgentResourceFrom(testutil.CreateTestAgent("default", "agent-2")), }, } diff --git a/go/core/internal/a2a/a2a_handler_mux.go b/go/core/internal/a2a/a2a_handler_mux.go index 8c3e33340..3d4bb7722 100644 --- a/go/core/internal/a2a/a2a_handler_mux.go +++ b/go/core/internal/a2a/a2a_handler_mux.go @@ -3,6 +3,7 @@ package a2a import ( "fmt" "net/http" + "strings" "sync" "github.com/gorilla/mux" @@ -28,19 +29,21 @@ type A2AHandlerMux interface { } type handlerMux struct { - handlers map[string]http.Handler - lock sync.RWMutex - basePathPrefix string - authenticator auth.AuthProvider + handlers map[string]http.Handler + lock sync.RWMutex + agentPathPrefix string + sandboxPathPrefix string + authenticator auth.AuthProvider } var _ A2AHandlerMux = &handlerMux{} -func NewA2AHttpMux(pathPrefix string, authenticator auth.AuthProvider) *handlerMux { +func NewA2AHttpMux(agentPathPrefix, sandboxPathPrefix string, authenticator auth.AuthProvider) *handlerMux { return &handlerMux{ - handlers: make(map[string]http.Handler), - basePathPrefix: pathPrefix, - authenticator: authenticator, + handlers: make(map[string]http.Handler), + agentPathPrefix: agentPathPrefix, + sandboxPathPrefix: sandboxPathPrefix, + authenticator: authenticator, } } @@ -96,7 +99,7 @@ func (a *handlerMux) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } - handlerName := common.ResourceRefString(agentNamespace, agentName) + handlerName := routeKey(a.isSandboxRoute(r), agentNamespace, agentName) // get the underlying handler handlerHandler, ok := a.getHandler(handlerName) @@ -111,3 +114,14 @@ func (a *handlerMux) ServeHTTP(w http.ResponseWriter, r *http.Request) { handlerHandler.ServeHTTP(w, r) } + +func (a *handlerMux) isSandboxRoute(r *http.Request) bool { + return strings.HasPrefix(r.URL.Path, a.sandboxPathPrefix+"/") || r.URL.Path == a.sandboxPathPrefix +} + +func routeKey(isSandbox bool, namespace, name string) string { + if isSandbox { + return common.ResourceRefString("sandboxes", common.ResourceRefString(namespace, name)) + } + return common.ResourceRefString(namespace, name) +} diff --git a/go/core/internal/a2a/a2a_registrar.go b/go/core/internal/a2a/a2a_registrar.go index 6c699e842..d21dbec04 100644 --- a/go/core/internal/a2a/a2a_registrar.go +++ b/go/core/internal/a2a/a2a_registrar.go @@ -25,9 +25,9 @@ import ( type A2ARegistrar struct { cache crcache.Cache - translator agent_translator.AdkApiTranslator handlerMux A2AHandlerMux - a2aBaseUrl string + a2aBaseURL string + sandboxA2AURL string authenticator auth.AuthProvider a2aBaseOptions []a2aclient.Option } @@ -36,9 +36,9 @@ var _ manager.Runnable = (*A2ARegistrar)(nil) func NewA2ARegistrar( cache crcache.Cache, - translator agent_translator.AdkApiTranslator, mux A2AHandlerMux, a2aBaseUrl string, + sandboxA2ABaseURL string, authenticator auth.AuthProvider, streamingMaxBuf int, streamingInitialBuf int, @@ -46,9 +46,9 @@ func NewA2ARegistrar( ) *A2ARegistrar { reg := &A2ARegistrar{ cache: cache, - translator: translator, handlerMux: mux, - a2aBaseUrl: a2aBaseUrl, + a2aBaseURL: a2aBaseUrl, + sandboxA2AURL: sandboxA2ABaseURL, authenticator: authenticator, a2aBaseOptions: []a2aclient.Option{ a2aclient.WithTimeout(streamingTimeout), @@ -67,107 +67,95 @@ func (a *A2ARegistrar) NeedLeaderElection() bool { func (a *A2ARegistrar) Start(ctx context.Context) error { log := ctrllog.FromContext(ctx).WithName("a2a-registrar") - informer, err := a.cache.GetInformer(ctx, &v1alpha2.Agent{}) + if err := a.registerAgentInformer(ctx, &v1alpha2.Agent{}, log); err != nil { + return err + } + if err := a.registerAgentInformer(ctx, &v1alpha2.SandboxAgent{}, log); err != nil { + return err + } + + if ok := a.cache.WaitForCacheSync(ctx); !ok { + return fmt.Errorf("cache sync failed") + } + + <-ctx.Done() + return nil +} + +func (a *A2ARegistrar) registerAgentInformer(ctx context.Context, prototype v1alpha2.AgentObject, log logr.Logger) error { + informer, err := a.cache.GetInformer(ctx, prototype) if err != nil { - return fmt.Errorf("failed to get cache informer: %w", err) + return fmt.Errorf("failed to get cache informer for %T: %w", prototype, err) } if _, err := informer.AddEventHandler(cache.ResourceEventHandlerFuncs{ AddFunc: func(obj any) { - if agent, ok := obj.(*v1alpha2.Agent); ok { - if err := a.upsertAgentHandler(ctx, agent, log); err != nil { - log.Error(err, "failed to upsert A2A handler", "agent", common.GetObjectRef(agent)) - } + agent, ok := informerAgentObject(obj) + if !ok { + return + } + if err := a.upsertAgentHandler(ctx, agent, log); err != nil { + log.Error(err, "failed to upsert A2A handler", "agent", common.GetObjectRef(agent)) } }, UpdateFunc: func(oldObj, newObj any) { - oldAgent, ok1 := oldObj.(*v1alpha2.Agent) - newAgent, ok2 := newObj.(*v1alpha2.Agent) + oldAgent, ok1 := informerAgentObject(oldObj) + newAgent, ok2 := informerAgentObject(newObj) if !ok1 || !ok2 { return } - if oldAgent.Generation != newAgent.Generation || !reflect.DeepEqual(oldAgent.Spec, newAgent.Spec) { + if oldAgent.GetGeneration() != newAgent.GetGeneration() || !sameAgentSpec(oldAgent, newAgent) { if err := a.upsertAgentHandler(ctx, newAgent, log); err != nil { log.Error(err, "failed to upsert A2A handler", "agent", common.GetObjectRef(newAgent)) } } }, DeleteFunc: func(obj any) { - agent, ok := obj.(*v1alpha2.Agent) + agent, ok := deletedInformerAgentObject(obj) if !ok { - if tombstone, ok := obj.(cache.DeletedFinalStateUnknown); ok { - if a2, ok := tombstone.Obj.(*v1alpha2.Agent); ok { - agent = a2 - } - } - } - if agent == nil { return } - ref := common.GetObjectRef(agent) + ref := a2aRouteKey(agent) a.handlerMux.RemoveAgentHandler(ref) log.V(1).Info("removed A2A handler", "agent", ref) }, }); err != nil { - return fmt.Errorf("failed to add informer event handler: %w", err) + return fmt.Errorf("failed to add informer event handler for %T: %w", prototype, err) } - sandboxInformer, err := a.cache.GetInformer(ctx, &v1alpha2.SandboxAgent{}) - if err != nil { - return fmt.Errorf("failed to get sandboxagent cache informer: %w", err) - } + return nil +} - if _, err := sandboxInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{ - AddFunc: func(obj any) { - if sa, ok := obj.(*v1alpha2.SandboxAgent); ok { - agentView := agent_translator.AgentViewFromSandboxAgent(sa) - if err := a.upsertAgentHandler(ctx, agentView, log); err != nil { - log.Error(err, "failed to upsert A2A handler", "sandboxagent", common.GetObjectRef(sa)) - } - } - }, - UpdateFunc: func(oldObj, newObj any) { - oldSA, ok1 := oldObj.(*v1alpha2.SandboxAgent) - newSA, ok2 := newObj.(*v1alpha2.SandboxAgent) - if !ok1 || !ok2 { - return - } - if oldSA.Generation != newSA.Generation || !reflect.DeepEqual(oldSA.Spec, newSA.Spec) { - agentView := agent_translator.AgentViewFromSandboxAgent(newSA) - if err := a.upsertAgentHandler(ctx, agentView, log); err != nil { - log.Error(err, "failed to upsert A2A handler", "sandboxagent", common.GetObjectRef(newSA)) - } - } - }, - DeleteFunc: func(obj any) { - sa, ok := obj.(*v1alpha2.SandboxAgent) - if !ok { - if tombstone, ok := obj.(cache.DeletedFinalStateUnknown); ok { - if s2, ok := tombstone.Obj.(*v1alpha2.SandboxAgent); ok { - sa = s2 - } - } - } - if sa == nil { - return - } - ref := common.GetObjectRef(sa) - a.handlerMux.RemoveAgentHandler(ref) - log.V(1).Info("removed A2A handler", "sandboxagent", ref) - }, - }); err != nil { - return fmt.Errorf("failed to add sandboxagent informer event handler: %w", err) +func sameAgentSpec(oldAgent, newAgent v1alpha2.AgentObject) bool { + oldSpec := oldAgent.GetAgentSpec() + newSpec := newAgent.GetAgentSpec() + switch { + case oldSpec == nil && newSpec == nil: + return true + case oldSpec == nil || newSpec == nil: + return false + default: + return reflect.DeepEqual(oldSpec, newSpec) } +} - if ok := a.cache.WaitForCacheSync(ctx); !ok { - return fmt.Errorf("cache sync failed") - } +func informerAgentObject(obj any) (v1alpha2.AgentObject, bool) { + typed, ok := obj.(v1alpha2.AgentObject) + return typed, ok +} - <-ctx.Done() - return nil +func deletedInformerAgentObject(obj any) (v1alpha2.AgentObject, bool) { + if typed, ok := informerAgentObject(obj); ok { + return typed, true + } + tombstone, ok := obj.(cache.DeletedFinalStateUnknown) + if !ok { + return nil, false + } + return informerAgentObject(tombstone.Obj) } -func (a *A2ARegistrar) upsertAgentHandler(ctx context.Context, agent *v1alpha2.Agent, log logr.Logger) error { +func (a *A2ARegistrar) upsertAgentHandler(ctx context.Context, agent v1alpha2.AgentObject, log logr.Logger) error { agentRef := types.NamespacedName{Namespace: agent.GetNamespace(), Name: agent.GetName()} card := agent_translator.GetA2AAgentCard(agent) @@ -192,9 +180,9 @@ func (a *A2ARegistrar) upsertAgentHandler(ctx context.Context, agent *v1alpha2.A } cardCopy := *card - cardCopy.URL = fmt.Sprintf("%s/%s/", a.a2aBaseUrl, agentRef) + cardCopy.URL = a.a2aRouteURL(agent) - if err := a.handlerMux.SetAgentHandler(agentRef.String(), client, cardCopy, newA2ATracingMiddleware(agentRef, provider)); err != nil { + if err := a.handlerMux.SetAgentHandler(a2aRouteKey(agent), client, cardCopy, newA2ATracingMiddleware(agentRef, provider)); err != nil { return fmt.Errorf("set handler for %s: %w", agentRef, err) } @@ -217,3 +205,20 @@ func debugOpt() a2aclient.Option { return func(*a2aclient.A2AClient) {} } } + +func (a *A2ARegistrar) a2aRouteURL(agent v1alpha2.AgentObject) string { + baseURL := a.a2aBaseURL + if agent.GetWorkloadMode() == v1alpha2.WorkloadModeSandbox { + baseURL = a.sandboxA2AURL + } + return baseURL + "/" + types.NamespacedName{Namespace: agent.GetNamespace(), Name: agent.GetName()}.String() + "/" +} + +func a2aRouteKey(agent v1alpha2.AgentObject) string { + return a2aRoutePath(agent) +} + +func a2aRoutePath(agent v1alpha2.AgentObject) string { + agentRef := types.NamespacedName{Namespace: agent.GetNamespace(), Name: agent.GetName()} + return routeKey(agent.GetWorkloadMode() == v1alpha2.WorkloadModeSandbox, agentRef.Namespace, agentRef.Name) +} diff --git a/go/core/internal/a2a/trace.go b/go/core/internal/a2a/trace.go index 0c08f06b7..bf8d7c94a 100644 --- a/go/core/internal/a2a/trace.go +++ b/go/core/internal/a2a/trace.go @@ -60,11 +60,12 @@ func (h *traceInjectHandler) Handle(ctx context.Context, client *http.Client, re // resolveProviderName looks up the ModelConfig for a declarative agent and // returns the corresponding gen_ai.provider.name attribute. Falls back to "kagent" // for BYO agents or if the ModelConfig cannot be fetched. -func resolveProviderName(ctx context.Context, cache crcache.Cache, agent *v1alpha2.Agent) attribute.KeyValue { - if agent.Spec.Declarative == nil { +func resolveProviderName(ctx context.Context, cache crcache.Cache, agent v1alpha2.AgentObject) attribute.KeyValue { + spec := agent.GetAgentSpec() + if spec.Declarative == nil { return semconv.GenAIProviderNameKey.String("kagent") } - mcName := agent.Spec.Declarative.ModelConfig + mcName := spec.Declarative.ModelConfig if mcName == "" { mcName = "default-model-config" } diff --git a/go/core/internal/controller/agent_controller.go b/go/core/internal/controller/agent_controller.go index 6742a23c3..e13a03d5c 100644 --- a/go/core/internal/controller/agent_controller.go +++ b/go/core/internal/controller/agent_controller.go @@ -19,7 +19,6 @@ package controller import ( "context" - corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" @@ -28,15 +27,12 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/event" - "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/predicate" - "sigs.k8s.io/controller-runtime/pkg/reconcile" "github.com/kagent-dev/kagent/go/api/v1alpha2" "github.com/kagent-dev/kagent/go/core/internal/controller/reconciler" agent_translator "github.com/kagent-dev/kagent/go/core/internal/controller/translator/agent" - "github.com/kagent-dev/kmcp/api/v1alpha1" ) var ( @@ -74,303 +70,37 @@ func (r *AgentController) SetupWithManager(mgr ctrl.Manager) error { }). For(&v1alpha2.Agent{}, builder.WithPredicates(predicate.Or(predicate.GenerationChangedPredicate{}, predicate.LabelChangedPredicate{}))) - // Setup owns relationships for resources created by the Agent controller - - // for now ownership of agent resources is handled by the ADK translator - for _, ownedType := range r.AdkTranslator.GetOwnedResourceTypes() { - build = build.Owns(ownedType, builder.WithPredicates(ownedObjectPredicate{}, predicate.ResourceVersionChangedPredicate{})) + var err error + build, err = addOwnedResourceWatches(build, mgr, r.AdkTranslator.GetOwnedResourceTypes()) + if err != nil { + return err } - - // Setup watches for secondary resources that are not owned by the Agent - build = build.Watches( - &v1alpha2.ModelConfig{}, - handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []reconcile.Request { - requests := []reconcile.Request{} - - for _, agent := range r.findAgentsUsingModelConfig(ctx, mgr.GetClient(), types.NamespacedName{ - Name: obj.GetName(), - Namespace: obj.GetNamespace(), - }) { - requests = append(requests, reconcile.Request{ - NamespacedName: types.NamespacedName{ - Name: agent.ObjectMeta.Name, - Namespace: agent.ObjectMeta.Namespace, - }, - }) - } - - return requests - }), - builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}), - ). - Watches( - &v1alpha2.RemoteMCPServer{}, - handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []reconcile.Request { - requests := []reconcile.Request{} - - for _, agent := range r.findAgentsUsingRemoteMCPServer(ctx, mgr.GetClient(), types.NamespacedName{ - Name: obj.GetName(), - Namespace: obj.GetNamespace(), - }) { - requests = append(requests, reconcile.Request{ - NamespacedName: types.NamespacedName{ - Name: agent.ObjectMeta.Name, - Namespace: agent.ObjectMeta.Namespace, - }, - }) - } - - return requests - }), - ). - Watches( - &corev1.Service{}, - handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []reconcile.Request { - requests := []reconcile.Request{} - - for _, agent := range r.findAgentsUsingMCPService(ctx, mgr.GetClient(), types.NamespacedName{ - Name: obj.GetName(), - Namespace: obj.GetNamespace(), - }) { - requests = append(requests, reconcile.Request{ - NamespacedName: types.NamespacedName{ - Name: agent.Name, - Namespace: agent.Namespace, - }, - }) - } - - return requests - }), - builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}), - ) - - // Watch ConfigMaps referenced by agents via promptTemplates or systemMessageFrom. - // When a ConfigMap changes, re-reconcile any agents that reference it. - build = build.Watches( - &corev1.ConfigMap{}, - handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []reconcile.Request { - requests := []reconcile.Request{} - - for _, agent := range r.findAgentsReferencingConfigMap(ctx, mgr.GetClient(), types.NamespacedName{ - Name: obj.GetName(), - Namespace: obj.GetNamespace(), - }) { - requests = append(requests, reconcile.Request{ - NamespacedName: types.NamespacedName{ - Name: agent.Name, - Namespace: agent.Namespace, - }, - }) - } - - return requests - }), - builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}), - ) - - if _, err := mgr.GetRESTMapper().RESTMapping(mcpServerGK); err == nil { - build = build.Watches( - &v1alpha1.MCPServer{}, - handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []reconcile.Request { - requests := []reconcile.Request{} - - for _, agent := range r.findAgentsUsingMCPServer(ctx, mgr.GetClient(), types.NamespacedName{ - Name: obj.GetName(), - Namespace: obj.GetNamespace(), - }) { - requests = append(requests, reconcile.Request{ - NamespacedName: types.NamespacedName{ - Name: agent.Name, - Namespace: agent.Namespace, - }, - }) - } - - return requests - }), - builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}), - ) + build, err = addCommonAgentWatches(build, mgr, agentWatchFinders{ + modelConfig: r.agentDependencyFinder("failed to list Agents in order to reconcile ModelConfig update", usesModelConfig), + remoteMCPServer: r.agentDependencyFinder("failed to list Agents in order to reconcile ToolServer update", usesRemoteMCPServer), + mcpService: r.agentDependencyFinder("failed to list agents in order to reconcile MCPService update", usesMCPService), + configMap: r.agentDependencyFinder("failed to list agents in order to reconcile ConfigMap update", referencesConfigMap), + mcpServer: r.agentDependencyFinder("failed to list agents in order to reconcile MCPServer update", usesMCPServer), + }) + if err != nil { + return err } return build.Named("agent").Complete(r) } -func (r *AgentController) findAgentsUsingMCPServer(ctx context.Context, cl client.Client, obj types.NamespacedName) []*v1alpha2.Agent { - var agentsList v1alpha2.AgentList - if err := cl.List( - ctx, - &agentsList, - ); err != nil { - agentControllerLog.Error(err, "failed to list agents in order to reconcile MCPServer update") - return nil - } - - var agents []*v1alpha2.Agent - for _, agent := range agentsList.Items { - if agent.Spec.Type != v1alpha2.AgentType_Declarative { - continue - } - - for _, tool := range agent.Spec.Declarative.Tools { - if tool.McpServer == nil { - continue - } - - if tool.McpServer.ApiGroup != "kagent.dev" || tool.McpServer.Kind != "MCPServer" { - continue - } - - mcpServerRef := tool.McpServer.NamespacedName(agent.Namespace) - if mcpServerRef == obj { - agents = append(agents, &agent) - } - } - } - - return agents -} - -func (r *AgentController) findAgentsUsingRemoteMCPServer(ctx context.Context, cl client.Client, obj types.NamespacedName) []*v1alpha2.Agent { - var agents []*v1alpha2.Agent - - var agentsList v1alpha2.AgentList - if err := cl.List( - ctx, - &agentsList, - ); err != nil { - agentControllerLog.Error(err, "failed to list Agents in order to reconcile ToolServer update") - return agents - } - - appendAgentIfUsesRemoteMCPServer := func(agent *v1alpha2.Agent) { - if agent.Spec.Type != v1alpha2.AgentType_Declarative { - return - } - - for _, tool := range agent.Spec.Declarative.Tools { - if tool.McpServer == nil { - return - } - - mcpServerRef := tool.McpServer.NamespacedName(agent.Namespace) - if mcpServerRef == obj { - agents = append(agents, agent) - return - } - } - } - - for _, agent := range agentsList.Items { - appendAgentIfUsesRemoteMCPServer(&agent) - } - - return agents -} - -func (r *AgentController) findAgentsUsingMCPService(ctx context.Context, cl client.Client, obj types.NamespacedName) []*v1alpha2.Agent { - var agentsList v1alpha2.AgentList - - if err := cl.List( - ctx, - &agentsList, - ); err != nil { - agentControllerLog.Error(err, "failed to list agents in order to reconcile MCPService update") - return nil - } - - var agents []*v1alpha2.Agent - for _, agent := range agentsList.Items { - if agent.Spec.Type != v1alpha2.AgentType_Declarative { - continue - } - - for _, tool := range agent.Spec.Declarative.Tools { - if tool.McpServer == nil { - continue - } - - if tool.McpServer.ApiGroup != "" || tool.McpServer.Kind != "Service" { - continue - } - - mcpServerRef := tool.McpServer.NamespacedName(agent.Namespace) - if mcpServerRef == obj { - agents = append(agents, &agent) - } - } - } - - return agents -} - -func (r *AgentController) findAgentsUsingModelConfig(ctx context.Context, cl client.Client, obj types.NamespacedName) []*v1alpha2.Agent { - var agents []*v1alpha2.Agent - - var agentsList v1alpha2.AgentList - if err := cl.List( - ctx, - &agentsList, - ); err != nil { - agentControllerLog.Error(err, "failed to list Agents in order to reconcile ModelConfig update") - return agents - } - - for i := range agentsList.Items { - agent := &agentsList.Items[i] - // Must be in the same namespace as the model config - if agent.Namespace != obj.Namespace { - continue - } - - if agent.Spec.Type != v1alpha2.AgentType_Declarative { - continue - } - - if agent.Spec.Declarative.ModelConfig == obj.Name { - agents = append(agents, agent) - } - } - - return agents -} - -func (r *AgentController) findAgentsReferencingConfigMap(ctx context.Context, cl client.Client, obj types.NamespacedName) []*v1alpha2.Agent { - var agentsList v1alpha2.AgentList - if err := cl.List(ctx, &agentsList); err != nil { - agentControllerLog.Error(err, "failed to list agents in order to reconcile ConfigMap update") - return nil - } - - var agents []*v1alpha2.Agent - for i := range agentsList.Items { - agent := &agentsList.Items[i] - if agent.Namespace != obj.Namespace { - continue - } - if agent.Spec.Type != v1alpha2.AgentType_Declarative || agent.Spec.Declarative == nil { - continue - } - - // Check if systemMessageFrom references this ConfigMap. - if ref := agent.Spec.Declarative.SystemMessageFrom; ref != nil { - if ref.Type == v1alpha2.ConfigMapValueSource && ref.Name == obj.Name { - agents = append(agents, agent) - continue - } +func (r *AgentController) agentDependencyFinder(errMsg string, pred agentDependencyPredicate) dependentRefFinder { + return func(ctx context.Context, cl client.Client, obj types.NamespacedName) []types.NamespacedName { + var agentsList v1alpha2.AgentList + if err := cl.List(ctx, &agentsList); err != nil { + agentControllerLog.Error(err, errMsg) + return nil } - // Check if any promptTemplate dataSources reference this ConfigMap. - if pt := agent.Spec.Declarative.PromptTemplate; pt != nil { - for _, ds := range pt.DataSources { - if ds.Name == obj.Name { - agents = append(agents, agent) - break - } - } - } + return collectAgentRefs(agentsList.Items, func(agent v1alpha2.AgentObject) bool { + return pred(agent, obj) + }) } - - return agents } type ownedObjectPredicate = typedOwnedObjectPredicate[client.Object] diff --git a/go/core/internal/controller/agentobject_helpers.go b/go/core/internal/controller/agentobject_helpers.go new file mode 100644 index 000000000..c66542438 --- /dev/null +++ b/go/core/internal/controller/agentobject_helpers.go @@ -0,0 +1,111 @@ +package controller + +import ( + "slices" + + "github.com/kagent-dev/kagent/go/api/v1alpha2" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +type agentDependencyPredicate func(v1alpha2.AgentObject, types.NamespacedName) bool + +func collectAgentRefs(items []v1alpha2.Agent, pred func(v1alpha2.AgentObject) bool) []types.NamespacedName { + var out []types.NamespacedName + for i := range items { + agent := &items[i] + if pred(agent) { + out = append(out, types.NamespacedName{Name: agent.Name, Namespace: agent.Namespace}) + } + } + return out +} + +func collectSandboxAgentRefs(items []v1alpha2.SandboxAgent, pred func(v1alpha2.AgentObject) bool) []types.NamespacedName { + var out []types.NamespacedName + for i := range items { + agent := &items[i] + if pred(agent) { + out = append(out, types.NamespacedName{Name: agent.Name, Namespace: agent.Namespace}) + } + } + return out +} + +func reconcileRequestsForRefs(refs []types.NamespacedName) []reconcile.Request { + requests := make([]reconcile.Request, 0, len(refs)) + for _, ref := range refs { + requests = append(requests, reconcile.Request{NamespacedName: ref}) + } + return requests +} + +func usesMCPServer(agent v1alpha2.AgentObject, obj types.NamespacedName) bool { + spec := agent.GetAgentSpec() + if spec.Type != v1alpha2.AgentType_Declarative || spec.Declarative == nil { + return false + } + + return slices.ContainsFunc(spec.Declarative.Tools, func(tool *v1alpha2.Tool) bool { + return tool != nil && + tool.McpServer != nil && + tool.McpServer.ApiGroup == "kagent.dev" && + tool.McpServer.Kind == "MCPServer" && + tool.McpServer.NamespacedName(agent.GetNamespace()) == obj + }) +} + +func usesRemoteMCPServer(agent v1alpha2.AgentObject, obj types.NamespacedName) bool { + spec := agent.GetAgentSpec() + if spec.Type != v1alpha2.AgentType_Declarative || spec.Declarative == nil { + return false + } + + return slices.ContainsFunc(spec.Declarative.Tools, func(tool *v1alpha2.Tool) bool { + return tool != nil && tool.McpServer != nil && tool.McpServer.NamespacedName(agent.GetNamespace()) == obj + }) +} + +func usesMCPService(agent v1alpha2.AgentObject, obj types.NamespacedName) bool { + spec := agent.GetAgentSpec() + if spec.Type != v1alpha2.AgentType_Declarative || spec.Declarative == nil { + return false + } + + return slices.ContainsFunc(spec.Declarative.Tools, func(tool *v1alpha2.Tool) bool { + return tool != nil && + tool.McpServer != nil && + tool.McpServer.ApiGroup == "" && + tool.McpServer.Kind == "Service" && + tool.McpServer.NamespacedName(agent.GetNamespace()) == obj + }) +} + +func usesModelConfig(agent v1alpha2.AgentObject, obj types.NamespacedName) bool { + spec := agent.GetAgentSpec() + return agent.GetNamespace() == obj.Namespace && + spec.Type == v1alpha2.AgentType_Declarative && + spec.Declarative != nil && + spec.Declarative.ModelConfig == obj.Name +} + +func referencesConfigMap(agent v1alpha2.AgentObject, obj types.NamespacedName) bool { + spec := agent.GetAgentSpec() + if agent.GetNamespace() != obj.Namespace || spec.Type != v1alpha2.AgentType_Declarative || spec.Declarative == nil { + return false + } + + if ref := spec.Declarative.SystemMessageFrom; ref != nil { + if ref.Type == v1alpha2.ConfigMapValueSource && ref.Name == obj.Name { + return true + } + } + + if pt := spec.Declarative.PromptTemplate; pt != nil { + return slices.ContainsFunc(pt.DataSources, func(ds v1alpha2.PromptSource) bool { + return ds.Name == obj.Name + }) + } + + return false +} diff --git a/go/core/internal/controller/reconciler/reconciler.go b/go/core/internal/controller/reconciler/reconciler.go index 03f70577d..112794bcf 100644 --- a/go/core/internal/controller/reconciler/reconciler.go +++ b/go/core/internal/controller/reconciler/reconciler.go @@ -93,13 +93,11 @@ func NewKagentReconciler( } func (a *kagentReconciler) ReconcileKagentAgent(ctx context.Context, req ctrl.Request) error { - // TODO(sbx0r): missing finalizer logic agent := &v1alpha2.Agent{} if err := a.kube.Get(ctx, req.NamespacedName, agent); err != nil { if apierrors.IsNotFound(err) { - return a.handleAgentDeletion(ctx, req) + return a.handleDeletedAgentResource(ctx, req, "agent") } - return fmt.Errorf("failed to get agent %s: %w", req.NamespacedName, err) } @@ -111,41 +109,30 @@ func (a *kagentReconciler) ReconcileKagentAgent(ctx context.Context, req ctrl.Re return a.reconcileAgentStatus(ctx, agent, err) } -func (a *kagentReconciler) handleAgentDeletion(ctx context.Context, req ctrl.Request) error { - id := utils.ConvertToPythonIdentifier(req.String()) - if err := a.dbClient.DeleteAgent(ctx, id); err != nil { - return fmt.Errorf("failed to delete agent %s: %w", - req.String(), err) - } - - reconcileLog.Info("Agent was deleted", "namespace", req.Namespace, "name", req.Name) - return nil -} - func (a *kagentReconciler) ReconcileKagentSandboxAgent(ctx context.Context, req ctrl.Request) error { - sa := &v1alpha2.SandboxAgent{} - if err := a.kube.Get(ctx, req.NamespacedName, sa); err != nil { + sandboxAgent := &v1alpha2.SandboxAgent{} + if err := a.kube.Get(ctx, req.NamespacedName, sandboxAgent); err != nil { if apierrors.IsNotFound(err) { - return a.handleSandboxAgentDeletion(ctx, req) + return a.handleDeletedAgentResource(ctx, req, "sandbox agent") } return fmt.Errorf("failed to get sandboxagent %s: %w", req.NamespacedName, err) } - err := a.reconcileSandboxAgent(ctx, sa) + err := a.reconcileSandboxAgent(ctx, sandboxAgent) if err != nil { reconcileLog.Error(err, "failed to reconcile sandboxagent", "sandboxagent", req.NamespacedName) } - return a.reconcileSandboxAgentStatus(ctx, sa, err) + return a.reconcileSandboxAgentStatus(ctx, sandboxAgent, err) } -func (a *kagentReconciler) handleSandboxAgentDeletion(ctx context.Context, req ctrl.Request) error { +func (a *kagentReconciler) handleDeletedAgentResource(ctx context.Context, req ctrl.Request, resourceName string) error { id := utils.ConvertToPythonIdentifier(req.String()) if err := a.dbClient.DeleteAgent(ctx, id); err != nil { - return fmt.Errorf("failed to delete sandbox agent %s from db: %w", req.String(), err) + return fmt.Errorf("failed to delete %s %s from db: %w", resourceName, req.String(), err) } - reconcileLog.Info("SandboxAgent was deleted", "namespace", req.Namespace, "name", req.Name) + reconcileLog.Info(fmt.Sprintf("%s was deleted", resourceName), "namespace", req.Namespace, "name", req.Name) return nil } @@ -159,86 +146,61 @@ func (a *kagentReconciler) reassignManifestOwnershipToSandboxAgent(sa *v1alpha2. return nil } -func (a *kagentReconciler) reconcileSandboxAgent(ctx context.Context, sa *v1alpha2.SandboxAgent) error { - if a.sandboxBackend != nil { - if err := sandboxbackend.EnsureAgentSandboxAPIsRegistered(ctx, a.kube); err != nil { - return err - } +func (a *kagentReconciler) reconcileTranslatedAgent( + ctx context.Context, + agent v1alpha2.AgentObject, + resourceName string, + mutateManifest func([]client.Object) error, +) error { + if err := a.validateCrossNamespaceReferences(ctx, agent); err != nil { + return err } - agentView := agent_translator.AgentViewFromSandboxAgent(sa) - if err := a.validateCrossNamespaceReferences(ctx, agentView); err != nil { - return err + inputs, err := a.adkTranslator.CompileAgent(ctx, agent) + if err != nil { + return fmt.Errorf("failed to compile %s %s/%s: %w", resourceName, agent.GetNamespace(), agent.GetName(), err) } - agentOutputs, err := a.adkTranslator.TranslateAgent(ctx, agentView, true) + agentOutputs, err := a.adkTranslator.BuildManifest(ctx, agent, inputs) if err != nil { - return fmt.Errorf("failed to translate sandboxagent %s/%s: %w", sa.Namespace, sa.Name, err) + return fmt.Errorf("failed to build manifest for %s %s/%s: %w", resourceName, agent.GetNamespace(), agent.GetName(), err) } - if err := a.reassignManifestOwnershipToSandboxAgent(sa, agentOutputs.Manifest); err != nil { - return err + if mutateManifest != nil { + if err := mutateManifest(agentOutputs.Manifest); err != nil { + return err + } } - ownedObjects, err := reconcilerutils.FindOwnedObjects(ctx, a.kube, sa.UID, sa.Namespace, a.adkTranslator.GetOwnedResourceTypes()) + ownedObjects, err := reconcilerutils.FindOwnedObjects(ctx, a.kube, agent.GetUID(), agent.GetNamespace(), a.adkTranslator.GetOwnedResourceTypes()) if err != nil { return err } - if err := a.reconcileDesiredObjects(ctx, sa, agentOutputs.Manifest, ownedObjects); err != nil { + if err := a.reconcileDesiredObjects(ctx, agent, agentOutputs.Manifest, ownedObjects); err != nil { return fmt.Errorf("failed to reconcile owned objects: %w", err) } - if err := a.upsertAgent(ctx, agentView, agentOutputs, true); err != nil { - return fmt.Errorf("failed to upsert agent %s/%s: %w", sa.Namespace, sa.Name, err) + if err := a.upsertAgent(ctx, agent, agentOutputs); err != nil { + return fmt.Errorf("failed to upsert %s %s/%s: %w", resourceName, agent.GetNamespace(), agent.GetName(), err) } return nil } -func (a *kagentReconciler) reconcileSandboxAgentStatus(ctx context.Context, sa *v1alpha2.SandboxAgent, reconcileErr error) error { - agentView := agent_translator.AgentViewFromSandboxAgent(sa) - var ( - status metav1.ConditionStatus - message string - reason string - ) - if reconcileErr != nil { - status = metav1.ConditionFalse - message = reconcileErr.Error() - reason = "ReconcileFailed" - } else { - status = metav1.ConditionTrue - reason = "Reconciled" - message = "SandboxAgent configuration accepted" +func (a *kagentReconciler) reconcileSandboxAgent(ctx context.Context, sa *v1alpha2.SandboxAgent) error { + if a.sandboxBackend != nil { + if err := sandboxbackend.EnsureAgentSandboxAPIsRegistered(ctx, a.kube); err != nil { + return err + } } - conditionChanged := meta.SetStatusCondition(&sa.Status.Conditions, metav1.Condition{ - Type: v1alpha2.AgentConditionTypeAccepted, - Status: status, - Reason: reason, - Message: message, - ObservedGeneration: sa.Generation, + return a.reconcileTranslatedAgent(ctx, sa, "sandboxagent", func(manifest []client.Object) error { + return a.reassignManifestOwnershipToSandboxAgent(sa, manifest) }) +} - if warning := a.validateRuntimeFeatures(agentView); warning != "" { - conditionChanged = conditionChanged || meta.SetStatusCondition(&sa.Status.Conditions, metav1.Condition{ - Type: v1alpha2.AgentConditionTypeUnsupportedFeatures, - Status: metav1.ConditionTrue, - Reason: "UnsupportedFeatures", - Message: warning, - ObservedGeneration: sa.Generation, - }) - } else { - for i, cond := range sa.Status.Conditions { - if cond.Type == v1alpha2.AgentConditionTypeUnsupportedFeatures && cond.Reason == "UnsupportedFeatures" { - sa.Status.Conditions = append(sa.Status.Conditions[:i], sa.Status.Conditions[i+1:]...) - conditionChanged = true - break - } - } - } - +func (a *kagentReconciler) reconcileSandboxAgentStatus(ctx context.Context, sa *v1alpha2.SandboxAgent, reconcileErr error) error { deployedCondition := metav1.Condition{ Type: v1alpha2.AgentConditionTypeReady, Status: metav1.ConditionUnknown, @@ -259,101 +221,97 @@ func (a *kagentReconciler) reconcileSandboxAgentStatus(ctx context.Context, sa * } } - conditionChanged = conditionChanged || meta.SetStatusCondition(&sa.Status.Conditions, deployedCondition) + return a.updateAgentObjectStatus(ctx, sa, reconcileErr, deployedCondition) +} + +func (a *kagentReconciler) reconcileAgentStatus(ctx context.Context, agent *v1alpha2.Agent, err error) error { + deployedCondition := metav1.Condition{ + Type: v1alpha2.AgentConditionTypeReady, + Status: metav1.ConditionUnknown, + ObservedGeneration: agent.Generation, + } - if conditionChanged || sa.Status.ObservedGeneration != sa.Generation { - sa.Status.ObservedGeneration = sa.Generation - if err := a.kube.Status().Update(ctx, sa); err != nil { - return fmt.Errorf("failed to update sandboxagent status: %w", err) + switch agent.Spec.Type { + default: + // Check if the deployment exists + deployment := &appsv1.Deployment{} + if err := a.kube.Get(ctx, types.NamespacedName{Namespace: agent.Namespace, Name: agent.Name}, deployment); err != nil { + deployedCondition.Status = metav1.ConditionUnknown + deployedCondition.Reason = "DeploymentNotFound" + deployedCondition.Message = err.Error() + } else { + replicas := int32(1) + if deployment.Spec.Replicas != nil { + replicas = *deployment.Spec.Replicas + } + if deployment.Status.AvailableReplicas >= replicas { + deployedCondition.Status = metav1.ConditionTrue + deployedCondition.Reason = AgentReadyReasonDeploymentReady + deployedCondition.Message = "Deployment is ready" + } else { + deployedCondition.Status = metav1.ConditionFalse + deployedCondition.Reason = "DeploymentNotReady" + deployedCondition.Message = fmt.Sprintf("Deployment is not ready, %d/%d pods are ready", deployment.Status.AvailableReplicas, replicas) + } } } - return nil + return a.updateAgentObjectStatus(ctx, agent, err, deployedCondition) } -func (a *kagentReconciler) reconcileAgentStatus(ctx context.Context, agent *v1alpha2.Agent, err error) error { +func (a *kagentReconciler) updateAgentObjectStatus(ctx context.Context, agent v1alpha2.AgentObject, reconcileErr error, readyCondition metav1.Condition) error { + statusRef := agent.GetAgentStatus() var ( status metav1.ConditionStatus message string reason string ) - if err != nil { + if reconcileErr != nil { status = metav1.ConditionFalse - message = err.Error() + message = reconcileErr.Error() reason = "ReconcileFailed" } else { status = metav1.ConditionTrue reason = "Reconciled" - message = "Agent configuration accepted" + message = fmt.Sprintf("%s configuration accepted", agentKind(agent)) } - conditionChanged := meta.SetStatusCondition(&agent.Status.Conditions, metav1.Condition{ + conditionChanged := meta.SetStatusCondition(&statusRef.Conditions, metav1.Condition{ Type: v1alpha2.AgentConditionTypeAccepted, Status: status, Reason: reason, Message: message, - ObservedGeneration: agent.Generation, + ObservedGeneration: agent.GetGeneration(), }) // Warn users when they configure features unsupported by their chosen runtime. // This implements soft validation - warns but doesn't fail reconciliation. if warning := a.validateRuntimeFeatures(agent); warning != "" { - conditionChanged = conditionChanged || meta.SetStatusCondition(&agent.Status.Conditions, metav1.Condition{ + conditionChanged = conditionChanged || meta.SetStatusCondition(&statusRef.Conditions, metav1.Condition{ Type: v1alpha2.AgentConditionTypeUnsupportedFeatures, Status: metav1.ConditionTrue, Reason: "UnsupportedFeatures", Message: warning, - ObservedGeneration: agent.Generation, + ObservedGeneration: agent.GetGeneration(), }) } else { // Clear warning condition if previously set - for i, cond := range agent.Status.Conditions { + for i, cond := range statusRef.Conditions { if cond.Type == v1alpha2.AgentConditionTypeUnsupportedFeatures && cond.Reason == "UnsupportedFeatures" { - agent.Status.Conditions = append(agent.Status.Conditions[:i], agent.Status.Conditions[i+1:]...) + statusRef.Conditions = append(statusRef.Conditions[:i], statusRef.Conditions[i+1:]...) conditionChanged = true break } } } - deployedCondition := metav1.Condition{ - Type: v1alpha2.AgentConditionTypeReady, - Status: metav1.ConditionUnknown, - ObservedGeneration: agent.Generation, - } - - switch agent.Spec.Type { - default: - // Check if the deployment exists - deployment := &appsv1.Deployment{} - if err := a.kube.Get(ctx, types.NamespacedName{Namespace: agent.Namespace, Name: agent.Name}, deployment); err != nil { - deployedCondition.Status = metav1.ConditionUnknown - deployedCondition.Reason = "DeploymentNotFound" - deployedCondition.Message = err.Error() - } else { - replicas := int32(1) - if deployment.Spec.Replicas != nil { - replicas = *deployment.Spec.Replicas - } - if deployment.Status.AvailableReplicas >= replicas { - deployedCondition.Status = metav1.ConditionTrue - deployedCondition.Reason = AgentReadyReasonDeploymentReady - deployedCondition.Message = "Deployment is ready" - } else { - deployedCondition.Status = metav1.ConditionFalse - deployedCondition.Reason = "DeploymentNotReady" - deployedCondition.Message = fmt.Sprintf("Deployment is not ready, %d/%d pods are ready", deployment.Status.AvailableReplicas, replicas) - } - } - } - - conditionChanged = conditionChanged || meta.SetStatusCondition(&agent.Status.Conditions, deployedCondition) + conditionChanged = conditionChanged || meta.SetStatusCondition(&statusRef.Conditions, readyCondition) // update the status if it has changed or the generation has changed - if conditionChanged || agent.Status.ObservedGeneration != agent.Generation { - agent.Status.ObservedGeneration = agent.Generation + if conditionChanged || statusRef.ObservedGeneration != agent.GetGeneration() { + statusRef.ObservedGeneration = agent.GetGeneration() if err := a.kube.Status().Update(ctx, agent); err != nil { - return fmt.Errorf("failed to update agent status: %w", err) + return fmt.Errorf("failed to update %s status: %w", strings.ToLower(agentKind(agent)), err) } } @@ -686,20 +644,21 @@ func (a *kagentReconciler) reconcileRemoteMCPServerStatus( // references in the agent's tools target namespaces that are watched by the // controller. This prevents agents from referencing tools or agents in // namespaces that the controller cannot access. -func (a *kagentReconciler) validateCrossNamespaceReferences(ctx context.Context, agent *v1alpha2.Agent) error { - if agent.Spec.Type != v1alpha2.AgentType_Declarative || agent.Spec.Declarative == nil { +func (a *kagentReconciler) validateCrossNamespaceReferences(ctx context.Context, agent v1alpha2.AgentObject) error { + spec := agent.GetAgentSpec() + if spec.Type != v1alpha2.AgentType_Declarative || spec.Declarative == nil { return nil } - decl := agent.Spec.Declarative + decl := spec.Declarative for _, tool := range decl.Tools { switch { case tool.McpServer != nil: - if err := a.validateMcpServerReference(ctx, agent.Namespace, tool.McpServer); err != nil { + if err := a.validateMcpServerReference(ctx, agent.GetNamespace(), tool.McpServer); err != nil { return err } case tool.Agent != nil: - if err := a.validateAgentToolReference(ctx, agent.Namespace, tool.Agent); err != nil { + if err := a.validateAgentToolReference(ctx, agent.GetNamespace(), tool.Agent); err != nil { return err } } @@ -804,40 +763,18 @@ func (a *kagentReconciler) validateMcpServerReference(ctx context.Context, sourc } func (a *kagentReconciler) reconcileAgent(ctx context.Context, agent *v1alpha2.Agent) error { - // Validate that any cross-namespace references are allowed - if err := a.validateCrossNamespaceReferences(ctx, agent); err != nil { - return err - } - - agentOutputs, err := a.adkTranslator.TranslateAgent(ctx, agent, false) - if err != nil { - return fmt.Errorf("failed to translate agent %s/%s: %w", agent.Namespace, agent.Name, err) - } - - ownedObjects, err := reconcilerutils.FindOwnedObjects(ctx, a.kube, agent.UID, agent.Namespace, a.adkTranslator.GetOwnedResourceTypes()) - if err != nil { - return err - } - - if err := a.reconcileDesiredObjects(ctx, agent, agentOutputs.Manifest, ownedObjects); err != nil { - return fmt.Errorf("failed to reconcile owned objects: %w", err) - } - - if err := a.upsertAgent(ctx, agent, agentOutputs, false); err != nil { - return fmt.Errorf("failed to upsert agent %s/%s: %w", agent.Namespace, agent.Name, err) - } - - return nil + return a.reconcileTranslatedAgent(ctx, agent, "agent", nil) } // validateRuntimeFeatures checks if the agent configures features unsupported by its runtime. // Returns a warning message if unsupported features are detected, empty string otherwise. // This implements soft validation - warns but doesn't fail reconciliation. -func (a *kagentReconciler) validateRuntimeFeatures(agent *v1alpha2.Agent) string { - if agent.Spec.Type != v1alpha2.AgentType_Declarative || agent.Spec.Declarative == nil { +func (a *kagentReconciler) validateRuntimeFeatures(agent v1alpha2.AgentObject) string { + spec := agent.GetAgentSpec() + if spec.Type != v1alpha2.AgentType_Declarative || spec.Declarative == nil { return "" } - decl := agent.Spec.Declarative + decl := spec.Declarative // Get runtime (defaults to python) runtime := decl.Runtime @@ -993,16 +930,17 @@ func (a *kagentReconciler) deleteObjects(ctx context.Context, objects map[types. return errors.Join(pruneErrs...) } -func (a *kagentReconciler) upsertAgent(ctx context.Context, agent *v1alpha2.Agent, agentOutputs *agent_translator.AgentOutputs, runInSandbox bool) error { +func (a *kagentReconciler) upsertAgent(ctx context.Context, agent v1alpha2.AgentObject, agentOutputs *agent_translator.AgentOutputs) error { id := utils.ConvertToPythonIdentifier(utils.GetObjectRef(agent)) - dbType := string(agent.Spec.Type) - if runInSandbox { + dbType := string(agent.GetAgentSpec().Type) + if agent.GetWorkloadMode() == v1alpha2.WorkloadModeSandbox { dbType = "SandboxAgent" } dbAgent := &database.Agent{ - ID: id, - Type: dbType, - Config: agentOutputs.Config, + ID: id, + Type: dbType, + WorkloadType: agent.GetWorkloadMode(), + Config: agentOutputs.Config, } if err := a.dbClient.StoreAgent(ctx, dbAgent); err != nil { @@ -1012,6 +950,13 @@ func (a *kagentReconciler) upsertAgent(ctx context.Context, agent *v1alpha2.Agen return nil } +func agentKind(agent v1alpha2.AgentObject) string { + if agent.GetWorkloadMode() == v1alpha2.WorkloadModeSandbox { + return "SandboxAgent" + } + return "Agent" +} + func (a *kagentReconciler) upsertToolServerForRemoteMCPServer(ctx context.Context, toolServer *database.ToolServer, remoteMcpServer *v1alpha2.RemoteMCPServer) ([]*v1alpha2.MCPTool, error) { if _, err := a.dbClient.StoreToolServer(ctx, toolServer); err != nil { return nil, fmt.Errorf("failed to store toolServer %s: %w", toolServer.Name, err) diff --git a/go/core/internal/controller/reconciler/utils/reconciler_utils.go b/go/core/internal/controller/reconciler/utils/reconciler_utils.go index 70377df69..856cf4680 100644 --- a/go/core/internal/controller/reconciler/utils/reconciler_utils.go +++ b/go/core/internal/controller/reconciler/utils/reconciler_utils.go @@ -142,6 +142,17 @@ func DeepEqual(val1, val2 any) bool { // SetupOwnerIndexes sets up caching and indexing of owned resources. func SetupOwnerIndexes(mgr ctrl.Manager, ownedTypes []client.Object) error { for _, resource := range ownedTypes { + gvk, err := apiutil.GVKForObject(resource, mgr.GetScheme()) + if err != nil { + return err + } + if _, err := mgr.GetRESTMapper().RESTMapping(gvk.GroupKind(), gvk.Version); err != nil { + if meta.IsNoMatchError(err) { + continue + } + return err + } + if err := mgr.GetFieldIndexer().IndexField(context.Background(), resource, ownerIndexKey, func(rawObj client.Object) []string { owner := metav1.GetControllerOf(rawObj) if owner == nil { diff --git a/go/core/internal/controller/sandboxagent_controller.go b/go/core/internal/controller/sandboxagent_controller.go index 9f4dd5445..6b496c172 100644 --- a/go/core/internal/controller/sandboxagent_controller.go +++ b/go/core/internal/controller/sandboxagent_controller.go @@ -19,21 +19,17 @@ package controller import ( "context" - corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" - "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/predicate" - "sigs.k8s.io/controller-runtime/pkg/reconcile" "github.com/kagent-dev/kagent/go/api/v1alpha2" "github.com/kagent-dev/kagent/go/core/internal/controller/reconciler" agent_translator "github.com/kagent-dev/kagent/go/core/internal/controller/translator/agent" - "github.com/kagent-dev/kmcp/api/v1alpha1" ) var ( @@ -71,252 +67,35 @@ func (r *SandboxAgentController) SetupWithManager(mgr ctrl.Manager) error { }). For(&v1alpha2.SandboxAgent{}, builder.WithPredicates(predicate.Or(predicate.GenerationChangedPredicate{}, predicate.LabelChangedPredicate{}))) - for _, ownedType := range r.AdkTranslator.GetOwnedResourceTypes() { - build = build.Owns(ownedType, builder.WithPredicates(ownedObjectPredicate{}, predicate.ResourceVersionChangedPredicate{})) + var err error + build, err = addOwnedResourceWatches(build, mgr, r.AdkTranslator.GetOwnedResourceTypes()) + if err != nil { + return err } - - build = build.Watches( - &v1alpha2.ModelConfig{}, - handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []reconcile.Request { - var requests []reconcile.Request - for _, sa := range r.findSandboxAgentsUsingModelConfig(ctx, mgr.GetClient(), types.NamespacedName{ - Name: obj.GetName(), - Namespace: obj.GetNamespace(), - }) { - requests = append(requests, reconcile.Request{ - NamespacedName: types.NamespacedName{ - Name: sa.Name, - Namespace: sa.Namespace, - }, - }) - } - return requests - }), - builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}), - ). - Watches( - &v1alpha2.RemoteMCPServer{}, - handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []reconcile.Request { - var requests []reconcile.Request - for _, sa := range r.findSandboxAgentsUsingRemoteMCPServer(ctx, mgr.GetClient(), types.NamespacedName{ - Name: obj.GetName(), - Namespace: obj.GetNamespace(), - }) { - requests = append(requests, reconcile.Request{ - NamespacedName: types.NamespacedName{ - Name: sa.Name, - Namespace: sa.Namespace, - }, - }) - } - return requests - }), - ). - Watches( - &corev1.Service{}, - handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []reconcile.Request { - var requests []reconcile.Request - for _, sa := range r.findSandboxAgentsUsingMCPService(ctx, mgr.GetClient(), types.NamespacedName{ - Name: obj.GetName(), - Namespace: obj.GetNamespace(), - }) { - requests = append(requests, reconcile.Request{ - NamespacedName: types.NamespacedName{ - Name: sa.Name, - Namespace: sa.Namespace, - }, - }) - } - return requests - }), - builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}), - ). - Watches( - &corev1.ConfigMap{}, - handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []reconcile.Request { - var requests []reconcile.Request - for _, sa := range r.findSandboxAgentsReferencingConfigMap(ctx, mgr.GetClient(), types.NamespacedName{ - Name: obj.GetName(), - Namespace: obj.GetNamespace(), - }) { - requests = append(requests, reconcile.Request{ - NamespacedName: types.NamespacedName{ - Name: sa.Name, - Namespace: sa.Namespace, - }, - }) - } - return requests - }), - builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}), - ) - - if _, err := mgr.GetRESTMapper().RESTMapping(mcpServerGK); err == nil { - build = build.Watches( - &v1alpha1.MCPServer{}, - handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []reconcile.Request { - var requests []reconcile.Request - for _, sa := range r.findSandboxAgentsUsingMCPServer(ctx, mgr.GetClient(), types.NamespacedName{ - Name: obj.GetName(), - Namespace: obj.GetNamespace(), - }) { - requests = append(requests, reconcile.Request{ - NamespacedName: types.NamespacedName{ - Name: sa.Name, - Namespace: sa.Namespace, - }, - }) - } - return requests - }), - builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}), - ) + build, err = addCommonAgentWatches(build, mgr, agentWatchFinders{ + modelConfig: r.sandboxAgentDependencyFinder("failed to list sandboxagents for ModelConfig watch", usesModelConfig), + remoteMCPServer: r.sandboxAgentDependencyFinder("failed to list sandboxagents for RemoteMCPServer watch", usesRemoteMCPServer), + mcpService: r.sandboxAgentDependencyFinder("failed to list sandboxagents for Service watch", usesMCPService), + configMap: r.sandboxAgentDependencyFinder("failed to list sandboxagents for ConfigMap watch", referencesConfigMap), + mcpServer: r.sandboxAgentDependencyFinder("failed to list sandboxagents for MCPServer watch", usesMCPServer), + }) + if err != nil { + return err } return build.Named("sandboxagent").Complete(r) } -func (r *SandboxAgentController) findSandboxAgentsUsingMCPServer(ctx context.Context, cl client.Client, obj types.NamespacedName) []*v1alpha2.SandboxAgent { - var list v1alpha2.SandboxAgentList - if err := cl.List(ctx, &list); err != nil { - sandboxAgentControllerLog.Error(err, "failed to list sandboxagents for MCPServer watch") - return nil - } - - var out []*v1alpha2.SandboxAgent - for i := range list.Items { - sa := &list.Items[i] - decl := sa.Spec.Declarative - if decl == nil { - continue - } - for _, tool := range decl.Tools { - if tool.McpServer == nil { - continue - } - if tool.McpServer.ApiGroup != "kagent.dev" || tool.McpServer.Kind != "MCPServer" { - continue - } - if tool.McpServer.NamespacedName(sa.Namespace) == obj { - out = append(out, sa) - } - } - } - return out -} - -func (r *SandboxAgentController) findSandboxAgentsUsingRemoteMCPServer(ctx context.Context, cl client.Client, obj types.NamespacedName) []*v1alpha2.SandboxAgent { - var out []*v1alpha2.SandboxAgent - - var list v1alpha2.SandboxAgentList - if err := cl.List(ctx, &list); err != nil { - sandboxAgentControllerLog.Error(err, "failed to list sandboxagents for RemoteMCPServer watch") - return out - } - - for i := range list.Items { - sa := &list.Items[i] - decl := sa.Spec.Declarative - if decl == nil { - continue - } - for _, tool := range decl.Tools { - if tool.McpServer == nil { - continue - } - mcpServerRef := tool.McpServer.NamespacedName(sa.Namespace) - if mcpServerRef == obj { - out = append(out, sa) - break - } - } - } - return out -} - -func (r *SandboxAgentController) findSandboxAgentsUsingMCPService(ctx context.Context, cl client.Client, obj types.NamespacedName) []*v1alpha2.SandboxAgent { - var list v1alpha2.SandboxAgentList - if err := cl.List(ctx, &list); err != nil { - sandboxAgentControllerLog.Error(err, "failed to list sandboxagents for Service watch") - return nil - } - - var out []*v1alpha2.SandboxAgent - for i := range list.Items { - sa := &list.Items[i] - decl := sa.Spec.Declarative - if decl == nil { - continue - } - for _, tool := range decl.Tools { - if tool.McpServer == nil { - continue - } - if tool.McpServer.ApiGroup != "" || tool.McpServer.Kind != "Service" { - continue - } - if tool.McpServer.NamespacedName(sa.Namespace) == obj { - out = append(out, sa) - } - } - } - return out -} - -func (r *SandboxAgentController) findSandboxAgentsUsingModelConfig(ctx context.Context, cl client.Client, obj types.NamespacedName) []*v1alpha2.SandboxAgent { - var list v1alpha2.SandboxAgentList - if err := cl.List(ctx, &list); err != nil { - sandboxAgentControllerLog.Error(err, "failed to list sandboxagents for ModelConfig watch") - return nil - } - - var out []*v1alpha2.SandboxAgent - for i := range list.Items { - sa := &list.Items[i] - if sa.Namespace != obj.Namespace { - continue - } - if sa.Spec.Declarative != nil && sa.Spec.Declarative.ModelConfig == obj.Name { - out = append(out, sa) +func (r *SandboxAgentController) sandboxAgentDependencyFinder(errMsg string, pred agentDependencyPredicate) dependentRefFinder { + return func(ctx context.Context, cl client.Client, obj types.NamespacedName) []types.NamespacedName { + var list v1alpha2.SandboxAgentList + if err := cl.List(ctx, &list); err != nil { + sandboxAgentControllerLog.Error(err, errMsg) + return nil } - } - return out -} -func (r *SandboxAgentController) findSandboxAgentsReferencingConfigMap(ctx context.Context, cl client.Client, obj types.NamespacedName) []*v1alpha2.SandboxAgent { - var list v1alpha2.SandboxAgentList - if err := cl.List(ctx, &list); err != nil { - sandboxAgentControllerLog.Error(err, "failed to list sandboxagents for ConfigMap watch") - return nil + return collectSandboxAgentRefs(list.Items, func(agent v1alpha2.AgentObject) bool { + return pred(agent, obj) + }) } - - var out []*v1alpha2.SandboxAgent - for i := range list.Items { - sa := &list.Items[i] - if sa.Namespace != obj.Namespace { - continue - } - decl := sa.Spec.Declarative - if decl == nil { - continue - } - - if ref := decl.SystemMessageFrom; ref != nil { - if ref.Type == v1alpha2.ConfigMapValueSource && ref.Name == obj.Name { - out = append(out, sa) - continue - } - } - - if pt := decl.PromptTemplate; pt != nil { - for _, ds := range pt.DataSources { - if ds.Name == obj.Name { - out = append(out, sa) - break - } - } - } - } - - return out } diff --git a/go/core/internal/controller/translator/agent/adk_api_translator.go b/go/core/internal/controller/translator/agent/adk_api_translator.go index 34b8e2709..35fe9fa36 100644 --- a/go/core/internal/controller/translator/agent/adk_api_translator.go +++ b/go/core/internal/controller/translator/agent/adk_api_translator.go @@ -7,7 +7,6 @@ import ( _ "embed" "encoding/binary" "encoding/hex" - "encoding/json" "errors" "fmt" "maps" @@ -22,7 +21,6 @@ import ( "github.com/kagent-dev/kagent/go/api/adk" "github.com/kagent-dev/kagent/go/api/v1alpha2" - "github.com/kagent-dev/kagent/go/core/internal/controller/translator/labels" "github.com/kagent-dev/kagent/go/core/internal/utils" "github.com/kagent-dev/kagent/go/core/internal/version" "github.com/kagent-dev/kagent/go/core/pkg/env" @@ -31,13 +29,9 @@ import ( "github.com/kagent-dev/kmcp/api/v1alpha1" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/intstr" "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - "trpc.group/trpc-go/trpc-a2a-go/server" ) const ( @@ -120,10 +114,18 @@ var DefaultAgentPodLabels map[string]string type AgentOutputs = translator.AgentOutputs type AdkApiTranslator interface { + CompileAgent( + ctx context.Context, + agent v1alpha2.AgentObject, + ) (*AgentManifestInputs, error) + BuildManifest( + ctx context.Context, + agent v1alpha2.AgentObject, + inputs *AgentManifestInputs, + ) (*AgentOutputs, error) TranslateAgent( ctx context.Context, - agent *v1alpha2.Agent, - runInSandbox bool, + agent v1alpha2.AgentObject, ) (*AgentOutputs, error) GetOwnedResourceTypes() []client.Object } @@ -180,82 +182,10 @@ type adkApiTranslator struct { sandboxBackend sandboxbackend.Backend } -const MAX_DEPTH = 10 - -type tState struct { - // used to prevent infinite loops - // The recursion limit is 10 - depth uint8 - // used to enforce DAG - // The final member of the list will be the "parent" agent - visitedAgents []string -} - -func (s *tState) with(agent *v1alpha2.Agent) *tState { - visited := make([]string, len(s.visitedAgents), len(s.visitedAgents)+1) - copy(visited, s.visitedAgents) - visited = append(visited, utils.GetObjectRef(agent)) - return &tState{ - depth: s.depth + 1, - visitedAgents: visited, - } -} - -func (t *tState) isVisited(agentName string) bool { - return slices.Contains(t.visitedAgents, agentName) -} - -func (a *adkApiTranslator) TranslateAgent( - ctx context.Context, - agent *v1alpha2.Agent, - runInSandbox bool, -) (*AgentOutputs, error) { - err := a.validateAgent(ctx, agent, &tState{}) - if err != nil { - return nil, err - } - - var cfg *adk.AgentConfig - var dep *resolvedDeployment - var secretHashBytes []byte - - switch agent.Spec.Type { - case v1alpha2.AgentType_Declarative: - var mdd *modelDeploymentData - cfg, mdd, secretHashBytes, err = a.translateInlineAgent(ctx, agent) - if err != nil { - return nil, err - } - dep, err = resolveInlineDeployment(agent, mdd) - if err != nil { - return nil, err - } - - case v1alpha2.AgentType_BYO: - - dep, err = resolveByoDeployment(agent) - if err != nil { - return nil, err - } - - default: - return nil, fmt.Errorf("unknown agent type: %s", agent.Spec.Type) - } - - if runInSandbox && a.sandboxBackend == nil { - return nil, fmt.Errorf("sandbox backend is not configured") - } - - card := GetA2AAgentCard(agent) - - return a.buildManifest(ctx, agent, dep, cfg, card, secretHashBytes, runInSandbox) -} - // GetOwnedResourceTypes returns all the resource types that may be created for an agent. // Even though this method returns an array of client.Object, these are (empty) // example structs rather than actual resources. func (r *adkApiTranslator) GetOwnedResourceTypes() []client.Object { - ctx := context.Background() ownedResources := []client.Object{ &appsv1.Deployment{}, &corev1.ConfigMap{}, @@ -268,577 +198,15 @@ func (r *adkApiTranslator) GetOwnedResourceTypes() []client.Object { ownedResources = append(ownedResources, plugin.GetOwnedResourceTypes()...) } - // Only set this up if the sandbox CRDs are available - if err := sandboxbackend.EnsureAgentSandboxAPIsRegistered(ctx, r.kube); err == nil { + // Startup watch/index setup already skips NoMatch resources, so return sandbox-owned + // types whenever sandbox support is configured rather than probing the API too early. + if r.sandboxBackend != nil { ownedResources = append(ownedResources, r.sandboxBackend.GetOwnedResourceTypes()...) } return ownedResources } -func (a *adkApiTranslator) validateAgent(ctx context.Context, agent *v1alpha2.Agent, state *tState) error { - agentRef := utils.GetObjectRef(agent) - - if state.isVisited(agentRef) { - return fmt.Errorf("cycle detected in agent tool chain: %s -> %s", agentRef, agentRef) - } - - if state.depth > MAX_DEPTH { - return fmt.Errorf("recursion limit reached in agent tool chain: %s -> %s", agentRef, agentRef) - } - - if agent.Spec.Type != v1alpha2.AgentType_Declarative { - // We only need to validate loops in declarative agents - return nil - } - - for _, tool := range agent.Spec.Declarative.Tools { - switch tool.Type { - case v1alpha2.ToolProviderType_Agent: - if tool.Agent == nil { - return fmt.Errorf("tool must have an agent reference") - } - - agentRef := tool.Agent.NamespacedName(agent.Namespace) - - if agentRef.Namespace == agent.Namespace && agentRef.Name == agent.Name { - return fmt.Errorf("agent tool cannot be used to reference itself, %s", agentRef) - } - - toolAgent := &v1alpha2.Agent{} - err := a.kube.Get(ctx, agentRef, toolAgent) - if err != nil { - return err - } - - err = a.validateAgent(ctx, toolAgent, state.with(agent)) - if err != nil { - return err - } - } - } - - return nil -} - -func (a *adkApiTranslator) buildManifest( - ctx context.Context, - agent *v1alpha2.Agent, - dep *resolvedDeployment, - cfg *adk.AgentConfig, // nil for BYO - card *server.AgentCard, // nil for BYO - modelConfigSecretHashBytes []byte, // nil for BYO - runInSandbox bool, -) (*AgentOutputs, error) { - outputs := &AgentOutputs{} - - // Optional config/card for Inline - var cfgHash uint64 - var secretVol []corev1.Volume - var secretMounts []corev1.VolumeMount - var cfgJson string - var agentCard string - if cfg != nil && card != nil { - bCfg, err := json.Marshal(cfg) - if err != nil { - return nil, err - } - bCard, err := json.Marshal(card) - if err != nil { - return nil, err - } - // Include secret hash bytes in config hash to trigger redeployment on secret changes - secretData := modelConfigSecretHashBytes - if secretData == nil { - secretData = []byte{} - } - cfgHash = computeConfigHash(bCfg, bCard, secretData) - - cfgJson = string(bCfg) - agentCard = string(bCard) - - secretVol = []corev1.Volume{{ - Name: "config", - VolumeSource: corev1.VolumeSource{ - Secret: &corev1.SecretVolumeSource{ - SecretName: agent.Name, - }, - }, - }} - secretMounts = []corev1.VolumeMount{{Name: "config", MountPath: "/config"}} - } - - selectorLabels := map[string]string{ - "app": labels.ManagedByKagent, - "kagent": agent.Name, - } - podLabels := func() map[string]string { - l := maps.Clone(selectorLabels) - if dep.Labels != nil { - maps.Copy(l, dep.Labels) - } - return l - } - - objMeta := func() metav1.ObjectMeta { - return metav1.ObjectMeta{ - Name: agent.Name, - Namespace: agent.Namespace, - Annotations: agent.Annotations, - Labels: podLabels(), - } - } - - // Secret - outputs.Manifest = append(outputs.Manifest, &corev1.Secret{ - TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: "Secret"}, - ObjectMeta: objMeta(), - StringData: map[string]string{ - "config.json": cfgJson, - "agent-card.json": agentCard, - }, - }) - - // Service Account - only created if using the default name - if *dep.ServiceAccountName == agent.Name { - sa := &corev1.ServiceAccount{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "v1", - Kind: "ServiceAccount", - }, - ObjectMeta: objMeta(), - } - if dep.ServiceAccountConfig != nil { - if dep.ServiceAccountConfig.Labels != nil { - if sa.Labels == nil { - sa.Labels = make(map[string]string) - } - maps.Copy(sa.Labels, dep.ServiceAccountConfig.Labels) - } - if dep.ServiceAccountConfig.Annotations != nil { - if sa.Annotations == nil { - sa.Annotations = make(map[string]string) - } - maps.Copy(sa.Annotations, dep.ServiceAccountConfig.Annotations) - } - } - outputs.Manifest = append(outputs.Manifest, sa) - } - - // Base env for both types - sharedEnv := make([]corev1.EnvVar, 0, 8) - sharedEnv = append(sharedEnv, collectOtelEnvFromProcess()...) - sharedEnv = append(sharedEnv, - corev1.EnvVar{ - Name: env.KagentNamespace.Name(), - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.namespace"}, - }, - }, - corev1.EnvVar{ - Name: env.KagentName.Name(), - Value: agent.Name, - }, - corev1.EnvVar{ - Name: env.KagentURL.Name(), - Value: fmt.Sprintf("http://%s.%s:8083", utils.GetControllerName(), utils.GetResourceNamespace()), - }, - ) - - var skills []string - var gitRefs []v1alpha2.GitRepo - var gitAuthSecretRef *corev1.LocalObjectReference - if agent.Spec.Skills != nil { - skills = agent.Spec.Skills.Refs - gitRefs = agent.Spec.Skills.GitRefs - gitAuthSecretRef = agent.Spec.Skills.GitAuthSecretRef - } - hasSkills := len(skills) > 0 || len(gitRefs) > 0 - - // Build workload pod (Deployment or pluggable Sandbox CRD) - volumes := append(secretVol, dep.Volumes...) - volumeMounts := append(secretMounts, dep.VolumeMounts...) - needCodeExecIsolation := cfg != nil && cfg.GetExecuteCode() - - var initContainers []corev1.Container - - // Add shared skills volume and env var when any skills (OCI or git) are present - if hasSkills { - skillsEnv := corev1.EnvVar{ - Name: env.KagentSkillsFolder.Name(), - Value: "/skills", - } - // Skills use the BashTool which calls srt (Anthropic Sandbox Runtime) → bubblewrap. - // Mark that code-exec isolation is needed so Privileged is set when possible. - // Exception: if the user explicitly set AllowPrivilegeEscalation=false (PSS Restricted), - // we respect their security context and let srt fall back to user-namespace sandboxing. - needCodeExecIsolation = true - volumes = append(volumes, corev1.Volume{ - Name: "kagent-skills", - VolumeSource: corev1.VolumeSource{ - EmptyDir: &corev1.EmptyDirVolumeSource{}, - }, - }) - volumeMounts = append(volumeMounts, corev1.VolumeMount{ - Name: "kagent-skills", - MountPath: "/skills", - ReadOnly: true, - }) - sharedEnv = append(sharedEnv, skillsEnv) - - insecure := agent.Spec.Skills != nil && agent.Spec.Skills.InsecureSkipVerify - - var initResources *corev1.ResourceRequirements - var initEnv []corev1.EnvVar - if agent.Spec.Skills.InitContainer != nil { - if agent.Spec.Skills.InitContainer.Resources != nil { - initResources = agent.Spec.Skills.InitContainer.Resources.DeepCopy() - } - initEnv = append(initEnv, agent.Spec.Skills.InitContainer.Env...) - } - - container, skillsVolumes, err := buildSkillsInitContainer(gitRefs, gitAuthSecretRef, skills, insecure, dep.SecurityContext, initEnv, getDefaultResources(initResources)) - if err != nil { - return nil, fmt.Errorf("failed to build skills init container: %w", err) - } - initContainers = append(initContainers, container) - volumes = append(volumes, skillsVolumes...) - } - - // Token volume - volumes = append(volumes, corev1.Volume{ - Name: "kagent-token", - VolumeSource: corev1.VolumeSource{ - Projected: &corev1.ProjectedVolumeSource{ - Sources: []corev1.VolumeProjection{ - { - ServiceAccountToken: &corev1.ServiceAccountTokenProjection{ - Audience: "kagent", - ExpirationSeconds: new(int64(3600)), - Path: "kagent-token", - }, - }, - }, - }, - }, - }) - volumeMounts = append(volumeMounts, corev1.VolumeMount{ - Name: "kagent-token", - MountPath: "/var/run/secrets/tokens", - }) - env := append(dep.Env, sharedEnv...) - - var cmd []string - if len(dep.Cmd) != 0 { - cmd = []string{dep.Cmd} - } - - podTemplateAnnotations := dep.Annotations - if podTemplateAnnotations == nil { - podTemplateAnnotations = map[string]string{} - } - // Add hash annotations to pod template to force rollout on agent config or model config secret changes - podTemplateAnnotations["kagent.dev/config-hash"] = fmt.Sprintf("%d", cfgHash) - - // Merge container security context: start with user-provided, then apply sandbox requirements - var securityContext *corev1.SecurityContext - if dep.SecurityContext != nil { - // Deep copy the user-provided security context - securityContext = dep.SecurityContext.DeepCopy() - // Set Privileged for sandbox ONLY if it won't create an invalid securityContext. - // Kubernetes rejects {Privileged:true, AllowPrivilegeEscalation:false} as contradictory. - // When the user explicitly sets AllowPrivilegeEscalation=false (PSS Restricted namespace), - // we respect their choice: srt will use unprivileged user-namespace sandboxing instead. - // On modern kernels (EKS, GKE) unprivileged_userns_clone is enabled by default. - if needCodeExecIsolation && !allowPrivilegeEscalationExplicitlyFalse(securityContext) { - securityContext.Privileged = new(true) - } - } else if needCodeExecIsolation { - // No user-provided securityContext: create one with Privileged for code execution - securityContext = &corev1.SecurityContext{ - Privileged: new(true), - } - } - // If neither user-provided securityContext nor code-exec isolation is needed, securityContext remains nil - - // Determine runtime for probe configuration - runtime := v1alpha2.DeclarativeRuntime_Python - if agent.Spec.Type == v1alpha2.AgentType_Declarative && agent.Spec.Declarative.Runtime != "" { - runtime = agent.Spec.Declarative.Runtime - } - probeConf := getRuntimeProbeConfig(runtime) - - podTemplate := corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{Labels: podLabels(), Annotations: podTemplateAnnotations}, - Spec: corev1.PodSpec{ - ServiceAccountName: *dep.ServiceAccountName, - ImagePullSecrets: dep.ImagePullSecrets, - SecurityContext: dep.PodSecurityContext, - InitContainers: initContainers, - Containers: []corev1.Container{{ - Name: "kagent", - Image: dep.Image, - ImagePullPolicy: dep.ImagePullPolicy, - Command: cmd, - Args: dep.Args, - Ports: []corev1.ContainerPort{{Name: "http", ContainerPort: dep.Port}}, - Resources: dep.Resources, - Env: env, - ReadinessProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{Path: "/.well-known/agent-card.json", Port: intstr.FromString("http")}, - }, - InitialDelaySeconds: probeConf.InitialDelaySeconds, - TimeoutSeconds: probeConf.TimeoutSeconds, - PeriodSeconds: probeConf.PeriodSeconds, - }, - SecurityContext: securityContext, - VolumeMounts: volumeMounts, - }}, - Volumes: volumes, - Tolerations: dep.Tolerations, - Affinity: dep.Affinity, - NodeSelector: dep.NodeSelector, - }, - } - - var workloadObj client.Object - if runInSandbox { - sbObjs, err := a.sandboxBackend.BuildSandbox(ctx, sandboxbackend.BuildInput{ - Agent: agent, - PodTemplate: podTemplate, - }) - if err != nil { - return nil, fmt.Errorf("build sandbox workload: %w", err) - } - outputs.Manifest = append(outputs.Manifest, sbObjs...) - workloadObj = nil - } else { - deployment := &appsv1.Deployment{ - TypeMeta: metav1.TypeMeta{APIVersion: "apps/v1", Kind: "Deployment"}, - ObjectMeta: objMeta(), - Spec: appsv1.DeploymentSpec{ - Replicas: dep.Replicas, - Strategy: appsv1.DeploymentStrategy{ - Type: appsv1.RollingUpdateDeploymentStrategyType, - RollingUpdate: &appsv1.RollingUpdateDeployment{ - MaxUnavailable: &intstr.IntOrString{Type: intstr.Int, IntVal: 0}, - MaxSurge: &intstr.IntOrString{Type: intstr.Int, IntVal: 1}, - }, - }, - Selector: &metav1.LabelSelector{MatchLabels: selectorLabels}, - Template: podTemplate, - }, - } - workloadObj = deployment - } - if workloadObj != nil { - outputs.Manifest = append(outputs.Manifest, workloadObj) - } - - // Service: for Sandbox workloads the agent-sandbox controller creates and owns the Service - // (same name as the Sandbox), so we only create a Service for non-sandboxed agents. - if !runInSandbox { - outputs.Manifest = append(outputs.Manifest, &corev1.Service{ - TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: "Service"}, - ObjectMeta: objMeta(), - Spec: corev1.ServiceSpec{ - Selector: selectorLabels, - Ports: []corev1.ServicePort{{ - Name: "http", - Port: dep.Port, - TargetPort: intstr.FromInt(int(dep.Port)), - }}, - Type: corev1.ServiceTypeClusterIP, - }, - }) - } - - // Owner refs - for _, obj := range outputs.Manifest { - if err := controllerutil.SetControllerReference(agent, obj, a.kube.Scheme()); err != nil { - return nil, err - } - } - - // Inline-only return values - outputs.Config = cfg - if card != nil { - outputs.AgentCard = *card - } - - return outputs, a.runPlugins(ctx, agent, outputs) -} - -func (a *adkApiTranslator) translateInlineAgent(ctx context.Context, agent *v1alpha2.Agent) (*adk.AgentConfig, *modelDeploymentData, []byte, error) { - model, mdd, secretHashBytes, err := a.translateModel(ctx, agent.Namespace, agent.Spec.Declarative.ModelConfig) - if err != nil { - return nil, nil, nil, err - } - - // Resolve the raw system message (template processing happens after tools are translated). - rawSystemMessage, err := a.resolveRawSystemMessage(ctx, agent) - if err != nil { - return nil, nil, nil, err - } - - cfg := &adk.AgentConfig{ - Description: agent.Spec.Description, - Instruction: rawSystemMessage, - Model: model, - ExecuteCode: agent.Spec.Declarative.ExecuteCodeBlocks, - Stream: new(agent.Spec.Declarative.Stream), - } - - // Translate context management configuration - if agent.Spec.Declarative.Context != nil { - contextCfg := &adk.AgentContextConfig{} - - if agent.Spec.Declarative.Context.Compaction != nil { - comp := agent.Spec.Declarative.Context.Compaction - compCfg := &adk.AgentCompressionConfig{ - CompactionInterval: comp.CompactionInterval, - OverlapSize: comp.OverlapSize, - TokenThreshold: comp.TokenThreshold, - EventRetentionSize: comp.EventRetentionSize, - } - - if comp.Summarizer != nil { - if comp.Summarizer.PromptTemplate != nil { - compCfg.PromptTemplate = *comp.Summarizer.PromptTemplate - } - - summarizerModelName := "" - if comp.Summarizer.ModelConfig != nil { - summarizerModelName = *comp.Summarizer.ModelConfig - } - - if summarizerModelName == "" || summarizerModelName == agent.Spec.Declarative.ModelConfig { - compCfg.SummarizerModel = model - } else { - summarizerModel, summarizerMdd, summarizerSecretHash, err := a.translateModel(ctx, agent.Namespace, summarizerModelName) - if err != nil { - return nil, nil, nil, fmt.Errorf("failed to translate summarizer model config %q: %w", summarizerModelName, err) - } - compCfg.SummarizerModel = summarizerModel - mergeDeploymentData(mdd, summarizerMdd) - if len(summarizerSecretHash) > 0 { - secretHashBytes = append(secretHashBytes, summarizerSecretHash...) - } - } - } - - contextCfg.Compaction = compCfg - } - - cfg.ContextConfig = contextCfg - } - - // Handle Memory Configuration: presence of Memory field enables it. - if agent.Spec.Declarative.Memory != nil { - embCfg, embMdd, embHash, err := a.translateEmbeddingConfig(ctx, agent.Namespace, agent.Spec.Declarative.Memory.ModelConfig) - if err != nil { - return nil, nil, nil, fmt.Errorf("failed to resolve embedding config: %w", err) - } - - cfg.Memory = &adk.MemoryConfig{ - TTLDays: agent.Spec.Declarative.Memory.TTLDays, - Embedding: embCfg, - } - - mergeDeploymentData(mdd, embMdd) - if agent.Spec.Declarative.Memory.ModelConfig != agent.Spec.Declarative.ModelConfig { - secretHashBytes = append(secretHashBytes, embHash...) - } - } - - for _, tool := range agent.Spec.Declarative.Tools { - headers, err := tool.ResolveHeaders(ctx, a.kube, agent.Namespace) - if err != nil { - return nil, nil, nil, err - } - - // Skip tools that are not applicable to the model provider - switch { - case tool.McpServer != nil: - // Use proxy for MCP server/tool communication - err := a.translateMCPServerTarget(ctx, cfg, agent.Namespace, tool.McpServer, headers, a.globalProxyURL) - if err != nil { - return nil, nil, nil, err - } - case tool.Agent != nil: - agentRef := tool.Agent.NamespacedName(agent.Namespace) - - if agentRef.Namespace == agent.Namespace && agentRef.Name == agent.Name { - return nil, nil, nil, fmt.Errorf("agent tool cannot be used to reference itself, %s", agentRef) - } - - toolAgent := &v1alpha2.Agent{} - err := a.kube.Get(ctx, agentRef, toolAgent) - if err != nil { - return nil, nil, nil, err - } - - switch toolAgent.Spec.Type { - case v1alpha2.AgentType_BYO, v1alpha2.AgentType_Declarative: - originalURL := fmt.Sprintf("http://%s.%s:8080", toolAgent.Name, toolAgent.Namespace) - - // If proxy is configured, use proxy URL and set header for Gateway API routing - targetURL := originalURL - if a.globalProxyURL != "" { - targetURL, headers, err = applyProxyURL(originalURL, a.globalProxyURL, headers) - if err != nil { - return nil, nil, nil, err - } - } - - cfg.RemoteAgents = append(cfg.RemoteAgents, adk.RemoteAgentConfig{ - Name: utils.ConvertToPythonIdentifier(utils.GetObjectRef(toolAgent)), - Url: targetURL, - Headers: headers, - Description: toolAgent.Spec.Description, - }) - default: - return nil, nil, nil, fmt.Errorf("unknown agent type: %s", toolAgent.Spec.Type) - } - - default: - return nil, nil, nil, fmt.Errorf("tool must have a provider or tool server") - } - } - - // Apply prompt template processing after tools are translated, so tool names - // from the config are available as template variables. - if agent.Spec.Declarative.PromptTemplate != nil && len(agent.Spec.Declarative.PromptTemplate.DataSources) > 0 { - lookup, err := resolvePromptSources(ctx, a.kube, agent.Namespace, agent.Spec.Declarative.PromptTemplate.DataSources) - if err != nil { - return nil, nil, nil, fmt.Errorf("failed to resolve prompt sources: %w", err) - } - - tplCtx := buildTemplateContext(agent, cfg) - - resolved, err := executeSystemMessageTemplate(cfg.Instruction, lookup, tplCtx) - if err != nil { - return nil, nil, nil, fmt.Errorf("failed to execute system message template: %w", err) - } - cfg.Instruction = resolved - } - - return cfg, mdd, secretHashBytes, nil -} - -// resolveRawSystemMessage gets the raw system message string from the agent spec -// without applying any template processing. -func (a *adkApiTranslator) resolveRawSystemMessage(ctx context.Context, agent *v1alpha2.Agent) (string, error) { - if agent.Spec.Declarative.SystemMessageFrom != nil { - return agent.Spec.Declarative.SystemMessageFrom.Resolve(ctx, a.kube, agent.Namespace) - } - if agent.Spec.Declarative.SystemMessage != "" { - return agent.Spec.Declarative.SystemMessage, nil - } - return "", fmt.Errorf("at least one system message source (SystemMessage or SystemMessageFrom) must be specified") -} - const ( googleCredsVolumeName = "google-creds" tlsCACertVolumeName = "tls-ca-cert" @@ -1849,7 +1217,7 @@ func buildSkillsInitContainer( return container, volumes, nil } -func (a *adkApiTranslator) runPlugins(ctx context.Context, agent *v1alpha2.Agent, outputs *AgentOutputs) error { +func (a *adkApiTranslator) runPlugins(ctx context.Context, agent v1alpha2.AgentObject, outputs *AgentOutputs) error { var errs error for _, plugin := range a.plugins { if err := plugin.ProcessAgent(ctx, agent, outputs); err != nil { diff --git a/go/core/internal/controller/translator/agent/adk_api_translator_test.go b/go/core/internal/controller/translator/agent/adk_api_translator_test.go index be2c1aaf0..284b37b67 100644 --- a/go/core/internal/controller/translator/agent/adk_api_translator_test.go +++ b/go/core/internal/controller/translator/agent/adk_api_translator_test.go @@ -181,7 +181,7 @@ func Test_AdkApiTranslator_CrossNamespaceAgentTool(t *testing.T) { trans := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil) - _, err := trans.TranslateAgent(context.Background(), tt.sourceAgent, false) + _, err := trans.TranslateAgent(context.Background(), tt.sourceAgent) if tt.wantErr { require.Error(t, err) @@ -344,7 +344,7 @@ func Test_AdkApiTranslator_CrossNamespaceRemoteMCPServer(t *testing.T) { trans := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil) - _, err := trans.TranslateAgent(context.Background(), tt.agent, false) + _, err := trans.TranslateAgent(context.Background(), tt.agent) if tt.wantErr { require.Error(t, err) @@ -418,7 +418,7 @@ func Test_AdkApiTranslator_OllamaOptions(t *testing.T) { trans := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil) - outputs, err := trans.TranslateAgent(context.Background(), agent, false) + outputs, err := trans.TranslateAgent(context.Background(), agent) require.NoError(t, err) require.NotNil(t, outputs) require.NotNil(t, outputs.Config) @@ -537,7 +537,7 @@ func Test_AdkApiTranslator_ServiceAccountNameOverride(t *testing.T) { trans := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil) - outputs, err := trans.TranslateAgent(context.Background(), tt.agent, false) + outputs, err := trans.TranslateAgent(context.Background(), tt.agent) require.NoError(t, err) require.NotNil(t, outputs) @@ -684,7 +684,7 @@ func Test_AdkApiTranslator_RecursionDepthTracking(t *testing.T) { kubeClient := builder.Build() trans := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil) - _, err := trans.TranslateAgent(context.Background(), root, false) + _, err := trans.TranslateAgent(context.Background(), root) require.NoError(t, err, "flat list of 12 agent tools should not hit recursion limit") }) @@ -728,7 +728,7 @@ func Test_AdkApiTranslator_RecursionDepthTracking(t *testing.T) { kubeClient := builder.Build() trans := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil) - _, err := trans.TranslateAgent(context.Background(), agents[0], false) + _, err := trans.TranslateAgent(context.Background(), agents[0]) require.NoError(t, err, "deep nesting of 10 levels should pass") }) @@ -771,7 +771,7 @@ func Test_AdkApiTranslator_RecursionDepthTracking(t *testing.T) { kubeClient := builder.Build() trans := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil) - _, err := trans.TranslateAgent(context.Background(), agents[0], false) + _, err := trans.TranslateAgent(context.Background(), agents[0]) require.Error(t, err, "deep nesting of 12 levels should fail") assert.Contains(t, err.Error(), "recursion limit reached") }) @@ -826,7 +826,7 @@ func Test_AdkApiTranslator_RecursionDepthTracking(t *testing.T) { WithObjects(modelConfig, agentA, agentB).Build() trans := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil) - _, err := trans.TranslateAgent(context.Background(), agentA, false) + _, err := trans.TranslateAgent(context.Background(), agentA) require.Error(t, err, "cycle A->B->A should be detected") assert.Contains(t, err.Error(), "cycle detected") }) @@ -912,7 +912,7 @@ func Test_AdkApiTranslator_RecursionDepthTracking(t *testing.T) { WithObjects(modelConfig, agentA, agentB, agentC, agentD).Build() trans := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil) - _, err := trans.TranslateAgent(context.Background(), agentA, false) + _, err := trans.TranslateAgent(context.Background(), agentA) require.NoError(t, err, "diamond pattern should pass — D is not a cycle, just shared") }) } @@ -1118,7 +1118,7 @@ func Test_AdkApiTranslator_MergeDeploymentData(t *testing.T) { defaultModel := types.NamespacedName{Namespace: "default", Name: tt.agentModel.Name} trans := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil) - outputs, err := trans.TranslateAgent(context.Background(), makeAgent(tt.agentModel.Name, tt.summModel.Name), false) + outputs, err := trans.TranslateAgent(context.Background(), makeAgent(tt.agentModel.Name, tt.summModel.Name)) require.NoError(t, err) require.NotNil(t, outputs) @@ -1270,7 +1270,7 @@ func Test_AdkApiTranslator_ContextConfig(t *testing.T) { defaultModel := types.NamespacedName{Namespace: "default", Name: "test-model"} trans := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil) - outputs, err := trans.TranslateAgent(context.Background(), tt.agent, false) + outputs, err := trans.TranslateAgent(context.Background(), tt.agent) if tt.wantErr { require.Error(t, err) @@ -1304,7 +1304,7 @@ func Test_AdkApiTranslator_SandboxAgent_defaultEmitsSandbox(t *testing.T) { Provider: v1alpha2.ModelProviderOpenAI, }, } - agent := &v1alpha2.Agent{ + sa := &v1alpha2.SandboxAgent{ ObjectMeta: metav1.ObjectMeta{Name: "ag1", Namespace: "sandbox-ns"}, Spec: v1alpha2.AgentSpec{ Type: v1alpha2.AgentType_Declarative, @@ -1316,7 +1316,7 @@ func Test_AdkApiTranslator_SandboxAgent_defaultEmitsSandbox(t *testing.T) { } kubeClient := fake.NewClientBuilder(). WithScheme(scheme). - WithObjects(ns, modelConfig, agent). + WithObjects(ns, modelConfig). Build() trans := translator.NewAdkApiTranslator( @@ -1326,7 +1326,7 @@ func Test_AdkApiTranslator_SandboxAgent_defaultEmitsSandbox(t *testing.T) { "", agentsxk8s.New(), ) - outputs, err := trans.TranslateAgent(ctx, agent, true) + outputs, err := trans.TranslateAgent(ctx, sa) require.NoError(t, err) require.NotNil(t, outputs) @@ -1346,7 +1346,7 @@ func Test_AdkApiTranslator_SandboxAgent_defaultEmitsSandbox(t *testing.T) { require.False(t, sawService, "sandbox runtime must not include Service; agent-sandbox owns it") } -func Test_AdkApiTranslator_SandboxAgentView_BYOEmitsSandbox(t *testing.T) { +func Test_AdkApiTranslator_SandboxAgent_BYOEmitsSandbox(t *testing.T) { ctx := context.Background() scheme := schemev1.Scheme require.NoError(t, v1alpha2.AddToScheme(scheme)) @@ -1366,7 +1366,6 @@ func Test_AdkApiTranslator_SandboxAgentView_BYOEmitsSandbox(t *testing.T) { }, }, } - agentView := translator.AgentViewFromSandboxAgent(sa) kubeClient := fake.NewClientBuilder(). WithScheme(scheme). WithObjects(ns). @@ -1379,7 +1378,7 @@ func Test_AdkApiTranslator_SandboxAgentView_BYOEmitsSandbox(t *testing.T) { "", agentsxk8s.New(), ) - outputs, err := trans.TranslateAgent(ctx, agentView, true) + outputs, err := trans.TranslateAgent(ctx, sa) require.NoError(t, err) require.NotNil(t, outputs) diff --git a/go/core/internal/controller/translator/agent/adk_translator_golden_test.go b/go/core/internal/controller/translator/agent/adk_translator_golden_test.go index 64d8ebc09..9cc1d99fb 100644 --- a/go/core/internal/controller/translator/agent/adk_translator_golden_test.go +++ b/go/core/internal/controller/translator/agent/adk_translator_golden_test.go @@ -178,7 +178,7 @@ func runGoldenTest(t *testing.T, inputFile, outputsDir, testName string, updateG // Use proxy URL from test input if provided proxyURL := testInput.ProxyURL - result, err = translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, proxyURL, nil).TranslateAgent(ctx, agent, false) + result, err = translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, proxyURL, nil).TranslateAgent(ctx, agent) require.NoError(t, err) default: diff --git a/go/core/internal/controller/translator/agent/compiler.go b/go/core/internal/controller/translator/agent/compiler.go new file mode 100644 index 000000000..7be78c987 --- /dev/null +++ b/go/core/internal/controller/translator/agent/compiler.go @@ -0,0 +1,319 @@ +package agent + +import ( + "context" + "fmt" + "slices" + + "github.com/kagent-dev/kagent/go/api/adk" + "github.com/kagent-dev/kagent/go/api/v1alpha2" + "github.com/kagent-dev/kagent/go/core/internal/utils" + "trpc.group/trpc-go/trpc-a2a-go/server" +) + +// AgentManifestInputs holds the translated data needed to emit Kubernetes resources. +type AgentManifestInputs struct { + Config *adk.AgentConfig + Deployment *resolvedDeployment + AgentCard *server.AgentCard + SecretHashBytes []byte +} + +const MAX_DEPTH = 10 + +type tState struct { + // used to prevent infinite loops + // The recursion limit is 10 + depth uint8 + // used to enforce DAG + // The final member of the list will be the "parent" agent + visitedAgents []string +} + +func (s *tState) with(agent v1alpha2.AgentObject) *tState { + visited := make([]string, len(s.visitedAgents), len(s.visitedAgents)+1) + copy(visited, s.visitedAgents) + visited = append(visited, utils.GetObjectRef(agent)) + return &tState{ + depth: s.depth + 1, + visitedAgents: visited, + } +} + +func (t *tState) isVisited(agentName string) bool { + return slices.Contains(t.visitedAgents, agentName) +} + +func (a *adkApiTranslator) TranslateAgent( + ctx context.Context, + agent v1alpha2.AgentObject, +) (*AgentOutputs, error) { + inputs, err := a.CompileAgent(ctx, agent) + if err != nil { + return nil, err + } + return a.BuildManifest(ctx, agent, inputs) +} + +func (a *adkApiTranslator) CompileAgent( + ctx context.Context, + agent v1alpha2.AgentObject, +) (*AgentManifestInputs, error) { + spec := agent.GetAgentSpec() + err := a.validateAgent(ctx, agent, &tState{}) + if err != nil { + return nil, err + } + + var cfg *adk.AgentConfig + var dep *resolvedDeployment + var secretHashBytes []byte + + switch spec.Type { + case v1alpha2.AgentType_Declarative: + var mdd *modelDeploymentData + cfg, mdd, secretHashBytes, err = a.translateInlineAgent(ctx, agent) + if err != nil { + return nil, err + } + dep, err = resolveInlineDeployment(agent, mdd) + if err != nil { + return nil, err + } + + case v1alpha2.AgentType_BYO: + dep, err = resolveByoDeployment(agent) + if err != nil { + return nil, err + } + + default: + return nil, fmt.Errorf("unknown agent type: %s", spec.Type) + } + + runInSandbox := agent.GetWorkloadMode() == v1alpha2.WorkloadModeSandbox + if runInSandbox && a.sandboxBackend == nil { + return nil, fmt.Errorf("sandbox backend is not configured") + } + + card := GetA2AAgentCard(agent) + + return &AgentManifestInputs{ + Config: cfg, + Deployment: dep, + AgentCard: card, + SecretHashBytes: secretHashBytes, + }, nil +} + +func (a *adkApiTranslator) validateAgent(ctx context.Context, agent v1alpha2.AgentObject, state *tState) error { + agentRef := utils.GetObjectRef(agent) + spec := agent.GetAgentSpec() + + if state.isVisited(agentRef) { + return fmt.Errorf("cycle detected in agent tool chain: %s -> %s", agentRef, agentRef) + } + + if state.depth > MAX_DEPTH { + return fmt.Errorf("recursion limit reached in agent tool chain: %s -> %s", agentRef, agentRef) + } + + if spec.Type != v1alpha2.AgentType_Declarative || spec.Declarative == nil { + // We only need to validate loops in declarative agents + return nil + } + + for _, tool := range spec.Declarative.Tools { + switch tool.Type { + case v1alpha2.ToolProviderType_Agent: + if tool.Agent == nil { + return fmt.Errorf("tool must have an agent reference") + } + + agentRef := tool.Agent.NamespacedName(agent.GetNamespace()) + + if agentRef.Namespace == agent.GetNamespace() && agentRef.Name == agent.GetName() { + return fmt.Errorf("agent tool cannot be used to reference itself, %s", agentRef) + } + + toolAgent := &v1alpha2.Agent{} + err := a.kube.Get(ctx, agentRef, toolAgent) + if err != nil { + return err + } + + err = a.validateAgent(ctx, toolAgent, state.with(agent)) + if err != nil { + return err + } + } + } + + return nil +} + +func (a *adkApiTranslator) translateInlineAgent(ctx context.Context, agent v1alpha2.AgentObject) (*adk.AgentConfig, *modelDeploymentData, []byte, error) { + spec := agent.GetAgentSpec() + model, mdd, secretHashBytes, err := a.translateModel(ctx, agent.GetNamespace(), spec.Declarative.ModelConfig) + if err != nil { + return nil, nil, nil, err + } + + // Resolve the raw system message (template processing happens after tools are translated). + rawSystemMessage, err := a.resolveRawSystemMessage(ctx, agent) + if err != nil { + return nil, nil, nil, err + } + + cfg := &adk.AgentConfig{ + Description: spec.Description, + Instruction: rawSystemMessage, + Model: model, + ExecuteCode: spec.Declarative.ExecuteCodeBlocks, + Stream: new(spec.Declarative.Stream), + } + + // Translate context management configuration + if spec.Declarative.Context != nil { + contextCfg := &adk.AgentContextConfig{} + + if spec.Declarative.Context.Compaction != nil { + comp := spec.Declarative.Context.Compaction + compCfg := &adk.AgentCompressionConfig{ + CompactionInterval: comp.CompactionInterval, + OverlapSize: comp.OverlapSize, + TokenThreshold: comp.TokenThreshold, + EventRetentionSize: comp.EventRetentionSize, + } + + if comp.Summarizer != nil { + if comp.Summarizer.PromptTemplate != nil { + compCfg.PromptTemplate = *comp.Summarizer.PromptTemplate + } + + summarizerModelName := "" + if comp.Summarizer.ModelConfig != nil { + summarizerModelName = *comp.Summarizer.ModelConfig + } + + if summarizerModelName == "" || summarizerModelName == spec.Declarative.ModelConfig { + compCfg.SummarizerModel = model + } else { + summarizerModel, summarizerMdd, summarizerSecretHash, err := a.translateModel(ctx, agent.GetNamespace(), summarizerModelName) + if err != nil { + return nil, nil, nil, fmt.Errorf("failed to translate summarizer model config %q: %w", summarizerModelName, err) + } + compCfg.SummarizerModel = summarizerModel + mergeDeploymentData(mdd, summarizerMdd) + if len(summarizerSecretHash) > 0 { + secretHashBytes = append(secretHashBytes, summarizerSecretHash...) + } + } + } + + contextCfg.Compaction = compCfg + } + + cfg.ContextConfig = contextCfg + } + + // Handle Memory Configuration: presence of Memory field enables it. + if spec.Declarative.Memory != nil { + embCfg, embMdd, embHash, err := a.translateEmbeddingConfig(ctx, agent.GetNamespace(), spec.Declarative.Memory.ModelConfig) + if err != nil { + return nil, nil, nil, fmt.Errorf("failed to resolve embedding config: %w", err) + } + + cfg.Memory = &adk.MemoryConfig{ + TTLDays: spec.Declarative.Memory.TTLDays, + Embedding: embCfg, + } + + mergeDeploymentData(mdd, embMdd) + if spec.Declarative.Memory.ModelConfig != spec.Declarative.ModelConfig { + secretHashBytes = append(secretHashBytes, embHash...) + } + } + + for _, tool := range spec.Declarative.Tools { + headers, err := tool.ResolveHeaders(ctx, a.kube, agent.GetNamespace()) + if err != nil { + return nil, nil, nil, err + } + + switch { + case tool.McpServer != nil: + err := a.translateMCPServerTarget(ctx, cfg, agent.GetNamespace(), tool.McpServer, headers, a.globalProxyURL) + if err != nil { + return nil, nil, nil, err + } + case tool.Agent != nil: + agentRef := tool.Agent.NamespacedName(agent.GetNamespace()) + + if agentRef.Namespace == agent.GetNamespace() && agentRef.Name == agent.GetName() { + return nil, nil, nil, fmt.Errorf("agent tool cannot be used to reference itself, %s", agentRef) + } + + toolAgent := &v1alpha2.Agent{} + err := a.kube.Get(ctx, agentRef, toolAgent) + if err != nil { + return nil, nil, nil, err + } + + switch toolAgent.Spec.Type { + case v1alpha2.AgentType_BYO, v1alpha2.AgentType_Declarative: + originalURL := fmt.Sprintf("http://%s.%s:8080", toolAgent.Name, toolAgent.Namespace) + + targetURL := originalURL + if a.globalProxyURL != "" { + targetURL, headers, err = applyProxyURL(originalURL, a.globalProxyURL, headers) + if err != nil { + return nil, nil, nil, err + } + } + + cfg.RemoteAgents = append(cfg.RemoteAgents, adk.RemoteAgentConfig{ + Name: utils.ConvertToPythonIdentifier(utils.GetObjectRef(toolAgent)), + Url: targetURL, + Headers: headers, + Description: toolAgent.Spec.Description, + }) + default: + return nil, nil, nil, fmt.Errorf("unknown agent type: %s", toolAgent.Spec.Type) + } + + default: + return nil, nil, nil, fmt.Errorf("tool must have a provider or tool server") + } + } + + if spec.Declarative.PromptTemplate != nil && len(spec.Declarative.PromptTemplate.DataSources) > 0 { + lookup, err := resolvePromptSources(ctx, a.kube, agent.GetNamespace(), spec.Declarative.PromptTemplate.DataSources) + if err != nil { + return nil, nil, nil, fmt.Errorf("failed to resolve prompt sources: %w", err) + } + + tplCtx := buildTemplateContext(agent, cfg) + + resolved, err := executeSystemMessageTemplate(cfg.Instruction, lookup, tplCtx) + if err != nil { + return nil, nil, nil, fmt.Errorf("failed to execute system message template: %w", err) + } + cfg.Instruction = resolved + } + + return cfg, mdd, secretHashBytes, nil +} + +// resolveRawSystemMessage gets the raw system message string from the agent spec +// without applying any template processing. +func (a *adkApiTranslator) resolveRawSystemMessage(ctx context.Context, agent v1alpha2.AgentObject) (string, error) { + spec := agent.GetAgentSpec() + if spec.Declarative.SystemMessageFrom != nil { + return spec.Declarative.SystemMessageFrom.Resolve(ctx, a.kube, agent.GetNamespace()) + } + if spec.Declarative.SystemMessage != "" { + return spec.Declarative.SystemMessage, nil + } + return "", fmt.Errorf("at least one system message source (SystemMessage or SystemMessageFrom) must be specified") +} diff --git a/go/core/internal/controller/translator/agent/deployments.go b/go/core/internal/controller/translator/agent/deployments.go index 276eabb51..d1275fd8b 100644 --- a/go/core/internal/controller/translator/agent/deployments.go +++ b/go/core/internal/controller/translator/agent/deployments.go @@ -105,7 +105,8 @@ func getRuntimeImageRepository(runtime v1alpha2.DeclarativeRuntime) string { } } -func resolveInlineDeployment(agent *v1alpha2.Agent, mdd *modelDeploymentData) (*resolvedDeployment, error) { +func resolveInlineDeployment(agent v1alpha2.AgentObject, mdd *modelDeploymentData) (*resolvedDeployment, error) { + specRef := agent.GetAgentSpec() // Defaults port := int32(8080) args := []string{ @@ -117,18 +118,19 @@ func resolveInlineDeployment(agent *v1alpha2.Agent, mdd *modelDeploymentData) (* "/config", } - serviceAccountName := new(agent.Name) + serviceAccountName := new(string) + *serviceAccountName = agent.GetName() // Start with spec deployment spec spec := v1alpha2.DeclarativeDeploymentSpec{} - if agent.Spec.Declarative.Deployment != nil { - spec = *agent.Spec.Declarative.Deployment + if specRef.Declarative.Deployment != nil { + spec = *specRef.Declarative.Deployment } // Determine runtime (defaults to python if not set) runtime := v1alpha2.DeclarativeRuntime_Python - if agent.Spec.Declarative.Runtime != "" { - runtime = agent.Spec.Declarative.Runtime + if specRef.Declarative.Runtime != "" { + runtime = specRef.Declarative.Runtime } // Get registry @@ -164,7 +166,7 @@ func resolveInlineDeployment(agent *v1alpha2.Agent, mdd *modelDeploymentData) (* ImagePullSecrets: slices.Clone(spec.ImagePullSecrets), Volumes: append(slices.Clone(spec.Volumes), mdd.Volumes...), VolumeMounts: append(slices.Clone(spec.VolumeMounts), mdd.VolumeMounts...), - Labels: getDefaultLabels(agent.Name, spec.Labels), + Labels: getDefaultLabels(agent.GetName(), spec.Labels), Annotations: maps.Clone(spec.Annotations), Env: append(slices.Clone(spec.Env), mdd.EnvVars...), Resources: getDefaultResources(spec.Resources), // Set default resources if not specified @@ -200,8 +202,8 @@ func checkPullSecretAlreadyPresent(spec v1alpha2.DeclarativeDeploymentSpec) bool return alreadyPresent } -func resolveByoDeployment(agent *v1alpha2.Agent) (*resolvedDeployment, error) { - spec := agent.Spec.BYO.Deployment +func resolveByoDeployment(agent v1alpha2.AgentObject) (*resolvedDeployment, error) { + spec := agent.GetAgentSpec().BYO.Deployment if spec == nil { return nil, fmt.Errorf("BYO deployment spec is required") } @@ -245,7 +247,7 @@ func resolveByoDeployment(agent *v1alpha2.Agent) (*resolvedDeployment, error) { ImagePullSecrets: slices.Clone(spec.ImagePullSecrets), Volumes: slices.Clone(spec.Volumes), VolumeMounts: slices.Clone(spec.VolumeMounts), - Labels: getDefaultLabels(agent.Name, spec.Labels), + Labels: getDefaultLabels(agent.GetName(), spec.Labels), Annotations: maps.Clone(spec.Annotations), Env: slices.Clone(spec.Env), Resources: getDefaultResources(spec.Resources), // Set default resources if not specified @@ -261,9 +263,11 @@ func resolveByoDeployment(agent *v1alpha2.Agent) (*resolvedDeployment, error) { // Precedence: agent-level serviceAccountName > global default > auto-created SA (agent name) if dep.ServiceAccountName == nil { if DefaultServiceAccountName != "" { - dep.ServiceAccountName = new(DefaultServiceAccountName) + dep.ServiceAccountName = new(string) + *dep.ServiceAccountName = DefaultServiceAccountName } else { - dep.ServiceAccountName = new(agent.Name) + dep.ServiceAccountName = new(string) + *dep.ServiceAccountName = agent.GetName() } } diff --git a/go/core/internal/controller/translator/agent/git_skills_test.go b/go/core/internal/controller/translator/agent/git_skills_test.go index 75e38dbe2..4ce06ae11 100644 --- a/go/core/internal/controller/translator/agent/git_skills_test.go +++ b/go/core/internal/controller/translator/agent/git_skills_test.go @@ -301,7 +301,7 @@ func Test_AdkApiTranslator_Skills(t *testing.T) { trans := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil) - outputs, err := trans.TranslateAgent(context.Background(), tt.agent, false) + outputs, err := trans.TranslateAgent(context.Background(), tt.agent) require.NoError(t, err) require.NotNil(t, outputs) @@ -486,7 +486,7 @@ func Test_AdkApiTranslator_SkillsConfigurableImage(t *testing.T) { } trans := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil) - outputs, err := trans.TranslateAgent(context.Background(), agent, false) + outputs, err := trans.TranslateAgent(context.Background(), agent) require.NoError(t, err) var deployment *appsv1.Deployment @@ -683,7 +683,7 @@ func Test_AdkApiTranslator_SkillsInitContainer(t *testing.T) { Build() trans := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil) - outputs, err := trans.TranslateAgent(context.Background(), tt.agent, false) + outputs, err := trans.TranslateAgent(context.Background(), tt.agent) require.NoError(t, err) var deployment *appsv1.Deployment diff --git a/go/core/internal/controller/translator/agent/manifest_builder.go b/go/core/internal/controller/translator/agent/manifest_builder.go new file mode 100644 index 000000000..e146e3204 --- /dev/null +++ b/go/core/internal/controller/translator/agent/manifest_builder.go @@ -0,0 +1,497 @@ +package agent + +import ( + "context" + "encoding/json" + "fmt" + "maps" + + "github.com/kagent-dev/kagent/go/api/adk" + "github.com/kagent-dev/kagent/go/api/v1alpha2" + "github.com/kagent-dev/kagent/go/core/internal/controller/translator/labels" + "github.com/kagent-dev/kagent/go/core/internal/utils" + "github.com/kagent-dev/kagent/go/core/pkg/env" + "github.com/kagent-dev/kagent/go/core/pkg/sandboxbackend" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "trpc.group/trpc-go/trpc-a2a-go/server" +) + +type manifestContext struct { + agent v1alpha2.AgentObject + deployment *resolvedDeployment + selectorLabels map[string]string +} + +type configSecretInputs struct { + secret *corev1.Secret + configHash uint64 + volumes []corev1.Volume + mounts []corev1.VolumeMount +} + +type podRuntimeInputs struct { + initContainers []corev1.Container + envVars []corev1.EnvVar + volumes []corev1.Volume + volumeMounts []corev1.VolumeMount + securityContext *corev1.SecurityContext +} + +func (a *adkApiTranslator) BuildManifest( + ctx context.Context, + agent v1alpha2.AgentObject, + inputs *AgentManifestInputs, +) (*AgentOutputs, error) { + if inputs == nil { + return nil, fmt.Errorf("agent manifest inputs are required") + } + if inputs.Deployment == nil { + return nil, fmt.Errorf("resolved deployment is required") + } + + outputs := &AgentOutputs{} + manifestCtx := newManifestContext(agent, inputs.Deployment) + + configSecret, err := a.buildConfigSecret(manifestCtx, inputs.Config, inputs.AgentCard, inputs.SecretHashBytes) + if err != nil { + return nil, err + } + outputs.Manifest = append(outputs.Manifest, configSecret.secret) + + if sa := buildServiceAccount(manifestCtx); sa != nil { + outputs.Manifest = append(outputs.Manifest, sa) + } + + podRuntime, err := buildPodRuntime(manifestCtx, inputs.Config, configSecret.volumes, configSecret.mounts) + if err != nil { + return nil, err + } + + podTemplate := buildPodTemplate(manifestCtx, podRuntime, configSecret.configHash) + + workloadObjects, err := a.buildWorkloadObjects(ctx, manifestCtx, podTemplate) + if err != nil { + return nil, err + } + outputs.Manifest = append(outputs.Manifest, workloadObjects...) + + if err := a.setManifestOwnerReferences(agent, outputs.Manifest); err != nil { + return nil, err + } + + outputs.Config = inputs.Config + if inputs.AgentCard != nil { + outputs.AgentCard = *inputs.AgentCard + } + + return outputs, a.runPlugins(ctx, agent, outputs) +} + +func newManifestContext(agent v1alpha2.AgentObject, dep *resolvedDeployment) manifestContext { + return manifestContext{ + agent: agent, + deployment: dep, + selectorLabels: map[string]string{ + "app": labels.ManagedByKagent, + "kagent": agent.GetName(), + }, + } +} + +func (m manifestContext) runInSandbox() bool { + return m.agent.GetWorkloadMode() == v1alpha2.WorkloadModeSandbox +} + +func (m manifestContext) podLabels() map[string]string { + podLabels := maps.Clone(m.selectorLabels) + if m.deployment.Labels != nil { + maps.Copy(podLabels, m.deployment.Labels) + } + return podLabels +} + +func (m manifestContext) objectMeta() metav1.ObjectMeta { + return metav1.ObjectMeta{ + Name: m.agent.GetName(), + Namespace: m.agent.GetNamespace(), + Annotations: m.agent.GetAnnotations(), + Labels: m.podLabels(), + } +} + +func (a *adkApiTranslator) buildConfigSecret( + manifestCtx manifestContext, + cfg *adk.AgentConfig, + card *server.AgentCard, + modelConfigSecretHashBytes []byte, +) (*configSecretInputs, error) { + cfgJSON := "" + agentCard := "" + var configHash uint64 + var volumes []corev1.Volume + var mounts []corev1.VolumeMount + + if cfg != nil && card != nil { + bCfg, err := json.Marshal(cfg) + if err != nil { + return nil, err + } + bCard, err := json.Marshal(card) + if err != nil { + return nil, err + } + + secretData := modelConfigSecretHashBytes + if secretData == nil { + secretData = []byte{} + } + configHash = computeConfigHash(bCfg, bCard, secretData) + cfgJSON = string(bCfg) + agentCard = string(bCard) + volumes = []corev1.Volume{{ + Name: "config", + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{SecretName: manifestCtx.agent.GetName()}, + }, + }} + mounts = []corev1.VolumeMount{{Name: "config", MountPath: "/config"}} + } + + return &configSecretInputs{ + secret: &corev1.Secret{ + TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: "Secret"}, + ObjectMeta: manifestCtx.objectMeta(), + StringData: map[string]string{ + "config.json": cfgJSON, + "agent-card.json": agentCard, + }, + }, + configHash: configHash, + volumes: volumes, + mounts: mounts, + }, nil +} + +func buildServiceAccount(manifestCtx manifestContext) *corev1.ServiceAccount { + serviceAccountName := manifestCtx.deployment.ServiceAccountName + if serviceAccountName == nil || *serviceAccountName != manifestCtx.agent.GetName() { + return nil + } + + sa := &corev1.ServiceAccount{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "ServiceAccount", + }, + ObjectMeta: manifestCtx.objectMeta(), + } + + if manifestCtx.deployment.ServiceAccountConfig == nil { + return sa + } + + if manifestCtx.deployment.ServiceAccountConfig.Labels != nil { + if sa.Labels == nil { + sa.Labels = make(map[string]string) + } + maps.Copy(sa.Labels, manifestCtx.deployment.ServiceAccountConfig.Labels) + } + if manifestCtx.deployment.ServiceAccountConfig.Annotations != nil { + if sa.Annotations == nil { + sa.Annotations = make(map[string]string) + } + maps.Copy(sa.Annotations, manifestCtx.deployment.ServiceAccountConfig.Annotations) + } + + return sa +} + +func buildPodRuntime( + manifestCtx manifestContext, + cfg *adk.AgentConfig, + secretVolumes []corev1.Volume, + secretMounts []corev1.VolumeMount, +) (*podRuntimeInputs, error) { + sharedEnv := collectSharedEnv(manifestCtx.agent) + + volumes := append([]corev1.Volume{}, secretVolumes...) + volumes = append(volumes, manifestCtx.deployment.Volumes...) + volumeMounts := append([]corev1.VolumeMount{}, secretMounts...) + volumeMounts = append(volumeMounts, manifestCtx.deployment.VolumeMounts...) + + needCodeExecIsolation := cfg != nil && cfg.GetExecuteCode() + initContainers, err := buildSkillsRuntime(manifestCtx, &sharedEnv, &volumes, &volumeMounts, &needCodeExecIsolation) + if err != nil { + return nil, err + } + + volumes = append(volumes, projectedTokenVolume()) + volumeMounts = append(volumeMounts, corev1.VolumeMount{ + Name: "kagent-token", + MountPath: "/var/run/secrets/tokens", + }) + + envVars := append([]corev1.EnvVar{}, manifestCtx.deployment.Env...) + envVars = append(envVars, sharedEnv...) + + return &podRuntimeInputs{ + initContainers: initContainers, + envVars: envVars, + volumes: volumes, + volumeMounts: volumeMounts, + securityContext: buildContainerSecurityContext(manifestCtx.deployment.SecurityContext, needCodeExecIsolation), + }, nil +} + +func collectSharedEnv(agent v1alpha2.AgentObject) []corev1.EnvVar { + sharedEnv := make([]corev1.EnvVar, 0, 8) + sharedEnv = append(sharedEnv, collectOtelEnvFromProcess()...) + sharedEnv = append(sharedEnv, + corev1.EnvVar{ + Name: env.KagentNamespace.Name(), + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.namespace"}, + }, + }, + corev1.EnvVar{ + Name: env.KagentName.Name(), + Value: agent.GetName(), + }, + corev1.EnvVar{ + Name: env.KagentURL.Name(), + Value: fmt.Sprintf("http://%s.%s:8083", utils.GetControllerName(), utils.GetResourceNamespace()), + }, + ) + return sharedEnv +} + +func buildSkillsRuntime( + manifestCtx manifestContext, + sharedEnv *[]corev1.EnvVar, + volumes *[]corev1.Volume, + volumeMounts *[]corev1.VolumeMount, + needCodeExecIsolation *bool, +) ([]corev1.Container, error) { + spec := manifestCtx.agent.GetAgentSpec() + if spec.Skills == nil { + return nil, nil + } + + skills := spec.Skills.Refs + gitRefs := spec.Skills.GitRefs + if len(skills) == 0 && len(gitRefs) == 0 { + return nil, nil + } + + *needCodeExecIsolation = true + *sharedEnv = append(*sharedEnv, corev1.EnvVar{ + Name: env.KagentSkillsFolder.Name(), + Value: "/skills", + }) + *volumes = append(*volumes, corev1.Volume{ + Name: "kagent-skills", + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{}, + }, + }) + *volumeMounts = append(*volumeMounts, corev1.VolumeMount{ + Name: "kagent-skills", + MountPath: "/skills", + ReadOnly: true, + }) + + var initResources *corev1.ResourceRequirements + var initEnv []corev1.EnvVar + if spec.Skills.InitContainer != nil { + if spec.Skills.InitContainer.Resources != nil { + initResources = spec.Skills.InitContainer.Resources.DeepCopy() + } + initEnv = append(initEnv, spec.Skills.InitContainer.Env...) + } + + container, skillsVolumes, err := buildSkillsInitContainer( + gitRefs, + spec.Skills.GitAuthSecretRef, + skills, + spec.Skills.InsecureSkipVerify, + manifestCtx.deployment.SecurityContext, + initEnv, + getDefaultResources(initResources), + ) + if err != nil { + return nil, fmt.Errorf("failed to build skills init container: %w", err) + } + + *volumes = append(*volumes, skillsVolumes...) + return []corev1.Container{container}, nil +} + +func projectedTokenVolume() corev1.Volume { + return corev1.Volume{ + Name: "kagent-token", + VolumeSource: corev1.VolumeSource{ + Projected: &corev1.ProjectedVolumeSource{ + Sources: []corev1.VolumeProjection{{ + ServiceAccountToken: &corev1.ServiceAccountTokenProjection{ + Audience: "kagent", + ExpirationSeconds: new(int64(3600)), + Path: "kagent-token", + }, + }}, + }, + }, + } +} + +func buildContainerSecurityContext( + base *corev1.SecurityContext, + needCodeExecIsolation bool, +) *corev1.SecurityContext { + if base != nil { + securityContext := base.DeepCopy() + if needCodeExecIsolation && !allowPrivilegeEscalationExplicitlyFalse(securityContext) { + securityContext.Privileged = new(true) + } + return securityContext + } + + if !needCodeExecIsolation { + return nil + } + + return &corev1.SecurityContext{Privileged: new(true)} +} + +func buildPodTemplate( + manifestCtx manifestContext, + runtimeInputs *podRuntimeInputs, + configHash uint64, +) corev1.PodTemplateSpec { + dep := manifestCtx.deployment + podTemplateAnnotations := maps.Clone(dep.Annotations) + if podTemplateAnnotations == nil { + podTemplateAnnotations = map[string]string{} + } + podTemplateAnnotations["kagent.dev/config-hash"] = fmt.Sprintf("%d", configHash) + + probeConf := getRuntimeProbeConfig(agentRuntime(manifestCtx.agent.GetAgentSpec())) + + var cmd []string + if dep.Cmd != "" { + cmd = []string{dep.Cmd} + } + + return corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: manifestCtx.podLabels(), + Annotations: podTemplateAnnotations, + }, + Spec: corev1.PodSpec{ + ServiceAccountName: *dep.ServiceAccountName, + ImagePullSecrets: dep.ImagePullSecrets, + SecurityContext: dep.PodSecurityContext, + InitContainers: runtimeInputs.initContainers, + Containers: []corev1.Container{{ + Name: "kagent", + Image: dep.Image, + ImagePullPolicy: dep.ImagePullPolicy, + Command: cmd, + Args: dep.Args, + Ports: []corev1.ContainerPort{{Name: "http", ContainerPort: dep.Port}}, + Resources: dep.Resources, + Env: runtimeInputs.envVars, + ReadinessProbe: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Path: "/.well-known/agent-card.json", + Port: intstr.FromString("http"), + }, + }, + InitialDelaySeconds: probeConf.InitialDelaySeconds, + TimeoutSeconds: probeConf.TimeoutSeconds, + PeriodSeconds: probeConf.PeriodSeconds, + }, + SecurityContext: runtimeInputs.securityContext, + VolumeMounts: runtimeInputs.volumeMounts, + }}, + Volumes: runtimeInputs.volumes, + Tolerations: dep.Tolerations, + Affinity: dep.Affinity, + NodeSelector: dep.NodeSelector, + }, + } +} + +func agentRuntime(spec *v1alpha2.AgentSpec) v1alpha2.DeclarativeRuntime { + runtime := v1alpha2.DeclarativeRuntime_Python + if spec.Type == v1alpha2.AgentType_Declarative && spec.Declarative != nil && spec.Declarative.Runtime != "" { + runtime = spec.Declarative.Runtime + } + return runtime +} + +func (a *adkApiTranslator) buildWorkloadObjects( + ctx context.Context, + manifestCtx manifestContext, + podTemplate corev1.PodTemplateSpec, +) ([]client.Object, error) { + if manifestCtx.runInSandbox() { + sbObjs, err := a.sandboxBackend.BuildSandbox(ctx, sandboxbackend.BuildInput{ + Agent: manifestCtx.agent, + PodTemplate: podTemplate, + }) + if err != nil { + return nil, fmt.Errorf("build sandbox workload: %w", err) + } + return sbObjs, nil + } + + return []client.Object{ + &appsv1.Deployment{ + TypeMeta: metav1.TypeMeta{APIVersion: "apps/v1", Kind: "Deployment"}, + ObjectMeta: manifestCtx.objectMeta(), + Spec: appsv1.DeploymentSpec{ + Replicas: manifestCtx.deployment.Replicas, + Strategy: appsv1.DeploymentStrategy{ + Type: appsv1.RollingUpdateDeploymentStrategyType, + RollingUpdate: &appsv1.RollingUpdateDeployment{ + MaxUnavailable: &intstr.IntOrString{Type: intstr.Int, IntVal: 0}, + MaxSurge: &intstr.IntOrString{Type: intstr.Int, IntVal: 1}, + }, + }, + Selector: &metav1.LabelSelector{MatchLabels: manifestCtx.selectorLabels}, + Template: podTemplate, + }, + }, + &corev1.Service{ + TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: "Service"}, + ObjectMeta: manifestCtx.objectMeta(), + Spec: corev1.ServiceSpec{ + Selector: manifestCtx.selectorLabels, + Ports: []corev1.ServicePort{{ + Name: "http", + Port: manifestCtx.deployment.Port, + TargetPort: intstr.FromInt(int(manifestCtx.deployment.Port)), + }}, + Type: corev1.ServiceTypeClusterIP, + }, + }, + }, nil +} + +func (a *adkApiTranslator) setManifestOwnerReferences( + agent v1alpha2.AgentObject, + manifest []client.Object, +) error { + for _, obj := range manifest { + if err := controllerutil.SetControllerReference(agent, obj, a.kube.Scheme()); err != nil { + return err + } + } + return nil +} diff --git a/go/core/internal/controller/translator/agent/mcp_validation_test.go b/go/core/internal/controller/translator/agent/mcp_validation_test.go index 275285bf6..57fc03f1b 100644 --- a/go/core/internal/controller/translator/agent/mcp_validation_test.go +++ b/go/core/internal/controller/translator/agent/mcp_validation_test.go @@ -98,7 +98,7 @@ func TestMCPServerValidation_InvalidPort(t *testing.T) { ) // TranslateAgent should fail with error about invalid port - _, err = translator.TranslateAgent(ctx, agent, false) + _, err = translator.TranslateAgent(ctx, agent) require.Error(t, err) assert.Contains(t, err.Error(), "cannot determine port") assert.Contains(t, err.Error(), "test-mcp-server") @@ -184,7 +184,7 @@ func TestMCPServerValidation_ValidPort(t *testing.T) { ) // TranslateAgent should succeed - outputs, err := translator.TranslateAgent(ctx, agent, false) + outputs, err := translator.TranslateAgent(ctx, agent) require.NoError(t, err) assert.NotNil(t, outputs) assert.NotNil(t, outputs.Config) @@ -255,7 +255,7 @@ func TestMCPServerValidation_NotFound(t *testing.T) { ) // TranslateAgent should fail with not found error - _, err = translator.TranslateAgent(ctx, agent, false) + _, err = translator.TranslateAgent(ctx, agent) require.Error(t, err) assert.True(t, apierrors.IsNotFound(err)) } @@ -317,7 +317,7 @@ func TestMCPServerValidation_NoMCPServerReference(t *testing.T) { ) // TranslateAgent should fail with provider or tool server error - _, err = translator.TranslateAgent(ctx, agent, false) + _, err = translator.TranslateAgent(ctx, agent) require.Error(t, err) assert.Contains(t, err.Error(), "tool must have a provider or tool server") } @@ -396,7 +396,7 @@ func TestMCPServerValidation_RemoteMCPServer(t *testing.T) { ) // TranslateAgent should succeed - RemoteMCPServer doesn't have port validation - outputs, err := translator.TranslateAgent(ctx, agent, false) + outputs, err := translator.TranslateAgent(ctx, agent) require.NoError(t, err) assert.NotNil(t, outputs) assert.NotNil(t, outputs.Config) @@ -555,7 +555,7 @@ func TestMCPServerValidation_MultipleTools(t *testing.T) { ) // TranslateAgent should fail because one of the MCPServers is invalid - _, err = translator.TranslateAgent(ctx, agent, false) + _, err = translator.TranslateAgent(ctx, agent) require.Error(t, err) assert.Contains(t, err.Error(), "cannot determine port") assert.Contains(t, err.Error(), "invalid-mcp-server") diff --git a/go/core/internal/controller/translator/agent/proxy_test.go b/go/core/internal/controller/translator/agent/proxy_test.go index 73889074f..72ed440df 100644 --- a/go/core/internal/controller/translator/agent/proxy_test.go +++ b/go/core/internal/controller/translator/agent/proxy_test.go @@ -119,7 +119,7 @@ func TestProxyConfiguration_ThroughTranslateAgent(t *testing.T) { nil, ) - result, err := translator.TranslateAgent(ctx, agent, false) + result, err := translator.TranslateAgent(ctx, agent) require.NoError(t, err) require.NotNil(t, result) require.NotNil(t, result.Config) @@ -149,7 +149,7 @@ func TestProxyConfiguration_ThroughTranslateAgent(t *testing.T) { nil, ) - result, err := translator.TranslateAgent(ctx, agent, false) + result, err := translator.TranslateAgent(ctx, agent) require.NoError(t, err) require.NotNil(t, result) require.NotNil(t, result.Config) @@ -251,7 +251,7 @@ func TestProxyConfiguration_RemoteMCPServer_ExternalURL(t *testing.T) { nil, ) - result, err := translator.TranslateAgent(ctx, agent, false) + result, err := translator.TranslateAgent(ctx, agent) require.NoError(t, err) require.NotNil(t, result) require.NotNil(t, result.Config) @@ -344,7 +344,7 @@ func TestProxyConfiguration_MCPServer(t *testing.T) { nil, ) - result, err := translator.TranslateAgent(ctx, agent, false) + result, err := translator.TranslateAgent(ctx, agent) require.NoError(t, err) require.NotNil(t, result) require.NotNil(t, result.Config) @@ -442,7 +442,7 @@ func TestProxyConfiguration_Service(t *testing.T) { nil, ) - result, err := translator.TranslateAgent(ctx, agent, false) + result, err := translator.TranslateAgent(ctx, agent) require.NoError(t, err) require.NotNil(t, result) require.NotNil(t, result.Config) diff --git a/go/core/internal/controller/translator/agent/runtime_test.go b/go/core/internal/controller/translator/agent/runtime_test.go index b0870cd6d..dca524320 100644 --- a/go/core/internal/controller/translator/agent/runtime_test.go +++ b/go/core/internal/controller/translator/agent/runtime_test.go @@ -65,7 +65,7 @@ func TestRuntime_GoRuntime(t *testing.T) { translatorInstance := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil) // Translate agent - result, err := translatorInstance.TranslateAgent(ctx, agent, false) + result, err := translatorInstance.TranslateAgent(ctx, agent) require.NoError(t, err) require.NotNil(t, result) @@ -140,7 +140,7 @@ func TestRuntime_PythonRuntime(t *testing.T) { translatorInstance := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil) // Translate agent - result, err := translatorInstance.TranslateAgent(ctx, agent, false) + result, err := translatorInstance.TranslateAgent(ctx, agent) require.NoError(t, err) require.NotNil(t, result) @@ -215,7 +215,7 @@ func TestRuntime_DefaultToPython(t *testing.T) { translatorInstance := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil) // Translate agent - result, err := translatorInstance.TranslateAgent(ctx, agent, false) + result, err := translatorInstance.TranslateAgent(ctx, agent) require.NoError(t, err) require.NotNil(t, result) @@ -299,7 +299,7 @@ func TestRuntime_CustomRepositoryPath(t *testing.T) { translatorInstance := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil) // Translate agent - result, err := translatorInstance.TranslateAgent(ctx, agent, false) + result, err := translatorInstance.TranslateAgent(ctx, agent) require.NoError(t, err) require.NotNil(t, result) diff --git a/go/core/internal/controller/translator/agent/sandbox_agent_view.go b/go/core/internal/controller/translator/agent/sandbox_agent_view.go deleted file mode 100644 index cebfc10fd..000000000 --- a/go/core/internal/controller/translator/agent/sandbox_agent_view.go +++ /dev/null @@ -1,26 +0,0 @@ -package agent - -import ( - "github.com/kagent-dev/kagent/go/api/v1alpha2" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// AgentViewFromSandboxAgent returns an in-memory Agent with the same spec, metadata, and status -// as the SandboxAgent. Use with TranslateAgent(ctx, view, true) so the translator emits sandbox -// workload objects; the returned value is not persisted as an Agent resource. -func AgentViewFromSandboxAgent(sa *v1alpha2.SandboxAgent) *v1alpha2.Agent { - if sa == nil { - return nil - } - spec := sa.Spec - a := &v1alpha2.Agent{ - TypeMeta: metav1.TypeMeta{ - APIVersion: v1alpha2.GroupVersion.String(), - Kind: "Agent", - }, - ObjectMeta: sa.ObjectMeta, - Spec: spec, - } - sa.Status.DeepCopyInto(&a.Status) - return a -} diff --git a/go/core/internal/controller/translator/agent/security_context_test.go b/go/core/internal/controller/translator/agent/security_context_test.go index ae2e8ddd8..fa0e02d25 100644 --- a/go/core/internal/controller/translator/agent/security_context_test.go +++ b/go/core/internal/controller/translator/agent/security_context_test.go @@ -86,7 +86,7 @@ func TestSecurityContext_AppliedToPodSpec(t *testing.T) { translatorInstance := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil) // Translate agent - result, err := translatorInstance.TranslateAgent(ctx, agent, false) + result, err := translatorInstance.TranslateAgent(ctx, agent) require.NoError(t, err) require.NotNil(t, result) @@ -176,7 +176,7 @@ func TestSecurityContext_OnlyPodSecurityContext(t *testing.T) { } translatorInstance := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil) - result, err := translatorInstance.TranslateAgent(ctx, agent, false) + result, err := translatorInstance.TranslateAgent(ctx, agent) require.NoError(t, err) var deployment *appsv1.Deployment @@ -251,7 +251,7 @@ func TestSecurityContext_OnlyContainerSecurityContext(t *testing.T) { } translatorInstance := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil) - result, err := translatorInstance.TranslateAgent(ctx, agent, false) + result, err := translatorInstance.TranslateAgent(ctx, agent) require.NoError(t, err) var deployment *appsv1.Deployment @@ -325,7 +325,7 @@ func TestSecurityContext_SkillsDefaultPrivilegedSandbox(t *testing.T) { } translatorInstance := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil) - result, err := translatorInstance.TranslateAgent(ctx, agent, false) + result, err := translatorInstance.TranslateAgent(ctx, agent) require.NoError(t, err) var deployment *appsv1.Deployment @@ -409,7 +409,7 @@ func TestSecurityContext_SkillsPSSRestricted(t *testing.T) { } translatorInstance := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil) - result, err := translatorInstance.TranslateAgent(ctx, agent, false) + result, err := translatorInstance.TranslateAgent(ctx, agent) require.NoError(t, err) var deployment *appsv1.Deployment diff --git a/go/core/internal/controller/translator/agent/template.go b/go/core/internal/controller/translator/agent/template.go index cd1cdcae8..584aef960 100644 --- a/go/core/internal/controller/translator/agent/template.go +++ b/go/core/internal/controller/translator/agent/template.go @@ -61,11 +61,12 @@ func resolvePromptSources(ctx context.Context, kube client.Client, namespace str // buildTemplateContext constructs the template context from an Agent resource and its // already-translated AgentConfig. Tool names are extracted from the config rather than // recomputed from the spec. -func buildTemplateContext(agent *v1alpha2.Agent, cfg *adk.AgentConfig) PromptTemplateContext { +func buildTemplateContext(agent v1alpha2.AgentObject, cfg *adk.AgentConfig) PromptTemplateContext { + spec := agent.GetAgentSpec() tplCtx := PromptTemplateContext{ - AgentName: agent.Name, - AgentNamespace: agent.Namespace, - Description: agent.Spec.Description, + AgentName: agent.GetName(), + AgentNamespace: agent.GetNamespace(), + Description: spec.Description, } // Collect tool names from the already-translated agent config. @@ -77,13 +78,13 @@ func buildTemplateContext(agent *v1alpha2.Agent, cfg *adk.AgentConfig) PromptTem } // Collect skill names using the shared OCI/Git name helpers. - if agent.Spec.Skills != nil { - for _, ref := range agent.Spec.Skills.Refs { + if spec.Skills != nil { + for _, ref := range spec.Skills.Refs { if name := ociSkillName(ref); name != "" { tplCtx.SkillNames = append(tplCtx.SkillNames, name) } } - for _, gitRef := range agent.Spec.Skills.GitRefs { + for _, gitRef := range spec.Skills.GitRefs { if name := gitSkillName(gitRef); name != "" { tplCtx.SkillNames = append(tplCtx.SkillNames, name) } diff --git a/go/core/internal/controller/translator/agent/utils.go b/go/core/internal/controller/translator/agent/utils.go index 85ca1e172..a6ffa31cf 100644 --- a/go/core/internal/controller/translator/agent/utils.go +++ b/go/core/internal/controller/translator/agent/utils.go @@ -10,11 +10,12 @@ import ( "trpc.group/trpc-go/trpc-a2a-go/server" ) -func GetA2AAgentCard(agent *v1alpha2.Agent) *server.AgentCard { +func GetA2AAgentCard(agent v1alpha2.AgentObject) *server.AgentCard { + spec := agent.GetAgentSpec() card := server.AgentCard{ - Name: strings.ReplaceAll(agent.Name, "-", "_"), - Description: agent.Spec.Description, - URL: fmt.Sprintf("http://%s.%s:8080", agent.Name, agent.Namespace), + Name: strings.ReplaceAll(agent.GetName(), "-", "_"), + Description: spec.Description, + URL: fmt.Sprintf("http://%s.%s:8080", agent.GetName(), agent.GetNamespace()), Capabilities: server.AgentCapabilities{ Streaming: new(true), PushNotifications: new(false), @@ -25,8 +26,8 @@ func GetA2AAgentCard(agent *v1alpha2.Agent) *server.AgentCard { DefaultInputModes: []string{"text"}, DefaultOutputModes: []string{"text"}, } - if agent.Spec.Type == v1alpha2.AgentType_Declarative && agent.Spec.Declarative != nil && agent.Spec.Declarative.A2AConfig != nil { - decl := agent.Spec.Declarative + if spec.Type == v1alpha2.AgentType_Declarative && spec.Declarative != nil && spec.Declarative.A2AConfig != nil { + decl := spec.Declarative card.Skills = slices.Collect(utils.Map(slices.Values(decl.A2AConfig.Skills), func(skill v1alpha2.AgentSkill) server.AgentSkill { return server.AgentSkill(skill) })) diff --git a/go/core/internal/controller/watch_helpers.go b/go/core/internal/controller/watch_helpers.go new file mode 100644 index 000000000..3f2e32922 --- /dev/null +++ b/go/core/internal/controller/watch_helpers.go @@ -0,0 +1,102 @@ +package controller + +import ( + "context" + "fmt" + + "github.com/kagent-dev/kagent/go/api/v1alpha2" + "github.com/kagent-dev/kmcp/api/v1alpha1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/apiutil" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +type dependentRefFinder func(context.Context, client.Client, types.NamespacedName) []types.NamespacedName + +type agentWatchFinders struct { + modelConfig dependentRefFinder + remoteMCPServer dependentRefFinder + mcpService dependentRefFinder + configMap dependentRefFinder + mcpServer dependentRefFinder +} + +func addOwnedResourceWatches(build *builder.Builder, mgr ctrl.Manager, owned []client.Object) (*builder.Builder, error) { + for _, ownedType := range owned { + gvk, err := apiutil.GVKForObject(ownedType, mgr.GetScheme()) + if err != nil { + return nil, fmt.Errorf("resolve GVK for owned resource %T: %w", ownedType, err) + } + if _, err := mgr.GetRESTMapper().RESTMapping(gvk.GroupKind(), gvk.Version); err != nil { + if meta.IsNoMatchError(err) { + continue + } + return nil, fmt.Errorf("resolve REST mapping for owned resource %s: %w", gvk.String(), err) + } + build = build.Owns(ownedType, builder.WithPredicates(ownedObjectPredicate{}, predicate.ResourceVersionChangedPredicate{})) + } + return build, nil +} + +func addCommonAgentWatches(build *builder.Builder, mgr ctrl.Manager, finders agentWatchFinders) (*builder.Builder, error) { + build = build.Watches( + &v1alpha2.ModelConfig{}, + handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []reconcile.Request { + return reconcileRequestsForRefs(finders.modelConfig(ctx, mgr.GetClient(), types.NamespacedName{ + Name: obj.GetName(), + Namespace: obj.GetNamespace(), + })) + }), + builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}), + ).Watches( + &v1alpha2.RemoteMCPServer{}, + handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []reconcile.Request { + return reconcileRequestsForRefs(finders.remoteMCPServer(ctx, mgr.GetClient(), types.NamespacedName{ + Name: obj.GetName(), + Namespace: obj.GetNamespace(), + })) + }), + ).Watches( + &corev1.Service{}, + handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []reconcile.Request { + return reconcileRequestsForRefs(finders.mcpService(ctx, mgr.GetClient(), types.NamespacedName{ + Name: obj.GetName(), + Namespace: obj.GetNamespace(), + })) + }), + builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}), + ).Watches( + &corev1.ConfigMap{}, + handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []reconcile.Request { + return reconcileRequestsForRefs(finders.configMap(ctx, mgr.GetClient(), types.NamespacedName{ + Name: obj.GetName(), + Namespace: obj.GetNamespace(), + })) + }), + builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}), + ) + + if _, err := mgr.GetRESTMapper().RESTMapping(mcpServerGK); err == nil { + build = build.Watches( + &v1alpha1.MCPServer{}, + handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []reconcile.Request { + return reconcileRequestsForRefs(finders.mcpServer(ctx, mgr.GetClient(), types.NamespacedName{ + Name: obj.GetName(), + Namespace: obj.GetNamespace(), + })) + }), + builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}), + ) + } else if !meta.IsNoMatchError(err) { + return nil, fmt.Errorf("resolve REST mapping for %s: %w", mcpServerGK.String(), err) + } + + return build, nil +} diff --git a/go/core/internal/database/client_postgres.go b/go/core/internal/database/client_postgres.go index 68952ddbc..50915bd4d 100644 --- a/go/core/internal/database/client_postgres.go +++ b/go/core/internal/database/client_postgres.go @@ -45,9 +45,10 @@ func (c *postgresClient) withTx(ctx context.Context, fn func(*dbgen.Queries) err func (c *postgresClient) StoreAgent(ctx context.Context, agent *dbpkg.Agent) error { return c.q.UpsertAgent(ctx, dbgen.UpsertAgentParams{ - ID: agent.ID, - Type: agent.Type, - Config: agent.Config, + ID: agent.ID, + Type: agent.Type, + WorkloadType: string(agent.WorkloadType), + Config: agent.Config, }) } @@ -126,6 +127,18 @@ func (c *postgresClient) ListSessionsForAgent(ctx context.Context, agentID, user return sessions, nil } +func (c *postgresClient) ListSessionsForAgentAllUsers(ctx context.Context, agentID string) ([]dbpkg.Session, error) { + rows, err := c.q.ListSessionsForAgentAllUsers(ctx, &agentID) + if err != nil { + return nil, fmt.Errorf("failed to list sessions for agent across all users: %w", err) + } + sessions := make([]dbpkg.Session, len(rows)) + for i, r := range rows { + sessions[i] = *toSession(r) + } + return sessions, nil +} + func (c *postgresClient) DeleteSession(ctx context.Context, sessionID, userID string) error { return c.q.SoftDeleteSession(ctx, dbgen.SoftDeleteSessionParams{ID: sessionID, UserID: userID}) } @@ -683,12 +696,13 @@ func (c *postgresClient) PruneExpiredMemories(ctx context.Context) error { func toAgent(r dbgen.Agent) *dbpkg.Agent { return &dbpkg.Agent{ - ID: r.ID, - CreatedAt: derefTime(r.CreatedAt), - UpdatedAt: derefTime(r.UpdatedAt), - DeletedAt: r.DeletedAt, - Type: r.Type, - Config: r.Config, + ID: r.ID, + CreatedAt: derefTime(r.CreatedAt), + UpdatedAt: derefTime(r.UpdatedAt), + DeletedAt: r.DeletedAt, + Type: r.Type, + WorkloadType: v1alpha2.WorkloadMode(r.WorkloadType), + Config: r.Config, } } diff --git a/go/core/internal/database/fake/client.go b/go/core/internal/database/fake/client.go index 86005a61d..801514f3d 100644 --- a/go/core/internal/database/fake/client.go +++ b/go/core/internal/database/fake/client.go @@ -358,6 +358,25 @@ func (c *InMemoryFakeClient) ListSessionsForAgent(_ context.Context, agentID str return result, nil } +func (c *InMemoryFakeClient) ListSessionsForAgentAllUsers(_ context.Context, agentID string) ([]database.Session, error) { + c.mu.RLock() + defer c.mu.RUnlock() + + var result []database.Session + for _, session := range c.sessions { + if session.AgentID != nil && *session.AgentID == agentID { + if session.Source != nil && *session.Source == database.SessionSourceAgent { + continue + } + result = append(result, *session) + } + } + slices.SortStableFunc(result, func(i, j database.Session) int { + return strings.Compare(i.ID, j.ID) + }) + return result, nil +} + // ListAgents lists all agents func (c *InMemoryFakeClient) ListAgents(_ context.Context) ([]database.Agent, error) { c.mu.RLock() diff --git a/go/core/internal/database/gen/agents.sql.go b/go/core/internal/database/gen/agents.sql.go index e987bbc63..ad0d972a2 100644 --- a/go/core/internal/database/gen/agents.sql.go +++ b/go/core/internal/database/gen/agents.sql.go @@ -12,7 +12,7 @@ import ( ) const getAgent = `-- name: GetAgent :one -SELECT id, created_at, updated_at, deleted_at, type, config FROM agent +SELECT id, created_at, updated_at, deleted_at, type, config, workload_type FROM agent WHERE id = $1 AND deleted_at IS NULL LIMIT 1 ` @@ -27,12 +27,13 @@ func (q *Queries) GetAgent(ctx context.Context, id string) (Agent, error) { &i.DeletedAt, &i.Type, &i.Config, + &i.WorkloadType, ) return i, err } const listAgents = `-- name: ListAgents :many -SELECT id, created_at, updated_at, deleted_at, type, config FROM agent +SELECT id, created_at, updated_at, deleted_at, type, config, workload_type FROM agent WHERE deleted_at IS NULL ORDER BY created_at ASC ` @@ -53,6 +54,7 @@ func (q *Queries) ListAgents(ctx context.Context) ([]Agent, error) { &i.DeletedAt, &i.Type, &i.Config, + &i.WorkloadType, ); err != nil { return nil, err } @@ -74,22 +76,29 @@ func (q *Queries) SoftDeleteAgent(ctx context.Context, id string) error { } const upsertAgent = `-- name: UpsertAgent :exec -INSERT INTO agent (id, type, config, created_at, updated_at) -VALUES ($1, $2, $3, NOW(), NOW()) +INSERT INTO agent (id, type, workload_type, config, created_at, updated_at) +VALUES ($1, $2, $3, $4, NOW(), NOW()) ON CONFLICT (id) DO UPDATE SET type = EXCLUDED.type, + workload_type = EXCLUDED.workload_type, config = EXCLUDED.config, updated_at = NOW(), deleted_at = NULL ` type UpsertAgentParams struct { - ID string - Type string - Config *adk.AgentConfig + ID string + Type string + WorkloadType string + Config *adk.AgentConfig } func (q *Queries) UpsertAgent(ctx context.Context, arg UpsertAgentParams) error { - _, err := q.db.Exec(ctx, upsertAgent, arg.ID, arg.Type, arg.Config) + _, err := q.db.Exec(ctx, upsertAgent, + arg.ID, + arg.Type, + arg.WorkloadType, + arg.Config, + ) return err } diff --git a/go/core/internal/database/gen/models.go b/go/core/internal/database/gen/models.go index 92a5585d2..e05efa2ae 100644 --- a/go/core/internal/database/gen/models.go +++ b/go/core/internal/database/gen/models.go @@ -13,12 +13,13 @@ import ( ) type Agent struct { - ID string - CreatedAt *time.Time - UpdatedAt *time.Time - DeletedAt *time.Time - Type string - Config *adk.AgentConfig + ID string + CreatedAt *time.Time + UpdatedAt *time.Time + DeletedAt *time.Time + Type string + Config *adk.AgentConfig + WorkloadType string } type CrewaiAgentMemory struct { diff --git a/go/core/internal/database/gen/querier.go b/go/core/internal/database/gen/querier.go index 5675c1f0c..e58850e00 100644 --- a/go/core/internal/database/gen/querier.go +++ b/go/core/internal/database/gen/querier.go @@ -41,6 +41,7 @@ type Querier interface { ListPushNotifications(ctx context.Context, taskID string) ([]PushNotification, error) ListSessions(ctx context.Context, userID string) ([]Session, error) ListSessionsForAgent(ctx context.Context, arg ListSessionsForAgentParams) ([]Session, error) + ListSessionsForAgentAllUsers(ctx context.Context, agentID *string) ([]Session, error) ListTasksForSession(ctx context.Context, sessionID *string) ([]Task, error) ListToolServers(ctx context.Context) ([]Toolserver, error) ListTools(ctx context.Context) ([]Tool, error) diff --git a/go/core/internal/database/gen/sessions.sql.go b/go/core/internal/database/gen/sessions.sql.go index 8c432bfba..4b44ddeb1 100644 --- a/go/core/internal/database/gen/sessions.sql.go +++ b/go/core/internal/database/gen/sessions.sql.go @@ -112,6 +112,42 @@ func (q *Queries) ListSessionsForAgent(ctx context.Context, arg ListSessionsForA return items, nil } +const listSessionsForAgentAllUsers = `-- name: ListSessionsForAgentAllUsers :many +SELECT id, user_id, name, created_at, updated_at, deleted_at, agent_id, source FROM session +WHERE agent_id = $1 AND deleted_at IS NULL + AND (source IS NULL OR source != 'agent') +ORDER BY created_at ASC +` + +func (q *Queries) ListSessionsForAgentAllUsers(ctx context.Context, agentID *string) ([]Session, error) { + rows, err := q.db.Query(ctx, listSessionsForAgentAllUsers, agentID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Session + for rows.Next() { + var i Session + if err := rows.Scan( + &i.ID, + &i.UserID, + &i.Name, + &i.CreatedAt, + &i.UpdatedAt, + &i.DeletedAt, + &i.AgentID, + &i.Source, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const softDeleteSession = `-- name: SoftDeleteSession :exec UPDATE session SET deleted_at = NOW() WHERE id = $1 AND user_id = $2 AND deleted_at IS NULL diff --git a/go/core/internal/database/queries/agents.sql b/go/core/internal/database/queries/agents.sql index 135fbb1ec..1424ac5a1 100644 --- a/go/core/internal/database/queries/agents.sql +++ b/go/core/internal/database/queries/agents.sql @@ -9,10 +9,11 @@ WHERE deleted_at IS NULL ORDER BY created_at ASC; -- name: UpsertAgent :exec -INSERT INTO agent (id, type, config, created_at, updated_at) -VALUES ($1, $2, $3, NOW(), NOW()) +INSERT INTO agent (id, type, workload_type, config, created_at, updated_at) +VALUES ($1, $2, $3, $4, NOW(), NOW()) ON CONFLICT (id) DO UPDATE SET type = EXCLUDED.type, + workload_type = EXCLUDED.workload_type, config = EXCLUDED.config, updated_at = NOW(), deleted_at = NULL; diff --git a/go/core/internal/database/queries/sessions.sql b/go/core/internal/database/queries/sessions.sql index 9dcf9bbbb..a9d8c9eb2 100644 --- a/go/core/internal/database/queries/sessions.sql +++ b/go/core/internal/database/queries/sessions.sql @@ -14,6 +14,12 @@ WHERE agent_id = $1 AND user_id = $2 AND deleted_at IS NULL AND (source IS NULL OR source != 'agent') ORDER BY created_at ASC; +-- name: ListSessionsForAgentAllUsers :many +SELECT * FROM session +WHERE agent_id = $1 AND deleted_at IS NULL + AND (source IS NULL OR source != 'agent') +ORDER BY created_at ASC; + -- name: UpsertSession :exec INSERT INTO session (id, user_id, name, agent_id, source, created_at, updated_at) VALUES ($1, $2, $3, $4, $5, NOW(), NOW()) diff --git a/go/core/internal/httpserver/handlers/agents.go b/go/core/internal/httpserver/handlers/agents.go index 6209bed5b..8711629ae 100644 --- a/go/core/internal/httpserver/handlers/agents.go +++ b/go/core/internal/httpserver/handlers/agents.go @@ -44,6 +44,23 @@ func (h *AgentsHandler) HandleListAgents(w ErrorResponseWriter, r *http.Request) return } + agentsWithID := make([]api.AgentResponse, 0) + h.appendAgentResponses(r.Context(), log, agentObjects(agentList.Items), &agentsWithID) + + log.Info("Successfully listed agents", "count", len(agentsWithID)) + data := api.NewResponse(agentsWithID, "Successfully listed agents", false) + RespondWithJSON(w, http.StatusOK, data) +} + +// HandleListSandboxAgents handles GET /api/sandboxagents requests using database. +func (h *AgentsHandler) HandleListSandboxAgents(w ErrorResponseWriter, r *http.Request) { + log := ctrllog.FromContext(r.Context()).WithName("agents-handler").WithValues("operation", "list-sandboxagents") + + if err := Check(h.Authorizer, r, auth.Resource{Type: "Agent"}); err != nil { + w.RespondWithError(err) + return + } + sandboxAgentList := &v1alpha2.SandboxAgentList{} if err := h.KubeClient.List(r.Context(), sandboxAgentList); err != nil { w.RespondWithError(errors.NewInternalServerError("Failed to list SandboxAgents from Kubernetes", err)) @@ -51,35 +68,52 @@ func (h *AgentsHandler) HandleListAgents(w ErrorResponseWriter, r *http.Request) } agentsWithID := make([]api.AgentResponse, 0) - for _, agent := range agentList.Items { - agentRef := utils.GetObjectRef(&agent) - log.V(1).Info("Processing Agent", "agentRef", agentRef) + h.appendAgentResponses(r.Context(), log, sandboxAgentObjects(sandboxAgentList.Items), &agentsWithID) - // When listing agents, we don't want a failure when a single agent has an issue, so we ignore the error. - // The getAgentResponse should return its reconciliation status in the agentResponse. - agentResponse, _ := h.getAgentResponse(r.Context(), log, &agent) + log.Info("Successfully listed sandbox agents", "count", len(agentsWithID)) + data := api.NewResponse(agentsWithID, "Successfully listed sandbox agents", false) + RespondWithJSON(w, http.StatusOK, data) +} - agentsWithID = append(agentsWithID, agentResponse) +func (h *AgentsHandler) appendAgentResponses( + ctx context.Context, + log logr.Logger, + items []v1alpha2.AgentObject, + responses *[]api.AgentResponse, +) { + for _, agent := range items { + agentRef := utils.GetObjectRef(agent) + log.V(1).Info("Processing agent", "agentRef", agentRef) + + agentResponse, _ := h.getAgentResponse(ctx, log, agent) + *responses = append(*responses, agentResponse) } +} - for _, sa := range sandboxAgentList.Items { - agentRef := utils.GetObjectRef(&sa) - log.V(1).Info("Processing SandboxAgent", "agentRef", agentRef) - agentResponse, _ := h.getSandboxAgentResponse(r.Context(), log, &sa) - agentsWithID = append(agentsWithID, agentResponse) +func agentObjects(items []v1alpha2.Agent) []v1alpha2.AgentObject { + out := make([]v1alpha2.AgentObject, 0, len(items)) + for i := range items { + out = append(out, &items[i]) } + return out +} - log.Info("Successfully listed agents", "count", len(agentsWithID)) - data := api.NewResponse(agentsWithID, "Successfully listed agents", false) - RespondWithJSON(w, http.StatusOK, data) +func sandboxAgentObjects(items []v1alpha2.SandboxAgent) []v1alpha2.AgentObject { + out := make([]v1alpha2.AgentObject, 0, len(items)) + for i := range items { + out = append(out, &items[i]) + } + return out } -func (h *AgentsHandler) getAgentResponse(ctx context.Context, log logr.Logger, agent *v1alpha2.Agent) (api.AgentResponse, error) { +func (h *AgentsHandler) getAgentResponse(ctx context.Context, log logr.Logger, agent v1alpha2.AgentObject) (api.AgentResponse, error) { agentRef := utils.GetObjectRef(agent) log.V(1).Info("Processing Agent", "agentRef", agentRef) + spec := agent.GetAgentSpec() + status := agent.GetAgentStatus() deploymentReady := false - for _, condition := range agent.Status.Conditions { + for _, condition := range status.Conditions { if condition.Type == "Ready" && condition.Status == "True" { if condition.Reason == reconciler.AgentReadyReasonDeploymentReady || condition.Reason == reconciler.AgentReadyReasonWorkloadReady { deploymentReady = true @@ -89,7 +123,7 @@ func (h *AgentsHandler) getAgentResponse(ctx context.Context, log logr.Logger, a } accepted := false - for _, condition := range agent.Status.Conditions { + for _, condition := range status.Conditions { // The exact reason is not important (although "AgentReconciled" is the current one), as long as the agent is accepted if condition.Type == "Accepted" && condition.Status == "True" { accepted = true @@ -99,17 +133,18 @@ func (h *AgentsHandler) getAgentResponse(ctx context.Context, log logr.Logger, a response := api.AgentResponse{ ID: utils.ConvertToPythonIdentifier(agentRef), - Agent: agent, + Agent: api.AgentResourceFrom(agent), DeploymentReady: deploymentReady, Accepted: accepted, + WorkloadMode: agent.GetWorkloadMode(), } - if agent.Spec.Type == v1alpha2.AgentType_Declarative { + if spec.Type == v1alpha2.AgentType_Declarative && spec.Declarative != nil { // Get the ModelConfig for the team modelConfig := &v1alpha2.ModelConfig{} objKey := client.ObjectKey{ - Namespace: agent.Namespace, - Name: agent.Spec.Declarative.ModelConfig, + Namespace: agent.GetNamespace(), + Name: spec.Declarative.ModelConfig, } if err := h.KubeClient.Get( ctx, @@ -126,457 +161,437 @@ func (h *AgentsHandler) getAgentResponse(ctx context.Context, log logr.Logger, a response.ModelProvider = modelConfig.Spec.Provider response.Model = modelConfig.Spec.Model response.ModelConfigRef = utils.GetObjectRef(modelConfig) - response.Tools = agent.Spec.Declarative.Tools + response.Tools = spec.Declarative.Tools } return response, nil } -func (h *AgentsHandler) getSandboxAgentResponse(ctx context.Context, log logr.Logger, sa *v1alpha2.SandboxAgent) (api.AgentResponse, error) { - agentView := agent_translator.AgentViewFromSandboxAgent(sa) - resp, err := h.getAgentResponse(ctx, log, agentView) - if err != nil { - return resp, err - } - resp.RunInSandbox = true - return resp, nil +func (h *AgentsHandler) buildTranslator(kubeClient client.Client) agent_translator.AdkApiTranslator { + return agent_translator.NewAdkApiTranslator( + kubeClient, + h.DefaultModelConfig, + nil, + h.ProxyURL, + h.SandboxBackend, + ) } -// HandleGetAgent handles GET /api/agents/{namespace}/{name} requests using database -func (h *AgentsHandler) HandleGetAgent(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()).WithName("agents-handler").WithValues("operation", "get-db") - - agentName, err := GetPathParam(r, "name") - if err != nil { - w.RespondWithError(errors.NewBadRequestError("Failed to get name from path", err)) - return +func (h *AgentsHandler) validateAgentObject(ctx context.Context, agent v1alpha2.AgentObject) error { + if agent.GetWorkloadMode() == v1alpha2.WorkloadModeSandbox && h.SandboxBackend != nil { + if err := sandboxbackend.EnsureAgentSandboxAPIsRegistered(ctx, h.KubeClient); err != nil { + return errors.NewBadRequestError(err.Error(), err) + } } - log = log.WithValues("agentName", agentName) - agentNamespace, err := GetPathParam(r, "namespace") - if err != nil { - w.RespondWithError(errors.NewBadRequestError("Failed to get namespace from path", err)) - return + kubeClientWrapper := utils.NewKubeClientWrapper(h.KubeClient) + if err := kubeClientWrapper.AddInMemory(agent); err != nil { + return errors.NewInternalServerError("Failed to add Agent to Kubernetes wrapper", err) } - log = log.WithValues("agentNamespace", agentNamespace) - if err := Check(h.Authorizer, r, auth.Resource{Type: "Agent", Name: types.NamespacedName{Namespace: agentNamespace, Name: agentName}.String()}); err != nil { - w.RespondWithError(err) - return - } - agent := &v1alpha2.Agent{} - err = h.KubeClient.Get( - r.Context(), - client.ObjectKey{ - Namespace: agentNamespace, - Name: agentName, - }, - agent, - ) + apiTranslator := h.buildTranslator(kubeClientWrapper) + inputs, err := apiTranslator.CompileAgent(ctx, agent) if err != nil { - if apierrors.IsNotFound(err) { - sa := &v1alpha2.SandboxAgent{} - if err2 := h.KubeClient.Get(r.Context(), client.ObjectKey{Namespace: agentNamespace, Name: agentName}, sa); err2 != nil { - w.RespondWithError(errors.NewNotFoundError("Agent not found", err2)) - return - } - agentResponse, err3 := h.getSandboxAgentResponse(r.Context(), log, sa) - if err3 != nil { - w.RespondWithError(err3) - return - } - log.Info("Successfully retrieved sandbox agent") - data := api.NewResponse(agentResponse, "Successfully retrieved agent", false) - RespondWithJSON(w, http.StatusOK, data) - return - } - w.RespondWithError(errors.NewNotFoundError("Agent not found", err)) - return + return errors.NewBadRequestError("Invalid agent configuration", err) } - - agentResponse, err := h.getAgentResponse(r.Context(), log, agent) - if err != nil { - w.RespondWithError(err) - return + if _, err := apiTranslator.BuildManifest(ctx, agent, inputs); err != nil { + return errors.NewBadRequestError("Invalid agent configuration", err) } - log.Info("Successfully retrieved agent") - data := api.NewResponse(agentResponse, "Successfully retrieved agent", false) - RespondWithJSON(w, http.StatusOK, data) + return nil } -// HandleCreateAgent handles POST /api/agents requests using database -func (h *AgentsHandler) HandleCreateAgent(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()).WithName("agents-handler").WithValues("operation", "create-db") - - var agentReq v1alpha2.Agent - if err := DecodeJSONBody(r, &agentReq); err != nil { - w.RespondWithError(errors.NewBadRequestError("Invalid request body", err)) - return - } - if agentReq.Namespace == "" { - agentReq.Namespace = utils.GetResourceNamespace() +func (h *AgentsHandler) parseAgentRef(log logr.Logger, agent client.Object, invalidMsg string) (logr.Logger, types.NamespacedName, error) { + if agent.GetNamespace() == "" { + agent.SetNamespace(utils.GetResourceNamespace()) log.V(4).Info("Namespace not provided in request. Creating in controller installation namespace", - "namespace", agentReq.Namespace) + "namespace", agent.GetNamespace()) } - agentRef, err := utils.ParseRefString(agentReq.Name, agentReq.Namespace) + agentRef, err := utils.ParseRefString(agent.GetName(), agent.GetNamespace()) if err != nil { - w.RespondWithError(errors.NewBadRequestError("Invalid agent metadata", err)) - return + return log, types.NamespacedName{}, errors.NewBadRequestError(invalidMsg, err) } - log = log.WithValues( + return log.WithValues( "agentNamespace", agentRef.Namespace, "agentName", agentRef.Name, - ) - - if err := Check(h.Authorizer, r, auth.Resource{Type: "Agent", Name: agentRef.String()}); err != nil { - w.RespondWithError(err) - return - } + ), agentRef, nil +} - kubeClientWrapper := utils.NewKubeClientWrapper(h.KubeClient) - if err := kubeClientWrapper.AddInMemory(&agentReq); err != nil { - w.RespondWithError(errors.NewInternalServerError("Failed to add Agent to Kubernetes wrapper", err)) - return +func (h *AgentsHandler) getAgentObject( + ctx context.Context, + key client.ObjectKey, + agent v1alpha2.AgentObject, + notFoundMsg string, +) (v1alpha2.AgentObject, error) { + if err := h.KubeClient.Get(ctx, key, agent); err != nil { + if apierrors.IsNotFound(err) { + return nil, errors.NewNotFoundError(notFoundMsg, err) + } + return nil, errors.NewInternalServerError("Failed to get Agent", err) } + return agent, nil +} - apiTranslator := agent_translator.NewAdkApiTranslator( - kubeClientWrapper, - h.DefaultModelConfig, - nil, - h.ProxyURL, - h.SandboxBackend, - ) - - log.V(1).Info("Translating Agent to ADK format") - _, err = apiTranslator.TranslateAgent(r.Context(), &agentReq, false) +func (h *AgentsHandler) handleGetAgentObject( + w ErrorResponseWriter, + r *http.Request, + log logr.Logger, + agent v1alpha2.AgentObject, + notFoundMsg string, + successMessage string, +) { + agentName, err := GetPathParam(r, "name") if err != nil { - w.RespondWithError(errors.NewInternalServerError("Failed to translate Agent to ADK format", err)) + w.RespondWithError(errors.NewBadRequestError("Failed to get name from path", err)) return } - - // Team is valid, we can store it - log.V(1).Info("Creating Agent in Kubernetes") - if err := h.KubeClient.Create(r.Context(), &agentReq); err != nil { - w.RespondWithError(errors.NewInternalServerError("Failed to create Agent in Kubernetes", err)) + agentNamespace, err := GetPathParam(r, "namespace") + if err != nil { + w.RespondWithError(errors.NewBadRequestError("Failed to get namespace from path", err)) return } + log = log.WithValues("agentName", agentName, "agentNamespace", agentNamespace) - log.Info("Successfully created agent", "agentRef", agentRef) - data := api.NewResponse(&agentReq, "Successfully created agent", false) - RespondWithJSON(w, http.StatusCreated, data) -} - -// HandleUpdateAgent handles PUT /api/agents/{namespace}/{name} requests using database -func (h *AgentsHandler) HandleUpdateAgent(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()).WithName("agents-handler").WithValues("operation", "update-db") - - var agentReq v1alpha2.Agent - if err := DecodeJSONBody(r, &agentReq); err != nil { - w.RespondWithError(errors.NewBadRequestError("Invalid request body", err)) + if err := Check(h.Authorizer, r, auth.Resource{Type: "Agent", Name: types.NamespacedName{Namespace: agentNamespace, Name: agentName}.String()}); err != nil { + w.RespondWithError(err) return } - if agentReq.Namespace == "" { - agentReq.Namespace = utils.GetResourceNamespace() - log.V(4).Info("Namespace not provided in request. Creating in controller installation namespace", - "namespace", agentReq.Namespace) - } - agentRef, err := utils.ParseRefString(agentReq.Name, agentReq.Namespace) + obj, err := h.getAgentObject(r.Context(), client.ObjectKey{Namespace: agentNamespace, Name: agentName}, agent, notFoundMsg) if err != nil { - w.RespondWithError(errors.NewBadRequestError("Invalid Agent metadata", err)) - } - - log = log.WithValues( - "agentNamespace", agentRef.Namespace, - "agentName", agentRef.Name, - ) - - if err := Check(h.Authorizer, r, auth.Resource{Type: "Agent", Name: agentRef.String()}); err != nil { w.RespondWithError(err) return } - log.V(1).Info("Getting existing Agent") - existingAgent := &v1alpha2.Agent{} - err = h.KubeClient.Get( - r.Context(), - agentRef, - existingAgent, - ) + agentResponse, err := h.getAgentResponse(r.Context(), log, obj) if err != nil { - if apierrors.IsNotFound(err) { - log.Info("Agent not found") - w.RespondWithError(errors.NewNotFoundError("Agent not found", nil)) - return - } - log.Error(err, "Failed to get Agent") - w.RespondWithError(errors.NewInternalServerError("Failed to get Agent", err)) - return - } - - // We set the .spec from the incoming request, so - // we don't have to copy/set any other fields - existingAgent.Spec = agentReq.Spec - - if err := h.KubeClient.Update(r.Context(), existingAgent); err != nil { - w.RespondWithError(errors.NewInternalServerError("Failed to update Agent", err)) + w.RespondWithError(err) return } - log.Info("Successfully updated agent") - data := api.NewResponse(existingAgent, "Successfully updated agent", false) - RespondWithJSON(w, http.StatusOK, data) + log.Info(successMessage) + RespondWithJSON(w, http.StatusOK, api.NewResponse(agentResponse, successMessage, false)) } -// HandleDeleteAgent handles DELETE /api/agents/{namespace}/{name} requests using database -func (h *AgentsHandler) HandleDeleteAgent(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()).WithName("agents-handler").WithValues("operation", "delete-db") - +func (h *AgentsHandler) handleDeleteAgentObject( + w ErrorResponseWriter, + r *http.Request, + log logr.Logger, + agent v1alpha2.AgentObject, + notFoundMsg string, + getFailedMsg string, + deleteFailedMsg string, + successMessage string, +) { agentName, err := GetPathParam(r, "name") if err != nil { w.RespondWithError(errors.NewBadRequestError("Failed to get name from path", err)) return } - log = log.WithValues("agentName", agentName) - agentNamespace, err := GetPathParam(r, "namespace") if err != nil { w.RespondWithError(errors.NewBadRequestError("Failed to get namespace from path", err)) return } + log = log.WithValues("agentName", agentName, "agentNamespace", agentNamespace) if err := Check(h.Authorizer, r, auth.Resource{Type: "Agent", Name: types.NamespacedName{Namespace: agentNamespace, Name: agentName}.String()}); err != nil { w.RespondWithError(err) return } - log = log.WithValues("agentNamespace", agentNamespace) - - log.V(1).Info("Getting Agent from Kubernetes") - agent := &v1alpha2.Agent{} - err = h.KubeClient.Get( - r.Context(), - client.ObjectKey{ - Namespace: agentNamespace, - Name: agentName, - }, - agent, - ) - if err != nil { + if err := h.KubeClient.Get(r.Context(), client.ObjectKey{Namespace: agentNamespace, Name: agentName}, agent); err != nil { if apierrors.IsNotFound(err) { - sa := &v1alpha2.SandboxAgent{} - if err2 := h.KubeClient.Get(r.Context(), client.ObjectKey{Namespace: agentNamespace, Name: agentName}, sa); err2 != nil { - if apierrors.IsNotFound(err2) { - log.Info("Agent not found") - w.RespondWithError(errors.NewNotFoundError("Agent not found", nil)) - return - } - log.Error(err2, "Failed to get SandboxAgent") - w.RespondWithError(errors.NewInternalServerError("Failed to get SandboxAgent", err2)) - return - } - log.V(1).Info("Deleting SandboxAgent from Kubernetes") - if err := h.KubeClient.Delete(r.Context(), sa); err != nil { - w.RespondWithError(errors.NewInternalServerError("Failed to delete SandboxAgent", err)) - return - } - log.Info("Successfully deleted sandbox agent") - data := api.NewResponse(struct{}{}, "Successfully deleted agent", false) - RespondWithJSON(w, http.StatusOK, data) + w.RespondWithError(errors.NewNotFoundError(notFoundMsg, nil)) return } - log.Error(err, "Failed to get Agent") - w.RespondWithError(errors.NewInternalServerError("Failed to get Agent", err)) + w.RespondWithError(errors.NewInternalServerError(getFailedMsg, err)) return } - log.V(1).Info("Deleting Agent from Kubernetes") if err := h.KubeClient.Delete(r.Context(), agent); err != nil { - w.RespondWithError(errors.NewInternalServerError("Failed to delete Agent", err)) + w.RespondWithError(errors.NewInternalServerError(deleteFailedMsg, err)) return } - log.Info("Successfully deleted agent") - data := api.NewResponse(struct{}{}, "Successfully deleted agent", false) - RespondWithJSON(w, http.StatusOK, data) + log.Info(successMessage) + RespondWithJSON(w, http.StatusOK, api.NewResponse(struct{}{}, successMessage, false)) } -func normalizeSandboxAgentForAPI(sa *v1alpha2.SandboxAgent) { - if sa == nil { - return - } - if sa.Spec.Type == "" { - sa.Spec.Type = v1alpha2.AgentType_Declarative +func (h *AgentsHandler) authorizeAgentRequest(w ErrorResponseWriter, r *http.Request, agentRef types.NamespacedName) bool { + if err := Check(h.Authorizer, r, auth.Resource{Type: "Agent", Name: agentRef.String()}); err != nil { + w.RespondWithError(err) + return false } + return true } -// HandleCreateSandboxAgent handles POST /api/sandboxagents requests. -func (h *AgentsHandler) HandleCreateSandboxAgent(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()).WithName("agents-handler").WithValues("operation", "create-sandboxagent") +func respondWithObjectResponse[T any]( + w ErrorResponseWriter, + status int, + data T, + message string, +) { + RespondWithJSON(w, status, api.NewResponse(data, message, false)) +} - var saReq v1alpha2.SandboxAgent - if err := DecodeJSONBody(r, &saReq); err != nil { +func (h *AgentsHandler) handleCreateAgentObject( + w ErrorResponseWriter, + r *http.Request, + log logr.Logger, + agent v1alpha2.AgentObject, + invalidMetadataMsg string, + successMessage string, + normalize func(v1alpha2.AgentObject), + responseData func(context.Context, logr.Logger, v1alpha2.AgentObject) (any, error), +) { + if err := DecodeJSONBody(r, agent); err != nil { w.RespondWithError(errors.NewBadRequestError("Invalid request body", err)) return } - normalizeSandboxAgentForAPI(&saReq) - if saReq.Namespace == "" { - saReq.Namespace = utils.GetResourceNamespace() - log.V(4).Info("Namespace not provided in request. Creating in controller installation namespace", - "namespace", saReq.Namespace) - } - agentRef, err := utils.ParseRefString(saReq.Name, saReq.Namespace) - if err != nil { - w.RespondWithError(errors.NewBadRequestError("Invalid sandboxagent metadata", err)) - return + if normalize != nil { + normalize(agent) } - log = log.WithValues( - "agentNamespace", agentRef.Namespace, - "agentName", agentRef.Name, - ) - - if err := Check(h.Authorizer, r, auth.Resource{Type: "Agent", Name: agentRef.String()}); err != nil { - w.RespondWithError(err) + var err error + log, agentRef, wrappedErr := h.parseAgentRef(log, agent, invalidMetadataMsg) + if wrappedErr != nil { + w.RespondWithError(wrappedErr) return } - - if h.SandboxBackend != nil { - if err := sandboxbackend.EnsureAgentSandboxAPIsRegistered(r.Context(), h.KubeClient); err != nil { - w.RespondWithError(errors.NewBadRequestError(err.Error(), err)) - return - } - } - - kubeClientWrapper := utils.NewKubeClientWrapper(h.KubeClient) - if err := kubeClientWrapper.AddInMemory(&saReq); err != nil { - w.RespondWithError(errors.NewInternalServerError("Failed to add SandboxAgent to Kubernetes wrapper", err)) + if !h.authorizeAgentRequest(w, r, agentRef) { return } - apiTranslator := agent_translator.NewAdkApiTranslator( - kubeClientWrapper, - h.DefaultModelConfig, - nil, - h.ProxyURL, - h.SandboxBackend, - ) - - agentView := agent_translator.AgentViewFromSandboxAgent(&saReq) - log.V(1).Info("Translating SandboxAgent to ADK format") - if _, err := apiTranslator.TranslateAgent(r.Context(), agentView, true); err != nil { - w.RespondWithError(errors.NewInternalServerError("Failed to translate SandboxAgent to ADK format", err)) + if err = h.validateAgentObject(r.Context(), agent); err != nil { + w.RespondWithError(err) return } - - if err := h.KubeClient.Create(r.Context(), &saReq); err != nil { - w.RespondWithError(errors.NewInternalServerError("Failed to create SandboxAgent in Kubernetes", err)) + if err = h.KubeClient.Create(r.Context(), agent); err != nil { + w.RespondWithError(errors.NewInternalServerError("Failed to create Agent in Kubernetes", err)) return } - agentResponse, err := h.getSandboxAgentResponse(r.Context(), log, &saReq) + response, err := responseData(r.Context(), log, agent) if err != nil { w.RespondWithError(err) return } - log.Info("Successfully created sandbox agent", "agentRef", agentRef) - data := api.NewResponse(agentResponse, "Successfully created sandbox agent", false) - RespondWithJSON(w, http.StatusCreated, data) + log.Info(successMessage, "agentRef", agentRef) + respondWithObjectResponse(w, http.StatusCreated, response, successMessage) } -// HandleUpdateSandboxAgent handles PUT /api/sandboxagents/{namespace}/{name} requests. -func (h *AgentsHandler) HandleUpdateSandboxAgent(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()).WithName("agents-handler").WithValues("operation", "update-sandboxagent") - - var saReq v1alpha2.SandboxAgent - if err := DecodeJSONBody(r, &saReq); err != nil { +func (h *AgentsHandler) handleUpdateAgentObject( + w ErrorResponseWriter, + r *http.Request, + log logr.Logger, + incoming v1alpha2.AgentObject, + existing v1alpha2.AgentObject, + invalidMetadataMsg string, + getFailedMsg string, + updateFailedMsg string, + notFoundMsg string, + successMessage string, + normalize func(v1alpha2.AgentObject), + validatePathMatch bool, + responseData func(context.Context, logr.Logger, v1alpha2.AgentObject) (any, error), +) { + if err := DecodeJSONBody(r, incoming); err != nil { w.RespondWithError(errors.NewBadRequestError("Invalid request body", err)) return } - normalizeSandboxAgentForAPI(&saReq) - if saReq.Namespace == "" { - saReq.Namespace = utils.GetResourceNamespace() - } - agentRef, err := utils.ParseRefString(saReq.Name, saReq.Namespace) - if err != nil { - w.RespondWithError(errors.NewBadRequestError("Invalid SandboxAgent metadata", err)) - return + if normalize != nil { + normalize(incoming) } - agentNamespace, err := GetPathParam(r, "namespace") - if err != nil { - w.RespondWithError(errors.NewBadRequestError("Failed to get namespace from path", err)) - return - } - agentName, err := GetPathParam(r, "name") - if err != nil { - w.RespondWithError(errors.NewBadRequestError("Failed to get name from path", err)) + log, agentRef, wrappedErr := h.parseAgentRef(log, incoming, invalidMetadataMsg) + if wrappedErr != nil { + w.RespondWithError(wrappedErr) return } - if agentRef.Namespace != agentNamespace || agentRef.Name != agentName { - w.RespondWithError(errors.NewBadRequestError("Path does not match request body metadata", nil)) - return + if validatePathMatch { + agentNamespace, err := GetPathParam(r, "namespace") + if err != nil { + w.RespondWithError(errors.NewBadRequestError("Failed to get namespace from path", err)) + return + } + agentName, err := GetPathParam(r, "name") + if err != nil { + w.RespondWithError(errors.NewBadRequestError("Failed to get name from path", err)) + return + } + if agentRef.Namespace != agentNamespace || agentRef.Name != agentName { + w.RespondWithError(errors.NewBadRequestError("Path does not match request body metadata", nil)) + return + } } - if err := Check(h.Authorizer, r, auth.Resource{Type: "Agent", Name: agentRef.String()}); err != nil { - w.RespondWithError(err) + if !h.authorizeAgentRequest(w, r, agentRef) { return } - if h.SandboxBackend != nil { - if err := sandboxbackend.EnsureAgentSandboxAPIsRegistered(r.Context(), h.KubeClient); err != nil { - w.RespondWithError(errors.NewBadRequestError(err.Error(), err)) - return - } - } - - existing := &v1alpha2.SandboxAgent{} if err := h.KubeClient.Get(r.Context(), agentRef, existing); err != nil { if apierrors.IsNotFound(err) { - w.RespondWithError(errors.NewNotFoundError("SandboxAgent not found", nil)) + w.RespondWithError(errors.NewNotFoundError(notFoundMsg, nil)) return } - w.RespondWithError(errors.NewInternalServerError("Failed to get SandboxAgent", err)) + w.RespondWithError(errors.NewInternalServerError(getFailedMsg, err)) return } - existing.Spec = saReq.Spec + *existing.GetAgentSpec() = *incoming.GetAgentSpec() - kubeClientWrapper := utils.NewKubeClientWrapper(h.KubeClient) - if err := kubeClientWrapper.AddInMemory(existing); err != nil { - w.RespondWithError(errors.NewInternalServerError("Failed to add SandboxAgent to Kubernetes wrapper", err)) - return - } - - apiTranslator := agent_translator.NewAdkApiTranslator( - kubeClientWrapper, - h.DefaultModelConfig, - nil, - h.ProxyURL, - h.SandboxBackend, - ) - agentView := agent_translator.AgentViewFromSandboxAgent(existing) - if _, err := apiTranslator.TranslateAgent(r.Context(), agentView, true); err != nil { - w.RespondWithError(errors.NewInternalServerError("Failed to translate SandboxAgent to ADK format", err)) + if err := h.validateAgentObject(r.Context(), existing); err != nil { + w.RespondWithError(err) return } - if err := h.KubeClient.Update(r.Context(), existing); err != nil { - w.RespondWithError(errors.NewInternalServerError("Failed to update SandboxAgent", err)) + w.RespondWithError(errors.NewInternalServerError(updateFailedMsg, err)) return } - agentResponse, err := h.getSandboxAgentResponse(r.Context(), log, existing) + response, err := responseData(r.Context(), log, existing) if err != nil { w.RespondWithError(err) return } - log.Info("Successfully updated sandbox agent", "agentRef", agentRef) - data := api.NewResponse(agentResponse, "Successfully updated sandbox agent", false) - RespondWithJSON(w, http.StatusOK, data) + log.Info(successMessage, "agentRef", agentRef) + respondWithObjectResponse(w, http.StatusOK, response, successMessage) +} + +// HandleGetAgent handles GET /api/agents/{namespace}/{name} requests using database +func (h *AgentsHandler) HandleGetAgent(w ErrorResponseWriter, r *http.Request) { + log := ctrllog.FromContext(r.Context()).WithName("agents-handler").WithValues("operation", "get-db") + h.handleGetAgentObject(w, r, log, &v1alpha2.Agent{}, "Agent not found", "Successfully retrieved agent") +} + +// HandleGetSandboxAgent handles GET /api/sandboxagents/{namespace}/{name} requests. +func (h *AgentsHandler) HandleGetSandboxAgent(w ErrorResponseWriter, r *http.Request) { + log := ctrllog.FromContext(r.Context()).WithName("agents-handler").WithValues("operation", "get-sandboxagent") + h.handleGetAgentObject(w, r, log, &v1alpha2.SandboxAgent{}, "SandboxAgent not found", "Successfully retrieved sandbox agent") +} + +// HandleCreateAgent handles POST /api/agents requests using database +func (h *AgentsHandler) HandleCreateAgent(w ErrorResponseWriter, r *http.Request) { + log := ctrllog.FromContext(r.Context()).WithName("agents-handler").WithValues("operation", "create-db") + h.handleCreateAgentObject( + w, + r, + log, + &v1alpha2.Agent{}, + "Invalid agent metadata", + "Successfully created agent", + nil, + func(_ context.Context, _ logr.Logger, agent v1alpha2.AgentObject) (any, error) { + return agent, nil + }, + ) +} + +// HandleUpdateAgent handles PUT /api/agents/{namespace}/{name} requests using database +func (h *AgentsHandler) HandleUpdateAgent(w ErrorResponseWriter, r *http.Request) { + log := ctrllog.FromContext(r.Context()).WithName("agents-handler").WithValues("operation", "update-db") + h.handleUpdateAgentObject( + w, + r, + log, + &v1alpha2.Agent{}, + &v1alpha2.Agent{}, + "Invalid Agent metadata", + "Failed to get Agent", + "Failed to update Agent", + "Agent not found", + "Successfully updated agent", + nil, + false, + func(_ context.Context, _ logr.Logger, agent v1alpha2.AgentObject) (any, error) { + return agent, nil + }, + ) +} + +// HandleDeleteAgent handles DELETE /api/agents/{namespace}/{name} requests using database +func (h *AgentsHandler) HandleDeleteAgent(w ErrorResponseWriter, r *http.Request) { + log := ctrllog.FromContext(r.Context()).WithName("agents-handler").WithValues("operation", "delete-db") + h.handleDeleteAgentObject( + w, + r, + log, + &v1alpha2.Agent{}, + "Agent not found", + "Failed to get Agent", + "Failed to delete Agent", + "Successfully deleted agent", + ) +} + +func normalizeSandboxAgentForAPI(sa *v1alpha2.SandboxAgent) { + if sa == nil { + return + } + if sa.Spec.Type == "" { + sa.Spec.Type = v1alpha2.AgentType_Declarative + } +} + +// HandleCreateSandboxAgent handles POST /api/sandboxagents requests. +func (h *AgentsHandler) HandleCreateSandboxAgent(w ErrorResponseWriter, r *http.Request) { + log := ctrllog.FromContext(r.Context()).WithName("agents-handler").WithValues("operation", "create-sandboxagent") + h.handleCreateAgentObject( + w, + r, + log, + &v1alpha2.SandboxAgent{}, + "Invalid sandboxagent metadata", + "Successfully created sandbox agent", + func(agent v1alpha2.AgentObject) { + normalizeSandboxAgentForAPI(agent.(*v1alpha2.SandboxAgent)) + }, + func(ctx context.Context, log logr.Logger, agent v1alpha2.AgentObject) (any, error) { + return h.getAgentResponse(ctx, log, agent) + }, + ) +} + +// HandleUpdateSandboxAgent handles PUT /api/sandboxagents/{namespace}/{name} requests. +func (h *AgentsHandler) HandleUpdateSandboxAgent(w ErrorResponseWriter, r *http.Request) { + log := ctrllog.FromContext(r.Context()).WithName("agents-handler").WithValues("operation", "update-sandboxagent") + h.handleUpdateAgentObject( + w, + r, + log, + &v1alpha2.SandboxAgent{}, + &v1alpha2.SandboxAgent{}, + "Invalid SandboxAgent metadata", + "Failed to get SandboxAgent", + "Failed to update SandboxAgent", + "SandboxAgent not found", + "Successfully updated sandbox agent", + func(agent v1alpha2.AgentObject) { + normalizeSandboxAgentForAPI(agent.(*v1alpha2.SandboxAgent)) + }, + true, + func(ctx context.Context, log logr.Logger, agent v1alpha2.AgentObject) (any, error) { + return h.getAgentResponse(ctx, log, agent) + }, + ) +} + +// HandleDeleteSandboxAgent handles DELETE /api/sandboxagents/{namespace}/{name} requests. +func (h *AgentsHandler) HandleDeleteSandboxAgent(w ErrorResponseWriter, r *http.Request) { + log := ctrllog.FromContext(r.Context()).WithName("agents-handler").WithValues("operation", "delete-sandboxagent") + h.handleDeleteAgentObject( + w, + r, + log, + &v1alpha2.SandboxAgent{}, + "SandboxAgent not found", + "Failed to get SandboxAgent", + "Failed to delete SandboxAgent", + "Successfully deleted sandbox agent", + ) } diff --git a/go/core/internal/httpserver/handlers/agents_test.go b/go/core/internal/httpserver/handlers/agents_test.go index 547293508..a6f27d3a0 100644 --- a/go/core/internal/httpserver/handlers/agents_test.go +++ b/go/core/internal/httpserver/handlers/agents_test.go @@ -131,7 +131,7 @@ func TestHandleGetAgent(t *testing.T) { var response api.StandardResponse[api.AgentResponse] err := json.Unmarshal(w.Body.Bytes(), &response) require.NoError(t, err) - require.Equal(t, "test-team", response.Data.Agent.Name) + require.Equal(t, "test-team", response.Data.Agent.Metadata.Name) require.Equal(t, "default/test-model-config", response.Data.ModelConfigRef, w.Body.String()) require.Equal(t, "gpt-4", response.Data.Model) require.Equal(t, v1alpha2.ModelProviderOpenAI, response.Data.ModelProvider) @@ -231,7 +231,7 @@ func TestHandleGetAgent(t *testing.T) { require.False(t, response.Data.DeploymentReady) }) - t.Run("SandboxAgent CRD gets Accepted and DeploymentReady from status (GET falls back after Agent missing)", func(t *testing.T) { + t.Run("returns 404 when only sandbox agent exists with that name", func(t *testing.T) { modelConfig := createTestModelConfig() conditions := []metav1.Condition{ { @@ -256,6 +256,41 @@ func TestHandleGetAgent(t *testing.T) { handler.HandleGetAgent(&testErrorResponseWriter{w}, req) + require.Equal(t, http.StatusNotFound, w.Code) + }) + + t.Run("returns 404 for missing agent", func(t *testing.T) { + handler, _ := setupTestHandler() + + req := httptest.NewRequest("GET", "/api/agents/default/test-team", nil) + req = mux.SetURLVars(req, map[string]string{"namespace": "default", "name": "test-team"}) + req = setUser(req, "test-user") + w := httptest.NewRecorder() + + handler.HandleGetAgent(&testErrorResponseWriter{w}, req) + + require.Equal(t, http.StatusNotFound, w.Code, w.Body.String()) + }) +} + +func TestHandleGetSandboxAgent(t *testing.T) { + t.Run("gets sandbox agent successfully", func(t *testing.T) { + modelConfig := createTestModelConfig() + conditions := []metav1.Condition{ + {Type: "Accepted", Status: "True", Reason: "AgentReconciled"}, + {Type: "Ready", Status: "True", Reason: "WorkloadReady"}, + } + sa := createTestSandboxAgentCRD("sandbox-accepted", modelConfig, conditions) + + handler, _ := setupTestHandler(sa, modelConfig) + + req := httptest.NewRequest("GET", "/api/sandboxagents/default/sandbox-accepted", nil) + req = mux.SetURLVars(req, map[string]string{"namespace": "default", "name": "sandbox-accepted"}) + req = setUser(req, "test-user") + w := httptest.NewRecorder() + + handler.HandleGetSandboxAgent(&testErrorResponseWriter{w}, req) + require.Equal(t, http.StatusOK, w.Code) var response api.StandardResponse[api.AgentResponse] @@ -263,21 +298,27 @@ func TestHandleGetAgent(t *testing.T) { require.NoError(t, err) require.True(t, response.Data.Accepted) require.True(t, response.Data.DeploymentReady) - require.Equal(t, v1alpha2.AgentType_Declarative, response.Data.Agent.Spec.Type) - require.True(t, response.Data.RunInSandbox) + require.Equal(t, v1alpha2.WorkloadModeSandbox, response.Data.WorkloadMode) }) - t.Run("returns 404 for missing agent", func(t *testing.T) { - handler, _ := setupTestHandler() + t.Run("same name as regular agent still returns sandbox resource", func(t *testing.T) { + modelConfig := createTestModelConfig() + agent := createTestAgent("shared-name", modelConfig) + sa := createTestSandboxAgentCRD("shared-name", modelConfig, nil) + handler, _ := setupTestHandler(agent, sa, modelConfig) - req := httptest.NewRequest("GET", "/api/agents/default/test-team", nil) - req = mux.SetURLVars(req, map[string]string{"namespace": "default", "name": "test-team"}) + req := httptest.NewRequest("GET", "/api/sandboxagents/default/shared-name", nil) + req = mux.SetURLVars(req, map[string]string{"namespace": "default", "name": "shared-name"}) req = setUser(req, "test-user") w := httptest.NewRecorder() - handler.HandleGetAgent(&testErrorResponseWriter{w}, req) + handler.HandleGetSandboxAgent(&testErrorResponseWriter{w}, req) - require.Equal(t, http.StatusNotFound, w.Code, w.Body.String()) + require.Equal(t, http.StatusOK, w.Code) + var response api.StandardResponse[api.AgentResponse] + err := json.Unmarshal(w.Body.Bytes(), &response) + require.NoError(t, err) + require.Equal(t, v1alpha2.WorkloadModeSandbox, response.Data.WorkloadMode) }) } @@ -320,12 +361,12 @@ func TestHandleListAgents(t *testing.T) { err := json.Unmarshal(w.Body.Bytes(), &response) require.NoError(t, err) require.Len(t, response.Data, 2) - require.Equal(t, "not-ready-agent", response.Data[0].Agent.Name) + require.Equal(t, "not-ready-agent", response.Data[0].Agent.Metadata.Name) require.Equal(t, "default/test-model-config", response.Data[0].ModelConfigRef) require.Equal(t, "gpt-4", response.Data[0].Model) require.Equal(t, v1alpha2.ModelProviderOpenAI, response.Data[0].ModelProvider) require.Equal(t, false, response.Data[0].DeploymentReady) - require.Equal(t, "ready-agent", response.Data[1].Agent.Name) + require.Equal(t, "ready-agent", response.Data[1].Agent.Metadata.Name) require.Equal(t, "default/test-model-config", response.Data[1].ModelConfigRef) require.Equal(t, "gpt-4", response.Data[1].Model) require.Equal(t, v1alpha2.ModelProviderOpenAI, response.Data[1].ModelProvider) @@ -381,10 +422,10 @@ func TestHandleListAgents(t *testing.T) { err := json.Unmarshal(w.Body.Bytes(), &response) require.NoError(t, err) require.Len(t, response.Data, 2) - require.Equal(t, "ready-agent", response.Data[1].Agent.Name) + require.Equal(t, "ready-agent", response.Data[1].Agent.Metadata.Name) require.Equal(t, true, response.Data[1].Accepted) require.Equal(t, true, response.Data[1].DeploymentReady) - require.Equal(t, "invalid-agent", response.Data[0].Agent.Name) + require.Equal(t, "invalid-agent", response.Data[0].Agent.Metadata.Name) require.Equal(t, false, response.Data[0].Accepted) require.Equal(t, true, response.Data[0].DeploymentReady) }) @@ -406,38 +447,108 @@ func TestHandleListAgents(t *testing.T) { require.Equal(t, http.StatusOK, w.Code) + var response api.StandardResponse[[]api.AgentResponse] + err := json.Unmarshal(w.Body.Bytes(), &response) + require.NoError(t, err) + require.Empty(t, response.Data) + }) +} + +func TestHandleListSandboxAgents(t *testing.T) { + t.Run("lists sandbox agents successfully", func(t *testing.T) { + modelConfig := createTestModelConfig() + conditions := []metav1.Condition{ + {Type: "Accepted", Status: "True", Reason: "Reconciled"}, + {Type: "Ready", Status: "True", Reason: "WorkloadReady"}, + } + sa := createTestSandboxAgentCRD("mysandbox", modelConfig, conditions) + agent := createTestAgent("myagent", modelConfig) + handler, _ := setupTestHandler(sa, agent, modelConfig) + + req := httptest.NewRequest("GET", "/api/sandboxagents", nil) + req = setUser(req, "test-user") + w := httptest.NewRecorder() + + handler.HandleListSandboxAgents(&testErrorResponseWriter{w}, req) + + require.Equal(t, http.StatusOK, w.Code) + var response api.StandardResponse[[]api.AgentResponse] err := json.Unmarshal(w.Body.Bytes(), &response) require.NoError(t, err) require.Len(t, response.Data, 1) - require.Equal(t, "mysandbox", response.Data[0].Agent.Name) + require.Equal(t, "mysandbox", response.Data[0].Agent.Metadata.Name) require.True(t, response.Data[0].Accepted) require.True(t, response.Data[0].DeploymentReady) - require.Equal(t, v1alpha2.AgentType_Declarative, response.Data[0].Agent.Spec.Type) - require.True(t, response.Data[0].RunInSandbox) + require.Equal(t, v1alpha2.WorkloadModeSandbox, response.Data[0].WorkloadMode) + }) + + t.Run("same names across kinds are both preserved by separate list endpoints", func(t *testing.T) { + modelConfig := createTestModelConfig() + agent := createTestAgent("shared-name", modelConfig) + sa := createTestSandboxAgentCRD("shared-name", modelConfig, nil) + handler, _ := setupTestHandler(agent, sa, modelConfig) + + agentReq := httptest.NewRequest("GET", "/api/agents", nil) + agentReq = setUser(agentReq, "test-user") + agentW := httptest.NewRecorder() + handler.HandleListAgents(&testErrorResponseWriter{agentW}, agentReq) + + sandboxReq := httptest.NewRequest("GET", "/api/sandboxagents", nil) + sandboxReq = setUser(sandboxReq, "test-user") + sandboxW := httptest.NewRecorder() + handler.HandleListSandboxAgents(&testErrorResponseWriter{sandboxW}, sandboxReq) + + require.Equal(t, http.StatusOK, agentW.Code) + require.Equal(t, http.StatusOK, sandboxW.Code) + + var agentResp api.StandardResponse[[]api.AgentResponse] + var sandboxResp api.StandardResponse[[]api.AgentResponse] + require.NoError(t, json.Unmarshal(agentW.Body.Bytes(), &agentResp)) + require.NoError(t, json.Unmarshal(sandboxW.Body.Bytes(), &sandboxResp)) + require.Len(t, agentResp.Data, 1) + require.Len(t, sandboxResp.Data, 1) + require.Equal(t, v1alpha2.WorkloadModeDeployment, agentResp.Data[0].WorkloadMode) + require.Equal(t, v1alpha2.WorkloadModeSandbox, sandboxResp.Data[0].WorkloadMode) }) } func TestHandleUpdateAgent(t *testing.T) { t.Run("updates agent successfully", func(t *testing.T) { + oldModelConfig := &v1alpha2.ModelConfig{ + ObjectMeta: metav1.ObjectMeta{Name: "old-model-config", Namespace: "default"}, + Spec: v1alpha2.ModelConfigSpec{ + Model: "gpt-4o-mini", + Provider: v1alpha2.ModelProviderOpenAI, + }, + } + newModelConfig := &v1alpha2.ModelConfig{ + ObjectMeta: metav1.ObjectMeta{Name: "new-model-config", Namespace: "default"}, + Spec: v1alpha2.ModelConfigSpec{ + Model: "gpt-4.1", + Provider: v1alpha2.ModelProviderOpenAI, + }, + } existingAgent := &v1alpha2.Agent{ ObjectMeta: metav1.ObjectMeta{Name: "test-team", Namespace: "default"}, Spec: v1alpha2.AgentSpec{ Type: v1alpha2.AgentType_Declarative, Declarative: &v1alpha2.DeclarativeAgentSpec{ - ModelConfig: "old-model-config", + ModelConfig: "old-model-config", + SystemMessage: "old system message", }, }, } - handler, _ := setupTestHandler(existingAgent) + handler, _ := setupTestHandler(existingAgent, oldModelConfig, newModelConfig) updatedAgent := &v1alpha2.Agent{ ObjectMeta: metav1.ObjectMeta{Name: "test-team", Namespace: "default"}, Spec: v1alpha2.AgentSpec{ Type: v1alpha2.AgentType_Declarative, Declarative: &v1alpha2.DeclarativeAgentSpec{ - ModelConfig: "new-model-config", + ModelConfig: "new-model-config", + SystemMessage: "new system message", }, }, } @@ -459,6 +570,50 @@ func TestHandleUpdateAgent(t *testing.T) { require.Equal(t, "new-model-config", response.Data.Spec.Declarative.ModelConfig) }) + t.Run("returns 400 for invalid updated agent configuration", func(t *testing.T) { + modelConfig := &v1alpha2.ModelConfig{ + ObjectMeta: metav1.ObjectMeta{Name: "old-model-config", Namespace: "default"}, + Spec: v1alpha2.ModelConfigSpec{ + Model: "gpt-4o-mini", + Provider: v1alpha2.ModelProviderOpenAI, + }, + } + existingAgent := &v1alpha2.Agent{ + ObjectMeta: metav1.ObjectMeta{Name: "test-team", Namespace: "default"}, + Spec: v1alpha2.AgentSpec{ + Type: v1alpha2.AgentType_Declarative, + Declarative: &v1alpha2.DeclarativeAgentSpec{ + ModelConfig: modelConfig.Name, + SystemMessage: "old system message", + }, + }, + } + + handler, _ := setupTestHandler(existingAgent, modelConfig) + + updatedAgent := &v1alpha2.Agent{ + ObjectMeta: metav1.ObjectMeta{Name: "test-team", Namespace: "default"}, + Spec: v1alpha2.AgentSpec{ + Type: v1alpha2.AgentType_Declarative, + Declarative: &v1alpha2.DeclarativeAgentSpec{ + ModelConfig: "missing-model-config", + SystemMessage: "updated system message", + }, + }, + } + + body, _ := json.Marshal(updatedAgent) + req := httptest.NewRequest("PUT", "/api/agents/default/test-team", bytes.NewBuffer(body)) + req = mux.SetURLVars(req, map[string]string{"namespace": "default", "name": "test-team"}) + req.Header.Set("Content-Type", "application/json") + req = setUser(req, "test-user") + w := httptest.NewRecorder() + + handler.HandleUpdateAgent(&testErrorResponseWriter{w}, req) + + require.Equal(t, http.StatusBadRequest, w.Code) + }) + t.Run("returns 404 for non-existent team", func(t *testing.T) { handler, _ := setupTestHandler() @@ -558,4 +713,41 @@ func TestHandleDeleteTeam(t *testing.T) { require.Equal(t, http.StatusNotFound, w.Code) }) + + t.Run("does not delete sandbox agent with same name", func(t *testing.T) { + modelConfig := createTestModelConfig() + agent := createTestAgent("shared-name", modelConfig) + sa := createTestSandboxAgentCRD("shared-name", modelConfig, nil) + handler, _ := setupTestHandler(agent, sa, modelConfig) + + req := httptest.NewRequest("DELETE", "/api/agents/default/shared-name", nil) + req = mux.SetURLVars(req, map[string]string{"namespace": "default", "name": "shared-name"}) + req = setUser(req, "test-user") + w := httptest.NewRecorder() + + handler.HandleDeleteAgent(&testErrorResponseWriter{w}, req) + + require.Equal(t, http.StatusOK, w.Code) + + var stillThere v1alpha2.SandboxAgent + err := handler.KubeClient.Get(context.Background(), types.NamespacedName{Namespace: "default", Name: "shared-name"}, &stillThere) + require.NoError(t, err) + }) +} + +func TestHandleDeleteSandboxAgent(t *testing.T) { + t.Run("deletes sandbox agent successfully", func(t *testing.T) { + modelConfig := createTestModelConfig() + sa := createTestSandboxAgentCRD("test-sandbox", modelConfig, nil) + handler, _ := setupTestHandler(sa, modelConfig) + + req := httptest.NewRequest("DELETE", "/api/sandboxagents/default/test-sandbox", nil) + req = mux.SetURLVars(req, map[string]string{"namespace": "default", "name": "test-sandbox"}) + req = setUser(req, "test-user") + w := httptest.NewRecorder() + + handler.HandleDeleteSandboxAgent(&testErrorResponseWriter{w}, req) + + require.Equal(t, http.StatusOK, w.Code) + }) } diff --git a/go/core/internal/httpserver/handlers/sessions.go b/go/core/internal/httpserver/handlers/sessions.go index cf9472f6c..1ff768077 100644 --- a/go/core/internal/httpserver/handlers/sessions.go +++ b/go/core/internal/httpserver/handlers/sessions.go @@ -1,7 +1,6 @@ package handlers import ( - "context" "fmt" "net/http" "strconv" @@ -12,8 +11,6 @@ import ( "github.com/kagent-dev/kagent/go/api/v1alpha2" "github.com/kagent-dev/kagent/go/core/internal/httpserver/errors" "github.com/kagent-dev/kagent/go/core/internal/utils" - apierrors "k8s.io/apimachinery/pkg/api/errors" - "sigs.k8s.io/controller-runtime/pkg/client" ctrllog "sigs.k8s.io/controller-runtime/pkg/log" "trpc.group/trpc-go/trpc-a2a-go/protocol" ) @@ -28,18 +25,6 @@ func NewSessionsHandler(base *Base) *SessionsHandler { return &SessionsHandler{Base: base} } -func (h *SessionsHandler) isSandboxWorkload(ctx context.Context, namespace, name string) (bool, error) { - sa := &v1alpha2.SandboxAgent{} - err := h.KubeClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: name}, sa) - if err != nil { - if apierrors.IsNotFound(err) { - return false, nil - } - return false, err - } - return true, nil -} - // RunRequest represents a run creation request type RunRequest struct { Task string `json:"task"` @@ -147,18 +132,15 @@ func (h *SessionsHandler) HandleCreateSession(w ErrorResponseWriter, r *http.Req return } - nn, perr := utils.ParseRefString(*sessionRequest.AgentRef, "") - if perr == nil { - if isSandboxWorkload, err := h.isSandboxWorkload(r.Context(), nn.Namespace, nn.Name); err == nil && isSandboxWorkload { - existing, lerr := h.DatabaseService.ListSessionsForAgent(r.Context(), agent.ID, userID) - if lerr != nil { - w.RespondWithError(errors.NewInternalServerError("Failed to list sessions for agent", lerr)) - return - } - if len(existing) > 0 { - w.RespondWithError(errors.NewConflictError("Sandbox agents support only one chat session per user", fmt.Errorf("a session already exists for this agent"))) - return - } + if agent.WorkloadType == v1alpha2.WorkloadModeSandbox { + existing, lerr := h.DatabaseService.ListSessionsForAgentAllUsers(r.Context(), agent.ID) + if lerr != nil { + w.RespondWithError(errors.NewInternalServerError("Failed to list sessions for agent", lerr)) + return + } + if len(existing) > 0 { + w.RespondWithError(errors.NewConflictError("Sandbox agents support only one chat session", fmt.Errorf("a session already exists for this agent"))) + return } } diff --git a/go/core/internal/httpserver/handlers/sessions_test.go b/go/core/internal/httpserver/handlers/sessions_test.go index 0346394bb..cb265f10a 100644 --- a/go/core/internal/httpserver/handlers/sessions_test.go +++ b/go/core/internal/httpserver/handlers/sessions_test.go @@ -18,6 +18,7 @@ import ( "github.com/kagent-dev/kagent/go/api/database" api "github.com/kagent-dev/kagent/go/api/httpapi" + "github.com/kagent-dev/kagent/go/api/v1alpha2" database_fake "github.com/kagent-dev/kagent/go/core/internal/database/fake" authimpl "github.com/kagent-dev/kagent/go/core/internal/httpserver/auth" "github.com/kagent-dev/kagent/go/core/internal/httpserver/handlers" @@ -58,7 +59,8 @@ func TestSessionsHandler(t *testing.T) { createTestAgent := func(dbClient database.Client, agentRef string) *database.Agent { agent := &database.Agent{ - ID: agentRef, + ID: agentRef, + WorkloadType: v1alpha2.WorkloadModeDeployment, } dbClient.StoreAgent(context.Background(), agent) //nolint:errcheck // The fake client should assign an ID, but we'll use a default for testing @@ -209,6 +211,35 @@ func TestSessionsHandler(t *testing.T) { assert.Equal(t, http.StatusBadRequest, responseRecorder.Code) assert.NotNil(t, responseRecorder.errorReceived) }) + + t.Run("SandboxAgentAllowsOnlyOneSessionGlobally", func(t *testing.T) { + handler, dbClient, responseRecorder := setupHandler() + userID := "test-user" + agentRef := utils.ConvertToPythonIdentifier("default/test-sandbox-agent") + + require.NoError(t, dbClient.StoreAgent(context.Background(), &database.Agent{ + ID: agentRef, + WorkloadType: v1alpha2.WorkloadModeSandbox, + })) + + existingAgentID := agentRef + createTestSession(dbClient, "existing-session", "other-user", existingAgentID) + + sessionReq := api.SessionRequest{ + AgentRef: &agentRef, + Name: new("second-session"), + } + + jsonBody, _ := json.Marshal(sessionReq) + req := httptest.NewRequest("POST", "/api/sessions", bytes.NewBuffer(jsonBody)) + req.Header.Set("Content-Type", "application/json") + req = setUser(req, userID) + + handler.HandleCreateSession(responseRecorder, req) + + assert.Equal(t, http.StatusConflict, responseRecorder.Code) + assert.NotNil(t, responseRecorder.errorReceived) + }) }) t.Run("HandleGetSession", func(t *testing.T) { diff --git a/go/core/internal/httpserver/server.go b/go/core/internal/httpserver/server.go index ea85b2e32..40efeb810 100644 --- a/go/core/internal/httpserver/server.go +++ b/go/core/internal/httpserver/server.go @@ -41,6 +41,7 @@ const ( APIPathMemories = "/api/memories" APIPathNamespaces = "/api/namespaces" APIPathA2A = "/api/a2a" + APIPathA2ASandboxes = "/api/a2a-sandboxes" APIPathMCP = "/mcp" APIPathFeedback = "/api/feedback" APIPathLangGraph = "/api/langgraph" @@ -245,8 +246,11 @@ func (s *HTTPServer) setupRoutes() { s.router.HandleFunc(APIPathAgents+"/{namespace}/{name}", adaptHandler(s.handlers.Agents.HandleGetAgent)).Methods(http.MethodGet) s.router.HandleFunc(APIPathAgents+"/{namespace}/{name}", adaptHandler(s.handlers.Agents.HandleDeleteAgent)).Methods(http.MethodDelete) + s.router.HandleFunc(APIPathSandboxAgents, adaptHandler(s.handlers.Agents.HandleListSandboxAgents)).Methods(http.MethodGet) s.router.HandleFunc(APIPathSandboxAgents, adaptHandler(s.handlers.Agents.HandleCreateSandboxAgent)).Methods(http.MethodPost) + s.router.HandleFunc(APIPathSandboxAgents+"/{namespace}/{name}", adaptHandler(s.handlers.Agents.HandleGetSandboxAgent)).Methods(http.MethodGet) s.router.HandleFunc(APIPathSandboxAgents+"/{namespace}/{name}", adaptHandler(s.handlers.Agents.HandleUpdateSandboxAgent)).Methods(http.MethodPut) + s.router.HandleFunc(APIPathSandboxAgents+"/{namespace}/{name}", adaptHandler(s.handlers.Agents.HandleDeleteSandboxAgent)).Methods(http.MethodDelete) // Model Provider Configs s.router.HandleFunc(APIPathModelProviderConfigs+"/models", adaptHandler(s.handlers.ModelProviderConfig.HandleListSupportedModelProviders)).Methods(http.MethodGet) @@ -286,6 +290,7 @@ func (s *HTTPServer) setupRoutes() { // A2A s.router.PathPrefix(APIPathA2A + "/{namespace}/{name}").Handler(s.config.A2AHandler) + s.router.PathPrefix(APIPathA2ASandboxes + "/{namespace}/{name}").Handler(s.config.A2AHandler) // MCP if s.config.MCPHandler != nil { diff --git a/go/core/pkg/app/app.go b/go/core/pkg/app/app.go index f6735bdbd..74269c00a 100644 --- a/go/core/pkg/app/app.go +++ b/go/core/pkg/app/app.go @@ -562,13 +562,13 @@ func Start(getExtensionConfig GetExtensionConfig, migrationRunner MigrationRunne } // Register A2A handlers on all replicas - a2aHandler := a2a.NewA2AHttpMux(httpserver.APIPathA2A, extensionCfg.Authenticator) + a2aHandler := a2a.NewA2AHttpMux(httpserver.APIPathA2A, httpserver.APIPathA2ASandboxes, extensionCfg.Authenticator) if err := mgr.Add(a2a.NewA2ARegistrar( mgr.GetCache(), - apiTranslator, a2aHandler, cfg.A2ABaseUrl+httpserver.APIPathA2A, + cfg.A2ABaseUrl+httpserver.APIPathA2ASandboxes, extensionCfg.Authenticator, int(cfg.Streaming.MaxBufSize.Value()), int(cfg.Streaming.InitialBufSize.Value()), diff --git a/go/core/pkg/migrations/core/000003_agent_workload_type.down.sql b/go/core/pkg/migrations/core/000003_agent_workload_type.down.sql new file mode 100644 index 000000000..302f0e098 --- /dev/null +++ b/go/core/pkg/migrations/core/000003_agent_workload_type.down.sql @@ -0,0 +1 @@ +ALTER TABLE agent DROP COLUMN IF EXISTS workload_type; diff --git a/go/core/pkg/migrations/core/000003_agent_workload_type.up.sql b/go/core/pkg/migrations/core/000003_agent_workload_type.up.sql new file mode 100644 index 000000000..d760a87d0 --- /dev/null +++ b/go/core/pkg/migrations/core/000003_agent_workload_type.up.sql @@ -0,0 +1,3 @@ +ALTER TABLE agent ADD COLUMN IF NOT EXISTS workload_type TEXT; +UPDATE agent SET workload_type = 'deployment' WHERE workload_type IS NULL; +ALTER TABLE agent ALTER COLUMN workload_type SET NOT NULL; diff --git a/go/core/pkg/sandboxbackend/agentsxk8s/agentsxk8s.go b/go/core/pkg/sandboxbackend/agentsxk8s/agentsxk8s.go index 05643d816..e5940816a 100644 --- a/go/core/pkg/sandboxbackend/agentsxk8s/agentsxk8s.go +++ b/go/core/pkg/sandboxbackend/agentsxk8s/agentsxk8s.go @@ -34,7 +34,7 @@ func (b *Backend) BuildSandbox(_ context.Context, in sandboxbackend.BuildInput) if in.Agent == nil { return nil, fmt.Errorf("agent is required") } - name := in.Agent.Name + name := in.Agent.GetName() if in.WorkloadName != "" { name = in.WorkloadName } @@ -51,7 +51,7 @@ func (b *Backend) BuildSandbox(_ context.Context, in sandboxbackend.BuildInput) }, } - labelUnion := mapsUnion(podLabels, in.Agent.Labels) + labelUnion := mapsUnion(podLabels, in.Agent.GetLabels()) replicas := int32(1) sb := &agentsandboxv1.Sandbox{ @@ -61,8 +61,8 @@ func (b *Backend) BuildSandbox(_ context.Context, in sandboxbackend.BuildInput) }, ObjectMeta: metav1.ObjectMeta{ Name: name, - Namespace: in.Agent.Namespace, - Annotations: in.Agent.Annotations, + Namespace: in.Agent.GetNamespace(), + Annotations: in.Agent.GetAnnotations(), Labels: labelUnion, }, Spec: agentsandboxv1.SandboxSpec{ diff --git a/go/core/pkg/sandboxbackend/backend.go b/go/core/pkg/sandboxbackend/backend.go index 163b18fb5..02b17c14a 100644 --- a/go/core/pkg/sandboxbackend/backend.go +++ b/go/core/pkg/sandboxbackend/backend.go @@ -12,7 +12,7 @@ import ( // BuildInput carries the pod template for a Sandbox workload (agents.x-k8s.io Sandbox). type BuildInput struct { - Agent *v1alpha2.Agent + Agent v1alpha2.AgentObject PodTemplate corev1.PodTemplateSpec WorkloadName string ExtraLabels map[string]string diff --git a/go/core/pkg/translator/adk_api_translator_types.go b/go/core/pkg/translator/adk_api_translator_types.go index f27185584..07fd4268d 100644 --- a/go/core/pkg/translator/adk_api_translator_types.go +++ b/go/core/pkg/translator/adk_api_translator_types.go @@ -17,6 +17,6 @@ type AgentOutputs struct { } type TranslatorPlugin interface { - ProcessAgent(ctx context.Context, agent *v1alpha2.Agent, outputs *AgentOutputs) error + ProcessAgent(ctx context.Context, agent v1alpha2.AgentObject, outputs *AgentOutputs) error GetOwnedResourceTypes() []client.Object } diff --git a/go/core/test/e2e/invoke_api_test.go b/go/core/test/e2e/invoke_api_test.go index 6ebc1e09e..046a8db48 100644 --- a/go/core/test/e2e/invoke_api_test.go +++ b/go/core/test/e2e/invoke_api_test.go @@ -152,6 +152,11 @@ func setupAgent(t *testing.T, cli client.Client, modelConfigName string, tools [ return setupAgentWithOptions(t, cli, modelConfigName, tools, AgentOptions{}) } +// setupSandboxAgent creates and returns a sandbox agent resource, then waits for it to be ready. +func setupSandboxAgent(t *testing.T, cli client.Client, modelConfigName string, tools []*v1alpha2.Tool) *v1alpha2.SandboxAgent { + return setupSandboxAgentWithOptions(t, cli, modelConfigName, tools, AgentOptions{}) +} + // AgentOptions provides optional configuration for agent setup type AgentOptions struct { Name string @@ -196,14 +201,51 @@ func setupAgentWithOptions(t *testing.T, cli client.Client, modelConfigName stri return agent } +// setupSandboxAgentWithOptions creates and returns a sandbox agent resource with custom options. +func setupSandboxAgentWithOptions(t *testing.T, cli client.Client, modelConfigName string, tools []*v1alpha2.Tool, opts AgentOptions) *v1alpha2.SandboxAgent { + agent := generateSandboxAgent(modelConfigName, tools, opts) + err := cli.Create(t.Context(), agent) + if err != nil { + t.Fatalf("failed to create sandbox agent: %v", err) + } + cleanup(t, cli, agent) + + args := []string{ + "wait", + "--for", + "condition=Ready", + "--timeout=1m", + "sandboxagents.kagent.dev", + agent.Name, + "-n", + "kagent", + } + + cmd := exec.CommandContext(t.Context(), "kubectl", args...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + require.NoError(t, cmd.Run()) + + waitForSandboxEndpoint(t, agent.Namespace, agent.Name) + + return agent +} + // setupA2AClient creates an A2A client for the test agent func setupA2AClient(t *testing.T, agent *v1alpha2.Agent) *a2aclient.A2AClient { - a2aURL := a2aUrl(agent.Namespace, agent.Name) + a2aURL := a2aURL(agent.Namespace, agent.Name, false) a2aClient, err := a2aclient.NewA2AClient(a2aURL) require.NoError(t, err) return a2aClient } +// setupSandboxA2AClient creates an A2A client for the test sandbox agent. +func setupSandboxA2AClient(t *testing.T, agent *v1alpha2.SandboxAgent) *a2aclient.A2AClient { + a2aClient, err := a2aclient.NewA2AClient(a2aURL(agent.Namespace, agent.Name, true)) + require.NoError(t, err) + return a2aClient +} + // extractTextFromArtifacts extracts all text content from task artifacts func extractTextFromArtifacts(taskResult *protocol.Task) string { var text strings.Builder @@ -334,14 +376,21 @@ func runStreamingTest(t *testing.T, a2aClient *a2aclient.A2AClient, userMessage, require.NoError(t, err, lastJSON) } -func a2aUrl(namespace, name string) string { +func a2aURL(namespace, name string, sandbox bool) string { kagentURL := os.Getenv("KAGENT_URL") if kagentURL == "" { // if running locally on kind, do "kubectl port-forward -n kagent deployments/kagent-controller 8083" kagentURL = "http://localhost:8083" } - // A2A URL format: // - return kagentURL + "/api/a2a/" + namespace + "/" + name + path := "/api/a2a/" + if sandbox { + path = "/api/a2a-sandboxes/" + } + return kagentURL + path + namespace + "/" + name +} + +func a2aUrl(namespace, name string) string { + return a2aURL(namespace, name, false) } // waitForEndpoint polls the A2A agent card endpoint until it returns a non-5xx response. @@ -349,7 +398,16 @@ func a2aUrl(namespace, name string) string { // and the agent actually being able to serve requests through the controller proxy. func waitForEndpoint(t *testing.T, namespace, name string) { t.Helper() - url := a2aUrl(namespace, name) + waitForEndpointURL(t, a2aURL(namespace, name, false)) +} + +func waitForSandboxEndpoint(t *testing.T, namespace, name string) { + t.Helper() + waitForEndpointURL(t, a2aURL(namespace, name, true)) +} + +func waitForEndpointURL(t *testing.T, url string) { + t.Helper() httpClient := &http.Client{Timeout: 5 * time.Second} t.Logf("Waiting for endpoint %s to become ready", url) @@ -452,6 +510,14 @@ func generateAgent(modelConfigName string, tools []*v1alpha2.Tool, opts AgentOpt return agent } +func generateSandboxAgent(modelConfigName string, tools []*v1alpha2.Tool, opts AgentOptions) *v1alpha2.SandboxAgent { + agent := generateAgent(modelConfigName, tools, opts) + return &v1alpha2.SandboxAgent{ + ObjectMeta: agent.ObjectMeta, + Spec: agent.Spec, + } +} + func generateMCPServer() *v1alpha1.MCPServer { return &v1alpha1.MCPServer{ ObjectMeta: metav1.ObjectMeta{ @@ -566,6 +632,41 @@ func TestE2EInvokeInlineAgentWithStreaming(t *testing.T) { }) } +func TestE2EInvokeSandboxAgent(t *testing.T) { + baseURL, stopServer := setupMockServer(t, "mocks/invoke_inline_agent.json") + defer stopServer() + + cli := setupK8sClient(t, false) + + tools := []*v1alpha2.Tool{ + { + Type: v1alpha2.ToolProviderType_McpServer, + McpServer: &v1alpha2.McpServerTool{ + TypedReference: v1alpha2.TypedReference{ + ApiGroup: "kagent.dev", + Kind: "RemoteMCPServer", + Name: "kagent-tool-server", + }, + ToolNames: []string{"k8s_get_resources"}, + }, + }, + } + + modelCfg := setupModelConfig(t, cli, baseURL) + agent := setupSandboxAgentWithOptions(t, cli, modelCfg.Name, tools, AgentOptions{Stream: true}) + + a2aClient := setupSandboxA2AClient(t, agent) + var taskResult *protocol.Task + + t.Run("sync_invocation", func(t *testing.T) { + taskResult = runSyncTest(t, a2aClient, "List all nodes in the cluster", "kagent-control-plane", nil) + }) + + t.Run("streaming_invocation", func(t *testing.T) { + runStreamingTest(t, a2aClient, "List all nodes in the cluster", "kagent-control-plane", taskResult.ContextID) + }) +} + func TestE2EInvokeExternalAgent(t *testing.T) { // Setup A2A client for external agent a2aURL := a2aUrl("kagent", "kebab-agent") diff --git a/ui/public/mockServiceWorker.js b/ui/public/mockServiceWorker.js index daa58d0f1..b17fcd650 100644 --- a/ui/public/mockServiceWorker.js +++ b/ui/public/mockServiceWorker.js @@ -7,7 +7,7 @@ * - Please do NOT modify this file. */ -const PACKAGE_VERSION = '2.12.10' +const PACKAGE_VERSION = '2.12.14' const INTEGRITY_CHECKSUM = '4db4a41e972cec1b64cc569c66952d82' const IS_MOCKED_RESPONSE = Symbol('isMockedResponse') const activeClientIds = new Set() diff --git a/ui/src/app/a2a-sandboxes/[namespace]/[agentName]/route.ts b/ui/src/app/a2a-sandboxes/[namespace]/[agentName]/route.ts new file mode 100644 index 000000000..c01b22243 --- /dev/null +++ b/ui/src/app/a2a-sandboxes/[namespace]/[agentName]/route.ts @@ -0,0 +1,144 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { getBackendUrl } from '@/lib/utils'; +import { getAuthHeadersFromRequest } from '@/lib/auth'; + +export async function POST( + request: NextRequest, + { params }: { params: Promise<{ namespace: string; agentName: string }> } +) { + const { namespace, agentName } = await params; + + try { + const a2aRequest = await request.json(); + + const backendUrl = getBackendUrl(); + const targetUrl = `${backendUrl}/a2a-sandboxes/${namespace}/${agentName}/`; + + const authHeaders = getAuthHeadersFromRequest(request); + + const backendResponse = await fetch(targetUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'text/event-stream', + 'Cache-Control': 'no-cache', + 'Connection': 'keep-alive', + 'User-Agent': 'kagent-ui', + ...authHeaders, + }, + body: JSON.stringify(a2aRequest), + }); + + if (!backendResponse.ok) { + const errorText = await backendResponse.text(); + return new Response(errorText || 'Backend request failed', { + status: backendResponse.status, + headers: { + 'Content-Type': 'text/plain', + } + }); + } + + if (!backendResponse.body) { + return new Response('Backend response body is null', { status: 500 }); + } + + const responseHeaders = new Headers({ + 'Content-Type': 'text/event-stream', + 'Cache-Control': 'no-cache', + 'Connection': 'keep-alive', + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type, Authorization, Accept', + }); + + const KEEP_ALIVE_INTERVAL_MS = 30000; + const stream = new ReadableStream({ + start(controller) { + const reader = backendResponse.body!.getReader(); + const decoder = new TextDecoder(); + let buffer = ""; + let isClosed = false; + let keepAliveTimer: NodeJS.Timeout | null = null; + + const sendKeepAlive = () => { + if (!isClosed) { + controller.enqueue(new TextEncoder().encode(': keep-alive\n\n')); + resetKeepAliveTimer(); + } + }; + + const resetKeepAliveTimer = () => { + if (keepAliveTimer) clearTimeout(keepAliveTimer); + keepAliveTimer = setTimeout(sendKeepAlive, KEEP_ALIVE_INTERVAL_MS); + }; + + const pump = (): Promise => { + return reader.read().then(({ done, value }): Promise => { + if (done) { + if (!isClosed) { + if (keepAliveTimer) clearTimeout(keepAliveTimer); + controller.close(); + isClosed = true; + console.log('A2A Sandbox Proxy: Backend connection closed.'); + } + + return Promise.resolve(); + } + + buffer += decoder.decode(value, { stream: true }); + + let eventEndIndex; + while ((eventEndIndex = buffer.indexOf('\n\n')) >= 0) { + const eventText = buffer.substring(0, eventEndIndex); + buffer = buffer.substring(eventEndIndex + 2); + + if (eventText.trim()) { + const eventData = eventText + '\n\n'; + if (!isClosed) { + controller.enqueue(new TextEncoder().encode(eventData)); + resetKeepAliveTimer(); + } + } + } + + return pump(); + }).catch(error => { + console.error('A2A Sandbox Proxy: Error in stream pump:', error); + if (keepAliveTimer) clearTimeout(keepAliveTimer); + + if (!isClosed) { + controller.error(error); + isClosed = true; + } + + return Promise.resolve(); + }); + }; + + pump(); + } + }); + + return new Response(stream, { + headers: responseHeaders, + status: backendResponse.status, + }); + + } catch (error) { + const errorMessage = error instanceof Error ? error.message : 'Internal server error'; + return NextResponse.json({ error: errorMessage }, { status: 500 }); + } +} + +export async function OPTIONS() { + return new Response(null, { + status: 200, + headers: { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type, Authorization, Accept', + 'Access-Control-Max-Age': '86400', + }, + }); +} diff --git a/ui/src/app/agents/[namespace]/[name]/chat/page.tsx b/ui/src/app/agents/[namespace]/[name]/chat/page.tsx index bd30e0aa3..bd33137aa 100644 --- a/ui/src/app/agents/[namespace]/[name]/chat/page.tsx +++ b/ui/src/app/agents/[namespace]/[name]/chat/page.tsx @@ -32,7 +32,7 @@ export default function ChatAgentPage({ params }: { params: Promise<{ name: stri setGate("ready"); return; } - if (!agentRes.data.runInSandbox) { + if (agentRes.data.workloadMode !== "sandbox") { setGate("ready"); return; } diff --git a/ui/src/app/agents/new/page.tsx b/ui/src/app/agents/new/page.tsx index 5bc7b0bc6..5d12cc811 100644 --- a/ui/src/app/agents/new/page.tsx +++ b/ui/src/app/agents/new/page.tsx @@ -141,7 +141,7 @@ function AgentPageContent({ isEditMode, agentName, agentNamespace }: AgentPageCo name: agent.metadata.name || "", namespace: agent.metadata.namespace || "", description: agent.spec?.description || "", - agentType: formAgentTypeFromApi(agent.spec.type, agentResponse.runInSandbox), + agentType: formAgentTypeFromApi(agent.spec.type, agentResponse.workloadMode), }; const useDeclarativeForm = agent.spec.type === "Declarative"; if (useDeclarativeForm) { diff --git a/ui/src/components/chat/ChatInterface.tsx b/ui/src/components/chat/ChatInterface.tsx index d03bd5290..9d5bf5cd4 100644 --- a/ui/src/components/chat/ChatInterface.tsx +++ b/ui/src/components/chat/ChatInterface.tsx @@ -337,7 +337,8 @@ export default function ChatInterface({ selectedAgentName, selectedNamespace, se selectedNamespace, selectedAgentName, sendParams, - abortControllerRef.current?.signal + abortControllerRef.current?.signal, + runInSandbox ); let timeoutTimer: NodeJS.Timeout | null = null; diff --git a/ui/src/components/chat/ChatLayoutUI.tsx b/ui/src/components/chat/ChatLayoutUI.tsx index 26756f4a5..9690eb0e1 100644 --- a/ui/src/components/chat/ChatLayoutUI.tsx +++ b/ui/src/components/chat/ChatLayoutUI.tsx @@ -108,7 +108,7 @@ export default function ChatLayoutUI({
{children} @@ -120,4 +120,4 @@ export default function ChatLayoutUI({ /> ); -} \ No newline at end of file +} diff --git a/ui/src/components/sidebars/SessionsSidebar.tsx b/ui/src/components/sidebars/SessionsSidebar.tsx index ddd57a62e..e239393f5 100644 --- a/ui/src/components/sidebars/SessionsSidebar.tsx +++ b/ui/src/components/sidebars/SessionsSidebar.tsx @@ -41,8 +41,8 @@ export default function SessionsSidebar({ agentName={agentName} agentNamespace={agentNamespace} sessions={agentSessions} - hideNewChat={!!currentAgent.runInSandbox} - hideSessionDelete={!!currentAgent.runInSandbox} + hideNewChat={currentAgent.workloadMode === "sandbox"} + hideSessionDelete={currentAgent.workloadMode === "sandbox"} /> )} diff --git a/ui/src/lib/a2aClient.ts b/ui/src/lib/a2aClient.ts index 95848dcd0..d62f21a1d 100644 --- a/ui/src/lib/a2aClient.ts +++ b/ui/src/lib/a2aClient.ts @@ -20,8 +20,9 @@ export class KagentA2AClient { /** * Get the A2A URL for a specific agent */ - getAgentUrl(namespace: string, agentName: string): string { - return `${this.baseUrl}/a2a/${namespace}/${agentName}`; + getAgentUrl(namespace: string, agentName: string, runInSandbox = false): string { + const prefix = runInSandbox ? "a2a-sandboxes" : "a2a"; + return `${this.baseUrl}/${prefix}/${namespace}/${agentName}`; } /** @@ -44,10 +45,13 @@ export class KagentA2AClient { namespace: string, agentName: string, params: MessageSendParams, - signal?: AbortSignal + signal?: AbortSignal, + runInSandbox = false ): Promise> { const request = this.createStreamingRequest(params); - const proxyUrl = `/a2a/${namespace}/${agentName}`; + const proxyUrl = runInSandbox + ? `/a2a-sandboxes/${namespace}/${agentName}` + : `/a2a/${namespace}/${agentName}`; const response = await fetch(proxyUrl, { method: 'POST', @@ -124,4 +128,4 @@ export class KagentA2AClient { } } -export const kagentA2AClient = new KagentA2AClient(); \ No newline at end of file +export const kagentA2AClient = new KagentA2AClient(); diff --git a/ui/src/lib/agentFormLayout.ts b/ui/src/lib/agentFormLayout.ts index 5ea9e4258..0e1785c9b 100644 --- a/ui/src/lib/agentFormLayout.ts +++ b/ui/src/lib/agentFormLayout.ts @@ -27,7 +27,7 @@ export function formUsesByoSections( /** Create-agent form type from GET /agents response (SandboxAgent → form "Sandbox"). */ export function formAgentTypeFromApi( specType: AgentType, - runInSandbox: boolean | undefined, + workloadMode: "deployment" | "sandbox" | undefined, ): AgentType { - return runInSandbox ? "Sandbox" : specType; + return workloadMode === "sandbox" ? "Sandbox" : specType; } diff --git a/ui/src/types/index.ts b/ui/src/types/index.ts index 81b9ef971..87fe16176 100644 --- a/ui/src/types/index.ts +++ b/ui/src/types/index.ts @@ -329,8 +329,19 @@ export interface AgentSkill { export interface Agent { + apiVersion?: string; + kind?: string; metadata: ResourceMetadata; spec: AgentSpec; + status?: { + observedGeneration?: number; + conditions?: Array<{ + type: string; + status: string; + reason?: string; + message?: string; + }>; + }; } export interface AgentResponse { @@ -342,8 +353,7 @@ export interface AgentResponse { tools: Tool[]; deploymentReady: boolean; accepted: boolean; - /** Set when the workload is reconciled as a SandboxAgent. */ - runInSandbox?: boolean; + workloadMode?: "deployment" | "sandbox"; } export interface RemoteMCPServer { @@ -529,4 +539,4 @@ export interface AdkRequestConfirmationData { name: string; id: string; args: HitlRequestConfirmationArgs; -} \ No newline at end of file +} From e141812e33e550d414c092eb5e4abb6c94e41ce4 Mon Sep 17 00:00:00 2001 From: Eitan Yarmush Date: Thu, 9 Apr 2026 20:42:59 +0000 Subject: [PATCH 09/13] Fix sandbox CI bootstrap and e2e lint Signed-off-by: Eitan Yarmush --- .github/workflows/ci.yaml | 12 ++++++------ go/core/test/e2e/invoke_api_test.go | 5 ----- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index a56e77114..50e06b6f2 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -75,7 +75,7 @@ jobs: run: | kubectl apply -f "https://github.com/kubernetes-sigs/agent-sandbox/releases/download/${AGENT_SANDBOX_VERSION}/manifest.yaml" kubectl wait --for=condition=Established crd/sandboxes.agents.x-k8s.io --timeout=90s - kubectl rollout status statefulset/agent-sandbox-controller -n agent-sandbox-system --timeout=120s + kubectl rollout status deployment/agent-sandbox-controller -n agent-sandbox-system --timeout=120s kubectl wait --for=condition=Ready pod -l app=agent-sandbox-controller -n agent-sandbox-system --timeout=120s - name: Install Kagent @@ -126,15 +126,15 @@ jobs: run: | # Upgrade helm to use namespace-scoped RBAC make helm-install-provider - + # Wait for controller to be ready after upgrade kubectl rollout status deployment/kagent-controller -n kagent --timeout=90s - + # Setup environment variables (reusing logic from previous step) HOST_IP=$(docker network inspect kind -f '{{range .IPAM.Config}}{{if .Gateway}}{{.Gateway}}{{"\n"}}{{end}}{{end}}' | grep -E '^[0-9]+\.' | head -1) export KAGENT_LOCAL_HOST=$HOST_IP export KAGENT_URL="http://$(kubectl get svc -n kagent kagent-controller -o jsonpath='{.status.loadBalancer.ingress[0].ip}'):8083" - + # Run critical tests with namespace-scoped RBAC to verify the controller didn't lose needed permissions cd go go test -v github.com/kagent-dev/kagent/go/core/test/e2e -run '^TestE2EInvokeInlineAgent$|^TestE2EInvokeDeclarativeAgentWithMcpServerTool$' -failfast @@ -146,8 +146,8 @@ jobs: kubectl describe pods -n kagent echo "::error::Kubectl get pods -n agent-sandbox-system" kubectl get pods -n agent-sandbox-system -o wide || true - echo "::error::Kubectl logs -n agent-sandbox-system statefulset/agent-sandbox-controller" - kubectl logs -n agent-sandbox-system statefulset/agent-sandbox-controller || true + echo "::error::Kubectl logs -n agent-sandbox-system deployment/agent-sandbox-controller" + kubectl logs -n agent-sandbox-system deployment/agent-sandbox-controller || true echo "::error::Kubectl get events -n kagent" kubectl get events -n kagent echo "::error::Kubectl get agents -n kagent" diff --git a/go/core/test/e2e/invoke_api_test.go b/go/core/test/e2e/invoke_api_test.go index 046a8db48..23b08ffe2 100644 --- a/go/core/test/e2e/invoke_api_test.go +++ b/go/core/test/e2e/invoke_api_test.go @@ -152,11 +152,6 @@ func setupAgent(t *testing.T, cli client.Client, modelConfigName string, tools [ return setupAgentWithOptions(t, cli, modelConfigName, tools, AgentOptions{}) } -// setupSandboxAgent creates and returns a sandbox agent resource, then waits for it to be ready. -func setupSandboxAgent(t *testing.T, cli client.Client, modelConfigName string, tools []*v1alpha2.Tool) *v1alpha2.SandboxAgent { - return setupSandboxAgentWithOptions(t, cli, modelConfigName, tools, AgentOptions{}) -} - // AgentOptions provides optional configuration for agent setup type AgentOptions struct { Name string From 6d8f7d9f09947fa71ec1cb7420d806d55fd139cc Mon Sep 17 00:00:00 2001 From: Eitan Yarmush Date: Thu, 9 Apr 2026 20:43:22 +0000 Subject: [PATCH 10/13] make .agents symlink to .claude for skills Signed-off-by: Eitan Yarmush --- .agents/skills/kagent | 1 + .agents/skills/kagent-dev | 1 + 2 files changed, 2 insertions(+) create mode 120000 .agents/skills/kagent create mode 120000 .agents/skills/kagent-dev diff --git a/.agents/skills/kagent b/.agents/skills/kagent new file mode 120000 index 000000000..1013ebce7 --- /dev/null +++ b/.agents/skills/kagent @@ -0,0 +1 @@ +../../.claude/skills/kagent \ No newline at end of file diff --git a/.agents/skills/kagent-dev b/.agents/skills/kagent-dev new file mode 120000 index 000000000..76cf5f52b --- /dev/null +++ b/.agents/skills/kagent-dev @@ -0,0 +1 @@ +../../.claude/skills/kagent-dev \ No newline at end of file From 9e188017bbf859e145e770b5b8d7f1d85665ecbe Mon Sep 17 00:00:00 2001 From: Eitan Yarmush Date: Thu, 9 Apr 2026 20:55:49 +0000 Subject: [PATCH 11/13] Set default workload type for agents Signed-off-by: Eitan Yarmush --- go/core/pkg/migrations/core/000003_agent_workload_type.up.sql | 1 + 1 file changed, 1 insertion(+) diff --git a/go/core/pkg/migrations/core/000003_agent_workload_type.up.sql b/go/core/pkg/migrations/core/000003_agent_workload_type.up.sql index d760a87d0..46b50bc32 100644 --- a/go/core/pkg/migrations/core/000003_agent_workload_type.up.sql +++ b/go/core/pkg/migrations/core/000003_agent_workload_type.up.sql @@ -1,3 +1,4 @@ ALTER TABLE agent ADD COLUMN IF NOT EXISTS workload_type TEXT; UPDATE agent SET workload_type = 'deployment' WHERE workload_type IS NULL; +ALTER TABLE agent ALTER COLUMN workload_type SET DEFAULT 'deployment'; ALTER TABLE agent ALTER COLUMN workload_type SET NOT NULL; From 6b2874f7d74ccdbd49a438ae43f2f31164cd92ca Mon Sep 17 00:00:00 2001 From: Eitan Yarmush Date: Thu, 9 Apr 2026 21:06:49 +0000 Subject: [PATCH 12/13] Simplify translator interface surface Signed-off-by: Eitan Yarmush --- .../translator/agent/adk_api_translator.go | 4 --- .../agent/adk_api_translator_test.go | 26 +++++++++---------- .../agent/adk_translator_golden_test.go | 2 +- .../controller/translator/agent/compiler.go | 7 ++--- .../translator/agent/git_skills_test.go | 6 ++--- .../translator/agent/mcp_validation_test.go | 12 ++++----- .../controller/translator/agent/proxy_test.go | 10 +++---- .../translator/agent/runtime_test.go | 8 +++--- .../translator/agent/security_context_test.go | 10 +++---- 9 files changed, 41 insertions(+), 44 deletions(-) diff --git a/go/core/internal/controller/translator/agent/adk_api_translator.go b/go/core/internal/controller/translator/agent/adk_api_translator.go index 35fe9fa36..c4873e35c 100644 --- a/go/core/internal/controller/translator/agent/adk_api_translator.go +++ b/go/core/internal/controller/translator/agent/adk_api_translator.go @@ -123,10 +123,6 @@ type AdkApiTranslator interface { agent v1alpha2.AgentObject, inputs *AgentManifestInputs, ) (*AgentOutputs, error) - TranslateAgent( - ctx context.Context, - agent v1alpha2.AgentObject, - ) (*AgentOutputs, error) GetOwnedResourceTypes() []client.Object } diff --git a/go/core/internal/controller/translator/agent/adk_api_translator_test.go b/go/core/internal/controller/translator/agent/adk_api_translator_test.go index 284b37b67..0d19b5a93 100644 --- a/go/core/internal/controller/translator/agent/adk_api_translator_test.go +++ b/go/core/internal/controller/translator/agent/adk_api_translator_test.go @@ -181,7 +181,7 @@ func Test_AdkApiTranslator_CrossNamespaceAgentTool(t *testing.T) { trans := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil) - _, err := trans.TranslateAgent(context.Background(), tt.sourceAgent) + _, err := translator.TranslateAgent(context.Background(), trans, tt.sourceAgent) if tt.wantErr { require.Error(t, err) @@ -344,7 +344,7 @@ func Test_AdkApiTranslator_CrossNamespaceRemoteMCPServer(t *testing.T) { trans := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil) - _, err := trans.TranslateAgent(context.Background(), tt.agent) + _, err := translator.TranslateAgent(context.Background(), trans, tt.agent) if tt.wantErr { require.Error(t, err) @@ -418,7 +418,7 @@ func Test_AdkApiTranslator_OllamaOptions(t *testing.T) { trans := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil) - outputs, err := trans.TranslateAgent(context.Background(), agent) + outputs, err := translator.TranslateAgent(context.Background(), trans, agent) require.NoError(t, err) require.NotNil(t, outputs) require.NotNil(t, outputs.Config) @@ -537,7 +537,7 @@ func Test_AdkApiTranslator_ServiceAccountNameOverride(t *testing.T) { trans := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil) - outputs, err := trans.TranslateAgent(context.Background(), tt.agent) + outputs, err := translator.TranslateAgent(context.Background(), trans, tt.agent) require.NoError(t, err) require.NotNil(t, outputs) @@ -684,7 +684,7 @@ func Test_AdkApiTranslator_RecursionDepthTracking(t *testing.T) { kubeClient := builder.Build() trans := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil) - _, err := trans.TranslateAgent(context.Background(), root) + _, err := translator.TranslateAgent(context.Background(), trans, root) require.NoError(t, err, "flat list of 12 agent tools should not hit recursion limit") }) @@ -728,7 +728,7 @@ func Test_AdkApiTranslator_RecursionDepthTracking(t *testing.T) { kubeClient := builder.Build() trans := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil) - _, err := trans.TranslateAgent(context.Background(), agents[0]) + _, err := translator.TranslateAgent(context.Background(), trans, agents[0]) require.NoError(t, err, "deep nesting of 10 levels should pass") }) @@ -771,7 +771,7 @@ func Test_AdkApiTranslator_RecursionDepthTracking(t *testing.T) { kubeClient := builder.Build() trans := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil) - _, err := trans.TranslateAgent(context.Background(), agents[0]) + _, err := translator.TranslateAgent(context.Background(), trans, agents[0]) require.Error(t, err, "deep nesting of 12 levels should fail") assert.Contains(t, err.Error(), "recursion limit reached") }) @@ -826,7 +826,7 @@ func Test_AdkApiTranslator_RecursionDepthTracking(t *testing.T) { WithObjects(modelConfig, agentA, agentB).Build() trans := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil) - _, err := trans.TranslateAgent(context.Background(), agentA) + _, err := translator.TranslateAgent(context.Background(), trans, agentA) require.Error(t, err, "cycle A->B->A should be detected") assert.Contains(t, err.Error(), "cycle detected") }) @@ -912,7 +912,7 @@ func Test_AdkApiTranslator_RecursionDepthTracking(t *testing.T) { WithObjects(modelConfig, agentA, agentB, agentC, agentD).Build() trans := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil) - _, err := trans.TranslateAgent(context.Background(), agentA) + _, err := translator.TranslateAgent(context.Background(), trans, agentA) require.NoError(t, err, "diamond pattern should pass — D is not a cycle, just shared") }) } @@ -1118,7 +1118,7 @@ func Test_AdkApiTranslator_MergeDeploymentData(t *testing.T) { defaultModel := types.NamespacedName{Namespace: "default", Name: tt.agentModel.Name} trans := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil) - outputs, err := trans.TranslateAgent(context.Background(), makeAgent(tt.agentModel.Name, tt.summModel.Name)) + outputs, err := translator.TranslateAgent(context.Background(), trans, makeAgent(tt.agentModel.Name, tt.summModel.Name)) require.NoError(t, err) require.NotNil(t, outputs) @@ -1270,7 +1270,7 @@ func Test_AdkApiTranslator_ContextConfig(t *testing.T) { defaultModel := types.NamespacedName{Namespace: "default", Name: "test-model"} trans := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil) - outputs, err := trans.TranslateAgent(context.Background(), tt.agent) + outputs, err := translator.TranslateAgent(context.Background(), trans, tt.agent) if tt.wantErr { require.Error(t, err) @@ -1326,7 +1326,7 @@ func Test_AdkApiTranslator_SandboxAgent_defaultEmitsSandbox(t *testing.T) { "", agentsxk8s.New(), ) - outputs, err := trans.TranslateAgent(ctx, sa) + outputs, err := translator.TranslateAgent(ctx, trans, sa) require.NoError(t, err) require.NotNil(t, outputs) @@ -1378,7 +1378,7 @@ func Test_AdkApiTranslator_SandboxAgent_BYOEmitsSandbox(t *testing.T) { "", agentsxk8s.New(), ) - outputs, err := trans.TranslateAgent(ctx, sa) + outputs, err := translator.TranslateAgent(ctx, trans, sa) require.NoError(t, err) require.NotNil(t, outputs) diff --git a/go/core/internal/controller/translator/agent/adk_translator_golden_test.go b/go/core/internal/controller/translator/agent/adk_translator_golden_test.go index 9cc1d99fb..31a91dff7 100644 --- a/go/core/internal/controller/translator/agent/adk_translator_golden_test.go +++ b/go/core/internal/controller/translator/agent/adk_translator_golden_test.go @@ -178,7 +178,7 @@ func runGoldenTest(t *testing.T, inputFile, outputsDir, testName string, updateG // Use proxy URL from test input if provided proxyURL := testInput.ProxyURL - result, err = translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, proxyURL, nil).TranslateAgent(ctx, agent) + result, err = translator.TranslateAgent(ctx, translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, proxyURL, nil), agent) require.NoError(t, err) default: diff --git a/go/core/internal/controller/translator/agent/compiler.go b/go/core/internal/controller/translator/agent/compiler.go index 7be78c987..403014b34 100644 --- a/go/core/internal/controller/translator/agent/compiler.go +++ b/go/core/internal/controller/translator/agent/compiler.go @@ -44,15 +44,16 @@ func (t *tState) isVisited(agentName string) bool { return slices.Contains(t.visitedAgents, agentName) } -func (a *adkApiTranslator) TranslateAgent( +func TranslateAgent( ctx context.Context, + translator AdkApiTranslator, agent v1alpha2.AgentObject, ) (*AgentOutputs, error) { - inputs, err := a.CompileAgent(ctx, agent) + inputs, err := translator.CompileAgent(ctx, agent) if err != nil { return nil, err } - return a.BuildManifest(ctx, agent, inputs) + return translator.BuildManifest(ctx, agent, inputs) } func (a *adkApiTranslator) CompileAgent( diff --git a/go/core/internal/controller/translator/agent/git_skills_test.go b/go/core/internal/controller/translator/agent/git_skills_test.go index 4ce06ae11..aeabd3640 100644 --- a/go/core/internal/controller/translator/agent/git_skills_test.go +++ b/go/core/internal/controller/translator/agent/git_skills_test.go @@ -301,7 +301,7 @@ func Test_AdkApiTranslator_Skills(t *testing.T) { trans := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil) - outputs, err := trans.TranslateAgent(context.Background(), tt.agent) + outputs, err := translator.TranslateAgent(context.Background(), trans, tt.agent) require.NoError(t, err) require.NotNil(t, outputs) @@ -486,7 +486,7 @@ func Test_AdkApiTranslator_SkillsConfigurableImage(t *testing.T) { } trans := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil) - outputs, err := trans.TranslateAgent(context.Background(), agent) + outputs, err := translator.TranslateAgent(context.Background(), trans, agent) require.NoError(t, err) var deployment *appsv1.Deployment @@ -683,7 +683,7 @@ func Test_AdkApiTranslator_SkillsInitContainer(t *testing.T) { Build() trans := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil) - outputs, err := trans.TranslateAgent(context.Background(), tt.agent) + outputs, err := translator.TranslateAgent(context.Background(), trans, tt.agent) require.NoError(t, err) var deployment *appsv1.Deployment diff --git a/go/core/internal/controller/translator/agent/mcp_validation_test.go b/go/core/internal/controller/translator/agent/mcp_validation_test.go index 57fc03f1b..673cd158a 100644 --- a/go/core/internal/controller/translator/agent/mcp_validation_test.go +++ b/go/core/internal/controller/translator/agent/mcp_validation_test.go @@ -98,7 +98,7 @@ func TestMCPServerValidation_InvalidPort(t *testing.T) { ) // TranslateAgent should fail with error about invalid port - _, err = translator.TranslateAgent(ctx, agent) + _, err = agenttranslator.TranslateAgent(ctx, translator, agent) require.Error(t, err) assert.Contains(t, err.Error(), "cannot determine port") assert.Contains(t, err.Error(), "test-mcp-server") @@ -184,7 +184,7 @@ func TestMCPServerValidation_ValidPort(t *testing.T) { ) // TranslateAgent should succeed - outputs, err := translator.TranslateAgent(ctx, agent) + outputs, err := agenttranslator.TranslateAgent(ctx, translator, agent) require.NoError(t, err) assert.NotNil(t, outputs) assert.NotNil(t, outputs.Config) @@ -255,7 +255,7 @@ func TestMCPServerValidation_NotFound(t *testing.T) { ) // TranslateAgent should fail with not found error - _, err = translator.TranslateAgent(ctx, agent) + _, err = agenttranslator.TranslateAgent(ctx, translator, agent) require.Error(t, err) assert.True(t, apierrors.IsNotFound(err)) } @@ -317,7 +317,7 @@ func TestMCPServerValidation_NoMCPServerReference(t *testing.T) { ) // TranslateAgent should fail with provider or tool server error - _, err = translator.TranslateAgent(ctx, agent) + _, err = agenttranslator.TranslateAgent(ctx, translator, agent) require.Error(t, err) assert.Contains(t, err.Error(), "tool must have a provider or tool server") } @@ -396,7 +396,7 @@ func TestMCPServerValidation_RemoteMCPServer(t *testing.T) { ) // TranslateAgent should succeed - RemoteMCPServer doesn't have port validation - outputs, err := translator.TranslateAgent(ctx, agent) + outputs, err := agenttranslator.TranslateAgent(ctx, translator, agent) require.NoError(t, err) assert.NotNil(t, outputs) assert.NotNil(t, outputs.Config) @@ -555,7 +555,7 @@ func TestMCPServerValidation_MultipleTools(t *testing.T) { ) // TranslateAgent should fail because one of the MCPServers is invalid - _, err = translator.TranslateAgent(ctx, agent) + _, err = agenttranslator.TranslateAgent(ctx, translator, agent) require.Error(t, err) assert.Contains(t, err.Error(), "cannot determine port") assert.Contains(t, err.Error(), "invalid-mcp-server") diff --git a/go/core/internal/controller/translator/agent/proxy_test.go b/go/core/internal/controller/translator/agent/proxy_test.go index 72ed440df..b598a1260 100644 --- a/go/core/internal/controller/translator/agent/proxy_test.go +++ b/go/core/internal/controller/translator/agent/proxy_test.go @@ -119,7 +119,7 @@ func TestProxyConfiguration_ThroughTranslateAgent(t *testing.T) { nil, ) - result, err := translator.TranslateAgent(ctx, agent) + result, err := agenttranslator.TranslateAgent(ctx, translator, agent) require.NoError(t, err) require.NotNil(t, result) require.NotNil(t, result.Config) @@ -149,7 +149,7 @@ func TestProxyConfiguration_ThroughTranslateAgent(t *testing.T) { nil, ) - result, err := translator.TranslateAgent(ctx, agent) + result, err := agenttranslator.TranslateAgent(ctx, translator, agent) require.NoError(t, err) require.NotNil(t, result) require.NotNil(t, result.Config) @@ -251,7 +251,7 @@ func TestProxyConfiguration_RemoteMCPServer_ExternalURL(t *testing.T) { nil, ) - result, err := translator.TranslateAgent(ctx, agent) + result, err := agenttranslator.TranslateAgent(ctx, translator, agent) require.NoError(t, err) require.NotNil(t, result) require.NotNil(t, result.Config) @@ -344,7 +344,7 @@ func TestProxyConfiguration_MCPServer(t *testing.T) { nil, ) - result, err := translator.TranslateAgent(ctx, agent) + result, err := agenttranslator.TranslateAgent(ctx, translator, agent) require.NoError(t, err) require.NotNil(t, result) require.NotNil(t, result.Config) @@ -442,7 +442,7 @@ func TestProxyConfiguration_Service(t *testing.T) { nil, ) - result, err := translator.TranslateAgent(ctx, agent) + result, err := agenttranslator.TranslateAgent(ctx, translator, agent) require.NoError(t, err) require.NotNil(t, result) require.NotNil(t, result.Config) diff --git a/go/core/internal/controller/translator/agent/runtime_test.go b/go/core/internal/controller/translator/agent/runtime_test.go index dca524320..52be85a45 100644 --- a/go/core/internal/controller/translator/agent/runtime_test.go +++ b/go/core/internal/controller/translator/agent/runtime_test.go @@ -65,7 +65,7 @@ func TestRuntime_GoRuntime(t *testing.T) { translatorInstance := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil) // Translate agent - result, err := translatorInstance.TranslateAgent(ctx, agent) + result, err := translator.TranslateAgent(ctx, translatorInstance, agent) require.NoError(t, err) require.NotNil(t, result) @@ -140,7 +140,7 @@ func TestRuntime_PythonRuntime(t *testing.T) { translatorInstance := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil) // Translate agent - result, err := translatorInstance.TranslateAgent(ctx, agent) + result, err := translator.TranslateAgent(ctx, translatorInstance, agent) require.NoError(t, err) require.NotNil(t, result) @@ -215,7 +215,7 @@ func TestRuntime_DefaultToPython(t *testing.T) { translatorInstance := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil) // Translate agent - result, err := translatorInstance.TranslateAgent(ctx, agent) + result, err := translator.TranslateAgent(ctx, translatorInstance, agent) require.NoError(t, err) require.NotNil(t, result) @@ -299,7 +299,7 @@ func TestRuntime_CustomRepositoryPath(t *testing.T) { translatorInstance := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil) // Translate agent - result, err := translatorInstance.TranslateAgent(ctx, agent) + result, err := translator.TranslateAgent(ctx, translatorInstance, agent) require.NoError(t, err) require.NotNil(t, result) diff --git a/go/core/internal/controller/translator/agent/security_context_test.go b/go/core/internal/controller/translator/agent/security_context_test.go index fa0e02d25..ae7149ea1 100644 --- a/go/core/internal/controller/translator/agent/security_context_test.go +++ b/go/core/internal/controller/translator/agent/security_context_test.go @@ -86,7 +86,7 @@ func TestSecurityContext_AppliedToPodSpec(t *testing.T) { translatorInstance := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil) // Translate agent - result, err := translatorInstance.TranslateAgent(ctx, agent) + result, err := translator.TranslateAgent(ctx, translatorInstance, agent) require.NoError(t, err) require.NotNil(t, result) @@ -176,7 +176,7 @@ func TestSecurityContext_OnlyPodSecurityContext(t *testing.T) { } translatorInstance := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil) - result, err := translatorInstance.TranslateAgent(ctx, agent) + result, err := translator.TranslateAgent(ctx, translatorInstance, agent) require.NoError(t, err) var deployment *appsv1.Deployment @@ -251,7 +251,7 @@ func TestSecurityContext_OnlyContainerSecurityContext(t *testing.T) { } translatorInstance := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil) - result, err := translatorInstance.TranslateAgent(ctx, agent) + result, err := translator.TranslateAgent(ctx, translatorInstance, agent) require.NoError(t, err) var deployment *appsv1.Deployment @@ -325,7 +325,7 @@ func TestSecurityContext_SkillsDefaultPrivilegedSandbox(t *testing.T) { } translatorInstance := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil) - result, err := translatorInstance.TranslateAgent(ctx, agent) + result, err := translator.TranslateAgent(ctx, translatorInstance, agent) require.NoError(t, err) var deployment *appsv1.Deployment @@ -409,7 +409,7 @@ func TestSecurityContext_SkillsPSSRestricted(t *testing.T) { } translatorInstance := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil) - result, err := translatorInstance.TranslateAgent(ctx, agent) + result, err := translator.TranslateAgent(ctx, translatorInstance, agent) require.NoError(t, err) var deployment *appsv1.Deployment From b84bd477eff5f21ebe7e28745389ea64ddd89b99 Mon Sep 17 00:00:00 2001 From: Eitan Yarmush Date: Thu, 9 Apr 2026 21:12:22 +0000 Subject: [PATCH 13/13] Bump agent-sandbox CI pin to latest release Signed-off-by: Eitan Yarmush --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 50e06b6f2..f3f58eb4d 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -15,7 +15,7 @@ env: # Cache key components for better organization CACHE_KEY_PREFIX: kagent-v2 BRANCH_CACHE_KEY: ${{ github.head_ref || github.ref_name }} - AGENT_SANDBOX_VERSION: v0.2.1 + AGENT_SANDBOX_VERSION: v0.3.10 # Consistent builder configuration BUILDX_BUILDER_NAME: kagent-builder-v0.23.0 BUILDX_VERSION: v0.23.0