Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 12 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -243,11 +243,10 @@ desk deploy --profile <name> --dir ./my-watt-project --envfile ./my-watt-project

#### Deploy through ICC's deploy API (`--via-icc`)

By default `desk` templates the Deployment + Service and applies them directly
(the skew-protection `observe` model: you create the workload, ICC manages
routing). Use `--via-icc` to instead drive the deploy through ICC's deploy API,
which lets you exercise the `advise` actuation mode (`manage` is temporarily
parked -- see the mode list below):
By default `desk` templates the Deployment + Service and applies them directly.
Use `--via-icc` to instead drive the deploy through ICC's deploy API: ICC creates
the Deployment + Service itself and the pod registers back (the CI path a customer
uses -- the pipeline holds only a deploy token):

```sh
desk deploy --profile skew-protection --via-icc \
Expand All @@ -260,16 +259,10 @@ a CI needs only the token, image, and version, never the application UUID. Pass
`--app-id <id>` to force the app-scoped route instead (e.g. when driving it with
an admin cookie).

What happens depends on the app's mode (Settings → Skew Protection → Mode):

* `manage` — **parked**: ICC returns `503 ManageModeUnavailable` while the
ICC-owned creation path is reworked; use `observe` or `advise`. (When restored:
ICC creates the Deployment + Service itself and `desk` applies nothing.)
* `advise` — ICC returns the manifests as a plan and `desk` applies them with
`kubectl` (use `--dry-run` to print the plan without applying). For a strictly
read-only workflow — fetch the plan, inspect it, and apply it yourself — use
[`get-plan`](#get-plan) instead; `--via-icc` is the one-shot that applies for you.
* `observe` — ICC rejects the deploy API (this is the default direct path above).
The deploy API always creates the workload; it does not gate on the app's
actuation mode (the mode now only governs version routing). For a strictly
read-only workflow -- fetch the manifests, inspect them, and apply them yourself
-- use [`get-plan`](#get-plan) instead.

Flags:

Expand All @@ -282,10 +275,10 @@ Flags:
disabled for this call (local self-signed cert); for local testing only.

`--via-icc` still builds/pushes the image when `--dir` is used; the image must
exist before ICC can reference it (`--image <ref>` for a prebuilt one). (`manage`
mode, when restored, also requires the `plt-pod-manager` RBAC to create
Deployments/Services -- shipped in the helm chart; run `helm upgrade`.) See
`skew-protection/TESTING.md` for the full manual test walkthrough.
exist before ICC can reference it (`--image <ref>` for a prebuilt one). ICC needs
the deployer RBAC to create Deployments/Services/pull Secrets -- gated behind
`services.icc.features.deployer.enable` in the helm chart (run `helm upgrade`).
See `skew-protection/TESTING.md` for the full manual test walkthrough.

### `get-plan`

Expand Down
10 changes: 4 additions & 6 deletions cli/deploy.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,10 +122,9 @@ export default async function cli (argv) {
}
}

// ICC-driven deploy: hand the image to ICC's deploy API and let the app's
// actuation mode decide (manage = ICC creates the workload; advise = ICC
// returns manifests that desk applies). This is the CI path a customer uses,
// and the way to exercise manage/advise modes end to end.
// ICC-driven deploy: hand the image to ICC's deploy API. ICC creates the
// workload (Deployment + Service) itself and the pod registers back. This is
// the CI path a customer uses -- the pipeline holds only a deploy token.
if (args['via-icc']) {
// --app-id is optional: with a deploy token ICC resolves the application from
// the token (the CI needs only the token). Pass --app-id to force the
Expand All @@ -148,8 +147,7 @@ export default async function cli (argv) {
maxReplicas,
env: Object.keys(envVars).length ? envVars : undefined
})
info(`ICC actuation mode: ${result.mode}`)
await handleIccDeploy(context, args.namespace, result, args['dry-run'])
handleIccDeploy(result)
return
}

Expand Down
42 changes: 9 additions & 33 deletions lib/icc.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { addToRun } from './run-directory.js'
import { spawn, info, warn } from './utils.js'
import { info, warn } from './utils.js'

// Drive a deploy through ICC's deploy API instead of templating + applying the
// workload directly. Lets you exercise the skew-protection actuation modes:
// manage -> ICC creates the Deployment + Service itself (nothing to apply here)
// advise -> ICC returns the manifests as a plan; desk applies them (below)
// observe -> ICC rejects the deploy API (you create workloads yourself)
// workload directly. ICC creates the Deployment + Service itself (the deploy API
// always applies); the pod then registers and the version goes active. The
// read-only manifests are available via /deploy/plan (getDeployPlan).
//
// Auth is a scoped deploy token (Bearer plt_deploy_...), the same CI path a
// customer would use. icc.plt uses a local/self-signed cert, so TLS verification
Expand Down Expand Up @@ -46,37 +45,14 @@ export async function deployViaIcc ({ iccUrl, appId, token, image, version, host
}
}

// Apply the manifests from an advise-mode plan via kubectl (the external actor's
// job). Each step's manifest is written to the run dir and applied.
export async function applyIccPlan (context, namespace, plan, dryRun) {
let applied = 0
for (const step of plan) {
if (!step.manifest) continue
const name = step.manifest?.metadata?.name ?? `${step.kind ?? 'resource'}-${applied}`
info(` plan: ${step.kind}/${step.action} ${name}`)
if (step.command) info(` ${step.command}`)
if (dryRun) { applied++; continue }
const filePath = await addToRun(context.runDir, `icc-${step.kind}-${name}.json`, JSON.stringify(step.manifest))
await spawn('kubectl', [`--namespace=${namespace}`, 'apply', `--filename=${filePath}`])
applied++
}
return applied
}

// Report the outcome of an ICC deploy and, in advise mode, apply the plan.
export async function handleIccDeploy (context, namespace, result, dryRun) {
// Report the outcome of an ICC deploy. The deploy API always creates the
// workload, so the response is { deployed: true, controllerName, serviceName }.
export function handleIccDeploy (result) {
if (result.deployed) {
info('\nManage mode: ICC created the Deployment + Service. Pods will register and the version will go active.')
return
}
const plan = result.plan ?? []
if (result.pendingApply || plan.length > 0) {
info(`\nAdvise mode: ICC returned a ${plan.length}-step plan. Applying it now (external actor):`)
const applied = await applyIccPlan(context, namespace, plan, dryRun)
info(`\nApplied ${applied} manifest(s). ICC confirms the version active once pods register and the gateway route is Accepted.`)
info(`\nICC created the workload (${result.controllerName ?? 'Deployment'} + Service). The pod will register and the version will go active.`)
return
}
warn('ICC deploy returned no plan and deployed=false; nothing to do.')
warn('ICC deploy returned deployed=false; nothing to do.')
}

// Shared ICC request helper. icc.plt uses a local/self-signed cert, so TLS
Expand Down
23 changes: 23 additions & 0 deletions profiles/development.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,18 @@ platformatic:
enable: false
ffc:
enable: false
deployer:
enable: true # ICC creates workloads via the deploy API (grants create/patch on Deployments/Services/pull Secrets)
skew_protection:
enable: true
auto_cleanup: false # Keep expired Deployments/Services for inspection
http_grace_period_ms: 120000 # 2 min: keep >= traffic_window_ms
http_max_alive_ms: 900000 # 15 min for e2e testing
workflow_grace_period_ms: 300000 # 5 min for demo
workflow_max_alive_ms: 900000 # 15 min for demo
check_interval_ms: 10000 # 10 seconds
traffic_window_ms: 60000 # 1 min
cookie_max_age: 43200 # 12h in seconds (keep default)
icc_jobs:
enable: true

Expand Down Expand Up @@ -81,3 +93,14 @@ platformatic:
enable: false

log_level: debug

workflow:
hotReload: true
localRepo: "{{ WORKFLOW_REPO }}"
workingDir: /app/packages/workflow

replicas:
min: 1
max: 1

log_level: info
3 changes: 2 additions & 1 deletion profiles/skew-protection.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,10 @@ platformatic:
enable: false
ffc:
enable: false
deployer:
enable: true # ICC creates workloads via the deploy API (grants create/patch on Deployments/Services/pull Secrets)
skew_protection:
enable: true
manage_mode: true # Grant ICC create/patch on Deployments+Services so manage-mode deploys work (testing profile)
auto_cleanup: false # Keep expired Deployments/Services for inspection
http_grace_period_ms: 120000 # 2 min: keep >= traffic_window_ms
http_max_alive_ms: 900000 # 15 min for e2e testing
Expand Down