diff --git a/plugins/modules/azure_rm_storageaccount.py b/plugins/modules/azure_rm_storageaccount.py index bbfa05cba..2e2406869 100644 --- a/plugins/modules/azure_rm_storageaccount.py +++ b/plugins/modules/azure_rm_storageaccount.py @@ -184,6 +184,25 @@ type: list elements: str required: true + static_website: + description: + - Manage static website configuration for the storage account. + type: dict + version_added: "1.13.0" + suboptions: + enabled: + description: + - Indicates whether this account is hosting a static website. + type: bool + default: false + index_document: + description: + - The default name of the index page under each directory. + type: str + error_document404_path: + description: + - The absolute path of the custom 404 page. + type: str extends_documentation_fragment: - azure.azcollection.azure @@ -401,6 +420,31 @@ returned: always type: str sample: "Microsoft.Storage/storageAccounts" + static_website: + description: + - Static website configuration for the storage account. + returned: always + version_added: "1.13.0" + type: complex + contains: + enabled: + description: + - Whether this account is hosting a static website. + returned: always + type: bool + sample: true + index_document: + description: + - The default name of the index page under each directory. + returned: always + type: str + sample: index.html + error_document404_path: + description: + - The absolute path of the custom 404 page. + returned: always + type: str + sample: error.html ''' try: @@ -422,6 +466,12 @@ allowed_headers=dict(type='list', elements='str', required=True), ) +static_website_spec = dict( + enabled=dict(type='bool', default=False), + index_document=dict(type='str'), + error_document404_path=dict(type='str'), +) + def compare_cors(cors1, cors2): if len(cors1) != len(cors2): @@ -463,7 +513,8 @@ def __init__(self): minimum_tls_version=dict(type='str', choices=['TLS1_0', 'TLS1_1', 'TLS1_2']), allow_blob_public_access=dict(type='bool'), network_acls=dict(type='dict'), - blob_cors=dict(type='list', options=cors_rule_spec, elements='dict') + blob_cors=dict(type='list', options=cors_rule_spec, elements='dict'), + static_website=dict(type='dict', options=static_website_spec), ) self.results = dict( @@ -487,6 +538,7 @@ def __init__(self): self.allow_blob_public_access = None self.network_acls = None self.blob_cors = None + self.static_website = None super(AzureRMStorageAccount, self).__init__(self.module_arg_spec, supports_check_mode=True) @@ -540,7 +592,7 @@ def check_name_availability(self): self.log('Checking name availability for {0}'.format(self.name)) try: account_name = self.storage_models.StorageAccountCheckNameAvailabilityParameters(name=self.name) - response = self.storage_client.storage_accounts.check_name_availability(account_name) + self.storage_client.storage_accounts.check_name_availability(account_name) except Exception as e: self.log('Error attempting to validate name.') self.fail("Error checking name availability: {0}".format(str(e))) @@ -548,21 +600,24 @@ def check_name_availability(self): def get_account(self): self.log('Get properties for account {0}'.format(self.name)) account_obj = None - blob_service_props = None + blob_mgmt_props = None + blob_client_props = None account_dict = None try: account_obj = self.storage_client.storage_accounts.get_properties(self.resource_group, self.name) - blob_service_props = self.storage_client.blob_services.get_service_properties(self.resource_group, self.name) + blob_mgmt_props = self.storage_client.blob_services.get_service_properties(self.resource_group, self.name) + if self.kind != "FileStorage": + blob_client_props = self.get_blob_service_client(self.resource_group, self.name).get_service_properties() except Exception: pass if account_obj: - account_dict = self.account_obj_to_dict(account_obj, blob_service_props) + account_dict = self.account_obj_to_dict(account_obj, blob_mgmt_props, blob_client_props) return account_dict - def account_obj_to_dict(self, account_obj, blob_service_props=None): + def account_obj_to_dict(self, account_obj, blob_mgmt_props=None, blob_client_props=None): account_dict = dict( id=account_obj.id, name=account_obj.name, @@ -580,7 +635,12 @@ def account_obj_to_dict(self, account_obj, blob_service_props=None): https_only=account_obj.enable_https_traffic_only, minimum_tls_version=account_obj.minimum_tls_version, allow_blob_public_access=account_obj.allow_blob_public_access, - network_acls=account_obj.network_rule_set + network_acls=account_obj.network_rule_set, + static_website=dict( + enabled=False, + index_document=None, + error_document404_path=None, + ), ) account_dict['custom_domain'] = None if account_obj.custom_domain: @@ -606,14 +666,22 @@ def account_obj_to_dict(self, account_obj, blob_service_props=None): account_dict['tags'] = None if account_obj.tags: account_dict['tags'] = account_obj.tags - if blob_service_props and blob_service_props.cors and blob_service_props.cors.cors_rules: + if blob_mgmt_props and blob_mgmt_props.cors and blob_mgmt_props.cors.cors_rules: account_dict['blob_cors'] = [dict( allowed_origins=[to_native(y) for y in x.allowed_origins], allowed_methods=[to_native(y) for y in x.allowed_methods], max_age_in_seconds=x.max_age_in_seconds, exposed_headers=[to_native(y) for y in x.exposed_headers], allowed_headers=[to_native(y) for y in x.allowed_headers] - ) for x in blob_service_props.cors.cors_rules] + ) for x in blob_mgmt_props.cors.cors_rules] + + if blob_client_props and blob_client_props['static_website']: + static_website = blob_client_props['static_website'] + account_dict['static_website'] = dict( + enabled=static_website.enabled, + index_document=static_website.index_document, + error_document404_path=static_website.error_document404_path, + ) account_dict['network_acls'] = None if account_obj.network_rule_set: @@ -785,6 +853,11 @@ def update_account(self): if not self.check_mode: self.set_blob_cors() + if self.static_website and self.static_website != self.account_dict.get("static_website", dict()): + self.results['changed'] = True + self.account_dict['static_website'] = self.static_website + self.update_static_website() + def create_account(self): self.log("Creating account {0}".format(self.name)) @@ -809,7 +882,6 @@ def create_account(self): enable_https_traffic_only=self.https_only, minimum_tls_version=self.minimum_tls_version, allow_blob_public_access=self.allow_blob_public_access, - networks_acls=dict(), tags=dict() ) if self.tags: @@ -818,6 +890,8 @@ def create_account(self): account_dict['network_acls'] = self.network_acls if self.blob_cors: account_dict['blob_cors'] = self.blob_cors + if self.static_website: + account_dict['static_website'] = self.static_website return account_dict sku = self.storage_models.Sku(name=self.storage_models.SkuName(self.account_type)) sku.tier = self.storage_models.SkuTier.standard if 'Standard' in self.account_type else \ @@ -842,7 +916,8 @@ def create_account(self): self.set_network_acls() if self.blob_cors: self.set_blob_cors() - # the poller doesn't actually return anything + if self.static_website: + self.update_static_website() return self.get_account() def delete_account(self): @@ -866,6 +941,8 @@ def account_has_blob_containers(self): If there are blob containers, then there are likely VMs depending on this account and it should not be deleted. ''' + if self.kind == "FileStorage": + return False self.log('Checking for existing blob containers') blob_service = self.get_blob_service_client(self.resource_group, self.name) try: @@ -887,6 +964,14 @@ def set_blob_cors(self): except Exception as exc: self.fail("Failed to set CORS rules: {0}".format(str(exc))) + def update_static_website(self): + if self.kind == "FileStorage": + return + try: + self.get_blob_service_client(self.resource_group, self.name).set_service_properties(static_website=self.static_website) + except Exception as exc: + self.fail("Failed to set static website config: {0}".format(str(exc))) + def set_network_acls(self): try: parameters = self.storage_models.StorageAccountUpdateParameters(network_rule_set=self.network_acls) diff --git a/plugins/modules/azure_rm_storageaccount_info.py b/plugins/modules/azure_rm_storageaccount_info.py index 0ffeafc72..7584b0264 100644 --- a/plugins/modules/azure_rm_storageaccount_info.py +++ b/plugins/modules/azure_rm_storageaccount_info.py @@ -400,6 +400,31 @@ returned: always type: dict sample: { "tag1": "abc" } + static_website: + description: + - Static website configuration for the storage account. + returned: always + version_added: "1.13.0" + type: complex + contains: + enabled: + description: + - Whether this account is hosting a static website. + returned: always + type: bool + sample: true + index_document: + description: + - The default name of the index page under each directory. + returned: always + type: str + sample: index.html + error_document404_path: + description: + - The absolute path of the custom 404 page. + returned: always + type: str + sample: error.html ''' try: @@ -508,7 +533,7 @@ def serialize(self, raw): def format_to_dict(self, raw): return [self.account_obj_to_dict(item) for item in raw] - def account_obj_to_dict(self, account_obj, blob_service_props=None): + def account_obj_to_dict(self, account_obj): account_dict = dict( id=account_obj.id, name=account_obj.name, @@ -526,7 +551,12 @@ def account_obj_to_dict(self, account_obj, blob_service_props=None): primary_location=account_obj.primary_location, https_only=account_obj.enable_https_traffic_only, minimum_tls_version=account_obj.minimum_tls_version, - allow_blob_public_access=account_obj.allow_blob_public_access + allow_blob_public_access=account_obj.allow_blob_public_access, + static_website=dict( + enabled=False, + index_document=None, + error_document404_path=None, + ), ) id_dict = self.parse_resource_to_dict(account_obj.id) @@ -579,15 +609,23 @@ def account_obj_to_dict(self, account_obj, blob_service_props=None): account_dict['tags'] = None if account_obj.tags: account_dict['tags'] = account_obj.tags - blob_service_props = self.get_blob_service_props(account_dict['resource_group'], account_dict['name']) - if blob_service_props and blob_service_props.cors and blob_service_props.cors.cors_rules: + blob_mgmt_props = self.get_blob_mgmt_props(account_dict['resource_group'], account_dict['name']) + if blob_mgmt_props and blob_mgmt_props.cors and blob_mgmt_props.cors.cors_rules: account_dict['blob_cors'] = [dict( allowed_origins=to_native(x.allowed_origins), allowed_methods=to_native(x.allowed_methods), max_age_in_seconds=x.max_age_in_seconds, exposed_headers=to_native(x.exposed_headers), allowed_headers=to_native(x.allowed_headers) - ) for x in blob_service_props.cors.cors_rules] + ) for x in blob_mgmt_props.cors.cors_rules] + blob_client_props = self.get_blob_client_props(account_dict['resource_group'], account_dict['name'], account_dict['kind']) + if blob_client_props and blob_client_props['static_website']: + static_website = blob_client_props['static_website'] + account_dict['static_website'] = dict( + enabled=static_website.enabled, + index_document=static_website.index_document, + error_document404_path=static_website.error_document404_path, + ) return account_dict def format_endpoint_dict(self, name, key, endpoint, storagetype, protocol='https'): @@ -602,12 +640,20 @@ def format_endpoint_dict(self, name, key, endpoint, storagetype, protocol='https endpoint) return result - def get_blob_service_props(self, resource_group, name): + def get_blob_mgmt_props(self, resource_group, name): if not self.show_blob_cors: return None try: - blob_service_props = self.storage_client.blob_services.get_service_properties(resource_group, name) - return blob_service_props + return self.storage_client.blob_services.get_service_properties(resource_group, name) + except Exception: + pass + return None + + def get_blob_client_props(self, resource_group, name, kind): + if kind == "FileStorage": + return None + try: + return self.get_blob_service_client(resource_group, name).get_service_properties() except Exception: pass return None diff --git a/tests/integration/targets/azure_rm_storageaccount/tasks/main.yml b/tests/integration/targets/azure_rm_storageaccount/tasks/main.yml index 8a33a7c6f..bb1002009 100644 --- a/tests/integration/targets/azure_rm_storageaccount/tasks/main.yml +++ b/tests/integration/targets/azure_rm_storageaccount/tasks/main.yml @@ -11,8 +11,8 @@ register: output ignore_errors: true - name: Check intentional name failure. - assert: - that: + assert: + that: - output.failed - output.msg is regex('AccountNameInvalid') @@ -25,7 +25,9 @@ loop: - "{{ storage_account_name_default }}" - "{{ storage_account_name_explicit }}" + - "{{ storage_account_name_default }}01" - "{{ storage_account_name_default }}02" + - "{{ storage_account_name_default }}04" - name: Create new storage account with defaults (omitted parameters) azure_rm_storageaccount: @@ -44,6 +46,144 @@ - defaults_output.state.allow_blob_public_access == true - defaults_output.state.minimum_tls_version == "TLS1_0" +- name: Create storage account with static website disabled + azure_rm_storageaccount: + resource_group: "{{ resource_group }}" + name: "{{ storage_account_name_default }}01" + account_type: Standard_LRS + kind: StorageV2 + static_website: + enabled: false + register: output +- name: Assert output + assert: + that: + - output.changed + - output.state.static_website is defined + - not output.state.static_website.enabled + - output.state.static_website.index_document == None + - output.state.static_website.error_document404_path == None + +- name: Create storage account with static website disabled (idempotency test) + azure_rm_storageaccount: + resource_group: "{{ resource_group }}" + name: "{{ storage_account_name_default }}01" + account_type: Standard_LRS + kind: StorageV2 + static_website: + enabled: false + register: output +- name: Assert not changed + assert: + that: + - not output.changed + +- name: Enable storage account static website + azure_rm_storageaccount: + resource_group: "{{ resource_group }}" + name: "{{ storage_account_name_default }}01" + account_type: Standard_LRS + kind: StorageV2 + static_website: + enabled: true + register: output +- name: Assert output + assert: + that: + - output.changed + - output.state.static_website is defined + - output.state.static_website.enabled + - output.state.static_website.index_document == None + - output.state.static_website.error_document404_path == None + +- name: Configure additional storage account static website properties + azure_rm_storageaccount: + resource_group: "{{ resource_group }}" + name: "{{ storage_account_name_default }}01" + account_type: Standard_LRS + kind: StorageV2 + static_website: + enabled: true + index_document: "index.html" + error_document404_path: "error.html" + register: output +- name: Assert output + assert: + that: + - output.changed + - output.state.static_website is defined + - output.state.static_website.enabled + - output.state.static_website.index_document == 'index.html' + - output.state.static_website.error_document404_path == 'error.html' + +- name: Configure additional storage account static website properties (idempotency test) + azure_rm_storageaccount: + resource_group: "{{ resource_group }}" + name: "{{ storage_account_name_default }}01" + account_type: Standard_LRS + kind: StorageV2 + static_website: + enabled: true + index_document: "index.html" + error_document404_path: "error.html" + register: output +- name: Assert not changed + assert: + that: + - not output.changed + +- name: Create storage account with static website enabled + azure_rm_storageaccount: + resource_group: "{{ resource_group }}" + name: "{{ storage_account_name_default }}04" + account_type: Standard_LRS + kind: StorageV2 + static_website: + enabled: true + index_document: "abc.htm" + register: output +- name: Assert output + assert: + that: + - output.changed + - output.state.static_website is defined + - output.state.static_website.enabled + - output.state.static_website.index_document == "abc.htm" + - output.state.static_website.error_document404_path == None + +- name: Create storage account with static website enabled (idempotency test) + azure_rm_storageaccount: + resource_group: "{{ resource_group }}" + name: "{{ storage_account_name_default }}04" + account_type: Standard_LRS + kind: StorageV2 + static_website: + enabled: true + index_document: "abc.htm" + register: output +- name: Assert not changed + assert: + that: + - not output.changed + +- name: Disable storage account static website + azure_rm_storageaccount: + resource_group: "{{ resource_group }}" + name: "{{ storage_account_name_default }}04" + account_type: Standard_LRS + kind: StorageV2 + static_website: + enabled: false + register: output +- name: Assert output + assert: + that: + - output.changed + - output.state.static_website is defined + - not output.state.static_website.enabled + - output.state.static_website.index_document == None + - output.state.static_website.error_document404_path == None + - name: Create new storage account with I(kind=FileStorage) azure_rm_storageaccount: resource_group: "{{ resource_group }}" @@ -259,8 +399,8 @@ ignore_errors: true register: output - name: Assert CNAME failure - assert: - that: + assert: + that: - output.failed - output.msg is regex('custom domain name could not be verified') @@ -296,7 +436,6 @@ show_connection_string: True show_blob_cors: True register: output - - assert: that: - "output.storageaccounts | length == 1" @@ -310,6 +449,33 @@ - output.storageaccounts[0].network_acls.bypass == "AzureServices" - output.storageaccounts[0].network_acls.default_action == "Deny" - output.storageaccounts[0].network_acls.ip_rules | length == 1 + +- name: Gather enabled static website properties + azure_rm_storageaccount_info: + resource_group: "{{ resource_group }}" + name: "{{ storage_account_name_default }}01" + register: output +- assert: + that: + - output.storageaccounts | length == 1 + - output.storageaccounts[0].static_website is defined + - output.storageaccounts[0].static_website.enabled + - output.storageaccounts[0].static_website.index_document == 'index.html' + - output.storageaccounts[0].static_website.error_document404_path == 'error.html' + +- name: Gather disabled static website properties + azure_rm_storageaccount_info: + resource_group: "{{ resource_group }}" + name: "{{ storage_account_name_default }}04" + register: output +- assert: + that: + - output.storageaccounts | length == 1 + - output.storageaccounts[0].static_website is defined + - not output.storageaccounts[0].static_website.enabled + - output.storageaccounts[0].static_website.index_document == None + - output.storageaccounts[0].static_website.error_document404_path == None + - name: List storage accounts by resource group. azure_rm_storageaccount_info: resource_group: "{{ resource_group }}" @@ -326,5 +492,7 @@ force_delete_nonempty: True loop: - "{{ storage_account_name_default }}" - - "{{ storage_account_name_default }}02" - "{{ storage_account_name_explicit }}" + - "{{ storage_account_name_default }}01" + - "{{ storage_account_name_default }}02" + - "{{ storage_account_name_default }}04"