Skip to content

Commit

Permalink
Sync bitbucket and GitHub
Browse files Browse the repository at this point in the history
  • Loading branch information
carchi8py committed Jun 8, 2022
1 parent cac5ea0 commit b373ca2
Show file tree
Hide file tree
Showing 8 changed files with 346 additions and 5 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ Join our Slack Channel at [Netapp.io](http://netapp.io/slack)
- na_ontap_net_subnet - delete fails if ipspace is different than Default.
- na_ontap_portset - fixed idempotency issue when `ports` has identical values.
- na_ontap_portset - fixed error when trying to remove partial ports from portset if igroups are bound to it.
- na_ontap_quotas - fix another quota operation is currently in progress issue.
- na_ontap_quotas - fix idempotency issue on `threshold` option.
- na_ontap_snapmirror - support for SSL certificate authentication for both sides when using ONTAP.
- na_ontap_snapmirror - fix issue where there was no wait on quiesce before aborting.
Expand All @@ -63,6 +64,7 @@ Join our Slack Channel at [Netapp.io](http://netapp.io/slack)

### New Module
- na_ontap_s3_service - Manage S3 services.
- na_ontap_s3_users - Manage S3 users.

### Minor Changes
- na_ontap_aggregate - updated `disk_types` in documentation.
Expand Down
2 changes: 2 additions & 0 deletions changelogs/fragments/DEVOPS-4606.yaml
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.
1 change: 1 addition & 0 deletions meta/runtime.yml
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ action_groups:
- na_ontap_restit
- na_ontap_s3_buckets
- na_ontap_s3_services
- na_ontap_s3_users
- na_ontap_security_certificates
- na_ontap_security_config
- na_ontap_security_key_manager
Expand Down
2 changes: 1 addition & 1 deletion plugins/module_utils/rest_vserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def get_vserver_uuid(rest_api, name, module, error_on_none=None):
if error:
module.fail_json(msg=error)
if record is None and error_on_none:
return record, True
module.fail_json(msg="Error: Specified vserver %s not found" % name)
if record:
return record['uuid'], error
return record, error
7 changes: 5 additions & 2 deletions plugins/modules/na_ontap_quotas.py
Original file line number Diff line number Diff line change
Expand Up @@ -488,8 +488,11 @@ def apply(self):
modify_quota = self.na_helper.get_modified_attributes(current, self.parameters)
quota_status = self.get_quota_status()
if 'set_quota_status' in self.parameters and quota_status is not None:
quota_status_action = self.na_helper.get_modified_attributes({'set_quota_status': quota_status == 'on'}, self.parameters)

# if 'set_quota_status' == True in create, sometimes there is delay in status update from 'initializing' -> 'on'.
# if quota_status == 'on' and options(set_quota_status == True and activate_quota_on_change == 'resize'),
# sometimes there is delay in status update from 'resizing' -> 'on'
set_quota_status = True if quota_status in ('on', 'resizing', 'initializing') else False
quota_status_action = self.na_helper.get_modified_attributes({'set_quota_status': set_quota_status}, self.parameters)
if quota_status_action:
modify_quota_status = 'quota-on' if quota_status_action['set_quota_status'] else 'quota-off'
if (self.parameters['activate_quota_on_change'] in ['resize', 'reinitialize']
Expand Down
177 changes: 177 additions & 0 deletions plugins/modules/na_ontap_s3_users.py
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()
2 changes: 0 additions & 2 deletions plugins/modules/na_ontap_vscan_on_access_policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -434,8 +434,6 @@ def get_on_access_policy_rest(self):

def get_svm_uuid(self):
uuid, error = rest_vserver.get_vserver_uuid(self.rest_api, self.parameters['vserver'], self.module, True)
if error:
self.module.fail_json(msg="Error: Specified vserver %s not found" % self.parameters['vserver'])
return uuid

def create_on_access_policy_rest(self):
Expand Down
158 changes: 158 additions & 0 deletions tests/unit/plugins/modules/test_na_ontap_s3_users.py
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

0 comments on commit b373ca2

Please sign in to comment.