Skip to content

Commit 04f0bb3

Browse files
committed
feat: add CI/CD workflows
Add GitHub Actions workflows for: - build.yml: Build and push team-operator images - PRs: Push adhoc images to GHCR only - Main: Push releases to Docker Hub - flightdeck.yml: Build and push flightdeck images - Same pattern as team-operator - cleanup-adhoc-images.yml: Clean up GHCR adhoc images on PR close - release.yml: Semantic release for automatic versioning - Creates releases based on conventional commits - Packages and publishes Helm chart to OCI registry
1 parent acc4246 commit 04f0bb3

File tree

4 files changed

+594
-0
lines changed

4 files changed

+594
-0
lines changed

.github/workflows/build.yml

Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
# Team Operator Build and Push Workflow
2+
#
3+
# Image destinations:
4+
# - GHCR (ghcr.io/posit-dev/team-operator): PR builds only (adhoc testing)
5+
# - Docker Hub (posit/team-operator): Main branch only (releases)
6+
#
7+
# Adhoc images are automatically cleaned up when the PR is closed
8+
# (see cleanup-adhoc-images.yml)
9+
10+
on:
11+
push:
12+
branches:
13+
- main
14+
paths-ignore:
15+
- '*.md'
16+
- 'docs/**'
17+
pull_request:
18+
paths-ignore:
19+
- '*.md'
20+
- 'docs/**'
21+
22+
permissions:
23+
actions: read
24+
contents: read
25+
id-token: write
26+
packages: write
27+
28+
env:
29+
DOCKER_HUB_ORG: posit
30+
GHCR_REGISTRY: ghcr.io/posit-dev
31+
32+
name: build/push team-operator
33+
34+
jobs:
35+
build:
36+
runs-on: ubuntu-latest-8x
37+
name: build
38+
outputs:
39+
image-tag: ${{ steps.image-tag.outputs.full-image }}
40+
image-name: ${{ steps.image-tag.outputs.image }}
41+
adhoc-tag: ${{ steps.adhoc-tag.outputs.tag }}
42+
version: ${{ steps.metadata.outputs.version }}
43+
44+
steps:
45+
- name: Check Out Repo
46+
uses: actions/checkout@v4
47+
with:
48+
fetch-depth: 0
49+
50+
- uses: extractions/setup-just@v2
51+
52+
- uses: actions/cache@v4
53+
with:
54+
path: .local/bin
55+
key: ${{ runner.os }}-local-bins-${{ hashFiles('**/*.go', 'go.sum') }}
56+
restore-keys: |
57+
${{ runner.os }}-local-bins-
58+
59+
- name: Set up Snyk
60+
uses: snyk/actions/setup@0.4.0
61+
62+
- uses: actions/setup-go@v5
63+
id: setup-go
64+
with:
65+
go-version-file: go.mod
66+
cache: true
67+
cache-dependency-path: go.sum
68+
69+
- name: Set up Docker Buildx
70+
id: buildx
71+
uses: docker/setup-buildx-action@v3
72+
73+
- name: Cache Operator SDK bins
74+
uses: actions/cache@v4
75+
with:
76+
path: bin/
77+
key: ${{ runner.os }}-operator-sdk-bins-${{ hashFiles('Makefile') }}
78+
restore-keys: |
79+
${{ runner.os }}-operator-sdk-bins-
80+
81+
- name: Smoke test the Justfile
82+
run: just -l
83+
84+
- name: Smoke test the Makefile
85+
run: make help
86+
87+
- name: Build
88+
run: make build
89+
env:
90+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
91+
92+
- name: Run unit tests
93+
run: make go-test cov
94+
env:
95+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
96+
97+
- name: Test kustomization
98+
run: make test-kustomize
99+
env:
100+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
101+
102+
- name: Helm lint
103+
run: make helm-lint
104+
105+
- name: Helm template
106+
run: make helm-template > /dev/null
107+
108+
- name: Assert no diff
109+
run: |
110+
git diff --exit-code
111+
git diff --cached --exit-code
112+
113+
- name: Get build metadata
114+
id: metadata
115+
run: |
116+
GO_VERSION=$(go list -m -f '{{.GoVersion}}')
117+
VERSION=$(git describe --always --dirty --tags)
118+
echo "go-version=$GO_VERSION" >> $GITHUB_OUTPUT
119+
echo "version=$VERSION" >> $GITHUB_OUTPUT
120+
121+
- name: Compute image tag
122+
id: image-tag
123+
run: |
124+
IMAGE="team-operator:${{ steps.metadata.outputs.version }}"
125+
echo "image=$IMAGE" >> $GITHUB_OUTPUT
126+
echo "full-image=${{ env.GHCR_REGISTRY }}/team-operator:${{ steps.metadata.outputs.version }}" >> $GITHUB_OUTPUT
127+
128+
- name: Compute adhoc tag for PRs
129+
id: adhoc-tag
130+
if: github.event_name == 'pull_request'
131+
env:
132+
DOCKER_TAG_MAX_LENGTH: 128
133+
run: |
134+
BRANCH_NAME="${{ github.head_ref }}"
135+
VERSION="${{ steps.metadata.outputs.version }}"
136+
SANITIZED_BRANCH=$(echo "$BRANCH_NAME" | tr '/' '-')
137+
TAG="adhoc-${SANITIZED_BRANCH}-${VERSION}"
138+
139+
if [ ${#TAG} -gt $DOCKER_TAG_MAX_LENGTH ]; then
140+
OVERFLOW=$((${#TAG} - DOCKER_TAG_MAX_LENGTH))
141+
MAX_BRANCH_LEN=$((${#SANITIZED_BRANCH} - OVERFLOW))
142+
SANITIZED_BRANCH="${SANITIZED_BRANCH:0:$MAX_BRANCH_LEN}"
143+
TAG="adhoc-${SANITIZED_BRANCH}-${VERSION}"
144+
fi
145+
146+
echo "tag=$TAG" >> $GITHUB_OUTPUT
147+
148+
- name: Build and load Docker image
149+
uses: docker/build-push-action@v6
150+
with:
151+
context: .
152+
file: Dockerfile
153+
platforms: linux/amd64
154+
load: true
155+
tags: ${{ steps.image-tag.outputs.full-image }}
156+
build-args: |
157+
VERSION=${{ steps.metadata.outputs.version }}
158+
GO_VERSION=${{ steps.metadata.outputs.go-version }}
159+
cache-from: type=gha
160+
cache-to: type=gha,mode=max
161+
162+
- name: Show image size
163+
run: docker image ls
164+
165+
- name: Snyk scan container vulnerabilities
166+
run: snyk container monitor "${{ steps.image-tag.outputs.full-image }}" --exclude-app-vulns --file=Dockerfile --platform=linux/amd64
167+
env:
168+
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
169+
170+
- name: Login to GHCR
171+
uses: docker/login-action@v3
172+
with:
173+
registry: ghcr.io
174+
username: ${{ github.actor }}
175+
password: ${{ secrets.GITHUB_TOKEN }}
176+
177+
- name: Push to GHCR (for PRs - adhoc testing)
178+
if: github.event_name == 'pull_request'
179+
run: |
180+
ADHOC_TAG="${{ steps.adhoc-tag.outputs.tag }}"
181+
docker tag "${{ steps.image-tag.outputs.full-image }}" "${{ env.GHCR_REGISTRY }}/team-operator:${ADHOC_TAG}"
182+
docker push "${{ env.GHCR_REGISTRY }}/team-operator:${ADHOC_TAG}"
183+
184+
- name: Display adhoc image tag
185+
if: github.event_name == 'pull_request'
186+
run: |
187+
ADHOC_TAG="${{ steps.adhoc-tag.outputs.tag }}"
188+
IMAGE="${{ env.GHCR_REGISTRY }}/team-operator:${ADHOC_TAG}"
189+
echo "### Adhoc Team Operator Image" >> $GITHUB_STEP_SUMMARY
190+
echo "" >> $GITHUB_STEP_SUMMARY
191+
echo "Image pushed to GHCR: \`${IMAGE}\`" >> $GITHUB_STEP_SUMMARY
192+
echo "" >> $GITHUB_STEP_SUMMARY
193+
echo "This image will be automatically deleted when the PR is closed." >> $GITHUB_STEP_SUMMARY
194+
195+
push-dockerhub:
196+
if: github.ref == 'refs/heads/main'
197+
needs: [build]
198+
runs-on: ubuntu-latest
199+
name: push-dockerhub
200+
steps:
201+
- name: Login to GHCR
202+
uses: docker/login-action@v3
203+
with:
204+
registry: ghcr.io
205+
username: ${{ github.actor }}
206+
password: ${{ secrets.GITHUB_TOKEN }}
207+
208+
- name: Pull from GHCR
209+
run: docker pull "${{ needs.build.outputs.image-tag }}"
210+
211+
- name: Login to Docker Hub
212+
uses: docker/login-action@v3
213+
with:
214+
username: ${{ secrets.DOCKER_HUB_USERNAME }}
215+
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
216+
217+
- name: Push to Docker Hub
218+
run: |
219+
docker tag \
220+
"${{ needs.build.outputs.image-tag }}" \
221+
"docker.io/${{ env.DOCKER_HUB_ORG }}/team-operator:latest"
222+
docker tag \
223+
"${{ needs.build.outputs.image-tag }}" \
224+
"docker.io/${{ env.DOCKER_HUB_ORG }}/team-operator:${{ needs.build.outputs.version }}"
225+
docker push "docker.io/${{ env.DOCKER_HUB_ORG }}/team-operator:latest"
226+
docker push "docker.io/${{ env.DOCKER_HUB_ORG }}/team-operator:${{ needs.build.outputs.version }}"
227+
228+
- name: Display Docker Hub image tags
229+
run: |
230+
echo "### Docker Hub Images Pushed" >> $GITHUB_STEP_SUMMARY
231+
echo "" >> $GITHUB_STEP_SUMMARY
232+
echo "- \`docker.io/${{ env.DOCKER_HUB_ORG }}/team-operator:${{ needs.build.outputs.version }}\`" >> $GITHUB_STEP_SUMMARY
233+
echo "- \`docker.io/${{ env.DOCKER_HUB_ORG }}/team-operator:latest\`" >> $GITHUB_STEP_SUMMARY
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# Cleanup Adhoc GHCR Images
2+
#
3+
# This workflow automatically deletes adhoc GHCR images when a PR is closed.
4+
# Adhoc images are temporary testing images pushed during PR development.
5+
#
6+
# Tag format: adhoc-{sanitized-branch-name}-{version}
7+
8+
name: Cleanup adhoc GHCR images
9+
10+
on:
11+
pull_request:
12+
types: [closed]
13+
14+
permissions:
15+
packages: write
16+
17+
jobs:
18+
cleanup:
19+
runs-on: ubuntu-latest
20+
name: cleanup-adhoc-images
21+
strategy:
22+
fail-fast: false
23+
matrix:
24+
package: [team-operator, flightdeck]
25+
steps:
26+
- name: Compute tag prefix from branch name
27+
id: tag-prefix
28+
run: |
29+
BRANCH_NAME="${{ github.head_ref }}"
30+
SANITIZED_BRANCH=$(echo "$BRANCH_NAME" | tr '/' '-')
31+
TAG_PREFIX="adhoc-${SANITIZED_BRANCH}-"
32+
echo "prefix=$TAG_PREFIX" >> $GITHUB_OUTPUT
33+
echo "Cleaning up tags with prefix: $TAG_PREFIX"
34+
35+
- name: Delete adhoc package versions
36+
uses: actions/delete-package-versions@v5
37+
with:
38+
package-name: ${{ matrix.package }}
39+
package-type: container
40+
delete-only-pre-release-versions: false
41+
min-versions-to-keep: 0
42+
token: ${{ secrets.GITHUB_TOKEN }}
43+
continue-on-error: true
44+
45+
- name: Summary
46+
run: |
47+
echo "### Adhoc Image Cleanup: ${{ matrix.package }}" >> $GITHUB_STEP_SUMMARY
48+
echo "" >> $GITHUB_STEP_SUMMARY
49+
echo "- **Tag prefix:** \`${{ steps.tag-prefix.outputs.prefix }}\`" >> $GITHUB_STEP_SUMMARY
50+
echo "- **Package:** \`ghcr.io/posit-dev/${{ matrix.package }}\`" >> $GITHUB_STEP_SUMMARY

0 commit comments

Comments
 (0)