enable community gallery image

This commit is contained in:
Lili Deng 2024-04-01 22:19:28 +08:00 коммит произвёл LiliDeng
Родитель 402711ddf4
Коммит 4890deb5ee
4 изменённых файлов: 424 добавлений и 243 удалений

Просмотреть файл

@ -124,6 +124,10 @@ func getOsDiskSharedGallery(shared_gallery object) object => {
id: resourceId(shared_gallery.subscription_id, empty(shared_gallery.resource_group_name) ? 'None' : shared_gallery.resource_group_name, 'Microsoft.Compute/galleries/images/versions', shared_gallery.image_gallery, shared_gallery.image_definition, shared_gallery.image_version)
}
func getOSDiskCommunityGalleryImage(community_gallery_image object) object => {
communityGalleryImageId: '/CommunityGalleries/${community_gallery_image.image_gallery}/Images/${community_gallery_image.image_definition}/Versions/${community_gallery_image.image_version}'
}
func getOsDiskMarketplace(marketplace object) object => {
publisher: marketplace.publisher
offer: marketplace.offer
@ -131,10 +135,13 @@ func getOsDiskMarketplace(marketplace object) object => {
version: marketplace.version
}
func generateImageReference(node object) object => (isVhd(node) ? getOsDiskVhd(node.name)
: ((!empty(node.shared_gallery))
func generateImageReference(node object) object => isVhd(node)
? getOsDiskVhd(node.name)
: !empty(node.shared_gallery)
? getOsDiskSharedGallery(node.shared_gallery)
: getOsDiskMarketplace(node.marketplace)))
: !empty(node.community_gallery_image)
? getOSDiskCommunityGalleryImage(node.community_gallery_image)
: getOsDiskMarketplace(node.marketplace)
func getSecurityProfileForOSDisk(node object) object => empty(node.security_profile.disk_encryption_set_id)
? {

Просмотреть файл

@ -6,7 +6,7 @@
"_generator": {
"name": "bicep",
"version": "0.24.24.22086",
"templateHash": "2250159769761272390"
"templateHash": "10469517385668739272"
}
},
"functions": [
@ -211,6 +211,20 @@
}
}
},
"getOSDiskCommunityGalleryImage": {
"parameters": [
{
"type": "object",
"name": "community_gallery_image"
}
],
"output": {
"type": "object",
"value": {
"communityGalleryImageId": "[format('/CommunityGalleries/{0}/Images/{1}/Versions/{2}', parameters('community_gallery_image').image_gallery, parameters('community_gallery_image').image_definition, parameters('community_gallery_image').image_version)]"
}
}
},
"getOsDiskMarketplace": {
"parameters": [
{
@ -237,7 +251,7 @@
],
"output": {
"type": "object",
"value": "[if(__bicep.isVhd(parameters('node')), __bicep.getOsDiskVhd(parameters('node').name), if(not(empty(parameters('node').shared_gallery)), __bicep.getOsDiskSharedGallery(parameters('node').shared_gallery), __bicep.getOsDiskMarketplace(parameters('node').marketplace)))]"
"value": "[if(__bicep.isVhd(parameters('node')), __bicep.getOsDiskVhd(parameters('node').name), if(not(empty(parameters('node').shared_gallery)), __bicep.getOsDiskSharedGallery(parameters('node').shared_gallery), if(not(empty(parameters('node').community_gallery_image)), __bicep.getOSDiskCommunityGalleryImage(parameters('node').community_gallery_image), __bicep.getOsDiskMarketplace(parameters('node').marketplace))))]"
}
},
"getSecurityProfileForOSDisk": {

Просмотреть файл

@ -11,7 +11,7 @@ from functools import lru_cache, partial
from pathlib import Path, PurePath
from threading import Lock
from time import sleep, time
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Type, Union
import requests
from assertpy import assert_that
@ -152,6 +152,13 @@ SIG_IMAGE_KEYS = [
"image_definition",
"image_version",
]
CG_IMAGE_KEYS = [
"location",
"image_gallery",
"image_definition",
"image_version",
]
PURCHASE_PLAN_KEYS = ["name", "product", "publisher"]
# IMDS is a REST API that's available at a well-known, non-routable IP address (169.254.169.254). # noqa: E501
METADATA_ENDPOINT = "http://169.254.169.254/metadata/instance?api-version=2021-02-01"
@ -244,6 +251,20 @@ class VhdSchema(AzureImageSchema):
vmgs_path: Optional[str] = None
@dataclass_json()
@dataclass
class CommunityGalleryImageSchema(AzureImageSchema):
image_gallery: str = ""
image_definition: str = ""
image_version: str = ""
location: str = ""
def __hash__(self) -> int:
return hash(
f"{self.image_gallery}/{self.image_definition}/{self.image_version}"
)
@dataclass_json()
@dataclass
class AzureNodeSchema:
@ -275,7 +296,9 @@ class AzureNodeSchema:
vhd_raw: Optional[Union[Dict[Any, Any], str]] = field(
default=None, metadata=field_metadata(data_key="vhd")
)
community_gallery_image_raw: Optional[Union[Dict[Any, Any], str]] = field(
default=None, metadata=field_metadata(data_key="community_gallery_image")
)
hyperv_generation: int = field(
default=1,
metadata=field_metadata(validate=validate.OneOf([1, 2])),
@ -296,6 +319,8 @@ class AzureNodeSchema:
_orignal_vhd_path: str = ""
_community_gallery_image: InitVar[Optional[CommunityGalleryImageSchema]] = None
def __post_init__(self, *args: Any, **kwargs: Any) -> None:
# trim whitespace of values.
strip_strs(
@ -309,6 +334,7 @@ class AzureNodeSchema:
"marketplace_raw",
"shared_gallery_raw",
"vhd_raw",
"community_gallery_image_raw",
"data_disk_caching_type",
"os_disk_type",
"data_disk_type",
@ -322,226 +348,146 @@ class AzureNodeSchema:
@property
def purchase_plan(self) -> Optional[AzureVmPurchasePlanSchema]:
# this is a safe guard and prevent mypy error on typing
if not hasattr(self, "_purchase_plan"):
self._purchase_plan: Optional[AzureVmPurchasePlanSchema] = None
purchase_plan: Optional[AzureVmPurchasePlanSchema] = self._purchase_plan
if not purchase_plan:
if isinstance(self.purchase_plan_raw, dict):
purchase_plan = schema.load_by_type(
AzureVmPurchasePlanSchema, self.purchase_plan_raw
)
if not all(
[
purchase_plan.name.strip(),
purchase_plan.product.strip(),
purchase_plan.publisher.strip(),
]
):
purchase_plan = None
purchase_plan = self._parse_image(
"purchase_plan",
AzureVmPurchasePlanSchema,
PURCHASE_PLAN_KEYS,
self.purchase_plan_raw,
)
if isinstance(self.purchase_plan_raw, str):
self.purchase_plan_raw = self.purchase_plan_raw.strip()
if self.purchase_plan_raw:
purchase_plan_strings = re.split(r"[:\s]+", self.purchase_plan_raw)
if len(purchase_plan_strings) == 3:
purchase_plan = AzureVmPurchasePlanSchema(
name=purchase_plan_strings[0],
product=purchase_plan_strings[1],
publisher=purchase_plan_strings[2],
)
# purchase_plan_raw is used
self.purchase_plan_raw = purchase_plan.to_dict()
else:
# this step makes purchase_plan_raw is validated, and
# filter out any unwanted content.
self.purchase_plan_raw = purchase_plan.to_dict() # type: ignore
elif self.purchase_plan_raw:
assert isinstance(
self.purchase_plan_raw, str
), f"actual: {type(self.purchase_plan_raw)}"
self.purchase_plan_raw = self.purchase_plan_raw.strip()
if self.purchase_plan_raw:
purchase_plan_strings = re.split(r"[:\s]+", self.purchase_plan_raw)
if len(purchase_plan_strings) == 3:
purchase_plan = AzureVmPurchasePlanSchema(
name=purchase_plan_strings[0],
product=purchase_plan_strings[1],
publisher=purchase_plan_strings[2],
)
# purchase_plan_raw is used
self.purchase_plan_raw = purchase_plan.to_dict() # type: ignore
else:
raise LisaException(
f"Invalid value for the provided purchase_plan "
f"parameter: '{self.purchase_plan_raw}'."
f"The purchase_plan parameter should be in the format: "
f"'<name> <product> <publisher>' "
)
self._purchase_plan = purchase_plan
return purchase_plan
raise LisaException(
f"Invalid value for the provided purchase_plan "
f"parameter: '{self.purchase_plan_raw}'."
f"The purchase_plan parameter should be in the format: "
f"'<name> <product> <publisher>' "
)
self._purchase_plan = purchase_plan
return (
purchase_plan
if isinstance(purchase_plan, AzureVmPurchasePlanSchema)
else None
)
@purchase_plan.setter
def purchase_plan(self, value: Optional[AzureVmPurchasePlanSchema]) -> None:
self._purchase_plan = value
if value is None:
self.purchase_plan_raw = None
else:
self.purchase_plan_raw = value.to_dict() # type: ignore
self._parse_image_raw("purchase_plan", value)
@property
def marketplace(self) -> Optional[AzureVmMarketplaceSchema]:
# this is a safe guard and prevent mypy error on typing
if not hasattr(self, "_marketplace"):
self._marketplace: Optional[AzureVmMarketplaceSchema] = None
marketplace: Optional[AzureVmMarketplaceSchema] = self._marketplace
if not marketplace:
if isinstance(self.marketplace_raw, dict):
marketplace = self._parse_image(
"marketplace",
AzureVmMarketplaceSchema,
MARKETPLACE_IMAGE_KEYS,
self.marketplace_raw,
)
if isinstance(self.marketplace_raw, str):
self.marketplace_raw = self.marketplace_raw.strip()
if self.marketplace_raw:
# Users decide the cases of image names,
# the inconsistent cases cause the mismatched error in notifiers.
# The lower() normalizes the image names,
# it has no impact on deployment.
self.marketplace_raw = dict(
(
(k, v.lower())
if isinstance(v, str) and k in MARKETPLACE_IMAGE_KEYS
else (k, v)
marketplace_strings = re.split(r"[:\s]+", self.marketplace_raw.lower())
if len(marketplace_strings) == 4:
marketplace = AzureVmMarketplaceSchema(
publisher=marketplace_strings[0],
offer=marketplace_strings[1],
sku=marketplace_strings[2],
version=marketplace_strings[3],
)
for k, v in self.marketplace_raw.items()
)
marketplace = schema.load_by_type(
AzureVmMarketplaceSchema, self.marketplace_raw
)
if not all(
[
marketplace.publisher,
marketplace.offer,
marketplace.sku,
marketplace.version,
]
):
marketplace = None
# marketplace_raw is used
self.marketplace_raw = marketplace.to_dict()
else:
# this step makes marketplace_raw is validated, and
# filter out any unwanted content.
self.marketplace_raw = marketplace.to_dict() # type: ignore
elif self.marketplace_raw:
assert isinstance(
self.marketplace_raw, str
), f"actual: {type(self.marketplace_raw)}"
self.marketplace_raw = self.marketplace_raw.strip()
if self.marketplace_raw:
# Users decide the cases of image names,
# the inconsistent cases cause the mismatched error in notifiers.
# The lower() normalizes the image names,
# it has no impact on deployment.
marketplace_strings = re.split(
r"[:\s]+", self.marketplace_raw.lower()
raise LisaException(
f"Invalid value for the provided marketplace "
f"parameter: '{self.marketplace_raw}'."
f"The marketplace parameter should be in the format: "
f"'<Publisher> <Offer> <Sku> <Version>' "
f"or '<Publisher>:<Offer>:<Sku>:<Version>'"
)
if len(marketplace_strings) == 4:
marketplace = AzureVmMarketplaceSchema(
publisher=marketplace_strings[0],
offer=marketplace_strings[1],
sku=marketplace_strings[2],
version=marketplace_strings[3],
)
# marketplace_raw is used
self.marketplace_raw = marketplace.to_dict() # type: ignore
else:
raise LisaException(
f"Invalid value for the provided marketplace "
f"parameter: '{self.marketplace_raw}'."
f"The marketplace parameter should be in the format: "
f"'<Publisher> <Offer> <Sku> <Version>' "
f"or '<Publisher>:<Offer>:<Sku>:<Version>'"
)
self._marketplace = marketplace
return marketplace
self._marketplace = marketplace
return (
marketplace if isinstance(marketplace, AzureVmMarketplaceSchema) else None
)
@marketplace.setter
def marketplace(self, value: Optional[AzureVmMarketplaceSchema]) -> None:
self._marketplace = value
if value is None:
self.marketplace_raw = None
else:
self.marketplace_raw = value.to_dict() # type: ignore
self._parse_image_raw("marketplace", value)
@property
def shared_gallery(self) -> Optional[SharedImageGallerySchema]:
# this is a safe guard and prevent mypy error on typing
if not hasattr(self, "_shared_gallery"):
self._shared_gallery: Optional[SharedImageGallerySchema] = None
shared_gallery: Optional[SharedImageGallerySchema] = self._shared_gallery
if shared_gallery:
return shared_gallery
if isinstance(self.shared_gallery_raw, dict):
# Users decide the cases of image names,
# the inconsistent cases cause the mismatched error in notifiers.
# The lower() normalizes the image names,
# it has no impact on deployment.
self.shared_gallery_raw = dict(
(k, v.lower()) if isinstance(v, str) and k in SIG_IMAGE_KEYS else (k, v)
for k, v in self.shared_gallery_raw.items()
)
shared_gallery = schema.load_by_type(
SharedImageGallerySchema, self.shared_gallery_raw
)
if not all(
[
shared_gallery.image_definition,
shared_gallery.image_version,
shared_gallery.image_gallery,
]
):
shared_gallery = None
else:
if not shared_gallery.subscription_id:
shared_gallery.subscription_id = self.subscription_id
# this step makes shared_gallery_raw is validated, and
# filter out any unwanted content.
self.shared_gallery_raw = shared_gallery.to_dict() # type: ignore
elif self.shared_gallery_raw:
assert isinstance(
self.shared_gallery_raw, str
), f"actual: {type(self.shared_gallery_raw)}"
# Users decide the cases of image names,
# the inconsistent cases cause the mismatched error in notifiers.
# The lower() normalizes the image names,
# it has no impact on deployment.
shared_gallery_strings = re.split(
r"[/]+", self.shared_gallery_raw.strip().lower()
)
if len(shared_gallery_strings) == 5:
shared_gallery = SharedImageGallerySchema(
subscription_id=shared_gallery_strings[0],
resource_group_name=shared_gallery_strings[1],
image_gallery=shared_gallery_strings[2],
image_definition=shared_gallery_strings[3],
image_version=shared_gallery_strings[4],
)
# shared_gallery_raw is used
self.shared_gallery_raw = shared_gallery.to_dict() # type: ignore
elif len(shared_gallery_strings) == 3:
shared_gallery = SharedImageGallerySchema(
subscription_id=self.subscription_id,
image_gallery=shared_gallery_strings[0],
image_definition=shared_gallery_strings[1],
image_version=shared_gallery_strings[2],
)
# shared_gallery_raw is used
self.shared_gallery_raw = shared_gallery.to_dict() # type: ignore
else:
raise LisaException(
f"Invalid value for the provided shared gallery "
f"parameter: '{self.shared_gallery_raw}'."
f"The shared gallery parameter should be in the format: "
f"'<subscription_id>/<resource_group_name>/<image_gallery>/"
f"<image_definition>/<image_version>' or '<image_gallery>/"
f"<image_definition>/<image_version>'"
shared_gallery = self._parse_image(
"shared_gallery",
SharedImageGallerySchema,
SIG_IMAGE_KEYS,
self.shared_gallery_raw,
)
if (
isinstance(shared_gallery, SharedImageGallerySchema)
and not shared_gallery.subscription_id
):
shared_gallery.subscription_id = self.subscription_id
if isinstance(self.shared_gallery_raw, str):
self.shared_gallery_raw = self.shared_gallery_raw.strip()
if self.shared_gallery_raw:
# Users decide the cases of image names,
# the inconsistent cases cause the mismatched error in notifiers.
# The lower() normalizes the image names,
# it has no impact on deployment.
shared_gallery_strings = re.split(
r"[/]+", self.shared_gallery_raw.strip().lower()
)
if len(shared_gallery_strings) == 5:
shared_gallery = SharedImageGallerySchema(
subscription_id=shared_gallery_strings[0],
resource_group_name=shared_gallery_strings[1],
image_gallery=shared_gallery_strings[2],
image_definition=shared_gallery_strings[3],
image_version=shared_gallery_strings[4],
)
elif len(shared_gallery_strings) == 3:
shared_gallery = SharedImageGallerySchema(
subscription_id=self.subscription_id,
image_gallery=shared_gallery_strings[0],
image_definition=shared_gallery_strings[1],
image_version=shared_gallery_strings[2],
)
else:
raise LisaException(
f"Invalid value for the provided shared gallery "
f"parameter: '{self.shared_gallery_raw}'."
f"The shared gallery parameter should be in the format: "
f"'<subscription_id>/<resource_group_name>/<image_gallery>/"
f"<image_definition>/<image_version>' or '<image_gallery>/"
f"<image_definition>/<image_version>'"
)
self.shared_gallery_raw = shared_gallery.to_dict()
self._shared_gallery = shared_gallery
return shared_gallery
return (
shared_gallery
if isinstance(shared_gallery, SharedImageGallerySchema)
else None
)
@shared_gallery.setter
def shared_gallery(self, value: Optional[SharedImageGallerySchema]) -> None:
self._shared_gallery = value
if value is None:
self.shared_gallery_raw = None
else:
self.shared_gallery_raw = value.to_dict() # type: ignore
self._parse_image_raw("shared_gallery", value)
@property
def vhd(self) -> Optional[VhdSchema]:
@ -577,11 +523,50 @@ class AzureNodeSchema:
@vhd.setter
def vhd(self, value: Optional[VhdSchema]) -> None:
self._vhd = value
if value is None:
self.vhd_raw = None
else:
self.vhd_raw = self._vhd.to_dict() # type: ignore
self._parse_image_raw("vhd", value)
@property
def community_gallery_image(self) -> Optional[CommunityGalleryImageSchema]:
community_gallery_image = self._parse_image(
"community_gallery_image",
CommunityGalleryImageSchema,
CG_IMAGE_KEYS,
self.community_gallery_image_raw,
)
if isinstance(self.community_gallery_image_raw, str):
self.community_gallery_image_raw = self.community_gallery_image_raw.strip()
if self.community_gallery_image_raw:
community_gallery_image_strings = re.split(
r"[/]+", self.community_gallery_image_raw.lower()
)
if len(community_gallery_image_strings) == 4:
community_gallery_image = CommunityGalleryImageSchema(
location=community_gallery_image_strings[0],
image_gallery=community_gallery_image_strings[1],
image_definition=community_gallery_image_strings[2],
image_version=community_gallery_image_strings[3],
)
self.community_gallery_image_raw = community_gallery_image.to_dict()
else:
raise LisaException(
"Invalid value for the provided community gallery image"
f"parameter: '{self.community_gallery_image_raw}'."
"The community gallery image parameter should be in the"
" format: '<location>/<image_gallery>/<image_definition>"
"/<image_version>'"
)
self._community_gallery_image = community_gallery_image
return (
community_gallery_image
if isinstance(community_gallery_image, CommunityGalleryImageSchema)
else None
)
@community_gallery_image.setter
def community_gallery_image(
self, value: Optional[CommunityGalleryImageSchema]
) -> None:
self._parse_image_raw("community_gallery_image", value)
def get_image_name(self) -> str:
result = ""
@ -601,6 +586,13 @@ class AzureNodeSchema:
f"{self.shared_gallery.image_definition}/"
f"{self.shared_gallery.image_version}"
)
elif self.community_gallery_image:
assert isinstance(
self.community_gallery_image_raw, dict
), f"actual type: {type(self.community_gallery_image_raw)}"
result = "/".join(
[self.community_gallery_image_raw.get(k, "") for k in CG_IMAGE_KEYS]
)
elif self.marketplace:
assert isinstance(
self.marketplace_raw, dict
@ -610,6 +602,66 @@ class AzureNodeSchema:
)
return result
def _parse_image(
self,
prop_name: str,
schema_type: Type[
Union[
VhdSchema,
AzureImageSchema,
SharedImageGallerySchema,
CommunityGalleryImageSchema,
AzureVmPurchasePlanSchema,
]
],
keys: List[str],
raw_data: Optional[Union[Dict[Any, Any], str]],
) -> Any:
if not hasattr(self, f"_{prop_name}"):
setattr(self, f"_{prop_name}", None)
prop_value = getattr(self, f"_{prop_name}")
if prop_value:
return prop_value
if isinstance(raw_data, dict):
normalized_data = {
k: (v.lower() if isinstance(v, str) and hasattr(schema_type, k) else v)
for k, v in raw_data.items()
}
prop_value = schema.load_by_type(schema_type, normalized_data)
# Check if all required keys have values
if all(getattr(prop_value, key) for key in keys):
setattr(self, f"{prop_name}_raw", prop_value.to_dict())
else:
setattr(self, f"{prop_name}_raw", None)
return prop_value
def _parse_image_raw(
self,
prop_name: str,
value: Optional[
Union[
VhdSchema,
AzureImageSchema,
SharedImageGallerySchema,
CommunityGalleryImageSchema,
AzureVmPurchasePlanSchema,
]
],
) -> None:
setattr(self, f"_{prop_name}", value)
if value is not None:
raw_value = (
value.to_dict() if hasattr(value, "to_dict") else value # type: ignore
)
else:
raw_value = None
setattr(self, f"{prop_name}_raw", raw_value)
@dataclass_json()
@dataclass
@ -624,18 +676,17 @@ class AzureNodeArmParameter(AzureNodeSchema):
@classmethod
def from_node_runbook(cls, runbook: AzureNodeSchema) -> "AzureNodeArmParameter":
parameters = runbook.to_dict() # type: ignore
if "marketplace" in parameters:
parameters["marketplace_raw"] = parameters["marketplace"]
del parameters["marketplace"]
if "purchase_plan" in parameters:
parameters["purchase_plan_raw"] = parameters["purchase_plan"]
del parameters["purchase_plan"]
if "shared_gallery" in parameters:
parameters["shared_gallery_raw"] = parameters["shared_gallery"]
del parameters["shared_gallery"]
if "vhd" in parameters:
parameters["vhd_raw"] = parameters["vhd"]
del parameters["vhd"]
keys_to_rename = {
"marketplace": "marketplace_raw",
"purchase_plan": "purchase_plan_raw",
"shared_gallery": "shared_gallery_raw",
"community_gallery_image": "community_gallery_image_raw",
"vhd": "vhd_raw",
}
for old_key, new_key in keys_to_rename.items():
if old_key in parameters:
parameters[new_key] = parameters.pop(old_key)
arm_parameters = AzureNodeArmParameter(**parameters)

Просмотреть файл

@ -20,6 +20,8 @@ import requests
from azure.core.exceptions import HttpResponseError, ResourceNotFoundError
from azure.identity import DefaultAzureCredential
from azure.mgmt.compute.models import (
CommunityGalleryImage,
CommunityGalleryImageVersion,
GalleryImage,
GalleryImageVersion,
ResourceSku,
@ -97,6 +99,7 @@ from .common import (
AzureNodeSchema,
AzureVmMarketplaceSchema,
AzureVmPurchasePlanSchema,
CommunityGalleryImageSchema,
DataDiskCreateOption,
DataDiskSchema,
SharedImageGallerySchema,
@ -1402,11 +1405,27 @@ class AzurePlatform(Platform):
azure_node_runbook.vhd = vhd
azure_node_runbook.marketplace = None
azure_node_runbook.shared_gallery = None
log.debug(
f"current vhd generation is {azure_node_runbook.hyperv_generation}."
)
elif azure_node_runbook.shared_gallery:
azure_node_runbook.marketplace = None
azure_node_runbook.shared_gallery = self._parse_shared_gallery_image(
azure_node_runbook.shared_gallery
)
azure_node_runbook.hyperv_generation = _get_gallery_image_generation(
self._get_sig(azure_node_runbook.shared_gallery)
)
elif azure_node_runbook.community_gallery_image:
azure_node_runbook.marketplace = None
azure_node_runbook.community_gallery_image = (
self._parse_community_gallery_image(
azure_node_runbook.community_gallery_image
)
)
azure_node_runbook.hyperv_generation = _get_gallery_image_generation(
self._get_cgi(azure_node_runbook.community_gallery_image)
)
elif not azure_node_runbook.marketplace:
# set to default marketplace, if nothing specified
azure_node_runbook.marketplace = AzureVmMarketplaceSchema()
@ -1431,15 +1450,6 @@ class AzurePlatform(Platform):
and image_info.os_disk_image.operating_system == "Windows"
):
azure_node_runbook.is_linux = False
elif azure_node_runbook.shared_gallery:
azure_node_runbook.hyperv_generation = _get_gallery_image_generation(
self._get_detailed_sig(azure_node_runbook.shared_gallery)
)
else:
log.debug(
"there is no way to detect vhd generation, unless user provides it"
f" current vhd generation is {azure_node_runbook.hyperv_generation}"
)
if azure_node_runbook.is_linux is None:
# fill it default value
@ -1477,6 +1487,11 @@ class AzurePlatform(Platform):
arm_parameters.osdisk_size_in_gb,
self._get_sig_os_disk_size(arm_parameters.shared_gallery),
)
elif arm_parameters.community_gallery_image:
arm_parameters.osdisk_size_in_gb = max(
arm_parameters.osdisk_size_in_gb,
self._get_cgi_os_disk_size(arm_parameters.community_gallery_image),
)
else:
assert (
arm_parameters.marketplace
@ -2062,6 +2077,41 @@ class AzurePlatform(Platform):
return new_marketplace
@lru_cache(maxsize=10) # noqa: B019
def _parse_community_gallery_image(
self, community_gallery_image: CommunityGalleryImageSchema
) -> CommunityGalleryImageSchema:
new_community_gallery_image = copy.copy(community_gallery_image)
compute_client = get_compute_client(self)
if community_gallery_image.image_version.lower() == "latest":
community_gallery_images_list = (
compute_client.community_gallery_image_versions.list(
location=community_gallery_image.location,
public_gallery_name=community_gallery_image.image_gallery,
gallery_image_name=community_gallery_image.image_definition,
)
)
image: Optional[CommunityGalleryImageVersion] = None
time: Optional[datetime] = None
for image in community_gallery_images_list:
assert image, "'image' must not be 'None'"
assert image.name, "'image.name' must not be 'None'"
community_gallery_image_version = (
compute_client.community_gallery_image_versions.get(
location=community_gallery_image.location,
public_gallery_name=community_gallery_image.image_gallery,
gallery_image_name=community_gallery_image.image_definition,
gallery_image_version_name=image.name,
)
)
if not time:
time = community_gallery_image_version.published_date
new_community_gallery_image.image_version = image.name
if community_gallery_image_version.published_date > time:
time = community_gallery_image_version.published_date
new_community_gallery_image.image_version = image.name
return new_community_gallery_image
@lru_cache(maxsize=10) # noqa: B019
def _parse_shared_gallery_image(
self, shared_image: SharedImageGallerySchema
@ -2430,33 +2480,64 @@ class AzurePlatform(Platform):
assert isinstance(vhd_os_disk_size, int), f"actual: {type(vhd_os_disk_size)}"
return vhd_os_disk_size
def _get_sig_info(
def _get_sig_version(
self, shared_image: SharedImageGallerySchema
) -> GalleryImageVersion:
compute_client = get_compute_client(self)
sig_info = compute_client.gallery_image_versions.get(
sig_version = compute_client.gallery_image_versions.get(
resource_group_name=shared_image.resource_group_name,
gallery_name=shared_image.image_gallery,
gallery_image_name=shared_image.image_definition,
gallery_image_version_name=shared_image.image_version,
expand="ReplicationStatus",
)
assert isinstance(sig_info, GalleryImageVersion), f"actual: {type(sig_info)}"
return sig_info
assert isinstance(
sig_version, GalleryImageVersion
), f"actual: {type(sig_version)}"
return sig_version
@lru_cache(maxsize=10) # noqa: B019
def _get_detailed_sig(self, shared_image: SharedImageGallerySchema) -> GalleryImage:
def _get_cgi_version(
self, community_gallery_image: CommunityGalleryImageSchema
) -> CommunityGalleryImageVersion:
compute_client = get_compute_client(self)
detailed_sig = compute_client.gallery_images.get(
cgi_version = compute_client.community_gallery_image_versions.get(
location=community_gallery_image.location,
public_gallery_name=community_gallery_image.image_gallery,
gallery_image_name=community_gallery_image.image_definition,
gallery_image_version_name=community_gallery_image.image_version,
)
assert isinstance(
cgi_version, CommunityGalleryImageVersion
), f"actual: {type(cgi_version)}"
return cgi_version
@lru_cache(maxsize=10) # noqa: B019
def _get_cgi(
self, community_gallery_image: CommunityGalleryImageSchema
) -> CommunityGalleryImage:
compute_client = get_compute_client(self)
cgi = compute_client.community_gallery_images.get(
location=community_gallery_image.location,
public_gallery_name=community_gallery_image.image_gallery,
gallery_image_name=community_gallery_image.image_definition,
)
assert isinstance(cgi, CommunityGalleryImage), f"actual: {type(cgi)}"
return cgi
@lru_cache(maxsize=10) # noqa: B019
def _get_sig(self, shared_image: SharedImageGallerySchema) -> GalleryImage:
compute_client = get_compute_client(self)
sig = compute_client.gallery_images.get(
resource_group_name=shared_image.resource_group_name,
gallery_name=shared_image.image_gallery,
gallery_image_name=shared_image.image_definition,
)
assert isinstance(detailed_sig, GalleryImage), f"actual: {type(detailed_sig)}"
return detailed_sig
assert isinstance(sig, GalleryImage), f"actual: {type(sig)}"
return sig
def _get_sig_os_disk_size(self, shared_image: SharedImageGallerySchema) -> int:
found_image = self._get_sig_info(shared_image)
found_image = self._get_sig_version(shared_image)
assert found_image.storage_profile, "'storage_profile' must not be 'None'"
assert (
found_image.storage_profile.os_disk_image
@ -2466,6 +2547,18 @@ class AzurePlatform(Platform):
), "'size_in_gb' must not be 'None'"
return int(found_image.storage_profile.os_disk_image.size_in_gb)
def _get_cgi_os_disk_size(
self, community_gallery_image: CommunityGalleryImageSchema
) -> int:
found_image = self._get_cgi_version(community_gallery_image)
storage_profile = found_image.storage_profile # type: ignore
assert storage_profile, "'storage_profile' must not be 'None'"
assert storage_profile.os_disk_image, "'os_disk_image' must not be 'None'"
assert (
storage_profile.os_disk_image.disk_size_gb
), "'disk_size_gb' must not be 'None'"
return int(storage_profile.os_disk_image.disk_size_gb)
def _get_normalized_vm_sizes(
self, name: str, location: str, log: Logger
) -> List[str]:
@ -2853,7 +2946,7 @@ class AzurePlatform(Platform):
azure_runbook.shared_gallery = self._parse_shared_gallery_image(
azure_runbook.shared_gallery
)
sig = self._get_detailed_sig(azure_runbook.shared_gallery)
sig = self._get_sig(azure_runbook.shared_gallery)
generation = _get_gallery_image_generation(sig)
node_space.features.add(features.VhdGenerationSettings(gen=generation))
node_space.features.add(
@ -2863,7 +2956,16 @@ class AzurePlatform(Platform):
node_space.features.add(
features.VhdGenerationSettings(gen=azure_runbook.hyperv_generation)
)
elif azure_runbook.community_gallery_image:
azure_runbook.community_gallery_image = self._parse_community_gallery_image(
azure_runbook.community_gallery_image
)
cgi = self._get_cgi(azure_runbook.community_gallery_image)
generation = _get_gallery_image_generation(cgi)
node_space.features.add(features.VhdGenerationSettings(gen=generation))
node_space.features.add(
features.ArchitectureSettings(arch=cgi.architecture) # type: ignore
)
if node_space.disk:
assert node_space.disk.os_disk_type
if (
@ -2896,6 +2998,11 @@ class AzurePlatform(Platform):
azure_runbook.shared_gallery
)
return self._get_sig_os_disk_size(azure_runbook.shared_gallery)
elif azure_runbook.community_gallery_image:
azure_runbook.community_gallery_image = self._parse_community_gallery_image(
azure_runbook.community_gallery_image
)
return self._get_cgi_os_disk_size(azure_runbook.community_gallery_image)
else:
assert azure_runbook.vhd
assert azure_runbook.vhd.vhd_path
@ -2994,11 +3101,13 @@ def _get_vhd_generation(image_info: VirtualMachineImage) -> int:
return vhd_gen
def _get_gallery_image_generation(shared_image: GalleryImage) -> int:
def _get_gallery_image_generation(
image: Union[GalleryImage, CommunityGalleryImage]
) -> int:
assert (
shared_image.hyper_v_generation
), f"no hyper_v_generation property for image {shared_image.name}"
return int(shared_image.hyper_v_generation.strip("V"))
hasattr(image, "hyper_v_generation") and image.hyper_v_generation
), f"no hyper_v_generation property for image {image.name}"
return int(image.hyper_v_generation.strip("V"))
def _get_disk_size_in_gb(additional_properties: Dict[str, int]) -> int: