1# --------------------------------------------------------------------------------------------
2# Copyright (c) Microsoft Corporation. All rights reserved.
3# Licensed under the MIT License. See License.txt in the project root for license information.
4# --------------------------------------------------------------------------------------------
5
6from argcomplete.completers import FilesCompleter
7
8from knack.arguments import CLIArgumentType
9
10from azure.cli.core.commands.parameters import (resource_group_name_type, get_location_type,
11                                                get_resource_name_completion_list, file_type,
12                                                get_three_state_flag, get_enum_type, tags_type)
13from azure.cli.core.util import get_file_json
14from azure.cli.core.local_context import LocalContextAttribute, LocalContextAction
15from azure.cli.command_modules.appservice._appservice_utils import MSI_LOCAL_ID
16from azure.mgmt.web.models import DatabaseType, ConnectionStringType, BuiltInAuthenticationProvider, AzureStorageType
17
18from ._completers import get_hostname_completion_list
19from ._constants import FUNCTIONS_VERSIONS, FUNCTIONS_STACKS_API_JSON_PATHS, FUNCTIONS_STACKS_API_KEYS
20from ._validators import (validate_timeout_value, validate_site_create, validate_asp_create,
21                          validate_add_vnet, validate_front_end_scale_factor, validate_ase_create, validate_ip_address,
22                          validate_service_tag, validate_public_cloud)
23
24AUTH_TYPES = {
25    'AllowAnonymous': 'na',
26    'LoginWithAzureActiveDirectory': BuiltInAuthenticationProvider.azure_active_directory,
27    'LoginWithFacebook': BuiltInAuthenticationProvider.facebook,
28    'LoginWithGoogle': BuiltInAuthenticationProvider.google,
29    'LoginWithMicrosoftAccount': BuiltInAuthenticationProvider.microsoft_account,
30    'LoginWithTwitter': BuiltInAuthenticationProvider.twitter}
31
32MULTI_CONTAINER_TYPES = ['COMPOSE', 'KUBE']
33FTPS_STATE_TYPES = ['AllAllowed', 'FtpsOnly', 'Disabled']
34OS_TYPES = ['Windows', 'Linux']
35LINUX_RUNTIMES = ['dotnet', 'node', 'python', 'java']
36WINDOWS_RUNTIMES = ['dotnet', 'node', 'java', 'powershell']
37ACCESS_RESTRICTION_ACTION_TYPES = ['Allow', 'Deny']
38ASE_LOADBALANCER_MODES = ['Internal', 'External']
39ASE_KINDS = ['ASEv2', 'ASEv3']
40ASE_OS_PREFERENCE_TYPES = ['Windows', 'Linux']
41
42
43# pylint: disable=too-many-statements, too-many-lines
44
45
46def load_arguments(self, _):
47    # pylint: disable=line-too-long
48    # PARAMETER REGISTRATION
49    name_arg_type = CLIArgumentType(options_list=['--name', '-n'], metavar='NAME')
50    sku_arg_type = CLIArgumentType(
51        help='The pricing tiers, e.g., F1(Free), D1(Shared), B1(Basic Small), B2(Basic Medium), B3(Basic Large), S1(Standard Small), P1V2(Premium V2 Small), P1V3(Premium V3 Small), P2V3(Premium V3 Medium), P3V3(Premium V3 Large), PC2 (Premium Container Small), PC3 (Premium Container Medium), PC4 (Premium Container Large), I1 (Isolated Small), I2 (Isolated Medium), I3 (Isolated Large), I1v2 (Isolated V2 Small), I2v2 (Isolated V2 Medium), I3v2 (Isolated V2 Large)',
52        arg_type=get_enum_type(
53            ['F1', 'FREE', 'D1', 'SHARED', 'B1', 'B2', 'B3', 'S1', 'S2', 'S3', 'P1V2', 'P2V2', 'P3V2', 'P1V3', 'P2V3', 'P3V3', 'PC2', 'PC3',
54             'PC4', 'I1', 'I2', 'I3', 'I1v2', 'I2v2', 'I3v2']))
55    webapp_name_arg_type = CLIArgumentType(configured_default='web', options_list=['--name', '-n'], metavar='NAME',
56                                           completer=get_resource_name_completion_list('Microsoft.Web/sites'),
57                                           id_part='name',
58                                           help="name of the web app. If left unspecified, a name will be randomly generated. You can configure the default using `az configure --defaults web=<name>`",
59                                           local_context_attribute=LocalContextAttribute(name='web_name', actions=[
60                                               LocalContextAction.GET]))
61    functionapp_name_arg_type = CLIArgumentType(options_list=['--name', '-n'], metavar='NAME',
62                                                help="name of the function app.",
63                                                local_context_attribute=LocalContextAttribute(name='functionapp_name',
64                                                                                              actions=[
65                                                                                                  LocalContextAction.GET]))
66    logicapp_name_arg_type = CLIArgumentType(options_list=['--name', '-n'], metavar='NAME',
67                                             help="name of the logic app.",
68                                             local_context_attribute=LocalContextAttribute(name='logicapp_name',
69                                                                                           actions=[LocalContextAction.GET]))
70    name_arg_type_dict = {
71        'functionapp': functionapp_name_arg_type,
72        'logicapp': logicapp_name_arg_type
73    }
74    isolated_sku_arg_type = CLIArgumentType(
75        help='The Isolated pricing tiers, e.g., I1 (Isolated Small), I2 (Isolated Medium), I3 (Isolated Large)',
76        arg_type=get_enum_type(['I1', 'I2', 'I3']))
77
78    static_web_app_sku_arg_type = CLIArgumentType(
79        help='The pricing tiers for Static Web App',
80        arg_type=get_enum_type(['Free', 'Standard'])
81    )
82
83    functionapp_runtime_strings, functionapp_runtime_to_version_strings = _get_functionapp_runtime_versions()
84
85    # use this hidden arg to give a command the right instance, that functionapp commands
86    # work on function app and webapp ones work on web app
87    with self.argument_context('webapp') as c:
88        c.ignore('app_instance')
89        c.argument('resource_group_name', arg_type=resource_group_name_type)
90        c.argument('location', arg_type=get_location_type(self.cli_ctx))
91        c.argument('slot', options_list=['--slot', '-s'],
92                   help="the name of the slot. Default to the productions slot if not specified")
93        c.argument('name', arg_type=webapp_name_arg_type)
94
95    with self.argument_context('appservice') as c:
96        c.argument('resource_group_name', arg_type=resource_group_name_type)
97        c.argument('location', arg_type=get_location_type(self.cli_ctx))
98
99    with self.argument_context('appservice list-locations') as c:
100        c.argument('linux_workers_enabled', action='store_true',
101                   help='get regions which support hosting web apps on Linux workers')
102        c.argument('sku', arg_type=sku_arg_type)
103
104    with self.argument_context('appservice plan') as c:
105        c.argument('name', arg_type=name_arg_type, help='The name of the app service plan',
106                   completer=get_resource_name_completion_list('Microsoft.Web/serverFarms'),
107                   configured_default='appserviceplan', id_part='name',
108                   local_context_attribute=LocalContextAttribute(name='plan_name', actions=[LocalContextAction.GET]))
109        c.argument('number_of_workers', help='Number of workers to be allocated.', type=int, default=1)
110        c.argument('admin_site_name', help='The name of the admin web app.',
111                   deprecate_info=c.deprecate(expiration='0.2.17'))
112        c.ignore('max_burst')
113
114    with self.argument_context('appservice plan create') as c:
115        c.argument('name', options_list=['--name', '-n'], help="Name of the new app service plan", completer=None,
116                   validator=validate_asp_create,
117                   local_context_attribute=LocalContextAttribute(name='plan_name', actions=[LocalContextAction.SET],
118                                                                 scopes=['appservice', 'webapp', 'functionapp']))
119        c.argument('app_service_environment', options_list=['--app-service-environment', '-e'],
120                   help="Name or ID of the app service environment",
121                   local_context_attribute=LocalContextAttribute(name='ase_name', actions=[LocalContextAction.GET]))
122        c.argument('sku', arg_type=sku_arg_type)
123        c.argument('is_linux', action='store_true', required=False, help='host web app on Linux worker')
124        c.argument('hyper_v', action='store_true', required=False, help='Host web app on Windows container')
125        c.argument('per_site_scaling', action='store_true', required=False, help='Enable per-app scaling at the '
126                                                                                 'App Service plan level to allow for '
127                                                                                 'scaling an app independently from '
128                                                                                 'the App Service plan that hosts it.')
129        c.argument('tags', arg_type=tags_type)
130
131    with self.argument_context('appservice plan update') as c:
132        c.argument('sku', arg_type=sku_arg_type)
133        c.ignore('allow_pending_state')
134
135    with self.argument_context('appservice plan delete') as c:
136        c.argument('name', arg_type=name_arg_type, help='The name of the app service plan',
137                   completer=get_resource_name_completion_list('Microsoft.Web/serverFarms'),
138                   configured_default='appserviceplan', id_part='name', local_context_attribute=None)
139
140    with self.argument_context('webapp create') as c:
141        c.argument('name', options_list=['--name', '-n'], help='name of the new web app',
142                   validator=validate_site_create,
143                   local_context_attribute=LocalContextAttribute(name='web_name', actions=[LocalContextAction.SET],
144                                                                 scopes=['webapp', 'cupertino']))
145        c.argument('startup_file', help="Linux only. The web's startup file")
146        c.argument('docker_registry_server_user', options_list=['--docker-registry-server-user', '-s'], help='the container registry server username')
147        c.argument('docker_registry_server_password', options_list=['--docker-registry-server-password', '-w'], help='The container registry server password. Required for private registries.')
148        c.argument('multicontainer_config_type', options_list=['--multicontainer-config-type'], help="Linux only.", arg_type=get_enum_type(MULTI_CONTAINER_TYPES))
149        c.argument('multicontainer_config_file', options_list=['--multicontainer-config-file'], help="Linux only. Config file for multicontainer apps. (local or remote)")
150        c.argument('runtime', options_list=['--runtime', '-r'], help="canonicalized web runtime in the format of Framework|Version, e.g. \"PHP|7.2\". Allowed delimiters: \"|\" or \":\". "
151                                                                     "Use `az webapp list-runtimes` for available list")  # TODO ADD completer
152        c.argument('plan', options_list=['--plan', '-p'], configured_default='appserviceplan',
153                   completer=get_resource_name_completion_list('Microsoft.Web/serverFarms'),
154                   help="name or resource id of the app service plan. Use 'appservice plan create' to get one",
155                   local_context_attribute=LocalContextAttribute(name='plan_name', actions=[LocalContextAction.GET]))
156        c.ignore('language')
157        c.ignore('using_webapp_up')
158
159    with self.argument_context('webapp show') as c:
160        c.argument('name', arg_type=webapp_name_arg_type)
161
162    with self.argument_context('webapp list-instances') as c:
163        c.argument('name', arg_type=webapp_name_arg_type, id_part=None)
164        c.argument('slot', options_list=['--slot', '-s'], help='Name of the web app slot. Default to the productions slot if not specified.')
165
166    with self.argument_context('webapp list-runtimes') as c:
167        c.argument('linux', action='store_true', help='list runtime stacks for linux based web apps')
168
169    with self.argument_context('webapp deleted list') as c:
170        c.argument('name', arg_type=webapp_name_arg_type, id_part=None)
171        c.argument('slot', options_list=['--slot', '-s'], help='Name of the deleted web app slot.')
172
173    with self.argument_context('webapp deleted restore') as c:
174        c.argument('deleted_id', options_list=['--deleted-id'], help='Resource ID of the deleted web app')
175        c.argument('name', options_list=['--name', '-n'], help='name of the web app to restore the deleted content to')
176        c.argument('slot', options_list=['--slot', '-s'], help='slot to restore the deleted content to')
177        c.argument('restore_content_only', action='store_true',
178                   help='restore only deleted files without web app settings')
179
180    with self.argument_context('webapp traffic-routing') as c:
181        c.argument('distribution', options_list=['--distribution', '-d'], nargs='+',
182                   help='space-separated slot routings in a format of `<slot-name>=<percentage>` e.g. staging=50. Unused traffic percentage will go to the Production slot')
183
184    with self.argument_context('webapp update') as c:
185        c.argument('client_affinity_enabled', help="Enables sending session affinity cookies.",
186                   arg_type=get_three_state_flag(return_label=True))
187        c.argument('https_only', help="Redirect all traffic made to an app using HTTP to HTTPS.",
188                   arg_type=get_three_state_flag(return_label=True))
189        c.argument('force_dns_registration', help="If true, web app hostname is force registered with DNS",
190                   arg_type=get_three_state_flag(return_label=True), deprecate_info=c.deprecate(expiration='3.0.0'))
191        c.argument('skip_custom_domain_verification',
192                   help="If true, custom (non *.azurewebsites.net) domains associated with web app are not verified",
193                   arg_type=get_three_state_flag(return_label=True), deprecate_info=c.deprecate(expiration='3.0.0'))
194        c.argument('ttl_in_seconds', help="Time to live in seconds for web app's default domain name",
195                   arg_type=get_three_state_flag(return_label=True), deprecate_info=c.deprecate(expiration='3.0.0'))
196        c.argument('skip_dns_registration', help="If true web app hostname is not registered with DNS on creation",
197                   arg_type=get_three_state_flag(return_label=True), deprecate_info=c.deprecate(expiration='3.0.0'))
198
199    with self.argument_context('webapp browse') as c:
200        c.argument('logs', options_list=['--logs', '-l'], action='store_true',
201                   help='Enable viewing the log stream immediately after launching the web app')
202    with self.argument_context('webapp delete') as c:
203        c.argument('name', arg_type=webapp_name_arg_type, local_context_attribute=None)
204        c.argument('keep_empty_plan', action='store_true', help='keep empty app service plan')
205        c.argument('keep_metrics', action='store_true', help='keep app metrics')
206        c.argument('keep_dns_registration', action='store_true', help='keep DNS registration',
207                   deprecate_info=c.deprecate(expiration='3.0.0'))
208
209    with self.argument_context('webapp webjob') as c:
210        c.argument('webjob_name', help='The name of the webjob', options_list=['--webjob-name', '-w'])
211    with self.argument_context('webapp webjob continuous list') as c:
212        c.argument('name', arg_type=webapp_name_arg_type, id_part=None)
213    with self.argument_context('webapp webjob triggered list') as c:
214        c.argument('name', arg_type=webapp_name_arg_type, id_part=None)
215
216    for scope in ['webapp', 'functionapp', 'logicapp']:
217        with self.argument_context(scope + ' create') as c:
218            c.argument('deployment_container_image_name', options_list=['--deployment-container-image-name', '-i'],
219                       help='Linux only. Container image name from Docker Hub, e.g. publisher/image-name:tag')
220            c.argument('deployment_local_git', action='store_true', options_list=['--deployment-local-git', '-l'],
221                       help='enable local git')
222            c.argument('deployment_zip', options_list=['--deployment-zip', '-z'],
223                       help='perform deployment using zip file')
224            c.argument('deployment_source_url', options_list=['--deployment-source-url', '-u'],
225                       help='Git repository URL to link with manual integration')
226            c.argument('deployment_source_branch', options_list=['--deployment-source-branch', '-b'],
227                       help='the branch to deploy')
228            c.argument('tags', arg_type=tags_type)
229
230    for scope in ['webapp', 'functionapp']:
231        with self.argument_context(scope) as c:
232            c.argument('assign_identities', nargs='*', options_list=['--assign-identity'],
233                       help='accept system or user assigned identities separated by spaces. Use \'[system]\' to refer system assigned identity, or a resource id to refer user assigned identity. Check out help for more examples')
234            c.argument('scope', options_list=['--scope'], help="Scope that the system assigned identity can access")
235            c.argument('role', options_list=['--role'], help="Role name or id the system assigned identity will have")
236
237        with self.argument_context(scope + ' config ssl bind') as c:
238            c.argument('ssl_type', help='The ssl cert type', arg_type=get_enum_type(['SNI', 'IP']))
239        with self.argument_context(scope + ' config ssl upload') as c:
240            c.argument('certificate_password', help='The ssl cert password')
241            c.argument('certificate_file', type=file_type, help='The filepath for the .pfx file')
242            c.argument('slot', options_list=['--slot', '-s'],
243                       help='The name of the slot. Default to the productions slot if not specified')
244        with self.argument_context(scope + ' config ssl') as c:
245            c.argument('certificate_thumbprint', help='The ssl cert thumbprint')
246        with self.argument_context(scope + ' config appsettings') as c:
247            c.argument('settings', nargs='+', help="space-separated app settings in a format of `<name>=<value>`")
248            c.argument('setting_names', nargs='+', help="space-separated app setting names")
249        with self.argument_context(scope + ' config ssl import') as c:
250            c.argument('key_vault', help='The name or resource ID of the Key Vault')
251            c.argument('key_vault_certificate_name', help='The name of the certificate in Key Vault')
252        with self.argument_context(scope + ' config ssl create') as c:
253            c.argument('hostname', help='The custom domain name')
254            c.argument('name', options_list=['--name', '-n'], help='Name of the web app.')
255            c.argument('resource-group', options_list=['--resource-group', '-g'], help='Name of resource group.')
256        with self.argument_context(scope + ' config ssl show') as c:
257            c.argument('certificate_name', help='The name of the certificate')
258        with self.argument_context(scope + ' config hostname') as c:
259            c.argument('hostname', completer=get_hostname_completion_list,
260                       help="hostname assigned to the site, such as custom domains", id_part='child_name_1')
261        with self.argument_context(scope + ' deployment user') as c:
262            c.argument('user_name', help='user name')
263            c.argument('password', help='password, will prompt if not specified')
264        with self.argument_context(scope + ' deployment source') as c:
265            c.argument('manual_integration', action='store_true',
266                       help='disable automatic sync between source control and web')
267            c.argument('repo_url', options_list=['--repo-url', '-u'],
268                       help='repository url to pull the latest source from, e.g. https://github.com/foo/foo-web')
269            c.argument('branch', help='the branch name of the repository')
270            c.argument('repository_type', help='repository type',
271                       arg_type=get_enum_type(['git', 'mercurial', 'github', 'externalgit', 'localgit']))
272            c.argument('git_token', help='Git access token required for auto sync')
273            c.argument('github_action', options_list=['--github-action'], help='If using github action, default to False')
274        with self.argument_context(scope + ' identity') as c:
275            c.argument('scope', help="The scope the managed identity has access to")
276            c.argument('role', help="Role name or id the managed identity will be assigned")
277        with self.argument_context(scope + ' identity assign') as c:
278            c.argument('assign_identities', options_list=['--identities'], nargs='*', help="Space-separated identities to assign. Use '{0}' to refer to the system assigned identity. Default: '{0}'".format(MSI_LOCAL_ID))
279        with self.argument_context(scope + ' identity remove') as c:
280            c.argument('remove_identities', options_list=['--identities'], nargs='*', help="Space-separated identities to assign. Use '{0}' to refer to the system assigned identity. Default: '{0}'".format(MSI_LOCAL_ID))
281
282        with self.argument_context(scope + ' deployment source config-zip') as c:
283            c.argument('src', help='a zip file path for deployment')
284            c.argument('build_remote', help='enable remote build during deployment',
285                       arg_type=get_three_state_flag(return_label=True))
286            c.argument('timeout', type=int, options_list=['--timeout', '-t'],
287                       help='Configurable timeout in seconds for checking the status of deployment',
288                       validator=validate_timeout_value)
289
290        with self.argument_context(scope + ' config appsettings list') as c:
291            c.argument('name', arg_type=(webapp_name_arg_type if scope == 'webapp' else functionapp_name_arg_type),
292                       id_part=None)
293
294        with self.argument_context(scope + ' config hostname list') as c:
295            c.argument('webapp_name', arg_type=webapp_name_arg_type, id_part=None, options_list='--webapp-name')
296
297        with self.argument_context(scope + ' cors') as c:
298            c.argument('allowed_origins', options_list=['--allowed-origins', '-a'], nargs='*',
299                       help='space separated origins that should be allowed to make cross-origin calls (for example: http://example.com:12345). To allow all, use "*" and remove all other origins from the list')
300
301        with self.argument_context(scope + ' config set') as c:
302            c.argument('number_of_workers', help='The number of workers to be allocated.', type=int)
303            c.argument('remote_debugging_enabled', help='enable or disable remote debugging',
304                       arg_type=get_three_state_flag(return_label=True))
305            c.argument('web_sockets_enabled', help='enable or disable web sockets',
306                       arg_type=get_three_state_flag(return_label=True))
307            c.argument('always_on',
308                       help='ensure web app gets loaded all the time, rather unloaded after been idle. Recommended when you have continuous web jobs running',
309                       arg_type=get_three_state_flag(return_label=True))
310            c.argument('auto_heal_enabled', help='enable or disable auto heal',
311                       arg_type=get_three_state_flag(return_label=True))
312            c.argument('use32_bit_worker_process', options_list=['--use-32bit-worker-process'],
313                       help='use 32 bits worker process or not', arg_type=get_three_state_flag(return_label=True))
314            c.argument('php_version', help='The version used to run your web app if using PHP, e.g., 5.5, 5.6, 7.0')
315            c.argument('python_version', help='The version used to run your web app if using Python, e.g., 2.7, 3.4')
316            c.argument('net_framework_version', help="The version used to run your web app if using .NET Framework, e.g., 'v4.0' for .NET 4.6 and 'v3.0' for .NET 3.5")
317            c.argument('linux_fx_version', help="The runtime stack used for your linux-based webapp, e.g., \"RUBY|2.5.5\", \"NODE|10.14\", \"PHP|7.2\", \"DOTNETCORE|2.1\". See https://aka.ms/linux-stacks for more info.")
318            c.argument('windows_fx_version', help="A docker image name used for your windows container web app, e.g., microsoft/nanoserver:ltsc2016")
319            if scope == 'functionapp':
320                c.ignore('windows_fx_version')
321            c.argument('pre_warmed_instance_count', options_list=['--prewarmed-instance-count'],
322                       help="Number of pre-warmed instances a function app has")
323            if scope == 'webapp':
324                c.ignore('reserved_instance_count')
325            c.argument('java_version',
326                       help="The version used to run your web app if using Java, e.g., '1.7' for Java 7, '1.8' for Java 8")
327            c.argument('java_container', help="The java container, e.g., Tomcat, Jetty")
328            c.argument('java_container_version', help="The version of the java container, e.g., '8.0.23' for Tomcat")
329            c.argument('min_tls_version',
330                       help="The minimum version of TLS required for SSL requests, e.g., '1.0', '1.1', '1.2'")
331            c.argument('http20_enabled', help="configures a web site to allow clients to connect over http2.0.",
332                       arg_type=get_three_state_flag(return_label=True))
333            c.argument('app_command_line', options_list=['--startup-file'],
334                       help="The startup file for linux hosted web apps, e.g. 'process.json' for Node.js web")
335            c.argument('ftps_state', help="Set the Ftps state value for an app. Default value is 'AllAllowed'.",
336                       arg_type=get_enum_type(FTPS_STATE_TYPES))
337            c.argument('vnet_route_all_enabled', help="Configure regional VNet integration to route all traffic to the VNet.",
338                       arg_type=get_three_state_flag(return_label=True))
339            c.argument('generic_configurations', nargs='+',
340                       help='Provide site configuration list in a format of either `key=value` pair or `@<json_file>`. To avoid compatibility issues, it is recommended to use a JSON file to provide these configurations. If using a key=value pair, PowerShell and Windows Command Prompt users should be sure to use escape characters like so: {\\"key\\": value}, instead of: `{"key": value}`.')
341
342        with self.argument_context(scope + ' config container') as c:
343            c.argument('docker_registry_server_url', options_list=['--docker-registry-server-url', '-r'],
344                       help='the container registry server url')
345            c.argument('docker_custom_image_name', options_list=['--docker-custom-image-name', '-c', '-i'],
346                       help='the container custom image name and optionally the tag name')
347            c.argument('docker_registry_server_user', options_list=['--docker-registry-server-user', '-u'],
348                       help='the container registry server username')
349            c.argument('docker_registry_server_password', options_list=['--docker-registry-server-password', '-p'],
350                       help='the container registry server password')
351            c.argument('websites_enable_app_service_storage', options_list=['--enable-app-service-storage', '-t'],
352                       help='enables platform storage (custom container only)',
353                       arg_type=get_three_state_flag(return_label=True))
354            c.argument('multicontainer_config_type', options_list=['--multicontainer-config-type'], help='config type',
355                       arg_type=get_enum_type(MULTI_CONTAINER_TYPES))
356            c.argument('multicontainer_config_file', options_list=['--multicontainer-config-file'],
357                       help="config file for multicontainer apps")
358            c.argument('show_multicontainer_config', action='store_true',
359                       help='shows decoded config if a multicontainer config is set')
360
361        with self.argument_context(scope + ' deployment container config') as c:
362            c.argument('enable', options_list=['--enable-cd', '-e'], help='enable/disable continuous deployment',
363                       arg_type=get_three_state_flag(return_label=True))
364
365    with self.argument_context('webapp config connection-string list') as c:
366        c.argument('name', arg_type=webapp_name_arg_type, id_part=None)
367
368    with self.argument_context('webapp config storage-account list') as c:
369        c.argument('name', arg_type=webapp_name_arg_type, id_part=None)
370
371    with self.argument_context('webapp config hostname') as c:
372        c.argument('webapp_name',
373                   help="webapp name. You can configure the default using `az configure --defaults web=<name>`",
374                   configured_default='web',
375                   completer=get_resource_name_completion_list('Microsoft.Web/sites'), id_part='name',
376                   local_context_attribute=LocalContextAttribute(name='web_name', actions=[LocalContextAction.GET]))
377    with self.argument_context('webapp deployment list-publishing-profiles') as c:
378        c.argument('xml', options_list=['--xml'], required=False, help='retrieves the publishing profile details in XML format')
379    with self.argument_context('webapp deployment slot') as c:
380        c.argument('slot', help='the name of the slot')
381        c.argument('webapp', arg_type=name_arg_type, completer=get_resource_name_completion_list('Microsoft.Web/sites'),
382                   help='Name of the webapp', id_part='name',
383                   local_context_attribute=LocalContextAttribute(name='web_name', actions=[LocalContextAction.GET]))
384        c.argument('auto_swap_slot', help='target slot to auto swap', default='production')
385        c.argument('disable', help='disable auto swap', action='store_true')
386        c.argument('target_slot', help="target slot to swap, default to 'production'")
387        c.argument('preserve_vnet', help="preserve Virtual Network to the slot during swap, default to 'true'",
388                   arg_type=get_three_state_flag(return_label=True))
389    with self.argument_context('webapp deployment slot create') as c:
390        c.argument('configuration_source',
391                   help="source slot to clone configurations from. Use web app's name to refer to the production slot")
392    with self.argument_context('webapp deployment slot swap') as c:
393        c.argument('action',
394                   help="swap types. use 'preview' to apply target slot's settings on the source slot first; use 'swap' to complete it; use 'reset' to reset the swap",
395                   arg_type=get_enum_type(['swap', 'preview', 'reset']))
396
397    with self.argument_context('webapp deployment github-actions')as c:
398        c.argument('name', arg_type=webapp_name_arg_type)
399        c.argument('resource_group', arg_type=resource_group_name_type, options_list=['--resource-group', '-g'])
400        c.argument('repo', help='The GitHub repository to which the workflow file will be added. In the format: <owner>/<repository-name>')
401        c.argument('token', help='A Personal Access Token with write access to the specified repository. For more information: https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line')
402        c.argument('slot', options_list=['--slot', '-s'], help='The name of the slot. Default to the production slot if not specified.')
403        c.argument('branch', options_list=['--branch', '-b'], help='The branch to which the workflow file will be added. Defaults to "master" if not specified.')
404        c.argument('login_with_github', help='Interactively log in with Github to retrieve the Personal Access Token', action='store_true')
405
406    with self.argument_context('webapp deployment github-actions add')as c:
407        c.argument('runtime', options_list=['--runtime', '-r'], help='Canonicalized web runtime in the format of Framework|Version, e.g. "PHP|5.6". Use "az webapp list-runtimes" for available list.')
408        c.argument('force', options_list=['--force', '-f'], help='When true, the command will overwrite any workflow file with a conflicting name.', action='store_true')
409
410    with self.argument_context('webapp log config') as c:
411        c.argument('application_logging', help='configure application logging',
412                   arg_type=get_enum_type(['filesystem', 'azureblobstorage', 'off']))
413        c.argument('detailed_error_messages', help='configure detailed error messages',
414                   arg_type=get_three_state_flag(return_label=True))
415        c.argument('failed_request_tracing', help='configure failed request tracing',
416                   arg_type=get_three_state_flag(return_label=True))
417        c.argument('level', help='logging level',
418                   arg_type=get_enum_type(['error', 'warning', 'information', 'verbose']))
419        c.argument('web_server_logging', help='configure Web server logging',
420                   arg_type=get_enum_type(['off', 'filesystem']))
421        c.argument('docker_container_logging', help='configure gathering STDOUT and STDERR output from container',
422                   arg_type=get_enum_type(['off', 'filesystem']))
423
424    with self.argument_context('webapp log tail') as c:
425        c.argument('provider',
426                   help="By default all live traces configured by `az webapp log config` will be shown, but you can scope to certain providers/folders, e.g. 'application', 'http', etc. For details, check out https://github.com/projectkudu/kudu/wiki/Diagnostic-Log-Stream")
427
428    with self.argument_context('webapp log download') as c:
429        c.argument('log_file', default='webapp_logs.zip', type=file_type, completer=FilesCompleter(),
430                   help='the downloaded zipped log file path')
431
432    with self.argument_context('webapp log deployment show') as c:
433        c.argument('name', arg_type=webapp_name_arg_type, id_part=None)
434        c.argument('resource_group', arg_type=resource_group_name_type)
435        c.argument('slot', options_list=['--slot', '-s'], help="the name of the slot. Default to the productions slot if not specified")
436        c.argument('deployment_id', options_list=['--deployment-id'], help='Deployment ID. If none specified, returns the deployment logs of the latest deployment.')
437
438    with self.argument_context('webapp log deployment list') as c:
439        c.argument('name', arg_type=webapp_name_arg_type, id_part=None)
440        c.argument('resource_group', arg_type=resource_group_name_type)
441        c.argument('slot', options_list=['--slot', '-s'], help="the name of the slot. Default to the productions slot if not specified")
442
443    with self.argument_context('functionapp log deployment show') as c:
444        c.argument('name', arg_type=functionapp_name_arg_type, id_part=None)
445        c.argument('resource_group', arg_type=resource_group_name_type)
446        c.argument('slot', options_list=['--slot', '-s'], help="the name of the slot. Default to the productions slot if not specified")
447        c.argument('deployment_id', options_list=['--deployment-id'], help='Deployment ID. If none specified, returns the deployment logs of the latest deployment.')
448
449    with self.argument_context('functionapp log deployment list') as c:
450        c.argument('name', arg_type=functionapp_name_arg_type, id_part=None)
451        c.argument('resource_group', arg_type=resource_group_name_type)
452        c.argument('slot', options_list=['--slot', '-s'], help="the name of the slot. Default to the productions slot if not specified")
453
454    for scope in ['appsettings', 'connection-string']:
455        with self.argument_context('webapp config ' + scope) as c:
456            c.argument('settings', nargs='+', help="space-separated {} in a format of `<name>=<value>`".format(scope))
457            c.argument('slot_settings', nargs='+',
458                       help="space-separated slot {} in a format of either `<name>=<value>` or `@<json_file>`".format(
459                           scope))
460            c.argument('setting_names', nargs='+', help="space-separated {} names".format(scope))
461
462    with self.argument_context('webapp config connection-string') as c:
463        c.argument('connection_string_type', options_list=['--connection-string-type', '-t'],
464                   help='connection string type', arg_type=get_enum_type(ConnectionStringType))
465        c.argument('ids', options_list=['--ids'],
466                   help="One or more resource IDs (space delimited). If provided no other 'Resource Id' arguments should be specified.",
467                   required=True)
468        c.argument('resource_group', options_list=['--resource-group', '-g'],
469                   help='Name of resource group. You can configure the default group using `az configure --default-group=<name>`. If `--ids` is provided this should NOT be specified.')
470        c.argument('name', options_list=['--name', '-n'],
471                   help='Name of the web app. You can configure the default using `az configure --defaults web=<name>`. If `--ids` is provided this should NOT be specified.',
472                   local_context_attribute=LocalContextAttribute(name='web_name', actions=[LocalContextAction.GET]))
473
474    with self.argument_context('webapp config storage-account') as c:
475        c.argument('custom_id', options_list=['--custom-id', '-i'], help='name of the share configured within the web app')
476        c.argument('storage_type', options_list=['--storage-type', '-t'], help='storage type',
477                   arg_type=get_enum_type(AzureStorageType))
478        c.argument('account_name', options_list=['--account-name', '-a'], help='storage account name')
479        c.argument('share_name', options_list=['--share-name', '--sn'],
480                   help='name of the file share as given in the storage account')
481        c.argument('access_key', options_list=['--access-key', '-k'], help='storage account access key')
482        c.argument('mount_path', options_list=['--mount-path', '-m'],
483                   help='the path which the web app uses to read-write data ex: /share1 or /share2')
484        c.argument('slot', options_list=['--slot', '-s'],
485                   help="the name of the slot. Default to the productions slot if not specified")
486    with self.argument_context('webapp config storage-account add') as c:
487        c.argument('slot_setting', options_list=['--slot-setting'], help="slot setting")
488    with self.argument_context('webapp config storage-account update') as c:
489        c.argument('slot_setting', options_list=['--slot-setting'], help="slot setting")
490
491    with self.argument_context('webapp config backup') as c:
492        c.argument('storage_account_url', help='URL with SAS token to the blob storage container',
493                   options_list=['--container-url'])
494        c.argument('webapp_name', help='The name of the web app',
495                   local_context_attribute=LocalContextAttribute(name='web_name', actions=[LocalContextAction.GET]))
496        c.argument('db_name', help='Name of the database in the backup', arg_group='Database')
497        c.argument('db_connection_string', help='Connection string for the database in the backup',
498                   arg_group='Database')
499        c.argument('db_type', help='Type of database in the backup', arg_group='Database',
500                   arg_type=get_enum_type(DatabaseType))
501
502    with self.argument_context('webapp config backup create') as c:
503        c.argument('backup_name',
504                   help='Name of the backup. If unspecified, the backup will be named with the web app name and a timestamp',
505                   local_context_attribute=LocalContextAttribute(name='backup_name', actions=[LocalContextAction.SET],
506                                                                 scopes=['webapp']))
507
508    with self.argument_context('webapp config backup update') as c:
509        c.argument('backup_name',
510                   help='Name of the backup. If unspecified, the backup will be named with the web app name and a timestamp',
511                   local_context_attribute=LocalContextAttribute(name='backup_name', actions=[LocalContextAction.GET]))
512        c.argument('frequency',
513                   help='How often to backup. Use a number followed by d or h, e.g. 5d = 5 days, 2h = 2 hours')
514        c.argument('keep_at_least_one_backup', help='Always keep one backup, regardless of how old it is',
515                   options_list=['--retain-one'], arg_type=get_three_state_flag(return_label=True))
516        c.argument('retention_period_in_days',
517                   help='How many days to keep a backup before automatically deleting it. Set to 0 for indefinite retention',
518                   options_list=['--retention'])
519
520    with self.argument_context('webapp config backup restore') as c:
521        c.argument('backup_name', help='Name of the backup to restore',
522                   local_context_attribute=LocalContextAttribute(name='backup_name', actions=[LocalContextAction.GET]))
523        c.argument('target_name',
524                   help='The name to use for the restored web app. If unspecified, will default to the name that was used when the backup was created')
525        c.argument('overwrite', help='Overwrite the source web app, if --target-name is not specified',
526                   action='store_true')
527        c.argument('ignore_hostname_conflict', help='Ignores custom hostnames stored in the backup',
528                   action='store_true')
529
530    with self.argument_context('webapp config snapshot') as c:
531        c.argument('name', arg_type=webapp_name_arg_type)
532        c.argument('slot', options_list=['--slot', '-s'], help='The name of the slot.')
533
534    with self.argument_context('webapp config snapshot list') as c:
535        c.argument('name', arg_type=webapp_name_arg_type, id_part=None)
536
537    with self.argument_context('webapp config snapshot restore') as c:
538        c.argument('time', help='Timestamp of the snapshot to restore.')
539        c.argument('restore_content_only', help='Restore the web app files without restoring the settings.')
540        c.argument('source_resource_group', help='Name of the resource group to retrieve snapshot from.')
541        c.argument('source_name', help='Name of the web app to retrieve snapshot from.')
542        c.argument('source_slot', help='Name of the web app slot to retrieve snapshot from.')
543
544    with self.argument_context('webapp auth update') as c:
545        c.argument('enabled', arg_type=get_three_state_flag(return_label=True))
546        c.argument('token_store_enabled', options_list=['--token-store'],
547                   arg_type=get_three_state_flag(return_label=True), help='use App Service Token Store')
548        c.argument('action', arg_type=get_enum_type(AUTH_TYPES))
549        c.argument('runtime_version',
550                   help='Runtime version of the Authentication/Authorization feature in use for the current app')
551        c.argument('token_refresh_extension_hours', type=float, help="Hours, must be formattable into a float")
552        c.argument('allowed_external_redirect_urls', nargs='+', help="One or more urls (space-delimited).")
553        c.argument('client_id', options_list=['--aad-client-id'], arg_group='Azure Active Directory',
554                   help='Application ID to integrate AAD organization account Sign-in into your web app')
555        c.argument('client_secret', options_list=['--aad-client-secret'], arg_group='Azure Active Directory',
556                   help='AAD application secret')
557        c.argument('client_secret_certificate_thumbprint', options_list=['--aad-client-secret-certificate-thumbprint', '--thumbprint'], arg_group='Azure Active Directory',
558                   help='Alternative to AAD Client Secret, thumbprint of a certificate used for signing purposes')
559        c.argument('allowed_audiences', nargs='+', options_list=['--aad-allowed-token-audiences'],
560                   arg_group='Azure Active Directory', help="One or more token audiences (space-delimited).")
561        c.argument('issuer', options_list=['--aad-token-issuer-url'],
562                   help='This url can be found in the JSON output returned from your active directory endpoint using your tenantID. The endpoint can be queried from `az cloud show` at \"endpoints.activeDirectory\". '
563                        'The tenantID can be found using `az account show`. Get the \"issuer\" from the JSON at <active directory endpoint>/<tenantId>/.well-known/openid-configuration.',
564                   arg_group='Azure Active Directory')
565        c.argument('facebook_app_id', arg_group='Facebook',
566                   help="Application ID to integrate Facebook Sign-in into your web app")
567        c.argument('facebook_app_secret', arg_group='Facebook', help='Facebook Application client secret')
568        c.argument('facebook_oauth_scopes', nargs='+',
569                   help="One or more facebook authentication scopes (space-delimited).", arg_group='Facebook')
570        c.argument('twitter_consumer_key', arg_group='Twitter',
571                   help='Application ID to integrate Twitter Sign-in into your web app')
572        c.argument('twitter_consumer_secret', arg_group='Twitter', help='Twitter Application client secret')
573        c.argument('google_client_id', arg_group='Google',
574                   help='Application ID to integrate Google Sign-in into your web app')
575        c.argument('google_client_secret', arg_group='Google', help='Google Application client secret')
576        c.argument('google_oauth_scopes', nargs='+', help="One or more Google authentication scopes (space-delimited).",
577                   arg_group='Google')
578        c.argument('microsoft_account_client_id', arg_group='Microsoft',
579                   help="AAD V2 Application ID to integrate Microsoft account Sign-in into your web app")
580        c.argument('microsoft_account_client_secret', arg_group='Microsoft', help='AAD V2 Application client secret')
581        c.argument('microsoft_account_oauth_scopes', nargs='+',
582                   help="One or more Microsoft authentification scopes (space-delimited).", arg_group='Microsoft')
583
584    with self.argument_context('webapp hybrid-connection') as c:
585        c.argument('name', arg_type=webapp_name_arg_type, id_part=None)
586        c.argument('slot', help="the name of the slot. Default to the productions slot if not specified")
587        c.argument('namespace', help="Hybrid connection namespace")
588        c.argument('hybrid_connection', help="Hybrid connection name")
589
590    with self.argument_context('functionapp hybrid-connection') as c:
591        c.argument('name', id_part=None, local_context_attribute=LocalContextAttribute(name='functionapp_name',
592                                                                                       actions=[
593                                                                                           LocalContextAction.GET]))
594        c.argument('slot', help="the name of the slot. Default to the productions slot if not specified")
595        c.argument('namespace', help="Hybrid connection namespace")
596        c.argument('hybrid_connection', help="Hybrid connection name")
597
598    with self.argument_context('appservice hybrid-connection set-key') as c:
599        c.argument('plan', help="AppService plan",
600                   local_context_attribute=LocalContextAttribute(name='plan_name', actions=[LocalContextAction.GET]))
601        c.argument('namespace', help="Hybrid connection namespace")
602        c.argument('hybrid_connection', help="Hybrid connection name")
603        c.argument('key_type', help="Which key (primary or secondary) should be used")
604
605    with self.argument_context('appservice vnet-integration list') as c:
606        c.argument('plan', help="AppService plan",
607                   local_context_attribute=LocalContextAttribute(name='plan_name', actions=[LocalContextAction.GET]))
608        c.argument('resource_group', arg_type=resource_group_name_type)
609
610    with self.argument_context('webapp up') as c:
611        c.argument('name', arg_type=webapp_name_arg_type,
612                   local_context_attribute=LocalContextAttribute(name='web_name', actions=[LocalContextAction.GET,
613                                                                                           LocalContextAction.SET],
614                                                                 scopes=['webapp', 'cupertino']))
615        c.argument('plan', options_list=['--plan', '-p'], configured_default='appserviceplan',
616                   completer=get_resource_name_completion_list('Microsoft.Web/serverFarms'),
617                   help="name of the appserviceplan associated with the webapp",
618                   local_context_attribute=LocalContextAttribute(name='plan_name', actions=[LocalContextAction.GET]))
619        c.argument('sku', arg_type=sku_arg_type)
620        c.argument('os_type', options_list=['--os-type'], arg_type=get_enum_type(OS_TYPES), help="Set the OS type for the app to be created.")
621        c.argument('runtime', options_list=['--runtime', '-r'], help="canonicalized web runtime in the format of Framework|Version, e.g. \"PHP|7.2\". Allowed delimiters: \"|\" or \":\". "
622                                                                     "Use `az webapp list-runtimes` for available list.")
623        c.argument('dryrun', help="show summary of the create and deploy operation instead of executing it",
624                   default=False, action='store_true')
625        c.argument('location', arg_type=get_location_type(self.cli_ctx))
626        c.argument('launch_browser', help="Launch the created app using the default browser", default=False,
627                   action='store_true', options_list=['--launch-browser', '-b'])
628        c.argument('logs',
629                   help="Configure default logging required to enable viewing log stream immediately after launching the webapp",
630                   default=False, action='store_true')
631        c.argument('html', help="Ignore app detection and deploy as an html app", default=False, action='store_true')
632        c.argument('app_service_environment', options_list=['--app-service-environment', '-e'], help='name of the (pre-existing) App Service Environment to deploy to. Requires an Isolated V2 sku [I1v2, I2v2, I3v2]')
633
634    with self.argument_context('webapp ssh') as c:
635        c.argument('port', options_list=['--port', '-p'],
636                   help='Port for the remote connection. Default: Random available port', type=int)
637        c.argument('timeout', options_list=['--timeout', '-t'], help='timeout in seconds. Defaults to none', type=int)
638        c.argument('instance', options_list=['--instance', '-i'], help='Webapp instance to connect to. Defaults to none.')
639
640    with self.argument_context('webapp create-remote-connection') as c:
641        c.argument('port', options_list=['--port', '-p'],
642                   help='Port for the remote connection. Default: Random available port', type=int)
643        c.argument('timeout', options_list=['--timeout', '-t'], help='timeout in seconds. Defaults to none', type=int)
644        c.argument('instance', options_list=['--instance', '-i'], help='Webapp instance to connect to. Defaults to none.')
645
646    with self.argument_context('webapp vnet-integration') as c:
647        c.argument('name', arg_type=webapp_name_arg_type, id_part=None)
648        c.argument('slot', help="The name of the slot. Default to the productions slot if not specified.")
649        c.argument('vnet', help="The name or resource ID of the Vnet",
650                   local_context_attribute=LocalContextAttribute(name='vnet_name', actions=[LocalContextAction.GET]))
651        c.argument('subnet', help="The name or resource ID of the subnet",
652                   local_context_attribute=LocalContextAttribute(name='subnet_name', actions=[LocalContextAction.GET]))
653        c.argument('skip_delegation_check', help="Skip check if you do not have permission or the VNet is in another subscription.",
654                   arg_type=get_three_state_flag(return_label=True))
655
656    with self.argument_context('webapp deploy') as c:
657        c.argument('name', options_list=['--name', '-n'], help='Name of the webapp to deploy to.')
658        c.argument('src_path', options_list=['--src-path'], help='Path of the artifact to be deployed. Ex: "myapp.zip" or "/myworkspace/apps/myapp.war"')
659        c.argument('src_url', options_list=['--src-url'], help='URL of the artifact. The webapp will pull the artifact from this URL. Ex: "http://mysite.com/files/myapp.war?key=123"')
660        c.argument('target_path', options_list=['--target-path'], help='Absolute path that the artifact should be deployed to. Defaults to "home/site/wwwroot/" Ex: "/home/site/deployments/tools/", "/home/site/scripts/startup-script.sh".')
661        c.argument('artifact_type', options_list=['--type'], help='Used to override the type of artifact being deployed.', choices=['war', 'jar', 'ear', 'lib', 'startup', 'static', 'zip'])
662        c.argument('is_async', options_list=['--async'], help='If true, the artifact is deployed asynchronously. (The command will exit once the artifact is pushed to the web app.)', choices=['true', 'false'])
663        c.argument('restart', options_list=['--restart'], help='If true, the web app will be restarted following the deployment. Set this to false if you are deploying multiple artifacts and do not want to restart the site on the earlier deployments.', choices=['true', 'false'])
664        c.argument('clean', options_list=['--clean'], help='If true, cleans the target directory prior to deploying the file(s). Default value is determined based on artifact type.', choices=['true', 'false'])
665        c.argument('ignore_stack', options_list=['--ignore-stack'], help='If true, any stack-specific defaults are ignored.', choices=['true', 'false'])
666        c.argument('timeout', options_list=['--timeout'], help='Timeout for the deployment operation in milliseconds.')
667        c.argument('slot', help="The name of the slot. Default to the productions slot if not specified.")
668
669    with self.argument_context('functionapp deploy') as c:
670        c.argument('name', options_list=['--name', '-n'], help='Name of the function app to deploy to.')
671        c.argument('src_path', options_list=['--src-path'], help='Path of the artifact to be deployed. Ex: "myapp.zip" or "/myworkspace/apps/myapp.war"')
672        c.argument('src_url', options_list=['--src-url'], help='URL of the artifact. The webapp will pull the artifact from this URL. Ex: "http://mysite.com/files/myapp.war?key=123"')
673        c.argument('target_path', options_list=['--target-path'], help='Absolute path that the artifact should be deployed to. Defaults to "home/site/wwwroot/". Ex: "/home/site/deployments/tools/", "/home/site/scripts/startup-script.sh".')
674        c.argument('artifact_type', options_list=['--type'], help='Used to override the type of artifact being deployed.', choices=['war', 'jar', 'ear', 'lib', 'startup', 'static', 'zip'])
675        c.argument('is_async', options_list=['--async'], help='Asynchronous deployment', choices=['true', 'false'])
676        c.argument('restart', options_list=['--restart'], help='If true, the web app will be restarted following the deployment, default value is true. Set this to false if you are deploying multiple artifacts and do not want to restart the site on the earlier deployments.', choices=['true', 'false'])
677        c.argument('clean', options_list=['--clean'], help='If true, cleans the target directory prior to deploying the file(s). Default value is determined based on artifact type.', choices=['true', 'false'])
678        c.argument('ignore_stack', options_list=['--ignore-stack'], help='If true, any stack-specific defaults are ignored.', choices=['true', 'false'])
679        c.argument('timeout', options_list=['--timeout'], help='Timeout for the deployment operation in milliseconds.')
680        c.argument('slot', help="The name of the slot. Default to the productions slot if not specified.")
681
682    with self.argument_context('functionapp vnet-integration') as c:
683        c.argument('name', arg_type=functionapp_name_arg_type, id_part=None)
684        c.argument('slot', help="The name of the slot. Default to the productions slot if not specified")
685        c.argument('vnet', help="The name or resource ID of the Vnet", validator=validate_add_vnet,
686                   local_context_attribute=LocalContextAttribute(name='vnet_name', actions=[LocalContextAction.GET]))
687        c.argument('subnet', help="The name or resource ID of the subnet",
688                   local_context_attribute=LocalContextAttribute(name='subnet_name', actions=[LocalContextAction.GET]))
689        c.argument('skip_delegation_check', help="Skip check if you do not have permission or the VNet is in another subscription.",
690                   arg_type=get_three_state_flag(return_label=True))
691
692    for scope in ['functionapp', 'logicapp']:
693        app_type = scope[:-3]  # 'function' or 'logic'
694        with self.argument_context(scope) as c:
695            c.ignore('app_instance')
696            c.argument('name', arg_type=name_arg_type_dict[scope], id_part='name', help='name of the {} app'.format(app_type))
697            c.argument('slot', options_list=['--slot', '-s'],
698                       help="the name of the slot. Default to the productions slot if not specified")
699
700        with self.argument_context(scope + ' create') as c:
701            c.argument('plan', options_list=['--plan', '-p'], configured_default='appserviceplan',
702                       completer=get_resource_name_completion_list('Microsoft.Web/serverFarms'),
703                       help="name or resource id of the {} app service plan. Use 'appservice plan create' to get one. If using an App Service plan from a different resource group, the full resource id must be used and not the plan name.".format(scope),
704                       local_context_attribute=LocalContextAttribute(name='plan_name', actions=[LocalContextAction.GET]))
705            c.argument('name', options_list=['--name', '-n'], help='name of the new {} app'.format(app_type),
706                       local_context_attribute=LocalContextAttribute(name=scope + '_name',
707                       actions=[LocalContextAction.SET],
708                       scopes=[scope]))
709            c.argument('storage_account', options_list=['--storage-account', '-s'],
710                       help='Provide a string value of a Storage Account in the provided Resource Group. Or Resource ID of a Storage Account in a different Resource Group',
711                       local_context_attribute=LocalContextAttribute(name='storage_account_name', actions=[LocalContextAction.GET]))
712            c.argument('consumption_plan_location', options_list=['--consumption-plan-location', '-c'],
713                       help="Geographic location where {} app will be hosted. Use `az {} list-consumption-locations` to view available locations.".format(app_type, scope))
714            c.argument('os_type', arg_type=get_enum_type(OS_TYPES), help="Set the OS type for the app to be created.")
715            c.argument('app_insights_key', help="Instrumentation key of App Insights to be added.")
716            c.argument('app_insights',
717                       help="Name of the existing App Insights project to be added to the {} app. Must be in the ".format(app_type) +
718                       "same resource group.")
719            c.argument('disable_app_insights', arg_type=get_three_state_flag(return_label=True),
720                       help="Disable creating application insights resource during {} create. No logs will be available.".format(scope))
721            c.argument('docker_registry_server_user', options_list=['--docker-registry-server-user', '-d'], help='The container registry server username.')
722            c.argument('docker_registry_server_password', options_list=['--docker-registry-server-password', '-w'],
723                       help='The container registry server password. Required for private registries.')
724            if scope == 'functionapp':
725                c.argument('functions_version', help='The functions app version.', arg_type=get_enum_type(FUNCTIONS_VERSIONS))
726                c.argument('runtime', help='The functions runtime stack.',
727                           arg_type=get_enum_type(functionapp_runtime_strings))
728                c.argument('runtime_version',
729                           help='The version of the functions runtime stack. '
730                           'Allowed values for each --runtime are: ' + ', '.join(functionapp_runtime_to_version_strings))
731
732    with self.argument_context('functionapp config hostname') as c:
733        c.argument('webapp_name', arg_type=functionapp_name_arg_type, id_part='name')
734    # For commands with shared impl between web app and function app and has output, we apply type validation to avoid confusions
735    with self.argument_context('functionapp show') as c:
736        c.argument('name', arg_type=functionapp_name_arg_type)
737    with self.argument_context('functionapp delete') as c:
738        c.argument('name', arg_type=functionapp_name_arg_type, local_context_attribute=None)
739    with self.argument_context('functionapp config appsettings') as c:
740        c.argument('slot_settings', nargs='+', help="space-separated slot app settings in a format of `<name>=<value>`")
741
742    with self.argument_context('logicapp show') as c:
743        c.argument('name', arg_type=logicapp_name_arg_type)
744    with self.argument_context('logicapp delete') as c:
745        c.argument('name', arg_type=logicapp_name_arg_type, local_context_attribute=None)
746
747    with self.argument_context('functionapp plan') as c:
748        c.argument('name', arg_type=name_arg_type, help='The name of the app service plan',
749                   completer=get_resource_name_completion_list('Microsoft.Web/serverFarms'),
750                   configured_default='appserviceplan', id_part='name',
751                   local_context_attribute=LocalContextAttribute(name='plan_name', actions=[LocalContextAction.GET]))
752        c.argument('is_linux', arg_type=get_three_state_flag(return_label=True), required=False,
753                   help='host function app on Linux worker')
754        c.argument('number_of_workers', options_list=['--number-of-workers', '--min-instances'],
755                   help='The number of workers for the app service plan.')
756        c.argument('max_burst',
757                   help='The maximum number of elastic workers for the plan.')
758        c.argument('tags', arg_type=tags_type)
759
760    with self.argument_context('functionapp update') as c:
761        c.argument('plan', required=False, help='The name or resource id of the plan to update the functionapp with.')
762        c.argument('force', required=False, help='Required if attempting to migrate functionapp from Premium to Consumption --plan.',
763                   action='store_true')
764
765    with self.argument_context('functionapp plan create') as c:
766        c.argument('name', arg_type=name_arg_type, help='The name of the app service plan',
767                   completer=get_resource_name_completion_list('Microsoft.Web/serverFarms'),
768                   configured_default='appserviceplan', id_part='name',
769                   local_context_attribute=LocalContextAttribute(name='plan_name', actions=[LocalContextAction.SET],
770                                                                 scopes=['appservice', 'webapp', 'functionapp']))
771        c.argument('sku', required=True, help='The SKU of the app service plan.')
772
773    with self.argument_context('functionapp plan update') as c:
774        c.argument('sku', required=False, help='The SKU of the app service plan.')
775
776    with self.argument_context('functionapp plan delete') as c:
777        c.argument('name', arg_type=name_arg_type, help='The name of the app service plan',
778                   completer=get_resource_name_completion_list('Microsoft.Web/serverFarms'),
779                   configured_default='appserviceplan', id_part='name',
780                   local_context_attribute=None)
781
782    with self.argument_context('functionapp devops-build create') as c:
783        c.argument('functionapp_name', help="Name of the Azure function app that you want to use", required=False,
784                   local_context_attribute=LocalContextAttribute(name='functionapp_name',
785                                                                 actions=[LocalContextAction.GET]))
786        c.argument('organization_name', help="Name of the Azure DevOps organization that you want to use",
787                   required=False)
788        c.argument('project_name', help="Name of the Azure DevOps project that you want to use", required=False)
789        c.argument('repository_name', help="Name of the Azure DevOps repository that you want to use", required=False)
790        c.argument('overwrite_yaml', help="If you have an existing yaml, should it be overwritten?",
791                   arg_type=get_three_state_flag(return_label=True), required=False)
792        c.argument('allow_force_push',
793                   help="If Azure DevOps repository is not clean, should it overwrite remote content?",
794                   arg_type=get_three_state_flag(return_label=True), required=False)
795        c.argument('github_pat', help="Github personal access token for creating pipeline from Github repository",
796                   required=False)
797        c.argument('github_repository', help="Fullname of your Github repository (e.g. Azure/azure-cli)",
798                   required=False)
799
800    with self.argument_context('functionapp devops-pipeline create') as c:
801        c.argument('functionapp_name', help="Name of the Azure function app that you want to use", required=False,
802                   local_context_attribute=LocalContextAttribute(name='functionapp_name',
803                                                                 actions=[LocalContextAction.GET]))
804        c.argument('organization_name', help="Name of the Azure DevOps organization that you want to use",
805                   required=False)
806        c.argument('project_name', help="Name of the Azure DevOps project that you want to use", required=False)
807        c.argument('repository_name', help="Name of the Azure DevOps repository that you want to use", required=False)
808        c.argument('overwrite_yaml', help="If you have an existing yaml, should it be overwritten?",
809                   arg_type=get_three_state_flag(return_label=True), required=False)
810        c.argument('allow_force_push',
811                   help="If Azure DevOps repository is not clean, should it overwrite remote content?",
812                   arg_type=get_three_state_flag(return_label=True), required=False)
813        c.argument('github_pat', help="Github personal access token for creating pipeline from Github repository",
814                   required=False)
815        c.argument('github_repository', help="Fullname of your Github repository (e.g. Azure/azure-cli)",
816                   required=False)
817
818    with self.argument_context('functionapp deployment list-publishing-profiles') as c:
819        c.argument('xml', options_list=['--xml'], required=False, help='retrieves the publishing profile details in XML format')
820    with self.argument_context('functionapp deployment slot') as c:
821        c.argument('slot', help='the name of the slot')
822        # This is set to webapp to simply reuse webapp functions, without rewriting same functions for function apps.
823        # The help will still show "-n or --name", so it should not be a problem to do it this way
824        c.argument('webapp', arg_type=functionapp_name_arg_type,
825                   completer=get_resource_name_completion_list('Microsoft.Web/sites'),
826                   help='Name of the function app', id_part='name')
827        c.argument('auto_swap_slot', help='target slot to auto swap', default='production')
828        c.argument('disable', help='disable auto swap', action='store_true')
829        c.argument('target_slot', help="target slot to swap, default to 'production'")
830        c.argument('preserve_vnet', help="preserve Virtual Network to the slot during swap, default to 'true'",
831                   arg_type=get_three_state_flag(return_label=True))
832    with self.argument_context('functionapp deployment slot create') as c:
833        c.argument('configuration_source',
834                   help="source slot to clone configurations from. Use function app's name to refer to the production slot")
835    with self.argument_context('functionapp deployment slot swap') as c:
836        c.argument('action',
837                   help="swap types. use 'preview' to apply target slot's settings on the source slot first; use 'swap' to complete it; use 'reset' to reset the swap",
838                   arg_type=get_enum_type(['swap', 'preview', 'reset']))
839
840    with self.argument_context('functionapp keys', id_part=None) as c:
841        c.argument('resource_group_name', arg_type=resource_group_name_type,)
842        c.argument('name', arg_type=functionapp_name_arg_type,
843                   completer=get_resource_name_completion_list('Microsoft.Web/sites'),
844                   help='Name of the function app')
845        c.argument('slot', options_list=['--slot', '-s'],
846                   help="The name of the slot. Defaults to the productions slot if not specified")
847    with self.argument_context('functionapp keys set', id_part=None) as c:
848        c.argument('key_name', help="Name of the key to set.")
849        c.argument('key_value', help="Value of the new key. If not provided, a value will be generated.")
850        c.argument('key_type', help="Type of key.", arg_type=get_enum_type(['systemKey', 'functionKeys', 'masterKey']))
851    with self.argument_context('functionapp keys delete', id_part=None) as c:
852        c.argument('key_name', help="Name of the key to set.")
853        c.argument('key_type', help="Type of key.", arg_type=get_enum_type(['systemKey', 'functionKeys', 'masterKey']))
854
855    with self.argument_context('functionapp function', id_part=None) as c:
856        c.argument('resource_group_name', arg_type=resource_group_name_type,)
857        c.argument('name', arg_type=functionapp_name_arg_type,
858                   completer=get_resource_name_completion_list('Microsoft.Web/sites'),
859                   help='Name of the function app')
860        c.argument('function_name', help="Name of the Function")
861    with self.argument_context('functionapp function keys', id_part=None) as c:
862        c.argument('slot', options_list=['--slot', '-s'],
863                   help="The name of the slot. Defaults to the productions slot if not specified")
864    with self.argument_context('functionapp function keys set', id_part=None) as c:
865        c.argument('key_name', help="Name of the key to set.")
866        c.argument('key_value', help="Value of the new key. If not provided, a value will be generated.")
867    with self.argument_context('functionapp function keys delete', id_part=None) as c:
868        c.argument('key_name', help="Name of the key to set.")
869
870    # Access Restriction Commands
871    for scope in ['webapp', 'functionapp']:
872        with self.argument_context(scope + ' config access-restriction show') as c:
873            c.argument('name', arg_type=(webapp_name_arg_type if scope == 'webapp' else functionapp_name_arg_type))
874
875        with self.argument_context(scope + ' config access-restriction add') as c:
876            c.argument('name', arg_type=(webapp_name_arg_type if scope == 'webapp' else functionapp_name_arg_type))
877            c.argument('rule_name', options_list=['--rule-name', '-r'],
878                       help='Name of the access restriction rule to add')
879            c.argument('priority', options_list=['--priority', '-p'],
880                       help="Priority of the access restriction rule")
881            c.argument('description', help='Description of the access restriction rule')
882            c.argument('action', arg_type=get_enum_type(ACCESS_RESTRICTION_ACTION_TYPES),
883                       help="Allow or deny access")
884            c.argument('ip_address', help="IP address or CIDR range (optional comma separated list of up to 8 ranges)",
885                       validator=validate_ip_address)
886            c.argument('service_tag', help="Service Tag (optional comma separated list of up to 8 tags)",
887                       validator=validate_service_tag)
888            c.argument('vnet_name', help="vNet name")
889            c.argument('subnet', help="Subnet name (requires vNet name) or subnet resource id")
890            c.argument('ignore_missing_vnet_service_endpoint',
891                       options_list=['--ignore-missing-endpoint', '-i'],
892                       help='Create access restriction rule with checking if the subnet has Microsoft.Web service endpoint enabled',
893                       arg_type=get_three_state_flag(),
894                       default=False)
895            c.argument('scm_site', help='True if access restrictions is added for scm site',
896                       arg_type=get_three_state_flag())
897            c.argument('vnet_resource_group', help='Resource group of virtual network (default is web app resource group)')
898            c.argument('http_headers', nargs='+', help="space-separated http headers in a format of `<name>=<value>`")
899        with self.argument_context(scope + ' config access-restriction remove') as c:
900            c.argument('name', arg_type=(webapp_name_arg_type if scope == 'webapp' else functionapp_name_arg_type))
901            c.argument('rule_name', options_list=['--rule-name', '-r'],
902                       help='Name of the access restriction to remove')
903            c.argument('ip_address', help="IP address or CIDR range (optional comma separated list of up to 8 ranges)",
904                       validator=validate_ip_address)
905            c.argument('service_tag', help="Service Tag (optional comma separated list of up to 8 tags)",
906                       validator=validate_service_tag)
907            c.argument('vnet_name', help="vNet name")
908            c.argument('subnet', help="Subnet name (requires vNet name) or subnet resource id")
909            c.argument('scm_site', help='True if access restriction should be removed from scm site',
910                       arg_type=get_three_state_flag())
911            c.argument('action', arg_type=get_enum_type(ACCESS_RESTRICTION_ACTION_TYPES),
912                       help="Allow or deny access")
913        with self.argument_context(scope + ' config access-restriction set') as c:
914            c.argument('name', arg_type=(webapp_name_arg_type if scope == 'webapp' else functionapp_name_arg_type))
915            c.argument('use_same_restrictions_for_scm_site',
916                       help="Use same access restrictions for scm site",
917                       arg_type=get_three_state_flag())
918
919    # App Service Environment Commands
920    with self.argument_context('appservice ase show') as c:
921        c.argument('name', options_list=['--name', '-n'], help='Name of the app service environment',
922                   local_context_attribute=LocalContextAttribute(name='ase_name', actions=[LocalContextAction.GET]))
923    with self.argument_context('appservice ase create') as c:
924        c.argument('name', options_list=['--name', '-n'], validator=validate_ase_create,
925                   help='Name of the app service environment',
926                   local_context_attribute=LocalContextAttribute(name='ase_name', actions=[LocalContextAction.SET],
927                                                                 scopes=['appservice']))
928        c.argument('kind', options_list=['--kind', '-k'], arg_type=get_enum_type(ASE_KINDS),
929                   default='ASEv2', help="Specify App Service Environment version")
930        c.argument('subnet', help='Name or ID of existing subnet. To create vnet and/or subnet \
931                   use `az network vnet [subnet] create`')
932        c.argument('vnet_name', help='Name of the vNet. Mandatory if only subnet name is specified.')
933        c.argument('virtual_ip_type', arg_type=get_enum_type(ASE_LOADBALANCER_MODES),
934                   help="Specify if app service environment should be accessible from internet")
935        c.argument('ignore_subnet_size_validation', arg_type=get_three_state_flag(),
936                   help='Do not check if subnet is sized according to recommendations.')
937        c.argument('ignore_route_table', arg_type=get_three_state_flag(),
938                   help='Configure route table manually. Applies to ASEv2 only.')
939        c.argument('ignore_network_security_group', arg_type=get_three_state_flag(),
940                   help='Configure network security group manually. Applies to ASEv2 only.')
941        c.argument('force_route_table', arg_type=get_three_state_flag(),
942                   help='Override route table for subnet. Applies to ASEv2 only.')
943        c.argument('force_network_security_group', arg_type=get_three_state_flag(),
944                   help='Override network security group for subnet. Applies to ASEv2 only.')
945        c.argument('front_end_scale_factor', type=int, validator=validate_front_end_scale_factor,
946                   help='Scale of front ends to app service plan instance ratio. Applies to ASEv2 only.', default=15)
947        c.argument('front_end_sku', arg_type=isolated_sku_arg_type, default='I1',
948                   help='Size of front end servers. Applies to ASEv2 only.')
949        c.argument('os_preference', arg_type=get_enum_type(ASE_OS_PREFERENCE_TYPES),
950                   help='Determine if app service environment should start with Linux workers. Applies to ASEv2 only.')
951        c.argument('zone_redundant', arg_type=get_three_state_flag(),
952                   help='Configure App Service Environment as Zone Redundant. Applies to ASEv3 only.')
953    with self.argument_context('appservice ase delete') as c:
954        c.argument('name', options_list=['--name', '-n'], help='Name of the app service environment')
955    with self.argument_context('appservice ase update') as c:
956        c.argument('name', options_list=['--name', '-n'], help='Name of the app service environment',
957                   local_context_attribute=LocalContextAttribute(name='ase_name', actions=[LocalContextAction.GET]))
958        c.argument('front_end_scale_factor', type=int, validator=validate_front_end_scale_factor,
959                   help='Scale of front ends to app service plan instance ratio between 5 and 15.')
960        c.argument('front_end_sku', arg_type=isolated_sku_arg_type, help='Size of front end servers.')
961    with self.argument_context('appservice ase list-addresses') as c:
962        c.argument('name', options_list=['--name', '-n'], help='Name of the app service environment',
963                   local_context_attribute=LocalContextAttribute(name='ase_name', actions=[LocalContextAction.GET]))
964    with self.argument_context('appservice ase list-plans') as c:
965        c.argument('name', options_list=['--name', '-n'], help='Name of the app service environment',
966                   local_context_attribute=LocalContextAttribute(name='ase_name', actions=[LocalContextAction.GET]))
967    with self.argument_context('appservice ase create-inbound-services') as c:
968        c.argument('name', options_list=['--name', '-n'], help='Name of the app service environment',
969                   local_context_attribute=LocalContextAttribute(name='ase_name', actions=[LocalContextAction.GET]))
970        c.argument('subnet', help='Name or ID of existing subnet for inbound traffic to ASEv3. \
971                   To create vnet and/or subnet use `az network vnet [subnet] create`')
972        c.argument('vnet_name', help='Name of the vNet. Mandatory if only subnet name is specified.')
973        c.argument('skip_dns', arg_type=get_three_state_flag(),
974                   help='Do not create Private DNS Zone and DNS records.')
975
976    # App Service Domain Commands
977    with self.argument_context('appservice domain create') as c:
978        c.argument('hostname', options_list=['--hostname', '-n'], help='Name of the custom domain')
979        c.argument('contact_info', options_list=['--contact-info', '-c'], help='The file path to a JSON object with your contact info for domain registration. '
980                                                                               'Please see the following link for the format of the JSON file expected: '
981                                                                               'https://github.com/AzureAppServiceCLI/appservice_domains_templates/blob/master/contact_info.json')
982        c.argument('privacy', options_list=['--privacy', '-p'], help='Enable privacy protection')
983        c.argument('auto_renew', options_list=['--auto-renew', '-a'], help='Enable auto-renew on the domain')
984        c.argument('accept_terms', options_list=['--accept-terms'], help='By using this flag, you are accepting '
985                                                                         'the conditions shown using the --show-hostname-purchase-terms flag. ')
986        c.argument('tags', arg_type=tags_type)
987        c.argument('dryrun', help='Show summary of the purchase and create operation instead of executing it')
988        c.argument('no_wait', help='Do not wait for the create to complete, and return immediately after queuing the create.')
989        c.argument('validate', help='Generate and validate the ARM template without creating any resources')
990
991    with self.argument_context('appservice domain show-terms') as c:
992        c.argument('hostname', options_list=['--hostname', '-n'], help='Name of the custom domain')
993
994    with self.argument_context('staticwebapp', validator=validate_public_cloud) as c:
995        c.argument('name', options_list=['--name', '-n'], metavar='NAME', help="Name of the static site")
996        c.argument('source', options_list=['--source', '-s'], help="URL for the repository of the static site.")
997        c.argument('token', options_list=['--token', '-t'],
998                   help="A user's github repository token. This is used to setup the Github Actions workflow file and "
999                        "API secrets. If you need to create a Github Personal Access Token, "
1000                        "please run with the '--login-with-github' flag or follow the steps found at the following link:\n"
1001                        "https://help.github.com/en/articles/creating-a-personal-access-token-for-the-command-line")
1002        c.argument('login_with_github', help="Interactively log in with Github to retrieve the Personal Access Token")
1003        c.argument('branch', options_list=['--branch', '-b'], help="The target branch in the repository.")
1004    with self.argument_context('staticwebapp environment') as c:
1005        c.argument('environment_name',
1006                   options_list=['--environment-name'], help="Name of the environment of static site")
1007    with self.argument_context('staticwebapp hostname') as c:
1008        c.argument('hostname',
1009                   options_list=['--hostname'],
1010                   help="custom hostname such as www.example.com. Only support sub domain in preview.")
1011    with self.argument_context('staticwebapp appsettings') as c:
1012        c.argument('setting_pairs', options_list=['--setting-names'],
1013                   help="Space-separated app settings in 'key=value' format. ",
1014                   nargs='*')
1015        c.argument('setting_names', options_list=['--setting-names'], help="Space-separated app setting names.",
1016                   nargs='*')
1017    with self.argument_context('staticwebapp users') as c:
1018        c.argument('authentication_provider', options_list=['--authentication-provider'],
1019                   help="Authentication provider of the user identity such as AAD, Facebook, GitHub, Google, Twitter.")
1020        c.argument('user_details', options_list=['--user-details'],
1021                   help="Email for AAD, Facebook, and Google. Account name (handle) for GitHub and Twitter.")
1022        c.argument('user_id',
1023                   help="Given id of registered user.")
1024        c.argument('domain', options_list=['--domain'],
1025                   help="A domain added to the static app in quotes.")
1026        c.argument('roles', options_list=['--roles'],
1027                   help="Comma-separated default or user-defined role names. "
1028                        "Roles that can be assigned to a user are comma separated and case-insensitive (at most 50 "
1029                        "roles up to 25 characters each and restricted to 0-9,A-Z,a-z, and _). "
1030                        "Define roles in routes.json during root directory of your GitHub repo.")
1031        c.argument('invitation_expiration_in_hours', options_list=['--invitation-expiration-in-hours'],
1032                   help="This value sets when the link will expire in hours. The maximum is 168 (7 days).")
1033    with self.argument_context('staticwebapp create') as c:
1034        c.argument('location', arg_type=get_location_type(self.cli_ctx))
1035        c.argument('tags', arg_type=tags_type)
1036        c.argument('sku', arg_type=static_web_app_sku_arg_type)
1037        c.argument('app_location', options_list=['--app-location'],
1038                   help="Location of your application code. For example, '/' represents the root of your app, "
1039                        "while '/app' represents a directory called 'app'")
1040        c.argument('api_location', options_list=['--api-location'],
1041                   help="Location of your Azure Functions code. For example, '/api' represents a folder called 'api'.")
1042        c.argument('app_artifact_location', options_list=['--app-artifact-location'],
1043                   help="The path of your build output relative to your apps location. For example, setting a value "
1044                        "of 'build' when your app location is set to '/app' will cause the content at '/app/build' to "
1045                        "be served.",
1046                   deprecate_info=c.deprecate(expiration='2.22.1'))
1047        c.argument('output_location', options_list=['--output-location'],
1048                   help="The path of your build output relative to your apps location. For example, setting a value "
1049                        "of 'build' when your app location is set to '/app' will cause the content at '/app/build' to "
1050                        "be served.")
1051    with self.argument_context('staticwebapp update') as c:
1052        c.argument('tags', arg_type=tags_type)
1053        c.argument('sku', arg_type=static_web_app_sku_arg_type)
1054
1055
1056def _get_functionapp_runtime_versions():
1057    # set up functionapp create help menu
1058    KEYS = FUNCTIONS_STACKS_API_KEYS()
1059    stacks_api_json_list = []
1060    stacks_api_json_list.append(get_file_json(FUNCTIONS_STACKS_API_JSON_PATHS['windows']))
1061    stacks_api_json_list.append(get_file_json(FUNCTIONS_STACKS_API_JSON_PATHS['linux']))
1062
1063    # build a map of runtime -> runtime version -> runtime version properties
1064    runtime_to_version = {}
1065    for stacks_api_json in stacks_api_json_list:
1066        for runtime_json in stacks_api_json[KEYS.VALUE]:
1067            runtime_name = runtime_json[KEYS.NAME]
1068            for runtime_version_json in runtime_json[KEYS.PROPERTIES][KEYS.MAJOR_VERSIONS]:
1069                runtime_version = runtime_version_json[KEYS.DISPLAY_VERSION]
1070                runtime_version_properties = {
1071                    KEYS.IS_HIDDEN: runtime_version_json[KEYS.IS_HIDDEN],
1072                    KEYS.IS_DEPRECATED: runtime_version_json[KEYS.IS_DEPRECATED],
1073                    KEYS.IS_PREVIEW: runtime_version_json[KEYS.IS_PREVIEW],
1074                }
1075                runtime_to_version[runtime_name] = runtime_to_version.get(runtime_name, dict())
1076                runtime_to_version[runtime_name][runtime_version] = runtime_version_properties
1077
1078    # traverse the map to build an ordered string of runtimes -> runtime versions,
1079    # taking their properties into account (i.e. isHidden, isPreview)
1080    runtime_to_version_strings = []
1081    for runtime, runtime_versions in runtime_to_version.items():
1082        # dotnet and custom version is not configurable, so leave out of help menu
1083        if runtime in ('dotnet', 'custom'):
1084            continue
1085        ordered_runtime_versions = list(runtime_versions.keys())
1086        ordered_runtime_versions.sort(key=float)
1087        ordered_runtime_versions_strings = []
1088        for version in ordered_runtime_versions:
1089            if runtime_versions[version][KEYS.IS_HIDDEN] or runtime_versions[version][KEYS.IS_DEPRECATED]:
1090                continue
1091            if runtime_versions[version][KEYS.IS_PREVIEW]:
1092                ordered_runtime_versions_strings.append(version + ' (preview)')
1093            else:
1094                ordered_runtime_versions_strings.append(version)
1095        runtime_to_version_strings.append(runtime + ' -> [' + ', '.join(ordered_runtime_versions_strings) + ']')
1096
1097    return runtime_to_version.keys(), runtime_to_version_strings
1098