Skip to content

Module: !kubectl (apply/get/delete/wait + Kustomize support) #43

Description

@Zorlin

Problem

Today the only way to run kubectl from a Jetpack playbook is !shell / !command / !external (grep confirms zero kubectl/helm/kustomize references anywhere in src/). examples/github_release_advanced.yml even shows the gap: it can discover a k8s version via !github_release but cannot apply it. There is no idempotent, declarative k8s task — no server-side diff for changed_when, no first-class -k kustomize, no structured get -o json exposed to later tasks.

Proposed module

A new !kubectl module implementing the standard two-trait module model:

  • IsTask (src/tasks/common.rs:30) — deserialized struct, evaluate() does arg templating + validation.
  • IsAction (src/tasks/common.rs:50) — dispatch() runs the subprocess, pattern-matches Query/Execute/Create/Modify.
- !kubectl
  action: apply              # apply | delete | get | wait | kustomize
  # --- manifest source (exactly one) ---
  manifest: |                # (a) inline YAML
    apiVersion: v1
    kind: Namespace
    metadata: { name: moosefs-system }
  # manifest: deploy/manifests/moosefs-system.yaml   # (b) BYO file in the automation repo
  # kustomize: deploy/overlays/dev                   # (c) kustomization dir → `kubectl apply -k`
  namespace: moosefs-system
  wait_for:                  # optional `kubectl wait --for=condition=Ready`
    kind: Pod
    selector: app=moosefs-master
    timeout: 300s
  save: result               # exposes result.rc / result.out / result.stdout

action: get should default to -o json and expose the parsed object under result (via the existing save: machinery) so later tasks can branch on it.

Manifest: inline OR bring-your-own file

The manifest source must support both:

  • inline — a literal YAML block.
  • BYO file — a path to an existing YAML in the automation repo, so users with manifests already on disk don't have to inline them. This is the common case.

Recommended design: manifest: accepts a string; if it resolves to an existing file it is loaded as YAML, otherwise the string is treated as inline YAML. File lookup should reuse the same mechanism !copy uses for src (find_file_path, src/modules/files/copy.rs:~90) — resolved relative to the role/playbook/repo root. Anchoring to the repo root matters: it lets a playbook in playbooks/foo/ reference manifest: deploy/manifests/x.yaml at the repo root rather than a CWD-relative path — which is exactly the "automation repository" root introduced in #46 (JET_REPO_ROOT), so this issue depends on / composes with #46. (Less-magic alternative if file-existence detection is rejected: two explicit keys — manifest: inline, manifest_file: path. Either resolves the BYO case.)

Exactly one of manifest / kustomize may be set; specifying both is a validation error.

Implementation anchors

  • Registration (6 edits, one file): src/registry/list.rs — import + enum variant Kubectl + the four match arms (get_module/get_name/get_with/evaluate) + the category() match. The !kubectl YAML tag is the enum variant lowercased via #[serde(rename_all = "lowercase")] (list.rs:84-86), enforced by every_module_tag_is_lowercase_snake (list.rs:391).
  • Structured args: follow the FileAttributesInput Input/Evaluated-pair + deny_unknown_fields + template() constructor pattern (src/tasks/files.rs:27-41). manifest: and wait_for: are nested.
  • File resolution for BYO manifests: reuse find_file_path as !copy does (src/modules/files/copy.rs:~90), anchored to the repo root (DNS output lands in the playbook dir, not the repo root — introduce a first-class "automation repository" root #46).
  • Reuse verbatim from src/modules/commands/shell.rs: handle.remote.run_unsafe (src/handle/remote.rs:166), CheckRc, cmd_info (src/connection/command.rs:36), build_results_map (shell.rs:173-186), save: → update_variables (src/inventory/hosts.rs:205), and failed_when/changed_when (shell.rs:129-151).
  • Inherit for free: all with:/and: modifiers (condition/items/delegate_to/sudo/skip_if_exists) are applied by the FSM (src/playbooks/task_fsm.rs:316-410); sudo via src/handle/remote.rs:207.

Idempotency / changed_when (the real value over !shell)

apply should compute changed_when from a server-side dry-run diff, not "always changed": kubectl apply --dry-run=server -o name vs the applied set. delete should be changed_when: false when the resource is already absent (kubectl get rc != 0).

Caveat

CommandResult { cmd, out, rc } (src/connection/command.rs:23) captures combined stdout+stderr with no separate stderr (src/connection/local.rs:148). For get -o json that is fine (stdout), but flag if any flow needs stderr isolation — that may require extending CommandResult.

Tests (TDD)

  • Unit-test arg → argv construction (mirror how proxmox_lxc tests only the templating layer, no subprocess): assert action: kustomize + kustomize: x yields ["apply","-k","x"], etc.
  • Unit-test manifest-source resolution: an inline block is passed through; a string that resolves to an existing file is loaded from disk; both manifest + kustomize set → error.
  • Integration (needs a cluster): apply a Namespace from an inline manifest and from a BYO file, assert changed_when true on first run / false on second; get returns the object under result.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions