Skip to content

Commit 2f9737c

Browse files
committed
feat: pro rock in ACR (WIP)
Jobs to be done: Rebuild, Continuous scanning
1 parent 3eeecd0 commit 2f9737c

11 files changed

Lines changed: 380 additions & 60 deletions

File tree

.github/actions/commit-releases-json/action.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ runs:
4747
git config user.name "${{ inputs.actor }}"
4848
4949
git add oci/${{ inputs.image-name }}/_releases.json
50+
if [[ -f oci/${{ inputs.image-name }}/_pro_releases.json ]]; then
51+
git add oci/${{ inputs.image-name }}/_pro_releases.json
52+
fi
5053
git commit -m "${{ inputs.message }}"
5154
5255
for i in {1..10}; do

.github/actions/fetch-releases-json/copy-releases-files.sh

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,14 @@ if [[ "$RUNNER_DEBUG" == "1" ]]; then
1414
fi
1515

1616
if [[ "$image_name" = "*" ]]; then
17-
echo "Copying all _releases.json files"
17+
echo "Copying all _releases.json (including _pro_releases.json) files"
1818
cd "$directory" || exit 1
1919
find . -name "_releases.json" -exec cp --parents {} "$OLDPWD" \;
20+
find . -name "_pro_releases.json" -exec cp --parents {} "$OLDPWD" \;
2021
cd "$OLDPWD" || exit 1
2122
elif [[ -f "$directory/oci/$image_name/_releases.json" ]]; then
2223
cp "$directory/oci/$image_name/_releases.json" "oci/$image_name/_releases.json"
24+
if [[ -f "$directory/oci/$image_name/_pro_releases.json" ]]; then
25+
cp "$directory/oci/$image_name/_pro_releases.json" "oci/$image_name/_pro_releases.json"
26+
fi
2327
fi

.github/workflows/Image.yaml

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,11 @@ jobs:
197197
rock-repo-commit: ${{ matrix.commit }}
198198
rockfile-directory: ${{ matrix.directory }}
199199
lpci-fallback: true
200-
secrets: inherit
200+
pro-services: ${{ matrix.pro-services }}
201+
secrets:
202+
source-github-token: ${{ secrets.ROCKSBOT_TOKEN }}
203+
pro-token: ${{ secrets[matrix.pro-token] }}
204+
pro-artifact-passphrase: ${{ secrets[matrix.pro-artifact-passphrase] }}
201205

202206
test-rock:
203207
needs: [prepare-build, build-rock]
@@ -210,7 +214,8 @@ jobs:
210214
oci-archive-name: "${{ matrix.name }}_${{ matrix.commit }}_${{ matrix.dir_identifier }}"
211215
trivyignore-path: ${{ matrix.ignored-vulnerabilities == '' && format('oci/{0}/.trivyignore', matrix.name) || '' }}
212216
ignored-vulnerabilities: ${{ matrix.ignored-vulnerabilities }}
213-
secrets: inherit
217+
secrets:
218+
pro-artifact-passphrase: ${{ secrets[matrix.pro-artifact-passphrase] }}
214219

215220
prepare-upload:
216221
runs-on: self-hosted-linux-amd64-noble-private-endpoint-small
@@ -318,7 +323,7 @@ jobs:
318323
UMOCI_VERSION: "v0.4.7"
319324
UMOCI_BINARY: "umoci.amd64"
320325
outputs:
321-
artefacts-hashes: ${{ steps.artefacts-hashes.outputs.hashes }}
326+
artifacts-hashes: ${{ steps.artifacts-hashes.outputs.hashes }}
322327

323328
permissions:
324329
packages: write
@@ -350,7 +355,16 @@ jobs:
350355
with:
351356
name: ${{ env.OCI_ARCHIVE_NAME }}
352357

353-
- name: Name output artefact
358+
- name: Decrypt pro artifact
359+
if: ${{ matrix.pro-services != '' }}
360+
uses: ./.github/actions/crypt-pro-artifact
361+
with:
362+
mode: decrypt
363+
input-path: ${{ env.OCI_ARCHIVE_NAME }}.gpg
364+
passphrase: ${{ secrets[matrix.pro-artifact-passphrase] }}
365+
output-path: ${{ env.OCI_ARCHIVE_NAME }}
366+
367+
- name: Name output artifact
354368
id: rename-oci-archive
355369
run: |
356370
# Rename the OCI archive tarball
@@ -460,8 +474,8 @@ jobs:
460474
name: ${{ env.OCI_ARCHIVE_NAME }}${{ env.VULNERABILITY_REPORT_SUFFIX }}
461475

