Skip to content

Commit a1a1e5a

Browse files
committed
tasks: Add integration test for image-refresh via issue-scan
This covers the real thing in between getting the "issue" webhook data and uploading the image to the S3 server, and hence `issue-scan`, `image-refresh`, `image-create`, S3 image credentials, etc. Extend mock-github for the operations that `image-refresh` does, like converting an issue to a pull, posting test statuses, etc. These aren't asserted: it's possible in principle with keeping state in the mock, but these aren't the parts that are sensitive to us changing our bots infrastructure. Also, these are better done in bots' unittests than this integration test.
1 parent f551d01 commit a1a1e5a

File tree

2 files changed

+121
-1
lines changed

2 files changed

+121
-1
lines changed

tasks/mock-github

+47-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#!/usr/bin/env python3
2-
# Mock GitHub API server for testing an opened PR
2+
# Mock GitHub API server for testing an opened PR or an issue for an image-refresh
33
# You can run this manually in `tasks/run-local.sh -i` with `podman cp` and running
44
# cd bots
55
# PYTHONPATH=. ./mock-github cockpit-project/bots $(git rev-parse HEAD) &
@@ -27,6 +27,12 @@ class Handler(MockHandler):
2727
self.replyJson(self.server.data[self.path])
2828
elif self.path.startswith(f'/repos/{repo}/pulls?'):
2929
self.replyJson([self.server.data[f'/repos/{repo}/pulls/1']])
30+
elif self.path == f'/repos/{repo}/pulls/2':
31+
# image-refresh issue converted into PR
32+
self.replyJson({
33+
**self.server.data[f'/repos/{repo}/issues/2'],
34+
"head": {"sha": "a1b2c3"},
35+
})
3036
elif self.path == f'/{repo}/{sha}/.cockpit-ci/container':
3137
self.replyData('quay.io/cockpit/tasks')
3238
else:
@@ -35,6 +41,18 @@ class Handler(MockHandler):
3541
def do_POST(self):
3642
if self.path.startswith(f'/repos/{repo}/statuses/{sha}'):
3743
self.replyJson({})
44+
# new SHA from mock-pushed PR #2 for image-refresh
45+
elif self.path.startswith(f'/repos/{repo}/statuses/a1b2c3'):
46+
self.replyJson({})
47+
elif self.path.startswith(f'/repos/{repo}/issues/2'):
48+
# updates the issue to "in progress", sets label, adds comment etc.; maybe keep state and assert?
49+
self.replyJson({})
50+
elif self.path == f'/repos/{repo}/pulls':
51+
# image-refresh creates a PR for a refresh isssue
52+
self.replyJson({
53+
**GITHUB_DATA[f'/repos/{repo}/issues/2'],
54+
"head": {"sha": "987654"},
55+
})
3856
else:
3957
self.send_error(405, 'Method not allowed: ' + self.path)
4058

@@ -43,6 +61,8 @@ argparser = argparse.ArgumentParser()
4361
argparser.add_argument('--port', type=int, default=8443, help="Port to listen on (default: %(default)s)")
4462
argparser.add_argument('--print-pr-event', action='store_true',
4563
help="Print GitHub webhook pull_request event and exit")
64+
argparser.add_argument('--print-image-refresh-event', action='store_true',
65+
help="Print GitHub webhook issue event for an image-refresh and exit")
4666
argparser.add_argument('repo', metavar='USER/PROJECT', help="GitHub user/org and project name")
4767
argparser.add_argument('sha', help="SHA to test in repo for the mock PR")
4868
args = argparser.parse_args()
@@ -52,6 +72,9 @@ sha = args.sha
5272
ADDRESS = ('127.0.0.7', args.port)
5373

5474
GITHUB_DATA = {
75+
f'/repos/{repo}': {
76+
"default_branch": "main",
77+
},
5578
f'/repos/{repo}/pulls/1': {
5679
'title': 'mock PR',
5780
'number': 1,
@@ -67,6 +90,18 @@ GITHUB_DATA = {
6790
'statuses': [],
6891
'sha': sha,
6992
},
93+
f'/repos/{repo}/issues/2': {
94+
'title': 'Refresh foonux image',
95+
'number': 2,
96+
'body': "blabla\n - [ ] image-refresh foonux\n",
97+
# is in our allowlist
98+
'user': {"login": "cockpit-project"},
99+
'labels': [{"name": "bot"}],
100+
'url': f'http://{ADDRESS[0]}/{repo}/issues/2',
101+
},
102+
f'/repos/{repo}/git/ref/heads/main': {
103+
'object': {'sha': sha},
104+
},
70105
}
71106

72107
if args.print_pr_event:
@@ -79,6 +114,17 @@ if args.print_pr_event:
79114
}, indent=4))
80115
exit(0)
81116

117+
if args.print_image_refresh_event:
118+
print(json.dumps({
119+
'event': 'issues',
120+
'request': {
121+
'action': 'opened',
122+
'issue': GITHUB_DATA[f'/repos/{repo}/issues/2'],
123+
'repository': {'full_name': repo},
124+
}
125+
}, indent=4))
126+
exit(0)
127+
82128
temp = tempfile.TemporaryDirectory()
83129
cache_dir = os.path.join(temp.name, 'cache')
84130
os.environ['XDG_CACHE_HOME'] = cache_dir

