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
12 changes: 10 additions & 2 deletions src/azure-cli/azure/cli/command_modules/appservice/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'] = """
Expand All @@ -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.
Expand Down
14 changes: 14 additions & 0 deletions src/azure-cli/azure/cli/command_modules/appservice/_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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():
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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__)


Expand Down Expand Up @@ -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()

Expand All @@ -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,
Expand All @@ -211,14 +213,45 @@ 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,
resource_group_name=resource_group_name, name=name,
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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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()
Expand All @@ -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)

Expand Down Expand Up @@ -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'
Expand All @@ -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)
Expand Down
8 changes: 8 additions & 0 deletions src/azure-cli/azure/cli/command_modules/appservice/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down