shared image gallery modules (#57386)

* modules to handle shared image gallery

* update test

* and firewall update

* fixed gallery problems

* fixed gallery version

* fix

* several fixes to the gallery

* several fixes

* fixes

* fix non-updatable

* fixed test & image

* fixed idempotency

* fix test

* image version test fixed

* fixes

* changed ux

* trigger

* fix syntax

* fixed sanity

* updated module + test delete

* fixed some sanity & delete

* continue fixing sanity

* sanity fix and pause after deleting image version

* extended delay

* removed sanity ignore

* try to ignore errors

* repeat until successful

* more retries

* updated test, etc.

* updated test

* updated shared image gallery docs
This commit is contained in:
Zim Kalinowski 2019-06-27 08:15:55 +08:00 committed by Matt Davis
parent eda5dd826f
commit c9d82024c7
10 changed files with 1637 additions and 29 deletions

View file

@ -140,34 +140,44 @@ class AzureRMModuleBaseExt(AzureRMModuleBase):
if new is None:
return True
elif isinstance(new, dict):
comparison_result = True
if not isinstance(old, dict):
result['compare'] = 'changed [' + path + '] old dict is null'
return False
for k in new.keys():
if not self.default_compare(modifiers, new.get(k), old.get(k, None), path + '/' + k, result):
return False
return True
elif isinstance(new, list):
if not isinstance(old, list) or len(new) != len(old):
result['compare'] = 'changed [' + path + '] length is different or null'
return False
if isinstance(old[0], dict):
key = None
if 'id' in old[0] and 'id' in new[0]:
key = 'id'
elif 'name' in old[0] and 'name' in new[0]:
key = 'name'
else:
key = next(iter(old[0]))
new = sorted(new, key=lambda x: x.get(key, None))
old = sorted(old, key=lambda x: x.get(key, None))
result['compare'].append('changed [' + path + '] old dict is null')
comparison_result = False
else:
new = sorted(new)
old = sorted(old)
for i in range(len(new)):
if not self.default_compare(modifiers, new[i], old[i], path + '/*', result):
return False
return True
for k in set(new.keys()) | set(old.keys()):
new_item = new.get(k, None)
old_item = old.get(k, None)
if new_item is None:
if isinstance(old_item, dict):
new[k] = old_item
result['compare'].append('new item was empty, using old [' + path + '][ ' + k + ' ]')
elif not self.default_compare(modifiers, new_item, old_item, path + '/' + k, result):
comparison_result = False
return comparison_result
elif isinstance(new, list):
comparison_result = True
if not isinstance(old, list) or len(new) != len(old):
result['compare'].append('changed [' + path + '] length is different or old value is null')
comparison_result = False
else:
if isinstance(old[0], dict):
key = None
if 'id' in old[0] and 'id' in new[0]:
key = 'id'
elif 'name' in old[0] and 'name' in new[0]:
key = 'name'
else:
key = next(iter(old[0]))
new = sorted(new, key=lambda x: x.get(key, None))
old = sorted(old, key=lambda x: x.get(key, None))
else:
new = sorted(new)
old = sorted(old)
for i in range(len(new)):
if not self.default_compare(modifiers, new[i], old[i], path + '/*', result):
comparison_result = False
return comparison_result
else:
updatable = modifiers.get(path, {}).get('updatable', True)
comparison = modifiers.get(path, {}).get('comparison', 'default')
@ -182,7 +192,7 @@ class AzureRMModuleBaseExt(AzureRMModuleBase):
new = new.replace(' ', '').lower()
old = old.replace(' ', '').lower()
if str(new) != str(old):
result['compare'] = 'changed [' + path + '] ' + str(new) + ' != ' + str(old) + ' - ' + str(comparison)
result['compare'].append('changed [' + path + '] ' + str(new) + ' != ' + str(old) + ' - ' + str(comparison))
if updatable:
return False
else:

View file

@ -19,7 +19,7 @@ module: azure_rm_azurefirewall
version_added: '2.9'
short_description: Manage Azure Firewall instance.
description:
- 'Create, update and delete instance of Azure Firewall.'
- Create, update and delete instance of Azure Firewall.
options:
resource_group:
description:
@ -600,6 +600,7 @@ class AzureRMAzureFirewalls(AzureRMModuleBaseExt):
modifiers = {}
self.create_compare_modifiers(self.module_arg_spec, '', modifiers)
self.results['modifiers'] = modifiers
self.results['compare'] = []
if not self.default_compare(modifiers, self.body, old_response, '', self.results):
self.to_do = Actions.Update

View file

@ -0,0 +1,314 @@
#!/usr/bin/python
#
# Copyright (c) 2019 Zim Kalinowski, (@zikalino)
#
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: azure_rm_gallery
version_added: '2.9'
short_description: Manage Azure Shared Image Gallery instance.
description:
- 'Create, update and delete instance of Azure Shared Image Gallery (SIG).'
options:
resource_group:
description:
- The name of the resource group.
required: true
type: str
name:
description:
- >-
The name of the Shared Image Gallery.
Valid names consist of less than 80 alphanumeric characters, underscores and periods.
required: true
type: str
location:
description:
- Resource location
type: str
description:
description:
- >-
The description of this Shared Image Gallery resource. This property is
updatable.
type: str
state:
description:
- Assert the state of the Gallery.
- >-
Use C(present) to create or update an Gallery and C(absent) to delete
it.
default: present
type: str
choices:
- absent
- present
extends_documentation_fragment:
- azure
- azure_tags
author:
- Zim Kalinowski (@zikalino)
'''
EXAMPLES = '''
- name: Create or update a simple gallery.
azure_rm_gallery:
resource_group: myResourceGroup
name: myGallery1283
location: West US
description: This is the gallery description.
'''
RETURN = '''
id:
description:
- Resource Id
returned: always
type: str
sample: "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/myResourceGroup/providers/Microsoft.Compute/galleries/myGallery1283"
'''
import time
import json
import re
from ansible.module_utils.azure_rm_common_ext import AzureRMModuleBaseExt
from ansible.module_utils.azure_rm_common_rest import GenericRestClient
from copy import deepcopy
try:
from msrestazure.azure_exceptions import CloudError
except ImportError:
# This is handled in azure_rm_common
pass
class Actions:
NoAction, Create, Update, Delete = range(4)
class AzureRMGalleries(AzureRMModuleBaseExt):
def __init__(self):
self.module_arg_spec = dict(
resource_group=dict(
type='str',
updatable=False,
disposition='resourceGroupName',
required=True
),
name=dict(
type='str',
updatable=False,
disposition='galleryName',
required=True
),
location=dict(
type='str',
updatable=False,
disposition='/'
),
description=dict(
type='str',
disposition='/properties/*'
),
state=dict(
type='str',
default='present',
choices=['present', 'absent']
)
)
self.resource_group = None
self.name = None
self.gallery = None
self.results = dict(changed=False)
self.mgmt_client = None
self.state = None
self.url = None
self.status_code = [200, 201, 202]
self.to_do = Actions.NoAction
self.body = {}
self.query_parameters = {}
self.query_parameters['api-version'] = '2019-03-01'
self.header_parameters = {}
self.header_parameters['Content-Type'] = 'application/json; charset=utf-8'
super(AzureRMGalleries, self).__init__(derived_arg_spec=self.module_arg_spec,
supports_check_mode=True,
supports_tags=True)
def exec_module(self, **kwargs):
for key in list(self.module_arg_spec.keys()):
if hasattr(self, key):
setattr(self, key, kwargs[key])
elif kwargs[key] is not None:
self.body[key] = kwargs[key]
self.inflate_parameters(self.module_arg_spec, self.body, 0)
old_response = None
response = None
self.mgmt_client = self.get_mgmt_svc_client(GenericRestClient,
base_url=self._cloud_environment.endpoints.resource_manager)
resource_group = self.get_resource_group(self.resource_group)
if 'location' not in self.body:
self.body['location'] = resource_group.location
self.url = ('/subscriptions' +
'/{{ subscription_id }}' +
'/resourceGroups' +
'/{{ resource_group }}' +
'/providers' +
'/Microsoft.Compute' +
'/galleries' +
'/{{ gallery_name }}')
self.url = self.url.replace('{{ subscription_id }}', self.subscription_id)
self.url = self.url.replace('{{ resource_group }}', self.resource_group)
self.url = self.url.replace('{{ gallery_name }}', self.name)
old_response = self.get_resource()
if not old_response:
self.log("Gallery instance doesn't exist")
if self.state == 'absent':
self.log("Old instance didn't exist")
else:
self.to_do = Actions.Create
else:
self.log('Gallery instance already exists')
if self.state == 'absent':
self.to_do = Actions.Delete
else:
modifiers = {}
self.create_compare_modifiers(self.module_arg_spec, '', modifiers)
self.results['modifiers'] = modifiers
self.results['compare'] = []
if not self.default_compare(modifiers, self.body, old_response, '', self.results):
self.to_do = Actions.Update
self.body['properties'].pop('identifier', None)
if (self.to_do == Actions.Create) or (self.to_do == Actions.Update):
self.log('Need to Create / Update the Gallery instance')
if self.check_mode:
self.results['changed'] = True
return self.results
response = self.create_update_resource()
# if not old_response:
self.results['changed'] = True
# else:
# self.results['changed'] = old_response.__ne__(response)
self.log('Creation / Update done')
elif self.to_do == Actions.Delete:
self.log('Gallery instance deleted')
self.results['changed'] = True
if self.check_mode:
return self.results
self.delete_resource()
# make sure instance is actually deleted, for some Azure resources, instance is hanging around
# for some time after deletion -- this should be really fixed in Azure
while self.get_resource():
time.sleep(20)
else:
self.log('Gallery instance unchanged')
self.results['changed'] = False
response = old_response
if response:
self.results["id"] = response["id"]
return self.results
def create_update_resource(self):
# self.log('Creating / Updating the Gallery instance {0}'.format(self.))
try:
response = self.mgmt_client.query(self.url,
'PUT',
self.query_parameters,
self.header_parameters,
self.body,
self.status_code,
600,
30)
except CloudError as exc:
self.log('Error attempting to create the Gallery instance.')
self.fail('Error creating the Gallery instance: {0}'.format(str(exc)))
try:
response = json.loads(response.text)
except Exception:
response = {'text': response.text}
pass
return response
def delete_resource(self):
# self.log('Deleting the Gallery instance {0}'.format(self.))
try:
response = self.mgmt_client.query(self.url,
'DELETE',
self.query_parameters,
self.header_parameters,
None,
self.status_code,
600,
30)
except CloudError as e:
self.log('Error attempting to delete the Gallery instance.')
self.fail('Error deleting the Gallery instance: {0}'.format(str(e)))
return True
def get_resource(self):
# self.log('Checking if the Gallery instance {0} is present'.format(self.))
found = False
try:
response = self.mgmt_client.query(self.url,
'GET',
self.query_parameters,
self.header_parameters,
None,
self.status_code,
600,
30)
response = json.loads(response.text)
found = True
self.log("Response : {0}".format(response))
# self.log("AzureFirewall instance : {0} found".format(response.name))
except CloudError as e:
self.log('Did not find the AzureFirewall instance.')
if found is True:
return response
return False
def main():
AzureRMGalleries()
if __name__ == '__main__':
main()

View file

@ -0,0 +1,554 @@
#!/usr/bin/python
#
# Copyright (c) 2019 Zim Kalinowski, (@zikalino)
#
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: azure_rm_galleryimage
version_added: '2.9'
short_description: Manage Azure SIG Image instance.
description:
- 'Create, update and delete instance of Azure SIG Image.'
options:
resource_group:
description:
- The name of the resource group.
required: true
type: str
gallery_name:
description:
- >-
The name of the Shared Image Gallery in which the Image Definition is to
be created.
required: true
type: str
name:
description:
- >-
The name of the gallery Image Definition to be created or updated. The
allowed characters are alphabets and numbers with dots, dashes, and
periods allowed in the middle. The maximum length is 80 characters.
required: true
type: str
location:
description:
- Resource location
type: str
description:
description:
- >-
The description of this gallery Image Definition resource. This property
is updatable.
type: str
eula:
description:
- The Eula agreement for the gallery Image Definition.
type: str
privacy_statement_uri:
description:
- The privacy statement uri.
type: str
release_note_uri:
description:
- The release note uri.
type: str
os_type:
description:
- >-
This property allows you to specify the type of the OS that is included
in the disk when creating a VM from a managed image.
choices:
- windows
- linux
required: true
type: str
os_state:
description:
- The allowed values for OS State are 'Generalized'.
choices:
- generalized
- specialized
required: true
type: str
end_of_life_date:
description:
- >-
The end of life date of the gallery Image Definition. This property can
be used for decommissioning purposes. This property is updatable.
Format should be according to ISO-8601, for instance "2019-06-26".
type: str
identifier:
description:
- Image identifier.
required: true
type: dict
suboptions:
publisher:
description:
- The name of the gallery Image Definition publisher.
required: true
type: str
offer:
description:
- The name of the gallery Image Definition offer.
required: true
type: str
sku:
description:
- The name of the gallery Image Definition SKU.
required: true
type: str
recommended:
description:
- Recommended parameter values.
type: dict
suboptions:
v_cpus:
description:
- Number of virtual CPUs.
type: dict
suboptions:
min:
description:
- The minimum number of the resource.
type: int
max:
description:
- The maximum number of the resource.
type: int
memory:
description:
- Memory.
type: dict
suboptions:
min:
description:
- The minimum number of the resource.
type: int
max:
description:
- The maximum number of the resource.
type: int
disallowed:
description:
- Disalloved parameter values.
type: dict
suboptions:
disk_types:
description:
- A list of disallowed disk types.
type: list
purchase_plan:
description:
- Purchase plan.
type: dict
suboptions:
name:
description:
- The plan ID.
type: str
publisher:
description:
- The publisher ID.
type: str
product:
description:
- The product ID.
type: str
state:
description:
- Assert the state of the GalleryImage.
- >-
Use C(present) to create or update an GalleryImage and C(absent) to
delete it.
default: present
choices:
- absent
- present
type: str
extends_documentation_fragment:
- azure
- azure_tags
author:
- Zim Kalinowski (@zikalino)
'''
EXAMPLES = '''
- name: Create or update gallery image
azure_rm_galleryimage:
resource_group: myResourceGroup
gallery_name: myGallery1283
name: myImage
location: West US
os_type: linux
os_state: generalized
identifier:
publisher: myPublisherName
offer: myOfferName
sku: mySkuName
'''
RETURN = '''
id:
description:
- Resource Id
returned: always
type: str
sample: "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/myResourceGroup/providers/Microsoft.Compute/galleries/myGalle
ry1283/images/myImage"
'''
import time
import json
import re
from ansible.module_utils.azure_rm_common_ext import AzureRMModuleBaseExt
from ansible.module_utils.azure_rm_common_rest import GenericRestClient
from copy import deepcopy
try:
from msrestazure.azure_exceptions import CloudError
except ImportError:
# This is handled in azure_rm_common
pass
class Actions:
NoAction, Create, Update, Delete = range(4)
class AzureRMGalleryImages(AzureRMModuleBaseExt):
def __init__(self):
self.module_arg_spec = dict(
resource_group=dict(
type='str',
updatable=False,
disposition='resourceGroupName',
required=True
),
gallery_name=dict(
type='str',
updatable=False,
disposition='galleryName',
required=True
),
name=dict(
type='str',
updatable=False,
disposition='galleryImageName',
required=True
),
location=dict(
type='str',
updatable=False,
disposition='/'
),
description=dict(
type='str',
disposition='/properties/*'
),
eula=dict(
type='str',
disposition='/properties/*'
),
privacy_statement_uri=dict(
type='str',
disposition='/properties/privacyStatementUri'
),
release_note_uri=dict(
type='str',
disposition='/properties/releaseNoteUri'
),
os_type=dict(
type='str',
disposition='/properties/osType',
choices=['windows',
'linux']
),
os_state=dict(
type='str',
disposition='/properties/osState',
choices=['generalized',
'specialized']
),
end_of_life_date=dict(
type='str',
disposition='/properties/endOfLifeDate'
),
identifier=dict(
type='dict',
disposition='/properties/*',
options=dict(
publisher=dict(
type='str',
required=True,
updatable=False
),
offer=dict(
type='str',
required=True
),
sku=dict(
type='str',
required=True
)
)
),
recommended=dict(
type='dict',
disposition='/properties/*',
options=dict(
v_cpus=dict(
type='dict',
disposition='vCPUs',
options=dict(
min=dict(
type='int'
),
max=dict(
type='int'
)
)
),
memory=dict(
type='dict',
options=dict(
min=dict(
type='int'
),
max=dict(
type='int'
)
)
)
)
),
disallowed=dict(
type='dict',
disposition='/properties/*',
options=dict(
disk_types=dict(
type='list',
disposition='diskTypes'
)
)
),
purchase_plan=dict(
type='dict',
disposition='/properties/purchasePlan',
options=dict(
name=dict(
type='str'
),
publisher=dict(
type='str'
),
product=dict(
type='str'
)
)
),
state=dict(
type='str',
default='present',
choices=['present', 'absent']
)
)
self.resource_group = None
self.gallery_name = None
self.name = None
self.gallery_image = None
self.results = dict(changed=False)
self.mgmt_client = None
self.state = None
self.url = None
self.status_code = [200, 201, 202]
self.to_do = Actions.NoAction
self.body = {}
self.query_parameters = {}
self.query_parameters['api-version'] = '2019-03-01'
self.header_parameters = {}
self.header_parameters['Content-Type'] = 'application/json; charset=utf-8'
super(AzureRMGalleryImages, self).__init__(derived_arg_spec=self.module_arg_spec,
supports_check_mode=True,
supports_tags=True)
def exec_module(self, **kwargs):
for key in list(self.module_arg_spec.keys()):
if hasattr(self, key):
setattr(self, key, kwargs[key])
elif kwargs[key] is not None:
self.body[key] = kwargs[key]
self.inflate_parameters(self.module_arg_spec, self.body, 0)
old_response = None
response = None
self.mgmt_client = self.get_mgmt_svc_client(GenericRestClient,
base_url=self._cloud_environment.endpoints.resource_manager)
resource_group = self.get_resource_group(self.resource_group)
if 'location' not in self.body:
self.body['location'] = resource_group.location
self.url = ('/subscriptions' +
'/{{ subscription_id }}' +
'/resourceGroups' +
'/{{ resource_group }}' +
'/providers' +
'/Microsoft.Compute' +
'/galleries' +
'/{{ gallery_name }}' +
'/images' +
'/{{ image_name }}')
self.url = self.url.replace('{{ subscription_id }}', self.subscription_id)
self.url = self.url.replace('{{ resource_group }}', self.resource_group)
self.url = self.url.replace('{{ gallery_name }}', self.gallery_name)
self.url = self.url.replace('{{ image_name }}', self.name)
old_response = self.get_resource()
if not old_response:
self.log("GalleryImage instance doesn't exist")
if self.state == 'absent':
self.log("Old instance didn't exist")
else:
self.to_do = Actions.Create
else:
self.log('GalleryImage instance already exists')
if self.state == 'absent':
self.to_do = Actions.Delete
else:
modifiers = {}
self.create_compare_modifiers(self.module_arg_spec, '', modifiers)
self.results['modifiers'] = modifiers
self.results['compare'] = []
if not self.default_compare(modifiers, self.body, old_response, '', self.results):
self.to_do = Actions.Update
if (self.to_do == Actions.Create) or (self.to_do == Actions.Update):
self.log('Need to Create / Update the GalleryImage instance')
if self.check_mode:
self.results['changed'] = True
return self.results
response = self.create_update_resource()
# if not old_response:
self.results['changed'] = True
# else:
# self.results['changed'] = old_response.__ne__(response)
self.log('Creation / Update done')
elif self.to_do == Actions.Delete:
self.log('GalleryImage instance deleted')
self.results['changed'] = True
if self.check_mode:
return self.results
self.delete_resource()
# make sure instance is actually deleted, for some Azure resources, instance is hanging around
# for some time after deletion -- this should be really fixed in Azure
while self.get_resource():
time.sleep(20)
else:
self.log('GalleryImage instance unchanged')
self.results['changed'] = False
response = old_response
if response:
self.results["id"] = response["id"]
return self.results
def create_update_resource(self):
# self.log('Creating / Updating the GalleryImage instance {0}'.format(self.))
try:
response = self.mgmt_client.query(self.url,
'PUT',
self.query_parameters,
self.header_parameters,
self.body,
self.status_code,
600,
30)
except CloudError as exc:
self.log('Error attempting to create the GalleryImage instance.')
self.fail('Error creating the GalleryImage instance: {0}'.format(str(exc)))
try:
response = json.loads(response.text)
except Exception:
response = {'text': response.text}
pass
return response
def delete_resource(self):
# self.log('Deleting the GalleryImage instance {0}'.format(self.))
try:
response = self.mgmt_client.query(self.url,
'DELETE',
self.query_parameters,
self.header_parameters,
None,
self.status_code,
600,
30)
except CloudError as e:
self.log('Error attempting to delete the GalleryImage instance.')
self.fail('Error deleting the GalleryImage instance: {0}'.format(str(e)))
return True
def get_resource(self):
# self.log('Checking if the GalleryImage instance {0} is present'.format(self.))
found = False
try:
response = self.mgmt_client.query(self.url,
'GET',
self.query_parameters,
self.header_parameters,
None,
self.status_code,
600,
30)
response = json.loads(response.text)
found = True
self.log("Response : {0}".format(response))
# self.log("AzureFirewall instance : {0} found".format(response.name))
except CloudError as e:
self.log('Did not find the AzureFirewall instance.')
if found is True:
return response
return False
def main():
AzureRMGalleryImages()
if __name__ == '__main__':
main()

View file

@ -0,0 +1,459 @@
#!/usr/bin/python
#
# Copyright (c) 2019 Zim Kalinowski, (@zikalino)
#
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: azure_rm_galleryimageversion
version_added: '2.9'
short_description: Manage Azure SIG Image Version instance.
description:
- 'Create, update and delete instance of Azure SIG Image Version.'
options:
resource_group:
description:
- The name of the resource group.
required: true
type: str
gallery_name:
description:
- >-
The name of the Shared Image Gallery in which the Image Definition
resides.
required: true
type: str
gallery_image_name:
description:
- >-
The name of the gallery Image Definition in which the Image Version is
to be created.
required: true
type: str
name:
description:
- >-
The name of the gallery Image Version to be created. Needs to follow
semantic version name pattern: The allowed characters are digit and
period. Digits must be within the range of a 32-bit integer. Format:
<MajorVersion>.<MinorVersion>.<Patch>
required: true
type: str
location:
description:
- Resource location
type: str
publishing_profile:
description:
- Publishing profile.
required: true
type: dict
suboptions:
target_regions:
description:
- >-
The target regions where the Image Version is going to be replicated
to. This property is updatable.
type: list
suboptions:
name:
description:
- Region name.
type: str
regional_replica_count:
description:
- >-
The number of replicas of the Image Version to be created per
region. This property would take effect for a region when
regionalReplicaCount is not specified. This property is updatable.
type: str
storage_account_type:
description:
- Storage account type.
type: str
managed_image:
description:
- Managed image reference, could be resource id, or dictionary containing C(resource_group) and C(name)
required: true
type: raw
replica_count:
description:
- >-
The number of replicas of the Image Version to be created per
region. This property would take effect for a region when
regionalReplicaCount is not specified. This property is updatable.
type: number
exclude_from_latest:
description:
- >-
If set to true, Virtual Machines deployed from the latest version of
the Image Definition won't use this Image Version.
type: bool
end_of_life_date:
description:
- >-
The end of life date of the gallery Image Version. This property can
be used for decommissioning purposes. This property is updatable.
Format should be according to ISO-8601, for instance "2019-06-26".
type: str
storage_account_type:
description:
- >-
Specifies the storage account type to be used to store the image.
This property is not updatable.
type: str
state:
description:
- Assert the state of the GalleryImageVersion.
- >-
Use C(present) to create or update an GalleryImageVersion and C(absent)
to delete it.
default: present
choices:
- absent
- present
type: str
extends_documentation_fragment:
- azure
- azure_tags
author:
- Zim Kalinowski (@zikalino)
'''
EXAMPLES = '''
- name: Create or update a simple gallery Image Version.
azure_rm_galleryimageversion:
resource_group: myResourceGroup
gallery_name: myGallery1283
gallery_image_name: myImage
name: 10.1.3
location: West US
publishing_profile:
end_of_life_date: "2020-10-01t00:00:00+00:00"
exclude_from_latest: yes
replica_count: 3
storage_account_type: Standard_LRS
target_regions:
- name: West US
regional_replica_count: 1
- name: East US
regional_replica_count: 2
storage_account_type: Standard_ZRS
managed_image:
name: myImage
resource_group: myResourceGroup
'''
RETURN = '''
id:
description:
- Resource Id
returned: always
type: str
sample: "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/myResourceGroup/providers/Microsoft.Compute/galleries/myGalle
ry1283/images/myImage/versions/10.1.3"
'''
import time
import json
import re
from ansible.module_utils.azure_rm_common_ext import AzureRMModuleBaseExt
from ansible.module_utils.azure_rm_common_rest import GenericRestClient
from copy import deepcopy
try:
from msrestazure.azure_exceptions import CloudError
except ImportError:
# This is handled in azure_rm_common
pass
class Actions:
NoAction, Create, Update, Delete = range(4)
class AzureRMGalleryImageVersions(AzureRMModuleBaseExt):
def __init__(self):
self.module_arg_spec = dict(
resource_group=dict(
type='str',
updatable=False,
disposition='resourceGroupName',
required=True
),
gallery_name=dict(
type='str',
updatable=False,
disposition='galleryName',
required=True
),
gallery_image_name=dict(
type='str',
updatable=False,
disposition='galleryImageName',
required=True
),
name=dict(
type='str',
updatable=False,
disposition='galleryImageVersionName',
required=True
),
location=dict(
type='str',
updatable=False,
disposition='/'
),
publishing_profile=dict(
type='dict',
disposition='/properties/publishingProfile',
options=dict(
target_regions=dict(
type='list',
disposition='targetRegions',
options=dict(
name=dict(
type='str',
required=True
),
regional_replica_count=dict(
type='int',
disposition='regionalReplicaCount'
),
storage_account_type=dict(
type='str',
disposition='storageAccountType'
)
)
),
managed_image=dict(
type='raw',
pattern=('/subscriptions/{subscription_id}/resourceGroups'
'/{resource_group}/providers/Microsoft.Compute'
'/images/{name}'),
disposition='source/managedImage/id'
),
replica_count=dict(
type='int',
disposition='replicaCount'
),
exclude_from_latest=dict(
type='bool',
disposition='excludeFromLatest'
),
end_of_life_date=dict(
type='str',
disposition='endOfLifeDate'
),
storage_account_type=dict(
type='str',
disposition='storageAccountType',
choices=['Standard_LRS',
'Standard_ZRS']
)
)
),
state=dict(
type='str',
default='present',
choices=['present', 'absent']
)
)
self.resource_group = None
self.gallery_name = None
self.gallery_image_name = None
self.name = None
self.gallery_image_version = None
self.results = dict(changed=False)
self.mgmt_client = None
self.state = None
self.url = None
self.status_code = [200, 201, 202]
self.to_do = Actions.NoAction
self.body = {}
self.query_parameters = {}
self.query_parameters['api-version'] = '2019-03-01'
self.header_parameters = {}
self.header_parameters['Content-Type'] = 'application/json; charset=utf-8'
super(AzureRMGalleryImageVersions, self).__init__(derived_arg_spec=self.module_arg_spec,
supports_check_mode=True,
supports_tags=True)
def exec_module(self, **kwargs):
for key in list(self.module_arg_spec.keys()):
if hasattr(self, key):
setattr(self, key, kwargs[key])
elif kwargs[key] is not None:
self.body[key] = kwargs[key]
self.inflate_parameters(self.module_arg_spec, self.body, 0)
old_response = None
response = None
self.mgmt_client = self.get_mgmt_svc_client(GenericRestClient,
base_url=self._cloud_environment.endpoints.resource_manager)
resource_group = self.get_resource_group(self.resource_group)
if 'location' not in self.body:
self.body['location'] = resource_group.location
self.url = ('/subscriptions' +
'/{{ subscription_id }}' +
'/resourceGroups' +
'/{{ resource_group }}' +
'/providers' +
'/Microsoft.Compute' +
'/galleries' +
'/{{ gallery_name }}' +
'/images' +
'/{{ image_name }}' +
'/versions' +
'/{{ version_name }}')
self.url = self.url.replace('{{ subscription_id }}', self.subscription_id)
self.url = self.url.replace('{{ resource_group }}', self.resource_group)
self.url = self.url.replace('{{ gallery_name }}', self.gallery_name)
self.url = self.url.replace('{{ image_name }}', self.gallery_image_name)
self.url = self.url.replace('{{ version_name }}', self.name)
old_response = self.get_resource()
if not old_response:
self.log("GalleryImageVersion instance doesn't exist")
if self.state == 'absent':
self.log("Old instance didn't exist")
else:
self.to_do = Actions.Create
else:
self.log('GalleryImageVersion instance already exists')
if self.state == 'absent':
self.to_do = Actions.Delete
else:
modifiers = {}
self.create_compare_modifiers(self.module_arg_spec, '', modifiers)
self.results['modifiers'] = modifiers
self.results['compare'] = []
if not self.default_compare(modifiers, self.body, old_response, '', self.results):
self.to_do = Actions.Update
if (self.to_do == Actions.Create) or (self.to_do == Actions.Update):
self.log('Need to Create / Update the GalleryImageVersion instance')
if self.check_mode:
self.results['changed'] = True
return self.results
response = self.create_update_resource()
self.results['changed'] = True
self.log('Creation / Update done')
elif self.to_do == Actions.Delete:
self.log('GalleryImageVersion instance deleted')
self.results['changed'] = True
if self.check_mode:
return self.results
self.delete_resource()
else:
self.log('GalleryImageVersion instance unchanged')
self.results['changed'] = False
response = old_response
if response:
self.results["id"] = response["id"]
self.results["old_response"] = response
return self.results
def create_update_resource(self):
# self.log('Creating / Updating the GalleryImageVersion instance {0}'.format(self.))
try:
response = self.mgmt_client.query(self.url,
'PUT',
self.query_parameters,
self.header_parameters,
self.body,
self.status_code,
600,
30)
except CloudError as exc:
self.log('Error attempting to create the GalleryImageVersion instance.')
self.fail('Error creating the GalleryImageVersion instance: {0}'.format(str(exc)))
try:
response = json.loads(response.text)
except Exception:
response = {'text': response.text}
pass
while response['properties']['provisioningState'] == 'Creating':
time.sleep(60)
response = self.get_resource()
return response
def delete_resource(self):
# self.log('Deleting the GalleryImageVersion instance {0}'.format(self.))
try:
response = self.mgmt_client.query(self.url,
'DELETE',
self.query_parameters,
self.header_parameters,
None,
self.status_code,
600,
30)
except CloudError as e:
self.log('Error attempting to delete the GalleryImageVersion instance.')
self.fail('Error deleting the GalleryImageVersion instance: {0}'.format(str(e)))
return True
def get_resource(self):
# self.log('Checking if the GalleryImageVersion instance {0} is present'.format(self.))
found = False
try:
response = self.mgmt_client.query(self.url,
'GET',
self.query_parameters,
self.header_parameters,
None,
self.status_code,
600,
30)
response = json.loads(response.text)
found = True
self.log("Response : {0}".format(response))
# self.log("AzureFirewall instance : {0} found".format(response.name))
except CloudError as e:
self.log('Did not find the AzureFirewall instance.')
if found is True:
return response
return False
def main():
AzureRMGalleryImageVersions()
if __name__ == '__main__':
main()

View file

@ -20,6 +20,7 @@ options:
description:
- Active Directory user password. Use when authenticating with an Active Directory user rather than service
principal.
type: str
profile:
description:
- Security profile found in ~/.azure/credentials file.

View file

@ -0,0 +1,5 @@
cloud/azure
shippable/azure/group4
destructive
azure_rm_galleryimage
azure_rm_galleryimageversion

View file

@ -0,0 +1,2 @@
dependencies:
- setup_azure

View file

@ -0,0 +1,263 @@
- name: Prepare random number
set_fact:
rpfx: "{{ resource_group | hash('md5') | truncate(7, True, '') }}{{ 1000 | random }}"
run_once: yes
- name: Create virtual network
azure_rm_virtualnetwork:
resource_group: "{{ resource_group }}"
name: testVnet
address_prefixes: "10.0.0.0/16"
- name: Add subnet
azure_rm_subnet:
resource_group: "{{ resource_group }}"
name: testSubnet
address_prefix: "10.0.1.0/24"
virtual_network: testVnet
- name: Create public IP address
azure_rm_publicipaddress:
resource_group: "{{ resource_group }}"
allocation_method: Static
name: testPublicIP
- name: Create virtual network inteface cards for VM A and B
azure_rm_networkinterface:
resource_group: "{{ resource_group }}"
name: "vmforimage{{ rpfx }}nic"
virtual_network: testVnet
subnet: testSubnet
- name: Create VM
azure_rm_virtualmachine:
resource_group: "{{ resource_group }}"
name: "vmforimage{{ rpfx }}"
admin_username: testuser
admin_password: "Password1234!"
vm_size: Standard_B1ms
network_interfaces: "vmforimage{{ rpfx }}nic"
image:
offer: UbuntuServer
publisher: Canonical
sku: 16.04-LTS
version: latest
- name: Generalize VM
azure_rm_virtualmachine:
resource_group: "{{ resource_group }}"
name: "vmforimage{{ rpfx }}"
generalized: yes
- name: Create custom image
azure_rm_image:
resource_group: "{{ resource_group }}"
name: testimagea
source: "vmforimage{{ rpfx }}"
- name: Create or update a simple gallery.
azure_rm_gallery:
resource_group: "{{ resource_group }}"
name: myGallery{{ rpfx }}
location: West US
description: This is the gallery description.
register: output
- assert:
that:
- output.changed
- name: Create or update a simple gallery - idempotent
azure_rm_gallery:
resource_group: "{{ resource_group }}"
name: myGallery{{ rpfx }}
location: West US
description: This is the gallery description.
register: output
- assert:
that:
- not output.changed
- name: Create or update a simple gallery - change description
azure_rm_gallery:
resource_group: "{{ resource_group }}"
name: myGallery{{ rpfx }}
location: West US
description: This is the gallery description - xxx.
register: output
- assert:
that:
- output.changed
- name: Create or update gallery image
azure_rm_galleryimage:
resource_group: "{{ resource_group }}"
gallery_name: myGallery{{ rpfx }}
name: myImage
location: West US
os_type: linux
os_state: generalized
identifier:
publisher: myPublisherName
offer: myOfferName
sku: mySkuName
description: Image Description
register: output
- assert:
that:
- output.changed
- name: Create or update gallery image - idempotent
azure_rm_galleryimage:
resource_group: "{{ resource_group }}"
gallery_name: myGallery{{ rpfx }}
name: myImage
location: West US
os_type: linux
os_state: generalized
identifier:
publisher: myPublisherName
offer: myOfferName
sku: mySkuName
description: Image Description
register: output
- assert:
that:
- not output.changed
- name: Create or update gallery image - change description
azure_rm_galleryimage:
resource_group: "{{ resource_group }}"
gallery_name: myGallery{{ rpfx }}
name: myImage
location: West US
os_type: linux
os_state: generalized
identifier:
publisher: myPublisherName
offer: myOfferName
sku: mySkuName
description: Image Description XXXs
register: output
- assert:
that:
- output.changed
- name: Create or update a simple gallery Image Version.
azure_rm_galleryimageversion:
resource_group: "{{ resource_group }}"
gallery_name: myGallery{{ rpfx }}
gallery_image_name: myImage
name: 10.1.3
location: West US
publishing_profile:
end_of_life_date: "2020-10-01t00:00:00+00:00"
exclude_from_latest: yes
replica_count: 3
storage_account_type: Standard_LRS
target_regions:
- name: West US
regional_replica_count: 1
- name: East US
regional_replica_count: 2
storage_account_type: Standard_ZRS
managed_image:
name: testimagea
resource_group: "{{ resource_group }}"
register: output
- assert:
that:
- output.changed
- name: Create or update a simple gallery Image Version - idempotent
azure_rm_galleryimageversion:
resource_group: "{{ resource_group }}"
gallery_name: myGallery{{ rpfx }}
gallery_image_name: myImage
name: 10.1.3
location: West US
publishing_profile:
end_of_life_date: "2020-10-01t00:00:00+00:00"
exclude_from_latest: yes
replica_count: 3
storage_account_type: Standard_LRS
target_regions:
- name: West US
regional_replica_count: 1
- name: East US
regional_replica_count: 2
storage_account_type: Standard_ZRS
managed_image:
name: testimagea
resource_group: "{{ resource_group }}"
register: output
- assert:
that:
- not output.changed
- name: Create or update a simple gallery Image Version - change end of life
azure_rm_galleryimageversion:
resource_group: "{{ resource_group }}"
gallery_name: myGallery{{ rpfx }}
gallery_image_name: myImage
name: 10.1.3
location: West US
publishing_profile:
end_of_life_date: "2021-10-01t00:00:00+00:00"
exclude_from_latest: yes
replica_count: 3
storage_account_type: Standard_LRS
target_regions:
- name: West US
regional_replica_count: 1
- name: East US
regional_replica_count: 2
storage_account_type: Standard_ZRS
managed_image:
name: testimagea
resource_group: "{{ resource_group }}"
register: output
- assert:
that:
- output.changed
- name: Delete gallery image Version.
azure_rm_galleryimageversion:
resource_group: "{{ resource_group }}"
gallery_name: myGallery{{ rpfx }}
gallery_image_name: myImage
name: 10.1.3
state: absent
register: output
- assert:
that:
- output.changed
- name: Delete gallery image
azure_rm_galleryimage:
resource_group: "{{ resource_group }}"
gallery_name: myGallery{{ rpfx }}
name: myImage
state: absent
register: output
- assert:
that:
- output.changed
- name: Delete gallery
azure_rm_gallery:
resource_group: "{{ resource_group }}"
name: myGallery{{ rpfx }}
state: absent
register: output
- assert:
that:
- output.changed

View file

@ -442,7 +442,6 @@ lib/ansible/modules/cloud/azure/azure_rm_loadbalancer.py E337
lib/ansible/modules/cloud/azure/azure_rm_loganalyticsworkspace_facts.py E337
lib/ansible/modules/cloud/azure/azure_rm_loganalyticsworkspace.py E337
lib/ansible/modules/cloud/azure/azure_rm_manageddisk_facts.py E325
lib/ansible/modules/cloud/azure/azure_rm_manageddisk_facts.py E337
lib/ansible/modules/cloud/azure/azure_rm_manageddisk.py E337
lib/ansible/modules/cloud/azure/azure_rm_mariadbconfiguration_facts.py E337
lib/ansible/modules/cloud/azure/azure_rm_mariadbconfiguration.py E337