Skip to content

Commit 89b4a3c

Browse files
committed
fix(RELEASE-2650): add timestamp tag to data-acceptable-bundles
The update-trusted-tasks task now creates timestamp-based tags for the data-acceptable-bundles OCI artifact to prevent garbage collection. This follows the pattern used in build-definitions. The task now: - Generates a timestamp at the start (unix epoch format) - Outputs ec track bundle to the timestamp tag - Copies the timestamp tag to :latest after each bundle is added - Uses docker:// transport for skopeo copy (not oci:) - Uses skopeo inspect instead of list-tags for efficiency - Adds --retry-times 3 to both skopeo inspect and copy commands This ensures that even when :latest moves to a new digest, the old digest remains tagged and won't be garbage collected, preventing breakage of digest-pinned references. Assisted-by: Claude Code Signed-off-by: Martin Malina <mmalina@redhat.com>
1 parent 3ff53b2 commit 89b4a3c

7 files changed

Lines changed: 135 additions & 41 deletions

tasks/managed/update-trusted-tasks/tests/mocks.sh

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,29 @@
22
set -eux
33

44
# mocks to be injected into task step scripts
5+
function date() {
6+
# Return a fixed timestamp for testing
7+
if [[ "$*" == "+%s" ]]; then
8+
echo "1234567890"
9+
return
10+
fi
11+
12+
# Fall back to real date for other uses
13+
command date "$@"
14+
}
15+
516
function skopeo() {
617
echo Mock skopeo called with: $* >&2
718
echo $* >> "$(params.dataDir)/mock_skopeo.txt"
819

9-
if [[ "$*" =~ list-tags\ docker://quay.io/exists ]]; then
10-
echo '{"Tags": ["v2.0.0-3", "latest", "v2.0.0-2"]}'
11-
return
12-
elif [[ "$*" =~ list-tags\ docker://quay.io ]]; then
13-
echo '{"Tags": ["v2.0.0-4", "v2.0.0-3", "v2.0.0-2"]}'
20+
if [[ "$*" =~ inspect.*docker://quay.io/exists.*:latest ]]; then
21+
# Exists repo has :latest tag - return success
22+
return 0
23+
elif [[ "$*" =~ inspect.*docker://quay.io/.*:latest ]]; then
24+
# Other repos don't have :latest tag - return failure
25+
return 1
26+
elif [[ "$*" =~ ^copy ]]; then
27+
# Mock skopeo copy - just record that it was called
1428
return
1529
fi
1630

@@ -24,11 +38,11 @@ function ec() {
2438

2539
if [[ "$*" =~ "track bundle".*fail-image.* ]]; then
2640
exit 1
27-
41+
2842
elif [[ "$*" =~ "track bundle".* ]]; then
2943
return
3044
fi
31-
45+
3246
echo Error: Unexpected call
3347
exit 1
3448
}

tasks/managed/update-trusted-tasks/tests/test-update-trusted-tasks-fail-ec.yaml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,12 +131,14 @@ spec:
131131
#!/bin/bash
132132
set -eux
133133
134+
# Expect only 1 skopeo call for inspect (task fails before copy)
134135
if [ "$(wc -l < "$(params.dataDir)/mock_skopeo.txt")" != 1 ]; then
135-
echo Error: skopeo was expected to be called 1 times. Actual calls:
136+
echo Error: skopeo was expected to be called 1 time. Actual calls:
136137
cat "$(params.dataDir)/mock_skopeo.txt"
137138
exit 1
138139
fi
139140
141+
# Expect 1 ec call which should fail
140142
if [ "$(wc -l < "$(params.dataDir)/mock_ec.txt")" != 1 ]; then
141143
echo Error: ec was expected to be called 1 time. Actual calls:
142144
cat "$(params.dataDir)/mock_ec.txt"

tasks/managed/update-trusted-tasks/tests/test-update-trusted-tasks-success-latest.yaml

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -163,8 +163,9 @@ spec:
163163
#!/bin/bash
164164
set -eux
165165
166-
if [ "$(wc -l < "$(params.dataDir)/mock_skopeo.txt")" != 1 ]; then
167-
echo Error: skopeo was expected to be called 1 times. Actual calls:
166+
# Expect 2 skopeo calls: 1 for inspect, 1 for copy to tag as :latest
167+
if [ "$(wc -l < "$(params.dataDir)/mock_skopeo.txt")" != 2 ]; then
168+
echo Error: skopeo was expected to be called 2 times. Actual calls:
168169
cat "$(params.dataDir)/mock_skopeo.txt"
169170
exit 1
170171
fi
@@ -175,12 +176,22 @@ spec:
175176
exit 1
176177
fi
177178
179+
# Expect ec track bundle to use :latest as input and output to timestamp tag
178180
out="track bundle --bundle quay.io/exists/task-echo:0.1@sha256:abcde \
179181
--input oci:quay.io/exists/data-acceptable-bundles:latest \
180-
--output oci:quay.io/exists/data-acceptable-bundles:latest"
182+
--output oci:quay.io/exists/data-acceptable-bundles:1234567890"
181183
182184
if ! grep -qF -- "$out" "$(params.dataDir)/mock_ec.txt"; then
183185
echo "Error: $out was not found in the ec command"
184186
cat "$(params.dataDir)/mock_ec.txt"
185187
exit 1
186188
fi
189+
190+
# Expect skopeo copy to tag the timestamp image as :latest
191+
copy_cmd="copy --retry-times 3 docker://quay.io/exists/data-acceptable-bundles:1234567890 \
192+
docker://quay.io/exists/data-acceptable-bundles:latest"
193+
if ! grep -qF -- "$copy_cmd" "$(params.dataDir)/mock_skopeo.txt"; then
194+
echo "Error: $copy_cmd was not found in the skopeo commands"
195+
cat "$(params.dataDir)/mock_skopeo.txt"
196+
exit 1
197+
fi

tasks/managed/update-trusted-tasks/tests/test-update-trusted-tasks-success-multiple-components.yaml

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -195,30 +195,33 @@ spec:
195195
#!/bin/bash
196196
set -eux
197197
198-
if [ "$(wc -l < "$(params.dataDir)/mock_skopeo.txt")" != 3 ]; then
199-
echo Error: skopeo was expected to be called 1 times. Actual calls:
198+
# Expect 6 skopeo calls:
199+
# 3 for inspect (one for notexist, two for exists repo - once per component)
200+
# 3 for copy (one per component)
201+
if [ "$(wc -l < "$(params.dataDir)/mock_skopeo.txt")" != 6 ]; then
202+
echo Error: skopeo was expected to be called 6 times. Actual calls:
200203
cat "$(params.dataDir)/mock_skopeo.txt"
201204
exit 1
202205
fi
203206
204207
if [ "$(wc -l < "$(params.dataDir)/mock_ec.txt")" != 3 ]; then
205-
echo Error: ec was expected to be called 1 time. Actual calls:
208+
echo Error: ec was expected to be called 3 times. Actual calls:
206209
cat "$(params.dataDir)/mock_ec.txt"
207210
exit 1
208211
fi
209212
210213
all_found=true
211214
outs=(
212215
"track bundle --bundle quay.io/notexist/task-echo-v01:0.1@sha256:abcde \
213-
--output oci:quay.io/notexist/data-acceptable-bundles:latest"
216+
--output oci:quay.io/notexist/data-acceptable-bundles:1234567890"
214217
215218
"track bundle --bundle quay.io/exists/task-echo-v02:0.2@sha256:abcde \
216219
--input oci:quay.io/exists/data-acceptable-bundles:latest \
217-
--output oci:quay.io/exists/data-acceptable-bundles:latest"
220+
--output oci:quay.io/exists/data-acceptable-bundles:1234567890"
218221
219222
"track bundle --bundle quay.io/exists/task-echo:0.3@sha256:abcde \
220223
--input oci:quay.io/exists/data-acceptable-bundles:latest \
221-
--output oci:quay.io/exists/data-acceptable-bundles:latest"
224+
--output oci:quay.io/exists/data-acceptable-bundles:1234567890"
222225
)
223226
224227
for out in "${outs[@]}"; do
@@ -231,3 +234,21 @@ spec:
231234
cat "$(params.dataDir)/mock_ec.txt"
232235
exit 1
233236
fi
237+
238+
# Expect 3 skopeo copy commands
239+
notexist_copy="copy --retry-times 3 docker://quay.io/notexist/data-acceptable-bundles:1234567890 \
240+
docker://quay.io/notexist/data-acceptable-bundles:latest"
241+
if ! grep -qF "$notexist_copy" "$(params.dataDir)/mock_skopeo.txt"; then
242+
echo "Error: expected copy for notexist repo"
243+
cat "$(params.dataDir)/mock_skopeo.txt"
244+
exit 1
245+
fi
246+
247+
exists_copy="copy --retry-times 3 docker://quay.io/exists/data-acceptable-bundles:1234567890 \
248+
docker://quay.io/exists/data-acceptable-bundles:latest"
249+
copy_count=$(grep -cF "$exists_copy" "$(params.dataDir)/mock_skopeo.txt" || true)
250+
if [ "$copy_count" != 2 ]; then
251+
echo "Error: expected 2 copy commands for exists repo, found $copy_count"
252+
cat "$(params.dataDir)/mock_skopeo.txt"
253+
exit 1
254+
fi

tasks/managed/update-trusted-tasks/tests/test-update-trusted-tasks-success-multiple-tags.yaml

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -159,28 +159,32 @@ spec:
159159
#!/bin/bash
160160
set -eux
161161
162-
if [ "$(wc -l < "$(params.dataDir)/mock_skopeo.txt")" != 3 ]; then
163-
echo Error: skopeo was expected to be called 1 times. Actual calls:
162+
# Expect 4 skopeo calls: 1 for inspect, 3 for copy (one per tag)
163+
if [ "$(wc -l < "$(params.dataDir)/mock_skopeo.txt")" != 4 ]; then
164+
echo Error: skopeo was expected to be called 4 times. Actual calls:
164165
cat "$(params.dataDir)/mock_skopeo.txt"
165166
exit 1
166167
fi
167168
168169
if [ "$(wc -l < "$(params.dataDir)/mock_ec.txt")" != 3 ]; then
169-
echo Error: ec was expected to be called 1 time. Actual calls:
170+
echo Error: ec was expected to be called 3 times. Actual calls:
170171
cat "$(params.dataDir)/mock_ec.txt"
171172
exit 1
172173
fi
173174
174175
all_found=true
176+
# First tag creates the bundle (no input), subsequent ones use :latest as input
175177
outs=(
176178
"track bundle --bundle quay.io/notexist/task-echo:0.1@sha256:abcde \
177-
--output oci:quay.io/notexist/data-acceptable-bundles:latest"
179+
--output oci:quay.io/notexist/data-acceptable-bundles:1234567890"
178180
179181
"track bundle --bundle quay.io/notexist/task-echo:test@sha256:abcde \
180-
--output oci:quay.io/notexist/data-acceptable-bundles:latest"
182+
--input oci:quay.io/notexist/data-acceptable-bundles:latest \
183+
--output oci:quay.io/notexist/data-acceptable-bundles:1234567890"
181184
182185
"track bundle --bundle quay.io/notexist/task-echo:stage@sha256:abcde \
183-
--output oci:quay.io/notexist/data-acceptable-bundles:latest"
186+
--input oci:quay.io/notexist/data-acceptable-bundles:latest \
187+
--output oci:quay.io/notexist/data-acceptable-bundles:1234567890"
184188
)
185189
186190
for out in "${outs[@]}"; do
@@ -193,3 +197,13 @@ spec:
193197
cat "$(params.dataDir)/mock_ec.txt"
194198
exit 1
195199
fi
200+
201+
# Expect 3 skopeo copy commands (one after each ec track bundle)
202+
copy_cmd="copy --retry-times 3 docker://quay.io/notexist/data-acceptable-bundles:1234567890 \
203+
docker://quay.io/notexist/data-acceptable-bundles:latest"
204+
copy_count=$(grep -cF "$copy_cmd" "$(params.dataDir)/mock_skopeo.txt" || true)
205+
if [ "$copy_count" != 3 ]; then
206+
echo "Error: expected 3 skopeo copy commands, found $copy_count"
207+
cat "$(params.dataDir)/mock_skopeo.txt"
208+
exit 1
209+
fi

tasks/managed/update-trusted-tasks/tests/test-update-trusted-tasks-success-no-latest.yaml

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -158,8 +158,9 @@ spec:
158158
#!/bin/bash
159159
set -eux
160160
161-
if [ "$(wc -l < "$(params.dataDir)/mock_skopeo.txt")" != 1 ]; then
162-
echo Error: skopeo was expected to be called 1 times. Actual calls:
161+
# Expect 2 skopeo calls: 1 for inspect, 1 for copy to tag as :latest
162+
if [ "$(wc -l < "$(params.dataDir)/mock_skopeo.txt")" != 2 ]; then
163+
echo Error: skopeo was expected to be called 2 times. Actual calls:
163164
cat "$(params.dataDir)/mock_skopeo.txt"
164165
exit 1
165166
fi
@@ -170,11 +171,21 @@ spec:
170171
exit 1
171172
fi
172173
174+
# Expect ec track bundle to output to timestamp tag (1234567890 from mocked date)
173175
out="track bundle --bundle quay.io/notexist/task-echo:0.1@sha256:abcde \
174-
--output oci:quay.io/notexist/data-acceptable-bundles:latest"
176+
--output oci:quay.io/notexist/data-acceptable-bundles:1234567890"
175177
176178
if ! grep -qF -- "$out" "$(params.dataDir)/mock_ec.txt"; then
177179
echo "Error: $out was not found in the ec command"
178180
cat "$(params.dataDir)/mock_ec.txt"
179181
exit 1
180182
fi
183+
184+
# Expect skopeo copy to tag the timestamp image as :latest
185+
copy_cmd="copy --retry-times 3 docker://quay.io/notexist/data-acceptable-bundles:1234567890 \
186+
docker://quay.io/notexist/data-acceptable-bundles:latest"
187+
if ! grep -qF -- "$copy_cmd" "$(params.dataDir)/mock_skopeo.txt"; then
188+
echo "Error: $copy_cmd was not found in the skopeo commands"
189+
cat "$(params.dataDir)/mock_skopeo.txt"
190+
exit 1
191+
fi

tasks/managed/update-trusted-tasks/update-trusted-tasks.yaml

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -127,14 +127,19 @@ spec:
127127
set -eux
128128
129129
SNAPSHOT_SPEC_FILE="$(params.dataDir)/$(params.snapshotPath)"
130-
TAG="latest"
131130
132131
# check if snapshot exsits
133132
if [ ! -f "${SNAPSHOT_SPEC_FILE}" ] ; then
134133
echo "No valid snapshot file was found."
135134
exit 1
136135
fi
137136
137+
# The OPA data bundle is tagged with the current timestamp. This has two main
138+
# advantages. First, it prevents the image from accidentally not having any tags,
139+
# and getting garbage collected. Second, it helps us create a timeline of the
140+
# changes done to the data over time.
141+
TIMESTAMP_TAG=$(date '+%s')
142+
138143
# Extract the componentGroup from the snapshot
139144
componentGroup=$(jq -r '.componentGroup' "${SNAPSHOT_SPEC_FILE}")
140145
@@ -165,6 +170,21 @@ spec:
165170
ACCEPTABLE_BUNDLES=$(echo "${repository}" \
166171
| awk -F'/' '{$NF="data-acceptable-bundles"; print $0}' OFS='/')
167172
173+
# Check once if :latest exists for this ACCEPTABLE_BUNDLES repo
174+
# Use inspect instead of list-tags for efficiency as tag count grows
175+
set +e
176+
skopeo inspect --retry-times 3 --raw --no-tags docker://"${ACCEPTABLE_BUNDLES}:latest" &> /dev/null
177+
RESULT=$?
178+
set -e
179+
180+
if [ ${RESULT} -eq 0 ]; then
181+
echo "${ACCEPTABLE_BUNDLES}:latest exists - using it as an input"
182+
LATEST_EXISTS="true"
183+
else
184+
echo "${ACCEPTABLE_BUNDLES}:latest does not exist"
185+
LATEST_EXISTS="false"
186+
fi
187+
168188
# Get the number of tags from the repository
169189
NUM_TAGS=$(jq --argjson j "$j" '.repositories[$j].tags | length' <<< "$component")
170190
@@ -173,24 +193,25 @@ spec:
173193
do
174194
imageTag=$(jq -c -r --argjson j "$j" --argjson k "$k" '.repositories[$j].tags[$k]' <<< "$component")
175195
176-
set +e
177-
# Check if ACCEPTABLE_BUNDLES OCI artifact has a latest tag
178-
skopeo list-tags docker://"${ACCEPTABLE_BUNDLES}" | jq -r '.Tags[]' | grep "^${TAG}$" &> /dev/null
179-
RESULT=$?
180-
set -e
181-
182-
# If it has a latest tag, use it as an --input for the ec track bundle command
183-
if [ $RESULT -eq 0 ]; then
184-
echo "${ACCEPTABLE_BUNDLES}:${TAG} exists - using it as an input"
196+
# If :latest exists, use it as an --input for the ec track bundle command
197+
# Output to the timestamp tag to prevent garbage collection
198+
if [[ "${LATEST_EXISTS}" == "true" ]]; then
199+
echo "Adding ${repository}:${imageTag}${sha} to ${ACCEPTABLE_BUNDLES}:${TIMESTAMP_TAG}"
185200
ec track bundle --bundle "${repository}:${imageTag}${sha}" \
186-
--input oci:"${ACCEPTABLE_BUNDLES}:${TAG}" --output oci:"${ACCEPTABLE_BUNDLES}:${TAG}"
187-
188-
# Else - Do not use it as an --input
201+
--input oci:"${ACCEPTABLE_BUNDLES}:latest" --output oci:"${ACCEPTABLE_BUNDLES}:${TIMESTAMP_TAG}"
189202
else
190-
echo "${ACCEPTABLE_BUNDLES}:${TAG} does not exist"
203+
echo "Creating ${ACCEPTABLE_BUNDLES}:${TIMESTAMP_TAG} with ${repository}:${imageTag}${sha}"
191204
ec track bundle --bundle "${repository}:${imageTag}${sha}" \
192-
--output oci:"${ACCEPTABLE_BUNDLES}:${TAG}"
205+
--output oci:"${ACCEPTABLE_BUNDLES}:${TIMESTAMP_TAG}"
193206
fi
207+
208+
# Tag the timestamp-based image as :latest for the next iteration
209+
echo "Tagging ${ACCEPTABLE_BUNDLES}:${TIMESTAMP_TAG} as :latest"
210+
skopeo copy --retry-times 3 "docker://${ACCEPTABLE_BUNDLES}:${TIMESTAMP_TAG}" \
211+
"docker://${ACCEPTABLE_BUNDLES}:latest"
212+
213+
# Now that we've created :latest, mark it as existing for subsequent tags
214+
LATEST_EXISTS="true"
194215
done
195216
done
196217
done

0 commit comments

Comments
 (0)