From 3a6fc3b6286613080ad83f574d5a2b220af666d7 Mon Sep 17 00:00:00 2001 From: jartoo Date: Mon, 10 Jun 2024 08:37:53 -0500 Subject: [PATCH 1/3] Add new module azure_rm_afdruleset to support Azure Frontdoor Standard and Premium --- plugins/modules/azure_rm_afdruleset.py | 257 ++++++++++++++++++ plugins/modules/azure_rm_afdruleset_info.py | 226 +++++++++++++++ .../targets/azure_rm_afdruleset/aliases | 3 + .../targets/azure_rm_afdruleset/meta/main.yml | 2 + .../azure_rm_afdruleset/tasks/main.yml | 126 +++++++++ 5 files changed, 614 insertions(+) create mode 100644 plugins/modules/azure_rm_afdruleset.py create mode 100644 plugins/modules/azure_rm_afdruleset_info.py create mode 100644 tests/integration/targets/azure_rm_afdruleset/aliases create mode 100644 tests/integration/targets/azure_rm_afdruleset/meta/main.yml create mode 100644 tests/integration/targets/azure_rm_afdruleset/tasks/main.yml diff --git a/plugins/modules/azure_rm_afdruleset.py b/plugins/modules/azure_rm_afdruleset.py new file mode 100644 index 000000000..f68545358 --- /dev/null +++ b/plugins/modules/azure_rm_afdruleset.py @@ -0,0 +1,257 @@ +#!/usr/bin/python +# +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# +# Python SDK Reference: https://learn.microsoft.com/en-us/python/api/azure-mgmt-cdn/azure.mgmt.cdn.operations.rulesetsoperations?view=azure-python + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: azure_rm_afdruleset +version_added: "0.1.0" +short_description: Manage an Azure Front Door Rule Set +description: + - Create, update and delete an Azure Front Door Rule Set to be used by a Front Door Service Profile created using azure_rm_cdnprofile. + +options: + name: + description: + - Name of the Front Door Rule Set. + required: true + type: str + profile_name: + description: + - Name of the Front Door Profile. + required: true + type: str + resource_group: + description: + - Name of a resource group where the CDN front door ruleset exists or will be created. + required: true + type: str + state: + description: + - Assert the state of the CDN profile. Use C(present) to create or update a CDN profile and C(absent) to delete it. + default: present + type: str + choices: + - absent + - present + +extends_documentation_fragment: + - azure.azcollection.azure + +author: + - Jarret Tooley (@jartoo) +''' + +EXAMPLES = ''' +- name: Create a Ruleset + azure_rm_afdruleset: + name: myRuleset + profile_name: myProfile + resource_group: myResourceGroup + state: present + +- name: Delete a Ruleset + azure_rm_afdruleset: + name: myRuleset + profile_name: myProfile + resource_group: myResourceGroup + state: absent +''' +RETURN = ''' +id: + description: Resource ID + returned: always + type: str + example: "/subscriptions/xxxxxx-xxxx-xxxx-xxxx-xxxxxxx/resourcegroups/myRG/providers/Microsoft.Cdn/profiles/myProf/rulesets/myRS" +''' +from ansible_collections.azure.azcollection.plugins.module_utils.azure_rm_common import AzureRMModuleBase + +try: + # from azure.mgmt.cdn.models import RuleSet + from azure.mgmt.cdn import CdnManagementClient +except ImportError as ec: + # This is handled in azure_rm_common + pass + + +class AzureRMRuleSet(AzureRMModuleBase): + """Class representing a Ruleset for Azure Frontdoor Standard or Premium""" + + def __init__(self): + self.module_arg_spec = dict( + name=dict( + type='str', + required=True + ), + profile_name=dict( + type='str', + required=True + ), + resource_group=dict( + type='str', + required=True + ), + state=dict( + type='str', + default='present', + choices=['present', 'absent'], + required=False + ) + ) + + self.name = None + self.profile_name = None + self.resource_group = None + self.state = None + + self.ruleset_client = None + + self.results = dict(changed=False) + + super(AzureRMRuleSet, self).__init__( + derived_arg_spec=self.module_arg_spec, + supports_check_mode=True, + supports_tags=False) + + def exec_module(self, **kwargs): + """Main module execution method""" + + for key in list(self.module_arg_spec.keys()): + setattr(self, key, kwargs[key]) + + self.ruleset_client = self.get_ruleset_client() + + response = self.get_ruleset() + + self.results['changed'] = False + self.results['id'] = None + + if self.state == 'present': + + if not response: + self.log("Need to create the Rule Set") + if not self.check_mode: + new_response = self.create_ruleset() + self.results['id'] = new_response['id'] + + self.results['changed'] = True + + else: + self.results['id'] = response['id'] + self.log('Results : {0}'.format(response)) + + elif self.state == 'absent': + if not response: + self.log("Rule Set {0} does not exist.".format(self.name)) + else: + self.log("Need to delete the Rule Set") + self.results['changed'] = True + + self.results['id'] = response['id'] + if not self.check_mode: + self.delete_ruleset() + + return self.results + + def create_ruleset(self): + ''' + Creates an Azure Rule Set. + + :return: deserialized Azure Rule Set instance state dictionary + ''' + self.log("Creating the Azure Rule Set instance {0}".format(self.name)) + + try: + response = self.ruleset_client.rule_sets.create( + resource_group_name=self.resource_group, + profile_name=self.profile_name, + rule_set_name=self.name + ) + return ruleset_to_dict(response) + except AttributeError as exc: + self.fail("Please ensure azure-mgmt-cdn is upgraded to version 13.1.0 or greater: {0}".format(str(exc))) + except Exception as exc: + self.log('Error attempting to create Azure Rule Set instance.') + self.fail("Error Creating Azure Rule Set instance: {0}".format(str(exc))) + + def delete_ruleset(self): + ''' + Deletes the specified Azure Rule Set in the specified subscription and resource group. + + :return: bool + ''' + self.log("Deleting the Rule Set {0}".format(self.name)) + try: + poller = self.ruleset_client.rule_sets.begin_delete( + resource_group_name=self.resource_group, + profile_name=self.profile_name, + rule_set_name=self.name) + self.get_poller_result(poller) + return True + except Exception as exc: + self.log('Error attempting to delete the Rule Set.') + self.fail("Error deleting the Rule Set: {0}".format(str(exc))) + return False + + def get_ruleset(self): + ''' + Gets the properties of the specified Rule Set. + + :return: deserialized Rule Set state dictionary + ''' + self.log( + "Checking if the Rule Set {0} is present".format(self.name)) + try: + response = self.ruleset_client.rule_sets.get( + resource_group_name=self.resource_group, + profile_name=self.profile_name, + rule_set_name=self.name, + ) + self.log("Response : {0}".format(response)) + self.log("Rule Set : {0} found".format(response.name)) + return ruleset_to_dict(response) + except Exception as err: + self.log('Did not find the Rule Set.' + err.args[0]) + return False + + def get_ruleset_client(self): + ''' + Gets the client object to use to manage the Rule Sets + + :return: management client object + ''' + if not self.ruleset_client: + self.ruleset_client = self.get_mgmt_svc_client( + CdnManagementClient, + base_url=self._cloud_environment.endpoints.resource_manager, + api_version='2023-05-01') + return self.ruleset_client + + +def ruleset_to_dict(ruleset): + ''' + Sets the properties of the specified Rule Set. + + :return: deserialized Rule Set state dictionary + ''' + return dict( + deployment_status=ruleset.deployment_status, + id=ruleset.id, + name=ruleset.name, + provisioning_state=ruleset.provisioning_state, + type=ruleset.type + ) + + +def main(): + """Main execution""" + AzureRMRuleSet() + + +if __name__ == '__main__': + main() diff --git a/plugins/modules/azure_rm_afdruleset_info.py b/plugins/modules/azure_rm_afdruleset_info.py new file mode 100644 index 000000000..8732ddd7c --- /dev/null +++ b/plugins/modules/azure_rm_afdruleset_info.py @@ -0,0 +1,226 @@ +#!/usr/bin/python +# +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# +# Python SDK Reference: https://learn.microsoft.com/en-us/python/api/azure-mgmt-cdn/azure.mgmt.cdn.operations.afdrulesetsoperations?view=azure-python +# + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: azure_rm_afdruleset_info +version_added: "2.4.0" +short_description: Get Azure Front Door Ruleset facts to be used with Standard or Premium Frontdoor Service +description: + - Get facts for a specific Azure Front Door (AFD) Ruleset or all AFD Rulesets. + - This differs from the Front Door classic service and only is intended to be used by the Standard or Premium service offering. + +options: + name: + description: + - Limit results to a specific AFD Ruleset. + type: str + profile_name: + description: + - Name of the Azure Front Door Standard or Azure Front Door Premium profile which is unique within the resource group. + required: true + type: str + resource_group: + description: + - Name of the Resource group within the Azure subscription. + required: true + type: str + +extends_documentation_fragment: + - azure.azcollection.azure + +author: + - Jarret Tooley (@jartoo) +''' + +EXAMPLES = ''' +- name: Get facts for all Rulesets in AFD Profile + azure_rm_afdruleset_info: + resource_group: myResourceGroup + profile_name: myProfile + +- name: Get facts of specific AFD Ruleset + azure_rm_afdruleset_info: + name: myRuleset + profile_name: myProfile + resource_group: myResourceGroup +''' + +RETURN = ''' +afdrulesets: + description: List of AFD Rulesets. + returned: always + type: complex + contains: + deployment_status: + description: + - Current state of the resource. + type: str + sample: NotStarted + id: + description: + - ID of the AFD Ruleset. + type: str + sample: "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourcegroups/myRG/providers/Microsoft.Cdn/profiles/myProf/rulesets/myRS" + name: + description: + - Name of the AFD Ruleset. + type: str + profile_name: + description: + - The name of the profile which holds the rule set. + type: str + provisioning_state: + description: + - Provisioning status. + type: str + sample: Succeeded + resource_group: + description: + - Name of a resource group. + type: str + type: + description: + - Resource type. + type: str + sample: Microsoft.Cdn/profiles/afdrulesets +''' + +import re +from ansible_collections.azure.azcollection.plugins.module_utils.azure_rm_common import AzureRMModuleBase + +try: + from azure.mgmt.cdn import CdnManagementClient +except ImportError: + # handled in azure_rm_common + pass + + +AZURE_OBJECT_CLASS = 'Ruleset' + + +class AzureRMAFDRulesetInfo(AzureRMModuleBase): + """Utility class to get Azure AFD Ruleset facts""" + + def __init__(self): + + self.module_args = dict( + name=dict( + type='str' + ), + resource_group=dict( + type='str', + required=True + ), + profile_name=dict( + type='str', + required=True + ) + ) + + self.results = dict( + changed=False, + afdrulesets=[] + ) + + self.name = None + self.profile_name = None + self.resource_group = None + + super(AzureRMAFDRulesetInfo, self).__init__( + supports_check_mode=True, + derived_arg_spec=self.module_args, + supports_tags=False, + facts_module=True + ) + + def exec_module(self, **kwargs): + + for key in self.module_args: + setattr(self, key, kwargs[key]) + + self.endpoint_client = self.get_mgmt_svc_client( + CdnManagementClient, + base_url=self._cloud_environment.endpoints.resource_manager, + api_version='2023-05-01') + + if self.name: + self.results['afdrulesets'] = self.get_item() + else: + self.results['afdrulesets'] = self.list_by_profile() + + return self.results + + def get_item(self): + """Get a single Azure AFD Ruleset""" + + self.log('Get properties for {0}'.format(self.name)) + + item = None + result = [] + + try: + item = self.endpoint_client.rule_sets.get( + resource_group_name=self.resource_group, + profile_name=self.profile_name, + rule_set_name=self.name) + except Exception: + pass + + if item: + result = [self.serialize_afdruleset(item)] + + return result + + def list_by_profile(self): + """Get all Azure AFD Rulesets within an AFD profile""" + + self.log('List all AFD Rulesets within an AFD profile') + + try: + response = self.endpoint_client.rule_sets.list_by_profile( + resource_group_name=self.resource_group, + profile_name=self.profile_name) + except Exception as exc: + self.fail('Failed to list all items - {0}'.format(str(exc))) + + results = [] + for item in response: + results.append(self.serialize_afdruleset(item)) + + return results + + def serialize_afdruleset(self, afdruleset): + ''' + Convert a AFD Ruleset object to dict. + :param afdruleset: AFD Ruleset object + :return: dict + ''' + result = self.serialize_obj(afdruleset, AZURE_OBJECT_CLASS) + + new_result = {} + new_result['deployment_status'] = afdruleset.deployment_status + new_result['id'] = afdruleset.id + new_result['name'] = afdruleset.name + new_result['profile_name'] = re.sub('\\/.*', '', re.sub('.*profiles\\/', '', result['id'])) + new_result['provisioning_state'] = afdruleset.provisioning_state + new_result['resource_group'] = re.sub('\\/.*', '', re.sub('.*resourcegroups\\/', '', result['id'])) + new_result['type'] = afdruleset.type + return new_result + + +def main(): + """Main module execution code path""" + AzureRMAFDRulesetInfo() + + +if __name__ == '__main__': + main() diff --git a/tests/integration/targets/azure_rm_afdruleset/aliases b/tests/integration/targets/azure_rm_afdruleset/aliases new file mode 100644 index 000000000..3b050cbc1 --- /dev/null +++ b/tests/integration/targets/azure_rm_afdruleset/aliases @@ -0,0 +1,3 @@ +cloud/azure +destructive +shippable/azure/group6 diff --git a/tests/integration/targets/azure_rm_afdruleset/meta/main.yml b/tests/integration/targets/azure_rm_afdruleset/meta/main.yml new file mode 100644 index 000000000..95e1952f9 --- /dev/null +++ b/tests/integration/targets/azure_rm_afdruleset/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - setup_azure diff --git a/tests/integration/targets/azure_rm_afdruleset/tasks/main.yml b/tests/integration/targets/azure_rm_afdruleset/tasks/main.yml new file mode 100644 index 000000000..e985c6259 --- /dev/null +++ b/tests/integration/targets/azure_rm_afdruleset/tasks/main.yml @@ -0,0 +1,126 @@ +- name: Prepare random name for Profile & Ruleset + ansible.builtin.set_fact: + profile: "prof-{{ resource_group | hash('md5') | truncate(4, True, '') }}{{ 100000 | random }}" + ruleset: "ruleset{{ resource_group | hash('md5') | truncate(4, True, '') }}{{ 100000 | random }}" + ruleset2: "ruleset2{{ resource_group | hash('md5') | truncate(4, True, '') }}{{ 100000 | random }}" + +- name: Create Standard Frontdoor Profile + azure_rm_cdnprofile: + name: "{{ profile }}" + location: "Global" + resource_group: "{{ resource_group }}" + sku: "standard_azurefrontdoor" + state: "present" + +- name: Create Ruleset + azure_rm_afdruleset: + name: "{{ ruleset }}" + profile_name: "{{ profile }}" + resource_group: "{{ resource_group }}" + state: "present" + register: output + +- name: Assert the resource was created + ansible.builtin.assert: + that: + - output.changed + - output.id + +- name: Update Ruleset (idempotent test) + azure_rm_afdruleset: + name: "{{ ruleset }}" + profile_name: "{{ profile }}" + resource_group: "{{ resource_group }}" + state: "present" + register: output + +- name: Assert the resource was not changed + ansible.builtin.assert: + that: + - not output.changed + +- name: Create Ruleset 2 + azure_rm_afdruleset: + name: "{{ ruleset2 }}" + profile_name: "{{ profile }}" + resource_group: "{{ resource_group }}" + state: "present" + register: output + +- name: Assert the resource was created + ansible.builtin.assert: + that: + - output.changed + - output.id + +- name: Load info for all Rulesets + azure_rm_afdruleset_info: + profile_name: "{{ profile }}" + resource_group: "{{ resource_group }}" + register: output + +- name: Assert there are two resources + ansible.builtin.assert: + that: + - output.afdrulesets | length == 2 + - not output.changed + +- name: Delete Origin Group 2 + azure_rm_afdruleset: + name: "{{ ruleset2 }}" + profile_name: "{{ profile }}" + resource_group: "{{ resource_group }}" + state: "absent" + register: output + +- name: Assert the resource was deleted + ansible.builtin.assert: + that: + - output.changed + - output.id + +- name: Load info for all Rulesets + azure_rm_afdruleset_info: + profile_name: "{{ profile }}" + resource_group: "{{ resource_group }}" + register: output + +- name: Assert the resource has the proper values set + ansible.builtin.assert: + that: + - output.afdrulesets | length == 1 + - output.afdrulesets[0].name == "{{ ruleset }}" + - not output.changed + +- name: Delete Ruleset + azure_rm_afdruleset: + name: "{{ ruleset }}" + profile_name: "{{ profile }}" + resource_group: "{{ resource_group }}" + state: "absent" + register: output + +- name: Assert the resource was changed + ansible.builtin.assert: + that: + - output.changed + +- name: Delete Ruleset (idempotent test) + azure_rm_afdruleset: + name: "{{ ruleset }}" + profile_name: "{{ profile }}" + resource_group: "{{ resource_group }}" + state: "absent" + register: output + +- name: Assert the resource was not changed + ansible.builtin.assert: + that: + - not output.changed + +- name: Delete Frontdoor Profile + azure_rm_cdnprofile: + name: "{{ profile }}" + resource_group: "{{ resource_group }}" + state: "absent" + From bad4100253fa8ce784d60497bf93d69d9e60545a Mon Sep 17 00:00:00 2001 From: jartoo Date: Thu, 13 Jun 2024 08:23:41 -0500 Subject: [PATCH 2/3] Updated pipeline and runtime yml files --- meta/runtime.yml | 2 ++ pr-pipelines.yml | 1 + 2 files changed, 3 insertions(+) diff --git a/meta/runtime.yml b/meta/runtime.yml index 2da8ce1a3..699b77e78 100644 --- a/meta/runtime.yml +++ b/meta/runtime.yml @@ -14,6 +14,8 @@ action_groups: - azure.azcollection.azure_rm_adserviceprincipal_info - azure.azcollection.azure_rm_aduser - azure.azcollection.azure_rm_aduser_info + - azure.azcollection.azure_rm_afdruleset + - azure.azcollection.azure_rm_afdruleset_info - azure.azcollection.azure_rm_aks - azure.azcollection.azure_rm_aks_info - azure.azcollection.azure_rm_aksagentpool diff --git a/pr-pipelines.yml b/pr-pipelines.yml index 744860d8d..b3e087076 100644 --- a/pr-pipelines.yml +++ b/pr-pipelines.yml @@ -35,6 +35,7 @@ parameters: - "azure_rm_acs" - "azure_rm_adgroup" - "azure_rm_aduser" + - "azure_rm_afdruleset" - "azure_rm_aks" - "azure_rm_aksagentpool" - "azure_rm_apimanagement" From 8579f92a1b76c4c93a66efead383d5c0cb2c9119 Mon Sep 17 00:00:00 2001 From: Jarret Tooley <89205294+jartoo@users.noreply.github.com> Date: Thu, 27 Jun 2024 11:30:59 -0500 Subject: [PATCH 3/3] Update tests/integration/targets/azure_rm_afdruleset/tasks/main.yml Co-authored-by: Fred-sun <37327967+Fred-sun@users.noreply.github.com> --- tests/integration/targets/azure_rm_afdruleset/tasks/main.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/integration/targets/azure_rm_afdruleset/tasks/main.yml b/tests/integration/targets/azure_rm_afdruleset/tasks/main.yml index e985c6259..0692312a1 100644 --- a/tests/integration/targets/azure_rm_afdruleset/tasks/main.yml +++ b/tests/integration/targets/azure_rm_afdruleset/tasks/main.yml @@ -123,4 +123,3 @@ name: "{{ profile }}" resource_group: "{{ resource_group }}" state: "absent" -