Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 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
4 changes: 3 additions & 1 deletion src/spring/HISTORY.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
Release History
===============
Upcoming
1.15.0
---
* Add arguments `--type` and `--git-sub-path` in `spring application-accelerator customized-accelerator create` and `spring application-accelerator customized-accelerator update` for accelerator fragment support.
* Add new argument `--server-version` in `az spring app deploy and az spring app deployment create` to support war file deployment in Standard tier.
* Add argument `--enable-auto-sync` in `az spring certificate add`.
* Add new command `az spring certificate update` to update a certificate.

1.14.3
---
Expand Down
10 changes: 10 additions & 0 deletions src/spring/azext_spring/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,16 @@
text: az spring certificate add --name MyCertName --vault-uri MyKeyVaultUri --vault-certificate-name MyKeyVaultCertName --service MyCluster --resource-group MyResourceGroup
"""

helps['spring certificate update'] = """
type: command
short-summary: Update a certificate in Azure Spring Apps.
examples:
- name: Enable auto sync feature of a key vault certificate in Azure Spring Apps.
text: az spring certificate update --name MyCertName --service MyCluster --resource-group MyResourceGroup --enable-auto-sync true
- name: Disable auto sync feature of a key vault certificate in Azure Spring Apps.
text: az spring certificate update --name MyCertName --service MyCluster --resource-group MyResourceGroup --enable-auto-sync false
"""

helps['spring certificate show'] = """
type: command
short-summary: Show a certificate in Azure Spring Apps.
Expand Down
6 changes: 6 additions & 0 deletions src/spring/azext_spring/_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -677,6 +677,12 @@ def prepare_logs_argument(c):
help='If true, only import public certificate part from key vault.', default=False)
c.argument('public_certificate_file', options_list=['--public-certificate-file', '-f'],
help='A file path for the public certificate to be uploaded')
c.argument('enable_auto_sync', options_list=['--enable-auto-sync'], arg_type=get_three_state_flag(),
help='Whether to automatically synchronize certificate from key vault', default=False)

with self.argument_context('spring certificate update') as c:
c.argument('enable_auto_sync', options_list=['--enable-auto-sync'], arg_type=get_three_state_flag(),
help='Whether to automatically synchronize certificate from key vault')

