Skip to content

Add module-ci-action: raptor-driven CI for Facets modules repos#11

Merged
anujhydrabadi merged 8 commits into
masterfrom
feat/module-ci-action
Jul 17, 2026
Merged

Add module-ci-action: raptor-driven CI for Facets modules repos#11
anujhydrabadi merged 8 commits into
masterfrom
feat/module-ci-action

Conversation

@anujhydrabadi

Copy link
Copy Markdown
Contributor

What

A new composite GitHub Action, module-ci-action/, that provides end-to-end CI for a Facets modules repository (modules/{intent}/{flavor}/{version} — each dir a facets.yaml + *.tf) using the raptor CLI (not ftf, not Python).

It is a sibling of the existing module-preview-action/, which is left completely unchanged for ftf users. Consumed as Facets-cloud/github-actions/module-ci-action@master.

Three event-derived modes

Event Mode Behavior
pull_request opened/synchronize/reopened preview raptor create iac-module -f <dir> --dry-run (validation gate), then raptor create iac-module -f <dir> --feature-branch --git-ref <PR head sha>. Upserts a marker-based PR comment when github_token is set.
push publish raptor create iac-module -f <dir> then raptor publish iac-module <intent>/<flavor>/<version> (metadata read from facets.yaml).
pull_request closed cleanup Reads each module's previewGitRef from raptor get iac-module -o json and deletes the preview only if it belongs to one of this PR's commit SHAs (via gh api .../pulls/{n}/commits). Never touches a preview owned by another branch.

mode defaults to auto (derives from the event) but can be set explicitly. Other inputs: control_plane_url / username / token (required), github_token, raptor_version (default latest), all-modules (default false), path-prefix (default "").