462476
# https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/generic/README.md
463-
- name: Calculate artefacts hashes
464-
id: artefacts-hashes
477+
- name: Calculate artifacts hashes
478+
id: artifacts-hashes
465479
env:
466480
VULN_REPORT: ${{ env.OCI_ARCHIVE_NAME }}${{ env.VULNERABILITY_REPORT_SUFFIX }}
467481
SBOMS: ${{ steps.generate-sboms.outputs.sboms }}
@@ -475,6 +489,7 @@ jobs:
475489
476490
- name: Login to GHCR
477491
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121
492+
if: ${{ matrix.pro-services == '' }}
478493
with:
479494
registry: ghcr.io
480495
username: ${{ github.actor }}
@@ -497,6 +512,7 @@ jobs:
497512
- name: Upload to GHCR
498513
id: upload-image
499514
uses: ./.github/actions/upload-rock
515+
if: ${{ matrix.pro-services == '' }}
500516
with:
501517
artifact_name: ${{ env.OCI_ARCHIVE_NAME }}
502518
name: ${{ github.repository_owner }}/oci-factory/${{ matrix.name }}
@@ -521,12 +537,14 @@ jobs:
521537
MATRIX_NAME: ${{ matrix.name }}
522538
MATRIX_TRACK: ${{ matrix.track }}
523539
MATRIX_REVISION: ${{ matrix.revision }}
540+
PRO_SERVICES: ${{ matrix.pro-services }}
524541
STEPS_GENERATE_SBOMS_OUTPUTS_SBOMS: ${{ steps.generate-sboms.outputs.sboms }}
525542
run: |
526543
jq --arg base "${MATRIX_BASE}" \
527544
--arg digest "${STEPS_UPLOAD_IMAGE_OUTPUTS_DIGEST}" \
528545
--arg ignored_vulnerabilities "${MATRIX_IGNORED_VULNERABILITIES}" \
529-
'. + {base: $base, digest: $digest, "ignored-vulnerabilities": $ignored_vulnerabilities}' \
546+
--arg pro_services "${PRO_SERVICES}" \
547+
'. + {base: $base, digest: $digest, "ignored-vulnerabilities": $ignored_vulnerabilities, "pro-services": $pro_services}' \
530548
<<< '${{ toJSON(matrix) }}' > build_metadata.json
531549
./src/uploads/upload_to_swift.sh \
532550
"${MATRIX_NAME}" \
@@ -694,7 +712,7 @@ jobs:
694712
# contents: write # Needed for release uploads
695713
# uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@7f4fdb871876c23e455853d694197440c5a91506
696714
# with:
697-
# base64-subjects: "${{ needs.upload.outputs.artefacts-hashes }}"
715+
# base64-subjects: "${{ needs.upload.outputs.artifacts-hashes }}"
698716

699717
notify:
700718
runs-on: ubuntu-22.04

.github/workflows/Release.yaml

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ jobs:
8585
needs: [validate-push-release-request]
8686
outputs:
8787
gh-releases-matrix: ${{ steps.release-image.outputs.gh-releases-matrix }}
88+
is-public-release: ${{ steps.release-type.outputs.is-public-release }}
89+
is-pro-release: ${{ steps.release-type.outputs.is-pro-release }}
8890
env:
8991
IS_PROD: ${{ ! startsWith(inputs.oci-image-name, 'mock-') }}
9092
RELEASES_BRANCH: _releases
@@ -108,6 +110,62 @@ jobs:
108110
key: ${{ inputs.image-trigger-cache-key }}
109111
fail-on-cache-miss: true
110112

