Skip to content
Open
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
11 changes: 8 additions & 3 deletions src/azure-cli/azure/cli/command_modules/appservice/_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,17 +135,22 @@ def load_arguments(self, _):
local_context_attribute=LocalContextAttribute(name='ase_name', actions=[LocalContextAction.GET]))
c.argument('sku', arg_type=sku_arg_type)
c.argument('is_linux', arg_type=get_three_state_flag(), default=None, required=False,
help='Host web app on Linux worker. Defaults to true unless --hyper-v is specified. '
help='Host web app on Linux worker. Defaults to true unless --hyper-v or '
'--is-managed-instance is specified. '
'Use "--is-linux false" to create a Windows plan.')
c.argument('hyper_v', action='store_true', required=False, help='Host Windows Container Web App on Hyper-V worker.')
c.argument('hyper_v', action='store_true', required=False,
help='Host Windows Container Web App on Hyper-V worker. Cannot be used with '
'--is-managed-instance.')
c.argument('per_site_scaling', action='store_true', required=False, help='Enable per-app scaling at the '
'App Service plan level to allow for '
'scaling an app independently from '
'the App Service plan that hosts it.')
c.argument('zone_redundant', options_list=['--zone-redundant', '-z'], help='Enable zone redundancy for high availability. Minimum instance count is 2.')
c.argument('tags', arg_type=tags_type)
c.argument('async_scaling_enabled', arg_type=get_three_state_flag(), help='Enables async scaling for the app service plan. Set to "true" to create an async operation if there are insufficient workers to scale synchronously. The SKU must be Dedicated.')
c.argument('is_managed_instance', action='store_true', help='host web app on managed instance')
c.argument('is_managed_instance', action='store_true',
help='Host web app on Managed Instance. Managed Instance supports Windows plans only and '
'cannot be used with --hyper-v.')
c.argument('mi_system_assigned',
arg_type=get_three_state_flag(),
help="Enable system-assigned managed identity for this app service plan.")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,11 +113,18 @@ def _validate_asp_sku(sku, app_service_environment, zone_redundant):
def validate_asp_create(namespace):
validate_tags(namespace)
# is_linux is None when not explicitly provided by the user (default).
# Resolve the default: Linux unless --hyper-v is specified.
# Resolve the default: Linux unless a Windows-only plan type is specified.
if namespace.hyper_v and namespace.is_managed_instance:
raise MutuallyExclusiveArgumentError(
'--hyper-v and --is-managed-instance cannot be used together because Windows Containers '
'are not supported in Managed Instance on Azure App Service.')
if namespace.is_linux is None:
namespace.is_linux = not namespace.hyper_v
elif namespace.is_linux and namespace.hyper_v:
raise MutuallyExclusiveArgumentError('Usage error: --is-linux true and --hyper-v cannot be used together.')
namespace.is_linux = not (namespace.hyper_v or namespace.is_managed_instance)
elif namespace.is_linux and (namespace.hyper_v or namespace.is_managed_instance):
windows_plan_argument = '--hyper-v' if namespace.hyper_v else '--is-managed-instance'
raise MutuallyExclusiveArgumentError(
'{} creates a Windows plan and cannot be combined with --is-linux true. '
'Omit --is-linux or use "--is-linux false".'.format(windows_plan_argument))
if namespace.sku is None:
if namespace.is_linux:
namespace.sku = 'P0V3'
Expand Down
15 changes: 11 additions & 4 deletions src/azure-cli/azure/cli/command_modules/appservice/custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -5462,11 +5462,18 @@ def create_app_service_plan(cmd, resource_group_name, name, is_linux, hyper_v, p
default_identity=None, rdp_enabled=None, vnet=None, subnet=None,
registry_adapters=None, install_scripts=None, storage_mounts=None,
enriched_errors=False):
if hyper_v and is_managed_instance:
raise MutuallyExclusiveArgumentError(
'--hyper-v and --is-managed-instance cannot be used together because Windows Containers '
'are not supported in Managed Instance on Azure App Service.')

if is_linux is None:
is_linux = not hyper_v
elif is_linux and hyper_v:
raise MutuallyExclusiveArgumentError('--hyper-v creates a Windows container plan and cannot be combined '
'with --is-linux true. Omit --is-linux or use "--is-linux false".')
is_linux = not (hyper_v or is_managed_instance)
elif is_linux and (hyper_v or is_managed_instance):
Comment thread
apwestgarth marked this conversation as resolved.
windows_plan_argument = '--hyper-v' if hyper_v else '--is-managed-instance'
raise MutuallyExclusiveArgumentError(
'{} creates a Windows plan and cannot be combined with --is-linux true. '
'Omit --is-linux or use "--is-linux false".'.format(windows_plan_argument))