tasks/run-local.sh

+74
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ EOF
176176

177177
# Run tasks container in the background
178178
# use bash as pid 1 to mop up zombies
179+
# we always want to upload images to our local S3 store
179180
podman run -d -it --name cockpituous-tasks --pod=cockpituous \
180181
--security-opt=label=disable \
181182
-v "$SECRETS"/tasks:/run/secrets/tasks:ro \
@@ -189,6 +190,7 @@ EOF
189190
--env=AMQP_SERVER=$AMQP_POD \
190191
--env=S3_LOGS_URL=$S3_URL_POD/logs/ \
191192
--env=COCKPIT_S3_KEY_DIR=/run/secrets/tasks/s3-keys \
193+
--env=COCKPIT_IMAGE_UPLOAD_STORE=$S3_URL_POD/images/ \
192194
--env=SKIP_STATIC_CHECK=1 \
193195
quay.io/cockpit/tasks:${TASKS_TAG:-latest} bash
194196

@@ -340,6 +342,76 @@ test_pr() {
340342
fi
341343
}
342344

345+
test_mock_image_refresh() {
346+
podman cp "$MYDIR/mock-github" cockpituous-tasks:/work/bots/mock-github
347+
348+
# the last step of an image refresh is to push the branch back to origin; we can't nor
349+
# want to do that here, so divert "git push" to a log file and check that
350+
cat <<EOF | podman exec -i -u root cockpituous-tasks sh -euxc "cat > /usr/local/bin/git; chmod +x /usr/local/bin/git"
351+
#!/bin/sh
352+
if [ "\$1" = push ]; then
353+
echo "\$@" >> /work/git-push.log
354+
exit 0
355+
fi
356+
exec /usr/bin/git "\$@"
357+
EOF
358+
359+
podman exec -i cockpituous-tasks sh -euxc "
360+
cd bots
361+
# start mock GH server; use a mock SHA, only used for posting statuses
362+
SHA=123abc
363+
PYTHONPATH=. ./mock-github cockpit-project/bots \$SHA &
364+
GH_MOCK_PID=\$!
365+
export GITHUB_API=http://127.0.0.7:8443
366+
until curl --silent \$GITHUB_API; do sleep 0.1; done
367+
368+
# simulate GitHub webhook event, put that into the webhook queue
369+
PYTHONPATH=. ./mock-github --print-image-refresh-event cockpit-project/bots \$SHA | \
370+
./publish-queue --amqp $AMQP_POD --create --queue webhook
371+
372+
./inspect-queue --amqp $AMQP_POD
373+
374+
# first run-queue processes webhook → issue-scan → public queue
375+
./run-queue --amqp $AMQP_POD
376+
./inspect-queue --amqp $AMQP_POD
377+
378+
# second run-queue actually runs the image refresh
379+
./run-queue --amqp $AMQP_POD
380+
381+
kill \$GH_MOCK_PID
382+
"
383+
384+
# successful refresh log
385+
LOGS_URL="$S3_URL_HOST/logs/"
386+
CURL="curl --cacert $SECRETS/ca.pem --silent --fail --show-error"
387+
LOG_MATCH="$($CURL $LOGS_URL| grep -o "image-refresh-foonux-[[:alnum:]-]*/log<")"
388+
LOG="$($CURL "${LOGS_URL}${LOG_MATCH%<}")"
389+
echo "--------------- mock image-refresh test log -----------------"
390+
echo "$LOG"
391+
echo "--------------- mock image-refresh test log end -------------"
392+
assert_in 'Running on:.*cockpituous' "$LOG"
393+
assert_in './image-create.*foonux' "$LOG"
394+
assert_in "Uploading to $S3_URL_POD/images/foonux.*qcow2" "$LOG"
395+
assert_in 'Success.' "$LOG"
396+
397+
# branch was (mock) pushed
398+
PUSH_LOG="$(podman exec -i cockpituous-tasks cat /work/git-push.log)"
399+
assert_in 'push origin +HEAD:refs/heads/image-refresh-foonux-' "$PUSH_LOG"
400+
podman exec -i cockpituous-tasks rm /work/git-push.log
401+
podman exec -i -u root cockpituous-tasks rm /usr/local/bin/git
402+
403+
podman exec -i cockpituous-tasks sh -euxc '
404+
# validate image contents
405+
qemu-img convert /cache/images/foonux-*.qcow2 /tmp/foonux.raw
406+
grep "^fakeimage" /tmp/foonux.raw
407+
rm /tmp/foonux.raw
408+
409+
# image is on the S3 server
410+
cd bots
411+
python3 -m lib.s3 ls '$S3_URL_POD'/images/ | grep "foonux.*qcow"
412+
'
413+
}
414+
343415
test_queue() {
344416
# tasks can connect to queue
345417
OUT=$(podman exec -i cockpituous-tasks bots/inspect-queue --amqp $AMQP_POD)
@@ -376,6 +448,8 @@ else
376448
test_podman
377449
# "almost" end-to-end, starting with GitHub webhook JSON payload injection; fully localy, no privs
378450
test_mock_pr
451+
# similar structure for issue-scan for an image refresh
452+
test_mock_image_refresh
379453
# if we have a PR number, run a unit test inside local deployment, and update PR status
380454
[ -z "$PR" ] || test_pr
381455
fi

0 commit comments

Comments
 (0)