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
6# pylint: disable=line-too-long
7from azure.cli.core.commands import CliCommandType
8from azure.cli.core.util import empty_on_404
9
10from ._client_factory import cf_web_client, cf_plans, cf_webapps
11from ._validators import validate_onedeploy_params
12
13
14def output_slots_in_table(slots):
15    return [{'name': s['name'], 'status': s['state'], 'plan': s['appServicePlan']} for s in slots]
16
17
18def transform_list_location_output(result):
19    return [{'name': x.name} for x in result]
20
21
22def transform_web_output(web):
23    props = ['name', 'state', 'location', 'resourceGroup', 'defaultHostName', 'appServicePlanId', 'ftpPublishingUrl']
24    result = {k: web[k] for k in web if k in props}
25    # to get width under control, also the plan usually is in the same RG
26    result['appServicePlan'] = result.pop('appServicePlanId').split('/')[-1]
27    return result
28
29
30def transform_web_list_output(webs):
31    return [transform_web_output(w) for w in webs]
32
33
34def ex_handler_factory(creating_plan=False):
35    def _ex_handler(ex):
36        ex = _polish_bad_errors(ex, creating_plan)
37        raise ex
38    return _ex_handler
39
40
41def update_function_ex_handler_factory():
42    from azure.cli.core.azclierror import ClientRequestError
43
44    def _ex_handler(ex):
45        http_error_response = False
46        if hasattr(ex, 'response'):
47            http_error_response = True
48        ex = _polish_bad_errors(ex, False)
49        # only include if an update was attempted and failed on the backend
50        if http_error_response:
51            try:
52                detail = ('If using \'--plan\', a consumption plan may be unable to migrate '
53                          'to a given premium plan. Please confirm that the premium plan '
54                          'exists in the same resource group and region. Note: Not all '
55                          'functionapp plans support premium instances. If you have verified '
56                          'your resource group and region and are still unable to migrate, '
57                          'please redeploy on a premium functionapp plan.')
58                ex = ClientRequestError(ex.args[0] + '\n\n' + detail)
59            except Exception:  # pylint: disable=broad-except
60                pass
61        raise ex
62    return _ex_handler
63
64
65def _polish_bad_errors(ex, creating_plan):
66    import json
67    from knack.util import CLIError
68    try:
69        if 'text/plain' in ex.response.headers['Content-Type']:  # HTML Response
70            detail = ex.response.text
71        else:
72            detail = json.loads(ex.response.text)['Message']
73            if creating_plan:
74                if 'Requested features are not supported in region' in detail:
75                    detail = ("Plan with linux worker is not supported in current region. For " +
76                              "supported regions, please refer to https://docs.microsoft.com/"
77                              "azure/app-service-web/app-service-linux-intro")
78                elif 'Not enough available reserved instance servers to satisfy' in detail:
79                    detail = ("Plan with Linux worker can only be created in a group " +
80                              "which has never contained a Windows worker, and vice versa. " +
81                              "Please use a new resource group. Original error:" + detail)
82        ex = CLIError(detail)
83    except Exception:  # pylint: disable=broad-except
84        pass
85    return ex
86
87
88# pylint: disable=too-many-statements
89def load_command_table(self, _):
90    webclient_sdk = CliCommandType(
91        operations_tmpl='azure.mgmt.web.operations#WebSiteManagementClientOperationsMixin.{}',
92        client_factory=cf_web_client
93    )
94    appservice_plan_sdk = CliCommandType(
95        operations_tmpl='azure.mgmt.web.operations#AppServicePlansOperations.{}',
96        client_factory=cf_plans
97    )
98    webapp_sdk = CliCommandType(
99        operations_tmpl='azure.mgmt.web.operations#WebAppsOperations.{}',
100        client_factory=cf_webapps
101    )
102
103    appservice_custom = CliCommandType(operations_tmpl='azure.cli.command_modules.appservice.custom#{}')
104
105    webapp_access_restrictions = CliCommandType(operations_tmpl='azure.cli.command_modules.appservice.access_restrictions#{}')
106
107    appservice_environment = CliCommandType(operations_tmpl='azure.cli.command_modules.appservice.appservice_environment#{}')
108
109    staticsite_sdk = CliCommandType(operations_tmpl='azure.cli.command_modules.appservice.static_sites#{}')
110
111    appservice_domains = CliCommandType(operations_tmpl='azure.cli.command_modules.appservice.appservice_domains#{}')
112
113    logicapp_custom = CliCommandType(operations_tmpl='azure.cli.command_modules.appservice.logicapp.custom#{}')
114
115    with self.command_group('webapp', webapp_sdk) as g:
116        g.custom_command('create', 'create_webapp', exception_handler=ex_handler_factory())
117        g.custom_command('up', 'webapp_up', exception_handler=ex_handler_factory())
118        g.custom_command('ssh', 'ssh_webapp', exception_handler=ex_handler_factory(), is_preview=True)
119        g.custom_command('list', 'list_webapp', table_transformer=transform_web_list_output)
120        g.custom_show_command('show', 'show_webapp', table_transformer=transform_web_output)
121        g.custom_command('delete', 'delete_webapp')
122        g.custom_command('stop', 'stop_webapp')
123        g.custom_command('start', 'start_webapp')
124        g.custom_command('restart', 'restart_webapp')
125        g.custom_command('browse', 'view_in_browser')
126        g.custom_command('list-instances', 'list_instances')
127        # TO DO: Move back to using list_runtimes function once Available Stacks API is updated (it's updated with Antares deployments)
128        g.custom_command('list-runtimes', 'list_runtimes_hardcoded')
129        g.custom_command('identity assign', 'assign_identity')
130        g.custom_show_command('identity show', 'show_identity')
131        g.custom_command('identity remove', 'remove_identity')
132        g.custom_command('create-remote-connection', 'create_tunnel', exception_handler=ex_handler_factory())
133        g.custom_command('deploy', 'perform_onedeploy', validator=validate_onedeploy_params, is_preview=True)
134        g.generic_update_command('update', getter_name='get_webapp', setter_name='set_webapp', custom_func_name='update_webapp', command_type=appservice_custom)
135
136    with self.command_group('webapp traffic-routing') as g:
137        g.custom_command('set', 'set_traffic_routing')
138        g.custom_show_command('show', 'show_traffic_routing')
139        g.custom_command('clear', 'clear_traffic_routing')
140
141    with self.command_group('webapp cors') as g:
142        g.custom_command('add', 'add_cors')
143        g.custom_command('remove', 'remove_cors')
144        g.custom_show_command('show', 'show_cors')
145
146    with self.command_group('webapp config') as g:
147        g.custom_command('set', 'update_site_configs')
148        g.custom_show_command('show', 'get_site_configs')
149
150    with self.command_group('webapp config appsettings') as g:
151        g.custom_command('list', 'get_app_settings', exception_handler=empty_on_404)
152        g.custom_command('set', 'update_app_settings')
153        g.custom_command('delete', 'delete_app_settings')
154
155    with self.command_group('webapp config connection-string') as g:
156        g.custom_command('list', 'get_connection_strings', exception_handler=empty_on_404)
157        g.custom_command('set', 'update_connection_strings')
158        g.custom_command('delete', 'delete_connection_strings')
159
160    with self.command_group('webapp config storage-account') as g:
161        g.custom_command('list', 'get_azure_storage_accounts', exception_handler=empty_on_404)
162        g.custom_command('add', 'add_azure_storage_account')
163        g.custom_command('update', 'update_azure_storage_account')
164        g.custom_command('delete', 'delete_azure_storage_accounts')
165
166    with self.command_group('webapp config hostname') as g:
167        g.custom_command('add', 'add_hostname', exception_handler=ex_handler_factory())
168        g.custom_command('list', 'list_hostnames')
169        g.custom_command('delete', 'delete_hostname')
170        g.custom_command('get-external-ip', 'get_external_ip')
171
172    with self.command_group('webapp config container') as g:
173        g.custom_command('set', 'update_container_settings')
174        g.custom_command('delete', 'delete_container_settings')
175        g.custom_show_command('show', 'show_container_settings')
176
177    with self.command_group('webapp config ssl') as g:
178        g.custom_command('upload', 'upload_ssl_cert')
179        g.custom_command('list', 'list_ssl_certs', exception_handler=ex_handler_factory())
180        g.custom_show_command('show', 'show_ssl_cert', exception_handler=ex_handler_factory())
181        g.custom_command('bind', 'bind_ssl_cert', exception_handler=ex_handler_factory())
182        g.custom_command('unbind', 'unbind_ssl_cert')
183        g.custom_command('delete', 'delete_ssl_cert', exception_handler=ex_handler_factory())
184        g.custom_command('import', 'import_ssl_cert', exception_handler=ex_handler_factory())
185        g.custom_command('create', 'create_managed_ssl_cert', exception_handler=ex_handler_factory(), is_preview=True)
186
187    with self.command_group('webapp config backup') as g:
188        g.custom_command('list', 'list_backups')
189        g.custom_show_command('show', 'show_backup_configuration')
190        g.custom_command('create', 'create_backup', exception_handler=ex_handler_factory())
191        g.custom_command('update', 'update_backup_schedule', exception_handler=ex_handler_factory())
192        g.custom_command('restore', 'restore_backup', exception_handler=ex_handler_factory())
193
194    with self.command_group('webapp config snapshot') as g:
195        g.custom_command('list', 'list_snapshots')
196        g.custom_command('restore', 'restore_snapshot')
197
198    with self.command_group('webapp webjob continuous') as g:
199        g.custom_command('list', 'list_continuous_webjobs', exception_handler=ex_handler_factory())
200        g.custom_command('remove', 'remove_continuous_webjob', exception_handler=ex_handler_factory())
201        g.custom_command('start', 'start_continuous_webjob', exception_handler=ex_handler_factory())
202        g.custom_command('stop', 'stop_continuous_webjob', exception_handler=ex_handler_factory())
203
204    with self.command_group('webapp webjob triggered') as g:
205        g.custom_command('list', 'list_triggered_webjobs', exception_handler=ex_handler_factory())
206        g.custom_command('remove', 'remove_triggered_webjob', exception_handler=ex_handler_factory())
207        g.custom_command('run', 'run_triggered_webjob', exception_handler=ex_handler_factory())
208        g.custom_command('log', 'get_history_triggered_webjob', exception_handler=ex_handler_factory())
209
210    with self.command_group('webapp deployment source') as g:
211        g.custom_command('config-local-git', 'enable_local_git')
212        g.custom_command('config-zip', 'enable_zip_deploy_webapp')
213        g.custom_command('config', 'config_source_control', exception_handler=ex_handler_factory())
214        g.custom_command('sync', 'sync_site_repo', exception_handler=ex_handler_factory())
215        g.custom_show_command('show', 'show_source_control')
216        g.custom_command('delete', 'delete_source_control')
217        g.custom_command('update-token', 'update_git_token', exception_handler=ex_handler_factory())
218
219    with self.command_group('webapp log') as g:
220        g.custom_command('tail', 'get_streaming_log')
221        g.custom_command('download', 'download_historical_logs')
222        g.custom_command('config', 'config_diagnostics')
223        g.custom_show_command('show', 'show_diagnostic_settings')
224
225    with self.command_group('webapp log deployment', is_preview=True) as g:
226        g.custom_show_command('show', 'show_deployment_log')
227        g.custom_command('list', 'list_deployment_logs')
228
229    with self.command_group('functionapp log deployment', is_preview=True) as g:
230        g.custom_show_command('show', 'show_deployment_log')
231        g.custom_command('list', 'list_deployment_logs')
232
233    with self.command_group('webapp deployment slot') as g:
234        g.custom_command('list', 'list_slots', table_transformer=output_slots_in_table)
235        g.custom_command('delete', 'delete_slot')
236        g.custom_command('auto-swap', 'config_slot_auto_swap')
237        g.custom_command('swap', 'swap_slot', exception_handler=ex_handler_factory())
238        g.custom_command('create', 'create_webapp_slot', exception_handler=ex_handler_factory())
239
240    with self.command_group('webapp deployment') as g:
241        g.custom_command('list-publishing-profiles', 'list_publish_profiles')
242        g.custom_command('list-publishing-credentials', 'list_publishing_credentials')
243
244    with self.command_group('webapp deployment user', webclient_sdk) as g:
245        g.custom_show_command('show', 'get_publishing_user')
246        g.custom_command('set', 'set_deployment_user', exception_handler=ex_handler_factory())
247
248    with self.command_group('webapp deployment container') as g:
249        g.custom_command('config', 'enable_cd')
250        g.custom_command('show-cd-url', 'show_container_cd_url')
251
252    with self.command_group('webapp deployment github-actions', is_preview=True) as g:
253        g.custom_command('add', 'add_github_actions')
254        g.custom_command('remove', 'remove_github_actions')
255
256    with self.command_group('webapp auth') as g:
257        g.custom_show_command('show', 'get_auth_settings')
258        g.custom_command('update', 'update_auth_settings')
259
260    with self.command_group('webapp deleted', is_preview=True) as g:
261        g.custom_command('list', 'list_deleted_webapp')
262        g.custom_command('restore', 'restore_deleted_webapp')
263
264    with self.command_group('webapp hybrid-connection') as g:
265        g.custom_command('list', 'list_hc')
266        g.custom_command('add', 'add_hc')
267        g.custom_command('remove', 'remove_hc')
268
269    with self.command_group('functionapp hybrid-connection') as g:
270        g.custom_command('list', 'list_hc')
271        g.custom_command('add', 'add_hc')
272        g.custom_command('remove', 'remove_hc')
273
274    with self.command_group('appservice hybrid-connection') as g:
275        g.custom_command('set-key', 'set_hc_key')
276
277    with self.command_group('webapp vnet-integration') as g:
278        g.custom_command('add', 'add_vnet_integration')
279        g.custom_command('list', 'list_vnet_integration')
280        g.custom_command('remove', 'remove_vnet_integration')
281
282    with self.command_group('functionapp vnet-integration') as g:
283        g.custom_command('add', 'add_vnet_integration')
284        g.custom_command('list', 'list_vnet_integration')
285        g.custom_command('remove', 'remove_vnet_integration')
286
287    with self.command_group('appservice plan', appservice_plan_sdk) as g:
288        g.custom_command('create', 'create_app_service_plan', supports_no_wait=True,
289                         exception_handler=ex_handler_factory(creating_plan=True))
290        g.command('delete', 'delete', confirmation=True)
291        g.custom_command('list', 'list_app_service_plans')
292        g.custom_show_command('show', 'show_plan')
293        g.generic_update_command('update', setter_name='begin_create_or_update', custom_func_name='update_app_service_plan',
294                                 setter_arg_name='app_service_plan', supports_no_wait=True,
295                                 exception_handler=ex_handler_factory())
296
297    with self.command_group('appservice') as g:
298        g.custom_command('list-locations', 'list_locations', transform=transform_list_location_output)
299
300    with self.command_group('appservice vnet-integration') as g:
301        g.custom_command('list', 'appservice_list_vnet')
302
303    with self.command_group('functionapp') as g:
304        g.custom_command('create', 'create_function', exception_handler=ex_handler_factory())
305        g.custom_command('list', 'list_function_app', table_transformer=transform_web_list_output)
306        g.custom_show_command('show', 'show_functionapp', table_transformer=transform_web_output)
307        g.custom_command('delete', 'delete_function_app')
308        g.custom_command('stop', 'stop_webapp')
309        g.custom_command('start', 'start_webapp')
310        g.custom_command('restart', 'restart_webapp')
311        g.custom_command('list-consumption-locations', 'list_consumption_locations')
312        g.custom_command('identity assign', 'assign_identity')
313        g.custom_show_command('identity show', 'show_identity')
314        g.custom_command('identity remove', 'remove_identity')
315        g.custom_command('deploy', 'perform_onedeploy', validator=validate_onedeploy_params, is_preview=True)
316        g.generic_update_command('update', getter_name="get_functionapp", setter_name='set_functionapp', exception_handler=update_function_ex_handler_factory(),
317                                 custom_func_name='update_functionapp', getter_type=appservice_custom, setter_type=appservice_custom, command_type=webapp_sdk)
318
319    with self.command_group('functionapp config') as g:
320        g.custom_command('set', 'update_site_configs')
321        g.custom_show_command('show', 'get_site_configs')
322
323    with self.command_group('functionapp config appsettings') as g:
324        g.custom_command('list', 'get_app_settings', exception_handler=empty_on_404)
325        g.custom_command('set', 'update_app_settings', exception_handler=ex_handler_factory())
326        g.custom_command('delete', 'delete_app_settings', exception_handler=ex_handler_factory())
327
328    with self.command_group('functionapp config hostname') as g:
329        g.custom_command('add', 'add_hostname', exception_handler=ex_handler_factory())
330        g.custom_command('list', 'list_hostnames')
331        g.custom_command('delete', 'delete_hostname')
332        g.custom_command('get-external-ip', 'get_external_ip')
333
334    with self.command_group('functionapp config ssl') as g:
335        g.custom_command('upload', 'upload_ssl_cert', exception_handler=ex_handler_factory())
336        g.custom_command('list', 'list_ssl_certs')
337        g.custom_show_command('show', 'show_ssl_cert')
338        g.custom_command('bind', 'bind_ssl_cert', exception_handler=ex_handler_factory())
339        g.custom_command('unbind', 'unbind_ssl_cert')
340        g.custom_command('delete', 'delete_ssl_cert')
341        g.custom_command('import', 'import_ssl_cert', exception_handler=ex_handler_factory())
342        g.custom_command('create', 'create_managed_ssl_cert', exception_handler=ex_handler_factory(), is_preview=True)
343
344    with self.command_group('functionapp deployment source') as g:
345        g.custom_command('config-local-git', 'enable_local_git')
346        g.custom_command('config-zip', 'enable_zip_deploy_functionapp')
347        g.custom_command('config', 'config_source_control', exception_handler=ex_handler_factory())
348        g.custom_command('sync', 'sync_site_repo')
349        g.custom_show_command('show', 'show_source_control')
350        g.custom_command('delete', 'delete_source_control')
351        g.custom_command('update-token', 'update_git_token', exception_handler=ex_handler_factory())
352
353    with self.command_group('functionapp deployment user', webclient_sdk) as g:
354        g.custom_command('set', 'set_deployment_user', exception_handler=ex_handler_factory())
355        g.show_command('show', 'get_publishing_user')
356
357    with self.command_group('functionapp deployment') as g:
358        g.custom_command('list-publishing-profiles', 'list_publish_profiles')
359        g.custom_command('list-publishing-credentials', 'list_publishing_credentials')
360
361    with self.command_group('functionapp cors') as g:
362        g.custom_command('add', 'add_cors')
363        g.custom_command('remove', 'remove_cors')
364        g.custom_show_command('show', 'show_cors')
365
366    with self.command_group('functionapp plan', appservice_plan_sdk) as g:
367        g.custom_command('create', 'create_functionapp_app_service_plan', exception_handler=ex_handler_factory())
368        g.generic_update_command('update', setter_name='begin_create_or_update',
369                                 custom_func_name='update_functionapp_app_service_plan',
370                                 setter_arg_name='app_service_plan', exception_handler=ex_handler_factory())
371        g.command('delete', 'delete', confirmation=True)
372        g.custom_command('list', 'list_app_service_plans')
373        g.show_command('show', 'get')
374
375    with self.command_group('functionapp deployment container') as g:
376        g.custom_command('config', 'enable_cd')
377        g.custom_command('show-cd-url', 'show_container_cd_url')
378
379    with self.command_group('functionapp config container') as g:
380        g.custom_command('set', 'update_container_settings_functionapp')
381        g.custom_command('delete', 'delete_container_settings')
382        g.custom_show_command('show', 'show_container_settings_functionapp')
383
384    with self.command_group('functionapp devops-pipeline') as g:
385        g.custom_command('create', 'create_devops_pipeline')
386
387    with self.command_group('functionapp deployment slot') as g:
388        g.custom_command('list', 'list_slots', table_transformer=output_slots_in_table)
389        g.custom_command('delete', 'delete_slot')
390        g.custom_command('auto-swap', 'config_slot_auto_swap')
391        g.custom_command('swap', 'swap_slot', exception_handler=ex_handler_factory())
392        g.custom_command('create', 'create_functionapp_slot', exception_handler=ex_handler_factory())
393
394    with self.command_group('functionapp keys') as g:
395        g.custom_command('set', 'update_host_key')
396        g.custom_command('list', 'list_host_keys')
397        g.custom_command('delete', 'delete_host_key')
398
399    with self.command_group('functionapp function') as g:
400        g.custom_command('show', 'show_function')  # pylint: disable=show-command
401        g.custom_command('delete', 'delete_function')
402
403    with self.command_group('functionapp function keys') as g:
404        g.custom_command('set', 'update_function_key')
405        g.custom_command('list', 'list_function_keys')
406        g.custom_command('delete', 'delete_function_key')
407
408    with self.command_group('webapp config access-restriction', custom_command_type=webapp_access_restrictions) as g:
409        g.custom_show_command('show', 'show_webapp_access_restrictions')
410        g.custom_command('add', 'add_webapp_access_restriction')
411        g.custom_command('remove', 'remove_webapp_access_restriction')
412        g.custom_command('set', 'set_webapp_access_restriction')
413
414    with self.command_group('functionapp config access-restriction', custom_command_type=webapp_access_restrictions) as g:
415        g.custom_show_command('show', 'show_webapp_access_restrictions')
416        g.custom_command('add', 'add_webapp_access_restriction')
417        g.custom_command('remove', 'remove_webapp_access_restriction')
418        g.custom_command('set', 'set_webapp_access_restriction')
419
420    with self.command_group('appservice ase', custom_command_type=appservice_environment) as g:
421        g.custom_command('list', 'list_appserviceenvironments')
422        g.custom_command('list-addresses', 'list_appserviceenvironment_addresses')
423        g.custom_command('list-plans', 'list_appserviceenvironment_plans')
424        g.custom_show_command('show', 'show_appserviceenvironment')
425        g.custom_command('create', 'create_appserviceenvironment_arm', supports_no_wait=True)
426        g.custom_command('update', 'update_appserviceenvironment', supports_no_wait=True)
427        g.custom_command('delete', 'delete_appserviceenvironment', supports_no_wait=True, confirmation=True)
428        g.custom_command('create-inbound-services', 'create_ase_inbound_services', is_preview=True)
429
430    with self.command_group('appservice domain', custom_command_type=appservice_domains, is_preview=True) as g:
431        g.custom_command('create', 'create_domain')
432        g.custom_command('show-terms', 'show_domain_purchase_terms')
433
434    with self.command_group('staticwebapp', custom_command_type=staticsite_sdk) as g:
435        g.custom_command('list', 'list_staticsites')
436        g.custom_show_command('show', 'show_staticsite')
437        g.custom_command('create', 'create_staticsites', supports_no_wait=True)
438        g.custom_command('delete', 'delete_staticsite', supports_no_wait=True, confirmation=True)
439        g.custom_command('disconnect', 'disconnect_staticsite', supports_no_wait=True)
440        g.custom_command('reconnect', 'reconnect_staticsite', supports_no_wait=True)
441        g.custom_command('update', 'update_staticsite', supports_no_wait=True)
442
443    with self.command_group('staticwebapp environment', custom_command_type=staticsite_sdk) as g:
444        g.custom_command('list', 'list_staticsite_environments')
445        g.custom_show_command('show', 'show_staticsite_environment')
446        g.custom_command('functions', 'list_staticsite_functions')
447        g.custom_command('delete', 'delete_staticsite_environment', confirmation=True)
448
449    with self.command_group('staticwebapp hostname', custom_command_type=staticsite_sdk) as g:
450        g.custom_command('list', 'list_staticsite_domains')
451        g.custom_command('set', 'set_staticsite_domain', supports_no_wait=True)
452        g.custom_command('delete', 'delete_staticsite_domain', supports_no_wait=True, confirmation=True)
453
454    with self.command_group('staticwebapp appsettings', custom_command_type=staticsite_sdk) as g:
455        g.custom_command('list', 'list_staticsite_function_app_settings')
456        g.custom_command('set', 'set_staticsite_function_app_settings')
457        g.custom_command('delete', 'delete_staticsite_function_app_settings')
458
459    with self.command_group('staticwebapp users', custom_command_type=staticsite_sdk) as g:
460        g.custom_command('list', 'list_staticsite_users')
461        g.custom_command('invite', 'invite_staticsite_users')
462        g.custom_command('update', 'update_staticsite_users')
463
464    with self.command_group('staticwebapp secrets', custom_command_type=staticsite_sdk) as g:
465        g.custom_command('list', 'list_staticsite_secrets')
466        g.custom_command('reset-api-key', 'reset_staticsite_api_key', supports_no_wait=True)
467
468    with self.command_group('logicapp') as g:
469        g.custom_command('delete', 'delete_function_app', confirmation=True)
470        g.custom_command('stop', 'stop_webapp')
471        g.custom_command('start', 'start_webapp')
472        g.custom_command('restart', 'restart_webapp')
473
474    with self.command_group('logicapp', custom_command_type=logicapp_custom) as g:
475        g.custom_command('create', 'create_logicapp', exception_handler=ex_handler_factory())
476        g.custom_command('list', 'list_logicapp', table_transformer=transform_web_list_output)
477        g.custom_show_command('show', 'show_logicapp', table_transformer=transform_web_output)
478