if sku is None:
sku = 'P0V3' if is_linux else 'B1'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@

import unittest
from unittest import mock
from types import SimpleNamespace

from azure.cli.core.azclierror import ArgumentUsageError, ValidationError
from azure.cli.core.azclierror import ArgumentUsageError, MutuallyExclusiveArgumentError, ValidationError

from azure.cli.command_modules.appservice._constants import ISOLATED_V4_SKUS
from azure.cli.command_modules.appservice._validators import (
Expand All @@ -15,10 +16,46 @@
_validate_asp_sku,
_validate_ip_address_existence,
_validate_service_tag_existence,
validate_asp_create,
)
from azure.cli.command_modules.appservice.utils import get_sku_tier


class ValidateAppServicePlanCreateTest(unittest.TestCase):
@staticmethod
def _make_namespace(is_linux=None, hyper_v=False, is_managed_instance=False):
return SimpleNamespace(
tags=None,
is_linux=is_linux,
hyper_v=hyper_v,
is_managed_instance=is_managed_instance,
sku='P1V4',
app_service_environment=None,
zone_redundant=False,
)

def test_managed_instance_defaults_to_windows(self):
namespace = self._make_namespace(is_managed_instance=True)

validate_asp_create(namespace)

self.assertFalse(namespace.is_linux)

def test_managed_instance_rejects_linux(self):
namespace = self._make_namespace(is_linux=True, is_managed_instance=True)

with self.assertRaisesRegex(MutuallyExclusiveArgumentError,
'--is-managed-instance creates a Windows plan'):
validate_asp_create(namespace)

def test_managed_instance_rejects_hyper_v(self):
namespace = self._make_namespace(hyper_v=True, is_managed_instance=True)

with self.assertRaisesRegex(MutuallyExclusiveArgumentError,
'--hyper-v and --is-managed-instance cannot be used together'):
validate_asp_create(namespace)


class ValidateAppServicePlanSkuTest(unittest.TestCase):
def test_isolated_v4_skus_are_supported_for_ase_plans(self):
for sku in ISOLATED_V4_SKUS:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1767,6 +1767,43 @@ def test_is_linux_false_creates_windows_plan(self, mock_location, mock_client_fa
call_kwargs = mock_app_service_plan_cls.call_args
self.assertIn('reserved=False', str(call_kwargs))

@mock.patch('azure.cli.command_modules.appservice.custom.web_client_factory')
@mock.patch('azure.cli.command_modules.appservice.custom._get_location_from_resource_group', return_value='eastus')
def test_managed_instance_defaults_to_windows(self, mock_location, mock_client_factory):
"""When --is-managed-instance is specified, an omitted --is-linux defaults to false."""
from azure.cli.command_modules.appservice.custom import create_app_service_plan
mock_cmd = mock.MagicMock()
mock_app_service_plan_cls = mock.MagicMock()
mock_cmd.get_models.return_value = (mock.MagicMock(), mock.MagicMock(), mock_app_service_plan_cls)
mock_cmd.cli_ctx = mock.MagicMock()
mock_client_factory.return_value = mock.MagicMock()

try:
create_app_service_plan(mock_cmd, 'rg', 'plan', is_linux=None, hyper_v=False,
is_managed_instance=True)
except Exception:
pass

mock_app_service_plan_cls.assert_called()
self.assertIn('reserved=False', str(mock_app_service_plan_cls.call_args))

def test_managed_instance_rejects_linux(self):
"""Managed Instance on App Service supports only Windows plans."""
from azure.cli.command_modules.appservice.custom import create_app_service_plan

with self.assertRaisesRegex(MutuallyExclusiveArgumentError, '--is-managed-instance creates a Windows plan'):
create_app_service_plan(mock.MagicMock(), 'rg', 'plan', is_linux=True, hyper_v=False,
is_managed_instance=True)

def test_managed_instance_rejects_hyper_v(self):
"""Managed Instance on App Service does not support Windows Containers."""
from azure.cli.command_modules.appservice.custom import create_app_service_plan

with self.assertRaisesRegex(MutuallyExclusiveArgumentError,
'--hyper-v and --is-managed-instance cannot be used together'):
create_app_service_plan(mock.MagicMock(), 'rg', 'plan', is_linux=None, hyper_v=True,
is_managed_instance=True)


class TestOneDeployScmCache(unittest.TestCase):
"""Tests for the per-invocation SCM URL / SCM headers cache on OneDeployParams.
Expand Down
Loading