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
)