Skip to content

feat: invoke customActions from deploy hooks and verify (skaffold/v4beta15) - #10067

Open
bogdannazarenko wants to merge 10 commits into
GoogleContainerTools:mainfrom
unburdenedio:feat/lifecycle-hooks-run-custom-actions
Open

feat: invoke customActions from deploy hooks and verify (skaffold/v4beta15)#10067
bogdannazarenko wants to merge 10 commits into
GoogleContainerTools:mainfrom
unburdenedio:feat/lifecycle-hooks-run-custom-actions

Conversation

@bogdannazarenko

@bogdannazarenko bogdannazarenko commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

Invoke customActions from deploy hooks and verify

A customAction is a reusable, containerized task. Today it can only be run standalone via skaffold exec. This PR lets the same action definition be invoked from two more places, so users don't have to re-implement the task in each shape:

  1. Deploy lifecycle hooksdeploy.*.hooks.before/after: [action: {name: ...}]
  2. Verify test casesverify[].action: {name: ...} instead of an inline container

Both reuse the existing customActions runtime (the same one skaffold exec drives), so an action runs identically — including its own executionMode, timeout, failFast, and (once #10066 lands) runArgs — regardless of where it's triggered from.

customActions:
  - name: db-migrate
    containers: [{ name: migrator, image: myorg/migrator, command: ["./migrate"] }]
  - name: smoke-test
    containers: [{ name: smoke, image: myorg/smoke, command: ["./smoke"] }]

deploy:
  kubectl:
    hooks:
      before: [ { action: { name: db-migrate } } ]   # (1) deploy hook

verify:
  - name: smoke
    action: { name: smoke-test }                     # (2) verify test case
  - name: health                                     # inline container still works
    container: { name: health, image: alpine:3.20, command: ["/bin/sh","-c","wget -qO- http://svc/healthz"] }

Hooks (action: deploy hook)

  • New action: union member on DeployHookItem, alongside host:/container:.
  • hooks.ActionInvoker interface + SetDefaultActionInvoker, wired once in runner.New after GetActionsRunner. The hooks package stays free of any pkg/skaffold/actions import (no new cycle); a small actionsRunnerInvoker adapter in pkg/skaffold/runner bridges the two.
  • validateActionHookRefs: unknown / empty action references fail at load time across kubectl, helm, and kpt deployers.

Verify (verify[].action)

  • New optional action field on VerifyTestCase; container is now conditionally required (validation enforces exactly one of container / action).
  • GetVerifier skips action-referencing test cases (they aren't container verifiers); SkaffoldRunner.Verify dispatches them to r.actionsRunner.Exec after the container verifier completes.
  • Validation reuses a shared knownActionNames helper (also used by the hook validator) to reject unknown references.
  • Docs section in verify.md; integration fixture integration/testdata/verify-custom-action mixes an inline container test case with an action-referencing one.

Design note — runArgs compose for free. Because both entry points delegate to the actions runner rather than copying the action's containers into the hook/verify container path, the action's declared runArgs (PR #10066) are applied automatically. The two PRs are orthogonal — only a trivial rebase ties them together (both sit on skaffold/v4beta15).

Schema: skaffold/v4beta15 (final commit)

v4beta14 is released, so the new fields required cutting v4beta15 (frozen v4beta14 package + upgrade path). Per the schema-last convention, the whole generated cut — v4beta15.json, CLI reference, docs version, and the apiVersion bump of the unreleased integration/{examples,testdata} — is isolated in the last commit, keeping the hand-written feature diff reviewable. The cut also corrects v4beta14.json (an earlier WIP had captured a stray ActionHook into the released schema).

Notes

  • examples/hooks-action/ was removed; an example using the unreleased action: field can't satisfy the released-version constraint on examples/, so it lives only under integration/examples/ until v4beta15 ships.
  • Per-commit note: the feature commits reference the new schema fields, which are defined in the final (schema) commit — so they don't build in isolation, but the PR HEAD does and CI runs on HEAD. Say the word if you'd prefer schema-first ordering.

Out of scope

  • CloudRun deploy hooks (still []HostHook only), pre/post-delete hooks, render/build action hooks.

Tests

  • go build ./..., go test ./pkg/skaffold/hooks/... ./pkg/skaffold/schema/... ./pkg/skaffold/runner/... ./pkg/skaffold/verify/... ./cmd/..., hack/schemas, ./hack/check-samples.sh — all green locally.
  • Docker-gated integration (TestLocalVerifyWithCustomActionRef, hook examples) defer to CI.

@bogdannazarenko
bogdannazarenko requested a review from a team as a code owner April 24, 2026 17:03

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces 'Action hooks,' allowing users to reference custom actions as deployment lifecycle hooks. The implementation includes the ActionInvoker interface to prevent package cycles, updated validation to ensure action names exist, and new documentation and examples. A review comment points out that adding fields to the v4beta14 schema might violate versioning policies if that version is already frozen, suggesting a move to a new schema version.

Comment thread pkg/skaffold/schema/latest/config.go
@bogdannazarenko
bogdannazarenko force-pushed the feat/lifecycle-hooks-run-custom-actions branch from 6453e17 to c8d0251 Compare April 24, 2026 17:15
@bogdannazarenko

bogdannazarenko commented Apr 24, 2026

Copy link
Copy Markdown
Contributor Author

Waiting on maintainers to cut new schema version on main branch

@bogdannazarenko
bogdannazarenko force-pushed the feat/lifecycle-hooks-run-custom-actions branch from c8d0251 to d7e5025 Compare April 24, 2026 17:37
@bogdannazarenko
bogdannazarenko marked this pull request as draft April 24, 2026 20:15
Adds a new ActionHook to the deploy lifecycle-hook union so users can
reference an existing customActions entry from
deploy.*.hooks.before / .after instead of re-implementing the
containerized task as a host hook with ad-hoc docker run arguments:

    deploy:
      kubectl: { ... }
      hooks:
        before:
          - action: { name: pre-deploy-check }
        after:
          - action: { name: migrate-db }
    customActions:
      - name: migrate-db
        containers: [{ image: myorg/migrator:latest, ... }]

Implementation
  * schema: ActionHook{Name} joins HostHook and ContainerHook as a
    third oneOf=deploy_hook union member on DeployHookItem.
  * hooks: new package-level ActionInvoker interface plus a
    SetDefaultActionInvoker setter so deploy hook runners can dispatch
    without an import cycle back into pkg/skaffold/actions. The
    invoker is registered once in runner.New immediately after
    GetActionsRunner succeeds, mirroring how
    SetupStaticEnvOptions is wired.
  * runner: small actionsRunnerInvoker adapter satisfies the new
    interface by delegating to ActionsRunner.Exec with nil artifact
    slices (hooks do not themselves build).
  * deploy.go: the run() dispatch loop recognises ActionHook and
    calls runActionHook, which returns a helpful error if no invoker
    is wired (e.g. from tests that stub hooks out).

CloudRunDeployHooks still accepts only []HostHook and is therefore
unaffected; wiring ActionHook into CloudRun is a follow-up once the
CloudRun deployer grows its own union type.
Unit coverage for the new ActionHook dispatch path:
  - happy path fires pre then post hooks in declaration order
  - first failure aborts the remaining hooks and surfaces both the
    phase and the action name in the wrapped error
  - a config with ActionHook but no registered invoker returns the
    'no custom-actions runner available' sentinel

Also wires a new validateActionHookRefs pass into
ProcessWithRunContext so configs that reference an unknown custom
action fail at load time with a targeted message instead of deep
inside the deploy. Covers the three kubernetes-family deployers
(LegacyHelmDeploy, KubectlDeploy, KptDeploy); CloudRunDeployHooks
is intentionally still HostHook-only in this PR.
Adds a new 'Action hooks' section to docs-v2/docs/lifecycle-hooks.md
describing the action: union member, how it relates to customActions,
which deployers support it (kubectl, helm, kpt), and that it shares a
runtime with skaffold exec (so a single action definition is runnable
standalone or wrapped as a deploy hook).

Ships a runnable examples/hooks-action/ fixture (mirrored under
integration/examples/) with pre-deploy-check + post-deploy-smoke
actions and a minimal busybox Pod manifest so maintainers can smoke
test the new schema end-to-end.
A verify test case may now set `action: {name: <customAction>}` instead of
an inline `container`. Such test cases are dispatched to the same actions
runner used by `skaffold exec` and deploy action hooks, so the referenced
action runs with its own executionMode, timeout, failFast and runArgs.

  verify:
    - name: smoke
      action:
        name: smoke-test
    - name: health
      container:
        name: health
        image: alpine:3.20
        command: ["/bin/sh", "-c", "wget -qO- http://svc/healthz"]

  * GetVerifier skips action-referencing test cases so they are not handed
    to the docker/k8s container verifiers.
  * SkaffoldRunner.Verify runs them via r.actionsRunner.Exec after the
    container verifier completes.
  * getVerifyImgs skips them (no inline image to register).

Signed-off-by: Bogdan Nazarenko <bogdan.nazarenko@outlook.com>
Extends validateVerifyTests to require exactly one of `container` or
`action` per verify test case and to verify that each `action` reference
points to a known customActions entry. Extracts a shared knownActionNames
helper reused by the existing validateActionHookRefs.

Signed-off-by: Bogdan Nazarenko <bogdan.nazarenko@outlook.com>
  * Unit: validation cases for the verify oneOf(container, action) rule and
    unknown/empty action references.
  * Integration: verify-custom-action fixture mixing an inline container
    test case with an action-referencing one, asserting both run.

Signed-off-by: Bogdan Nazarenko <bogdan.nazarenko@outlook.com>
Signed-off-by: Bogdan Nazarenko <bogdan.nazarenko@outlook.com>
The hooks-action example demonstrates the `action:` deploy hook, which is a
skaffold/v4beta15 feature. `examples/` must use the latest released schema
version, so a v4beta14 copy cannot parse the unreleased field and fails
TestParseExamples. Keep the example only under integration/examples (latest
version) until v4beta15 is released; check-samples permits integration-only
examples.

Signed-off-by: Bogdan Nazarenko <bogdan.nazarenko@outlook.com>
Signed-off-by: Bogdan Nazarenko <bogdan.nazarenko@outlook.com>
@bogdannazarenko
bogdannazarenko force-pushed the feat/lifecycle-hooks-run-custom-actions branch from d7e5025 to e53fb80 Compare June 26, 2026 21:38
@bogdannazarenko bogdannazarenko changed the title feat(hooks): invoke customActions from deploy.*.hooks.before/after feat: invoke customActions from deploy hooks and verify (skaffold/v4beta15) Jun 26, 2026
Signed-off-by: Bogdan Nazarenko <bogdan.nazarenko@outlook.com>
@bogdannazarenko
bogdannazarenko force-pushed the feat/lifecycle-hooks-run-custom-actions branch from e53fb80 to a0f8009 Compare June 26, 2026 22:00
@bogdannazarenko
bogdannazarenko marked this pull request as ready for review July 7, 2026 00:31
@bogdannazarenko

Copy link
Copy Markdown
Contributor Author

Hi @Darien-Lin @menahyouyeah could you take a look please when you get a chance? Thank you

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant