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

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

__contracts__ = ["resource"]


[docs]async def present( hub, ctx, name: str, controller_cluster_id: str, region: str, compute: Dict = None, resource_id: str = None, capacity: Dict = None, logging: Dict = None, scheduling: Dict = None, security: Dict = None, strategy: Dict = None, auto_scaler=None, update_policy: Dict = None, ) -> Dict[str, Any]: """Create a Ocean k8s Cluster. Refer the `Spot Create Ocean Cluster documentation <https://docs.spot.io/api/#operation/OceanAWSClusterCreate>`_ to get insight of functionality and input parameters Args: name(str): ocean cluster name. resource_id(str, Optional): The ocean cluster identifier. auto_scaler(dict, Optional): The automatic scaling mechanism used in Ocean for Kubernetes. capacity (dict, Optional): The overall capability of the Ocean cluster expressed as number of instances and specified with a minimum, a maximum, and a target number of running instances. compute (dict, Optional): Compute specifications for the Ocean cluster. This is required for greenfield and optional for brownfield. controller_cluster_id(str): Reporting identifier for the Ocean Controller. logging (dict, Optional): Logging configuration for ocean aws cluster. region(str): Region for the Ocean cluster to run in. scheduling(dict, Optional) : An object used to define times for a task such as a shutdown to be activated. security(dict, Optional): Object for cluster security features. strategy(dict, Optional): An object defining the cluster strategy with regard to waiting periods and utilization of on-demand and reserved instances. update_policy(dict, Optional): Configures the cluster update policy. You can configure to start the roll on update and run it in defined batches. Request Syntax: .. code-block:: sls [spotinst.ocean.aws.k8s_cluster-state-id]: spotinst.ocean.aws.k8s_cluster.present: - name: 'string' - controller_cluster_id: 'string' - region: 'string' - update_policy: shouldRoll: boolean roll: batchSizePercentage: Integer comment: 'string' batchMinHealthyPercentage: Integer - compute: launchSpecification: keyPair: 'string' imageId: 'string' associatePublicIpAddress: boolean rootVolumeSize: Integer iamInstanceProfile: arn: 'string' userData: 'string' tags: - tagKey: 'string' tagValue: 'string' - tagKey: 'string' tagValue: 'string' securityGroupIds: - 'string' subnetIds: - 'string' instanceTypes: whitelist: - 'string' - strategy: fallbackToOd: boolean spotPercentage: Integer utilizeReservedInstances: boolean - auto_scaler: isEnabled: boolean isAutoConfig: boolean cooldown: Integer headroom: cpuPerUnit: Integer memoryPerUnit: Integer numOfUnits: Integer down: maxScaleDownPercentage: Integer - resourceLimits: maxMemoryGib: Integer maxVCpu: Integer - capacity: maximum: Integer minimum: Integer Returns: Dict[str, Any] Examples: .. code-block:: sls ocean_cluster: spotinst.ocean.aws.k8s_cluster.present: - name: idem-test - controller_cluster_id: idem-test - region: ap-east-1 - update_policy: shouldRoll: false roll: batchSizePercentage: 30 comment: Idem cluster roll testing batchMinHealthyPercentage: 100 - compute: launchSpecification: keyPair: test-idem-key imageId: ami-06c9598a8dcd87e79 associatePublicIpAddress: False rootVolumeSize: 100 iamInstanceProfile: arn: arn:aws:iam::111222333:instance-profile/idem-instance-profile-test userData: | #!/bin/bash -xe /etc/eks/test-idem.sh tags: - tagKey: tag-ket-1 tagValue: tag-value-1 - tagKey: tag-ket-2 tagValue: tag-value-2 securityGroupIds: - sg-qbc111122223333 subnetIds: - subnet-0abc111222333 instanceTypes: whitelist: - c5.large - strategy: fallbackToOd: true spotPercentage: 100 utilizeReservedInstances: true - auto_scaler: isEnabled: True isAutoConfig: true cooldown: 300 headroom: cpuPerUnit: 1024 memoryPerUnit: 512 numOfUnits: 3 down: maxScaleDownPercentage: 60 resourceLimits: maxMemoryGib: 20000 maxVCpu: 100000 - capacity: maximum: 1000 minimum: 1 """ if auto_scaler is None: auto_scaler = {} result = dict(comment=(), old_state=None, new_state=None, name=name, result=True) # If userdata is set in compute, encode userData to base64 if ( compute and compute.get("launchSpecification") and compute.get("launchSpecification").get("userData") ): try: user_data_bytes = ( compute.get("launchSpecification").get("userData").encode("utf-8") ) base64_string = base64.b64encode(user_data_bytes).decode("utf-8") compute["launchSpecification"]["userData"] = base64_string 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 = { "cluster": { "name": name, "capacity": capacity, "compute": compute, "controllerClusterId": controller_cluster_id, "logging": logging, "scheduling": scheduling, "security": security, "strategy": strategy, } } if auto_scaler: data["cluster"]["autoScaler"] = auto_scaler resource_parameters = { "capacity": capacity, "compute": compute, "controller_cluster_id": controller_cluster_id, "logging": logging, "region": region, "scheduling": scheduling, "security": security, "strategy": strategy, "auto_scaler": auto_scaler, } 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: ocean_custer_resource_url = f"{hub.exec.spotinst.URL}/ocean/aws/k8s/cluster/{resource_id}?accountId={ctx.acct.account_id}" ret = await hub.exec.request.json.get( ctx, url=ocean_custer_resource_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.k8s_cluster '{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_k8s_cluster_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("compute") .get("launchSpecification") .get("tags"), new_tags_list=desire_state.get("compute") .get("launchSpecification") .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.k8s_cluster", name=resource_id ) if update_policy and update_policy["shouldRoll"]: roll_result = await hub.tool.spotinst.ocean.aws.k8s_cluster_utils.initiate_roll( ctx, update_policy, resource_id ) result["comment"] += roll_result["comment"] result["new_state"]["update_policy"] = copy.deepcopy(update_policy) return result # update in real ret = await hub.exec.request.json.put( ctx, success_codes=[200], headers={"Authorization": f"Bearer {ctx.acct.token}"}, url=ocean_custer_resource_url, data=json.dumps(data), ) 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_k8s_cluster_to_present( raw_resource=ret["ret"].get("response").get("items")[0], idem_resource_name=resource_id, ) result["comment"] = hub.tool.spotinst.comment_utils.update_comment( resource_type="spotinst.ocean.aws.k8s_cluster", name=resource_id ) # initiate roll only in real update (when update is successful) if update_policy and update_policy["shouldRoll"]: roll_result = ( await hub.tool.spotinst.ocean.aws.k8s_cluster_utils.initiate_roll( ctx, update_policy, resource_id ) ) result["comment"] += roll_result["comment"] if not roll_result["result"]: result["result"] = False return result result["new_state"]["update_policy"] = copy.deepcopy(update_policy) else: result["comment"] = hub.tool.spotinst.comment_utils.already_present_comment( resource_type="spotinst.ocean.aws.k8s_cluster", 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.k8s_cluster", name=name ) return result # create in real try: data.get("cluster")["region"] = region 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/cluster?accountId={ctx.acct.account_id}", data=json.dumps(data), ) except Exception as e: hub.log.debug(f"Could not create spotinst.ocean.aws.k8s_cluster {e}") result["result"] = False result["comment"] = (f"{e.__class__.__name__}: {e}",) return result if not ret["result"]: result["result"] = False errors = json.loads(ret["ret"]).get("response").get("errors") if errors: result["comment"] = ( ret["comment"], errors[0].get("message"), ) else: result["comment"] = ret["comment"] return result resource_id = ret["ret"].get("response").get("items")[0].get("id") result[ "new_state" ] = hub.tool.spotinst.ocean.aws.conversion_utils.convert_raw_k8s_cluster_to_present( raw_resource=ret["ret"].get("response").get("items")[0], idem_resource_name=resource_id, ) result["comment"] = hub.tool.spotinst.comment_utils.create_comment( resource_type="spotinst.ocean.aws.k8s_cluster", name=name ) return result
[docs]async def absent( hub, ctx, name: str, resource_id: str = None, ) -> Dict[str, Any]: """Deletes the specified Ocean cluster. When this call completes, the cluster is no longer available for use. Refer the `Spot Delete Ocean Cluster documentation <https://docs.spot.io/api/#operation/OceanAWSClusterDelete>`_ to get insight of functionality and input parameters Args: name(str): An idem name of the ocean cluster. resource_id(str, Optional): The ID of the ocean cluster. Request Syntax: .. code-block:: sls [k8s_cluster-resource-id]: spotinst.ocean.aws.k8s_cluster.absent: - name: 'string' - resource_id: 'string' Returns: Dict[str, Any] Examples: .. code-block:: sls idem-test-k8s_cluster: spotinst.ocean.aws.k8s_cluster.absent: - name: idem-test-k8s_cluster - resource_id:idem-test-k8s_cluster """ 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.k8s_cluster", name=name ) return result url = f"{hub.exec.spotinst.URL}/ocean/aws/k8s/cluster/{resource_id}?accountId={ctx.acct.account_id}" 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 K8s Cluster {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.k8s_cluster", name=name ) elif before["result"]: result[ "old_state" ] = hub.tool.spotinst.ocean.aws.conversion_utils.convert_raw_k8s_cluster_to_present( raw_resource=before["ret"].get("response").get("items")[0], idem_resource_name=resource_id, ) if ctx.get("test", False): result["comment"] = hub.tool.spotinst.comment_utils.would_delete_comment( resource_type="spotinst.ocean.aws.k8s_cluster", name=name ) return result try: after = await hub.exec.request.json.delete( ctx, url=url, success_codes=[200], headers={"Authorization": f"Bearer {ctx.acct.token}"}, ) except Exception as e: hub.log.debug(f"Could not delete spotinst.ocean.aws.k8s_cluster {e}") result["result"] = False result["comment"] = (f"{e.__class__.__name__}: {e}",) return result if not after["result"] or after["status"] == 400: hub.log.debug( f"Could not delete spotinst.ocean.aws.k8s_cluster {result['comment']} {result['ret']}" ) result["comment"] = after["comment"] return result result["result"] = after["result"] result["comment"] = hub.tool.spotinst.comment_utils.delete_comment( resource_type="spotinst.ocean.aws.k8s_cluster", name=name ) else: hub.log.debug(f"Could not get k8s Cluster {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 ocean clusters. Refer the `Spot Listing Ocean Cluster documentation <https://docs.spot.io/api/#operation/OceanAWSClusterList>`_ to get insight of functionality and input parameters Returns: Dict[str, Dict[str, Any]] Examples: .. code-block:: bash $ idem describe spotinst.ocean.aws.k8s_cluster """ result = {} url = ( f"{hub.exec.spotinst.URL}/ocean/aws/k8s/cluster?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"]: raise ValueError( f"Error on requesting GET {url} with status code {ret['status']}:" f" {ret.get('comment', '')}" ) cluster_list = ret.get("ret").get("response").get("items", None) for cluster_item in cluster_list: resource_id = cluster_item.get("id") translated_resource = hub.tool.spotinst.ocean.aws.conversion_utils.convert_raw_k8s_cluster_to_present( raw_resource=cluster_item, idem_resource_name=resource_id ) result[resource_id] = { "spotinst.ocean.aws.k8s_cluster.present": [ {parameter_key: parameter_value} for parameter_key, parameter_value in translated_resource.items() ] } return result