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
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,8 @@ def load_arguments(self, _):
with self.argument_context('webapp deployment source config-zip')as c:
c.argument('track_status', help="If true, web app startup status during deployment will be tracked for linux web apps.",
arg_type=get_three_state_flag())
c.argument('enable_kudu_warmup', help="If true, kudu will be warmed up before performing deployment for a linux webapp.",
arg_type=get_three_state_flag())

with self.argument_context('webapp log config') as c:
c.argument('application_logging', help='configure application logging',
Expand Down Expand Up @@ -775,6 +777,8 @@ def load_arguments(self, _):
c.argument('basic_auth', help='Enable or disable basic auth.', arg_type=get_enum_type(BASIC_AUTH_TYPES))
c.argument('track_status', help="If true, web app startup status during deployment will be tracked for linux web apps.",
arg_type=get_three_state_flag())
c.argument('enable_kudu_warmup', help="If true, kudu will be warmed up before performing deployment for a linux webapp.",
arg_type=get_three_state_flag())

with self.argument_context('webapp ssh') as c:
c.argument('port', options_list=['--port', '-p'],
Expand Down Expand Up @@ -813,6 +817,8 @@ def load_arguments(self, _):
c.argument('slot', help="The name of the slot. Default to the productions slot if not specified.")
c.argument('track_status', help="If true, web app startup status during deployment will be tracked for linux web apps.",
arg_type=get_three_state_flag())
c.argument('enable_kudu_warmup', help="If true, kudu will be warmed up before performing deployment for a linux webapp.",
arg_type=get_three_state_flag())

with self.argument_context('functionapp deploy') as c:
c.argument('name', options_list=['--name', '-n'], help='Name of the function app to deploy to.')
Expand Down
130 changes: 122 additions & 8 deletions src/azure-cli/azure/cli/command_modules/appservice/custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -690,8 +690,9 @@ def enable_zip_deploy_functionapp(cmd, resource_group_name, name, src, build_rem
return enable_zip_deploy(cmd, resource_group_name, name, src, timeout, slot)


def enable_zip_deploy_webapp(cmd, resource_group_name, name, src, timeout=None, slot=None, track_status=True):
return enable_zip_deploy(cmd, resource_group_name, name, src, timeout, slot, track_status)
def enable_zip_deploy_webapp(cmd, resource_group_name, name, src, timeout=None, slot=None, track_status=True,
enable_kudu_warmup=True):
return enable_zip_deploy(cmd, resource_group_name, name, src, timeout, slot, track_status, enable_kudu_warmup)


def check_flex_app_after_deployment(cmd, resource_group_name, name):
Expand Down Expand Up @@ -777,7 +778,9 @@ def enable_zip_deploy_flex(cmd, resource_group_name, name, src, timeout=None, sl
.format(res.status_code, res.text))


def enable_zip_deploy(cmd, resource_group_name, name, src, timeout=None, slot=None, track_status=False):
# This funtion performs deployment using /zipdeploy for both function app and web app
def enable_zip_deploy(cmd, resource_group_name, name, src, timeout=None, slot=None,
track_status=False, enable_kudu_warmup=True):
logger.warning("Getting scm site credentials for zip deployment")

try:
Expand All @@ -800,6 +803,7 @@ def enable_zip_deploy(cmd, resource_group_name, name, src, timeout=None, slot=No
from azure.cli.core.util import should_disable_connection_verify
# check if the app is a linux web app
app_is_linux_webapp = is_linux_webapp(app)
app_is_function_app = is_functionapp(app)

# Read file content
with open(os.path.realpath(os.path.expanduser(src)), 'rb') as fs:
Expand All @@ -808,7 +812,26 @@ def enable_zip_deploy(cmd, resource_group_name, name, src, timeout=None, slot=No
if app_is_linux_webapp and track_status is not None and track_status:
headers["x-ms-artifact-checksum"] = _compute_checksum(zip_content)

res = requests.post(zip_url, data=zip_content, headers=headers, verify=not should_disable_connection_verify())
if app_is_linux_webapp and not app_is_function_app and enable_kudu_warmup:
try:
logger.info("Warming up Kudu before deployment.")
cookies = _warmup_kudu_and_get_cookie_internal(cmd, resource_group_name, name, slot)
if cookies is None:
logger.info("Failed to fetch affinity cookie. Deployment "
"will proceed without pre-warming a Kudu instance.")
res = requests.post(zip_url, data=zip_content, headers=headers,
verify=not should_disable_connection_verify())
else:
res = requests.post(zip_url, data=zip_content, headers=headers, cookies=cookies,
verify=not should_disable_connection_verify())
except Exception as ex: # pylint: disable=broad-except
logger.info("Failed to deploy using affinity cookie. "
"Deployment will proceed without pre-warming a Kudu instance. Exception: %s", ex)
res = requests.post(zip_url, data=zip_content, headers=headers,
verify=not should_disable_connection_verify())
else:
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
Expand Down Expand Up @@ -6890,7 +6913,7 @@ def get_history_triggered_webjob(cmd, resource_group_name, name, webjob_name, sl

def webapp_up(cmd, name=None, resource_group_name=None, plan=None, location=None, sku=None, # pylint: disable=too-many-statements,too-many-branches
os_type=None, runtime=None, dryrun=False, logs=False, launch_browser=False, html=False,
app_service_environment=None, track_status=True, basic_auth=""):
app_service_environment=None, track_status=True, enable_kudu_warmup=True, basic_auth=""):
if not name:
name = generate_default_app_name(cmd)

Expand Down Expand Up @@ -7067,7 +7090,8 @@ def webapp_up(cmd, name=None, resource_group_name=None, plan=None, location=None
logger.warning("Creating zip with contents of dir %s ...", src_dir)
# zip contents & deploy
zip_file_path = zip_contents_from_dir(src_dir, language)
enable_zip_deploy(cmd, rg_name, name, zip_file_path, track_status=track_status)
enable_zip_deploy(cmd, rg_name, name, zip_file_path, track_status=track_status,
enable_kudu_warmup=enable_kudu_warmup)

if launch_browser:
logger.warning("Launching app using default browser")
Expand Down Expand Up @@ -7286,6 +7310,7 @@ def perform_onedeploy_functionapp(cmd,
params.timeout = timeout
params.slot = slot
params.track_status = False
params.is_functionapp = True

return _perform_onedeploy_internal(params)

Expand All @@ -7303,7 +7328,8 @@ def perform_onedeploy_webapp(cmd,
ignore_stack=None,
timeout=None,
slot=None,
track_status=True):
track_status=True,
enable_kudu_warmup=True):
params = OneDeployParams()

params.cmd = cmd
Expand All @@ -7320,7 +7346,12 @@ def perform_onedeploy_webapp(cmd,
params.timeout = timeout
params.slot = slot
params.track_status = track_status
params.enable_kudu_warmup = enable_kudu_warmup

client = web_client_factory(cmd.cli_ctx)
app = client.web_apps.get(resource_group_name, name)
params.is_linux_webapp = is_linux_webapp(app)
params.is_functionapp = False
return _perform_onedeploy_internal(params)


Expand All @@ -7342,6 +7373,9 @@ def __init__(self):
self.timeout = None
self.slot = None
self.track_status = False
self.enable_kudu_warmup = None
self.is_linux_webapp = None
self.is_functionapp = None
# pylint: enable=too-many-instance-attributes,too-few-public-methods


Expand Down Expand Up @@ -7484,6 +7518,65 @@ def _update_artifact_type(params):
"Possible values: war, jar, ear, zip, startup, script, static", params.artifact_type)


def _get_instance_id_internal(cmd, resource_group_name, webapp_name, slot):
from azure.cli.core.commands.client_factory import get_subscription_id
try:
client = web_client_factory(cmd.cli_ctx)
sub_id = get_subscription_id(cmd.cli_ctx)
if slot:
base_url = (
f"subscriptions/{sub_id}/resourceGroups/{resource_group_name}/providers/Microsoft.Web/sites/"
f"{webapp_name}/slots/{slot}/instances"
f"?api-version={client.DEFAULT_API_VERSION}"
)
else:
base_url = (
f"subscriptions/{sub_id}/resourceGroups/{resource_group_name}/providers/Microsoft.Web/sites/"
f"{webapp_name}/instances?api-version={client.DEFAULT_API_VERSION}"
)

url = cmd.cli_ctx.cloud.endpoints.resource_manager + base_url
response = send_raw_request(cmd.cli_ctx, "GET", url)
if response.status_code == 200:
instances = response.json().get("value", [])
if not instances or len(instances) == 0:
return None
sorted_instances = sorted(instances, key=lambda x: x['name'])
return sorted_instances[0]['name']
return None
except Exception as ex: # pylint: disable=broad-except
logger.info("Failed to get list of available Kudu instances for deployment. Exception:%s", ex)
return None


def _warmup_kudu_and_get_cookie_internal(cmd, resource_group_name, webapp_name, slot):
import requests
scm_url = _get_scm_url(cmd, resource_group_name, webapp_name, slot)
instance_id = _get_instance_id_internal(cmd, resource_group_name, webapp_name, slot)
if instance_id is None:
logger.info("Failed to get a Kudu instance id...")
return None
cookies = {"ARRAffinity": instance_id, "ARRAffinitySameSite": instance_id}
max_retries = 3
time_out = 60

for _ in range(max_retries):
try:
headers = get_scm_site_headers(cmd.cli_ctx, webapp_name, resource_group_name)
response = requests.get(scm_url + '/api/deployments?warmup=true',
headers=headers, cookies=cookies, timeout=time_out)
if response.status_code in (200, 201, 202):
logger.warning("Warmed up Kudu instance successfully.")
return cookies
time_out = 300
except Exception as ex: # pylint: disable=broad-except
logger.info("Error while warming-up Kudu with instanceid: %s, ex: %s", instance_id, ex)
return False
logger.warning("Failed to warm-up Kudu with instanceid: %s, "
"the deployment will proceed without pre-warmup.", instance_id)
return None


def _make_onedeploy_request(params):
import requests
from azure.cli.core.util import should_disable_connection_verify
Expand All @@ -7501,7 +7594,28 @@ def _make_onedeploy_request(params):
# For that, set poll_async_deployment_for_debugging=True
logger.info("Deployment API: %s", deploy_url)
if not params.src_url: # use SCM endpoint
response = requests.post(deploy_url, data=body, headers=headers, verify=not should_disable_connection_verify())
# if linux webapp and not function app, then warmup kudu and use warmed up kudu for deployment
if params.is_linux_webapp and not params.is_functionapp and params.enable_kudu_warmup:
try:
logger.warning("Warming up Kudu before deployment.")
cookies = _warmup_kudu_and_get_cookie_internal(params.cmd, params.resource_group_name,
params.webapp_name, params.slot)
if cookies is None:
logger.info("Failed to fetch affinity cookie for Kudu. "
"Deployment will proceed without pre-warming a Kudu instance.")
response = requests.post(deploy_url, data=body, headers=headers,
verify=not should_disable_connection_verify())
else:
response = requests.post(deploy_url, data=body, headers=headers, cookies=cookies,
verify=not should_disable_connection_verify())
except Exception as ex: # pylint: disable=broad-except
logger.info("Failed to deploy using affinity cookie. "
"Deployment will proceed without pre-warming a Kudu instance. Ex: %s", ex)
response = requests.post(deploy_url, data=body, headers=headers,
verify=not should_disable_connection_verify())
else:
response = requests.post(deploy_url, data=body, headers=headers,
verify=not should_disable_connection_verify())
poll_async_deployment_for_debugging = True
else:
response = send_raw_request(params.cmd.cli_ctx, "PUT", deploy_url, body=body)
Expand Down
Loading