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 azure.cli.core.commands.client_factory import get_mgmt_service_client
7from azure.cli.core.util import sdk_no_wait
8from knack.util import CLIError
9from knack.log import get_logger
10
11from .utils import normalize_sku_for_staticapp, raise_missing_token_suggestion
12
13logger = get_logger(__name__)
14
15
16def list_staticsites(cmd, resource_group_name=None):
17    client = _get_staticsites_client_factory(cmd.cli_ctx)
18    if resource_group_name:
19        result = list(client.get_static_sites_by_resource_group(resource_group_name))
20    else:
21        result = list(client.list())
22    return result
23
24
25def show_staticsite(cmd, name, resource_group_name=None):
26    client = _get_staticsites_client_factory(cmd.cli_ctx)
27    if not resource_group_name:
28        resource_group_name = _get_resource_group_name_of_staticsite(client, name)
29
30    return client.get_static_site(resource_group_name, name)
31
32
33def disconnect_staticsite(cmd, name, resource_group_name=None, no_wait=False):
34    client = _get_staticsites_client_factory(cmd.cli_ctx)
35    if not resource_group_name:
36        resource_group_name = _get_resource_group_name_of_staticsite(client, name)
37
38    return sdk_no_wait(no_wait, client.begin_detach_static_site,
39                       resource_group_name=resource_group_name, name=name)
40
41
42def reconnect_staticsite(cmd, name, source, branch, token=None, resource_group_name=None, login_with_github=False,
43                         no_wait=False):
44    client = _get_staticsites_client_factory(cmd.cli_ctx)
45    if not resource_group_name:
46        resource_group_name = _get_resource_group_name_of_staticsite(client, name)
47
48    location = _get_staticsite_location(client, name, resource_group_name)
49
50    return create_staticsites(cmd, resource_group_name, name, location,
51                              source, branch, token, login_with_github=login_with_github, no_wait=no_wait)
52
53
54def list_staticsite_environments(cmd, name, resource_group_name=None):
55    client = _get_staticsites_client_factory(cmd.cli_ctx)
56    if not resource_group_name:
57        resource_group_name = _get_resource_group_name_of_staticsite(client, name)
58
59    return client.get_static_site_builds(resource_group_name, name)
60
61
62def delete_staticsite_environment(cmd, name, environment_name='default', resource_group_name=None, no_wait=False):
63    client = _get_staticsites_client_factory(cmd.cli_ctx)
64    if not resource_group_name:
65        resource_group_name = _get_resource_group_name_of_staticsite(client, name)
66
67    return sdk_no_wait(no_wait, client.begin_delete_static_site_build,
68                       resource_group_name, name, environment_name)
69
70
71def show_staticsite_environment(cmd, name, environment_name='default', resource_group_name=None):
72    client = _get_staticsites_client_factory(cmd.cli_ctx)
73    if not resource_group_name:
74        resource_group_name = _get_resource_group_name_of_staticsite(client, name)
75
76    return client.get_static_site_build(resource_group_name, name, environment_name)
77
78
79def list_staticsite_domains(cmd, name, resource_group_name=None):
80    client = _get_staticsites_client_factory(cmd.cli_ctx)
81    if not resource_group_name:
82        resource_group_name = _get_resource_group_name_of_staticsite(client, name)
83
84    return client.list_static_site_custom_domains(resource_group_name, name)
85
86
87def set_staticsite_domain(cmd, name, hostname, resource_group_name=None, no_wait=False):
88    client = _get_staticsites_client_factory(cmd.cli_ctx)
89    if not resource_group_name:
90        resource_group_name = _get_resource_group_name_of_staticsite(client, name)
91
92    client.begin_validate_custom_domain_can_be_added_to_static_site(resource_group_name, name, hostname)
93
94    return sdk_no_wait(no_wait, client.create_or_update_static_site_custom_domain,
95                       resource_group_name=resource_group_name, name=name, domain_name=hostname)
96
97
98def delete_staticsite_domain(cmd, name, hostname, resource_group_name=None, no_wait=False):
99    client = _get_staticsites_client_factory(cmd.cli_ctx)
100    if not resource_group_name:
101        resource_group_name = _get_resource_group_name_of_staticsite(client, name)
102
103    logger.warning("After deleting a custom domain, there can be a 15 minute delay for the change to propagate.")
104    return sdk_no_wait(no_wait, client.begin_delete_static_site_custom_domain,
105                       resource_group_name=resource_group_name, name=name, domain_name=hostname)
106
107
108def list_staticsite_functions(cmd, name, resource_group_name=None, environment_name='default'):
109    client = _get_staticsites_client_factory(cmd.cli_ctx)
110    if not resource_group_name:
111        resource_group_name = _get_resource_group_name_of_staticsite(client, name)
112
113    return client.list_static_site_build_functions(resource_group_name, name, environment_name)
114
115
116def list_staticsite_function_app_settings(cmd, name, resource_group_name=None):
117    client = _get_staticsites_client_factory(cmd.cli_ctx)
118    if not resource_group_name:
119        resource_group_name = _get_resource_group_name_of_staticsite(client, name)
120
121    return client.list_static_site_function_app_settings(resource_group_name, name)
122
123
124def set_staticsite_function_app_settings(cmd, name, setting_pairs, resource_group_name=None):
125    client = _get_staticsites_client_factory(cmd.cli_ctx)
126    if not resource_group_name:
127        resource_group_name = _get_resource_group_name_of_staticsite(client, name)
128
129    setting_dict = {}
130    for pair in setting_pairs:
131        key, value = _parse_pair(pair, "=")
132        setting_dict[key] = value
133
134    return client.create_or_update_static_site_function_app_settings(
135        resource_group_name, name, app_settings=setting_dict)
136
137
138def delete_staticsite_function_app_settings(cmd, name, setting_names, resource_group_name=None):
139    client = _get_staticsites_client_factory(cmd.cli_ctx)
140    if not resource_group_name:
141        resource_group_name = _get_resource_group_name_of_staticsite(client, name)
142
143    app_settings = client.list_static_site_function_app_settings(resource_group_name, name).properties
144
145    for key in setting_names:
146        if key in app_settings:
147            app_settings.pop(key)
148        else:
149            logger.warning("key '%s' not found in app settings", key)
150
151    return client.create_or_update_static_site_function_app_settings(
152        resource_group_name, name, app_settings=app_settings)
153
154
155def list_staticsite_users(cmd, name, resource_group_name=None, authentication_provider='all'):
156    client = _get_staticsites_client_factory(cmd.cli_ctx)
157    if not resource_group_name:
158        resource_group_name = _get_resource_group_name_of_staticsite(client, name)
159
160    return client.list_static_site_users(resource_group_name, name, authentication_provider)
161
162
163def invite_staticsite_users(cmd, name, authentication_provider, user_details, domain,
164                            roles, invitation_expiration_in_hours, resource_group_name=None):
165    client = _get_staticsites_client_factory(cmd.cli_ctx)
166    if not resource_group_name:
167        resource_group_name = _get_resource_group_name_of_staticsite(client, name)
168
169    StaticSiteUserInvitationRequestResource = cmd.get_models('StaticSiteUserInvitationRequestResource')
170
171    invite_request = StaticSiteUserInvitationRequestResource(
172        domain=domain,
173        provider=authentication_provider,
174        user_details=user_details,
175        roles=roles,
176        num_hours_to_expiration=invitation_expiration_in_hours
177    )
178
179    return client.create_user_roles_invitation_link(resource_group_name, name, invite_request)
180
181
182def update_staticsite_users(cmd, name, roles, authentication_provider=None, user_details=None, user_id=None,
183                            resource_group_name=None):
184    client = _get_staticsites_client_factory(cmd.cli_ctx)
185    if not resource_group_name:
186        resource_group_name = _get_resource_group_name_of_staticsite(client, name)
187
188    if not authentication_provider and user_id:
189        authentication_provider = _find_authentication_provider(
190            client, resource_group_name, name, user_id, authentication_provider)
191
192    if not user_id and user_details:
193        user_id, authentication_provider = _find_user_id_and_authentication_provider(
194            client, resource_group_name, name, user_id, authentication_provider, user_details)
195
196    if not authentication_provider or not user_id:
197        raise CLIError("Either user id or user details is required.")
198
199    return client.update_static_site_user(resource_group_name, name, authentication_provider, user_id, roles=roles)
200
201
202def create_staticsites(cmd, resource_group_name, name, location,
203                       source, branch, token=None,
204                       app_location='.', api_location='.', output_location='.github/workflows',
205                       tags=None, no_wait=False, sku='Free', login_with_github=False):
206    if not token and not login_with_github:
207        raise_missing_token_suggestion()
208    elif not token:
209        from ._github_oauth import get_github_access_token
210        scopes = ["admin:repo_hook", "repo", "workflow"]
211        token = get_github_access_token(cmd, scopes)
212    elif token and login_with_github:
213        logger.warning("Both token and --login-with-github flag are provided. Will use provided token")
214
215    StaticSiteARMResource, StaticSiteBuildProperties, SkuDescription = cmd.get_models(
216        'StaticSiteARMResource', 'StaticSiteBuildProperties', 'SkuDescription')
217
218    build = StaticSiteBuildProperties(
219        app_location=app_location,
220        api_location=api_location,
221        app_artifact_location=output_location)
222
223    sku_def = SkuDescription(name=normalize_sku_for_staticapp(sku), tier=normalize_sku_for_staticapp(sku))
224
225    staticsite_deployment_properties = StaticSiteARMResource(
226        location=location,
227        tags=tags,
228        repository_url=source,
229        branch=branch,
230        repository_token=token,
231        build_properties=build,
232        sku=sku_def)
233
234    client = _get_staticsites_client_factory(cmd.cli_ctx)
235    return sdk_no_wait(no_wait, client.begin_create_or_update_static_site,
236                       resource_group_name=resource_group_name, name=name,
237                       static_site_envelope=staticsite_deployment_properties)
238
239
240def update_staticsite(cmd, name, source=None, branch=None, token=None,
241                      tags=None, sku=None, no_wait=False):
242    existing_staticsite = show_staticsite(cmd, name)
243    if not existing_staticsite:
244        raise CLIError("No static web app found with name {0}".format(name))
245
246    if tags is not None:
247        existing_staticsite.tags = tags
248
249    StaticSiteARMResource, SkuDescription = cmd.get_models('StaticSiteARMResource', 'SkuDescription')
250
251    sku_def = None
252    if sku is not None:
253        sku_def = SkuDescription(name=normalize_sku_for_staticapp(sku), tier=normalize_sku_for_staticapp(sku))
254
255    staticsite_deployment_properties = StaticSiteARMResource(
256        location=existing_staticsite.location,
257        tags=existing_staticsite.tags,
258        repository_url=source or existing_staticsite.repository_url,
259        branch=branch or existing_staticsite.branch,
260        repository_token=token or existing_staticsite.repository_token,
261        build_properties=existing_staticsite.build_properties,
262        sku=sku_def or existing_staticsite.sku)
263
264    client = _get_staticsites_client_factory(cmd.cli_ctx)
265    resource_group_name = _get_resource_group_name_of_staticsite(client, name)
266    return sdk_no_wait(no_wait, client.update_static_site,
267                       resource_group_name=resource_group_name, name=name,
268                       static_site_envelope=staticsite_deployment_properties)
269
270
271def delete_staticsite(cmd, name, resource_group_name=None, no_wait=False):
272    client = _get_staticsites_client_factory(cmd.cli_ctx)
273    if not resource_group_name:
274        resource_group_name = _get_resource_group_name_of_staticsite(client, name)
275
276    return sdk_no_wait(no_wait, client.begin_delete_static_site,
277                       resource_group_name=resource_group_name, name=name)
278
279
280def _parse_pair(pair, delimiter):
281    if delimiter not in pair:
282        CLIError("invalid format of pair {0}".format(pair))
283
284    index = pair.index(delimiter)
285    return pair[:index], pair[1 + index:]
286
287
288def _get_staticsite_location(client, static_site_name, resource_group_name):
289    static_sites = client.list()
290    for static_site in static_sites:
291        if static_site.name.lower() == static_site_name.lower():
292            found_rg = _parse_resource_group_from_arm_id(static_site.id)
293            if found_rg:
294                if found_rg.lower() == resource_group_name.lower():
295                    return static_site.location
296
297    raise CLIError("Static site was '{}' not found in subscription and resource group '{}'."
298                   .format(static_site_name, resource_group_name))
299
300
301def _get_resource_group_name_of_staticsite(client, static_site_name):
302    static_sites = client.list()
303    for static_site in static_sites:
304        if static_site.name.lower() == static_site_name.lower():
305            resource_group = _parse_resource_group_from_arm_id(static_site.id)
306            if resource_group:
307                return resource_group
308
309    raise CLIError("Static site was '{}' not found in subscription.".format(static_site_name))
310
311
312def _parse_resource_group_from_arm_id(arm_id):
313    from msrestazure.tools import parse_resource_id
314    components = parse_resource_id(arm_id)
315    rg_key = 'resource_group'
316    if rg_key not in components:
317        return None
318
319    return components['resource_group']
320
321
322def _get_staticsites_client_factory(cli_ctx, api_version=None):
323    from azure.mgmt.web import WebSiteManagementClient
324    client = get_mgmt_service_client(cli_ctx, WebSiteManagementClient).static_sites
325    if api_version:
326        client.api_version = api_version
327    return client
328
329
330def _find_authentication_provider(client, resource_group_name, name, user_id, authentication_provider):
331    users = client.list_static_site_users(resource_group_name, name, 'all')
332    for user in users:
333        if user.name.lower() == user_id.lower():
334            authentication_provider = user.provider
335
336    if not authentication_provider:
337        raise CLIError("user id was not found.")
338
339    return authentication_provider
340
341
342def _find_user_id_and_authentication_provider(client, resource_group_name, name,
343                                              user_id, authentication_provider, user_details):
344    users = client.list_static_site_users(resource_group_name, name, 'all')
345    for user in users:
346        if user.display_name.lower() == user_details.lower():
347            if not authentication_provider:
348                authentication_provider = user.provider
349                user_id = user.name
350            else:
351                if user.provider.lower() == authentication_provider.lower():
352                    user_id = user.name
353
354    if not user_id or not authentication_provider:
355        raise CLIError("user details and authentication provider was not found.")
356
357    return user_id, authentication_provider
358
359
360def list_staticsite_secrets(cmd, name, resource_group_name=None):
361    client = _get_staticsites_client_factory(cmd.cli_ctx)
362    if not resource_group_name:
363        resource_group_name = _get_resource_group_name_of_staticsite(client, name)
364
365    return client.list_static_site_secrets(resource_group_name=resource_group_name, name=name)
366
367
368def reset_staticsite_api_key(cmd, name, resource_group_name=None):
369    client = _get_staticsites_client_factory(cmd.cli_ctx)
370    if not resource_group_name:
371        resource_group_name = _get_resource_group_name_of_staticsite(client, name)
372
373    existing_staticsite = show_staticsite(cmd, name, resource_group_name)
374    ResetPropertiesEnvelope = cmd.get_models('StaticSiteResetPropertiesARMResource')
375    reset_envelope = ResetPropertiesEnvelope(repository_token=existing_staticsite.repository_token)
376    return client.reset_static_site_api_key(resource_group_name=resource_group_name,
377                                            name=name,
378                                            reset_properties_envelope=reset_envelope)
379