Skip to content

Commit

Permalink
Merge pull request Azure#11 from kamperiadis/kaibocai/flexTesting
Browse files Browse the repository at this point in the history
Add zip deployment support
  • Loading branch information
kaibocai committed May 24, 2023
2 parents 3e6393e + 0f01b20 commit b8824ed
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 3 deletions.
54 changes: 51 additions & 3 deletions src/azure-cli/azure/cli/command_modules/appservice/custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
_normalize_location,
get_pool_manager, use_additional_properties, get_app_service_plan_from_webapp,
get_resource_if_exists, repo_url_to_name, get_token,
app_service_plan_exists, is_centauri_functionapp)
app_service_plan_exists, is_centauri_functionapp, is_flex_functionapp)
from ._create_util import (zip_contents_from_dir, get_runtime_version_details, create_resource_group, get_app_details,
check_resource_group_exists, set_location, get_site_availability, get_profile_username,
get_plan_to_use, get_lang_from_content, get_rg_to_use, get_sku_to_use,
Expand Down Expand Up @@ -620,6 +620,9 @@ def enable_zip_deploy_functionapp(cmd, resource_group_name, name, src, build_rem
if is_consumption and app.reserved:
validate_zip_deploy_app_setting_exists(cmd, resource_group_name, name, slot)

if is_flex_functionapp(cmd, resource_group_name, name):
return enable_zip_deploy_flex(cmd, resource_group_name, name, src, timeout, slot, build_remote)

if (not build_remote) and is_consumption and app.reserved:
return upload_zip_to_storage(cmd, resource_group_name, name, src, slot)
if build_remote and app.reserved:
Expand All @@ -634,6 +637,49 @@ def enable_zip_deploy_webapp(cmd, resource_group_name, name, src, timeout=None,
return enable_zip_deploy(cmd, resource_group_name, name, src, timeout=timeout, slot=slot)


def enable_zip_deploy_flex(cmd, resource_group_name, name, src, timeout=None, slot=None, build_remote=False):
logger.warning("Getting scm site credentials for zip deployment")

try:
scm_url = _get_scm_url(cmd, resource_group_name, name, slot)
except ValueError:
raise ResourceNotFoundError('Failed to fetch scm url for function app')

zip_url = scm_url + '/api/Deploy/Zip?RemoteBuild={}&Deployer=az_cli'.format(build_remote)
deployment_status_url = scm_url + '/api/deployments/latest'

additional_headers = {"Content-Type": "application/zip", "Cache-Control": "no-cache"}
headers = get_scm_site_headers(cmd.cli_ctx, name, resource_group_name, slot,
additional_headers=additional_headers)

import os
import requests
from azure.cli.core.util import should_disable_connection_verify
# Read file content

with open(os.path.realpath(os.path.expanduser(src)), 'rb') as fs:
zip_content = fs.read()
logger.warning("Starting zip deployment. This operation can take a while to complete ...")
res = requests.post(zip_url, data=zip_content, headers=headers, verify=not should_disable_connection_verify())
logger.warning("Deployment endpoint responded with status code %d", res.status_code)

# check the status of async deployment
if res.status_code == 202:
response = _check_zip_deployment_status(cmd, resource_group_name, name, deployment_status_url,
headers, timeout)
return response

# check if there's an ongoing process
if res.status_code == 409:
raise UnclassifiedUserFault("There may be an ongoing deployment. Please track your deployment in {}"
.format(deployment_status_url))

# check if an error occured during deployment
if res.status_code:
raise AzureInternalError("An error occured during deployment. Status Code: {}, Details: {}"
.format(res.status_code, res.text))


def enable_zip_deploy(cmd, resource_group_name, name, src, timeout=None, slot=None):
logger.warning("Getting scm site credentials for zip deployment")

Expand Down Expand Up @@ -4334,7 +4380,8 @@ def _check_zip_deployment_status(cmd, rg_name, name, deployment_status_url, head
num_trials = num_trials + 1

if res_dict.get('status', 0) == 3:
_configure_default_logging(cmd, rg_name, name)
if not is_flex_functionapp(cmd, rg_name, name):
_configure_default_logging(cmd, rg_name, name)
raise CLIError("Zip deployment failed. {}. Please run the command az webapp log deployment show "
"-n {} -g {}".format(res_dict, name, rg_name))
if res_dict.get('status', 0) == 4:
Expand All @@ -4343,7 +4390,8 @@ def _check_zip_deployment_status(cmd, rg_name, name, deployment_status_url, head
logger.info(res_dict['progress']) # show only in debug mode, customers seem to find this confusing
# if the deployment is taking longer than expected
if res_dict.get('status', 0) != 4:
_configure_default_logging(cmd, rg_name, name)
if not is_flex_functionapp(cmd, rg_name, name):
_configure_default_logging(cmd, rg_name, name)
raise CLIError("""Timeout reached by the command, however, the deployment operation
is still on-going. Navigate to your scm site to check the deployment status""")
return res_dict
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from azure.cli.command_modules.appservice.custom import (
enable_zip_deploy_functionapp,
enable_zip_deploy,
enable_zip_deploy_flex,
add_remote_build_app_settings,
remove_remote_build_app_settings,
validate_app_settings_in_scm)
Expand Down Expand Up @@ -50,11 +51,13 @@ def setUp(self):

@mock.patch('azure.cli.command_modules.appservice.custom.web_client_factory', autospec=True)
@mock.patch('azure.cli.command_modules.appservice.custom.parse_resource_id')
@mock.patch('azure.cli.command_modules.appservice.custom.is_flex_functionapp', return_value=False)
@mock.patch('azure.cli.command_modules.appservice.custom.enable_zip_deploy')
@mock.patch('azure.cli.command_modules.appservice.custom.add_remote_build_app_settings')
def test_functionapp_zip_deploy_flow(self,
add_remote_build_app_settings_mock,
enable_zip_deploy_mock,
is_flex_functionapp_mock,
parse_resource_id_mock,
web_client_factory_mock):
cmd_mock = _get_test_cmd()
Expand All @@ -71,11 +74,13 @@ def test_functionapp_zip_deploy_flow(self,

@mock.patch('azure.cli.command_modules.appservice.custom.web_client_factory', autospec=True)
@mock.patch('azure.cli.command_modules.appservice.custom.parse_resource_id')
@mock.patch('azure.cli.command_modules.appservice.custom.is_flex_functionapp', return_value=False)
@mock.patch('azure.cli.command_modules.appservice.custom.enable_zip_deploy')
@mock.patch('azure.cli.command_modules.appservice.custom.remove_remote_build_app_settings')
def test_functionapp_zip_deploy_flow(self,
remove_remote_build_app_settings_mock,
enable_zip_deploy_mock,
is_flex_functionapp_mock,
parse_resource_id_mock,
web_client_factory_mock):
cmd_mock = _get_test_cmd()
Expand All @@ -95,7 +100,9 @@ def test_functionapp_zip_deploy_flow(self,
@mock.patch('azure.cli.command_modules.appservice.custom.validate_zip_deploy_app_setting_exists')
@mock.patch('azure.cli.command_modules.appservice.custom.upload_zip_to_storage')
@mock.patch('azure.cli.command_modules.appservice.custom.is_plan_consumption', return_value=True)
@mock.patch('azure.cli.command_modules.appservice.custom.is_flex_functionapp', return_value=False)
def test_functionapp_linux_consumption_non_remote_build(self,
is_flex_functionapp_mock,
is_plan_consumption_mock,
upload_zip_to_storage_mock,
validate_zip_deploy_app_setting_exists_mock,
Expand Down Expand Up @@ -127,7 +134,9 @@ def test_functionapp_linux_consumption_non_remote_build(self,
@mock.patch('azure.cli.command_modules.appservice.custom.validate_zip_deploy_app_setting_exists')
@mock.patch('azure.cli.command_modules.appservice.custom.upload_zip_to_storage')
@mock.patch('azure.cli.command_modules.appservice.custom.is_plan_consumption', return_value=True)
@mock.patch('azure.cli.command_modules.appservice.custom.is_flex_functionapp', return_value=False)
def test_functionapp_linux_consumption_non_remote_build_with_slot(self,
is_flex_functionapp_mock,
is_plan_consumption_mock,
upload_zip_to_storage_mock,
validate_zip_deploy_app_setting_exists_mock,
Expand Down Expand Up @@ -157,9 +166,11 @@ def test_functionapp_linux_consumption_non_remote_build_with_slot(self,
@mock.patch('azure.cli.command_modules.appservice.custom.add_remote_build_app_settings')
@mock.patch('azure.cli.command_modules.appservice.custom.web_client_factory', autospec=True)
@mock.patch('azure.cli.command_modules.appservice.custom.parse_resource_id')
@mock.patch('azure.cli.command_modules.appservice.custom.is_flex_functionapp', return_value=False)
@mock.patch('azure.cli.command_modules.appservice.custom.enable_zip_deploy')
def test_functionapp_remote_build_supports_linux(self,
enable_zip_deploy_mock,
is_flex_functionapp_mock,
parse_resource_id_mock,
web_client_factory_mock,
add_remote_build_app_settings_mock):
Expand All @@ -183,6 +194,39 @@ def test_functionapp_remote_build_supports_linux(self,
web_client_mock.web_apps.get.assert_called_with('rg', 'name')
enable_zip_deploy_mock.assert_called_with(cmd_mock, 'rg', 'name', 'src', None, None)

@mock.patch('azure.cli.command_modules.appservice.custom.get_scm_site_headers')
@mock.patch('azure.cli.command_modules.appservice.custom._get_scm_url', return_value='https://mock-scm')
@mock.patch('requests.post', autospec=True)
@mock.patch('azure.cli.command_modules.appservice.custom._check_zip_deployment_status')
def test_enable_zip_deploy_flex(self,
check_zip_deployment_status_mock,
requests_post_mock,
get_scm_url_mock,
get_scm_headers_mock):
# prepare
cmd_mock = _get_test_cmd()
cli_ctx_mock = mock.MagicMock()
cmd_mock.cli_ctx = cli_ctx_mock

response = mock.MagicMock()
response.status_code = 202
requests_post_mock.return_value = response

expected_zip_deploy_headers = _get_zip_deploy_headers('usr', 'pwd', cmd_mock.cli_ctx)
get_scm_headers_mock.return_value = expected_zip_deploy_headers

# action
with mock.patch('builtins.open', new_callable=mock.mock_open, read_data='zip-content'):
enable_zip_deploy_flex(cmd_mock, 'rg', 'name', 'src', slot=None, build_remote=True)

# assert
requests_post_mock.assert_called_with('https://mock-scm/api/Deploy/Zip?RemoteBuild=True&Deployer=az_cli', data='zip-content',
headers=expected_zip_deploy_headers, verify=mock.ANY)
# TODO improve authorization matcher
check_zip_deployment_status_mock.assert_called_with(cmd_mock, 'rg', 'name',
'https://mock-scm/api/deployments/latest', mock.ANY, None)


@mock.patch('azure.cli.command_modules.appservice.custom.get_scm_site_headers')
@mock.patch('azure.cli.command_modules.appservice.custom._get_scm_url', side_effect=ValueError())
def test_enable_zip_deploy_remote_build_no_scm_site(self,
Expand Down
12 changes: 12 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 @@ -176,6 +176,18 @@ def is_centauri_functionapp(cmd, resource_group, name):
return function_app.get("properties", {}).get("managedEnvironmentId", None) is not None


def is_flex_functionapp(cmd, resource_group, name):
client = web_client_factory(cmd.cli_ctx)
app = client.web_apps.get(resource_group, name)
parse_plan_id = parse_resource_id(app.server_farm_id)
plan_info = client.app_service_plans.get(parse_plan_id['resource_group'], parse_plan_id['name'])
SkuDescription, AppServicePlan = cmd.get_models('SkuDescription', 'AppServicePlan')
if isinstance(plan_info, AppServicePlan):
if isinstance(plan_info.sku, SkuDescription):
return plan_info.sku.tier.lower() == 'flexconsumption'
return False


def _list_app(cli_ctx, resource_group_name=None):
client = web_client_factory(cli_ctx)
if resource_group_name:
Expand Down

0 comments on commit b8824ed

Please sign in to comment.