diff --git a/.github/workflows/dark-factory.yml b/.github/workflows/dark-factory.yml new file mode 100644 index 00000000..53250faa --- /dev/null +++ b/.github/workflows/dark-factory.yml @@ -0,0 +1,77 @@ +# Dark Factory (Flow B) trigger β€” issue labeled `dark-factory` β†’ orchestrator. +# +# This is the P1 entrypoint (Β§4 step 1). It gates on the label, mints a +# short-TTL token scoped to this repo, and POSTs the run request to the +# orchestrator running on spoke-dev. The orchestrator (not this Action) holds +# the long-lived credentials and drives the sandbox; the Action's only job is +# to authenticate the trigger and hand over a short-lived token. +# +# The token here is the workflow's GITHUB_TOKEN (auto-expires when the run +# ends). For cross-repo / org installs, swap in a GitHub App token minted via +# actions/create-github-app-token β€” the orchestrator treats it the same way. +name: dark-factory + +on: + issues: + types: [labeled] + +# Least-privilege: the orchestrator opens the PR + updates the sticky comment +# using the token we pass, so it needs write on contents + PRs + issues. +permissions: + contents: write + pull-requests: write + issues: write + +jobs: + dispatch: + # Only fire when the `dark-factory` label is the one that was added. + if: github.event.label.name == 'dark-factory' + runs-on: ubuntu-latest + steps: + - name: Acknowledge on the issue + uses: actions/github-script@v7 + with: + script: | + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: '🏭 Dark Factory accepted this issue β€” claiming a warm Kata sandbox on the hub…', + }); + + - name: Submit df-run to Argo Workflows + env: + # argo-server events endpoint base, e.g. https:///argo-workflows + # Configure as a repo/org variable so the host isn't hard-coded. + ARGO_SERVER: ${{ vars.DARK_FACTORY_ARGO_SERVER }} + # Bearer token for the argo-server events API (a token bound to the + # dark-factory-workflow SA, or an SSO token). Stored as a repo secret. + ARGO_TOKEN: ${{ secrets.DARK_FACTORY_ARGO_TOKEN }} + ISSUE_ID: ${{ github.event.issue.id }} + ISSUE_NUMBER: ${{ github.event.issue.number }} + ISSUE_TITLE: ${{ github.event.issue.title }} + ISSUE_BODY: ${{ github.event.issue.body }} + REPO: ${{ github.repository }} + BASE: ${{ github.event.repository.default_branch }} + run: | + set -euo pipefail + if [ -z "${ARGO_SERVER:-}" ]; then + echo "::error::DARK_FACTORY_ARGO_SERVER is not set β€” cannot submit." + exit 1 + fi + # POST the issue to the WorkflowEventBinding 'dark-factory' in the argo + # namespace. The discriminator header + payload.issue selector gate it. + payload="$(jq -n \ + --arg id "$ISSUE_ID" \ + --arg number "$ISSUE_NUMBER" \ + --arg repo "$REPO" \ + --arg title "$ISSUE_TITLE" \ + --arg body "$ISSUE_BODY" \ + --arg base "$BASE" \ + '{issue: {id: $id, number: ($number|tonumber), repo: $repo, title: $title, body: $body, base: $base}}')" + curl -fsS -X POST "${ARGO_SERVER}/api/v1/events/argo/dark-factory" \ + -H "Content-Type: application/json" \ + -H "x-dark-factory: true" \ + -H "Authorization: Bearer ${ARGO_TOKEN}" \ + -d "$payload" + echo "Submitted df-run for issue #$ISSUE_NUMBER to Argo Workflows." diff --git a/.gitignore b/.gitignore index 568bff5e..6c43881a 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,10 @@ config.local.yaml private/ .local/ + +# Dark Factory IAM terraform working files (committed: iam/*.tf; ignored: state/plugins) +gitops/addons/charts/dark-factory/iam/.terraform/ +gitops/addons/charts/dark-factory/iam/.terraform.lock.hcl +gitops/addons/charts/dark-factory/iam/terraform.tfstate +gitops/addons/charts/dark-factory/iam/terraform.tfstate.backup +gitops/addons/charts/dark-factory/iam/_provider.tf diff --git a/README.md b/README.md index 8110945d..0244dcac 100644 --- a/README.md +++ b/README.md @@ -1,75 +1,154 @@ -# Open Agentic Platform on Amazon EKS +# Open Agentic Platform (OAP) on Amazon EKS -An AI agent platform built on Amazon EKS, featuring Bifrost LLM gateway, Langfuse observability, AgentGateway (A2A MCP auth), and multi-agent orchestration. +**A complete, production-shaped platform for building, running, securing, and observing AI agents on +Amazon EKS β€” installed with one command.** + +OAP turns a set of EKS clusters into an agent platform: a place where a team can onboard a model, +declare an agent as a Kubernetes resource, give it tools (MCP), memory, a browser, and a code +interpreter, wire multiple agents together (A2A), secure every hop with real identity, and watch it +all through end-to-end traces β€” entirely via GitOps. It ships with a hands-on workshop and two +flagship patterns: a **multi-agent financial-services** system and the **Dark Factory** autonomous +coding pipeline running in hardware-isolated micro-VM sandboxes. + +Everything is declarative and GitOps-driven (ArgoCD + Crossplane + KubeVela). You describe intent; +the platform reconciles it across a hub and any number of spoke clusters. + +--- + +## Platform at a glance + +Agent workloads sit on a layered stack β€” **Agent Platform Capabilities (APC)** on top of a +platform-engineering foundation, all on Amazon EKS Auto Mode: + +

+ OAP layered architecture +

+ +> _Diagrams are editable draw.io sources under [`docs/architecture/diagrams/src/`](docs/architecture/diagrams/src/) +> (open in [draw.io](https://app.diagrams.net) or the VS Code Draw.io extension) with SVGs exported to `img/`._ + +--- + +## Why OAP + +- **Agents as first-class Kubernetes resources** β€” `kind: Agent`, `kind: RemoteMCPServer`. Declare, + version, blue/green, and roll back agents like any other workload. +- **Batteries included** β€” model gateway, identity, tool gateway, runtime, memory, browser, code + interpreter, observability, and hardware isolation are pre-wired addons, not homework. +- **Real security posture** β€” Keycloak OIDC with JWT-enforced clientβ†’agent, agentβ†’tool, and + agentβ†’agent authorization, plus per-workload LLM identity. The "lethal trifecta" is designed out. +- **Runs the hard patterns** β€” multi-agent orchestration, and an autonomous coding factory where + untrusted, code-writing agents run inside **Kata micro-VMs** next to your control plane, safely. +- **One-command install, GitOps forever after** β€” `task install` provisions the hub, spokes, and + every capability; after that, git is the source of truth. + +--- ## Quick Start ### Prerequisites -- AWS account with Bedrock access -- [Task](https://taskfile.dev), kubectl, Helm 3.x, AWS CLI, `yq` -- Podman or Docker (for Kind-based bootstrap) +- AWS account with Amazon Bedrock access +- [Task](https://taskfile.dev), `kubectl`, Helm 3.x, AWS CLI, `yq` +- Podman or Docker (for the Kind-based bootstrap) +- A domain with an ACM cert + Route53 zone (ingress), and IAM Identity Center (ArgoCD SSO) ### Install ```bash # 1. Configure cp config.yaml config.local.yaml -# Edit config.local.yaml with your values +# Edit config.local.yaml with your AWS / domain / SSO values -# 2. Install everything (platform + agentic components) +# 2. Install everything (platform + spokes + agentic capabilities) task install ``` -That's it. The installer provisions an EKS hub cluster, deploys the base platform (ArgoCD, Crossplane, observability), then layers on the agentic components. +That's it. The installer bootstraps from Kind, provisions an EKS **hub** cluster, deploys the base +platform (ArgoCD, Crossplane, observability), provisions optional **spoke** clusters, then layers on +the agentic capabilities as ArgoCD-managed addons. The Kind bootstrap is destroyed once the hub is +self-managing. -### Configuration +--- -Edit `config.local.yaml`: +## Agent Platform Capabilities (APC) -| Section | Key Fields | Description | -|---------|-----------|-------------| -| `platform` | `repo`, `ref` | Base platform repo and version tag | -| `aws` | `region`, `accountId`, `profile` | AWS settings | -| `hub` | `clusterName`, `kubernetesVersion` | Hub cluster config | -| `domain` | | Ingress domain (must have ACM cert + Route53 zone) | -| `identityCenter` | `instanceArn`, `region`, `adminGroupId` | SSO for ArgoCD | -| `agenticRepo` | `url`, `revision`, `basepath` | This repo's git coordinates (for ArgoCD) | -| `spokes` | | Optional spoke clusters (see below) | +Each capability is a GitOps addon (`gitops/addons/charts/`), gated per cluster via ArgoCD +ApplicationSets. Status reflects the current reference deployment. -### Spoke Clusters +| Capability | Delivered by | Status | What it gives you | +|---|---|---|---| +| **Model as a Service** | `bifrost` (platform) Β· `litellm` (workshop) | βœ… | LLM gateway to Bedrock with routing, fallbacks, rate limiting, caching, cost tracking. Onboard a model declaratively. | +| **Agent Identities** | `agent-gateway` + Keycloak | βœ… | OIDC identities for agents, users, and MCP clients (`platform` realm). | +| **Agent Gateway** | `agent-gateway`, `gateway-api-crds` | βœ… | A2A + MCP gateway with JWT-auth policies enforced on every call. | +| **Agent Runtime** | `crossplane-agentcore` | βœ… | Crossplane compositions for Amazon Bedrock AgentCore (`agentruntimes` CRD). | +| **Agent Lifecycle** | `oam-agent-components` + KAgent | βœ… | Declarative `Agent` CRDs, KubeVela OAM components, blue/green via ArgoCD. | +| **Agent Observability** | `otel-collector`, `langfuse`, Jaeger, AMP, AMG | βœ… | End-to-end traces (userβ†’agentβ†’toolβ†’agent), LLM traces/cost, metrics + dashboards. | +| **Agent Memory** | `crossplane-agentcore` | ⚠️ CRD ready | `memories.*` CRDs registered; wire an instance for persistent memory. | +| **Agent Browser** | `crossplane-agentcore` | ⚠️ CRD ready | Managed headless browser resource for agents that browse. | +| **Agent Code Interpreter** | `crossplane-agentcore` / sandbox | ⚠️ CRD ready | Sandboxed code execution for agents. | +| **Agent Isolation** | `agent-sandbox` | βœ… (V2) | **Kata + Cloud Hypervisor micro-VMs** β€” hardware-isolated, credential-less sandboxes for untrusted agent code. | +| **Agent Evaluation** | (planned) | ⬜ | Eval tooling (AgentCore Evals / RAGAS) β€” roadmap. | -Add spoke clusters for workload environments: +**Gateway note:** the platform ships **Bifrost** as the enabled AI gateway (per-workload virtual +keys, model routing); **LiteLLM** is included as an alternative chart and is the gateway used in the +workshop teaching path. Both front Amazon Bedrock. -```yaml -spokes: - dev: - region: us-west-2 - kubernetesVersion: "1.35" - vpcCidr: "10.1.0.0/16" - autoMode: true - prod: - region: us-west-2 - kubernetesVersion: "1.35" - vpcCidr: "10.2.0.0/16" - autoMode: true -``` +--- + +## Provisioning & Topology -Spokes are provisioned via Crossplane from the hub. Agentic components deploy to all clusters automatically. +OAP uses a **hub + spokes** model, provisioned declaratively: -### Fleet Management & Targeting +- **Hub cluster** β€” runs ArgoCD, Crossplane, the platform control plane, and (by default) the agentic + capabilities. Crossplane on the hub provisions the spokes. +- **Spoke clusters** (`dev`, `prod`, …) β€” workload environments; agentic addons deploy to them + automatically based on their `environment` label. +- **Kind bootstrap** β€” a throwaway local cluster that stands up the hub, then self-destructs. -This repo controls which clusters receive the agentic platform via `gitops/overlays/environments/*/enabled-addons.yaml`: +Addons are targeted through a layered ApplicationSet model in `gitops/addons/`: + +``` +bootstrap/default/addons.yaml # master catalog: every addon, its chart path + selector +default/addons/ # values applied to all clusters +environments//addons # per-environment overrides (e.g. control-plane) +clusters//addons # per-cluster overrides (e.g. hub) +``` + +Enable/disable capabilities per environment via `gitops/overlays/environments//enabled-addons.yaml`: ```yaml # gitops/overlays/environments/dev/enabled-addons.yaml enabledAddons: - agent_platform: true # deploy agentic components to dev clusters + agent_platform: true # deploy agentic capabilities to dev + bifrost: true + agent_sandbox: true ``` -Set `agent_platform: false` to exclude an environment. Fleet member definitions in `gitops/fleet/members/` control spoke discovery. +### Configuration (`config.local.yaml`) -## Available Commands +| Section | Key Fields | Description | +|---------|-----------|-------------| +| `platform` | `repo`, `ref` | Base platform repo (appmod-blueprints) + version | +| `aws` | `region`, `accountId`, `profile` | AWS settings | +| `hub` | `clusterName`, `kubernetesVersion` | Hub cluster config | +| `domain` | | Ingress domain (ACM cert + Route53 zone) | +| `identityCenter` | `instanceArn`, `region`, `adminGroupId` | SSO for ArgoCD | +| `agenticRepo` | `url`, `revision`, `basepath` | This repo's coordinates (for ArgoCD) | +| `spokes` | | Optional spoke clusters (below) | + +```yaml +spokes: + dev: { region: us-west-2, kubernetesVersion: "1.35", vpcCidr: "10.1.0.0/16", autoMode: true } + prod: { region: us-west-2, kubernetesVersion: "1.35", vpcCidr: "10.2.0.0/16", autoMode: true } +``` + +Spokes are provisioned via Crossplane from the hub. Fleet member definitions in `gitops/fleet/members/` +control spoke discovery. + +--- + +## Commands | Command | Description | |---------|-------------| @@ -77,75 +156,168 @@ Set `agent_platform: false` to exclude an environment. Fleet member definitions | `task platform:install` | Provision base EKS platform only | | `task spokes:install` | Provision spoke clusters only | | `task spokes:status` | Check spoke provisioning progress | -| `task agentic:install` | Deploy agentic components only | +| `task agentic:install` | Deploy agentic capabilities only | | `task status` | Show ArgoCD application status | | `task upgrade` | Upgrade everything | -| `task destroy` | Remove agentic components (keeps base platform) | +| `task destroy` | Remove agentic capabilities (keeps base platform) | | `task spokes:destroy` | Delete spoke clusters | +--- + ## Architecture ``` -β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” -β”‚ open-agentic-platform (this repo) β”‚ -β”‚ config.local.yaml β†’ task install β”‚ -β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ - β”‚ β”‚ - β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β” - β”‚ appmod-blueprintsβ”‚ β”‚ ArgoCD Applicationβ”‚ - β”‚ (base platform) β”‚ β”‚ (agentic addons) β”‚ - β”‚ read-only clone β”‚ β”‚ points to this repoβ”‚ - β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ - β”‚ β”‚ - β–Ό β–Ό - β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” - β”‚ EKS Hub Cluster β”‚ - β”‚ ArgoCD ─── watches both repos (read-only) β”‚ - β”‚ Crossplane ─── provisions spoke clusters β”‚ - β”‚ β”‚ - β”‚ Agentic: LiteLLM, Langfuse, Jaeger, β”‚ - β”‚ AgentGateway, Bifrost, AgentCore β”‚ - β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ open-agentic-platform (this repo) β”‚ +β”‚ config.local.yaml β†’ task install β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ β”‚ + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ appmod-blueprints β”‚ β”‚ ArgoCD Application β”‚ + β”‚ (base platform) β”‚ β”‚ (agentic addons) β”‚ + β”‚ read-only clone β”‚ β”‚ points to this repoβ”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ β”‚ + β–Ό β–Ό + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ EKS HUB CLUSTER β”‚ + β”‚ ArgoCD (GitOps) Β· Crossplane (provisions spokes) β”‚ + β”‚ Capabilities: Bifrost / LiteLLM Β· AgentGateway Β· Keycloak β”‚ + β”‚ Langfuse Β· Jaeger Β· OTEL Β· AMP / AMG β”‚ + β”‚ AgentCore (Crossplane) Β· Kata sandboxes β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ β”‚ + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ spoke: dev β”‚ β”‚ spoke: prod β”‚ + β”‚ agent workloadsβ”‚ β”‚ agent workloadsβ”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` -## Components - -| Component | Purpose | -|-----------|---------| -| **LiteLLM** | LLM gateway with rate limiting, caching, fallbacks | -| **Langfuse** | LLM observability β€” traces, costs, analytics | -| **Jaeger** | Distributed tracing for agent interactions | -| **AgentGateway** | MCP auth gateway with Keycloak OIDC | -| **Bifrost** | AI gateway for model routing | -| **AgentCore** | Crossplane compositions for Bedrock AgentCore | +--- ## Workshop -The `workshop/` directory contains hands-on examples: +`workshop/` is a hands-on path from zero to a secured, observable multi-agent system: | Module | Description | |--------|-------------| -| `00-initial-setup` | Bedrock + LiteLLM configuration | -| `01-first-agent` | Basic KAgent with Bedrock | -| `02-k8s-ops-agent` | Kubernetes operations agent | -| `03-multi-tool-agent` | Agent with MCP tool servers | -| `04-multi-agents` | Financial services multi-agent system | -| `05-observability` | Monitoring and tracing setup | +| `00-initial-setup` | Bedrock + model gateway configuration (Model as a Service) | +| `01-first-agent` | Deploy your first agent as a CRD | +| `02-k8s-ops-agent` | A Kubernetes operations agent | +| `03-multi-tool-agent` | Onboard MCP tool servers and wire them to an agent | +| `04-multi-agents` | Financial-services multi-agent orchestration (A2A) | +| `05-observability` | Tracing, LLM observability, metrics, dashboards | + +--- + +## Flagship Patterns + +### Multi-Agent Financial Services (A2A) +A `financial-advisor` agent delegates to specialist agents (portfolio, market, risk) over the A2A +protocol β€” every hop authenticated through the gateway and traced through OTEL / Jaeger / Langfuse. +See `workshop/04-multi-agents/financial-services`. + +### Dark Factory β€” autonomous coding in hardware-isolated sandboxes (V2) +The Dark Factory turns a **GitHub issue into a reviewed, merged PR, autonomously**: + +1. A labeled issue triggers an **Argo Workflow** on the hub. +2. A coding agent (Claude Code or Kiro CLI) implements the change inside a **credential-less Kata + micro-VM** β€” an untrusted, network-locked, hardware-isolated sandbox β€” and opens a PR. +3. The change is reviewed by the **real AWS DevOps Agent** (release readiness) and **AWS Security + Agent** (OWASP / secrets / IAM / dependency risk), plus a holdout gate and `terraform validate` / + ephemeral-namespace deploy tests β€” every step reporting onto the PR. +4. A human approves; a separate Argo workflow squash-merges and reaps the sandbox. + +

+ Dark Factory β€” issue to merged PR flow +

+ +> ### ⚠️ Prerequisite: "the agent never self-merges" requires branch protection +> +> The coder runs with its **own** low-privilege GitHub credential, separate from the +> orchestrator's (three scoped credentials β€” see +> [Β§10a](docs/dark-factory/README.md#10a-github-credentials-secrets-manager-setup)). That split is +> real, but it does **not** by itself make merging impossible: +> +> - The coder needs `Contents: write` to push its branch, and GitHub gates +> `PUT /repos/{o}/{r}/pulls/{n}/merge` on **Contents** for fine-grained tokens β€” so the coder's own +> token can call the merge endpoint. +> - It also needs `Pull requests: write` and `Commit statuses: write`, because it opens its own PR +> from inside the VM and self-reports `dark-factory/implementation`. +> +> **A protected default branch requiring the `dark-factory/*` checks is what actually enforces the +> human gate.** Without it, "the agent never self-merges" holds only because the agent's code chooses +> not to β€” not because it is prevented. Configure it before running the factory on anything you care +> about, and verify it is in force rather than assuming: +> +> ```bash +> gh api repos///branches/main/protection --jq '.required_status_checks.contexts' +> ``` +> +> **Branch protection and rulesets are unavailable on private repositories on the GitHub Free plan** β€” +> both APIs return `403 Upgrade to GitHub Pro or make this repository public`. On such a repo the +> merge gate cannot be enforced at all: make the target repo public, upgrade the plan, or accept that +> the pipeline could merge its own work. + +It is the platform's proof that you can run untrusted, code-writing agents safely alongside a control +plane. See [`docs/dark-factory/`](docs/dark-factory/) and [`examples/dark-factory/`](examples/dark-factory/). + +--- + +## Managed or open source β€” your choice, per capability + +OAP is **CNCF-aligned and cloud-agnostic**. Every capability can be backed by an **Amazon Bedrock +AgentCore** managed service **or** an **open-source alternative** β€” same declarative manifest, +different backend. Agents are onboarded either **imageless** (CRD-defined, no container to build) or +**BYO-image** (any OCI image / SDK). The abstraction layer (Kro / KubeVela orchestrating Crossplane Β· +ACK Β· OpenTofu) resolves your choice at deploy time. + +

+ OAP β€” AgentCore managed or OSS alternative per capability +

+ +### Capabilities β†’ charts (in this repo) + +| Addon chart | Capability | +|---|---| +| `bifrost`, `litellm` | Model as a Service / AI gateway | +| `agent-gateway`, `gateway-api-crds` | Agent Gateway + identity (A2A / MCP authz) | +| `crossplane-agentcore` | Runtime, Memory, Browser, Code Interpreter (Bedrock AgentCore) | +| `oam-agent-components` | Agent lifecycle (KubeVela OAM components) | +| `otel-collector`, `langfuse` | Observability (traces + LLM analytics) | +| `agent-sandbox` | Agent isolation (Kata + Cloud Hypervisor micro-VMs) | +| `dark-factory` | Autonomous coding pipeline (Argo Workflows + agents) | +| `application-sets` | ArgoCD ApplicationSet wiring | + +--- + +## Roadmap + +- **V2 (in progress):** agent sandboxes (βœ… Dark Factory + Kata), full Memory / Browser / Code-Interpreter + instances, self-service agent onboarding (Backstage). +- **V3:** AgentCore Gateway / Runtime extensions; agentic workflow engines (Camunda, Pega); evaluation + tooling (AgentCore Evals / RAGAS). + +--- + +## Documentation & Design + +Design docs and open work items β€” check these before starting a new feature: + +| Document | Covers | Open items | +|---|---|---| +| [`docs/architecture/`](docs/architecture/) | Agent identity & token exchange, platform architecture | β€” | +| [`docs/OBSERVABILITY.md`](docs/OBSERVABILITY.md) | Tracing, LLM observability, metrics/dashboards | β€” | +| [`docs/dark-factory/README.md`](docs/dark-factory/README.md) | Dark Factory design, flows, diagrams | β€” | +| [`docs/dark-factory/AGENT-INSTALL.md`](docs/dark-factory/AGENT-INSTALL.md) | Connecting the AWS DevOps + Security Agents | β€” | +| [`gitops/DEPLOYMENT.md`](gitops/DEPLOYMENT.md) | GitOps deployment runbook (ArgoCD bootstrap, addon enablement, Pod Identity) | LiteLLM Pod-Identity step superseded by declarative Crossplane in the bifrost chart | +| [`gitops/addons/charts/bifrost/DESIGN.md`](gitops/addons/charts/bifrost/DESIGN.md) | Bifrost AI Gateway + **per-workload Virtual Key** target design | ⚠️ `is_vk_mandatory` disabled β€” implement per-workload VK minting before enabling | +| [`platform/oam/DESIGN.md`](platform/oam/DESIGN.md) | KubeVela OAM `agent` / `mcp-server` ComponentDefinitions | β€” | +| [`applications/strands-agent-base/ARCHITECTURE.md`](applications/strands-agent-base/ARCHITECTURE.md) | Strands agent internals, gateway integration, A2A | References LiteLLM β€” superseded by Bifrost (`OpenAIModel` + `x-bf-vk`) | ## Resources -- [LiteLLM](https://docs.litellm.ai) -- [Langfuse](https://langfuse.com/docs) -- [Amazon Bedrock](https://aws.amazon.com/bedrock) +- [Amazon Bedrock](https://aws.amazon.com/bedrock) Β· [AgentCore](https://aws.amazon.com/bedrock/agentcore/) +- [Bifrost](https://github.com/maximhq/bifrost) Β· [LiteLLM](https://docs.litellm.ai) Β· [Langfuse](https://langfuse.com/docs) +- [Kata Containers](https://katacontainers.io) Β· [Argo Workflows](https://argoproj.github.io/workflows/) - [appmod-blueprints](https://github.com/aws-samples/appmod-blueprints) (base platform) - -## Design & Architecture - -Design documents and open work items β€” check these before starting a new feature: - -| Document | What it covers | Open items | -|---|---|---| -| [gitops/addons/charts/bifrost/DESIGN.md](gitops/addons/charts/bifrost/DESIGN.md) | Bifrost AI Gateway architecture, current state (no auth), and **per-workload Virtual Key minting** target design | ⚠️ `is_vk_mandatory` is disabled β€” implement per-workload VK minting via KubeVela workflow step before enabling | -| [platform/oam/DESIGN.md](platform/oam/DESIGN.md) | KubeVela OAM `agent` and `mcp-server` ComponentDefinition design decisions | β€” | -| [applications/strands-agent-base/ARCHITECTURE.md](applications/strands-agent-base/ARCHITECTURE.md) | Strands agent internals, LLM gateway integration, A2A protocol | References LiteLLM β€” superseded by Bifrost (`OpenAIModel` + `x-bf-vk`) | -| [gitops/DEPLOYMENT.md](gitops/DEPLOYMENT.md) | GitOps deployment runbook: ArgoCD bootstrap, addon enablement, Pod Identity setup | LiteLLM Pod Identity step is superseded by declarative Crossplane in bifrost chart | diff --git a/Taskfile.yml b/Taskfile.yml index 06efdf49..194415f4 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -17,6 +17,20 @@ vars: sh: yq -r '.aws.accountId' {{.CONFIG_FILE}} AWS_PROFILE: sh: yq -r '.aws.profile // "default"' {{.CONFIG_FILE}} + # On EC2 the profile name (e.g. "default") has no entry in ~/.aws/config, so the + # --profile flags below and the kubectl exec plugin (aws eks get-token) would + # dead-end. Generate a config whose profile resolves to the instance role via + # the credential provider chain (IMDS) and return its path. Off-instance, fall + # back to the user's existing AWS_CONFIG_FILE or ~/.aws/config (zero regression). + AWS_CONFIG_FILE: + sh: | + if curl -s -m 2 -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 60" >/dev/null 2>&1; then + mkdir -p "{{.ROOT_DIR}}/.platform/private" + printf '[default]\ncredential_source = Ec2InstanceMetadata\n' > "{{.ROOT_DIR}}/.platform/private/aws-config" + echo "{{.ROOT_DIR}}/.platform/private/aws-config" + else + echo "${AWS_CONFIG_FILE:-$HOME/.aws/config}" + fi HUB_CLUSTER_NAME: sh: yq -r '.hub.clusterName' {{.CONFIG_FILE}} DOMAIN: @@ -40,6 +54,13 @@ vars: AGENTIC_REPO_BASEPATH: sh: yq -r '.agenticRepo.basepath' {{.CONFIG_FILE}} +# Exported to every task's command environment. AWS_CONFIG_FILE must be a real +# env var (not just a template var) so the aws CLI and the kubectl exec plugin +# (aws eks get-token) resolve --profile against the EC2-aware config resolved above. +env: + AWS_PAGER: "" + AWS_CONFIG_FILE: "{{.AWS_CONFIG_FILE}}" + tasks: install: desc: Install the full agentic platform (base platform + spokes + agentic components) @@ -252,9 +273,14 @@ tasks: REVISION: "{{.AGENTIC_REPO_REVISION}}" BASEPATH: "{{.AGENTIC_REPO_BASEPATH}}" HUB_CLUSTER_NAME: "{{.HUB_CLUSTER_NAME}}" + # Reference model: the appset-chart generator is sourced from the platform + # repo (appmod-blueprints), not vendored here. These feed the bootstrap + # Application's source B. + PLATFORM_REPO_URL: "{{.PLATFORM_REPO}}" + PLATFORM_REPO_REVISION: "{{.PLATFORM_REF}}" cmds: - task: agentic:hub-oidc-annotation - - envsubst '${REPO_URL} ${REVISION} ${BASEPATH} ${HUB_CLUSTER_NAME}' < gitops/fleet/bootstrap/agent-platform-app.yaml | kubectl --context {{.HUB_CLUSTER_NAME}} apply -f - + - envsubst '${REPO_URL} ${REVISION} ${BASEPATH} ${HUB_CLUSTER_NAME} ${PLATFORM_REPO_URL} ${PLATFORM_REPO_REVISION}' < gitops/bootstrap/agent-platform-app.yaml | kubectl --context {{.HUB_CLUSTER_NAME}} apply -f - - printf '\nβœ“ Agentic platform bootstrap applied. ArgoCD will sync components to clusters with enable_agent_platform=true label (set via fleet overlays).\n' agentic:seed-observability: diff --git a/docs/architecture/diagrams/img/dark-factory-flow.svg b/docs/architecture/diagrams/img/dark-factory-flow.svg new file mode 100644 index 00000000..a2b46421 --- /dev/null +++ b/docs/architecture/diagrams/img/dark-factory-flow.svg @@ -0,0 +1,3 @@ + + +
Dark Factory β€” GitHub issue β†’ autonomous, reviewed, merged PR
Untrusted, code-writing agents run inside hardware-isolated Kata micro-VMs, next to the control plane β€” safely.
🏷️ GitHub issue
labeled dark-factory
Argo Events
Sensor
df-run
Argo Workflow
πŸ”’ Kata micro-VM

coder: Claude Code / Kiro
credential-less Β· network-locked
πŸ”€ Pull Request
opened
AWS DevOps Agent
release readiness
(cross-repo Β· standards Β· access-control)
AWS Security Agent
code security review
(OWASP Β· secrets Β· IAM Β· deps)
Holdout gate
terraform validate
ephemeral deploy-test
πŸ‘€ Human
approve?
df-merge-teardown
squash-merge + reap the sandbox VM
βœ… Merged to main
cleared
yes
Text is not SVG - cannot display
\ No newline at end of file diff --git a/docs/architecture/diagrams/img/oap-layered.svg b/docs/architecture/diagrams/img/oap-layered.svg new file mode 100644 index 00000000..cd5d525b --- /dev/null +++ b/docs/architecture/diagrams/img/oap-layered.svg @@ -0,0 +1,3 @@ + + +
Open Agentic Platform β€” Layered Architecture
AGENT WORKLOADS
Financial Advisor | K8s Ops Agent | Multi-Tool Agent | Custom Agents (BYOA)
AGENT PLATFORM CAPABILITIES (OAP)
Model as a
Service
Agent
Identities
Agent
Gateway
Agent
Isolation
Agent
Runtime
Agent
Lifecycle
Agent
Memory
Agent
Browser
Agent Code
Interpreter
Agent
Observability
Agent
Evaluation
PLATFORM ENGINEERING
Developer
Portal
Identity
& Access
Infra as
Code
Continuous
Delivery
Workflow
Orchestration
Service
Discovery
Packaging /
Templating
Code
Repository
Config
Repository
Artifact
Registries
Secret
Repository
Observability
Security & Governance
Signing
COMPUTE PLATFORM β€” Amazon EKS Auto Mode
AWS Cloud
Text is not SVG - cannot display
\ No newline at end of file diff --git a/docs/architecture/diagrams/img/oap-managed-or-oss.svg b/docs/architecture/diagrams/img/oap-managed-or-oss.svg new file mode 100644 index 00000000..6d620ded --- /dev/null +++ b/docs/architecture/diagrams/img/oap-managed-or-oss.svg @@ -0,0 +1,3 @@ + + +
Open Agentic Platform on AWS
CNCF-aligned Β· cloud-agnostic Β· AgentCore managed OR OSS alternative per capability
COMPUTE PLATFORM β€” cloud-agnostic
Amazon EKS
ROSA
SUSE Rancher
Self-Managed K8s
AGENT ABSTRACTION β€” Kro / KubeVela CRDs (orchestrate Crossplane Β· ACK Β· OpenTofu)
Imageless Agents

CRD-defined Β· no container image
auto AuthN/AuthZ Β· declarative K8s
BYO-Image Agents

Strands SDK Β· Spring AI Β· Rust
LangGraph Β· CrewAI Β· any OCI image
CAPABILITY BACKENDS β€” AgentCore managed Β·ORΒ· OSS alternative per capability
Memory

AgentCore
β€” or β€”
Milvus Β· Qdrant Β· Mem0
Gateway

AgentCore
β€” or β€”
KGateway Β· LiteLLM Β· Envoy
Identity

AgentCore
β€” or β€”
Keycloak Β· SPIFFE/SPIRE
Observability

AgentCore
β€” or β€”
Langfuse Β· OTel Β· Prometheus
Policy

AgentCore
β€” or β€”
NeMo Β· OPA Β· Guardrails
Evaluations

AgentCore
β€” or β€”
Ragas Β· DeepEval
Code Interp.

AgentCore
β€” or β€”
gVisor Β· Kata Β· Firecracker
Browser

AgentCore
β€” or β€”
Playwright Β· Selenium
Amazon Bedrock
Claude 3.x / 4.x
Llama 3.x
Mistral
Amazon Nova
Titan Embeddings
Self-Hosted LLM
vLLM / RHOAI
llm-d
llama.cpp
AWS Neuron
Text is not SVG - cannot display
\ No newline at end of file diff --git a/docs/architecture/diagrams/src/dark-factory-flow.drawio b/docs/architecture/diagrams/src/dark-factory-flow.drawio new file mode 100644 index 00000000..dc036a9b --- /dev/null +++ b/docs/architecture/diagrams/src/dark-factory-flow.drawio @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/architecture/diagrams/src/oap-layered.drawio b/docs/architecture/diagrams/src/oap-layered.drawio new file mode 100644 index 00000000..83dd77fb --- /dev/null +++ b/docs/architecture/diagrams/src/oap-layered.drawio @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/architecture/diagrams/src/oap-managed-or-oss.drawio b/docs/architecture/diagrams/src/oap-managed-or-oss.drawio new file mode 100644 index 00000000..8bad92df --- /dev/null +++ b/docs/architecture/diagrams/src/oap-managed-or-oss.drawio @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/dark-factory/AGENT-INSTALL.md b/docs/dark-factory/AGENT-INSTALL.md new file mode 100644 index 00000000..a1078794 --- /dev/null +++ b/docs/dark-factory/AGENT-INSTALL.md @@ -0,0 +1,145 @@ +# Connecting the AWS DevOps Agent & AWS Security Agent to the Dark Factory + +The Dark Factory pipeline reviews every PR with two managed AWS agents: + +- **AWS DevOps Agent** β€” *Release Readiness* code review: cross-repo dependency risk, standards + compliance, access-control correctness, and (optionally) build+test in an AWS-managed environment. + Verdict: **BLOCK / Proceed with Caution / Safe to Release**. +- **AWS Security Agent** β€” code security review: OWASP Top 10, hardcoded secrets, IAM misuse, + dependency risk, with inline findings + recommended fixes. + +Both integrate with GitHub the same way: **install a GitHub App, connect the repo to an Agent +Space, enable review.** After that, every pull request is reviewed automatically and the agents post +their verdicts back onto the PR. This page is the end-to-end setup β€” follow it top to bottom. + +> **Prerequisites:** an AWS account with the DevOps Agent + Security Agent enabled, admin access to +> that account, and **owner/admin** rights on the GitHub org or user that owns your repo (needed to +> install a GitHub App). The examples use account ``, region `us-west-2`, repo +> `elamaran11/dark-factory-sandbox` β€” substitute your own. + +--- + +## Part A β€” AWS DevOps Agent (Release Readiness code review) + +### A1. Create / open an Agent Space +1. AWS Console β†’ **AWS DevOps Agent**. +2. Create an Agent Space (or open an existing one), e.g. **`dark-factory`**. + +### A2. Register GitHub (account level) +1. In the Agent Space β†’ **Capabilities** tab β†’ **Pipeline** section β†’ **Add** β†’ **GitHub**. +2. Choose **User** or **Organization** (must match where the repo lives), then submit. +3. GitHub opens its **authorize** screen β†’ authorize AWS DevOps Agent. +4. On the **Install & Authorize** page, choose **Only select repositories β†’ your repo** (or All), + then **Install & Authorize**. You're returned to the console with GitHub registered. + > Permission level: **Read & Write** (default) β€” lets the agent post PR comments, check-runs, and + > optional remediation PRs. Read-Only disables those write actions. + +### A3. Connect the repo to the Agent Space + enable review +1. In the Agent Space β†’ **Capabilities** β†’ **Pipeline** β†’ **Add** β†’ **GitHub** β†’ pick the + registration β†’ select your repo β†’ **Add**. +2. In **Code Review and Automated Testing**, per repo: + - **Auto trigger change review** = **ON** β€” reviews every PR automatically. + - **Automated verification testing** = ON (optional) β€” builds/tests the change in an AWS-managed + verification environment (deeper than static analysis). + - **Runtime role** (optional) β€” an IAM role the agent assumes for private-registry/artifact + access during builds. +3. **Save.** + +### A4. What you'll see on a PR +On every new/updated PR the DevOps Agent posts a status/check-run +**`aws-devops-agent/release-readiness-review`** (`pending` β†’ `success`/`failure`) with a link to the +full report, plus inline comments for any risks it finds (e.g. an unpinned image, a missing variable +default, an over-broad IAM change). A clean change gets **"change approved"**. + +--- + +## Part B β€” AWS Security Agent (code security review) + +The Security Agent can review a PR **two ways** β€” you can use either or both: + +### Path B1 (recommended) β€” GitHub App: inline bot findings +1. AWS Console β†’ **AWS Security Agent** β†’ your Agent Space (e.g. **`dark-factory`**). +2. **Integrations β†’ GitHub β†’ Connect** (or open + `https://github.com/apps/aws-security-agent/installations/new`). Authorize + install the App on + your repo, **Read & Write** (so it can post inline comments + optional fix PRs). +3. Back in the Security Agent console, **add the GitHub integration to your Agent Space** and + **enable code review** on the repo. *(Installing the App on GitHub and connecting it to the Agent + Space are two distinct steps β€” do both.)* +4. On the next PR the agent posts as **`aws-security-agent[bot]`**: an "AWS Security Agent is + reviewing…" notice, then inline findings (or **"No issues identified"**), with recommended fixes. + +### Path B2 β€” headless code-review API (no GitHub App) +Fully API-driven β€” useful for automation that shouldn't depend on a GitHub App. The Dark Factory's +`security-agent` step already implements this (`scripts/security-agent.sh`): stage the diff to S3, +then: +``` +aws securityagent create-code-review --agent-space-id --assets '{"sourceCode":[{"s3Location":"s3://.../src.zip"}]}' --service-role +aws securityagent start-code-review-job --agent-space-id --code-review-id --diff-source '{"s3Uri":"s3://.../diff.patch"}' +aws securityagent list-findings --agent-space-id --code-review-job-id +``` +Findings come back with `riskType`, `riskLevel` (INFORMATIONALβ†’CRITICAL), and `confidence`. The IAM +this needs (a service role trusting `securityagent.amazonaws.com` + an IRSA role for the workflow + +an S3 bucket) is committed as Terraform in `gitops/addons/charts/dark-factory/iam/securityagent.tf`, +and the Agent Space is reconciled by the PreSync bootstrap Job +(`templates/06-securityagent-bootstrap.yaml`). **The Dark Factory runs both paths** β€” the App for +inline bot findings and the headless path for the merge-gate signal. + +--- + +## Part C β€” How the Dark Factory pipeline uses the agents + +Once the agents are connected, the `df-run` workflow wires them into the PR lifecycle (ordering: +**DevOps first, then Security**): + +1. Coder opens the PR β†’ posts `dark-factory:coding` + `dark-factory:local-test` comments. +2. **`devops-gate`** waits for the DevOps Agent's `aws-devops-agent/release-readiness-review` verdict. + On a clear verdict it applies the **`needs-security-review`** label. *(Config: + `devopsAgent.checkContext` / `devopsAgent.checkRunName` in `values.yaml` β€” the check name the gate + watches for.)* +3. **`security-agent`** runs (gated on that label) β†’ the Security Agent reviews the diff; the + `aws-security-agent[bot]` also comments inline (Path B1). +4. **`deploy-test`** runs for deployable changes (`terraform validate` for `*.tf`, ephemeral-namespace + apply for k8s). +5. **Sticky status** rewrites the PR body into one board (build+tests, security, devops, deploy-test). +6. A human **approves** the PR β†’ the `df-merge-teardown` workflow **squash-merges** and reaps the + sandbox. *(GitHub blocks a PR author from approving their own PR, so the approver must be a + different identity than the one the coder opens PRs as.)* + +### Relevant `values.yaml` knobs +```yaml +devopsAgent: + enabled: true + gate: check # wait for the DevOps Agent check-run (native model) + checkContext: "(aws-devops-agent/release-readiness-review|...)" # JS regex (no (?i) flag) + checkRunName: "aws-devops-agent/release-readiness-review" # exact check name +securityAgent: + enabled: true # headless S3-diff path (Path B2) + app: + enabled: true # GitHub App inline bot (Path B1) + checkContext: "" # set only if the App posts a check/status to gate on + checkRunName: "" +``` + +--- + +## Verify the setup + +```bash +# Open a PR in the connected repo, then within a few minutes: + +# DevOps Agent posted its review? +gh api repos///commits//status \ + --jq '.statuses[] | select(.context|test("devops")) | {context,state,description}' + +# Security Agent bot commented inline? +gh api repos///issues//comments \ + --jq '.[] | select(.user.login=="aws-security-agent[bot]") | .body[0:80]' + +# Security Agent integration recorded on the space (headless path)? +aws securityagent list-integrations --query 'integrationSummaries[].provider' +aws securityagent list-integrated-resources --agent-space-id +``` + +If the DevOps check never appears, the repo isn't connected to the Agent Space (Part A3). If the +Security bot never comments, the GitHub App isn't connected to the Agent Space (Part B1 step 3) β€” +installing the App on GitHub alone is not enough. diff --git a/docs/dark-factory/FLOW-D-ENABLEMENT.md b/docs/dark-factory/FLOW-D-ENABLEMENT.md new file mode 100644 index 00000000..620363fe --- /dev/null +++ b/docs/dark-factory/FLOW-D-ENABLEMENT.md @@ -0,0 +1,374 @@ +# Enabling Flow D (Lambda MicroVM substrate) on your own account + +Flow D ships **disabled** (`microvm.enabled=false`) and, unlike Flow A/B, it cannot be turned on by +GitOps alone. Three artifacts have to be produced **by hand, per AWS account**, and two of them +cannot be templated from cluster annotations the way the rest of this repo does it. This page is the +end-to-end enablement path β€” follow it top to bottom. + +> **Prerequisites:** Managed KRO + Managed ACK capabilities ACTIVE on the hub (they are EKS +> Capabilities, provisioned in the platform repo β€” see `docs/EKS-Capabilities-KRO-ACK-Setup.md`), the +> self-managed `ack-lambdamicrovms` addon synced, a Docker host with `buildx`, and admin on the +> GitHub repo the factory will work on. Examples use account ``, region `us-west-2`, repo +> `/` β€” substitute your own. +> +> **Lambda MicroVM is ARM_64-only.** The service model (`lambda-microvms`, API 2025-09-09) defines +> `Architecture` with exactly one enum value, `['ARM_64']`, and it is required. Note the `MicrovmImage` +> CRD declares `architecture` as a bare string with **no** enum, so the API server will happily admit +> `X86_64` and you only discover the problem as an opaque AWS-side build failure. There is no x86 path. + +--- + +## Placeholders you must replace + +This repo ships Flow D with **deliberate placeholder values** rather than a working default, so that a +mis-configured deploy fails loudly instead of silently pointing at someone else's account or repo. +Every one of these must be replaced before Flow D works. Nothing here can be injected from cluster +annotations β€” that is what makes them manual. + +| Placeholder | File / location | Replace with | +|---|---|---| +| `REPLACE_ME_ACCOUNT_ID` | `examples/dark-factory/coder-microvm/Dockerfile` β€” `ARG CODER_IMAGE` | your 12-digit AWS account id (the one holding the ECR repo) | +| `REPLACE_ME_REGION` | same line | your region, e.g. `us-west-2` | +| `replace-me-owner` | `gitops/addons/charts/dark-factory/values.yaml` β€” `trigger.argoEvents.repositories[].owner` | your GitHub org or user | +| `replace-me-repo` | same block β€” `.names[]` | your repo name | +| `replace-me-owner-replace-me-repo` | **directory** `gitops/addons/charts/dark-factory/holdout/` | rename to `-`, matching the two values above **exactly** | +| `replace-me-owner/replace-me-repo` | inside that directory's `scenarios.json` (`_comment`) and `rubric.md` (heading) | your `owner/repo` β€” cosmetic, but the `require()` paths in the same file are **not** (see Part D4) | + +> **Two casing conventions, on purpose.** `REPLACE_ME_ACCOUNT_ID` is free text inside a Dockerfile, so +> it is loud and uppercase. `replace-me-owner` / `replace-me-repo` are lowercase and hyphenated because +> the holdout slug is interpolated into a **Kubernetes object name** +> (`df-holdout-{{ $slug | lower }}` in `50-holdout-configmap.yaml`), and underscores are invalid in +> RFC 1123 names β€” an uppercase/underscore placeholder there renders a manifest that cannot be applied. +> Keep any replacement DNS-label-safe for the same reason. + +The directory name and the two values are **coupled**: `50-holdout-configmap.yaml` derives the fixture +path from `-`, and `.Files.Get` returns an **empty string** for a missing file instead of +failing the render. Change one without the other and you get a ConfigMap with an empty +`scenarios.json` β€” the holdout gate then grades every run against nothing. + +Find anything you missed: + +```bash +grep -rniE 'replace[-_]me' --include='*.yaml' --include='*.json' --include='*.md' \ + gitops/ examples/ | grep -v docs/ +find gitops examples -iname '*replace*me*' # the fixture directory itself +``` + +--- + +## Part A β€” Build and push the arm64 coder base image + +The code-artifact ZIP's Dockerfile does `FROM .dkr.ecr..amazonaws.com/dark-factory-coder:-arm64`. +That base image must exist **in the account you are deploying to**. + +> **Cross-account pull does not work.** ECR cross-account requires *both* an identity grant and a +> repository resource policy on the far side. The build role's `ecr:BatchGetImage` on `*` is only the +> identity half, so pointing at another account's registry fails with +> `AccessDeniedException ... no resource-based policy allows the ecr:DescribeImages action`. + +### A1. Register QEMU if you are building on x86_64 + +`docker buildx` will accept `--platform linux/arm64` and pull arm64 layers, and `COPY`/`WORKDIR` +succeed β€” but any `RUN` step dies with `exec /bin/sh: exec format error`, because containers share the +host kernel. The `PLATFORMS` column in `docker buildx ls` lists what the worker can **execute**, not +what it can address. + +```bash +docker run --privileged --rm tonistiigi/binfmt --install arm64 # registers qemu-aarch64 +ls /proc/sys/fs/binfmt_misc/ # expect: qemu-aarch64 +# revert later with: docker run --privileged --rm tonistiigi/binfmt --uninstall arm64 +``` + +Emulated builds work but are slow (the `apk add` + `npm install -g` steps take minutes). A native +arm64 builder β€” e.g. a Graviton node the cluster already has β€” is faster if you have one. + +### A2. Build, verify, push + +```bash +docker buildx build --platform linux/arm64 \ + -t dark-factory-coder:v0.2.5-arm64 --load examples/dark-factory/coder + +# VERIFY BEFORE PUSHING β€” the ECR repo is IMMUTABLE, so a tag can only be pushed once. +docker image inspect dark-factory-coder:v0.2.5-arm64 \ + --format '{{.Architecture}} {{.Os}} user={{.Config.User}}' # expect: arm64 linux user=1000 +docker run --rm --platform linux/arm64 --entrypoint sh \ + dark-factory-coder:v0.2.5-arm64 -c 'uname -m; node -v; go version' # expect aarch64 + +aws ecr get-login-password --region us-west-2 \ + | docker login --username AWS --password-stdin .dkr.ecr.us-west-2.amazonaws.com +docker tag dark-factory-coder:v0.2.5-arm64 .dkr.ecr.us-west-2.amazonaws.com/dark-factory-coder:v0.2.5-arm64 +docker push .dkr.ecr.us-west-2.amazonaws.com/dark-factory-coder:v0.2.5-arm64 + +# Confirm from the registry side, not the push output: +docker manifest inspect --verbose .dkr.ecr.us-west-2.amazonaws.com/dark-factory-coder:v0.2.5-arm64 \ + | grep -A2 '"platform"' # expect: "architecture": "arm64", "os": "linux" +``` + +Preserve `USER 1000` and `ENV WORKSPACE` from `examples/dark-factory/coder/Dockerfile`. Rebuilding the +image from a public base instead is tempting but drops both, and would run the hook server as root. + +--- + +## Part B β€” Edit the ZIP's Dockerfile (the one thing GitOps cannot inject) + +`examples/dark-factory/coder-microvm/Dockerfile` ships as +`ARG CODER_IMAGE=REPLACE_ME_ACCOUNT_ID.dkr.ecr.REPLACE_ME_REGION.amazonaws.com/dark-factory-coder:v0.2.5-arm64` +and **must be hand-edited** before you build the ZIP in Part C. + +> **Why it cannot be templated.** `MicrovmImage.spec.codeArtifact` has exactly **one** property, `uri`. +> There is no build-args field β€” `spec.environmentVariables` is *runtime* env for the VM, not docker +> build args. So nothing in Helm, the addon registry, or the CR can reach inside the ZIP. Whatever the +> `ARG` default says is what Lambda pulls. This is the one ECR reference in the repo that the +> `{{.metadata.annotations.aws_account_id}}` pattern cannot fix. + +Verify the CRD yourself if you doubt it: + +```bash +kubectl get crd microvmimages.lambdamicrovms.services.k8s.aws -o json \ + | jq '.spec.versions[0].schema.openAPIV3Schema.properties.spec.properties.codeArtifact.properties' +# => { "uri": { ... } } β€” nothing else +``` + +--- + +## Part C β€” Let ACK create the artifact bucket, *then* upload + +The KRO RGD creates the bucket as `-microvm-artifacts-`. The account suffix is +required: S3 bucket names are **one global namespace**, so an un-suffixed `coder-microvm-artifacts` is +first-come-first-served and will already be owned by someone else β€” `CreateBucket` returns +`409 BucketAlreadyExists`, the `Bucket` CR never gets an ARN, and the image build fails with +`Access denied when fetching artifact from S3` β†’ `CREATE_FAILED`. + +> **Ordering matters β€” do not pre-create the bucket.** ACK refuses to manage a bucket it did not +> create: the CR parks at `ACK.Terminal=True`, *"This resource already exists but is not managed by +> ACK ... enable the ResourceAdoption feature gate and populate the +> `services.k8s.aws/adoption-policy` annotations."* On Managed ACK (which runs off-cluster) that gate +> is not yours to enable. Sync the chart first, wait for the `Bucket` CR to reach +> `ACK.ResourceSynced=True`, and only then upload. Builds still work with a `Terminal` bucket CR, but +> you are left with a permanently unhealthy resource. + +```bash +kubectl get bucket -n agent-sandbox-system \ + -o custom-columns='NAME:.spec.name,SYNCED:.status.conditions[?(@.type=="ACK.ResourceSynced")].status' +``` + +Then assemble and upload the ZIP. **No script in this repo does this** β€” the ZIP and the base image +have only ever been built by hand, which is the root cause of this whole class of drift. + +```bash +cd examples/dark-factory/coder-microvm +cp ../coder/entrypoint.js . # overlays the (newer) coder over the one baked in the base +zip -X /tmp/coder-v0.2.5-arm64-r8.zip Dockerfile hook-server.js entrypoint.js +unzip -p /tmp/coder-v0.2.5-arm64-r8.zip Dockerfile | grep ARG # confirm YOUR account id is inside +aws s3 cp /tmp/coder-v0.2.5-arm64-r8.zip \ + s3://coder-microvm-artifacts-/coder-v0.2.5-arm64-r8.zip --region us-west-2 +``` + +Set `microvm.codeArtifactKey` to that **object key only** β€” the bucket half is composed by the RGD +from the bucket it creates (`s3://${bucket.spec.name}/${schema.spec.codeArtifactKey}`), so bucket, IAM +grant, and artifact URI all derive from one expression. **Bump the key to rebuild**: Lambda does not +rebuild when the same S3 key is overwritten. + +--- + +## Part D β€” GitHub: repo target, label, credentials + +### D1. Point the EventSource at your repo + +`trigger.argoEvents.repositories` in `charts/dark-factory/values.yaml` ships the placeholders +`replace-me-owner` / `replace-me-repo`, which match no real repo. Until you replace them Argo Events +cannot register the webhook and logs, every 60s: + +``` +failed to list existing webhooks of /. err: GET .../hooks: 404 Not Found +``` + +A 404 here means *either* the repo does not exist *or* the token cannot see it β€” GitHub deliberately +does not distinguish. This value is **load-bearing twice**: `50-holdout-configmap.yaml` derives the +holdout fixture path from it (Part D4). + +### D2. Create the trigger label + +The Lambda substrate is triggered by the **`darkfactory-lambda`** label (Kata uses `dark-factory`). +It does not exist on a fresh repo: + +```bash +gh label create darkfactory-lambda --repo / \ + --description "Hand this issue to the Dark Factory coder running in a Lambda MicroVM (Flow D)" \ + --color 5319E7 +``` + +### D3. The three credentials + +Full Secrets Manager setup is in [README Β§10a](README.md#10a-github-credentials-secrets-manager-setup). +Flow-D-specific notes: + +- The **events** token needs `admin:repo_hook` on the target repo β€” Argo Events *self-registers* the + webhook, and a read-only token cannot. Confirm the token can see the repo at all: + `GET /repos///hooks` should return 200, not 404. +- After a successful sync, verify the hook exists and that a delivery actually **authenticates** β€” + a `code=200` proves the cluster's `webhook-secret` matches the one on the hook (GitHub never + returns a hook's secret, so this is the only way to check): + ```bash + gh api repos///hooks --jq '.[]|{id,active,url:.config.url,events}' + gh api -X POST repos///hooks//pings + gh api repos///hooks//deliveries --jq '.[0]|{event,status,status_code}' + ``` + +> **Single-account caveat.** `iterate.js` skips any comment whose author equals the **orchestrator** +> token's identity (`GET /user`) β€” the self-trigger guard. If the orchestrator PAT is your own +> personal token, **you cannot drive a human fix round from your own account**: every comment you +> post looks like the factory talking to itself. Use a second GitHub identity for review comments, or +> submit the fix round directly (Part F). + +### D4. Add a holdout fixture for your repo + +`holdout/-/{scenarios.json,rubric.md}` is read by `.Files.Get`, which returns **empty +string** for a missing file rather than failing. The repo ships +`holdout/replace-me-owner-replace-me-repo/` β€” **rename it** to match your `owner`/`repo` values β€” so a repo with no fixture renders a ConfigMap with +empty `scenarios.json` and the gate grades every run against nothing. + +The fixture's `require()` paths must match **your** repo's layout. The shipped fixture requires +`$REPO/app/index.js`; against a root-level `index.js` every test throws `MODULE_NOT_FOUND`. That is +worse than a skip, because `appliesWhen` tests the **PR diff**, not file existence β€” the scenarios +become *applicable*, all error, and the gate hard-fails at 0%. Do **not** "fix" this by flipping +`appliesWhen` to false: `evaluate.js` warns that turning the mismatch into a SKIP makes the gate go +**green over a diff it never evaluated**, indistinguishable from a real pass. + +--- + +## Part E β€” Enable, sync, and trigger + +### E1. Opt in (nothing renders without this) + +Flow D is **off by default**. One label gates the whole stack β€” the pipeline chart, the Lambda MicroVM +substrate, and the pre-GA `lambdamicrovms` ACK controller: + +```yaml +# gitops/overlays/environments/control-plane/enabled-addons.yaml +enabledAddons: + dark_factory: true +``` + +Skip this and none of the three ArgoCD Applications are created at all β€” no error, they simply never +appear, because the registry entries select on `enable_dark_factory`. + +`dark_factory` is not sufficient on its own β€” **`agent_sandbox: true` is also required**, even for a +Lambda-only deployment: + +```yaml + agent_sandbox: true # REQUIRED by Flow D too β€” see below +``` + +The `agent-sandbox-operator` (gated by `enable_agent_sandbox`) is the only thing that installs the +`Sandbox*` CRDs, and the Lambda substrate itself renders a `SandboxTemplate` (the bridge) plus a +`SandboxWarmPool`. Neither of those CRs carries `SkipDryRunOnMissingResource`, and ArgoCD computes an +app's whole diff before applying any sync wave, so with the CRDs absent the `agent-sandbox-lambda` +Application fails at comparison β€” the same shape as the ComparisonError described in E2 below, and it +does **not** self-heal. The operator is cheap: CRDs plus one controller Deployment, no EC2 capacity. + +The **Kata** runtime flags are genuinely optional for Flow D, and only needed for `df-run` (Flow A/B), +which claims from the Kata warm pool: + +```yaml + agent_sandbox_kata: true # the clh/qemu runtime binaries on the node (kata-deploy) + kata_nodepool: true # dedicated Karpenter NodePool for nested-virt nodes +``` + +Enable those two **together** or not at all: `kata_nodepool` alone leaves a NodePool idle at 0 nodes, +and the runtime alone gives you a handler with no node to run on. Note the split β€” `kata-deploy` ships +the runtime binaries but is configured `runtimeClasses.enabled: false`, so the three RuntimeClasses +(`kata-clh`, `kata-fc`, `kata-qemu`) come from the `agent-sandbox` chart instead. Both halves are +needed for a working Kata sandbox. + +⚠️ **Flow-D-only clusters:** `agent_sandbox: true` also renders the Kata `SandboxWarmPool` +(`coder-warmpool`, `warmPool.targetIdle: 1`). Its pods pin `nodeSelector +katacontainers.io/kata-runtime=true` and tolerate the `kata` taint, which only `kata-nodepool` creates β€” +so without the two Kata flags that one warm pod sits **Pending** indefinitely. Harmless but untidy; set +`warmPool.enabled: false` in a per-cluster `agent-sandbox` overlay if you want Flow D with no Kata path. + +### E2. Point the substrate at your image + +```yaml +# clusters//agent-sandbox-lambda/values.yaml +microvm: + enabled: true + baseImageARN: "arn:aws:lambda:us-west-2:aws:microvm-image:al2023-1" + codeArtifactKey: "coder-v0.2.5-arm64-r8.zip" # object key ONLY +``` + +`accountId` and `podIdentity.clusterName` are injected from the cluster's `aws_account_id` / +`aws_cluster_name` annotations by the addon registry β€” leave them unset per cluster. The RGD instance +`require`s `accountId`, so a missing value fails the render loudly instead of emitting an invalid +trailing-hyphen bucket name. + +> **⚠️ Upgrading an existing cluster: CRD/instance ordering deadlock.** Adding a new field to the RGD +> schema while the same sync also sets it on the instance can wedge the Application: +> ``` +> ComparisonError: ... error building typed value from config resource: +> .spec.accountId: field not declared in schema +> ``` +> ArgoCD computes the whole app's diff **before** applying any sync wave, so the RGD (wave -1) that +> would regenerate the CRD never lands, and this does **not** self-heal β€” `retry`/`selfHeal` recompute +> the identical failing comparison. Sync-wave ordering does not save you either, because ArgoCD +> reports `health=None` for a `ResourceGraphDefinition` and treats wave -1 as complete once applied, +> not once KRO has regenerated the CRD. Break it by applying the RGD once out of band, or split the +> change into two commits (schema first, then the instance). + +Then label an issue and watch: + +```bash +gh issue create --repo / --title "..." --body "..." # create WITHOUT the label +gh issue edit --repo / --add-label darkfactory-lambda +``` + +Create first, label second: the sensor filters on `action: labeled`, and creating an issue with labels +attached emits `opened` (labels already present), which does not match. + +```bash +kubectl get wf -n argo -w +kubectl logs -n argo -l workflows.argoproj.io/workflow=df-run-lambda- --prefix --tail=200 +``` + +Healthy provision output ends with `/run -> HTTP 200 {"status":"started"}`. + +--- + +## Part F β€” Fix rounds (suspend β†’ resume) + +The VM is **suspended** after the first PR and resumed for a fix round, so the same warm VM (and its +cloned repo) is reused. Two hard constraints: + +- **The window is 8 hours.** `suspendedDurationSeconds=28800` is Lambda's maximum; after that the VM + **auto-terminates** and the fix round recreates a fresh one (`prior VM not resumable (gone) β€” + recreating fresh`). Submit the fix round inside that window if you want to exercise warm resume. +- **The review note must differ from the previous round.** `hook-server.js` keys its `/run` guard on a + run-id hashed from issue number + note; an identical note hashes to the same id and is correctly + ignored as a duplicate webhook (`/run duplicate for β€” ignoring`). + +To drive a fix round without a second GitHub identity, submit the workflow directly β€” the same shape +`iterate.js` builds: + +```bash +NOTE_B64=$(gh api repos///issues/comments/ --jq '.body' | base64 -w0) +# Workflow df-run-lambda--i, workflowTemplateRef df-run-lambda, parameters: +# issue-id, issue-number, repo, issue-title, issue-body, base-branch, +# trigger-label=darkfactory-lambda, iterate-note="", iterate-note-b64=$NOTE_B64 +``` + +Warm resume logs `RESUMING same VM (warm resume)` and keeps the same microvm id and endpoint. + +--- + +## Known issues (observed live, 2026-08) + +| Symptom | Status | +|---|---| +| `/run -> HTTP 502` immediately after a successful warm resume; `provision-microvm` exits 1 and the fix round **fails** rather than self-healing | **Open.** Resume itself works (same VM id, same endpoint). The likely cause is that provision waits for infrastructure `state=RUNNING` + an endpoint, which on resume is satisfied almost immediately β€” before the restored hook-server is listening on `:8080` again. A fresh VM incidentally avoids this because its boot loop takes several seconds. Unconfirmed; a readiness retry on `/run` is the obvious fix. | +| `MicrovmImage` stuck `CREATE_FAILED` never rebuilds after you fix the artifact | **Expected.** `CREATE_FAILED` is terminal on the pre-GA controller: spec patches bump `metadata.generation` but no new build is attempted (`list-microvm-image-builds` shows no records for the new version). Delete the CR and let KRO recreate it. Note `DELETION_POLICY=delete` β€” the finalizer deletes the real AWS image too. | +| `Bucket.spec.name` change rejected: `Value is immutable once set` | **Expected.** The CRD carries a CEL rule `self == oldSelf`. Delete the `Bucket` CR and let KRO recreate it β€” *after* the new RGD has synced, or KRO recreates it from the old template. A CR with no ARN and no finalizers issues no `DeleteBucket`, so no S3 data is touched. | +| No runtime logs in CloudWatch (`/aws/lambda/microvms/` has only build streams) | **Known pre-GA limitation** β€” see benchmark pitfall #3. Use the hook server's `/logs` endpoint (it captures the coder's stdout) rather than CloudWatch. | +| `Microvm` CR shows `RUNNING` while AWS says `SUSPENDED` | **By design.** suspend/resume are imperative SDK ops the ACK controller does not reconcile, which is why the RGD omits the running VM and the shim owns that lifecycle. | + +See [`SUBSTRATE-BENCHMARK.md`](SUBSTRATE-BENCHMARK.md) Β§"Gotchas the Lambda substrate needed" for the +13 substrate quirks already baked into the implementation. diff --git a/docs/dark-factory/PROFILES.md b/docs/dark-factory/PROFILES.md new file mode 100644 index 00000000..edd15c64 --- /dev/null +++ b/docs/dark-factory/PROFILES.md @@ -0,0 +1,56 @@ +# Dark Factory β€” language & stack support (no profiles) + +> **Decision:** the Dark Factory does **not** use per-language "profiles" in platform config. +> Language support is decoupled into the **coder image** (toolchains) and the **target repo** +> (build/test discovered from marker files) β€” devs control it, the platform stays generic. +> An earlier design added a `stackProfiles` map + a `dark-factory-` label; it was removed as +> redundant (the coder already auto-detects, and `detect-deployable` already classifies verification). + +## How a stack is supported β€” three decoupled layers + +1. **Toolchains β†’ the coder image.** `examples/dark-factory/coder/Dockerfile` is a generic image that + carries `git`, `node`, `python3`, `go`. To support Java/Rust/etc., add the toolchain **to the + image** (or maintain a variant image) β€” not to platform config. This is the dev/image concern. + *(The trusted `deploy-test` image separately carries `kubectl` + `terraform`.)* + +2. **Build/test β†’ discovered from the repo.** The coder picks the build/test command from the repo's + own marker files β€” devs control it by their repo layout, with a `Makefile` as the explicit override: + + | Marker in the repo | Coder runs | + |---|---| + | `Makefile` with a `test:` target | `make test` *(explicit dev override β€” checked first)* | + | `package.json` | `npm install` + `npm test` | + | `go.mod` | `go test ./...` | + | `pyproject.toml` / `setup.py` / `requirements.txt` | `pytest -q` | + | `Cargo.toml` | `cargo test` | + | `pom.xml` | `mvn -q test` | + | `build.gradle[.kts]` | `./gradlew test` | + | *(none β€” e.g. Terraform/config change)* | skipped (no unit suite; `deploy-test` still validates) | + +3. **Verification kind β†’ auto-detected.** `detect-deployable` classifies the changed files and + `deploy-test` runs the right tool β€” `*.tf` β†’ `terraform validate`; `Chart.yaml`/`k8s/`/`Dockerfile` + β†’ deploy into an ephemeral namespace. No label, no config. + +## Why no profiles (the reasoning) + +- The one thing a profile added over auto-detection was a `scaffoldHint` string β€” and the **issue text + already states the stack** ("Terraform for an S3 bucket", "a Spring Boot service"), which the coder + reads directly. The build/test commands and the verify kind were **already** covered by marker-file + detection and `detect-deployable`. +- Keeping per-language build/test in Helm values meant the platform had to know every language β€” the + opposite of generic. Pushing it to the **image** (toolchains) and the **repo** (marker files) keeps + the platform language-agnostic and puts control where it belongs: with the devs/repo. + +## Adding a new language + +1. Add its toolchain to the coder image (`coder/Dockerfile`) β€” e.g. `apk add openjdk maven`. +2. Ensure `buildAndTest()`'s marker list covers it (most already are; add a marker if exotic). +3. That's it β€” no values, no label, no pipeline change. A repo `Makefile test` target works for + anything without even a marker. + +## Greenfield note + +For a brand-new/empty repo with no marker files yet, the coder relies on the **issue text** to know +the stack (usually sufficient) and creates the idiomatic project (which then has the marker files a +re-run/iterate would detect). If a repo wants to be explicit, a committed `Makefile` (`build`/`test` +targets) is the clean, dev-owned contract. diff --git a/docs/dark-factory/README.md b/docs/dark-factory/README.md new file mode 100644 index 00000000..edb9370d --- /dev/null +++ b/docs/dark-factory/README.md @@ -0,0 +1,1049 @@ +# Dark Factory β€” Autonomous Agent Coding Pattern + +> **Status:** Design **+ implementation**. Phases **P0–P3b** are built and running on the hub (see +> [Β§12](#12-phased-delivery)). The **full event-driven lifecycle** works hands-off: a labeled issue β†’ +> coder β†’ holdout gate + Security/DevOps reviews β†’ PR with live status; a **PR comment β†’ bounded +> revision** (`df-iterate`); a **human approval β†’ green-gated merge + teardown** (`df-merge-teardown`). +> All verified end-to-end on a live cluster. This doc describes the architecture, the reuse map onto +> the platform, and what remains (P4: conditional deploy-test + reaper + metrics; P5). + +A **dark factory** is a manufacturing plant that runs *with the lights off* β€” no humans on the +floor, robots do everything. Applied to software: **a human writes an issue (a spec); AI agents +do the rest** β€” implement, build, test, security/ops review, open a PR, and (after a human +approves the *results*) merge and tear everything down. + +This pattern wires that idea onto the **Open Agent Platform (OAP)** using components the platform +already has: hardware-isolated **Kata micro-VM sandboxes** (from `eks-platform-openclaw`), the +**Bifrost β†’ Bedrock** LLM gateway, **Argo Workflows** on the hub as the orchestrator, the +**hub + spoke** cluster fleet, and **AWS-managed frontier agents** (Security, DevOps) for +independent review. The factory itself runs on the **hub build plane**; its output ships to the +spokes as normal deployments. + +--- + +## Table of contents +1. [What is a Dark Factory](#1-what-is-a-dark-factory) +2. [Two flows at a glance](#2-two-flows-at-a-glance) +3. [Flow A β€” Agent Sandbox capability](#3-flow-a--agent-sandbox-capability-permanent-platform-feature) +4. [Flow B β€” the Dark Factory pipeline](#4-flow-b--the-dark-factory-pipeline) + - [Flow D β€” Lambda MicroVM substrate (alternative to Flow A)](#45-flow-d--lambda-microvm-substrate-alternative-to-flow-a) + β€” enabling it: [`FLOW-D-ENABLEMENT.md`](FLOW-D-ENABLEMENT.md) +5. [The pluggable coding assistant](#5-the-pluggable-coding-assistant) +6. [Independent verification](#6-independent-verification-the-heart-of-the-pattern) +7. [Live status in the PR](#7-live-status-in-the-pr) +8. [Human-in-the-loop & the comment loop](#8-human-in-the-loop--the-iterative-comment-loop) +9. [Lifecycle, teardown & cost](#9-lifecycle-teardown--cost) +10. [Security model](#10-security-model) +10a. [GitHub credentials: Secrets Manager setup](#10a-github-credentials-secrets-manager-setup) +11. [Industry alignment & anti-patterns](#11-industry-alignment--anti-patterns-what-the-world-agrees-on) +12. [Phased delivery](#12-phased-delivery) +13. [Open questions / future work](#13-open-questions--future-work) +14. [References](#14-references) + +--- + +## 1. What is a Dark Factory + +The term is borrowed from manufacturing and popularized for coding by two sources this design +draws on: + +- **Steve Yegge β€” "Welcome to Gas City"**: a *supervisor plane* that deploys teams of + collaborating agents as composable "packs," where humans **watch the factory work** from a + rich console rather than typing code. Work is a first-class, versioned primitive. +- **HackerNoon β€” "The Dark Factory Pattern"**: an **autonomy-level** ladder and the key + engineering ideas β€” **specs instead of code**, **holdout scenarios** (the coding agent never + sees the acceptance tests; a separate evaluator judges), **build-before-push**, ephemeral + environments, and **humans reviewing results, not diffs**. + +### Autonomy levels (we target Level 3) + +| Level | What it looks like | +|------:|--------------------| +| 1 | AI finishes your sentences; you do everything else. | +| 2 | AI writes whole files; **you review every change**. | +| **3** | **AI generates code from a spec; a holdout gate + reviewers verify; you approve the merge.** ← *this design* | +| 3.5 | Some low-risk services auto-merge without you. | +| 4 | Full dark factory: specs in, merged tested code out. | + +At **Level 3**, the human's job shrinks from *"read every line of a diff"* to *"read the evidence +and click approve"* β€” but the human still gates the merge, and can drive changes via PR comments. + +--- + +## 2. Two flows at a glance + +This design is deliberately split into **two independent flows** so the sandbox capability is +useful on its own and the factory is a consumer of it. + +| | **Flow A β€” Agent Sandbox capability** | **Flow B β€” Dark Factory** | +|---|---|---| +| **What** | A permanent platform feature: Kata micro-VM sandboxes + `Sandbox` CRD + a **warm pool** kept ready | The autonomous coding pipeline: issue β†’ code β†’ test β†’ review β†’ PR β†’ merge β†’ teardown | +| **When** | Comes up automatically when the agent platform is deployed | Triggered per GitHub issue labeled `dark-factory` | +| **Where** | Installed on the **hub cluster** β€” the build/author plane, co-located with Argo Workflows | **Runs on the hub cluster**, orchestrated by Argo Workflows, co-located with the sandbox pool | +| **Lifecycle** | Long-lived; pool self-heals to a target buffer | Ephemeral per issue; torn down on merge/close | +| **Diagram** | [`diagrams/flow-a-sandbox-capability.md`](diagrams/flow-a-sandbox-capability.md) | [`diagrams/flow-b-dark-factory.md`](diagrams/flow-b-dark-factory.md) | + +> **Why split them?** The sandbox capability is generically useful (any agent workload can claim +> an isolated VM). The Dark Factory is one *consumer* of that capability. Keeping them separate +> means the isolation substrate can ship, be tested, and be reused independently of the factory. + +> **Flow D β€” a second substrate.** Flow A's isolation boundary is a **Kata micro-VM pod** on a +> platform-owned nested-virt node group. **Flow D** offers an *alternative* Flow-A substrate β€” an +> **AWS Lambda MicroVM** (serverless micro-VM, no node group) provisioned via the ACK `lambdamicrovms` +> controller and composed by a single **KRO `ResourceGraphDefinition`**. Flow B is unchanged and can +> target either substrate through the same `SandboxClaim` contract. See +> [Β§4.5](#45-flow-d--lambda-microvm-substrate-alternative-to-flow-a) and +> [`diagrams/flow-d-microvm-sandbox.md`](diagrams/flow-d-microvm-sandbox.md). *(Flow C is reserved for +> other work.)* + +> **Why the hub, not a spoke?** The Dark Factory is a **pre-dev build/author** activity: it *writes* +> code and needs GitHub write access. That belongs on the **hub β€” the control/build plane** β€” not on +> a spoke, which is the **deploy/run plane** hosting real enterprise workloads (putting a +> GitHub-write-capable author of untrusted code next to running apps is the wrong placement). The +> factory's output β€” *merged, reviewed* code β€” then flows to the spokes as a normal deployment, which +> is the right place for deployment-time security/DevOps gating. Co-locating the sandbox pool with +> Argo Workflows on the hub also keeps orchestration **single-cluster**: the workflow watches the +> coder pod and eval Job directly, owner-references cascade teardown, and no cross-cluster control +> plane is needed. The **Kata micro-VM is the isolation boundary** and travels with the workload β€” +> so "on the hub" is safe *provided* the control-plane-specific hardening in [Β§10](#10-security-model) +> is in place (dedicated tainted kata nodegroup + egress lockdown that denies the hub's own +> control-plane services). + +--- + +## 3. Flow A β€” Agent Sandbox capability (permanent platform feature) + +> πŸ“Š **See the fancy diagrams:** [`diagrams/flow-a-sandbox-capability.md`](diagrams/flow-a-sandbox-capability.md) +> (capability architecture + warm-pool state machine). + +Shipped as a GitOps addon, enabled exactly like every other platform addon β€” an ApplicationSet fans +the three agent-sandbox charts (`agent-sandbox-operator`, `agent-sandbox`, `kata-deploy`) onto the +target cluster. The capability is gated to the **hub** via `alwaysSelector`, which is honoured +regardless of the global `useSelectors` flag: + +```yaml +# gitops/addons/bootstrap/default/addons.yaml (per agent-sandbox entry) +alwaysSelector: + matchExpressions: + - key: environment + operator: In + values: ['control-plane'] # the hub's cluster-secret label β†’ hub-only +``` + +> **Migration note:** the capability was first proven on **spoke-dev** (gated `environment In [dev]`). +> It is being relocated to the **hub** (`environment In [control-plane]`) so it sits with Argo +> Workflows on the build plane β€” see [Β§2](#2-two-flows-at-a-glance) for why, and Phase 2 of the +> implementation plan for the devβ†’hub cutover (stand up on hub, then prune the spoke-dev pool). + +### What the addon installs + +| Piece | Source in this repo | Role | +|---|---|---| +| **Sandbox operator + CRDs** (`agents.x-k8s.io` + `extensions.agents.x-k8s.io/v1beta1`) | `gitops/addons/charts/agent-sandbox-operator/` (upstream `helm/` chart vendored at tag v0.5.1, sync-wave 0) | Materializes one Kata-VM pod per `Sandbox`; serves `SandboxClaim`/`SandboxTemplate`/`SandboxWarmPool` + the conversion webhook. Also owns the `agent-sandbox-system` **Namespace** (wave 0 β‡’ exists with PSS labels before wave-2 workloads). Needs `controller.extensions: true` β€” the `extensions.*` types are off by default | +| **Kata runtime (Cloud Hypervisor default)** | `kata-deploy` OCI chart (sync-wave 1) | Installs the containerd handlers on the tainted kata nodes | +| **RuntimeClasses** `kata-clh` Β· `kata-qemu` Β· `kata-fc` | `agent-sandbox/templates/10-runtimeclasses.yaml` (sync-wave 2) | Workload picks its VMM via `runtimeClassName`; the class force-merges `nodeSelector` **and tolerations** onto the pod, so selecting it is enough to land on the tainted kata pool. Owned here rather than by `kata-deploy` (whose chart emits no tolerations and would misname `kata-fc`) β€” its `runtimeClasses.enabled` is set `false` | +| **`SandboxTemplate` `coder-sandbox`** | `agent-sandbox/templates/20-sandboxtemplate.yaml` | The coder pod spec the warm pool clones (isolation invariants baked in) | +| **`SandboxWarmPool` `coder-warmpool`** | `agent-sandbox/templates/40-sandboxwarmpool.yaml` | Native operator primitive β€” keeps N idle sandboxes pre-warmed; refills on claim | +| **NetworkPolicy** (egress lockdown) | `agent-sandbox/templates/30-networkpolicy.yaml` | Default-deny egress; allow only DNS + Bifrost + HTTPS β€” **plus, on the hub, deny the control-plane services** (see [Β§10](#10-security-model)) | +| ~~kata-readiness DaemonSet~~ | *removed* | kata-deploy **4.0.0** (PR kata-containers#13284) removes the `runtime-not-ready` startup taint itself as its final install step, so the external DaemonSet is gone β€” see `startupTaints` on the kata-deploy registry entry | + +### Hub prerequisites (Auto Mode can't host Kata) + +The hub runs **EKS Auto Mode + Bottlerocket**, which **cannot** run Kata micro-VMs (same blocker +proven on the Auto-Mode spokes). Hosting the capability on the hub therefore requires a **dedicated, +self-managed nested-virt Managed Node Group** alongside Auto Mode: + +| Requirement | Detail | +|---|---| +| **Nested-virt nodes** | `c8i`/`m8i` instances with nested virtualization enabled, `/dev/kvm` present, scale-to-zero. Provisioned by the [`kata-nodepool`](../../gitops/addons/charts/kata-nodepool/README.md) chart (Karpenter `EC2NodeClass` + `NodePool`). The earlier self-managed-MNG artifacts under `agent-sandbox/nodepool/` are removed β€” see that directory's README for the superseded design. | +| **Auto-Mode addon prereqs** | Self-managed nodes get neither CNI nor kube-proxy from Auto Mode β€” the `vpc-cni` **and** `kube-proxy` EKS addons must be installed or the kata node stays `NotReady` / kata-deploy crashloops. | +| **Tainted + labelled** | Node registers `kata=true:NoSchedule` (workload taint) + `katacontainers.io/runtime-not-ready` (startup taint, removed by kata-deploy 4.0.0 itself), labelled `kata-enabled=true` β€” so **coder VMs never co-schedule with hub control-plane pods**. | + +> These are hard requirements: without the nested-virt MNG the warm pool has nowhere to run; without +> the taint + label + egress lockdown, an untrusted coder VM could land next to β€” or reach β€” the hub's +> control-plane services. See [Β§10](#10-security-model). + +> βœ… **As-built (certified on the hub):** the capability was migrated dev β†’ hub via GitOps. A +> `c8i.4xlarge` nested-virt MNG was provisioned, the `vpc-cni` + `kube-proxy` addons installed, and +> the node joined Ready. Verified end-to-end: operator `1/1`, warm pool `3/3`, a `kata-clh` pod runs +> a real micro-VM (guest kernel β‰  host), `SandboxClaim` binds + the pool refills, and the coder is +> blocked from the control plane while Bifrost + public GitHub + DNS work. The old spoke-dev pool was +> torn down. + +### Warm pool β€” instant claims, cheap idle + +When the platform finishes deploying, the operator's native `SandboxWarmPool` brings up a **target +buffer of 2–3 idle sandboxes**. A consumer binds to a *ready* VM instantly (no cold boot). Cycling +rules: + +- **On claim** β†’ provision a **refill** so the buffer stays at target. +- **On release** β†’ if the pool is above target, **remove** the extra idle sandbox. +- **Idle** sandboxes scale to `replicas: 0` (PVC retained) and resume on demand. + +> πŸ’‘ **Cost note (industry gotcha):** a literal pool of *parked, running* micro-VMs burns money. +> The consensus mitigation (E2B, Modal, Bedrock AgentCore) is **snapshot/fork-from-template + idle +> reaping**, not idle VMs left running. Implementation should prefer snapshot-restore where the +> Kata VMM supports it, and always pair the pool with aggressive idle TTL reaping. See +> [Β§9](#9-lifecycle-teardown--cost). + +--- + +## 4. Flow B β€” the Dark Factory pipeline + +> πŸ“Š **See the fancy diagrams:** [`diagrams/flow-b-dark-factory.md`](diagrams/flow-b-dark-factory.md) +> (end-to-end pipeline + detailed sequence + live-status mock). + +Runs on the **hub**, orchestrated by **Argo Workflows** (already deployed on the hub, GitOps-managed). +End to end: + +1. **Trigger** β€” an issue labeled `dark-factory` fires a GitHub **webhook** into an **Argo Events** + Sensor, which submits a `df-run` Workflow. **Dedup:** the workflow is named deterministically + `df-run-`, so GitHub's duplicate webhook deliveries (retries + rapid re-labels) collide + (`AlreadyExists`) and are harmless no-ops β€” **one issue = one in-flight run**. Without this, each + delivery spawned a competing run that force-pushed its own commit and split the `dark-factory/*` + statuses across SHAs. *(Argo Events is the native Kubernetes eventing path; a thin GitHub Action β†’ + `argo-server /api/v1/events` is the fallback if Argo Events isn't enabled.)* +2. **Claim** β€” the workflow's `claim` step creates a `SandboxClaim(warmPoolRef: coder-warmpool)` and + **binds a warm sandbox**; the operator refills the buffer. Because Argo and the pool are on the + **same cluster**, the step watches the claim's `status.conditions[Ready]` directly β€” no + cross-cluster control needed. +3. **Code** β€” the issue is written into the sandbox as `/workspace/SPEC.md`. The **pluggable + coder** (Claude Code headless by default; Kiro headless as a profile) implements on branch + `df/issue-` and **builds + runs unit tests until green** inside the Kata VM, then pushes the + branch. +4. **PR opens (after green)** β€” **the coder opens the PR itself**, from inside the VM, once its + tests are green (`entrypoint.js` β†’ `POST /repos/{repo}/pulls`), then self-reports + `dark-factory/implementation` on the head SHA. The workflow's `await-coder` step **polls GitHub** + for that PR + status β€” GitHub is the completion bus, since the VM has no cluster API access. + Before this point, status lives on the **issue**; from here on the canonical status board is the + **PR**. *(The coder therefore needs `Pull requests: write` and `Commit statuses: write` on top of + `Contents: write` β€” see [Β§10a](#10a-github-credentials-secrets-manager-setup). It still cannot + merge: that is a human approval plus the `df-merge-teardown` green-check gate.)* +5. **Independent verification** β€” *parallel* DAG steps, driven by the workflow, **never by the coder** + (see [Β§6](#6-independent-verification-the-heart-of-the-pattern) and + [diagram B.4](diagrams/flow-b-dark-factory.md#b4--the-df-run-dag-as-built--how-step-gating-works)). + Each step runs **outside** the coder in a trusted hub pod and is gated by a `when:` condition on a + prior step's output (Argo skips it if the condition is false β€” deterministic, not agentic): + - **Holdout gate** βœ… *built* β€” a hub-side step (scenarios in a ConfigMap the credential-less coder + cannot fetch) runs hidden BDD scenarios' **executable tests** (the un-gameable signal) plus a + **different-family Nova judge** that catches *gaming*; β‰₯90% to pass. Gated `when: pr-number != ""`. + - **Security review** β€” the **AWS Security Agent** (managed, read-only on the diff) as the primary + backend; an optional **Fable-5 deep-security sandbox** (a *second* isolated Kata VM) as a gated + deep tier. + - **DevOps review** β€” the **AWS DevOps Agent** (or a Fable-5 reviewer / IaC linters) for + reliability, deployability, cost, observability, and IaC correctness. Advisory in v1. + - **Deploy-test** *(P4, conditional)* β€” for PRs that touch deployable artifacts (a `detect-deployable` + step greps the diff for `Chart.yaml`/`k8s/`/`Dockerfile`), a **trusted** step deploys to an + ephemeral namespace, probes it, and tears it down. This is the only step that holds **K8s access** + β€” never the coder. Its probes are ground truth; the DevOps agent is the advisory second opinion. +6. **Gate + live status** β€” a `gate` step aggregates the findings; each step upserts the **one sticky + PR comment** β³β†’βœ…/❌ with timestamps and log/trace links (single writer = the workflow, serialized + by a per-issue mutex β€” see [Β§7](#7-live-status-in-the-pr)). +7. **Human review** β€” a human reviews the **evidence** (test results, holdout %, security/devops + findings) and either approves or comments. The `df-run` workflow ends here (PR labelled + `df/awaiting-approval`); the human's response arrives as a *new* event. +8. **Iterate** β€” a PR comment fires the Sensor β†’ a `df-iterate` workflow resumes the scaled-to-zero + sandbox (**same retained workspace PVC**) and the coder applies the change. **Bounded to N rounds**, + then a human breaks the tie (see [Β§8](#8-human-in-the-loop--the-iterative-comment-loop)). +9. **Merge + teardown** β€” a PR *review approved* event fires a `df-merge-teardown` workflow: it merges + the PR (the agent **never** self-merges β€” merge only follows an explicit human approval event), + then its `onExit` handler deletes the sandbox, PVC, and eval Job. A **reaper CronJob** sweeps + abandoned/timed-out runs as the crash-net. + +### Orchestration: Argo Workflows on the hub + +The orchestrator is **Argo Workflows**, not a bespoke long-running service. Each issue/comment/review +event submits a **short-lived Workflow** (`df-run`, `df-iterate`, `df-merge-teardown`) keyed on the +issue id β€” durable state lives in the retained workspace PVC + GitHub + a per-issue state ConfigMap, +not in a parked process. This buys per-issue isolation, concurrency (bounded by a semaphore against +the kata nodepool capacity), retries, a durable run history, native Prometheus metrics, and the Argo +UI β€” the substrate for scaling across many concurrent issues. + +> The earlier **P1 Node orchestrator** has been **removed** β€” its claim/coder/sticky-comment logic is +> now the `df-run` DAG's steps (a `resource` template creates the `SandboxClaim`, a `script` step +> polls GitHub, `onExit` tears down). Only the **coder image** remains under +> [`examples/dark-factory/coder/`](../../examples/dark-factory/coder/). + +### Two worked use-cases + +| Issue example | How it's tested | Teardown | +|---|---|---| +| *"Add a `weather-agent` to the examples"* | Deploy into an **ephemeral namespace** on the hub β†’ run holdout scenarios β†’ delete namespace | Namespace + branch artifacts | +| *"Build an EKS cluster with X"* | **Dry-run / crossplane-render** by default; a `deep-test` label spins a **real ephemeral `PlatformCluster`** (appmod-blueprints composition) | Delete the `PlatformCluster` claim | + +--- + +## 4.5. Flow D β€” Lambda MicroVM substrate (alternative to Flow A) + +> πŸ“Š **See the diagrams:** [`diagrams/flow-d-microvm-sandbox.md`](diagrams/flow-d-microvm-sandbox.md) +> (substrate architecture + platform/app ownership split + the RuntimeClass-shim bridge). +> +> πŸ”§ **To actually enable it:** [`FLOW-D-ENABLEMENT.md`](FLOW-D-ENABLEMENT.md) β€” the manual, +> per-account steps (arm64 base image, code-artifact ZIP, bucket ordering, GitHub label/tokens) that +> GitOps cannot do for you, plus the live known issues. + +Flow A's isolation boundary is a **Kata micro-VM pod** on a platform-owned nested-virt node group. +**Flow D is a second Flow-A substrate**: an **AWS Lambda MicroVM** β€” a *serverless* micro-VM with no +node group to run or pay for while idle, per-claim lifecycle, and sub-second warm starts. Flow B is +unchanged: it still creates a `SandboxClaim`, a pod still shows up, and the **same `dark-factory-coder`** +runs its coding/testing loop β€” except the coder executes inside a Lambda MicroVM. *(Flow C is reserved +for other work; this substrate is Flow D.)* + +### How it's built β€” KRO RGD over ACK primitives + +| Layer | Mechanism | Notes | +|---|---|---| +| **Composition** | **Managed KRO** (EKS Capability) + one `MicrovmSandbox` `ResourceGraphDefinition` | One CR expands into the IMAGE primitives below (built once); the running `Microvm` is NOT in the graph β€” the shim runs it imperatively | +| **GA primitives** | **Managed ACK** (EKS Capability) β€” `iam` Role, `s3` Bucket | AWS-run; the image store + build/exec roles | +| **Image primitive** | **Self-managed ACK** β€” the pre-GA `lambdamicrovms` controller | `MicrovmImage` CRD (`lambdamicrovms.services.k8s.aws/v1alpha1`); the `Microvm` is created via SDK by the shim, not as a graph resource | + +> **Why self-managed for the MicroVM controller?** Managed ACK bundles only controllers whose service +> is **GA upstream** (see the [ACK community services / GA list](https://aws-controllers-k8s.github.io/community/docs/community/services/)). +> `lambdamicrovms` is **pre-GA** (`v1alpha1`, not on that list), so it isn't in Managed ACK yet β€” it +> runs as its own GitOps addon. **Managed ACK + self-managed lambdamicrovms coexist** (different CRD +> groups β†’ no conflict). When `lambdamicrovms` goes GA, delete the self-managed addon and Managed ACK +> adopts it β€” **the RGD is unchanged**. This "install both now" posture is deliberate and futuristic. +> +> The **Managed KRO + Managed ACK capabilities themselves** are enabled at the platform layer in the +> **appmod-blueprints** repo (an EKS Capability toggle) β€” see that repo's +> `docs/EKS-Capabilities-KRO-ACK-Setup.md`. This repo owns only the **self-managed `lambdamicrovms` +> controller + the KRO `MicrovmSandbox` RGD + the sandbox shim** (Flow D). + +### The split: KRO builds the image ONCE; the shim runs the VM per session + +This is the load-bearing design decision (and it matches the ACK controller's own guidance β€” +image build is slow/declarative, running a VM is fast/imperative): + +- **Platform image β€” declarative, built ONCE by KRO/ACK.** The `MicrovmSandbox` RGD + (`agent-sandbox-lambda/templates/image/`) composes only the slow-changing infra: `MicrovmImage` + (`baseImageARN`, `buildRoleARN`, `codeArtifact.uri` β€” an **S3 zip** of the arm64 `dark-factory-coder` + + a Dockerfile) plus its **build role**, **execution role**, and **S3 artifact bucket** (ACK GA + controllers). A **single committed `MicrovmSandbox` instance** (GitOps-applied) is reconciled once; + KRO gates the handoff on a successful build (`readyWhen state == CREATED||UPDATED`). Its status + surfaces `imageARN` + `executionRoleARN`. The RGD **does not** contain a `Microvm`. +- **Per-session VM β€” imperative, driven by the shim.** Running a MicroVM (`RunMicrovm`), and its + `suspend` / `resume` / `TerminateMicrovm`, are request-time SDK ops the ACK controller does **not** + reconcile. So the shim owns them β€” never a `Microvm` CR per claim. + +### The RuntimeClass shim (claim β†’ pod β†’ MicroVM) + +A literal K8s `RuntimeClass` (like `kata-clh`) maps to a **node-local containerd handler**; Lambda +MicroVM is a **remote AWS service**, so a true node-level RuntimeClass would require a virtual-kubelet +provider (a large Go runtime β€” **out of scope**). Flow D instead ships a **`lambda-microvm` +SandboxTemplate variant** (`agent-sandbox-lambda/templates/shim/`) whose pod is a lightweight +**bridge**: on claim it **reads the platform image handoff** (`imageARN` + `executionRoleARN` from the +one built `MicrovmSandbox`) and calls **`RunMicrovm`** (SDK) to launch this session's VM, records the +`microvmID` as an annotation on the owning `Sandbox`, and holds the pod so its lifecycle mirrors the +MicroVM's. On real teardown it calls `TerminateMicrovm`. To Flow B and the user the UX is identical to +Flow A. Interactive exec/attach passthrough is **best-effort**; full fidelity is a virtual-kubelet follow-up. + +### Suspend / resume / terminate β€” the coder VM persists across the review loop + +Because the substrate is a Lambda MicroVM (not a pod), Flow D uses **suspend/resume through the Agent +Sandbox CRD** to keep the coder's context across the whole reviewβ†’fixβ†’re-review loop β€” the payoff of +this substrate over Kata (where each fix round claims a fresh pod): + +1. **Coder finishes the coding task β†’ SUSPEND** (`df-run` flips `Sandbox.operatingMode=Suspended`; the + `microvm-lifecycle` reconcile loop calls `suspend-microvm` by the annotated id). Compute is freed; + the VM's memory/disk are snapshotted. +2. DevOps + Security agents review the PR while the coder is suspended. +3. **Findings + "fix" β†’ RESUME the SAME VM** (df-iterate sets `operatingMode=Running` β†’ `resume-microvm`). + Context intact β€” no cold re-implement. +4. Coder fixes β†’ SUSPEND again; loop 2–4 until both agents clear. +5. **Final exit (merge) β†’ TERMINATE** (`df-merge-teardown` calls `TerminateMicrovm`, then deletes the + claim). This is the **only** place the VM is destroyed β€” `df-run`'s onExit is substrate-aware and + **keeps** the suspended Lambda VM (unlike Kata, which frees its pod on df-run exit). + +The ACK `Microvm` has no suspend field, so the `microvm-lifecycle` loop supplies the intentβ†’SDK +translation β€” pure shim, no virtual-kubelet. See +[`diagrams/flow-d-microvm-sandbox.md` Β§D.3a](diagrams/flow-d-microvm-sandbox.md). + +### Delivery & status + +Shipped as GitOps in its **own chart** β€” `gitops/addons/charts/agent-sandbox-lambda/` (separate from +the Kata `agent-sandbox` chart), structured as `templates/image/` (KRO RGD + the one platform +`MicrovmSandbox`) and `templates/shim/` (bridge SandboxTemplate + warm pool + `microvm-lifecycle` +controller). **Disabled by default** (`microvm.enabled=false`); the hub overlay +(`gitops/overlays/clusters/hub/agent-sandbox-lambda/values.yaml`) carries cluster-specific values, and a gated +`agent-sandbox-lambda` addon entry deploys it hub-only. The platform-capability enablement (Managed ACK ++ Managed KRO) lands separately in the **appmod-blueprints** platform repo (they're EKS Capabilities, +like the Managed ArgoCD the hub already runs). The end-to-end path has since been **run live** (image built from an arm64 base, `RunMicrovm`, coder +in the VM, PR opened, holdout gate green, suspend + warm resume). Turning it on is **not** a +GitOps-only change: three artifacts must be produced by hand per AWS account, and two of them cannot +be templated from cluster annotations. See +**[`FLOW-D-ENABLEMENT.md`](FLOW-D-ENABLEMENT.md)** for the runbook (arm64 base image, the +non-templatable `ARG` inside the code-artifact ZIP, artifact bucket ordering, GitHub label + +credentials, fix rounds) and its known-issues table. + +--- + +## 5. The pluggable coding assistant + +The coder is behind a **thin, swappable interface** β€” a deliberate choice (the industry lesson is +*don't marry a single vendor*). Two profiles ship; both run **inside** the Kata sandbox and reach +models only through the **Bifrost** LLM gateway. + +| Profile | Why | Notes | +|---|---|---| +| **A β€” Claude Code headless** *(primary)* | Purpose-built for autonomous implementβ†’buildβ†’testβ†’git loops; proven headless/CI autonomy | `CLAUDE_CODE_USE_BEDROCK` / base-URL β†’ Bifrost; strongest multi-file + shell | +| **B β€” Kiro headless** | **Spec-driven** (`spec β†’ requirements β†’ design β†’ tasks`) β€” the most natural fit since *an issue is a spec*; supports a headless GitHub Actions mode | AWS-native; documented as the second profile | + +### The coder contract (drop-in interface) + +Everything crosses the boundary as **files + env**, so swapping profiles is one config line: + +``` +INPUTS (mounted into the sandbox) + /workspace/SPEC.md # the issue, as a spec + /workspace/repo/ # the checked-out target repo (branch df/issue-) + /workspace/RETRY.md # (optional) one-line failure reasons from a prior holdout run + tmpfs: bifrost-api-key # mode 0400, read then unset β€” never in env + tmpfs: gh-token # short-TTL, mode 0400 +ENV + CODER_PROFILE=claude-code|kiro + BIFROST_URL=http://bifrost.bifrost.svc:8080 +OUTPUTS (produced by the coder) + git branch df/issue- with commits + /workspace/artifacts/result.json # what changed, build/test logs, evidence links +``` + +> The holdout scenarios are **deliberately absent** from this list β€” the coder never receives them. +> See [Β§6](#6-independent-verification-the-heart-of-the-pattern). + +--- + +## 6. Independent verification (the heart of the pattern) + +This is the part most teams skip β€” and it's why their agents learn to *game the tests*. Two +independent checks run **outside** the coder's control. + +### 6.1 Holdout gate β€” train/test separation for code βœ… *built (P2, advisory)* + +Acceptance criteria are **plain-English BDD scenarios**, each paired with an **executable test**, +stored where the coder **cannot see or edit** them. A separate hub-side step runs them against the +built code. As built, the content lives in the chart under +[`gitops/addons/charts/dark-factory/holdout/`](../../gitops/addons/charts/dark-factory/holdout/) and +renders into **hub ConfigMaps** in the `argo` namespace: + +``` +holdout/ + evaluate.js # the evaluator (β†’ ConfigMap df-holdout-eval) + -/scenarios.json # hidden scenarios + executable tests (β†’ ConfigMap df-holdout-) + -/rubric.md # how the judge scores +``` + +The `holdout-gate` Argo step (see [diagram B.4](./diagrams/flow-b-dark-factory.md#b4--the-df-run-dag-as-built--how-step-gating-works)) +clones the coder's `df/issue-N`, diffs it vs base, and runs `evaluate.js`. + +**Hard rules (these are the whole point):** + +1. **The coder cannot read or write the holdout β€” enforced by capability, not policy.** The scenarios + live in a Kubernetes ConfigMap; the coder runs in a Kata VM with **no K8s API access** + (`automountServiceAccountToken: false`, verified β€” no token, API times out), so it *cannot fetch + the ConfigMap even if it tried*. It only ever clones the target repo, never the holdout. On a + failed run the coder gets only **one-line reasons** (`RETRY.md`) β€” never the scenario text. +2. **Two signals, both required to pass a scenario:** + - **The executable test** β€” run against the built code, this is the **hard, un-gameable** signal: + it *proves* the behaviour (a `return true` stub cannot pass a real test with unseen inputs). + - **A different-family LLM judge** β€” we judge Claude's code with **Amazon Nova** + (`us.amazon.nova-pro-v1:0`) to defeat self-preference bias (a model scores its own output + higher). **2-of-3** votes smooth non-determinism. +3. **The judge detects *gaming*, not behaviour.** The test already proves behaviour; asking the judge + to re-derive it from a diff produced false negatives. So the judge's *only* job is to catch code + that passes the narrow test **without genuinely implementing it** β€” hard-coded example inputs, + lookup tables, `return true`, reaching the grading path β€” defaulting to PASS. It only sees + scenarios whose test already passed. +4. **Gate = β‰₯90%** of scenarios pass (test-green **and** judge-quorum). Posts a `dark-factory/holdout` + commit status. **Advisory in v1** (`holdout.blocking=false`) β€” reported, not enforced; flip to + `blocking: true` to gate the workflow. + +> This mirrors ML holdout sets and is directly validated by StrongDM's "Software Factory," which +> found *"`return true` is a great way to pass narrowly written tests"* and fixed it by storing +> scenarios **outside** the codebase. +> +> **Verified both directions (2026-07-15):** honest `subtract` β†’ all 4 hidden tests green, judge +> confirms no gaming β†’ **4/4 (100%) gate passed**. A hard-coded-lookup stub β†’ tests with unseen +> inputs go RED, and on the one narrow test it passes the judge votes 0/3 catching the lookup table β†’ +> **0/4 gate FAIL**. Neither signal alone is the gate; together they resist gaming. + +### 6.2 Reviews β€” the REAL AWS Frontier Agents (DevOps β†’ Security) βœ… *built* + +The reviews are the **genuine managed AWS agents** β€” **AWS DevOps Agent** and **AWS Security Agent** +(AWS Continuum / Frontier Agents). Not linters, not a Nova stand-in, not a stub. They run **outside +the coder VM** (the coder never grades itself) and are **ordered**, matching the AI-DLC model: + +``` +coder implements β†’ AWS DevOps Agent (broad, FIRST) β†’ clears? β†’ label `needs-security-review` + ↓ + AWS Security Agent (narrow/strict, SECOND, gated on the label) β†’ both clear β†’ merge +``` + +| Agent | Order | Scope | How it's invoked | +|---|---|---|---| +| **AWS DevOps Agent** β€” Release Readiness code review | **1st (broad)** | cross-repo dependency risk, standards compliance, access-control correctness, build+test in an AWS-managed env β†’ **BLOCK / Proceed with Caution / Safe to Release** | **GitHub App** auto-reviews the PR and posts a check-run (`devopsAgent.gate: check`, default). The df-run `devops-gate` step **waits** for that check, then applies `needs-security-review`. *(No headless code-review API exists; the coding-agent plugin is a `label`-mode fallback β€” see the manual-step note.)* | +| **AWS Security Agent** β€” code security review | **2nd (narrow)** | OWASP Top 10, hardcoded secrets, IAM misuse, dependency risk | **Dual-path (both run, redundant by design):** (1) **headless** β€” the `security-agent` step clones read-only, stages `{source archive, unified diff}` in S3, calls `securityagent create-code-review β†’ start-code-review-job β†’ list-findings` via the workflow's **IRSA** role, maps findings to `dark-factory/security` + a relayed PR comment (no GitHub App, no OAuth); (2) **`aws-security-agent` GitHub App** β€” once installed on the repo, auto-reviews every PR and posts **inline findings as `aws-security-agent [Bot]`** (like the DevOps Agent App). `merge.js`/`status.js` read the App's real check (`securityAgent.app.checkRunName`) so a Security **BLOCK** gates the merge. See `docs/dark-factory/AGENT-INSTALL.md`. | + +**Why the split matters (verified live 2026-07-16):** +- The **Security Agent path is 100% GitOps + headless** β€” proven end-to-end against the real service: + a flawed sample (`hardcoded AWS key + SQL injection + wildcard IAM`) returned exactly + `DEFAULT_CREDENTIALS (HIGH)`, `SQL_INJECTION (HIGH)`, `PRIVILEGE_ESCALATION (CRITICAL)`. The + committed `scripts/security-agent.sh` drives that same chain and was validated against the live API. +- The **agent space + application** are reconciled **once** by an idempotent ArgoCD **PreSync Job** + (`scripts/bootstrap-agentspace.sh`), which writes their IDs into a Secret the review step reads. Only + the **per-PR code-review + job** are created per run. IAM (IRSA role, service role, S3 bucket, OIDC + provider) is committed Terraform in **`iam/securityagent.tf`** β€” the chart *consumes* ARNs, never + mints IAM. +- **v1 = advisory** (`securityAgent.blockLevel: none`) β€” findings are reported, never fail the run. + Raise `blockLevel` to `low|medium|high|critical` to **block** on a finding at/above that risk level. + +> **⚠️ The one manual, non-GitOps step (flagged, never faked).** Both agents need a **one-time console +> connect** of the GitHub repo to the Agent Space (an OAuth grant no tool can script). For the +> **Security Agent** this is optional β€” the headless diff API needs no repo connect. For the **DevOps +> Agent** it's required (its review only runs via the GitHub App / plugin / chat). Until it's done, the +> `devops-gate` reports **not-cleared** and the Security Agent step is **skipped** β€” the pipeline +> **never fakes a DevOps pass**. Connecting the repo (β‰ˆ5 min in the AWS DevOps Agent console) is the +> single manual action in the whole pipeline. + +**Engine parameterization (coder).** The coder VM runs either engine via `coder.engine`: +`claude` (Claude Code `claude -p`, default + tested) or `kiro` (Kiro CLI `kiro run --headless`). Both +are first-class; the image (`examples/dark-factory/coder/Dockerfile`) carries both CLIs +(`KIRO_CLI_URL` build-arg pins the Kiro artifact). `entrypoint.js` branches on the engine. + +> **Why the workflow invokes them, not the coder:** it keeps the untrusted sandbox +> **credential-less** and preserves *separation of concerns* β€” the agent doing the work is not the +> one grading it (see the [lethal-trifecta gotcha](#11-industry-alignment--anti-patterns-what-the-world-agrees-on)). +> This is also why security testing is a **separate step, not folded into the coding assistant**: a +> coder that ran its own security scan would grade its own work and could be prompt-injected into +> suppressing findings. + +--- + +## 7. Live status in the PR βœ… *built* + +The human **watches the factory work** through **two coordinated surfaces on the PR** β€” no comment +spam, one canonical board. + +**1. Commit statuses = the live check surface (per-step, verifiable).** Each step posts a GitHub +**commit status** on the PR head SHA as it finishes β€” the coder posts `dark-factory/implementation`, +and the hub-side verify steps post `dark-factory/{holdout,security,devops}`. These render as the PR's +**Checks** and roll up into a single combined state. Because they're pinned to the exact SHA, they're +tamper-evident evidence, not prose. + +**2. The PR body = the one sticky status board (marker-managed).** The coder opens the PR with a +`` marker block; since it opens the PR *before* verification runs, it can +only mark the checks **running**: + +```markdown +### 🏭 Dark Factory β€” verification +- βœ… Build + unit tests: implemented, built + tests green +- ⏳ Holdout gate: running… +- ⏳ Security review: running… +- ⏳ DevOps review: running… +``` + +The workflow's **`sticky-status` step** (`review/status.js`) runs *after* every verify step, reads +the authoritative `dark-factory/*` commit statuses back from GitHub, and **rewrites the marker block +in place** with the real verdicts + overall state: + +```markdown +### 🏭 Dark Factory β€” verification +- βœ… Build + unit tests: implemented, built + tests green +- βœ… Holdout gate: holdout 4/4 (100%) β€” gate passed +- βœ… Security review: security: no findings +- βœ… DevOps review: devops: no findings + +_Overall: **success**. … Awaiting human review._ +``` + +**Single writer, idempotent.** Only the workflow rewrites the block (never the coder); it regenerates +the block from the commit statuses each run, so re-runs and the per-issue mutex never produce +duplicate or racing edits. Commit statuses are the source of truth; the body is the human-readable +rollup. *(Future: link each line to raw logs / the Argo run / the Langfuse trace β€” +verifiability-by-citation.)* + +--- + +## 7a. Success metrics (Argo/GitOps-native) + +Platform success is measured the same GitOps-native way everything else is β€” no bespoke telemetry. +Each workflow declares Prometheus metrics via Argo's `metrics:` blocks (scraped by the hub's +kube-prometheus-stack); a `grafana_dashboard`-labelled ConfigMap renders them, and **Langfuse** (on +the hub) captures the LLM-level token/cost/latency traces for per-issue drill-down. + +**Built (P4):** `df-run` emits `df_runs_total{status}` and `df_run_duration_seconds` (lead-time proxy) +via its `metrics:` block (`metrics.enabled`). The richer per-signal metrics below and the Grafana +dashboard ConfigMap are the next increment. + +| Metric | Meaning | Status | +|---|---|---| +| `df_runs_total{status}` | df-run outcomes by status (throughput + outcome mix) | βœ… built | +| `df_run_duration_seconds` | df-run wall-clock (lead-time proxy) | βœ… built | +| `df_claim_latency_seconds` | `SandboxClaim` create β†’ Ready (warm-pool health) | ⬜ next | +| `df_holdout_pass_pct` | Holdout satisfaction per run | ⬜ next | +| `df_iteration_rounds` | Human comment loops per issue (convergence) | ⬜ next | +| `df_vm_minutes` | Kata VM lifetime per run β€” the cost proxy | ⬜ next | +| `df_teardown_success` | Teardown completed (leak detection) | ⬜ next | +| change-failure rate | merged `df` PRs later reverted (post-merge signal) | ⬜ next | + +> This mirrors the Dark Factory deck's **Metrics & Cost Attribution** model: token counters β†’ +> cost-tier routing β†’ computed signals (items/hour, cycle time, queue depth) surfaced on a status +> API. Here the "status API" is Prometheus + the Argo UI + the sticky PR comment. + +--- + +## 8. Human-in-the-loop & the iterative comment loop βœ… *built (P3b)* + +Level 3 means the **human approves the merge** β€” and can steer via comments: + +- **Comment β†’ revision (`df-iterate`).** A human comment on a Dark Factory PR fires the Argo Events + Sensor (`pr-commented` dependency: `issue_comment` created, on a PR, **non-bot** β€” so the factory's + own sticky/status comments can't self-trigger a loop). It submits a **`df-iterate` workflow** that + resolves the PR β†’ `df/issue-` β†’ issue number, then re-submits **`df-run`** with `iterate-note` = + the comment. `df-run` injects it as `DF_ITERATE_NOTE`; the coder **checks out the existing branch** + (building on prior work β€” no PVC needed), appends the note to `SPEC.md`, revises, and force-pushes + β†’ the same PR updates in place and re-verifies. *(Verified: "add multiply" comment β†’ coder kept + `subtract` and added `multiply` in a new commit on the same branch.)* +- **Bounded convergence:** capped at `iterate.maxIterations` rounds (default 3), tracked by a + `df-iterations/` label on the PR (stateless across workflows); past the cap `df-iterate` + comments that a human must break the tie. Each revision is deduped by the comment id + (`df-iterate-`), and each round's run by `df-run--i`. +- The agent **never self-merges**; it only pushes to its own `df/issue-` branch. Merge happens + only in the `df-merge-teardown` workflow, and *only* in response to a genuine **human PR-approval + event** (`pr-approved`) β€” there is no path where the pipeline's own output produces an approval + (GitHub also blocks author self-approval). Branch protections and CI still apply. + +--- + +## 9. Lifecycle, teardown & cost + +| Phase | Sandbox state | Cost posture | +|---|---|---| +| Idle in warm pool | `replicas: 0` or snapshot | Minimal (no running VM) | +| Claimed / coding | `replicas: 1` | Active VM billed | +| Awaiting review | **`replicas: 0`** (PVC kept) | Minimal β€” resumes on comment | +| Merged / closed | **Deleted** (Sandbox + PVC + test infra + eval job) | Zero | + +- **Scale-to-zero between activity** keeps the (possibly long) review window cheap. +- **Teardown happens two ways (both built):** (1) every `df-run` has an **`onExit` handler** that + releases its `SandboxClaim` on success *or* failure (pool refills); (2) on human approval, + **`df-merge-teardown`** squash-merges the (green-verified) PR, deletes the coder branch, and reaps + the claim by its `dark-factory.io/issue-number` label. Because Argo and the pool are co-located on + the hub, owner-references cascade cleanup for in-workflow-created objects. +- **Merge is human-gated, never self-merge.** `df-merge-teardown` fires *only* from a + `pull_request_review` **approved** event on a `df/issue-*` branch, and `merge.js` re-checks that + every `dark-factory/*` status is `success` before merging β€” so a stray approval on a red PR can't + land. (GitHub also blocks the PR author from approving their own PR, so the approver is necessarily + a different human.) +- **Reaper CronJob** βœ… *built* β€” runs every 15 min (`reaper.schedule`) as a narrowly-scoped SA, + sweeping **stale ephemeral deploy-test namespaces** (`dark-factory.io/ephemeral=true`) and + **abandoned `df-run` `SandboxClaims`** older than `reaper.reapAfterSeconds` (3h) with no live + workflow. The crash-net behind the per-run `onExit` teardown and the claim's own TTL. +- **Conditional deploy-test** βœ… *built (P4)* β€” for PRs that touch deployable artifacts + (`detect-deployable` greps the GitHub compare API for `Chart.yaml`/`k8s/`/`Dockerfile`), a + **trusted** step creates an **ephemeral namespace**, applies `deployTest.manifestPath`, waits for + workloads to become `Available` (and flags crashloops), then **tears the namespace down via a + `trap`** β€” always, even on failure. It is the **only step that holds K8s access** (a scoped + ClusterRole: namespaces + workload kinds, no secrets/RBAC), bound to the workflow SA β€” never the + coder. Posts `dark-factory/deploy-test`; advisory in v1 (`deployTest.blocking=false`). *(Verified: + a PR adding a `k8s/hello.yaml` nginx Deployment deployed Ready into `df-test-14-…` and was reaped.)* +- **Ephemeral EKS test targets** (`deep-test` `PlatformCluster`) remain a **label-gated later tier** + (they provision a real cluster, ~15–20 min); the built default is the in-cluster ephemeral namespace. + +--- + +## 10. Security model + +Untrusted, LLM-generated code + issue text from anyone = treat the whole sandbox as hostile. + +- **Hardware isolation:** every coder runs in a **Kata micro-VM** (own kernel), not a shared-kernel + container. The isolation boundary is the VM β€” it travels with the workload regardless of host + cluster. +- **No cloud credentials in the sandbox:** the coder holds only a **Bifrost API key** and a + **scoped GitHub token** via **projected tmpfs (mode 0400)** β€” read then unset, never in env. All + AWS IAM lives with the **Argo workflow orchestrator, outside the VM**. The coder pushes its branch + and opens the PR; the *workflow* does the **merge**, and only on human approval. + > ⚠️ The coder token is **not** `contents:write`-only β€” it also needs `pull requests: write` and + > `commit statuses: write` (it opens its own PR and self-reports). Fine-grained PATs gate + > `PUT /pulls/{n}/merge` on **Contents**, so a coder token with `contents: write` *can* call the + > merge endpoint. **Branch protection (or a ruleset) requiring the `dark-factory/*` checks on the + > default branch is what actually prevents self-merge** β€” without it this boundary is advisory. + > Note branch protection and rulesets are **unavailable on private repos on the GitHub free plan** + > (both APIs return `403 Upgrade to GitHub Pro or make this repository public`), so a free-plan + > private test repo cannot enforce the merge gate at all. +- **Egress lockdown:** a **NetworkPolicy** default-denies egress and allows only **DNS + Bifrost:8080 + + GitHub/HTTPS**. `automountServiceAccountToken: false`, runAsNonRoot, seccomp `RuntimeDefault`, + drop `ALL` caps. + +**Running on the hub β€” the three-layer control-plane isolation (verified live).** Because the sandbox +pool is co-located with the hub's control-plane services (Keycloak, ArgoCD, external-secrets, Argo), +a single egress NetworkPolicy is **not** sufficient β€” and on EKS it is also *incomplete* (see the +CNI note below). The hub deployment enforces isolation in **three independent layers**, all certified +against a live coder pod: + +1. **Standard `NetworkPolicy`** (`30-networkpolicy.yaml`) β€” default-deny egress; allow only DNS + + Bifrost:8080 + public HTTPS with all RFC-1918 + link-local (incl. IMDS `169.254.169.254`) + excepted, so pod-IP egress to the control plane is blocked. +2. **Admin-tier `ClusterNetworkPolicy`** (`31-clusternetworkpolicy.yaml`) β€” a `Deny` on egress to the + control-plane namespaces. Needed because EKS VPC-CNI standard NetworkPolicy *only applies to + Deployment-owned pods*, and coder pods are owned by a **`Sandbox` CR** β€” the Admin tier applies + regardless of ownership and is evaluated first (Deny wins). +3. **ClusterIP egress-firewall DaemonSet** (`32-clusterip-egress-firewall.yaml`) β€” a host-network, + `NET_ADMIN` DaemonSet on the kata node (in `kube-system`, since the sandbox namespace's `baseline` + PodSecurity forbids privileged pods) that installs `FORWARD` iptables rules matching + `conntrack --ctorigdst` (the **original ClusterIP before kube-proxy DNAT**): allow the Bifrost + ClusterIP, drop the rest of the service CIDR. This closes the CNI gap in the note below. + +> ⚠️ **EKS VPC-CNI ClusterIP gap (found + fixed).** Neither standard NetworkPolicy nor Admin +> ClusterNetworkPolicy egress applies to traffic sent to a **Service ClusterIP** β€” kube-proxy DNATs +> it to a backend pod IP *before* policy evaluation, so control-plane Services (e.g. `172.20.x`) slip +> past the CIDR/namespace denies even though backend **pod IPs are correctly blocked**. Layer 3 (the +> node firewall) is what actually closes this. Verified: before it, `external-secrets` + the API +> server were reachable by ClusterIP; after it, both are blocked while Bifrost + public egress + DNS +> still work. + +Plus the always-on basics: + +- **Dedicated tainted kata nodegroup:** coder VMs schedule only onto the nested-virt MNG + (`kata=true:NoSchedule` + `kata-enabled=true`); control-plane pods never land there and vice-versa. +- **No cluster API from the VM:** `automountServiceAccountToken: false`, no RBAC β€” even if a coder + opens a TCP socket to a ClusterIP, it has **no credentials** to authenticate (services reject it: + the external-secrets webhook returns TLS alert 47; the API server rejects unauthenticated calls). + Layers: Kata VM + no creds + pod-IP deny (2 tiers) + ClusterIP node-firewall + service auth. + +- **Prod is never a test bed, and neither is a spoke:** the factory runs on the **hub build plane**; + the spokes are the **deploy/run plane**. Unreviewed agent code never runs next to enterprise + workloads (spoke) or touches prod β€” its output reaches the spokes only as *merged, reviewed* code + through the normal deployment path. +- **⚠️ Lethal trifecta (the #1 risk β€” see [Β§11](#11-industry-alignment--anti-patterns-what-the-world-agrees-on)):** untrusted + issue text + credentials + egress is the exact recipe for prompt-injection exfiltration + (demonstrated against GitHub-issue-driven agents in the wild). The mitigations above exist + specifically to break that trifecta: keep credentials out of the issue-ingesting context, deny + egress (including the hub's own control plane), and treat all issue/repo content as hostile input. + +--- + +## 10a. GitHub credentials: Secrets Manager setup + +The factory needs **three separate GitHub credentials**, not one. This is the fix for the +review finding that the coder VM was handed the orchestrator's full-power token: the coder runs +model-written code whose prompt is attacker-controllable issue text, so it must not hold a +credential that can merge to `main` or administer webhooks. + +Each is a `ClusterSecretStore`-backed `ExternalSecret` reading AWS Secrets Manager. Nothing is +created by hand in-cluster. + +### The three credentials + +| Credential | Cluster / namespace | SM key | k8s keys | GitHub permission | +|---|---|---|---|---| +| `dark-factory-github-events` | hub / `argo-events` | `/dark-factory/github/events` | `token`, `webhook-secret` | Metadata **read**, Contents **read**, Webhooks **write** | +| `dark-factory-github-orchestrator` | hub / `argo` | `/dark-factory/github/orchestrator` | `token` | Metadata **read**, Contents **write**, Pull requests **write**, Commit statuses **write**, Issues **write** | +| `dark-factory-github-coder` | *sandbox cluster* / `agent-sandbox-system` | `/dark-factory/github/coder` | `gh-token` | Metadata **read**, Contents **write**, Pull requests **write**, Commit statuses **write**, Issues **read** | + +Each permission above is load-bearing β€” these are the minimum sets the code actually +exercises, not aspirational ones. Under-scoping fails **mid-run**, after a sandbox has +been claimed and the model has already burned tokens: + +| Permission | Who | Why it is required | +|---|---|---| +| Issues **read** | coder | `GET /repos/{repo}/issues/{n}` β€” the coder fetches the issue to build `SPEC.md` | +| Pull requests **write** | coder | `POST /repos/{repo}/pulls` β€” **the coder opens its own PR**, from inside the VM | +| Commit statuses **write** | coder | `POST /repos/{repo}/statuses/{sha}` β€” self-reports `dark-factory/implementation`, which is what `df-run` polls | +| Contents **write** | orchestrator | `PUT /pulls/{n}/merge` and `DELETE /git/refs/heads/…` β€” the merge endpoint and the post-merge branch delete both need it | +| Issues **write** | orchestrator | `review/comment.js` creates/updates the sticky status comment (PR comments are issue comments) | + +Notes that matter: + +- **The orchestrator secret lives in `argo`, not the release namespace.** The `df-run` / + `df-iterate` / `df-merge-teardown` WorkflowTemplates are created in `.Values.argo.namespace`, + and a Workflow pod resolves `secretKeyRef` in *its own* namespace. Secrets are namespace-scoped, + so putting it anywhere else leaves the workflows unable to read it. +- **Events needs Webhooks:write** because `trigger.argoEvents.active: true` makes Argo Events + self-register the repo webhook. A strictly read-only token *will* fail registration. To make it + read-only instead, set `active: false` and register the webhook yourself. +- **`webhook-secret` is not a GitHub credential** β€” it is an arbitrary strong random string used to + validate GitHub's `X-Hub-Signature-256`. It only has to be consistent. +- **The coder's SM key is on whichever cluster runs the warm pool** β€” **today the hub**, since + `df-run` claims a sandbox with no cross-cluster mechanism. The `/` prefix resolves from + that cluster's `aws_cluster_name` annotation, so it follows the pool automatically. +- **The coder token *can* merge β€” branch protection is the only thing stopping it.** GitHub + documents `PUT /pulls/{n}/merge` as requiring **Contents** (write) for fine-grained tokens, and the + coder holds `Contents: write` (to push) plus `Pull requests: write` (to open its PR). So the C1 + split does **not** by itself prevent self-merge; a protected default branch requiring the + `dark-factory/*` checks is **required** for this boundary to mean anything. That is the same + protection [Β§9](#9-lifecycle-teardown--cost)'s merge gate depends on. + > **Not available on private repos on the GitHub free plan** β€” both + > `PUT /repos/{o}/{r}/branches/{b}/protection` and `POST /repos/{o}/{r}/rulesets` return + > `403 Upgrade to GitHub Pro or make this repository public`. On such a repo the merge gate is + > unenforceable: make the test repo **public**, upgrade the account, or run knowing the agent + > could merge its own work. + +### Why the keys are cluster-scoped + +The SM paths are prefixed `/` (injected from the `aws_cluster_name` cluster-secret +annotation via the addon registry, the same pattern as `keycloak-clients`). That lets each +cluster's external-secrets IRSA be scoped to `/*` β€” so the sandbox cluster **cannot read +the hub's orchestrator token** even if something asks it to. A flat shared prefix would re-merge at +the IAM layer exactly what the three-way split separates. + +### Creating the secrets + +Mint the three GitHub credentials first (a GitHub App installation is preferable to PATs β€” see the +follow-up below), then: + +```bash +REGION=us-west-2 +HUB=hub # cluster running argo + argo-events +SANDBOX=hub # cluster running the warm pool β€” see note below +WEBHOOK_SECRET="$(openssl rand -hex 20)" + +# 1. events (hub) β€” read + webhook admin, plus the HMAC +aws secretsmanager create-secret --region "$REGION" \ + --name "${HUB}/dark-factory/github/events" \ + --secret-string "$(jq -n --arg t "$EVENTS_TOKEN" --arg w "$WEBHOOK_SECRET" \ + '{token:$t, "webhook-secret":$w}')" + +# 2. orchestrator (hub) β€” merge + PRs + commit statuses + sticky comment +aws secretsmanager create-secret --region "$REGION" \ + --name "${HUB}/dark-factory/github/orchestrator" \ + --secret-string "$(jq -n --arg t "$ORCHESTRATOR_TOKEN" '{token:$t}')" + +# 3. coder (cluster running the warm pool) β€” push + open PR + self-report status +aws secretsmanager create-secret --region "$REGION" \ + --name "${SANDBOX}/dark-factory/github/coder" \ + --secret-string "$(jq -n --arg t "$CODER_TOKEN" '{token:$t}')" +``` + +> **`SANDBOX` is the cluster running the warm pool, which is now the hub.** `df-run`'s claim step +> creates a `SandboxClaim` with no cross-cluster mechanism, so the pool must sit on the same cluster +> as Argo. `agent_sandbox` lives in `overlays/environments/control-plane/enabled-addons.yaml` and is +> now `false` there by default (explicit opt-in β€” see FLOW-D-ENABLEMENT.md Β§E1); set it, plus +> `agent_sandbox_kata` and `kata_nodepool`, to run the Kata path. All three secrets land under `hub/`. The `/` +> prefix resolves from each cluster's own `aws_cluster_name` annotation, so if you move the pool back +> to a spoke, only this variable changes. + +Pass tokens via environment variables as above rather than inline, so they do not land in shell +history. To rotate, use `put-secret-value` with the same `--name`; ESO picks the change up within +`refreshInterval` (1h by default). + +### Verifying + +```bash +# ExternalSecrets should report SecretSynced +kubectl --context "$HUB" get externalsecret -A | grep dark-factory-github +kubectl --context "$SANDBOX" get externalsecret -n agent-sandbox-system | grep dark-factory-github + +# and the resulting Secrets should carry the expected keys +kubectl --context "$HUB" get secret dark-factory-github-events -n argo-events \ + -o jsonpath='{range $k,$v := .data}{$k}{"\n"}{end}' +``` + +If an `ExternalSecret` reports `SecretSyncedError`, check in this order: the SM secret exists in the +right region; its JSON contains the property names above; and **the cluster's external-secrets IRSA +policy covers the `/dark-factory/github/*` prefix** β€” if that policy enumerates specific +secret ARNs rather than a prefix, new paths fail with `AccessDenied` and no amount of chart +configuration fixes it. + +Symptom when a credential is missing: the consuming pods sit in `ContainerCreating` with +`MountVolume.SetUp failed … secret "dark-factory-github-*" not found`, indefinitely. Nothing +crashes and nothing retries visibly β€” the mount just never satisfies. + +### Follow-up: per-run minted tokens + +All three are **standing** credentials. The stronger design, and what [Β§10](#10-security-model) +describes as the coder's "short-TTL token", mints a GitHub App installation token **per run** +scoped to the target repo, injected via the `SandboxClaim`, so the VM never holds anything +reusable. external-secrets ships a `GithubAccessToken` generator that can do this and can +down-scope permissions per token, so one App can serve all three roles. Keep the App **private +key** on the hub only: anything holding it can mint any permission the App has, so placing it on +the sandbox cluster would undo the split. + +--- + +## 11. Industry alignment & anti-patterns (what the world agrees on) + +We validated this design against how GitHub Copilot coding agent, OpenAI Codex cloud, Devin, Google +Jules, Cursor background agents, Factory.ai, and StrongDM's "Software Factory" actually work. + +### βœ… Where we match consensus + +| Design choice | Industry practice | +|---|---| +| Issue β†’ event β†’ ephemeral sandbox β†’ build/test β†’ PR | The recurring ~7-stage pipeline across Copilot/Codex/Devin/Jules/Factory | +| **DAG orchestration + concurrent dispatch** (Argo Workflows) | *Convergent pattern.* Stripe/Coinbase/Ramp/StrongDM independently arrived at isolated sandboxes + subagent/DAG orchestration + cost-routing for scale | +| **Kata micro-VM isolation** | *Above-consensus.* microVM-class isolation (Firecracker, Kata, Bedrock AgentCore's per-session microVM) is the defensible choice for untrusted LLM code; shared-kernel containers are considered insufficient | +| Build/test **until green before** the PR | Explicit in codex-1's RL training, Devin, Copilot | +| **Holdout scenarios the coder never sees** | *Above-consensus.* Directly matches StrongDM's Software Factory (they learned it the hard way after `return true` gamed their tests) | +| **One sticky status surface**, not comment spam | Copilot draft-PR + session logs; Devin single review status; Factory "Mission Control" | +| Human **approves the PR**, agent iterates on comments | The dominant gating norm β€” agents do **not** self-merge by default | +| Single coder + **independent read-only reviewers** | Anthropic + Cognition agree parallel multi-agent *authoring* is a poor fit for coding; the good pattern is one coder + a fresh model reviewing the finished diff (CodeRabbit's Security Agent) | + +### ⚠️ Anti-patterns we explicitly design against + +1. **Lethal trifecta / prompt injection (highest risk).** Untrusted issue text + cloud creds + egress + β†’ data exfiltration. Invariant Labs demonstrated a malicious GitHub *issue* injecting an agent + into leaking private-repo data via an auto-PR. **Our defense:** credentials never in the + issue-ingesting sandbox context; egress denied except Bifrost/GitHub; issue/repo content treated + as hostile; frontier agents scoped read-only. *(Willison "lethal trifecta"; Invariant Labs.)* +2. **Reward hacking / test-gaming.** Frontier models stub evaluators (`evaluate = _always_ok`), make + `verify()` return true, read reference answers, or delete the test oracle (METR, OpenAI, + Anthropic). **Our defense:** the holdout the coder cannot see or edit β€” if the coder can reach it, + the holdout is theater. +3. **LLM-judge as a sole hard gate.** Judges have proven position/verbosity/**self-preference** bias + (causal β€” a model favors its own family's output). **Our defense:** different judge model + + paired executable tests + 2-of-3 + a probabilistic satisfaction score, never a lone boolean. +4. **Multi-agent over-orchestration.** **Our defense:** single-threaded coder; Security/DevOps are + stateless read-only reviewers on the finished diff, never co-authors. +5. **Non-converging comment loops.** **Our defense:** batch comments into one run, cap iterations, + hard time/turn limits. +6. **Warm-pool idle burn.** **Our defense:** snapshot/fork + idle reaping + scale-to-zero, not parked + VMs. +7. **Rubber-stamp reviews.** AI-co-authored PRs carry measurably more issues; "review results not + code" can decay into a green rubber stamp. **Our defense:** the human reviews *structured + evidence* (tests + holdout % + security findings + diff-path confinement), and high-risk changes + (infra, `deep-test`) get a firmer gate. + +--- + +## 12. Phased delivery + +Each phase is independently valuable β€” if you stop after any one, you're better off than before. + +| Phase | Delivers | Status | Independently useful? | +|------:|----------|:------:|-----------------------| +| **P0** | **Relocate the sandbox capability to the hub** β€” nested-virt MNG + control-plane isolation + devβ†’hub cutover (all GitOps) | βœ… **done** | βœ… Kata warm pool co-located with Argo on the build plane | +| **P1** | First `df-run` **WorkflowTemplate**: trigger β†’ claim warm sandbox β†’ Claude Code coder β†’ build/test β†’ workflow opens PR + sticky status β†’ manual teardown | βœ… **done** | βœ… A working autonomous-PR loop on Argo | +| **P2** | Strict **holdout gate** (hidden scenarios in a hub ConfigMap, executable tests + a different-family Nova judge, β‰₯90% gate) | βœ… **done** (advisory) | βœ… Quality gate that resists gaming β€” verified green (honest code 4/4) *and* adversarially (gamed stub 0/4) | +| **P3** | **Security + DevOps review steps** β€” parallel hub-side reviewers, `auto` backend (linters + Nova), advisory, posting `dark-factory/{security,devops}` statuses (managed AWS-Agent backend swappable in when its API lands) | βœ… **done** (advisory) | βœ… Independent review evidence β€” verified: clean code 0 findings; adversarial diffs correctly flagged | +| **P3b** | **Full event-driven lifecycle**: trigger dedup (one issue = one run) + live PR-body status + **`df-merge-teardown`** (approval β†’ green-gated squash-merge + teardown) + **`df-iterate`** (PR comment β†’ bounded revision on the existing branch) + coder no-diff guard | βœ… **done** | βœ… Hands-off labelβ†’runβ†’verifyβ†’PR, commentβ†’revise, approveβ†’mergeβ†’teardown β€” all verified live | +| **P4** | Conditional **`deploy-test`** (gated on `detect-deployable`; ephemeral namespace deploy+probe+teardown, scoped ClusterRole) + **reaper CronJob** + **df-run Prometheus metrics**; *(deep-test `PlatformCluster` tier + Grafana dashboard remain)* | βœ… **done** (core) | βœ… Full lights-off lifecycle + measurement β€” deploy-test verified (nginx Ready + reaped) | +| **P5** | **Kiro** coder profile; per-severity **blocking** gate option; **Fable-5 deep-security sandbox** (`deep-sec`) | ⬜ planned | βœ… Vendor-plurality + higher autonomy + deep review | + +--- + +## 12a. Running Kata on EKS Auto Mode clusters (validated design) + +The hub (like the spokes) runs **EKS Auto Mode + Bottlerocket** (`c6a`/`c6g` nodes). Auto Mode's +managed nodes **cannot host Kata**: no control over `cpuOptions.nestedVirtualization`, no +kernel-module loading (`modprobe kvm_intel`), no `kata-deploy`, and those node types don't expose +VT-x. `eks-platform-openclaw` avoids Auto Mode entirely for this reason β€” but we don't have to. + +> **Applies to the hub.** This design was first validated on spoke-dev, but the mechanism β€” a +> nested-virt MNG *alongside* Auto Mode β€” is exactly what the hub relocation requires. The same +> chart artifacts, node bootstrap, and hard-won lessons below carry over verbatim; only the target +> cluster changes (and the hub adds the control-plane egress lockdown from [Β§10](#10-security-model)). + +### Decision: self-managed nested-virt MNG *alongside* Auto Mode + +Add a small, tainted **self-managed Managed Node Group** of **nested-virt `c8i`/`m8i`** instances to +the cluster (spoke-dev in the original validation; **the hub** under the current design). Auto Mode +keeps running everything else; kata sandboxes schedule onto the MNG via the `kata=true:NoSchedule` +taint the chart already applies. We chose an **MNG, not a second Karpenter** β€” running a +self-managed Karpenter beside Auto Mode's managed Karpenter risks NodePool/CRD conflicts, whereas +MNGs are additive and coexist cleanly. + +Rejected alternatives: **Bedrock AgentCore / Fargate** (breaks the k8s-native pod model our whole +Sandbox/warm-pool/claim design depends on β€” it's an invoke-a-session runtime, not a pod we own); +**gVisor** (same Auto-Mode node-install blocker as Kata, weaker isolation). + +### βœ… Validated by two live tests (spoke-dev, 2026-07-10) + +A `c8i.4xlarge` kata MNG was created on spoke-dev, exercised, then torn down. Results: + +| Question | Result | +|---|---| +| Self-managed MNG coexists with Auto Mode? | **βœ… Yes** β€” MNG provisioned alongside Auto Mode nodepools, no conflict; Auto Mode stayed healthy | +| Nested virtualization / `/dev/kvm`? | **βœ… Yes** β€” `/dev/kvm` present, `kvm_intel` loaded, 32 `vmx` flags, via `CpuOptions.NestedVirtualization: enabled` | +| Node joins the cluster & goes Ready? | **βœ… Yes** β€” with the fixes below (nodeadm endpoint/CA + vpc-cni + kube-proxy) | +| Kata runtime install (kata-deploy)? | **βœ… Yes** β€” `1/1`, zero restarts, once `kube-proxy` was installed | +| **Real Kata VM runs?** | **βœ… YES** β€” pod under `kata-clh` had guest kernel `6.18.35` vs host `6.12.90` = true hardware VM isolation | + +### Hard-won lessons (baked into the implementation) + +1. **Node bootstrap** β€” do **not** override the AMI + userData with plain bash; that clobbers the + EKS bootstrap and the node boots (`/dev/kvm` present) but never joins. Use the **AL2023 nodeadm + MIME userData**, and set nested-virt via the launch-template `CpuOptions`, not userData. +2. **Teardown ordering** β€” delete the **MNG first and let it drain** (set min/desired=0 first). + Terminating the instance out from under the MNG makes the ASG respawn and can wedge the delete on + a `Pending:Wait` lifecycle hook; recover with `terminate-instance-in-auto-scaling-group` + + `complete-lifecycle-action`. +3. **Custom-AMI nodeadm needs cluster coordinates** β€” with a custom `ImageId`, nodeadm can't + auto-discover the API; you must set `apiServerEndpoint` + `certificateAuthority` + `cidr` in the + NodeConfig, or it fails "Apiserver endpoint is missing in cluster configuration". +4. **Auto Mode has no `vpc-cni`** β€” self-managed MNG nodes stay `NotReady` (`cni plugin not + initialized`) until you install the `vpc-cni` EKS addon. `aws-node` tolerates all taints and + schedules onto the kata node once installed. +5. **kata-deploy on Auto Mode (open item)** β€” the upstream kata-deploy chart defaults to the + **experimental nydus snapshotter** (`EXPERIMENTAL_SETUP_SNAPSHOTTER=nydus`), which restarts + containerd and briefly drops CNI networking; kata-deploy then fails its own API call + (`Failed to get node ... client error (Connect)`) and crashloops before installing the runtime. + Fix to apply next: disable the experimental nydus snapshotter (openclaw uses overlayfs) and/or + raise kata-deploy's API-retry tolerance. Everything *up to* the runtime install is proven; the + runtime install itself needs this one chart-tuning fix. + +Also fixed during testing: the kata-deploy Helm values are **top-level** (`nodeSelector`, +`tolerations`, `shims`) for a direct install β€” the nested `kata-deploy:` key only applies when it's +a subchart. Our catalog entry uses the nested form (correct, since ArgoCD deploys it as its own +app), but a direct `helm install` must use top-level values. + +--- + +## 13. Open questions / future work + +*To resolve during implementation β€” flagged honestly rather than assumed:* + +- **Nested-virt capacity on the hub** β€” confirm `c8i`/`m8i` availability + headroom for the kata MNG + alongside the hub control plane; size the warm-pool/semaphore ceiling to it. +- **Exact hub control-plane egress deny list** β€” the concrete namespaces/service CIDRs (keycloak, + argocd, external-secrets, argo) to encode in the NetworkPolicy for the hub deployment. +- **Headless auth** for Claude Code & Kiro through a Bifrost base-URL override inside a Kata VM + (prototype first in P1). +- **Bifrost VK + tmpfs secret projection** β€” mint a short-TTL GitHub token + Bifrost virtual key and + project them onto the claimed sandbox (mode 0400). Documented but not yet wired β€” close in P1. +- **Exact AWS Security / DevOps Agent APIs & auth** β€” confirm the invocation contract at build time; + clear **Fable-5** provider-data-share / 30-day retention before enabling the deep-sec tier. +- **Workflow RBAC scope** β€” the Argo workflow SA needs `sandboxclaims` (CRUD) + read `sandboxes` + + eval `Job`/`ConfigMap` in `agent-sandbox-system`, plus `PlatformCluster` claims for the `deep-test` + path. No pod/exec, no secrets, no cluster scope. +- **Argo `resource`-template `successCondition` on CRD conditions** β€” validate the JSONPath filter + form against Argo v3.6.7, or fall back to a `kubectl wait --for=condition=Ready` step. +- **Workspace access mode** β€” RWO (EBS) forces strict coder↔eval serialization + same-AZ pinning; + RWX (EFS) allows read-only eval-alongside and cheaper iteration. Decide before P2. + +> βœ… **Resolved since the first draft:** native `SandboxWarmPool` CRD is adopted (custom pool-manager +> CronJob dropped); the upstream operator is vendored into the addon catalog; Kata-on-Auto-Mode is +> validated ([Β§12a](#12a-running-kata-on-eks-auto-mode-clusters-validated-design)); the sandbox +> capability is being relocated devβ†’hub (this design). + +--- + +## 14. References + +**Pattern sources** +- Steve Yegge β€” *Welcome to Gas City* β€” https://steve-yegge.medium.com/welcome-to-gas-city-57f564bb3607 +- *The Dark Factory Pattern: Moving From AI-Assisted to Fully Autonomous Coding* β€” https://hackernoon.com/the-dark-factory-pattern-moving-from-ai-assisted-to-fully-autonomous-coding +- Kiro headless in GitHub Actions β€” https://builder.aws.com/content/35cLFnKM6DJMgRzdZQ7XPZkJmoz/automate-reviews-in-github-actions-with-kiro-headless-mode + +**Industry pipelines** +- GitHub Copilot coding agent β€” https://github.blog/news-insights/product-news/github-copilot-meet-the-new-coding-agent/ +- OpenAI Codex β€” https://openai.com/index/introducing-codex/ +- Devin SDLC integration β€” https://docs.devin.ai/essential-guidelines/sdlc-integration +- Factory.ai Missions β€” https://docs.factory.ai/cli/features/missions/overview +- StrongDM Software Factory β€” https://factory.strongdm.ai/ +- AWS Bedrock AgentCore runtime sessions (per-session microVM) β€” https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/runtime-sessions.md +- AWS DevOps Agent β€” https://aws.amazon.com/devops-agent/ + +**Failure modes / safety** +- Simon Willison β€” *The lethal trifecta* β€” https://simonwillison.net/2025/Jun/16/the-lethal-trifecta/ +- Invariant Labs β€” GitHub MCP prompt-injection exfiltration β€” https://invariantlabs.ai/blog/mcp-github-vulnerability +- METR β€” *Recent frontier models are reward hacking* β€” https://metr.org/blog/2025-06-05-recent-reward-hacking/ +- OpenAI β€” *Detecting misbehavior in frontier reasoning models* β€” https://openai.com/index/chain-of-thought-monitoring/ +- Anthropic β€” *Reward tampering* β€” https://www.anthropic.com/research/reward-tampering +- LLM-judge self-preference bias β€” https://arxiv.org/abs/2404.13076 Β· MT-Bench β€” https://arxiv.org/abs/2306.05685 +- Cognition β€” *Don't build multi-agents* β€” https://cognition.ai/blog/dont-build-multi-agents +- Anthropic β€” *Claude Code best practices* β€” https://www.anthropic.com/engineering/claude-code-best-practices + +**Platform building blocks (this monorepo & siblings)** +- `eks-platform-openclaw` β€” Kata micro-VM sandbox, `Sandbox` CRD, session-router lifecycle (uses LiteLLM there; **this platform uses Bifrost** as the LLM gateway) +- `appmod-blueprints` β€” `PlatformCluster` Crossplane composition (ephemeral EKS), KRO CI/CD pipeline +- `agent-platform-amazon-eks` β€” hub/spoke fleet, addon ApplicationSets, kagent, agent-gateway, **Bifrost** LLM gateway diff --git a/docs/dark-factory/SUBSTRATE-BENCHMARK.md b/docs/dark-factory/SUBSTRATE-BENCHMARK.md new file mode 100644 index 00000000..2d2084f1 --- /dev/null +++ b/docs/dark-factory/SUBSTRATE-BENCHMARK.md @@ -0,0 +1,178 @@ +# Dark Factory β€” Substrate Benchmark: Kata micro-VM vs Lambda MicroVM + +A side-by-side comparison of the two sandbox substrates that run the autonomous coder, +measured on **identical issues fired in parallel** on the same hub cluster. + +- **Flow B β€” Kata micro-VM** (mature, default): the coder runs in a hardware-isolated Kata + pod on a self-managed nested-virt EKS node group. +- **Flow D β€” AWS Lambda MicroVM** (pre-GA): the coder runs in a Firecracker MicroVM + provisioned via the `lambdamicrovms` ACK controller, driven by a bridge pod. + +Both run the **same `dark-factory-coder`** (same `entrypoint.js`), produce the same kind of +PR, and go through the **same review gates** (AWS DevOps Agent + AWS Security Agent). The only +difference is *where the coder executes* and *how it's provisioned*. + +--- + +## TL;DR + +| | Kata micro-VM (Flow B) | Lambda MicroVM (Flow D) | +| --- | --- | --- | +| **Workflow** | `df-run` (certified) | `df-run-lambda` (separate, MicroVM-native) | +| **Provisioning** | pre-warmed pool β†’ **instant claim** | **RunMicrovm cold-start per session** (~90s) | +| **Time to first PR** (from label) | ~**2 min** | ~**2.5 min** | +| **LLM path** | Bifrost gateway (in-cluster) + Langfuse traces | **Bedrock-direct** (exec role) β€” no cluster network | +| **Scale-to-zero when idle** | ❌ node pool runs continuously | βœ… **suspend-to-zero** between PR and merge | +| **Fix-round mechanic** | fresh pod each round | **resume the SAME suspended VM** (warm); recreate if the pre-GA resume fails | +| **Infra to manage** | nested-virt node group (Karpenter/MNG) | none β€” serverless MicroVMs | +| **Observability** | native `kubectl logs` | custom `/logs` HTTP endpoint (no runtime CloudWatch) | +| **Maturity** | production-ready today | pre-GA (preview) β€” pilot-grade | +| **Economics at 1000s scale** | pay for idle capacity | pay per active minute (the strategic win) | + +**Bottom line:** at small scale the two feel equivalent (the LLM coding step ~2–4 min and the +external review agents ~8–15 min dominate total time on *both*). The Lambda substrate's advantage +is **not latency** β€” it's **operational + economic**: no node pool to run, and suspend-to-zero +between the PR and the human's review/merge. Its cost is **maturity** (pre-GA control plane β€” resume +from suspend is occasionally flaky, mitigated by the recreate-fallback) and the extra plumbing below. + +--- + +## Benchmarked run (identical issue, per substrate) + +### Time to first PR (from label β†’ PR opened) +| Substrate | Issue | PR | Elapsed | Notes | +| --- | --- | --- | --- | --- | +| Kata | #137 | β€” | ~**2 min** | pre-warmed pod, instant claim | +| Lambda | #135 | #136 | ~**2.5 min** (20:53:03 β†’ 20:55:35) | native `df-run-lambda`, RunMicrovm cold-start | + +**Ξ” β‰ˆ 30–90s** β€” the MicroVM cold-start (`RunMicrovm` β†’ RUNNING β†’ `/run`) vs Kata's pre-warmed pod +claim. Note the MicroVM-native `df-run-lambda` is **faster than the old bridge path** (~3.7 min): +removing the SandboxClaim/warm-pool indirection cut ~1 min. Everything after (clone β†’ LLM β†’ push) is +identical code and takes the same time. + +### Lifecycle timing (Lambda #135, native pipeline) +| Phase | Time | +| --- | --- | +| provision-microvm (RunMicrovm β†’ RUNNING β†’ `/run` HTTP 200) | ~90s | +| **drive-coder** (clone β†’ LLM β†’ push β†’ PR) | ~60s | +| suspend-microvm (VM β†’ SUSPENDED, stays down) | ~5s | +| holdout / deploy-test (terraform validate) | ~20–40s each | +| security-agent + devops-gate (external) | ~8–15 min combined (dominates) | +| fix round: resume-or-recreate + coder re-run β†’ new commit | ~2 min | + +*(The external review agents dominate total wall-clock on BOTH substrates. The MicroVM is SUSPENDED +for the entire multi-minute review window β€” that idle time is free on Lambda, billed on Kata.)* + +--- + +## Where the logs are + +| What | Kata (Flow B) | Lambda (Flow D) | +| --- | --- | --- | +| Pipeline steps | Argo UI (`/argo-workflows`) or `kubectl logs -n argo ` | **same** | +| Coder output | `kubectl logs -n agent-sandbox-system df-issue-` (native) | **`GET https:///logs`** with an auth token (runtime CloudWatch routing is unreliable on the pre-GA runtime, so the hook-server captures coder stdout to a file + serves it) | +| Image build | n/a (normal ECR image) | CloudWatch `/aws/lambda/microvms/coder-image` | + +--- + +## DAG β€” two SEPARATE WorkflowTemplates (one per substrate) + +The substrates run **different Argo WorkflowTemplates**, so each graph is clean and Kata's certified +pipeline is never touched by Flow D changes. The Argo Events sensor routes by label: +`dark-factory` β†’ `df-run` (Kata), `darkfactory-lambda` β†’ `df-run-lambda` (Flow D). + +**Kata β€” `df-run` (certified, byte-identical to the mature pipeline):** +``` +claim(SandboxClaim) β†’ drive-coder β†’ { holdout, devops-gate β†’ security, detect β†’ deploy-test } β†’ status β†’ onExit(teardown: delete claim) +``` + +**Lambda β€” `df-run-lambda` (MicroVM-native; NO SandboxClaim / bridge / warm pool):** +``` +provision-microvm β†’ drive-coder β†’ suspend-microvm β†’ { holdout, devops-gate, security, detect β†’ deploy-test } β†’ status β†’ onExit(keep suspended VM) +``` +The one extra node β€” `suspend-microvm` β€” is **explicit and lives only in the Lambda graph**, so the +Kata graph still contains zero MicroVM nodes. Suspend/resume is owned by the workflow directly (it +calls `aws lambda-microvms suspend/resume-microvm`), not a bridge or a lifecycle controller. + +### Substrate-specific mechanics +- **Kata:** `claim-sandbox` binds a **pre-warmed** pod from `coder-warmpool`; the operator injects + `DF_*` env; the baked `entrypoint.js` runs in-cluster, reaches models via **Bifrost**, native logs. +- **Lambda:** `provision-microvm` (a single workflow step, running as `dark-factory-workflow` with the + lambda-microvms role via Pod Identity) does it all β€” no bridge pod, no warm pool: + 1. reads the platform image handoff (imageARN + execRoleARN, built **once** by KRO/ACK), + 2. creates the **`Microvm` CR** (stable name `mvm-`) + a runHookPayload Secret + (the review note is folded in here β€” the coder has no claim env), + 3. waits RUNNING + endpoint, mints an auth token, **POSTs `/run`** β†’ the hook-server + background-spawns the same `entrypoint.js` with `USE_BEDROCK=1` (Bedrock-direct, no cluster net), + 4. the `suspend-microvm` DAG step suspends the VM once the PR is open (idlePolicy + `autoResumeEnabled=false` + nothing polls the endpoint β†’ it **stays** suspended), + 5. on a fix round `provision-microvm` **resumes the same suspended VM** (warm resume); if the + pre-GA service failed the resume (VM terminated), it **recreates a fresh VM** automatically, + 6. `df-merge-teardown` deletes the `Microvm` CR at merge β†’ controller `TerminateMicrovm`. + +--- + +## Step-by-step: what actually happens + +### Kata (Flow B) β€” `df-run` +1. Issue labeled `dark-factory` β†’ sensor dep `issue-labeled-kata` β†’ `df-run`. +2. `claim-sandbox` binds a **pre-warmed** Kata pod from `coder-warmpool` (instant). +3. Operator injects `DF_*` β†’ baked `entrypoint.js`: clone β†’ Claude Code (via **Bifrost**) β†’ **open PR**. +4. Review gates: DevOps Agent + Security Agent β†’ consolidated verdict. +5. "fix findings" β†’ `df-iterate` β†’ **new** Kata coder round β†’ re-review. +6. Approve β†’ `df-merge-teardown` merges + deletes the claim. + +### Lambda MicroVM (Flow D) β€” `df-run-lambda` +1. Issue labeled `darkfactory-lambda` β†’ sensor dep `issue-labeled-lambda` β†’ `df-run-lambda`. +2. `provision-microvm` creates the `Microvm` CR β†’ controller `RunMicrovm` (**cold-start ~90s**) β†’ + RUNNING; mints token; `POST /run` β†’ hook-server spawns the coder (`USE_BEDROCK=1`, + **Bedrock-direct**). Coder: clone β†’ Claude Code β†’ **open PR**. +3. `suspend-microvm` step suspends the VM β†’ it **stays SUSPENDED** while gates run (scale-to-zero). +4. Same review gates + verdict. +5. "fix findings" β†’ `df-iterate` β†’ `df-run-lambda` fix round: **resume the SAME VM** (warm) or, if the + pre-GA service failed the resume, **recreate fresh**; the coder re-runs with the note β†’ new commit. +6. Approve β†’ `df-merge-teardown` merges + deletes the `Microvm` CR β†’ controller `TerminateMicrovm`. + +--- + +## Gotchas the Lambda substrate needed (that Kata does not) + +Because a MicroVM is **outside the cluster network, has a read-only rootfs, and uses a +snapshot/hook execution model**: + +| # | Gotcha | Fix | +| --- | --- | --- | +| 1 | Coder crashed `EACCES mkdir /workspace/artifacts` (no writable volume like Kata) | set `WORKSPACE=/tmp/workspace` (writable tmpfs) β€” *the silent killer* | +| 2 | Can't reach Bifrost's ClusterIP from a MicroVM | **Bedrock-direct** via the exec role (`bedrock:InvokeModel`); no Bifrost/NLB/VPC-connector | +| 3 | Runtime logs don't reach CloudWatch | hook-server captures coder stdout β†’ `/logs` HTTP endpoint | +| 4 | Coder is one-shot but the MicroVM `/run` hook has a 30s timeout | `/run` **background-spawns** the coder + returns fast; pipeline polls GitHub for the PR | +| 5 | Ingress: `ALL_INGRESS` blocks auth-token minting | use **`HTTP_INGRESS`** | +| 6 | aws-cli image lacks `lambda-microvms`; no node | step image = `aws-cli:latest` (has the verbs) + python3 for JSON + fetch kubectl at start | +| 7 | `runHookPayload` is a `SecretKeyReference`; imperative `run-microvm --run-hook-payload` doesn't fire `/run` | deliver via the **declarative `Microvm` CR** | +| 8 | Image rebuild: overwriting the same S3 key doesn't rebuild | use versioned artifact keys; bump `codeArtifactKey` (the bucket half is composed by the RGD from the bucket it creates, so only the key is settable) | +| 9 | **VM would not stay SUSPENDED** (console showed RUNNING) | `idlePolicy.autoResumeEnabled=false` **and** never hit the VM endpoint after `/run` β€” any request auto-resumes it | +| 10 | **VM auto-terminated before the fix round** (resume hit "already terminated") | `suspendedDurationSeconds=28800` (Lambda's 8h max) so it survives the reviewβ†’human window; 5 min was far too short | +| 11 | **Fix round re-ran but committed nothing** (coder "done" on old sha) | the review note must ride on the **runHookPayload** (MicroVM has no claim env); hook-server keys its `/run` guard on a **per-invocation run-id** so a resumed VM accepts a fresh run | +| 12 | Pre-GA **resume-from-suspend is intermittently flaky** (Internal service error β†’ VM terminates) | `provision-microvm` checks the VM's real AWS state and **recreates a fresh VM** when resume isn't possible β€” the fix round self-heals | +| 13 | Two Sandboxes fought over one VM β†’ suspend/resume **flapped** until the VM died | name the CR/workflow by **issue-number** (stable across rounds) so there's exactly **one VM per issue** | +| β€” | IAM: the workflow SA calls the lambda-microvms verbs | Pod Identity binds `dark-factory-workflow` β†’ the lambda-microvms role (get/suspend/resume/terminate-microvm + create-auth-token); exec role keeps `bedrock:InvokeModel` | + +> **Note on the ACK CR status:** `Microvm.status.state` is **stale** β€” it does not reflect +> suspend/resume/terminate. Always read AWS truth with `aws lambda-microvms get-microvm --query state`. + +Kata needs **none** of these β€” it's an in-cluster pod with a mounted workspace, native logs, +Bifrost reachability, and a normal ECR image. That's why the two substrates are **separate +WorkflowTemplates** (`df-run` vs `df-run-lambda`): Flow D's plumbing never touches the certified Kata graph. + +--- + +## When to choose which + +- **Kata (today):** production-ready, mature, standard `kubectl`/IDE access, in-cluster networking. + Choose it now for reliability. Cost: you run + pay for a nested-virt node pool continuously. +- **Lambda MicroVM (strategic):** serverless, suspend-to-zero per idle session, no node pool β€” + the model that scales economically to thousands of sessions. Choose it as it reaches GA. Cost + today: pre-GA control-plane maturity + the plumbing above. + +Both share the **same coder, same pipeline, same review gates, same UX** β€” so migrating between +substrates is a label change, invisible to the developer/issue author. diff --git a/docs/dark-factory/SUBSTRATE-DIAGRAMS.md b/docs/dark-factory/SUBSTRATE-DIAGRAMS.md new file mode 100644 index 00000000..26442e88 --- /dev/null +++ b/docs/dark-factory/SUBSTRATE-DIAGRAMS.md @@ -0,0 +1,134 @@ +# Dark Factory β€” Substrate Diagrams (Kata vs Lambda MicroVM) + +Visual companion to [`SUBSTRATE-BENCHMARK.md`](./SUBSTRATE-BENCHMARK.md). All diagrams are +Mermaid (render on GitHub). + +--- + +## 1. Label-routed to two SEPARATE WorkflowTemplates + +The Argo Events sensor routes each label to a **different** WorkflowTemplate, so Kata's certified +pipeline is never touched by Flow D. Kata keeps its SandboxClaim; Lambda provisions a MicroVM directly. + +```mermaid +flowchart TD + ISSUE["GitHub issue labeled"] --> SENSOR["Argo Events sensor"] + SENSOR -->|"dark-factory
(issue-labeled-kata)"| DFRUN["df-run
(certified Kata)"] + SENSOR -->|"darkfactory-lambda
(issue-labeled-lambda)"| DFRUNL["df-run-lambda
(MicroVM-native)"] + DFRUN --> KCLAIM["claim-sandbox
(warm Kata pod)"] --> KCODE["drive-coder"] + DFRUNL --> PROV["provision-microvm
(create Microvm CR + POST /run)"] --> LCODE["drive-coder"] + LCODE --> SUSP["suspend-microvm
(scale-to-zero)"] + KCODE --> GATES["holdout · detect→deploy-test
devops-gate Β· security-agent"] + SUSP --> GATES + GATES --> STATUS["status (consolidated verdict)"] + STATUS --> EXIT["onExit: Kata deletes claim Β·
Lambda KEEPS suspended VM"] +``` + +The Kata graph has **zero MicroVM nodes**. `suspend-microvm` is explicit and lives only in +`df-run-lambda`; suspend/resume is driven by the workflow itself (Β§4), not a bridge or controller. + +--- + +## 2. Kata micro-VM substrate (Flow B) + +```mermaid +flowchart LR + CLAIM["SandboxClaim"] --> OP["agent-sandbox operator"] + OP --> POD["Kata pod (kata-clh)
on nested-virt node group"] + POD --> ENT["entrypoint.js (baked)"] + ENT -->|models| BIF["Bifrost gateway
(ClusterIP, in-cluster)"] + BIF --> BED["Bedrock"] + ENT -->|git/gh :443| GH["GitHub β†’ PR"] + ENT -->|secrets| SEC["/etc/secrets
(projected tmpfs)"] + POD -.native logs.-> KL["kubectl logs"] + BIF -.traces.-> LF["Langfuse"] +``` + +- Pre-warmed pod β†’ **instant claim**. +- Workspace is a **mounted writable volume**; logs are native; models via Bifrost (with Langfuse + traces). Node pool runs continuously. + +--- + +## 3. Lambda MicroVM substrate (Flow D) β€” MicroVM-native, no bridge + +```mermaid +flowchart LR + PROV["provision-microvm step
(dark-factory-workflow SA
+ lambda-microvms role)"] -->|reads handoff| IMG["MicrovmSandbox status
imageARN + execRoleARN
(built once by KRO/ACK)"] + PROV -->|creates mvm-<issue-number>| MCR["Microvm CR
(runHookPayload = Secret ref,
autoResume=false)"] + MCR --> CTRL["lambdamicrovms controller"] + CTRL -->|RunMicrovm cold-start| VM["Firecracker MicroVM
hook-server :8080"] + PROV -->|mint token, POST /run| VM + VM --> ENT["entrypoint.js (USE_BEDROCK=1)"] + ENT -->|models, direct| BED["Bedrock
(exec role, public egress)"] + ENT -->|git/gh :443| GH["GitHub β†’ PR"] + VM -.coder stdout.-> LOGS["GET /logs (token)"] + SUSP["suspend-microvm step
(after PR)"] -->|suspend-microvm| VM + MERGE["df-merge-teardown
(at merge)"] -->|delete CR| TERM["controller TerminateMicrovm"] +``` + +- One workflow **step** (`provision-microvm`) does create + drive `/run` β€” **no bridge pod, no + SandboxClaim, no warm pool**. `RunMicrovm` cold-start per session (~90s); no node pool. +- No cluster network dependency β€” **Bedrock-direct**. Logs via `/logs`. The explicit + `suspend-microvm` step suspends after the PR; the VM stays suspended (autoResume=false) until a fix + round resumes it or merge terminates it. + +--- + +## 4. Lambda suspend / resume (workflow-driven; warm resume + recreate-fallback) + +```mermaid +sequenceDiagram + participant W as df-run-lambda (workflow) + participant C as lambdamicrovms controller + participant V as MicroVM + W->>C: provision: create Microvm CR (autoResume=false) + C->>V: RunMicrovm (cold-start) + V-->>W: RUNNING + endpoint + W->>V: POST /run (token) β†’ coder starts β†’ PR + W->>V: suspend-microvm step + Note over V: SUSPENDED β€” stays down (no endpoint polling) + Note over W,V: review gates run while VM is suspended (free) + Note over W,V: FIX ROUND (df-iterate β†’ df-run-lambda): + W->>V: resume-microvm (warm β€” SAME VM) + alt resume OK (pre-GA happy path) + V-->>W: RUNNING β†’ POST /run β†’ coder re-runs β†’ new commit + else resume fails (pre-GA flakiness β†’ VM terminated) + W->>C: recreate: fresh Microvm CR + C->>V: RunMicrovm β†’ coder re-runs β†’ new commit + end + W->>C: at merge (df-merge-teardown): delete Microvm CR + C->>V: TerminateMicrovm +``` + +The CR is named `mvm-` (stable across rounds) β†’ exactly one VM per issue, so +suspend/resume never flap between competing owners. + +--- + +## 5. End-to-end lifecycle (issue β†’ PR β†’ fix β†’ merge) β€” both substrates + +```mermaid +flowchart TD + A["Issue labeled"] --> B["df-run: claim + coder β†’ PR"] + B --> C["DevOps Agent + Security Agent review"] + C --> D{"Security findings?"} + D -->|clean| APR["Human approves PR"] + D -->|findings| FIX["Human comments 'fix findings'"] + FIX --> IT["df-iterate β†’ df-run (same substrate via trigger-label)"] + IT --> B + APR --> MERGE["df-merge-teardown:
merge PR + release/terminate sandbox"] +``` + +The loop is identical for both substrates; `df-iterate` reads the originating issue's label to +route the fix round back to the **same** substrate (Kata pool or Lambda pool). + +--- + +## Legend / key facts + +- **Warm pool:** Kata = ready pods (instant); Lambda = bridge pods that RunMicrovm on claim. +- **LLM:** Kata β†’ Bifrost (traced in Langfuse); Lambda β†’ Bedrock-direct (exec role). +- **Workspace:** Kata β†’ mounted volume; Lambda β†’ `/tmp/workspace` (read-only rootfs). +- **Logs:** Kata β†’ `kubectl logs`; Lambda β†’ `/logs` endpoint (+ build logs in CloudWatch). +- **Teardown:** Kata β†’ release claim; Lambda β†’ delete `Microvm` CR β†’ TerminateMicrovm. diff --git a/docs/dark-factory/diagrams/flow-a-sandbox-capability.md b/docs/dark-factory/diagrams/flow-a-sandbox-capability.md new file mode 100644 index 00000000..caf6e44d --- /dev/null +++ b/docs/dark-factory/diagrams/flow-a-sandbox-capability.md @@ -0,0 +1,40 @@ +# Flow A β€” Agent Sandbox Capability (permanent platform feature) + +The **Agent Sandbox** capability ships as a first-class agent-platform GitOps addon. When the +platform is deployed, it stands up the Kata (Cloud Hypervisor) micro-VM runtime on a dedicated +nested-virt node group, the `Sandbox` CRD + operator, and a **warm pool** of pre-provisioned, +hardware-isolated sandboxes kept ready by the operator's native `SandboxWarmPool`. This is +independent of the Dark Factory β€” it is the reusable isolation substrate any agent workload can +claim. + +> **Hosted on the hub cluster** β€” the build/author plane, co-located with Argo Workflows so Flow B +> orchestrates the pool single-cluster. Because the hub runs EKS Auto Mode (which can't host Kata) +> and the fleet control plane (Keycloak/ArgoCD/external-secrets), the capability requires a +> **dedicated tainted nested-virt node group** and **control-plane egress isolation** β€” see +> [README Β§3](../README.md#3-flow-a--agent-sandbox-capability-permanent-platform-feature) and +> [Β§10](../README.md#10-security-model). + +> 🎨 Diagrams are editable draw.io β€” sources in [`src/`](./src/), rendered PNGs in [`img/`](./img/). + +--- + +## A.1 β€” Capability architecture + +Shipped as a GitOps addon: an ApplicationSet renders the operator, the Kata runtime on a nested-virt +node group, and a warm pool that any workload (notably the Dark Factory) claims on demand. + +![Flow A β€” capability architecture](./img/flow-a-capability.png) + +*Edit: [`src/flow-a-capability.drawio`](./src/flow-a-capability.drawio)* + +--- + +## A.2 β€” Warm-pool cycling (claim ↔ refill ↔ scale-to-zero) + +The operator keeps a steady buffer of idle sandboxes so a consumer binds a **ready** VM instantly +instead of paying cold-start. Idle sandboxes use the `Sandbox` `replicas: 0/1` **scale subresource**, +so "idle" is cheap; on claim the operator provisions a refill to keep the buffer at target. + +![Flow A β€” warm-pool cycling](./img/flow-a-warmpool.png) + +*Edit: [`src/flow-a-warmpool.drawio`](./src/flow-a-warmpool.drawio)* diff --git a/docs/dark-factory/diagrams/flow-b-dark-factory.md b/docs/dark-factory/diagrams/flow-b-dark-factory.md new file mode 100644 index 00000000..be21dd09 --- /dev/null +++ b/docs/dark-factory/diagrams/flow-b-dark-factory.md @@ -0,0 +1,125 @@ +# Flow B β€” Dark Factory (issue β†’ PR β†’ merge β†’ teardown) + +A GitHub issue is a **spec**. On the **hub cluster**, **Argo Workflows** claims a warm Kata sandbox, +a pluggable coding assistant implements + tests the change and pushes a branch, the workflow opens a +PR and runs independent verification (holdout gate + AWS Security/DevOps review), a human approves on +**results**, and everything is torn down on merge. Autonomy **Level 3**: the human's only job is to +approve the merge. + +> **Runs on the hub** β€” the build/author plane, co-located with Argo Workflows and the Flow A warm +> pool. Single-cluster orchestration: the workflow watches the coder pod and eval Job directly. See +> [README Β§2](../README.md#2-two-flows-at-a-glance) for *why the hub, not a spoke*, and +> [Β§10](../README.md#10-security-model) for the control-plane isolation that makes it safe. + +> 🎨 Diagrams are editable draw.io β€” sources in [`src/`](./src/), rendered PNGs in [`img/`](./img/). + +--- + +## B.1 β€” End-to-end lifecycle + +Issue β†’ claim a warm micro-VM β†’ coder implements + tests β†’ verify β†’ open PR with a live sticky +status β†’ human approves on results β†’ merge + teardown. A bounded feedback loop routes review +comments back to the coder. + +![Flow B β€” end-to-end lifecycle](./img/flow-b-lifecycle.png) + +*Edit: [`src/flow-b-lifecycle.drawio`](./src/flow-b-lifecycle.drawio)* + +--- + +## B.2 β€” Hub topology & three-layer isolation + +The Dark Factory shares a cluster with the fleet control plane, so untrusted coder VMs are fenced +off by **three independent, verified layers** β€” a standard NetworkPolicy (pod-IP egress), an +Admin-tier ClusterNetworkPolicy (applies to the Sandbox-CR-owned coder pods), and a ClusterIP +node-firewall (closes the EKS VPC-CNI DNAT gap). Net result: the coder reaches only DNS, Bifrost, +and public GitHub β€” never the control plane, node, or API server. + +![Flow B β€” hub topology and three-layer isolation](./img/flow-b-hub-topology.png) + +*Edit: [`src/flow-b-hub-topology.drawio`](./src/flow-b-hub-topology.drawio)* + +--- + +## B.3 β€” Event-driven lifecycle + +No long-running orchestrator. An **Argo Events Sensor** turns each GitHub webhook into a short-lived, +issue-keyed workflow (`df-run`, `df-iterate`, `df-merge-teardown`); durable state lives in the +retained workspace PVC + GitHub, not a parked process. + +![Flow B β€” event-driven lifecycle](./img/flow-b-lifecycle-events.png) + +*Edit: [`src/flow-b-lifecycle-events.drawio`](./src/flow-b-lifecycle-events.drawio)* + +--- + +## B.4 β€” The `df-run` DAG (as built) & how step-gating works + +This is the **implemented** pipeline (P1 + P2 holdout + P3 Security/DevOps reviewers, all solid +emerald), with the P4 `deploy-test` steps drawn dashed so the target shape is legible. It answers the +two questions the higher-level diagrams don't: **where each step runs** (trust boundary) and **how +Argo decides whether a step runs** (the `when:` gate). + +![Flow B β€” the df-run DAG as built](./img/flow-b-df-run-dag.png) + +*Edit: [`src/flow-b-df-run-dag.drawio`](./src/flow-b-df-run-dag.drawio)* + +**How a step is gated β€” `when:` on a prior step's output.** Argo is a declarative orchestrator, not +an agent: it does not "improvise" steps. Every task is *defined* in the DAG, and each carries an +optional `when:` expression that Argo evaluates at runtime against a value an earlier step emitted. +The `holdout-gate` step already does this: + +```yaml +- name: holdout-gate + dependencies: [drive-coder] + when: "{{tasks.drive-coder.outputs.parameters.pr-number}} != \"\"" # run only if a PR exists +``` + +`drive-coder` writes the PR number to a file β†’ Argo captures it as an output parameter β†’ Argo +substitutes the real value into the `when:` string (`"7" != ""` β†’ **run**; `"" != ""` β†’ **skip**, +no pod is created). Deterministic, file/value-based β€” no AI in the decision. + +**Conditional deploy-test (P4) uses the same mechanism, keyed on the diff.** A cheap `detect-deployable` +step runs `git diff --name-only` and greps for deployable files (`Chart.yaml`, `k8s/`, `deployment.yaml`, +`Dockerfile`); it emits `deployable = true|false`; `deploy-test` is gated `when: deployable == true`. +So "the PR touched a Deployment manifest β†’ deploy-test runs; it only touched app code β†’ skip." + +**Two levels of testing, two homes (the trust boundary):** + +| Test level | What it checks | Where it runs | K8s access | +|---|---|---|---| +| **Unit / build** | compiles, `subtract(5,3)==2`, `npm test`/`go test` green | inside the **coder** (Kata VM) + re-run by **holdout-gate** | **none** (correct β€” the VM is untrusted) | +| **Deploy / integration** (P4) | deploys to an ephemeral namespace, endpoint returns 200, pod healthy | a **trusted hub step** (`deploy-test`), never the VM | **yes** β€” held by the workflow SA, never the coder | + +**Executable tests decide; LLM/agents advise.** The holdout's hidden tests are the ground truth (a +stub can't pass a real test); the Nova judge is a *reviewer* that catches gaming the tests can't see +(hard-coded inputs, `return true`). The **P3 Security/DevOps reviewers** are the same shape β€” their +deterministic linters (secret scan, `npm audit`; Dockerfile/k8s hygiene) are the hard signal and the +Nova reviewer is the advisory second opinion; both are read-only on the diff and post +`dark-factory/{security,devops}` statuses (advisory in v1). For deploy-work, the `deploy-test` +executable probes will be ground truth and the DevOps agent the advisory second opinion. + +--- + +## B.5 β€” The one sticky PR comment + +The workflow maintains **one** comment, edited in place via a hidden marker β€” no comment spam. Until +tests are green there is no PR, so pre-PR status lives on the **issue**; from PR-open onward the +comment is the canonical board. Parallel review steps are serialized by a per-issue mutex. + +``` +## 🏭 Dark Factory β€” issue #42 Β· PR #128 +βœ… Claimed sandbox (hub) 12:01 +βœ… Branch df/issue-42 12:01 +βœ… Implement 12:04 +βœ… Build + unit tests 12:07 πŸ“„ log +βœ… PR opened #128 12:07 +⏳ Security review… +⬜ DevOps review +⬜ Holdout gate (0/12) +⬜ Ready for review +``` + +Each stage links to raw logs / the Argo run / the Langfuse trace (**verifiability-by-citation**). +The PR **body** carries the final report: what changed, test results, holdout satisfaction %, and +the Security/DevOps findings. diff --git a/docs/dark-factory/diagrams/flow-d-microvm-sandbox.md b/docs/dark-factory/diagrams/flow-d-microvm-sandbox.md new file mode 100644 index 00000000..0eadcd25 --- /dev/null +++ b/docs/dark-factory/diagrams/flow-d-microvm-sandbox.md @@ -0,0 +1,138 @@ +# Flow D β€” Lambda MicroVM–backed Agent Sandbox (alternative substrate) + +**Flow D is a second Flow-A substrate.** Where Flow A hands out **Kata micro-VM pods** on a +self-managed nested-virt EKS node group, Flow D hands out **AWS Lambda MicroVMs** provisioned by the +ACK `lambdamicrovms` controller and composed by a single **KRO `ResourceGraphDefinition`**. The Agent +Sandbox UX is unchanged: a consumer (notably **Flow B β€” Dark Factory**) creates a `SandboxClaim`, a +pod shows up, and the **same `dark-factory-coder`** runs its coding/testing loop β€” except the coder +executes inside a Lambda MicroVM instead of on the Kata node. + +> **Flow C is reserved for other work** β€” this substrate is **Flow D**. + +> **Why a second substrate?** Kata (Flow A) needs a dedicated nested-virt node group the platform owns +> and pays for while idle. Lambda MicroVM is a **serverless** micro-VM: no node group to run, per-claim +> lifecycle, sub-second warm starts, and a clean **platform-owns-the-image / app-owns-the-instance** +> split that maps directly onto the two ACK CRDs. Flow B can target either substrate with no pipeline +> change β€” it only ever sees the Agent Sandbox `SandboxClaim` contract. + +> 🎨 Diagrams are editable draw.io β€” sources in [`src/`](./src/), rendered PNGs in [`img/`](./img/). +> *(Flow D diagram sources are added alongside the Flow A/B ones; see `src/flow-d-*.drawio`.)* + +--- + +## D.1 β€” Substrate architecture (KRO RGD over ACK primitives) + +The platform installs two controllers and one composition layer, then exposes **one** custom +resource to consumers: + +- **Managed ACK** (EKS Capability) runs the **GA** controllers β€” `iam.services.k8s.aws` (Role) and + `s3.services.k8s.aws` (Bucket) β€” that the MicroVM image + instance depend on. +- **Self-managed ACK** runs **only** the pre-GA `lambdamicrovms.services.k8s.aws` controller (its own + Helm chart / ArgoCD addon), because Managed ACK bundles GA controllers only. +- **Managed KRO** (EKS Capability) runs the `ResourceGraphDefinition` engine. +- A single **`MicrovmSandbox` RGD** ties it all together: one CR expands into `MicrovmImage` + + `Microvm` + IAM `Role`(s) + S3 `Bucket`. + +``` +consumer (Flow B / any agent) + β”‚ creates + β–Ό + MicrovmSandbox (kro.run/v1alpha1 β€” the single abstraction) + β”‚ expands into + β”œβ”€β”€ MicrovmImage (lambdamicrovms.services.k8s.aws) ── platform-owned inputs + β”œβ”€β”€ S3 Bucket (s3.services.k8s.aws) ── image codeArtifact store + β”œβ”€β”€ IAM Role (build) (iam.services.k8s.aws) ── MicrovmImage.buildRoleArn + β”œβ”€β”€ IAM Role (exec) (iam.services.k8s.aws) ── Microvm.executionRoleArn + └── Microvm (lambdamicrovms.services.k8s.aws) ── app-owned instance lifecycle +``` + +*Edit: `src/flow-d-substrate.drawio` β†’ `img/flow-d-substrate.png`.* + +--- + +## D.2 β€” Platform-owned vs app-owned split (inside one RGD) + +The two ACK CRDs encode the ownership boundary the platform team and application teams care about; +the RGD schema surfaces each half to the right owner: + +| Layer | Owner | ACK resource | Key fields | +|---|---|---|---| +| **Image / substrate** | Platform | `MicrovmImage` | `baseImageARN`, `buildRoleArn`, `codeArtifact.uri` (S3), egress connectors | +| **Instance / run** | App team | `Microvm` | `imageIdentifier`, `executionRoleArn`, `ingress/egressNetworkConnectors`, `idlePolicy` | + +- **Platform** sets the image once (built **from the existing `dark-factory-coder` image** + its + `entrypoint.js`, published to the S3 `codeArtifact` bucket) β€” declarative, ACK-managed, GitOps. +- **App teams / Flow B** create per-claim `Microvm` instances referencing that image, and own the + instance lifecycle (`RunMicrovm` / `TerminateMicrovm`, idle policy) via the same claim they use today. + +*Edit: `src/flow-d-ownership.drawio` β†’ `img/flow-d-ownership.png`.* + +--- + +## D.3 β€” The RuntimeClass shim (claim β†’ pod β†’ MicroVM) + +A literal Kubernetes `RuntimeClass` (like `kata-clh`) maps to a **node-local containerd handler**. +Lambda MicroVM is a **remote AWS service**, so a true node-level RuntimeClass isn't possible without a +virtual-kubelet provider (a large Go runtime component β€” explicitly **out of scope**). Flow D uses a +**RuntimeClass-marked bridge pod** instead, preserving the exact Agent Sandbox UX: + +``` +SandboxClaim (Flow B injects DF_ISSUE_NUMBER, repo, branch β€” unchanged) + β”‚ + β–Ό +Sandbox β†’ Pod from the `lambda-microvm` SandboxTemplate variant + β”‚ (bridge container; lands on a normal Auto-Mode node, NOT the kata pool) + β–Ό +bridge applies a MicrovmSandbox (KRO) CR + β”‚ + β–Ό +Microvm RUNNING ── runs the SAME dark-factory-coder entrypoint (node /app/entrypoint.js) + β”‚ + β”œβ”€β”€ bridge streams MicroVM logs β†’ pod logs (pod Running ⇔ Microvm RUNNING) + └── pod exit / claim teardown β†’ TerminateMicrovm +``` + +To Flow B and the user this is identical to Flow A β€” "a sandbox pod appeared and ran the coder" β€” but +the coder actually executed in the Lambda MicroVM. Log streaming is straightforward; interactive +exec/attach passthrough is **best-effort** (full fidelity would need virtual-kubelet). + +*Edit: `src/flow-d-shim.drawio` β†’ `img/flow-d-shim.png`.* + +--- + +## D.3a β€” Suspend / resume (Sandbox.operatingMode β†’ MicroVM) + +The Agent Sandbox CRD exposes `spec.operatingMode ∈ {Running, Suspended}` β€” the declarative +suspend/resume intent. But the ACK `Microvm` CR has **no suspend field**: its spec is create-time +only, `State` is status-only, and `suspend-microvm`/`resume-microvm` are **imperative SDK ops the ACK +controller deliberately does not reconcile**. So flipping `operatingMode` does nothing on its own β€” a +controller must translate intent into the SDK call. + +Flow D closes that gap with a tiny always-on **`microvm-lifecycle`** reconcile loop (a ConfigMap +script on `alpine/k8s`, same pattern as the pool-manager β€” **no virtual-kubelet, no new image**): + +``` +Sandbox.operatingMode: Running β†’ Suspended : aws lambda-microvms suspend-microvm --microvm-identifier +Sandbox.operatingMode: Suspended β†’ Running : aws lambda-microvms resume-microvm --microvm-identifier +``` + +- `` (the `microvmID`) is resolved from the `MicrovmSandbox` (KRO) status; the loop is idempotent + (stamps a `last-mode` annotation, acts only on transitions). +- The `MicrovmSandbox` is **kept** across suspend (the bridge's `preStop` detects `operatingMode: + Suspended` and skips teardown), so the VM survives suspend/resume; it's deleted only on real claim + teardown β†’ `TerminateMicrovm`. +- Chosen over bridge `preStop` hooks alone because a reconcile loop is **robust to pod/node loss** and + resume needs no live pod. This is the open-source **Sandbox-CRD-driven** suspend/resume you get with + the MicroVM substrate. + +*Edit: `src/flow-d-suspend-resume.drawio` β†’ `img/flow-d-suspend-resume.png`.* + +--- + +## D.4 β€” Future: when `lambdamicrovms` goes GA + +`lambdamicrovms` is currently **pre-GA** (`v1alpha1`), so its controller is self-managed. When it +graduates to GA upstream, **Managed ACK adopts it automatically** β€” the self-managed chart is deleted +and the `MicrovmSandbox` RGD is **unchanged** (it references the same `lambdamicrovms.services.k8s.aws` +CRDs regardless of who runs the controller). The design deliberately keeps the RGD independent of the +controller install method so this migration is a one-line addon removal. diff --git a/docs/dark-factory/diagrams/img/flow-a-capability.png b/docs/dark-factory/diagrams/img/flow-a-capability.png new file mode 100644 index 00000000..8290cbc6 Binary files /dev/null and b/docs/dark-factory/diagrams/img/flow-a-capability.png differ diff --git a/docs/dark-factory/diagrams/img/flow-a-warmpool.png b/docs/dark-factory/diagrams/img/flow-a-warmpool.png new file mode 100644 index 00000000..96e84fb7 Binary files /dev/null and b/docs/dark-factory/diagrams/img/flow-a-warmpool.png differ diff --git a/docs/dark-factory/diagrams/img/flow-b-df-run-dag.png b/docs/dark-factory/diagrams/img/flow-b-df-run-dag.png new file mode 100644 index 00000000..337be0dc Binary files /dev/null and b/docs/dark-factory/diagrams/img/flow-b-df-run-dag.png differ diff --git a/docs/dark-factory/diagrams/img/flow-b-hub-topology.png b/docs/dark-factory/diagrams/img/flow-b-hub-topology.png new file mode 100644 index 00000000..dd0b14d1 Binary files /dev/null and b/docs/dark-factory/diagrams/img/flow-b-hub-topology.png differ diff --git a/docs/dark-factory/diagrams/img/flow-b-lifecycle-events.png b/docs/dark-factory/diagrams/img/flow-b-lifecycle-events.png new file mode 100644 index 00000000..9384d307 Binary files /dev/null and b/docs/dark-factory/diagrams/img/flow-b-lifecycle-events.png differ diff --git a/docs/dark-factory/diagrams/img/flow-b-lifecycle.png b/docs/dark-factory/diagrams/img/flow-b-lifecycle.png new file mode 100644 index 00000000..14461484 Binary files /dev/null and b/docs/dark-factory/diagrams/img/flow-b-lifecycle.png differ diff --git a/docs/dark-factory/diagrams/src/flow-a-capability.drawio b/docs/dark-factory/diagrams/src/flow-a-capability.drawio new file mode 100644 index 00000000..29bb04e5 --- /dev/null +++ b/docs/dark-factory/diagrams/src/flow-a-capability.drawio @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/dark-factory/diagrams/src/flow-a-warmpool.drawio b/docs/dark-factory/diagrams/src/flow-a-warmpool.drawio new file mode 100644 index 00000000..611d454a --- /dev/null +++ b/docs/dark-factory/diagrams/src/flow-a-warmpool.drawio @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/dark-factory/diagrams/src/flow-b-df-run-dag.drawio b/docs/dark-factory/diagrams/src/flow-b-df-run-dag.drawio new file mode 100644 index 00000000..a37a613d --- /dev/null +++ b/docs/dark-factory/diagrams/src/flow-b-df-run-dag.drawio @@ -0,0 +1,158 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/dark-factory/diagrams/src/flow-b-hub-topology.drawio b/docs/dark-factory/diagrams/src/flow-b-hub-topology.drawio new file mode 100644 index 00000000..dc1591be --- /dev/null +++ b/docs/dark-factory/diagrams/src/flow-b-hub-topology.drawio @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/dark-factory/diagrams/src/flow-b-lifecycle-events.drawio b/docs/dark-factory/diagrams/src/flow-b-lifecycle-events.drawio new file mode 100644 index 00000000..a3414440 --- /dev/null +++ b/docs/dark-factory/diagrams/src/flow-b-lifecycle-events.drawio @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/dark-factory/diagrams/src/flow-b-lifecycle.drawio b/docs/dark-factory/diagrams/src/flow-b-lifecycle.drawio new file mode 100644 index 00000000..f1dfa71e --- /dev/null +++ b/docs/dark-factory/diagrams/src/flow-b-lifecycle.drawio @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/dark-factory/flow-d-coder-in-microvm-design.md b/docs/dark-factory/flow-d-coder-in-microvm-design.md new file mode 100644 index 00000000..4e63a1a2 --- /dev/null +++ b/docs/dark-factory/flow-d-coder-in-microvm-design.md @@ -0,0 +1,214 @@ +# Flow D β€” Running the Coder *inside* the Lambda MicroVM (design) + +**Status:** design / spike β€” NOT implemented. Written after proving the Flow D **substrate** +end-to-end and discovering that running the actual coder in the VM is an application +re-architecture, not a wiring task. + +## TL;DR + +The Flow D **substrate + lifecycle is proven live**: a `darkfactory-lambda` GitHub issue β†’ +Argo sensor β†’ `df-run` claims the Lambda warm pool β†’ the bridge calls `RunMicrovm` β†’ a real +Lambda MicroVM reaches **RUNNING** in AWS β†’ `suspend`/`resume`/`terminate` are wired β†’ the VM +is terminated on teardown (verified, zero orphans). + +What is **NOT** done: the coder does not actually *execute* inside that MicroVM, so no PR is +produced. That is because the one-shot `dark-factory-coder` and the Lambda MicroVM +snapshot/hook execution model are **fundamentally different execution shapes**. Closing the gap +requires re-architecting the coder, plus VPC/Bifrost networking. This doc specifies that work +so it can be decided deliberately. + +## Why it isn't just wiring β€” the execution-model mismatch + +| | Kata coder (Flow B, works today) | Lambda MicroVM model | +| --- | --- | --- | +| Shape | **one-shot batch process**: `node entrypoint.js` runs cloneβ†’agentβ†’pushβ†’PR, then exits | **long-lived HTTP service** that is *snapshotted* at build, *resumed* per session | +| Duration | 5–15 min per run | per-request; the `run` lifecycle hook has a **30s timeout** ("keep it short β€” on the critical path") | +| Trigger | pod start + `DF_ISSUE_NUMBER` env injected by the SandboxClaim | build-time `ready`/`validate` hooks; per-instance `run` hook receives `runHookPayload` as the HTTP request body | +| Secrets/context | files projected into the pod (`/etc/secrets/gh-token`, `bifrost-api-key`) + `DF_*` env | `runHookPayload` β€” a **Kubernetes `SecretKeyReference`** on the `Microvm` CR, delivered as the `/run` hook body (≀16 KB); image must set `hooks.microvmHooks.run: ENABLED` | +| Network | in-cluster: reaches Bifrost by ClusterIP `172.20.181.17:8080`; git/gh over public :443 | runs **outside the cluster network**; only `INTERNET_EGRESS` by default; cannot reach a ClusterIP; VPC reach needs an egress **network connector** | + +The killer facts (verified against `mmeckes/lambdamicrovms-controller` docs + the live `aws +lambda-microvms`/`lambda-core` CLIs, 2026-08-03): + +1. **`/run` hook = 30s timeout.** A 5–15 min coder run cannot happen *in* the hook. +2. **The intended app model is request/response** (02-developer-handoff: RunMicrovm β†’ mint + auth token β†’ HTTP request β†’ response β†’ terminate) β€” not a batch job. +3. **`runHookPayload` is delivered as an HTTP body to a `/run` endpoint the app must SERVE** β€” + NOT an env var and NOT a mounted file. (An earlier attempt at an env/file boot-shim was + wrong and is discarded.) +4. So the coder must be **wrapped in a long-running HTTP server** that starts the coding work + asynchronously β€” the coder's current `entrypoint.js` is not written that way. + +## Proposed design (async `/run` pattern) + +Keep the coder *logic* (`entrypoint.js`) intact; change how it is *invoked*. + +``` +build: MicrovmImage (FROM arm64 dark-factory-coder + a thin HTTP wrapper) + hooks.port: 8080 + microvmImageHooks.ready: server up β†’ safe to snapshot + microvmHooks.run: ENABLED (30s), suspend/resume/terminate ENABLED + +run: controller delivers runHookPayload (Secret {ghToken, bifrostKey, bifrostUrl(NLB), + issueNumber, repo, branch, baseBranch, title}) as the /run body + β†’ wrapper writes /etc/secrets/{gh-token,bifrost-api-key} + exports DF_*/BIFROST_URL + β†’ wrapper spawns `node entrypoint.js` in the BACKGROUND, returns 200 within 30s + β†’ coder does cloneβ†’agentβ†’pushβ†’PR async (many minutes) + +observe: df-run's existing `await-coder` step ALREADY polls GitHub for the PR head β€” reuse it + verbatim; it doesn't care whether the coder ran in Kata or a MicroVM. + +teardown: suspend/resume/terminate hooks best-effort flush; bridge TerminateMicrovm on exit. +``` + +### Components to build + +1. **HTTP wrapper + artifact** (`coder-microvm/`): a small server (`server.js`) exposing + `ready`, `run`, `suspend`, `resume`, `terminate` on :8080; `run` materializes the payload + into the coder's existing file/env contract and background-spawns `entrypoint.js`. Dockerfile + `FROM .dkr.ecr.us-west-2.amazonaws.com/dark-factory-coder:-arm64`. Zip + (Dockerfile + server.js) β†’ S3, per the controller's `ci/package-artifact.sh` format. + *(Supersedes the placeholder `microvm-entry.js` listener that only existed to get the image + to CREATED.)* + +2. **MicrovmImage: enable hooks** (RGD `templates/image/10-rgd-and-image.yaml`): add + `hooks.port: 8080`, `microvmImageHooks.ready: ENABLED`, `microvmHooks.run/suspend/resume/ + terminate: ENABLED`. Without `run: ENABLED` the payload is silently never delivered. + +3. **VPC egress connector** (bootstrap Job β€” honest: *GitOps-provisioned, not continuously + reconciled*; no ACK/Crossplane API exists for `lambda-core` connectors). Committed + find-or-create Job modeled on `06-securityagent-bootstrap.yaml`: + `aws lambda-core get/create-network-connector` with + `VpcEgressConfiguration={SubnetIds:[hub subnets], SecurityGroupIds:[sg], NetworkProtocol:IPv4}` + β†’ writes the connector ARN to a ConfigMap the MicrovmImage `egressNetworkConnectors` reads. + IAM: the bootstrap/capability role needs `lambda-core:*NetworkConnector*` + the EC2 ENI perms + Lambda uses to provision ENIs. **Caveat:** if the connector is deleted out-of-band, nothing + self-heals until the Job re-runs (not a controller). + +4. **Bifrost VPC-reachable** (internal NLB β€” this part *is* declarative): a `Service + type=LoadBalancer` with `service.beta.kubernetes.io/aws-load-balancer-internal: "true"` + + `nlb-target-type` in the bifrost chart, reconciled by the AWS Load Balancer Controller. The + MicroVM (via the egress connector) reaches Bifrost at the NLB's stable VPC address on :8080. + (Bifrost's pod IP `10.0.x.x` is in-VPC and reachable via the connector, but ephemeral β€” the + NLB gives a stable target. Its ClusterIP `172.20.x.x` is NOT routable from a VPC ENI.) + **Shared-infra change β€” needs owner sign-off.** + +5. **runHookPayload Secret + Microvm wiring**: the bridge (or a per-session step) writes a + Secret with the payload key and the `Microvm`/RunMicrovm references it as + `runHookPayload: {name, key}`. Since it's a SecretKeyReference the **controller** delivers it + β€” confirm whether the imperative `RunMicrovm` path the bridge uses accepts the same, or + whether this session should create a short-lived `Microvm` CR instead. + +6. **Security-group rules**: allow the connector ENIs β†’ Bifrost NLB on :8080. + +## Open questions for review + +- **Async vs request-driven?** Background-spawn (df-run polls for the PR, minimal coder change) + vs. the reference's request/response model (bridge sends an HTTP "code this" request + waits; + needs the auth-token path). Background-spawn reuses `await-coder` and is less invasive. +- **Imperative RunMicrovm vs a `Microvm` CR per session?** `runHookPayload` being a + SecretKeyReference is controller-delivered; the current bridge calls `aws run-microvm` + imperatively. Decide whether per-session VMs become short-lived `Microvm` CRs (declarative + payload delivery) or stay imperative (verify the CLI accepts an inline/secret payload). +- **Cost:** the VPC egress connector provisions ENIs; the internal NLB is an hourly resource. + Both are ongoing while Flow D is enabled. +- **Is in-VM coder even required for the goal?** The substrate is a valid deliverable on its + own (a second sandbox substrate). Running the coder in it is the "make it actually code" step + β€” worth confirming it's in scope before the re-architecture. + +## 2026-08-03 build attempt β€” where it got to + the confirmed blocker + +Built and deployed the Bedrock-direct async design end-to-end. Live results: + +- βœ… **lambda-coder artifact + image**: `examples/dark-factory/coder-microvm/` (hook-server.js + + Dockerfile FROM the arm64 coder + the USE_BEDROCK entrypoint branch). MicrovmImage rebuilt + to **UPDATED** with `hooks` enabled; exec role has `bedrock:InvokeModel*`. Verified the + hook-server runs in the VM β€” CloudWatch shows `[hook-server] listening on :8080 (lambda-coder, + Bedrock-direct)`. +- βœ… **Bridge payload**: builds JSON (issue ctx + GitHub token + region) with python3 (node is + absent in the aws-cli image) and passes `--run-hook-payload` on `run-microvm`. VM launches + RUNNING with the payload; no bridge crash. +- ❌ **BLOCKER: the `/run` hook never fires** β†’ the coder never starts in the VM β†’ no PR. + CloudWatch shows the server `listening` but never logs the `/run` handling / background-spawn. + +**Confirmed root cause:** `runHookPayload` is a **`SecretKeyReference`, "not a literal"** β€” the +docs + the 02-developer-handoff example deliver it via the **declarative `Microvm` CR** +(`runHookPayload: {name, key}` β†’ the self-managed controller reads the Secret and drives the +`/run` hook). The imperative `run-microvm --run-hook-payload ""` CLI path the bridge uses +does **not** invoke `/run` (VM reaches RUNNING but the hook is silent). Two things also worth +noting from the reference: (a) the intended session model is the CLIENT minting an auth token and +sending an HTTP request to the VM endpoint with `X-aws-proxy-auth` (request/response), and (b) the +`/run` hook is service-internal on VM start. + +**Correct path (next):** make the per-session VM a **`Microvm` CR** (declarative), not a bridge +CLI call: +- bridge (or a per-session step) writes a **Secret** with the payload key, then creates a + `Microvm` CR referencing it (`imageIdentifierRef`, `executionRoleRef`, `runHookPayload:{name,key}`, + `idlePolicy`), and reads back `status.microvmID` for the lifecycle annotation. +- the self-managed lambdamicrovms controller reconciles it and delivers the payload to `/run`, + which background-spawns the coder. +- teardown = delete the `Microvm` CR (controller terminates), replacing the imperative + TerminateMicrovm. +This trades the imperative bridge for the declarative CR path the payload mechanism actually +requires β€” and it's MORE GitOps-faithful. Est: bridge rewrite (CR create/delete instead of CLI) ++ a per-session Secret; the hook-server/artifact/image/IAM/Bedrock pieces are already done and verified. + +## 2026-08-03 (later) β€” declarative Microvm CR path: reconciles + VM runs, /run still silent + +Switched the bridge from the imperative `run-microvm` CLI to the **declarative `Microvm` CR** +path (write payload Secret β†’ create `Microvm` CR with `runHookPayload:{name,key}` β†’ controller +reconciles β†’ delete CR on teardown). Verified working: +- βœ… Bridge creates the Secret + `Microvm` CR (`mvm-`); RBAC for microvms+secrets added. +- βœ… Controller reconciles it: CR `state=RUNNING`, `ACK.ResourceSynced=True`, `status.microvmID` + populated, annotated on the Sandbox. Deleting the CR cleanly terminates the VM (0 orphans). +- βœ… MicrovmImage is v2.0, `UPDATED`, `hooks` present (run/ready/suspend/resume/terminate). +- ❌ **STILL no `/run` output**: CloudWatch `/aws/lambda/microvms/coder-image` shows the build-time + `[hook-server] listening on :8080` but ZERO runtime events after the VM starts β€” the coder never + logs, no PR. The `/run` hook is not producing coder execution we can observe. + +**What's ruled out:** payload delivery mechanism (now declarative CR, the documented path), image +hooks (present, v2.0 built), IAM (bedrock on exec role), bridge crash (restarts=0), YAML (renders +clean). **What's NOT yet proven:** that the service actually invokes `/run` against the hook-server, +and that hook-server's `/run` handler + background-spawn + Bedrock call execute. Can't see inside +the VM beyond CloudWatch (which is empty at runtime) β€” needs either (a) the VM's runtime logs routed +somewhere visible, (b) hitting the VM endpoint directly with an auth token (X-aws-proxy-auth) to +probe the hook-server, or (c) the controller/service confirming the run-hook HTTP call + its response. +This is the current debugging frontier β€” the substrate, image, CR path, and teardown all work; the +open question is purely whether/how the `/run` hook reaches the in-VM hook-server and why it emits +no logs. + +## 2026-08-03 E2E run #106 β€” full chain works to /run; 2 pinpointed gaps + +Ran a clean GH-issue E2E and **probed the VM directly** (minted an auth token, hit the endpoint). +Stage-by-stage: issue β†’ label β†’ workflow β†’ bridge claim β†’ Microvm CR (`mvm-106`) β†’ VM RUNNING with +endpoint β€” all βœ…. Then the decisive probes against the live VM: +- `GET https:///` (X-aws-proxy-auth) β†’ `{"status":"ok","path":"/"}` β†’ **hook-server is + ALIVE and reachable at runtime.** +- `POST /run` β†’ `{"status":"started"}` β†’ **the /run handler works and background-spawns the coder.** + +So the entire chain β€” including the hook-server and its /runβ†’coder-spawn β€” is functional. The two +remaining gaps are now precisely isolated: + +1. **The service does not auto-invoke `/run` on launch.** After RunMicrovm/Microvm-CR reconcile, the + run hook is not called automatically β€” I had to POST /run manually to start the coder. Either the + run hook fires on a trigger we're not hitting, or the payload/hook wiring needs a specific field to + auto-fire. (auth-token minting: `create-microvm-auth-token --expiration-in-minutes N --allowed-ports + port=8080`; token is at `.authToken.X-aws-proxy-auth`.) +2. **Runtime logs don't reach CloudWatch.** `logging.cloudWatch.logGroup` on the image captures BUILD + logs only; after the VM runs, `/aws/lambda/microvms/coder-image` has 0 runtime events even though the + hook-server clearly runs (proven by the probe). This blinded every prior run β€” need to wire runtime + stdout/stderr to CloudWatch (or read it another way) to observe the coder. + +Both are now concrete, small-surface problems (a hook-trigger config + a log-routing config), NOT +architecture. The substrate, image+hooks, Bedrock exec role, declarative Microvm CR path, payload +delivery, hook-server, /runβ†’coder-spawn, and clean CR-delete teardown are all verified working. + +## What exists today (so nothing is lost) + +- Substrate live: RGD Active, S3 bucket, build/exec roles, **MicrovmImage CREATED (v1.0)**, + bridge launches/terminates a real MicroVM from a `darkfactory-lambda` issue. +- All the substrate + bridge fixes are committed on `flow-d-lambda-microvm-sandbox` (container + named `coder`, aws-cli v2 image, kubectl fetch, API-server + Pod Identity egress, + downward-API SANDBOX_NAME, microvmSuspend on, `darkfactory-lambda` label). +- The **placeholder** code artifact (`microvm-entry.js` listener) is what's in S3 today β€” it + only proved the image builds; it must be replaced per Β§1 above. diff --git a/examples/dark-factory/README.md b/examples/dark-factory/README.md new file mode 100644 index 00000000..6c5ba730 --- /dev/null +++ b/examples/dark-factory/README.md @@ -0,0 +1,144 @@ +# Dark Factory β€” Flow B coder image + +The **in-VM coder** for the Dark Factory (Flow B). This directory builds the container image that +runs **inside the Kata micro-VM sandbox** β€” the untrusted side of the trust boundary. Orchestration +(trigger β†’ claim β†’ drive β†’ PR β†’ teardown) is **not** here: it's done declaratively by **Argo +Workflows on the hub** (see the [`dark-factory` Helm chart](../../gitops/addons/charts/dark-factory/) +and [`docs/dark-factory/README.md`](../../docs/dark-factory/README.md) Β§4). + +> **History:** an earlier P1 used a bespoke long-running **Node orchestrator** (`orchestrator/`) and +> an HTTP-server coder (`coder/agent.js`). Both were removed once Flow B moved to Argo Workflows β€” +> the orchestrator's responsibilities are now the `df-run` WorkflowTemplate's DAG steps (a `resource` +> template creates the `SandboxClaim`; a `script` step polls GitHub; `onExit` releases the claim), +> and the coder became **credential-less + self-reporting** (`entrypoint.js`). + +## What's here + +| Path | Role | Trust | +|---|---|---| +| `coder/entrypoint.js` | The in-VM coder. Auto-runs on VM start; reads the `DF_*` env the `SandboxClaim` injects, fetches the issue as `SPEC.md`, checks out `df/issue-`, runs Claude Code headless via Bifrost, builds + tests, pushes the branch, opens the PR, and sets the `dark-factory/implementation` commit status. | **untrusted** (Kata VM, no cloud creds, no k8s API) | +| `coder/Dockerfile` | Lean `node:20-alpine` + git/bash/python3/go + the Claude Code CLI. Carries **no** credentials. | β€” | +| `deploy-test/Dockerfile` | kubectl + terraform + node/git/curl for the hub-side `deploy-test` step. The **only** step holding K8s access. | trusted (hub) | +| `setup-secrets.sh` | Writes the three GitHub credentials to Secrets Manager. Run once before the first factory run β€” see [Credentials](#credentials) below. | trusted (hub) | + +## How the coder is driven (single-cluster on the hub) + +``` +GitHub issue (label: dark-factory) + β†’ Argo Events: GitHub webhook EventSource β†’ Sensor + β†’ df-run WorkflowTemplate (argo ns, per-issue mutex) + β”œβ”€ claim : resource template creates SandboxClaim(warmPoolRef=coder-warmpool) + β”‚ with the issue injected as env (DF_ISSUE_NUMBER/REPO/BRANCH/…) + β”‚ β†’ operator binds a warm Kata VM (Flow A), status Ready + β”‚ β”Œβ”€β”€ coder VM (this image) auto-runs entrypoint.js on start: + β”‚ β”‚ issue β†’ SPEC.md β†’ checkout df/issue-N β†’ claude implements β†’ + β”‚ β”‚ build + unit tests β†’ push branch β†’ open PR β†’ + β”‚ β”‚ POST commit status dark-factory/implementation = success|failure + β”‚ └── credential-less to the k8s API β†’ self-reports through GitHub + β”œβ”€ drive-coder : script step POLLS the GitHub API for a PR on df/issue-N and + β”‚ reads the head commit's dark-factory/implementation status + β”œβ”€ status : sticky status (P1 stops here β€” PR open, awaiting human) + └─ onExit : teardown β€” delete the SandboxClaim (operator refills the pool) + β†’ human reviews the PR, approves, merges +``` + +## The `DF_*` contract (env the claim injects) + +`entrypoint.js` is entirely env-driven. The `df-run` claim step sets these via `SandboxClaim.spec.env` +(verified contract: the SandboxTemplate opts in with `envVarsInjectionPolicy: Overrides`): + +| Var | Purpose | +|---|---| +| `DF_REPO` | `owner/name` of the target repo | +| `DF_ISSUE_NUMBER` | the GitHub issue number (the spec) | +| `DF_BRANCH` | `df/issue-` | +| `DF_BASE_BRANCH` | base to branch from (default `main`) | +| `DF_ISSUE_TITLE` | issue title (for the PR title) | +| `CODER_PROFILE` | `claude-code` (primary) or `kiro` | +| `BIFROST_URL` | LLM gateway β€” the **ClusterIP** (the Kata VM guest DNS can't resolve svc names) | + +Secrets are **not** in env: the short-TTL GitHub token (+ optional Bifrost key) are projected into +the VM at `/etc/secrets` (tmpfs, mode 0400) and read at point of use. + +## Bifrost gotchas baked into `entrypoint.js` + +The Claude Code CLI talks to models only through the platform's **Bifrost** gateway. Four issues had +to be solved to make `claude -p` work inside the locked-down VM (see the commit history + memory): + +1. **User-Agent routing** β€” Bifrost 400s (`Unexpected field type`) on any request whose UA starts + with `claude-cli`. `entrypoint.js` fronts Bifrost with a **localhost UA-shim** (a separate `node` + process on `127.0.0.1:8791`) that rewrites the UA and transparently forwards everything (incl. the + SSE stream). `ANTHROPIC_BASE_URL` points at the shim. +2. **Writable HOME** β€” `readOnlyRootFilesystem` makes `$HOME` read-only, so Claude Code can't create + `~/.claude` (shell snapshots its Bash tool needs). `HOME`/`CLAUDE_CONFIG_DIR`/XDG are pointed at + the writable `/tmp` tmpfs. +3. **Model alias** β€” use `ANTHROPIC_MODEL=claude-sonnet` (a Bifrost alias β†’ `us.anthropic.claude-sonnet-4-5`). + Do **not** set `CLAUDE_CODE_USE_BEDROCK` (that bypasses the base URL and needs in-VM AWS creds). +4. **`git push --force`** (not `--force-with-lease`) β€” the depth-1 clone can't satisfy the lease + check; `df/issue-N` is bot-owned + single-writer (per-issue workflow mutex), so `--force` is safe. + +The GitHub self-report helper retries transient failures (transport/5xx/429) so a blip on the report +call can't mark a good run failed. + +## Trust boundary (Β§10) + +- The **coder is untrusted**: Kata micro-VM (own kernel), `automountServiceAccountToken: false` (no + k8s API), no cloud IAM. Its only credentials are a Bifrost key + short-TTL GitHub token via + projected tmpfs (0400). Flow A's NetworkPolicy + node ClusterIP firewall lock egress to Bifrost + + DNS + GitHub. Because it holds no k8s creds, it **self-reports through GitHub** (the workflow polls). +- The **AWS IAM stays with the hub orchestrator** (Argo), never in the VM. +- This breaks the **lethal trifecta** (untrusted issue text + credentials + egress): credentials are + never in the issue-ingesting sandbox context, and egress is denied by default. + +## Build & deploy + +The image is built + pushed to ECR and pinned on the Flow A `SandboxTemplate` via GitOps. **No +per-cluster overlay is needed:** the addon registry +([`gitops/addons/registry/sandbox.yaml`](../../gitops/addons/registry/sandbox.yaml)) builds each image +URI from the target cluster's own `aws_account_id` + `aws_region` annotations, so the same commit +resolves to whatever account it is deployed into and no account ID is committed to this repo. + +```bash +# amd64 (hub nodes are amd64); podman/docker both work. +aws ecr create-repository --repository-name dark-factory-coder --region # first time only +podman build --platform linux/amd64 -t .dkr.ecr..amazonaws.com/dark-factory-coder: \ + examples/dark-factory/coder +podman push .dkr.ecr..amazonaws.com/dark-factory-coder: +# β†’ bump the tag in the registry entry (valuesObject), commit, let ArgoCD sync. +``` + +Templating the registry **does not build the image** β€” each deployment must build it into its own ECR +at the pinned tag, or the warm pods report `ImagePullBackOff`. If your ECR repo uses **immutable +tags**, bump the tag rather than re-pushing one. + +Three values in the registry reuse this same coder image (`coderTemplate.image` on `agent-sandbox`; +`reviewImage` and `holdout.evalImage` on `dark-factory`) β€” repoint them together. `deployTest.image` +is a **separate** build (`deploy-test/`): it needs kubectl + terraform, which the coder image does not +carry, and it is wrapped in a Helm `required` guard, so the app fails to render if it is unset. + +## Credentials + +The factory needs **three separate GitHub credentials** so the untrusted coder VM never holds a +token that can administer webhooks. They come from AWS Secrets Manager via external-secrets; nothing +is created by hand in-cluster. + +```bash +export EVENTS_TOKEN='github_pat_...' ORCHESTRATOR_TOKEN='github_pat_...' CODER_TOKEN='github_pat_...' +bash examples/dark-factory/setup-secrets.sh +``` + +Minting the PATs is **manual** β€” GitHub ships no PAT-creation API, so this cannot be scripted. The +exact per-token permission sets are in the script's header comment, and the rationale (plus which +API call each permission is needed for) is in +[`docs/dark-factory/README.md` Β§10a](../../docs/dark-factory/README.md#10a-github-credentials-secrets-manager-setup). + +Symptom when they are missing: the warm pods sit in `ContainerCreating` indefinitely with +`MountVolume.SetUp failed … secret "dark-factory-github-coder" not found`. Nothing crashes and +nothing retries visibly β€” the mount just never satisfies. + +## Roadmap + +- **P1 (done):** issue β†’ PR, end-to-end, hands-off. The workflow stops at "PR open, awaiting human." +- **P2:** holdout gate β€” an isolated eval Job runs hidden BDD scenarios the coder never sees. +- **P3:** AWS Security / DevOps review agents (advisory β†’ blocking-capable). +- **P4:** auto-merge/teardown on approval + iterate loop (PR-comment driven). diff --git a/examples/dark-factory/coder-microvm/Dockerfile b/examples/dark-factory/coder-microvm/Dockerfile new file mode 100644 index 00000000..f3642423 --- /dev/null +++ b/examples/dark-factory/coder-microvm/Dockerfile @@ -0,0 +1,52 @@ +# Flow D β€” lambda-coder image: the dark-factory coder wrapped for the Lambda MicroVM +# snapshot/hook runtime. +# +# FROM the arm64 dark-factory-coder (Lambda MicroVM is ARM_64-only) β€” it carries +# entrypoint.js + the toolchain (git, node, claude-code). We add ONLY the hook server +# that adapts the one-shot coder to the MicroVM lifecycle (see hook-server.js): the +# /run hook background-spawns entrypoint.js with USE_BEDROCK=1 so the coder calls +# Bedrock directly via the MicroVM execution role β€” no Bifrost / EKS-network path. +# +# The image is built by Lambda from a code-artifact ZIP (this Dockerfile + hook-server.js) +# in S3 β€” NOT pushed to ECR as a normal image. The base coder image IS pulled from ECR +# during that build (the MicrovmImage build role keeps ecr:Get*). +# ⚠️ THIS ACCOUNT ID CANNOT BE TEMPLATED β€” it must be edited by hand per account. +# Everywhere else in this repo an ECR URI is injected from the cluster's aws_account_id +# annotation (gitops/addons/registry/sandbox.yaml), but that is impossible here: the +# MicrovmImage CRD's `spec.codeArtifact` has EXACTLY ONE property, `uri`. There is no +# build-args field (spec.environmentVariables is RUNTIME env for the VM, not docker build +# args), so nothing in Helm, the registry, or the CR can reach inside this ZIP. Whatever +# this default says is what Lambda pulls. Verified against the live CRD 2026-08-11. +# +# It previously named the chart AUTHOR's account, which no other account can +# pull from: cross-account ECR needs BOTH an identity grant AND a repository policy on +# the far side, and we hold no credentials there (confirmed: AccessDeniedException on +# ecr:DescribeImages, "no resource-based policy allows"). The build role's +# ecr:BatchGetImage on "*" is only the identity half, so it did NOT help. +# +# The durable fix is a templated packaging step that renders this line from +# aws_account_id at ZIP-assembly time. No such script exists yet β€” this image and the +# artifact ZIP have only ever been built by hand, which is the root cause of this whole +# drift class. Until then: EDIT THIS LINE when deploying to a new account, and rebuild +# the base with `docker buildx build --platform linux/arm64` (Lambda MicroVM is +# ARM_64-only; see the arch note above). +ARG CODER_IMAGE=REPLACE_ME_ACCOUNT_ID.dkr.ecr.REPLACE_ME_REGION.amazonaws.com/dark-factory-coder:v0.2.5-arm64 +FROM ${CODER_IMAGE} + +WORKDIR /app + +# The hook server (serves ready/validate/run/suspend/resume/terminate on :8080). +COPY hook-server.js /app/hook-server.js + +# Ship the UPDATED coder over the one baked into the ECR base. The base image's +# entrypoint.js predates the USE_BEDROCK branch; overlaying it here means the +# MicrovmImage build (which pulls the ECR base) gets the Bedrock-capable coder +# WITHOUT a separate ECR rebuild+push. Keep in sync with examples/dark-factory/coder/entrypoint.js. +COPY entrypoint.js /app/entrypoint.js + +# Lambda MicroVM snapshots the process started here. hooks.port on the MicrovmImage +# must match this (8080). The server stays up (long-running) so the VM isn't idle- +# suspended mid coder-run β€” the Microvm idlePolicy.maxIdleDurationSeconds is set +# longer than a coder run. +EXPOSE 8080 +CMD ["node", "/app/hook-server.js"] diff --git a/examples/dark-factory/coder-microvm/hook-server.js b/examples/dark-factory/coder-microvm/hook-server.js new file mode 100644 index 00000000..0cad32fd --- /dev/null +++ b/examples/dark-factory/coder-microvm/hook-server.js @@ -0,0 +1,118 @@ +// Flow D β€” Lambda MicroVM hook server (the lambda-coder wrapper). +// +// Lambda MicroVM is a snapshot/hook runtime: the platform builds an image by +// starting THIS process and snapshotting it once the `ready` hook says "go", then +// resumes that snapshot per session and calls the `run` hook with the session's +// runHookPayload as the request body. Hooks are HTTP endpoints we serve on :8080. +// +// The dark-factory coder (entrypoint.js) is a ONE-SHOT batch job (clone β†’ agent β†’ +// push β†’ PR, 5-15 min). It cannot run inside the 30s run hook, so /run just +// materializes the payload into the coder's file/env contract and BACKGROUND-SPAWNS +// entrypoint.js, then returns 200 immediately. The coder runs async; df-run's +// await-coder step polls GitHub for the PR (same as Kata). The VM stays alive because +// idlePolicy.maxIdleDurationSeconds > a coder run (idle = no inbound traffic). +// +// LLM: USE_BEDROCK=1 β†’ entrypoint.js calls Bedrock DIRECTLY via the MicroVM execution +// role (no Bifrost / EKS-network dependency). See docs/dark-factory/flow-d-coder-in-microvm-design.md. +// +// KEPT MINIMAL: this is the exact shape that built cleanly (v2.0). The /run handler +// stays trivial and synchronous so the build's ready-hook completes fast. Observability +// is via direct endpoint probes, not a /status route (adding one correlated with a +// hung ready-hook build on the pre-GA controller). + +const http = require("http"); +const fs = require("fs"); +const { spawn } = require("child_process"); + +const PORT = parseInt(process.env.HOOKS_PORT || "8080", 10); +const SECRETS_DIR = "/tmp/secrets"; +// Run-id of the coder invocation currently in flight (or last completed). NOT a plain +// boolean: the VM is SUSPENDED after the first PR and RESUMED for a fix round, and the +// resumed process keeps its in-memory state β€” a one-shot `coderStarted=true` guard, +// frozen in the snapshot, made the resumed VM ignore the fix round's /run entirely (the +// coder never re-ran; the fix round reported "done" on the old sha). Instead we key on a +// per-invocation run-id (issue + iterate-note hash): a /run whose id differs from the +// one in flight starts a fresh coder (this is a new round after a resume); a /run that +// repeats the current id is a duplicate webhook and is ignored. +let currentRunId = null; +let coderRunning = false; + +function runIdOf(d) { + const note = d.iterateNoteB64 || d.iterateNote || ""; + // Cheap stable hash of issue+note so a fix round (new note) => new id => re-run. + let h = 0; const s = `${d.issueNumber || ""}:${note}`; + for (let i = 0; i < s.length; i++) { h = ((h << 5) - h + s.charCodeAt(i)) | 0; } + return `${d.issueNumber || "?"}#${(h >>> 0).toString(36)}`; +} + +function startCoder(payload) { + let d = {}; + try { d = JSON.parse(payload || "{}"); } catch (e) { console.log("[hook-server] payload not JSON:", e.message); } + const rid = runIdOf(d); + if (rid === currentRunId) { console.log(`[hook-server] /run duplicate for ${rid} β€” ignoring`); return; } + if (coderRunning) { console.log(`[hook-server] /run for ${rid} but ${currentRunId} still running β€” ignoring`); return; } + const isRerun = currentRunId !== null; // a prior run existed => this is a post-resume fix round + currentRunId = rid; + coderRunning = true; + // Truncate the coder log on each new run. Otherwise the previous round's + // "done β€” PR opened on " line lingers and the bridge's /logs grep matches it + // instantly, suspending the VM before the fix-round coder has done anything. + try { fs.writeFileSync("/tmp/coder.log", ""); } catch {} + console.log(`[hook-server] /run accepted run-id=${rid}${isRerun ? " (post-resume re-run)" : ""}`); + fs.mkdirSync(SECRETS_DIR, { recursive: true, mode: 0o700 }); + if (d.ghToken) fs.writeFileSync(`${SECRETS_DIR}/gh-token`, d.ghToken, { mode: 0o400 }); + const env = { + ...process.env, + USE_BEDROCK: "1", + // MicroVM rootfs is read-only + there's no /workspace volume mount (unlike Kata, + // where the operator mounts a writable workspace). entrypoint.js mkdir's + // ${WORKSPACE}/artifacts and clones there, so point it at the writable tmpfs β€” + // else it crashes EACCES on /workspace/artifacts before doing any work. + WORKSPACE: "/tmp/workspace", + GH_TOKEN_PATH: `${SECRETS_DIR}/gh-token`, + AWS_REGION: d.region || process.env.AWS_REGION || "us-west-2", + DF_ISSUE_NUMBER: d.issueNumber ? String(d.issueNumber) : "", + DF_REPO: d.repo || "", + DF_BRANCH: d.branch || (d.issueNumber ? `df/issue-${d.issueNumber}` : ""), + DF_BASE_BRANCH: d.baseBranch || "main", + DF_ISSUE_TITLE: d.issueTitle || "", + }; + // Fix round (df-iterate): the bridge folds the human's change request into the + // payload as iterateNoteB64. Without this, a Lambda fix round re-runs the coder + // with NO instructions β†’ it sees the PR already open and reports "done" on the + // old sha with zero changes (the Kata path injects DF_ITERATE_NOTE_B64 as claim + // env; the MicroVM has no claim env, so it must ride in on the runHookPayload). + if (d.iterateNoteB64) env.DF_ITERATE_NOTE_B64 = d.iterateNoteB64; + if (d.iterateNote) env.DF_ITERATE_NOTE = d.iterateNote; + if (d.model) env.CODER_MODEL = d.model; + console.log(`[hook-server] /run β†’ spawning coder for issue #${env.DF_ISSUE_NUMBER} repo=${env.DF_REPO}`); + // Capture the coder's stdout+stderr to /tmp/coder.log so /logs can return it β€” + // runtime CloudWatch routing doesn't work on this runtime, and there's no shell, + // so this file (read over the HTTP token) is the ONLY way to see what the coder did. + const logFd = fs.openSync("/tmp/coder.log", "a"); + const child = spawn("node", ["/app/entrypoint.js"], { env, stdio: ["ignore", logFd, logFd], detached: true }); + child.unref(); + child.on("error", (e) => { coderRunning = false; try { fs.appendFileSync("/tmp/coder.log", "SPAWN-ERROR: " + e.message + "\n"); } catch {} }); + // Clear the in-flight flag when the coder exits so a resumed VM's next /run (fix round) + // is accepted. `unref`'d + detached, but we still get 'exit' while this process lives. + child.on("exit", (code) => { coderRunning = false; console.log(`[hook-server] coder run-id=${currentRunId} exited code=${code}`); }); +} + +const server = http.createServer((req, res) => { + let body = ""; + req.on("data", (c) => { body += c; }); + req.on("end", () => { + const ok = (o) => { res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify(o || { status: "ok" })); }; + switch (req.url) { + case "/ready": return ok({ status: "ready" }); + case "/validate": return ok({ status: "valid" }); + case "/run": startCoder(body); return ok({ status: "started" }); + case "/logs": { let l=""; try { l=fs.readFileSync("/tmp/coder.log","utf8"); } catch {} return ok({ status:"ok", runId: currentRunId, running: coderRunning, log: l.slice(-6000) }); } + case "/suspend": return ok({ status: "suspended" }); + case "/resume": return ok({ status: "resumed" }); + case "/terminate": return ok({ status: "terminated" }); + default: return ok({ status: "ok", path: req.url }); + } + }); +}); +server.listen(PORT, () => console.log(`[hook-server] listening on :${PORT} (lambda-coder, Bedrock-direct)`)); diff --git a/examples/dark-factory/coder/Dockerfile b/examples/dark-factory/coder/Dockerfile new file mode 100644 index 00000000..e079c0f1 --- /dev/null +++ b/examples/dark-factory/coder/Dockerfile @@ -0,0 +1,56 @@ +# Dark Factory coder image β€” runs INSIDE the Kata micro-VM sandbox. +# +# Bundles the in-VM agent + the toolchains a coder run needs (git, node, the +# agentic CLIs). This is untrusted-code territory: the image carries no +# credentials β€” secrets are injected at runtime via projected tmpfs (0400) and +# model access is only through Bifrost. Keep it lean and pinned. +FROM public.ecr.aws/docker/library/node:20-alpine + +# Toolchains for build/test across common stacks + git for checkout/push. Also +# aws-cli + zip + curl because the SAME image is reused by the hub-side steps +# (security-agent diff staging, bootstrap) which need those. bash for the scripts. +RUN apk add --no-cache git bash python3 py3-pip go zip curl aws-cli + +# ── Agentic engines β€” BOTH first-class + selectable via CODER_ENGINE ───────── +# claude β†’ Claude Code CLI (default) | kiro β†’ Kiro CLI (headless) +# entrypoint.js branches on the engine; the image carries both so a run can pick +# either without a rebuild. +RUN npm install -g @anthropic-ai/claude-code + +# Kiro CLI. Internally distributed via Builder Toolbox (`toolbox install +# kiro-cli`), which isn't available in this base image, so install from the +# platform's pinned artifact URL at build time. Best-effort: the image still +# builds (Claude as default) if the URL is unset/unreachable β€” CODER_ENGINE=kiro +# then logs a clear error at runtime rather than silently misbehaving. +ARG KIRO_CLI_URL="" +RUN if [ -n "$KIRO_CLI_URL" ]; then \ + echo "installing kiro-cli from $KIRO_CLI_URL" && \ + curl -fsSL "$KIRO_CLI_URL" -o /usr/local/bin/kiro && chmod +x /usr/local/bin/kiro ; \ + else \ + echo "WARN: KIRO_CLI_URL not set β€” CODER_ENGINE=kiro unavailable until provided at build" ; \ + fi + +# ── AWS DevOps Agent β€” release-readiness review plugin (NOT baked here) ────── +# The DevOps Agent review is invoked via the Claude Code / Kiro plugin (there is +# NO headless code-review API). That plugin installs via the AIM CLI and connects +# to an Agent Space needing a ONE-TIME console setup (Agent Space + repo connect / +# access key) β€” a connection that can't be baked into an image or performed from +# the credential-less, network-locked VM (no Midway). entrypoint.js therefore +# reports the DevOps review as "not-connected" (never a fake pass) until the +# platform wires the MCP/A2A access-key path from a hub-side step. See docs Β§6.2. + +WORKDIR /app +# entrypoint.js β€” env-driven Flow B coder (auto-runs on VM start, self-reports +# via GitHub commit status; the df-run Argo workflow polls GitHub for the result). +COPY entrypoint.js ./ + +# Non-root, matches the SandboxTemplate securityContext (runAsUser 1000). +USER 1000 + +ENV WORKSPACE=/workspace + +# Baked into the Flow A SandboxTemplate as the coder image. On VM start it reads +# the DF_* env injected by the SandboxClaim, implements + tests, pushes the +# branch, runs the DevOps Agent review (if connected), opens the PR, and sets the +# dark-factory/implementation commit status. +CMD ["node", "entrypoint.js"] diff --git a/examples/dark-factory/coder/entrypoint.js b/examples/dark-factory/coder/entrypoint.js new file mode 100644 index 00000000..5b43fa31 --- /dev/null +++ b/examples/dark-factory/coder/entrypoint.js @@ -0,0 +1,549 @@ +// entrypoint.js β€” the in-VM coder for Flow B P1 (runs on Kata micro-VM start). +// +// This is the UNTRUSTED side of the trust boundary. It holds NO cloud creds and +// NO Kubernetes API access (no SA token). Its only credentials are a Bifrost key +// and a short-TTL GitHub token, both read from projected tmpfs (mode 0400). +// Because it can't talk to the k8s API, it SELF-REPORTS through GitHub β€” the +// df-run workflow polls GitHub for the PR + the dark-factory/implementation +// commit status this script sets. +// +// Driven entirely by env injected via SandboxClaim.spec.env (contract verified +// against the live operator, envVarsInjectionPolicy=Allowed): +// DF_REPO owner/name of the target repo +// DF_ISSUE_NUMBER the GitHub issue number (the spec) +// DF_BRANCH df/issue- +// DF_BASE_BRANCH base to branch from (default main) +// DF_ISSUE_TITLE issue title (for the PR title) +// CODER_PROFILE claude-code | kiro +// BIFROST_URL LLM gateway (from the SandboxTemplate) +// +// Flow: fetch issue β†’ SPEC.md β†’ checkout df/issue-N β†’ coder implements β†’ +// build+test β†’ push β†’ open PR β†’ set commit status success/failure. +const fs = require("fs"); +const http = require("http"); +const https = require("https"); +const { URL } = require("url"); +const { execFileSync, spawn } = require("child_process"); + +const WORKSPACE = process.env.WORKSPACE || "/workspace"; +const BIFROST_URL = process.env.BIFROST_URL || "http://bifrost.bifrost.svc.cluster.local:8080"; +const REPO = process.env.DF_REPO; +const ISSUE = process.env.DF_ISSUE_NUMBER; +const BRANCH = process.env.DF_BRANCH || `df/issue-${ISSUE}`; +const BASE = process.env.DF_BASE_BRANCH || "main"; +const TITLE = process.env.DF_ISSUE_TITLE || `Dark Factory: issue #${ISSUE}`; +// Agentic engine the VM runs. Both engines are first-class and selectable: +// claude β†’ claude -p (Claude Code, default) | kiro β†’ kiro run --headless +// Accept the new CODER_ENGINE and the legacy CODER_PROFILE (claude-code|kiro); +// normalize either to a bare engine id. +const ENGINE = (() => { + const raw = (process.env.CODER_ENGINE || process.env.CODER_PROFILE || "claude").toLowerCase(); + return raw.startsWith("kiro") ? "kiro" : "claude"; +})(); +const PROFILE = ENGINE; // back-compat alias used in a few log lines +// AWS DevOps Agent β€” release-readiness review via the coding-agent plugin, run +// BEFORE the PR opens (docs Β§6.2). Modes: "claude-plugin" (Claude Code DevOps +// Agent plugin) | "off". On a clear verdict the coder applies DF_DEVOPS_CLEAR_LABEL +// so the hub's Security Agent step runs next (DevOps-first ordering). +const DEVOPS_AGENT_MODE = (process.env.DF_DEVOPS_AGENT_MODE || "off").toLowerCase(); +const DEVOPS_CLEAR_LABEL = process.env.DF_DEVOPS_CLEAR_LABEL || "needs-security-review"; +const DEVOPS_CLEAR_VERDICTS = (process.env.DF_DEVOPS_CLEAR_VERDICTS || "Safe to Release,Proceed with Caution") + .split(",").map((s) => s.trim().toLowerCase()).filter(Boolean); + +const GH_TOKEN_PATH = process.env.GH_TOKEN_PATH || "/etc/secrets/gh-token"; +const BIFROST_KEY_PATH = process.env.BIFROST_KEY_PATH || "/etc/secrets/bifrost-api-key"; +// LLM observability (traces/cost/tokens) is provided by BIFROST's telemetry, which +// already exports full per-call traces to Langfuse tagged user-agent=dark-factory-coder +// (prompt, response, model, tokens). We deliberately do NOT post a redundant +// coder-side trace here β€” Bifrost's is richer. See docs/dark-factory Β§7a. + +function readSecret(p) { + try { return fs.readFileSync(p, "utf8").trim(); } catch { return null; } +} +function sh(cmd, args, opts = {}) { + return execFileSync(cmd, args, { + cwd: opts.cwd || WORKSPACE, encoding: "utf8", + stdio: ["ignore", "pipe", "pipe"], env: opts.env || process.env, + maxBuffer: 64 * 1024 * 1024, + }); +} + +const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); + +// Minimal GitHub REST helper (self-report bus β€” no k8s API available). +// GitHub is the completion bus the df-run workflow polls, so a transient network +// blip on a report call (observed: "socket hang up" on the final success POST) +// must NOT be allowed to mark a good run as failed. Retry transient transport +// errors (ECONNRESET / socket hang up) and 5xx with a short backoff. +function ghOnce(method, path, body) { + const token = readSecret(GH_TOKEN_PATH); + return new Promise((resolve, reject) => { + const data = body ? JSON.stringify(body) : null; + const req = https.request( + { host: "api.github.com", method, path, + headers: { + "User-Agent": "dark-factory-coder", Authorization: `Bearer ${token}`, + Accept: "application/vnd.github+json", "X-GitHub-Api-Version": "2022-11-28", + ...(data ? { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(data) } : {}), + } }, + (res) => { + let buf = ""; res.on("data", (c) => (buf += c)); + res.on("end", () => { + if (res.statusCode >= 200 && res.statusCode < 300) resolve(buf ? JSON.parse(buf) : {}); + else { const e = new Error(`GitHub ${method} ${path} β†’ ${res.statusCode}: ${buf}`); e.statusCode = res.statusCode; reject(e); } + }); + }); + req.on("error", reject); + if (data) req.write(data); + req.end(); + }); +} + +async function gh(method, path, body) { + let lastErr; + for (let attempt = 1; attempt <= 4; attempt++) { + try { return await ghOnce(method, path, body); } + catch (e) { + // Retry only transient failures β€” never a 4xx (bad request / already exists). + const transient = e.statusCode === undefined || e.statusCode >= 500 || e.statusCode === 429; + if (!transient || attempt === 4) throw e; + lastErr = e; + await sleep(500 * attempt); + } + } + throw lastErr; +} + +// Upsert ONE marker-based PR comment (edit in place, no spam) β€” mirrors the +// hub-side comment.js so the coder's own steps (coding complete, local testing) +// leave a visible mark on the PR just like the review agents do. +async function postStickyComment(prNumber, marker, bodyMd) { + if (!prNumber) return; + const full = `\n${bodyMd.trim()}`; + try { + let existing = null; + for (let page = 1; page <= 5 && !existing; page++) { + const cs = await gh("GET", `/repos/${REPO}/issues/${prNumber}/comments?per_page=100&page=${page}`); + if (!Array.isArray(cs) || !cs.length) break; + existing = cs.find((c) => (c.body || "").includes(``)); + } + if (existing) await gh("PATCH", `/repos/${REPO}/issues/comments/${existing.id}`, { body: full }); + else await gh("POST", `/repos/${REPO}/issues/${prNumber}/comments`, { body: full }); + console.log(`[coder] posted PR comment ${marker}`); + } catch (e) { console.error(`[coder] comment ${marker} non-fatal: ${e.message}`); } +} + +async function fetchIssueSpec() { + const issue = await gh("GET", `/repos/${REPO}/issues/${ISSUE}`); + let spec = `# ${issue.title}\n\n${issue.body || ""}\n`; + // No profile/scaffold-hint injection: the issue text states the stack ("a Spring + // Boot service", "Terraform for an S3 bucket"), the coder generates idiomatic + // code from that, and build/test is discovered from the resulting marker files. + // Iterate mode (df-iterate): a human left a change request on the PR. Append it + // so the coder revises the EXISTING branch to address the feedback, rather than + // re-implementing from scratch. DF_ITERATE_NOTE is injected by the df-iterate + // claim; absent on a first (df-run) pass. + // Prefer the base64 form: the revision note (esp. auto-fed agent findings) is + // arbitrary markdown with newlines/quotes/braces that CANNOT be injected raw into + // the SandboxClaim env YAML (it broke the manifest). status.js/df-iterate base64 + // it into DF_ITERATE_NOTE_B64; decode here. Fall back to plain DF_ITERATE_NOTE. + const note = iterateNote(); + if (note && note.trim()) { + spec += `\n---\n\n## Revision requested (address this feedback on the existing branch)\n\n${note}\n`; + } + return spec; +} + +// Resolve the revision note from DF_ITERATE_NOTE_B64 (preferred, safe for arbitrary +// text) or the legacy plain DF_ITERATE_NOTE. +function iterateNote() { + const b64 = process.env.DF_ITERATE_NOTE_B64; + if (b64 && b64.trim()) { try { return Buffer.from(b64.trim(), "base64").toString("utf8"); } catch (_) { /* fall through */ } } + return process.env.DF_ITERATE_NOTE || ""; +} + +function checkout() { + const token = readSecret(GH_TOKEN_PATH); + const url = `https://x-access-token:${token}@github.com/${REPO}.git`; + const dir = `${WORKSPACE}/repo`; + const iterating = !!(iterateNote() && iterateNote().trim()); + if (!fs.existsSync(dir)) { + // On iterate, start from the existing coder branch (build on prior work); + // otherwise branch fresh from BASE. + if (iterating) { + try { sh("git", ["clone", "--depth", "1", "--branch", BRANCH, url, dir]); } + catch { sh("git", ["clone", "--depth", "1", "--branch", BASE, url, dir]); } + } else { + sh("git", ["clone", "--depth", "1", "--branch", BASE, url, dir]); + } + } + sh("git", ["checkout", "-B", BRANCH], { cwd: dir }); + sh("git", ["config", "user.email", "dark-factory@noreply"], { cwd: dir }); + sh("git", ["config", "user.name", "Dark Factory"], { cwd: dir }); + return dir; +} + +// Bifrost does User-Agent-prefix routing: any request whose UA starts with +// "claude-cli" is run through a Claude-Code-specific request transform that is +// broken on this build and returns `400 Unexpected field type` β€” REGARDLESS of +// the body (the identical body + any other UA returns 200; verified by header +// binary-search against the live gateway). We can't patch Bifrost from inside +// the untrusted VM, so we front it with a tiny localhost shim that rewrites the +// UA to a generic value and transparently forwards everything else β€” including +// SSE streams (Claude Code sends stream:true). Claude Code points at this shim +// via ANTHROPIC_BASE_URL; the shim proxies to the real Bifrost /anthropic route. +// +// The shim MUST run in its OWN process: we launch the coder CLI with the +// synchronous execFileSync (so we can await its exit), which blocks this Node +// event loop for the whole run β€” an in-process http.Server would never accept a +// connection (observed: ConnectionRefused). So we write the shim to a temp file +// and spawn `node` on it in the background, then wait for its port to open. +const SHIM_PORT = 8791; +function startBifrostUaShim(upstreamBase) { + const shimSrc = ` +const http=require("http"),https=require("https"),{URL}=require("url"); +const up=new URL(${JSON.stringify(upstreamBase)}); +const agent=up.protocol==="https:"?https:http; +http.createServer((cReq,cRes)=>{ + const headers={...cReq.headers,host:up.host,"user-agent":"dark-factory-coder"}; + const pReq=agent.request({protocol:up.protocol,hostname:up.hostname,port:up.port||(up.protocol==="https:"?443:80),method:cReq.method,path:up.pathname.replace(/\\/+$/,"")+cReq.url,headers}, + pRes=>{cRes.writeHead(pRes.statusCode,pRes.headers);pRes.pipe(cRes);}); + pReq.on("error",e=>{cRes.writeHead(502);cRes.end(String(e.message));}); + cReq.pipe(pReq); +}).listen(${SHIM_PORT},"127.0.0.1",()=>console.log("[ua-shim] listening on ${SHIM_PORT} -> "+up.href)); +`; + const shimPath = "/tmp/ua-shim.js"; + fs.writeFileSync(shimPath, shimSrc); + const child = spawn("node", [shimPath], { stdio: "inherit", detached: false }); + child.unref(); + // Block until the shim's port is accepting (execFileSync below can't yield). + const deadline = Date.now() + 5000; + while (Date.now() < deadline) { + try { execFileSync("node", ["-e", `require("net").connect(${SHIM_PORT},"127.0.0.1").on("connect",()=>process.exit(0)).on("error",()=>process.exit(1))`], { stdio: "ignore" }); break; } + catch { execFileSync("sleep", ["0.2"]); } + } + return `http://127.0.0.1:${SHIM_PORT}`; +} + +function runCoder(repoDir) { + // Two LLM transports, selected by USE_BEDROCK: + // - Kata (Flow B, default): Bifrost gateway. The Kata VM is credential-less + + // in-cluster, so it reaches models through Bifrost's /anthropic route (which + // also gives centralized Langfuse observability). CLAUDE_CODE_USE_BEDROCK is + // deliberately UNSET here (it would make the CLI use the Bedrock SDK directly + // and ignore ANTHROPIC_BASE_URL). + // - Lambda MicroVM (Flow D): USE_BEDROCK=1. A MicroVM runs OUTSIDE the cluster + // network and can't reach Bifrost's ClusterIP; forcing it back in-cluster + // needed a VPC connector + internal NLB. Instead the MicroVM's EXECUTION ROLE + // grants bedrock:InvokeModel, so Claude Code calls Bedrock directly over public + // egress β€” no EKS network dependency. Trade-off: these calls bypass Bifrost's + // Langfuse telemetry (documented in flow-d-coder-in-microvm-design.md). + const useBedrock = /^(1|true|yes)$/i.test(process.env.USE_BEDROCK || ""); + const baseEnv = { + ...process.env, + // The sandbox runs with readOnlyRootFilesystem, so $HOME (/home/node) is NOT + // writable. Claude Code writes its config, session state, and β€” critically β€” + // per-invocation SHELL SNAPSHOT files that its Bash tool sources before every + // command into ~/.claude. If that dir can't be created, every Bash call (npm + // install/test, git) fails and the agent loops retrying forever (observed: + // a trivial change ran >15min with no commit). Point HOME + config dir at the + // writable /tmp tmpfs so the CLI can persist and run shell commands. + HOME: "/tmp/coder-home", + CLAUDE_CONFIG_DIR: "/tmp/coder-home/.claude", + XDG_CONFIG_HOME: "/tmp/coder-home/.config", + XDG_CACHE_HOME: "/tmp/coder-home/.cache", + // Non-interactive: never open a browser / prompt for login in headless mode. + CI: "1", + }; + let env; + if (useBedrock) { + // Bedrock-direct: creds come from the MicroVM execution role (Pod Identity / + // instance creds); the CLI uses the Bedrock SDK. Model must be a real Bedrock + // model ID (NOT a Bifrost alias). AWS_REGION comes from the runHookPayload/env. + env = { + ...baseEnv, + CLAUDE_CODE_USE_BEDROCK: "1", + AWS_REGION: process.env.AWS_REGION || process.env.CODER_REGION || "us-west-2", + ANTHROPIC_MODEL: process.env.CODER_MODEL || "us.anthropic.claude-sonnet-4-5-20250929-v1:0", + ANTHROPIC_SMALL_FAST_MODEL: process.env.CODER_SMALL_MODEL || process.env.CODER_MODEL || "us.anthropic.claude-sonnet-4-5-20250929-v1:0", + }; + fs.mkdirSync("/tmp/coder-home/.claude", { recursive: true }); + console.log(`[coder] LLM: Bedrock-direct region=${env.AWS_REGION} model=${env.ANTHROPIC_MODEL}`); + } else { + // Bifrost is an Anthropic-compatible gateway. Route through the localhost + // UA-shim so Bifrost doesn't apply its broken claude-cli request transform. + // Bifrost auth is optional; send a placeholder so the CLI doesn't prompt. + const key = readSecret(BIFROST_KEY_PATH) || "bifrost"; + const base = startBifrostUaShim(`${BIFROST_URL.replace(/\/+$/, "")}/anthropic`); + env = { + ...baseEnv, + ANTHROPIC_BASE_URL: base, + ANTHROPIC_API_KEY: key, + // Bifrost maps model ALIASES β†’ Bedrock model IDs. Claude Code's default + // model name (e.g. claude-sonnet-4) isn't a Bifrost alias and returns + // "provided model identifier is invalid" (400). Use the platform's Bifrost + // alias (verified: 'claude-sonnet' β†’ us.anthropic.claude-sonnet-4-5). + ANTHROPIC_MODEL: process.env.CODER_MODEL || "claude-sonnet", + ANTHROPIC_SMALL_FAST_MODEL: process.env.CODER_MODEL || "claude-sonnet", + }; + fs.mkdirSync("/tmp/coder-home/.claude", { recursive: true }); + delete env.CLAUDE_CODE_USE_BEDROCK; + console.log(`[coder] LLM: Bifrost base=${base} model=${env.ANTHROPIC_MODEL}`); + } + // Inherit stdio so the coder CLI's own output + errors stream into the pod + // logs (kubectl logs), instead of being swallowed by execFileSync's exception. + const opts = { cwd: repoDir, env, stdio: "inherit", maxBuffer: 64 * 1024 * 1024 }; + const prompt = `Implement the change described in ${WORKSPACE}/SPEC.md. Build and run unit tests until green. Commit your work.`; + if (ENGINE === "kiro") { + // Kiro CLI headless β€” the coder image carries the `kiro` binary; it reads the + // same Bifrost/Bedrock env above. --headless drives it non-interactively. + console.log("[coder] engine=kiro (kiro run --headless)"); + return execFileSync("kiro", ["run", "--headless", "--spec", `${WORKSPACE}/SPEC.md`], opts); + } + console.log("[coder] engine=claude (claude -p)"); + return execFileSync( + "claude", + ["-p", prompt, "--permission-mode", "bypassPermissions", "--verbose"], + opts, + ); +} + +// AWS DevOps Agent β€” release-readiness code review, run in-VM via the coding-agent +// plugin BEFORE the PR opens (docs Β§6.2). Returns { verdict, cleared, summary }. +// This is the REAL managed agent, invoked through the engine's plugin β€” NOT a +// linter/LLM stand-in. Because the plugin needs a one-time console connect +// (Agent Space + repo), when it isn't wired the review is reported as +// "not-connected" and cleared=false (NEVER a fake pass) so the hub sticky-status +// shows DevOps as not-run and Security is correctly skipped. +function runDevopsReview(repoDir) { + if (DEVOPS_AGENT_MODE === "off") return { verdict: "skipped", cleared: false, summary: "DevOps Agent disabled" }; + const key = readSecret(BIFROST_KEY_PATH) || "bifrost"; + const base = startBifrostUaShim(`${BIFROST_URL.replace(/\/+$/, "")}/anthropic`); + const env = { + ...process.env, + ANTHROPIC_BASE_URL: base, ANTHROPIC_API_KEY: key, + ANTHROPIC_MODEL: process.env.CODER_MODEL || "claude-sonnet", + ANTHROPIC_SMALL_FAST_MODEL: process.env.CODER_MODEL || "claude-sonnet", + HOME: "/tmp/coder-home", CLAUDE_CONFIG_DIR: "/tmp/coder-home/.claude", + XDG_CONFIG_HOME: "/tmp/coder-home/.config", XDG_CACHE_HOME: "/tmp/coder-home/.cache", CI: "1", + }; + delete env.CLAUDE_CODE_USE_BEDROCK; + const opts = { cwd: repoDir, env, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], maxBuffer: 64 * 1024 * 1024, timeout: 15 * 60 * 1000 }; + // Ask the coding agent to invoke the DevOps Agent release-readiness review on + // the working changes and print the verdict on the last line as DF_DEVOPS_VERDICT=. + const prompt = + "Use the AWS DevOps Agent plugin to run a release readiness code review on the current uncommitted+committed changes in this repo " + + "(cross-repository dependency risks, internal standards compliance, access-control correctness; run static analysis). " + + "Summarize the findings, then print EXACTLY one final line: DF_DEVOPS_VERDICT=."; + try { + let out = ""; + if (ENGINE === "kiro") { + out = execFileSync("kiro", ["run", "--headless", "--prompt", prompt], opts) || ""; + } else { + out = execFileSync("claude", ["-p", prompt, "--permission-mode", "bypassPermissions"], opts) || ""; + } + const m = String(out).match(/DF_DEVOPS_VERDICT=\s*(.+)\s*$/im); + const verdict = m ? m[1].trim() : "unknown"; + const cleared = DEVOPS_CLEAR_VERDICTS.includes(verdict.toLowerCase()); + console.log(`[coder] AWS DevOps Agent verdict: ${verdict} (cleared=${cleared})`); + return { verdict, cleared, summary: `AWS DevOps Agent: ${verdict}` }; + } catch (e) { + // Plugin not connected / not installed β†’ report honestly, do NOT fake-pass. + const msg = (e.stderr || e.stdout || e.message || "").toString(); + const notConnected = /plugin|not installed|not connected|unknown command|No such|command not found/i.test(msg); + console.error(`[coder] DevOps Agent review unavailable: ${msg.slice(0, 200)}`); + return { verdict: notConnected ? "not-connected" : "error", cleared: false, summary: "AWS DevOps Agent not connected (one-time console setup required)" }; + } +} + +function buildAndTest(repoDir) { + // Build/test is DISCOVERED from the repo's own marker files β€” no per-language + // platform config. Devs control build/test by their repo layout; the toolchains + // live in the coder IMAGE (see coder/Dockerfile), not here. A repo Makefile with + // a `test` target is the explicit dev-controlled override, checked first. + // + // The project may not be at the repo ROOT (e.g. an infra/ + app/ split), so we + // probe a few conventional subdirs and run the toolchain in the first that has a + // recognizable marker. Root wins if present; otherwise app/src/backend/server. + const CANDIDATES = [".", "app", "src", "backend", "server"]; + const hasIn = (d, f) => fs.existsSync(`${repoDir}/${d}/${f}`.replace(/\/\.\//, "/")); + const dirOf = (f) => CANDIDATES.find((d) => hasIn(d, f)); + const makeTestDir = () => { + for (const d of CANDIDATES) { + try { if (hasIn(d, "Makefile") && /^test:/m.test(fs.readFileSync(`${repoDir}/${d}/Makefile`, "utf8"))) return d; } catch { /* skip */ } + } + return null; + }; + const wd = (d) => `${repoDir}/${d}`.replace(/\/\.$/, ""); + try { + let d; + if ((d = makeTestDir()) != null) { console.log(`[coder] Makefile test target in ${d}/`); sh("make", ["test"], { cwd: wd(d) }); } + else if ((d = dirOf("package.json")) != null) { console.log(`[coder] node project in ${d}/`); sh("npm", ["install", "--no-audit", "--no-fund"], { cwd: wd(d) }); sh("npm", ["test"], { cwd: wd(d) }); } + else if ((d = dirOf("go.mod")) != null) sh("go", ["test", "./..."], { cwd: wd(d) }); + else if ((d = ["pyproject.toml", "setup.py", "requirements.txt"].map(dirOf).find(Boolean)) != null) sh("python", ["-m", "pytest", "-q"], { cwd: wd(d) }); + else if ((d = dirOf("Cargo.toml")) != null) sh("cargo", ["test"], { cwd: wd(d) }); + else if ((d = dirOf("pom.xml")) != null) sh("mvn", ["-q", "test"], { cwd: wd(d) }); + else if ((d = ["build.gradle", "build.gradle.kts"].map(dirOf).find(Boolean)) != null) sh("./gradlew", ["test"], { cwd: wd(d) }); + else return { green: true, summary: "no recognized test suite β€” skipped (e.g. infra/config change)" }; + return { green: true, summary: "tests passed" }; + } catch (e) { + return { green: false, summary: (e.stdout || e.stderr || e.message || "").toString().slice(-400) }; + } +} + +async function main() { + for (const [k, v] of Object.entries({ DF_REPO: REPO, DF_ISSUE_NUMBER: ISSUE })) { + if (!v) { console.error(`[coder] missing required env ${k}`); process.exit(2); } + } + fs.mkdirSync(`${WORKSPACE}/artifacts`, { recursive: true }); + console.log(`[coder] issue #${ISSUE} of ${REPO} β†’ branch ${BRANCH} (profile=${PROFILE})`); + + const spec = await fetchIssueSpec(); + fs.writeFileSync(`${WORKSPACE}/SPEC.md`, spec); + const repoDir = checkout(); + + let headSha = ""; + try { + runCoder(repoDir); + const test = buildAndTest(repoDir); + if (!test.green) throw new Error(`tests not green: ${test.summary}`); + + // Nothing-to-do guard: if the coder produced no commits ahead of the base, + // there's no diff β€” GitHub rejects the PR with 422 "No commits between…". + // This happens when the requested change already exists on base. Report the + // implementation status as success (the spec is satisfied) and exit cleanly + // instead of crash-looping. The df-run poller sees success on the base head. + let ahead = "1"; // default to "has changes" if we can't determine (fail open to PR) + try { + execFileSync("git", ["fetch", "--depth", "1", "origin", BASE], { cwd: repoDir, stdio: "ignore" }); + ahead = sh("git", ["rev-list", "--count", `origin/${BASE}..HEAD`], { cwd: repoDir }).trim(); + } catch { /* base unfetchable β€” proceed to PR, GitHub will validate */ } + if (ahead === "0") { + console.log(`[coder] no changes ahead of ${BASE} β€” the spec appears already satisfied; skipping PR`); + headSha = sh("git", ["rev-parse", "HEAD"], { cwd: repoDir }).trim(); + await gh("POST", `/repos/${REPO}/statuses/${headSha}`, { + state: "success", context: "dark-factory/implementation", + description: "no changes needed β€” spec already satisfied on base", + }); + process.exit(0); + } + // df/issue-N is bot-owned and single-writer (the df-run workflow holds a + // per-issue mutex), so a plain --force is safe and correct. --force-with-lease + // can't be used: the depth-1 clone never fetched origin/df/issue-N, so its + // lease check fails ("stale info") whenever the branch already exists from a + // prior run. + // AWS DevOps Agent release-readiness review runs FIRST (before the PR opens), + // per the DevOps-first ordering. Its verdict drives the handoff label so the + // hub's Security Agent step runs only after DevOps clears. + const devops = runDevopsReview(repoDir); + + sh("git", ["push", "-u", "origin", BRANCH, "--force"], { cwd: repoDir }); + headSha = sh("git", ["rev-parse", "HEAD"], { cwd: repoDir }).trim(); + + // Open the PR (idempotent: ignore "already exists"). The coder opens the PR + // BEFORE the hub-side verify steps run, so it can only mark them "running". + // The workflow's sticky-status step rewrites the block below (between the + // dark-factory:status marker) with the real verdicts once holdout/security/ + // devops finish β€” the "one live sticky status" (README Β§7). Keep this block's + // shape in sync with that step. + // DevOps line reflects how the review is driven: + // - mode "off" (default = check-gate): the AWS DevOps Agent GitHub App reviews + // the PR and posts its own check β€” so show "pending (GitHub App)", NOT skipped. + // - mode "claude-plugin" (label-gate): the coder drove it β†’ show the verdict + // (or "not connected" honestly if the plugin isn't wired). + // Either way the hub sticky-status step overwrites this block with the live + // verdict (from the check-run / statuses) once verification completes. + // NEUTRAL placeholder only. The coder opens the PR BEFORE the hub verify steps + // + the AWS agents run, so it must NOT print per-step states (they'd be stale + // guesses that confused readers: "Holdout: running…" long after it finished, + // "Security: runs after DevOps…" while the bot was already done). The pipeline's + // ONE consolidated review (status.js β†’ dark-factory:verdict-review) is the + // authoritative live status. We keep the dark-factory:status marker so status.js + // can still replace this block with the final verdict summary at the end. + const prBody = [ + `Closes #${ISSUE}.`, + "", + "", + "### 🏭 Dark Factory β€” verification", + `- βœ… **Build + unit tests (in-VM):** ${test.summary}`, + "", + "⏳ **Verification in progress.** Hub gates (holdout, deploy-test) and the real", + "AWS DevOps + Security agents are reviewing this PR. Results are posted as a", + "single **consolidated verdict review** on this PR when they finish β€” that", + "review (not this body) is the source of truth for merge readiness.", + "", + "_Autonomously implemented in a hardware-isolated micro-VM. DevOps + Security reviews are the real AWS Frontier Agents._", + ].join("\n"); + let prNumber = ""; + try { + const created = await gh("POST", `/repos/${REPO}/pulls`, { + title: `Dark Factory: ${TITLE} (#${ISSUE})`, head: BRANCH, base: BASE, + body: prBody, + maintainer_can_modify: true, + }); + prNumber = created && created.number ? String(created.number) : ""; + } catch (e) { if (!/already exists|A pull request already/i.test(e.message)) throw e; } + // If the PR already existed, look up its number (labels attach to the PR). + if (!prNumber) { + try { + const list = await gh("GET", `/repos/${REPO}/pulls?head=${REPO.split("/")[0]}:${BRANCH}&state=open`); + if (Array.isArray(list) && list[0]) prNumber = String(list[0].number); + } catch (_) {} + } + + // Every step reports on the PR as a comment. The coder posts two: (1) coding + // complete β€” what it changed; (2) local testing β€” the in-VM build+test result. + // (The review agents post their own comments; the sticky PR-body block is the + // consolidated board.) Files changed = git name-status vs base. + let changed = ""; + try { changed = sh("git", ["diff", "--name-status", `origin/${BASE}...HEAD`], { cwd: repoDir }).trim(); } catch { try { changed = sh("git", ["show", "--name-status", "--oneline", "-1", "HEAD"], { cwd: repoDir }).trim(); } catch {} } + const filesBlock = changed ? "```\n" + changed.slice(0, 1500) + "\n```" : "_(diff summary unavailable)_"; + await postStickyComment(prNumber, "dark-factory:coding", + `### βœ… πŸ€– Coding complete (engine: ${ENGINE})\n\nImplemented the change for issue #${ISSUE} on \`${BRANCH}\` in a hardware-isolated Kata micro-VM.\n\n**Files changed:**\n${filesBlock}`); + await postStickyComment(prNumber, "dark-factory:local-test", + `### ${test.green ? "βœ…" : "❌"} πŸ§ͺ Local testing (in-VM, before PR)\n\n**${test.green ? "Build + unit tests passed" : "Tests NOT green"}** β€” discovered from the repo's own marker files (no central config).\n\n${test.summary ? "```\n" + String(test.summary).slice(0, 800) + "\n```" : ""}`); + + // Post the AWS DevOps Agent verdict as its own commit status, and β€” when the + // verdict clears β€” apply the handoff label so the hub's Security Agent step + // runs next (DevOps-first ordering). When not connected/BLOCK, we leave the + // label off (Security stays gated) and report honestly. + if (DEVOPS_AGENT_MODE !== "off") { + const dvState = devops.cleared ? "success" : (devops.verdict === "BLOCK" ? "failure" : "error"); + try { + await gh("POST", `/repos/${REPO}/statuses/${headSha}`, { + state: dvState, context: "dark-factory/devops", + description: devops.summary.slice(0, 130), + }); + } catch (_) {} + if (devops.cleared && prNumber) { + // Labels attach to the PR number (the hub devops-gate reads + // /issues//labels). Label the PR, not the issue. + try { + await gh("POST", `/repos/${REPO}/issues/${prNumber}/labels`, { labels: [DEVOPS_CLEAR_LABEL] }); + console.log(`[coder] AWS DevOps Agent cleared β†’ applied '${DEVOPS_CLEAR_LABEL}' to PR #${prNumber} (Security Agent will run)`); + } catch (e) { console.error(`[coder] could not apply handoff label: ${e.message}`); } + } else if (devops.cleared && !prNumber) { + console.error("[coder] DevOps cleared but PR number unknown β€” cannot apply handoff label"); + } else { + console.log(`[coder] AWS DevOps Agent did NOT clear (${devops.verdict}) β†’ Security Agent stays gated`); + } + } + + // Self-report SUCCESS on the head SHA β€” this is what df-run polls for. + await gh("POST", `/repos/${REPO}/statuses/${headSha}`, { + state: "success", context: "dark-factory/implementation", + description: "implemented, built + tests green", + }); + console.log(`[coder] done β€” PR opened, status success on ${headSha}`); + } catch (e) { + console.error(`[coder] failed: ${e.message}`); + if (headSha) { + try { await gh("POST", `/repos/${REPO}/statuses/${headSha}`, { state: "failure", context: "dark-factory/implementation", description: e.message.slice(0, 130) }); } catch (_) {} + } + process.exit(1); + } + // Keep the VM alive briefly so logs are collectible; the claim TTL / teardown reaps it. + process.exit(0); +} + +main(); diff --git a/examples/dark-factory/deploy-test/Dockerfile b/examples/dark-factory/deploy-test/Dockerfile new file mode 100644 index 00000000..b291aeef --- /dev/null +++ b/examples/dark-factory/deploy-test/Dockerfile @@ -0,0 +1,25 @@ +# Dark Factory deploy-test image β€” runs in the TRUSTED hub deploy-test step. +# +# Content-aware validation of a PR's deployable artifacts: +# kind=k8s β†’ kubectl apply into an ephemeral namespace + wait Ready +# kind=terraform β†’ terraform init -backend=false + validate (+ fmt check) +# Bundles exactly what both paths + the PR-comment upsert need: kubectl, +# terraform, node, git, curl, bash. This is the only Dark Factory image that +# holds K8s access at runtime (via the workflow SA) β€” never the coder. +FROM public.ecr.aws/docker/library/alpine:3.20 + +ARG KUBECTL_VERSION=v1.31.0 +ARG TERRAFORM_VERSION=1.9.8 +ARG TARGETARCH=amd64 + +RUN apk add --no-cache bash git curl nodejs unzip ca-certificates \ + && curl -fsSL "https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/${TARGETARCH}/kubectl" -o /usr/local/bin/kubectl \ + && chmod +x /usr/local/bin/kubectl \ + && curl -fsSL "https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/terraform_${TERRAFORM_VERSION}_linux_${TARGETARCH}.zip" -o /tmp/tf.zip \ + && unzip -o /tmp/tf.zip -d /usr/local/bin \ + && rm /tmp/tf.zip \ + && kubectl version --client=true 2>/dev/null | head -1 || true \ + && terraform version | head -1 + +# Non-root; the step needs no privileged access beyond the SA-bound RBAC. +USER 65532 diff --git a/examples/dark-factory/setup-secrets.sh b/examples/dark-factory/setup-secrets.sh new file mode 100755 index 00000000..93487b8c --- /dev/null +++ b/examples/dark-factory/setup-secrets.sh @@ -0,0 +1,154 @@ +#!/usr/bin/env bash +# Create the three Dark Factory GitHub credentials in AWS Secrets Manager. +# +# The charts do NOT create these β€” external-secrets pulls them from Secrets +# Manager into the three namespaces that consume them. Until they exist, the +# ExternalSecrets sit in SecretSyncedError and the warm pods hang in +# ContainerCreating on `secret "dark-factory-github-coder" not found`. +# +# ─── STEP 1: mint three fine-grained PATs (manual β€” no API exists for this) ──── +# +# GitHub ships no PAT-creation endpoint (POST /user/personal-access-tokens, +# /user/tokens, /authorizations all 404) and `gh auth` has no create subcommand, +# so this step cannot be scripted. A token able to mint tokens would be a +# privilege-escalation path. +# +# https://github.com/settings/personal-access-tokens/new +# +# For EACH token: Resource owner = your org/user, Repository access = +# "Only select repositories" β†’ the target repo. Metadata:read is added +# automatically. Do NOT grant Administration. +# +# df-events Contents read +# Webhooks write (Argo Events self-registers the +# repo webhook; a read-only token +# fails registration) +# +# df-orchestrator Contents write (PUT /pulls/{n}/merge is gated on +# Contents for fine-grained PATs, +# + DELETE of the merged branch ref) +# Pull requests write (the merge itself) +# Commit statuses write (holdout/security/devops verdicts) +# Issues write (sticky status comment β€” PR +# comments are issue comments) +# +# df-coder Contents write (push df/issue-) +# Pull requests write (the coder opens its OWN PR from +# inside the Kata VM) +# Commit statuses write (self-reports +# dark-factory/implementation, the +# signal df-run polls for) +# Issues read (GET /issues/{n} β†’ SPEC.md) +# +# These are the MINIMUM sets the code exercises, verified against +# examples/dark-factory/coder/entrypoint.js and the chart's review/*.js. +# Under-scoping fails MID-RUN β€” after a sandbox is claimed and model tokens are +# spent. See docs/dark-factory/README.md Β§10a. +# +# ⚠️ The coder token holds Contents:write, and fine-grained PATs gate the merge +# endpoint on Contents β€” so it CAN merge its own PR. Branch protection (or a +# ruleset) requiring the dark-factory/* checks on the default branch is the only +# real prevention, and it is UNAVAILABLE on private repos on the GitHub free plan +# (both APIs return 403). Verify protection is in force before treating the +# coder/orchestrator split as a boundary. +# +# ─── STEP 2: run this script ────────────────────────────────────────────────── +# +# export EVENTS_TOKEN='github_pat_...' +# export ORCHESTRATOR_TOKEN='github_pat_...' +# export CODER_TOKEN='github_pat_...' +# bash examples/dark-factory/setup-secrets.sh +# +# Tokens go via env vars, never argv, so they stay out of the process list. Add a +# leading space to each export if your shell honours HISTCONTROL=ignorespace β€” +# otherwise they land in .bash_history. +# +# Re-running is safe: an existing secret is updated in place with +# put-secret-value. external-secrets picks the change up within refreshInterval +# (1h default); the commands printed at the end force it immediately. + +set -euo pipefail + +REGION="${REGION:-us-west-2}" + +# Both Argo AND the warm pool live on cluster `hub`: df-run's claim step creates a +# SandboxClaim with no cross-cluster mechanism, so the pool must sit beside Argo +# (agent_sandbox in overlays/environments/control-plane/enabled-addons.yaml β€” now false by +# default, explicit opt-in; see docs/dark-factory/FLOW-D-ENABLEMENT.md Β§E1). +# +# These are `aws_cluster_name` ANNOTATION values β€” the registry interpolates them +# into the SM key as /dark-factory/github/*. They are not kubectl context +# names. Change SANDBOX only if the warm pool moves back to a spoke. +HUB="${HUB:-hub}" +SANDBOX="${SANDBOX:-hub}" + +for v in EVENTS_TOKEN ORCHESTRATOR_TOKEN CODER_TOKEN; do + if [ -z "${!v:-}" ]; then + echo "ERROR: \$$v is not set. Export all three tokens (see the header)." >&2 + exit 1 + fi +done + +for c in jq aws openssl; do + command -v "$c" >/dev/null || { echo "ERROR: $c is required." >&2; exit 1; } +done + +# Shared HMAC validating GitHub's X-Hub-Signature-256. NOT a GitHub credential β€” +# any strong random value. Argo Events registers the webhook with this value +# itself (trigger.argoEvents.active=true), so it is never set by hand in GitHub. +# Pass WEBHOOK_SECRET to reuse an existing one instead of rotating it. +WEBHOOK_SECRET="${WEBHOOK_SECRET:-$(openssl rand -hex 20)}" + +put() { + local name="$1" payload="$2" + if aws secretsmanager describe-secret --region "$REGION" --secret-id "$name" >/dev/null 2>&1; then + aws secretsmanager put-secret-value --region "$REGION" \ + --secret-id "$name" --secret-string "$payload" >/dev/null + echo " updated $name" + else + aws secretsmanager create-secret --region "$REGION" \ + --name "$name" --secret-string "$payload" >/dev/null + echo " created $name" + fi +} + +echo "Writing Dark Factory credentials to Secrets Manager ($REGION):" + +# The secretKey/property names below are fixed by the consuming ExternalSecrets +# (agent-sandbox + dark-factory charts, templates/05-externalsecret-github.yaml). +# Renaming a property here makes the sync fail with "could not get secret data +# from provider" and nothing else. + +# events β†’ hub/argo-events, keys: token + webhook-secret +put "${HUB}/dark-factory/github/events" \ + "$(jq -n --arg t "$EVENTS_TOKEN" --arg w "$WEBHOOK_SECRET" \ + '{token:$t, "webhook-secret":$w}')" + +# orchestrator β†’ hub/argo, key: token +put "${HUB}/dark-factory/github/orchestrator" \ + "$(jq -n --arg t "$ORCHESTRATOR_TOKEN" '{token:$t}')" + +# coder β†’ warm-pool cluster/agent-sandbox-system, SM property `token` +# projected to the k8s key `gh-token` (the filename the coder reads at +# /etc/secrets/gh-token). +put "${SANDBOX}/dark-factory/github/coder" \ + "$(jq -n --arg t "$CODER_TOKEN" '{token:$t}')" + +cat <<'EOF' + +Done. Force an immediate ESO refresh rather than waiting out refreshInterval: + + kubectl --context hub -n argo annotate externalsecret dark-factory-github-orchestrator force-sync=$(date +%s) --overwrite + kubectl --context hub -n argo-events annotate externalsecret dark-factory-github-events force-sync=$(date +%s) --overwrite + kubectl --context hub -n agent-sandbox-system annotate externalsecret dark-factory-github-coder force-sync=$(date +%s) --overwrite + +Then confirm all three report SecretSynced / READY=True: + + kubectl --context hub get externalsecret -A | grep dark-factory-github + +The warm pods are blocked on the coder secret, so once it syncs they should leave +ContainerCreating. Watch them reach Running β€” that is also the first real test of +whether the coder image pulls on-cluster: + + kubectl --context hub -n agent-sandbox-system get pods -w +EOF diff --git a/gitops/DEPLOYMENT.md b/gitops/DEPLOYMENT.md index 9f58c61f..bb14e0b6 100644 --- a/gitops/DEPLOYMENT.md +++ b/gitops/DEPLOYMENT.md @@ -93,7 +93,9 @@ aws eks create-pod-identity-association \ ### Deploy -The `application-sets` chart generates one ApplicationSet per addon. Deploy it as an ArgoCD Application: +The `appset-chart` generator (sourced from the platform repo, appmod-blueprints β€” **not** vendored here) generates one ApplicationSet per addon from this repo's `gitops/addons/registry/` files. Deploy it as a multi-source ArgoCD Application. + +> **The canonical, maintained copy is [`gitops/bootstrap/agent-platform-app.yaml`](bootstrap/agent-platform-app.yaml)** β€” a template whose repo coordinates are injected with `envsubst` by `task agentic:bootstrap` (or `scripts/bootstrap.sh`). Prefer that file over the illustrative YAML below: **add new `registry/*.yaml` entries there**, since a hand-maintained duplicate drifts (this block was missing `registry/sandbox.yaml` until it was spotted, which silently omitted every agent-sandbox addon). ```yaml apiVersion: argoproj.io/v1alpha1 @@ -107,23 +109,25 @@ spec: - ref: values repoURL: https://github.com/aws-samples/sample-open-agentic-platform.git targetRevision: main - - repoURL: https://github.com/aws-samples/sample-open-agentic-platform.git - path: gitops/addons/charts/application-sets - targetRevision: main + # Source B β€” the generator chart, pulled from the PLATFORM repo (reference model). + - repoURL: https://github.com/aws-samples/appmod-blueprints.git + path: platform-charts/appset-chart + targetRevision: feature/agent-platform helm: releaseName: agent-platform-addons ignoreMissingValueFiles: true + # Registry files (from THIS repo via $values) supply _defaults + addon entries. valueFiles: - - $values/gitops/addons/bootstrap/default/addons.yaml - - $values/gitops/addons/environments/control-plane/addons.yaml + - $values/gitops/addons/registry/_defaults.yaml + - $values/gitops/addons/registry/gateway.yaml + - $values/gitops/addons/registry/observability.yaml + - $values/gitops/addons/registry/agentcore.yaml + - $values/gitops/addons/registry/sandbox.yaml valuesObject: - useSelectors: false + # Umbrella gate only; per-addon enable_ selectors (useSelectors:true + # from _defaults) govern placement. Do NOT re-set repoURLGitBasePath here. globalSelectors: enable_agent_platform: "true" - litellm: - valuesObject: - global: - awsRegion: "" destination: namespace: argocd name: "" @@ -212,7 +216,7 @@ curl -N http://agentgateway-proxy.agentgateway-system.svc.cluster.local:8080/sse | LiteLLM "Unable to locate credentials" | Missing Pod Identity | Create Pod Identity association, restart LiteLLM | | LiteLLM "model version has reached end of life" | Outdated model IDs | Update to `us.anthropic.*` inference profiles in litellm chart | | ApplicationSet "map has no entry" | Cluster secret missing annotations | Use `useSelectors: false` with `globalSelectors` | -| langfuse ApplicationSet "map has no entry for key \"hub_cluster_name\"" (blocks all agentic addons β€” parent `agent-platform-addons` app retries forever and never applies later sync-wave ApplicationSets) | langfuse `secretManagerKey` referenced a non-existent `hub_cluster_name` annotation | Template must use `aws_cluster_name` (the per-cluster annotation set by the fleet-secret chart), not `hub_cluster_name`. Fixed in `gitops/addons/bootstrap/default/addons.yaml`. The `/keycloak-clients` Secrets Manager secret must also exist. | +| langfuse ApplicationSet "map has no entry for key \"hub_cluster_name\"" (blocks all agentic addons β€” parent `agent-platform-addons` app retries forever and never applies later sync-wave ApplicationSets) | langfuse `secretManagerKey` referenced a non-existent `hub_cluster_name` annotation | Template must use `aws_cluster_name` (the per-cluster annotation set by the fleet-secret chart), not `hub_cluster_name`. Fixed in the langfuse entry of `gitops/addons/registry/observability.yaml`. The `/keycloak-clients` Secrets Manager secret must also exist. | | Gateway API CRDs Job fails | No outbound internet or image pull issue | Verify NAT gateway, check `bitnami/kubectl:latest` availability | | AgentGateway proxy not starting | Missing Gateway API CRDs or JWKS fetch failure | Verify CRDs installed, check KeyCloak reachability | | JWT validation fails | Wrong issuer URL | Ensure issuer matches `iss` claim (`https:///keycloak/realms/platform`) | diff --git a/gitops/addons/bootstrap/default/addons.yaml b/gitops/addons/bootstrap/default/addons.yaml deleted file mode 100644 index 1521ece1..00000000 --- a/gitops/addons/bootstrap/default/addons.yaml +++ /dev/null @@ -1,361 +0,0 @@ -syncPolicy: - automated: - selfHeal: true - allowEmpty: true - prune: true - retry: - limit: -1 - backoff: - duration: 5s - factor: 2 - maxDuration: 10m - syncOptions: - - CreateNamespace=true - - ServerSideApply=true -syncPolicyAppSet: - preserveResourcesOnDeletion: false -useSelectors: true -repoURLGit: '{{default "https://github.com/aws-samples/sample-open-agentic-platform.git" .metadata.annotations.addons_repo_url}}' -repoURLGitRevision: '{{default "main" .metadata.annotations.addons_repo_revision}}' -repoURLGitBasePath: '{{default "gitops/addons/" .metadata.annotations.addons_repo_basepath}}' - -valueFiles: - - default/addons - - environments/{{.metadata.labels.environment}}/addons - - clusters/{{.nameNormalized}}/addons -useValuesFilePrefix: true -valuesFilePrefix: 'tenants/{{.metadata.labels.tenant}}/' - -######################################## -# Observability β€” LLM Gateway -######################################## - -litellm: - enabled: false - namespace: litellm - defaultVersion: '0.1.0' - path: 'gitops/addons/charts/litellm' - annotationsAppSet: - argocd.argoproj.io/sync-wave: '3' - selector: - matchExpressions: - - key: enable_litellm - operator: In - values: ['true'] - valuesObject: - global: - awsRegion: '{{.metadata.annotations.aws_region}}' - -######################################## -# Observability β€” Tracing & Metrics -######################################## - -langfuse: - enabled: true - namespace: langfuse - defaultVersion: '0.1.0' - path: 'gitops/addons/charts/langfuse' - annotationsAppSet: - argocd.argoproj.io/sync-wave: '5' - selectorMatchLabels: - enable_langfuse: "true" - selector: - matchExpressions: - - key: enable_langfuse - operator: In - values: ['true'] - # Langfuse is a hub singleton (one Keycloak client, hub-domain redirect, shared - # platform realm). It must run ONLY on the control-plane/hub. alwaysSelector is - # honored regardless of the global useSelectors flag, so the generated - # ApplicationSet matches only clusters whose `environment` label is control-plane - # (the hub) β€” spoke langfuse apps are pruned. Spoke agents send traces to the hub - # Langfuse via the OTel pipeline. - alwaysSelector: - matchExpressions: - - key: environment - operator: In - values: ['control-plane'] - valuesObject: - global: - awsRegion: '{{.metadata.annotations.aws_region}}' - clusterName: '{{.metadata.annotations.aws_cluster_name}}' - ingress: - enabled: true - host: '{{.metadata.annotations.ingress_domain_name}}' - keycloak: - issuerUrl: '{{default (printf "https://%s/keycloak/realms/platform" .metadata.annotations.ingress_domain_name) (index .metadata.annotations "keycloak_issuer_url")}}' - secretManagerKey: '{{.metadata.annotations.aws_cluster_name}}/keycloak-clients' - ignoreDifferences: - - group: external-secrets.io - kind: ExternalSecret - jsonPointers: - - /spec/dataFrom - -jaeger: - enabled: false - namespace: jaeger - chartName: jaeger - defaultVersion: '3.4.1' - chartRepository: 'https://jaegertracing.github.io/helm-charts' - annotationsAppSet: - argocd.argoproj.io/sync-wave: '5' - selector: - matchExpressions: - - key: enable_jaeger - operator: In - values: ['true'] - valuesObject: - provisionDataStore: - cassandra: false - allInOne: - enabled: true - resources: - requests: - cpu: 100m - memory: 256Mi - limits: - memory: 512Mi - storage: - type: memory - agent: - enabled: false - collector: - enabled: false - query: - enabled: false - -otel-collector: - enabled: true - namespace: otel - defaultVersion: '0.1.0' - path: 'gitops/addons/charts/otel-collector' - annotationsAppSet: - argocd.argoproj.io/sync-wave: '6' - selector: - matchExpressions: - - key: enable_otel_collector - operator: In - values: ['true'] - valuesObject: - global: - awsRegion: '{{.metadata.annotations.aws_region}}' - langfuse: - enabled: true - endpoint: 'https://{{.metadata.annotations.ingress_domain_name}}/api/public/otel' - secretManagerKey: 'hub/langfuse-otel' - bifrost: - enabled: true - scrapeEndpoint: 'bifrost.bifrost.svc.cluster.local:8080' - amp: - enabled: true - remoteWriteEndpoint: '{{with (index .metadata.annotations "amp_endpoint_url")}}{{.}}{{end}}' - clusterName: '{{.metadata.annotations.aws_cluster_name}}' - -prometheus-operator-crds: - enabled: true - namespace: monitoring - chartName: prometheus-operator-crds - defaultVersion: '28.0.1' - chartRepository: 'https://prometheus-community.github.io/helm-charts' - annotationsAppSet: - argocd.argoproj.io/sync-wave: '5' - selector: - matchExpressions: - - key: enable_monitoring - operator: In - values: ['true'] - ignoreDifferences: - - group: apiextensions.k8s.io - kind: CustomResourceDefinition - jsonPointers: - - /metadata/annotations - -######################################## -# AgentCore β€” Crossplane Compositions -######################################## - -crossplane-agentcore: - enabled: true - namespace: crossplane-system - defaultVersion: '0.1.0' - path: 'gitops/addons/charts/crossplane-agentcore' - annotationsAppSet: - argocd.argoproj.io/sync-wave: '4' - selector: - matchExpressions: - - key: enable_agent_core - operator: In - values: ['true'] - valuesObject: - global: - awsRegion: '{{.metadata.annotations.aws_region}}' - eksClusterName: '{{.metadata.annotations.aws_cluster_name}}' - projectName: '{{default "agent-core" (index .metadata.annotations "agent_core_project_name")}}' - networkMode: '{{default "PUBLIC" (index .metadata.annotations "agent_core_network_mode")}}' - ignoreDifferences: - - group: apiextensions.crossplane.io - kind: Composition - jsonPointers: - - /spec/mode - - /spec/publishConnectionDetailsWithStoreConfigRef - jqPathExpressions: - - .spec.resources[].readinessChecks - - .spec.resources[].patches[].type - - group: apiextensions.crossplane.io - kind: CompositeResourceDefinition - jsonPointers: - - /metadata/annotations - -######################################## -# Bifrost β€” AI Gateway -######################################## - -bifrost: - enabled: true - namespace: bifrost - defaultVersion: '0.1.0' - # Local umbrella chart: wraps upstream maximhq/bifrost and adds the Crossplane - # Pod Identity (Bedrock access) + ExternalSecret in the same Application. - path: 'gitops/addons/charts/bifrost' - annotationsAppSet: - argocd.argoproj.io/sync-wave: '5' - selector: - matchExpressions: - - key: enable_bifrost - operator: In - values: ['true'] - valuesObject: - podIdentity: - clusterName: '{{.metadata.annotations.aws_cluster_name}}' - region: '{{.metadata.annotations.aws_region}}' - ignoreDifferences: - - group: apps - kind: StatefulSet - jsonPointers: - - /spec/volumeClaimTemplates - -######################################## -# AgentGateway β€” MCP Auth Gateway -######################################## - -gateway-api-crds: - enabled: true - namespace: agentgateway-system - defaultVersion: '0.1.0' - path: 'gitops/addons/charts/gateway-api-crds' - annotationsAppSet: - argocd.argoproj.io/sync-wave: '7' - selector: - matchExpressions: - - key: enable_agent_gateway - operator: In - values: ['true'] - valuesObject: - gatewayApiVersion: "1.5.0" - -agentgateway-crds: - enabled: true - namespace: agentgateway-system - chartName: agentgateway-crds - defaultVersion: 'v1.1.0' - chartRepository: 'cr.agentgateway.dev' - chartNamespace: charts - annotationsAppSet: - argocd.argoproj.io/sync-wave: '7' - selector: - matchExpressions: - - key: enable_agent_gateway - operator: In - values: ['true'] - ignoreDifferences: - - group: apiextensions.k8s.io - kind: CustomResourceDefinition - jsonPointers: - - /metadata/annotations - -agentgateway: - enabled: true - namespace: agentgateway-system - chartName: agentgateway - defaultVersion: 'v1.1.0' - chartRepository: 'cr.agentgateway.dev' - chartNamespace: charts - annotationsAppSet: - argocd.argoproj.io/sync-wave: '8' - selector: - matchExpressions: - - key: enable_agent_gateway - operator: In - values: ['true'] - valuesObject: - controller: - extraEnv: - KGW_ENABLE_GATEWAY_API_EXPERIMENTAL_FEATURES: "true" - -agent-gateway: - enabled: true - namespace: agent-core-infra - defaultVersion: '0.1.0' - path: 'gitops/addons/charts/agent-gateway' - annotationsAppSet: - argocd.argoproj.io/sync-wave: '9' - selector: - matchExpressions: - - key: enable_agent_gateway - operator: In - values: ['true'] - valuesObject: - global: - keycloak: - issuerUrl: '{{default (printf "https://%s/keycloak/realms/platform" (index .metadata.annotations "ingress_domain_name")) (index .metadata.annotations "keycloak_issuer_url")}}' - serviceName: '{{default "keycloak" (index .metadata.annotations "keycloak_service_name")}}' - namespace: '{{default "keycloak" (index .metadata.annotations "keycloak_namespace")}}' - ingress: - enabled: true - host: '{{index .metadata.annotations "ingress_domain_name"}}' - # Agents get a dedicated hostname that is a SIBLING of the main ingress host - # (same parent domain), so a single wildcard ACM cert covers both. For host - # "idp.elamaras.people.aws.dev" the agents host is "agents.elamaras.people.aws.dev" - # β€” both matched by "*.elamaras.people.aws.dev". (The old "agents." - # default produced "agents.idp.elamaras..." which a single-level wildcard does - # NOT cover, so the ALB never provisioned: "no certificate found for host".) - # If the domain has no parent (bare apex), fall back to "agents.". - # The agent_gateway_host annotation, when set, overrides this entirely. - agentHost: '{{ $d := index .metadata.annotations "ingress_domain_name" }}{{ $parent := splitList "." $d | rest | join "." }}{{ $auto := ternary (printf "agents.%s" $parent) (printf "agents.%s" $d) (gt (len (splitList "." $d)) 2) }}{{ default $auto (index .metadata.annotations "agent_gateway_host") }}' - mcpAuth: - resourceMetadata: - resource: '{{default "http://agentgateway-proxy.agentgateway-system.svc.cluster.local:8080/sse" (index .metadata.annotations "agent_gateway_resource_url")}}' - # Secretless workload identity (Shape A): trust the cluster's own EKS OIDC - # issuer as a second JWT provider so agent ServiceAccount tokens authenticate - # without a secret. Sourced from the eks_oidc_provider cluster-secret - # annotation (set automatically by the platform cluster composition for - # spokes; set manually on the hub β€” see gitops/DEPLOYMENT.md). When the - # annotation is absent the value is empty and the workload provider is - # omitted (humans-only via Keycloak). - workloadIdentity: - issuer: '{{ index .metadata.annotations "eks_oidc_provider" }}' - jwksHost: '{{ with (index .metadata.annotations "eks_oidc_provider") }}oidc.eks.{{ index $.metadata.annotations "aws_region" }}.amazonaws.com{{ end }}' - jwksPath: '{{ with (index .metadata.annotations "eks_oidc_provider") }}{{ printf "%s/keys" (trimPrefix (printf "https://oidc.eks.%s.amazonaws.com" (index $.metadata.annotations "aws_region")) .) }}{{ end }}' - -# OAM Agent Components β€” KubeVela ComponentDefinitions for agentic workloads -# (agent, mcp-server, agentcore-memory). Requires KubeVela to be running -# (provisioned by the platform via appmod-blueprints' kubevela addon at -# sync-wave 3). Sync-wave 5 here ensures KubeVela CRDs and webhooks are -# established before these CRs are applied. -oam-agent-components: - enabled: true - namespace: vela-system - defaultVersion: '0.1.0' - path: 'gitops/addons/charts/oam-agent-components' - annotationsAppSet: - argocd.argoproj.io/sync-wave: '5' # after kubevela (wave 3 on platform side) - selector: - matchExpressions: - - key: enable_oam_components - operator: In - values: ['true'] - valuesObject: - global: - awsRegion: '{{.metadata.annotations.aws_region}}' - clusterName: '{{.metadata.annotations.aws_cluster_name}}' - awsAccountId: '{{.metadata.annotations.aws_account_id}}' diff --git a/gitops/addons/charts/agent-gateway/values.yaml b/gitops/addons/charts/agent-gateway/values.yaml index 78e0561c..79e09512 100644 --- a/gitops/addons/charts/agent-gateway/values.yaml +++ b/gitops/addons/charts/agent-gateway/values.yaml @@ -64,7 +64,7 @@ jwtAuth: # Activation is derived from `issuer`: when empty (the default) the workload # provider and its backend are omitted entirely (humans-only). Per-cluster # values are wired from the agent-gateway addon valuesObject in -# gitops/addons/bootstrap/default/addons.yaml. +# gitops/addons/registry/gateway.yaml. workloadIdentity: # Cluster OIDC issuer, e.g. https://oidc.eks.us-west-2.amazonaws.com/id/. # Empty = workload identity disabled. diff --git a/gitops/addons/charts/agent-sandbox-lambda/Chart.yaml b/gitops/addons/charts/agent-sandbox-lambda/Chart.yaml new file mode 100644 index 00000000..d5da419c --- /dev/null +++ b/gitops/addons/charts/agent-sandbox-lambda/Chart.yaml @@ -0,0 +1,14 @@ +apiVersion: v2 +name: agent-sandbox-lambda +description: >- + Flow D β€” Lambda MicroVM substrate for the Agent Sandbox capability. A second, + opt-in execution substrate alongside the Kata micro-VM chart (agent-sandbox): + the coder runs in an AWS Lambda MicroVM (Firecracker) instead of a Kata node. + KRO builds the platform image ONCE (MicrovmImage + build/exec IAM + S3 artifact + + CloudWatch logs, via ACK GA controllers); a lightweight shim (bridge pod + + lifecycle controller) drives the per-session VM (RunMicrovm / suspend / resume / + TerminateMicrovm) imperatively via the AWS SDK. Same dark-factory-coder (arm64) + image + same Agent Sandbox UX as Flow A. Disabled by default (microvm.enabled). +type: application +version: 0.1.0 +appVersion: "0.1.0" diff --git a/gitops/addons/charts/agent-sandbox-lambda/templates/_helpers.tpl b/gitops/addons/charts/agent-sandbox-lambda/templates/_helpers.tpl new file mode 100644 index 00000000..dc54887e --- /dev/null +++ b/gitops/addons/charts/agent-sandbox-lambda/templates/_helpers.tpl @@ -0,0 +1,28 @@ +{{/* +Common labels applied to every resource this chart renders. Kept under the +app.kubernetes.io/name "agent-sandbox" (same capability, Lambda substrate) so +Flow D resources associate with the Agent Sandbox capability; the chart name +distinguishes them. +*/}} +{{- define "agent-sandbox.labels" -}} +app.kubernetes.io/name: agent-sandbox +app.kubernetes.io/component: lambda-microvm +app.kubernetes.io/part-of: open-agent-platform +app.kubernetes.io/managed-by: {{ .Release.Service }} +helm.sh/chart: {{ printf "%s-%s" .Chart.Name .Chart.Version }} +{{- end -}} + +{{/* +Selector labels (stable subset used by controllers). +*/}} +{{- define "agent-sandbox.selectorLabels" -}} +app.kubernetes.io/name: agent-sandbox +app.kubernetes.io/component: lambda-microvm +{{- end -}} + +{{/* +The namespace the capability runs in (must match the Kata agent-sandbox chart). +*/}} +{{- define "agent-sandbox.namespace" -}} +{{- default "agent-sandbox-system" .Values.namespace -}} +{{- end -}} diff --git a/gitops/addons/charts/agent-sandbox-lambda/templates/image/10-rgd-and-image.yaml b/gitops/addons/charts/agent-sandbox-lambda/templates/image/10-rgd-and-image.yaml new file mode 100644 index 00000000..b4127496 --- /dev/null +++ b/gitops/addons/charts/agent-sandbox-lambda/templates/image/10-rgd-and-image.yaml @@ -0,0 +1,251 @@ +{{- if and .Values.microvm .Values.microvm.enabled }} +{{- /* +Flow D β€” MicrovmSandbox ResourceGraphDefinition (KRO). + +ONE composite CRD (kro.run) that ties together ALL the Lambda MicroVM primitives so +a consumer (the lambda-microvm SandboxTemplate bridge, or any agent) creates a single +`MicrovmSandbox` and gets the whole substrate: + + MicrovmSandbox -> S3 Bucket (s3.services.k8s.aws) image codeArtifact store + IAM Role (build) (iam.services.k8s.aws) MicrovmImage.buildRoleARN + IAM Role (exec) (iam.services.k8s.aws) Microvm.executionRoleARN + MicrovmImage (lambdamicrovms.services.k8s.aws) platform-owned image + Microvm (lambdamicrovms.services.k8s.aws) app-owned instance + +OWNERSHIP SPLIT is expressed IN THE SCHEMA: + spec.image.* β€” PLATFORM-owned (base image, code artifact) β€” set once per image + spec.run.* β€” APP-owned (per-claim instance: idle policy) + +NETWORK: Lambda MicroVMs have PUBLIC internet egress by DEFAULT, so no network +connectors are attached here (the coder only needs outbound git/gh/registry, like +Flow A). Ingress connectors (AWS-managed, inbound HTTPS) and VPC egress connectors +(created out-of-band via `aws lambda-core create-network-connector`) are optional +add-ons a future variant can wire in; they are intentionally omitted from v1. + +CONTROLLER SPLIT: the s3/iam resources are reconciled by MANAGED ACK (GA controllers); +MicrovmImage/Microvm by the SELF-MANAGED lambdamicrovms controller (pre-GA). The RGD is +identical regardless of who runs the controllers β€” when lambdamicrovms goes GA and +Managed ACK adopts it, THIS FILE DOES NOT CHANGE. + +Gated behind microvm.enabled so Flow D stays dormant until a cluster opts in. Requires +the Managed KRO capability (kro.run) + the ACK controllers to be present on the cluster. +*/ -}} +apiVersion: kro.run/v1alpha1 +kind: ResourceGraphDefinition +metadata: + name: microvm-sandbox + labels: + {{- include "agent-sandbox.labels" . | nindent 4 }} + annotations: + # Apply the RGD FIRST so KRO generates the MicrovmSandbox CRD before the instance + # (below) is synced β€” otherwise the instance fails dry-run ("CRD not found") and + # blocks the whole app. + argocd.argoproj.io/sync-wave: "-1" +spec: + schema: + apiVersion: v1alpha1 + kind: MicrovmSandbox + group: {{ .Values.microvm.apiGroup | default "kro.run" | quote }} + # SCOPE: this RGD builds ONLY the platform IMAGE + the slow-changing infra it + # needs (S3 artifact bucket, build role, execution role). It deliberately does + # NOT create a `Microvm` β€” a MicroVM instance is a per-SESSION, request-time + # resource whose create/suspend/resume/terminate are IMPERATIVE SDK ops that the + # ACK controller does not reconcile (confirmed in the lambdamicrovms-controller + # reference: run/suspend/resume/terminate are SDK calls, not desired state, and + # the 06-kro example likewise leaves the running Microvm out of the graph). The + # shim (SandboxTemplate bridge + microvm-lifecycle controller, templates 51/52) + # owns that lifecycle. So a `MicrovmSandbox` = "an image is built + ready + its + # exec identity", and its status is the HANDOFF the shim consumes to RunMicrovm. + spec: + # ── PLATFORM-owned: the image / substrate (set once per image) ────────── + # ARN of the AWS-published base MicroVM image to build from, e.g. + # arn:aws:lambda::aws:microvm-image:al2023-1 (ARM_64 β€” the only arch + # Lambda MicroVM supports). + baseImageARN: string + # OBJECT KEY (not a full URI) of the coder code artifact zip (app + Dockerfile). + # The bucket half is NOT passed in β€” resource 4 composes the URI as + # s3://${bucket.spec.name}/${schema.spec.codeArtifactKey} + # from the bucket THIS graph creates, so the artifact location and the bucket can + # never name different things. Taking a full s3:// URI here (the previous shape) + # meant the caller had to restate `-microvm-artifacts-` by hand, + # and it drifted twice β€” see the accountId comment below. + # Lambda MicroVM's codeArtifact.uri is S3-ONLY β€” it is NOT an ECR image reference + # (the Dockerfile inside the zip MAY pull private ECR base layers, which is why + # buildRole keeps ecr:Get*/BatchGetImage). The artifact must be published to the + # bucket before the first build runs. + codeArtifactKey: string + # AWS region + a name stem for the created resources. + region: string | default="{{ .Values.microvm.region }}" + name: string + # AWS account id of the DEPLOYING cluster β€” suffixed onto the artifact bucket + # name to make it globally unique. S3 bucket names share ONE global namespace + # across all AWS accounts, so the un-suffixed `-microvm-artifacts` is + # first-come-first-served: on this account CreateBucket returned + # 409 BucketAlreadyExists: the bucket namespace is shared by all users + # because another account already held `coder-microvm-artifacts`. The Bucket CR + # then never got an ARN, the build role's S3 grant pointed at a bucket this + # account cannot read, and the MicrovmImage build failed with + # "Access denied when fetching artifact from S3" β†’ CREATE_FAILED. + # Injected from the cluster's aws_account_id annotation (see the registry + # entry), the same portability pattern as the pod-identity role ARN. + accountId: string + status: + # The HANDOFF the shim reads to RunMicrovm (imperatively) per session: + # imageARN β†’ Microvm.imageIdentifier + # executionRoleARN β†’ Microvm.executionRoleARN + # Plus imageState so the shim only launches once the build is CREATED/UPDATED. + imageARN: ${image.status.ackResourceMetadata.arn} + imageState: ${image.status.state} + imageVersion: ${image.status.latestActiveImageVersion} + executionRoleARN: ${execRole.status.ackResourceMetadata.arn} + resources: + # 1) S3 bucket that stores the MicroVM code artifact (GA β€” Managed ACK). + - id: bucket + template: + apiVersion: s3.services.k8s.aws/v1alpha1 + kind: Bucket + metadata: + # metadata.name stays account-free: the CR name only has to be unique in the + # namespace, and keeping it stable avoids orphaning the existing Bucket CR. + name: ${schema.spec.name}-microvm-artifacts + spec: + # spec.name IS the real S3 bucket name β†’ must be GLOBALLY unique, hence the + # account suffix (see schema.spec.accountId above). buildRole's inline S3 + # policy references ${bucket.spec.name}, so it follows this automatically. + name: ${schema.spec.name}-microvm-artifacts-${schema.spec.accountId} + # 2) IAM role the image BUILD assumes (GA β€” Managed ACK). Platform-owned. + # Lambda assumes this during create-microvm-image to pull the code artifact + # (ECR/S3) + write build logs. Trust = lambda.amazonaws.com (verified in the + # Lambda MicroVM getting-started docs), with inline ECR-read + S3-read + logs. + - id: buildRole + template: + apiVersion: iam.services.k8s.aws/v1alpha1 + kind: Role + metadata: + name: ${schema.spec.name}-microvm-build + spec: + name: ${schema.spec.name}-microvm-build + assumeRolePolicyDocument: | + {"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Service":"lambda.amazonaws.com"},"Action":["sts:AssumeRole","sts:TagSession"]}]} + inlinePolicies: + microvm-build: | + {"Version":"2012-10-17","Statement":[ + {"Effect":"Allow","Action":["ecr:GetAuthorizationToken","ecr:BatchGetImage","ecr:GetDownloadUrlForLayer"],"Resource":"*"}, + {"Effect":"Allow","Action":["s3:GetObject","s3:ListBucket"],"Resource":["arn:aws:s3:::${bucket.spec.name}","arn:aws:s3:::${bucket.spec.name}/*"]}, + {"Effect":"Allow","Action":["logs:CreateLogGroup","logs:CreateLogStream","logs:PutLogEvents"],"Resource":"arn:aws:logs:*:*:log-group:/aws/lambda/microvms/*"} + ]} + # 3) IAM role the RUNNING MicroVM assumes (GA β€” Managed ACK). App-owned exec identity. + # trust = lambda.amazonaws.com. Grants bedrock:InvokeModel so the coder calls + # Bedrock DIRECTLY (Flow D is Bedrock-direct: a MicroVM runs outside the cluster + # and can't reach Bifrost's ClusterIP, so it uses this role's creds over public + # egress instead β€” see flow-d-coder-in-microvm-design.md). git/gh over public :443. + - id: execRole + template: + apiVersion: iam.services.k8s.aws/v1alpha1 + kind: Role + metadata: + name: ${schema.spec.name}-microvm-exec + spec: + name: ${schema.spec.name}-microvm-exec + assumeRolePolicyDocument: | + {"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Service":"lambda.amazonaws.com"},"Action":["sts:AssumeRole","sts:TagSession"]}]} + inlinePolicies: + bedrock-invoke: | + {"Version":"2012-10-17","Statement":[ + {"Effect":"Allow","Action":["bedrock:InvokeModel","bedrock:InvokeModelWithResponseStream","bedrock:Converse","bedrock:ConverseStream"],"Resource":["arn:aws:bedrock:*::foundation-model/*","arn:aws:bedrock:*:*:inference-profile/*"]} + ]} + # 4) MicrovmImage (pre-GA β€” SELF-MANAGED lambdamicrovms controller). Platform-owned. + # readyWhen gates the Microvm (resource 5) on a genuinely SUCCESSFUL build β€” + # state CREATED/UPDATED β€” so the instance never launches from a half-built or + # failed image (KRO holds the Microvm until this predicate is true). + - id: image + readyWhen: + - ${image.status.state == "CREATED" || image.status.state == "UPDATED"} + template: + apiVersion: lambdamicrovms.services.k8s.aws/v1alpha1 + kind: MicrovmImage + metadata: + name: ${schema.spec.name}-image + spec: + name: ${schema.spec.name}-image + baseImageARN: ${schema.spec.baseImageARN} + buildRoleARN: ${buildRole.status.ackResourceMetadata.arn} + codeArtifact: + # Composed from the BUCKET THIS GRAPH CREATES (resource 1) + the object key β€” + # never from a caller-supplied full URI. ${bucket.spec.name} already carries the + # `-${accountId}` suffix, so the build always reads from the exact bucket that + # was provisioned and that buildRole's S3 grant (also ${bucket.spec.name}) allows. + # Restating the bucket name in a second place is what drifted twice before. + uri: s3://${bucket.spec.name}/${schema.spec.codeArtifactKey} + # Lambda MicroVM is ARM_64-ONLY β€” the sole supported architecture. The + # code artifact + any bundled binaries must be arm64 (dark-factory-coder + # is built for arm64 for exactly this substrate). + cpuConfigurations: + - architecture: ARM_64 + # Lifecycle hooks β€” the lambda-coder (hook-server.js) serves these on :8080. + # ready : build waits for the server to be up before snapshotting a + # clean, waiting coder (else the snapshot is taken too early). + # run : per-session start; delivers runHookPayload (issue context + + # GitHub token) as the request body β†’ hook-server background- + # spawns the coder. WITHOUT run:ENABLED the payload is silently + # never delivered. + # suspend/resume/terminate : lifecycle acks (coder holds no external state). + hooks: + port: 8080 + microvmImageHooks: + ready: ENABLED + readyTimeoutInSeconds: 120 + microvmHooks: + run: ENABLED + runTimeoutInSeconds: 30 + suspend: ENABLED + suspendTimeoutInSeconds: 30 + resume: ENABLED + resumeTimeoutInSeconds: 30 + terminate: ENABLED + terminateTimeoutInSeconds: 30 + # CloudWatch build/runtime logs. On a CREATE_FAILED the controller can't + # see the build output β€” this is where it lands: + # aws logs tail /aws/lambda/microvms/${schema.spec.name}-image + logging: + cloudWatch: + logGroup: /aws/lambda/microvms/${schema.spec.name}-image + # NOTE: there is deliberately NO `Microvm` resource here. The running MicroVM is + # a per-session, request-time resource β€” the shim (microvm-lifecycle controller, + # template 52) creates it with RunMicrovm and drives suspend/resume/terminate as + # imperative SDK ops (the ACK controller does not reconcile those). This RGD stops + # at "image built + exec role ready", handed off via status above. +{{- if .Values.microvm.image.enabled | default true }} +--- +# The ONE platform MicrovmSandbox INSTANCE β€” applied by GitOps, reconciled by KRO +# ONCE to build the coder image + its build/exec roles + artifact bucket. This is +# slow-changing platform infra (one image per cluster), NOT per-session β€” so it lives +# here in the chart, not in the per-claim shim. The shim reads THIS object's status +# (imageARN + executionRoleARN) to RunMicrovm per session. Rebuild the image by +# bumping microvm.codeArtifactKey (a new artifact) and re-syncing. +apiVersion: {{ .Values.microvm.apiGroup | default "kro.run" }}/v1alpha1 +kind: MicrovmSandbox +metadata: + name: {{ .Values.microvm.image.name | default "coder" }} + namespace: {{ include "agent-sandbox.namespace" . }} + labels: + {{- include "agent-sandbox.labels" . | nindent 4 }} + annotations: + # Sync AFTER the RGD (wave -1) so its generated CRD exists. SkipDryRunOnMissingResource + # lets the first sync proceed even if KRO hasn't registered the CRD in the same pass β€” + # ArgoCD retries + selfHeal converge once the CRD appears (no whole-app block). + argocd.argoproj.io/sync-wave: "1" + argocd.argoproj.io/sync-options: SkipDryRunOnMissingResource=true +spec: + name: {{ .Values.microvm.image.name | default "coder" }} + region: {{ .Values.microvm.region | quote }} + baseImageARN: {{ .Values.microvm.baseImageARN | quote }} + # Object key only β€” the RGD prefixes s3://${bucket.spec.name}/ (see resource 4), so the + # bucket is never named twice and cannot drift from the one the graph creates. + codeArtifactKey: {{ required "microvm.codeArtifactKey is required when microvm.enabled=true (object key of the arm64 coder zip inside the RGD-created -microvm-artifacts- bucket)" .Values.microvm.codeArtifactKey | quote }} + # Hard-fail the render rather than emit `-microvm-artifacts-` (a trailing-hyphen, + # invalid bucket name) if the account id never got injected. microvm.enabled=true + # cannot work without it, so a loud template error beats a silently broken bucket. + accountId: {{ required "microvm.accountId is required when microvm.enabled=true (injected from the cluster's aws_account_id annotation in gitops/addons/registry/sandbox.yaml)" .Values.microvm.accountId | quote }} +{{- end }} +{{- end }} diff --git a/gitops/addons/charts/agent-sandbox-lambda/templates/shim/00-controller-pod-identity.yaml b/gitops/addons/charts/agent-sandbox-lambda/templates/shim/00-controller-pod-identity.yaml new file mode 100644 index 00000000..f4f639ef --- /dev/null +++ b/gitops/addons/charts/agent-sandbox-lambda/templates/shim/00-controller-pod-identity.yaml @@ -0,0 +1,150 @@ +{{/* + Self-managed ACK lambdamicrovms controller β†’ AWS access via EKS Pod Identity, + declared ALL-ACK (same mechanism as the RGD's build/exec roles): an ACK + iam.services.k8s.aws Role (trusted by the EKS Pod Identity service principal) + + an ACK eks.services.k8s.aws PodIdentityAssociation binding it to the controller's + ServiceAccount (ack-lambdamicrovms-controller in ack-system, created by the ACK + chart). No Crossplane, no Terraform, no CLI. + + This is the ONE bootstrap IAM the KRO RGD can't self-create (the controller needs + creds before it can create anything). It's reconciled by the Managed-ACK iam + eks + controllers (both live on the hub). Everything DOWNSTREAM β€” the S3 bucket, the build + role, the exec role, the MicrovmImage β€” is created by the KRO RGD via ACK too (see + templates/image/). So the entire IAM surface is ACK/KRO/GitOps. + + Earlier sync-wave so the role + association exist before the controller pod needs + them; Pod Identity creds are vended on demand + ArgoCD selfHeal converges with no + manual steps. Gated by microvm.enabled. +*/}} +{{- if and .Values.microvm .Values.microvm.enabled }} +apiVersion: iam.services.k8s.aws/v1alpha1 +kind: Role +metadata: + name: {{ .Values.microvm.podIdentity.clusterName }}-ack-lambdamicrovms + namespace: {{ include "agent-sandbox.namespace" . }} + labels: + {{- include "agent-sandbox.labels" . | nindent 4 }} + annotations: + argocd.argoproj.io/sync-wave: "-2" +spec: + name: {{ .Values.microvm.podIdentity.clusterName }}-ack-lambdamicrovms-controller + # Trust the EKS Pod Identity service principal (not IRSA/OIDC). + assumeRolePolicyDocument: | + { + "Version": "2012-10-17", + "Statement": [{ + "Effect": "Allow", + "Principal": {"Service": "pods.eks.amazonaws.com"}, + "Action": ["sts:AssumeRole", "sts:TagSession"] + }] + } + inlinePolicies: + lambdamicrovms: | + { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "LambdaMicrovms", + "Effect": "Allow", + "Action": [ + "lambda:CreateMicrovmImage","lambda:UpdateMicrovmImage","lambda:DeleteMicrovmImage", + "lambda:GetMicrovmImage","lambda:GetMicrovmImageVersion","lambda:ListMicrovmImages", + "lambda:RunMicrovm","lambda:GetMicrovm","lambda:TerminateMicrovm","lambda:ListMicrovms", + "lambda:SuspendMicrovm","lambda:ResumeMicrovm", + "lambda:CreateMicrovmAuthToken","lambda:CreateMicrovmShellAuthToken", + "lambda:TagResource","lambda:UntagResource","lambda:ListTagsForResource", + "lambda:ListNetworkConnectors","lambda:GetNetworkConnector" + ], + "Resource": "*" + }, + {{- /* + CreateMicrovmImage/RunMicrovm attach a network connector to the MicroVM. + With no explicit connectors in the RGD, the service uses the AWS-managed + default INTERNET_EGRESS connector, and passing it needs + lambda:PassNetworkConnector (verified: image build denied + 'lambda:PassNetworkConnector on .../network-connector:aws-network-connector: + INTERNET_EGRESS'). Scope to the AWS-managed connector ARNs in-region. + */ -}} + { + "Sid": "PassNetworkConnectors", + "Effect": "Allow", + "Action": "lambda:PassNetworkConnector", + "Resource": [ + "arn:aws:lambda:{{ .Values.microvm.region }}:aws:network-connector:*", + "arn:aws:lambda:{{ .Values.microvm.region }}:{{ .Values.microvm.accountId }}:network-connector:*" + ] + }, + {{- /* + Scope PassRole by the TARGET role ARN (the build/exec roles KRO creates), + NOT by an iam:PassedToService condition. CreateMicrovmImage/RunMicrovm pass + the role to the Lambda MicroVM sub-service whose principal is NOT plain + lambda.amazonaws.com β€” a StringEquals on lambda.amazonaws.com fails closed, + so the controller got AccessDenied on iam:PassRole for coder-microvm-build + (verified: sim ALLOWED for lambda.amazonaws.com yet the live API DENIED, i.e. + the real passed-to principal differs; microvms/microvm.lambda.amazonaws.com + also implicitDeny). ARN-scoping to *-microvm-build/-exec is net-TIGHTER than + the previous Resource:* β€” only these two purpose-built roles can be passed β€” + and is principal-agnostic so it survives whatever sub-service MicroVM uses. + */ -}} + { + "Sid": "PassBuildExecRoles", + "Effect": "Allow", + "Action": "iam:PassRole", + "Resource": [ + "arn:aws:iam::{{ .Values.microvm.accountId }}:role/*-microvm-build", + "arn:aws:iam::{{ .Values.microvm.accountId }}:role/*-microvm-exec" + ] + } + ] + } +--- +apiVersion: eks.services.k8s.aws/v1alpha1 +kind: PodIdentityAssociation +metadata: + name: {{ .Values.microvm.podIdentity.clusterName }}-ack-lambdamicrovms + namespace: {{ include "agent-sandbox.namespace" . }} + labels: + {{- include "agent-sandbox.labels" . | nindent 4 }} + annotations: + argocd.argoproj.io/sync-wave: "-2" +spec: + clusterName: {{ .Values.microvm.podIdentity.clusterName }} + namespace: {{ .Values.microvm.podIdentity.controllerNamespace | default "ack-system" }} + serviceAccount: {{ .Values.microvm.podIdentity.controllerServiceAccount | default "ack-lambdamicrovms-controller" }} + # ACK PodIdentityAssociation takes the role ARN (no role-ref selector). The ACK + # Role above has a deterministic name, so the ARN is constructed from the account id. + roleARN: "arn:aws:iam::{{ .Values.microvm.accountId }}:role/{{ .Values.microvm.podIdentity.clusterName }}-ack-lambdamicrovms-controller" +--- +# The BRIDGE SA (agent-sandbox-system) calls RunMicrovm/GetMicrovm/TerminateMicrovm β€” +# reuse the same lambda-microvms role via its own PodIdentityAssociation. +apiVersion: eks.services.k8s.aws/v1alpha1 +kind: PodIdentityAssociation +metadata: + name: {{ .Values.microvm.podIdentity.clusterName }}-microvm-bridge + namespace: {{ include "agent-sandbox.namespace" . }} + labels: + {{- include "agent-sandbox.labels" . | nindent 4 }} + annotations: + argocd.argoproj.io/sync-wave: "-2" +spec: + clusterName: {{ .Values.microvm.podIdentity.clusterName }} + namespace: {{ include "agent-sandbox.namespace" . }} + serviceAccount: microvm-bridge + roleARN: "arn:aws:iam::{{ .Values.microvm.accountId }}:role/{{ .Values.microvm.podIdentity.clusterName }}-ack-lambdamicrovms-controller" +--- +# The LIFECYCLE controller SA calls suspend-microvm/resume-microvm β€” same role. +apiVersion: eks.services.k8s.aws/v1alpha1 +kind: PodIdentityAssociation +metadata: + name: {{ .Values.microvm.podIdentity.clusterName }}-microvm-lifecycle + namespace: {{ include "agent-sandbox.namespace" . }} + labels: + {{- include "agent-sandbox.labels" . | nindent 4 }} + annotations: + argocd.argoproj.io/sync-wave: "-2" +spec: + clusterName: {{ .Values.microvm.podIdentity.clusterName }} + namespace: {{ include "agent-sandbox.namespace" . }} + serviceAccount: microvm-lifecycle + roleARN: "arn:aws:iam::{{ .Values.microvm.accountId }}:role/{{ .Values.microvm.podIdentity.clusterName }}-ack-lambdamicrovms-controller" +{{- end }} diff --git a/gitops/addons/charts/agent-sandbox-lambda/templates/shim/20-bridge-sandboxtemplate.yaml b/gitops/addons/charts/agent-sandbox-lambda/templates/shim/20-bridge-sandboxtemplate.yaml new file mode 100644 index 00000000..cc845c6a --- /dev/null +++ b/gitops/addons/charts/agent-sandbox-lambda/templates/shim/20-bridge-sandboxtemplate.yaml @@ -0,0 +1,469 @@ +{{- if and .Values.microvm .Values.microvm.enabled }} +{{- /* +Flow D β€” `lambda-microvm` SandboxTemplate + bridge RBAC + bridge script. + +The RuntimeClass "shim": Lambda MicroVM is a REMOTE AWS service, not a node-local +containerd handler, so there is no literal `lambda-microvm` RuntimeClass (that would +need a virtual-kubelet β€” out of scope). Instead this SandboxTemplate's pod is a thin +BRIDGE that preserves the Agent-Sandbox UX: + + 1. runs on a NORMAL Auto-Mode node (no kata runtimeClass / nodeSelector / taint) + 2. reads the ONE platform-built image (the committed MicrovmSandbox in template 50, + whose status carries imageARN + executionRoleARN β€” built ONCE by KRO/ACK, NOT + per session) and calls RunMicrovm (imperative AWS SDK) to launch a per-session + MicroVM running the SAME dark-factory-coder entrypoint + 3. records the microvmID on the owning Sandbox (annotation) so the microvm-lifecycle + controller (template 52) can suspend/resume it, and holds the pod so the pod + lifecycle mirrors the MicroVM; on real teardown it calls TerminateMicrovm + +ARCHITECTURE SPLIT (why the bridge no longer creates a MicrovmSandbox per claim): +image build = slow, declarative, ONE per cluster (KRO/ACK, template 50). Running a VM += fast, imperative, per SESSION (RunMicrovm/suspend/resume/terminate are SDK ops the +ACK controller does NOT reconcile). So the bridge drives the RUN side via the SDK and +only READS the platform image handoff β€” it does not re-run KRO per claim. + +To Flow B and the user this looks identical to a Flow A claim. The bridge needs a +ServiceAccount + AWS creds (Pod Identity) to call the Lambda MicroVM SDK. + +Gated behind microvm.enabled. Rendered as a SEPARATE SandboxTemplate +(`-microvm`) so both substrates can coexist on one cluster; a consumer +selects the substrate by which template its SandboxClaim references. +*/ -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: microvm-bridge + namespace: {{ include "agent-sandbox.namespace" . }} + labels: + {{- include "agent-sandbox.labels" . | nindent 4 }} +--- +{{- /* + Bridge API-server egress. + + The bridge pod carries agent-sandbox.io/role=coder (so Flow B's claim contract + + the shared coder-sandbox-egress policy apply to it). But that policy denies RFC-1918 + + the service CIDR on :443 to isolate UNTRUSTED coder code from the control plane β€” + and the K8s API server lives exactly there (kubernetes svc 172.20.0.1 + apiserver + endpoints in the VPC 10.0.0.0/8). NetworkPolicies are additive, so this ADDS an + egress allow for the API server, selected ONLY on the bridge's distinct + agent-sandbox.io/substrate=lambda-microvm label (Kata coders don't have it, so their + isolation is untouched). The bridge runs TRUSTED platform code (reads the MicrovmSandbox + image handoff + annotates the owning Sandbox with the microvmID) β€” unlike the Kata coder + it MUST reach the API server, or `kubectl get microvmsandbox` hangs and it never + RunMicrovm's (verified: in-pod kubectl to 172.20.0.1:443 timed out under coder-egress). +*/ -}} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: microvm-bridge-apiserver-egress + namespace: {{ include "agent-sandbox.namespace" . }} + labels: + {{- include "agent-sandbox.labels" . | nindent 4 }} +spec: + podSelector: + matchLabels: + agent-sandbox.io/substrate: lambda-microvm + policyTypes: + - Egress + egress: + # K8s API server β€” service ClusterIP + the in-VPC apiserver endpoints, :443 only. + - to: + - ipBlock: + cidr: {{ .Values.microvm.apiServerCidr | default "172.20.0.1/32" | quote }} + {{- range .Values.microvm.apiServerEndpointCidrs | default (list "10.0.0.0/16") }} + - ipBlock: + cidr: {{ . | quote }} + {{- end }} + ports: + - protocol: TCP + port: 443 + # EKS Pod Identity credential endpoint (link-local 169.254.170.23:80). The bridge + # gets its AWS creds β€” to call lambda-microvms run/suspend/resume/terminate β€” from + # the Pod Identity agent here. The shared coder-egress policy denies 169.254.0.0/16 + # (to block IMDS for untrusted coder code), which ALSO blocks Pod Identity, so the + # bridge's run-microvm failed "retrieving credentials from container-role: connect + # timeout http://169.254.170.23/v1/credentials". Allow ONLY the Pod Identity /32 (NOT + # IMDS 169.254.169.254, which stays denied) and ONLY for the bridge selector. + - to: + - ipBlock: + cidr: {{ .Values.microvm.podIdentityEndpoint | default "169.254.170.23/32" | quote }} + ports: + - protocol: TCP + port: 80 +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: microvm-bridge + namespace: {{ include "agent-sandbox.namespace" . }} + labels: + {{- include "agent-sandbox.labels" . | nindent 4 }} +rules: + # READS the platform MicrovmSandbox (image handoff: imageARN + executionRoleARN) β€” + # GitOps-owned platform infra (template 50), built once. + - apiGroups: [{{ .Values.microvm.apiGroup | default "kro.run" | quote }}] + resources: ["microvmsandboxes"] + verbs: ["get", "list", "watch"] + # Per-session Microvm CR: the bridge CREATES it (declarative path β€” the only way the + # controller fires the /run hook that delivers runHookPayload) and DELETES it on + # teardown (controller terminates the VM). + - apiGroups: ["lambdamicrovms.services.k8s.aws"] + resources: ["microvms"] + verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] + # Per-session payload Secret (runHookPayload SecretKeyReference). + - apiGroups: [""] + resources: ["secrets"] + verbs: ["get", "create", "update", "patch", "delete"] + # Read the owning Sandbox (suspend-vs-teardown in preStop) + patch it to record the + # per-session microvmID (annotation) so the microvm-lifecycle controller can + # suspend/resume THIS session's VM. + - apiGroups: ["agents.x-k8s.io"] + resources: ["sandboxes"] + verbs: ["get", "patch", "update"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: microvm-bridge + namespace: {{ include "agent-sandbox.namespace" . }} + labels: + {{- include "agent-sandbox.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: microvm-bridge +subjects: + - kind: ServiceAccount + name: microvm-bridge + namespace: {{ include "agent-sandbox.namespace" . }} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: microvm-bridge-script + namespace: {{ include "agent-sandbox.namespace" . }} + labels: + {{- include "agent-sandbox.labels" . | nindent 4 }} +data: + bridge.sh: | + #!/bin/sh + # Flow D bridge β€” claim -> RunMicrovm (SDK) against the pre-built platform image -> + # mirror lifecycle. Idles until a SandboxClaim injects DF_ISSUE_NUMBER (Flow B), + # exactly like the Kata coder. Needs AWS creds (Pod Identity) for the SDK calls. + set -eu + # The bridge image is public.ecr.aws/aws-cli/aws-cli:latest β€” a glibc, always-current + # aws-cli v2 that KNOWS the pre-GA `lambda-microvms` service (the alpine/k8s image's + # aws-cli 1.34 does NOT: `aws lambda-microvms` printed the service list = unrecognized, + # so RunMicrovm never fired). That image has no kubectl, so fetch a static one here + # (same pattern as the security-agent bootstrap). Both are then on PATH. + if ! command -v kubectl >/dev/null 2>&1; then + echo "[microvm-bridge] fetching kubectl..." + ARCH="$(uname -m)"; case "$ARCH" in aarch64|arm64) A=arm64;; *) A=amd64;; esac + KV="$(curl -fsSL https://dl.k8s.io/release/stable.txt)" + curl -fsSL "https://dl.k8s.io/release/${KV}/bin/linux/${A}/kubectl" -o /tmp/kubectl + chmod +x /tmp/kubectl; export PATH="/tmp:$PATH" + fi + REGION="{{ .Values.microvm.region }}" + PLATFORM_IMAGE="{{ .Values.microvm.image.name | default "coder" }}" # the ONE committed MicrovmSandbox (template 50) + NS="{{ include "agent-sandbox.namespace" . }}" + echo "[microvm-bridge] idle β€” waiting for a SandboxClaim to inject DF_ISSUE_NUMBER..." + while [ -z "${DF_ISSUE_NUMBER:-}" ]; do sleep 5; done + # SANDBOX_NAME (downward API = owning Sandbox CR name) lets the microvm-lifecycle + # controller correlate THIS session's MicroVM for suspend/resume. + SANDBOX_NAME="${SANDBOX_NAME:-df-${DF_ISSUE_NUMBER}}" + echo "[microvm-bridge] claim for issue #${DF_ISSUE_NUMBER} (sandbox=${SANDBOX_NAME})" + + # 1) READ the platform image handoff (built ONCE by KRO/ACK β€” template 50). Wait + # until its image build is terminal (CREATED/UPDATED) before launching. + echo "[microvm-bridge] reading platform image ${PLATFORM_IMAGE} (waiting for build ready)..." + i=0; IMAGE_ARN=""; EXEC_ROLE="" + while [ "$i" -lt 240 ]; do + IST=$(kubectl get microvmsandbox "${PLATFORM_IMAGE}" -n "${NS}" -o jsonpath='{.status.imageState}' 2>/dev/null || echo "") + if [ "${IST}" = "CREATED" ] || [ "${IST}" = "UPDATED" ]; then + IMAGE_ARN=$(kubectl get microvmsandbox "${PLATFORM_IMAGE}" -n "${NS}" -o jsonpath='{.status.imageARN}' 2>/dev/null || echo "") + EXEC_ROLE=$(kubectl get microvmsandbox "${PLATFORM_IMAGE}" -n "${NS}" -o jsonpath='{.status.executionRoleARN}' 2>/dev/null || echo "") + [ -n "${IMAGE_ARN}" ] && [ -n "${EXEC_ROLE}" ] && break + fi + i=$((i+1)); sleep 5 + done + if [ -z "${IMAGE_ARN}" ] || [ -z "${EXEC_ROLE}" ]; then + echo "[microvm-bridge] ERROR: platform image not ready (imageState=${IST:-}) β€” cannot RunMicrovm"; exit 1 + fi + echo "[microvm-bridge] image=${IMAGE_ARN} execRole=${EXEC_ROLE}" + + # 2) Build the runHookPayload β€” the per-session context the coder needs, delivered + # as the /run hook body (hook-server.js background-spawns the coder from it). + # CRITICAL: runHookPayload is a SecretKeyReference delivered by the DECLARATIVE + # Microvm CR (the controller reads the Secret + drives the /run hook). The + # imperative `run-microvm --run-hook-payload` CLI launches the VM but NEVER fires + # /run (verified: VM RUNNING, hook-server listening, but coder never started). So + # we write a Secret + create a Microvm CR. GitHub token from the mounted secret; + # NO Bifrost key β€” the coder is Bedrock-direct via the exec role. + GH_TOKEN="$(cat /etc/df/gh-token 2>/dev/null || echo "")" + [ -z "${GH_TOKEN}" ] && echo "[microvm-bridge] WARN: no gh-token mounted β€” coder cannot open a PR" + # python3 (node is absent in the aws-cli image); json.dumps escapes token/title. One + # line so it stays inside the bridge.sh: | YAML block scalar. + # FIX ROUND: df-run injects DF_ITERATE_NOTE_B64 (the human change request) into this + # `coder` container's env (same claim contract as Kata). The MicroVM coder has no + # claim env, so fold the note into the runHookPayload β€” hook-server maps it back to + # the coder's DF_ITERATE_NOTE_B64. Without this a Lambda fix round runs with NO + # instructions and reports "done" on the OLD sha (zero commits). + [ -n "${DF_ITERATE_NOTE_B64:-}" ] && echo "[microvm-bridge] fix round: forwarding iterate note to coder" + PAYLOAD=$(GH="${GH_TOKEN}" REGION="${REGION}" python3 -c 'import json,os; e=os.environ.get; print(json.dumps({"ghToken":e("GH",""),"region":e("REGION","us-west-2"),"issueNumber":e("DF_ISSUE_NUMBER",""),"repo":e("DF_REPO",""),"branch":e("DF_BRANCH",""),"baseBranch":e("DF_BASE_BRANCH","main"),"issueTitle":e("DF_ISSUE_TITLE",""),"iterateNoteB64":e("DF_ITERATE_NOTE_B64",""),"iterateNote":e("DF_ITERATE_NOTE","")}))') + + MVM="mvm-${DF_ISSUE_NUMBER}" # Microvm CR + payload Secret name for this session + # RESUME-ON-FIX-ROUND (the suspend/resume highlight): the CR name is stable + # (mvm-). On a fix round the previous round's VM is still around β€” SUSPENDED + # after the first PR (idlePolicy.autoResumeEnabled=false keeps it down). Rather than + # terminate + rebuild a fresh VM, we RESUME the suspended one via the Sandbox CRD: + # flip operatingMode=Running β†’ the microvm-lifecycle controller calls resume-microvm β†’ + # the SAME VM (memory+disk preserved) comes back, and hook-server accepts a NEW /run + # because its guard is keyed on a per-invocation run-id (issue+note hash), not a + # one-shot boolean (see coder-microvm/hook-server.js). This is the whole Flow D value + # prop: scale-to-zero between rounds, warm-resume for the fix. Fresh VMs are only made + # on the FIRST round (no existing CR). + RESUME_ROUND="" + if kubectl get microvm "${MVM}" -n "${NS}" >/dev/null 2>&1; then + echo "[microvm-bridge] fix round: resuming suspended VM ${MVM} via Sandbox.operatingMode=Running" + kubectl patch sandbox "${SANDBOX_NAME}" -n "${NS}" --type merge \ + -p '{"spec":{"operatingMode":"Running"}}' >/dev/null 2>&1 \ + && echo "[microvm-bridge] operatingMode=Running set β€” microvm-lifecycle will resume the VM" \ + || echo "[microvm-bridge] WARN: could not set operatingMode=Running" + # Refresh the payload Secret so the controller/hook sees the NEW iterate note. + RESUME_ROUND=1 + fi + # Build BOTH manifests as JSON with python3 and pipe to kubectl apply. JSON (not a + # heredoc) on purpose: a heredoc's column-0 EOF terminator breaks out of the + # bridge.sh: | YAML block scalar. JSON is valid YAML and stays on indented lines. + MAXIDLE={{ .Values.microvm.defaults.maxIdleDurationSeconds }}; SUSPDUR={{ .Values.microvm.defaults.suspendedDurationSeconds }} + INGRESS="arn:aws:lambda:${REGION}:aws:network-connector:aws-network-connector:ALL_INGRESS" + LOGGRP="/aws/lambda/microvms/{{ .Values.microvm.image.name | default "coder" }}-image" + echo "[microvm-bridge] writing payload Secret + Microvm CR ${MVM} for issue #${DF_ISSUE_NUMBER}..." + MVM="${MVM}" NS="${NS}" PAYLOAD="${PAYLOAD}" python3 -c 'import json,os; e=os.environ; mvm=e["MVM"]; ns=e["NS"]; print(json.dumps({"apiVersion":"v1","kind":"Secret","metadata":{"name":mvm+"-payload","namespace":ns},"type":"Opaque","stringData":{"payload":e["PAYLOAD"]}}))' | kubectl apply -f - >/dev/null 2>&1 || { echo "[microvm-bridge] payload secret apply failed"; exit 1; } + # Microvm CR: ingress ALL_INGRESS (so the bridge can reach the endpoint to drive /run), + # egress INTERNET_EGRESS (Bedrock + git/gh), runtime logging β†’ CloudWatch (logStream + # 'runtime' so the coder's stdout is visible, separate from build logs). + MVM="${MVM}" NS="${NS}" IMG="${IMAGE_ARN}" EXECROLE="${EXEC_ROLE}" MAXIDLE="${MAXIDLE}" SUSPDUR="${SUSPDUR}" INGRESS="${INGRESS}" REGION="${REGION}" LOGGRP="${LOGGRP}" python3 -c 'import json,os; e=os.environ; mvm=e["MVM"]; ns=e["NS"]; r=e["REGION"]; print(json.dumps({"apiVersion":"lambdamicrovms.services.k8s.aws/v1alpha1","kind":"Microvm","metadata":{"name":mvm,"namespace":ns},"spec":{"imageIdentifier":e["IMG"],"executionRoleARN":e["EXECROLE"],"ingressNetworkConnectors":[e["INGRESS"]],"egressNetworkConnectors":["arn:aws:lambda:"+r+":aws:network-connector:aws-network-connector:INTERNET_EGRESS"],"runHookPayload":{"name":mvm+"-payload","key":"payload","namespace":ns},"logging":{"cloudWatch":{"logGroup":e["LOGGRP"],"logStream":"runtime-"+mvm}},"idlePolicy":{"autoResumeEnabled":False,"maxIdleDurationSeconds":int(e["MAXIDLE"]),"suspendedDurationSeconds":int(e["SUSPDUR"])}}}))' | kubectl apply -f - >/dev/null 2>&1 || { echo "[microvm-bridge] Microvm CR apply failed"; exit 1; } + + # 4) Wait for the controller to report the running VM's id, record it on the Sandbox + # (lifecycle controller reads this to suspend/resume THIS session's VM). + VMID=""; i=0 + while [ "$i" -lt 60 ]; do + VMID=$(kubectl get microvm "${MVM}" -n "${NS}" -o jsonpath='{.status.microvmID}' 2>/dev/null || echo "") + [ -n "${VMID}" ] && break + i=$((i+1)); sleep 5 + done + echo "[microvm-bridge] Microvm ${MVM} -> ${VMID:-}" + [ -n "${VMID}" ] && kubectl annotate sandbox "${SANDBOX_NAME}" -n "${NS}" \ + "microvm-lifecycle.agents.x-k8s.io/microvm-id=${VMID}" --overwrite >/dev/null 2>&1 || true + + # 4b) DRIVE the coder: wait for RUNNING + an endpoint, mint an auth token, and POST the + # payload to /run on the endpoint. This is the deterministic invocation (verified by + # probe): the service's internal /run auto-fire wasn't reliably starting the coder, so + # the bridge drives it explicitly like the reference run_session. hook-server's /run + # background-spawns the coder (returns fast); df-run's await-coder polls GitHub for the PR. + if [ -n "${VMID}" ]; then + EP=""; i=0 + while [ "$i" -lt 60 ]; do + S=$(aws lambda-microvms get-microvm --region "${REGION}" --microvm-identifier "${VMID}" --query 'state' --output text 2>/dev/null || echo "") + EP=$(aws lambda-microvms get-microvm --region "${REGION}" --microvm-identifier "${VMID}" --query 'endpoint' --output text 2>/dev/null || echo "") + [ "$S" = "RUNNING" ] && [ -n "${EP}" ] && [ "${EP}" != "None" ] && break + i=$((i+1)); sleep 5 + done + TOKEN=$(aws lambda-microvms create-microvm-auth-token --region "${REGION}" --microvm-identifier "${VMID}" \ + --expiration-in-minutes 60 --allowed-ports 'port=8080' 2>/dev/null \ + | python3 -c 'import json,sys; print(json.load(sys.stdin)["authToken"]["X-aws-proxy-auth"])' 2>/dev/null || echo "") + if [ -n "${EP}" ] && [ -n "${TOKEN}" ]; then + echo "[microvm-bridge] driving coder: POST /run on ${EP}" + RC=$(curl -sS -m 30 -o /tmp/run.out -w '%{http_code}' -X POST "https://${EP}/run" \ + -H "X-aws-proxy-auth: ${TOKEN}" -H 'Content-Type: application/json' -d "${PAYLOAD}" 2>/tmp/run.err || echo "000") + echo "[microvm-bridge] /run -> HTTP ${RC} $(cat /tmp/run.out 2>/dev/null | head -c 120)" + else + echo "[microvm-bridge] WARN: no endpoint/token β€” cannot drive /run (ep=${EP:-none} token=$([ -n "${TOKEN}" ] && echo yes || echo no))" + fi + fi + + # 5) Teardown vs suspend β€” decided by whether the OWNING SANDBOX STILL EXISTS. + # The agent-sandbox operator handles operatingMode=Suspended by DELETING THE POD + # (verified in the operator log: "Deleting Pod because .Spec.OperatingMode is + # Suspended") while KEEPING the Sandbox object alive in state SandboxSuspended. So + # the bridge pod is torn down on EVERY suspend β€” and its cleanup trap must NOT delete + # the Microvm CR then, or the VM is terminated instead of suspended (exactly the bug + # we hit: Sandbox survived Suspended, but the VM was gone). + # - Sandbox STILL EXISTS => this is a SUSPEND (or transient pod restart) => KEEP CR. + # - Sandbox GONE => real teardown (df-merge-teardown deleted the claim) => + # delete CR so the controller TerminateMicrovm's the VM. + # (Checking Sandbox existence is more robust than a preStop /tmp marker, which raced + # the SIGTERM and didn't reliably stick.) + cleanup() { + if kubectl get sandbox "${SANDBOX_NAME}" -n "${NS}" >/dev/null 2>&1; then + M=$(kubectl get sandbox "${SANDBOX_NAME}" -n "${NS}" -o jsonpath='{.spec.operatingMode}' 2>/dev/null || echo "") + echo "[microvm-bridge] pod stopping but Sandbox ${SANDBOX_NAME} still exists (operatingMode=${M:-?}) β€” KEEPING Microvm ${MVM} (suspend, not teardown)" + return + fi + echo "[microvm-bridge] Sandbox ${SANDBOX_NAME} gone β€” real teardown: deleting Microvm ${MVM} (controller terminates the VM)" + kubectl delete microvm "${MVM}" -n "${NS}" --ignore-not-found >/dev/null 2>&1 || true + kubectl delete secret "${MVM}-payload" -n "${NS}" --ignore-not-found >/dev/null 2>&1 || true + } + trap cleanup EXIT INT TERM + + # 6) Hold the pod so Sandbox lifecycle == Microvm lifecycle. Poll the in-VM coder /logs + # (OBSERVABILITY β€” runtime CloudWatch routing is unreliable on this runtime) ONLY + # until the coder pushes its PR. Then suspend and STOP touching the endpoint. + # + # SUSPEND-VIA-CRD (the Flow D highlight): suspend/resume is driven declaratively + # through Sandbox.spec.operatingMode, reconciled by the microvm-lifecycle controller + # (template 30) β€” NOT by an imperative suspend-microvm call here. Two reasons this + # matters and why the old imperative path FAILED to keep the VM suspended: + # (a) The CR is created with idlePolicy.autoResumeEnabled=FALSE, so a suspended VM + # stays suspended. With autoResume=true (the old value) ANY hit to the VM + # endpoint auto-resumes it β€” and this loop used to curl /logs every 20s + # FOREVER, so the VM bounced back to RUNNING seconds after every suspend + # (observed in the Lambda console: never actually suspended). + # (b) Routing suspend through operatingMode is the whole point β€” it shows the + # Agent Sandbox CRD driving MicroVM scale-to-zero via the shim controller. + # So: once the coder pushes, set operatingMode=Suspended (controller suspends the + # VM), then switch to a lightweight CR-existence watch that NEVER touches the + # endpoint again. The VM's memory+disk persist; a fix round flips operatingMode + # back to Running (controller resumes) and the pipeline claims a fresh session. + echo "[microvm-bridge] Microvm ${MVM} running β€” pod now mirrors its lifecycle." + while true; do + # CR gone/terminating => real teardown => exit (cleanup trap handles CR delete). + ST=$(kubectl get microvm "${MVM}" -n "${NS}" -o jsonpath='{.status.state}' 2>/dev/null || echo "GONE") + case "${ST}" in + TERMINATED|TERMINATING|GONE|"") echo "[microvm-bridge] Microvm state=${ST:-gone} β€” exiting."; break ;; + esac + if [ -n "${EP:-}" ] && [ -n "${TOKEN:-}" ]; then + # Poll /logs for observability + to detect "coder pushed PR". + LOG=$(curl -sS -m 10 "https://${EP}/logs" -H "X-aws-proxy-auth: ${TOKEN}" 2>/dev/null \ + | python3 -c 'import json,sys; d=json.load(sys.stdin); print(d.get("log","")[-800:])' 2>/dev/null || echo "") + [ -n "${LOG}" ] && echo "[microvm-bridge] coder-log-tail: $(echo "$LOG" | tail -1)" + if echo "${LOG}" | grep -qiE 'PR opened|done β€” PR|status success'; then + # Coder is done β†’ request SUSPEND declaratively. The operator then DELETES this + # pod (operatingMode=Suspended), and the microvm-lifecycle controller reconciles + # the same intent into suspend-microvm. We EXIT right after setting it: the pod is + # about to be killed anyway, and exiting cleanly lets the cleanup trap run while + # the Sandbox still exists β†’ it KEEPS the CR (VM suspends, not terminates). We do + # NOT keep polling /logs β€” every endpoint hit would auto-resume the VM. + echo "[microvm-bridge] coder pushed PR β€” requesting SUSPEND via Sandbox.operatingMode (controller reconciles suspend-microvm ${VMID})" + kubectl patch sandbox "${SANDBOX_NAME}" -n "${NS}" --type merge \ + -p '{"spec":{"operatingMode":"Suspended"}}' >/dev/null 2>&1 \ + && echo "[microvm-bridge] operatingMode=Suspended set β€” exiting bridge (pod will be removed; CR + VM persist)" \ + || echo "[microvm-bridge] WARN: could not set operatingMode=Suspended" + break + fi + fi + sleep 20 + done +--- +apiVersion: extensions.agents.x-k8s.io/v1beta1 +kind: SandboxTemplate +metadata: + name: {{ .Values.warmPool.templateName }}-microvm + namespace: {{ include "agent-sandbox.namespace" . }} + labels: + {{- include "agent-sandbox.labels" . | nindent 4 }} + agent-sandbox.io/substrate: lambda-microvm +spec: + # Same env-injection contract as the Kata template so Flow B is unchanged. + envVarsInjectionPolicy: {{ .Values.coderTemplate.envVarsInjectionPolicy | default "Allowed" }} + podTemplate: + metadata: + labels: + {{- include "agent-sandbox.selectorLabels" . | nindent 8 }} + agent-sandbox.io/role: coder + agent-sandbox.io/substrate: lambda-microvm + spec: + # NO kata runtimeClass / nodeSelector / toleration β€” the bridge is a normal + # pod on an Auto-Mode node. The isolation boundary is the remote MicroVM. + serviceAccountName: microvm-bridge + # The bridge needs a k8s token (read platform image, patch Sandbox) AND AWS creds + # (Pod Identity association on this SA) to call the Lambda MicroVM SDK + # (RunMicrovm/GetMicrovm/TerminateMicrovm). The Kata coder is credential-less; the + # bridge is not, because RUN is imperative. + automountServiceAccountToken: true + securityContext: + runAsNonRoot: true + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 + seccompProfile: + type: RuntimeDefault + containers: + # Named `coder` (NOT `bridge`) on purpose: Flow B's SandboxClaim injects env + # (DF_ISSUE_NUMBER, DF_REPO, …) into a container called `coder` β€” the SAME claim + # contract as the Kata substrate. The operator REJECTS the claim + # ("target container coder not found") if this name differs, so the substrate + # must expose a `coder` container to stay transparent to df-run. This container + # is still the bridge (runs bridge.sh β†’ RunMicrovm); only the name matches Kata. + - name: coder + image: {{ .Values.microvm.bridgeImage }} + command: ["/bin/sh", "/scripts/bridge.sh"] + # SANDBOX_NAME = this pod's own name via the downward API. agent-sandbox names + # the Sandbox CR and its pod identically, so this IS the owning Sandbox name. + # Without it bridge.sh fell back to df- (e.g. df-9999) which does NOT + # match the real Sandbox (named after the claim, e.g. df-issue-smoke-d), so the + # microvm-id annotation write silently failed β†’ the lifecycle controller couldn't + # find the VM to suspend and the pod teardown TERMINATED it instead of suspending. + env: + - name: SANDBOX_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + # NOTE: no preStop suspend marker needed anymore β€” cleanup() decides suspend-vs- + # teardown by whether the owning Sandbox still EXISTS (it survives Suspended, + # is gone on real teardown), which is race-free unlike a preStop /tmp marker. + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: ["ALL"] + resources: + # The bridge runs python3 (JSON), curl, a fetched kubectl, and an aws-cli v2 + # (glibc) in a poll loop β€” 128Mi OOMKilled it mid fix-round (bridge died before + # the coder's new commit landed β†’ await-coder spun forever). Give it real headroom. + requests: { cpu: 100m, memory: 256Mi } + limits: { cpu: "1", memory: 1Gi } + volumeMounts: + - name: bridge-script + mountPath: /scripts + - name: tmp + mountPath: /tmp + # GitHub token (read-only) the bridge folds into the runHookPayload so the + # coder in the MicroVM can push + open the PR. Same secret the Kata coder uses. + - name: gh-token + mountPath: /etc/df + readOnly: true + volumes: + - name: bridge-script + configMap: + name: microvm-bridge-script + defaultMode: 0555 + - name: tmp + emptyDir: {} + - name: gh-token + secret: + secretName: {{ .Values.microvm.githubSecretName | default "dark-factory-github" }} + defaultMode: 0400 + optional: true +--- +{{- /* +Lambda-MicroVM SandboxWarmPool. SandboxClaim.spec.warmPoolRef is REQUIRED (a claim +can't bind a bare template), so Flow D needs its own pool the df-run claim step +targets when the darkfactory-lambda label fires. Kept small (bridge pods are tiny); +each idle member is a bridge waiting to RunMicrovm (from the pre-built platform image) +on claim. +*/ -}} +apiVersion: extensions.agents.x-k8s.io/v1beta1 +kind: SandboxWarmPool +metadata: + name: {{ .Values.microvm.warmPool.name | default "coder-warmpool-microvm" }} + namespace: {{ include "agent-sandbox.namespace" . }} + labels: + {{- include "agent-sandbox.labels" . | nindent 4 }} + agent-sandbox.io/substrate: lambda-microvm +spec: + replicas: {{ .Values.microvm.warmPool.targetIdle | default 1 }} + sandboxTemplateRef: + name: {{ .Values.warmPool.templateName }}-microvm +{{- end }} diff --git a/gitops/addons/charts/agent-sandbox-lambda/templates/shim/30-microvm-lifecycle.yaml b/gitops/addons/charts/agent-sandbox-lambda/templates/shim/30-microvm-lifecycle.yaml new file mode 100644 index 00000000..bc4e9757 --- /dev/null +++ b/gitops/addons/charts/agent-sandbox-lambda/templates/shim/30-microvm-lifecycle.yaml @@ -0,0 +1,184 @@ +{{- if and .Values.microvm .Values.microvm.enabled }} +{{- /* +Flow D β€” microvm-lifecycle controller (suspend/resume via Sandbox.operatingMode). + +WHY THIS EXISTS: the Agent Sandbox CRD exposes `spec.operatingMode ∈ {Running, +Suspended}` (the declarative suspend/resume intent), but the ACK Microvm CR has NO +suspend field β€” its spec is create-time only; suspend/resume are IMPERATIVE SDK ops +(`suspend-microvm`/`resume-microvm`) that the ACK controller deliberately does NOT +reconcile. So switching operatingMode does nothing on its own. This tiny always-on +reconcile loop closes that gap: + + Sandbox.operatingMode: Running -> Suspended : aws lambda-microvms suspend-microvm + Sandbox.operatingMode: Suspended -> Running : aws lambda-microvms resume-microvm + +It resolves the MicroVM id from the MicrovmSandbox (KRO) status (microvmID). The +MicrovmSandbox is NOT deleted on suspend (only on claim teardown -> terminate), so +the VM survives suspend/resume cycles. This is a reconcile loop (not preStop hooks) +so it is robust to pod/node loss and resume needs no live pod β€” pure shim, no +virtual-kubelet, no new image (alpine/k8s = kubectl + aws cli), matching the +pool-manager/bridge pattern. Auth via EKS Pod Identity (empty SA annotations; the +pod-identity association granting lambda-microvms Suspend/Resume/Get is created by +the platform). +*/ -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: microvm-lifecycle + namespace: {{ include "agent-sandbox.namespace" . }} + labels: + {{- include "agent-sandbox.labels" . | nindent 4 }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: microvm-lifecycle + namespace: {{ include "agent-sandbox.namespace" . }} + labels: + {{- include "agent-sandbox.labels" . | nindent 4 }} +rules: + # Read Sandboxes (watch operatingMode + read the bridge-written microvm-id annotation) + # and patch them (stamp the last-acted mode to detect transitions). No microvmsandboxes + # access needed β€” the per-session VM id lives on the Sandbox, not a KRO status. + - apiGroups: ["agents.x-k8s.io"] + resources: ["sandboxes"] + verbs: ["get", "list", "watch", "patch", "update"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: microvm-lifecycle + namespace: {{ include "agent-sandbox.namespace" . }} + labels: + {{- include "agent-sandbox.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: microvm-lifecycle +subjects: + - kind: ServiceAccount + name: microvm-lifecycle + namespace: {{ include "agent-sandbox.namespace" . }} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: microvm-lifecycle-script + namespace: {{ include "agent-sandbox.namespace" . }} + labels: + {{- include "agent-sandbox.labels" . | nindent 4 }} +data: + reconcile.sh: | + #!/bin/sh + # Reconcile Sandbox.operatingMode -> Lambda MicroVM suspend/resume. + # Idempotent: we stamp the last-acted mode on an annotation and only act on change. + set -eu + # bridgeImage is aws-cli v2 (has lambda-microvms; alpine/k8s's aws-cli 1.34 does not) + # but has no kubectl β€” fetch a static one (same as bridge.sh / security-agent). + if ! command -v kubectl >/dev/null 2>&1; then + echo "[microvm-lifecycle] fetching kubectl..." + ARCH="$(uname -m)"; case "$ARCH" in aarch64|arm64) A=arm64;; *) A=amd64;; esac + KV="$(curl -fsSL https://dl.k8s.io/release/stable.txt)" + curl -fsSL "https://dl.k8s.io/release/${KV}/bin/linux/${A}/kubectl" -o /tmp/kubectl + chmod +x /tmp/kubectl; export PATH="/tmp:$PATH" + fi + NS="{{ include "agent-sandbox.namespace" . }}" + REGION="{{ .Values.microvm.region }}" + APIGROUP="{{ .Values.microvm.apiGroup | default "kro.run" }}" + ANN="microvm-lifecycle.agents.x-k8s.io/last-mode" + INTERVAL="{{ .Values.microvm.lifecycle.intervalSeconds | default 15 }}" + echo "[microvm-lifecycle] reconciling every ${INTERVAL}s (ns=${NS} region=${REGION})" + while true; do + # Select Sandboxes on the lambda-microvm substrate by the microvm-id ANNOTATION the + # bridge writes after RunMicrovm β€” NOT a label. The agent-sandbox operator does NOT + # propagate SandboxTemplate labels onto the Sandbox object, so a label selector + # (agent-sandbox.io/substrate=lambda-microvm) matches NOTHING and the controller + # stays blind to every real session (observed: operatingMode=Suspended set, Sandbox + # went SandboxSuspended, but the VM was never suspended because this loop skipped it). + # Only lambda sessions carry the microvm-id annotation, so it's the reliable signal. + for sb in $(kubectl get sandbox -n "$NS" \ + -o jsonpath='{range .items[?(@.metadata.annotations.microvm-lifecycle\.agents\.x-k8s\.io/microvm-id)]}{.metadata.name}{"\n"}{end}' 2>/dev/null); do + MODE=$(kubectl get sandbox "$sb" -n "$NS" -o jsonpath='{.spec.operatingMode}' 2>/dev/null || echo "Running") + LAST=$(kubectl get sandbox "$sb" -n "$NS" -o jsonpath="{.metadata.annotations.${ANN}}" 2>/dev/null || echo "") + [ "$MODE" = "$LAST" ] && continue # no transition + # Resolve THIS session's MicroVM id from the annotation the BRIDGE writes on the + # Sandbox after RunMicrovm (microvm-lifecycle.agents.x-k8s.io/microvm-id). There + # is no per-session MicrovmSandbox anymore β€” the VM is created imperatively by the + # bridge (SDK), so the id lives on the Sandbox, not in a KRO status. + VMID=$(kubectl get sandbox "$sb" -n "$NS" -o jsonpath='{.metadata.annotations.microvm-lifecycle\.agents\.x-k8s\.io/microvm-id}' 2>/dev/null || echo "") + if [ -z "$VMID" ]; then + echo "[microvm-lifecycle] $sb: mode=$MODE but no microvm-id annotation yet β€” will retry" + continue + fi + case "$MODE" in + Suspended) + echo "[microvm-lifecycle] $sb: Running->Suspended -> suspend-microvm $VMID" + aws lambda-microvms suspend-microvm --microvm-identifier "$VMID" --region "$REGION" 2>&1 || true + ;; + Running) + echo "[microvm-lifecycle] $sb: Suspended->Running -> resume-microvm $VMID" + aws lambda-microvms resume-microvm --microvm-identifier "$VMID" --region "$REGION" 2>&1 || true + ;; + *) + echo "[microvm-lifecycle] $sb: unknown operatingMode '$MODE' β€” skipping"; continue ;; + esac + # Stamp the mode we acted on so we don't repeat the call. + kubectl annotate sandbox "$sb" -n "$NS" "${ANN}=${MODE}" --overwrite >/dev/null 2>&1 || true + done + sleep "$INTERVAL" + done +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: microvm-lifecycle + namespace: {{ include "agent-sandbox.namespace" . }} + labels: + {{- include "agent-sandbox.labels" . | nindent 4 }} +spec: + replicas: 1 + selector: + matchLabels: + {{- include "agent-sandbox.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: microvm-lifecycle + template: + metadata: + labels: + {{- include "agent-sandbox.selectorLabels" . | nindent 8 }} + app.kubernetes.io/component: microvm-lifecycle + spec: + serviceAccountName: microvm-lifecycle + securityContext: + runAsNonRoot: true + runAsUser: 1000 + runAsGroup: 1000 + seccompProfile: + type: RuntimeDefault + containers: + - name: lifecycle + image: {{ .Values.microvm.bridgeImage }} + command: ["/bin/sh", "/scripts/reconcile.sh"] + env: + - name: AWS_REGION + value: {{ .Values.microvm.region | quote }} + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: ["ALL"] + resources: + requests: { cpu: 25m, memory: 64Mi } + limits: { cpu: 100m, memory: 128Mi } + volumeMounts: + - name: script + mountPath: /scripts + - name: tmp + mountPath: /tmp + volumes: + - name: script + configMap: + name: microvm-lifecycle-script + defaultMode: 0555 + - name: tmp + emptyDir: {} +{{- end }} diff --git a/gitops/addons/charts/agent-sandbox-lambda/templates/shim/40-kro-graph-rbac.yaml b/gitops/addons/charts/agent-sandbox-lambda/templates/shim/40-kro-graph-rbac.yaml new file mode 100644 index 00000000..7d13f124 --- /dev/null +++ b/gitops/addons/charts/agent-sandbox-lambda/templates/shim/40-kro-graph-rbac.yaml @@ -0,0 +1,74 @@ +{{/* + KRO graph child-resource RBAC. + + The MicrovmSandbox ResourceGraphDefinition (templates/image/) is reconciled by EKS + Managed KRO, whose controller authenticates to the API server as the cluster's KRO + capability role (EKS access entry, session name "KRO"). The AWS-managed + AmazonEKSKROPolicy attached to that access entry grants KRO its own kro.run perms and + discovery, but NOT create/update/delete on the ACK kinds this graph composes. So when + KRO tries to materialize the graph it fails: + + resource reconciliation failed: buckets.s3.services.k8s.aws "coder-microvm-artifacts" + is forbidden: User ".../hub-KROCapabilityRole/KRO" cannot get resource "buckets" ... + + This ClusterRole grants KRO CRUD on EXACTLY the three ACK groups the MicrovmSandbox + graph creates as children β€” s3 Buckets, iam Roles, and the self-managed lambdamicrovms + MicrovmImages/Microvms β€” and nothing else. K8s RBAC cannot scope list/watch/create by + resource NAME, so the grant is per-kind; it deliberately does NOT include core/*, apps, + rbac, secrets, or any other ACK service. delete is required so KRO can garbage-collect + the graph's children when a MicrovmSandbox is removed. + + Gated by microvm.enabled AND microvm.podIdentity.kroCapability.enabled. Rendered as + ArgoCD sync-wave -2 (with the controller bootstrap IAM) so KRO can watch/CRUD the + children before the platform MicrovmSandbox instance (wave 1) reconciles. +*/}} +{{- if and .Values.microvm .Values.microvm.enabled }} +{{- with .Values.microvm.kroCapability }} +{{- if .enabled }} +{{- $cluster := $.Values.microvm.podIdentity.clusterName }} +{{- $account := $.Values.microvm.accountId | toString }} +{{- $role := .roleName | default (printf "%s-KROCapabilityRole" $cluster) }} +{{- $session := .sessionName | default "KRO" }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ $cluster }}-kro-microvm-graph + labels: + {{- include "agent-sandbox.labels" $ | nindent 4 }} + annotations: + argocd.argoproj.io/sync-wave: "-2" +rules: + # S3 artifact bucket (graph resource `bucket`). + - apiGroups: ["s3.services.k8s.aws"] + resources: ["buckets", "buckets/status"] + verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] + # Build + exec IAM roles (graph resources `buildRole`, `execRole`). + - apiGroups: ["iam.services.k8s.aws"] + resources: ["roles", "roles/status"] + verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] + # Platform image + (future) per-session instance (graph resource `image`; Microvm is + # driven imperatively by the shim but kept here so KRO can read/GC if ever graphed). + - apiGroups: ["lambdamicrovms.services.k8s.aws"] + resources: ["microvmimages", "microvmimages/status", "microvms", "microvms/status"] + verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ $cluster }}-kro-microvm-graph + labels: + {{- include "agent-sandbox.labels" $ | nindent 4 }} + annotations: + argocd.argoproj.io/sync-wave: "-2" +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ $cluster }}-kro-microvm-graph +subjects: + # The EKS access-entry username Managed KRO's controller presents. + - kind: User + name: "arn:aws:sts::{{ $account }}:assumed-role/{{ $role }}/{{ $session }}" + apiGroup: rbac.authorization.k8s.io +{{- end }} +{{- end }} +{{- end }} diff --git a/gitops/addons/charts/agent-sandbox-lambda/values.yaml b/gitops/addons/charts/agent-sandbox-lambda/values.yaml new file mode 100644 index 00000000..83a71921 --- /dev/null +++ b/gitops/addons/charts/agent-sandbox-lambda/values.yaml @@ -0,0 +1,159 @@ +# agent-sandbox-lambda β€” Flow D (Lambda MicroVM substrate) values. +# +# Opt-in second substrate for the Agent Sandbox capability. Disabled by default; +# set microvm.enabled=true (per-cluster overlay) to render it. Runs in the SAME +# namespace as the Kata agent-sandbox chart and reuses the same operator + CRDs β€” +# it only ADDS the Lambda-MicroVM image build (KRO) + the bridge/lifecycle shim. + +# Namespace the capability runs in (must match the agent-sandbox chart). +namespace: agent-sandbox-system + +# Warm-pool template name stem (the bridge SandboxTemplate is -microvm, +# matching the Kata chart's convention so df-run's claim step resolves it). +warmPool: + templateName: coder-sandbox + +# SandboxTemplate env-injection policy (same contract as the Kata template so Flow B +# is unchanged). +coderTemplate: + envVarsInjectionPolicy: Allowed + +# ── Lambda MicroVM (Flow D) ────────────────────────────────────────────────── +microvm: + # Master gate β€” Flow D stays dormant until a cluster opts in. + enabled: false + region: us-west-2 + # API group the generated MicrovmSandbox CRD is served under (KRO schema.group). + # MUST be "kro.run": EKS Managed KRO's controller only watches the kro.run group β€” + # an RGD whose schema.group is anything else (even a *.kro.run subdomain) never leaves + # state=Inactive ("cache sync timeout ... Resource=microvmsandboxes"), because the + # capability's controller identity (hub-KROCapabilityRole via AmazonEKSKROPolicy) has + # no list/watch on other groups. The generated CRD is microvmsandboxes.kro.run; the + # kind (MicrovmSandbox) is unchanged. Verified by group-probe on the hub 2026-08-03. + apiGroup: kro.run + # Bridge/lifecycle pod image: needs the AWS CLI that KNOWS the pre-GA lambda-microvms + # service (RunMicrovm/suspend/resume/terminate) AND kubectl. alpine/k8s bundles kubectl + # but its aws-cli 1.34 does NOT have lambda-microvms (verified: prints the service list + # = unrecognized). So use the glibc, always-current aws-cli v2 (has lambda-microvms) and + # fetch kubectl at start (bridge.sh / lifecycle loop do this, same as the security-agent). + bridgeImage: public.ecr.aws/aws-cli/aws-cli:latest + + # K8s API-server egress for the bridge (microvm-bridge-apiserver-egress NetworkPolicy). + # The bridge needs :443 to the API server to read the MicrovmSandbox handoff + annotate + # the Sandbox, but the shared coder-egress policy denies the service+VPC CIDRs. These + # scope the additive allow to the API server only. apiServerCidr = the `kubernetes` + # service ClusterIP /32; apiServerEndpointCidrs = the VPC range holding the apiserver + # endpoint IPs. Override per cluster (kubectl get svc kubernetes; get endpoints kubernetes). + apiServerCidr: "172.20.0.1/32" + apiServerEndpointCidrs: + - "10.0.0.0/16" + + # AWS account id. TWO consumers, both of which break cross-account without it: + # 1. the controller role ARN for the ACK PodIdentityAssociation (takes an ARN, + # not a role-ref) β€” templates/shim/10-rbac.yaml; + # 2. the GLOBALLY-UNIQUE suffix on the MicroVM artifact S3 bucket + # (-microvm-artifacts-) β€” templates/image/10-rgd-and-image.yaml. + # Leave EMPTY here: it is injected from the deploying cluster's aws_account_id + # annotation in gitops/addons/registry/sandbox.yaml, so no per-cluster overlay is + # needed. The RGD instance `require`s it when microvm.enabled=true. + accountId: "" + + # Controller bootstrap IAM (ALL-ACK: iam.services.k8s.aws Role + eks.services.k8s.aws + # PodIdentityAssociation β€” templates/shim/00-controller-pod-identity.yaml). This is + # the ONE IAM the KRO RGD can't self-create (creds-before-create). Managed by the + # Managed-ACK iam+eks controllers on the hub. The bridge/lifecycle SAs reuse this + # same role (they also call lambda-microvms). + podIdentity: + clusterName: hub + controllerNamespace: ack-system + controllerServiceAccount: ack-lambdamicrovms-controller + + # EKS Managed KRO runs its controller as the cluster's KRO capability role. On this + # cluster its k8s identity is the EKS access-entry username + # arn:aws:sts:::assumed-role/-KROCapabilityRole/KRO + # (session name "KRO"). AmazonEKSKROPolicy grants KRO its own kro.run perms but NOT + # CRUD on the ACK children the MicrovmSandbox RGD graph creates (s3 buckets, iam + # roles, lambdamicrovms images/instances) β€” so KRO's instance reconcile hits + # "forbidden: ... cannot get resource buckets". templates/shim/40-kro-graph-rbac.yaml + # grants exactly those child kinds to this identity. Override roleName/sessionName if + # your cluster's capability wiring differs (confirm via + # aws eks describe-access-entry --principal-arn .../KROCapabilityRole). + kroCapability: + enabled: true + roleName: "" # defaults to "-KROCapabilityRole" + sessionName: KRO + + # The ONE platform image built by KRO/ACK (10-rgd-microvm-image.yaml). Built ONCE + # per cluster; the shim reads its status (imageARN + executionRoleARN) to RunMicrovm + # per session. Lambda MicroVM is ARM_64-ONLY. The artifact is a zip containing the + # coder app + a Dockerfile β€” Lambda MicroVM's codeArtifact.uri is S3-ONLY, NOT an ECR + # image ref (the Dockerfile inside MAY pull private ECR base layers; the build role + # keeps ecr:Get*/BatchGetImage). Publish the arm64 coder artifact before enabling. + # baseImageARN: arn:aws:lambda::aws:microvm-image:al2023-1 + baseImageARN: "" + + # codeArtifactKey is the OBJECT KEY ONLY β€” deliberately NOT a full s3:// URI. The bucket + # half is composed inside the RGD from the bucket resource it actually creates: + # uri: s3://${bucket.spec.name}/${schema.spec.codeArtifactKey} + # ONE source of truth. A full URI value (as this held before) restated the + # `-microvm-artifacts-` bucket name a SECOND time β€” once in the + # Bucket resource, once in the URI β€” and the two drifted twice already: the un-suffixed + # bucket gave 409 BucketAlreadyExists + "Access denied when fetching artifact from S3" + # (CREATE_FAILED), and any change to image.name would have pointed the build at a bucket + # nothing creates. With a bare key, image.name and accountId can change freely and the + # URI follows the bucket automatically β€” nothing to keep in sync, in any overlay or in the + # registry (which CANNOT compose it anyway: its Go template only sees cluster + # metadata/annotations, never another Helm value). + # BUMP THIS KEY TO REBUILD the image β€” Lambda MicroVM does not rebuild when the same S3 + # key is overwritten (SUBSTRATE-BENCHMARK.md pitfall 8). Publish the arm64 zip to + # s3://-microvm-artifacts-/ before enabling. + # r8: Dockerfile ARG CODER_IMAGE repointed from the chart AUTHOR's account to + # the deploying account's ECR. The old value was unpullable β€” cross-account ECR needs + # a repository policy on the far side, which we cannot set β€” so every build failed at + # base-image pull even once the S3 artifact fetch was fixed. That ARG CANNOT be + # templated (the MicrovmImage CRD's codeArtifact has only `uri`; no build-args field), + # so it is edited in the Dockerfile and baked into the ZIP. Build the arm64 base + # and publish it to YOUR account's ECR first β€” see docs/dark-factory/FLOW-D-ENABLEMENT.md. + # r7: hook-server /run guard keyed on a per-invocation run-id (issue+note hash) instead + # of a one-shot boolean, so a RESUMED VM (fix round) accepts a fresh /run and re-runs + # the coder β€” the resume path was a no-op because the snapshot froze coderStarted=true. + # Also truncates /tmp/coder.log per run so the bridge's "PR pushed" grep can't match + # the previous round's line. + # r6: fix-round iterate note forwarded (bridge payload -> hook-server -> coder env). + codeArtifactKey: "coder-v0.2.5-arm64-r8.zip" + image: + # Render the single committed MicrovmSandbox instance that triggers the build. + enabled: true + # Name of that platform image object; the bridge reads its status by this name. + name: coder + + # Idle policy for RunMicrovm (auto-suspend/resume). EXPLICIT suspend/resume across + # the reviewβ†’fix loop is driven by the microvm-lifecycle controller off + # Sandbox.operatingMode β€” see lifecycle below and project_flow_d_lifecycle memory: + # coder codes β†’ SUSPEND β†’ agents review β†’ (fix findings) β†’ RESUME same VM β†’ + # … loop until cleared β†’ merge/exit β†’ TERMINATE. + defaults: + # maxIdleDurationSeconds: how long a RUNNING VM may sit idle (no inbound) before the + # runtime suspends it. Must exceed a coder run (a few min) β€” the bridge suspends + # explicitly anyway, this is just a backstop. + maxIdleDurationSeconds: 1800 + # suspendedDurationSeconds: how long a SUSPENDED VM is kept before the runtime + # AUTO-TERMINATES it. THIS IS CRITICAL for the reviewβ†’fix loop: the VM is suspended + # while the external review agents run (~8–15 min) and then waits for a human to post + # "fix findings" (minutes to hours). At the old 300s (5 min) the VM was ALWAYS + # auto-terminated before the fix round, so RESUME hit "has been terminated and its + # state cannot be changed" and the same-VM warm-resume was impossible. Keep it + # suspended long enough to span a realistic review+human cycle (24h); teardown + # terminates it explicitly at merge, so this only bounds abandoned PRs. + suspendedDurationSeconds: 86400 + + # microvm-lifecycle controller (30-microvm-lifecycle.yaml) β€” reconciles + # Sandbox.operatingMode Running↔Suspended β†’ suspend/resume-microvm. Loop interval. + lifecycle: + intervalSeconds: 15 + + # Lambda-MicroVM warm pool the df-run claim binds when the darkfactory-lambda label + # fires (Flow D). Small β€” bridge pods are tiny (the real coder runs in the MicroVM). + warmPool: + name: coder-warmpool-microvm + targetIdle: 1 diff --git a/gitops/addons/charts/agent-sandbox-operator/Chart.yaml b/gitops/addons/charts/agent-sandbox-operator/Chart.yaml new file mode 100644 index 00000000..dca12000 --- /dev/null +++ b/gitops/addons/charts/agent-sandbox-operator/Chart.yaml @@ -0,0 +1,15 @@ +apiVersion: v2 +name: agent-sandbox +description: Kubernetes controller for managing agent sandboxes +type: application +version: 0.1.0 +keywords: + - agent + - sandbox + - kubernetes +home: https://github.com/kubernetes-sigs/agent-sandbox +sources: + - https://github.com/kubernetes-sigs/agent-sandbox +maintainers: + - name: Kubernetes SIGs + url: https://github.com/kubernetes-sigs diff --git a/gitops/addons/charts/agent-sandbox-operator/README.md b/gitops/addons/charts/agent-sandbox-operator/README.md new file mode 100644 index 00000000..2d836529 --- /dev/null +++ b/gitops/addons/charts/agent-sandbox-operator/README.md @@ -0,0 +1,110 @@ +# Agent Sandbox Helm Chart + +This Helm chart installs the Agent Sandbox controller, which manages `Sandbox` resources on Kubernetes. +CRDs are bundled in the `crds/` directory and are installed automatically by Helm before any other resources. + +## Installation + +### Basic install + +```bash +helm install agent-sandbox ./helm/ \ + --namespace agent-sandbox-system \ + --create-namespace \ + --set image.tag= +``` + +### Install with extensions enabled + +Extensions add support for `SandboxWarmPool`, `SandboxTemplate`, and `SandboxClaim` resources. + +```bash +helm install agent-sandbox ./helm/ \ + --namespace agent-sandbox-system \ + --create-namespace \ + --set image.tag= \ + --set controller.extensions=true +``` + +### Install into an existing namespace + +```bash +helm install agent-sandbox ./helm/ \ + --namespace my-namespace \ + --set image.tag= \ + --set namespace.create=false \ + --set namespace.name=my-namespace +``` + +## Upgrading + +```bash +helm upgrade agent-sandbox ./helm/ \ + --namespace agent-sandbox-system \ + --reuse-values \ + --set image.tag= +``` + +> **Note**: Helm does not upgrade CRDs placed in `crds/` automatically. To update CRDs manually after a chart version bump, apply them directly: +> +> ```bash +> kubectl apply -f helm/crds/ +> ``` + +### v1alpha1 β†’ v1beta1 storage migration + +Upgrades to chart versions that move CRDs from `v1alpha1` to `v1beta1` require a manual storage migration using the `dev/tools/migrate.sh` script. + +See [`docs/api-migration-guide.md`](../docs/api-migration-guide.md) for full details, sequence of steps, and operational guidelines. + +## Uninstallation + +```bash +helm uninstall agent-sandbox --namespace agent-sandbox-system +``` + +> **Note**: Helm does not delete CRDs on uninstall. To remove all CRDs and their associated custom resources: +> +> ```bash +> kubectl delete -f helm/crds/ +> ``` +> +> Warning: This will delete **all** `Sandbox`, `SandboxWarmPool`, `SandboxTemplate`, and `SandboxClaim` objects across all namespaces. + +## Configuration + +The following table lists the configurable parameters and their defaults. + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `image.tag` | Controller image tag β€” **required** | `""` | +| `image.repository` | Controller image repository | `registry.k8s.io/agent-sandbox/agent-sandbox-controller` | +| `image.pullPolicy` | Image pull policy | `IfNotPresent` | +| `replicaCount` | Number of controller replicas | `1` | +| `namespace.create` | Create the namespace as part of the release | `true` | +| `namespace.name` | Namespace to deploy into | `agent-sandbox-system` | +| `controller.leaderElect` | Enable leader election | `true` | +| `controller.leaderElectionNamespace` | Namespace for the leader election resource (auto-detected if empty) | `""` | +| `controller.clusterDomain` | Kubernetes cluster domain for service FQDN generation | `"cluster.local"` | +| `controller.kubeApiQps` | Client-side QPS limit for the Kubernetes API client (`-1` = unlimited) | `-1.0` | +| `controller.kubeApiBurst` | Burst limit for the Kubernetes API client | `10` | +| `controller.sandboxConcurrentWorkers` | Max concurrent reconciles for the Sandbox controller | `1` | +| `controller.sandboxClaimConcurrentWorkers` | Max concurrent reconciles for the SandboxClaim controller (extensions only) | `1` | +| `controller.sandboxWarmPoolConcurrentWorkers` | Max concurrent reconciles for the SandboxWarmPool controller (extensions only) | `1` | +| `controller.sandboxTemplateConcurrentWorkers` | Max concurrent reconciles for the SandboxTemplate controller (extensions only) | `1` | +| `controller.enableTracing` | Enable OpenTelemetry tracing via OTLP | `false` | +| `controller.enablePprof` | Enable CPU profiling endpoint on the metrics server | `false` | +| `controller.enablePprofDebug` | Enable all pprof endpoints (implies enablePprof) | `false` | +| `controller.pprofBlockProfileRate` | Block profile sampling rate when pprof debug is enabled | `1000000` | +| `controller.pprofMutexProfileFraction` | Mutex contention sampling rate when pprof debug is enabled | `10` | +| `controller.extraArgs` | Additional flags not listed above (e.g. zap logging flags) | `[]` | +| `controller.extensions` | Enable extensions controller (WarmPool, Template, Claim) | `false` | +| `resources` | CPU/memory resource requests and limits | `{}` | +| `nodeSelector` | Node selector for the controller pod | `{}` | +| `tolerations` | Tolerations for the controller pod | `[]` | +| `affinity` | Affinity rules for the controller pod | `{}` | +| `podSecurityContext` | Pod `securityContext`; only rendered when set (e.g. Kyverno / Pod Security) | `null` | +| `containerSecurityContext` | Container `securityContext` for the controller; only rendered when set | `null` | +| `podAnnotations` | Annotations added to the controller pod template (e.g. service-mesh sidecar toggles, Prometheus scrape autodiscovery) | `{}` | +| `podLabels` | Extra labels added to the controller pod template alongside the chart's selector labels (selector labels take precedence on conflict) | `{}` | +| `webhookServiceName` | Name of the conversion webhook Service | `agent-sandbox-webhook-service` | diff --git a/gitops/addons/charts/agent-sandbox-operator/crds/agents.x-k8s.io_sandboxes.yaml b/gitops/addons/charts/agent-sandbox-operator/crds/agents.x-k8s.io_sandboxes.yaml new file mode 100644 index 00000000..aa2f7b52 --- /dev/null +++ b/gitops/addons/charts/agent-sandbox-operator/crds/agents.x-k8s.io_sandboxes.yaml @@ -0,0 +1,8035 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.21.0 + name: sandboxes.agents.x-k8s.io +spec: + group: agents.x-k8s.io + names: + kind: Sandbox + listKind: SandboxList + plural: sandboxes + shortNames: + - sandbox + singular: sandbox + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=='Ready')].status + name: Ready + type: string + - jsonPath: .status.conditions[?(@.type=='Ready')].reason + name: Reason + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + operatingMode: + default: Running + enum: + - Running + - Suspended + 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 + schedulingGroup: + properties: + podGroupName: + type: string + type: object + 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 + userAnnotations: + additionalProperties: + type: string + type: object + 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 + service: + type: boolean + shutdownPolicy: + default: Retain + enum: + - Delete + - Retain + type: string + shutdownTime: + format: date-time + type: string + volumeClaimTemplates: + items: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + name: + type: string + 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: array + x-kubernetes-list-type: atomic + required: + - podTemplate + 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 + nodeName: + type: string + podIPs: + items: + type: string + type: array + selector: + type: string + service: + type: string + serviceFQDN: + type: string + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} + - deprecated: true + deprecationWarning: agents.x-k8s.io/v1alpha1 Sandbox is deprecated; use agents.x-k8s.io/v1beta1 + Sandbox instead + name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + 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 + schedulingGroup: + properties: + podGroupName: + type: string + type: object + 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 + userAnnotations: + additionalProperties: + type: string + type: object + 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 + replicas: + default: 1 + format: int32 + maximum: 1 + minimum: 0 + type: integer + service: + type: boolean + shutdownPolicy: + default: Retain + enum: + - Delete + - Retain + type: string + shutdownTime: + format: date-time + type: string + volumeClaimTemplates: + items: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + name: + type: string + 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: array + x-kubernetes-list-type: atomic + required: + - podTemplate + 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 + podIPs: + items: + type: string + type: array + replicas: + format: int32 + minimum: 0 + type: integer + selector: + type: string + service: + type: string + serviceFQDN: + type: string + type: object + required: + - spec + type: object + served: true + storage: false + subresources: + scale: + labelSelectorPath: .status.selector + specReplicasPath: .spec.replicas + statusReplicasPath: .status.replicas + status: {} + conversion: + strategy: Webhook + webhook: + conversionReviewVersions: + - v1 + - v1beta1 + clientConfig: + service: + name: agent-sandbox-webhook-service + namespace: agent-sandbox-system + path: /convert diff --git a/gitops/addons/charts/agent-sandbox-operator/crds/extensions.agents.x-k8s.io_sandboxclaims.yaml b/gitops/addons/charts/agent-sandbox-operator/crds/extensions.agents.x-k8s.io_sandboxclaims.yaml new file mode 100644 index 00000000..11caac28 --- /dev/null +++ b/gitops/addons/charts/agent-sandbox-operator/crds/extensions.agents.x-k8s.io_sandboxclaims.yaml @@ -0,0 +1,396 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.21.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: + - additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=='Ready')].status + name: Ready + type: string + - jsonPath: .status.sandbox.name + name: Sandbox + type: string + - jsonPath: .status.conditions[?(@.type=='Ready')].reason + name: Reason + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + additionalPodMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + env: + items: + properties: + containerName: + type: string + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + lifecycle: + properties: + shutdownPolicy: + default: Retain + enum: + - Delete + - DeleteForeground + - Retain + type: string + shutdownTime: + format: date-time + type: string + ttlSecondsAfterFinished: + format: int32 + minimum: 0 + type: integer + type: object + volumeClaimTemplates: + items: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + name: + type: string + 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: array + x-kubernetes-list-type: atomic + warmPoolRef: + properties: + name: + type: string + required: + - name + type: object + required: + - warmPoolRef + 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 + podIPs: + items: + type: string + type: array + type: object + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} + - deprecated: true + deprecationWarning: extensions.agents.x-k8s.io/v1alpha1 SandboxClaim is deprecated; + use extensions.agents.x-k8s.io/v1beta1 SandboxClaim instead + name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + additionalPodMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + env: + items: + properties: + containerName: + type: string + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + lifecycle: + properties: + shutdownPolicy: + default: Retain + enum: + - Delete + - DeleteForeground + - Retain + type: string + shutdownTime: + format: date-time + type: string + ttlSecondsAfterFinished: + format: int32 + minimum: 0 + type: integer + type: object + sandboxTemplateRef: + properties: + name: + type: string + required: + - name + type: object + warmpool: + default: default + type: string + 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 + podIPs: + items: + type: string + type: array + type: object + type: object + required: + - spec + type: object + served: true + storage: false + subresources: + status: {} + conversion: + strategy: Webhook + webhook: + conversionReviewVersions: + - v1 + - v1beta1 + clientConfig: + service: + name: agent-sandbox-webhook-service + namespace: agent-sandbox-system + path: /convert diff --git a/gitops/addons/charts/agent-sandbox-operator/crds/extensions.agents.x-k8s.io_sandboxtemplates.yaml b/gitops/addons/charts/agent-sandbox-operator/crds/extensions.agents.x-k8s.io_sandboxtemplates.yaml new file mode 100644 index 00000000..0ea8c920 --- /dev/null +++ b/gitops/addons/charts/agent-sandbox-operator/crds/extensions.agents.x-k8s.io_sandboxtemplates.yaml @@ -0,0 +1,8286 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.21.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: v1beta1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + envVarsInjectionPolicy: + default: Disallowed + enum: + - Allowed + - Overrides + - Disallowed + type: string + 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 + schedulingGroup: + properties: + podGroupName: + type: string + type: object + 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 + userAnnotations: + additionalProperties: + type: string + type: object + 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 + service: + type: boolean + volumeClaimTemplates: + items: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + name: + type: string + 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: array + x-kubernetes-list-type: atomic + volumeClaimTemplatesPolicy: + default: Disallowed + enum: + - Disallowed + - Allowed + - Overrides + type: string + required: + - podTemplate + type: object + required: + - spec + type: object + served: true + storage: true + - deprecated: true + deprecationWarning: extensions.agents.x-k8s.io/v1alpha1 SandboxTemplate is deprecated; + use extensions.agents.x-k8s.io/v1beta1 SandboxTemplate instead + name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + envVarsInjectionPolicy: + default: Disallowed + enum: + - Allowed + - Overrides + - Disallowed + type: string + 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 + schedulingGroup: + properties: + podGroupName: + type: string + type: object + 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 + userAnnotations: + additionalProperties: + type: string + type: object + 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 + service: + type: boolean + volumeClaimTemplates: + items: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + name: + type: string + 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: array + x-kubernetes-list-type: atomic + required: + - podTemplate + type: object + required: + - spec + type: object + served: true + storage: false + conversion: + strategy: Webhook + webhook: + conversionReviewVersions: + - v1 + - v1beta1 + clientConfig: + service: + name: agent-sandbox-webhook-service + namespace: agent-sandbox-system + path: /convert diff --git a/gitops/addons/charts/agent-sandbox-operator/crds/extensions.agents.x-k8s.io_sandboxwarmpools.yaml b/gitops/addons/charts/agent-sandbox-operator/crds/extensions.agents.x-k8s.io_sandboxwarmpools.yaml new file mode 100644 index 00000000..9af4c2b3 --- /dev/null +++ b/gitops/addons/charts/agent-sandbox-operator/crds/extensions.agents.x-k8s.io_sandboxwarmpools.yaml @@ -0,0 +1,167 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.21.0 + name: sandboxwarmpools.extensions.agents.x-k8s.io +spec: + group: extensions.agents.x-k8s.io + names: + kind: SandboxWarmPool + listKind: SandboxWarmPoolList + plural: sandboxwarmpools + shortNames: + - swp + singular: sandboxwarmpool + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.readyReplicas + name: Ready + type: integer + - jsonPath: .spec.replicas + name: Desired + type: integer + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + replicas: + default: 1 + format: int32 + minimum: 0 + type: integer + sandboxTemplateRef: + properties: + name: + type: string + required: + - name + type: object + updateStrategy: + properties: + type: + default: OnReplenish + enum: + - Recreate + - OnReplenish + type: string + type: object + required: + - sandboxTemplateRef + type: object + status: + properties: + readyReplicas: + format: int32 + type: integer + replicas: + format: int32 + type: integer + selector: + type: string + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + scale: + labelSelectorPath: .status.selector + specReplicasPath: .spec.replicas + statusReplicasPath: .status.replicas + status: {} + - additionalPrinterColumns: + - jsonPath: .status.readyReplicas + name: Ready + type: integer + - jsonPath: .spec.replicas + name: Desired + type: integer + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + deprecated: true + deprecationWarning: extensions.agents.x-k8s.io/v1alpha1 SandboxWarmPool is deprecated; + use extensions.agents.x-k8s.io/v1beta1 SandboxWarmPool instead + name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + replicas: + format: int32 + minimum: 0 + type: integer + sandboxTemplateRef: + properties: + name: + type: string + required: + - name + type: object + updateStrategy: + properties: + type: + default: OnReplenish + enum: + - Recreate + - OnReplenish + type: string + type: object + required: + - replicas + - sandboxTemplateRef + type: object + status: + properties: + readyReplicas: + format: int32 + type: integer + replicas: + format: int32 + type: integer + selector: + type: string + type: object + required: + - spec + type: object + served: true + storage: false + subresources: + scale: + labelSelectorPath: .status.selector + specReplicasPath: .spec.replicas + statusReplicasPath: .status.replicas + status: {} + conversion: + strategy: Webhook + webhook: + conversionReviewVersions: + - v1 + - v1beta1 + clientConfig: + service: + name: agent-sandbox-webhook-service + namespace: agent-sandbox-system + path: /convert diff --git a/gitops/addons/charts/agent-sandbox-operator/files/migrate.sh b/gitops/addons/charts/agent-sandbox-operator/files/migrate.sh new file mode 100755 index 00000000..4255a03a --- /dev/null +++ b/gitops/addons/charts/agent-sandbox-operator/files/migrate.sh @@ -0,0 +1,448 @@ +#!/usr/bin/env bash +# Copyright 2026 The Kubernetes Authors. +# +# 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. +# +# migrate.sh β€” agent-sandbox v1alpha1 -> v1beta1 migration helper. +# +# Two phases, both idempotent: +# +# --phase=bootstrap Pre-upgrade. Scans every v1alpha1 SandboxClaim +# cluster-wide and, for each unique template referenced +# by a cold-start claim with no specific pool, creates +# a "shadow-pool-