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
37 changes: 37 additions & 0 deletions module-ci-action/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ modules live at `infra/modules/...`).
| `all-modules` | no | `false` | When `true`, operate on **every** module under `<path-prefix>modules/` instead of only the ones the event changed. |
| `mode` | no | `auto` | `auto` \| `preview` \| `publish` \| `cleanup`. `auto` derives the mode from the event (see the table above). Set explicitly to override. |
| `path-prefix` | no | `""` | Sub-path to the `modules/` tree relative to the repo root (e.g. `infra/`). Empty means `modules/` is at the root. |
| `auto-create-intents` | no | `true` | Pass `--auto-create` to raptor, so a module whose **intent** the Control Plane has never seen registers it. Without this the upload fails `404 Intent <kind> not found`. See **Intents** below before turning it off. |

### Secrets

Expand All @@ -92,6 +93,42 @@ modules repo. They are exported as the environment variables `raptor` reads dire
| `FACETS_USERNAME` | `FACETS_USERNAME` | Username |
| `FACETS_TOKEN` | `FACETS_TOKEN` | API token |


## Intents

A module's `intent` (its `kind`) is a **shared** object on the Control Plane: one
intent, many flavors. `postgres` is an intent; `gcp-cloudsql` and `aws-rds` are
flavors of it. Nothing in a modules repo creates an intent implicitly, and
`raptor create resource-type-mapping` rejects one it does not know — so the first
module of a new intent fails the upload outright:

```
Error: upload failed with status 404: {"message":"Intent gcs not found","code":"404"}
```

`auto-create-intents` (default `true`) passes raptor's `--auto-create` on the two
steps that upload — preview registration and publish. It is deliberately **not**
passed to the `--dry-run` validation, which never touches the intent.

**The flag does two things, and the second one is shared state.** Besides creating
a missing intent, `--auto-create` rewrites an existing intent's metadata from the
uploading module's `intentDetails`. Because every flavor of an intent writes the
same object, two flavors that disagree will fight, and the last module published
wins:

```yaml
# modules/cloud_account/gcp_provider/1.0/facets.yaml
intentDetails: { displayName: Cloud Account, ... }

# modules/cloud_account/gcp_org_provider/1.0/facets.yaml
intentDetails: { displayName: GCP Org Cloud Account, ... } # same intent!
```

Keep `intentDetails` identical across the flavors of one intent, and treat a
difference as a repo bug. Set `auto-create-intents: 'false'` to freeze intent
metadata instead — at the cost of failing on any intent the Control Plane has not
seen before.

## Example workflow

A single workflow wiring up all three triggers:
Expand Down
34 changes: 32 additions & 2 deletions module-ci-action/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ inputs:
description: "Sub-path to the modules/ tree, relative to the repo root (e.g. 'infra/' when modules live at infra/modules/...). Empty means modules/ is at the repo root."
required: false
default: ""
auto-create-intents:
description: "When 'true' (default), pass --auto-create to raptor so a module whose intent does not yet exist on the Control Plane creates it. Without this, the first module of a new intent fails with 404 'Intent <kind> not found' and a modules repo cannot bootstrap a fresh Control Plane. NOTE: --auto-create also rewrites an EXISTING intent's metadata from this module's intentDetails, and an intent is shared by all its flavors — so when two flavors disagree, the last one published wins. Set to 'false' to keep intent metadata frozen, at the cost of failing on any new intent."
required: false
default: "true"

runs:
using: "composite"
Expand Down Expand Up @@ -256,6 +260,7 @@ runs:
FACETS_USERNAME: ${{ inputs.username }}
FACETS_TOKEN: ${{ inputs.token }}
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
AUTO_CREATE_INTENTS: ${{ inputs.auto-create-intents }}
# Trivy runs inside raptor's module validation, AFTER terraform init has
# vendored remote deps into .terraform/. Trivy reads flags from TRIVY_*
# env vars; skip the vendored tree so findings in dependency code the
Expand Down Expand Up @@ -284,6 +289,17 @@ runs:
echo "|---|---|---|---|---|"
} > "$COMMENT"

# --auto-create makes raptor register the module's intent when the Control
# Plane has never seen that kind. Without it the upload 404s ("Intent
# <kind> not found"), which is what a modules repo hits the first time it
# carries a new intent — nothing else creates one: resource-type-mapping
# rejects unknown intents outright. The flag is NOT passed to --dry-run,
# which never touches the intent.
AUTO_CREATE=""
if [ "${AUTO_CREATE_INTENTS:-true}" = "true" ]; then
AUTO_CREATE="--auto-create"
fi

# shellcheck disable=SC2086,SC2154 # $dirs is an intentional space-separated list injected via $GITHUB_ENV
for dir in $dirs; do
echo "::group::Preview ${dir}"
Expand All @@ -309,7 +325,8 @@ runs:

if [ "$OK" = "1" ]; then
echo "Registering feature-branch preview for ${REF} at ${PR_HEAD_SHA}..."
if ! raptor create iac-module -f "$dir" --feature-branch --git-ref "$PR_HEAD_SHA"; then
# shellcheck disable=SC2086 # $AUTO_CREATE is a deliberate empty-or-one-flag word
if ! raptor create iac-module -f "$dir" $AUTO_CREATE --feature-branch --git-ref "$PR_HEAD_SHA"; then
echo "::error::Preview registration failed for ${REF}"
echo "${REF} (preview)" >> "$FAILURES"
RESULT="preview failed"; OK=0
Expand Down Expand Up @@ -357,6 +374,7 @@ runs:
CONTROL_PLANE_URL: ${{ inputs.control_plane_url }}
FACETS_USERNAME: ${{ inputs.username }}
FACETS_TOKEN: ${{ inputs.token }}
AUTO_CREATE_INTENTS: ${{ inputs.auto-create-intents }}
# Same rationale as the preview step: keep raptor's Trivy scan out of
# terraform-init'd dependencies under .terraform/ (issue #13).
TRIVY_SKIP_DIRS: "**/.terraform,**/.terraform/**"
Expand All @@ -369,6 +387,17 @@ runs:
| sed -E "s/[[:space:]]*#.*$//; s/^[\"']//; s/[\"'][[:space:]]*$//; s/[[:space:]]*$//"
}

# --auto-create makes raptor register the module's intent when the Control
# Plane has never seen that kind. Without it the upload 404s ("Intent
# <kind> not found"), which is what a modules repo hits the first time it
# carries a new intent — nothing else creates one: resource-type-mapping
# rejects unknown intents outright. The flag is NOT passed to --dry-run,
# which never touches the intent.
AUTO_CREATE=""
if [ "${AUTO_CREATE_INTENTS:-true}" = "true" ]; then
AUTO_CREATE="--auto-create"
fi

# shellcheck disable=SC2086,SC2154 # $dirs is an intentional space-separated list injected via $GITHUB_ENV
for dir in $dirs; do
echo "::group::Publish ${dir}"
Expand All @@ -383,7 +412,8 @@ runs:
# Upload as PREVIEW. Git provenance is auto-detected from the work tree
# (HEAD == the pushed commit on a push build).
echo "Uploading ${REF}..."
if ! raptor create iac-module -f "$dir"; then
# shellcheck disable=SC2086 # $AUTO_CREATE is a deliberate empty-or-one-flag word
if ! raptor create iac-module -f "$dir" $AUTO_CREATE; then
echo "::error::Upload failed for ${REF}"
echo "${REF} (upload)" >> "$FAILURES"
echo "::endgroup::"; continue
Expand Down