partnercenter/azext_partnercenter/clients/offer_submission_client.py (172 lines of code) (raw):

# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- # pylint: disable=line-too-long import datetime import requests from knack.cli import CLIError from azext_partnercenter.models.pending_update_info import PendingUpdateInfo from azext_partnercenter.models.offer_submission import OfferSubmission from azext_partnercenter.models.application_submission import ApplicationSubmission from azext_partnercenter.models.type_value import TypeValue from azext_partnercenter.models.submission_variant_resource import SubmissionVariantResource from azext_partnercenter.models.submission_publish_option import SubmissionPublishOption from azext_partnercenter.clients import OfferClient from azext_partnercenter.vendored_sdks.v1.partnercenter.model.microsoft_ingestion_api_models_submissions_submission_creation_request import ( MicrosoftIngestionApiModelsSubmissionsSubmissionCreationRequest) from azext_partnercenter.vendored_sdks.v1.partnercenter.model.microsoft_ingestion_api_models_submissions_submission import ( MicrosoftIngestionApiModelsSubmissionsSubmission) from ._base_client import BaseClient class OfferSubmissionClient(BaseClient): def __init__(self, cli_ctx, *_): super().__init__(cli_ctx, *_) self._offer_client = OfferClient(cli_ctx, *_) def get(self, offer_external_id, submission_id): offer = self._offer_client.get(offer_external_id) if offer.type == "AzureContainer": result = self._graph_api_client.get_submission(offer.resource.durable_id, submission_id) return self._map_submission(result) if offer.type == "AzureApplication": result = self._sdk.submission_client.products_product_id_submissions_submission_id_get(offer.resource.durable_id, submission_id, self._get_access_token()) return self._map_application_submission(result) raise CLIError("Only AzureContainer and AzureApplication offers are supported for submission commands at this time") def list(self, offer_external_id): offer = self._offer_client.get(offer_external_id) if offer.type == "AzureContainer": result = self._graph_api_client.get_submissions(offer.resource.durable_id) return list(map(self._map_submission, result)) if offer.type == "AzureApplication": result = self._sdk.submission_client.products_product_id_submissions_get(offer.resource.durable_id, self._get_access_token()) return list(map(self._map_application_submission, result.value)) raise CLIError("Only AzureContainer and AzureApplication offers are supported for submission commands at this time") def _get_offer_draft_instance(self, offer_durable_id, module): branches = self._sdk.branches_client.products_product_id_branches_get_by_module_modulemodule_get( offer_durable_id, module, self._get_access_token() ) if len(branches.value) == 0: return None variant_package_branch = next((b for b in branches.value if not hasattr(b, 'variant_id')), None) return variant_package_branch def _get_reseller_configuration(self, offer_external_id): # currently using a raw http client for ResellerConfiguration because the SDK does not support it # it is not listed as an avaialbile module in the openapi spec but the REST API does support it url = f"https://api.partner.microsoft.com/v1.0/ingestion/products/{offer_external_id}/branches/getByModule(module=ResellerConfiguration)" bearer_token = f"Bearer {self._get_access_token()}" headers = { "Content-Type": "application/json", "Authorization": bearer_token } response = requests.get(url, headers=headers, timeout=60) if response.status_code != 200: raise CLIError(f"Failed to get offer branches for {offer_external_id}, status code: {response.status_code}") reseller_configuration = response.json().get("value") if not reseller_configuration: return None reseller_configuration_obj = reseller_configuration[0] current_draft_instance_id = reseller_configuration_obj.get("currentDraftInstanceID") return current_draft_instance_id def _map_application_submission(self, submission): return ApplicationSubmission( id=submission.id if hasattr(submission, 'id') else None, resource_type=submission.resource_type if hasattr(submission, 'resource_type') else None, state=submission.state if hasattr(submission, 'state') else None, substate=submission.substate if hasattr(submission, 'substate') else None, targets=submission.targets if hasattr(submission, 'targets') else [], resources=self._map_list_to_type_value(submission.resources) if hasattr(submission, 'resources') else [], variant_resources=self._map_list_to_variant_resources(submission.variant_resources) if hasattr(submission, 'variant_resources') else [], publish_option=self._map_submission_publish_option(submission.publish_option) if hasattr(submission, 'publish_option') else None, published_time_in_utc=submission.published_time_in_utc.isoformat() if hasattr(submission, 'published_time_in_utc') else None, pending_update_info=self._map_pending_update_info(submission.pending_update_info) if hasattr(submission, 'pending_update_info') else None, extended_properties=self._map_list_to_type_value(submission.extended_properties) if hasattr(submission, 'extended_properties') else [], release_number=submission.release_number if hasattr(submission, 'release_number') else 0, friendly_name=submission.friendly_name if hasattr(submission, 'friendly_name') else None, are_resources_ready=submission.are_resources_ready if hasattr(submission, 'are_resources_ready') else False ) def _map_submission_publish_option(self, publish_option): return SubmissionPublishOption( release_time_in_utc=publish_option.release_time_in_utc if hasattr(publish_option, 'release_time_in_utc') else None, is_manual_publish=publish_option.is_manual_publish if hasattr(publish_option, 'is_manual_publish') else False, is_auto_promote=publish_option.is_auto_promote if hasattr(publish_option, 'is_auto_promote') else False, certification_notes=publish_option.certification_notes if hasattr(publish_option, 'certification_notes') else None ) def _map_pending_update_info(self, pending_update_info): return PendingUpdateInfo( update_type=pending_update_info.update_type if hasattr(pending_update_info, 'update_type') else None, status=pending_update_info.status if hasattr(pending_update_info, 'status') else None, href=pending_update_info.href if hasattr(pending_update_info, 'href') else None, failure_reason=pending_update_info.failure_reason if hasattr(pending_update_info, 'failure_reason') else None) def _map_list_to_type_value(self, type_value_list): return [TypeValue(type=type_value.type, value=type_value.value) for type_value in type_value_list] def _get_managed_application_variants(self, durable_id): variants = self._sdk.variant_client.products_product_id_variants_get(durable_id, self._get_access_token()) return {v.get("id") for v in variants.value if v.get("resourceType") == "AzureSkuVariant" and v.get("subType") in ("managed-application", "solution-template")} def _map_list_to_variant_resources(self, variant_resource_list): return [SubmissionVariantResource(variant_id=variant_resource.variant_id, resources=self._map_list_to_type_value(variant_resource.resources)) for variant_resource in variant_resource_list] def delete(self, offer_external_id, submission_id): offer = self._offer_client.get(offer_external_id) # if offer.type == "AzureContainer": # return self._graph_api_client.delete_submission(submission_id, offer.resource.durable_id) if offer.type == "AzureApplication": delete_result = self._sdk.submission_client.products_product_id_submissions_submission_id_delete(offer.resource.durable_id, submission_id, self._get_access_token()) print(f"Delete result: {delete_result}") return delete_result raise CLIError("Only AzureApplication offers are supported for submission delete commands") def publish(self, offer_external_id, submission_id, target): offer = self._offer_client.get(offer_external_id) if offer.type == "AzureContainer": return self._graph_api_client.publish_submission(target, offer.resource.durable_id, submission_id) if offer.type == "AzureApplication": managed_application_variants = self._get_managed_application_variants(offer.resource.durable_id) resources, variant_resources_list = self._get_resources_and_variant_resources(offer.resource.durable_id, managed_application_variants) reseller_instance_id = self._get_reseller_configuration(offer.resource.durable_id) resources.append({"type": "ResellerConfiguration", "value": reseller_instance_id}) offer_submission_dict = self._get_offer_submission_dictionary(resources, variant_resources_list) offer_creation_request = MicrosoftIngestionApiModelsSubmissionsSubmissionCreationRequest(**offer_submission_dict) result = self._sdk.submission_client.products_product_id_submissions_post( offer.resource.durable_id, self._get_access_token(), microsoft_ingestion_api_models_submissions_submission_creation_request=offer_creation_request ) return self._map_application_submission(result) raise CLIError("Only AzureContainer and AzureApplication offers are supported for publishing") def _get_offer_submission_dictionary(self, resources, variant_resources_list): offer_submission_dict = { "resourceType": "SubmissionCreationRequest", "targets": [ { "type": "Scope", "value": "preview" } ], "resources": resources, "variantResources": variant_resources_list, "publishOption": { "releaseTimeInUtc": datetime.datetime.utcnow().isoformat(), "isManualPublish": True, "isAutoPromote": False, "certificationNotes": "Submission automatically generated" }, "extendedProperties": [] } return offer_submission_dict def _get_resources_and_variant_resources(self, durable_id, managed_application_variants): resources = [] variant_resources_dict = {} modules = ["Availability", "Listing", "Package", "Property"] for m in modules: branches = self._sdk.branches_client.products_product_id_branches_get_by_module_modulemodule_get( durable_id, m, self._get_access_token() ) for b in branches.value: resource = {"type": m, "value": b.current_draft_instance_id} if not hasattr(b, 'variant_id'): resources.append(resource) else: variant_id = getattr(b, 'variant_id') if variant_id in managed_application_variants: variant_resources_dict.setdefault(variant_id, []).append(resource) variant_resources_list = [{"variantID": variant_id, "resources": resources} for variant_id, resources in variant_resources_dict.items()] return resources, variant_resources_list @staticmethod def _map_submission(s: MicrosoftIngestionApiModelsSubmissionsSubmission) -> OfferSubmission: return OfferSubmission( id=s.id.root.split('/')[-1], offer_id=s.product.root.root.split('/')[-1], lifecycle_state=s.lifecycle_state, target=s.target.target_type, status=s.status, result=s.result, created=s.created )