Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tasks: Add integration test for image-refresh via issue-scan #588

Merged
merged 2 commits into from
Mar 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ jobs:
- name: Check which containers changed
id: containers_changed
run: |
tasks=$(git diff --name-only origin/main..HEAD -- tasks/ | grep -Ev 'run-local.sh|openssl.cnf|README|mock-github-pr|.yaml' || true)
tasks=$(git diff --name-only origin/main..HEAD -- tasks/ | grep -Ev 'run-local.sh|openssl.cnf|README|mock-github|.yaml' || true)
# print for debugging
echo "tasks: $tasks"
[ -z "$tasks" ] || echo "tasks=true" >> "$GITHUB_OUTPUT"
Expand Down
57 changes: 52 additions & 5 deletions tasks/mock-github-pr → tasks/mock-github
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
#!/usr/bin/env python3
# Mock GitHub API server for testing an opened PR
# Mock GitHub API server for testing an opened PR or an issue for an image-refresh
# You can run this manually in `tasks/run-local.sh -i` with `podman cp` and running
# cd bots
# PYTHONPATH=. ./mock-github-pr cockpit-project/bots $(git rev-parse HEAD) &
# PYTHONPATH=. ./mock-github cockpit-project/bots $(git rev-parse HEAD) &
# export GITHUB_API=http://127.0.0.7:8443
# PYTHONPATH=. ./mock-github-pr --print-event cockpit-project/bots $(git rev-parse HEAD) | \
# PYTHONPATH=. ./mock-github --print-pr-event cockpit-project/bots $(git rev-parse HEAD) | \
# ./publish-queue --amqp localhost:5671 --queue webhook
#
# and then two `./run-queue --amqp localhost:5671`
Expand All @@ -27,6 +27,12 @@ class Handler(MockHandler):
self.replyJson(self.server.data[self.path])
elif self.path.startswith(f'/repos/{repo}/pulls?'):
self.replyJson([self.server.data[f'/repos/{repo}/pulls/1']])
elif self.path == f'/repos/{repo}/pulls/2':
# image-refresh issue converted into PR
self.replyJson({
**self.server.data[f'/repos/{repo}/issues/2'],
"head": {"sha": "a1b2c3"},
})
elif self.path == f'/{repo}/{sha}/.cockpit-ci/container':
self.replyData('quay.io/cockpit/tasks')
else:
Expand All @@ -35,13 +41,28 @@ class Handler(MockHandler):
def do_POST(self):
if self.path.startswith(f'/repos/{repo}/statuses/{sha}'):
self.replyJson({})
# new SHA from mock-pushed PR #2 for image-refresh
elif self.path.startswith(f'/repos/{repo}/statuses/a1b2c3'):
self.replyJson({})
elif self.path.startswith(f'/repos/{repo}/issues/2'):
# updates the issue to "in progress", sets label, adds comment etc.; maybe keep state and assert?
self.replyJson({})
elif self.path == f'/repos/{repo}/pulls':
# image-refresh creates a PR for a refresh isssue
self.replyJson({
**GITHUB_DATA[f'/repos/{repo}/issues/2'],
"head": {"sha": "987654"},
})
else:
self.send_error(405, 'Method not allowed: ' + self.path)


argparser = argparse.ArgumentParser()
argparser.add_argument('--port', type=int, default=8443, help="Port to listen on (default: %(default)s)")
argparser.add_argument('--print-event', action='store_true', help="Print GitHub webhook pull_request event and exit")
argparser.add_argument('--print-pr-event', action='store_true',
help="Print GitHub webhook pull_request event and exit")
argparser.add_argument('--print-image-refresh-event', action='store_true',
help="Print GitHub webhook issue event for an image-refresh and exit")
argparser.add_argument('repo', metavar='USER/PROJECT', help="GitHub user/org and project name")
argparser.add_argument('sha', help="SHA to test in repo for the mock PR")
args = argparser.parse_args()
Expand All @@ -51,6 +72,9 @@ sha = args.sha
ADDRESS = ('127.0.0.7', args.port)

