Skip to content

Commit 0a0bb22

Browse files
0x46616c6bclaude
andcommitted
feat: add create-deployment input for GitHub Deployment tracking
Add opt-in `create-deployment` boolean input that creates GitHub Deployments on the source repository and writes tracking annotations to the Application CR in the mops overlay. This enables the flux-deployment-reporter to identify and update deployment status without image-to-repo mapping or tag parsing. New inputs: `create-deployment`, `github-token` New output: `deployment-id` (JSON map of environment to deployment ID) Closes CI-1201 Co-Authored-By: Claude <claude@anthropic.com>
1 parent 4c47a27 commit 0a0bb22

2 files changed

Lines changed: 164 additions & 27 deletions

File tree

README.md

Lines changed: 60 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,14 @@ jobs:
3030
ci-cd:
3131
name: Build, Push and Deploy
3232

33-
runs-on: ubuntu-22.04
33+
runs-on: ubuntu-24.04
3434

3535
steps:
3636
- name: Checkout
37-
uses: actions/checkout@v3
37+
uses: actions/checkout@v6
3838

3939
- name: GitOps (build, push and deploy a new Docker image)
40-
uses: Staffbase/gitops-github-action@v7.0
40+
uses: Staffbase/gitops-github-action@v7.1
4141
with:
4242
docker-username: ${{ vars.HARBOR_USERNAME }}
4343
docker-password: ${{ secrets.HARBOR_PASSWORD }}
@@ -62,14 +62,14 @@ jobs:
6262
ci-cd:
6363
name: Build and Push
6464

65-
runs-on: ubuntu-22.04
65+
runs-on: ubuntu-24.04
6666

6767
steps:
6868
- name: Checkout
69-
uses: actions/checkout@v3
69+
uses: actions/checkout@v6
7070

7171
- name: GitOps (build and push a new Docker image)
72-
uses: Staffbase/gitops-github-action@v7.0
72+
uses: Staffbase/gitops-github-action@v7.1
7373
with:
7474
docker-username: ${{ vars.HARBOR_USERNAME }}
7575
docker-password: ${{ secrets.HARBOR_PASSWORD }}
@@ -87,14 +87,14 @@ jobs:
8787
ci-cd:
8888
name: Deploy
8989

90-
runs-on: ubuntu-22.04
90+
runs-on: ubuntu-24.04
9191

9292
steps:
9393
- name: Checkout
94-
uses: actions/checkout@v3
94+
uses: actions/checkout@v6
9595

