@@ -2,11 +2,27 @@ name: CI/CD Pipeline
22
33on :
44 pull_request :
5- branches : [ngwpc-candidate, ngwpc-release, main, nwm-main, development, release-candidate]
5+ branches :
6+ - ngwpc-candidate
7+ - ngwpc-release
8+ - main
9+ - nwm-main
10+ - development
11+ - release-candidate
612 push :
7- branches : [ngwpc-candidate, ngwpc-release, main, nwm-main, development, release-candidate]
8- release :
9- types : [published]
13+ branches :
14+ - ngwpc-candidate
15+ - ngwpc-release
16+ - main
17+ - nwm-main
18+ - development
19+ - release-candidate
20+ workflow_dispatch :
21+ inputs :
22+ NGEN_IMAGE_TAG :
23+ description : ' NGEN_IMAGE_TAG'
24+ required : false
25+ type : string
1026
1127permissions :
1228 contents : read
@@ -18,47 +34,122 @@ env:
1834 PYTHON_VERSION : ' 3.11'
1935
2036jobs :
37+ # set variables for use in other jobs
2138 setup :
39+ name : setup
2240 runs-on : ubuntu-latest
2341 outputs :
42+ org : ${{ steps.vars.outputs.org }}
2443 image_base : ${{ steps.vars.outputs.image_base }}
2544 pr_tag : ${{ steps.vars.outputs.pr_tag }}
2645 commit_sha : ${{ steps.vars.outputs.commit_sha }}
2746 commit_sha_short : ${{ steps.vars.outputs.commit_sha_short }}
2847 test_image_tag : ${{ steps.vars.outputs.test_image_tag }}
48+ alias_tag : ${{ steps.vars.outputs.alias_tag }}
49+ build_date : ${{ steps.vars.outputs.build_date }}
50+ clean_ref : ${{ steps.vars.outputs.clean_ref }}
2951 steps :
3052 - name : Compute image vars
3153 id : vars
3254 shell : bash
3355 run : |
3456 set -euo pipefail
57+
58+ # set variables to use with Docker images
3559 ORG="$(echo "${GITHUB_REPOSITORY_OWNER}" | tr '[:upper:]' '[:lower:]')"
3660 REPO="$(basename "${GITHUB_REPOSITORY}")"
3761 IMAGE_BASE="${REGISTRY}/${ORG}/${REPO}"
38- echo "image_base=${IMAGE_BASE}" >> "$GITHUB_OUTPUT"
3962
40- if [ "${GITHUB_EVENT_NAME}" = "pull_request" ]; then
41- PR_NUM="${{ github.event.pull_request.number }}"
42- PR_TAG="pr-${PR_NUM}-build"
43- echo "pr_tag=${PR_TAG}" >> "$GITHUB_OUTPUT"
44- echo "test_image_tag=${PR_TAG}" >> "$GITHUB_OUTPUT"
63+ # one datetime for all time variables
64+ NOW=$(date -u +'%Y-%m-%d %H:%M:%S')
65+
66+ # for OCI labels
67+ BUILD_DATE=$(date -u -d "$NOW" +'%Y-%m-%dT%H:%M:%SZ')
68+
69+ # for Docker image tags
70+ TIMESTAMP=$(date -u -d "$NOW" +'%Y%m%d%H%M%SZ')
71+
72+ # logic to get the real branch name and commit SHA on pull requests
73+ if [ "$GITHUB_EVENT_NAME" = "pull_request" ]; then
74+ REAL_REF="${{ github.head_ref }}"
75+ REAL_SHA="${{ github.event.pull_request.head.sha }}"
76+ else
77+ REAL_REF="${{ github.ref_name }}"
78+ REAL_SHA="${GITHUB_SHA}"
4579 fi
4680
47- if [ "${GITHUB_EVENT_NAME}" = "push" ]; then
48- COMMIT_SHA="${GITHUB_SHA}"
49- SHORT_SHA="${COMMIT_SHA:0:12}"
50- echo "commit_sha=${COMMIT_SHA}" >> "$GITHUB_OUTPUT"
51- echo "commit_sha_short=${SHORT_SHA}" >> "$GITHUB_OUTPUT"
52- echo "test_image_tag=${SHORT_SHA}" >> "$GITHUB_OUTPUT"
81+ # clean ref name and short commit sha
82+ CLEAN_REF=$(echo "$REAL_REF" | tr '[:upper:]' '[:lower:]' | sed 's/\//-/g')
83+ SHORT_SHA="${REAL_SHA:0:7}"
84+
85+ # logic for the tags:
86+ # test_image_tag (commit short sha): used for the initial build and test
87+ # alias_tag: used for final tagging on successful tests
88+
89+ # test tag is always commit short sha
90+ TEST_TAG="${SHORT_SHA}"
91+
92+ if [ "$GITHUB_EVENT_NAME" = "pull_request" ]; then
93+ # for pull requests, use pr-<pr number>-build
94+ ALIAS="pr-${{ github.event.pull_request.number }}-build"
95+ elif [ "$GITHUB_EVENT_NAME" = "workflow_dispatch" ] && [ "${{ github.ref_type }}" = "tag" ]; then
96+ # for manual workflow dispatch on tags, use the git tag
97+ ALIAS="${CLEAN_REF}"
98+ else
99+ # for pushes to branches, use timestamp-branchname
100+ ALIAS="${TIMESTAMP}-${CLEAN_REF}"
53101 fi
54102
103+ # save outputs
104+ echo "org=${ORG}" >> "$GITHUB_OUTPUT"
105+ echo "image_base=${IMAGE_BASE}" >> "$GITHUB_OUTPUT"
106+ echo "build_date=${BUILD_DATE}" >> "$GITHUB_OUTPUT"
107+ echo "test_image_tag=${TEST_TAG}" >> "$GITHUB_OUTPUT"
108+ echo "alias_tag=${ALIAS}" >> "$GITHUB_OUTPUT"
109+ echo "commit_sha=${REAL_SHA}" >> "$GITHUB_OUTPUT"
110+ echo "commit_sha_short=${SHORT_SHA}" >> "$GITHUB_OUTPUT"
111+ echo "clean_ref=${CLEAN_REF}" >> "$GITHUB_OUTPUT"
112+
113+ # CodeQL scan
114+ codeql-scan :
115+ name : codeql-scan
116+ if : |
117+ (github.event_name == 'pull_request') ||
118+ (github.event_name == 'push') ||
119+ (github.event_name == 'workflow_dispatch')
120+ runs-on : ubuntu-latest
121+ needs : setup
122+ permissions :
123+ actions : read
124+ contents : read
125+ security-events : write
126+ steps :
127+ - uses : actions/checkout@v6
128+ - name : Set up Python
129+ uses : actions/setup-python@v6
130+ with :
131+ python-version : ${{ env.PYTHON_VERSION }}
132+ - name : Initialize CodeQL
133+ uses : github/codeql-action/init@v4
134+ with :
135+ languages : python
136+ - name : Install dependencies
137+ run : |
138+ python -m pip install --upgrade pip
139+ if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
140+ - name : Perform CodeQL Analysis
141+ uses : github/codeql-action/analyze@v4
142+
55143 build :
56144 name : build
57- if : github.event_name == 'pull_request' || github.event_name == 'push'
145+ if : |
146+ (github.event_name == 'pull_request') ||
147+ (github.event_name == 'push') ||
148+ (github.event_name == 'workflow_dispatch')
58149 runs-on : ubuntu-latest
59150 needs : setup
60151 steps :
61- - uses : actions/checkout@v4
152+ - uses : actions/checkout@v6
62153
63154 - name : Log in to registry
64155 uses : docker/login-action@v3
@@ -74,145 +165,106 @@ jobs:
74165 push : true
75166 tags : ${{ needs.setup.outputs.image_base }}:${{ needs.setup.outputs.test_image_tag }}
76167 build-args : |
77- NGEN_IMAGE_TAG=${{ env.NGEN_IMAGE_TAG || 'latest' }}
78- CI_COMMIT_REF_NAME=${{ github.ref_name }}
168+ ORG=${{ needs.setup.outputs.org }}
169+ NGEN_IMAGE_TAG=${{ inputs.NGEN_IMAGE_TAG || 'latest' }}
170+ IMAGE_SOURCE=https://github.com/${{ github.repository }}
171+ IMAGE_VENDOR=${{ github.repository_owner }}
172+ IMAGE_VERSION=${{ needs.setup.outputs.clean_ref }}
173+ IMAGE_REVISION=${{ needs.setup.outputs.commit_sha }}
174+ IMAGE_CREATED=${{ needs.setup.outputs.build_date }}
175+ CI_COMMIT_REF_NAME=${{ needs.setup.outputs.clean_ref }}
79176
80177 unit-test :
81178 name : unit-test
82- if : github.event_name == 'pull_request' || github.event_name == 'push'
179+ if : |
180+ (github.event_name == 'pull_request') ||
181+ (github.event_name == 'push') ||
182+ (github.event_name == 'workflow_dispatch')
83183 runs-on : ubuntu-latest
84- needs : [setup, build]
184+ needs :
185+ - setup
186+ - build
85187 container :
86188 image : ${{ needs.setup.outputs.image_base }}:${{ needs.setup.outputs.test_image_tag }}
87189 steps :
88190 - name : Run unit tests
89191 run : |
90192 echo "TODO: add unit tests here"
91193
92- codeql-scan :
93- if : github.event_name == 'pull_request' || github.event_name == 'push'
94- runs-on : ubuntu-latest
95- needs : [setup, build]
96- permissions :
97- actions : read
98- contents : read
99- security-events : write
100- steps :
101- - uses : actions/checkout@v4
102- - name : Set up Python
103- uses : actions/setup-python@v5
104- with :
105- python-version : ${{ env.PYTHON_VERSION }}
106- - name : Initialize CodeQL
107- uses : github/codeql-action/init@v3
108- with :
109- languages : python
110- - name : Install dependencies
111- run : |
112- python -m pip install --upgrade pip
113- if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
114- - name : Perform CodeQL Analysis
115- uses : github/codeql-action/analyze@v3
116-
194+ # run container security scan using Trivy
117195 container-scanning :
118- if : github.event_name == 'pull_request' || github.event_name == 'push'
196+ if : |
197+ (github.event_name == 'pull_request') ||
198+ (github.event_name == 'push') ||
199+ (github.event_name == 'workflow_dispatch')
119200 runs-on : ubuntu-latest
120- needs : [setup, build]
201+ needs :
202+ - setup
203+ - build
121204 steps :
122- - name : Scan container with Trivy
123- uses : aquasecurity/trivy-action@0.20.0
124- with :
125- image-ref : ${{ needs.setup.outputs.image_base }}:${{ needs.setup.outputs.test_image_tag }}
126- format : ' template'
127- template : ' @/contrib/sarif.tpl'
128- output : ' trivy-results.sarif'
129- severity : ' CRITICAL,HIGH'
130- - name : Upload Trivy SARIF
131- uses : github/codeql-action/upload-sarif@v3
205+ - name : Install Trivy
206+ uses : aquasecurity/setup-trivy@v0.2.2
132207 with :
133- sarif_file : ' trivy-results.sarif'
208+ cache : true
209+ version : v0.68.2
134210
135- deploy-latest-on-development :
136- name : deploy-latest-on-development
137- if : github.event_name == 'push' && github.ref_name == 'development'
138- runs-on : ubuntu-latest
139- needs : [setup, build, unit-test, codeql-scan, container-scanning]
140- steps :
141- - name : Tag image with 'latest'
142- shell : bash
211+ - name : Trivy scan
212+ env :
213+ TMPDIR : /mnt/trivy-temp
143214 run : |
144- set -euo pipefail
145- IMAGE_BASE="${{ needs.setup.outputs.image_base }}"
146- SHORT_SHA="${{ needs.setup.outputs.commit_sha_short }}"
215+ sudo mkdir -p $TMPDIR
216+ sudo chown -R $USER:$USER $TMPDIR
147217
148- # ensure skopeo is available
149- if ! command -v skopeo >/dev/null 2>&1; then
150- sudo apt-get update -y
151- sudo apt-get install -y --no-install-recommends skopeo
152- fi
153-
154- skopeo copy \
155- --src-creds "${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}" \
156- --dest-creds "${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}" \
157- docker://"${IMAGE_BASE}:${SHORT_SHA}" docker://"${IMAGE_BASE}:latest"
218+ trivy image \
219+ --format sarif \
220+ --output trivy-results.sarif \
221+ --severity CRITICAL,HIGH \
222+ --scanners vuln \
223+ --ignore-unfixed \
224+ --timeout 45m \
225+ ${{ needs.setup.outputs.image_base }}:${{ needs.setup.outputs.test_image_tag }}
158226
159- release :
160- name : release
161- if : github.event_name == 'release' && github.event.action == 'published'
227+ # promote Docker image tags after successful tests
228+ promote-tags :
229+ name : Promote Tags
230+ if : |
231+ (github.event_name == 'pull_request') ||
232+ (github.event_name == 'push') ||
233+ (github.event_name == 'workflow_dispatch')
162234 runs-on : ubuntu-latest
163- needs : setup
235+ needs :
236+ - setup
237+ - codeql-scan
238+ - build
239+ - container-scanning
164240 steps :
165- - name : Get commit sha for the tag
166- id : rev
167- shell : bash
168- env :
169- GH_TOKEN : ${{ secrets.GITHUB_TOKEN }}
170- run : |
171- set -euo pipefail
172- TAG="${{ github.event.release.tag_name }}"
173- REPO="${{ github.repository }}"
174-
175- # ensure jq is available
176- if ! command -v jq >/dev/null 2>&1; then
177- sudo apt-get update -y
178- sudo apt-get install -y --no-install-recommends jq
179- fi
180-
181- # ensure gh cli is available
182- if ! command -v gh >/dev/null 2>&1; then
183- sudo apt-get update -y
184- sudo apt-get install -y --no-install-recommends gh
185- fi
186-
187- REF_JSON="$(gh api "repos/${REPO}/git/refs/tags/${TAG}")"
188- OBJ_SHA="$(jq -r '.object.sha' <<<"$REF_JSON")"
189- OBJ_TYPE="$(jq -r '.object.type' <<<"$REF_JSON")"
190-
191- if [ "$OBJ_TYPE" = "tag" ]; then
192- TAG_OBJ="$(gh api "repos/${REPO}/git/tags/${OBJ_SHA}")"
193- COMMIT_SHA="$(jq -r '.object.sha' <<<"$TAG_OBJ")"
194- else
195- COMMIT_SHA="$OBJ_SHA"
196- fi
197-
198- SHORT_SHA="${COMMIT_SHA:0:12}"
199- echo "short_sha=${SHORT_SHA}" >> "$GITHUB_OUTPUT"
200-
201- - name : Tag image with release tag
241+ - name : Tag image with alias and latest
202242 shell : bash
203243 run : |
204244 set -euo pipefail
205- IMAGE_BASE="${{ needs.setup.outputs.image_base }}"
206- SHORT_SHA="${{ steps.rev.outputs.short_sha }}"
207- RELEASE_TAG="${{ github.event.release.tag_name }}"
208245
209- # ensure skopeo is available
246+ # ensure skopeo is available for promotion
210247 if ! command -v skopeo >/dev/null 2>&1; then
211248 sudo apt-get update -y
212249 sudo apt-get install -y --no-install-recommends skopeo
213250 fi
214251
252+ IMAGE_BASE="${{ needs.setup.outputs.image_base }}"
253+ TEST_TAG="${{ needs.setup.outputs.test_image_tag }}"
254+ ALIAS_TAG="${{ needs.setup.outputs.alias_tag }}"
255+
256+ # apply the primary alias (pr tag or timestamp-branch)
215257 skopeo copy \
216258 --src-creds "${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}" \
217259 --dest-creds "${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}" \
218- docker://"${IMAGE_BASE}:${SHORT_SHA}" docker://"${IMAGE_BASE}:${RELEASE_TAG}"
260+ --all \
261+ "docker://${IMAGE_BASE}:${TEST_TAG}" "docker://${IMAGE_BASE}:${ALIAS_TAG}"
262+
263+ # tag with 'latest' on development branch push
264+ if [ "$GITHUB_EVENT_NAME" = "push" ] && [ "$GITHUB_REF_NAME" = "development" ]; then
265+ skopeo copy \
266+ --src-creds "${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}" \
267+ --dest-creds "${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}" \
268+ --all \
269+ "docker://${IMAGE_BASE}:${TEST_TAG}" "docker://${IMAGE_BASE}:latest"
270+ fi
0 commit comments