diff --git a/.github/actions/facetsctl-register/action.yml b/.github/actions/facetsctl-register/action.yml index 76bc660..1bef70d 100644 --- a/.github/actions/facetsctl-register/action.yml +++ b/.github/actions/facetsctl-register/action.yml @@ -1,5 +1,5 @@ -name: 'FacetsCloud CI Integration' -description: 'Register artifacts in FacetsCloud using facetsctl' +name: 'FacetsCloud CI Integration (deprecated)' +description: 'DEPRECATED - use Facets-cloud/github-actions/raptor-register instead. Registers artifacts using facetsctl v2.' inputs: docker_image: description: 'Docker image URL in the external registry' diff --git a/README.md b/README.md index 7371f4f..d97a6b5 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,17 @@ in three modes, derived automatically from the triggering event: This is the action the Facets control plane wires into bootstrapped modules repositories, and the recommended CI for all modules repos. -## 2. Facets Module Preview & Security Scan (deprecated) +## 2. Facets Artifact Register (current) + +Registers an already-pushed image against a Facets **artifact** — by environment, git +ref, or release stream — using the **`raptor`** CLI. It registers; it does not build or +push. + +[raptor-register README](./raptor-register/README.md). + +This replaces `facetsctl-register` below. + +## 3. Facets Module Preview & Security Scan (deprecated) > ⚠️ **Deprecated.** This action is based on the legacy `ftf` CLI and is kept only for > existing workflows that still use it. It receives no new features. New repos should @@ -39,3 +49,11 @@ security checks for Facets Terraform modules: - **Terraform Validation**: Verifies the correctness of Terraform configurations. - **Checkov Security Scanning**: Identifies security vulnerabilities in Terraform code. - **Facets Module Preview**: Registers a Preview only module with your Facets Control Plane. + +## 4. FacetsCloud CI Integration (deprecated) + +> ⚠️ **Deprecated, and broken as written.** `facetsctl-register` runs facetsctl **v2** +> in a Docker action, and reads `secrets.*` from inside the action — not a context an +> action can read — so its credentials arrive empty. Use +> [**raptor-register**](./raptor-register/README.md); its README has the input-by-input +> mapping. It lives at `.github/actions/facetsctl-register/`. diff --git a/raptor-register/README.md b/raptor-register/README.md new file mode 100644 index 0000000..0ed2e5b --- /dev/null +++ b/raptor-register/README.md @@ -0,0 +1,118 @@ +# Facets Artifact Register + +Registers an already-pushed image against a Facets **artifact**, using the +[`raptor`](https://github.com/Facets-cloud/raptor-releases/releases) CLI. + +It registers; it does not build or push. Build and push with whatever your pipeline +already uses, then hand the resulting URI to this action. + +```yaml +- uses: Facets-cloud/github-actions/raptor-register@v1 + with: + control_plane_url: ${{ secrets.FACETS_CP_URL }} + username: ${{ secrets.FACETS_USERNAME }} + token: ${{ secrets.FACETS_TOKEN }} + project: my-project + artifact: my-project-api + image: my.registry.example.com/my-project/api:${{ github.sha }} + git_ref: ${{ github.ref_name }} + registry: my-ecr +``` + +## Where the build lands + +Exactly one of three inputs, and the choice is the whole behaviour: + +| Input | What happens | +|---|---| +| `git_ref` | The branch is matched against the project's routing rules, and **they** decide the target. A branch that matches nothing leaves the build registered but unrouted. | +| `environment` | Registered straight to that environment. No rules consulted. | +| `release_stream` | Registered straight to that stream. No rules consulted. | + +An artifact takes environments or release streams, never both — it is fixed when the +artifact is created. `raptor get artifacts -p ` shows which. + +To see what a project's rules actually say, and where builds landed: + +```bash +raptor get ci-cd -p -a # the branch mapping and promotion ladder +raptor get builds -p # what is registered; flags unrouted builds +``` + +## `registry` decides whether the resource can find the build + +A resource asks for its image in one of two shapes. Read which one with +`raptor get resources -p PROJECT RESOURCE -o json`: + +| shape | how it resolves | `registry` | +|---|---|---| +| `spec.release.image: ${blueprint.self.artifacts.NAME}` | by artifact name | not needed | +| `spec.release.build: {name: NAME, artifactory: REG}` | by registry, then name | **required, and it must be REG** | + +The module resolves the second shape as `all_artifactories[REG][NAME]`. A build stored +under another registry is absent from that map, so the module falls back to the literal +string `NOT_FOUND` and deploys **that**. The pod then sits in `InvalidImageName`, and +nothing failed earlier to warn you. Read what a build carries with +`raptor get builds ARTIFACT -p PROJECT -o wide`. + +## Which artifact? + +Not always the resource name — the artifact is the CI integration a resource deploys +from, and one artifact is often shared by several resources across several projects. +Read it off the resource listing rather than guessing: + +```bash +raptor get resources -p -o wide # the ARTIFACT column +``` + +## Inputs + +| Input | Required | Default | Description | +|---|---|---|---| +| `control_plane_url` | yes | | Control Plane URL, e.g. `https://your-org.console.facets.cloud` | +| `username` | yes | | Facets username | +| `token` | yes | | Facets API token — pass a secret, never a literal | +| `project` | yes | | Project (blueprint) the artifact belongs to | +| `artifact` | yes | | Artifact to register against | +| `image` | yes | | Full image URI including the tag; must already be pushed | +| `git_ref` | one of three | `""` | Register against this branch and let the rules place it | +| `environment` | one of three | `""` | Register directly against this environment | +| `release_stream` | one of three | `""` | Register directly against this release stream | +| `registry` | see above | `""` | Registry the image lives in | +| `external_id` | no | this run's id | CI reference recorded on the build | +| `raptor_version` | no | `latest` | `latest` or an exact tag, e.g. `v0.1.98`. `--registry` needs v0.1.98 or later | +| `raptor-download-url` | no | `""` | Exact binary URL; overrides `raptor_version` | + +## Notes + +- **The token is scoped to one step.** Auth is set as a step-level `env:` inside the + action, so it is not exported into the rest of the caller's job. +- **Pushing to a Facets registry?** Get the credentials and the repository to tag + against from raptor, in the step before this one — the control plane owns the + repository name, so it is not something to assemble by hand: + + ```bash + raptor get registry-credentials -p -a # docker login + REPO=$(raptor get registry-credentials -p -a --repository-uri) + ``` + +- **Zip bundles** are not this action: `raptor set artifact-zip` uploads and registers + in one command, with no registry involved. + +## Replaces `facetsctl-register` + +[`facetsctl-register`](../.github/actions/facetsctl-register/action.yml) is deprecated. It runs +facetsctl **v2** in a Docker action and reads `secrets.*` from inside the action, which +is not a context an action can read — so its credentials arrive empty. Migrating: + +| facetsctl-register | raptor-register | +|---|---| +| `docker_image` | `image` | +| `service` | `artifact` (read it from the ARTIFACT column, see above) | +| `blueprint_name` | `project` (now required) | +| `git_ref` | `git_ref` | +| `external_id` | `external_id` (optional; defaults to the run id) | +| `registration_type` | drop it — the target input you pass says which mode you are in | +| `registry` | `registry` — keep it. It is not decoration: see the note above | +| `description` | drop it — not carried on a build registration | +| implicit `secrets.FACETS_*` | explicit `control_plane_url` / `username` / `token` inputs | diff --git a/raptor-register/action.yml b/raptor-register/action.yml new file mode 100644 index 0000000..e76bd12 --- /dev/null +++ b/raptor-register/action.yml @@ -0,0 +1,121 @@ +name: "Facets Artifact Register" +description: "Register a built image against a Facets artifact using the raptor CLI — by environment, git ref, or release stream." +author: "Facets" + +inputs: + control_plane_url: + description: "Facets Control Plane URL, e.g. https://your-org.console.facets.cloud." + required: true + username: + description: "Facets username." + required: true + token: + description: "Facets API token. Pass a secret — never a literal." + required: true + project: + description: "Project (blueprint) the artifact belongs to." + required: true + artifact: + description: "Artifact to register the build against. This is the CI integration's name, which is NOT always the resource name — read it from the ARTIFACT column of 'raptor get resources -p -o wide'." + required: true + image: + description: "Full image URI to register, including the tag. The image must already be pushed; this action registers it, it does not build or push." + required: true + git_ref: + description: "Register against this branch and let the project's routing rules decide the target. Exactly one of git_ref, environment or release_stream is required." + required: false + default: "" + environment: + description: "Register directly against this environment. Exactly one of git_ref, environment or release_stream is required." + required: false + default: "" + release_stream: + description: "Register directly against this release stream. Exactly one of git_ref, environment or release_stream is required." + required: false + default: "" + registry: + description: "Registry the image lives in. Required when the resource asks for its image as spec.release.build {name, artifactory}: the module resolves that shape as all_artifactories[registry][name], so a build registered without it lands under 'provided', the lookup misses, and the release deploys the literal string 'NOT_FOUND'. Not needed when the resource uses spec.release.image with an artifact expression." + required: false + default: "" + external_id: + description: "CI run reference recorded on the build — the only link from a deployed image back to the pipeline that produced it. Defaults to this workflow run's id." + required: false + default: "" + raptor_version: + description: "raptor CLI version to install: 'latest' or an exact release tag (e.g. v0.1.97). Ignored when raptor-download-url is set." + required: false + default: "latest" + raptor-download-url: + description: "Exact URL to download the raptor linux-amd64 binary from, bypassing the default Facets-cloud/raptor-releases location. When set, raptor_version is ignored. Escape hatch for testing/pre-release builds and enterprise mirrors." + required: false + default: "" + +runs: + using: "composite" + steps: + - name: Install raptor CLI + shell: bash + run: | + set -euo pipefail + VERSION="${{ inputs.raptor_version }}" + DOWNLOAD_URL="${{ inputs.raptor-download-url }}" + if [ -n "$DOWNLOAD_URL" ]; then + # Explicit override: fetch the binary from exactly this URL (raptor_version ignored). + URL="$DOWNLOAD_URL" + elif [ -z "$VERSION" ] || [ "$VERSION" = "latest" ]; then + URL="https://github.com/Facets-cloud/raptor-releases/releases/latest/download/raptor-linux-amd64" + else + URL="https://github.com/Facets-cloud/raptor-releases/releases/download/${VERSION}/raptor-linux-amd64" + fi + echo "Installing raptor from ${URL}" + curl -fsSL -o raptor "$URL" + chmod +x raptor + sudo mv raptor /usr/local/bin/raptor + + - name: Register the build + shell: bash + # raptor auth is set here, not exported job-wide, so the token stays out of + # every other step in the caller's job. + env: + CONTROL_PLANE_URL: ${{ inputs.control_plane_url }} + FACETS_USERNAME: ${{ inputs.username }} + FACETS_TOKEN: ${{ inputs.token }} + RAPTOR_NO_UPDATE_CHECK: "1" + PROJECT: ${{ inputs.project }} + ARTIFACT: ${{ inputs.artifact }} + IMAGE: ${{ inputs.image }} + GIT_REF: ${{ inputs.git_ref }} + ENVIRONMENT: ${{ inputs.environment }} + RELEASE_STREAM: ${{ inputs.release_stream }} + EXTERNAL_ID: ${{ inputs.external_id }} + REGISTRY: ${{ inputs.registry }} + RUN_ID: ${{ github.run_id }} + run: | + set -euo pipefail + + # Exactly one target. Registering against two would be two different builds, + # and against none would be a build nothing can deploy. + TARGETS=0 + [ -n "$GIT_REF" ] && TARGETS=$((TARGETS + 1)) || true + [ -n "$ENVIRONMENT" ] && TARGETS=$((TARGETS + 1)) || true + [ -n "$RELEASE_STREAM" ] && TARGETS=$((TARGETS + 1)) || true + if [ "$TARGETS" -ne 1 ]; then + echo "::error::Set exactly one of git_ref, environment or release_stream (got ${TARGETS})." + exit 1 + fi + + if [ -n "$GIT_REF" ]; then + set -- --git-ref "$GIT_REF" + elif [ -n "$ENVIRONMENT" ]; then + set -- -e "$ENVIRONMENT" + else + set -- --release-stream "$RELEASE_STREAM" + fi + + if [ -n "$REGISTRY" ]; then + set -- "$@" --registry "$REGISTRY" + fi + + raptor set artifact-uri -p "$PROJECT" "$@" "$ARTIFACT" \ + --uri "$IMAGE" \ + --external-id "${EXTERNAL_ID:-$RUN_ID}"