Skip to content

Commit 5e5b15c

Browse files
authored
Merge pull request #136 from release-engineering/azure-modular-publish
Improvement: Azure: Allow modular publishing
2 parents 2b2b61b + bd9364a commit 5e5b15c

File tree

3 files changed

+203
-27
lines changed

3 files changed

+203
-27
lines changed

cloudpub/ms_azure/service.py

Lines changed: 79 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -185,19 +185,19 @@ def _wait_for_job_completion(self, job_id: str) -> ConfigureStatus:
185185
log.debug("Job %s succeeded", job_id)
186186
return job_details
187187

188-
def configure(self, resource: AzureResource) -> ConfigureStatus:
188+
def configure(self, resources: List[AzureResource]) -> ConfigureStatus:
189189
"""
190190
Create or update a resource and wait until it's done.
191191
192192
Args:
193-
resource (AzureResource):
194-
The resource to create/modify in Azure.
193+
resources (List[AzureResource]):
194+
The list of resources to create/modify in Azure.
195195
Returns:
196196
dict: The result of job execution
197197
"""
198198
data = {
199199
"$schema": self.CONFIGURE_SCHEMA.format(AZURE_API_VERSION=self.AZURE_API_VERSION),
200-
"resources": [resource.to_json()],
200+
"resources": [x.to_json() for x in resources],
201201
}
202202
if log.isEnabledFor(logging.DEBUG):
203203
log.debug("Data to configure: %s", json.dumps(data, indent=2))
@@ -417,7 +417,9 @@ def diff_offer(self, product: Product, first_target="preview") -> DeepDiff:
417417
remote = self.get_product(product.id, first_target=first_target)
418418
return DeepDiff(remote.to_json(), product.to_json(), exclude_regex_paths=self.DIFF_EXCLUDES)
419419

420-
def submit_to_status(self, product_id: str, status: str) -> ConfigureStatus:
420+
def submit_to_status(
421+
self, product_id: str, status: str, resources: Optional[List[AzureResource]] = None
422+
) -> ConfigureStatus:
421423
"""
422424
Send a submission request to Microsoft with a new Product status.
423425
@@ -426,6 +428,8 @@ def submit_to_status(self, product_id: str, status: str) -> ConfigureStatus:
426428
The product ID to submit the new status.
427429
status (str)
428430
The new status: 'preview' or 'live'
431+
resources (optional(list(AzureRerouce)))
432+
Additional resources for modular push.
429433
Returns:
430434
The response from configure request.
431435
"""
@@ -446,9 +450,12 @@ def submit_to_status(self, product_id: str, status: str) -> ConfigureStatus:
446450

447451
# Update the status with the expected one
448452
submission.target.targetType = status
453+
cfg_res: List[AzureResource] = [submission]
454+
if resources:
455+
log.info("Performing a modular push to \"%s\" for \"%s\"", status, product_id)
456+
cfg_res = resources + cfg_res
449457
log.debug("Set the status \"%s\" to submission.", status)
450-
451-
return self.configure(resource=submission)
458+
return self.configure(resources=cfg_res)
452459

453460
@retry(
454461
wait=wait_fixed(300),
@@ -501,6 +508,54 @@ def get_plan_tech_config(self, product: Product, plan: PlanSummary) -> VMIPlanTe
501508
)
502509
return tconfigs[0] # It should have only one VMIPlanTechConfig per plan.
503510

