Skip to content

Commit

Permalink
Configure static website on storage account (#878)
Browse files Browse the repository at this point in the history
* remove unused storage imports

* correct test lints

* update azure-storage dependency

* fix storage blob test lint issues

* clean up storage test

* correct blob issues

* correct file upload with new APIs

* update storageblob for ansible-test

* allow configuration of static website on storage account

* add static website output to storage info module

* handle blob client access for non-blob account types

* ensure static website props always returned

* additional static website tests

* bump test storage name to reduce pr conflicts
  • Loading branch information
l3ender authored Jun 14, 2022
1 parent 58a7f87 commit a923c97
Show file tree
Hide file tree
Showing 3 changed files with 324 additions and 25 deletions.
107 changes: 96 additions & 11 deletions plugins/modules/azure_rm_storageaccount.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand All @@ -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):
Expand Down Expand Up @@ -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(
Expand All @@ -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)
Expand Down Expand Up @@ -540,29 +592,32 @@ 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)))

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,
Expand All @@ -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:
Expand All @@ -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:
Expand Down Expand Up @@ -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))

Expand All @@ -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:
Expand All @@ -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 \
Expand All @@ -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):
Expand All @@ -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:
Expand All @@ -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)
Expand Down
62 changes: 54 additions & 8 deletions plugins/modules/azure_rm_storageaccount_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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,
Expand All @@ -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)
Expand Down Expand Up @@ -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'):
Expand All @@ -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
Expand Down
Loading

0 comments on commit a923c97

Please sign in to comment.