diff --git a/.github/workflows/image-multiarch.yaml b/.github/workflows/image-multiarch.yaml index 40c8775..57255e5 100644 --- a/.github/workflows/image-multiarch.yaml +++ b/.github/workflows/image-multiarch.yaml @@ -51,6 +51,11 @@ on: type: string required: false default: "ci-base-scale-set" + codeartifact: + description: "Pass an AWS CodeArtifact token to the build as the CODEARTIFACT_TOKEN secret" + type: boolean + required: false + default: false env: VERSION_PREFIX: '' VERSION_LATEST: latest @@ -61,6 +66,12 @@ jobs: steps: - name: Checkout uses: actions/checkout@v7 + - name: Get CodeArtifact token + id: codeartifact + if: ${{ inputs.codeartifact }} + uses: Typeform/.github/shared-actions/codeartifact-token@v1 + with: + export-env: "false" - name: Set up QEMU if: ${{ contains(inputs.platforms, ',') }} uses: docker/setup-qemu-action@v4 @@ -89,7 +100,9 @@ jobs: tags: | ${{ vars.IMAGE_REGISTRY }}/${{ inputs.service }}:${{env.VERSION_PREFIX}}${{ inputs.version }} ${{ vars.IMAGE_REGISTRY }}/${{ inputs.service }}:${{env.VERSION_PREFIX}}${{ github.sha }} - secrets: ${{ secrets.build-secrets }} + secrets: | + ${{ secrets.build-secrets }} + ${{ inputs.codeartifact && format('CODEARTIFACT_TOKEN={0}', steps.codeartifact.outputs.token) || '' }} file: ${{ inputs.file }} - name: Set configured prefix on latest tag if: ${{ inputs.prefix != '' }} @@ -105,5 +118,7 @@ jobs: push: true tags: | ${{ vars.IMAGE_REGISTRY }}/${{ inputs.service }}:${{ env.VERSION_LATEST }} - secrets: ${{ secrets.build-secrets }} + secrets: | + ${{ secrets.build-secrets }} + ${{ inputs.codeartifact && format('CODEARTIFACT_TOKEN={0}', steps.codeartifact.outputs.token) || '' }} file: ${{ inputs.file }} diff --git a/shared-actions/codeartifact-token/README.md b/shared-actions/codeartifact-token/README.md new file mode 100644 index 0000000..a4d7ece --- /dev/null +++ b/shared-actions/codeartifact-token/README.md @@ -0,0 +1,51 @@ +# CodeArtifact token + +Gets an AWS CodeArtifact authorization token using the job's AWS credentials. It doesn't need the `aws` CLI, which the ARC runner images don't ship: it uses `uv` and boto3. + +The job must already have AWS credentials, either from the runner's IAM role (the ARC scale sets can read the `typeform` domain) or from an earlier `aws-actions/configure-aws-credentials` step. + +## Usage + +### Install packages in CI + +```yaml +- uses: Typeform/.github/shared-actions/codeartifact-token@v1 +- run: pip install -r requirements.txt # PIP_INDEX_URL / UV_INDEX_URL are set +``` + +By default the action exports: + +| Variable | Value | +| --- | --- | +| `CODEARTIFACT_TOKEN` | The token (masked in logs) | +| `PIP_INDEX_URL` | `https://aws:@-.d.codeartifact..amazonaws.com/pypi//simple/` | +| `UV_INDEX_URL` | Same as `PIP_INDEX_URL` | + +Use `PIP_INDEX_URL`, not `PIP_EXTRA_INDEX_URL`. CodeArtifact also serves public PyPI through its upstream, and a single index is what keeps internal package names from being resolved from public PyPI. + +### Docker builds + +Set `codeartifact: true` on the `image-multiarch` reusable workflow. It passes the token as the BuildKit secret `CODEARTIFACT_TOKEN`: + +```dockerfile +RUN --mount=type=secret,id=CODEARTIFACT_TOKEN \ + pip install --index-url "https://aws:$(cat /run/secrets/CODEARTIFACT_TOKEN)@typeform-567716553783.d.codeartifact.us-east-1.amazonaws.com/pypi/pypi/simple/" -r requirements.txt +``` + +## Inputs + +| Input | Default | Description | +| --- | --- | --- | +| `domain` | `typeform` | CodeArtifact domain | +| `domain-owner` | `567716553783` | AWS account that owns the domain | +| `region` | `us-east-1` | Region of the domain | +| `repository` | `pypi` | Repository used for the index URL | +| `duration-seconds` | `3600` | Token lifetime, 900 to 43200 | +| `export-env` | `true` | Export the variables above. Set `false` to only set outputs. | + +## Outputs + +| Output | Description | +| --- | --- | +| `token` | The token (masked) | +| `pypi-index-url` | Index URL without credentials | diff --git a/shared-actions/codeartifact-token/action.yml b/shared-actions/codeartifact-token/action.yml new file mode 100644 index 0000000..f7627c3 --- /dev/null +++ b/shared-actions/codeartifact-token/action.yml @@ -0,0 +1,75 @@ +name: 'CodeArtifact token' +description: 'Get an AWS CodeArtifact authorization token using the job AWS credentials, without the aws CLI' +inputs: + domain: + description: 'CodeArtifact domain' + required: false + default: 'typeform' + domain-owner: + description: 'AWS account ID that owns the domain' + required: false + default: '567716553783' + region: + description: 'AWS region of the domain' + required: false + default: 'us-east-1' + repository: + description: 'PyPI repository used for the index URL outputs and env vars' + required: false + default: 'pypi' + duration-seconds: + description: 'Token lifetime in seconds (900-43200)' + required: false + default: '3600' + export-env: + description: 'Export CODEARTIFACT_TOKEN, PIP_INDEX_URL and UV_INDEX_URL to later steps' + required: false + default: 'true' + +outputs: + token: + description: 'CodeArtifact authorization token (masked)' + value: ${{ steps.token.outputs.token }} + pypi-index-url: + description: 'PyPI simple index URL of the repository, without credentials' + value: ${{ steps.token.outputs.pypi-index-url }} + +runs: + using: 'composite' + steps: + - name: Setup uv + uses: astral-sh/setup-uv@v6 + - name: Get CodeArtifact token + id: token + shell: bash + env: + CA_DOMAIN: ${{ inputs.domain }} + CA_OWNER: ${{ inputs.domain-owner }} + CA_REGION: ${{ inputs.region }} + CA_REPOSITORY: ${{ inputs.repository }} + CA_DURATION: ${{ inputs.duration-seconds }} + CA_EXPORT_ENV: ${{ inputs.export-env }} + run: | + set -euo pipefail + token=$(uv run --no-project --quiet --with 'boto3>=1.34' python -c ' + import os, boto3 + print(boto3.client("codeartifact", region_name=os.environ["CA_REGION"]).get_authorization_token( + domain=os.environ["CA_DOMAIN"], + domainOwner=os.environ["CA_OWNER"], + durationSeconds=int(os.environ["CA_DURATION"]), + )["authorizationToken"]) + ') + echo "::add-mask::${token}" + host="${CA_DOMAIN}-${CA_OWNER}.d.codeartifact.${CA_REGION}.amazonaws.com" + path="pypi/${CA_REPOSITORY}/simple/" + { + echo "token=${token}" + echo "pypi-index-url=https://${host}/${path}" + } >> "$GITHUB_OUTPUT" + if [ "${CA_EXPORT_ENV}" = "true" ]; then + { + echo "CODEARTIFACT_TOKEN=${token}" + echo "PIP_INDEX_URL=https://aws:${token}@${host}/${path}" + echo "UV_INDEX_URL=https://aws:${token}@${host}/${path}" + } >> "$GITHUB_ENV" + fi