Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/spring/HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ Release History
===============
1.1.0
---
* New command `az spring create` has new argument "--ingress-read-timeout" to set ingress read timeout when create Azure Spring App.
* New command `az spring update` has new argument "--ingress-read-timeout" to update ingress read timeout for Azure Spring App.
* Command `az spring create` has new argument "--ingress-read-timeout" to set ingress read timeout when create Azure Spring Apps.
* Command `az spring update` has new argument "--ingress-read-timeout" to update ingress read timeout for Azure Spring Apps.
* Command `az spring create` has new argument "--marketplace-plan-id" to purchase SaaS resource against given plan for Azure Spring Apps.
* Command `az spring app deploy` and `az spring app deployment create` has new argument "--build-cpu" and "--build-memory" to set cpu and memory during build process.

1.0.0
Expand Down
11 changes: 11 additions & 0 deletions src/spring/azext_spring/_constant.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

# pylint: disable=wrong-import-order
# pylint: disable=unused-argument, logging-format-interpolation, protected-access, wrong-import-order, too-many-lines

MARKETPLACE_OFFER_ID = 'azure-spring-cloud-vmware-tanzu-2'
MARKETPLACE_PUBLISHER_ID = 'vmware-inc'
MARKETPLACE_PLAN_ID = 'asa-ent-hr-mtr'
8 changes: 8 additions & 0 deletions src/spring/azext_spring/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@
az spring create -n MyService -g MyResourceGroup --sku Enterprise --enable-application-configuration-service --enable-service-registry --enable-gateway --enable-api-portal
"""

helps['spring list-marketplace-plan'] = """
type: command
short-summary: (Enterprise Tier Only) List Marketplace plan to be purchased.
examples:
- name: List all plans.
text: az spring list-marketplace-plan -o table
"""

helps['spring update'] = """
type: command
short-summary: Update an Azure Spring Apps.
Expand Down
38 changes: 38 additions & 0 deletions src/spring/azext_spring/_marketplace.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

# pylint: disable=wrong-import-order
# pylint: disable=unused-argument, logging-format-interpolation, protected-access, wrong-import-order, too-many-lines
from asyncio.log import logger
from ._constant import (MARKETPLACE_OFFER_ID, MARKETPLACE_PUBLISHER_ID)


def _spring_list_marketplace_plan(cmd, client):
# return get_mgmt_service_client(cli_ctx, AppPlatformManagementClient_20220501preview)
from azure.cli.core.commands.client_factory import get_mgmt_service_client
from .vendored_sdks.marketplace.v2018_08_01_beta import MarketplaceRPService
from .vendored_sdks.marketplace.v2018_08_01_beta.models import Offer

client = get_mgmt_service_client(cmd.cli_ctx, MarketplaceRPService)
offer = client.offer.get('{}.{}'.format(MARKETPLACE_PUBLISHER_ID, MARKETPLACE_OFFER_ID))
offer.plans = [x for x in offer.plans if _is_valid_plan(x)]
return Offer.deserialize(offer).serialize(offer)


def _is_valid_plan(plan):
return plan.availabilities


def transform_marketplace_plan_output(result):
def _table_item_view(plan):
return {
'publisher id': result['properties']['publisherId'],
'product id': result['properties']['offerId'],
'plan id': plan['planId'],
'plan display name': plan['displayName']
}

plans = result['properties']['plans']
return [_table_item_view(plan) for plan in plans]
4 changes: 4 additions & 0 deletions src/spring/azext_spring/_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,10 @@ def load_arguments(self, _):
is_preview=True,
options_list=['--api-portal-instance-count', '--ap-instance'],
help='(Enterprise Tier Only) Number of API portal instances.')
c.argument('marketplace_plan_id',
is_preview=True,
help='(Enterprise Tier Only) Specify a different Marketplace plan to purchase with Spring instance. '
'List all plans by running `az spring list-marketplace-plan -o table`.')

