Skip to content

feat: add Packer composite actions and integration workflow - #1

Merged
posquit0 merged 8 commits into
mainfrom
feat/packer-workflows
Sep 21, 2026
Merged

posquit0 merged 8 commits into
mainfrom
feat/packer-workflows

Conversation

@posquit0

@posquit0 posquit0 commented Jun 22, 2026

Copy link
Copy Markdown
Member

CI building blocks for the packer-templates repository, restructured to follow the same convention as the terraform.* actions and integration workflows: the logic lives in composite actions and the workflows only wire them together.

Note

The Ansible half of this pull request (ansible.ansible-lint and ansible.integration.yaml) has been split out into #30 so it can be reviewed and adopted on its own. Nothing was shared between the two halves, so the Packer files here are unchanged by the split.

New composite actions

Action Purpose
packer.fmt packer fmt -check -diff, with an optional recursive (default true)
packer.validate packer init followed by packer validate

Each takes target_dir and, on failure, appends its output to the job summary through github.step-summary, so a failed check is readable without opening the raw log.

packer.validate exports its github_token input as PACKER_GITHUB_API_TOKEN, which the plugin installer uses to avoid the anonymous GitHub API rate limit.

New reusable workflow

Workflow Jobs Purpose
packer.templates.integration.yaml changed, lint, report Resolve changed directories under builds/**, then fan out fmt and validate per directory

packer.templates.integration.yaml mirrors terraform.modules.integration.yaml: checks run with continue-on-error so one failure does not hide the others, github.matrix-report collects each directory's outcome, and the report job publishes a single table to the job summary and a sticky pull request comment. pr_comment_enabled (default true) turns the comment off.

Changes from the first revision of this PR

The original revision added reusable workflows that each carried their own inline logic. The current one differs in these ways.

  • Composite actions instead of inline workflow steps, so packer-templates and any later caller share the same implementation.
  • Tools come from mise, replacing hashicorp/setup-packer. Default versions go into a throwaway config under RUNNER_TEMP that MISE_GLOBAL_CONFIG_FILE points at, so a repository's own mise.toml or .tool-versions still wins and a self-hosted runner's real global config is never overwritten. This matters here: packer-templates pins packer 1.15.4 in .tool-versions, and the previous approach would have silently overridden it.
  • packer_target_dir is now target_dir, matching every other <tool>.<action> in this repository.

Validation

yamllint (repo config), actionlint, and shellcheck pass.

Beyond the linters, every action's script was executed directly against a checkout of tedilabs/packer-templates:

Case Result
packer fmt on a formatted directory Passes
packer fmt on a deliberately unformatted copy Exits non-zero, diff captured and rendered into the job summary
packer init and packer validate on builds/ubuntu-2604-docker Pass
packer validate on a directory holding no template Exits non-zero, error captured

Notes

aws_region defaults to us-east-1 and is exported as AWS_DEFAULT_REGION so an Amazon builder that does not set region explicitly still validates on a runner with no AWS configuration. No credentials are involved. Worth flagging that the current packer-templates builds validated cleanly without it, so this is defensive rather than something reproduced as necessary. It can be dropped if you would rather not carry the input.

Consumed by

tedilabs/packer-templates, which calls this workflow from its own integration workflow, alongside the Ansible workflow in #30.

@gemini-code-assist

Copy link
Copy Markdown

Note

Gemini is unable to generate a review for this pull request due to the file types involved not being currently supported.

@posquit0
posquit0 force-pushed the feat/packer-workflows branch 2 times, most recently from 1074083 to 093e32b Compare June 23, 2026 07:27
@posquit0
posquit0 force-pushed the feat/packer-workflows branch 3 times, most recently from 2538562 to 1e50317 Compare September 14, 2026 15:17
@github-actions

github-actions Bot commented Sep 14, 2026

Copy link
Copy Markdown

GitHub Actions Integration

Repository actionlint check-jsonschema (workflows) check-jsonschema (actions)
tedilabs/github-actions

✅ All 1 passed.

@posquit0
posquit0 force-pushed the feat/packer-workflows branch from 1e50317 to 87091b8 Compare September 15, 2026 07:05
@posquit0 posquit0 changed the title feat: add reusable Packer + ansible-lint workflows feat: add Packer and Ansible composite actions and integration workflows Sep 15, 2026
@posquit0
posquit0 force-pushed the feat/packer-workflows branch from 87091b8 to 1b26eea Compare September 15, 2026 07:28
@posquit0
posquit0 force-pushed the feat/packer-workflows branch from 1b26eea to 1c0468d Compare September 19, 2026 15:58
@posquit0

Copy link
Copy Markdown
Member Author

Rebased onto main at bef73f1 (after #6, #8, #9, #11, #13) and aligned the three check actions with the interface every check action on main now has:

  • Outputs skipped, stdout, stderr, exitcode on packer.fmt, packer.validate, and ansible.ansible-lint, matching the Terraform and GitHub Actions checks (and the hashicorp/setup-terraform wrapper). Where a prerequisite step exists (packer init, ansible-galaxy collection install), the outputs describe it when it failed and the main command otherwise, as terraform.validate and terraform.tflint do.
  • shell.run replaces the 2>&1 | tee pipelines, so the capture logic lives in one place and the job summary reads its log_file output.
  • Resolve step per action: trailing slash stripped, tool version printed there so it stays out of stdout, and skipped=true with a notice when the target holds no Packer HCL files (fmt), no Packer templates (validate), or no YAML files (ansible-lint) instead of a vacuous pass or a confusing failure.
  • always() on the failure summary guards. On main the guards without it were being skipped after a failure; fix(actions): run the failure summary steps after a failed step #15 fixes the existing actions.
  • packer.templates.integration report reports skipped and drops disabled checks, as the Terraform integration workflows do.

Runner verification: #17 (closed) ran all three actions from this branch against passing, failing, and empty fixtures, run https://github.com/tedilabs/github-actions/actions/runs/35453505149. 11/11 assertions passed, including exitcode=3 with the diff in stdout for an unformatted template and exitcode=2 with the findings for a playbook with issues. Locally: yamllint, actionlint, check-jsonschema, shellcheck, and a simulation of every resolve step. CI is green on the rebased head.

@posquit0

Copy link
Copy Markdown
Member Author

Rebased onto main at d9539d0 (after #18, #20, #22, #23, #24), no conflicts, and brought the three checks onto the annotations_enabled interface that #24 and #26 give the Terraform and GitHub Actions checks:

  • packer.fmt reads file and hunk line from the captured diff (+++ new/<file>, @@ -a,b +c,d @@); paths are already repository-relative because the directory is passed as an argument.
  • packer.validate pairs each Error: <summary> line with the following on <file> line <n> line, since packer has no machine-readable output.
  • ansible.ansible-lint turns the tool's own GitHub annotations off in the main run (GITHUB_ACTIONS=false), because their paths are relative to the project directory and their titles carry no tool name, and re-runs with -f json after a failure to emit annotations with target_dir prefixed and ansible-lint · <rule> as the title. Severity maps minor → warning, info → notice, anything else → error.

Runner verification: #28 (closed) ran all three against failing fixtures, run https://github.com/tedilabs/github-actions/actions/runs/35524387836:

Level Location Title
error fixtures/annotations3/pk-fmt/build.pkr.hcl:1 packer fmt · Not formatted
error fixtures/annotations3/pk-validate/build.pkr.hcl:3 packer validate · Unsupported argument
error fixtures/annotations3/ansible/playbook.yml:4 ansible-lint · name[missing] / no-free-form
error fixtures/annotations3/ansible/playbook.yml ansible-lint · name[play] / fqcn[action-core]

stdout of the ansible-lint step carries no ::error lines, and annotations_enabled: "false" adds nothing. CI is green on the new head.

- Check formatting with `packer fmt -check -diff`
- Support `recursive` input to also check subdirectories (default `true`)
- Add the diff to the job summary on failure via github.step-summary
- Install required plugins with `packer init` and run `packer validate`
- Export `github_token` as `PACKER_GITHUB_API_TOKEN` to avoid anonymous
  GitHub API rate limits when downloading plugins
- Add init/validate output to the job summary on failure via github.step-summary
- Detect changed Packer template directories under builds/**
- Run packer fmt and packer init/validate per directory with
  continue-on-error so all checks run
- Collect per-directory outcomes via github.matrix-report and publish
  an aggregated report to the job summary and a sticky PR comment
- Add pr_comment_enabled input (default true) to toggle the PR comment
- Write default tool versions to a throwaway global mise config pointed at
  by `MISE_GLOBAL_CONFIG_FILE`, so repository pins in `mise.toml` /
  `.tool-versions` win and a self-hosted runner's config is left alone
- Export `AWS_DEFAULT_REGION` so Amazon builders validate without AWS config
…rface

- Run `packer fmt`, `packer init`, and `packer validate` through
  `shell.run`, and expose `skipped`, `stdout`, `stderr`, and `exitcode`
  like the Terraform and GitHub Actions checks
- Resolve the target in a dedicated step: strip the trailing slash, skip
  with a notice when the directory holds no Packer HCL or template
  files, and print the tool version there so it stays out of `stdout`
- Guard the failure summary steps with `always()`, since the implicit
  `success()` otherwise skips them after the check fails
- Report `skipped` and drop disabled checks in the Packer integration
  report, as the Terraform integration workflows do
- Add `annotations_enabled` (default `true`) on the pattern of the Terraform
  and GitHub Actions checks
- `packer.fmt` reads file and hunk line from the captured diff;
  `packer.validate` pairs each `Error:` line with its `on <file> line <n>`
  line; both print repository-relative paths already
@posquit0
posquit0 force-pushed the feat/packer-workflows branch from 425a22c to e0c800d Compare September 21, 2026 06:23
@posquit0 posquit0 changed the title feat: add Packer and Ansible composite actions and integration workflows feat: add Packer composite actions and integration workflow Sep 21, 2026
@posquit0

Copy link
Copy Markdown
Member Author

Split the Ansible half out into #30 (ansible.ansible-lint composite action and ansible.integration.yaml), so it can be reviewed and adopted separately. Force-pushed this branch, rebased onto main at 8b38c0b (after #26), with the two Ansible commits dropped and the two mixed commits (refactor(actions): align …, feat(actions): annotate findings …) reapplied with only their Packer changes. The three Packer files are byte-identical to the previous head at 425a22c; only the two commit messages were reworded to drop the Ansible mentions.

…on-less diagnostics

- Add `syntax_only` (default `false`), which passes `-syntax-only` and
  skips `packer init`, since a syntax-only check resolves no plugins and
  `packer init` would otherwise fail on a runner that cannot reach the
  plugin registry
- Annotate a diagnostic that names no file, such as `Unset variable`,
  without a location instead of dropping it: the summary was only ever
  emitted once an `on <file> line <n>` line followed it
- Correct the comment on the annotation step. `packer validate` does take
  `-machine-readable`, but it is the legacy stream format, which packs
  every diagnostic into one `ui,error` record as the same text with `\n`
  escaped and commas replaced by `%!(PACKER_COMMA)`, and carries no file
  or line field
@posquit0

Copy link
Copy Markdown
Member Author

Good catch on -machine-readable — the comment was wrong and is now fixed, but I kept parsing the text output. Checked what the flag actually emits (packer 1.16.0 locally, on a template with two errors):

1789972601,,ui,error,Error: Unsupported argument\n\n  on multi.pkr.hcl line 3:\n ... \n\nError: Unsupported argument\n\n  on multi.pkr.hcl line 8:\n ...

It is the legacy Packer 1.x stream format (timestamp,target,type,data): every diagnostic of the run lands in a single ui,error record as the same human-readable text with \n escaped, with no file or line field, and commas inside the message are mangled — a Host must be specified%!(PACKER_COMMA) please reference your communicator documentation. Strictly worse than the text output for both annotations and the job summary, so the comment now says that instead.

While testing I found a real gap: a diagnostic that names no file, such as Error: Unset variable "needed", produced no annotation at all, because the summary was only emitted once an on <file> line <n> line followed it. It now falls back to a location-less annotation.

On the other flags — packer-templates has a default on every variable, no data blocks, one source per build directory, and .auto.pkrvars.hcl loads on its own, so -var/-var-file/-only/-except have nothing to do here yet; I left them out until a caller needs them. -evaluate-datasources I would rather not expose: Packer's own help says it may incur costs, and on a pull-request validate it would also want cloud credentials, so off is a property worth keeping. Added -syntax-only only, as syntax_only (default false), which also skips packer init — verified that a syntax-only check passes with no plugins installed while a full validate fails with Missing plugins, which is the situation that makes the flag worth having.

Not wired into packer.templates.integration.yaml, matching how recursive and annotations_enabled are action-level only there. Say the word if you want it on the workflow too.

Verified: annotator re-run against real packer validate logs for the location-less, two-diagnostic, comma-bearing, mixed, and empty cases; builds/ubuntu-2604-docker still passes both -syntax-only and a full validate; yamllint and shellcheck pass.

Without `install_args`, `mise install` resolves every tool in the calling
repository's mise config. `packer-templates` pins `terraform` alongside
`packer` in `.tool-versions`, so each leg of the matrix was installing
Terraform it never runs, as the Terraform integration workflows already
avoid by naming the tools they need.
@posquit0

Copy link
Copy Markdown
Member Author

Right — install_args was missing. Fixed in 1ee644e: the mise step now passes install_args: packer.

The cost was real: packer-templates/.tool-versions pins terraform 1.15.6 next to packer 1.15.4, so every leg of the matrix was installing Terraform that this workflow never runs. Both checks need packer and nothing else, so it is passed unconditionally rather than keyed off fmt_enabled / validate_enabled — an empty install_args falls back to installing everything, which is the case worth avoiding.

One difference from the Terraform integration workflows I left alone: they also pass working_directory: ${{ matrix.path }} so a mise.toml inside the module directory overrides the repository-wide one. packer-templates has no per-build mise config today, so I did not add it here. Happy to wire it up if you want the workflows symmetric.

…se config

Drop the `packer_version` input and the throwaway global mise config it
was written to, matching the Terraform integration workflows since #18:
the tools and their versions come from the calling repository's own
`mise.toml` or `.tool-versions`, with no workflow-side default.

Pass the build directory as `working_directory` as well, so a
`mise.toml` placed there overrides the repository-wide one.
@posquit0

Copy link
Copy Markdown
Member Author

Agreed on both counts — 97c91d3.

  • default-tools removed, along with the packer_version input. It was a workflow-side default written to a throwaway global mise config, which is exactly the thing feat(workflows)!: take Terraform tool versions from the repository's mise config #18 took out of the Terraform integration workflows; neither of them has an equivalent step or a version input today. Versions now come only from the calling repository's mise.toml / .tool-versions.
  • working_directory: ${{ matrix.path }} added, so a mise.toml inside a build directory overrides the repository-wide one, same as the Terraform workflows pass the module directory. Safe against deleted directories, since the matrix is fed from changed.outputs.directories, which already drops paths that no longer exist in the checkout.

Companion change on the consumer side: tedilabs/packer-templates#17 folds .tool-versions into mise.toml, keeping packer 1.15.4 and terraform 1.15.6 as they were.

@posquit0
posquit0 merged commit b691333 into main Sep 21, 2026
3 checks passed
@posquit0
posquit0 deleted the feat/packer-workflows branch September 21, 2026 07:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant