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
6import platform
7
8from azure.cli.core.commands.arm import resource_exists
9
10from knack.log import get_logger
11from knack.util import CLIError
12
13logger = get_logger(__name__)
14
15
16def get_folded_parameter_help_string(
17        display_name, allow_none=False, allow_new=False, default_none=False,
18        other_required_option=None, allow_cross_sub=True):
19    """ Assembles a parameterized help string for folded parameters. """
20    quotes = '""' if platform.system() == 'Windows' else "''"
21
22    if default_none and not allow_none:
23        raise CLIError('Cannot use default_none=True and allow_none=False')
24
25    if not allow_new and not allow_none and not default_none:
26        help_text = 'Name or ID of an existing {}.'.format(display_name)
27    elif not allow_new and allow_none and not default_none:
28        help_text = 'Name or ID of an existing {}, or {} for none.'.format(display_name, quotes)
29    elif allow_new and not allow_none and not default_none:
30        help_text = 'Name or ID of the {}. Will create resource if it does not exist.'.format(
31            display_name)
32    elif allow_new and allow_none and not default_none:
33        help_text = 'Name or ID of the {}, or {} for none. Uses existing resource if available or will create a new ' \
34                    'resource with defaults if omitted.'
35        help_text = help_text.format(display_name, quotes)
36    elif not allow_new and allow_none and default_none:
37        help_text = 'Name or ID of an existing {}, or none by default.'.format(display_name)
38    elif allow_new and allow_none and default_none:
39        help_text = 'Name or ID of a {}. Uses existing resource or creates new if specified, or none if omitted.'
40        help_text = help_text.format(display_name)
41
42    # add parent name option string (if applicable)
43    if other_required_option:
44        help_text = '{} If name specified, also specify {}.'.format(help_text, other_required_option)
45        extra_sub_text = " or subscription" if allow_cross_sub else ""
46        help_text = '{} If you want to use an existing {display_name} in other resource group{append_sub}, ' \
47                    'please provide the ID instead of the name of the {display_name}'.format(help_text,
48                                                                                             display_name=display_name,
49                                                                                             append_sub=extra_sub_text)
50    return help_text
51
52
53def _validate_name_or_id(
54        cli_ctx, resource_group_name, property_value, property_type, parent_value, parent_type):
55    from azure.cli.core.commands.client_factory import get_subscription_id
56    from msrestazure.tools import parse_resource_id, is_valid_resource_id
57    has_parent = parent_type is not None
58    if is_valid_resource_id(property_value):
59        resource_id_parts = parse_resource_id(property_value)
60        value_supplied_was_id = True
61    elif has_parent:
62        resource_id_parts = dict(
63            name=parent_value,
64            resource_group=resource_group_name,
65            namespace=parent_type.split('/')[0],
66            type=parent_type.split('/')[1],
67            subscription=get_subscription_id(cli_ctx),
68            child_name_1=property_value,
69            child_type_1=property_type)
70        value_supplied_was_id = False
71    else:
72        resource_id_parts = dict(
73            name=property_value,
74            resource_group=resource_group_name,
75            namespace=property_type.split('/')[0],
76            type=property_type.split('/')[1],
77            subscription=get_subscription_id(cli_ctx))
78        value_supplied_was_id = False
79    return (resource_id_parts, value_supplied_was_id)
80
81
82def get_folded_parameter_validator(
83        property_name, property_type, property_option,
84        parent_name=None, parent_type=None, parent_option=None,
85        allow_none=False, allow_new=False, default_none=False):
86
87    # Ensure that all parent parameters are specified if any are
88    parent_params = [parent_name, parent_type, parent_option]
89    has_parent = any(parent_params)
90    if has_parent and not all(parent_params):
91        raise CLIError('All parent parameters must be specified (name, type, option) if any are.')
92
93    if default_none and not allow_none:
94        raise CLIError('Cannot use default_none=True if allow_none=False')
95
96    # construct the validator
97    def validator(cmd, namespace):
98        from msrestazure.tools import resource_id
99        type_field_name = '{}_type'.format(property_name)
100        property_val = getattr(namespace, property_name, None)
101        parent_val = getattr(namespace, parent_name, None) if parent_name else None
102
103        # Check for the different scenarios (order matters)
104        # 1) provided value indicates None (pair of empty quotes)
105        if property_val in ('', '""', "''") or (property_val is None and default_none):
106            if not allow_none:
107                raise CLIError('{} cannot be None.'.format(property_option))
108            setattr(namespace, type_field_name, 'none')
109            setattr(namespace, property_name, None)
110            if parent_name and parent_val:
111                logger.warning('Ignoring: %s %s', parent_option, parent_val)
112                setattr(namespace, parent_name, None)
113            return  # SUCCESS
114
115        # Create a resource ID we can check for existence.
116        (resource_id_parts, value_was_id) = _validate_name_or_id(
117            cmd.cli_ctx, namespace.resource_group_name, property_val, property_type, parent_val, parent_type)
118
119        # 2) resource exists
120        if resource_exists(cmd.cli_ctx, **resource_id_parts):
121            setattr(namespace, type_field_name, 'existingId')
122            setattr(namespace, property_name, resource_id(**resource_id_parts))
123            if parent_val:
124                if value_was_id:
125                    logger.warning('Ignoring: %s %s', parent_option, parent_val)
126                setattr(namespace, parent_name, None)
127            return  # SUCCESS
128
129        # if a parent name was required but not specified, raise a usage error
130        if has_parent and not value_was_id and not parent_val and not allow_new:
131            raise ValueError('incorrect usage: {0} ID | {0} NAME {1} NAME'.format(
132                property_option, parent_option))
133
134        # if non-existent ID was supplied, throw error depending on whether a new resource can
135        # be created.
136        if value_was_id:
137            usage_message = '{} NAME'.format(property_option) if not has_parent \
138                else '{} NAME [{} NAME]'.format(property_option, parent_option)
139            action_message = 'Specify ( {} ) to create a new resource.'.format(usage_message) if \
140                allow_new else 'Create the required resource and try again.'
141            raise CLIError('{} {} does not exist. {}'.format(
142                property_name, property_val, action_message))
143
144        # 3) try to create new resource
145        if allow_new:
146            setattr(namespace, type_field_name, 'new')
147        else:
148            raise CLIError(
149                '{} {} does not exist. Create the required resource and try again.'.format(
150                    property_name, property_val))
151
152    return validator
153