Skip to content

Commit

Permalink
Support for Gardener integration tests on Azure (#38)
Browse files Browse the repository at this point in the history
* Support Azure in integration test pipeline

Provide cleanup code for community gallery images identified from
a component descriptor and provide a dedicated publishing
configuration for Gardener integration tests that includes Azure.

* add code for config validation
  • Loading branch information
MrBatschner committed Jan 18, 2024
1 parent 41781ec commit eeb1351
Show file tree
Hide file tree
Showing 6 changed files with 201 additions and 4 deletions.
2 changes: 2 additions & 0 deletions .cicd-cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -817,6 +817,8 @@ def end_phase(name):
)
else:
raise ValueError(on_absent) # programming error
else:
publish.validate_publishing_configuration(manifest, cfg)

phase_logger.info('publishing-cfg was found to be okay - starting publishing now')

Expand Down
49 changes: 48 additions & 1 deletion cleanup.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import glci.aws
import glci.gcp
import glci.openstack_image
import glci.az

import glci.model as gm
import glci.util
Expand Down Expand Up @@ -40,7 +41,7 @@ def cleanup_image(
elif release.platform == 'gcp':
cleanup_function = None # cleanup_gcp_images
elif release.platform == 'azure':
cleanup_function = None
cleanup_function = cleanup_azure_community_gallery_images
elif release.platform == 'openstack':
cleanup_function = cleanup_openstack_images_by_id
elif release.platform == 'oci':
Expand Down Expand Up @@ -215,6 +216,52 @@ def cleanup_openstack_images(
)


def cleanup_azure_community_gallery_images(
release: gm.OnlineReleaseManifest,
publishing_cfg: gm.PublishingCfg,
dry_run: bool = False
):
cfg_factory = ci.util.ctx().cfg_factory()
azure_publishing_cfg: gm.PublishingTargetAzure = publishing_cfg.target(platform=release.platform)

azure_principal = cfg_factory.azure_service_principal(
cfg_name=azure_publishing_cfg.service_principal_cfg_name,
)

azure_principal_serialized = gm.AzureServicePrincipalCfg(
tenant_id=azure_principal.tenant_id(),
client_id=azure_principal.client_id(),
client_secret=azure_principal.client_secret(),
subscription_id=azure_principal.subscription_id(),
)

shared_gallery_cfg = cfg_factory.azure_shared_gallery(
cfg_name=azure_publishing_cfg.gallery_cfg_name,
)
shared_gallery_cfg_serialized = gm.AzureSharedGalleryCfg(
resource_group_name=shared_gallery_cfg.resource_group_name(),
gallery_name=shared_gallery_cfg.gallery_name(),
location=shared_gallery_cfg.location(),
published_name=shared_gallery_cfg.published_name(),
description=shared_gallery_cfg.description(),
eula=shared_gallery_cfg.eula(),
release_note_uri=shared_gallery_cfg.release_note_uri(),
identifier_publisher=shared_gallery_cfg.identifier_publisher(),
identifier_offer=shared_gallery_cfg.identifier_offer(),
identifier_sku=shared_gallery_cfg.identifier_sku(),
)

published_gallery_images = release.published_image_metadata.published_gallery_images

for gallery_image in published_gallery_images:
glci.az.delete_from_azure_community_gallery(
community_gallery_image_id=gallery_image.community_gallery_image_id,
service_principal_cfg=azure_principal_serialized,
shared_gallery_cfg=shared_gallery_cfg_serialized,
dry_run=dry_run
)


def clean_release_manifest_sets(
max_age_days: int=14,
cicd_cfg=None,
Expand Down
115 changes: 114 additions & 1 deletion glci/az.py
Original file line number Diff line number Diff line change
Expand Up @@ -757,6 +757,7 @@ def publish_azure_image(
storage_account_cfg: glci.model.AzureStorageAccountCfg,
shared_gallery_cfg: glci.model.AzureSharedGalleryCfg,
marketplace_cfg: glci.model.AzureMarketplaceCfg,
hyper_v_generations: list[glci.model.AzureHyperVGeneration],
publish_to_community_gallery: bool = True,
publish_to_marketplace: bool = False,
) -> glci.model.OnlineReleaseManifest:
Expand Down Expand Up @@ -808,7 +809,7 @@ def publish_azure_image(
published_gallery_images=[],
)

for hyper_v_generation in glci.model.AzureHyperVGeneration:
for hyper_v_generation in hyper_v_generations:
if publish_to_marketplace:
logger.info(f'Publishing Azure Marketplace image for {hyper_v_generation}...')
marketplace_published_image = publish_to_azure_marketplace(
Expand Down Expand Up @@ -836,3 +837,115 @@ def publish_azure_image(
published_image.published_gallery_images.append(gallery_published_image)

return dataclasses.replace(release, published_image_metadata=published_image)


def delete_from_azure_community_gallery(
community_gallery_image_id: str,
service_principal_cfg: glci.model.AzureServicePrincipalCfg,
shared_gallery_cfg: glci.model.AzureSharedGalleryCfg,
dry_run: bool
):
credential = ClientSecretCredential(
tenant_id=service_principal_cfg.tenant_id,
client_id=service_principal_cfg.client_id,
client_secret=service_principal_cfg.client_secret
)
cclient = ComputeManagementClient(credential, service_principal_cfg.subscription_id)

# unfortunately, it is not possible to obtain image information from its
# community gallery image id through the API
# so we have to dissect the string and apply some implicit knowledge about its structure
gallery_image_id_parts = community_gallery_image_id.split('/')
if len(gallery_image_id_parts) != 7:
raise RuntimeError(f"community gallery image id {community_gallery_image_id} does not follow expected semantics")

image_community_gallery_name = gallery_image_id_parts[2]
image_definition = gallery_image_id_parts[4]
image_version = gallery_image_id_parts[6]

# check if the gallery names from the released artefact and the publishing cfg match
configured_gallery = cclient.galleries.get(
resource_group_name=shared_gallery_cfg.resource_group_name,
gallery_name=shared_gallery_cfg.gallery_name
)

image_gallery_is_configured_gallery = False
for public_name in configured_gallery.sharing_profile.community_gallery_info.public_names:
if public_name == image_community_gallery_name:
image_gallery_is_configured_gallery = True
break

if not image_gallery_is_configured_gallery:
raise RuntimeError(f"The community gallery of image {community_gallery_image_id} is not from the configured community gallery {shared_gallery_cfg.gallery_name}.")

gallery_image_version = cclient.gallery_image_versions.get(
resource_group_name=shared_gallery_cfg.resource_group_name,
gallery_name=shared_gallery_cfg.gallery_name,
gallery_image_name=image_definition,
gallery_image_version_name=image_version
)

# once again, resource group and image name has to be extracted from this string
image_vhd = gallery_image_version.storage_profile.source.id
image_vhd_parts = image_vhd.split('/')
if len(image_vhd_parts) != 9:
raise RuntimeError(f"image resource string {image_vhd} does not follow expected semantics")

image_vhd_resource_group = image_vhd_parts[4]
image_vhd_name = image_vhd_parts[8]

if dry_run:
logger.warning(f"DRY RUN: would delete gallery image version {gallery_image_version.name}")
logger.warning(f"DRY RUN: would delete image VHD {image_vhd_name} in resource group {image_vhd_resource_group}")
else:
logger.info(f"Deleting {image_version=} for {image_definition=} in gallery {shared_gallery_cfg.gallery_name}...")
result = cclient.gallery_image_versions.begin_delete(
resource_group_name=shared_gallery_cfg.resource_group_name,
gallery_name=shared_gallery_cfg.gallery_name,
gallery_image_name=image_definition,
gallery_image_version_name=image_version
)
logger.info('...waiting for asynchronous operation to complete')
result = result.result()

logger.info(f"Deleting image VHD {image_vhd_name} in resource group {image_vhd_resource_group}...")
result = cclient.images.begin_delete(
resource_group_name=image_vhd_resource_group,
image_name=image_vhd_name
)
logger.info('...waiting for asynchronous operation to complete')
result = result.result()

# check how many image versions are present in this image definition
# if none, that delete the image definition
gallery_image_versions = cclient.gallery_image_versions.list_by_gallery_image(
resource_group_name=shared_gallery_cfg.resource_group_name,
gallery_name=shared_gallery_cfg.gallery_name,
gallery_image_name=image_definition
)

image_version_count = sum(1 for _ in gallery_image_versions)
if image_version_count == 0:
if dry_run:
logger.warning(f"DRY RUN: would delete {image_definition=} in gallery {shared_gallery_cfg.gallery_name}")
else:
logger.info(f"Deleting {image_definition=} in gallery {shared_gallery_cfg.gallery_name}...")
result = cclient.gallery_images.begin_delete(
resource_group_name=shared_gallery_cfg.resource_group_name,
gallery_name=shared_gallery_cfg.gallery_name,
gallery_image_name=image_definition
)
logger.info('...waiting for asynchronous operation to complete')
result = result.result()
else:
logger.warning(f"{image_definition=} still contains {image_version_count} image versions - keeping definition")


def validate_azure_publishing_config(
release: glci.model.OnlineReleaseManifest,
publishing_cfg: glci.model.PublishingCfg,
):
azure_publishing_cfg: glci.model.PublishingTargetAzure = publishing_cfg.target(platform=release.platform)

if azure_publishing_cfg.publish_to_marketplace and not azure_publishing_cfg.marketplace_cfg:
raise RuntimeError(f"Expected to publish to Azure Marketplace but no marketplace config in publishing config.")
3 changes: 2 additions & 1 deletion glci/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -758,7 +758,8 @@ class PublishingTargetAzure:
gallery_cfg_name: str
storage_account_cfg_name: str
service_principal_cfg_name: str
marketplace_cfg: AzureMarketplaceCfg
marketplace_cfg: typing.Optional[AzureMarketplaceCfg]
hyper_v_generations: typing.List[AzureHyperVGeneration]
publish_to_marketplace: bool
publish_to_community_galleries: bool
platform: Platform = 'azure' # should not overwrite
Expand Down
28 changes: 27 additions & 1 deletion publish.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,31 @@ def publish_image(
raise


def validate_publishing_configuration(
release: gm.OnlineReleaseManifest,
cfg: gm.PublishingCfg
):
if release.platform == 'azure':
validation_function = glci.az.validate_azure_publishing_config
elif release.platform == 'ali':
validation_function = None
elif release.platform == 'aws':
validation_function = None
elif release.platform == 'gcp':
validation_function = None
elif release.platform == 'openstack':
validation_function = None
elif release.platform == 'openstackbaremetal':
validation_function = None
elif release.platform == 'oci':
validation_function = None
else:
validation_function = None

if validation_function:
validation_function(release, cfg)


def _publish_alicloud_image(
release: gm.OnlineReleaseManifest,
publishing_cfg: gm.PublishingCfg,
Expand Down Expand Up @@ -161,8 +186,9 @@ def _publish_azure_image(
storage_account_cfg=storage_account_cfg_serialized,
shared_gallery_cfg=shared_gallery_cfg_serialized,
marketplace_cfg=azure_publishing_cfg.marketplace_cfg,
hyper_v_generations=azure_publishing_cfg.hyper_v_generations,
publish_to_community_gallery=azure_publishing_cfg.publish_to_community_galleries,
publish_to_marketplace=azure_publishing_cfg.publish_to_marketplace,
publish_to_marketplace=azure_publishing_cfg.publish_to_marketplace
)


Expand Down
8 changes: 8 additions & 0 deletions publishing-cfg.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
gallery_cfg_name: 'gardenlinux-community-gallery'
storage_account_cfg_name: 'gardenlinux-community-gallery'
service_principal_cfg_name: 'gardenlinux'
hyper_v_generations: ['V1', 'V2']
marketplace_cfg:
offer_id: 'gardenlinux'
publisher_id: 'sap'
Expand Down Expand Up @@ -102,3 +103,10 @@
hw_disk_bus: scsi
vmware_adaptertype: paraVirtual
vmware_disktype: streamOptimized
- platform: 'azure'
service_principal_cfg_name: 'gardenlinux-integration-test'
storage_account_cfg_name: 'gardenlinux-integration-test'
gallery_cfg_name: 'gardenlinux-integration-test'
hyper_v_generations: ['V1']
publish_to_marketplace: false
publish_to_community_galleries: true

0 comments on commit eeb1351

Please sign in to comment.