1#!/usr/local/bin/python3.8
2#
3# Copyright (c) 2017 Yawei Wang, <yaweiw@microsoft.com>
4#
5# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
6
7from __future__ import absolute_import, division, print_function
8__metaclass__ = type
9
10DOCUMENTATION = '''
11---
12module: azure_rm_containerregistry
13version_added: "0.1.2"
14short_description: Manage an Azure Container Registry
15description:
16    - Create, update and delete an Azure Container Registry.
17
18options:
19    resource_group:
20        description:
21            - Name of a resource group where the Container Registry exists or will be created.
22        required: true
23        type: str
24    name:
25        description:
26            - Name of the Container Registry.
27        required: true
28        type: str
29    state:
30        description:
31            - Assert the state of the container registry. Use C(present) to create or update an container registry and C(absent) to delete it.
32        default: present
33        type: str
34        choices:
35            - absent
36            - present
37    location:
38        description:
39            - Valid azure location. Defaults to location of the resource group.
40        type: str
41    admin_user_enabled:
42        description:
43            - If enabled, you can use the registry name as username and admin user access key as password to docker login to your container registry.
44        type: bool
45        default: no
46    sku:
47        description:
48            - Specifies the SKU to use. Currently can be either C(Basic), C(Standard) or C(Premium).
49        default: Standard
50        type: str
51        choices:
52            - Basic
53            - Standard
54            - Premium
55
56extends_documentation_fragment:
57    - azure.azcollection.azure
58    - azure.azcollection.azure_tags
59
60author:
61    - Yawei Wang (@yaweiw)
62
63'''
64
65EXAMPLES = '''
66    - name: Create an azure container registry
67      azure_rm_containerregistry:
68        name: myRegistry
69        location: eastus
70        resource_group: myResourceGroup
71        admin_user_enabled: true
72        sku: Premium
73        tags:
74            Release: beta1
75            Environment: Production
76
77    - name: Remove an azure container registry
78      azure_rm_containerregistry:
79        name: myRegistry
80        resource_group: myResourceGroup
81        state: absent
82'''
83RETURN = '''
84id:
85    description:
86        - Resource ID.
87    returned: always
88    type: str
89    sample: /subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/myResourceGroup/providers/Microsoft.ContainerRegistry/registries/myRegistry
90name:
91    description:
92        - Registry name.
93    returned: always
94    type: str
95    sample: myregistry
96location:
97    description:
98        - Resource location.
99    returned: always
100    type: str
101    sample: westus
102admin_user_enabled:
103    description:
104        - Is admin user enabled.
105    returned: always
106    type: bool
107    sample: true
108sku:
109    description:
110        - The SKU name of the container registry.
111    returned: always
112    type: str
113    sample: Standard
114provisioning_state:
115    description:
116        - Provisioning state.
117    returned: always
118    type: str
119    sample: Succeeded
120login_server:
121    description:
122        - Registry login server.
123    returned: always
124    type: str
125    sample: myregistry.azurecr.io
126credentials:
127    description:
128        - Passwords defined for the registry.
129    returned: always
130    type: complex
131    contains:
132        password:
133            description:
134                - password value.
135            returned: when registry exists and C(admin_user_enabled) is set
136            type: str
137            sample: pass1value
138        password2:
139            description:
140                - password2 value.
141            returned: when registry exists and C(admin_user_enabled) is set
142            type: str
143            sample: pass2value
144tags:
145    description:
146        - Tags assigned to the resource. Dictionary of string:string pairs.
147    returned: always
148    type: dict
149'''
150
151from ansible_collections.azure.azcollection.plugins.module_utils.azure_rm_common import AzureRMModuleBase
152
153try:
154    from msrestazure.azure_exceptions import CloudError
155    from azure.mgmt.containerregistry.models import (
156        Registry,
157        RegistryUpdateParameters,
158        StorageAccountProperties,
159        Sku,
160        SkuName,
161        SkuTier,
162        ProvisioningState,
163        PasswordName,
164        WebhookCreateParameters,
165        WebhookUpdateParameters,
166        WebhookAction,
167        WebhookStatus
168    )
169    from azure.mgmt.containerregistry import ContainerRegistryManagementClient
170except ImportError as exc:
171    # This is handled in azure_rm_common
172    pass
173
174
175def create_containerregistry_dict(registry, credentials):
176    '''
177    Helper method to deserialize a ContainerRegistry to a dict
178    :param: registry: return container registry object from Azure rest API call
179    :param: credentials: return credential objects from Azure rest API call
180    :return: dict of return container registry and it's credentials
181    '''
182    results = dict(
183        id=registry.id if registry is not None else "",
184        name=registry.name if registry is not None else "",
185        location=registry.location if registry is not None else "",
186        admin_user_enabled=registry.admin_user_enabled if registry is not None else "",
187        sku=registry.sku.name if registry is not None else "",
188        provisioning_state=registry.provisioning_state if registry is not None else "",
189        login_server=registry.login_server if registry is not None else "",
190        credentials=dict(),
191        tags=registry.tags if registry is not None else ""
192    )
193    if credentials:
194        results['credentials'] = dict(
195            password=credentials.passwords[0].value,
196            password2=credentials.passwords[1].value
197        )
198
199    return results
200
201
202class Actions:
203    NoAction, Create, Update = range(3)
204
205
206class AzureRMContainerRegistry(AzureRMModuleBase):
207    """Configuration class for an Azure RM container registry resource"""
208
209    def __init__(self):
210        self.module_arg_spec = dict(
211            resource_group=dict(
212                type='str',
213                required=True
214            ),
215            name=dict(
216                type='str',
217                required=True
218            ),
219            state=dict(
220                type='str',
221                default='present',
222                choices=['present', 'absent']
223            ),
224            location=dict(
225                type='str'
226            ),
227            admin_user_enabled=dict(
228                type='bool',
229                default=False
230            ),
231            sku=dict(
232                type='str',
233                default='Standard',
234                choices=['Basic', 'Standard', 'Premium']
235            )
236        )
237
238        self.resource_group = None
239        self.name = None
240        self.location = None
241        self.state = None
242        self.sku = None
243        self.tags = None
244
245        self.results = dict(changed=False, state=dict())
246
247        super(AzureRMContainerRegistry, self).__init__(
248            derived_arg_spec=self.module_arg_spec,
249            supports_check_mode=True,
250            supports_tags=True)
251
252    def exec_module(self, **kwargs):
253        """Main module execution method"""
254        for key in list(self.module_arg_spec.keys()) + ['tags']:
255            setattr(self, key, kwargs[key])
256
257        resource_group = None
258        response = None
259        to_do = Actions.NoAction
260
261        resource_group = self.get_resource_group(self.resource_group)
262        if not self.location:
263            self.location = resource_group.location
264
265        # Check if the container registry instance already present in the RG
266        if self.state == 'present':
267            response = self.get_containerregistry()
268
269            if not response:
270                to_do = Actions.Create
271            else:
272                self.log('Results : {0}'.format(response))
273                self.results.update(response)
274                if response['provisioning_state'] == "Succeeded":
275                    to_do = Actions.NoAction
276                    if (self.location is not None) and self.location != response['location']:
277                        to_do = Actions.Update
278                    elif (self.sku is not None) and self.sku != response['sku']:
279                        to_do = Actions.Update
280                else:
281                    to_do = Actions.NoAction
282
283            self.log("Create / Update the container registry instance")
284            if self.check_mode:
285                return self.results
286
287            self.results.update(self.create_update_containerregistry(to_do))
288            if to_do != Actions.NoAction:
289                self.results['changed'] = True
290            else:
291                self.results.update(response)
292                self.results['changed'] = False
293
294            self.log("Container registry instance created or updated")
295        elif self.state == 'absent':
296            if self.check_mode:
297                return self.results
298            self.delete_containerregistry()
299            self.log("Container registry instance deleted")
300
301        return self.results
302
303    def create_update_containerregistry(self, to_do):
304        '''
305        Creates or updates a container registry.
306
307        :return: deserialized container registry instance state dictionary
308        '''
309        self.log("Creating / Updating the container registry instance {0}".format(self.name))
310
311        try:
312            if to_do != Actions.NoAction:
313                if to_do == Actions.Create:
314                    name_status = self.containerregistry_client.registries.check_name_availability(self.name)
315                    if name_status.name_available:
316                        poller = self.containerregistry_client.registries.create(
317                            resource_group_name=self.resource_group,
318                            registry_name=self.name,
319                            registry=Registry(
320                                location=self.location,
321                                sku=Sku(
322                                    name=self.sku
323                                ),
324                                tags=self.tags,
325                                admin_user_enabled=self.admin_user_enabled
326                            )
327                        )
328                    else:
329                        raise Exception("Invalid registry name. reason: " + name_status.reason + " message: " + name_status.message)
330                else:
331                    registry = self.containerregistry_client.registries.get(self.resource_group, self.name)
332                    if registry is not None:
333                        poller = self.containerregistry_client.registries.update(
334                            resource_group_name=self.resource_group,
335                            registry_name=self.name,
336                            registry_update_parameters=RegistryUpdateParameters(
337                                sku=Sku(
338                                    name=self.sku
339                                ),
340                                tags=self.tags,
341                                admin_user_enabled=self.admin_user_enabled
342                            )
343                        )
344                    else:
345                        raise Exception("Update registry failed as registry '" + self.name + "' doesn't exist.")
346                response = self.get_poller_result(poller)
347                if self.admin_user_enabled:
348                    credentials = self.containerregistry_client.registries.list_credentials(self.resource_group, self.name)
349                else:
350                    self.log('Cannot perform credential operations as admin user is disabled')
351                    credentials = None
352            else:
353                response = None
354                credentials = None
355        except (CloudError, Exception) as exc:
356            self.log('Error attempting to create / update the container registry instance.')
357            self.fail("Error creating / updating the container registry instance: {0}".format(str(exc)))
358        return create_containerregistry_dict(response, credentials)
359
360    def delete_containerregistry(self):
361        '''
362        Deletes the specified container registry in the specified subscription and resource group.
363
364        :return: True
365        '''
366        self.log("Deleting the container registry instance {0}".format(self.name))
367        try:
368            self.containerregistry_client.registries.delete(self.resource_group, self.name).wait()
369        except CloudError as e:
370            self.log('Error attempting to delete the container registry instance.')
371            self.fail("Error deleting the container registry instance: {0}".format(str(e)))
372
373        return True
374
375    def get_containerregistry(self):
376        '''
377        Gets the properties of the specified container registry.
378
379        :return: deserialized container registry state dictionary
380        '''
381        self.log("Checking if the container registry instance {0} is present".format(self.name))
382        found = False
383        try:
384            response = self.containerregistry_client.registries.get(self.resource_group, self.name)
385            found = True
386            self.log("Response : {0}".format(response))
387            self.log("Container registry instance : {0} found".format(response.name))
388        except CloudError as e:
389            if e.error.error == 'ResourceNotFound':
390                self.log('Did not find the container registry instance: {0}'.format(str(e)))
391            else:
392                self.fail('Error while trying to get container registry instance: {0}'.format(str(e)))
393            response = None
394        if found is True and self.admin_user_enabled is True:
395            try:
396                credentials = self.containerregistry_client.registries.list_credentials(self.resource_group, self.name)
397            except CloudError as e:
398                self.fail('List registry credentials failed: {0}'.format(str(e)))
399                credentials = None
400        elif found is True and self.admin_user_enabled is False:
401            credentials = None
402        else:
403            return None
404        return create_containerregistry_dict(response, credentials)
405
406
407def main():
408    """Main execution"""
409    AzureRMContainerRegistry()
410
411
412if __name__ == '__main__':
413    main()
414