diff --git a/.github/workflows/aio-app-deployment.yml b/.github/workflows/aio-app-deployment.yml new file mode 100644 index 0000000..d0a6182 --- /dev/null +++ b/.github/workflows/aio-app-deployment.yml @@ -0,0 +1,107 @@ +name: AIO App Deployment + +on: + workflow_call: + inputs: + environment: + description: GitHub environment to run in + type: string + required: true + aio-cli-version: + description: Adobe I/O CLI version to install + type: string + required: false + default: 11.x.x + app-directory: + description: > + Working directory for the app, relative to the repo root. + Use this when deploying a subdirectory app in an NX monorepo. + type: string + required: false + default: '.' + package-manager: + description: Node package manager to use (npm or yarn) + type: string + required: false + default: yarn + debug: + description: Enable verbose logging + type: boolean + required: false + default: false + +jobs: + deploy: + name: Deploy AIO App + runs-on: ubuntu-latest + environment: ${{ inputs.environment }} + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2 + with: + persist-credentials: false + + - name: Setup Node.js + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f #v6.3.0 + with: + node-version-file: .nvmrc + cache: ${{ inputs.package-manager }} + + - name: Enable Corepack + if: inputs.package-manager == 'yarn' + run: corepack enable + + - name: Install dependencies (npm) + if: inputs.package-manager != 'yarn' + run: npm ci + + - name: Install dependencies (yarn) + if: inputs.package-manager == 'yarn' + run: yarn install + + - name: Setup Adobe I/O CLI + uses: adobe/aio-cli-setup-action@5a7a7313f7024283e7eb384dc79b69b02effb307 #v1.3.0 + with: + os: ubuntu-latest + version: ${{ inputs.aio-cli-version }} + + - name: Authenticate with Adobe I/O + uses: adobe/aio-apps-action@0a7e7eb813fbdf73faea7ee7d03fd20e6dd7badb #v4.0.0 + with: + os: ubuntu-latest + command: oauth_sts + CLIENTID: ${{ secrets.AIO_CLIENT_ID }} + CLIENTSECRET: ${{ secrets.AIO_CLIENT_SECRET }} + TECHNICALACCOUNTID: ${{ secrets.AIO_TECHNICAL_ACCOUNT_ID }} + TECHNICALACCOUNTEMAIL: ${{ secrets.AIO_TECHNICAL_ACCOUNT_EMAIL }} + IMSORGID: ${{ secrets.AIO_IMS_ORG_ID }} + SCOPES: ${{ secrets.AIO_SCOPES }} + + - name: Export extra environment variables + env: + EXTRA_VARS: ${{ vars.AIO_DEPLOY_EXTRA_VARS }} + EXTRA_SECRETS: ${{ secrets.AIO_DEPLOY_EXTRA_SECRETS }} + run: | + if [ -n "$EXTRA_VARS" ]; then + while IFS= read -r line; do + [ -n "$line" ] && echo "$line" >> "$GITHUB_ENV" + done <<< "$EXTRA_VARS" + fi + if [ -n "$EXTRA_SECRETS" ]; then + while IFS= read -r line; do + [ -n "$line" ] && echo "$line" >> "$GITHUB_ENV" + done <<< "$EXTRA_SECRETS" + fi + + - name: Deploy + working-directory: ${{ inputs.app-directory }} + env: + AIO_RUNTIME_NAMESPACE: ${{ secrets.AIO_RUNTIME_NAMESPACE }} + AIO_RUNTIME_AUTH: ${{ secrets.AIO_RUNTIME_AUTH }} + AIO_PROJECT_ID: ${{ vars.AIO_PROJECT_ID }} + AIO_PROJECT_NAME: ${{ vars.AIO_PROJECT_NAME }} + AIO_PROJECT_ORG_ID: ${{ vars.AIO_PROJECT_ORG_ID }} + AIO_PROJECT_WORKSPACE_ID: ${{ vars.AIO_PROJECT_WORKSPACE_ID }} + AIO_PROJECT_WORKSPACE_NAME: ${{ vars.AIO_PROJECT_WORKSPACE_NAME }} + VERBOSE: ${{ inputs.debug && '--verbose' || '' }} + run: aio app deploy${VERBOSE:+ $VERBOSE} diff --git a/.github/workflows/aio-mesh-deployment.yml b/.github/workflows/aio-mesh-deployment.yml new file mode 100644 index 0000000..01a3e01 --- /dev/null +++ b/.github/workflows/aio-mesh-deployment.yml @@ -0,0 +1,188 @@ +name: AIO Mesh Deployment + +on: + workflow_call: + inputs: + environment: + description: GitHub environment to run in + type: string + required: true + aio-cli-version: + description: Adobe I/O CLI version to install + type: string + required: false + default: 11.x.x + mesh-config: + description: Path to the mesh config file, relative to mesh-directory + type: string + required: false + default: mesh.json + mesh-directory: + description: > + Working directory for the mesh, relative to the repo root. + Use this when deploying a subdirectory mesh in an NX monorepo. + type: string + required: false + default: '.' + package-manager: + description: Node package manager to use (npm or yarn) + type: string + required: false + default: yarn + build-command: + description: Optional command to run before deploying the mesh (e.g. yarn build:resolvers) + type: string + required: false + default: '' + provisioning-timeout: + description: Seconds to wait for the mesh to finish provisioning before failing + type: number + required: false + default: 300 + debug: + description: Enable verbose logging + type: boolean + required: false + default: false + +jobs: + deploy: + name: Deploy API Mesh + runs-on: ubuntu-latest + environment: ${{ inputs.environment }} + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2 + with: + persist-credentials: false + + - name: Setup Node.js + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f #v6.3.0 + with: + node-version-file: .nvmrc + cache: ${{ inputs.package-manager }} + + - name: Enable Corepack + if: inputs.package-manager == 'yarn' + run: corepack enable + + - name: Install dependencies (npm) + if: inputs.package-manager != 'yarn' + run: npm ci + + - name: Install dependencies (yarn) + if: inputs.package-manager == 'yarn' + run: yarn install + + - name: Setup Adobe I/O CLI + uses: adobe/aio-cli-setup-action@5a7a7313f7024283e7eb384dc79b69b02effb307 #v1.3.0 + with: + os: ubuntu-latest + version: ${{ inputs.aio-cli-version }} + + - name: Install API Mesh plugin + run: aio plugins:install @adobe/aio-cli-plugin-api-mesh + + - name: Authenticate with Adobe I/O + uses: adobe/aio-apps-action@0a7e7eb813fbdf73faea7ee7d03fd20e6dd7badb #v4.0.0 + with: + os: ubuntu-latest + command: oauth_sts + CLIENTID: ${{ secrets.AIO_CLIENT_ID }} + CLIENTSECRET: ${{ secrets.AIO_CLIENT_SECRET }} + TECHNICALACCOUNTID: ${{ secrets.AIO_TECHNICAL_ACCOUNT_ID }} + TECHNICALACCOUNTEMAIL: ${{ secrets.AIO_TECHNICAL_ACCOUNT_EMAIL }} + IMSORGID: ${{ secrets.AIO_IMS_ORG_ID }} + SCOPES: ${{ secrets.AIO_SCOPES }} + + - name: Select Adobe I/O organisation, project and workspace + env: + AIO_PROJECT_ORG_ID: ${{ vars.AIO_PROJECT_ORG_ID }} + AIO_PROJECT_ID: ${{ vars.AIO_PROJECT_ID }} + AIO_PROJECT_WORKSPACE_ID: ${{ vars.AIO_PROJECT_WORKSPACE_ID }} + run: | + aio config set cli.env prod + aio console:org:select "$AIO_PROJECT_ORG_ID" + aio console:project:select "$AIO_PROJECT_ID" + aio console:workspace:select "$AIO_PROJECT_WORKSPACE_ID" + + - name: Pre-deploy build + if: inputs.build-command != '' + working-directory: ${{ inputs.mesh-directory }} + env: + BUILD_COMMAND: ${{ inputs.build-command }} + run: $BUILD_COMMAND + + - name: Generate mesh config files + working-directory: ${{ inputs.mesh-directory }} + env: + AIO_MESH_ENV_VARS: ${{ vars.AIO_MESH_ENV_VARS }} + AIO_MESH_SECRETS: ${{ secrets.AIO_MESH_SECRETS }} + run: | + if [ -n "$AIO_MESH_ENV_VARS" ]; then + echo "$AIO_MESH_ENV_VARS" > .env + fi + if [ -n "$AIO_MESH_SECRETS" ]; then + while IFS= read -r line; do + if [ -n "$line" ]; then + key="${line%%=*}" + value="${line#*=}" + printf '%s: %s\n' "$key" "$value" + fi + done <<< "$AIO_MESH_SECRETS" > secrets.yaml + fi + + - name: Create or update mesh + working-directory: ${{ inputs.mesh-directory }} + env: + MESH_CONFIG: ${{ inputs.mesh-config }} + run: | + FLAGS="-c $MESH_CONFIG" + [ -f ".env" ] && FLAGS="$FLAGS --env=.env" + [ -f "secrets.yaml" ] && FLAGS="$FLAGS --secrets=secrets.yaml" + + MESH_STATUS=$(aio api-mesh:status 2>&1 || echo "NO_MESH") + + if [[ "$MESH_STATUS" == *"NO_MESH"* ]] || [[ "$MESH_STATUS" == *"Error"* ]]; then + echo "Creating new mesh..." + aio api-mesh:create $FLAGS + else + echo "Updating existing mesh..." + aio api-mesh:update $FLAGS + fi + + - name: Wait for mesh to be provisioned + env: + TIMEOUT: ${{ inputs.provisioning-timeout }} + run: | + echo "Waiting for mesh to be provisioned..." + START_TIME=$(date +%s) + POLL_INTERVAL=30 + + while true; do + STATUS_OUTPUT=$(aio api-mesh:status 2>&1 || true) + + if [[ "$STATUS_OUTPUT" == *"Mesh provisioned successfully"* ]]; then + echo "Mesh provisioned successfully!" + break + elif [[ "$STATUS_OUTPUT" == *"Currently provisioning your mesh"* ]] || \ + [[ "$STATUS_OUTPUT" == *"Mesh is currently building"* ]]; then + CURRENT_TIME=$(date +%s) + ELAPSED=$((CURRENT_TIME - START_TIME)) + + if [ "$ELAPSED" -ge "$TIMEOUT" ]; then + echo "ERROR: Mesh provisioning timed out after ${TIMEOUT} seconds" + exit 1 + fi + + echo "Still provisioning... waiting ${POLL_INTERVAL}s (elapsed: ${ELAPSED}s)" + sleep $POLL_INTERVAL + else + echo "ERROR: Unexpected mesh status:" + echo "$STATUS_OUTPUT" + exit 1 + fi + done + + - name: Describe mesh + run: aio api-mesh:describe diff --git a/.github/workflows/aio-secure-actions.yml b/.github/workflows/aio-secure-actions.yml new file mode 100644 index 0000000..bcc417a --- /dev/null +++ b/.github/workflows/aio-secure-actions.yml @@ -0,0 +1,100 @@ +name: AIO Secure Actions + +on: + workflow_call: + inputs: + environment: + description: GitHub environment to run in + type: string + required: true + aio-cli-version: + description: Adobe I/O CLI version to install + type: string + required: false + default: 11.x.x + actions: + description: Newline-separated list of runtime action names to secure + type: string + required: true + secrets: + AIO_CLIENT_ID: + description: Adobe I/O client ID + required: true + AIO_CLIENT_SECRET: + description: Adobe I/O client secret + required: true + AIO_TECHNICAL_ACCOUNT_ID: + description: Adobe I/O technical account ID + required: true + AIO_TECHNICAL_ACCOUNT_EMAIL: + description: Adobe I/O technical account email + required: true + AIO_IMS_ORG_ID: + description: Adobe I/O IMS organisation ID + required: true + AIO_SCOPES: + description: Adobe I/O OAuth scopes + required: true + AIO_RUNTIME_NAMESPACE: + description: Adobe I/O Runtime namespace + required: true + AIO_RUNTIME_AUTH: + description: Adobe I/O Runtime auth token + required: true + AIO_ACTION_AUTH_HASH: + description: Web-secure hash to apply to the runtime actions + required: true + +jobs: + secure: + name: Secure Actions + runs-on: ubuntu-latest + environment: ${{ inputs.environment }} + steps: + - name: Setup Adobe I/O CLI + uses: adobe/aio-cli-setup-action@5a7a7313f7024283e7eb384dc79b69b02effb307 #v1.3.0 + with: + os: ubuntu-latest + version: ${{ inputs.aio-cli-version }} + + - name: Authenticate with Adobe I/O + uses: adobe/aio-apps-action@0a7e7eb813fbdf73faea7ee7d03fd20e6dd7badb #v4.0.0 + with: + os: ubuntu-latest + command: oauth_sts + CLIENTID: ${{ secrets.AIO_CLIENT_ID }} + CLIENTSECRET: ${{ secrets.AIO_CLIENT_SECRET }} + TECHNICALACCOUNTID: ${{ secrets.AIO_TECHNICAL_ACCOUNT_ID }} + TECHNICALACCOUNTEMAIL: ${{ secrets.AIO_TECHNICAL_ACCOUNT_EMAIL }} + IMSORGID: ${{ secrets.AIO_IMS_ORG_ID }} + SCOPES: ${{ secrets.AIO_SCOPES }} + + - name: Secure Actions + env: + AIO_RUNTIME_NAMESPACE: ${{ secrets.AIO_RUNTIME_NAMESPACE }} + AIO_RUNTIME_AUTH: ${{ secrets.AIO_RUNTIME_AUTH }} + AIO_PROJECT_ID: ${{ vars.AIO_PROJECT_ID }} + AIO_PROJECT_NAME: ${{ vars.AIO_PROJECT_NAME }} + AIO_PROJECT_ORG_ID: ${{ vars.AIO_PROJECT_ORG_ID }} + AIO_PROJECT_WORKSPACE_ID: ${{ vars.AIO_PROJECT_WORKSPACE_ID }} + AIO_PROJECT_WORKSPACE_NAME: ${{ vars.AIO_PROJECT_WORKSPACE_NAME }} + ACTIONS: ${{ inputs.actions }} + AIO_ACTION_AUTH_HASH: ${{ secrets.AIO_ACTION_AUTH_HASH }} + run: | + ACTION_LIST=$(aio runtime action list --json) + + while IFS= read -r action; do + action=$(echo "$action" | tr -d '[:space:]') + [ -z "$action" ] && continue + + JQ_FILTER='.[] | select(.name == $name) | "\(.namespace)/\(.name)"' + action_path=$(echo "$ACTION_LIST" | jq -r --arg name "$action" "$JQ_FILTER") + + if [ -z "$action_path" ]; then + echo "Warning: action '$action' not found in runtime action list, skipping" + continue + fi + + echo "Securing action: $action_path" + aio runtime action update "$action_path" --web=true --web-secure="$AIO_ACTION_AUTH_HASH" + done <<< "$ACTIONS" diff --git a/README.md b/README.md index 815b9cf..9798086 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ A collection of GitHub action workflows. Built using the [reusable workflows](ht | Workflow | Description | |----------|-------------| +| [AIO App Deployment](docs/aio-app-deployment.md) | Adobe I/O App Builder deployment for standalone apps and NX monorepos | +| [AIO Mesh Deployment](docs/aio-mesh-deployment.md) | Adobe I/O API Mesh create/update with provisioning polling | | [AWS CDK](docs/aws-cdk.md) | Multi-environment infrastructure synthesis, diffs and deployments with automatic package manager detection | | [Changeset Check](docs/changeset-check.md) | Advisory PR comments when changesets are missing for affected packages | | [Changeset Release](docs/changeset-release.md) | Automated package versioning and publishing with Changesets | diff --git a/docs/aio-app-deployment.md b/docs/aio-app-deployment.md new file mode 100644 index 0000000..216a75e --- /dev/null +++ b/docs/aio-app-deployment.md @@ -0,0 +1,193 @@ +# AIO App Deployment + +Deploys an Adobe I/O App Builder application using `aio app deploy`. Supports both standalone apps and apps within NX monorepos. + +#### **Inputs** + +| Name | Required | Type | Default | Description | +|------|----------|------|---------|-------------| +| `environment` | ✅ | string | | GitHub environment to run in | +| `aio-cli-version` | ❌ | string | `11.x.x` | Adobe I/O CLI version to install | +| `app-directory` | ❌ | string | `.` | Working directory for the app, relative to the repo root. Use for NX monorepo subdirectory apps. | +| `package-manager` | ❌ | string | `yarn` | Node package manager to use (`npm` or `yarn`) | +| `debug` | ❌ | boolean | `false` | Enable verbose logging | + +#### **Variables and Secrets** + +Configure these in the GitHub Environment (or at the repository level if not using environments). + +**AIO Authentication** — required: + +| Name | Type | Description | +|------|------|-------------| +| `AIO_CLIENT_ID` | Secret | Adobe I/O OAuth client ID | +| `AIO_CLIENT_SECRET` | Secret | Adobe I/O OAuth client secret | +| `AIO_TECHNICAL_ACCOUNT_ID` | Secret | Technical account ID | +| `AIO_TECHNICAL_ACCOUNT_EMAIL` | Secret | Technical account email | +| `AIO_IMS_ORG_ID` | Secret | IMS organisation ID | +| `AIO_SCOPES` | Secret | OAuth scopes (space or comma separated) | + +**AIO Runtime / Project** — required: + +| Name | Type | Description | +|------|------|-------------| +| `AIO_RUNTIME_NAMESPACE` | Secret | Adobe I/O Runtime namespace | +| `AIO_RUNTIME_AUTH` | Secret | Adobe I/O Runtime auth token | +| `AIO_PROJECT_ID` | Variable | Adobe I/O project ID | +| `AIO_PROJECT_NAME` | Variable | Adobe I/O project name | +| `AIO_PROJECT_ORG_ID` | Variable | Adobe I/O project org ID | +| `AIO_PROJECT_WORKSPACE_ID` | Variable | Adobe I/O workspace ID | +| `AIO_PROJECT_WORKSPACE_NAME` | Variable | Adobe I/O workspace name | + +**App-specific extras** — optional: + +| Name | Type | Description | +|------|------|-------------| +| `AIO_DEPLOY_EXTRA_VARS` | Variable | Additional non-secret environment variables to inject into the deploy step | +| `AIO_DEPLOY_EXTRA_SECRETS` | Secret | Additional secret environment variables to inject into the deploy step | + +Both extra fields accept multiline `KEY=VALUE` pairs — one per line. Use these for app-specific runtime configuration that varies per project, such as third-party API credentials, AWS credentials, or feature flags. + +Example `AIO_DEPLOY_EXTRA_VARS` value: +``` +AWS_REGION=ap-southeast-2 +AWS_SNS_ARN=arn:aws:sns:ap-southeast-2:123456789:my-topic +STAGE=production +``` + +Example `AIO_DEPLOY_EXTRA_SECRETS` value: +``` +EXTERNAL_API_BASE_URL=https://api.example.com +EXTERNAL_API_KEY=abc123 +AWS_ACCESS_KEY_ID=AKIA... +AWS_SECRET_ACCESS_KEY=... +``` + +--- + +#### **Example Usage** + +##### Standalone app + +```yaml +name: Deploy + +on: + push: + branches: + - staging + - production + +jobs: + deploy: + uses: aligent/workflows/.github/workflows/aio-app-deployment.yml@main + with: + environment: ${{ github.ref_name }} + secrets: inherit +``` + +GitHub Environments `staging` and `production` each contain the required AIO secrets plus any app-specific extras in `AIO_DEPLOY_EXTRA_VARS` / `AIO_DEPLOY_EXTRA_SECRETS`. + +--- + +##### NX monorepo — affected apps only + +For NX monorepos, affected-app detection is the caller's responsibility. The workflow is called once per app using a matrix strategy. + +```yaml +name: Deploy + +on: + push: + branches: + - main + workflow_dispatch: + inputs: + app: + description: App to deploy (leave empty to deploy all affected apps) + required: false + type: string + +jobs: + affected: + name: Get Affected Apps + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.affected.outputs.matrix }} + has_affected: ${{ steps.affected.outputs.has_affected }} + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + with: + node-version-file: .nvmrc + + - name: Install nx + run: npm install --no-save --ignore-scripts nx + + - name: Get affected apps + id: affected + env: + APP_INPUT: ${{ inputs.app }} + BASE_SHA: ${{ github.event.before }} + run: | + if [ -n "$APP_INPUT" ]; then + matrix=$(echo "$APP_INPUT" | jq -R -c '[.]') + echo "has_affected=true" >> $GITHUB_OUTPUT + echo "matrix=$matrix" >> $GITHUB_OUTPUT + else + BASE="${BASE_SHA:-origin/main~1}" + apps=$(npx nx show projects --affected --base="$BASE" --head=HEAD || echo "") + if [ -z "$apps" ]; then + echo "has_affected=false" >> $GITHUB_OUTPUT + echo "matrix=[]" >> $GITHUB_OUTPUT + else + matrix=$(echo "$apps" | jq -R -s -c 'split("\n") | map(select(length > 0))') + echo "has_affected=true" >> $GITHUB_OUTPUT + echo "matrix=$matrix" >> $GITHUB_OUTPUT + fi + fi + + deploy: + name: Deploy ${{ matrix.app }} + needs: affected + if: needs.affected.outputs.has_affected == 'true' + strategy: + matrix: + app: ${{ fromJson(needs.affected.outputs.matrix) }} + uses: aligent/workflows/.github/workflows/aio-app-deployment.yml@main + with: + environment: ${{ matrix.app }}-production + app-directory: ${{ matrix.app }} + secrets: inherit +``` + +Each app gets its own GitHub Environment (e.g. `my-app-production`) containing that app's AIO secrets and any project-specific extras. + +--- + +##### NX monorepo — all apps (no affected detection) + +```yaml +name: Deploy All + +on: + workflow_dispatch: + +jobs: + deploy-app-one: + uses: aligent/workflows/.github/workflows/aio-app-deployment.yml@main + with: + environment: app-one-production + app-directory: app-one + secrets: inherit + + deploy-app-two: + uses: aligent/workflows/.github/workflows/aio-app-deployment.yml@main + with: + environment: app-two-production + app-directory: app-two + secrets: inherit +``` diff --git a/docs/aio-mesh-deployment.md b/docs/aio-mesh-deployment.md new file mode 100644 index 0000000..c892009 --- /dev/null +++ b/docs/aio-mesh-deployment.md @@ -0,0 +1,217 @@ +# AIO Mesh Deployment + +Creates or updates an Adobe I/O API Mesh. Automatically detects whether the mesh already exists and calls `aio api-mesh:create` or `aio api-mesh:update` accordingly. Polls until provisioning completes. Supports both standalone mesh repos and meshes within NX monorepos. + +#### **Inputs** + +| Name | Required | Type | Default | Description | +|------|----------|------|---------|-------------| +| `environment` | ✅ | string | | GitHub environment to run in | +| `aio-cli-version` | ❌ | string | `11.x.x` | Adobe I/O CLI version to install | +| `mesh-config` | ❌ | string | `mesh.json` | Path to the mesh config file, relative to `mesh-directory` | +| `mesh-directory` | ❌ | string | `.` | Working directory for the mesh, relative to the repo root. Use for NX monorepo subdirectories. | +| `package-manager` | ❌ | string | `yarn` | Node package manager to use (`npm` or `yarn`) | +| `build-command` | ❌ | string | | Command to run before deploying (e.g. `yarn build:resolvers`). Required when the mesh uses custom resolvers that must be compiled first. | +| `provisioning-timeout` | ❌ | number | `300` | Seconds to wait for provisioning before failing | +| `debug` | ❌ | boolean | `false` | Enable verbose logging | + +#### **Variables and Secrets** + +Configure these in the GitHub Environment (or at the repository level if not using environments). + +**AIO Authentication** — required: + +| Name | Type | Description | +|------|------|-------------| +| `AIO_CLIENT_ID` | Secret | Adobe I/O OAuth client ID | +| `AIO_CLIENT_SECRET` | Secret | Adobe I/O OAuth client secret | +| `AIO_TECHNICAL_ACCOUNT_ID` | Secret | Technical account ID | +| `AIO_TECHNICAL_ACCOUNT_EMAIL` | Secret | Technical account email | +| `AIO_IMS_ORG_ID` | Secret | IMS organisation ID | +| `AIO_SCOPES` | Secret | OAuth scopes (space or comma separated) | + +**AIO Project** — required: + +| Name | Type | Description | +|------|------|-------------| +| `AIO_PROJECT_ID` | Variable | Adobe I/O project ID | +| `AIO_PROJECT_ORG_ID` | Variable | Adobe I/O project org ID | +| `AIO_PROJECT_WORKSPACE_ID` | Variable | Adobe I/O workspace ID | + +**Mesh configuration** — optional: + +| Name | Type | Description | +|------|------|-------------| +| `AIO_MESH_ENV_VARS` | Variable | Environment variables to inject into the mesh via `.env` (passed as `--env=.env`) | +| `AIO_MESH_SECRETS` | Secret | Secrets to inject into the mesh via `secrets.yaml` (passed as `--secrets=secrets.yaml`) | + +Both fields accept multiline `KEY=VALUE` pairs — one per line. If neither is set, the `--env` and `--secrets` flags are omitted from the mesh command. + +Example `AIO_MESH_ENV_VARS` value: +``` +BACKEND_ENDPOINT=https://api.example.com +CATEGORIES_URL=https://api.example.com/categories +``` + +Example `AIO_MESH_SECRETS` value: +``` +AUTH_HASH=abc123 +INTERNAL_API_URL=https://internal.example.com/parts +``` + +--- + +#### **Example Usage** + +##### Standalone mesh repo + +```yaml +name: Deploy API Mesh + +on: + push: + branches: + - staging + - production + +jobs: + deploy: + uses: aligent/workflows/.github/workflows/aio-mesh-deployment.yml@main + with: + environment: ${{ github.ref_name }} + secrets: inherit +``` + +GitHub Environments `staging` and `production` each contain the required AIO secrets. Optionally set `AIO_MESH_ENV_VARS` and `AIO_MESH_SECRETS` for mesh-specific configuration. + +--- + +##### Standalone mesh with custom resolvers + +When the mesh requires a build step before deployment (e.g. TypeScript resolvers): + +```yaml +name: Deploy API Mesh + +on: + push: + branches: + - staging + - production + +jobs: + deploy: + uses: aligent/workflows/.github/workflows/aio-mesh-deployment.yml@main + with: + environment: ${{ github.ref_name }} + build-command: yarn build:resolvers + secrets: inherit +``` + +--- + +##### NX monorepo — mesh as a subdirectory + +When a mesh lives alongside AIO apps in a monorepo: + +```yaml +name: Deploy + +on: + push: + branches: + - main + workflow_dispatch: + inputs: + app: + description: App to deploy (use 'common-api-mesh' to deploy the mesh only) + required: false + type: string + +jobs: + affected: + name: Get Affected Apps + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.affected.outputs.matrix }} + has_affected: ${{ steps.affected.outputs.has_affected }} + mesh_affected: ${{ steps.affected.outputs.mesh_affected }} + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + with: + node-version-file: .nvmrc + + - name: Install nx + run: npm install --no-save --ignore-scripts nx + + - name: Get affected apps + id: affected + env: + APP_INPUT: ${{ inputs.app }} + BASE_SHA: ${{ github.event.before }} + run: | + if [ -n "$APP_INPUT" ]; then + if [ "$APP_INPUT" = "common-api-mesh" ]; then + echo "mesh_affected=true" >> $GITHUB_OUTPUT + echo "has_affected=false" >> $GITHUB_OUTPUT + echo "matrix=[]" >> $GITHUB_OUTPUT + else + matrix=$(echo "$APP_INPUT" | jq -R -c '[.]') + echo "has_affected=true" >> $GITHUB_OUTPUT + echo "matrix=$matrix" >> $GITHUB_OUTPUT + echo "mesh_affected=false" >> $GITHUB_OUTPUT + fi + else + BASE="${BASE_SHA:-origin/main~1}" + all_apps=$(npx nx show projects --affected --base="$BASE" --head=HEAD || echo "") + if [ -z "$all_apps" ]; then + echo "has_affected=false" >> $GITHUB_OUTPUT + echo "matrix=[]" >> $GITHUB_OUTPUT + echo "mesh_affected=false" >> $GITHUB_OUTPUT + else + if echo "$all_apps" | grep -q "^common-api-mesh$"; then + echo "mesh_affected=true" >> $GITHUB_OUTPUT + else + echo "mesh_affected=false" >> $GITHUB_OUTPUT + fi + other_apps=$(echo "$all_apps" | grep -v "^common-api-mesh$" || true) + if [ -z "$other_apps" ]; then + echo "has_affected=false" >> $GITHUB_OUTPUT + echo "matrix=[]" >> $GITHUB_OUTPUT + else + matrix=$(echo "$other_apps" | jq -R -s -c 'split("\n") | map(select(length > 0))') + echo "has_affected=true" >> $GITHUB_OUTPUT + echo "matrix=$matrix" >> $GITHUB_OUTPUT + fi + fi + fi + + deploy-apps: + name: Deploy ${{ matrix.app }} + needs: affected + if: needs.affected.outputs.has_affected == 'true' + strategy: + matrix: + app: ${{ fromJson(needs.affected.outputs.matrix) }} + uses: aligent/workflows/.github/workflows/aio-app-deployment.yml@main + with: + environment: ${{ matrix.app }}-production + app-directory: ${{ matrix.app }} + secrets: inherit + + deploy-mesh: + name: Deploy API Mesh + needs: affected + if: needs.affected.outputs.mesh_affected == 'true' + uses: aligent/workflows/.github/workflows/aio-mesh-deployment.yml@main + with: + environment: common-api-mesh-production + mesh-directory: common-api-mesh + secrets: inherit +``` + +The GitHub Environment for each app (e.g. `common-api-mesh-production`) contains its own AIO secrets and any mesh-specific `AIO_MESH_ENV_VARS` / `AIO_MESH_SECRETS`. diff --git a/docs/aio-secure-actions.md b/docs/aio-secure-actions.md new file mode 100644 index 0000000..ff7aaec --- /dev/null +++ b/docs/aio-secure-actions.md @@ -0,0 +1,106 @@ +# AIO Secure Actions + +Applies web-secure authentication to a specified set of Adobe I/O Runtime actions using `aio runtime action update --web=true --web-secure=`. Action names are resolved to their full `namespace/name` path via `aio runtime action list` before updating. + +#### **Inputs** + +| Name | Required | Type | Default | Description | +|------|----------|------|---------|-------------| +| `environment` | ✅ | string | | GitHub environment to run in | +| `aio-cli-version` | ❌ | string | `11.x.x` | Adobe I/O CLI version to install | +| `actions` | ✅ | string | | Newline-separated list of runtime action names to secure | + +#### **Variables and Secrets** + +Configure these in the GitHub Environment (or at the repository level if not using environments). + +**AIO Authentication** — required: + +| Name | Type | Description | +|------|------|-------------| +| `AIO_CLIENT_ID` | Secret | Adobe I/O OAuth client ID | +| `AIO_CLIENT_SECRET` | Secret | Adobe I/O OAuth client secret | +| `AIO_TECHNICAL_ACCOUNT_ID` | Secret | Technical account ID | +| `AIO_TECHNICAL_ACCOUNT_EMAIL` | Secret | Technical account email | +| `AIO_IMS_ORG_ID` | Secret | IMS organisation ID | +| `AIO_SCOPES` | Secret | OAuth scopes (space or comma separated) | + +**AIO Runtime / Project** — required: + +| Name | Type | Description | +|------|------|-------------| +| `AIO_RUNTIME_NAMESPACE` | Secret | Adobe I/O Runtime namespace | +| `AIO_RUNTIME_AUTH` | Secret | Adobe I/O Runtime auth token | +| `AIO_PROJECT_ID` | Variable | Adobe I/O project ID | +| `AIO_PROJECT_NAME` | Variable | Adobe I/O project name | +| `AIO_PROJECT_ORG_ID` | Variable | Adobe I/O project org ID | +| `AIO_PROJECT_WORKSPACE_ID` | Variable | Adobe I/O workspace ID | +| `AIO_PROJECT_WORKSPACE_NAME` | Variable | Adobe I/O workspace name | + +**Web-secure hash** — required: + +| Name | Type | Description | +|------|------|-------------| +| `AIO_ACTION_AUTH_HASH` | Secret | Hash passed to `--web-secure` on each action. Callers may map a differently-named secret (e.g. `WHISK_AUTH_HASH`) to this when passing secrets explicitly. | + +--- + +#### **Example Usage** + +##### Securing specific actions after deployment + +```yaml +name: Deploy + +on: + push: + branches: + - staging + - production + +jobs: + deploy: + uses: aligent/workflows/.github/workflows/aio-app-deployment.yml@main + with: + environment: ${{ github.ref_name }} + secrets: inherit + + secure: + needs: deploy + uses: aligent/workflows/.github/workflows/aio-secure-actions.yml@main + with: + environment: ${{ github.ref_name }} + actions: | + my-action + another-action + secrets: inherit +``` + +The GitHub Environment contains all required AIO secrets, with `AIO_ACTION_AUTH_HASH` holding the web-secure hash. + +--- + +##### Mapping a differently-named secret + +If the secret is stored under a different name (e.g. `WHISK_AUTH_HASH`), pass secrets explicitly rather than using `secrets: inherit`: + +```yaml + secure: + needs: deploy + uses: aligent/workflows/.github/workflows/aio-secure-actions.yml@main + with: + environment: production + actions: | + my-action + another-action + secrets: + AIO_CLIENT_ID: ${{ secrets.AIO_CLIENT_ID }} + AIO_CLIENT_SECRET: ${{ secrets.AIO_CLIENT_SECRET }} + AIO_TECHNICAL_ACCOUNT_ID: ${{ secrets.AIO_TECHNICAL_ACCOUNT_ID }} + AIO_TECHNICAL_ACCOUNT_EMAIL: ${{ secrets.AIO_TECHNICAL_ACCOUNT_EMAIL }} + AIO_IMS_ORG_ID: ${{ secrets.AIO_IMS_ORG_ID }} + AIO_SCOPES: ${{ secrets.AIO_SCOPES }} + AIO_RUNTIME_NAMESPACE: ${{ secrets.AIO_RUNTIME_NAMESPACE }} + AIO_RUNTIME_AUTH: ${{ secrets.AIO_RUNTIME_AUTH }} + AIO_ACTION_AUTH_HASH: ${{ secrets.WHISK_AUTH_HASH }} +```