Notable design points

  • Auth: exports CONTROL_PLANE_URL / FACETS_USERNAME / FACETS_TOKEN env vars — raptor's highest-priority auth. No non-interactive raptor login exists, so none is attempted.
  • --feature-branch: a new raptor flag shipping in a parallel raptor PR (not yet in any raptor ref — the base iac-module commands don't have it). The README documents the raptor version requirement; the previewGitRef field cleanup reads is part of the same parallel work, and cleanup degrades gracefully (skips) when it's absent.
  • Provenance: preview passes the PR head SHA explicitly because the PR checkout is a merge commit; publish relies on auto-detected provenance (on push, HEAD == the pushed commit). Git remote URL auto-detects from the work tree.
  • Failure handling: keeps going across modules, records failures, prints a summary, and exits non-zero at the end if any module failed.
  • Single-preview-slot semantics: concurrent PRs on the same module → last write wins; cleanup is ownership-checked.

Validation performed

  • action.yml parses as YAML (9 steps).
  • Every embedded bash script passes bash -n and shellcheck (v0.10.0) with zero findings (intentional word-splitting annotated with scoped disable directives).
  • Added .github/workflows/test-module-ci.yaml that re-runs exactly this validation in CI on any change under module-ci-action/**. A true act-style run was intentionally not added: the action drives a live Control Plane (even --dry-run needs an API client), so it can't authenticate in CI without secrets + a module fixture.

🤖 Generated with Claude Code

anujhydrabadi and others added 8 commits July 15, 2026 17:57
New composite action, sibling to module-preview-action (which is unchanged
and remains the ftf-based action). It drives the raptor CLI for a modules
repo (modules/{intent}/{flavor}/{version}) in three event-derived modes:

- preview  (pull_request): validate each changed module, then register an
  unpublishable feature-branch preview pinned to the PR head commit; upsert
  a PR comment when github_token is set.
- publish  (push): upload each changed module and publish PREVIEW->PUBLISHED.
- cleanup  (pull_request closed): delete a module's preview only when its
  previewGitRef belongs to one of this PR's commits (ownership-checked).

Auth is via CONTROL_PLANE_URL/FACETS_USERNAME/FACETS_TOKEN env vars (raptor's
highest-priority auth; no non-interactive login). Preview's --feature-branch
flag ships in a parallel raptor PR, noted in the README version requirement.

Includes the action README, a numbered root README section, and a
lint-action workflow that parses action.yml and shellchecks every embedded
script.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Live e2e testing showed cleanup always skipped previews for brand-new
modules. The Control Plane only populates previewGitRef on a PUBLISHED
module that also has a preview sibling; a never-published module IS the
preview (stage PREVIEW) and carries its owning commit in plain gitRef,
with previewGitRef null — the common PR case.

Cleanup now derives the owning commit per module row by stage:
- stage PREVIEW            -> .gitRef
- stage PUBLISHED w/ preview sibling (.previewModuleId set) -> .previewGitRef
- otherwise                -> no preview slot, skip
Prefers a PREVIEW row when both exist. Downstream SHA comparison against
the PR's commits is unchanged, and absent/older provenance fields still
degrade to a safe skip. README cleanup/single-preview-slot section updated
to match.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Live testing showed `raptor delete iac-module REF --yes --force` deletes the
PUBLISHED doc when a module has both published and preview versions — so
cleanup on an in-place-update PR would delete the customer's live module.

Delete now uses `raptor delete iac-module REF --stage PREVIEW --yes`, which
targets only the preview doc (and errors if no preview exists). Dropped
--force so the usage guard stays active and a genuinely in-use preview
surfaces as a real error instead of being force-deleted; that failure is
already recorded and gates the job. README version requirement updated to
list --stage alongside --feature-branch and list-JSON provenance.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Optional input; when non-empty the install step downloads the raptor
linux-amd64 binary from that exact URL (raptor_version ignored), otherwise
behavior is unchanged. Escape hatch for testing/pre-release raptor builds
and enterprise mirrors. Documented in the README inputs table.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ubuntu-latest runners ship neither terraform nor trivy, so raptor's
--dry-run validation hard-failed ("terraform is not installed") and the
security scan silently degraded. Added an install step (after mode
resolution, gated to skip cleanup mode, which needs neither):

- terraform: pinned via new input terraform-version (default 1.5.7 to match
  module-preview-action), unzipped from releases.hashicorp.com to
  /usr/local/bin.
- trivy: pinned via new input trivy-version (default 0.72.0), tarball from
  aquasecurity/trivy releases to /usr/local/bin.

No third-party setup actions. README inputs table + requirements note
updated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
On PR merge, the push (publish) and pull_request-closed (cleanup) workflows
fire concurrently. Publish re-uploads the preview at the merge SHA and
mark-published consumes it, so cleanup is redundant — and racy: cleanup's
ownership check (old PR-head SHA) and its --stage PREVIEW delete resolve at
different times, so a bad interleaving can delete the merge-SHA preview the
publish run just created and kill the publish. (Observed live: green publish
+ red cleanup on a merged PR.)

Mode resolution now downgrades cleanup to a no-op when
github.event.pull_request.merged == true, with a clear notice. Cleanup runs
only for PRs closed without merging. Tooling-install gate tightened to
preview/publish (skips the no-op too). README lifecycle table + single-
preview-slot section updated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Changed-file detection walks UP ancestors to the nearest facets.yaml, so a
  change under modules/x/y/1.0/templates/ maps to the module (was dropped by
  immediate-dirname mapping, shipping it unvalidated).
- Cleanup re-fetches and re-verifies the owning SHA immediately before the
  --stage PREVIEW delete (TOCTOU); residual race documented in the README.
- Reject pull_request_target explicitly (its base checkout yields an empty
  diff and wrong provenance) instead of treating it as pull_request.
- Stop exporting CONTROL_PLANE_URL/FACETS_USERNAME/FACETS_TOKEN via
  GITHUB_ENV (leaked the token into every later step of the caller's job);
  set them as step-scoped env: on the raptor-invoking steps instead.
- Guard force-push/rebase: verify github.event.before is reachable
  (rev-parse --verify) and fall back to the pushed commit's diff-tree,
  matching the all-zeros handling.
- Cleanup jq tolerates a non-array payload and emits an explicit ::warning
  when a listed module resolves no owning commit, instead of silently
  claiming "no preview slot".
- README: add the --git-ref raptor dependency to requirements.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…mp managed actions

- Add release.yml: pushing a vX.Y.Z tag moves the major alias tag (v1) and
  creates the GitHub release; consumers pin module-ci-action@v1 instead of
  @master.
- Document the versioning convention in the root and module-ci-action READMEs;
  example workflows now reference @v1.
- Mark the ftf-based module-preview-action as deprecated (root README +
  its own README); module-ci-action is the current action.
- Bump managed actions: actions/checkout v4 -> v7 (action steps, test
  workflows, README examples), actions/setup-python v5 -> v6.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@anujhydrabadi
anujhydrabadi merged commit 53344bd into master Jul 17, 2026
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