Source code for idem_vra.states.vra.iaas.cloudaccount

from typing import Any

from idem_vra.helpers.mapper import add_properties
from idem_vra.helpers.mapper import omit_properties
from idem_vra.helpers.models import StateReturn


__contracts__ = ["resource"]

TREQ = {"present": {"require": []}, "absent": {"require": ["vra.iaas.project.absent"]}}


[docs]async def present( hub, ctx, name: str, cloudAccountProperties: Any, regions: Any, cloudAccountType: Any, privateKey: Any, privateKeyId: Any, **kwargs, ): """ :param object cloudAccountProperties: (required in body) Cloud Account specific properties supplied in as name value pairs :param array regions: (required in body) A set of regions to enable provisioning on.Refer to /iaas/api/cloud- accounts/region-enumeration. 'regionInfos' is a required parameter for AWS, AZURE, GCP, VSPHERE, VMC, VCF cloud account types. :param string cloudAccountType: (required in body) Cloud account type :param string privateKey: (required in body) Secret access key or password to be used to authenticate with the cloud account. In case of AAP pass a dummy value. :param string name: (required in body) A human-friendly name used as an identifier in APIs that support this option. :param string privateKeyId: (required in body) Access key id or username to be used to authenticate with the cloud account :param string apiVersion: (optional in query) The version of the API in yyyy-MM-dd format (UTC). For versioning information refer to /iaas/api/about :param string validateOnly: (optional in query) If provided, it only validates the credentials in the Cloud Account Specification, and cloud account will not be created. :param array associatedCloudAccountIds: (optional in body) Cloud accounts to associate with this cloud account :param string description: (optional in body) A human-friendly description. :param Any certificateInfo: (optional in body) :param array tags: (optional in body) A set of tag keys and optional values to set on the Cloud Account :param boolean createDefaultZones: (optional in body) Create default cloud zones for the enabled regions. :param object customProperties: (optional in body) Additional custom properties that may be used to extend the Cloud Account. In case of AAP, provide environment property here.Example: "customProperties": { "environment": "aap" } :param object associatedMobilityCloudAccountIds: (optional in body) Cloud Account IDs and directionalities create associations to other vSphere cloud accounts that can be used for workload mobility. ID refers to an associated cloud account, and directionality can be unidirectional or bidirectional. Only supported on vSphere cloud accounts. """ try: state = CloudaccountStateImpl(hub, ctx) return await state.present( hub, ctx, name, cloudAccountProperties, regions, cloudAccountType, privateKey, privateKeyId, **kwargs, ) except Exception as error: hub.log.error("Error during enforcing present state: cloudaccount") hub.log.error(str(error)) raise error
[docs]async def absent(hub, ctx, name: str, **kwargs): """ :param string p_id: (required in path) The ID of the Cloud Account :param string apiVersion: (optional in query) The version of the API in yyyy-MM-dd format (UTC). For versioning information refer to /iaas/api/about """ """ :param string name: (required) name of the resource """ try: state = CloudaccountStateImpl(hub, ctx) return await state.absent(hub, ctx, name, **kwargs) except Exception as error: hub.log.error("Error during enforcing absent state: cloudaccount") hub.log.error(str(error)) raise error
[docs]async def describe(hub, ctx): try: state = CloudaccountStateImpl(hub, ctx) return await state.describe(hub, ctx) except Exception as error: hub.log.error("Error during describe: cloudaccount") hub.log.error(str(error)) raise error
[docs]def is_pending(hub, ret: dict, state: str = None, **pending_kwargs): try: state = CloudaccountStateImpl(hub, None) return state.is_pending(hub, ret, state, **pending_kwargs) except Exception as error: hub.log.error("Error during is_pending: cloudaccount") hub.log.error(str(error)) raise error
[docs]class CloudaccountState: def __init__(self, hub, ctx): self.hub = hub self.ctx = ctx
[docs] async def present( self, hub, ctx, name: str, cloudAccountProperties: Any, regions: Any, cloudAccountType: Any, privateKey: Any, privateKeyId: Any, **kwargs, ): search_result = (await self.paginate_find(hub, ctx))["ret"] for s in search_result.content: if name == s["name"] and True: hub.log.info( f'Returning resource cloudaccount "{s["name"]}" due to existing resource "{name}"' ) s = await self.remap_resource_structure(hub, ctx, s) return StateReturn( result=True, comment=f"Resource cloudaccount {name} already exists.", old=s, new=s, ) res = ( await hub.exec.vra.iaas.cloudaccount.create_cloud_account_async( ctx, cloudAccountProperties, regions, cloudAccountType, privateKey, name, privateKeyId, **kwargs, ) )["ret"] res = await self.remap_resource_structure(hub, ctx, res) return StateReturn( result=True, comment=f"Creation of cloudaccount {name} success.", old=None, new=res, )
[docs] async def absent(self, hub, ctx, name: str, **kwargs): search_result = (await self.paginate_find(hub, ctx))["ret"] resource = None for s in search_result.content: if name == s["name"] and True: hub.log.info( f'Found resource cloudaccount "{s["name"]}" due to existing resource "{name}"' ) s = await self.remap_resource_structure(hub, ctx, s) resource = s if resource: # it exists! delete_kwargs = {} delete_kwargs["p_id"] = resource.get("id") hub.log.debug( f"cloudaccount with name = {resource.get('name')} already exists" ) await hub.exec.vra.iaas.cloudaccount.delete_cloud_account( ctx, **delete_kwargs ) return StateReturn( result=True, comment=f"Resource with name = {resource.get('name')} deleted.", old=resource, new=None, ) return StateReturn( result=True, comment=f"Resource with name = {name} is already absent.", old=None, new=None, )
[docs] async def describe(self, hub, ctx): result = {} res = await self.paginate_find(hub, ctx) for obj in res.get("ret", {}).get("content", []): # Keep track of name and id properties as they may get remapped obj_name = obj.get("name", "unknown") obj_id = obj.get("id", "unknown") obj = await self.remap_resource_structure(hub, ctx, obj) # Define props props = [{key: value} for key, value in obj.items()] # Build result result[f"{obj_name}-{obj_id.split('-')[-1]}"] = { "vra.iaas.cloudaccount.present": props } return result
[docs] async def paginate_find(self, hub, ctx, **kwargs): """ Paginate through all resources using their 'find' method. """ res = await hub.exec.vra.iaas.cloudaccount.get_cloud_accounts(ctx, **kwargs) numberOfElements = res.get("ret", {}).get("numberOfElements", 0) totalElements = res.get("ret", {}).get("totalElements", 0) initialElements = numberOfElements if numberOfElements != totalElements and totalElements != 0: while initialElements < totalElements: hub.log.debug( f"Requesting cloudaccount with offset={initialElements} out of {totalElements}" ) pres = await hub.exec.vra.iaas.cloudaccount.get_cloud_accounts( ctx, skip=initialElements ) initialElements += pres.get("ret", {}).get("numberOfElements", 0) aggO = res.get("ret", {}).get("content", []) aggN = pres.get("ret", {}).get("content", []) res["ret"]["content"] = [*aggO, *aggN] res["ret"]["numberOfElements"] = initialElements return res
[docs] def is_pending(self, hub, ret: dict, state: str = None, **pending_kwargs): """ State reconciliation """ hub.log.debug(f'Running is_pending for resource: {ret.get("__id__", None)}...') is_pending_result = False hub.log.debug( f'is_pending_result for resource "{ret.get("__id__", None)}": {is_pending_result}' ) return is_pending_result
[docs] async def remap_resource_structure(self, hub, ctx, obj: dict) -> dict: schema_mapper = { "add": [ { "key": "certificateInfo.certificate", "value": "jsonpath:cloudAccountProperties.certificate", }, { "key": "regions", "value": "jsonpath:$", "source": "lambda", "lambda": "return [{\n 'externalRegionId': region.get('externalRegionId'),\n 'name': region.get('name'),\n 'regionId': region.get('id')\n} for region in resource.get('enabledRegions')] if resource.get('enabledRegions') else []\n", "kwargs": {"resource": "jsonpath:$"}, }, { "key": "associatedCloudAccountIds", "value": "jsonpath:$", "source": hub.states.vra.iaas.cloudaccount.remap_associated_cloudaccount_ids, "kwargs": {"resource": "jsonpath:$"}, "hub": hub, "ctx": ctx, }, { "key": "privateKeyId", "value": "jsonpath:cloudAccountProperties.privateKeyId", }, {"key": "privateKey", "value": "<private key / password>"}, ], "omit": [ "orgId", "createdAt", "updatedAt", "owner", "_links", "healthy", "inMaintenanceMode", "customProperties", "cloudAccountProperties.privateKeyId", "cloudAccountProperties.certificate", "cloudAccountProperties.supportTagging", "cloudAccountProperties.supportDatastores", "enabledRegions", ], } # Perform resource mapping by adding properties and omitting properties. # Property renaming is addition followed by omission. if schema_mapper: resource_name = "cloudaccount" hub.log.debug(f"Remapping resource {resource_name}...") obj = await add_properties(obj, schema_mapper.get("add", [])) obj = omit_properties(obj, schema_mapper.get("omit", [])) return obj
# ==================================== # State override # ==================================== import time from idem_vra.helpers.models import FunctionReturn import json
[docs]class CloudaccountStateImpl(CloudaccountState):
[docs] async def present( self, hub, ctx, name: str, cloudAccountProperties: Any, privateKey: Any, regions: Any, cloudAccountType: Any, privateKeyId: Any, **kwargs, ): # ================================== # Handle account that already exists # ================================== hub.log.debug(f"we are searching for a name {name}") search_result = (await self.paginate_find(hub, ctx))["ret"] for s in search_result.content: if name == s["name"] and True: hub.log.info( f'Returning resource cloudaccount "{s["name"]}" due to existing resource "{name}"' ) hub.log.debug(f'search_result: "{json.dumps(s)}') enumeration_task_state = s.get("customProperties", {}).get( "enumerationTaskState", None ) image_enumeration_task_state = s.get("customProperties", {}).get( "imageEnumerationTaskState", None ) # Check for datacollection if ( enumeration_task_state == "FINISHED" and image_enumeration_task_state == "FINISHED" ): hub.log.debug(f'Enumeration finished: "{json.dumps(s)}') s = await self.remap_resource_structure(hub, ctx, s) # return StateReturn( # result=True, # comment=f"Creation of cloudaccount {name} success.", # old=s, # new=s # ) s.update({"account_creation_in_progress": False}) hub.log.debug(f"proper result location for cloud account {name}") return { "result": True, "name": name, "comment": f"Creation of cloudaccount {name} success.", "old_state": {}, "new_state": s, } elif ( enumeration_task_state == "FAILED" or image_enumeration_task_state == "FAILED" ): # return StateReturn( # result=False, # comment=f"Unable to data collect cloud account {name}.", # old=None, # new=None # ) return { "result": False, "name": name, "comment": f"Unable to data collect cloud account {name}.", "old_state": {}, "new_state": {}, } # Set account_creation_in_progress flag if data collection is not finished elif ( enumeration_task_state != "FINISHED" or image_enumeration_task_state != "FINISHED" ): hub.log.debug(f'Enumeration not finished: "{json.dumps(s)}') s = await self.remap_resource_structure(hub, ctx, s) s.update({"account_creation_in_progress": True}) # return StateReturn( # result=False, # comment=f"Waiting for datacolection on {name}.", # old=s, # new=s # ) return { "result": False, "name": name, "comment": f"Waiting for datacolection on {name}.", "old_state": s, "new_state": s, } s = await self.remap_resource_structure(hub, ctx, s) # return StateReturn( # result=True, # comment=f"Resource cloudaccount {name} already exists.", # old=s, # new=s # ) s.update({"account_creation_in_progress": False}) return { "result": True, "name": name, "comment": f"Resource cloudaccount {name} already exists", "old_state": s, "new_state": s, } # ================================== # Handle new account # ================================== # Do the async create res = ( await hub.exec.vra.iaas.cloudaccount.create_cloud_account_async( ctx, cloudAccountProperties, privateKey, regions, cloudAccountType, name, privateKeyId, **kwargs, ) )["ret"] # Monitor the create flow (wait for 10 seconds) iteration = 0 interval = 1 max_iterations = 10 while res.get("status", None) == "INPROGRESS" and iteration < max_iterations: hub.log.debug(f"Cloud account creation is in progress... for {name}") time.sleep(interval) res = await hub.tool.vra.authed.get(ctx, res.get("selfLink")) res.update({"name": name}) res.update({"account_creation_in_progress": True}) iteration += 1 hub.log.debug(f"after successfull creation search again for {name}") search_result = (await self.paginate_find(hub, ctx))["ret"] for s in search_result.content: if name == s["name"] and True: s.update({"account_creation_in_progress": True}) s = await self.remap_resource_structure(hub, ctx, s) return { "result": True, "name": name, "comment": f"Creation of cloudaccount {name} is success, waiting for other stuff", "old_state": {}, "new_state": s, } # return StateReturn( # result=True, # comment=f"Creation of cloudaccount {name} in progress...", # old=None, # new=res, # ) hub.log.debug(f"Returning with rerun_data: {name} {res}") return { "result": False, "name": name, "comment": f"Creation of cloudaccount {name} in progress...", "rerun_data": res.get("selfLink"), "old_state": {}, "new_state": {}, }
[docs] def is_pending(self, hub, ret: dict, state: str = None, **pending_kwargs) -> bool: """ State reconciliation for cloud accounts This method enables state specific implementation of is_pending logic, based on resource specific attribute(s). Usage 'idem state <sls-file> --reconciler=basic', where the reconciler attribute can be missed. :param hub: The Hub into which the resolved callable will get placed. :param ret: The returned dictionary of the last run. :param state: The name of the state. :param pending_kwargs: (dict, Optional) May include 'ctx' and 'reruns_wo_change_count'. :return: True | False """ hub.log.debug(f'Running is_pending for resource: {ret.get("__id__", None)}...') hub.log.debug(f"ret: {json.dumps(ret)}") # Limiting reconciliation to up to 600 consecutive times w/o change if pending_kwargs and pending_kwargs.get("reruns_wo_change_count", 0) >= 600: hub.log.debug("is_pending: False") return False if ( ret.get("new_state") != None and ret.get("new_state").get("status", None) == "FAILED" ): hub.log.debug("is_pending: False") hub.log.debug(f'message: {ret.get("new_state").get("message", None)}') return False if ( ret.get("new_state") != None and ret.get("new_state").get("account_creation_in_progress", None) == True ): hub.log.debug("is_pending: True") return True if ret["result"]: hub.log.debug(f"found result breaking is_pending {ret}") return False # hub.log.debug('is_pending: False') hub.log.debug(f"why ret{ret}") return True
[docs]async def remap_associated_cloudaccount_ids(hub, ctx, resource): associated_account_ids = [] associated_accounts = resource.get("_links", {}).get( "associated-cloud-accounts", None ) # Output accociated cloud account ids only for vsphere cloud accounts if resource.get("cloudAccountType", None) == "vsphere" and associated_accounts: associated_account_ids = [ account.split("/")[-1] for account in associated_accounts.get("hrefs", []) ] return FunctionReturn(ret=associated_account_ids)