Source code for idem_spotinst.states.spotinst.ocean.aws.launch_spec

"""State module for managing Ocean AWS LaunchSpec."""
import base64
import copy
import json
from typing import Any
from typing import Dict
from typing import List

import dict_tools.differ as differ

__contracts__ = ["resource"]


[docs]async def present( hub, ctx, name: str, image_id: str, ocean_id: str, resource_id: str = None, iam_instance_profile: Dict = None, taints: List = None, tags: List = None, subnet_ids: List = None, user_data: str = None, security_group_ids: List = None, root_volume_size: int = None, strategy: Dict = None, restrict_scale_down: bool = None, resource_limits: Dict = None, labels: List = None, instance_types: List = None, preferred_spot_types: List = None, elastic_ip_pool: Dict = None, auto_scale: Dict = None, block_device_mappings: List = None, associate_public_ip_address: bool = None, scheduling: Dict = None, instance_metadata_options: Dict = None, ) -> Dict[str, Any]: """Create a Launch Spec(Virtual Node Group). Refer the `Spot Create Launch Spec(Virtual Node Group) documentation <https://docs.spot.io/api/#operation/OceanAWSLaunchSpecCreate>`_ to get insight of functionality and input parameters Args: name(str): launch specification name. image_id(str): AWS image identifier. ocean_id(str): The Ocean cluster identifier. Required for Launch Spec creation resource_id(str, Optional): The virtual node group identifier. iam_instance_profile(dict, Optional): The instance profile iamRole object. taints(list, Optional): Add taints to Launch Spec. tags(list, Optional): List of kay-value pairs of tags. subnet_ids(list, Optional): Set subnets in launchSpec. Each element in the array is a subnet identifier. user_data(str, Optional): Set user data script. security_group_ids(list, Optional): Set security groups. Each element in the array is a security group identifier. root_volume_size(int, Optional): Set root volume size (in GB). This field and blockDeviceMappings cannot be used in the same specification. strategy(dict, Optional): Similar to a strategy for an Ocean cluster, but applying only to a virtual node group. restrict_scale_down(bool, Optional): When set to “True”, VNG nodes will be treated as if all pods running have the restrict-scale-down label. Therefore, Ocean will not scale nodes down unless empty. resource_limits(dict, Optional): Option to set a maximum/minimum number of instances per launch specification. labels(list, Optional): An array of labels to add to the VNG nodes. Only custom user labels are allowed, and not Kubernetes built-in labels or Spot internal labels. instance_types(list, Optional): A list of instance types allowed to be provisioned for pods pending for the launch specification. preferred_spot_types(list, Optional): When Ocean scales up instances, it takes your preferred types into consideration while maintaining a variety of machine types running for optimized distribution. elastic_ip_pool(dict, Optional): Assign an Elastic IP to the instances launched by the launch spec. auto_scale(dict, Optional): Object specifying the automatic scaling of an Ocean VNG. block_device_mappings(list, Optional): Block devices that are exposed to the instance. You can specify virtual devices and EBS volumes. This parameter and rootVolumeSize cannot be in the spec at the same time. This parameter can be null, but if not null, it must contain at least one block device. associate_public_ip_address(bool, Optional): Configure public IP address allocation. scheduling(dict, Optional): An object used to define scheduled tasks such as a manual headroom update. instance_metadata_options(dict, Optional): Ocean instance metadata options object for IMDSv2. Request Syntax: .. code-block:: sls [spotinst.ocean.aws.launch_spec-resource-id]: spotinst.ocean.aws.launch_spec.present: - name: 'string' - resource_id: 'string' - taints: - effect: 'string' key: 'string' value: 'string' - tags: - tagKey: 'string' tagValue: 'string' - tagKey: 'string' tagValue: 'string' - subnet_ids: - 'string' - user_data: 'string' - security_group_ids: - 'string' - root_volume_size: Integer - resource_limits: maxInstanceCount: Integer - ocean_id: 'string' - labels: - key: 'string' value: 'string' - key: 'string' value: 'string' - instance_types: - 'string' - image_id: 'string' - iam_instance_profile: arn: 'string' - associate_public_ip_address: true/false - preferred_spot_types: - 'string' - elastic_ip_pool: tagSelector: tagKey: 'string' tagValue: 'string' - auto_scale: headrooms: - cpuPerUnit: Integer memoryPerUnit: Integer numOfUnits: Integer - block_device_mappings: - deviceName: 'string' ebs: deleteOnTermination: true/false encrypted: true/false volumeSize: Integer volumeType: 'string' - scheduling: tasks: - isEnabled: true/false cronExpression: 'string' taskType: 'string' shutdownHours: timeWindows: - 'string' isEnabled: true/false - instanceMetadataOptions: httpTokens: 'string' httpPutResponseHopLimit: Integer Returns: Dict[str, Any] Examples: .. code-block:: sls ols-77e27034: spotinst.ocean.aws.launch_spec.present: - name: cluster-arm - resource_id: ols-77e27034 - taints: - effect: NoSchedule key: dedicated value: idem-test-value - tags: - tagKey: tag-key-1 tagValue: tag-value-1 - tagKey: tag-key-2 tagValue: tag-value-2 - subnet_ids: - subnet-111122223333abcd - subnet-444455556666abcd - user_data: /etc/eks/idem-test.sh - security_group_ids: - sg-111122223333abcd - sg-444455556666abcd - root_volume_size: 8 - resource_limits: maxInstanceCount: 1 - ocean_id: o-b78b1d69 - labels: - key: autoscale value: 'false' - key: dedicated value: idem-test-value - instance_types: - t2.micro - image_id: ami-111122223333abcd - iam_instance_profile: arn: arn:aws:iam::111122223333:instance-profile/spotinst-eks-stack-NodeInstanceProfile - auto_scale: headrooms: - cpuPerUnit: 1024 memoryPerUnit: 512 numOfUnits: 3 - block_device_mappings: - deviceName: /dev/xvda ebs: deleteOnTermination: true encrypted: true volumeSize: 30 volumeType: gp2 - scheduling: tasks: - isEnabled: true cronExpression: '0 1 * * *' taskType: manualHeadroomUpdate shutdownHours: timeWindows: - "Sat:08:00-Sun:08:00" isEnabled: true - instanceMetadataOptions: httpTokens: optional httpPutResponseHopLimit: 12 """ result = dict(comment=(), old_state=None, new_state=None, name=name, result=True) user_data_base64 = None if user_data: try: user_data_base64 = base64.b64encode(user_data.encode("utf-8")).decode( "utf-8" ) except UnicodeError as e: hub.log.debug(f"base 64 encoding failed for user_data {e}") result["result"] = False result["comment"] = (f"{e.__class__.__name__}: {e}",) return result data = { "launchSpec": { "name": name, "taints": taints, "tags": tags, "subnetIds": subnet_ids, "userData": user_data_base64, "securityGroupIds": security_group_ids, "strategy": strategy, "restrictScaleDown": restrict_scale_down, "resourceLimits": resource_limits, "labels": labels, "instanceTypes": instance_types, "imageId": image_id, "iamInstanceProfile": iam_instance_profile, "elasticIpPool": elastic_ip_pool, "preferredSpotTypes": preferred_spot_types, "autoScale": auto_scale, "associatePublicIpAddress": associate_public_ip_address, "scheduling": scheduling, "instanceMetadataOptions": instance_metadata_options, } } # blockDeviceMappings and rootVolumeSize cannot be in the spec at the same time if block_device_mappings: data["launchSpec"]["blockDeviceMappings"] = block_device_mappings else: data["launchSpec"]["rootVolumeSize"] = root_volume_size resource_parameters = { "taints": taints, "tags": tags, "subnet_ids": subnet_ids, "user_data": user_data_base64, "security_group_ids": security_group_ids, "root_volume_size": root_volume_size, "strategy": strategy, "restrict_scale_down": restrict_scale_down, "resource_limits": resource_limits, "ocean_id": ocean_id, "labels": labels, "instance_types": instance_types, "image_id": image_id, "iam_instance_profile": iam_instance_profile, "elastic_ip_pool": elastic_ip_pool, "preferred_spot_types": preferred_spot_types, "auto_scale": auto_scale, "block_device_mappings": block_device_mappings, "associate_public_ip_address": associate_public_ip_address, "scheduling": scheduling, "instance_metadata_options": instance_metadata_options, } desire_state = {"name": name, "resource_id": resource_id} for parameter_key, parameter_value in resource_parameters.items(): if parameter_value is not None: desire_state[parameter_key] = parameter_value before = None if resource_id: get_url = f"{hub.exec.spotinst.URL}/ocean/aws/k8s/launchSpec/{resource_id}?accountId={ctx.acct.account_id}" ret = await hub.exec.request.json.get( ctx, url=get_url, success_codes=[200], headers={"Authorization": f"Bearer {ctx.acct.token}"}, ) if not ret["result"]: if ret["status"] == 400: result["comment"] = ( f"spotinst.ocean.aws.launch_spec '{name}'(resource Id: '{resource_id}') not found", ) # In case of non-existance resource id, comment returns ' Bad Request' else: result["comment"] = ret["comment"] result["result"] = ret["result"] return result if ret["ret"].get("response").get("items"): before = ret["ret"].get("response").get("items")[0] if before: result[ "old_state" ] = hub.tool.spotinst.ocean.aws.conversion_utils.convert_raw_launch_spec_to_present( raw_resource=before, idem_resource_name=resource_id, ) desire_state = hub.tool.spotinst.state_utils.handle_null( desire_state=desire_state, current_state=result["old_state"] ) diff = hub.tool.spotinst.state_comparison_utils.deep_diff( result["old_state"].copy(), desire_state.copy(), ignore="tags" ) update_required = False if diff and "desire_state" in diff: for item in diff["desire_state"]: if diff["desire_state"].get(item): # if value is not None update_required = True is_tags_updated = hub.tool.spotinst.ocean.aws.tag_utils.is_update_required( old_tags_list=result["old_state"].get("tags"), new_tags_list=desire_state.get("tags"), ) update_required = update_required or is_tags_updated if update_required: if ctx.get("test", False): result["new_state"] = desire_state result[ "comment" ] = hub.tool.spotinst.comment_utils.would_update_comment( resource_type="spotinst.ocean.aws.launch_spec", name=resource_id ) return result # Update in real try: ret = await hub.exec.request.json.put( ctx, success_codes=[200], headers={"Authorization": f"Bearer {ctx.acct.token}"}, url=f"{hub.exec.spotinst.URL}/ocean/aws/k8s/launchSpec/{resource_id}?accountId={ctx.acct.account_id}", data=json.dumps(data), ) except Exception as e: hub.log.debug(f"Could not update spotinst.ocean.aws.launch_spec {e}") result["result"] = False result["comment"] = (f"{e.__class__.__name__}: {e}",) return result if not ret["result"]: errors = json.loads(ret["ret"]).get("response").get("errors") if errors: result["comment"] = ( ret["comment"], errors[0].get("message"), ) else: result["comment"] = ret["comment"] result["result"] = False return result result[ "new_state" ] = hub.tool.spotinst.ocean.aws.conversion_utils.convert_raw_launch_spec_to_present( raw_resource=ret["ret"].get("response").get("items")[0], idem_resource_name=name, ) result[ "comment" ] = f"Updated spotinst.ocean.aws.launch_spec Id: '{resource_id}')" else: result["comment"] = hub.tool.spotinst.comment_utils.already_present_comment( resource_type="spotinst.ocean.aws.launch_spec", name=name, resource_id=resource_id, ) result["new_state"] = copy.deepcopy(result["old_state"]) return result else: # create new resource if ctx.get("test", False): # For test we should add resource_id if desire_state.get("resource_id", None) is None: desire_state["resource_id"] = "resource_id_known_after_present" result["new_state"] = desire_state result["comment"] = hub.tool.spotinst.comment_utils.would_create_comment( resource_type="spotinst.ocean.aws.launch_spec", name=name ) return result # create in real data.get("launchSpec")["oceanId"] = ocean_id try: ret = await hub.exec.request.json.post( ctx, success_codes=[200], headers={"Authorization": f"Bearer {ctx.acct.token}"}, url=f"{hub.exec.spotinst.URL}/ocean/aws/k8s/launchSpec?accountId={ctx.acct.account_id}", data=json.dumps(data), ) except Exception as e: hub.log.debug(f"Could not create spotinst.ocean.aws.launch_spec {e}") result["result"] = False result["comment"] = (f"{e.__class__.__name__}: {e}",) return result if not ret["result"]: errors = json.loads(ret["ret"]).get("response").get("errors") if errors: result["comment"] = ( ret["comment"], errors[0].get("message"), ) else: result["comment"] = ret["comment"] result["result"] = False return result result[ "new_state" ] = hub.tool.spotinst.ocean.aws.conversion_utils.convert_raw_launch_spec_to_present( raw_resource=ret["ret"].get("response").get("items")[0], idem_resource_name=name, ) result["comment"] = hub.tool.spotinst.comment_utils.create_comment( resource_type="spotinst.ocean.aws.launch_spec", name=name ) return result
[docs]async def absent(hub, ctx, name: str, resource_id: str = None) -> Dict[str, Any]: """Deletes the specified launch Spec. When this call completes, the launch configuration is no longer available for use. Refer the `Spot Delete Launch Spec(Virtual Node Group) documentation <https://docs.spot.io/api/#operation/OceanAWSLaunchSpecDelete>`_ to get insight of functionality and input parameters Args: name(str): An idem name of the launch spec. resource_id(str, Optional): The AWS ID of the launch spec. Request Syntax: .. code-block:: sls [launch_spec-resource-id]: spotinst.ocean.aws.launch_spec.absent: - name: 'string' - resource_id: 'string' Returns: Dict[str, Any] Examples: .. code-block:: sls idem-test-launch_spec: spotinst.ocean.aws.launch_spec.absent: - name: idem-test-launch_spec - resource_id: idem-test-launch_spec """ result = dict(comment=(), old_state=None, new_state=None, name=name, result=True) if not resource_id: result["comment"] = hub.tool.spotinst.comment_utils.already_absent_comment( resource_type="spotinst.ocean.aws.launch_spec", name=name ) return result url = f"{hub.exec.spotinst.URL}/ocean/aws/k8s/launchSpec/{resource_id}?accountId={ctx.acct.account_id}" before = None try: before = await hub.exec.request.json.get( ctx, url=url, success_codes=[200], headers={"Authorization": f"Bearer {ctx.acct.token}"}, ) except Exception as e: hub.log.debug(f"Could not get Launch Spec {e}") result["result"] = False result["comment"] = (f"{e.__class__.__name__}: {e}",) return result if before["status"] == 400: result["comment"] = hub.tool.spotinst.comment_utils.already_absent_comment( resource_type="spotinst.ocean.aws.launch_spec", name=name ) elif before["result"]: result[ "old_state" ] = hub.tool.spotinst.ocean.aws.conversion_utils.convert_raw_launch_spec_to_present( raw_resource=before["ret"].get("response").get("items")[0], idem_resource_name=name, ) if ctx.get("test", False): result["comment"] = hub.tool.spotinst.comment_utils.would_delete_comment( resource_type="spotinst.ocean.aws.launch_spec", name=name ) return result try: ret = await hub.exec.request.json.delete( ctx, url=url, success_codes=[200], headers={"Authorization": f"Bearer {ctx.acct.token}"}, ) result["result"] = ret["result"] if not result["result"]: hub.log.debug( f"Could not delete spotinst.ocean.aws.launch_spec {result['comment']} {result['ret']}" ) result["comment"] = ret["comment"] return result result["comment"] = hub.tool.spotinst.comment_utils.delete_comment( resource_type="spotinst.ocean.aws.launch_spec", name=name ) except Exception as e: hub.log.debug(f"Could not delete spotinst.ocean.aws.launch_spec {e}") result["result"] = False result["comment"] = (f"{e.__class__.__name__}: {e}",) return result else: hub.log.debug(f"Could not get Launch Spec {before['comment']} {before['ret']}") result["result"] = False result["comment"] = before["comment"] return result
[docs]async def describe(hub, ctx) -> Dict[str, Dict[str, Any]]: """Describe the resource in a way that can be recreated/managed with the corresponding "present" function. Gets information about the virtual node groups for the cluster. Please refer the `Spot Listing Launch Spec(Virtual Node Group) documentation <https://docs.spot.io/api/#operation/OceanAWSLaunchSpecList>`_ to get insight of functionality and input parameters Returns: Dict[str, Dict[str, Any]] Examples: .. code-block:: bash $ idem describe spotinst.ocean.aws.launch_spec """ result = {} url = f"{hub.exec.spotinst.URL}/ocean/aws/k8s/launchSpec?accountId={ctx.acct.account_id}" ret = await hub.exec.request.json.get( ctx, url=url, success_codes=[200], headers={"Authorization": f"Bearer {ctx.acct.token}"}, ) if not ret["result"]: hub.log.debug(f"Could not describe launch_spec {ret['comment']}") return {} for launch_spec_item in ret["ret"].get("response").get("items"): resource_id = launch_spec_item.get("id") resource_translated = hub.tool.spotinst.ocean.aws.conversion_utils.convert_raw_launch_spec_to_present( raw_resource=launch_spec_item, idem_resource_name=resource_id ) result[resource_id] = { "spotinst.ocean.aws.launch_spec.present": [ {parameter_key: parameter_value} for parameter_key, parameter_value in resource_translated.items() ] } return result