with self.argument_context('spring certificate list') as c:
c.argument('certificate_type', help='Type of uploaded certificate',
Expand Down
1 change: 1 addition & 0 deletions src/spring/azext_spring/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@ def load_command_table(self, _):
with self.command_group('spring certificate', client_factory=cf_spring,
exception_handler=handle_asc_exception) as g:
g.custom_command('add', 'certificate_add')
g.custom_command('update', 'certificate_update')
g.custom_show_command('show', 'certificate_show', table_transformer=transform_spring_certificate_output)
g.custom_command('list', 'certificate_list', table_transformer=transform_spring_certificate_output)
g.custom_command('remove', 'certificate_remove')
Expand Down
26 changes: 14 additions & 12 deletions src/spring/azext_spring/custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -1341,7 +1341,7 @@ def storage_list_persistent_storage(client, resource_group, service, name):


def certificate_add(cmd, client, resource_group, service, name, only_public_cert=None,
vault_uri=None, vault_certificate_name=None, public_certificate_file=None):
vault_uri=None, vault_certificate_name=None, public_certificate_file=None, enable_auto_sync=None):
if vault_uri is None and public_certificate_file is None:
raise InvalidArgumentValueError("One of --vault-uri and --public-certificate-file should be provided")
if vault_uri is not None and public_certificate_file is not None:
Expand All @@ -1354,10 +1354,11 @@ def certificate_add(cmd, client, resource_group, service, name, only_public_cert
if only_public_cert is None:
only_public_cert = False
properties = models.KeyVaultCertificateProperties(
type="KeyVaultCertificate",
type='KeyVaultCertificate',
vault_uri=vault_uri,
key_vault_cert_name=vault_certificate_name,
exclude_private_key=only_public_cert
exclude_private_key=only_public_cert,
auto_sync='Enabled' if enable_auto_sync is True else 'Disabled'
)
else:
if os.path.exists(public_certificate_file):
Expand All @@ -1375,16 +1376,17 @@ def certificate_add(cmd, client, resource_group, service, name, only_public_cert
)
certificate_resource = models.CertificateResource(properties=properties)

def callback(pipeline_response, deserialized, headers):
return models.CertificateResource.deserialize(json.loads(pipeline_response.http_response.text()))
return client.certificates.begin_create_or_update(resource_group, service, name, certificate_resource)

return client.certificates.begin_create_or_update(
resource_group_name=resource_group,
service_name=service,
certificate_name=name,
certificate_resource=certificate_resource,
cls=callback
)

def certificate_update(cmd, client, resource_group, service, name, enable_auto_sync=None):
certificate_resource = client.certificates.get(resource_group, service, name)

if certificate_resource.properties.type == 'KeyVaultCertificate':
if enable_auto_sync is not None:
certificate_resource.properties.auto_sync = 'Enabled' if enable_auto_sync is True else 'Disabled'

return client.certificates.begin_create_or_update(resource_group, service, name, certificate_resource)


def certificate_show(cmd, client, resource_group, service, name):
Expand Down
76 changes: 76 additions & 0 deletions src/spring/azext_spring/tests/latest/test_asa_certificate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------
import unittest
import os
from ...vendored_sdks.appplatform.v2023_09_01_preview import models
from ..._utils import _get_sku_name
from ...custom import (certificate_add, certificate_update)
try:
import unittest.mock as mock
except ImportError:
from unittest import mock

from azure.cli.core.mock import DummyCli
from azure.cli.core import AzCommandsLoader
from azure.cli.core.commands import AzCliCommand

from knack.log import get_logger

logger = get_logger(__name__)
TEST_DIR = os.path.abspath(os.path.join(os.path.abspath(__file__), '..'))

def _get_test_cmd():
cli_ctx = DummyCli()
cli_ctx.data['subscription_id'] = '00000000-0000-0000-0000-000000000000'
loader = AzCommandsLoader(cli_ctx, resource_type='Microsoft.AppPlatform')
cmd = AzCliCommand(loader, 'test', None)
cmd.command_kwargs = {'resource_type': 'Microsoft.AppPlatform'}
cmd.cli_ctx = cli_ctx
return cmd


class BasicTest(unittest.TestCase):
def _get_basic_mock_client(self, sku='Standard'):
client = mock.MagicMock()
client.services.get.return_value = models.ServiceResource(
sku=models.Sku(
tier=sku,
name=_get_sku_name(sku)
)
)
return client


class CertificateTests(BasicTest):

def test_create_certificate(self):
client = self._get_basic_mock_client()
certificate_add(_get_test_cmd(), client, 'rg', 'asc', 'my-cert',
False, "vault-uri", "kv-cert-name")
args = client.certificates.begin_create_or_update.call_args_list
self.assertEqual(1, len(args))
self.assertEqual(4, len(args[0][0]))
self.assertEqual(('rg', 'asc', 'my-cert'), args[0][0][0:3])
resource = args[0][0][3]
self.assertEqual('vault-uri', resource.properties.vault_uri)
self.assertEqual('kv-cert-name', resource.properties.key_vault_cert_name)

def test_update_certificate(self):
client = self._get_basic_mock_client()
client.certificates.get.return_value = models.CertificateResource(
properties=models.KeyVaultCertificateProperties(
type="KeyVaultCertificate",
vault_uri="vault-uri",
key_vault_cert_name="kv-cert-name",
exclude_private_key=False,
auto_sync="Disabled")
)
certificate_update(_get_test_cmd(), client, 'rg', 'asc', 'my-cert', True)
args = client.certificates.begin_create_or_update.call_args_list
self.assertEqual(1, len(args))
self.assertEqual(4, len(args[0][0]))
self.assertEqual(('rg', 'asc', 'my-cert'), args[0][0][0:3])
resource = args[0][0][3]
self.assertEqual("Enabled", resource.properties.auto_sync)
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
build_list_globally_enabled_apms_request,
build_list_request,
build_list_supported_apm_types_request,
build_list_supported_server_versions_request,
build_list_test_keys_request,
build_regenerate_test_key_request,
build_start_request,
Expand Down Expand Up @@ -2283,3 +2284,98 @@ async def get_next(next_link=None):
list.metadata = {
"url": "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.AppPlatform/Spring"
}

@distributed_trace
def list_supported_server_versions(
self, resource_group_name: str, service_name: str, **kwargs: Any
) -> AsyncIterable["_models.SupportedServerVersion"]:
"""Lists all of the available server versions supported by Microsoft.AppPlatform provider.

:param resource_group_name: The name of the resource group that contains the resource. You can
obtain this value from the Azure Resource Manager API or the portal. Required.
:type resource_group_name: str
:param service_name: The name of the Service resource. Required.
:type service_name: str
:keyword callable cls: A custom type or function that will be passed the direct response
:return: An iterator like instance of either SupportedServerVersion or the result of
cls(response)
:rtype:
~azure.core.async_paging.AsyncItemPaged[~azure.mgmt.appplatform.v2023_09_01_preview.models.SupportedServerVersion]
:raises ~azure.core.exceptions.HttpResponseError:
"""
_headers = kwargs.pop("headers", {}) or {}
_params = case_insensitive_dict(kwargs.pop("params", {}) or {})

api_version: str = kwargs.pop(
"api_version", _params.pop("api-version", self._api_version or "2023-09-01-preview")
)
cls: ClsType[_models.SupportedServerVersions] = kwargs.pop("cls", None)

error_map = {
401: ClientAuthenticationError,
404: ResourceNotFoundError,
409: ResourceExistsError,
304: ResourceNotModifiedError,
}
error_map.update(kwargs.pop("error_map", {}) or {})

def prepare_request(next_link=None):
if not next_link:

request = build_list_supported_server_versions_request(
resource_group_name=resource_group_name,
service_name=service_name,
subscription_id=self._config.subscription_id,
api_version=api_version,
template_url=self.list_supported_server_versions.metadata["url"],
headers=_headers,
params=_params,
)
request = _convert_request(request)
request.url = self._client.format_url(request.url)

else:
# make call to next link with the client's api-version
_parsed_next_link = urllib.parse.urlparse(next_link)
_next_request_params = case_insensitive_dict(
{
key: [urllib.parse.quote(v) for v in value]
for key, value in urllib.parse.parse_qs(_parsed_next_link.query).items()
}
)
_next_request_params["api-version"] = self._config.api_version
request = HttpRequest(
"GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params
)
request = _convert_request(request)
request.url = self._client.format_url(request.url)
request.method = "GET"
return request

async def extract_data(pipeline_response):
deserialized = self._deserialize("SupportedServerVersions", pipeline_response)
list_of_elem = deserialized.value
if cls:
list_of_elem = cls(list_of_elem) # type: ignore
return deserialized.next_link or None, AsyncList(list_of_elem)

async def get_next(next_link=None):
request = prepare_request(next_link)

_stream = False
pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access
request, stream=_stream, **kwargs
)
response = pipeline_response.http_response