with self.argument_context('spring update') as c:
c.argument('sku', arg_type=sku_type, validator=normalize_sku)
Expand Down
22 changes: 16 additions & 6 deletions src/spring/azext_spring/_validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from knack.log import get_logger
from ._utils import (ApiType, _get_rg_location, _get_file_type, _get_sku_name)
from .vendored_sdks.appplatform.v2020_07_01 import models
from ._constant import (MARKETPLACE_OFFER_ID, MARKETPLACE_PLAN_ID, MARKETPLACE_PUBLISHER_ID)

logger = get_logger(__name__)

Expand Down Expand Up @@ -47,6 +48,7 @@ def validate_sku(cmd, namespace):
_validate_saas_provider(cmd, namespace)
_validate_terms(cmd, namespace)
else:
_check_saas_not_set(cmd, namespace)
_check_tanzu_components_not_enable(cmd, namespace)
normalize_sku(cmd, namespace)

Expand All @@ -56,6 +58,11 @@ def normalize_sku(cmd, namespace):
namespace.sku = models.Sku(name=_get_sku_name(namespace.sku), tier=namespace.sku)


def _check_saas_not_set(cmd, namespace):
if namespace.marketplace_plan_id:
raise InvalidArgumentValueError('--marketplace-plan-id is supported only when --sku=Enterprise')


def _validate_saas_provider(cmd, namespace):
from azure.cli.core.commands.client_factory import get_mgmt_service_client
from azure.cli.core.profiles import ResourceType
Expand All @@ -69,15 +76,18 @@ def _validate_terms(cmd, namespace):
from azure.mgmt.marketplaceordering import MarketplaceOrderingAgreements
from azure.cli.core.commands.client_factory import get_mgmt_service_client
client = get_mgmt_service_client(cmd.cli_ctx, MarketplaceOrderingAgreements).marketplace_agreements
plan_id = namespace.marketplace_plan_id or MARKETPLACE_PLAN_ID
term = client.get(offer_type="virtualmachine",
publisher_id='vmware-inc',
offer_id='azure-spring-cloud-vmware-tanzu-2',
plan_id='tanzu-asc-ent-mtr')
publisher_id=MARKETPLACE_PUBLISHER_ID,
offer_id=MARKETPLACE_OFFER_ID,
plan_id=plan_id)
if not term.accepted:
raise InvalidArgumentValueError('Terms for Azure Spring Apps Enterprise is not accepted.\n'
'Run "az term accept --publisher vmware-inc '
'--product azure-spring-cloud-vmware-tanzu-2 '
'--plan tanzu-asc-ent-mtr" to accept the term.')
'Run "az term accept --publisher {} '
'--product {} '
'--plan {}" to accept the term.'.format(MARKETPLACE_PUBLISHER_ID,
MARKETPLACE_OFFER_ID,
plan_id))


def _check_tanzu_components_not_enable(cmd, namespace):
Expand Down
4 changes: 4 additions & 0 deletions src/spring/azext_spring/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
transform_service_registry_output,
transform_spring_cloud_gateway_output,
transform_api_portal_output)
from ._marketplace import (transform_marketplace_plan_output)
from ._validators_enterprise import (validate_gateway_update, validate_api_portal_update)
from ._app_managed_identity_validator import (validate_app_identity_remove_or_warning,
validate_app_identity_assign_or_warning)
Expand Down Expand Up @@ -91,6 +92,9 @@ def load_command_table(self, _):
with self.command_group('spring', custom_command_type=spring_routing_util,
exception_handler=handle_asc_exception) as g:
g.custom_command('create', 'spring_create', supports_no_wait=True)
g.custom_command('list-marketplace-plan', 'spring_list_marketplace_plan',
is_preview=True,
table_transformer=transform_marketplace_plan_output)

with self.command_group('spring', client_factory=cf_spring_20220501preview,
exception_handler=handle_asc_exception) as g:
Expand Down
16 changes: 16 additions & 0 deletions src/spring/azext_spring/spring_instance.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@

from ._validators import (_parse_sku_name)
from knack.log import get_logger
from ._marketplace import _spring_list_marketplace_plan
from ._constant import (MARKETPLACE_OFFER_ID, MARKETPLACE_PUBLISHER_ID)

