Skip to content

Commit b703e70

Browse files
authored
Merge pull request #136 from Staffbase/feat/CI-1229-remove-github-deployments
refactor!: drop GitHub Deployments, rename annotations for Swarmia (CI-1229)
2 parents c5e4ada + d2eaa3a commit b703e70

6 files changed

Lines changed: 29 additions & 247 deletions

File tree

README.md

Lines changed: 8 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -106,51 +106,17 @@ 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
109+
### Deployment tracking annotations
110110
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
111+
Whenever the action updates a GitOps file, it stamps the following annotations onto the manifest's `metadata.annotations`:
115112

116-
The environment name is derived from the mops file path (e.g. `kubernetes/namespaces/<service>/prod/de1/...` becomes `prod-de1`).
113+
| Annotation | Value |
114+
|------------|-------|
115+
| `deploy.staffbase.com/repositoryFullName` | The source repository in `owner/repo` form (`$GITHUB_REPOSITORY`) |
116+
| `deploy.staffbase.com/commitSha` | The commit SHA being deployed (`$GITHUB_SHA`) |
117+
| `deploy.staffbase.com/version` | The deployed image tag — `dev-<short-sha>` on `dev`, `main-<short-sha>` on `main`/`master`, the tag name on tag pushes |
117118

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-
```
119+
These keys mirror the [Swarmia Deployment API](https://help.swarmia.com/settings/organization/configuring-deployments-in-swarmia) field names and are read by `flux-deployment-reporter` to report deployments to Swarmia once Flux finishes reconciling.
154120

155121
## Inputs
156122

@@ -179,16 +145,13 @@ jobs:
179145
| `gitops-stage` | Files which should be updated by the GitHub Action for STAGE, must be relative to the root of the GitOps repository | |
180146
| `gitops-prod` | Files which should be updated by the GitHub Action for PROD, must be relative to the root of the GitOps repository | |
181147
| `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`. | |
184148

185149
## Outputs
186150

187151
| Name | Description |
188152
|-----------------|---------------------|
189153
| `docker-digest` | Digest of the image |
190154
| `docker-tag` | Tag of the image |
191-
| `deployment-id` | JSON map of environment to GitHub Deployment ID (set when `create-deployment` is `true`) |
192155

193156
## Contributing
194157

action.yml

Lines changed: 0 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -95,13 +95,6 @@ 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
10598

10699
outputs:
107100
docker-tag:
@@ -110,9 +103,6 @@ outputs:
110103
docker-digest:
111104
description: 'Docker digest'
112105
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.create_deployments.outputs.deployment_ids || steps.update_image.outputs.deployment_ids }}
116106

117107
runs:
118108
using: "composite"
@@ -187,109 +177,6 @@ runs:
187177
token: ${{ inputs.gitops-token }}
188178
path: .github/${{ inputs.gitops-repository }}
189179

190-
- name: Create GitHub Deployments
191-
id: create_deployments
192-
if: inputs.create-deployment == 'true' && inputs.github-token != '' && inputs.gitops-token != ''
193-
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
194-
env:
195-
INPUT_GITOPS_DEV: ${{ inputs.gitops-dev }}
196-
INPUT_GITOPS_STAGE: ${{ inputs.gitops-stage }}
197-
INPUT_GITOPS_PROD: ${{ inputs.gitops-prod }}
198-
DOCKER_IMAGE: ${{ inputs.docker-registry }}/${{ inputs.docker-image }}
199-
DOCKER_TAG: ${{ steps.preparation.outputs.tag }}
200-
with:
201-
github-token: ${{ inputs.github-token }}
202-
script: |
203-
const ref = process.env.GITHUB_REF;
204-
const image = process.env.DOCKER_IMAGE;
205-
const tag = process.env.DOCKER_TAG;
206-
207-
let fileList = '';
208-
if ((ref === 'refs/heads/master' || ref === 'refs/heads/main') && process.env.INPUT_GITOPS_STAGE) {
209-
fileList = process.env.INPUT_GITOPS_STAGE;
210-
} else if (ref === 'refs/heads/dev' && process.env.INPUT_GITOPS_DEV) {
211-
fileList = process.env.INPUT_GITOPS_DEV;
212-
} else if (ref.startsWith('refs/tags/') && process.env.INPUT_GITOPS_PROD) {
213-
fileList = process.env.INPUT_GITOPS_PROD;
214-
}
215-
216-
if (!fileList) {
217-
core.setOutput('deployment_ids', '{}');
218-
return;
219-
}
220-
221-
// Derive unique environments from file paths
222-
// Path format: kubernetes/namespaces/<service>/<env>/<cluster>/<file>.yaml
223-
const environments = [...new Set(
224-
fileList.split('\n')
225-
.map(line => line.trim())
226-
.filter(line => line.length > 0)
227-
.map(line => {
228-
const filePath = line.split(/\s+/)[0];
229-
const parts = filePath.split('/');
230-
return `${parts[3]}-${parts[4]}`;
231-
})
232-
)];
233-
234-
// Deactivate stale in-progress/queued/pending deployments for an environment
235-
async function deactivateStaleDeployments(environment) {
236-
const { data: deployments } = await github.rest.repos.listDeployments({
237-
...context.repo,
238-
environment,
239-
per_page: 30
240-
});
241-
242-
for (const dep of deployments) {
243-
const { data: statuses } = await github.rest.repos.listDeploymentStatuses({
244-
...context.repo,
245-
deployment_id: dep.id,
246-
per_page: 1
247-
});
248-
249-
const latestState = statuses[0]?.state;
250-
if (['in_progress', 'queued', 'pending'].includes(latestState)) {
251-
core.info(`Marking stale deployment ${dep.id} (${latestState}) as inactive`);
252-
await github.rest.repos.createDeploymentStatus({
253-
...context.repo,
254-
deployment_id: dep.id,
255-
state: 'inactive',
256-
description: 'Superseded by newer deployment'
257-
});
258-
}
259-
}
260-
}
261-
262-
const ids = {};
263-
for (const env of environments) {
264-
try {
265-
await deactivateStaleDeployments(env);
266-
267-
const { data: deployment } = await github.rest.repos.createDeployment({
268-
...context.repo,
269-
ref: context.sha,
270-
environment: env,
271-
auto_merge: false,
272-
required_contexts: [],
273-
payload: { image, tag },
274-
description: `Deploy ${image}:${tag} to ${env}`
275-
});
276-
277-
await github.rest.repos.createDeploymentStatus({
278-
...context.repo,
279-
deployment_id: deployment.id,
280-
state: 'in_progress',
281-
description: 'Updating GitOps repository'
282-
});
283-
284-
core.info(`Created deployment ${deployment.id} for environment ${env}`);
285-
ids[env] = String(deployment.id);
286-
} catch (error) {
287-
core.warning(`Failed to create GitHub Deployment for ${env}: ${error.message}`);
288-
}
289-
}
290-
291-
core.setOutput('deployment_ids', JSON.stringify(ids));
292-
293180
- name: Update Docker Image in Repository
294181
id: update_image
295182
if: inputs.gitops-token != ''
@@ -300,8 +187,6 @@ runs:
300187
INPUT_DOCKER_IMAGE: ${{ inputs.docker-image }}
301188
INPUT_TAG: ${{ steps.preparation.outputs.tag }}
302189
INPUT_PUSH: ${{ steps.preparation.outputs.push }}
303-
INPUT_CREATE_DEPLOYMENT: ${{ inputs.create-deployment }}
304-
INPUT_DEPLOYMENT_IDS: ${{ steps.create_deployments.outputs.deployment_ids }}
305190
INPUT_GITOPS_USER: ${{ inputs.gitops-user }}
306191
INPUT_GITOPS_EMAIL: ${{ inputs.gitops-email }}
307192
INPUT_GITOPS_TOKEN: ${{ inputs.gitops-token }}

scripts/lib/gitops-functions.sh

Lines changed: 4 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
#
55
# Expected env vars (set by caller):
66
# INPUT_DOCKER_REGISTRY, INPUT_DOCKER_IMAGE, INPUT_TAG, INPUT_PUSH,
7-
# INPUT_CREATE_DEPLOYMENT, INPUT_DEPLOYMENT_IDS,
87
# INPUT_GITOPS_USER, INPUT_GITOPS_TOKEN,
98
# INPUT_GITOPS_ORGANIZATION, INPUT_GITOPS_REPOSITORY,
109
# GITHUB_REPOSITORY, GITHUB_SHA, IMAGE
@@ -29,16 +28,6 @@ commit_changes() {
2928
fi
3029
}
3130

32-
# Derives the environment identifier from a mops file path.
33-
# Expected path format: kubernetes/namespaces/<service>/<env>/<cluster>/<file>.yaml
34-
derive_environment() {
35-
local file_path="$1"
36-
local env cluster
37-
env=$(echo "$file_path" | cut -d'/' -f4)
38-
cluster=$(echo "$file_path" | cut -d'/' -f5)
39-
echo "${env}-${cluster}"
40-
}
41-
4231
update_file() {
4332
local file="$1"
4433
local field="$2"
@@ -49,24 +38,10 @@ update_file() {
4938
echo "Run update ${file} ${field} ${image}"
5039
yq -i ."${field}"=\""${image}"\" "${file}"
5140

52-
if [[ "${INPUT_CREATE_DEPLOYMENT}" == "true" ]]; then
53-
local deploy_env
54-
deploy_env=$(derive_environment "${file}")
55-
56-
echo "Writing deployment annotations to ${file}"
57-
yq -i '.metadata.annotations["deploy.staffbase.com/repo"] = "'"${GITHUB_REPOSITORY}"'"' "${file}"
58-
yq -i '.metadata.annotations["deploy.staffbase.com/sha"] = "'"${GITHUB_SHA}"'"' "${file}"
59-
60-
# Write deployment-id annotation if available from the create_deployments step
61-
local deploy_id=""
62-
if [[ -n "${INPUT_DEPLOYMENT_IDS:-}" && "${INPUT_DEPLOYMENT_IDS}" != "{}" ]]; then
63-
deploy_id=$(echo "${INPUT_DEPLOYMENT_IDS}" | jq -r --arg env "$deploy_env" '.[$env] // empty')
64-
fi
65-
66-
if [[ -n "$deploy_id" ]]; then
67-
yq -i '.metadata.annotations["deploy.staffbase.com/deployment-id"] = "'"${deploy_id}"'"' "${file}"
68-
fi
69-
fi
41+
echo "Writing deployment annotations to ${file}"
42+
yq -i '.metadata.annotations["deploy.staffbase.com/repositoryFullName"] = "'"${GITHUB_REPOSITORY}"'"' "${file}"
43+
yq -i '.metadata.annotations["deploy.staffbase.com/commitSha"] = "'"${GITHUB_SHA}"'"' "${file}"
44+
yq -i '.metadata.annotations["deploy.staffbase.com/version"] = "'"${INPUT_TAG}"'"' "${file}"
7045
}
7146

7247
process_file_updates() {

scripts/update-gitops.sh

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,9 @@
55
#
66
# Required env vars: GITHUB_REF, GITHUB_SHA, GITHUB_REPOSITORY,
77
# INPUT_DOCKER_REGISTRY, INPUT_DOCKER_IMAGE, INPUT_TAG, INPUT_PUSH,
8-
# INPUT_CREATE_DEPLOYMENT, INPUT_GITOPS_USER, INPUT_GITOPS_EMAIL,
8+
# INPUT_GITOPS_USER, INPUT_GITOPS_EMAIL,
99
# INPUT_GITOPS_TOKEN, INPUT_GITOPS_ORGANIZATION, INPUT_GITOPS_REPOSITORY
10-
# Optional env vars: INPUT_GITOPS_DEV, INPUT_GITOPS_STAGE, INPUT_GITOPS_PROD,
11-
# INPUT_DEPLOYMENT_IDS
10+
# Optional env vars: INPUT_GITOPS_DEV, INPUT_GITOPS_STAGE, INPUT_GITOPS_PROD
1211

1312
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
1413
# shellcheck source=lib/common.sh

tests/lib-gitops-functions.bats

Lines changed: 15 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@ setup() {
1313
export INPUT_DOCKER_IMAGE="my-service"
1414
export INPUT_TAG="main-abcdef12"
1515
export INPUT_PUSH="true"
16-
export INPUT_CREATE_DEPLOYMENT="false"
17-
export INPUT_DEPLOYMENT_IDS="{}"
1816
export INPUT_GITOPS_USER="Staffbot"
1917
export INPUT_GITOPS_TOKEN="fake-token"
2018
export INPUT_GITOPS_ORGANIZATION="Staffbase"
@@ -44,47 +42,13 @@ esac
4442
GIT_MOCK
4543
chmod +x "${TEST_TEMP_DIR}/mocks/git"
4644

47-
# Create mock jq that passes through
48-
cat > "${TEST_TEMP_DIR}/mocks/jq" << 'JQ_MOCK'
49-
#!/usr/bin/env bash
50-
# Use real jq if available, otherwise simple passthrough
51-
if command -v /usr/bin/jq &>/dev/null; then
52-
/usr/bin/jq "$@"
53-
elif command -v /opt/homebrew/bin/jq &>/dev/null; then
54-
/opt/homebrew/bin/jq "$@"
55-
else
56-
cat
57-
fi
58-
JQ_MOCK
59-
chmod +x "${TEST_TEMP_DIR}/mocks/jq"
60-
6145
export PATH="${TEST_TEMP_DIR}/mocks:$PATH"
6246
}
6347

6448
teardown() {
6549
teardown_common
6650
}
6751

68-
# --- derive_environment ---
69-
70-
@test "derive_environment extracts env and cluster from standard mops path" {
71-
run derive_environment "kubernetes/namespaces/my-service/prod/de1/deployment.yaml"
72-
assert_success
73-
assert_output "prod-de1"
74-
}
75-
76-
@test "derive_environment handles stage environment" {
77-
run derive_environment "kubernetes/namespaces/my-service/stage/us1/deployment.yaml"
78-
assert_success
79-
assert_output "stage-us1"
80-
}
81-
82-
@test "derive_environment handles dev environment" {
83-
run derive_environment "kubernetes/namespaces/my-service/dev/de1/deployment.yaml"
84-
assert_success
85-
assert_output "dev-de1"
86-
}
87-
8852
# --- update_file ---
8953

9054
@test "update_file calls yq to check and update field" {
@@ -94,33 +58,31 @@ teardown() {
9458
grep -q 'yq -i' "${TEST_TEMP_DIR}/yq_calls.log"
9559
}
9660

97-
@test "update_file writes deployment annotations when create-deployment is true" {
98-
export INPUT_CREATE_DEPLOYMENT="true"
99-
update_file "kubernetes/namespaces/svc/prod/de1/deployment.yaml" "spec.image" "$IMAGE"
100-
grep -q 'deploy.staffbase.com/repo' "${TEST_TEMP_DIR}/yq_calls.log"
101-
grep -q 'deploy.staffbase.com/sha' "${TEST_TEMP_DIR}/yq_calls.log"
61+
@test "update_file always writes deploy.staffbase.com/repositoryFullName annotation" {
62+
update_file "deployment.yaml" "spec.image" "$IMAGE"
63+
grep -q 'deploy.staffbase.com/repositoryFullName' "${TEST_TEMP_DIR}/yq_calls.log"
10264
}
10365

104-
@test "update_file skips annotations when create-deployment is false" {
105-
export INPUT_CREATE_DEPLOYMENT="false"
66+
@test "update_file always writes deploy.staffbase.com/commitSha annotation" {
10667
update_file "deployment.yaml" "spec.image" "$IMAGE"
107-
! grep -q 'deploy.staffbase.com' "${TEST_TEMP_DIR}/yq_calls.log" 2>/dev/null || true
68+
grep -q 'deploy.staffbase.com/commitSha' "${TEST_TEMP_DIR}/yq_calls.log"
10869
}
10970

110-
@test "update_file writes deployment-id annotation when deployment ID is available" {
111-
export INPUT_CREATE_DEPLOYMENT="true"
112-
export INPUT_DEPLOYMENT_IDS='{"prod-de1":"12345"}'
113-
update_file "kubernetes/namespaces/svc/prod/de1/deployment.yaml" "spec.image" "$IMAGE"
114-
grep -q 'deploy.staffbase.com/deployment-id' "${TEST_TEMP_DIR}/yq_calls.log"
71+
@test "update_file writes deploy.staffbase.com/version annotation with INPUT_TAG" {
72+
update_file "deployment.yaml" "spec.image" "$IMAGE"
73+
grep -q "deploy.staffbase.com/version.*${INPUT_TAG}" "${TEST_TEMP_DIR}/yq_calls.log"
11574
}
11675

117-
@test "update_file skips deployment-id annotation when no matching deployment ID" {
118-
export INPUT_CREATE_DEPLOYMENT="true"
119-
export INPUT_DEPLOYMENT_IDS='{"stage-us1":"99999"}'
120-
update_file "kubernetes/namespaces/svc/prod/de1/deployment.yaml" "spec.image" "$IMAGE"
76+
@test "update_file does not write deploy.staffbase.com/deployment-id annotation" {
77+
update_file "deployment.yaml" "spec.image" "$IMAGE"
12178
! grep -q 'deployment-id' "${TEST_TEMP_DIR}/yq_calls.log"
12279
}
12380

81+
@test "update_file does not write legacy /repo or /sha annotation keys" {
82+
update_file "deployment.yaml" "spec.image" "$IMAGE"
83+
! grep -qE 'deploy\.staffbase\.com/(repo|sha)"' "${TEST_TEMP_DIR}/yq_calls.log"
84+
}
85+
12486
# --- commit_changes ---
12587

12688
@test "commit_changes commits and pushes when push is true" {

0 commit comments

Comments
 (0)