Skip to content

Commit d1e064a

Browse files
thozzaondrejbudai
authored andcommitted
koji_test.py: test upload to cloud with AWS
Extend the integration test with a new case, testing that direct upload to the cloud works for Koji composes. Test this using a single cloud provider, specifically AWS. The test case submits a new osbuild-image build using Koji CLI, determines the image information once the build finishes and then checks that such image exists in AWS. The image is then deleted as part of the test case tear-down. The AWS credentials are now configured in the worker's configuration, if the appropriate environment variables are set. Update the SPEC file with a new test dependency and update the required osbuild-composer version.
1 parent c76e97d commit d1e064a

File tree

6 files changed

+176
-7
lines changed

6 files changed

+176
-7
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717
steps:
1818

1919
- name: Install test dependencies
20-
run: dnf -y install python3-flexmock python3-httpretty python3-jsonschema python3-koji python3-pylint python3-requests
20+
run: dnf -y install python3-boto3 python3-flexmock python3-httpretty python3-jsonschema python3-koji python3-pylint python3-requests
2121

2222
- name: Check out code
2323
uses: actions/checkout@v3

koji-osbuild.spec

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,10 +143,11 @@ Requires: jq
143143
Requires: koji
144144
Requires: krb5-workstation
145145
Requires: openssl
146-
Requires: osbuild-composer >= 22
146+
Requires: osbuild-composer >= 58
147147
Requires: osbuild-composer-tests
148148
Requires: podman
149149
Requires: podman-plugins
150+
Requires: python3-boto3
150151

151152
%description tests
152153
Integration tests for koji-osbuild. To be run on a dedicated system.

test/copy-creds.sh

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,25 @@ cp ${TEST_DATA}/osbuild-worker.toml \
3333

3434
echo "koji" > /etc/osbuild-worker/oauth-secret
3535

36+
# if AWS credentials are defined in the ENV, add them to the worker's configuration
37+
# This is needed to test the upload to the cloud
38+
V2_AWS_ACCESS_KEY_ID="${V2_AWS_ACCESS_KEY_ID:-}"
39+
V2_AWS_SECRET_ACCESS_KEY="${V2_AWS_SECRET_ACCESS_KEY:-}"
40+
if [[ -n "$V2_AWS_ACCESS_KEY_ID" && -n "$V2_AWS_SECRET_ACCESS_KEY" ]]; then
41+
echo "Adding AWS credentials to the worker's configuration"
42+
sudo tee /etc/osbuild-worker/aws-credentials.toml > /dev/null << EOF
43+
[default]
44+
aws_access_key_id = "$V2_AWS_ACCESS_KEY_ID"
45+
aws_secret_access_key = "$V2_AWS_SECRET_ACCESS_KEY"
46+
EOF
47+
sudo tee -a /etc/osbuild-worker/osbuild-worker.toml > /dev/null << EOF
48+
49+
[aws]
50+
credentials = "/etc/osbuild-worker/aws-credentials.toml"
51+
bucket = "${AWS_BUCKET}"
52+
EOF
53+
fi
54+
3655
echo "Copying system kerberos configuration"
3756
cp ${TEST_DATA}/krb5.local.conf \
3857
/etc/krb5.conf.d/local

test/integration.sh

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ greenprint "Testing Koji hub API access"
3939
koji --server=http://localhost:8080/kojihub --user=osbuild --password=osbuildpass --authtype=password hello
4040

4141
greenprint "Copying credentials, certificates and configuration files"
42-
sudo /usr/libexec/koji-osbuild-tests/copy-creds.sh /usr/share/koji-osbuild-tests
42+
sudo -E /usr/libexec/koji-osbuild-tests/copy-creds.sh /usr/share/koji-osbuild-tests
4343

4444
greenprint "Starting mock OpenID server"
4545
sudo /usr/libexec/koji-osbuild-tests/run-openid.sh start
@@ -63,6 +63,9 @@ greenprint "Creating Koji tag infrastructure"
6363
/usr/libexec/koji-osbuild-tests/make-tags.sh
6464

