diff --git a/src/azure-cli/azure/cli/command_modules/appservice/_help.py b/src/azure-cli/azure/cli/command_modules/appservice/_help.py index ea325b2d804..86564f748a4 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/_help.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/_help.py @@ -2293,12 +2293,12 @@ text: az staticwebapp list """ -helps['staticwebapp browse'] = """ +helps['staticwebapp show'] = """ type: command short-summary: Show details of a static app. examples: - name: Show static app in a subscription. - text: az staticwebapp browse -n MyStaticAppName + text: az staticwebapp show -n MyStaticAppName """ helps['staticwebapp create'] = """ @@ -2310,6 +2310,14 @@ -s https://github.com/JohnDoe/my-first-static-web-app -l WestUs2 -b master """ +helps['staticwebapp update'] = """ + type: command + short-summary: Update a static app. Return the app updated. + examples: + - name: Update static app to standard sku. + text: az staticwebapp update -n MyStaticAppName --sku Standard +""" + helps['staticwebapp disconnect'] = """ type: command short-summary: Disconnect source control to enable connecting to a different repo. diff --git a/src/azure-cli/azure/cli/command_modules/appservice/_params.py b/src/azure-cli/azure/cli/command_modules/appservice/_params.py index 76c36c0a333..65fe9378372 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/_params.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/_params.py @@ -67,6 +67,11 @@ def load_arguments(self, _): help='The Isolated pricing tiers, e.g., I1 (Isolated Small), I2 (Isolated Medium), I3 (Isolated Large)', arg_type=get_enum_type(['I1', 'I2', 'I3'])) + static_web_app_sku_arg_type = CLIArgumentType( + help='The pricing tiers for Static Web App', + arg_type=get_enum_type(['Free', 'Standard']) + ) + functionapp_runtime_strings, functionapp_runtime_to_version_strings = _get_functionapp_runtime_versions() # use this hidden arg to give a command the right instance, that functionapp commands @@ -1013,15 +1018,24 @@ def load_arguments(self, _): with self.argument_context('staticwebapp create') as c: c.argument('location', arg_type=get_location_type(self.cli_ctx)) c.argument('tags', arg_type=tags_type) + c.argument('sku', arg_type=static_web_app_sku_arg_type) c.argument('app_location', options_list=['--app-location'], help="Location of your application code. For example, '/' represents the root of your app, " "while '/app' represents a directory called 'app'") c.argument('api_location', options_list=['--api-location'], help="Location of your Azure Functions code. For example, '/api' represents a folder called 'api'.") c.argument('app_artifact_location', options_list=['--app-artifact-location'], + help="The path of your build output relative to your apps location. For example, setting a value " + "of 'build' when your app location is set to '/app' will cause the content at '/app/build' to " + "be served.", + deprecate_info=c.deprecate(expiration='2.22.1')) + c.argument('output_location', options_list=['--output-location'], help="The path of your build output relative to your apps location. For example, setting a value " "of 'build' when your app location is set to '/app' will cause the content at '/app/build' to " "be served.") + with self.argument_context('staticwebapp update') as c: + c.argument('tags', arg_type=tags_type) + c.argument('sku', arg_type=static_web_app_sku_arg_type) def _get_functionapp_runtime_versions(): diff --git a/src/azure-cli/azure/cli/command_modules/appservice/commands.py b/src/azure-cli/azure/cli/command_modules/appservice/commands.py index 6d027fe0bee..e7549eca506 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/commands.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/commands.py @@ -427,11 +427,12 @@ def load_command_table(self, _): with self.command_group('staticwebapp', custom_command_type=staticsite_sdk, is_preview=True) as g: g.custom_command('list', 'list_staticsites') - g.custom_command('browse', 'show_staticsite') + g.custom_show_command('show', 'show_staticsite') g.custom_command('create', 'create_staticsites', supports_no_wait=True) g.custom_command('delete', 'delete_staticsite', supports_no_wait=True, confirmation=True) g.custom_command('disconnect', 'disconnect_staticsite', supports_no_wait=True) g.custom_command('reconnect', 'reconnect_staticsite', supports_no_wait=True) + g.custom_command('update', 'update_staticsite', supports_no_wait=True) with self.command_group('staticwebapp environment', custom_command_type=staticsite_sdk, is_preview=True) as g: g.custom_command('list', 'list_staticsite_environments') diff --git a/src/azure-cli/azure/cli/command_modules/appservice/static_sites.py b/src/azure-cli/azure/cli/command_modules/appservice/static_sites.py index 67a5e932ccd..c74e0be2a15 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/static_sites.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/static_sites.py @@ -8,6 +8,8 @@ from knack.util import CLIError from knack.log import get_logger +from .utils import normalize_sku_for_staticapp + logger = get_logger(__name__) @@ -189,8 +191,8 @@ def update_staticsite_users(cmd, name, roles, authentication_provider=None, user def create_staticsites(cmd, resource_group_name, name, location, source, branch, token=None, - app_location='.', api_location='.', app_artifact_location='.github/workflows', - tags=None, no_wait=False): + app_location='.', api_location='.', output_location='.github/workflows', + tags=None, no_wait=False, sku='Free'): if not token: _raise_missing_token_suggestion() @@ -200,9 +202,9 @@ def create_staticsites(cmd, resource_group_name, name, location, build = StaticSiteBuildProperties( app_location=app_location, api_location=api_location, - app_artifact_location=app_artifact_location) + app_artifact_location=output_location) - sku = SkuDescription(name='Free', tier='Free') + sku_def = SkuDescription(name=normalize_sku_for_staticapp(sku), tier=normalize_sku_for_staticapp(sku)) staticsite_deployment_properties = StaticSiteARMResource( location=location, @@ -211,7 +213,7 @@ def create_staticsites(cmd, resource_group_name, name, location, branch=branch, repository_token=token, build_properties=build, - sku=sku) + sku=sku_def) client = _get_staticsites_client_factory(cmd.cli_ctx) return sdk_no_wait(no_wait, client.create_or_update_static_site, @@ -219,6 +221,37 @@ def create_staticsites(cmd, resource_group_name, name, location, static_site_envelope=staticsite_deployment_properties) +def update_staticsite(cmd, name, source=None, branch=None, token=None, + tags=None, sku=None, no_wait=False): + existing_staticsite = show_staticsite(cmd, name) + if not existing_staticsite: + raise CLIError("No static web app found with name {0}".format(name)) + + if tags is not None: + existing_staticsite.tags = tags + + StaticSiteARMResource, SkuDescription = cmd.get_models('StaticSiteARMResource', 'SkuDescription') + + sku_def = None + if sku is not None: + sku_def = SkuDescription(name=normalize_sku_for_staticapp(sku), tier=normalize_sku_for_staticapp(sku)) + + staticsite_deployment_properties = StaticSiteARMResource( + location=existing_staticsite.location, + tags=existing_staticsite.tags, + repository_url=source or existing_staticsite.repository_url, + branch=branch or existing_staticsite.branch, + repository_token=token or existing_staticsite.repository_token, + build_properties=existing_staticsite.build_properties, + sku=sku_def or existing_staticsite.sku) + + client = _get_staticsites_client_factory(cmd.cli_ctx) + resource_group_name = _get_resource_group_name_of_staticsite(client, name) + return sdk_no_wait(no_wait, client.update_static_site, + resource_group_name=resource_group_name, name=name, + static_site_envelope=staticsite_deployment_properties) + + def delete_staticsite(cmd, name, resource_group_name=None, no_wait=False): client = _get_staticsites_client_factory(cmd.cli_ctx) if not resource_group_name: diff --git a/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_staticapp_commands_thru_mock.py b/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_staticapp_commands_thru_mock.py index bef8878ce45..f650ae17534 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_staticapp_commands_thru_mock.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_staticapp_commands_thru_mock.py @@ -10,7 +10,7 @@ reconnect_staticsite, list_staticsite_environments, show_staticsite_environment, list_staticsite_domains, \ set_staticsite_domain, delete_staticsite_domain, list_staticsite_functions, list_staticsite_function_app_settings, \ set_staticsite_function_app_settings, delete_staticsite_function_app_settings, list_staticsite_users, \ - invite_staticsite_users, update_staticsite_users + invite_staticsite_users, update_staticsite_users, update_staticsite class TestStaticAppCommands(unittest.TestCase): @@ -88,13 +88,13 @@ def test_create_staticapp(self): self.mock_cmd.get_models.return_value = StaticSiteARMResource, StaticSiteBuildProperties, SkuDescription app_location = './src' api_location = './api/' - app_artifact_location = '/.git/' + output_location = '/.git/' tags = {'key1': 'value1'} create_staticsites( self.mock_cmd, self.rg1, self.name1, self.location1, self.source1, self.branch1, self.token1, - app_location=app_location, api_location=api_location, app_artifact_location=app_artifact_location, + app_location=app_location, api_location=api_location, output_location=output_location, tags=tags) self.staticapp_client.create_or_update_static_site.assert_called_once() @@ -108,21 +108,78 @@ def test_create_staticapp(self): self.assertEqual('Free', arg_list["static_site_envelope"].sku.name) self.assertEqual(app_location, arg_list["static_site_envelope"].build_properties.app_location) self.assertEqual(api_location, arg_list["static_site_envelope"].build_properties.api_location) - self.assertEqual(app_artifact_location, arg_list["static_site_envelope"].build_properties.app_artifact_location) + self.assertEqual(output_location, arg_list["static_site_envelope"].build_properties.app_artifact_location) + + def test_create_staticapp_with_standard_sku(self): + from azure.mgmt.web.models import StaticSiteARMResource, StaticSiteBuildProperties, SkuDescription + self.mock_cmd.get_models.return_value = StaticSiteARMResource, StaticSiteBuildProperties, SkuDescription + + create_staticsites( + self.mock_cmd, self.rg1, self.name1, self.location1, + self.source1, self.branch1, self.token1, sku='standard') + + self.staticapp_client.create_or_update_static_site.assert_called_once() + arg_list = self.staticapp_client.create_or_update_static_site.call_args.kwargs + self.assertEqual('Standard', arg_list["static_site_envelope"].sku.name) def test_create_staticapp_missing_token(self): app_location = './src' api_location = './api/' - app_artifact_location = '/.git/' + output_location = '/.git/' tags = {'key1': 'value1'} with self.assertRaises(CLIError): create_staticsites( self.mock_cmd, self.rg1, self.name1, self.location1, self.source1, self.branch1, - app_location=app_location, api_location=api_location, app_artifact_location=app_artifact_location, + app_location=app_location, api_location=api_location, output_location=output_location, tags=tags) + def test_update_staticapp(self): + from azure.mgmt.web.models import StaticSiteARMResource, SkuDescription + self.mock_cmd.get_models.return_value = StaticSiteARMResource, SkuDescription + self.staticapp_client.get_static_site.return_value = self.app1 + self.staticapp_client.list.return_value = [self.app1, self.app2] + tags = {'key1': 'value1'} + sku = 'Standard' + + update_staticsite(self.mock_cmd, self.name1, self.source2, self.branch2, self.token2, tags=tags, sku=sku) + + self.staticapp_client.update_static_site.assert_called_once() + arg_list = self.staticapp_client.update_static_site.call_args.kwargs + self.assertEqual(self.name1, arg_list["name"]) + self.assertEqual(self.source2, arg_list["static_site_envelope"].repository_url) + self.assertEqual(self.branch2, arg_list["static_site_envelope"].branch) + self.assertEqual(self.token2, arg_list["static_site_envelope"].repository_token) + self.assertEqual(tags, arg_list["static_site_envelope"].tags) + self.assertEqual(sku, arg_list["static_site_envelope"].sku.name) + + def test_update_staticapp_with_no_values_passed_in(self): + from azure.mgmt.web.models import StaticSiteARMResource, SkuDescription + self.mock_cmd.get_models.return_value = StaticSiteARMResource, SkuDescription + self.staticapp_client.get_static_site.return_value = self.app1 + self.staticapp_client.list.return_value = [self.app1, self.app2] + + update_staticsite(self.mock_cmd, self.name1) + + self.staticapp_client.update_static_site.assert_called_once() + arg_list = self.staticapp_client.update_static_site.call_args.kwargs + self.assertEqual(self.name1, arg_list["name"]) + self.assertEqual(self.source1, arg_list["static_site_envelope"].repository_url) + self.assertEqual(self.branch1, arg_list["static_site_envelope"].branch) + self.assertEqual(self.token1, arg_list["static_site_envelope"].repository_token) + self.assertEqual(self.app1.tags, arg_list["static_site_envelope"].tags) + self.assertEqual('Free', arg_list["static_site_envelope"].sku.name) + + def test_update_staticapp_not_exist(self): + from azure.mgmt.web.models import StaticSiteARMResource, SkuDescription + self.mock_cmd.get_models.return_value = StaticSiteARMResource, SkuDescription + self.staticapp_client.get_static_site.return_value = self.app1 + self.staticapp_client.list.return_value = [self.app1, self.app2] + + with self.assertRaises(CLIError): + update_staticsite(self.mock_cmd, self.name1_not_exist) + def test_disconnect_staticapp_with_resourcegroup(self): disconnect_staticsite(self.mock_cmd, self.name1, self.rg1) @@ -471,7 +528,7 @@ def _set_up_fake_apps(self): self.hostname1 = 'www.app1.com' self.app1 = _contruct_static_site_object( self.rg1, self.name1, self.location1, - self.source1, self.branch1) + self.source1, self.branch1, self.token1) self.rg2 = 'rg2' self.name2 = 'name2' @@ -483,12 +540,17 @@ def _set_up_fake_apps(self): self.hostname1 = 'www.app2.com' self.app2 = _contruct_static_site_object( self.rg2, self.name2, self.location2, - self.source2, self.branch2) + self.source2, self.branch2, self.token2) -def _contruct_static_site_object(rg, app_name, location, source, branch): - from azure.mgmt.web.models import StaticSiteARMResource - app = StaticSiteARMResource(location=location, repository_url=source, branch=branch) +def _contruct_static_site_object(rg, app_name, location, source, branch, token): + from azure.mgmt.web.models import StaticSiteARMResource, SkuDescription + app = StaticSiteARMResource( + location=location, + repository_url=source, + branch=branch, + repository_token=token, + sku=SkuDescription(name='Free', tier='Free')) app.name = app_name app.id = \ "/subscriptions/sub/resourceGroups/{}/providers/Microsoft.Web/staticSites/{}".format(rg, app_name) diff --git a/src/azure-cli/azure/cli/command_modules/appservice/utils.py b/src/azure-cli/azure/cli/command_modules/appservice/utils.py index 5fc8eceeca1..a0e4caf91aa 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/utils.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/utils.py @@ -53,6 +53,14 @@ def get_sku_name(tier): # pylint: disable=too-many-return-statements raise CLIError("Invalid sku(pricing tier), please refer to command help for valid values") +def normalize_sku_for_staticapp(sku): + if sku.lower() == 'free': + return 'Free' + if sku.lower() == 'standard': + return 'Standard' + raise CLIError("Invalid sku(pricing tier), please refer to command help for valid values") + + def retryable_method(retries=3, interval_sec=5, excpt_type=Exception): def decorate(func): def call(*args, **kwargs):