511+
def get_modular_resources_to_publish(
512+
self, product: Product, tech_config: VMIPlanTechConfig
513+
) -> List[AzureResource]:
514+
"""Return the required resources for a modular publishing.
515+
516+
According to Microsoft docs:
517+
"For a modular publish, all resources are required except for the product level details
518+
(for example, listing, availability, packages, reseller) as applicable to your
519+
product type."
520+
521+
Args:
522+
product (Product): The original product to filter the resources from
523+
tech_config (VMIPlanTechConfig): The updated tech config to publish
524+
525+
Returns:
526+
List[AzureResource]: _description_
527+
"""
528+
# The following resources shouldn't be required:
529+
# -> customer-leads
530+
# -> test-drive
531+
# -> property
532+
# -> *listing*
533+
# -> reseller
534+
# -> price-and-availability-*
535+
# NOTE: The "submission" resource will be already added by the "submit_to_status" method
536+
#
537+
# With that it needs only the related "product" and "plan" resources alongisde the
538+
# updated tech_config
539+
product_id = tech_config.product_id
540+
plan_id = tech_config.plan_id
541+
prod_res = cast(
542+
List[ProductSummary],
543+
[
544+
prd
545+
for prd in self.filter_product_resources(product=product, resource="product")
546+
if prd.id == product_id
547+
],
548+
)[0]
549+
plan_res = cast(
550+
List[PlanSummary],
551+
[
552+
pln
553+
for pln in self.filter_product_resources(product=product, resource="plan")
554+
if pln.id == plan_id
555+
],
556+
)[0]
557+
return [prod_res, plan_res, tech_config]
558+
504559
def _is_submission_in_preview(self, current: ProductSubmission) -> bool:
505560
"""Return True if the latest submission state is "preview", False otherwise.
506561
@@ -528,17 +583,21 @@ def _is_submission_in_preview(self, current: ProductSubmission) -> bool:
528583
stop=stop_after_attempt(3),
529584
reraise=True,
530585
)
531-
def _publish_preview(self, product: Product, product_name: str) -> None:
586+
def _publish_preview(
587+
self, product: Product, product_name: str, resources: Optional[List[AzureResource]] = None
588+
) -> None:
532589
"""
533590
Submit the product to 'preview' if it's not already in this state.
534591
535592
This is required to execute the validation pipeline on Azure side.
536593
537594
Args:
538595
product
539-
The product with changes to publish live
596+
The product with changes to publish to preview
540597
product_name
541598
The product name to display in logs.
599+
resources:
600+
Additional resources for modular push.
542601
"""
543602
# We just want to set the ProductSubmission to 'preview' if it's not in this status.
544603
#
@@ -553,14 +612,14 @@ def _publish_preview(self, product: Product, product_name: str) -> None:
553612
log.info("The product \"%s\" is already set to preview", product_name)
554613
return
555614

556-
res = self.submit_to_status(product_id=product.id, status='preview')
615+
res = self.submit_to_status(product_id=product.id, status='preview', resources=resources)
557616

558617
if res.job_result != 'succeeded' or not self.get_submission_state(
559618
product.id, state="preview"
560619
):
561620
errors = "\n".join(res.errors)
562621
failure_msg = (
563-
f"Failed to submit the product {product.id} to preview. "
622+
f"Failed to submit the product {product_name} ({product.id}) to preview. "
564623
f"Status: {res.job_result} Errors: {errors}"
565624
)
566625
raise RuntimeError(failure_msg)
@@ -587,7 +646,7 @@ def _publish_live(self, product: Product, product_name: str) -> None:
587646
if res.job_result != 'succeeded' or not self.get_submission_state(product.id, state="live"):
588647
errors = "\n".join(res.errors)
589648
failure_msg = (
590-
f"Failed to submit the product {product.id} to live. "
649+
f"Failed to submit the product {product_name} ({product.id}) to live. "
591650
f"Status: {res.job_result} Errors: {errors}"
592651
)
593652
raise RuntimeError(failure_msg)
@@ -685,7 +744,7 @@ def publish(self, metadata: AzurePublishingMetadata) -> None:
685744
metadata.destination,
686745
tgt,
687746
)
688-
self.configure(resource=tech_config)
747+
self.configure(resources=[tech_config])
689748

690749
# 5. Proceed to publishing if it was requested.
691750
# Note: The publishing will only occur if it made changes in disk_version.
@@ -705,7 +764,13 @@ def publish(self, metadata: AzurePublishingMetadata) -> None:
705764
logdiff(self.diff_offer(product))
706765
self.ensure_can_publish(product.id)
707766

708-
self._publish_preview(product, product_name)
767+
# According to the documentation we only need to pass the
768+
# required resources for modular publish on "preview"
769+
# https://learn.microsoft.com/en-us/partner-center/marketplace-offers/product-ingestion-api#method-2-publish-specific-draft-resources-also-known-as-modular-publish # noqa: E501
770+
modular_resources = None
771+
if metadata.modular_push:
772+
modular_resources = self.get_modular_resources_to_publish(product, tech_config)
773+
self._publish_preview(product, product_name, resources=modular_resources)
709774
self._publish_live(product, product_name)
710775

711776
log.info(

cloudpub/ms_azure/utils.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@ def __init__(
5454
check_base_sas_only (bool, optional):
5555
Indicates to skip checking SAS parameters when set as ``True``.
5656
Default to ``False``
57+
modular_push (bool, optional):
58+
Indicate whether to perform a modular push or not.
59+
The modular push causes the effect to only publish
60+
the changed plan instead of the whole offer to preview/live.
61+
Default to ``False``.
5762
**kwargs
5863
Arguments for :class:`~cloudpub.common.PublishingMetadata`.
5964
"""
@@ -64,6 +69,7 @@ def __init__(
6469
self.recommended_sizes = recommended_sizes or []
6570
self.legacy_sku_id = kwargs.pop("legacy_sku_id", None)
6671
self.check_base_sas_only = kwargs.pop("check_base_sas_only", False)
72+
self.modular_push = kwargs.pop("modular_push", None) or False
6773

6874
if generation == "V1" or not support_legacy:
6975
self.legacy_sku_id = None

0 commit comments

Comments
 (0)