6565
greenprint "Running integration tests"
66+
# export environment variables for the Boto3 client to work out of the box
67+
AWS_ACCESS_KEY_ID="${V2_AWS_ACCESS_KEY_ID:-}" \
68+
AWS_SECRET_ACCESS_KEY="${V2_AWS_SECRET_ACCESS_KEY:-}" \
6669
python3 -m unittest discover -v /usr/libexec/koji-osbuild-tests/integration/
6770

6871
greenprint "Stopping koji builder"

test/integration/test_koji.py

Lines changed: 148 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,38 @@
44

55

66
import functools
7+
import json
8+
import logging
9+
import os
710
import platform
8-
import unittest
11+
import re
12+
import shutil
913
import string
1014
import subprocess
15+
import tempfile
16+
import unittest
17+
18+
import boto3
19+
from botocore.config import Config as BotoConfig
20+
from botocore.exceptions import ClientError as BotoClientError
21+
22+
23+
logger = logging.getLogger(__name__)
24+
logging.basicConfig(format = '%(asctime)s %(levelname)s: %(message)s', level = logging.INFO)
1125

1226

1327
def koji_command(*args, _input=None, _globals=None, **kwargs):
28+
return koji_command_cwd(*args, _input=_input, _globals=_globals, **kwargs)
29+
30+
31+
def koji_command_cwd(*args, cwd=None, _input=None, _globals=None, **kwargs):
1432
args = list(args) + [f'--{k}={v}' for k, v in kwargs.items()]
1533
if _globals:
1634
args = [f'--{k}={v}' for k, v in _globals.items()] + args
1735
cmd = ["koji"] + args
18-
print(cmd)
36+
logger.info("Running %s", str(cmd))
1937
return subprocess.run(cmd,
38+
cwd=cwd,
2039
encoding="utf-8",
2140
stdout=subprocess.PIPE,
2241
stderr=subprocess.STDOUT,
@@ -92,16 +111,28 @@ def testing_repos(self):
92111

93112

94113
class TestIntegration(unittest.TestCase):
114+
logger = logging.getLogger(__name__)
95115

96116
def setUp(self):
97-
global_args = dict(
117+
self.koji_global_args = dict(
98118
server="http://localhost:8080/kojihub",
119+
topurl="http://localhost:8080/kojifiles",
99120
user="kojiadmin",
100121
password="kojipass",
101122
authtype="password")
102123
self.koji = functools.partial(koji_command,
103124
"osbuild-image",
104-
_globals=global_args)
125+
_globals=self.koji_global_args)
126+
127+
self.workdir = tempfile.mkdtemp()
128+
# EC2 image ID to clean up in tearDown() if set to a value
129+
self.ec2_image_id = None
130+
131+
def tearDown(self):
132+
shutil.rmtree(self.workdir)
133+
if self.ec2_image_id is not None:
134+
self.delete_ec2_image(self.ec2_image_id)
135+
self.ec2_image_id = None
105136

106137
def check_res(self, res: subprocess.CompletedProcess):
107138
if res.returncode != 0:
@@ -117,6 +148,55 @@ def check_fail(self, res: subprocess.CompletedProcess):
117148
"\n error: " + res.stdout)
118149
self.fail(msg)
119150

