#!/usr/bin/python # Copyright 2018 Daniel Pivonka # Copyright 2018 Red Hat, Inc. # # GNU General Public License v3.0+ from ansible.module_utils.basic import AnsibleModule from socket import error as socket_error import boto import radosgw ANSIBLE_METADATA = { 'metadata_version': '1.1', 'status': ['preview'], 'supported_by': 'community' } DOCUMENTATION = ''' --- module: ceph_add_users_buckets short_description: bulk create user and buckets description: - Bulk create Ceph Object Storage users and buckets option: rgw_host: description: - a radosgw host in the ceph cluster required: true port: description: - tcp port of the radosgw host required: true is_secure: description: - boolean indicating whether the instance is running over https required: false default: false admin_access_key: description: - radosgw admin user's access key required: true admin_secret_key: description: - radosgw admin user's secret key required: true users: description: - list of users to be created containing sub options required: false sub_options: username: description: - username for new user required: true fullname: description: - fullname for new user required: true email: description: - email for new user required: false maxbucket: description: - max bucket for new user required: false default: 1000 suspend: description: - suspend a new user apon creation required: false default: false autogenkey: description: - auto generate keys for new user required: false default: true accesskey: description: - access key for new user required: false secretkey: description: - secret key for new user required: false userquota: description: - enable/disable user quota for new user required: false default: false usermaxsize: description: - with user quota enabled specify quota size in kb required: false default: unlimited usermaxobjects: description: - with user quota enabled specify maximum number of objects required: false default: unlimited bucketquota: description: - enable/disable bucket quota for new user required: false default: false bucketmaxsize: description: - with bucket quota enabled specify bucket size in kb required: false default: unlimited bucketmaxobjects: description: - with bucket quota enabled specify maximum number of objects # noqa: E501 required: false default: unlimited buckets: description: - list of buckets to be created containing sub options required: false sub_options: bucket: description: - name for new bucket required: true user: description: - user new bucket will be linked too required: true requirements: ['radosgw', 'boto'] author: - 'Daniel Pivonka' ''' EXAMPLES = ''' # single basic user - name: single basic user ceph_add_users_buckets: rgw_host: '172.16.0.12' port: 8080 admin_access_key: 'N61I8625V4XTWGDTLBLL' admin_secret_key: 'HZrkuHHO9usUurDWBQHTeLIjO325bIULaC7DxcoV' users: - username: 'test1' fullname: 'tester' # single complex user - name: single complex user ceph_add_users_buckets: rgw_host: '172.16.0.12' port: 8080 admin_access_key: 'N61I8625V4XTWGDTLBLL' admin_secret_key: 'HZrkuHHO9usUurDWBQHTeLIjO325bIULaC7DxcoV' users: - username: 'test1' fullname: 'tester' email: 'dan@email.com' maxbucket: 666 suspend: true autogenkey: true accesskey: 'B3AR4Q33L59YV56A9A2F' secretkey: 'd84BRnMysnVGSyZiRlYUMduVgIarQWiNMdKzrF76' userquota: true usermaxsize: '1000' usermaxobjects: 3 bucketquota: true bucketmaxsize: '1000' bucketmaxobjects: 3 # multi user - name: multi user ceph_add_users_buckets: rgw_host: '172.16.0.12' port: 8080 admin_access_key: 'N61I8625V4XTWGDTLBLL' admin_secret_key: 'HZrkuHHO9usUurDWBQHTeLIjO325bIULaC7DxcoV' users: - username: 'test1' fullname: 'tester' email: 'dan@email.com' maxbucket: 666 suspend: true autogenkey: true accesskey: 'B3AR4Q33L59YV56A9A2F' secretkey: 'd84BRnMysnVGSyZiRlYUMduVgIarQWiNMdKzrF76' userquota: true usermaxsize: '1000K' usermaxobjects: 3 bucketquota: true bucketmaxsize: '1000K' bucketmaxobjects: 3 - username: 'test2' fullname: 'tester' # single bucket - name: single basic user ceph_add_users_buckets: rgw_host: '172.16.0.12' port: 8080 admin_access_key: 'N61I8625V4XTWGDTLBLL' admin_secret_key: 'HZrkuHHO9usUurDWBQHTeLIjO325bIULaC7DxcoV' buckets: - bucket: 'heyimabucket1' user: 'test1' # multi bucket - name: single basic user ceph_add_users_buckets: rgw_host: '172.16.0.12' port: 8080 admin_access_key: 'N61I8625V4XTWGDTLBLL' admin_secret_key: 'HZrkuHHO9usUurDWBQHTeLIjO325bIULaC7DxcoV' buckets: - bucket: 'heyimabucket1' user: 'test1' - bucket: 'heyimabucket2' user: 'test2' - bucket: 'heyimabucket3' user: 'test2' # buckets and users - name: single basic user ceph_add_users_buckets: rgw_host: '172.16.0.12' port: 8080 admin_access_key: 'N61I8625V4XTWGDTLBLL' admin_secret_key: 'HZrkuHHO9usUurDWBQHTeLIjO325bIULaC7DxcoV' users: - username: 'test1' fullname: 'tester' email: 'dan@email.com' maxbucket: 666 - username: 'test2' fullname: 'tester' email: 'dan1@email.com' accesskey: 'B3AR4Q33L59YV56A9A2F' secretkey: 'd84BRnMysnVGSyZiRlYUMduVgIarQWiNMdKzrF76' userquota: true usermaxsize: '1000' usermaxobjects: 3 bucketquota: true bucketmaxsize: '1000' bucketmaxobjects: 3 buckets: - bucket: 'heyimabucket1' user: 'test1' - bucket: 'heyimabucket2' user: 'test2' - bucket: 'heyimabucket3' user: 'test2' ''' RETURN = ''' error_messages: description: error for failed user or bucket. returned: always type: list sample: [ "test2: could not modify user: unable to modify user, cannot add duplicate email\n" # noqa: E501 ] failed_users: description: users that were not created. returned: always type: str sample: "test2" added_users: description: users that were created. returned: always type: str sample: "test1" failed_buckets: description: buckets that were not created. returned: always type: str sample: "heyimabucket3" added_buckets: description: buckets that were created. returned: always type: str sample: "heyimabucket1, heyimabucket2" ''' def create_users(rgw, users, result): added_users = [] failed_users = [] for user in users: # get info username = user['username'] fullname = user['fullname'] email = user['email'] maxbucket = user['maxbucket'] suspend = user['suspend'] autogenkey = user['autogenkey'] accesskey = user['accesskey'] secretkey = user['secretkey'] userquota = user['userquota'] usermaxsize = user['usermaxsize'] usermaxobjects = user['usermaxobjects'] bucketquota = user['bucketquota'] bucketmaxsize = user['bucketmaxsize'] bucketmaxobjects = user['bucketmaxobjects'] fail_flag = False # check if user exists try: user_info = rgw.get_user(uid=username) except radosgw.exception.RadosGWAdminError: # it doesnt exist user_info = None # user exists can not create if user_info: result['error_messages'].append(username + ' UserExists') failed_users.append(username) else: # user doesnt exist create it if email: if autogenkey: try: rgw.create_user(username, fullname, email=email, key_type='s3', # noqa: E501 generate_key=autogenkey, max_buckets=maxbucket, suspended=suspend) # noqa: E501 except radosgw.exception.RadosGWAdminError as e: result['error_messages'].append(username + ' ' + e.get_code()) # noqa: E501 fail_flag = True else: try: rgw.create_user(username, fullname, email=email, key_type='s3', # noqa: E501 access_key=accesskey, secret_key=secretkey, # noqa: E501 max_buckets=maxbucket, suspended=suspend) # noqa: E501 except radosgw.exception.RadosGWAdminError as e: result['error_messages'].append(username + ' ' + e.get_code()) # noqa: E501 fail_flag = True else: if autogenkey: try: rgw.create_user(username, fullname, key_type='s3', generate_key=autogenkey, max_buckets=maxbucket, suspended=suspend) # noqa: E501 except radosgw.exception.RadosGWAdminError as e: result['error_messages'].append(username + ' ' + e.get_code()) # noqa: E501 fail_flag = True else: try: rgw.create_user(username, fullname, key_type='s3', access_key=accesskey, secret_key=secretkey, # noqa: E501 max_buckets=maxbucket, suspended=suspend) # noqa: E501 except radosgw.exception.RadosGWAdminError as e: result['error_messages'].append(username + ' ' + e.get_code()) # noqa: E501 fail_flag = True if not fail_flag and userquota: try: rgw.set_quota(username, 'user', max_objects=usermaxobjects, max_size_kb=usermaxsize, enabled=True) except radosgw.exception.RadosGWAdminError as e: result['error_messages'].append(username + ' ' + e.get_code()) # noqa: E501 fail_flag = True if not fail_flag and bucketquota: try: rgw.set_quota(username, 'bucket', max_objects=bucketmaxobjects, # noqa: E501 max_size_kb=bucketmaxsize, enabled=True) except radosgw.exception.RadosGWAdminError as e: result['error_messages'].append(username + ' ' + e.get_code()) # noqa: E501 fail_flag = True if fail_flag: try: rgw.delete_user(username) except radosgw.exception.RadosGWAdminError: pass failed_users.append(username) else: added_users.append(username) result['added_users'] = ", ".join(added_users) result['failed_users'] = ", ".join(failed_users) def create_buckets(rgw, buckets, result): added_buckets = [] failed_buckets = [] for bucket_info in buckets: bucket = bucket_info['bucket'] user = bucket_info['user'] # check if bucket exists try: bucket_info = rgw.get_bucket(bucket_name=bucket) except TypeError: # it doesnt exist bucket_info = None # if it exists add to failed list if bucket_info: failed_buckets.append(bucket) result['error_messages'].append(bucket + ' BucketExists') else: # bucket doesn't exist, so we need to create it bucket_info = create_bucket(rgw, bucket) if bucket_info: # bucket created ok, link to user # check if user exists try: user_info = rgw.get_user(uid=user) except radosgw.exception.RadosGWAdminError: # it doesnt exist user_info = None # user exists, link if user_info: try: rgw.link_bucket(bucket_name=bucket, bucket_id=bucket_info.id, uid=user) added_buckets.append(bucket) except radosgw.exception.RadosGWAdminError as e: result['error_messages'].append(bucket + e.get_code()) try: rgw.delete_bucket(bucket, purge_objects=True) except radosgw.exception.RadosGWAdminError: pass failed_buckets.append(bucket) else: # user doesnt exist cant be link delete bucket try: rgw.delete_bucket(bucket, purge_objects=True) except radosgw.exception.RadosGWAdminError: pass failed_buckets.append(bucket) result['error_messages'].append(bucket + ' could not be linked' + ', NoSuchUser ' + user) # noqa: E501 else: # something went wrong failed_buckets.append(bucket) result['error_messages'].append(bucket + ' could not be created') # noqa: E501 result['added_buckets'] = ", ".join(added_buckets) result['failed_buckets'] = ", ".join(failed_buckets) def create_bucket(rgw, bucket): conn = boto.connect_s3(aws_access_key_id=rgw.provider._access_key, aws_secret_access_key=rgw.provider._secret_key, host=rgw._connection[0], port=rgw.port, is_secure=rgw.is_secure, calling_format=boto.s3.connection.OrdinaryCallingFormat(), # noqa: E501 ) try: conn.create_bucket(bucket_name=bucket) bucket_info = rgw.get_bucket(bucket_name=bucket) except boto.exception.S3ResponseError: return None else: return bucket_info def main(): # arguments/parameters that a user can pass to the module fields = dict(rgw_host=dict(type='str', required=True), port=dict(type='int', required=True), is_secure=dict(type='bool', required=False, default=False), admin_access_key=dict(type='str', required=True), admin_secret_key=dict(type='str', required=True), buckets=dict(type='list', required=False, elements='dict', options=dict(bucket=dict(type='str', required=True), # noqa: E501 user=dict(type='str', required=True))), # noqa: E501 users=dict(type='list', required=False, elements='dict', options=dict(username=dict(type='str', required=True), # noqa: E501 fullname=dict(type='str', required=True), # noqa: E501 email=dict(type='str', required=False), # noqa: E501 maxbucket=dict(type='int', required=False, default=1000), # noqa: E501 suspend=dict(type='bool', required=False, default=False), # noqa: E501 autogenkey=dict(type='bool', required=False, default=True), # noqa: E501 accesskey=dict(type='str', required=False), # noqa: E501 secretkey=dict(type='str', required=False), # noqa: E501 userquota=dict(type='bool', required=False, default=False), # noqa: E501 usermaxsize=dict(type='str', required=False, default='-1'), # noqa: E501 usermaxobjects=dict(type='int', required=False, default=-1), # noqa: E501 bucketquota=dict(type='bool', required=False, default=False), # noqa: E501 bucketmaxsize=dict(type='str', required=False, default='-1'), # noqa: E501 bucketmaxobjects=dict(type='int', required=False, default=-1)))) # noqa: E501 # the AnsibleModule object module = AnsibleModule(argument_spec=fields, supports_check_mode=False) # get vars rgw_host = module.params.get('rgw_host') port = module.params.get('port') is_secure = module.params.get('is_secure') admin_access_key = module.params.get('admin_access_key') admin_secret_key = module.params.get('admin_secret_key') users = module.params['users'] buckets = module.params.get('buckets') # seed the result dict in the object result = dict( changed=False, error_messages=[], added_users='', failed_users='', added_buckets='', failed_buckets='', ) # radosgw connection rgw = radosgw.connection.RadosGWAdminConnection(host=rgw_host, port=port, access_key=admin_access_key, # noqa: E501 secret_key=admin_secret_key, # noqa: E501 aws_signature='AWS4', is_secure=is_secure) # test connection connected = True try: rgw.get_usage() except radosgw.exception.RadosGWAdminError as e: connected = False result['error_messages'] = e.get_code() except socket_error as e: connected = False result['error_messages'] = str(e) if connected and users: create_users(rgw, users, result) if connected and buckets: create_buckets(rgw, buckets, result) if result['added_users'] != '' or result['added_buckets'] != '': result['changed'] = True # conditional state caused a failure if result['added_users'] == '' and result['added_buckets'] == '': module.fail_json(msg='No users or buckets were added successfully', **result) # EXIT module.exit_json(**result) if __name__ == '__main__': main()