diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 12:04:41 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 12:04:41 +0000 |
commit | 975f66f2eebe9dadba04f275774d4ab83f74cf25 (patch) | |
tree | 89bd26a93aaae6a25749145b7e4bca4a1e75b2be /ansible_collections/netapp/storagegrid/plugins/modules | |
parent | Initial commit. (diff) | |
download | ansible-975f66f2eebe9dadba04f275774d4ab83f74cf25.tar.xz ansible-975f66f2eebe9dadba04f275774d4ab83f74cf25.zip |
Adding upstream version 7.7.0+dfsg.upstream/7.7.0+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ansible_collections/netapp/storagegrid/plugins/modules')
19 files changed, 5898 insertions, 0 deletions
diff --git a/ansible_collections/netapp/storagegrid/plugins/modules/na_sg_grid_account.py b/ansible_collections/netapp/storagegrid/plugins/modules/na_sg_grid_account.py new file mode 100644 index 000000000..88943c082 --- /dev/null +++ b/ansible_collections/netapp/storagegrid/plugins/modules/na_sg_grid_account.py @@ -0,0 +1,458 @@ +#!/usr/bin/python + +# (c) 2020, NetApp Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +"""NetApp StorageGRID - Manage Accounts""" + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["preview"], + "supported_by": "community", +} + + +DOCUMENTATION = """ +module: na_sg_grid_account +short_description: NetApp StorageGRID manage accounts. +extends_documentation_fragment: + - netapp.storagegrid.netapp.sg +version_added: '20.6.0' +author: NetApp Ansible Team (@joshedmonds) <ng-ansibleteam@netapp.com> +description: +- Create, Update, Delete Tenant Accounts on NetApp StorageGRID. +options: + state: + description: + - Whether the specified account should exist or not. + - Required for all operations. + type: str + choices: ['present', 'absent'] + default: present + name: + description: + - Name of the tenant. + - Required for create or modify operation. + type: str + account_id: + description: + - Account Id of the tenant. + - May be used for modify or delete operation. + type: str + protocol: + description: + - Object Storage protocol used by the tenancy. + - Required for create operation. + type: str + choices: ['s3', 'swift'] + management: + description: + - Whether the tenant can login to the StorageGRID tenant portal. + type: bool + default: true + use_own_identity_source: + description: + - Whether the tenant account should configure its own identity source. + type: bool + allow_platform_services: + description: + - Allows tenant to use platform services features such as CloudMirror. + type: bool + root_access_group: + description: + - Existing federated group to have initial Root Access permissions for the tenant. + - Must begin with C(federated-group/) + type: str + version_added: 20.11.0 + quota_size: + description: + - Quota to apply to the tenant specified in I(quota_size_unit). + - If you intend to have no limits, assign C(0). + type: int + default: 0 + quota_size_unit: + description: + - The unit used to interpret the size parameter. + choices: ['bytes', 'b', 'kb', 'mb', 'gb', 'tb', 'pb', 'eb', 'zb', 'yb'] + type: str + default: 'gb' + password: + description: + - Root password for tenant account. + - Requires root privilege. + type: str + update_password: + description: + - Choose when to update the password. + - When set to C(always), the password will always be updated. + - When set to C(on_create) the password will only be set upon a new user creation. + default: on_create + choices: + - on_create + - always + type: str +""" + +EXAMPLES = """ + - name: create a tenant account + netapp.storagegrid.na_sg_grid_account: + api_url: "https://<storagegrid-endpoint-url>" + auth_token: "storagegrid-auth-token" + validate_certs: false + state: present + name: storagegrid-tenant-1 + protocol: s3 + management: true + use_own_identity_source: false + allow_platform_services: false + password: "tenant-password" + quota_size: 0 + + - name: update a tenant account + netapp.storagegrid.na_sg_grid_account: + api_url: "https://<storagegrid-endpoint-url>" + auth_token: "storagegrid-auth-token" + validate_certs: false + state: present + name: storagegrid-tenant-1 + protocol: s3 + management: true + use_own_identity_source: false + allow_platform_services: true + password: "tenant-password" + quota_size: 10240 + + - name: delete a tenant account + netapp.storagegrid.na_sg_grid_account: + api_url: "https://<storagegrid-endpoint-url>" + auth_token: "storagegrid-auth-token" + validate_certs: false + state: absent + name: storagegrid-tenant-1 + protocol: s3 +""" + +RETURN = """ +resp: + description: Returns information about the StorageGRID tenant account. + returned: success + type: dict + sample: { + "name": "Example Account", + "capabilities": ["management", "s3"], + "policy": { + "useAccountIdentitySource": true, + "allowPlatformServices": false, + "quotaObjectBytes": 100000000000 + }, + "id": "12345678901234567890" + } +""" + +import ansible_collections.netapp.storagegrid.plugins.module_utils.netapp as netapp_utils +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp_module import ( + NetAppModule, +) +from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp import ( + SGRestAPI, +) + + +class SgGridAccount(object): + """ + Create, modify and delete StorageGRID Tenant Account + """ + + def __init__(self): + """ + Parse arguments, setup state variables, + check parameters and ensure request module is installed + """ + self.argument_spec = netapp_utils.na_storagegrid_host_argument_spec() + self.argument_spec.update( + dict( + state=dict(required=False, type="str", choices=["present", "absent"], default="present"), + name=dict(required=False, type="str"), + account_id=dict(required=False, type="str"), + protocol=dict(required=False, choices=["s3", "swift"]), + management=dict(required=False, type="bool", default=True), + use_own_identity_source=dict(required=False, type="bool"), + allow_platform_services=dict(required=False, type="bool"), + root_access_group=dict(required=False, type="str"), + quota_size=dict(required=False, type="int", default=0), + quota_size_unit=dict( + default="gb", + choices=[ + "bytes", + "b", + "kb", + "mb", + "gb", + "tb", + "pb", + "eb", + "zb", + "yb", + ], + type="str", + ), + password=dict(required=False, type="str", no_log=True), + update_password=dict( + default="on_create", choices=["on_create", "always"] + ), + ) + ) + + self.module = AnsibleModule( + argument_spec=self.argument_spec, + required_if=[ + ( + "state", + "present", + [ + "name", + "protocol", + "use_own_identity_source", + "allow_platform_services", + ], + ) + ], + supports_check_mode=True, + ) + + self.na_helper = NetAppModule() + + # set up state variables + self.parameters = self.na_helper.set_parameters(self.module.params) + # Calling generic SG rest_api class + self.rest_api = SGRestAPI(self.module) + + # Checking for the parameters passed and create new parameters list + self.data = {} + self.data["name"] = self.parameters["name"] + self.data["capabilities"] = [self.parameters["protocol"]] + + if self.parameters.get("password") is not None: + self.data["password"] = self.parameters["password"] + + # Append "management" to the capability list only if parameter is True + if self.parameters.get("management"): + self.data["capabilities"].append("management") + + self.data["policy"] = {} + + if "use_own_identity_source" in self.parameters: + self.data["policy"]["useAccountIdentitySource"] = self.parameters[ + "use_own_identity_source" + ] + + if "allow_platform_services" in self.parameters: + self.data["policy"]["allowPlatformServices"] = self.parameters[ + "allow_platform_services" + ] + + if self.parameters.get("root_access_group") is not None: + self.data["grantRootAccessToGroup"] = self.parameters["root_access_group"] + + if self.parameters["quota_size"] > 0: + self.parameters["quota_size"] = ( + self.parameters["quota_size"] + * netapp_utils.POW2_BYTE_MAP[ + self.parameters["quota_size_unit"] + ] + ) + self.data["policy"]["quotaObjectBytes"] = self.parameters[ + "quota_size" + ] + elif self.parameters["quota_size"] == 0: + self.data["policy"]["quotaObjectBytes"] = None + + self.pw_change = {} + if self.parameters.get("password") is not None: + self.pw_change["password"] = self.parameters["password"] + + def get_tenant_account_id(self): + # Check if tenant account exists + # Return tenant account info if found, or None + api = "api/v3/grid/accounts?limit=350" + + list_accounts, error = self.rest_api.get(api) + + if error: + self.module.fail_json(msg=error) + + for account in list_accounts.get("data"): + if account["name"] == self.parameters["name"]: + return account["id"] + + return None + + def get_tenant_account(self, account_id): + api = "api/v3/grid/accounts/%s" % account_id + account, error = self.rest_api.get(api) + + if error: + self.module.fail_json(msg=error) + else: + return account["data"] + return None + + def create_tenant_account(self): + api = "api/v3/grid/accounts" + + response, error = self.rest_api.post(api, self.data) + + if error: + self.module.fail_json(msg=error) + + return response["data"] + + def delete_tenant_account(self, account_id): + api = "api/v3/grid/accounts/" + account_id + + self.data = None + response, error = self.rest_api.delete(api, self.data) + if error: + self.module.fail_json(msg=error) + + def update_tenant_account(self, account_id): + api = "api/v3/grid/accounts/" + account_id + + if "password" in self.data: + del self.data["password"] + + if "grantRootAccessToGroup" in self.data: + del self.data["grantRootAccessToGroup"] + + response, error = self.rest_api.put(api, self.data) + if error: + self.module.fail_json(msg=error) + + return response["data"] + + def set_tenant_root_password(self, account_id): + api = "api/v3/grid/accounts/%s/change-password" % account_id + response, error = self.rest_api.post(api, self.pw_change) + + if error: + self.module.fail_json(msg=error["text"]) + + def apply(self): + """ + Perform pre-checks, call functions and exit + """ + + tenant_account = None + + if self.parameters.get("account_id"): + tenant_account = self.get_tenant_account( + self.parameters["account_id"] + ) + + else: + tenant_account_id = self.get_tenant_account_id() + if tenant_account_id: + tenant_account = self.get_tenant_account(tenant_account_id) + + cd_action = self.na_helper.get_cd_action( + tenant_account, self.parameters + ) + + if cd_action is None and self.parameters["state"] == "present": + # let's see if we need to update parameters + update = False + + capability_diff = [ + i + for i in self.data["capabilities"] + + tenant_account["capabilities"] + if i not in self.data["capabilities"] + or i not in tenant_account["capabilities"] + ] + + if self.parameters["quota_size"] > 0: + if ( + tenant_account["policy"]["quotaObjectBytes"] + != self.parameters["quota_size"] + ): + update = True + elif ( + self.parameters["quota_size"] == 0 + and tenant_account["policy"]["quotaObjectBytes"] is not None + ): + update = True + + if ( + "use_own_identity_source" in self.parameters + and tenant_account["policy"]["useAccountIdentitySource"] + != self.parameters["use_own_identity_source"] + ): + update = True + + elif ( + "allow_platform_services" in self.parameters + and tenant_account["policy"]["allowPlatformServices"] + != self.parameters["allow_platform_services"] + ): + update = True + + elif capability_diff: + update = True + + if update: + self.na_helper.changed = True + + result_message = "" + resp_data = tenant_account + if self.na_helper.changed: + if self.module.check_mode: + pass + else: + if cd_action == "delete": + self.delete_tenant_account(tenant_account["id"]) + result_message = "Tenant Account deleted" + resp_data = None + + elif cd_action == "create": + resp_data = self.create_tenant_account() + result_message = "Tenant Account created" + + else: + resp_data = self.update_tenant_account(tenant_account["id"]) + result_message = "Tenant Account updated" + + # If a password has been set + if self.pw_change: + if self.module.check_mode: + pass + else: + # Only update the password if update_password is always + # On a create action, the password is set directly by the POST /grid/accounts method + if self.parameters["update_password"] == "always" and cd_action != "create": + self.set_tenant_root_password(tenant_account["id"]) + self.na_helper.changed = True + + results = [result_message, "Tenant Account root password updated"] + result_message = "; ".join(filter(None, results)) + + self.module.exit_json( + changed=self.na_helper.changed, msg=result_message, resp=resp_data + ) + + +def main(): + """ + Main function + """ + na_sg_grid_account = SgGridAccount() + na_sg_grid_account.apply() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/netapp/storagegrid/plugins/modules/na_sg_grid_certificate.py b/ansible_collections/netapp/storagegrid/plugins/modules/na_sg_grid_certificate.py new file mode 100644 index 000000000..97f9ab972 --- /dev/null +++ b/ansible_collections/netapp/storagegrid/plugins/modules/na_sg_grid_certificate.py @@ -0,0 +1,226 @@ +#!/usr/bin/python + +# (c) 2021, NetApp Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +"""NetApp StorageGRID - Manage Certificates""" + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = """ +module: na_sg_grid_certificate +short_description: Manage the Storage API and Grid Management certificates on StorageGRID. +extends_documentation_fragment: + - netapp.storagegrid.netapp.sg +version_added: '21.6.0' +author: NetApp Ansible Team (@joshedmonds) <ng-ansibleteam@netapp.com> +description: +- Set and update the Storage API and Grid Management certificates on NetApp StorageGRID. +options: + state: + description: + - Whether the specified certificate should be set. + type: str + choices: ['present', 'absent'] + default: present + type: + description: + - Which certificate to update. + type: str + choices: ['storage-api', 'management'] + required: true + server_certificate: + description: + - X.509 server certificate in PEM-encoding. + type: str + ca_bundle: + description: + - Intermediate CA certificate bundle in concatenated PEM-encoding. + - Omit if there is no intermediate CA. + type: str + private_key: + description: + - Certificate private key in PEM-encoding. + - Required if I(server_certificate) is specified. + type: str +""" + +EXAMPLES = """ + - name: set storage API certificate + netapp.storagegrid.na_sg_grid_certificate: + api_url: "https://<storagegrid-endpoint-url>" + auth_token: "storagegrid-auth-token" + validate_certs: false + state: present + type: storage-api + server_certificate: | + -----BEGIN CERTIFICATE----- + MIIC6DCCAdACCQC7l4WukhKD0zANBgkqhkiG9w0BAQsFADA2MQswCQYDVQQGEwJB + BAMMHnNnYW4wMS5kZXYubWljcm9icmV3Lm5ldGFwcC5hdTCCASIwDQYJKoZIhvcN + AQEBBQADggEPADCCAQoCggEBAMvjm9I35lmKcC7ITVL8+QiZ/klvdkbfZCUQrfdy + 71inP+XmPjs0rnkhICA9ItODteRcVlO+t7nDTfm7HgG0mJFkcJm0ffyEYrcx24qu + S7gXYQjRsJmrep1awoaCa20BMGuqK2WKI3IvZ7YiT22qkBqKJD+hIFffX6u3Jy+B + 77pR6YcATtpMHW/AaOx+OX9l80dIRsRZKMDxYQ== + -----END CERTIFICATE----- + private_key: | + -----BEGIN PRIVATE KEY----- + MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDL45vSN+ZZinAu + L25W0+cz1Oi69AKkI7d9nbFics2ay5+7o+4rKqf3en2R4MSxiJvy+iDlOmATib5O + x8TN5pJ9AgMBAAECggEADDLM8tHXXUoUFihzv+BUwff8p8YcbHcXFcSes+xTd5li + po8lNsx/v2pQx4ByBkuaYLZGIEXOWS6gkp44xhIXgQKBgQD4Hq7862u5HLbmhrV3 + vs8nC69b3QKBgQDacCD8d8JpwPbg8t2VjXM3UvdmgAaLUfU7O1DWV+W3jqzmDOoN + zWVgPbPNj0UmzvLDbgxLoxe77wjn2BHsAJVAfJ9VeQKBgGqFAegYO+wHR8lJUoa5 + ZEe8Upy2oBtvND/0dnwO2ym2FGsBJN0Gr4NKdG5vkzLsthKkcwRm0ikwEUOUZQKE + K8J5yEVeo9K2v3wggtq8fYn6 + -----END PRIVATE KEY----- + +""" + +RETURN = """ +resp: + description: Returns information about the StorageGRID server certificates. + returned: success + type: dict + sample: { + "serverCertificateEncoded": "-----BEGIN CERTIFICATE-----MIIC6DCCAdACCQC7l4WukhKD0zANBgkqhkiG9w0BAQsFADA2MQswCQYDVQQGE...-----END CERTIFICATE-----", + "caBundleEncoded": "-----BEGIN CERTIFICATE-----MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELM...-----END CERTIFICATE-----" + } +""" + +import json + +import ansible_collections.netapp.storagegrid.plugins.module_utils.netapp as netapp_utils +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp_module import NetAppModule +from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp import SGRestAPI + + +class SgGridCertificate: + """ + Update StorageGRID certificates + """ + + def __init__(self): + """ + Parse arguments, setup state variables, + check parameters and ensure request module is installed + """ + self.argument_spec = netapp_utils.na_storagegrid_host_argument_spec() + self.argument_spec.update( + dict( + state=dict(required=False, type="str", choices=["present", "absent"], default="present"), + type=dict(required=True, type="str", choices=["storage-api", "management"]), + server_certificate=dict(required=False, type="str"), + ca_bundle=dict(required=False, type="str"), + private_key=dict(required=False, type="str", no_log=True), + ) + ) + + parameter_map = { + "server_certificate": "serverCertificateEncoded", + "ca_bundle": "caBundleEncoded", + "private_key": "privateKeyEncoded", + } + + self.module = AnsibleModule( + argument_spec=self.argument_spec, + required_if=[("state", "present", ["server_certificate", "private_key"])], + supports_check_mode=True, + ) + + self.na_helper = NetAppModule() + + # set up state variables + self.parameters = self.na_helper.set_parameters(self.module.params) + # Calling generic SG rest_api class + self.rest_api = SGRestAPI(self.module) + # Checking for the parameters passed and create new parameters list + self.data = {} + + if self.parameters["state"] == "present": + for k in parameter_map.keys(): + if self.parameters.get(k) is not None: + self.data[parameter_map[k]] = self.parameters[k] + + self.module.fail_json + + def get_grid_certificate(self, cert_type): + api = "api/v3/grid/%s" % cert_type + + response, error = self.rest_api.get(api) + + if error: + self.module.fail_json(msg=error) + + return response["data"] + + def update_grid_certificate(self, cert_type): + api = "api/v3/grid/%s/update" % cert_type + + response, error = self.rest_api.post(api, self.data) + if error: + self.module.fail_json(msg=error) + + def apply(self): + """ + Perform pre-checks, call functions and exit + """ + + cert_type = "" + cd_action = None + + if self.parameters.get("type") == "storage-api": + cert_type = "storage-api-certificate" + elif self.parameters.get("type") == "management": + cert_type = "management-certificate" + + cert_data = self.get_grid_certificate(cert_type) + + if cert_data["serverCertificateEncoded"] is None and cert_data["caBundleEncoded"] is None: + cd_action = self.na_helper.get_cd_action(None, self.parameters) + else: + cd_action = self.na_helper.get_cd_action(cert_data, self.parameters) + + if cd_action is None and self.parameters["state"] == "present": + # let's see if we need to update parameters + update = False + + if self.data.get("serverCertificateEncoded") is not None and self.data.get("privateKeyEncoded") is not None: + for item in ["serverCertificateEncoded", "caBundleEncoded"]: + if self.data.get(item) != cert_data.get(item): + update = True + + if update: + self.na_helper.changed = True + + result_message = "" + resp_data = cert_data + if self.na_helper.changed: + if self.module.check_mode: + pass + else: + if cd_action == "delete": + self.update_grid_certificate(cert_type) + resp_data = self.get_grid_certificate(cert_type) + result_message = "Grid %s removed" % cert_type + + else: + self.update_grid_certificate(cert_type) + resp_data = self.get_grid_certificate(cert_type) + result_message = "Grid %s updated" % cert_type + + self.module.exit_json(changed=self.na_helper.changed, msg=result_message, resp=resp_data) + + +def main(): + """ + Main function + """ + na_sg_grid_certificate = SgGridCertificate() + na_sg_grid_certificate.apply() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/netapp/storagegrid/plugins/modules/na_sg_grid_client_certificate.py b/ansible_collections/netapp/storagegrid/plugins/modules/na_sg_grid_client_certificate.py new file mode 100644 index 000000000..aa381b397 --- /dev/null +++ b/ansible_collections/netapp/storagegrid/plugins/modules/na_sg_grid_client_certificate.py @@ -0,0 +1,265 @@ +#!/usr/bin/python + +# (c) 2022, NetApp Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +"""NetApp StorageGRID - Manage Certificates""" + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = """ +module: na_sg_grid_client_certificate +short_description: Manage Client Certificates on StorageGRID +extends_documentation_fragment: + - netapp.storagegrid.netapp.sg +version_added: '21.11.0' +author: NetApp Ansible Team (@joshedmonds) <ng-ansibleteam@netapp.com> +description: +- Create, Update, Delete Client Certificates on NetApp StorageGRID. +options: + state: + description: + - Whether the specified certificate should exist. + type: str + choices: ['present', 'absent'] + default: present + certificate_id: + description: + - ID of the client certificate. + type: str + display_name: + description: + - A display name for the client certificate configuration. + - This parameter can be modified if I(certificate_id) is also specified. + type: str + public_key: + description: + - X.509 client certificate in PEM-encoding. + type: str + allow_prometheus: + description: + - Whether the external monitoring tool can access Prometheus metrics. + type: bool +""" + +EXAMPLES = """ + - name: create client certificate + netapp.storagegrid.na_sg_grid_client_certificate: + api_url: "https://<storagegrid-endpoint-url>" + auth_token: "storagegrid-auth-token" + validate_certs: false + state: present + display_name: client-cert1 + public_key: | + -----BEGIN CERTIFICATE----- + MIIC6DCCAdACCQC7l4WukhKD0zANBgkqhkiG9w0BAQsFADA2..swCQYDVQQGEwJB + BAMMHnNnYW4wMS5kZXYubWljcm9icmV3Lm5ldGFwcC5hdTCC..IwDQYJKoZIhvcN + AQEBBQADggEPADCCAQoCggEBAMvjm9I35lmKcC7ITVL8+QiZ..lvdkbfZCUQrfdy + 71inP+XmPjs0rnkhICA9ItODteRcVlO+t7nDTfm7HgG0mJFk..m0ffyEYrcx24qu + S7gXYQjRsJmrep1awoaCa20BMGuqK2WKI3IvZ7YiT22qkBqK..+hIFffX6u3Jy+B + 77pR6YcATtpMHW/AaOx+OX9l80dIRsRZKMDxYQ== + -----END CERTIFICATE----- + allow_prometheus: true + + - name: rename client certificate + netapp.storagegrid.na_sg_grid_client_certificate: + api_url: "https://<storagegrid-endpoint-url>" + auth_token: "storagegrid-auth-token" + validate_certs: false + state: present + certificate_id: 00000000-0000-0000-0000-000000000000 + display_name: client-cert1-rename + public_key: | + -----BEGIN CERTIFICATE----- + MIIC6DCCAdACCQC7l4WukhKD0zANBgkqhkiG9w0BAQsFADA2..swCQYDVQQGEwJB + BAMMHnNnYW4wMS5kZXYubWljcm9icmV3Lm5ldGFwcC5hdTCC..IwDQYJKoZIhvcN + AQEBBQADggEPADCCAQoCggEBAMvjm9I35lmKcC7ITVL8+QiZ..lvdkbfZCUQrfdy + 71inP+XmPjs0rnkhICA9ItODteRcVlO+t7nDTfm7HgG0mJFk..m0ffyEYrcx24qu + S7gXYQjRsJmrep1awoaCa20BMGuqK2WKI3IvZ7YiT22qkBqK..+hIFffX6u3Jy+B + 77pR6YcATtpMHW/AaOx+OX9l80dIRsRZKMDxYQ== + -----END CERTIFICATE----- + allow_prometheus: true + + - name: delete client certificate + netapp.storagegrid.na_sg_grid_client_certificate: + api_url: "https://<storagegrid-endpoint-url>" + auth_token: "storagegrid-auth-token" + validate_certs: false + state: absent + display_name: client-cert1-rename +""" + +RETURN = """ +resp: + description: Returns information about the StorageGRID server certificates. + returned: success + type: dict + sample: { + "id": "abcABC_01234-0123456789abcABCabc0123456789==", + "displayName": "client-cert1", + "expiryDate": "2024-01-01T00:00:00.000Z", + "publicKey": "-----BEGIN CERTIFICATE-----MIIC6DCCAdACCQC7l4WukhKD0zANBgkqhkiG9w0BAQsFADA2MQswCQYDVQQGE...-----END CERTIFICATE-----", + "allowPrometheus": true + } +""" + +import json + +import ansible_collections.netapp.storagegrid.plugins.module_utils.netapp as netapp_utils +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp_module import NetAppModule +from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp import SGRestAPI + + +class SgGridClientCertificate: + """ + Update StorageGRID client certificates + """ + + def __init__(self): + """ + Parse arguments, setup state variables, + check parameters and ensure request module is installed + """ + self.argument_spec = netapp_utils.na_storagegrid_host_argument_spec() + self.argument_spec.update( + dict( + state=dict(required=False, type="str", choices=["present", "absent"], default="present"), + certificate_id=dict(required=False, type="str"), + display_name=dict(required=False, type="str"), + public_key=dict(required=False, type="str"), + allow_prometheus=dict(required=False, type="bool"), + ) + ) + + parameter_map = { + "display_name": "displayName", + "public_key": "publicKey", + "allow_prometheus": "allowPrometheus", + } + + self.module = AnsibleModule( + argument_spec=self.argument_spec, + required_if=[("state", "present", ["display_name", "public_key"])], + required_one_of=[("display_name", "certificate_id")], + supports_check_mode=True, + ) + + self.na_helper = NetAppModule() + + # set up state variables + self.parameters = self.na_helper.set_parameters(self.module.params) + # Calling generic SG rest_api class + self.rest_api = SGRestAPI(self.module) + # Checking for the parameters passed and create new parameters list + self.data = {} + + if self.parameters["state"] == "present": + for k in parameter_map.keys(): + if self.parameters.get(k) is not None: + self.data[parameter_map[k]] = self.parameters[k] + + self.module.fail_json + + def get_grid_client_certificate_id(self): + # Check if certificate with name exists + # Return certificate ID if found, or None + api = "api/v3/grid/client-certificates" + + response, error = self.rest_api.get(api) + + if error: + self.module.fail_json(msg=error) + + for cert in response.get("data"): + if cert["displayName"] == self.parameters["display_name"]: + return cert["id"] + return None + + def get_grid_client_certificate(self, cert_id): + api = "api/v3/grid/client-certificates/%s" % cert_id + account, error = self.rest_api.get(api) + + if error: + self.module.fail_json(msg=error) + else: + return account["data"] + return None + + def create_grid_client_certificate(self): + api = "api/v3/grid/client-certificates" + + response, error = self.rest_api.post(api, self.data) + + if error: + self.module.fail_json(msg=error["text"]) + + return response["data"] + + def delete_grid_client_certificate(self, cert_id): + api = "api/v3/grid/client-certificates/" + cert_id + + self.data = None + response, error = self.rest_api.delete(api, self.data) + if error: + self.module.fail_json(msg=error) + + def update_grid_client_certificate(self, cert_id): + api = "api/v3/grid/client-certificates/" + cert_id + + response, error = self.rest_api.put(api, self.data) + if error: + self.module.fail_json(msg=error["text"]) + + return response["data"] + + def apply(self): + """ + Perform pre-checks, call functions and exit + """ + + client_certificate = None + + if self.parameters.get("certificate_id"): + client_certificate = self.get_grid_client_certificate(self.parameters["certificate_id"]) + + else: + client_cert_id = self.get_grid_client_certificate_id() + if client_cert_id: + client_certificate = self.get_grid_client_certificate(client_cert_id) + + cd_action = self.na_helper.get_cd_action(client_certificate, self.parameters) + + if cd_action is None and self.parameters["state"] == "present": + # let's see if we need to update parameters + modify = self.na_helper.get_modified_attributes(client_certificate, self.data) + + result_message = "" + resp_data = client_certificate + + if self.na_helper.changed and not self.module.check_mode: + if cd_action == "delete": + self.delete_grid_client_certificate(client_certificate["id"]) + result_message = "Client Certificate deleted" + elif cd_action == "create": + resp_data = self.create_grid_client_certificate() + result_message = "Client Certificate created" + elif modify: + resp_data = self.update_grid_client_certificate(client_certificate["id"]) + result_message = "Client Certificate updated" + + self.module.exit_json(changed=self.na_helper.changed, msg=result_message, resp=resp_data) + + +def main(): + """ + Main function + """ + na_sg_grid_certificate = SgGridClientCertificate() + na_sg_grid_certificate.apply() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/netapp/storagegrid/plugins/modules/na_sg_grid_dns.py b/ansible_collections/netapp/storagegrid/plugins/modules/na_sg_grid_dns.py new file mode 100644 index 000000000..95e4e4594 --- /dev/null +++ b/ansible_collections/netapp/storagegrid/plugins/modules/na_sg_grid_dns.py @@ -0,0 +1,163 @@ +#!/usr/bin/python + +# (c) 2020, NetApp Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +"""NetApp StorageGRID - Manage Grid DNS Servers""" + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["preview"], + "supported_by": "community", +} + + +DOCUMENTATION = """ +module: na_sg_grid_dns +short_description: NetApp StorageGRID manage external DNS servers for the grid. +extends_documentation_fragment: + - netapp.storagegrid.netapp.sg +version_added: '20.6.0' +author: NetApp Ansible Team (@joshedmonds) <ng-ansibleteam@netapp.com> +description: +- Update NetApp StorageGRID DNS addresses. +options: + state: + description: + - Whether the specified DNS address should exist or not. + - Required for all operations. + type: str + choices: ['present'] + default: present + dns_servers: + description: + - List of comma separated DNS Addresses to be updated or delete. + type: list + elements: str + required: true +""" + +EXAMPLES = """ + - name: update DNS servers on StorageGRID + netapp.storagegrid.na_sg_grid_dns: + api_url: "https://<storagegrid-endpoint-url>" + auth_token: "storagegrid-auth-token" + validate_certs: false + state: present + dns_servers: "x.x.x.x,xxx.xxx.xxx.xxx" +""" + +RETURN = """ +resp: + description: Returns information about the configured DNS servers. + returned: success + type: list + elements: str + sample: ["8.8.8.8", "8.8.4.4"] +""" + +import ansible_collections.netapp.storagegrid.plugins.module_utils.netapp as netapp_utils +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp_module import NetAppModule +from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp import SGRestAPI + + +class SgGridDns(object): + """ + Create, modify and delete DNS entries for StorageGRID + """ + + def __init__(self): + """ + Parse arguments, setup state variables, + check parameters and ensure request module is installed + """ + self.argument_spec = netapp_utils.na_storagegrid_host_argument_spec() + self.argument_spec.update( + dict( + state=dict(required=False, type="str", choices=["present"], default="present"), + dns_servers=dict(required=True, type="list", elements="str"), + ) + ) + + self.module = AnsibleModule( + argument_spec=self.argument_spec, + # required_if=[("state", "present", ["state", "name", "protocol"])], + supports_check_mode=True, + ) + + self.na_helper = NetAppModule() + + # set up state variables + self.parameters = self.na_helper.set_parameters(self.module.params) + # Calling generic SG rest_api class + self.rest_api = SGRestAPI(self.module) + # Checking for the parameters passed and create new parameters list + self.data = self.parameters["dns_servers"] + + def get_grid_dns(self): + # Check if tenant account exists + # Return tenant account info if found, or None + api = "api/v3/grid/dns-servers" + + response, error = self.rest_api.get(api) + + if error: + self.module.fail_json(msg=error) + + return response["data"] + + def update_grid_dns(self): + api = "api/v3/grid/dns-servers" + + response, error = self.rest_api.put(api, self.data) + if error: + self.module.fail_json(msg=error) + + return response["data"] + + def apply(self): + """ + Perform pre-checks, call functions and exit + """ + grid_dns = self.get_grid_dns() + + cd_action = self.na_helper.get_cd_action(grid_dns, self.parameters["dns_servers"]) + + if cd_action is None and self.parameters["state"] == "present": + # let's see if we need to update parameters + update = False + + dns_diff = [i for i in self.data + grid_dns if i not in self.data or i not in grid_dns] + if dns_diff: + update = True + + if update: + self.na_helper.changed = True + result_message = "" + resp_data = grid_dns + if self.na_helper.changed: + if self.module.check_mode: + pass + else: + resp_data = self.update_grid_dns() + result_message = "Grid DNS updated" + + self.module.exit_json(changed=self.na_helper.changed, msg=result_message, resp=resp_data) + + +def main(): + """ + Main function + """ + na_sg_grid_dns = SgGridDns() + na_sg_grid_dns.apply() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/netapp/storagegrid/plugins/modules/na_sg_grid_gateway.py b/ansible_collections/netapp/storagegrid/plugins/modules/na_sg_grid_gateway.py new file mode 100644 index 000000000..9202decff --- /dev/null +++ b/ansible_collections/netapp/storagegrid/plugins/modules/na_sg_grid_gateway.py @@ -0,0 +1,532 @@ +#!/usr/bin/python + +# (c) 2021, NetApp Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +"""NetApp StorageGRID - Manage Load Balancer Endpoints""" + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = """ +module: na_sg_grid_gateway +short_description: Manage Load balancer (gateway) endpoints on StorageGRID. +extends_documentation_fragment: + - netapp.storagegrid.netapp.sg +version_added: '21.7.0' +author: NetApp Ansible Team (@jkandati) <ng-sg-ansibleteam@netapp.com> +description: +- Create or Update Load Balancer Endpoints on StorageGRID. +- This module is idempotent if I(private_key) is not specified. +- The module will match an existing config based on I(port) and I(display_name). +- If multiple load balancer endpoints exist utilizing the same port and display name, use I(gateway_id) to select the intended endpoint. +options: + state: + description: + - Whether the specified load balancer endpoint should be configured. + type: str + choices: ['present', 'absent'] + default: present + gateway_id: + description: + - ID of the load balancer endpoint. + type: str + version_added: '21.9.0' + display_name: + description: + - A display name for the configuration. + - This parameter can be modified if I(gateway_id) is also specified. + type: str + port: + description: + - The TCP port to serve traffic on. + - This parameter cannot be modified after the load balancer endpoint has been created. + type: int + required: true + secure: + description: + - Whether the load balancer endpoint serves HTTP or HTTPS traffic. + - This parameter cannot be modified after the load balancer endpoint has been created. + type: bool + default: true + enable_ipv4: + description: + - Indicates whether to listen for connections on IPv4. + type: bool + default: true + enable_ipv6: + description: + - Indicates whether to listen for connections on IPv6. + type: bool + default: true + binding_mode: + description: + - Binding mode to restrict accessibility of the load balancer endpoint. + - A binding mode other than I(global) requires StorageGRID 11.5 or greater. + type: str + choices: ['global', 'ha-groups', 'node-interfaces'] + default: 'global' + version_added: '21.9.0' + ha_groups: + description: + - A set of StorageGRID HA Groups by name or UUID to bind the load balancer endpoint to. + - Option is ignored unless I(binding_mode=ha-groups). + type: list + elements: str + version_added: '21.9.0' + node_interfaces: + description: + - A set of StorageGRID node interfaces to bind the load balancer endpoint to. + type: list + elements: dict + suboptions: + node: + description: + - Name of the StorageGRID node. + type: str + interface: + description: + - The interface to bind to. eth0 corresponds to the Grid Network, eth1 to the Admin Network, and eth2 to the Client Network. + type: str + version_added: '21.9.0' + default_service_type: + description: + - The type of service to proxy through the load balancer. + type: str + choices: ['s3', 'swift'] + default: 's3' + server_certificate: + description: + - X.509 server certificate in PEM-encoding. + - Omit if using default certificates. + type: str + required: false + private_key: + description: + - Certficate private key in PEM-encoding. + - Required if I(server_certificate) is not empty. + type: str + required: false + ca_bundle: + description: + - Intermediate CA certificate bundle in concatenated PEM-encoding. + - Omit when there is no intermediate CA. + type: str + required: false + +""" +EXAMPLES = """ + - name: Create and Upload Certificate to a Gateway Endpoint with global binding + netapp.storagegrid.na_sg_grid_gateway: + api_url: "https://<storagegrid-endpoint-url>" + auth_token: "storagegrid-auth-token" + displayName: "FabricPool Endpoint" + port: 10443 + secure: True + enable_ipv4: True + enable_ipv6: True + default_service_type: "s3" + server_certificate: | + -----BEGIN CERTIFICATE----- + MIIC6DCCAdACCQC7l4WukhKD0zANBgkqhkiG9w0BAQsFADA2..swCQYDVQQGEwJB + BAMMHnNnYW4wMS5kZXYubWljcm9icmV3Lm5ldGFwcC5hdTCC..IwDQYJKoZIhvcN + AQEBBQADggEPADCCAQoCggEBAMvjm9I35lmKcC7ITVL8+QiZ..lvdkbfZCUQrfdy + 71inP+XmPjs0rnkhICA9ItODteRcVlO+t7nDTfm7HgG0mJFk..m0ffyEYrcx24qu + S7gXYQjRsJmrep1awoaCa20BMGuqK2WKI3IvZ7YiT22qkBqK..+hIFffX6u3Jy+B + 77pR6YcATtpMHW/AaOx+OX9l80dIRsRZKMDxYQ== + -----END CERTIFICATE----- + private_key: | + -----BEGIN PRIVATE KEY----- + MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIB..DL45vSN+ZZinAu + L25W0+cz1Oi69AKkI7d9nbFics2ay5+7o+4rKqf3en2R4MSx..vy+iDlOmATib5O + x8TN5pJ9AgMBAAECggEADDLM8tHXXUoUFihzv+BUwff8p8Yc..cXFcSes+xTd5li + po8lNsx/v2pQx4ByBkuaYLZGIEXOWS6gkp44xhIXgQKBgQD4..7862u5HLbmhrV3 + vs8nC69b3QKBgQDacCD8d8JpwPbg8t2VjXM3UvdmgAaLUfU7..DWV+W3jqzmDOoN + zWVgPbPNj0UmzvLDbgxLoxe77wjn2BHsAJVAfJ9VeQKBgGqF..gYO+wHR8lJUoa5 + ZEe8Upy2oBtvND/0dnwO2ym2FGsBJN0Gr4NKdG5vkzLsthKk..Rm0ikwEUOUZQKE + K8J5yEVeo9K2v3wggtq8fYn6 + -----END PRIVATE KEY----- + validate_certs: false + + - name: Create a HTTP Gateway Endpoint with HA Group Binding + netapp.storagegrid.na_sg_grid_gateway: + api_url: "https://<storagegrid-endpoint-url>" + auth_token: "storagegrid-auth-token" + displayName: "App Endpoint 1" + port: 10501 + secure: false + enable_ipv4: True + enable_ipv6: True + default_service_type: "s3" + binding_mode: ha-groups + ha_groups: site1_ha_group + validate_certs: false + + - name: Create a HTTP Gateway Endpoint with Node Interface Binding + netapp.storagegrid.na_sg_grid_gateway: + api_url: "https://<storagegrid-endpoint-url>" + auth_token: "storagegrid-auth-token" + displayName: "App Endpoint 2" + port: 10502 + secure: false + enable_ipv4: True + enable_ipv6: True + default_service_type: "s3" + binding_mode: node-interfaces + node_interfaecs: + - node: SITE1_ADM1 + interface: eth2 + - node: SITE2_ADM1 + interface: eth2 + validate_certs: false + + - name: Delete Gateway Endpoint + netapp.storagegrid.na_sg_grid_gateway: + api_url: "https://<storagegrid-endpoint-url>" + auth_token: "storagegrid-auth-token" + displayName: "App Endpoint 2" + port: 10502 + default_service_type: "s3" + validate_certs: false +""" + +RETURN = """ +resp: + description: Returns information about the StorageGRID Load Balancer Endpoint. + returned: success + type: dict + sample: { + "id": "ffffffff-ffff-ffff-ffff-ffffffffffff", + "displayName": "ansibletest-secure", + "enableIPv4": True, + "enableIPv6": True, + "port": 10443, + "secure": True, + "accountId": "0", + "defaultServiceType": "s3", + "certSource": "plaintext", + "plaintextCertData": { + "serverCertificateEncoded": "-----BEGIN CERTIFICATE-----MIIC6DCCAdACCQC7l4WukhKD0zANBgkqhkiG9w0BAQsFADA2MQswCQYDVQQGE...-----END CERTIFICATE-----", + "caBundleEncoded": "-----BEGIN CERTIFICATE-----MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELM...-----END CERTIFICATE-----", + "metadata": {...} + } + } +""" + +import ansible_collections.netapp.storagegrid.plugins.module_utils.netapp as netapp_utils +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp_module import NetAppModule +from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp import SGRestAPI + + +class SgGridGateway: + """ + Create, modify and delete Gateway entries for StorageGRID + """ + + def __init__(self): + """ + Parse arguments, setup state variables, + check parameters and ensure request module is installed + """ + self.argument_spec = netapp_utils.na_storagegrid_host_argument_spec() + self.argument_spec.update( + dict( + # Arguments for Creating Gateway Port + state=dict(required=False, type="str", choices=["present", "absent"], default="present"), + gateway_id=dict(required=False, type="str"), + display_name=dict(required=False, type="str"), + port=dict(required=True, type="int"), + secure=dict(required=False, type="bool", default=True), + enable_ipv4=dict(required=False, type="bool", default=True), + enable_ipv6=dict(required=False, type="bool", default=True), + binding_mode=dict( + required=False, type="str", choices=["global", "ha-groups", "node-interfaces"], default="global" + ), + ha_groups=dict(required=False, type="list", elements="str"), + node_interfaces=dict( + required=False, + type="list", + elements="dict", + options=dict( + node=dict(required=False, type="str"), + interface=dict(required=False, type="str"), + ), + ), + # Arguments for setting Gateway Virtual Server + default_service_type=dict(required=False, type="str", choices=["s3", "swift"], default="s3"), + server_certificate=dict(required=False, type="str"), + ca_bundle=dict(required=False, type="str"), + private_key=dict(required=False, type="str", no_log=True), + ) + ) + + parameter_map_gateway = { + "gateway_id": "id", + "display_name": "displayName", + "port": "port", + "secure": "secure", + "enable_ipv4": "enableIPv4", + "enable_ipv6": "enableIPv6", + } + parameter_map_server = { + "server_certificate": "serverCertificateEncoded", + "ca_bundle": "caBundleEncoded", + "private_key": "privateKeyEncoded", + } + self.module = AnsibleModule( + argument_spec=self.argument_spec, + required_if=[("state", "present", ["display_name"])], + supports_check_mode=True, + ) + + self.na_helper = NetAppModule() + + # set up state variables + self.parameters = self.na_helper.set_parameters(self.module.params) + # Calling generic SG rest_api class + self.rest_api = SGRestAPI(self.module) + # Get API version + self.rest_api.get_sg_product_version() + + # Checking for the parameters passed and create new parameters list + + # Parameters for creating a new gateway port configuration + self.data_gateway = {} + self.data_gateway["accountId"] = "0" + + for k in parameter_map_gateway.keys(): + if self.parameters.get(k) is not None: + self.data_gateway[parameter_map_gateway[k]] = self.parameters[k] + + # Parameters for setting a gateway virtual server configuration for a gateway port + self.data_server = {} + self.data_server["defaultServiceType"] = self.parameters["default_service_type"] + + if self.parameters["secure"]: + self.data_server["plaintextCertData"] = {} + self.data_server["certSource"] = "plaintext" + + for k in parameter_map_server.keys(): + if self.parameters.get(k) is not None: + self.data_server["plaintextCertData"][parameter_map_server[k]] = self.parameters[k] + + if self.parameters["binding_mode"] != "global": + self.rest_api.fail_if_not_sg_minimum_version("non-global binding mode", 11, 5) + + if self.parameters["binding_mode"] == "ha-groups": + self.data_gateway["pinTargets"] = {} + self.data_gateway["pinTargets"]["haGroups"] = self.build_ha_group_list() + self.data_gateway["pinTargets"]["nodeInterfaces"] = [] + + elif self.parameters["binding_mode"] == "node-interfaces": + self.data_gateway["pinTargets"] = {} + self.data_gateway["pinTargets"]["nodeInterfaces"] = self.build_node_interface_list() + self.data_gateway["pinTargets"]["haGroups"] = [] + + else: + self.data_gateway["pinTargets"] = {} + self.data_gateway["pinTargets"]["haGroups"] = [] + self.data_gateway["pinTargets"]["nodeInterfaces"] = [] + + def build_ha_group_list(self): + ha_group_ids = [] + + api = "api/v3/private/ha-groups" + ha_groups, error = self.rest_api.get(api) + if error: + self.module.fail_json(msg=error) + + for param in self.parameters["ha_groups"]: + ha_group = next( + (item for item in ha_groups["data"] if (item["name"] == param or item["id"] == param)), None + ) + if ha_group is not None: + ha_group_ids.append(ha_group["id"]) + else: + self.module.fail_json(msg="HA Group '%s' is invalid" % param) + + return ha_group_ids + + def build_node_interface_list(self): + node_interfaces = [] + + api = "api/v3/grid/node-health" + nodes, error = self.rest_api.get(api) + + if error: + self.module.fail_json(msg=error) + + for node_interface in self.parameters["node_interfaces"]: + node_dict = {} + node = next((item for item in nodes["data"] if item["name"] == node_interface["node"]), None) + if node is not None: + node_dict["nodeId"] = node["id"] + node_dict["interface"] = node_interface["interface"] + node_interfaces.append(node_dict) + else: + self.module.fail_json(msg="Node '%s' is invalid" % node_interface["node"]) + + return node_interfaces + + def get_grid_gateway_config(self, gateway_id): + api = "api/v3/private/gateway-configs/%s" % gateway_id + response, error = self.rest_api.get(api) + + if error: + self.module.fail_json(msg=error) + + gateway = response["data"] + gateway_config = self.get_grid_gateway_server_config(gateway["id"]) + + return gateway, gateway_config + + def get_grid_gateway_server_config(self, gateway_id): + api = "api/v3/private/gateway-configs/%s/server-config" % gateway_id + response, error = self.rest_api.get(api) + + if error: + self.module.fail_json(msg=error) + + return response["data"] + + def get_grid_gateway_ports(self, target_port): + + configured_ports = [] + gateway = {} + gateway_config = {} + + api = "api/v3/private/gateway-configs" + response, error = self.rest_api.get(api) + + if error: + self.module.fail_json(msg=error) + + grid_gateway_ports = response["data"] + + # Get only a list of used ports + configured_ports = [data["port"] for data in grid_gateway_ports] + + for index, port in enumerate(configured_ports): + # if port already exists then get gateway ID and get the gateway port server configs + if target_port == port and grid_gateway_ports[index]["displayName"] == self.parameters["display_name"]: + gateway = grid_gateway_ports[index] + gateway_config = self.get_grid_gateway_server_config(gateway["id"]) + break + + return gateway, gateway_config + + def create_grid_gateway(self): + api = "api/v3/private/gateway-configs" + response, error = self.rest_api.post(api, self.data_gateway) + + if error: + self.module.fail_json(msg=error) + + return response["data"] + + def delete_grid_gateway(self, gateway_id): + api = "api/v3/private/gateway-configs/" + gateway_id + self.data = None + response, error = self.rest_api.delete(api, self.data) + + if error: + self.module.fail_json(msg=error) + + def update_grid_gateway(self, gateway_id): + api = "api/v3/private/gateway-configs/%s" % gateway_id + response, error = self.rest_api.put(api, self.data_gateway) + + if error: + self.module.fail_json(msg=error) + + return response["data"] + + def update_grid_gateway_server(self, gateway_id): + api = "api/v3/private/gateway-configs/%s/server-config" % gateway_id + response, error = self.rest_api.put(api, self.data_server) + + if error: + self.module.fail_json(msg=error) + + return response["data"] + + def apply(self): + gateway = None + gateway_config = None + + update_gateway = False + update_gateway_server = False + + if self.parameters.get("gateway_id"): + gateway, gateway_config = self.get_grid_gateway_config(self.parameters["gateway_id"]) + + else: + # Get list of all gateway port configurations + gateway, gateway_config = self.get_grid_gateway_ports(self.data_gateway["port"]) + + cd_action = self.na_helper.get_cd_action(gateway.get("id"), self.parameters) + + if cd_action is None and self.parameters["state"] == "present": + update = False + + if self.data_server.get("plaintextCertData"): + if self.data_server["plaintextCertData"].get("privateKeyEncoded") is not None: + update = True + self.module.warn("This module is not idempotent when private_key is present.") + + if gateway_config.get("plaintextCertData"): + # If certificate private key supplied, update + if gateway_config["plaintextCertData"].get("metadata"): + # remove metadata because we can't compare that + del gateway_config["plaintextCertData"]["metadata"] + + # compare current and desired state + # gateway config cannot be modified until StorageGRID 11.5 + if self.rest_api.meets_sg_minimum_version(11, 5): + update_gateway = self.na_helper.get_modified_attributes(gateway, self.data_gateway) + update_gateway_server = self.na_helper.get_modified_attributes(gateway_config, self.data_server) + + if update: + self.na_helper.changed = True + + result_message = "" + resp_data = {} + + if self.na_helper.changed and not self.module.check_mode: + if cd_action == "delete": + self.delete_grid_gateway(gateway["id"]) + result_message = "Load Balancer Gateway Port Deleted" + + elif cd_action == "create": + resp_data = self.create_grid_gateway() + gateway["id"] = resp_data["id"] + resp_data_server = self.update_grid_gateway_server(gateway["id"]) + resp_data.update(resp_data_server) + result_message = "Load Balancer Gateway Port Created" + + else: + resp_data = gateway + if update_gateway: + resp_data = self.update_grid_gateway(gateway["id"]) + resp_data.update(gateway_config) + + if update_gateway_server: + resp_data_server = self.update_grid_gateway_server(gateway["id"]) + resp_data.update(resp_data_server) + result_message = "Load Balancer Gateway Port Updated" + + self.module.exit_json(changed=self.na_helper.changed, msg=result_message, resp=resp_data) + + +def main(): + """ + Main function + """ + na_sg_grid_gateway = SgGridGateway() + na_sg_grid_gateway.apply() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/netapp/storagegrid/plugins/modules/na_sg_grid_group.py b/ansible_collections/netapp/storagegrid/plugins/modules/na_sg_grid_group.py new file mode 100644 index 000000000..60592c609 --- /dev/null +++ b/ansible_collections/netapp/storagegrid/plugins/modules/na_sg_grid_group.py @@ -0,0 +1,341 @@ +#!/usr/bin/python + +# (c) 2020, NetApp Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +"""NetApp StorageGRID - Manage Grid Groups""" + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["preview"], + "supported_by": "community", +} + + +DOCUMENTATION = """ +module: na_sg_grid_group +short_description: NetApp StorageGRID manage groups. +extends_documentation_fragment: + - netapp.storagegrid.netapp.sg +version_added: '20.6.0' +author: NetApp Ansible Team (@joshedmonds) <ng-ansibleteam@netapp.com> +description: +- Create, Update, Delete Administration Groups within NetApp StorageGRID. +options: + state: + description: + - Whether the specified group should exist or not. + type: str + choices: ['present', 'absent'] + default: present + display_name: + description: + - Name of the group. + - Required for create operation + type: str + unique_name: + description: + - Unique Name for the group. Must begin with C(group/) or C(federated-group/) + - Required for create, modify or delete operation. + type: str + required: true + management_policy: + description: + - Management access controls granted to the group within the tenancy. + type: dict + suboptions: + alarm_acknowledgement: + description: + - Group members can have permission to acknowledge alarms. + required: false + type: bool + other_grid_configuration: + description: + - Need to investigate. + required: false + type: bool + grid_topology_page_configuration: + description: + - Users in this group will have permissions to change grid topology. + required: false + type: bool + tenant_accounts: + description: + - Users in this group will have permissions to manage tenant accounts. + required: false + type: bool + change_tenant_root_password: + description: + - Users in this group will have permissions to change tenant password. + required: false + type: bool + maintenance: + description: + - Users in this group will have permissions to run maintenance tasks on StorageGRID. + required: false + type: bool + metrics_query: + description: + - Users in this group will have permissions to query metrics on StorageGRID. + required: false + type: bool + activate_features: + description: + - Users in this group will have permissions to reactivate features. + required: false + type: bool + ilm: + description: + - Users in this group will have permissions to manage ILM rules on StorageGRID. + required: false + type: bool + object_metadata: + description: + - Users in this group will have permissions to manage object metadata. + required: false + type: bool + root_access: + description: + - Users in this group will have root access. + required: false + type: bool +""" + +EXAMPLES = """ + - name: create a StorageGRID group + netapp.storagegrid.na_sg_grid_group: + api_url: "https://<storagegrid-endpoint-url>" + auth_token: "storagegrid-auth-token" + validate_certs: false + state: present + display_name: ansiblegroup100 + unique_name: group/ansiblegroup100 + management_policy: + tenant_accounts: true + maintenance: true + root_access: false +""" + +RETURN = """ +resp: + description: Returns information about the StorageGRID group attributes. + returned: success + type: dict + sample: { + "displayName": "Example Group", + "policies": { + "management": { + "alarmAcknowledgment": true, + "manageAlerts": true, + "otherGridConfiguration": true, + "gridTopologyPageConfiguration": true, + "tenantAccounts": true, + "changeTenantRootPassword": true, + "maintenance": true, + "metricsQuery": true, + "activateFeatures": false, + "ilm": true, + "objectMetadata": true, + "storageAdmin": true, + "rootAccess": true + } + }, + "uniqueName": "group/examplegroup", + "accountId": "12345678901234567890", + "id": "00000000-0000-0000-0000-000000000000", + "federated": false, + "groupURN": "urn:sgws:identity::12345678901234567890:group/examplegroup" + } +""" + +import json +import re + +import ansible_collections.netapp.storagegrid.plugins.module_utils.netapp as netapp_utils +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp_module import NetAppModule +from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp import SGRestAPI + + +class SgGridGroup(object): + """ + Create, modify and delete StorageGRID Grid-administration Group + """ + + def __init__(self): + """ + Parse arguments, setup state variables, + check parameters and ensure request module is installed + """ + self.argument_spec = netapp_utils.na_storagegrid_host_argument_spec() + self.argument_spec.update( + dict( + state=dict(required=False, type="str", choices=["present", "absent"], default="present"), + display_name=dict(required=False, type="str"), + unique_name=dict(required=True, type="str"), + management_policy=dict( + required=False, + type="dict", + options=dict( + alarm_acknowledgement=dict(required=False, type="bool"), + other_grid_configuration=dict(required=False, type="bool"), + grid_topology_page_configuration=dict(required=False, type="bool"), + tenant_accounts=dict(required=False, type="bool"), + change_tenant_root_password=dict(required=False, type="bool"), + maintenance=dict(required=False, type="bool"), + metrics_query=dict(required=False, type="bool"), + activate_features=dict(required=False, type="bool"), + ilm=dict(required=False, type="bool"), + object_metadata=dict(required=False, type="bool"), + root_access=dict(required=False, type="bool"), + ), + ), + ) + ) + parameter_map = { + "alarm_acknowledgement": "alarmAcknowledgement", + "other_grid_configuration": "otherGridConfiguration", + "grid_topology_page_configuration": "gridTopologyPageConfiguration", + "tenant_accounts": "tenantAccounts", + "change_tenant_root_password": "changeTenantRootPassword", + "maintenance": "maintenance", + "metrics_query": "metricsQuery", + "activate_features": "activateFeatures", + "ilm": "ilm", + "object_metadata": "objectMetadata", + "root_access": "rootAccess", + } + self.module = AnsibleModule(argument_spec=self.argument_spec, supports_check_mode=True,) + + self.na_helper = NetAppModule() + + # set up state variables + self.parameters = self.na_helper.set_parameters(self.module.params) + # Calling generic SG rest_api class + self.rest_api = SGRestAPI(self.module) + # Checking for the parameters passed and create new parameters list + self.data = {} + self.data["displayName"] = self.parameters.get("display_name") + self.data["uniqueName"] = self.parameters["unique_name"] + # Only add the parameter if value is True, as JSON response does not include non-true objects + self.data["policies"] = {} + + if self.parameters.get("management_policy"): + self.data["policies"] = { + "management": dict( + (parameter_map[k], v) for (k, v) in self.parameters["management_policy"].items() if v + ) + } + if not self.data["policies"].get("management"): + self.data["policies"]["management"] = None + + self.re_local_group = re.compile("^group/") + self.re_fed_group = re.compile("^federated-group/") + + if ( + self.re_local_group.match(self.parameters["unique_name"]) is None + and self.re_fed_group.match(self.parameters["unique_name"]) is None + ): + self.module.fail_json(msg="unique_name must begin with 'group/' or 'federated-group/'") + + def get_grid_group(self, unique_name): + # Use the unique name to check if the group exists + api = "api/v3/grid/groups/%s" % unique_name + response, error = self.rest_api.get(api) + + if error: + if response["code"] != 404: + self.module.fail_json(msg=error) + else: + return response["data"] + return None + + def create_grid_group(self): + api = "api/v3/grid/groups" + + response, error = self.rest_api.post(api, self.data) + + if error: + self.module.fail_json(msg=error) + + return response["data"] + + def delete_grid_group(self, group_id): + api = "api/v3/grid/groups/" + group_id + + self.data = None + response, error = self.rest_api.delete(api, self.data) + if error: + self.module.fail_json(msg=error) + + def update_grid_group(self, group_id): + api = "api/v3/grid/groups/" + group_id + + response, error = self.rest_api.put(api, self.data) + if error: + self.module.fail_json(msg=error) + + return response["data"] + + def apply(self): + """ + Perform pre-checks, call functions and exit + """ + grid_group = self.get_grid_group(self.parameters["unique_name"]) + + cd_action = self.na_helper.get_cd_action(grid_group, self.parameters) + + if cd_action is None and self.parameters["state"] == "present": + # let's see if we need to update parameters + update = False + + if self.parameters.get("management_policy"): + if ( + grid_group.get("policies") is None + or grid_group.get("policies", {}).get("management") != self.data["policies"]["management"] + ): + update = True + + if update: + self.na_helper.changed = True + result_message = "" + resp_data = grid_group + if self.na_helper.changed: + if self.module.check_mode: + pass + else: + if cd_action == "delete": + self.delete_grid_group(grid_group["id"]) + result_message = "Grid Group deleted" + + elif cd_action == "create": + resp_data = self.create_grid_group() + result_message = "Grid Group created" + + else: + # for a federated group, the displayName parameter needs to be specified + # and must match the existing displayName + if self.re_fed_group.match(self.parameters["unique_name"]): + self.data["displayName"] = grid_group["displayName"] + + resp_data = self.update_grid_group(grid_group["id"]) + result_message = "Grid Group updated" + + self.module.exit_json(changed=self.na_helper.changed, msg=result_message, resp=resp_data) + + +def main(): + """ + Main function + """ + na_sg_grid_group = SgGridGroup() + na_sg_grid_group.apply() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/netapp/storagegrid/plugins/modules/na_sg_grid_ha_group.py b/ansible_collections/netapp/storagegrid/plugins/modules/na_sg_grid_ha_group.py new file mode 100644 index 000000000..c99719c6d --- /dev/null +++ b/ansible_collections/netapp/storagegrid/plugins/modules/na_sg_grid_ha_group.py @@ -0,0 +1,334 @@ +#!/usr/bin/python + +# (c) 2022, NetApp Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +"""NetApp StorageGRID - Manage HA Groups""" + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = """ +module: na_sg_grid_ha_group +short_description: Manage high availability (HA) group configuration on StorageGRID. +extends_documentation_fragment: + - netapp.storagegrid.netapp.sg +version_added: '21.10.0' +author: NetApp Ansible Team (@joshedmonds) <ng-ansibleteam@netapp.com> +description: +- Create, Update, Delete HA Groups on NetApp StorageGRID. +options: + state: + description: + - Whether the specified HA Group should exist. + type: str + choices: ['present', 'absent'] + default: present + name: + description: + - Name of the HA Group. + type: str + ha_group_id: + description: + - HA Group ID. + - May be used for modify or delete operation. + type: str + description: + description: + - Description of the HA Group. + type: str + gateway_cidr: + description: + - CIDR for the gateway IP and VIP subnet. + type: str + virtual_ips: + description: + - A list of virtual IP addresses. + type: list + elements: str + interfaces: + description: + - A set of StorageGRID node interface pairs. + - The primary interface is specified first, followed by the other interface pairs in failover order. + type: list + elements: dict + suboptions: + node: + description: + - Name of the StorageGRID node. + type: str + interface: + description: + - The interface to bind to. eth0 corresponds to the Grid Network, eth1 to the Admin Network, and eth2 to the Client Network. + type: str +""" + +EXAMPLES = """ + - name: create HA Group + netapp.storagegrid.na_sg_grid_ha_group: + api_url: "https://<storagegrid-endpoint-url>" + auth_token: "storagegrid-auth-token" + validate_certs: false + state: present + name: Site1-HA-Group + description: "Site 1 HA Group" + gateway_cidr: 192.168.50.1/24 + virtual_ips: 192.168.50.5 + interfaces: + - node: SITE1-ADM1 + interface: eth2 + - node: SITE1-G1 + interface: eth2 + + - name: add VIP to HA Group + netapp.storagegrid.na_sg_grid_ha_group: + api_url: "https://<storagegrid-endpoint-url>" + auth_token: "storagegrid-auth-token" + validate_certs: false + state: present + name: Site1-HA-Group + description: "Site 1 HA Group" + gateway_cidr: 192.168.50.1/24 + virtual_ips: 192.168.50.5,192.168.50.6 + interfaces: + - node: SITE1-ADM1 + interface: eth2 + - node: SITE1-G1 + interface: eth2 + + - name: rename HA Group + netapp.storagegrid.na_sg_grid_ha_group: + api_url: "https://<storagegrid-endpoint-url>" + auth_token: "storagegrid-auth-token" + validate_certs: false + state: present + ha_group_id: 00000000-0000-0000-0000-000000000000 + name: Site1-HA-Group-New-Name + description: "Site 1 HA Group" + gateway_cidr: 192.168.50.1/24 + virtual_ips: 192.168.50.5 + interfaces: + - node: SITE1-ADM1 + interface: eth2 + - node: SITE1-G1 + interface: eth2 + + - name: delete HA Group + netapp.storagegrid.na_sg_grid_ha_group: + api_url: "https://<storagegrid-endpoint-url>" + auth_token: "storagegrid-auth-token" + validate_certs: false + state: absent + name: Site1-HA-Group +""" + +RETURN = """ +resp: + description: Returns information about the StorageGRID HA Group. + returned: success + type: dict + sample: { + "description": "Site 1 HA Group", + "gatewayCidr": "192.168.50.1/24", + "id": "bb386f30-805d-4fec-a2c5-85790b460db0", + "interfaces": [ + { + "interface": "eth2", + "nodeId": "0b1866ed-d6e7-41b4-815f-bf867348b76b" + }, + { + "interface": "eth2", + "nodeId": "7bb5bf05-a04c-4344-8abd-08c5c4048666" + } + ], + "name": "Site1-HA-Group", + "virtualIps": [ + "192.168.50.5", + "192.168.50.6" + ] + } +""" + +import json + +import ansible_collections.netapp.storagegrid.plugins.module_utils.netapp as netapp_utils +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp_module import NetAppModule +from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp import SGRestAPI + + +class SgGridHaGroup: + """ + Create, modify and delete HA Group configurations for StorageGRID + """ + + def __init__(self): + """ + Parse arguments, setup state variables, + check parameters and ensure request module is installed + """ + self.argument_spec = netapp_utils.na_storagegrid_host_argument_spec() + self.argument_spec.update( + dict( + state=dict(required=False, type="str", choices=["present", "absent"], default="present"), + name=dict(required=False, type="str"), + ha_group_id=dict(required=False, type="str"), + description=dict(required=False, type="str"), + gateway_cidr=dict(required=False, type="str"), + virtual_ips=dict(required=False, type="list", elements="str"), + interfaces=dict( + required=False, + type="list", + elements="dict", + options=dict( + node=dict(required=False, type="str"), + interface=dict(required=False, type="str"), + ), + ), + ) + ) + + parameter_map = { + "name": "name", + "description": "description", + "gateway_cidr": "gatewayCidr", + "virtual_ips": "virtualIps", + } + + self.module = AnsibleModule( + argument_spec=self.argument_spec, + required_if=[("state", "present", ["name", "gateway_cidr", "virtual_ips", "interfaces"])], + required_one_of=[("name", "ha_group_id")], + ) + + self.na_helper = NetAppModule() + + # set up state variables + self.parameters = self.na_helper.set_parameters(self.module.params) + # Calling generic SG rest_api class + self.rest_api = SGRestAPI(self.module) + # Checking for the parameters passed and create new parameters list + self.data = {} + + if self.parameters["state"] == "present": + for k in parameter_map.keys(): + if self.parameters.get(k) is not None: + self.data[parameter_map[k]] = self.parameters[k] + + if self.parameters.get("interfaces") is not None: + self.data["interfaces"] = self.build_node_interface_list() + + def build_node_interface_list(self): + node_interfaces = [] + + api = "api/v3/grid/node-health" + nodes, error = self.rest_api.get(api) + + if error: + self.module.fail_json(msg=error) + + for node_interface in self.parameters["interfaces"]: + node_dict = {} + node = next((item for item in nodes["data"] if item["name"] == node_interface["node"]), None) + if node is not None: + node_dict["nodeId"] = node["id"] + node_dict["interface"] = node_interface["interface"] + node_interfaces.append(node_dict) + else: + self.module.fail_json(msg="Node '%s' is invalid" % node_interface["node"]) + + return node_interfaces + + def get_ha_group_id(self): + # Check if HA Group exists + # Return HA Group info if found, or None + api = "api/v3/private/ha-groups" + response, error = self.rest_api.get(api) + + if error: + self.module.fail_json(msg=error) + + return next((item["id"] for item in response.get("data") if item["name"] == self.parameters["name"]), None) + + def get_ha_group(self, ha_group_id): + api = "api/v3/private/ha-groups/%s" % ha_group_id + response, error = self.rest_api.get(api) + + if error: + self.module.fail_json(msg=error) + + return response["data"] + + def create_ha_group(self): + api = "api/v3/private/ha-groups" + response, error = self.rest_api.post(api, self.data) + + if error: + self.module.fail_json(msg=error) + + return response["data"] + + def delete_ha_group(self, ha_group_id): + api = "api/v3/private/ha-groups/%s" % ha_group_id + dummy, error = self.rest_api.delete(api, self.data) + + if error: + self.module.fail_json(msg=error) + + def update_ha_group(self, ha_group_id): + api = "api/v3/private/ha-groups/%s" % ha_group_id + response, error = self.rest_api.put(api, self.data) + + if error: + self.module.fail_json(msg=error) + + return response["data"] + + def apply(self): + """ + Perform pre-checks, call functions and exit + """ + + ha_group = None + + if self.parameters.get("ha_group_id"): + ha_group = self.get_ha_group(self.parameters["ha_group_id"]) + else: + ha_group_id = self.get_ha_group_id() + if ha_group_id: + ha_group = self.get_ha_group(ha_group_id) + + cd_action = self.na_helper.get_cd_action(ha_group, self.parameters) + + if cd_action is None and self.parameters["state"] == "present": + # let's see if we need to update parameters + modify = self.na_helper.get_modified_attributes(ha_group, self.data) + + result_message = "" + resp_data = {} + + if self.na_helper.changed and not self.module.check_mode: + if cd_action == "delete": + self.delete_ha_group(ha_group["id"]) + result_message = "HA Group deleted" + elif cd_action == "create": + resp_data = self.create_ha_group() + result_message = "HA Group created" + elif modify: + resp_data = self.update_ha_group(ha_group["id"]) + result_message = "HA Group updated" + + self.module.exit_json(changed=self.na_helper.changed, msg=result_message, resp=resp_data) + + +def main(): + """ + Main function + """ + na_sg_grid_ha_group = SgGridHaGroup() + na_sg_grid_ha_group.apply() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/netapp/storagegrid/plugins/modules/na_sg_grid_identity_federation.py b/ansible_collections/netapp/storagegrid/plugins/modules/na_sg_grid_identity_federation.py new file mode 100644 index 000000000..729cf4545 --- /dev/null +++ b/ansible_collections/netapp/storagegrid/plugins/modules/na_sg_grid_identity_federation.py @@ -0,0 +1,335 @@ +#!/usr/bin/python + +# (c) 2021, NetApp Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +"""NetApp StorageGRID - Manage Grid Identity Federation""" + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + + +DOCUMENTATION = """ +module: na_sg_grid_identity_federation +short_description: NetApp StorageGRID manage Grid identity federation. +extends_documentation_fragment: + - netapp.storagegrid.netapp.sg +version_added: '21.6.0' +author: NetApp Ansible Team (@joshedmonds) <ng-ansibleteam@netapp.com> +description: +- Configure Grid Identity Federation within NetApp StorageGRID. +- If module is run with I(check_mode), a connectivity test will be performed using the supplied values without changing the configuration. +- This module is idempotent if I(password) is not specified. +options: + state: + description: + - Whether identity federation should be enabled or not. + type: str + choices: ['present', 'absent'] + default: present + username: + description: + - The username to bind to the LDAP server. + type: str + password: + description: + - The password associated with the username. + type: str + hostname: + description: + - The hostname or IP address of the LDAP server. + type: str + port: + description: + - The port used to connect to the LDAP server. Typically 389 for LDAP, or 636 for LDAPS. + type: int + base_group_dn: + description: + - The Distinguished Name of the LDAP subtree to search for groups. + type: str + base_user_dn: + description: + - The Distinguished Name of the LDAP subtree to search for users. + type: str + ldap_service_type: + description: + - The type of LDAP server. + choices: ['Active Directory', 'OpenLDAP', 'Other'] + type: str + type: + description: + - The type of identity source. + - Default is C(ldap). + type: str + default: ldap + ldap_user_id_attribute: + description: + - The LDAP attribute which contains the unique user name of a user. + - Should be configured if I(ldap_service_type=Other). + type: str + ldap_user_uuid_attribute: + description: + - The LDAP attribute which contains the permanent unique identity of a user. + - Should be configured if I(ldap_service_type=Other). + type: str + ldap_group_id_attribute: + description: + - The LDAP attribute which contains the group for a user. + - Should be configured if I(ldap_service_type=Other). + type: str + ldap_group_uuid_attribute: + description: + - The LDAP attribute which contains the group's permanent unique identity. + - Should be configured if I(ldap_service_type=Other). + type: str + tls: + description: + - Whether Transport Layer Security is used to connect to the LDAP server. + choices: ['STARTTLS', 'LDAPS', 'Disabled'] + type: str + default: STARTTLS + ca_cert: + description: + - Custom certificate used to connect to the LDAP server. + - If a custom certificate is not supplied, the operating system CA certificate will be used. + type: str +""" + +EXAMPLES = """ + - name: test identity federation configuration + netapp.storagegrid.na_sg_grid_identity_federation: + api_url: "https://<storagegrid-endpoint-url>" + auth_token: "storagegrid-auth-token" + validate_certs: false + state: present + ldap_service_type: "Active Directory" + hostname: "ad.example.com" + port: 389 + username: "binduser" + password: "bindpass" + base_group_dn: "DC=example,DC=com" + base_user_dn: "DC=example,DC=com" + tls: "Disabled" + check_mode: yes + + - name: configure identity federation with AD and TLS + netapp.storagegrid.na_sg_grid_identity_federation: + api_url: "https://<storagegrid-endpoint-url>" + auth_token: "storagegrid-auth-token" + validate_certs: false + state: present + ldap_service_type: "Active Directory" + hostname: "ad.example.com" + port: 636 + username: "binduser" + password: "bindpass" + base_group_dn: "DC=example,DC=com" + base_user_dn: "DC=example,DC=com" + tls: "LDAPS" + ca_cert: | + -----BEGIN CERTIFICATE----- + MIIC+jCCAeICCQDmn9Gow08LTzANBgkqhkiG9w0BAQsFADA/..swCQYDVQQGEwJV + bXBsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB..JFzNIXQEGnsgjV + JGU4giuvOLOZ8Q3gyuUbkSUQDjmjpMR8PliwJ6iW2Ity89Dv..dl1TaIYI/ansyZ + Uxk4YXeN6kUkrDtNxCg1McALzXVAfxMTtj2SFlLxne4Z6rX2..UyftQrfM13F1vY + gK8dBPz+l+X/Uozo/xNm7gxe68p9le9/pcULst1CQn5/sPqq..kgWcSvlKUItu82 + lq3B2169rovdIaNdcvaQjMPhrDGo5rvLfMN35U3Hgbz41PL5..x2BcUE6/0ab5T4 + qKBxKa3t9twj+zpUqOzyL0PFfCE+SK5fEXAS1ow4eAcLN+eB..gR/PuvGAyIPCtE + 1+X4GrECAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAFpO+04Ra..FMJPH6dBmzfb7l + k04BWTvSlur6HiQdXY+oFQMJZzyI7MQ8v9HBIzS0ZAzYWLp4..VZhHmRxnrWyxVs + u783V5YfQH2L4QnBDoiDefgxyfDs2PcoF5C+X9CGXmPqzst2..y/6tdOVJzdiA== + -----END CERTIFICATE----- +""" + +RETURN = """ +resp: + description: Returns information about the StorageGRID management identity source configuration. + returned: success + type: dict + sample: { + "id": "00000000-0000-0000-0000-000000000000", + "disable": false, + "hostname": "10.1.2.3", + "port": 389, + "username": "MYDOMAIN\\\\Administrator", + "password": "********", + "baseGroupDn": "DC=example,DC=com", + "baseUserDn": "DC=example,DC=com", + "ldapServiceType": "Active Directory", + "type": "ldap", + "disableTLS": false, + "enableLDAPS": false, + "caCert": "-----BEGIN CERTIFICATE----- abcdefghijkl123456780ABCDEFGHIJKL 123456/7890ABCDEFabcdefghijklABCD -----END CERTIFICATE-----\n" + } +""" + +import json +import re + +import ansible_collections.netapp.storagegrid.plugins.module_utils.netapp as netapp_utils +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp_module import NetAppModule +from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp import SGRestAPI + + +class SgGridIdentityFederation: + """ + Configure and modify StorageGRID Grid Identity Federation + """ + + def __init__(self): + """ + Parse arguments, setup state variables, + check parameters and ensure request module is installed + """ + self.argument_spec = netapp_utils.na_storagegrid_host_argument_spec() + self.argument_spec.update( + dict( + state=dict(required=False, type="str", choices=["present", "absent"], default="present"), + username=dict(required=False, type="str"), + password=dict(required=False, type="str", no_log=True), + hostname=dict(required=False, type="str"), + port=dict(required=False, type="int"), + base_group_dn=dict(required=False, type="str"), + base_user_dn=dict(required=False, type="str"), + ldap_service_type=dict(required=False, type="str", choices=["OpenLDAP", "Active Directory", "Other"]), + type=dict(required=False, type="str", default="ldap"), + ldap_user_id_attribute=dict(required=False, type="str"), + ldap_user_uuid_attribute=dict(required=False, type="str"), + ldap_group_id_attribute=dict(required=False, type="str"), + ldap_group_uuid_attribute=dict(required=False, type="str"), + tls=dict(required=False, type="str", choices=["STARTTLS", "LDAPS", "Disabled"], default="STARTTLS"), + ca_cert=dict(required=False, type="str"), + ), + ) + + parameter_map = { + "username": "username", + "password": "password", + "hostname": "hostname", + "port": "port", + "base_group_dn": "baseGroupDn", + "base_user_dn": "baseUserDn", + "ldap_service_type": "ldapServiceType", + "ldap_user_id_attribute": "ldapUserIdAttribute", + "ldap_user_uuid_attribute": "ldapUserUUIDAttribute", + "ldap_group_id_attribute": "ldapGroupIdAttribute", + "ldap_group_uuid_attribute": "ldapGroupUUIDAttribute", + "ca_cert": "caCert", + } + self.module = AnsibleModule(argument_spec=self.argument_spec, supports_check_mode=True,) + + self.na_helper = NetAppModule() + + # set up state variables + self.parameters = self.na_helper.set_parameters(self.module.params) + # Calling generic SG rest_api class + self.rest_api = SGRestAPI(self.module) + # Checking for the parameters passed and create new parameters list + self.data = {} + + if self.parameters["state"] == "present": + self.data["disable"] = False + + for k in parameter_map.keys(): + if self.parameters.get(k) is not None: + self.data[parameter_map[k]] = self.parameters[k] + + if self.parameters.get("tls") == "STARTTLS": + self.data["disableTLS"] = False + self.data["enableLDAPS"] = False + elif self.parameters.get("tls") == "LDAPS": + self.data["disableTLS"] = False + self.data["enableLDAPS"] = True + else: + self.data["disableTLS"] = True + self.data["enableLDAPS"] = False + + def get_grid_identity_source(self): + api = "api/v3/grid/identity-source" + response, error = self.rest_api.get(api) + + if error: + self.module.fail_json(msg=error) + else: + return response["data"] + return None + + def update_identity_federation(self, test=False): + api = "api/v3/grid/identity-source" + + params = {} + + if test: + params["test"] = True + + response, error = self.rest_api.put(api, self.data, params=params) + if error: + self.module.fail_json(msg=error, payload=self.data) + + if response is not None: + return response["data"] + else: + return None + + def apply(self): + """ + Perform pre-checks, call functions and exit + """ + grid_identity_source = self.get_grid_identity_source() + + cd_action = self.na_helper.get_cd_action(grid_identity_source, self.parameters) + + if cd_action is None and self.parameters["state"] == "present": + # let's see if we need to update parameters + update = False + + for k in (i for i in self.data.keys() if i != "password"): + if self.data[k] != grid_identity_source.get(k): + update = True + break + + # if a password has been specified we need to update it + if self.data.get("password") and self.parameters["state"] == "present": + update = True + self.module.warn("Password attribute has been specified. Task is not idempotent.") + + if update: + self.na_helper.changed = True + + if cd_action == "delete": + # if identity federation is already in a disable state + if grid_identity_source.get("disable"): + self.na_helper.changed = False + + result_message = "" + resp_data = grid_identity_source + + if self.na_helper.changed and not self.module.check_mode: + if cd_action == "delete": + self.data = dict(disable=True) + resp_data = self.update_identity_federation() + result_message = "Grid identity federation disabled" + else: + resp_data = self.update_identity_federation() + result_message = "Grid identity federation updated" + + if self.module.check_mode: + self.update_identity_federation(test=True) + # if no error, connection test successful + self.module.exit_json(changed=self.na_helper.changed, msg="Connection test successful") + + self.module.exit_json(changed=self.na_helper.changed, msg=result_message, resp=resp_data) + + +def main(): + """ + Main function + """ + na_sg_grid_identity_federation = SgGridIdentityFederation() + na_sg_grid_identity_federation.apply() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/netapp/storagegrid/plugins/modules/na_sg_grid_info.py b/ansible_collections/netapp/storagegrid/plugins/modules/na_sg_grid_info.py new file mode 100644 index 000000000..b14f88a22 --- /dev/null +++ b/ansible_collections/netapp/storagegrid/plugins/modules/na_sg_grid_info.py @@ -0,0 +1,405 @@ +#!/usr/bin/python + +# (c) 2020, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +""" NetApp StorageGRID Grid Info using REST APIs """ + + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community'} + +DOCUMENTATION = """ +module: na_sg_grid_info +author: NetApp Ansible Team (@jasonl4) <ng-ansibleteam@netapp.com> +extends_documentation_fragment: + - netapp.storagegrid.netapp.sg +short_description: NetApp StorageGRID Grid information gatherer. +description: + - This module allows you to gather various information about StorageGRID Grid configuration. +version_added: 20.11.0 + +options: + gather_subset: + type: list + elements: str + description: + - When supplied, this argument will restrict the information collected to a given subset. + - Either the info name or the REST API can be given. + - Possible values for this argument include + - C(grid_accounts_info) or C(grid/accounts) + - C(grid_alarms_info) or C(grid/alarms) + - C(grid_audit_info) or C(grid/audit) + - C(grid_compliance_global_info) or C(grid/compliance-global) + - C(grid_config_info) or C(grid/config) + - C(grid_config_management_info) or C(grid/config/management) + - C(grid_config_product_version_info) or C(grid/config/product-version) + - C(grid_deactivated_features_info) or C(grid/deactivated-features) + - C(grid_dns_servers_info) or C(grid/dns-servers) + - C(grid_domain_names_info) or C(grid/domain-names) + - C(grid_ec_profiles_info) or C(grid/ec-profiles) + - C(grid_expansion_info) or C(grid/expansion) + - C(grid_expansion_nodes_info) or C(grid/expansion/nodes) + - C(grid_expansion_sites_info) or C(grid/expansion/sites) + - C(grid_grid_networks_info) or C(grid/grid-networks) + - C(grid_groups_info) or C(grid/groups) + - C(grid_health_info) or C(grid/health) + - C(grid_health_topology_info) or C(grid/health/topology) + - C(grid_identity_source_info) or C(grid/identity-source) + - C(grid_ilm_criteria_info) or C(grid/ilm-criteria) + - C(grid_ilm_policies_info) or C(grid/ilm-policies) + - C(grid_ilm_rules_info) or C(grid/ilm-rules) + - C(grid_license_info) or C(grid/license) + - C(grid_management_certificate_info) or C(grid/management-certificate) + - C(grid_ntp_servers_info) or C(grid/ntp-servers) + - C(grid_recovery_available_nodes_info) or C(grid/recovery/available-nodes) + - C(grid_recovery_info) or C(grid/recovery) + - C(grid_regions_info) or C(grid/regions) + - C(grid_schemes_info) or C(grid/schemes) + - C(grid_snmp_info) or C(grid/snmp) + - C(grid_storage_api_certificate_info) or C(grid/storage-api-certificate) + - C(grid_untrusted_client_network_info) or C(grid/untrusted-client-network) + - C(grid_users_info) or C(grid/users) + - C(grid_users_root_info) or C(grid/users/root) + - C(versions_info) or C(versions) + - Can specify a list of values to include a larger subset. + default: all + parameters: + description: + - Allows for any rest option to be passed in. + type: dict +""" + +EXAMPLES = """ +- name: Gather StorageGRID Grid info + netapp.storagegrid.na_sg_grid_info: + api_url: "https://1.2.3.4/" + auth_token: "storagegrid-auth-token" + validate_certs: false + register: sg_grid_info + +- name: Gather StorageGRID Grid info for grid/accounts and grid/config subsets + netapp.storagegrid.na_sg_grid_info: + api_url: "https://1.2.3.4/" + auth_token: "storagegrid-auth-token" + validate_certs: false + gather_subset: + - grid_accounts_info + - grid/config + register: sg_grid_info + +- name: Gather StorageGRID Grid info for all subsets + netapp.storagegrid.na_sg_grid_info: + api_url: "https://1.2.3.4/" + auth_token: "storagegrid-auth-token" + validate_certs: false + gather_subset: + - all + register: sg_grid_info + +- name: Gather StorageGRID Grid info for grid/accounts and grid/users subsets, limit to 5 results for each subset + netapp.storagegrid.na_sg_grid_info: + api_url: "https://1.2.3.4/" + auth_token: "storagegrid-auth-token" + validate_certs: false + gather_subset: + - grid/accounts + - grid/users + parameters: + limit: 5 + register: sg_grid_info +""" + +RETURN = """ +sg_info: + description: Returns various information about the StorageGRID Grid configuration. + returned: always + type: dict + sample: { + "grid/accounts": {...}, + "grid/alarms": {...}, + "grid/audit": {...}, + "grid/compliance-global": {...}, + "grid/config": {...}, + "grid/config/management": {...}, + "grid/config/product-version": {...}, + "grid/deactivated-features": {...}, + "grid/dns-servers": {...}, + "grid/domain-names": {...}, + "grid/ec-profiles": {...}, + "grid/expansion": {...}, + "grid/expansion/nodes": {...}, + "grid/expansion/sites": {...}, + "grid/networks": {...}, + "grid/groups": {...}, + "grid/health": {...}, + "grid/health/topology": {...}, + "grid/identity-source": {...}, + "grid/ilm-criteria": {...}, + "grid/ilm-policies": {...}, + "grid/ilm-rules": {...}, + "grid/license": {...}, + "grid/management-certificate": {...}, + "grid/ntp-servers": {...}, + "grid/recovery/available-nodes": {...}, + "grid/recovery": {...}, + "grid/regions": {...}, + "grid/schemes": {...}, + "grid/snmp": {...}, + "grid/storage-api-certificate": {...}, + "grid/untrusted-client-network": {...}, + "grid/users": {...}, + "grid/users/root": {...}, + "grid/versions": {...} + } +""" + +from ansible.module_utils.basic import AnsibleModule +import ansible_collections.netapp.storagegrid.plugins.module_utils.netapp as netapp_utils +from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp_module import NetAppModule +from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp import SGRestAPI + + +class NetAppSgGatherInfo(object): + """ Class with gather info methods """ + + def __init__(self): + """ + Parse arguments, setup variables, check parameters and ensure + request module is installed. + """ + self.argument_spec = netapp_utils.na_storagegrid_host_argument_spec() + self.argument_spec.update(dict( + gather_subset=dict(default=['all'], type='list', elements='str', required=False), + parameters=dict(type='dict', required=False) + )) + + self.module = AnsibleModule( + argument_spec=self.argument_spec, + supports_check_mode=True + ) + + # set up variables + self.na_helper = NetAppModule() + self.parameters = self.na_helper.set_parameters(self.module.params) + self.rest_api = SGRestAPI(self.module) + + def get_subset_info(self, gather_subset_info): + """ + Gather StorageGRID information for the given subset using REST APIs + Input for REST APIs call : (api, data) + return gathered_sg_info + """ + + api = gather_subset_info['api_call'] + data = {} + # allow for passing in any additional rest api parameters + if self.parameters.get('parameters'): + for each in self.parameters['parameters']: + data[each] = self.parameters['parameters'][each] + + gathered_sg_info, error = self.rest_api.get(api, data) + + if error: + self.module.fail_json(msg=error) + else: + return gathered_sg_info + + return None + + def convert_subsets(self): + """ Convert an info to the REST API """ + info_to_rest_mapping = { + 'grid_accounts_info': 'grid/accounts', + 'grid_alarms_info': 'grid/alarms', + 'grid_audit_info': 'grid/audit', + 'grid_compliance_global_info': 'grid/compliance-global', + 'grid_config_info': 'grid/config', + 'grid_config_management_info': 'grid/config/management', + 'grid_config_product_version_info': 'grid/config/product-version', + 'grid_deactivated_features_info': 'grid/deactivated-features', + 'grid_dns_servers_info': 'grid/dns-servers', + 'grid_domain_names_info': 'grid/domain-names', + 'grid_ec_profiles_info': 'grid/ec-profiles', + 'grid_expansion_info': 'grid/expansion', + 'grid_expansion_nodes_info': 'grid/expansion/nodes', + 'grid_expansion_sites_info': 'grid/expansion/sites', + 'grid_grid_networks_info': 'grid/grid-networks', + 'grid_groups_info': 'grid/groups', + 'grid_health_info': 'grid/health', + 'grid_health_topology_info': 'grid/health/topology', + 'grid_identity_source_info': 'grid/identity-source', + 'grid_ilm_criteria_info': 'grid/ilm-criteria', + 'grid_ilm_policies_info': 'grid/ilm-policies', + 'grid_ilm_rules_info': 'grid/ilm-rules', + 'grid_license_info': 'grid/license', + 'grid_management_certificate_info': 'grid/management-certificate', + 'grid_ntp_servers_info': 'grid/ntp-servers', + 'grid_recovery_available_nodes_info': 'grid/recovery/available-nodes', + 'grid_recovery_info': 'grid/recovery', + 'grid_regions_info': 'grid/regions', + 'grid_schemes_info': 'grid/schemes', + 'grid_snmp_info': 'grid/snmp', + 'grid_storage_api_certificate_info': 'grid/storage-api-certificate', + 'grid_untrusted_client_network_info': 'grid/untrusted-client-network', + 'grid_users_info': 'grid/users', + 'grid_users_root_info': 'grid/users/root', + 'versions_info': 'versions', + } + # Add rest API names as there info version, also make sure we don't add a duplicate + subsets = [] + for subset in self.parameters['gather_subset']: + if subset in info_to_rest_mapping: + if info_to_rest_mapping[subset] not in subsets: + subsets.append(info_to_rest_mapping[subset]) + else: + if subset not in subsets: + subsets.append(subset) + return subsets + + def apply(self): + """ Perform pre-checks, call functions and exit """ + + result_message = dict() + + # Defining gather_subset and appropriate api_call + get_sg_subset_info = { + 'grid/accounts': { + 'api_call': 'api/v3/grid/accounts', + }, + 'grid/alarms': { + 'api_call': 'api/v3/grid/alarms', + }, + 'grid/audit': { + 'api_call': 'api/v3/grid/audit', + }, + 'grid/compliance-global': { + 'api_call': 'api/v3/grid/compliance-global', + }, + 'grid/config': { + 'api_call': 'api/v3/grid/config', + }, + 'grid/config/management': { + 'api_call': 'api/v3/grid/config/management', + }, + 'grid/config/product-version': { + 'api_call': 'api/v3/grid/config/product-version', + }, + 'grid/deactivated-features': { + 'api_call': 'api/v3/grid/deactivated-features', + }, + 'grid/dns-servers': { + 'api_call': 'api/v3/grid/dns-servers', + }, + 'grid/domain-names': { + 'api_call': 'api/v3/grid/domain-names', + }, + 'grid/ec-profiles': { + 'api_call': 'api/v3/grid/ec-profiles', + }, + 'grid/expansion': { + 'api_call': 'api/v3/grid/expansion', + }, + 'grid/expansion/nodes': { + 'api_call': 'api/v3/grid/expansion/nodes', + }, + 'grid/expansion/sites': { + 'api_call': 'api/v3/grid/expansion/sites', + }, + 'grid/grid-networks': { + 'api_call': 'api/v3/grid/grid-networks', + }, + 'grid/groups': { + 'api_call': 'api/v3/grid/groups', + }, + 'grid/health': { + 'api_call': 'api/v3/grid/health', + }, + 'grid/health/topology': { + 'api_call': 'api/v3/grid/health/topology', + }, + 'grid/identity-source': { + 'api_call': 'api/v3/grid/identity-source', + }, + 'grid/ilm-criteria': { + 'api_call': 'api/v3/grid/ilm-criteria', + }, + 'grid/ilm-policies': { + 'api_call': 'api/v3/grid/ilm-policies', + }, + 'grid/ilm-rules': { + 'api_call': 'api/v3/grid/ilm-rules', + }, + 'grid/license': { + 'api_call': 'api/v3/grid/license', + }, + 'grid/management-certificate': { + 'api_call': 'api/v3/grid/management-certificate', + }, + 'grid/ntp-servers': { + 'api_call': 'api/v3/grid/ntp-servers', + }, + 'grid/recovery/available-nodes': { + 'api_call': 'api/v3/grid/recovery/available-nodes', + }, + 'grid/recovery': { + 'api_call': 'api/v3/grid/recovery', + }, + 'grid/regions': { + 'api_call': 'api/v3/grid/regions', + }, + 'grid/schemes': { + 'api_call': 'api/v3/grid/schemes', + }, + 'grid/snmp': { + 'api_call': 'api/v3/grid/snmp', + }, + 'grid/storage-api-certificate': { + 'api_call': 'api/v3/grid/storage-api-certificate', + }, + 'grid/untrusted-client-network': { + 'api_call': 'api/v3/grid/untrusted-client-network', + }, + 'grid/users': { + 'api_call': 'api/v3/grid/users', + }, + 'grid/users/root': { + 'api_call': 'api/v3/grid/users/root', + }, + 'versions': { + 'api_call': 'api/v3/versions', + }, + } + + if 'all' in self.parameters['gather_subset']: + # If all in subset list, get the information of all subsets + self.parameters['gather_subset'] = sorted(get_sg_subset_info.keys()) + + converted_subsets = self.convert_subsets() + + for subset in converted_subsets: + try: + # Verify whether the supported subset passed + specified_subset = get_sg_subset_info[subset] + except KeyError: + self.module.fail_json(msg="Specified subset %s not found, supported subsets are %s" % + (subset, list(get_sg_subset_info.keys()))) + + result_message[subset] = self.get_subset_info(specified_subset) + + self.module.exit_json(changed='False', sg_info=result_message) + + +def main(): + """ Main function """ + obj = NetAppSgGatherInfo() + obj.apply() + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/netapp/storagegrid/plugins/modules/na_sg_grid_ntp.py b/ansible_collections/netapp/storagegrid/plugins/modules/na_sg_grid_ntp.py new file mode 100644 index 000000000..0c22ba2c1 --- /dev/null +++ b/ansible_collections/netapp/storagegrid/plugins/modules/na_sg_grid_ntp.py @@ -0,0 +1,173 @@ +#!/usr/bin/python + +# (c) 2020, NetApp Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +"""NetApp StorageGRID - Manage Grid NTP Servers""" + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["preview"], + "supported_by": "community", +} + + +DOCUMENTATION = """ +module: na_sg_grid_ntp +short_description: NetApp StorageGRID manage external NTP servers for the grid. +extends_documentation_fragment: + - netapp.storagegrid.netapp.sg +version_added: '20.6.0' +author: NetApp Ansible Team (@jkandati) <ng-sg-ansibleteam@netapp.com> +description: +- Update NTP server on NetApp StorageGRID. +options: + state: + description: + - Whether the specified user should exist or not. + type: str + choices: ['present'] + default: present + ntp_servers: + description: + - List of comma separated NTP server address. + type: list + elements: str + required: true + passphrase: + description: + - passphrase for GRID. + type: str + required: true +""" + +EXAMPLES = """ + - name: update NTP servers + netapp.storagegrid.na_sg_grid_ntp: + api_url: "https://<storagegrid-endpoint-url>" + auth_token: "storagegrid-auth-token" + validate_certs: false + state: present + passphrase: "{{ grid_pass }}" + ntp_servers: "x.x.x.x,xx.x.xx.x" +""" + +RETURN = """ +resp: + description: Returns information about the configured NTP servers. + returned: success + type: list + elements: str + sample: ["10.0.0.1", "10.0.0.2", "10.0.0.3", "10.0.0.4"] +""" + +import ansible_collections.netapp.storagegrid.plugins.module_utils.netapp as netapp_utils +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp_module import NetAppModule +from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp import SGRestAPI + + +class SgGridNtp(object): + """ + Create, modify and delete NTP entries for StorageGRID + """ + + def __init__(self): + """ + Parse arguments, setup state variables, + check parameters and ensure request module is installed + """ + self.argument_spec = netapp_utils.na_storagegrid_host_argument_spec() + self.argument_spec.update( + dict( + state=dict(required=False, type="str", choices=["present"], default="present"), + ntp_servers=dict(required=True, type="list", elements="str"), + passphrase=dict(required=True, type="str", no_log=True), + ) + ) + + self.module = AnsibleModule( + argument_spec=self.argument_spec, + # required_if=[("state", "present", ["state", "name", "protocol"])], + supports_check_mode=True, + ) + + self.na_helper = NetAppModule() + + # set up state variables + self.parameters = self.na_helper.set_parameters(self.module.params) + # Calling generic SG rest_api class + self.rest_api = SGRestAPI(self.module) + # Checking for the parameters passed and create new parameters list + self.data = self.parameters["ntp_servers"] + self.passphrase = self.parameters["passphrase"] + self.ntp_input = {"passphrase": self.passphrase, "servers": self.data} + + def get_grid_ntp(self): + # Check if tenant account exists + # Return tenant account info if found, or None + api = "api/v3/grid/ntp-servers" + + response, error = self.rest_api.get(api) + + if error: + self.module.fail_json(msg=error) + + return response["data"] + + def update_grid_ntp(self): + api = "api/v3/grid/ntp-servers/update" + + response, error = self.rest_api.post(api, self.ntp_input) + + if error: + self.module.fail_json(msg=error) + + return response["data"] + + def apply(self): + """ + Perform pre-checks, call functions and exit + """ + grid_ntp = self.get_grid_ntp() + + cd_action = self.na_helper.get_cd_action(grid_ntp, self.parameters["ntp_servers"]) + + if cd_action is None and self.parameters["state"] == "present": + # let's see if we need to update parameters + update = False + + ntp_diff = [i for i in self.data + grid_ntp if i not in self.data or i not in grid_ntp] + if ntp_diff: + update = True + + if update: + self.na_helper.changed = True + + result_message = "" + resp_data = grid_ntp + if self.na_helper.changed: + if self.module.check_mode: + pass + else: + resp_data = self.update_grid_ntp() + result_message = "Grid NTP updated" + + self.module.exit_json(changed=self.na_helper.changed, msg=result_message, resp=resp_data) + + +def main(): + """ + Main function + """ + na_sg_grid_ntp = SgGridNtp() + na_sg_grid_ntp.apply() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/netapp/storagegrid/plugins/modules/na_sg_grid_regions.py b/ansible_collections/netapp/storagegrid/plugins/modules/na_sg_grid_regions.py new file mode 100644 index 000000000..58179cf03 --- /dev/null +++ b/ansible_collections/netapp/storagegrid/plugins/modules/na_sg_grid_regions.py @@ -0,0 +1,163 @@ +#!/usr/bin/python + +# (c) 2020, NetApp Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +"""NetApp StorageGRID - Manage Grid Regions""" + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["preview"], + "supported_by": "community", +} + + +DOCUMENTATION = """ +module: na_sg_grid_regions +short_description: NetApp StorageGRID manage Regions for the grid. +extends_documentation_fragment: + - netapp.storagegrid.netapp.sg +version_added: '20.6.0' +author: NetApp Ansible Team (@joshedmonds) <ng-ansibleteam@netapp.com> +description: +- Create, Update, Delete Users within a NetApp StorageGRID tenant. +options: + state: + description: + - Whether the specified user should exist or not. + type: str + choices: ['present'] + default: present + regions: + description: + - List of regions + required: true + type: list + elements: str +""" + +EXAMPLES = """ + - name: update Regions + netapp.storagegrid.na_sg_grid_regions: + api_url: "https://<storagegrid-endpoint-url>" + auth_token: "storagegrid-auth-token" + validate_certs: false + state: present + regions: "us-east-1" +""" + +RETURN = """ +resp: + description: Returns information about the configured regions. + returned: success + type: list + elements: str + sample: ["us-east-1", "us-central-1"] +""" + +import ansible_collections.netapp.storagegrid.plugins.module_utils.netapp as netapp_utils +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp_module import NetAppModule +from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp import SGRestAPI + + +class SgGridRegions(object): + """ + Create, modify and delete Regions for StorageGRID + """ + + def __init__(self): + """ + Parse arguments, setup state variables, + check parameters and ensure request module is installed + """ + self.argument_spec = netapp_utils.na_storagegrid_host_argument_spec() + self.argument_spec.update( + dict( + state=dict(required=False, type="str", choices=["present"], default="present"), + regions=dict(required=True, type="list", elements="str"), + ) + ) + + self.module = AnsibleModule( + argument_spec=self.argument_spec, + # required_if=[("state", "present", ["state", "name", "protocol"])], + supports_check_mode=True, + ) + + self.na_helper = NetAppModule() + + # set up state variables + self.parameters = self.na_helper.set_parameters(self.module.params) + # Calling generic SG rest_api class + self.rest_api = SGRestAPI(self.module) + # Checking for the parameters passed and create new parameters list + self.data = self.parameters["regions"] + + def get_grid_regions(self): + # Check if tenant account exists + # Return tenant account info if found, or None + api = "api/v3/grid/regions" + + response, error = self.rest_api.get(api) + + if error: + self.module.fail_json(msg=error) + + return response["data"] + + def update_grid_regions(self): + api = "api/v3/grid/regions" + + response, error = self.rest_api.put(api, self.data) + if error: + self.module.fail_json(msg=error) + + return response["data"] + + def apply(self): + """ + Perform pre-checks, call functions and exit + """ + grid_regions = self.get_grid_regions() + + cd_action = self.na_helper.get_cd_action(grid_regions, self.parameters["regions"]) + + if cd_action is None and self.parameters["state"] == "present": + # let's see if we need to update parameters + update = False + + regions_diff = [i for i in self.data + grid_regions if i not in self.data or i not in grid_regions] + if regions_diff: + update = True + + if update: + self.na_helper.changed = True + + result_message = "" + resp_data = grid_regions + if self.na_helper.changed: + if self.module.check_mode: + pass + else: + resp_data = self.update_grid_regions() + result_message = "Grid Regions updated" + + self.module.exit_json(changed=self.na_helper.changed, msg=result_message, resp=resp_data) + + +def main(): + """ + Main function + """ + na_sg_grid_regions = SgGridRegions() + na_sg_grid_regions.apply() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/netapp/storagegrid/plugins/modules/na_sg_grid_traffic_classes.py b/ansible_collections/netapp/storagegrid/plugins/modules/na_sg_grid_traffic_classes.py new file mode 100644 index 000000000..9901a3e00 --- /dev/null +++ b/ansible_collections/netapp/storagegrid/plugins/modules/na_sg_grid_traffic_classes.py @@ -0,0 +1,375 @@ +#!/usr/bin/python + +# (c) 2022, NetApp Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +"""NetApp StorageGRID - Manage Traffic Classification Policies""" + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = """ +module: na_sg_grid_traffic_classes +short_description: Manage Traffic Classification Policy configuration on StorageGRID. +extends_documentation_fragment: + - netapp.storagegrid.netapp.sg +version_added: '21.10.0' +author: NetApp Ansible Team (@joshedmonds) <ng-ansibleteam@netapp.com> +description: +- Create, Update, Delete Traffic Classification Policies on NetApp StorageGRID. +options: + state: + description: + - Whether the specified Traffic Classification Policy should exist. + type: str + choices: ['present', 'absent'] + default: present + name: + description: + - Name of the Traffic Classification Policy. + type: str + policy_id: + description: + - Traffic Classification Policy ID. + - May be used for modify or delete operation. + type: str + description: + description: + - Description of the Traffic Classification Policy. + type: str + matchers: + description: + - A set of parameters to match. + - The traffic class will match requests where any of these matchers match. + type: list + elements: dict + suboptions: + type: + description: + - The attribute of the request to match. + - C(bucket) - The S3 bucket (or Swift container) being accessed. + - C(bucket-regex) - A regular expression to evaluate against the S3 bucket (or Swift container) being accessed. + - C(cidr) - Matches if the client request source IP is in the specified IPv4 CIDR (RFC4632). + - C(tenant) - Matches if the S3 bucket (or Swift container) is owned by the tenant account with this ID. + choices: ['bucket', 'bucket-regex', 'cidr', 'endpoint', 'tenant'] + type: str + required: true + inverse: + description: + - If I(true), entities that match the value are excluded. + type: bool + default: false + members: + description: + - A list of members to match on. + type: list + elements: str + required: true + limits: + description: + - Optional limits to impose on client requests matched by this traffic class. + - Only one of each limit type can be specified. + type: list + elements: dict + suboptions: + type: + description: + - The type of limit to apply. + - C(aggregateBandwidthIn) - The maximum combined upload bandwidth in bytes/second of all concurrent requests that match this policy. + - C(aggregateBandwidthOut) - The maximum combined download bandwidth in bytes/second of all concurrent requests that match this policy. + - C(concurrentReadRequests) - The maximum number of download requests that can be in progress at the same time. + - C(concurrentWriteRequests) - The maximum number of upload requests that can be in progress at the same time. + - C(readRequestRate) - The maximum number of download requests that can be started each second. + - C(writeRequestRate) - The maximum number of download requests that can be started each second. + - C(perRequestBandwidthIn) - The maximum upload bandwidth in bytes/second allowed for each request that matches this policy. + - C(perRequestBandwidthOut) - The maximum download bandwidth in bytes/second allowed for each request that matches this policy. + choices: [ + 'aggregateBandwidthIn', + 'aggregateBandwidthOut', + 'concurrentReadRequests', + 'concurrentWriteRequests', + 'readRequestRate', + 'writeRequestRate', + 'perRequestBandwidthIn', + 'perRequestBandwidthOut' + ] + type: str + required: true + value: + description: + - The limit to apply. + - Limit values are type specific. + type: int + required: true +""" + +EXAMPLES = """ + - name: create Traffic Classification Policy with bandwidth limit on buckets + netapp.storagegrid.na_sg_grid_traffic_classes: + api_url: "https://<storagegrid-endpoint-url>" + auth_token: "storagegrid-auth-token" + validate_certs: false + state: present + name: Traffic-Policy1 + matchers: + - type: bucket + members: bucket1,anotherbucket + limits: + - type: aggregateBandwidthOut + value: 100000000 + + - name: create Traffic Classification Policy with bandwidth limits except for specific tenant account + netapp.storagegrid.na_sg_grid_traffic_classes: + api_url: "https://<storagegrid-endpoint-url>" + auth_token: "storagegrid-auth-token" + validate_certs: false + state: present + name: Fabricpool-Policy + description: "Limit all to 500MB/s except FabricPool tenant" + matchers: + - type: tenant + inverse: True + members: 12345678901234567890 + limits: + - type: aggregateBandwidthIn + value: 50000000 + - type: aggregateBandwidthOut + value: 50000000 + + - name: rename Traffic Classification Policy + netapp.storagegrid.na_sg_grid_traffic_classes: + api_url: "https://<storagegrid-endpoint-url>" + auth_token: "storagegrid-auth-token" + validate_certs: false + state: present + policy_id: 00000000-0000-0000-0000-000000000000 + name: Traffic-Policy1-New-Name + matchers: + - type: bucket + members: bucket1,anotherbucket + limits: + - type: aggregateBandwidthOut + value: 100000000 + + - name: delete Traffic Classification Policy + netapp.storagegrid.na_sg_grid_traffic_classes: + api_url: "https://<storagegrid-endpoint-url>" + auth_token: "storagegrid-auth-token" + validate_certs: false + state: absent + name: Traffic-Policy1 +""" + +RETURN = """ +resp: + description: Returns information about the StorageGRID Traffic Classification Policy. + returned: success + type: dict + sample: { + "id": "6b2946e6-7fed-40d0-9262-8e922580aba7", + "name": "Traffic-Policy1", + "description": "Traffic Classification Policy 1", + "matchers": [ + { + "type": "cidr", + "inverse": False, + "members": [ + "192.168.50.0/24" + ] + }, + { + "type": "bucket", + "inverse": False, + "members": [ + "mybucket1", + "mybucket2" + ] + }, + ], + "limits": [ + { + "type": "aggregateBandwidthOut", + "value": 100000000 + } + ], + } +""" + +import json + +import ansible_collections.netapp.storagegrid.plugins.module_utils.netapp as netapp_utils +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp_module import NetAppModule +from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp import SGRestAPI + + +class SgGridTrafficClasses: + """ + Create, modify and delete Traffic Classification Policies for StorageGRID + """ + + def __init__(self): + """ + Parse arguments, setup state variables, + check parameters and ensure request module is installed + """ + self.argument_spec = netapp_utils.na_storagegrid_host_argument_spec() + self.argument_spec.update( + dict( + state=dict(required=False, type="str", choices=["present", "absent"], default="present"), + name=dict(required=False, type="str"), + policy_id=dict(required=False, type="str"), + description=dict(required=False, type="str"), + matchers=dict( + required=False, + type="list", + elements="dict", + options=dict( + type=dict( + required=True, + type="str", + choices=["bucket", "bucket-regex", "cidr", "endpoint", "tenant"], + ), + inverse=dict(required=False, type="bool", default="false"), + members=dict(required=True, type="list", elements="str"), + ), + ), + limits=dict( + required=False, + type="list", + elements="dict", + options=dict( + type=dict( + required=True, + type="str", + choices=[ + "aggregateBandwidthIn", + "aggregateBandwidthOut", + "concurrentReadRequests", + "concurrentWriteRequests", + "readRequestRate", + "writeRequestRate", + "perRequestBandwidthIn", + "perRequestBandwidthOut", + ], + ), + value=dict(required=True, type="int"), + ), + ), + ) + ) + + self.module = AnsibleModule( + argument_spec=self.argument_spec, + required_if=[("state", "present", ["name"])], + required_one_of=[("name", "policy_id")], + ) + + self.na_helper = NetAppModule() + + # set up state variables + self.parameters = self.na_helper.set_parameters(self.module.params) + # Calling generic SG rest_api class + self.rest_api = SGRestAPI(self.module) + # Checking for the parameters passed and create new parameters list + self.data = {} + + if self.parameters["state"] == "present": + for k in ["name", "description", "matchers", "limits"]: + if self.parameters.get(k) is not None: + self.data[k] = self.parameters[k] + + def get_traffic_class_policy_id(self): + # Check if Traffic Classification Policy exists + # Return policy ID if found, or None + api = "api/v3/grid/traffic-classes/policies" + response, error = self.rest_api.get(api) + + if error: + self.module.fail_json(msg=error) + + return next((item["id"] for item in response.get("data") if item["name"] == self.parameters["name"]), None) + + def get_traffic_class_policy(self, policy_id): + api = "api/v3/grid/traffic-classes/policies/%s" % policy_id + response, error = self.rest_api.get(api) + + if error: + self.module.fail_json(msg=error) + + return response["data"] + + def create_traffic_class_policy(self): + api = "api/v3/grid/traffic-classes/policies" + # self.module.fail_json(msg=self.data) + response, error = self.rest_api.post(api, self.data) + + if error: + self.module.fail_json(msg=error) + + return response["data"] + + def delete_traffic_class_policy(self, policy_id): + api = "api/v3/grid/traffic-classes/policies/%s" % policy_id + dummy, error = self.rest_api.delete(api, self.data) + + if error: + self.module.fail_json(msg=error) + + def update_traffic_class_policy(self, policy_id): + api = "api/v3/grid/traffic-classes/policies/%s" % policy_id + response, error = self.rest_api.put(api, self.data) + + if error: + self.module.fail_json(msg=error) + + return response["data"] + + def apply(self): + """ + Perform pre-checks, call functions and exit + """ + + traffic_class_policy = None + + if self.parameters.get("policy_id"): + traffic_class_policy = self.get_traffic_class_policy(self.parameters["policy_id"]) + else: + policy_id = self.get_traffic_class_policy_id() + if policy_id: + traffic_class_policy = self.get_traffic_class_policy(policy_id) + + cd_action = self.na_helper.get_cd_action(traffic_class_policy, self.parameters) + + if cd_action is None and self.parameters["state"] == "present": + # let's see if we need to update parameters + modify = self.na_helper.get_modified_attributes(traffic_class_policy, self.data) + + result_message = "" + resp_data = {} + + if self.na_helper.changed and not self.module.check_mode: + if cd_action == "delete": + self.delete_traffic_class_policy(traffic_class_policy["id"]) + result_message = "Traffic Classification Policy deleted" + elif cd_action == "create": + resp_data = self.create_traffic_class_policy() + result_message = "Traffic Classification Policy created" + elif modify: + resp_data = self.update_traffic_class_policy(traffic_class_policy["id"]) + result_message = "Traffic Classification Policy updated" + + self.module.exit_json(changed=self.na_helper.changed, msg=result_message, resp=resp_data) + + +def main(): + """ + Main function + """ + na_sg_grid_traffic_classes = SgGridTrafficClasses() + na_sg_grid_traffic_classes.apply() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/netapp/storagegrid/plugins/modules/na_sg_grid_user.py b/ansible_collections/netapp/storagegrid/plugins/modules/na_sg_grid_user.py new file mode 100644 index 000000000..521d4f566 --- /dev/null +++ b/ansible_collections/netapp/storagegrid/plugins/modules/na_sg_grid_user.py @@ -0,0 +1,316 @@ +#!/usr/bin/python + +# (c) 2020, NetApp Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +"""NetApp StorageGRID - Manage Grid-administration Users""" + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["preview"], + "supported_by": "community", +} + + +DOCUMENTATION = """ +module: na_sg_grid_user +short_description: NetApp StorageGRID manage users. +extends_documentation_fragment: + - netapp.storagegrid.netapp.sg +version_added: '20.6.0' +author: NetApp Ansible Team (@joshedmonds) <ng-ansibleteam@netapp.com> +description: +- Create, Update, Delete Administrative Users within NetApp StorageGRID. +options: + state: + description: + - Whether the specified user should exist or not. + type: str + choices: ['present', 'absent'] + default: present + full_name: + description: + - Full Name of the user. + - Required for create operation + type: str + unique_name: + description: + - Unique Name for the user. Must begin with C(user/) or C(federated-user/) + - Required for create, modify or delete operation. + type: str + required: true + member_of: + description: + - List of C(unique_groups) that the user is a member of. + type: list + elements: str + password: + description: + - Set a password for a local user. Does not apply to federated users. + - Requires root privilege. + required: false + type: str + update_password: + description: + - Choose when to update the password. + - When set to C(always), the password will always be updated. + - When set to C(on_create), the password will only be set upon a new user creation. + default: on_create + choices: + - on_create + - always + type: str + disable: + description: + - Disable the user from signing in. Does not apply to federated users. + type: bool +""" + +EXAMPLES = """ + - name: create a user + netapp.storagegrid.na_sg_grid_user: + api_url: "https://<storagegrid-endpoint-url>" + auth_token: "storagegrid-auth-token" + validate_certs: false + state: present + full_name: ansibleuser100 + unique_name: user/ansibleuser100 + member_of: "group/ansiblegroup100" + disable: false + +""" + +RETURN = """ +resp: + description: Returns information about the StorageGRID Grid user. + returned: always + type: dict + sample: { + "fullName": "Example User", + "memberOf": ["00000000-0000-0000-0000-000000000000"], + "disable": false, + "uniqueName": "user/Example", + "accountId": "0", + "id": "00000000-0000-0000-0000-000000000000", + "federated": false, + "userURN": "urn:sgws:identity::0:user/Example" + } +""" + +import json +import re + + +import ansible_collections.netapp.storagegrid.plugins.module_utils.netapp as netapp_utils +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp_module import NetAppModule +from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp import SGRestAPI + + +class SgGridUser(object): + """ + Create, modify and delete user within a StorageGRID Tenant Account + """ + + def __init__(self): + """ + Parse arguments, setup state variables, + check parameters and ensure request module is installed + """ + self.argument_spec = netapp_utils.na_storagegrid_host_argument_spec() + self.argument_spec.update( + dict( + state=dict(required=False, type="str", choices=["present", "absent"], default="present"), + full_name=dict(required=False, type="str"), + unique_name=dict(required=True, type="str"), + member_of=dict(required=False, type="list", elements="str"), + disable=dict(required=False, type="bool"), + password=dict(required=False, type="str", no_log=True), + update_password=dict(default="on_create", choices=["on_create", "always"]), + ) + ) + + self.module = AnsibleModule( + argument_spec=self.argument_spec, + required_if=[("state", "present", ["full_name", "unique_name"])], + supports_check_mode=True, + ) + + self.na_helper = NetAppModule() + + # set up state variables + self.parameters = self.na_helper.set_parameters(self.module.params) + # Calling generic SG rest_api class + self.rest_api = SGRestAPI(self.module) + # Checking for the parameters passed and create new parameters list + self.data = {} + self.data["memberOf"] = [] + if self.parameters.get("full_name"): + self.data["fullName"] = self.parameters["full_name"] + if self.parameters.get("unique_name"): + self.data["uniqueName"] = self.parameters["unique_name"] + + if self.parameters.get("disable") is not None: + self.data["disable"] = self.parameters["disable"] + + re_local_user = re.compile("^user/") + re_fed_user = re.compile("^federated-user/") + + if ( + re_local_user.match(self.parameters["unique_name"]) is None + and re_fed_user.match(self.parameters["unique_name"]) is None + ): + self.module.fail_json(msg="unique_name must begin with 'user/' or 'federated-user/'") + + self.pw_change = {} + if self.parameters.get("password") is not None: + if re_fed_user.match(self.parameters["unique_name"]): + self.module.fail_json(msg="password cannot be set for a federated user") + self.pw_change["password"] = self.parameters["password"] + + def get_grid_groups(self): + # Get list of admin groups + # Retrun mapping of uniqueName to ids if found, or None + api = "api/v3/grid/groups?limit=350" + + response, error = self.rest_api.get(api) + + if error: + self.module.fail_json(msg=error) + + if response["data"]: + name_to_id_map = dict(zip([i["uniqueName"] for i in response["data"]], [j["id"] for j in response["data"]])) + return name_to_id_map + + return None + + def get_grid_user(self, unique_name): + # Use the unique name to check if the user exists + api = "api/v3/grid/users/%s" % unique_name + response, error = self.rest_api.get(api) + + if error: + if response["code"] != 404: + self.module.fail_json(msg=error["text"]) + else: + return response["data"] + return None + + def create_grid_user(self): + api = "api/v3/grid/users" + + response, error = self.rest_api.post(api, self.data) + + if error: + self.module.fail_json(msg=error["text"]) + + return response["data"] + + def delete_grid_user(self, user_id): + api = "api/v3/grid/users/" + user_id + + self.data = None + response, error = self.rest_api.delete(api, self.data) + if error: + self.module.fail_json(msg=error) + + def update_grid_user(self, user_id): + api = "api/v3/grid/users/" + user_id + + response, error = self.rest_api.put(api, self.data) + if error: + self.module.fail_json(msg=error["text"]) + + return response["data"] + + def set_grid_user_password(self, unique_name): + api = "api/v3/grid/users/%s/change-password" % unique_name + response, error = self.rest_api.post(api, self.pw_change) + + if error: + self.module.fail_json(msg=error["text"]) + + def apply(self): + """ + Perform pre-checks, call functions and exit + """ + grid_user = self.get_grid_user(self.parameters["unique_name"]) + + if self.parameters.get("member_of"): + grid_groups = self.get_grid_groups() + try: + self.data["memberOf"] = [grid_groups[x] for x in self.parameters["member_of"]] + except KeyError as e: + self.module.fail_json(msg="Invalid unique_group supplied: '%s' not found" % e.args[0]) + + cd_action = self.na_helper.get_cd_action(grid_user, self.parameters) + + if cd_action is None and self.parameters["state"] == "present": + # let's see if we need to update parameters + update = False + + if grid_user["memberOf"] is None: + member_of_diff = [] + else: + member_of_diff = [ + i + for i in self.data["memberOf"] + grid_user["memberOf"] + if i not in self.data["memberOf"] or i not in grid_user["memberOf"] + ] + if member_of_diff: + update = True + + if self.parameters.get("disable") is not None and self.parameters["disable"] != grid_user.get("disable"): + update = True + + if update: + self.na_helper.changed = True + result_message = "" + resp_data = grid_user + if self.na_helper.changed: + if self.module.check_mode: + pass + else: + if cd_action == "delete": + self.delete_grid_user(grid_user["id"]) + result_message = "Grid User deleted" + + elif cd_action == "create": + resp_data = self.create_grid_user() + result_message = "Grid User created" + + else: + resp_data = self.update_grid_user(grid_user["id"]) + result_message = "Grid User updated" + + # If a password has been set + if self.pw_change: + if self.module.check_mode: + pass + else: + # Only update the password if update_password is always, or a create activity has occurred + if cd_action == "create" or self.parameters["update_password"] == "always": + self.set_grid_user_password(self.parameters["unique_name"]) + self.na_helper.changed = True + + results = [result_message, "Grid User password updated"] + result_message = "; ".join(filter(None, results)) + + self.module.exit_json(changed=self.na_helper.changed, msg=result_message, resp=resp_data) + + +def main(): + """ + Main function + """ + na_sg_grid_user = SgGridUser() + na_sg_grid_user.apply() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/netapp/storagegrid/plugins/modules/na_sg_org_container.py b/ansible_collections/netapp/storagegrid/plugins/modules/na_sg_org_container.py new file mode 100644 index 000000000..da9663184 --- /dev/null +++ b/ansible_collections/netapp/storagegrid/plugins/modules/na_sg_org_container.py @@ -0,0 +1,352 @@ +#!/usr/bin/python + +# (c) 2020, NetApp Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +"""NetApp StorageGRID - Manage Buckets""" + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["preview"], + "supported_by": "community", +} + + +DOCUMENTATION = """ +module: na_sg_org_container +short_description: Manage buckets on StorageGRID. +extends_documentation_fragment: + - netapp.storagegrid.netapp.sg +version_added: '20.6.0' +author: NetApp Ansible Team (@joshedmonds) <ng-ansibleteam@netapp.com> +description: +- Create S3 buckets on NetApp StorageGRID. +options: + state: + description: + - Whether the specified bucket should exist or not. + type: str + choices: ['present', 'absent'] + default: present + name: + description: + - Name of the bucket. + required: true + type: str + region: + description: + - Set a region for the bucket. + type: str + compliance: + description: + - Configure compliance settings for an S3 bucket. + - Cannot be specified along with I(s3_object_lock_enabled). + type: dict + suboptions: + auto_delete: + description: + - If enabled, objects will be deleted automatically when its retention period expires, unless the bucket is under a legal hold. + type: bool + legal_hold: + description: + - If enabled, objects in this bucket cannot be deleted, even if their retention period has expired. + type: bool + retention_period_minutes: + description: + - specify the length of the retention period for objects added to this bucket, in minutes. + type: int + s3_object_lock_enabled: + description: + - Enable S3 Object Lock on the bucket. + - S3 Object Lock requires StorageGRID 11.5 or greater. + type: bool + version_added: '21.9.0' + bucket_versioning_enabled: + description: + - Enable versioning on the bucket. + - This API requires StorageGRID 11.6 or greater. + type: bool + version_added: '21.11.0' +""" + +EXAMPLES = """ + - name: create a s3 bucket + netapp.storagegrid.na_sg_org_container: + api_url: "https://<storagegrid-endpoint-url>" + auth_token: "storagegrid-auth-token" + validate_certs: false + state: present + name: ansiblebucket1 + + - name: delete a s3 bucket + netapp.storagegrid.na_sg_org_container: + api_url: "https://<storagegrid-endpoint-url>" + auth_token: "storagegrid-auth-token" + validate_certs: false + state: absent + name: ansiblebucket1 + + - name: create a s3 bucket with Object Lock + netapp.storagegrid.na_sg_org_container: + api_url: "https://<storagegrid-endpoint-url>" + auth_token: "storagegrid-auth-token" + validate_certs: false + state: present + name: objectlock-bucket1 + s3_object_lock_enabled: true + + - name: create a s3 bucket with versioning enabled + netapp.storagegrid.na_sg_org_container: + api_url: "https://<storagegrid-endpoint-url>" + auth_token: "storagegrid-auth-token" + validate_certs: false + state: present + name: ansiblebucket1 + bucket_versioning_enabled: true +""" + +RETURN = """ +resp: + description: Returns information about the StorageGRID bucket. + returned: always + type: dict + sample: { + "name": "example-bucket", + "creationTime": "2021-01-01T00:00:00.000Z", + "region": "us-east-1", + "compliance": { + "autoDelete": false, + "legalHold": false, + "retentionPeriodMinutes": 2629800 + }, + "s3ObjectLock": { + "enabled": false + } + } +""" + +import json + +import ansible_collections.netapp.storagegrid.plugins.module_utils.netapp as netapp_utils +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp_module import NetAppModule +from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp import SGRestAPI + + +class SgOrgContainer(object): + """ + Create, modify and delete StorageGRID Tenant Account + """ + + def __init__(self): + """ + Parse arguments, setup state variables, + check parameters and ensure request module is installed + """ + self.argument_spec = netapp_utils.na_storagegrid_host_argument_spec() + self.argument_spec.update( + dict( + state=dict(required=False, type="str", choices=["present", "absent"], default="present"), + name=dict(required=True, type="str"), + region=dict(required=False, type="str"), + compliance=dict( + required=False, + type="dict", + options=dict( + auto_delete=dict(required=False, type="bool"), + legal_hold=dict(required=False, type="bool"), + retention_period_minutes=dict(required=False, type="int"), + ), + ), + s3_object_lock_enabled=dict(required=False, type="bool"), + bucket_versioning_enabled=dict(required=False, type="bool"), + ) + ) + parameter_map = { + "auto_delete": "autoDelete", + "legal_hold": "legalHold", + "retention_period_minutes": "retentionPeriodMinutes", + } + self.module = AnsibleModule( + argument_spec=self.argument_spec, + mutually_exclusive=[("compliance", "s3_object_lock_enabled")], + supports_check_mode=True, + ) + + self.na_helper = NetAppModule() + + # set up state variables + self.parameters = self.na_helper.set_parameters(self.module.params) + # Calling generic SG rest_api class + self.rest_api = SGRestAPI(self.module) + # Get API version + self.rest_api.get_sg_product_version(api_root="org") + + # Checking for the parameters passed and create new parameters list + + self.data_versioning = {} + self.data_versioning["versioningSuspended"] = True + + self.data = {} + self.data["name"] = self.parameters["name"] + self.data["region"] = self.parameters.get("region") + if self.parameters.get("compliance"): + self.data["compliance"] = dict( + (parameter_map[k], v) for (k, v) in self.parameters["compliance"].items() if v is not None + ) + + if self.parameters.get("s3_object_lock_enabled") is not None: + self.rest_api.fail_if_not_sg_minimum_version("S3 Object Lock", 11, 5) + self.data["s3ObjectLock"] = dict(enabled=self.parameters["s3_object_lock_enabled"]) + + if self.parameters.get("bucket_versioning_enabled") is not None: + self.rest_api.fail_if_not_sg_minimum_version("Bucket versioning configuration", 11, 6) + self.data_versioning["versioningEnabled"] = self.parameters["bucket_versioning_enabled"] + if self.data_versioning["versioningEnabled"]: + self.data_versioning["versioningSuspended"] = False + + def get_org_container(self): + # Check if bucket/container exists + # Return info if found, or None + + params = {"include": "compliance,region"} + response, error = self.rest_api.get("api/v3/org/containers", params=params) + + if error: + self.module.fail_json(msg=error) + + for container in response["data"]: + if container["name"] == self.parameters["name"]: + return container + + return None + + def create_org_container(self): + api = "api/v3/org/containers" + + response, error = self.rest_api.post(api, self.data) + + if error: + self.module.fail_json(msg=error) + + return response["data"] + + def get_org_container_versioning(self): + api = "api/v3/org/containers/%s/versioning" % self.parameters["name"] + response, error = self.rest_api.get(api) + + if error: + self.module.fail_json(msg=error) + + return response["data"] + + def update_org_container_versioning(self): + api = "api/v3/org/containers/%s/versioning" % self.parameters["name"] + + response, error = self.rest_api.put(api, self.data_versioning) + if error: + self.module.fail_json(msg=error) + + return response["data"] + + def fail_if_global_object_lock_disabled(self): + api = "api/v3/org/compliance-global" + + response, error = self.rest_api.get(api) + if error: + self.module.fail_json(msg=error) + + if not response["data"]["complianceEnabled"]: + self.module.fail_json(msg="Error: Global S3 Object Lock setting is not enabled.") + + def update_org_container_compliance(self): + api = "api/v3/org/containers/%s/compliance" % self.parameters["name"] + + response, error = self.rest_api.put(api, self.data["compliance"]) + if error: + self.module.fail_json(msg=error) + + return response["data"] + + def delete_org_container(self): + api = "api/v3/org/containers/%s" % self.parameters["name"] + + response, error = self.rest_api.delete(api, None) + if error: + self.module.fail_json(msg=error["text"]) + + def apply(self): + """ + Perform pre-checks, call functions and exit + """ + versioning_config = None + update_versioning = False + + org_container = self.get_org_container() + + if org_container and self.parameters.get("bucket_versioning_enabled") is not None: + versioning_config = self.get_org_container_versioning() + + cd_action = self.na_helper.get_cd_action(org_container, self.parameters) + + if cd_action is None and self.parameters["state"] == "present": + # let's see if we need to update parameters + update_compliance = False + + if self.parameters.get("compliance") and org_container.get("compliance") != self.data["compliance"]: + update_compliance = True + self.na_helper.changed = True + + if ( + versioning_config + and versioning_config["versioningEnabled"] != self.data_versioning["versioningEnabled"] + ): + update_versioning = True + self.na_helper.changed = True + + result_message = "" + resp_data = org_container + if self.na_helper.changed: + if self.module.check_mode: + pass + else: + if cd_action == "delete": + self.delete_org_container() + resp_data = None + result_message = "Org Container deleted" + + elif cd_action == "create": + if self.parameters.get("s3_object_lock_enabled"): # if it is set and true + self.fail_if_global_object_lock_disabled() + + resp_data = self.create_org_container() + + if self.parameters.get("bucket_versioning_enabled") is not None: + self.update_org_container_versioning() + result_message = "Org Container created" + + else: + if update_compliance: + resp_data = self.update_org_container_compliance() + if update_versioning: + self.update_org_container_versioning() + result_message = "Org Container updated" + + self.module.exit_json(changed=self.na_helper.changed, msg=result_message, resp=resp_data) + + +def main(): + """ + Main function + """ + na_sg_org_container = SgOrgContainer() + na_sg_org_container.apply() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/netapp/storagegrid/plugins/modules/na_sg_org_group.py b/ansible_collections/netapp/storagegrid/plugins/modules/na_sg_org_group.py new file mode 100644 index 000000000..d13a7559a --- /dev/null +++ b/ansible_collections/netapp/storagegrid/plugins/modules/na_sg_org_group.py @@ -0,0 +1,301 @@ +#!/usr/bin/python + +# (c) 2020, NetApp Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +"""NetApp StorageGRID - Manage tenant Groups""" + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["preview"], + "supported_by": "community", +} + + +DOCUMENTATION = """ +module: na_sg_org_group +short_description: NetApp StorageGRID manage groups within a tenancy. +extends_documentation_fragment: + - netapp.storagegrid.netapp.sg +version_added: '20.6.0' +author: NetApp Ansible Team (@joshedmonds) <ng-ansibleteam@netapp.com> +description: +- Create, Update, Delete Groups within NetApp StorageGRID tenant. +options: + state: + description: + - Whether the specified group should exist or not. + type: str + choices: ['present', 'absent'] + default: present + unique_name: + description: + - Unique Name for the group. Must begin with C(group/) or C(federated-group/). + - Required for create, modify or delete operation. + type: str + required: true + display_name: + description: + - Name of the group. + - Required for create operation. + type: str + management_policy: + description: + - Management access controls granted to the group within the tenancy. + type: dict + suboptions: + manage_all_containers: + description: + - Allows users to manage the settings for all S3 buckets in the tenant account, regardless of S3 bucket or group policies. + type: bool + manage_endpoints: + description: + - Allows users to use the Tenant Manager or the Tenant Management API to create or edit endpoints. + - Endpoints are used as the destination for StorageGRID platform services. + type: bool + manage_own_s3_credentials: + description: + - Allows users to create and remove their own S3 access keys. + - Users who do not have this permission do not see the S3 > My Credentials menu option. + type: bool + root_access: + description: + - Provides full access to the Tenant Manager and the Tenant Management API. + type: bool + s3_policy: + description: + - StorageGRID S3 Group Policy. + default: "" + type: json +""" + +EXAMPLES = """ + - name: create a group + netapp.storagegrid.na_sg_org_group: + api_url: "https://<storagegrid-endpoint-url>" + auth_token: "storagegrid-auth-token" + validate_certs: false + state: present + display_name: ansiblegroup1 + unique_name: group/ansiblegroup1 + management_policy: + manage_all_containers: true + manage_endpoints: true + manage_own_s3_credentials: false + root_access: false + s3_policy: {"Statement":[{"Effect":"Deny","Action":"s3:*","Resource":"arn:aws:s3:::*"}]} +""" + +RETURN = """ +resp: + description: Returns information about the StorageGRID tenant group attributes. + returned: success + type: dict + sample: { + "displayName": "Example Group", + "policies": { + "management": { + "manageAllContainers": true, + "manageEndpoints": true, + "manageOwnS3Credentials": true, + "rootAccess": true + }, + "s3": {...}, + "swift": {...} + }, + "uniqueName": "group/examplegroup", + "accountId": "12345678901234567890", + "id": "00000000-0000-0000-0000-000000000000", + "federated": false, + "groupURN": "urn:sgws:identity::12345678901234567890:group/examplegroup" + } +""" + +import json +import re + +import ansible_collections.netapp.storagegrid.plugins.module_utils.netapp as netapp_utils +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp_module import NetAppModule +from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp import SGRestAPI + + +class SgOrgGroup(object): + """ + Create, modify and delete StorageGRID Tenant Account + """ + + def __init__(self): + """ + Parse arguments, setup state variables, + check parameters and ensure request module is installed + """ + self.argument_spec = netapp_utils.na_storagegrid_host_argument_spec() + self.argument_spec.update( + dict( + state=dict(required=False, type="str", choices=["present", "absent"], default="present"), + display_name=dict(required=False, type="str"), + unique_name=dict(required=True, type="str"), + management_policy=dict( + required=False, + type="dict", + options=dict( + manage_all_containers=dict(required=False, type="bool"), + manage_endpoints=dict(required=False, type="bool"), + manage_own_s3_credentials=dict(required=False, type="bool"), + root_access=dict(required=False, type="bool"), + ), + ), + s3_policy=dict(required=False, type="json"), + ) + ) + parameter_map = { + "manage_all_containers": "manageAllContainers", + "manage_endpoints": "manageEndpoints", + "manage_own_s3_credentials": "manageOwnS3Credentials", + "root_access": "rootAccess", + } + self.module = AnsibleModule( + argument_spec=self.argument_spec, + # required_if=[("state", "present", ["display_name"])], + supports_check_mode=True, + ) + + self.na_helper = NetAppModule() + + # set up state variables + self.parameters = self.na_helper.set_parameters(self.module.params) + # Calling generic SG rest_api class + self.rest_api = SGRestAPI(self.module) + # Checking for the parameters passed and create new parameters list + self.data = {} + self.data["displayName"] = self.parameters.get("display_name") + self.data["uniqueName"] = self.parameters["unique_name"] + # Only add the parameter if value is True, as JSON response does not include non-true objects + self.data["policies"] = {} + + if self.parameters.get("management_policy"): + self.data["policies"] = { + "management": dict( + (parameter_map[k], v) for (k, v) in self.parameters["management_policy"].items() if v + ) + } + if not self.data["policies"].get("management"): + self.data["policies"]["management"] = None + + if self.parameters.get("s3_policy"): + try: + self.data["policies"]["s3"] = json.loads(self.parameters["s3_policy"]) + except ValueError: + self.module.fail_json(msg="Failed to decode s3_policy. Invalid JSON.") + + self.re_local_group = re.compile("^group/") + self.re_fed_group = re.compile("^federated-group/") + + if ( + self.re_local_group.match(self.parameters["unique_name"]) is None + and self.re_fed_group.match(self.parameters["unique_name"]) is None + ): + self.module.fail_json(msg="unique_name must begin with 'group/' or 'federated-group/'") + + def get_org_group(self, unique_name): + # Use the unique name to check if the group exists + api = "api/v3/org/groups/%s" % unique_name + response, error = self.rest_api.get(api) + + if error: + if response["code"] != 404: + self.module.fail_json(msg=error) + else: + return response["data"] + return None + + def create_org_group(self): + api = "api/v3/org/groups" + + response, error = self.rest_api.post(api, self.data) + + if error: + self.module.fail_json(msg=error) + + return response["data"] + + def delete_org_group(self, group_id): + api = "api/v3/org/groups/" + group_id + + self.data = None + response, error = self.rest_api.delete(api, self.data) + if error: + self.module.fail_json(msg=error) + + def update_org_group(self, group_id): + api = "api/v3/org/groups/" + group_id + + response, error = self.rest_api.put(api, self.data) + if error: + self.module.fail_json(msg=error) + + return response["data"] + + def apply(self): + """ + Perform pre-checks, call functions and exit + """ + org_group = self.get_org_group(self.parameters["unique_name"]) + + cd_action = self.na_helper.get_cd_action(org_group, self.parameters) + + if cd_action is None and self.parameters["state"] == "present": + # let's see if we need to update parameters + update = False + + if self.parameters.get("management_policy"): + if org_group.get("policies") is None or org_group.get("policies", {}).get("management") != self.data["policies"]["management"]: + update = True + if self.parameters.get("s3_policy"): + if org_group.get("policies") is None or org_group.get("policies", {}).get("s3") != self.data["policies"]["s3"]: + update = True + + if update: + self.na_helper.changed = True + result_message = "" + resp_data = org_group + if self.na_helper.changed: + if self.module.check_mode: + pass + else: + if cd_action == "delete": + self.delete_org_group(org_group["id"]) + result_message = "Org Group deleted" + + elif cd_action == "create": + resp_data = self.create_org_group() + result_message = "Org Group created" + + else: + # for a federated group, the displayName parameter needs to be specified + # and must match the existing displayName + if self.re_fed_group.match(self.parameters["unique_name"]): + self.data["displayName"] = org_group["displayName"] + + resp_data = self.update_org_group(org_group["id"]) + result_message = "Org Group updated" + + self.module.exit_json(changed=self.na_helper.changed, msg=result_message, resp=resp_data) + + +def main(): + """ + Main function + """ + na_sg_org_group = SgOrgGroup() + na_sg_org_group.apply() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/netapp/storagegrid/plugins/modules/na_sg_org_identity_federation.py b/ansible_collections/netapp/storagegrid/plugins/modules/na_sg_org_identity_federation.py new file mode 100644 index 000000000..4b6811cd6 --- /dev/null +++ b/ansible_collections/netapp/storagegrid/plugins/modules/na_sg_org_identity_federation.py @@ -0,0 +1,335 @@ +#!/usr/bin/python + +# (c) 2021, NetApp Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +"""NetApp StorageGRID - Manage Tenant Identity Federation""" + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + + +DOCUMENTATION = """ +module: na_sg_org_identity_federation +short_description: NetApp StorageGRID manage Tenant identity federation. +extends_documentation_fragment: + - netapp.storagegrid.netapp.sg +version_added: '21.6.0' +author: NetApp Ansible Team (@joshedmonds) <ng-ansibleteam@netapp.com> +description: +- Configure Tenant Identity Federation within NetApp StorageGRID. +- If module is run with C(check_mode), a connectivity test will be performed using the supplied values without changing the configuration. +- This module is idempotent if I(password) is not specified. +options: + state: + description: + - Whether identity federation should be enabled or not. + type: str + choices: ['present', 'absent'] + default: present + username: + description: + - The username to bind to the LDAP server. + type: str + password: + description: + - The password associated with the username. + type: str + hostname: + description: + - The hostname or IP address of the LDAP server. + type: str + port: + description: + - The port used to connect to the LDAP server. Typically 389 for LDAP, or 636 for LDAPS. + type: int + base_group_dn: + description: + - The Distinguished Name of the LDAP subtree to search for groups. + type: str + base_user_dn: + description: + - The Distinguished Name of the LDAP subtree to search for users. + type: str + ldap_service_type: + description: + - The type of LDAP server. + choices: ['Active Directory', 'OpenLDAP', 'Other'] + type: str + type: + description: + - The type of identity source. + - Default is 'ldap'. + type: str + default: ldap + ldap_user_id_attribute: + description: + - The LDAP attribute which contains the unique user name of a user. + - Should be configured if I(ldap_service_type=Other). + type: str + ldap_user_uuid_attribute: + description: + - The LDAP attribute which contains the permanent unique identity of a user. + - Should be configured if I(ldap_service_type=Other). + type: str + ldap_group_id_attribute: + description: + - The LDAP attribute which contains the group for a user. + - Should be configured if I(ldap_service_type=Other). + type: str + ldap_group_uuid_attribute: + description: + - The LDAP attribute which contains the group's permanent unique identity. + - Should be configured if I(ldap_service_type=Other). + type: str + tls: + description: + - Whether Transport Layer Security is used to connect to the LDAP server. + choices: ['STARTTLS', 'LDAPS', 'Disabled'] + type: str + default: STARTTLS + ca_cert: + description: + - Custom certificate used to connect to the LDAP server. + - If a custom certificate is not supplied, the operating system CA certificate will be used. + type: str +""" + +EXAMPLES = """ + - name: test identity federation configuration + netapp.storagegrid.na_sg_org_identity_federation: + api_url: "https://<storagegrid-endpoint-url>" + auth_token: "storagegrid-auth-token" + validate_certs: false + state: present + ldap_service_type: "Active Directory" + hostname: "ad.example.com" + port: 389 + username: "binduser" + password: "bindpass" + base_group_dn: "DC=example,DC=com" + base_user_dn: "DC=example,DC=com" + tls: "Disabled" + check_mode: yes + + - name: configure identity federation with AD and TLS + netapp.storagegrid.na_sg_org_identity_federation: + api_url: "https://<storagegrid-endpoint-url>" + auth_token: "storagegrid-auth-token" + validate_certs: false + state: present + ldap_service_type: "Active Directory" + hostname: "ad.example.com" + port: 636, + username: "binduser" + password: "bindpass" + base_group_dn: "DC=example,DC=com" + base_user_dn: "DC=example,DC=com" + tls: "LDAPS" + ca_cert: | + -----BEGIN CERTIFICATE----- + MIIC+jCCAeICCQDmn9Gow08LTzANBgkqhkiG9w0BAQsFADA/..swCQYDVQQGEwJV + bXBsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB..JFzNIXQEGnsgjV + JGU4giuvOLOZ8Q3gyuUbkSUQDjmjpMR8PliwJ6iW2Ity89Dv..dl1TaIYI/ansyZ + Uxk4YXeN6kUkrDtNxCg1McALzXVAfxMTtj2SFlLxne4Z6rX2..UyftQrfM13F1vY + gK8dBPz+l+X/Uozo/xNm7gxe68p9le9/pcULst1CQn5/sPqq..kgWcSvlKUItu82 + lq3B2169rovdIaNdcvaQjMPhrDGo5rvLfMN35U3Hgbz41PL5..x2BcUE6/0ab5T4 + qKBxKa3t9twj+zpUqOzyL0PFfCE+SK5fEXAS1ow4eAcLN+eB..gR/PuvGAyIPCtE + 1+X4GrECAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAFpO+04Ra..FMJPH6dBmzfb7l + k04BWTvSlur6HiQdXY+oFQMJZzyI7MQ8v9HBIzS0ZAzYWLp4..VZhHmRxnrWyxVs + u783V5YfQH2L4QnBDoiDefgxyfDs2PcoF5C+X9CGXmPqzst2..y/6tdOVJzdiA== + -----END CERTIFICATE----- +""" + +RETURN = """ +resp: + description: Returns information about the StorageGRID tenant account identity source configuration. + returned: success + type: dict + sample: { + "id": "00000000-0000-0000-0000-000000000000", + "disable": false, + "hostname": "10.1.2.3", + "port": 389, + "username": "MYDOMAIN\\\\Administrator", + "password": "********", + "baseGroupDn": "DC=example,DC=com", + "baseUserDn": "DC=example,DC=com", + "ldapServiceType": "Active Directory", + "type": "ldap", + "disableTLS": false, + "enableLDAPS": false, + "caCert": "-----BEGIN CERTIFICATE----- abcdefghijkl123456780ABCDEFGHIJKL 123456/7890ABCDEFabcdefghijklABCD -----END CERTIFICATE-----\n" + } +""" + +import json +import re + +import ansible_collections.netapp.storagegrid.plugins.module_utils.netapp as netapp_utils +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp_module import NetAppModule +from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp import SGRestAPI + + +class SgOrgIdentityFederation: + """ + Configure and modify StorageGRID Tenant Identity Federation + """ + + def __init__(self): + """ + Parse arguments, setup state variables, + check parameters and ensure request module is installed + """ + self.argument_spec = netapp_utils.na_storagegrid_host_argument_spec() + self.argument_spec.update( + dict( + state=dict(required=False, type="str", choices=["present", "absent"], default="present"), + username=dict(required=False, type="str"), + password=dict(required=False, type="str", no_log=True), + hostname=dict(required=False, type="str"), + port=dict(required=False, type="int"), + base_group_dn=dict(required=False, type="str"), + base_user_dn=dict(required=False, type="str"), + ldap_service_type=dict(required=False, type="str", choices=["OpenLDAP", "Active Directory", "Other"]), + type=dict(required=False, type="str", default="ldap"), + ldap_user_id_attribute=dict(required=False, type="str"), + ldap_user_uuid_attribute=dict(required=False, type="str"), + ldap_group_id_attribute=dict(required=False, type="str"), + ldap_group_uuid_attribute=dict(required=False, type="str"), + tls=dict(required=False, type="str", choices=["STARTTLS", "LDAPS", "Disabled"], default="STARTTLS"), + ca_cert=dict(required=False, type="str"), + ), + ) + + parameter_map = { + "username": "username", + "password": "password", + "hostname": "hostname", + "port": "port", + "base_group_dn": "baseGroupDn", + "base_user_dn": "baseUserDn", + "ldap_service_type": "ldapServiceType", + "ldap_user_id_attribute": "ldapUserIdAttribute", + "ldap_user_uuid_attribute": "ldapUserUUIDAttribute", + "ldap_group_id_attribute": "ldapGroupIdAttribute", + "ldap_group_uuid_attribute": "ldapGroupUUIDAttribute", + "ca_cert": "caCert", + } + self.module = AnsibleModule(argument_spec=self.argument_spec, supports_check_mode=True,) + + self.na_helper = NetAppModule() + + # set up state variables + self.parameters = self.na_helper.set_parameters(self.module.params) + # Calling generic SG rest_api class + self.rest_api = SGRestAPI(self.module) + # Checking for the parameters passed and create new parameters list + self.data = {} + + if self.parameters["state"] == "present": + self.data["disable"] = False + + for k in parameter_map.keys(): + if self.parameters.get(k) is not None: + self.data[parameter_map[k]] = self.parameters[k] + + if self.parameters.get("tls") == "STARTTLS": + self.data["disableTLS"] = False + self.data["enableLDAPS"] = False + elif self.parameters.get("tls") == "LDAPS": + self.data["disableTLS"] = False + self.data["enableLDAPS"] = True + else: + self.data["disableTLS"] = True + self.data["enableLDAPS"] = False + + def get_org_identity_source(self): + api = "api/v3/org/identity-source" + response, error = self.rest_api.get(api) + + if error: + self.module.fail_json(msg=error) + else: + return response["data"] + return None + + def update_identity_federation(self, test=False): + api = "api/v3/org/identity-source" + + params = {} + + if test: + params["test"] = True + + response, error = self.rest_api.put(api, self.data, params=params) + if error: + self.module.fail_json(msg=error, payload=self.data) + + if response is not None: + return response["data"] + else: + return None + + def apply(self): + """ + Perform pre-checks, call functions and exit + """ + org_identity_source = self.get_org_identity_source() + + cd_action = self.na_helper.get_cd_action(org_identity_source, self.parameters) + + if cd_action is None and self.parameters["state"] == "present": + # let's see if we need to update parameters + update = False + + for k in (i for i in self.data.keys() if i != "password"): + if self.data[k] != org_identity_source.get(k): + update = True + break + + # if a password has been specified we need to update it + if self.data.get("password") and self.parameters["state"] == "present": + update = True + self.module.warn("Password attribute has been specified. Task is not idempotent.") + + if update: + self.na_helper.changed = True + + if cd_action == "delete": + # if identity federation is already in a disable state + if org_identity_source.get("disable"): + self.na_helper.changed = False + + result_message = "" + resp_data = org_identity_source + + if self.na_helper.changed and not self.module.check_mode: + if cd_action == "delete": + self.data = dict(disable=True) + resp_data = self.update_identity_federation() + result_message = "Tenant identity federation disabled" + else: + resp_data = self.update_identity_federation() + result_message = "Tenant identity federation updated" + + if self.module.check_mode: + self.update_identity_federation(test=True) + # if no error, connection test successful + self.module.exit_json(changed=self.na_helper.changed, msg="Connection test successful") + + self.module.exit_json(changed=self.na_helper.changed, msg=result_message, resp=resp_data) + + +def main(): + """ + Main function + """ + na_sg_org_identity_federation = SgOrgIdentityFederation() + na_sg_org_identity_federation.apply() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/netapp/storagegrid/plugins/modules/na_sg_org_info.py b/ansible_collections/netapp/storagegrid/plugins/modules/na_sg_org_info.py new file mode 100644 index 000000000..b2d3c4e48 --- /dev/null +++ b/ansible_collections/netapp/storagegrid/plugins/modules/na_sg_org_info.py @@ -0,0 +1,279 @@ +#!/usr/bin/python + +# (c) 2020, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +""" NetApp StorageGRID Org Info using REST APIs """ + + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community'} + +DOCUMENTATION = """ +module: na_sg_org_info +author: NetApp Ansible Team (@jasonl4) <ng-ansibleteam@netapp.com> +extends_documentation_fragment: + - netapp.storagegrid.netapp.sg +short_description: NetApp StorageGRID Org information gatherer. +description: + - This module allows you to gather various information about StorageGRID Org configuration. +version_added: 20.11.0 + +options: + gather_subset: + type: list + elements: str + description: + - When supplied, this argument will restrict the information collected to a given subset. + - Either the info name or the Rest API can be given. + - Possible values for this argument include + - C(org_compliance_global_info) or C(org/compliance-global) + - C(org_config_info) or C(org/config) + - C(org_config_product_version_info) or C(org/config/product-version) + - C(org_containers_info) or C(org/containers) + - C(org_deactivated_features_info) or C(org/deactivated-features) + - C(org_endpoints_info) or C(org/endpoints) + - C(org_groups_info) or C(org/groups) + - C(org_identity_source_info) or C(org/identity-source) + - C(org_regions_info) or C(org/regions) + - C(org_users_current_user_s3_access_keys_info) or C(org/users/current-user/s3-access-keys) + - C(org_usage_info) or C(org/usage) + - C(org_users_info) or C(org/users) + - C(org_users_root_info) or C(org/users/root) + - C(versions_info) or C(versions) + - Can specify a list of values to include a larger subset. + default: "all" + parameters: + description: + - Allows for any rest option to be passed in. + type: dict +""" + +EXAMPLES = """ +- name: Gather StorageGRID Org info + netapp.storagegrid.na_sg_org_info: + api_url: "https://1.2.3.4/" + auth_token: "storagegrid-auth-token" + validate_certs: false + register: sg_org_info + +- name: Gather StorageGRID Org info for org/containers and org/config subsets + netapp.storagegrid.na_sg_org_info: + api_url: "https://1.2.3.4/" + auth_token: "storagegrid-auth-token" + validate_certs: false + gather_subset: + - org_containers_info + - org/config + register: sg_org_info + +- name: Gather StorageGRID Org info for all subsets + netapp.storagegrid.na_sg_org_info: + api_url: "https://1.2.3.4/" + auth_token: "storagegrid-auth-token" + validate_certs: false + gather_subset: + - all + register: sg_org_info + +- name: Gather StorageGRID Org info for org/containers and org/users subsets, limit to 5 results for each subset + netapp.storagegrid.na_sg_org_info: + api_url: "https://1.2.3.4/" + auth_token: "storagegrid-auth-token" + validate_certs: false + gather_subset: + - org/containers + - org/users + parameters: + limit: 5 + register: sg_org_info +""" + +RETURN = """ +sg_info: + description: Returns various information about the StorageGRID Grid configuration. + returned: always + type: dict + sample: { + "org/compliance-global": {...}, + "org/config": {...}, + "org/config/product-version": {...}, + "org/containers": {...}, + "org/deactivated-features": {...}, + "org/endpoints": {...}, + "org/groups": {...}, + "org/identity-source": {...}, + "org/regions": {...}, + "org/users/current-user/s3-access-keys": {...}, + "org/usage": {...}, + "org/users": {...}, + "org/users/root": {...}, + "org/versions": {...} + } +""" + +from ansible.module_utils.basic import AnsibleModule +import ansible_collections.netapp.storagegrid.plugins.module_utils.netapp as netapp_utils +from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp_module import NetAppModule +from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp import SGRestAPI + + +class NetAppSgGatherInfo(object): + """ Class with gather info methods """ + + def __init__(self): + """ + Parse arguments, setup variables, check parameters and ensure + request module is installed. + """ + self.argument_spec = netapp_utils.na_storagegrid_host_argument_spec() + self.argument_spec.update(dict( + gather_subset=dict(default=['all'], type='list', elements='str', required=False), + parameters=dict(type='dict', required=False) + )) + + self.module = AnsibleModule( + argument_spec=self.argument_spec, + supports_check_mode=True + ) + + # set up variables + self.na_helper = NetAppModule() + self.parameters = self.na_helper.set_parameters(self.module.params) + self.rest_api = SGRestAPI(self.module) + + def get_subset_info(self, gather_subset_info): + """ + Gather StorageGRID information for the given subset using REST APIs + Input for REST APIs call : (api, data) + return gathered_sg_info + """ + + api = gather_subset_info['api_call'] + data = {} + # allow for passing in any additional rest api parameters + if self.parameters.get('parameters'): + for each in self.parameters['parameters']: + data[each] = self.parameters['parameters'][each] + + gathered_sg_info, error = self.rest_api.get(api, data) + + if error: + self.module.fail_json(msg=error) + else: + return gathered_sg_info + + return None + + def convert_subsets(self): + """ Convert an info to the REST API """ + info_to_rest_mapping = { + 'org_compliance_global_info': 'org/compliance-global', + 'org_config_info': 'org/config', + 'org_config_product_version_info': 'org/config/product-version', + 'org_containers_info': 'org/containers', + 'org_deactivated_features_info': 'org/deactivated-features', + 'org_endpoints_info': 'org/endpoints', + 'org_groups_info': 'org/groups', + 'org_identity_source_info': 'org/identity-source', + 'org_regions_info': 'org/regions', + 'org_users_current_user_s3_access_keys_info': 'org/users/current-user/s3-access-keys', + 'org_usage_info': 'org/usage', + 'org_users_info': 'org/users', + 'org_users_root_info': 'org/users/root', + 'versions_info': 'versions' + } + # Add rest API names as there info version, also make sure we don't add a duplicate + subsets = [] + for subset in self.parameters['gather_subset']: + if subset in info_to_rest_mapping: + if info_to_rest_mapping[subset] not in subsets: + subsets.append(info_to_rest_mapping[subset]) + else: + if subset not in subsets: + subsets.append(subset) + return subsets + + def apply(self): + """ Perform pre-checks, call functions and exit """ + + result_message = dict() + + # Defining gather_subset and appropriate api_call + get_sg_subset_info = { + 'org/compliance-global': { + 'api_call': 'api/v3/org/compliance-global', + }, + 'org/config': { + 'api_call': 'api/v3/org/config', + }, + 'org/config/product-version': { + 'api_call': 'api/v3/org/config/product-version', + }, + 'org/containers': { + 'api_call': 'api/v3/org/containers', + }, + 'org/deactivated-features': { + 'api_call': 'api/v3/org/deactivated-features', + }, + 'org/endpoints': { + 'api_call': 'api/v3/org/endpoints', + }, + 'org/groups': { + 'api_call': 'api/v3/org/groups', + }, + 'org/identity-source': { + 'api_call': 'api/v3/org/identity-source', + }, + 'org/regions': { + 'api_call': 'api/v3/org/regions', + }, + 'org/users/current-user/s3-access-keys': { + 'api_call': 'api/v3/org/users/current-user/s3-access-keys', + }, + 'org/usage': { + 'api_call': 'api/v3/org/usage', + }, + 'org/users': { + 'api_call': 'api/v3/org/users', + }, + 'org/users/root': { + 'api_call': 'api/v3/org/users/root', + }, + 'versions': { + 'api_call': 'api/v3/versions', + }, + } + + if 'all' in self.parameters['gather_subset']: + # If all in subset list, get the information of all subsets + self.parameters['gather_subset'] = sorted(get_sg_subset_info.keys()) + + converted_subsets = self.convert_subsets() + + for subset in converted_subsets: + try: + # Verify whether the supported subset passed + specified_subset = get_sg_subset_info[subset] + except KeyError: + self.module.fail_json(msg="Specified subset %s not found, supported subsets are %s" % + (subset, list(get_sg_subset_info.keys()))) + + result_message[subset] = self.get_subset_info(specified_subset) + + self.module.exit_json(changed='False', sg_info=result_message) + + +def main(): + """ Main function """ + obj = NetAppSgGatherInfo() + obj.apply() + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/netapp/storagegrid/plugins/modules/na_sg_org_user.py b/ansible_collections/netapp/storagegrid/plugins/modules/na_sg_org_user.py new file mode 100644 index 000000000..455ffa345 --- /dev/null +++ b/ansible_collections/netapp/storagegrid/plugins/modules/na_sg_org_user.py @@ -0,0 +1,335 @@ +#!/usr/bin/python + +# (c) 2020, NetApp Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +"""NetApp StorageGRID - Manage Tenant Users""" + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["preview"], + "supported_by": "community", +} + + +DOCUMENTATION = """ +module: na_sg_org_user +short_description: NetApp StorageGRID manage users within a tenancy. +extends_documentation_fragment: + - netapp.storagegrid.netapp.sg +version_added: '20.6.0' +author: NetApp Ansible Team (@joshedmonds) <ng-ansibleteam@netapp.com> +description: +- Create, Update, Delete Users within a NetApp StorageGRID tenant. +options: + state: + description: + - Whether the specified user should exist or not. + type: str + choices: ['present', 'absent'] + default: present + full_name: + description: + - Full Name of the user. + - Required for create operation + type: str + unique_name: + description: + - Unique Name for the user. Must begin with C(user/) or C(federated-user/). + - Required for create, modify or delete operation. + type: str + required: true + member_of: + description: + - List of unique_groups that the user is a member of. + type: list + elements: str + password: + description: + - Set a password for a local user. Does not apply to federated users. + - Requires root privilege. + required: false + type: str + update_password: + description: + - Choose when to update the password. + - When set to C(always), the password will always be updated. + - When set to C(on_create), the password will only be set upon a new user creation. + default: on_create + choices: + - on_create + - always + type: str + disable: + description: + - Disable the user from signing in. Does not apply to federated users. + type: bool +""" + +EXAMPLES = """ + - name: create a tenant user + netapp.storagegrid.na_sg_org_user: + api_url: "https://<storagegrid-endpoint-url>" + auth_token: "storagegrid-auth-token" + validate_certs: false + state: present + full_name: ansibleuser1 + unique_name: user/ansibleuser1 + member_of: "group/ansiblegroup1" + disable: false + +""" + +RETURN = """ +resp: + description: Returns information about the StorageGRID tenant user. + returned: always + type: dict + sample: { + "fullName": "Example User", + "memberOf": ["00000000-0000-0000-0000-000000000000"], + "disable": false, + "uniqueName": "user/Example", + "accountId": "0", + "id": "00000000-0000-0000-0000-000000000000", + "federated": false, + "userURN": "urn:sgws:identity::0:user/Example" + } +""" + +import json +import re + + +import ansible_collections.netapp.storagegrid.plugins.module_utils.netapp as netapp_utils +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp_module import ( + NetAppModule, +) +from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp import SGRestAPI + + +class SgOrgUser(object): + """ + Create, modify and delete user within a StorageGRID Tenant Account + """ + + def __init__(self): + """ + Parse arguments, setup state variables, + check parameters and ensure request module is installed + """ + self.argument_spec = netapp_utils.na_storagegrid_host_argument_spec() + self.argument_spec.update( + dict( + state=dict(required=False, type="str", choices=["present", "absent"], default="present"), + full_name=dict(required=False, type="str"), + unique_name=dict(required=True, type="str"), + member_of=dict(required=False, type="list", elements="str"), + disable=dict(required=False, type="bool"), + password=dict(required=False, type="str", no_log=True), + update_password=dict( + default="on_create", choices=["on_create", "always"] + ), + ) + ) + + self.module = AnsibleModule( + argument_spec=self.argument_spec, + required_if=[("state", "present", ["full_name", "unique_name"])], + supports_check_mode=True, + ) + + self.na_helper = NetAppModule() + + # set up state variables + self.parameters = self.na_helper.set_parameters(self.module.params) + # Calling generic SG rest_api class + self.rest_api = SGRestAPI(self.module) + # Checking for the parameters passed and create new parameters list + self.data = {} + self.data["memberOf"] = [] + if self.parameters.get("full_name"): + self.data["fullName"] = self.parameters["full_name"] + if self.parameters.get("unique_name"): + self.data["uniqueName"] = self.parameters["unique_name"] + + if self.parameters.get("disable") is not None: + self.data["disable"] = self.parameters["disable"] + + re_local_user = re.compile("^user/") + re_fed_user = re.compile("^federated-user/") + + if ( + re_local_user.match(self.parameters["unique_name"]) is None + and re_fed_user.match(self.parameters["unique_name"]) is None + ): + self.module.fail_json( + msg="unique_name must begin with 'user/' or 'federated-user/'" + ) + + self.pw_change = {} + if self.parameters.get("password") is not None: + if re_fed_user.match(self.parameters["unique_name"]): + self.module.fail_json(msg="password cannot be set for a federated user") + self.pw_change["password"] = self.parameters["password"] + + def get_org_groups(self): + # Get list of groups + # Retrun mapping of uniqueName to ids if found, or None + api = "api/v3/org/groups?limit=350" + response, error = self.rest_api.get(api) + + if error: + self.module.fail_json(msg=error) + + if response["data"]: + name_to_id_map = dict( + zip( + [i["uniqueName"] for i in response["data"]], + [j["id"] for j in response["data"]], + ) + ) + return name_to_id_map + + return None + + def get_org_user(self, unique_name): + # Use the unique name to check if the user exists + api = "api/v3/org/users/%s" % unique_name + response, error = self.rest_api.get(api) + + if error: + if response["code"] != 404: + self.module.fail_json(msg=error) + else: + return response["data"] + return None + + def create_org_user(self): + api = "api/v3/org/users" + + response, error = self.rest_api.post(api, self.data) + + if error: + self.module.fail_json(msg=error) + + return response["data"] + + def delete_org_user(self, user_id): + api = "api/v3/org/users/" + user_id + + self.data = None + response, error = self.rest_api.delete(api, self.data) + if error: + self.module.fail_json(msg=error) + + def update_org_user(self, user_id): + api = "api/v3/org/users/" + user_id + + response, error = self.rest_api.put(api, self.data) + if error: + self.module.fail_json(msg=error) + + return response["data"] + + def set_org_user_password(self, unique_name): + api = "api/v3/org/users/%s/change-password" % unique_name + response, error = self.rest_api.post(api, self.pw_change) + + if error: + self.module.fail_json(msg=error["text"]) + + def apply(self): + """ + Perform pre-checks, call functions and exit + """ + org_user = self.get_org_user(self.parameters["unique_name"]) + + if self.parameters.get("member_of"): + org_groups = self.get_org_groups() + try: + self.data["memberOf"] = [ + org_groups[x] for x in self.parameters["member_of"] + ] + except KeyError as e: + self.module.fail_json( + msg="Invalid unique_group supplied: '%s' not found" % e.args[0] + ) + + cd_action = self.na_helper.get_cd_action(org_user, self.parameters) + + if cd_action is None and self.parameters["state"] == "present": + # let's see if we need to update parameters + update = False + + if org_user["memberOf"] is None: + member_of_diff = [] + else: + member_of_diff = [ + i + for i in self.data["memberOf"] + org_user["memberOf"] + if i not in self.data["memberOf"] or i not in org_user["memberOf"] + ] + if member_of_diff: + update = True + + if self.parameters.get("disable") is not None and self.parameters[ + "disable" + ] != org_user.get("disable"): + update = True + + if update: + self.na_helper.changed = True + + result_message = "" + resp_data = org_user + if self.na_helper.changed: + if self.module.check_mode: + pass + else: + if cd_action == "delete": + self.delete_org_user(org_user["id"]) + result_message = "Org User deleted" + + elif cd_action == "create": + resp_data = self.create_org_user() + result_message = "Org User created" + + else: + resp_data = self.update_org_user(org_user["id"]) + result_message = "Org User updated" + + # If a password has been set + if self.pw_change: + if self.module.check_mode: + pass + else: + # Only update the password if update_password is always, or a create activity has occurred + if cd_action == "create" or self.parameters["update_password"] == "always": + self.set_org_user_password(self.parameters["unique_name"]) + self.na_helper.changed = True + + results = [result_message, "Org User password updated"] + result_message = "; ".join(filter(None, results)) + + self.module.exit_json( + changed=self.na_helper.changed, msg=result_message, resp=resp_data + ) + + +def main(): + """ + Main function + """ + na_sg_org_user = SgOrgUser() + na_sg_org_user.apply() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/netapp/storagegrid/plugins/modules/na_sg_org_user_s3_key.py b/ansible_collections/netapp/storagegrid/plugins/modules/na_sg_org_user_s3_key.py new file mode 100644 index 000000000..0de396eb7 --- /dev/null +++ b/ansible_collections/netapp/storagegrid/plugins/modules/na_sg_org_user_s3_key.py @@ -0,0 +1,210 @@ +#!/usr/bin/python + +# (c) 2020, NetApp Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +"""NetApp StorageGRID - Manage User S3 keys""" + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["preview"], + "supported_by": "community", +} + + +DOCUMENTATION = """ +module: na_sg_org_user_s3_key +short_description: Creates NetApp StorageGRID User S3 keys. +extends_documentation_fragment: + - netapp.storagegrid.netapp.sg +version_added: '20.6.0' +author: NetApp Ansible Team (@joshedmonds) <ng-ansibleteam@netapp.com> +description: +- Create, Delete Users S3 keys on NetApp StorageGRID. +options: + state: + description: + - Whether the specified account should exist or not. + type: str + choices: ['present', 'absent'] + default: present + unique_user_name: + description: + - Unique user name owning the S3 Key. + required: true + type: str + expires: + description: + - Date-Time string for the key to expire. + type: str + access_key: + description: + - Access Key or S3 credential pair identifier. + - Required for delete operation. + type: str +""" + +EXAMPLES = """ + - name: create a s3 key + netapp.storagegrid.na_sg_org_user_s3_key: + api_url: "https://<storagegrid-endpoint-url>" + auth_token: "storagegrid-auth-token" + validate_certs: false + state: present + unique_user_name: user/ansibleuser1 +""" + +RETURN = """ +resp: + description: Returns information about an S3 access key for the user. + returned: always + type: dict + sample: { + "id": "abcABC_01234-0123456789abcABCabc0123456789==", + "accountId": 12345678901234567000, + "displayName": "****************AB12", + "userURN": "urn:sgws:identity::12345678901234567000:root", + "userUUID": "00000000-0000-0000-0000-000000000000", + "expires": "2020-09-04T00:00:00.000Z" + } +""" + +import json + +import ansible_collections.netapp.storagegrid.plugins.module_utils.netapp as netapp_utils +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp_module import NetAppModule +from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp import SGRestAPI + + +class SgOrgUserS3Key(object): + """ + Create, modify and delete StorageGRID Tenant Account + """ + + def __init__(self): + """ + Parse arguments, setup state variables, + check parameters and ensure request module is installed + """ + self.argument_spec = netapp_utils.na_storagegrid_host_argument_spec() + self.argument_spec.update( + dict( + state=dict(required=False, type="str", choices=["present", "absent"], default="present"), + unique_user_name=dict(required=True, type="str"), + expires=dict(required=False, type="str"), + access_key=dict(required=False, type="str", no_log=False), + ) + ) + + self.module = AnsibleModule( + argument_spec=self.argument_spec, + required_if=[("state", "absent", ["access_key"])], + supports_check_mode=False, + ) + + self.na_helper = NetAppModule() + + # set up state variables + self.parameters = self.na_helper.set_parameters(self.module.params) + # Calling generic SG rest_api class + self.rest_api = SGRestAPI(self.module) + # Checking for the parameters passed and create new parameters list + self.data = {} + self.data["expires"] = self.parameters.get("expires") + + def get_org_user_id(self, unique_name): + # Use the unique name to check if the user exists + api = "api/v3/org/users/%s" % unique_name + response, error = self.rest_api.get(api) + + if error: + if response["code"] != 404: + self.module.fail_json(msg=error) + else: + return response["data"]["id"] + return None + + def get_org_user_s3_key(self, user_id, access_key): + # Use the unique name to check if the user exists + api = "api/v3/org/users/current-user/s3-access-keys/%s" % access_key + + if user_id: + api = "api/v3/org/users/%s/s3-access-keys/%s" % (user_id, access_key,) + + response, error = self.rest_api.get(api) + + if error: + self.module.fail_json(msg=error) + else: + return response["data"] + return None + + def create_org_user_s3_key(self, user_id): + api = "api/v3/org/users/current-user/s3-access-keys" + + if user_id: + api = "api/v3/org/users/%s/s3-access-keys" % user_id + + response, error = self.rest_api.post(api, self.data) + + if error: + self.module.fail_json(msg=error) + + return response["data"] + + def delete_org_user_s3_key(self, user_id, access_key): + api = "api/v3/org/users/current-user/s3-access-keys" + + if user_id: + api = "api/v3/org/users/%s/s3-access-keys/%s" % (user_id, access_key,) + + self.data = None + response, error = self.rest_api.delete(api, self.data) + if error: + self.module.fail_json(msg=error) + + def apply(self): + """ + Perform pre-checks, call functions and exit + """ + result_message = "" + resp_data = {} + user_id = None + + if self.parameters.get("unique_user_name"): + user_id = self.get_org_user_id(self.parameters["unique_user_name"]) + + if self.parameters["state"] == "present": + org_user_s3_key = None + if self.parameters.get("access_key"): + org_user_s3_key = self.get_org_user_s3_key(user_id, self.parameters["access_key"]) + resp_data = org_user_s3_key + + if not org_user_s3_key: # create + resp_data = self.create_org_user_s3_key(user_id) + self.na_helper.changed = True + + if self.parameters["state"] == "absent": + self.delete_org_user_s3_key(user_id, self.parameters["access_key"]) + self.na_helper.changed = True + result_message = "Org User S3 key deleted" + + self.module.exit_json(changed=self.na_helper.changed, msg=result_message, resp=resp_data) + + +def main(): + """ + Main function + """ + na_sg_org_user_s3_key = SgOrgUserS3Key() + na_sg_org_user_s3_key.apply() + + +if __name__ == "__main__": + main() |