summaryrefslogtreecommitdiffstats
path: root/ansible_collections/netapp/storagegrid/plugins/modules
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:04:41 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:04:41 +0000
commit975f66f2eebe9dadba04f275774d4ab83f74cf25 (patch)
tree89bd26a93aaae6a25749145b7e4bca4a1e75b2be /ansible_collections/netapp/storagegrid/plugins/modules
parentInitial commit. (diff)
downloadansible-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')
-rw-r--r--ansible_collections/netapp/storagegrid/plugins/modules/na_sg_grid_account.py458
-rw-r--r--ansible_collections/netapp/storagegrid/plugins/modules/na_sg_grid_certificate.py226
-rw-r--r--ansible_collections/netapp/storagegrid/plugins/modules/na_sg_grid_client_certificate.py265
-rw-r--r--ansible_collections/netapp/storagegrid/plugins/modules/na_sg_grid_dns.py163
-rw-r--r--ansible_collections/netapp/storagegrid/plugins/modules/na_sg_grid_gateway.py532
-rw-r--r--ansible_collections/netapp/storagegrid/plugins/modules/na_sg_grid_group.py341
-rw-r--r--ansible_collections/netapp/storagegrid/plugins/modules/na_sg_grid_ha_group.py334
-rw-r--r--ansible_collections/netapp/storagegrid/plugins/modules/na_sg_grid_identity_federation.py335
-rw-r--r--ansible_collections/netapp/storagegrid/plugins/modules/na_sg_grid_info.py405
-rw-r--r--ansible_collections/netapp/storagegrid/plugins/modules/na_sg_grid_ntp.py173
-rw-r--r--ansible_collections/netapp/storagegrid/plugins/modules/na_sg_grid_regions.py163
-rw-r--r--ansible_collections/netapp/storagegrid/plugins/modules/na_sg_grid_traffic_classes.py375
-rw-r--r--ansible_collections/netapp/storagegrid/plugins/modules/na_sg_grid_user.py316
-rw-r--r--ansible_collections/netapp/storagegrid/plugins/modules/na_sg_org_container.py352
-rw-r--r--ansible_collections/netapp/storagegrid/plugins/modules/na_sg_org_group.py301
-rw-r--r--ansible_collections/netapp/storagegrid/plugins/modules/na_sg_org_identity_federation.py335
-rw-r--r--ansible_collections/netapp/storagegrid/plugins/modules/na_sg_org_info.py279
-rw-r--r--ansible_collections/netapp/storagegrid/plugins/modules/na_sg_org_user.py335
-rw-r--r--ansible_collections/netapp/storagegrid/plugins/modules/na_sg_org_user_s3_key.py210
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()