@@ -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 (
0 commit comments