GITHUB_DATA = {
f'/repos/{repo}': {
"default_branch": "main",
},
f'/repos/{repo}/pulls/1': {
'title': 'mock PR',
'number': 1,
Expand All @@ -66,9 +90,21 @@ GITHUB_DATA = {
'statuses': [],
'sha': sha,
},
f'/repos/{repo}/issues/2': {
'title': 'Refresh foonux image',
'number': 2,
'body': "blabla\n - [ ] image-refresh foonux\n",
# is in our allowlist
'user': {"login": "cockpit-project"},
'labels': [{"name": "bot"}],
'url': f'http://{ADDRESS[0]}/{repo}/issues/2',
},
f'/repos/{repo}/git/ref/heads/main': {
'object': {'sha': sha},
},
}

if args.print_event:
if args.print_pr_event:
print(json.dumps({
'event': 'pull_request',
'request': {
Expand All @@ -78,6 +114,17 @@ if args.print_event:
}, indent=4))
exit(0)

if args.print_image_refresh_event:
print(json.dumps({
'event': 'issues',
'request': {
'action': 'opened',
'issue': GITHUB_DATA[f'/repos/{repo}/issues/2'],
'repository': {'full_name': repo},
}
}, indent=4))
exit(0)

temp = tempfile.TemporaryDirectory()
cache_dir = os.path.join(temp.name, 'cache')
os.environ['XDG_CACHE_HOME'] = cache_dir
Expand Down
80 changes: 77 additions & 3 deletions tasks/run-local.sh
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ EOF

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

Expand Down Expand Up @@ -246,20 +248,20 @@ test_image() {
}

test_mock_pr() {
podman cp "$MYDIR/mock-github-pr" cockpituous-tasks:/work/bots/mock-github-pr
podman cp "$MYDIR/mock-github" cockpituous-tasks:/work/bots/mock-github
podman exec -i cockpituous-tasks sh -euxc "
cd bots
# test mock PR against our checkout, so that cloning will work
SHA=\$(git rev-parse HEAD)

# start mock GH server
PYTHONPATH=. ./mock-github-pr cockpit-project/bots \$SHA &
PYTHONPATH=. ./mock-github cockpit-project/bots \$SHA &
GH_MOCK_PID=\$!
export GITHUB_API=http://127.0.0.7:8443
until curl --silent \$GITHUB_API; do sleep 0.1; done

# simulate GitHub webhook event, put that into the webhook queue
PYTHONPATH=. ./mock-github-pr --print-event cockpit-project/bots \$SHA | \
PYTHONPATH=. ./mock-github --print-pr-event cockpit-project/bots \$SHA | \
./publish-queue --amqp $AMQP_POD --create --queue webhook

./inspect-queue --amqp $AMQP_POD
Expand Down Expand Up @@ -340,6 +342,76 @@ test_pr() {
fi
}

test_mock_image_refresh() {
podman cp "$MYDIR/mock-github" cockpituous-tasks:/work/bots/mock-github

# the last step of an image refresh is to push the branch back to origin; we can't nor
# want to do that here, so divert "git push" to a log file and check that
cat <<EOF | podman exec -i -u root cockpituous-tasks sh -euxc "cat > /usr/local/bin/git; chmod +x /usr/local/bin/git"
#!/bin/sh
if [ "\$1" = push ]; then
echo "\$@" >> /work/git-push.log
exit 0
fi
exec /usr/bin/git "\$@"
EOF

podman exec -i cockpituous-tasks sh -euxc "
cd bots
# start mock GH server; use a mock SHA, only used for posting statuses
SHA=123abc
PYTHONPATH=. ./mock-github cockpit-project/bots \$SHA &
GH_MOCK_PID=\$!
export GITHUB_API=http://127.0.0.7:8443
until curl --silent \$GITHUB_API; do sleep 0.1; done

# simulate GitHub webhook event, put that into the webhook queue
PYTHONPATH=. ./mock-github --print-image-refresh-event cockpit-project/bots \$SHA | \
./publish-queue --amqp $AMQP_POD --create --queue webhook

./inspect-queue --amqp $AMQP_POD

# first run-queue processes webhook → issue-scan → public queue
./run-queue --amqp $AMQP_POD
./inspect-queue --amqp $AMQP_POD

# second run-queue actually runs the image refresh
./run-queue --amqp $AMQP_POD

kill \$GH_MOCK_PID
"

# successful refresh log
LOGS_URL="$S3_URL_HOST/logs/"
CURL="curl --cacert $SECRETS/ca.pem --silent --fail --show-error"
LOG_MATCH="$($CURL $LOGS_URL| grep -o "image-refresh-foonux-[[:alnum:]-]*/log<")"
LOG="$($CURL "${LOGS_URL}${LOG_MATCH%<}")"
echo "--------------- mock image-refresh test log -----------------"
echo "$LOG"
echo "--------------- mock image-refresh test log end -------------"
assert_in 'Running on:.*cockpituous' "$LOG"
assert_in './image-create.*foonux' "$LOG"
assert_in "Uploading to $S3_URL_POD/images/foonux.*qcow2" "$LOG"
assert_in 'Success.' "$LOG"

# branch was (mock) pushed
PUSH_LOG="$(podman exec -i cockpituous-tasks cat /work/git-push.log)"
assert_in 'push origin +HEAD:refs/heads/image-refresh-foonux-' "$PUSH_LOG"
podman exec -i cockpituous-tasks rm /work/git-push.log
podman exec -i -u root cockpituous-tasks rm /usr/local/bin/git

podman exec -i cockpituous-tasks sh -euxc '
# validate image contents
qemu-img convert /cache/images/foonux-*.qcow2 /tmp/foonux.raw
grep "^fakeimage" /tmp/foonux.raw
rm /tmp/foonux.raw

# image is on the S3 server
cd bots
python3 -m lib.s3 ls '$S3_URL_POD'/images/ | grep "foonux.*qcow"
'
}

test_queue() {
# tasks can connect to queue
OUT=$(podman exec -i cockpituous-tasks bots/inspect-queue --amqp $AMQP_POD)
Expand Down Expand Up @@ -376,6 +448,8 @@ else
test_podman
# "almost" end-to-end, starting with GitHub webhook JSON payload injection; fully localy, no privs
test_mock_pr
# similar structure for issue-scan for an image refresh
test_mock_image_refresh
# if we have a PR number, run a unit test inside local deployment, and update PR status
[ -z "$PR" ] || test_pr
fi
Expand Down