-
Notifications
You must be signed in to change notification settings - Fork 36
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
346 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
bugfixes: | ||
- na_ontap_quotas - fix another quota operation is currently in progress issue. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
#!/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) | ||
|
||
from __future__ import absolute_import, division, print_function | ||
|
||
__metaclass__ = type | ||
|
||
DOCUMENTATION = ''' | ||
module: na_ontap_s3_users | ||
short_description: NetApp ONTAP S3 users | ||
extends_documentation_fragment: | ||
- netapp.ontap.netapp.na_ontap | ||
version_added: 21.19.0 | ||
author: NetApp Ansible Team (@carchi8py) <[email protected]> | ||
description: | ||
- Create, delete, or modify S3 users on NetApp ONTAP. | ||
options: | ||
state: | ||
description: | ||
- Whether the specified S3 user should exist or not. | ||
choices: ['present', 'absent'] | ||
type: str | ||
default: 'present' | ||
name: | ||
description: | ||
- The name of the S3 user. | ||
type: str | ||
required: true | ||
vserver: | ||
description: | ||
- Name of the vserver to use. | ||
type: str | ||
required: true | ||
comment: | ||
description: | ||
- comment about the user | ||
type: str | ||
''' | ||
|
||
EXAMPLES = """ | ||
- name: create or modify s3 user | ||
na_ontap_s3_users: | ||
state: present | ||
name: carchi8py | ||
vserver: ansibleSVM | ||
comment: not enabled | ||
hostname: "{{ netapp_hostname }}" | ||
username: "{{ netapp_username }}" | ||
password: "{{ netapp_password }}" | ||
https: true | ||
validate_certs: false | ||
use_rest: always | ||
- name: delete s3 user | ||
na_ontap_s3_users: | ||
state: absent | ||
name: carchi8py | ||
vserver: ansibleSVM | ||
hostname: "{{ netapp_hostname }}" | ||
username: "{{ netapp_username }}" | ||
password: "{{ netapp_password }}" | ||
https: true | ||
validate_certs: false | ||
use_rest: always | ||
""" | ||
|
||
RETURN = """ | ||
""" | ||
|
||
import traceback | ||
from ansible.module_utils.basic import AnsibleModule | ||
from ansible.module_utils._text import to_native | ||
import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils | ||
from ansible_collections.netapp.ontap.plugins.module_utils.netapp_module import NetAppModule | ||
from ansible_collections.netapp.ontap.plugins.module_utils.netapp import OntapRestAPI | ||
from ansible_collections.netapp.ontap.plugins.module_utils import rest_generic | ||
from ansible_collections.netapp.ontap.plugins.module_utils import rest_vserver | ||
|
||
|
||
class NetAppOntapS3Users: | ||
def __init__(self): | ||
self.argument_spec = netapp_utils.na_ontap_host_argument_spec() | ||
self.argument_spec.update(dict( | ||
state=dict(required=False, type='str', choices=['present', 'absent'], default='present'), | ||
vserver=dict(required=True, type='str'), | ||
name=dict(required=True, type='str'), | ||
comment=dict(required=False, type='str'), | ||
)) | ||
self.module = AnsibleModule( | ||
argument_spec=self.argument_spec, | ||
supports_check_mode=True | ||
) | ||
self.svm_uuid = None | ||
self.na_helper = NetAppModule(self.module) | ||
self.parameters = self.na_helper.check_and_set_parameters(self.module) | ||
self.rest_api = OntapRestAPI(self.module) | ||
self.use_rest = self.rest_api.is_rest() | ||
if not self.use_rest: | ||
self.module.fail_json(msg='na_ontap_S3_users is only supported with REST API') | ||
if not self.rest_api.meets_rest_minimum_version(self.use_rest, 9, 8): | ||
self.module.fail_json(msg="ONTAP version must be 9.8 or higher") | ||
|
||
def get_s3_user(self): | ||
self.get_svm_uuid() | ||
api = 'protocols/s3/services/%s/users' % self.svm_uuid | ||
fields = ','.join(('name', | ||
'comment')) | ||
params = {'name': self.parameters['name'], | ||
'fields': fields} | ||
record, error = rest_generic.get_one_record(self.rest_api, api, params) | ||
if error: | ||
self.module.fail_json(msg='Error fetching S3 user %s: %s' % (self.parameters['name'], to_native(error)), | ||
exception=traceback.format_exc()) | ||
return record | ||
|
||
def get_svm_uuid(self): | ||
record, error = rest_vserver.get_vserver_uuid(self.rest_api, self.parameters['vserver'], self.module, True) | ||
self.svm_uuid = record | ||
|
||
def create_s3_user(self): | ||
api = 'protocols/s3/services/%s/users' % self.svm_uuid | ||
body = {'name': self.parameters['name']} | ||
if self.parameters.get('comment'): | ||
body['comment'] = self.parameters['comment'] | ||
dummy, error = rest_generic.post_async(self.rest_api, api, body) | ||
if error: | ||
self.module.fail_json(msg='Error creating S3 user %s: %s' % (self.parameters['name'], to_native(error)), | ||
exception=traceback.format_exc()) | ||
|
||
def delete_s3_user(self): | ||
api = 'protocols/s3/services/%s/users' % self.svm_uuid | ||
dummy, error = rest_generic.delete_async(self.rest_api, api, self.parameters['name']) | ||
if error: | ||
self.module.fail_json(msg='Error deleting S3 user %s: %s' % (self.parameters['name'], to_native(error)), | ||
exception=traceback.format_exc()) | ||
|
||
def modify_s3_user(self, modify): | ||
api = 'protocols/s3/services/%s/users' % self.svm_uuid | ||
body = {} | ||
if modify.get('comment'): | ||
body['comment'] = self.parameters['comment'] | ||
dummy, error = rest_generic.patch_async(self.rest_api, api, self.parameters['name'], body) | ||
if error: | ||
self.module.fail_json(msg='Error modifying S3 user %s: %s' % (self.parameters['name'], to_native(error)), | ||
exception=traceback.format_exc()) | ||
|
||
def apply(self): | ||
current = self.get_s3_user() | ||
cd_action, modify = None, None | ||
cd_action = self.na_helper.get_cd_action(current, self.parameters) | ||
if cd_action is None: | ||
modify = self.na_helper.get_modified_attributes(current, self.parameters) | ||
if self.na_helper.changed and not self.module.check_mode: | ||
if cd_action == 'create': | ||
self.create_s3_user() | ||
if cd_action == 'delete': | ||
self.delete_s3_user() | ||
if modify: | ||
self.modify_s3_user(modify) | ||
self.module.exit_json(changed=self.na_helper.changed) | ||
|
||
|
||
def main(): | ||
'''Apply volume operations from playbook''' | ||
obj = NetAppOntapS3Users() | ||
obj.apply() | ||
|
||
|
||
if __name__ == '__main__': | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
# (c) 2022, NetApp, Inc | ||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) | ||
|
||
from __future__ import (absolute_import, division, print_function) | ||
|
||
__metaclass__ = type | ||
|
||
import pytest | ||
import sys | ||
|
||
from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch, call | ||
import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils | ||
from ansible_collections.netapp.ontap.tests.unit.plugins.module_utils.ansible_mocks import set_module_args, \ | ||
patch_ansible, create_and_apply, create_module, expect_and_capture_ansible_exception | ||
from ansible_collections.netapp.ontap.tests.unit.framework.mock_rest_and_zapi_requests import get_mock_record, \ | ||
patch_request_and_invoke, register_responses | ||
from ansible_collections.netapp.ontap.tests.unit.framework.rest_factory import rest_responses | ||
|
||
from ansible_collections.netapp.ontap.plugins.modules.na_ontap_s3_users \ | ||
import NetAppOntapS3Users as my_module, main as my_main # module under test | ||
|
||
if not netapp_utils.HAS_REQUESTS and sys.version_info < (2, 7): | ||
pytestmark = pytest.mark.skip('Skipping Unit Tests on 2.6 as requests is not available') | ||
|
||
SRR = rest_responses({ | ||
's3_user': (200, { | ||
"records": [ | ||
{ | ||
"comment": "S3 user", | ||
"name": "carchi8py", | ||
"svm": { | ||
"name": "svm1", | ||
"uuid": "e3cb5c7f-cd20" | ||
} | ||
} | ||
], | ||
"num_records": 1 | ||
}, None), | ||
'svm_uuid': (200, {"records": [ | ||
{ | ||
'uuid': 'e3cb5c7f-cd20' | ||
}], "num_records": 1}, None) | ||
}) | ||
|
||
DEFAULT_ARGS = { | ||
'hostname': 'hostname', | ||
'username': 'username', | ||
'password': 'password', | ||
'name': 'carchi8py', | ||
'vserver': 'vserver' | ||
} | ||
|
||
|
||
def test_low_version(): | ||
register_responses([ | ||
('GET', 'cluster', SRR['is_rest_97']) | ||
]) | ||
error = create_module(my_module, DEFAULT_ARGS, fail=True)['msg'] | ||
print('Info: %s' % error) | ||
msg = 'ONTAP version must be 9.8 or higher' | ||
assert msg in error | ||
|
||
|
||
def test_get_s3_users_none(): | ||
register_responses([ | ||
('GET', 'cluster', SRR['is_rest_9_10_1']), | ||
('GET', 'svm/svms', SRR['svm_uuid']), | ||
('GET', 'protocols/s3/services/e3cb5c7f-cd20/users', SRR['empty_records']) | ||
]) | ||
set_module_args(DEFAULT_ARGS) | ||
my_obj = my_module() | ||
assert my_obj.get_s3_user() is None | ||
|
||
|
||
def test_get_s3_users_error(): | ||
register_responses([ | ||
('GET', 'cluster', SRR['is_rest_9_10_1']), | ||
('GET', 'svm/svms', SRR['svm_uuid']), | ||
('GET', 'protocols/s3/services/e3cb5c7f-cd20/users', SRR['generic_error']) | ||
]) | ||
my_module_object = create_module(my_module, DEFAULT_ARGS) | ||
msg = 'Error fetching S3 user carchi8py: calling: protocols/s3/services/e3cb5c7f-cd20/users: got Expected error.' | ||
assert msg in expect_and_capture_ansible_exception(my_module_object.get_s3_user, 'fail')['msg'] | ||
|
||
|
||
def test_create_s3_users(): | ||
register_responses([ | ||
('GET', 'cluster', SRR['is_rest_9_10_1']), | ||
('GET', 'svm/svms', SRR['svm_uuid']), | ||
('GET', 'protocols/s3/services/e3cb5c7f-cd20/users', SRR['empty_records']), | ||
('POST', 'protocols/s3/services/e3cb5c7f-cd20/users', SRR['empty_good']) | ||
]) | ||
module_args = { | ||
'comment': 'this is a s3 user', | ||
} | ||
assert create_and_apply(my_module, DEFAULT_ARGS, module_args)['changed'] | ||
|
||
|
||
def test_create_s3_user_error(): | ||
register_responses([ | ||
('GET', 'cluster', SRR['is_rest_9_8_0']), | ||
('POST', 'protocols/s3/services/e3cb5c7f-cd20/users', SRR['generic_error']) | ||
]) | ||
my_obj = create_module(my_module, DEFAULT_ARGS) | ||
my_obj.parameters['comment'] = 'this is a s3 user' | ||
my_obj.svm_uuid = 'e3cb5c7f-cd20' | ||
error = expect_and_capture_ansible_exception(my_obj.create_s3_user, 'fail')['msg'] | ||
print('Info: %s' % error) | ||
assert 'Error creating S3 user carchi8py: calling: protocols/s3/services/e3cb5c7f-cd20/users: got Expected error.' == error | ||
|
||
|
||
def test_delete_s3_user(): | ||
register_responses([ | ||
('GET', 'cluster', SRR['is_rest_9_10_1']), | ||
('GET', 'svm/svms', SRR['svm_uuid']), | ||
('GET', 'protocols/s3/services/e3cb5c7f-cd20/users', SRR['s3_user']), | ||
('DELETE', 'protocols/s3/services/e3cb5c7f-cd20/users/carchi8py', SRR['empty_good']) | ||
]) | ||
module_args = {'state': 'absent'} | ||
assert create_and_apply(my_module, DEFAULT_ARGS, module_args)['changed'] | ||
|
||
|
||
def test_delete_s3_user_error(): | ||
register_responses([ | ||
('GET', 'cluster', SRR['is_rest_9_8_0']), | ||
('DELETE', 'protocols/s3/services/e3cb5c7f-cd20/users/carchi8py', SRR['generic_error']) | ||
]) | ||
my_obj = create_module(my_module, DEFAULT_ARGS) | ||
my_obj.parameters['state'] = 'absent' | ||
my_obj.svm_uuid = 'e3cb5c7f-cd20' | ||
error = expect_and_capture_ansible_exception(my_obj.delete_s3_user, 'fail')['msg'] | ||
print('Info: %s' % error) | ||
assert 'Error deleting S3 user carchi8py: calling: protocols/s3/services/e3cb5c7f-cd20/users/carchi8py: got Expected error.' == error | ||
|
||
|
||
def test_modify_s3_user(): | ||
register_responses([ | ||
('GET', 'cluster', SRR['is_rest_9_10_1']), | ||
('GET', 'svm/svms', SRR['svm_uuid']), | ||
('GET', 'protocols/s3/services/e3cb5c7f-cd20/users', SRR['s3_user']), | ||
('PATCH', 'protocols/s3/services/e3cb5c7f-cd20/users/carchi8py', SRR['empty_good']) | ||
]) | ||
module_args = {'comment': 'this is a modify comment'} | ||
assert create_and_apply(my_module, DEFAULT_ARGS, module_args)['changed'] | ||
|
||
|
||
def test_modify_s3_user_error(): | ||
register_responses([ | ||
('GET', 'cluster', SRR['is_rest_9_8_0']), | ||
('PATCH', 'protocols/s3/services/e3cb5c7f-cd20/users/carchi8py', SRR['generic_error']) | ||
]) | ||
my_obj = create_module(my_module, DEFAULT_ARGS) | ||
my_obj.parameters['comment'] = 'this is a modified s3 service' | ||
current = {'comment': 'this is a modified s3 service'} | ||
my_obj.svm_uuid = 'e3cb5c7f-cd20' | ||
error = expect_and_capture_ansible_exception(my_obj.modify_s3_user, 'fail', current)['msg'] | ||
print('Info: %s' % error) | ||
assert 'Error modifying S3 user carchi8py: calling: protocols/s3/services/e3cb5c7f-cd20/users/carchi8py: got Expected error.' == error |