151+
def task_id_from_res(self, res: subprocess.CompletedProcess) -> str:
152+
"""
153+
Extract the Task ID from `koji osbuild-image` command output and return it.
154+
"""
155+
r = re.compile(r'^Created task:[ \t]+(\d+)$', re.MULTILINE)
156+
m = r.search(res.stdout)
157+
if not m:
158+
self.fail("Could not find task id in output")
159+
return m.group(1)
160+
161+
@staticmethod
162+
def get_ec2_client():
163+
aws_region = os.getenv("AWS_REGION")
164+
return boto3.client('ec2', config=BotoConfig(region_name=aws_region))
165+
166+
def check_ec2_image_exists(self, image_id: str) -> None:
167+
"""
168+
Check if an EC2 image with the given ID exists.
169+
If not, fail the test case.
170+
"""
171+
client = self.get_ec2_client()
172+
try:
173+
resp = client.describe_images(ImageIds=[image_id])
174+
except BotoClientError as e:
175+
self.fail(str(e))
176+
self.assertEqual(len(resp["Images"]), 1)
177+
178+
def delete_ec2_image(self, image_id: str) -> None:
179+
client = self.get_ec2_client()
180+
# first get the snapshot ID associated with the image
181+
try:
182+
resp = client.describe_images(ImageIds=[image_id])
183+
except BotoClientError as e:
184+
self.fail(str(e))
185+
self.assertEqual(len(resp["Images"]), 1)
186+
187+
snapshot_id = resp["Images"][0]["BlockDeviceMappings"][0]["Ebs"]["SnapshotId"]
188+
# deregister the image
189+
try:
190+
resp = client.deregister_image(ImageId=image_id)
191+
except BotoClientError as e:
192+
self.logger.warning("Failed to deregister image %s: %s", image_id, str(e))
193+
194+
# delete the associated snapshot
195+
try:
196+
resp = client.delete_snapshot(SnapshotId=snapshot_id)
197+
except BotoClientError as e:
198+
self.logger.warning("Failed to delete snapshot %s: %s", snapshot_id, str(e))
199+
120200
def test_compose(self):
121201
"""Successful compose"""
122202
# Simple test of a successful compose of RHEL
@@ -155,3 +235,67 @@ def test_unknown_tag_check(self):
155235
"UNKNOWNTAG",
156236
sut_info.os_arch)
157237
self.check_fail(res)
238+
239+
def test_cloud_upload_aws(self):
240+
"""Successful compose with cloud upload to AWS"""
241+
sut_info = SutInfo()
242+
243+
repos = []
244+
for repo in sut_info.testing_repos():
245+
url = repo["url"]
246+
package_sets = repo.get("package_sets")
247+
repos += ["--repo", url]
248+
if package_sets:
249+
repos += ["--repo-package-sets", package_sets]
250+
251+
package = "aws"
252+
aws_region = os.getenv("AWS_REGION")
253+
254+
upload_options = {
255+
"region": aws_region,
256+
"share_with_accounts": [os.getenv("AWS_API_TEST_SHARE_ACCOUNT")]
257+
}
258+
259+
upload_options_file = os.path.join(self.workdir, "upload_options.json")
260+
with open(upload_options_file, "w", encoding="utf-8") as f:
261+
json.dump(upload_options, f)
262+
263+
res = self.koji(package,
264+
sut_info.os_version_major,
265+
sut_info.composer_distro_name,
266+
sut_info.koji_tag,
267+
sut_info.os_arch,
268+
"--wait",
269+
*repos,
270+
f"--image-type={package}",
271+
f"--upload-options={upload_options_file}")
272+
self.check_res(res)
273+
274+
task_id = self.task_id_from_res(res)
275+
# Download files uploaded by osbuild plugins to the Koji build task.
276+
# requires koji client of version >= 1.29.1
277+
res_download = koji_command_cwd(
278+
"download-task", "--all", task_id, cwd=self.workdir, _globals=self.koji_global_args
279+
)
280+
self.check_res(res_download)
281+
282+
# Extract information about the uploaded AMI from compose status response.
283+
compose_status_file = os.path.join(self.workdir, "compose-status.noarch.json")
284+
with open(compose_status_file, "r", encoding="utf-8") as f:
285+
compose_status = json.load(f)
286+
287+
self.assertEqual(compose_status["status"], "success")
288+
image_statuses = compose_status["image_statuses"]
289+
self.assertEqual(len(image_statuses), 1)
290+
291+
upload_status = image_statuses[0]["upload_status"]
292+
self.assertEqual(upload_status["status"], "success")
293+
self.assertEqual(upload_status["type"], "aws")
294+
295+
upload_options = upload_status["options"]
296+
self.assertEqual(upload_options["region"], aws_region)
297+
298+
image_id = upload_options["ami"]
299+
self.assertNotEqual(len(image_id), 0)
300+
self.ec2_image_id = image_id
301+
self.check_ec2_image_exists(image_id)

test/make-tags.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,6 @@ $KOJI add-pkg --owner kojiadmin "${TAG_CANDIDATE}" rhel-guest
2626

2727
$KOJI add-pkg --owner kojiadmin "${TAG_CANDIDATE}" fedora-iot
2828

29+
$KOJI add-pkg --owner kojiadmin "${TAG_CANDIDATE}" aws
30+
2931
$KOJI regen-repo "${TAG_BUILD}"

0 commit comments

Comments
 (0)