9696
- name: GitOps (deploy a new Docker image)
97-
uses: Staffbase/gitops-github-action@v7.0
97+
uses: Staffbase/gitops-github-action@v7.1
9898
with:
9999
docker-image: private/diablo-redbook
100100
gitops-token: ${{ secrets.GITOPS_TOKEN }}
@@ -106,6 +106,52 @@ jobs:
106106
clusters/customization/prod/mothership/diablo-redbook/diablo-redbook-helm.yaml spec.template.spec.containers.redbook.image
107107
```
108108
109+
### Build, Push, Deploy and Track Deployment
110+
111+
When `create-deployment` is set to `true`, the action will:
112+
1. Create a GitHub Deployment on the source repository for each target environment
113+
2. Set the deployment status to `in_progress`
114+
3. Write deployment tracking annotations (`deploy.staffbase.com/repo`, `deploy.staffbase.com/sha`, `deploy.staffbase.com/deployment-id`) to the Application CR in the mops overlay
115+
116+
The environment name is derived from the mops file path (e.g. `kubernetes/namespaces/<service>/prod/de1/...` becomes `prod-de1`).
117+
118+
The calling workflow must grant the `deployments: write` permission:
119+
120+
```yaml
121+
name: CD
122+
123+
on: [ push ]
124+
125+
permissions:
126+
deployments: write
127+
128+
jobs:
129+
ci-cd:
130+
name: Build, Push and Deploy
131+
132+
runs-on: ubuntu-24.04
133+
134+
steps:
135+
- name: Checkout
136+
uses: actions/checkout@v6
137+
138+
- name: GitOps (build, push, deploy and track)
139+
uses: Staffbase/gitops-github-action@v7.1
140+
with:
141+
docker-username: ${{ vars.HARBOR_USERNAME }}
142+
docker-password: ${{ secrets.HARBOR_PASSWORD }}
143+
docker-image: private/diablo-redbook
144+
gitops-token: ${{ secrets.GITOPS_TOKEN }}
145+
create-deployment: true
146+
github-token: ${{ github.token }}
147+
gitops-dev: |-
148+
clusters/customization/dev/mothership/diablo-redbook/diablo-redbook-helm.yaml spec.template.spec.containers.redbook.image
149+
gitops-stage: |-
150+
clusters/customization/stage/mothership/diablo-redbook/diablo-redbook-helm.yaml spec.template.spec.containers.redbook.image
151+
gitops-prod: |-
152+
clusters/customization/prod/mothership/diablo-redbook/diablo-redbook-helm.yaml spec.template.spec.containers.redbook.image
153+
```
154+
109155
## Inputs
110156

111157
| Name | Description | Default |
@@ -133,13 +179,16 @@ jobs:
133179
| `gitops-stage` | Files which should be updated by the GitHub Action for STAGE, must be relative to the root of the GitOps repository | |
134180
| `gitops-prod` | Files which should be updated by the GitHub Action for PROD, must be relative to the root of the GitOps repository | |
135181
| `working-directory` | The directory in which the GitOps action should be executed. The docker-file variable should be relative to working directory. | `.` |
182+
| `create-deployment` | Create GitHub Deployments on the source repository and write tracking annotations to the GitOps CRs | `false` |
183+
| `github-token` | GitHub Token for creating deployments (requires `deployments: write` permission). Required when `create-deployment` is `true`. | |
136184

137185
## Outputs
138186

139187
| Name | Description |
140188
|-----------------|---------------------|
141-
| `docker-digest` | Digest of the image |
142-
| `docker-tag` | Tag of the image |
189+
| `docker-digest` | Digest of the image |
190+
| `docker-tag` | Tag of the image |
191+
| `deployment-id` | JSON map of environment to GitHub Deployment ID (set when `create-deployment` is `true`) |
143192

144193
## Contributing
145194

action.yml

Lines changed: 104 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,13 @@ inputs:
9595
description: 'The path relative to the repo root dir in which the GitOps action should be executed.'
9696
required: false
9797
default: '.'
98+
create-deployment:
99+
description: 'Create GitHub Deployments on the source repository and write tracking annotations to the GitOps CRs'
100+
required: false
101+
default: 'false'
102+
github-token:
103+
description: 'GitHub Token for creating deployments (requires deployments: write permission). Required when create-deployment is true.'
104+
required: false
98105

99106
outputs:
100107
docker-tag:
@@ -103,6 +110,9 @@ outputs:
103110
docker-digest:
104111
description: 'Docker digest'
105112
value: ${{ steps.docker_build.outputs.digest || steps.docker_retag.outputs.digest }}
113+
deployment-id:
114+
description: 'JSON map of environment to GitHub Deployment ID (only set when create-deployment is true)'
115+
value: ${{ steps.update_image.outputs.deployment_ids }}
106116

107117
runs:
108118
using: "composite"
@@ -283,10 +293,16 @@ runs:
283293
path: .github/${{ inputs.gitops-repository }}
284294

285295
- name: Update Docker Image in Repository
296+
id: update_image
286297
if: inputs.gitops-token != ''
287298
working-directory: .github/${{ inputs.gitops-repository }}
288299
shell: bash
289300
run: |
301+
IMAGE="${{ inputs.docker-registry }}/${{ inputs.docker-image }}:${{ steps.preparation.outputs.tag }}"
302+
CREATE_DEPLOYMENT="${{ inputs.create-deployment }}"
303+
GITHUB_TOKEN_INPUT="${{ inputs.github-token }}"
304+
DEPLOYMENT_IDS='{}'
305+
290306
push_to_gitops_repo () {
291307
# In case there was another push in the meantime, we pull it again
292308
git pull --rebase https://${{ inputs.gitops-user }}:${{ inputs.gitops-token }}@github.com/${{ inputs.gitops-organization }}/${{ inputs.gitops-repository }}.git
@@ -310,53 +326,125 @@ runs:
310326
fi
311327
}
312328
329+
derive_environment () {
330+
local file_path="$1"
331+
# Path: kubernetes/namespaces/<service>/<env>/<cluster>/<file>.yaml
332+
local env=$(echo "$file_path" | cut -d'/' -f4)
333+
local cluster=$(echo "$file_path" | cut -d'/' -f5)
334+
echo "${env}-${cluster}"
335+
}
336+
337+
create_deployment () {
338+
local environment="$1"
339+
local image="$2"
340+
local tag="$3"
341+
342+
RESPONSE=$(curl -s -w "\n%{http_code}" \
343+
-X POST \
344+
-H "Accept: application/vnd.github+json" \
345+
-H "Authorization: Bearer ${GITHUB_TOKEN_INPUT}" \
346+
-H "X-GitHub-Api-Version: 2022-11-28" \
347+
"https://api.github.com/repos/${GITHUB_REPOSITORY}/deployments" \
348+
-d "{
349+
\"ref\": \"${GITHUB_SHA}\",
350+
\"environment\": \"${environment}\",
351+
\"auto_merge\": false,
352+
\"required_contexts\": [],
353+
\"payload\": {
354+
\"image\": \"${image}\",
355+
\"tag\": \"${tag}\"
356+
},
357+
\"description\": \"Deploy ${image}:${tag} to ${environment}\"
358+
}")
359+
360+
HTTP_CODE=$(echo "$RESPONSE" | tail -1)
361+
BODY=$(echo "$RESPONSE" | sed '$d')
362+
363+
if [[ "$HTTP_CODE" -lt 200 || "$HTTP_CODE" -ge 300 ]]; then
364+
echo "::warning::Failed to create GitHub Deployment for ${environment} (HTTP ${HTTP_CODE}): ${BODY}" >&2
365+
return
366+
fi
367+
368+
local deployment_id=$(echo "$BODY" | jq -r '.id')
369+
echo "Created deployment ${deployment_id} for environment ${environment}" >&2
370+
371+
# Set initial status to in_progress
372+
curl -s \
373+
-X POST \
374+
-H "Accept: application/vnd.github+json" \
375+
-H "Authorization: Bearer ${GITHUB_TOKEN_INPUT}" \
376+
-H "X-GitHub-Api-Version: 2022-11-28" \
377+
"https://api.github.com/repos/${GITHUB_REPOSITORY}/deployments/${deployment_id}/statuses" \
378+
-d "{\"state\": \"in_progress\", \"description\": \"Updating GitOps repository\"}" > /dev/null
379+
380+
echo "$deployment_id"
381+
}
382+
383+
update_file () {
384+
local file="$1"
385+
local field="$2"
386+
local image="$3"
387+
388+
echo "Check if path ${file} ${field} exists and get old current version"
389+
yq -e ."${field}" "${file}"
390+
echo "Run update ${file} ${field} ${image}"
391+
yq -i ."${field}"=\""${image}"\" "${file}"
392+
393+
if [[ "$CREATE_DEPLOYMENT" == "true" ]]; then
394+
local deploy_env=$(derive_environment "${file}")
395+
local deploy_id=""
396+
397+
if [[ -n "$GITHUB_TOKEN_INPUT" ]]; then
398+
deploy_id=$(create_deployment "${deploy_env}" "${{ inputs.docker-registry }}/${{ inputs.docker-image }}" "${{ steps.preparation.outputs.tag }}")
399+
fi
400+
401+
echo "Writing deployment annotations to ${file}"
402+
yq -i '.metadata.annotations["deploy.staffbase.com/repo"] = "'"${GITHUB_REPOSITORY}"'"' "${file}"
403+
yq -i '.metadata.annotations["deploy.staffbase.com/sha"] = "'"${GITHUB_SHA}"'"' "${file}"
404+
if [[ -n "$deploy_id" ]]; then
405+
yq -i '.metadata.annotations["deploy.staffbase.com/deployment-id"] = "'"${deploy_id}"'"' "${file}"
406+
DEPLOYMENT_IDS=$(echo "$DEPLOYMENT_IDS" | jq -c --arg env "$deploy_env" --arg id "$deploy_id" '. + {($env): $id}')
407+
fi
408+
fi
409+
}
410+
313411
# configure git user
314412
git config --global user.email "${{ inputs.gitops-email }}" && git config --global user.name "${{ inputs.gitops-user }}"
315413
316414
if [[ ( $GITHUB_REF == refs/heads/master || $GITHUB_REF == refs/heads/main ) && -n "${{ inputs.gitops-stage }}" ]]; then
317415
echo "Run update for STAGE"
318416
while IFS= read -r line; do
319417
array=($line)
320-
echo "Check if path $line exists and get old current version"
321-
yq -e .${array[1]} ${array[0]}
322-
echo "Run update $line ${{ inputs.docker-registry }}/${{ inputs.docker-image }}:${{ steps.preparation.outputs.tag }}"
323-
yq -i .${array[1]}=\"${{ inputs.docker-registry }}/${{ inputs.docker-image }}:${{ steps.preparation.outputs.tag }}\" ${array[0]}
418+
update_file "${array[0]}" "${array[1]}" "$IMAGE"
324419
done <<< "${{ inputs.gitops-stage }}"
325420
commit_changes
326421
327422
elif [[ $GITHUB_REF == refs/heads/dev && -n "${{ inputs.gitops-dev }}" ]]; then
328423
echo "Run update for DEV"
329424
while IFS= read -r line; do
330425
array=($line)
331-
echo "Check if path $line exists and get old current version"
332-
yq -e .${array[1]} ${array[0]}
333-
echo "Run update $line ${{ inputs.docker-registry }}/${{ inputs.docker-image }}:${{ steps.preparation.outputs.tag }}"
334-
yq -i .${array[1]}=\"${{ inputs.docker-registry }}/${{ inputs.docker-image }}:${{ steps.preparation.outputs.tag }}\" ${array[0]}
426+
update_file "${array[0]}" "${array[1]}" "$IMAGE"
335427
done <<< "${{ inputs.gitops-dev }}"
336428
commit_changes
337429
338430
elif [[ $GITHUB_REF == refs/tags/* && -n "${{ inputs.gitops-prod }}" ]]; then
339431
echo "Run update for PROD"
340432
while IFS= read -r line; do
341433
array=($line)
342-
echo "Check if path $line exists and get old current version"
343-
yq -e .${array[1]} ${array[0]}
344-
echo "Run update $line ${{ inputs.docker-registry }}/${{ inputs.docker-image }}:${{ steps.preparation.outputs.tag }}"
345-
yq -i .${array[1]}=\"${{ inputs.docker-registry }}/${{ inputs.docker-image }}:${{ steps.preparation.outputs.tag }}\" ${array[0]}
434+
update_file "${array[0]}" "${array[1]}" "$IMAGE"
346435
done <<< "${{ inputs.gitops-prod }}"
347436
commit_changes
348437
349438
elif [[ -n "${{ inputs.gitops-dev }}" ]]; then
350439
echo "Simulate update for DEV"
351440
while IFS= read -r line; do
352441
array=($line)
353-
echo "Check if path $line exists and get old current version"
354-
yq -e .${array[1]} ${array[0]}
355-
echo "Run update $line ${{ inputs.docker-registry }}/${{ inputs.docker-image }}:${{ steps.preparation.outputs.tag }}"
356-
yq -i .${array[1]}=\"${{ inputs.docker-registry }}/${{ inputs.docker-image }}:${{ steps.preparation.outputs.tag }}\" ${array[0]}
442+
update_file "${array[0]}" "${array[1]}" "$IMAGE"
357443
done <<< "${{ inputs.gitops-dev }}"
358444
fi
359445
446+
echo "deployment_ids=$DEPLOYMENT_IDS" >> $GITHUB_OUTPUT
447+
360448
- name: Emit Image Build Event to Upwind.io
361449
env:
362450
UPWIND_CLIENT_SECRET: ${{ inputs.upwind-client-secret }}

0 commit comments

Comments
 (0)