logger = get_logger(__name__)

Expand Down Expand Up @@ -58,11 +60,19 @@ def create_service(self,
sku=None,
tags=None,
ingress_read_timeout=None,
marketplace_plan_id=None,
**_):
properties = models.ClusterResourceProperties(
zone_redundant=zone_redundant
)

if marketplace_plan_id:
properties.marketplace_resource = models.MarketplaceResource(
plan=marketplace_plan_id,
product=MARKETPLACE_OFFER_ID,
publisher=MARKETPLACE_PUBLISHER_ID
)

if service_runtime_subnet or app_subnet or reserved_cidr_range:
properties.network_profile = models.NetworkProfile(
service_runtime_subnet_id=service_runtime_subnet,
Expand Down Expand Up @@ -137,6 +147,7 @@ def spring_create(cmd, client, resource_group, name,
enable_api_portal=False,
api_portal_instance_count=None,
ingress_read_timeout=None,
marketplace_plan_id=None,
no_wait=False):
"""
Because Standard/Basic tier vs. Enterprise tier creation are very different. Here routes the command to different
Expand Down Expand Up @@ -165,6 +176,7 @@ def spring_create(cmd, client, resource_group, name,
'gateway_instance_count': gateway_instance_count,
'enable_api_portal': enable_api_portal,
'api_portal_instance_count': api_portal_instance_count,
'marketplace_plan_id': marketplace_plan_id,
'no_wait': no_wait
}

Expand All @@ -180,3 +192,7 @@ def _enable_app_insights(cmd, client, resource_group, name, location, app_insigh
return create_default_buildpack_binding_for_application_insights(cmd, client, resource_group, name,
location, app_insights_key, app_insights,
sampling_rate)


def spring_list_marketplace_plan(cmd, client):
return _spring_list_marketplace_plan(cmd, client)
11 changes: 11 additions & 0 deletions src/spring/azext_spring/tests/latest/test_asa_create.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,17 @@ def test_asc_create_enterprise(self):
self.assertEqual('E0', resource.sku.name)
self.assertEqual('Enterprise', resource.sku.tier)
self.assertEqual(False, resource.properties.zone_redundant)
self.assertIsNone(resource.properties.marketplace_resource)

def test_asc_create_enterprise_with_plan(self):
self._execute('rg', 'asc', sku=self._get_sku('Enterprise'), disable_app_insights=True, marketplace_plan_id='my-plan')
resource = self.created_resource
self.assertEqual('E0', resource.sku.name)
self.assertEqual('Enterprise', resource.sku.tier)
self.assertEqual(False, resource.properties.zone_redundant)
self.assertEqual('my-plan', resource.properties.marketplace_resource.plan)
self.assertEqual('azure-spring-cloud-vmware-tanzu-2', resource.properties.marketplace_resource.product)
self.assertEqual('vmware-inc', resource.properties.marketplace_resource.publisher)


class TestSpringCloudCreateWithAI(BasicTest):
Expand Down
6 changes: 3 additions & 3 deletions src/spring/azext_spring/tests/latest/test_asa_validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -308,20 +308,20 @@ def _mock_not_registered_client(cli_ctx, client_type, **kwargs):
class TestSkuValidator(unittest.TestCase):
@mock.patch('azure.cli.core.commands.client_factory.get_mgmt_service_client', _mock_happy_client)
def test_happy_path(self):
ns = Namespace(sku='Enterprise')
ns = Namespace(sku='Enterprise', marketplace_plan_id=None)
validate_sku(_get_test_cmd(), ns)
self.assertEqual('Enterprise', ns.sku.tier)

@mock.patch('azure.cli.core.commands.client_factory.get_mgmt_service_client', _mock_not_accepted_term_client)
def test_term_not_accept(self):
ns = Namespace(sku='Enterprise')
ns = Namespace(sku='Enterprise', marketplace_plan_id=None)
with self.assertRaises(InvalidArgumentValueError) as context:
validate_sku(_get_test_cmd(), ns)
self.assertTrue('Terms for Azure Spring Apps Enterprise is not accepted.' in str(context.exception))

@mock.patch('azure.cli.core.commands.client_factory.get_mgmt_service_client', _mock_not_registered_client)
def test_provider_not_registered(self):
ns = Namespace(sku='Enterprise')
ns = Namespace(sku='Enterprise', marketplace_plan_id=None)
with self.assertRaises(InvalidArgumentValueError) as context:
validate_sku(_get_test_cmd(), ns)
self.assertTrue('Microsoft.SaaS resource provider is not registered.' in str(context.exception))
Expand Down
16 changes: 16 additions & 0 deletions src/spring/azext_spring/vendored_sdks/marketplace/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# coding=utf-8
# --------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# Code generated by Microsoft (R) AutoRest Code Generator.
# Changes may cause incorrect behavior and will be lost if the code is regenerated.
# --------------------------------------------------------------------------

from ._marketplace_rp_service import MarketplaceRPService
__all__ = ['MarketplaceRPService']

try:
from ._patch import patch_sdk # type: ignore
patch_sdk()
except ImportError:
pass
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# coding=utf-8
# --------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for
# license information.
#
# Code generated by Microsoft (R) AutoRest Code Generator.
# Changes may cause incorrect behavior and will be lost if the code is
# regenerated.
# --------------------------------------------------------------------------
from typing import TYPE_CHECKING

from azure.core.configuration import Configuration
from azure.core.pipeline import policies
from azure.mgmt.core.policies import ARMChallengeAuthenticationPolicy, ARMHttpLoggingPolicy

from ._version import VERSION

if TYPE_CHECKING:
# pylint: disable=unused-import,ungrouped-imports
from typing import Any, Optional

from azure.core.credentials import TokenCredential

class MarketplaceRPServiceConfiguration(Configuration):
"""Configuration for MarketplaceRPService.

Note that all parameters used to create this instance are saved as instance
attributes.

:param credential: Credential needed for the client to connect to Azure.
:type credential: ~azure.core.credentials.TokenCredential
:param market: The Market to use for the request. Default value is "US".
:type market: str
:param include_stop_sold_plans: The Market to use for the request. Default value is "true".
:type include_stop_sold_plans: str
"""

def __init__(
self,
credential, # type: "TokenCredential"
market="US", # type: Optional[str]
include_stop_sold_plans="true", # type: Optional[str]
**kwargs # type: Any
):
# type: (...) -> None
if credential is None:
raise ValueError("Parameter 'credential' must not be None.")
super(MarketplaceRPServiceConfiguration, self).__init__(**kwargs)

self.credential = credential
self.market = market
self.include_stop_sold_plans = include_stop_sold_plans
self.credential_scopes = kwargs.pop('credential_scopes', ['https://management.azure.com/.default'])
kwargs.setdefault('sdk_moniker', 'azure-mgmt-marketplace/{}'.format(VERSION))
self._configure(**kwargs)

def _configure(
self,
**kwargs # type: Any
):
# type: (...) -> None
self.user_agent_policy = kwargs.get('user_agent_policy') or policies.UserAgentPolicy(**kwargs)
self.headers_policy = kwargs.get('headers_policy') or policies.HeadersPolicy(**kwargs)
self.proxy_policy = kwargs.get('proxy_policy') or policies.ProxyPolicy(**kwargs)
self.logging_policy = kwargs.get('logging_policy') or policies.NetworkTraceLoggingPolicy(**kwargs)
self.http_logging_policy = kwargs.get('http_logging_policy') or ARMHttpLoggingPolicy(**kwargs)
self.retry_policy = kwargs.get('retry_policy') or policies.RetryPolicy(**kwargs)
self.custom_hook_policy = kwargs.get('custom_hook_policy') or policies.CustomHookPolicy(**kwargs)
self.redirect_policy = kwargs.get('redirect_policy') or policies.RedirectPolicy(**kwargs)
self.authentication_policy = kwargs.get('authentication_policy')
if self.credential and not self.authentication_policy:
self.authentication_policy = ARMChallengeAuthenticationPolicy(self.credential, *self.credential_scopes, **kwargs)
Loading