if response.status_code not in [200]:
map_error(status_code=response.status_code, response=response, error_map=error_map)
raise HttpResponseError(response=response, error_format=ARMErrorFormat)

return pipeline_response

return AsyncItemPaged(get_next, extract_data)

list_supported_server_versions.metadata = {
"url": "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.AppPlatform/Spring/{serviceName}/supportedServerVersions"
}
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,8 @@
from ._models_py3 import SupportedBuildpackResourceProperties
from ._models_py3 import SupportedBuildpacksCollection
from ._models_py3 import SupportedRuntimeVersion
from ._models_py3 import SupportedServerVersion
from ._models_py3 import SupportedServerVersions
from ._models_py3 import SupportedStackResource
from ._models_py3 import SupportedStackResourceProperties
from ._models_py3 import SupportedStacksCollection
Expand All @@ -256,7 +258,6 @@
from ._models_py3 import WeeklyMaintenanceScheduleConfiguration

from ._app_platform_management_client_enums import ActionType
from ._app_platform_management_client_enums import ApiPortalApiTryOutEnabledState
from ._app_platform_management_client_enums import ApiPortalProvisioningState
from ._app_platform_management_client_enums import ApmProvisioningState
from ._app_platform_management_client_enums import ApmType
Expand Down Expand Up @@ -294,6 +295,7 @@
from ._app_platform_management_client_enums import GitImplementation
from ._app_platform_management_client_enums import HTTPSchemeType
from ._app_platform_management_client_enums import KPackBuildStageProvisioningState
from ._app_platform_management_client_enums import KeyVaultCertificateAutoSync
from ._app_platform_management_client_enums import LastModifiedByType
from ._app_platform_management_client_enums import ManagedIdentityType
from ._app_platform_management_client_enums import MonitoringSettingState
Expand Down Expand Up @@ -551,6 +553,8 @@
"SupportedBuildpackResourceProperties",
"SupportedBuildpacksCollection",
"SupportedRuntimeVersion",
"SupportedServerVersion",
"SupportedServerVersions",
"SupportedStackResource",
"SupportedStackResourceProperties",
"SupportedStacksCollection",
Expand All @@ -568,7 +572,6 @@
"WarUploadedUserSourceInfo",
"WeeklyMaintenanceScheduleConfiguration",
"ActionType",
"ApiPortalApiTryOutEnabledState",
"ApiPortalProvisioningState",
"ApmProvisioningState",
"ApmType",
Expand Down Expand Up @@ -606,6 +609,7 @@
"GitImplementation",
"HTTPSchemeType",
"KPackBuildStageProvisioningState",
"KeyVaultCertificateAutoSync",
"LastModifiedByType",
"ManagedIdentityType",
"MonitoringSettingState",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,6 @@ class ActionType(str, Enum, metaclass=CaseInsensitiveEnumMeta):
INTERNAL = "Internal"


class ApiPortalApiTryOutEnabledState(str, Enum, metaclass=CaseInsensitiveEnumMeta):
"""Indicates whether the API try-out feature is enabled or disabled. When enabled, users can try
out the API by sending requests and viewing responses in API portal. When disabled, users
cannot try out the API.
"""

ENABLED = "Enabled"
DISABLED = "Disabled"


class ApiPortalProvisioningState(str, Enum, metaclass=CaseInsensitiveEnumMeta):
"""State of the API portal."""

Expand Down Expand Up @@ -366,6 +356,13 @@ class HTTPSchemeType(str, Enum, metaclass=CaseInsensitiveEnumMeta):
HTTPS = "HTTPS"


class KeyVaultCertificateAutoSync(str, Enum, metaclass=CaseInsensitiveEnumMeta):
"""Indicates whether to automatically synchronize certificate from key vault or not."""

DISABLED = "Disabled"
ENABLED = "Enabled"


class KPackBuildStageProvisioningState(str, Enum, metaclass=CaseInsensitiveEnumMeta):
"""The provisioning state of this build stage resource."""

Expand Down
Loading