Skip to content

Dev

Dev #1423

Workflow file for this run

name: Deploy
# Single entry point - prevents race conditions by enforcing dependency order
on:
pull_request:
push:
branches: [main, dev, aws-dev, aws-proof, gcp-dev]
workflow_dispatch:
inputs:
gcp_require_active_certificate:
description: >-
Fail the GCP deploy if the GKE ManagedCertificate is not Active for the
public hostname. Set false only for first-time bootstrap, before DNS for
the hostname has been pointed at the ingress IP. Automatic (push) deploys
always require an active certificate.
type: boolean
default: true
aws_first_deploy:
description: >-
Allow the AWS engine deploy to skip the ECS task-family existence
check. Set true only for the first-ever deploy to a fresh AWS
environment, before the platform Terraform apply has created the
provisioner task definition. Automatic (push) deploys always require
the family to exist, so a typo'd family name fails the run.
type: boolean
default: false
concurrency:
group: deploy-${{ github.ref }}
# PR runs may be superseded safely, but env-branch deploys can be mid-apply.
# Queue branch/manual deploy runs so Terraform is not killed after mutating
# remote state or while holding the backend lock.
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
# ============================================================================
# CHANGE DETECTION
# ============================================================================
changes:
name: Detect Changes
runs-on: ubuntu-latest
# `dorny/paths-filter` calls the GitHub API to list files changed in
# the PR. The default GITHUB_TOKEN scopes under this repo's
# permissions policy don't include `pull-requests: read`, so without
# this block paths-filter fails with
# "Resource not accessible by integration" on every PR-event run.
permissions:
contents: read
pull-requests: read
outputs:
core: ${{ steps.filter.outputs.core }}
range: ${{ steps.filter.outputs.range }}
shifter_engine: ${{ steps.filter.outputs.shifter_engine }}
shifter_platform: ${{ steps.filter.outputs.shifter_platform }}
quality_relevant: ${{ steps.quality_non_docs.outputs.non_docs == 'true' || steps.quality_guardrails.outputs.guardrail_docs == 'true' }}
portal_image: ${{ steps.filter.outputs.portal_image }}
stack_smoke: ${{ steps.filter.outputs.stack_smoke }}
mcp: ${{ steps.filter.outputs.mcp }}
quality_only: ${{ steps.filter.outputs.quality_only }}
gcp: ${{ steps.filter.outputs.gcp }}
aws_environment: ${{ steps.env.outputs.aws_environment }}
aws_is_dev: ${{ steps.env.outputs.aws_is_dev }}
aws_github_environment: ${{ steps.env.outputs.aws_github_environment }}
gcp_environment: ${{ steps.env.outputs.gcp_environment }}
gcp_github_environment: ${{ steps.env.outputs.gcp_github_environment }}
run_aws: ${{ steps.env.outputs.run_aws }}
run_gcp: ${{ steps.env.outputs.run_gcp }}
apply_aws: ${{ steps.env.outputs.apply_aws }}
deploy_gcp: ${{ steps.env.outputs.deploy_gcp }}
fast_gcp_deploy: ${{ steps.env.outputs.fast_gcp_deploy }}
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0 # Need full history to access PR head commit
- uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
id: filter
with:
filters: |
core:
- 'platform/terraform/modules/ecr/**'
- 'platform/terraform/environments/*/*.tf'
- '.github/workflows/_core.yml'
- '.github/workflows/deploy.yml'
range:
- 'platform/terraform/modules/range/**'
- 'platform/terraform/modules/pulumi-state/**'
- 'platform/terraform/environments/*/range/**'
- '.github/workflows/_range.yml'
shifter_engine:
- 'shifter/engine/provisioner/**'
- 'shifter/cyberscript/**'
# NB: module is `engine-provisioner`, not the historical
# `pulumi-provisioner` name; the latter was a stale typo
# the path filter inherited from before the rename.
- 'platform/terraform/modules/engine-provisioner/**'
- 'platform/terraform/modules/ecr/**'
- 'platform/terraform/environments/*/*.tf'
- '.github/workflows/_shifter-engine.yml'
- '.github/workflows/deploy.yml'
shifter_platform:
- 'platform/terraform/modules/portal/**'
- 'platform/terraform/modules/guacamole/**'
- 'platform/terraform/environments/*/portal/**'
- '.github/workflows/_shifter-platform.yml'
# Portal image build/deploy trigger (#913). Mirrors the
# portal image's build inputs: the Docker context controls
# plus every tree the Dockerfile COPYs
# (shifter_platform, cyberscript, installation). Kept
# separate from the Terraform-only `shifter_platform`
# filter (ADR-003-R2) and from the broad `quality_relevant`
# classifier so engine/packer-only changes do not redeploy
# the portal. In-app documentation under
# shifter/shifter_platform/documentation/** is image
# content, so it deploys with the image; top-level docs/**
# never does.
portal_image:
- 'shifter/shifter_platform/**'
- 'shifter/cyberscript/**'
- 'shifter/installation/**'
- 'shifter/.dockerignore'
# The built-image stack smoke must re-run when its own
# implementation changes, not only when the portal image does -
# otherwise the CI guardrail can be edited (harness, ElasticMQ
# config, websocket probe, or the _quality.yml job that owns it)
# and merged without ever booting the built image through the
# changed harness. Feeds `run_stack_smoke` alongside the
# portal_image / shifter_platform deploy signals.
stack_smoke:
- 'scripts/stack-smoke/**'
- '.github/workflows/_quality.yml'
mcp:
- 'mcp/**'
# Quality-only non-deploy support surfaces. These paths have
# test or guardrail jobs in _quality.yml, but they must not
# trigger Terraform plans, image builds, or environment deploys.
quality_only:
- '.github/workflows/_quality.yml'
- '.github/quality-path-filters.yaml'
- 'scripts/polaris-aws-range/**'
- 'scenario-dev/polaris/tests/**'
# Guardrail scripts that the Quality workflow invokes
# directly (their CI steps live in _quality.yml's
# terraform-lint job). A change to these scripts or their
# tests must re-run Quality so the guardrail's enforcement
# is verified, but it shouldn't trigger any deploy.
- 'scripts/check_tf_iam_ec2_scope/**'
- 'scripts/check_tf_iam_elb_scope/**'
- 'scripts/check_tf_sg_cidrs/**'
- 'scripts/check_tf_kms_secrets_grant/**'
- 'scripts/check_tf_rds_security/**'
# Guardrail / ADR-registry surfaces. A change to any of
# these must re-run Quality so adr_guard catches a broken
# registry, malformed exception entry, or pre-commit hook
# wiring mismatch before it lands. Without this, a PR
# touching only ADR metadata or hook configuration would
# skip every architecture gate.
- '.pre-commit-config.yaml'
- 'docs/adr/**'
- 'scripts/adr_guard/**'
- 'shifter/shifter_platform/documentation/docs/technical/dev/adr-enforcement.md'
gcp:
- 'platform/terraform/gcp/**'
- 'platform/k8s/gcp/**'
- 'scripts/gcp/**'
- 'shifter/shifter_platform/shared/cloud/gcp/**'
- 'shifter/shifter_platform/shared/cloud/types.py'
- 'shifter/engine/provisioner/cloud/gcp/**'
- 'shifter/shifter_platform/config/settings.py'
- 'shifter/shifter_platform/engine/ecs.py'
- 'shifter/shifter_platform/entrypoint.sh'
- 'shifter/shifter_platform/entrypoint-lib.sh'
- 'shifter/shifter_platform/shared/management/commands/run_worker.py'
- 'shifter/shifter_platform/requirements-gcp.txt'
- 'shifter/shifter_platform/requirements-gcp.lock'
- 'shifter/engine/provisioner/requirements-gcp.txt'
- 'shifter/shifter_platform/Dockerfile'
- 'shifter/engine/provisioner/Dockerfile'
- '.github/workflows/_gcp-dev.yml'
- '.github/workflows/deploy.yml'
# Quality runs by exclusion: ordinary docs-only diffs are the only
# general PR/dev skip path. This classifier intentionally lives beside
# the deploy path filters so future path categories are added once.
- uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
id: quality_non_docs
with:
predicate-quantifier: every
filters: |
non_docs:
- '**'
- '!docs/**'
- '!**/*.md'
- '!shifter/shifter_platform/documentation/**'
# Guardrail docs are architecture inputs, not ordinary docs-only
# changes. They must still run Quality so ADR guard validates them.
- uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
id: quality_guardrails
with:
filters: |
guardrail_docs:
- '.github/pull_request_template.md'
- '.github/copilot-instructions.md'
- '.gc/plan-rules.md'
- 'changelog.d/README.md'
- 'docs/adr/**'
- 'shifter/shifter_platform/documentation/docs/technical/dev/adr-enforcement.md'
- name: Set environment
id: env
run: |
AWS_ENV="dev"
AWS_IS_DEV="true"
AWS_GITHUB_ENV="aws-dev"
GCP_ENV="gcp-dev"
GCP_GITHUB_ENV="gcp-dev"
RUN_AWS="false"
RUN_GCP="false"
APPLY_AWS="false"
DEPLOY_GCP="false"
FAST_GCP_DEPLOY="false"
if [ "${{ github.event_name }}" == "pull_request" ]; then
# PR-triggered deploy workflow runs are hosted-only quality gates.
# Never route provider deploy jobs from untrusted PR code.
:
elif [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
BRANCH="${GITHUB_REF#refs/heads/}"
case "$BRANCH" in
main)
AWS_ENV="prod"
AWS_IS_DEV="false"
AWS_GITHUB_ENV="aws-prod"
RUN_AWS="true"
APPLY_AWS="true"
;;
aws-dev)
RUN_AWS="true"
APPLY_AWS="true"
;;
aws-proof)
AWS_ENV="proof"
AWS_IS_DEV="false"
AWS_GITHUB_ENV="aws-proof"
RUN_AWS="true"
APPLY_AWS="true"
;;
gcp-dev)
RUN_GCP="true"
DEPLOY_GCP="true"
FAST_GCP_DEPLOY="true"
;;
dev)
RUN_AWS="true"
RUN_GCP="true"
;;
esac
elif [ "${{ github.event_name }}" == "push" ]; then
BRANCH="${GITHUB_REF#refs/heads/}"
case "$BRANCH" in
aws-dev)
RUN_AWS="true"
APPLY_AWS="true"
;;
aws-proof)
AWS_ENV="proof"
AWS_IS_DEV="false"
AWS_GITHUB_ENV="aws-proof"
RUN_AWS="true"
APPLY_AWS="true"
;;
gcp-dev)
RUN_GCP="true"
DEPLOY_GCP="true"
FAST_GCP_DEPLOY="true"
;;
esac
fi
{
echo "aws_environment=$AWS_ENV"
echo "aws_is_dev=$AWS_IS_DEV"
echo "aws_github_environment=$AWS_GITHUB_ENV"
echo "gcp_environment=$GCP_ENV"
echo "gcp_github_environment=$GCP_GITHUB_ENV"
echo "run_aws=$RUN_AWS"
echo "run_gcp=$RUN_GCP"
echo "apply_aws=$APPLY_AWS"
echo "deploy_gcp=$DEPLOY_GCP"
echo "fast_gcp_deploy=$FAST_GCP_DEPLOY"
} >> "$GITHUB_OUTPUT"
# Always-present CI enforcement for the fast local pre-commit baseline that
# must not be bypassed by path filters: file hygiene and secret scanning.
precommit:
name: Pre-commit
if: |
github.event_name == 'pull_request' ||
github.ref == 'refs/heads/dev' ||
github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v6
- name: Run file hygiene and secret scan hooks
run: |
set -euo pipefail
export PRE_COMMIT_HOME="${RUNNER_TEMP}/pre-commit-cache"
python3 -m venv "${RUNNER_TEMP}/precommit-venv"
# shellcheck disable=SC1091
. "${RUNNER_TEMP}/precommit-venv/bin/activate"
python -m pip install --upgrade pip
python -m pip install 'pre-commit==4.3.0'
hooks=(
trailing-whitespace
end-of-file-fixer
check-yaml
check-json
check-added-large-files
check-merge-conflict
detect-private-key
gitleaks
)
for hook in "${hooks[@]}"; do
pre-commit run "$hook" --all-files --show-diff-on-failure
done
# ============================================================================
# DEPENDENCY CHAIN: Core -> Range -> Platform
# Each component waits for its upstream dependency before running
# ============================================================================
quality:
name: Quality
needs: changes
# Run Quality on PRs and direct pushes to `dev` unless the diff is
# provably ordinary docs-only. Manual dispatch remains a deliberate
# full-validation path, except for the existing fast GCP deploy route.
# Pushes to deployment branches (aws-dev, gcp-dev) only happen via merge
# from `dev` (per repo rule: deploy branches must never be ahead of dev),
# so the same SHA already passed Quality on its dev push and re-running
# here is pure duplicate work. Deploy jobs already tolerate skipped
# Quality via `(needs.quality.result == 'success' || ... == 'skipped')`.
# The proof tenant standup path (manual dispatch on aws-proof) also skips
# Quality: it redeploys a SHA already validated on dev.
if: |
!(github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/aws-proof') &&
(github.event_name == 'pull_request' ||
github.event_name == 'workflow_dispatch' ||
github.ref == 'refs/heads/dev') &&
needs.changes.outputs.fast_gcp_deploy != 'true' &&
(needs.changes.outputs.quality_relevant == 'true' ||
github.event_name == 'workflow_dispatch')
uses: ./.github/workflows/_quality.yml
with:
# Commit-message / label-based test skips are not accepted on protected
# branches (ADR-003-R2, #760). Unit-test jobs honor skip_tests inside
# _quality.yml, but lint, architecture, and security jobs do not.
skip_tests: false
run_full_matrix: ${{ github.event_name == 'workflow_dispatch' }}
# Build + boot the production portal image when the image's own inputs
# change (portal_image), the portal platform Terraform changes
# (shifter_platform), or the smoke's own implementation changes
# (stack_smoke - so a CI guardrail re-runs when it is edited);
# workflow_dispatch is the deliberate full-validation path. Engine-only /
# docs-only PRs never pay the image build.
run_stack_smoke: ${{ needs.changes.outputs.portal_image == 'true' || needs.changes.outputs.shifter_platform == 'true' || needs.changes.outputs.stack_smoke == 'true' || github.event_name == 'workflow_dispatch' }}
secrets:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
# PR Gate — a single always-present aggregate check for branch
# protection. `dev` protection requires this one context instead of the
# individual Quality / * checks: those are only created when the
# `quality` job runs (it is conditional on quality-relevant paths), so
# requiring them directly leaves branch protection waiting forever on
# PRs that touch none of those paths. Scoped to pull_request events, so
# it never runs on aws-dev / gcp-dev / main pushes.
pr-gate:
name: PR Gate
needs: [changes, precommit, quality]
if: ${{ always() && github.event_name == 'pull_request' }}
runs-on: ubuntu-latest
steps:
- name: Assert required PR jobs passed
run: |
changes_result='${{ needs.changes.result }}'
precommit_result='${{ needs.precommit.result }}'
quality_result='${{ needs.quality.result }}'
quality_relevant='${{ needs.changes.outputs.quality_relevant }}'
echo "changes=$changes_result precommit=$precommit_result quality=$quality_result quality_relevant=$quality_relevant"
if [ "$changes_result" != "success" ]; then
echo "::error::Detect Changes did not succeed (result=$changes_result)"
exit 1
fi
if [ "$precommit_result" != "success" ]; then
echo "::error::Pre-commit did not pass (result=$precommit_result)"
exit 1
fi
# `quality` is legitimately skipped only for ordinary docs-only
# PRs. Bootstrap, k8s, workflow, ADR, and guardrail changes remain
# quality-relevant and must not disappear behind a skipped job.
if [ "$quality_result" = "skipped" ] && [ "$quality_relevant" != "false" ]; then
echo "::error::Quality was skipped even though quality_relevant=$quality_relevant"
exit 1
fi
if [ "$quality_result" != "success" ] && [ "$quality_result" != "skipped" ]; then
echo "::error::Quality did not pass (result=$quality_result)"
exit 1
fi
echo "PR gate passed"
gcp-dev:
name: GCP Dev
needs: [changes, quality]
# `_gcp-dev.yml::deploy` requests `id-token: write` (GCP OIDC via
# workload identity federation). Reusable workflows can't request
# higher GITHUB_TOKEN scopes than the caller grants — without this
# block the workflow fails at startup with
# `nested job 'deploy' is requesting 'id-token: write', but is only
# allowed 'id-token: none'`.
permissions:
contents: read
id-token: write
if: |
always() &&
github.event_name != 'pull_request' &&
needs.changes.outputs.run_gcp == 'true' &&
(
needs.changes.outputs.gcp == 'true' ||
needs.changes.outputs.shifter_engine == 'true' ||
needs.changes.outputs.shifter_platform == 'true' ||
github.event_name == 'workflow_dispatch'
) &&
(needs.quality.result == 'success' || needs.quality.result == 'skipped')
uses: ./.github/workflows/_gcp-dev.yml
with:
environment: ${{ needs.changes.outputs.gcp_environment }}
github_environment: ${{ needs.changes.outputs.gcp_github_environment }}
deploy_changes: ${{ needs.changes.outputs.deploy_gcp == 'true' }}
# Automatic deploys always require an active certificate; only a manual
# workflow_dispatch may opt into the first-time-bootstrap bypass.
require_active_certificate: ${{ github.event_name != 'workflow_dispatch' || inputs.gcp_require_active_certificate }}
secrets:
GCP_SERVICE_ACCOUNT: ${{ secrets.GCP_SERVICE_ACCOUNT }}
GCP_WORKLOAD_IDENTITY_PROVIDER: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }}
# Deployment-owned identity/config values consumed by _gcp-dev.yml's
# render steps. Must be forwarded here: the reusable workflow only sees
# secrets the caller passes, so an omitted one renders empty and the
# deploy fails loud (e.g. "GCP_PROJECT_ID is not set").
GCP_PROJECT_ID: ${{ secrets.GCP_PROJECT_ID }}
GCP_PUBLIC_HOSTNAME: ${{ secrets.GCP_PUBLIC_HOSTNAME }}
GCP_IDENTITY_ALLOWED_EMAIL_DOMAIN: ${{ secrets.GCP_IDENTITY_ALLOWED_EMAIL_DOMAIN }}
GCP_MASTER_AUTHORIZED_CIDRS: ${{ secrets.GCP_MASTER_AUTHORIZED_CIDRS }}
SHIFTER_CONFIG_GCP_DEV: ${{ secrets.SHIFTER_CONFIG_GCP_DEV }}
GCP_BOOTSTRAP_ADMIN_EMAIL: ${{ secrets.GCP_BOOTSTRAP_ADMIN_EMAIL }}
GCP_BOOTSTRAP_ADMIN_PASSWORD: ${{ secrets.GCP_BOOTSTRAP_ADMIN_PASSWORD }}
PLATFORM_BOOTSTRAP_STAFF_EMAILS: ${{ secrets.PLATFORM_BOOTSTRAP_STAFF_EMAILS }}
PLATFORM_BOOTSTRAP_SUPERUSER_EMAILS: ${{ secrets.PLATFORM_BOOTSTRAP_SUPERUSER_EMAILS }}
core:
name: Core
needs: [changes, quality]
# `_core.yml::plan` + `apply` request `id-token: write` (AWS OIDC
# via configure-aws-credentials) and `pull-requests: write` (plan
# comments). Caller must grant the union.
permissions:
contents: read
id-token: write
pull-requests: write
if: |
always() &&
github.event_name != 'pull_request' &&
needs.changes.outputs.run_aws == 'true' &&
(needs.changes.outputs.core == 'true' || github.event_name == 'workflow_dispatch') &&
(needs.quality.result == 'success' || needs.quality.result == 'skipped')
uses: ./.github/workflows/_core.yml
with:
environment: ${{ needs.changes.outputs.aws_environment }}
github_environment: ${{ needs.changes.outputs.aws_github_environment }}
is_dev: ${{ needs.changes.outputs.aws_is_dev == 'true' }}
apply_changes: ${{ needs.changes.outputs.apply_aws == 'true' }}
secrets:
AWS_ROLE_ARN: ${{ secrets.AWS_ROLE_ARN }}
AWS_ROLE_ARN_DEV: ${{ secrets.AWS_ROLE_ARN_DEV }}
TF_INFRA_STATE_BUCKET: ${{ secrets.TF_INFRA_STATE_BUCKET }}
TF_INFRA_STATE_BUCKET_DEV: ${{ secrets.TF_INFRA_STATE_BUCKET_DEV }}
TF_INFRA_STATE_BUCKET_PROOF: ${{ secrets.TF_INFRA_STATE_BUCKET_PROOF }}
AWS_ROLE_ARN_PROOF: ${{ secrets.AWS_ROLE_ARN_PROOF }}
TF_VARS_DEV_CORE: ${{ secrets.TF_VARS_DEV_CORE }}
TF_VARS_PROOF_CORE: ${{ secrets.TF_VARS_PROOF_CORE }}
TF_VARS_PROD_CORE: ${{ secrets.TF_VARS_PROD_CORE }}
range:
name: Range
needs: [changes, quality, core]
# Same OIDC + PR-comment scopes as `core` — `_range.yml::plan` and
# `apply` mirror the core workflow's permission requests.
permissions:
contents: read
id-token: write
pull-requests: write
if: |
always() &&
github.event_name != 'pull_request' &&
needs.changes.outputs.run_aws == 'true' &&
(needs.changes.outputs.range == 'true' || github.event_name == 'workflow_dispatch') &&
(needs.core.result == 'success' || needs.core.result == 'skipped') &&
(needs.quality.result == 'success' || needs.quality.result == 'skipped')
uses: ./.github/workflows/_range.yml
with:
environment: ${{ needs.changes.outputs.aws_environment }}
github_environment: ${{ needs.changes.outputs.aws_github_environment }}
is_dev: ${{ needs.changes.outputs.aws_is_dev == 'true' }}
apply_changes: ${{ needs.changes.outputs.apply_aws == 'true' }}
secrets:
AWS_ROLE_ARN: ${{ secrets.AWS_ROLE_ARN }}
AWS_ROLE_ARN_DEV: ${{ secrets.AWS_ROLE_ARN_DEV }}
AWS_ROLE_ARN_PROOF: ${{ secrets.AWS_ROLE_ARN_PROOF }}
TF_VARS_DEV_RANGE: ${{ secrets.TF_VARS_DEV_RANGE }}
TF_VARS_PROOF_RANGE: ${{ secrets.TF_VARS_PROOF_RANGE }}
TF_VARS_PROD_RANGE: ${{ secrets.TF_VARS_PROD_RANGE }}
SHIFTER_CONFIG_DEV_RANGE: ${{ secrets.SHIFTER_CONFIG_DEV_RANGE }}
SHIFTER_CONFIG_PROOF_RANGE: ${{ secrets.SHIFTER_CONFIG_PROOF_RANGE }}
SHIFTER_CONFIG_PROD_RANGE: ${{ secrets.SHIFTER_CONFIG_PROD_RANGE }}
TF_INFRA_STATE_BUCKET: ${{ secrets.TF_INFRA_STATE_BUCKET }}
TF_INFRA_STATE_BUCKET_DEV: ${{ secrets.TF_INFRA_STATE_BUCKET_DEV }}
TF_INFRA_STATE_BUCKET_PROOF: ${{ secrets.TF_INFRA_STATE_BUCKET_PROOF }}
shifter-engine:
name: Shifter Engine
needs: [changes, quality, core]
# `_shifter-engine.yml::build` + `deploy` request `id-token: write`
# (AWS OIDC for ECR + ECS task push).
permissions:
contents: read
id-token: write
if: |
always() &&
github.event_name != 'pull_request' &&
needs.changes.outputs.run_aws == 'true' &&
(needs.changes.outputs.shifter_engine == 'true' || github.event_name == 'workflow_dispatch') &&
(needs.core.result == 'success' || needs.core.result == 'skipped') &&
(needs.quality.result == 'success' || needs.quality.result == 'skipped')
uses: ./.github/workflows/_shifter-engine.yml
with:
environment: ${{ needs.changes.outputs.aws_environment }}
github_environment: ${{ needs.changes.outputs.aws_github_environment }}
is_dev: ${{ needs.changes.outputs.aws_is_dev == 'true' }}
deploy_changes: ${{ needs.changes.outputs.apply_aws == 'true' }}
# Only a manual workflow_dispatch may opt into the first-deploy bootstrap
# bypass; automatic (push) deploys always require the task family to exist.
first_deploy: ${{ github.event_name == 'workflow_dispatch' && inputs.aws_first_deploy }}
secrets:
AWS_ROLE_ARN: ${{ secrets.AWS_ROLE_ARN }}
AWS_ROLE_ARN_DEV: ${{ secrets.AWS_ROLE_ARN_DEV }}
AWS_ROLE_ARN_PROOF: ${{ secrets.AWS_ROLE_ARN_PROOF }}
shifter_platform:
name: Shifter Platform
needs: [changes, quality, core, range, shifter-engine]
# `_shifter-platform.yml` jobs (plan, push-guacamole-images, apply,
# build, deploy, post-deploy-smoke) request `id-token: write` (AWS OIDC),
# `pull-requests: write` (plan comments), and `issues: write` (smoke
# failure issues). Caller grants the union.
permissions:
contents: read
id-token: write
pull-requests: write
issues: write
if: |
always() &&
github.event_name != 'pull_request' &&
needs.changes.outputs.run_aws == 'true' &&
(needs.changes.outputs.shifter_platform == 'true' ||
needs.changes.outputs.portal_image == 'true' ||
github.event_name == 'workflow_dispatch') &&
(needs.core.result == 'success' || needs.core.result == 'skipped') &&
(needs.range.result == 'success' || needs.range.result == 'skipped') &&
(needs.shifter-engine.result == 'success' || needs.shifter-engine.result == 'skipped') &&
(needs.quality.result == 'success' || needs.quality.result == 'skipped')
uses: ./.github/workflows/_shifter-platform.yml
with:
environment: ${{ needs.changes.outputs.aws_environment }}
github_environment: ${{ needs.changes.outputs.aws_github_environment }}
is_dev: ${{ needs.changes.outputs.aws_is_dev == 'true' }}
apply_changes: ${{ needs.changes.outputs.apply_aws == 'true' }}
engine_image_digest: ${{ needs.shifter-engine.outputs.image_digest }}
# Split per concern (#913): Terraform plan/apply stays scoped to
# Terraform-relevant changes, while app-only changes drive just
# the portal image build/deploy. workflow_dispatch remains the
# deliberate full-deploy path for both concerns.
platform_changes: ${{ needs.changes.outputs.shifter_platform == 'true' || github.event_name == 'workflow_dispatch' }}
portal_image_changes: ${{ needs.changes.outputs.portal_image == 'true' || github.event_name == 'workflow_dispatch' }}
secrets:
AWS_ROLE_ARN: ${{ secrets.AWS_ROLE_ARN }}
AWS_ROLE_ARN_DEV: ${{ secrets.AWS_ROLE_ARN_DEV }}
AWS_ROLE_ARN_PROOF: ${{ secrets.AWS_ROLE_ARN_PROOF }}
PLATFORM_BOOTSTRAP_STAFF_EMAILS: ${{ secrets.PLATFORM_BOOTSTRAP_STAFF_EMAILS }}
PLATFORM_BOOTSTRAP_SUPERUSER_EMAILS: ${{ secrets.PLATFORM_BOOTSTRAP_SUPERUSER_EMAILS }}
TF_VARS_DEV_PORTAL: ${{ secrets.TF_VARS_DEV_PORTAL }}
TF_VARS_PROD_PORTAL: ${{ secrets.TF_VARS_PROD_PORTAL }}
TF_INFRA_STATE_BUCKET: ${{ secrets.TF_INFRA_STATE_BUCKET }}
TF_INFRA_STATE_BUCKET_DEV: ${{ secrets.TF_INFRA_STATE_BUCKET_DEV }}
TF_INFRA_STATE_BUCKET_PROOF: ${{ secrets.TF_INFRA_STATE_BUCKET_PROOF }}
TF_VARS_PROOF_PORTAL: ${{ secrets.TF_VARS_PROOF_PORTAL }}
SMOKE_TEST_USER_EMAIL: ${{ secrets.SMOKE_TEST_USER_EMAIL }}
SMOKE_LINUX_AGENT_ID: ${{ secrets.SMOKE_LINUX_AGENT_ID }}
SMOKE_WINDOWS_AGENT_ID: ${{ secrets.SMOKE_WINDOWS_AGENT_ID }}