113+
114+
# Use yq to find out if `pro-release` is present and non-empty in the image trigger.
115+
- name: Release type
116+
id: release-type
117+
run: |
118+
set -e
119+
if [[ "$RUNNER_DEBUG" == "1" ]]; then
120+
set -x
121+
fi
122+
source src/shared/logs.sh
123+
image_trigger_file="oci/${INPUTS_OCI_IMAGE_NAME}/image.yaml"
124+
yq eval '.["pro-release"] | length > 0' "$image_trigger_file" > is_pro_release.txt
125+
yq eval '.["release"] | length > 0' "$image_trigger_file" > is_public_release.txt
126+
is_pro_release=$(cat is_pro_release.txt)
127+
echo "is-pro-release=$is_pro_release" >> "$GITHUB_OUTPUT"
128+
is_public_release=$(cat is_public_release.txt)
129+
echo "is-public-release=$is_public_release" >> "$GITHUB_OUTPUT"
130+
env:
131+
INPUTS_OCI_IMAGE_NAME: ${{ inputs.oci-image-name }}
132+
133+
# Download the image if `pro-release` is present and non-empty in the image trigger.
134+
# pattern should match <image_name>_<version>-<base>_*, e.g., mock-rock_1.2-22.04_*
135+
- name: Download image artifact
136+
id: download-image-artifact
137+
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
138+
if: ${{ steps.release-type.outputs.is-pro-release == 'true' }}
139+
env:
140+
INPUTS_OCI_IMAGE_NAME: ${{ inputs.oci-image-name }}
141+
with:
142+
pattern: +([A-Za-z0-9-])_+([A-Za-z0-9.-])-[0-9][0-9].[0-9][0-9]_+([0-9])
143+
merge-multiple: true
144+
145+
# Decrypt the Pro image artifact being downloaded.
146+
# They are encrypted with GPG, and has the same name as the downloaded artifact, but with .gpg extension.
147+
# put a for loop to use the script ./.github/action/crypt-artifact/crypt-artifact.sh to decrypt the artifact,
148+
- name: Decrypt Pro image artifact
149+
id: decrypt-pro-image-artifact
150+
if: ${{ steps.release-type.outputs.is-pro-release == 'true' }}
151+
run: |
152+
set -e
153+
if [[ "$RUNNER_DEBUG" == "1" ]]; then
154+
set -x
155+
fi
156+
source src/shared/logs.sh
157+
artifact_path="${{ steps.download-image-artifact.outputs.download-path }}"
158+
for artifact in $artifact_path/*.gpg; do
159+
if [ -f "$artifact" ]; then
160+
log_info "Decrypting artifact: $artifact"
161+
./.github/actions/crypt-artifact/crypt-artifact.sh decrypt \
162+
-i "$artifact" \
163+
-p "${{ secrets.ROCKS_PRO_ARTIFACT_PASSPHRASE }}"
164+
else
165+
log_info "No .gpg artifacts found to decrypt."
166+
fi
167+
done
168+
111169
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405
112170
with:
113171
python-version: "3.x"
@@ -139,6 +197,7 @@ jobs:
139197

140198
- name: Do releases from ${{ inputs.oci-image-name }}
141199
id: release-image
200+
if: ${{ steps.release-type.outputs.is-public-release == 'true' }}
142201
env:
143202
# GH has issues with boolean expressions
144203
# https://github.com/actions/runner/issues/1483
@@ -174,25 +233,72 @@ jobs:
174233
--all-revision-tags "${STEPS_GET_ALL_CANONICAL_TAGS_OUTPUTS_CANONICAL_TAGS_FILE}" \
175234
--ghcr-repo "${{ github.repository_owner }}/oci-factory"
176235
236+
- name: Do Pro releases from ${{ inputs.oci-image-name }}
237+
if: ${{ steps.release-type.outputs.is-pro-release == 'true' }}
238+
id: release-pro-image
239+
env:
240+
# GH has issues with boolean expressions
241+
ACR_CREDS_USR: ${{ env.IS_PROD == 'true' && secrets.ACR_CREDS_USR || secrets.ACR_CREDS_USR_DEV }}
242+
ACR_CREDS_PSW: ${{ env.IS_PROD == 'true' && secrets.ACR_CREDS_PSW || secrets.ACR_CREDS_PSW_DEV }}
243+
ACR_REGISTRY: ${{ env.IS_PROD == 'true' && 'ubuntu.azurecr.io' || secrets.ACR_REGISTRY_DEV }}
244+
PYTHONUNBUFFERED: 1
245+
INPUTS_OCI_IMAGE_NAME: ${{ inputs.oci-image-name }}
246+
STEPS_GET_ALL_CANONICAL_TAGS_OUTPUTS_CANONICAL_TAGS_FILE: ${{ steps.get-all-canonical-tags.outputs.canonical-tags-file }}
247+
run: |
248+
set -e
249+
if [[ "$RUNNER_DEBUG" == "1" ]]; then
250+
set -x
251+
fi
252+
source src/shared/logs.sh
253+
log_info "Running in production? $([[ "$IS_PROD" == "true" ]] && echo YES || echo NO)"
254+
255+
python3 -m src.image.release \
256+
--image-trigger oci/${INPUTS_OCI_IMAGE_NAME}/image.yaml \
257+
--image-name ${INPUTS_OCI_IMAGE_NAME} \
258+
--all-releases oci/${INPUTS_OCI_IMAGE_NAME}/_releases.json \
259+
--all-revision-tags "${STEPS_GET_ALL_CANONICAL_TAGS_OUTPUTS_CANONICAL_TAGS_FILE}" \
260+
--pro
261+
177262
- name: Update _releases.json
263+
if: ${{ steps.release-type.outputs.is-public-release == 'true' }}
178264
run: |
179265
python3 -m src.image.release \
180266
--image-trigger oci/${INPUTS_OCI_IMAGE_NAME}/image.yaml \
181267
--image-name ${INPUTS_OCI_IMAGE_NAME} \
182268
--all-releases oci/${INPUTS_OCI_IMAGE_NAME}/_releases.json \
183269
--all-revision-tags "${STEPS_GET_ALL_CANONICAL_TAGS_OUTPUTS_CANONICAL_TAGS_FILE}" \
184270
--update-releases-json
271+
272+
- name: Update _pro_releases.json
273+
if: ${{ steps.release-type.outputs.is-pro-release == 'true' }}
274+
run: |
275+
python3 -m src.image.release \
276+
--image-trigger oci/${INPUTS_OCI_IMAGE_NAME}/image.yaml \
277+
--image-name ${INPUTS_OCI_IMAGE_NAME} \
278+
--all-releases oci/${INPUTS_OCI_IMAGE_NAME}/_pro_releases.json \
279+
--all-revision-tags "${STEPS_GET_ALL_CANONICAL_TAGS_OUTPUTS_CANONICAL_TAGS_FILE}" \
280+
--update-releases-json \
281+
--pro
185282
env:
186283
INPUTS_OCI_IMAGE_NAME: ${{ inputs.oci-image-name }}
187284
STEPS_GET_ALL_CANONICAL_TAGS_OUTPUTS_CANONICAL_TAGS_FILE: ${{ steps.get-all-canonical-tags.outputs.canonical-tags-file }}
188285

189286
- name: Overwrite _releases.json
287+
if: ${{ steps.release-type.outputs.is-public-release == 'true' }}
190288
run: |
191289
mkdir -p ${{ env.RELEASE_REPO_DIR }}/oci/${INPUTS_OCI_IMAGE_NAME}
192290
cp oci/${INPUTS_OCI_IMAGE_NAME}/_releases.json ${{ env.RELEASE_REPO_DIR }}/oci/${INPUTS_OCI_IMAGE_NAME}/_releases.json
193291
env:
194292
INPUTS_OCI_IMAGE_NAME: ${{ inputs.oci-image-name }}
195293

294+
- name: Overwrite _pro_releases.json
295+
if: ${{ steps.release-type.outputs.is-pro-release == 'true' }}
296+
run: |
297+
mkdir -p ${{ env.RELEASE_REPO_DIR }}/oci/${INPUTS_OCI_IMAGE_NAME}
298+
cp oci/${INPUTS_OCI_IMAGE_NAME}/_pro_releases.json ${{ env.RELEASE_REPO_DIR }}/oci/${INPUTS_OCI_IMAGE_NAME}/_pro_releases.json
299+
env:
300+
INPUTS_OCI_IMAGE_NAME: ${{ inputs.oci-image-name }}
301+
196302
# Commit with actor's GitHub noreply email to pass CLA and keep the privacy of the actor
197303
# Use force push, to bypass the branch protection rules
198304
- name: Commit oci/${{ inputs.oci-image-name }}/_releases.json
@@ -207,6 +313,9 @@ jobs:
207313
update-documentation:
208314
name: Update documentation
209315
needs: [do-releases]
316+
# Documentation should only be updated for public releases, not for pro releases.
317+
# As ACR does not have a Web UI.
318+
if: ${{ needs.do-releases.outputs.is-public-release == 'true' }}
210319
uses: ./.github/workflows/Documentation.yaml
211320
with:
212321
oci-image-name: "${{ inputs.oci-image-name }}"

oci/mock-rock/image.yaml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,24 @@ upload:
5555
ignored-vulnerabilities:
5656
- "CVE-2023-1234"
5757
- "CVE-2023-5678"
58+
- source: "canonical/rocks-toolbox"
59+
commit: eee5f5ebeff62848903156fb3fe93c5d37174085
60+
directory: mock_rock/1.2
61+
pro:
62+
services:
63+
- esm-infra
64+
- esm-apps
65+
config:
66+
token: secrets.ROCKS_PRO_TOKEN
67+
artifact-passphrase: secrets.ROCKS_PRO_ARTIFACT_PASSPHRASE
68+
release:
69+
1.2-22.04:
70+
end-of-life: "2030-05-01T00:00:00Z"
71+
risks:
72+
- beta
73+
ignored-vulnerabilities:
74+
- "CVE-2023-1234"
75+
- "CVE-2023-5678"
5876
# TODO: Restore the devel release once the https://github.com/canonical/rockcraft/issues/1262 is resolved.
5977
# - source: "canonical/rocks-toolbox"
6078
# commit: bb9138fbe81f893b4303a0e29df7cf9581069de0

src/image/merge_release_info.py

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,21 @@ def backfill_higher_risks(channels: dict) -> None:
4444
val[risk] = f"{track}_{KNOWN_RISKS_ORDERED[i-1]}"
4545

4646

47+
def _assemble_pro_track(_track: str, _pro_services: list[str]) -> str:
48+
"""Assembles the track name with the pro services."""
49+
# Remove any esm- services from the pro services, as they are not part of the track name.
50+
_pro_services = [ps for ps in _pro_services if not ps.startswith("esm-")]
51+
52+
# If there are no pro services, return the original track name.
53+
# ESM-only pro services are not part of the track name, so we can return the original track name.
54+
if not _pro_services:
55+
return _track
56+
57+
version, base = _track.split("-", 1)
58+
59+
return f"{version}-{'-'.join(sorted(_pro_services))}-{base}"
60+
61+
4762
if __name__ == "__main__":
4863
parser = argparse.ArgumentParser()
4964
parser.add_argument(
@@ -65,8 +80,6 @@ def backfill_higher_risks(channels: dict) -> None:
6580

6681
_ = ImageSchema(**image_trigger)
6782

68-
user_releases = image_trigger.get("release", {})
69-
7083
logger.info(f"Getting pre-release from {args.revision_data_file}")
7184
with open(args.revision_data_file, encoding="UTF-8") as revision_data_f:
7285
revision_data = json.load(revision_data_f)
@@ -76,8 +89,24 @@ def backfill_higher_risks(channels: dict) -> None:
7689
new_revision_releases = revision_data["release"]
7790
new_revision = revision_data["revision"]
7891

92+
pro_entry = revision_data.get("pro", {})
93+
if not pro_entry:
94+
logger.info("No pro entry found in revision data.")
95+
pro_services = []
96+
else:
97+
pro_services = pro_entry.get("services", [])
98+
99+
user_releases = (
100+
image_trigger.get("pro-release", {})
101+
if pro_services
102+
else image_trigger.get("release", {})
103+
)
104+
79105
# Update "release" from image trigger with new revision releases
80106
for track, val in new_revision_releases.items():
107+
if pro_services:
108+
track = _assemble_pro_track(track, pro_services)
109+
81110
if track not in user_releases:
82111
user_releases[track] = {}
83112

@@ -87,11 +116,17 @@ def backfill_higher_risks(channels: dict) -> None:
87116
for risk in val["risks"]:
88117
user_releases[track][risk] = str(new_revision)
89118

119+
if pro_services:
120+
user_releases[track]["services"] = pro_services
121+
90122
# For every track, we need to backfill the risks
91123
backfill_higher_risks(user_releases)
92124

93125
# Overwrite the image trigger with the new release value
94-
image_trigger["release"] = user_releases
126+
if pro_services:
127+
image_trigger["pro-release"] = user_releases
128+
else:
129+
image_trigger["release"] = user_releases
95130

96131
logger.info(f"Finished merging pre releases:\n{json.dumps(image_trigger)}")
97132
logger.info(f"Overwriting {args.image_trigger}...")

0 commit comments

Comments
 (0)