osbuild: support specifying upload_options
Since version 9, the `koji-osbuild` plugin supports specifying upload options as part of a Koji build. This enables one to upload the built image directly to the cloud environment as part of the image build in Koji. Extend the configuration schema with `upload_options`. Extend the documentation and describe valid `upload_options` values. Add a unit test testing a scenario when `upload_options` are specified. Signed-off-by: Tomas Hozza <thozza@redhat.com>
This commit is contained in:
parent
805a1083a2
commit
57739c238f
@ -1617,6 +1617,45 @@ OSBuild Composer for building images
|
||||
* ``ostree_ref`` -- name of the ostree branch
|
||||
* ``ostree_parent`` -- commit hash or a a branch-like reference to the
|
||||
parent commit.
|
||||
* ``upload_options`` -- a dictionary with upload options specific to the
|
||||
target cloud environment. If provided, the image will be uploaded to the
|
||||
cloud environment, in addition to the Koji server. One can't combine
|
||||
arbitrary image types with arbitrary upload options.
|
||||
The dictionary keys differ based on the target cloud environment. The
|
||||
following keys are supported:
|
||||
|
||||
* **AWS EC2 upload options** -- upload to Amazon Web Services.
|
||||
|
||||
* ``region`` -- AWS region to upload the image to
|
||||
* ``share_with_accounts`` -- list of AWS account IDs to share the image
|
||||
with
|
||||
* ``snapshot_name`` -- Snapshot name of the uploaded EC2 image
|
||||
(optional)
|
||||
|
||||
* **AWS S3 upload options** -- upload to Amazon Web Services S3.
|
||||
|
||||
* ``region`` -- AWS region to upload the image to
|
||||
|
||||
* **Azure upload options** -- upload to Microsoft Azure.
|
||||
|
||||
* ``tenant_id`` -- Azure tenant ID to upload the image to
|
||||
* ``subscription_id`` -- Azure subscription ID to upload the image to
|
||||
* ``resource_group`` -- Azure resource group to upload the image to
|
||||
* ``location`` -- Azure location to upload the image to
|
||||
* ``image_name`` -- Image name of the uploaded Azure image (optional)
|
||||
|
||||
* **GCP upload options** -- upload to Google Cloud Platform.
|
||||
|
||||
* ``region`` -- GCP region to upload the image to
|
||||
* ``bucket`` -- GCP bucket to upload the image to
|
||||
* ``share_with_accounts`` -- list of GCP accounts to share the image
|
||||
with
|
||||
* ``image_name`` -- Image name of the uploaded GCP image (optional)
|
||||
|
||||
* **Container upload options** -- upload to a container registry.
|
||||
|
||||
* ``name`` -- name of the container image (optional)
|
||||
* ``tag`` -- container tag to upload the image to (optional)
|
||||
|
||||
.. note::
|
||||
There is initial support for having this task as failable without aborting
|
||||
|
@ -1194,6 +1194,86 @@ def make_schema():
|
||||
"ostree_url": {"type": "string"},
|
||||
"ostree_ref": {"type": "string"},
|
||||
"ostree_parent": {"type": "string"},
|
||||
"upload_options": {
|
||||
"oneOf": [
|
||||
# AWSEC2UploadOptions
|
||||
{
|
||||
"type": "object",
|
||||
"additionalProperties": False,
|
||||
"required": [
|
||||
"region",
|
||||
"share_with_accounts",
|
||||
],
|
||||
"properties": {
|
||||
"region": {
|
||||
"type": "string",
|
||||
},
|
||||
"snapshot_name": {
|
||||
"type": "string",
|
||||
},
|
||||
"share_with_accounts": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
},
|
||||
},
|
||||
},
|
||||
# AWSS3UploadOptions
|
||||
{
|
||||
"type": "object",
|
||||
"additionalProperties": False,
|
||||
"required": ["region"],
|
||||
"properties": {
|
||||
"region": {"type": "string"}
|
||||
},
|
||||
},
|
||||
# AzureUploadOptions
|
||||
{
|
||||
"type": "object",
|
||||
"additionalProperties": False,
|
||||
"required": [
|
||||
"tenant_id",
|
||||
"subscription_id",
|
||||
"resource_group",
|
||||
"location",
|
||||
],
|
||||
"properties": {
|
||||
"tenant_id": {"type": "string"},
|
||||
"subscription_id": {"type": "string"},
|
||||
"resource_group": {"type": "string"},
|
||||
"location": {"type": "string"},
|
||||
"image_name": {
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
# GCPUploadOptions
|
||||
{
|
||||
"type": "object",
|
||||
"additionalProperties": False,
|
||||
"required": ["region", "bucket"],
|
||||
"properties": {
|
||||
"region": {"type": "string"},
|
||||
"bucket": {"type": "string"},
|
||||
"image_name": {
|
||||
"type": "string",
|
||||
},
|
||||
"share_with_accounts": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
},
|
||||
},
|
||||
},
|
||||
# ContainerUploadOptions
|
||||
{
|
||||
"type": "object",
|
||||
"additionalProperties": False,
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"tag": {"type": "string"},
|
||||
},
|
||||
},
|
||||
]
|
||||
},
|
||||
},
|
||||
"required": ["name", "distro", "image_types"],
|
||||
"additionalProperties": False,
|
||||
|
@ -126,6 +126,10 @@ class RunOSBuildThread(WorkerThread):
|
||||
if ostree:
|
||||
opts["ostree"] = ostree
|
||||
|
||||
upload_options = config.get("upload_options")
|
||||
if upload_options:
|
||||
opts["upload_options"] = upload_options
|
||||
|
||||
if release:
|
||||
opts["release"] = release
|
||||
task_id = koji.koji_proxy.osbuildImage(
|
||||
|
@ -399,6 +399,125 @@ class RunOSBuildThreadTest(helpers.PungiTestCase):
|
||||
],
|
||||
)
|
||||
|
||||
@mock.patch("pungi.util.get_file_size", new=lambda fp: 65536)
|
||||
@mock.patch("pungi.util.get_mtime", new=lambda fp: 1024)
|
||||
@mock.patch("pungi.phases.osbuild.Linker")
|
||||
@mock.patch("pungi.phases.osbuild.kojiwrapper.KojiWrapper")
|
||||
def test_process_upload_options(self, KojiWrapper, Linker):
|
||||
cfg = {
|
||||
"name": "test-image",
|
||||
"distro": "rhel-8",
|
||||
"image_types": ["rhel-ec2"],
|
||||
"upload_options": {
|
||||
"region": "us-east-1",
|
||||
"share_with_accounts": ["123456789012"],
|
||||
},
|
||||
}
|
||||
build_id = 5678
|
||||
koji = KojiWrapper.return_value
|
||||
koji.watch_task.side_effect = self.make_fake_watch(0)
|
||||
koji.koji_proxy.osbuildImage.return_value = 1234
|
||||
koji.koji_proxy.getTaskResult.return_value = {
|
||||
"composer": {"server": "https://composer.osbuild.org", "id": ""},
|
||||
"koji": {"build": build_id},
|
||||
}
|
||||
koji.koji_proxy.getBuild.return_value = {
|
||||
"build_id": build_id,
|
||||
"name": "test-image",
|
||||
"version": "1",
|
||||
"release": "1",
|
||||
}
|
||||
koji.koji_proxy.listArchives.return_value = [
|
||||
{
|
||||
"extra": {"image": {"arch": "x86_64"}},
|
||||
"filename": "image.raw.xz",
|
||||
"type_name": "raw-xz",
|
||||
}
|
||||
]
|
||||
koji.koji_module.pathinfo = orig_koji.pathinfo
|
||||
|
||||
self.t.process(
|
||||
(
|
||||
self.compose,
|
||||
self.compose.variants["Everything"],
|
||||
cfg,
|
||||
["x86_64"],
|
||||
"1", # version
|
||||
"15", # release
|
||||
"image-target",
|
||||
[self.topdir + "/compose/Everything/$arch/os"],
|
||||
["x86_64"],
|
||||
),
|
||||
1,
|
||||
)
|
||||
|
||||
# Verify two Koji instances were created.
|
||||
self.assertEqual(len(KojiWrapper.call_args), 2)
|
||||
# Verify correct calls to Koji
|
||||
self.assertEqual(
|
||||
koji.mock_calls,
|
||||
[
|
||||
mock.call.login(),
|
||||
mock.call.koji_proxy.osbuildImage(
|
||||
"test-image",
|
||||
"1",
|
||||
"rhel-8",
|
||||
["rhel-ec2"],
|
||||
"image-target",
|
||||
["x86_64"],
|
||||
opts={
|
||||
"release": "15",
|
||||
"repo": [self.topdir + "/compose/Everything/$arch/os"],
|
||||
"upload_options": {
|
||||
"region": "us-east-1",
|
||||
"share_with_accounts": ["123456789012"],
|
||||
},
|
||||
},
|
||||
),
|
||||
mock.call.save_task_id(1234),
|
||||
mock.call.watch_task(1234, mock.ANY),
|
||||
mock.call.koji_proxy.getTaskResult(1234),
|
||||
mock.call.koji_proxy.getBuild(build_id),
|
||||
mock.call.koji_proxy.listArchives(buildID=build_id),
|
||||
],
|
||||
)
|
||||
|
||||
# Assert there is one image added to manifest and the arguments are sane
|
||||
self.assertEqual(
|
||||
self.compose.im.add.call_args_list,
|
||||
[
|
||||
mock.call(arch="x86_64", variant="Everything", image=mock.ANY),
|
||||
],
|
||||
)
|
||||
for call in self.compose.im.add.call_args_list:
|
||||
_, kwargs = call
|
||||
image = kwargs["image"]
|
||||
self.assertEqual(kwargs["variant"], "Everything")
|
||||
self.assertIn(kwargs["arch"], ("x86_64"))
|
||||
self.assertEqual(kwargs["arch"], image.arch)
|
||||
self.assertEqual(
|
||||
"Everything/x86_64/images/image.raw.xz",
|
||||
image.path,
|
||||
)
|
||||
self.assertEqual("raw.xz", image.format)
|
||||
self.assertEqual("raw-xz", image.type)
|
||||
self.assertEqual("Everything", image.subvariant)
|
||||
|
||||
self.assertTrue(
|
||||
os.path.isdir(self.topdir + "/compose/Everything/x86_64/images")
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
Linker.return_value.mock_calls,
|
||||
[
|
||||
mock.call.link(
|
||||
"/mnt/koji/packages/test-image/1/1/images/image.raw.xz",
|
||||
self.topdir + "/compose/Everything/x86_64/images/image.raw.xz",
|
||||
link_type="hardlink-or-copy",
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
@mock.patch("pungi.util.get_file_size", new=lambda fp: 65536)
|
||||
@mock.patch("pungi.util.get_mtime", new=lambda fp: 1024)
|
||||
@mock.patch("pungi.phases.osbuild.Linker")
|
||||
|
Loading…
Reference in New Issue
Block a user