#!/usr/bin/python DOCUMENTATION = r''' --- module: decort_rg description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home). ''' from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.basic import env_fallback from ansible.module_utils.decort_utils import * class decort_rg(DecortController): def __init__(self): super(decort_rg, self).__init__(AnsibleModule(**self.amodule_init_args)) amodule = self.amodule self.validated_acc_id = 0 self.rg_id: int = 0 if amodule.params['rg_id'] is None: if self.amodule.params['account_id']: self.validated_acc_id, _ = self.account_find("", amodule.params['account_id']) elif amodule.params['account_name']: self.validated_acc_id, _ = self.account_find(amodule.params['account_name']) if not self.validated_acc_id: # we failed to locate account by either name or ID - abort with an error self.result['failed'] = True self.result['msg'] = ("Current user does not have access to the requested account " "or non-existent account specified.") self.amodule.fail_json(**self.result) # Check if the RG with the specified parameters already exists self.get_info() if amodule.params['state'] != "absent": self.rg_should_exist = True else: self.rg_should_exist = False if self.rg_id and ( self.rg_info.status != sdk_types.ResourceGroupStatus.DESTROYED ): self.check_amodule_args_for_change() def get_info(self): # If this is the first getting info if self._rg_info is None: self.rg_id, self._rg_info = self.rg_find( arg_account_id=self.validated_acc_id, arg_rg_id=self.aparams['rg_id'], arg_rg_name=self.aparams['rg_name'], arg_check_state=False, ) # If this is a repeated getting info else: # If check mode is enabled, there is no needed to # request info again if self.amodule.check_mode: return _, self._rg_info = self.rg_find(arg_rg_id=self.rg_id) def access(self): should_change_access = False acc_granted = False for rg_item in self.rg_info.acl: if rg_item.user_name == self.amodule.params['access']['user']: acc_granted = True if self.amodule.params['access']['action'] == 'grant': if rg_item.access_type.value != self.amodule.params['access']['right']: should_change_access = True if self.amodule.params['access']['action'] == 'revoke': should_change_access = True if acc_granted == False and self.amodule.params['access']['action'] == 'grant': should_change_access = True if should_change_access == True: if self.amodule.params['access']['action'] == "grant": self.sdk_checkmode(self.api.ca.rg.access_grant)( access_type=sdk_types.AccessTypeForSet( self.amodule.params['access']['right'] ), rg_id=self.rg_id, user_name=self.amodule.params['access']['user'], ) else: self.sdk_checkmode(self.api.ca.rg.access_revoke)( rg_id=self.rg_id, user_name=self.amodule.params['access']['user'], ) self.rg_should_exist = True return def error(self): self.result['failed'] = True self.result['changed'] = False if self.rg_id: self.result['msg'] = ("Invalid target state '{}' requested for rg ID {} in the " "current status '{}'.").format(self.rg_id, self.amodule.params['state'], self.rg_info.status.value) else: self.result['msg'] = ("Invalid target state '{}' requested for non-existent rg name '{}' " "in account ID {} ").format(self.amodule.params['state'], self.amodule.params['rg_name'], self.validated_acc_id) return def update(self): try: rg_res_model = ( self.api.cloudapi.rg.get_resource_consumption(rg_id=self.rg_id) ) except sdk_exceptions.RequestException as e: self.message( msg=( f'Failed to get RG Resources by ID {self.rg_id}: {e}' ) ) self.exit(fail=True) reserved_resources = rg_res_model.reserved incorrect_quota = dict(Requested=dict(), Reserved=dict(),) query_key_map = dict( cpu='cpu_count', ram='ram_size_mb', disk='storage_size_gb_by_real_usage', ext_ips='ext_ip_count', storage_policies='storage_policies', ) if self.amodule.params['quotas']: for quota_item in self.amodule.params['quotas']: if quota_item == 'storage_policies': rg_storage_policies = reserved_resources.storage_policies aparam_storage_policies = self.amodule.params['quotas'][ quota_item ] for aparam_storage_policy in aparam_storage_policies: aparam_storage_policy_id = aparam_storage_policy['id'] rg_storage_policy = rg_storage_policies.get( str(aparam_storage_policy_id) ) if ( rg_storage_policy and aparam_storage_policy['storage_size_gb'] < rg_storage_policy.storage_size_gb_by_real_usage ): incorrect_quota['Requested'][quota_item] = ( self.amodule.params['quotas'][quota_item] ) incorrect_quota['Reserved'][quota_item] = ( getattr( reserved_resources, query_key_map[quota_item] ) ) elif ( self.amodule.params['quotas'][quota_item] < getattr(reserved_resources, query_key_map[quota_item]) ): incorrect_quota['Requested'][quota_item] = ( self.amodule.params['quotas'][quota_item] ) incorrect_quota['Reserved'][quota_item] = ( getattr(reserved_resources, query_key_map[quota_item]) ) if reserved_resources and incorrect_quota['Requested']: self.result['msg'] = ("Cannot limit less than already reserved'{}'").format(incorrect_quota) self.result['failed'] = True if not self.result['failed']: self.rg_update( rg_model=self.rg_info, arg_quotas=self.amodule.params['quotas'], arg_res_types=self.amodule.params['resType'], arg_newname=self.amodule.params['rename'], arg_sep_pools=self.amodule.params['sep_pools'], arg_desc=self.amodule.params['description'], ) self.rg_should_exist = True return def setDefNet(self): rg_def_net_type = self.rg_info.default_net_type.value rg_def_net_id = self.rg_info.default_net_id aparam_def_net_type = self.aparams['def_netType'] aparam_def_net_id = self.aparams['def_netId'] need_to_reset = ( aparam_def_net_type == sdk_types.RGDefaultNetType.NONE and rg_def_net_type != aparam_def_net_type ) need_to_change = False if aparam_def_net_id is not None: need_to_change = (aparam_def_net_id != rg_def_net_id or aparam_def_net_type != rg_def_net_type) if need_to_reset or need_to_change: if aparam_def_net_type == sdk_types.RGDefaultNetType.NONE: self.sdk_checkmode(self.api.ca.rg.remove_def_net)( rg_id=self.rg_id, ) else: if aparam_def_net_type == sdk_types.RGDefaultNetType.PRIVATE: net_type = sdk_types.RGDefaultNetTypeForSet.PRIVATE elif aparam_def_net_type == sdk_types.RGDefaultNetType.PUBLIC: net_type = sdk_types.RGDefaultNetTypeForSet.PUBLIC self.sdk_checkmode(self.api.ca.rg.set_def_net)( net_type=net_type, rg_id=self.rg_id, net_id=aparam_def_net_id, ) self.rg_should_exist = True return def create(self): self.rg_id = self.rg_provision( self.validated_acc_id, self.amodule.params['rg_name'], self.amodule.params['owner'], self.amodule.params['description'], self.amodule.params['resType'], self.amodule.params['def_netType'], self.amodule.params['ipcidr'], self.amodule.params['extNetId'], self.amodule.params['extNetIp'], self.amodule.params['quotas'], "", # this is location code. TODO: add module argument sdn_access_group_id=self.aparams['sdn_access_group_id'], ) if self.rg_id: self.rg_id, self._rg_info = self.rg_find( arg_account_id=self.validated_acc_id, arg_rg_id=self.rg_id, arg_rg_name="", arg_check_state=False ) self.rg_should_exist = True return def enable(self): if self.amodule.params['state'] == "enabled": self.sdk_checkmode(self.api.ca.rg.enable)( rg_id=self.rg_id, ) elif self.amodule.params['state'] == "disabled": self.sdk_checkmode(self.api.ca.rg.disable)( rg_id=self.rg_id, ) self.rg_should_exist = True return def restore(self): self.sdk_checkmode(self.api.ca.rg.restore)( rg_id=self.rg_id, ) self.rg_should_exist = True return def destroy(self): self.sdk_checkmode(self.api.ca.rg.delete)( rg_id=self.rg_id, permanently=self.amodule.params['permanently'], recursively=self.aparams['recursive_deletion'], ) self.rg_should_exist = False return def package_facts(self, check_mode=False): """Package a dictionary of RG facts according to the decort_rg module specification. This dictionary will be returned to the upstream Ansible engine at the completion of the module run. @param arg_rg_facts: dictionary with RG facts as returned by API call to .../rg/get @param arg_check_mode: boolean that tells if this Ansible module is run in check mode """ ret_dict = dict(id=0, name="none", state="CHECK_MODE", ) if check_mode: # in check mode return immediately with the default values return ret_dict #if arg_rg_facts is None: # # if void facts provided - change state value to ABSENT and return # ret_dict['state'] = "ABSENT" # return ret_dict return self.rg_info.model_dump() @property def amodule_init_args(self) -> dict: return self.pack_amodule_init_args( argument_spec=dict( account_id=dict( type='int', ), account_name=dict( type='str', default='', ), access=dict( type='dict', options=dict( action=dict( type='str', required=True, choices=[ 'grant', 'revoke', ], ), user=dict( type='str', required=True, ), right=dict( type='str', choices=sdk_types.AccessTypeForSet._member_names_, ), ), ), description=dict( type='str', default='', ), def_netType=dict( type='str', choices=sdk_types.RGDefaultNetType._member_names_, default=sdk_types.RGDefaultNetType.PRIVATE.name, ), def_netId=dict( type='int', ), extNetId=dict( type='int', default=0, ), extNetIp=dict( type='str', default='', ), owner=dict( type='str', default='', ), ipcidr=dict( type='str', default='', ), rename=dict( type='str', default='', ), quotas=dict( type='dict', ), resType=dict( type='list', ), state=dict( type='str', default='present', choices=[ 'absent', 'disabled', 'enabled', 'present', ], ), permanently=dict( type='bool', default='False', ), rg_name=dict( type='str', ), rg_id=dict( type='int', ), sep_pools=dict( type='list', elements='dict', options=dict( sep_id=dict( type='int', required=True, ), pool_names=dict( type='list', required=True, elements='str', ), ), ), recursive_deletion=dict( type='bool', default=False, ), sdn_access_group_id=dict( type='str', ), ), supports_check_mode=True, ) def check_amodule_args_for_change(self): check_errors = False if ( self.aparams['sdn_access_group_id'] is not None and ( self.aparams['sdn_access_group_id'] != self.rg_info.sdn_access_group_id ) ): self.message( 'Check for parameter "sdn_access_group_id" failed: ' 'cannot change sdn_access_group_id for an existing resource ' f'group ID {self.rg_id}.' ) check_errors = True if check_errors: self.exit(fail=True) # Workflow digest: # 1) authenticate to DECORT controller & validate authentication by issuing API call - done when creating DECORTController # 2) check if the RG with the specified id or rg_name:name exists # 3) if RG does not exist -> deploy # 4) if RG exists: check desired state, desired configuration -> initiate action accordingly # 5) report result to Ansible @DecortController.handle_sdk_exceptions def run(self): amodule = self.amodule #amodule.check_mode=True if self.rg_id: if self.rg_info.status in [ sdk_types.ResourceGroupStatus.MODELED, sdk_types.ResourceGroupStatus.DISABLING, sdk_types.ResourceGroupStatus.ENABLING, sdk_types.ResourceGroupStatus.DESTROYING, ]: self.error() elif self.rg_info.status == sdk_types.ResourceGroupStatus.CREATED: if amodule.params['state'] == 'absent': self.destroy() elif amodule.params['state'] == "disabled": self.enable() if amodule.params['state'] in ['present', 'enabled']: if ( amodule.params['quotas'] or amodule.params['resType'] or amodule.params['rename'] != "" or amodule.params['sep_pools'] is not None or amodule.params['description'] is not None ): self.update() if amodule.params['access']: self.access() if amodule.params['def_netType'] is not None: self.setDefNet() elif self.rg_info.status == sdk_types.ResourceGroupStatus.DELETED: if amodule.params['state'] == 'absent' and amodule.params['permanently'] == True: self.destroy() elif (amodule.params['state'] == 'present' or amodule.params['state'] == 'disabled'): self.restore() elif amodule.params['state'] == 'enabled': self.restore() self.enable() elif self.rg_info.status == sdk_types.ResourceGroupStatus.DISABLED: if amodule.params['state'] == 'absent': self.destroy() elif amodule.params['state'] == ("enabled"): self.enable() else: if amodule.params['state'] in ('present', 'enabled'): if not amodule.params['rg_name']: self.result['failed'] = True self.result['msg'] = ( 'Resource group could not be created because' ' the "rg_name" parameter was not specified.' ) else: self.create() if amodule.params['access'] and not amodule.check_mode: self.access() elif amodule.params['state'] in ('disabled'): self.error() if self.result['failed']: amodule.fail_json(**self.result) else: if self.rg_should_exist: if self.result['changed']: self.get_info() self.result['facts'] = self.package_facts(amodule.check_mode) amodule.exit_json(**self.result) else: amodule.exit_json(**self.result) def main(): decort_rg().run() if __name__ == '__main__': main()