"""State module for managing EKS Node groups."""
import copy
from dataclasses import field
from dataclasses import make_dataclass
from typing import Any
from typing import Dict
from typing import List
from dict_tools import differ
__contracts__ = ["resource"]
TREQ = {
"absent": {
"require": [
"aws.eks.cluster.absent",
],
},
"present": {
"require": [
"aws.eks.cluster.present",
],
},
}
default_attempts = 120
[docs]async def present(
hub,
ctx,
name: str,
cluster_name: str,
subnets: List[str],
node_role: str,
resource_id: str = None,
scaling_config: make_dataclass(
"NodegroupScalingConfig",
[
("minSize", int, field(default=None)),
("maxSize", int, field(default=None)),
("desiredSize", int, field(default=None)),
],
) = None,
disk_size: int = None,
instance_types: List[str] = None,
ami_type: str = None,
remote_access: make_dataclass(
"RemoteAccessConfig",
[
("ec2SshKey", str, field(default=None)),
("sourceSecurityGroups", List[str], field(default=None)),
],
) = None,
labels: Dict[str, str] = None,
taints: List[
make_dataclass(
"Taint",
[
("key", str, field(default=None)),
("value", str, field(default=None)),
("effect", str, field(default=None)),
],
)
] = None,
tags: Dict[str, str] = None,
client_request_token: str = None,
launch_template: make_dataclass(
"LaunchTemplateSpecification",
[
("name", str, field(default=None)),
("version", str, field(default=None)),
("id", str, field(default=None)),
],
) = None,
update_config: make_dataclass(
"NodegroupUpdateConfig",
[
("maxUnavailable", int, field(default=None)),
("maxUnavailablePercentage", int, field(default=None)),
],
) = None,
capacity_type: str = None,
version: str = None,
release_version: str = None,
timeout: make_dataclass(
"Timeout",
[
(
"create",
make_dataclass(
"CreateTimeout",
[
("delay", int, field(default=10)),
("max_attempts", int, field(default=60)),
],
),
field(default=None),
),
(
"update",
make_dataclass(
"UpdateTimeout",
[
("delay", int, field(default=10)),
("max_attempts", int, field(default=60)),
],
),
field(default=None),
),
],
) = None,
) -> Dict[str, Any]:
"""Creates a managed node group for an Amazon EKS cluster.
You can only create a node group for your cluster that is equal to the current Kubernetes version for the cluster.
All node groups are created with the latest AMI release version for the respective minor Kubernetes version of the
cluster, unless you deploy a custom AMI using a launch template. For more information about using launch templates,
see Launch template support. An Amazon EKS managed node group is an Amazon EC2 Auto Scaling group and associated
Amazon EC2 instances that are managed by Amazon Web Services for an Amazon EKS cluster. Each node group uses a version
of the Amazon EKS optimized Amazon Linux 2 AMI. For more information, see Managed Node Groups in the Amazon EKS User Guide.
Args:
name(str):
An Idem name of EKS Node group.
cluster_name(str):
The name of the cluster to create the node group in.
subnets(list[str]):
The subnets to use for the Auto Scaling group that is created for your node group. If you specify launchTemplate,
then don't specify SubnetId in your launch template, or the node group deployment will fail. For more
information about using launch templates with Amazon EKS, see Launch template support in the Amazon EKS User Guide.
node_role(str):
The Amazon Resource Name (ARN) of the IAM role to associate with your node group. The Amazon EKS worker node
kubelet daemon makes calls to Amazon Web Services APIs on your behalf. Nodes receive permissions for these
API calls through an IAM instance profile and associated policies. Before you can launch nodes and register
them into a cluster, you must create an IAM role for those nodes to use when they are launched.
resource_id(str, Optional):
AWS EKS Node group name
scaling_config(dict[str, Any], Optional):
The scaling configuration details for the Auto Scaling group that is created for your node
group. Defaults to None.
* minSize (int, Optional):
The minimum number of nodes that the managed node group can scale in to.
* maxSize (int, Optional):
The maximum number of nodes that the managed node group can scale out to. For information about
the maximum number that you can specify, see Amazon EKS service quotas in the Amazon EKS User
Guide.
* desiredSize (int, Optional):
The current number of nodes that the managed node group should maintain. If you use Cluster
Autoscaler, you shouldn't change the desiredSize value directly, as this can cause the Cluster
Autoscaler to suddenly scale up or scale down. Whenever this parameter changes, the number of
worker nodes in the node group is updated to the specified size. If this parameter is given a
value that is smaller than the current number of running worker nodes, the necessary number of
worker nodes are terminated to match the given value. When using CloudFormation, no action
occurs if you remove this parameter from your CFN template. This parameter can be different from
minSize in some cases, such as when starting with extra hosts for testing. This parameter can
also be different when you want to start with an estimated number of needed hosts, but let
Cluster Autoscaler reduce the number if there are too many. When Cluster Autoscaler is used, the
desiredSize parameter is altered by Cluster Autoscaler (but can be out-of-date for short periods
of time). Cluster Autoscaler doesn't scale a managed node group lower than minSize or higher
than maxSize.
capacity_type(str, Optional):
The capacity type for your node group
disk_size(int, Optional):
The root device disk size (in GiB) for your node group instances. The default
disk size is 20 GiB.
instance_types(list[str], Optional):
Specify the instance types for a node group. If you specify a GPU instance type, be sure to specify AL2_x86_64_GPU
with the amiType parameter. If you specify launchTemplate, then you can specify zero or one instance type in
your launch template or you can specify 0-20 instance types for instanceTypes. If however, you specify an
instance type in your launch template and specify any instanceTypes, the node group deployment will fail. If
you don't specify an instance type in a launch template or for instanceTypes, then t3.medium is used, by default.
If you specify Spot for capacityType, then we recommend specifying multiple values for instanceTypes. For more
information, see Managed node group capacity types and Launch template support in the Amazon EKS
User Guide. Defaults to None.
ami_type(str, Optional):
The AMI type for your node group. GPU instance types should use the AL2_x86_64_GPU AMI type. Non-GPU instances
should use the AL2_x86_64 AMI type. Arm instances should use the AL2_ARM_64 AMI type.
remote_access(dict[str, Any], Optional):
The remote access (SSH) configuration to use with your node group. If you specify launchTemplate, then don't
specify remoteAccess, or the node group deployment will fail. For more information about using launch templates
with Amazon EKS, see Launch template support in the Amazon EKS User Guide. Defaults to None.
* ec2SshKey (str, Optional):
The Amazon EC2 SSH key that provides access for SSH communication with the nodes in the managed node group.
For more information, see Amazon EC2 key pairs and Linux instances in the Amazon Elastic Compute Cloud User
Guide for Linux Instances.
* sourceSecurityGroups (list[str], Optional):
The security groups that are allowed SSH access (port 22) to the nodes. If you specify an Amazon EC2 SSH
key but do not specify a source security group when you create a managed node group, then port 22 on the
nodes is opened to the internet (0.0.0.0/0). For more information, see Security Groups for Your VPC in
the Amazon Virtual Private Cloud User Guide.
labels(dict[str, str], Optional):
The Kubernetes labels to be applied to the nodes in the node group when they are created. Defaults to None.
taints(list[dict[str, Any]], Optional):
The Kubernetes taints to be applied to the nodes in the node group. For more information, see
Node taints on managed node groups. Defaults to None.
* key (str, Optional): The key of the taint.
* value (str, Optional): The value of the taint.
* effect (str, Optional): The effect of the taint.
tags(dict, Optional):
The metadata to apply to the node group to assist with categorization and organization.
Each tag consists of a key and an optional value. You define both. Node group tags do not propagate to any
other resources associated with the node group, such as the Amazon EC2 instances or subnets.
client_request_token(str, Optional):
Unique, case-sensitive identifier that you provide to ensure the
idempotency of the request.
launch_template(dict[str, Any], Optional):
An object representing a node group's launch template specification. If specified, then do not
specify instanceTypes, diskSize, or remoteAccess and make sure that the launch template meets
the requirements in launchTemplateSpecification. Defaults to None.
* name (str, Optional):
The name of the launch template.
* version (str, Optional):
The version of the launch template to use. If no version is specified, then the template's
default version is used.
* id (str, Optional):
The ID of the launch template.
update_config(dict[str, Any], Optional):
The node group update configuration. Defaults to None.
* maxUnavailable (int, Optional):
The maximum number of nodes unavailable at once during a version update. Nodes will be updated
in parallel. This value or maxUnavailablePercentage is required to have a value.The maximum
number is 100.
* maxUnavailablePercentage (int, Optional):
The maximum percentage of nodes unavailable during a version update. This percentage of nodes
will be updated in parallel, up to 100 nodes at once. This value or maxUnavailable is required
to have a value.
version(str, Optional):
The Kubernetes version to use for your managed nodes. By default, the Kubernetes version of the
cluster is used, and this is the only accepted specified value. If you specify launchTemplate,
and your launch template uses a custom AMI, then don't specify version, or the node group
deployment will fail. For more information about using launch templates with Amazon EKS, see
Launch template support in the Amazon EKS User Guide. Defaults to None.
release_version(str, Optional):
The AMI version of the Amazon EKS optimized AMI to use with your node group. By default, the
latest available AMI version for the node group's current Kubernetes version is used. For more
information, see Amazon EKS optimized Amazon Linux 2 AMI versions in the Amazon EKS User Guide.
If you specify launchTemplate, and your launch template uses a custom AMI, then don't specify
releaseVersion, or the node group deployment will fail. For more information about using launch
templates with Amazon EKS, see Launch template support in the Amazon EKS User Guide. Defaults to None.
timeout(dict, Optional):
Timeout configuration for creating or updating nodegroup.
* create (dict):
Timeout configuration for creating nodegroup
* delay(int, default=30): The amount of time in seconds to wait between attempts.
* max_attempts(int, default=80): Customized timeout configuration containing delay and max attempts.
* update (str):
Timeout configuration for updating nodegroup
* delay(int, default=30): The amount of time in seconds to wait between attempts.
* max_attempts(int, default=80): Customized timeout configuration containing delay and max attempts.
Request Syntax:
.. code-block:: sls
[eks-nodegroup-name]:
aws.eks.nodegroup.present:
- cluster_name: "string"
- node_group_arn: "string"
- version: "string"
- release_version: "string"
- resource_id: "string"
- status: "string"
- capacity_type: "ON_DEMAND|SPOT"
- instance_types:
- string
- subnets:
- string
- ami_type: "string"
- node_role: "string"
- disk_size: integer
- scaling_config:
desiredSize: integer
maxSize: integer
minSize: integer
- update_config:
maxUnavailable: integer
- tags:
- "string": "string"
Returns:
Dict[str, Any]
Examples:
.. code-block:: sls
nodes1:
aws.eks.nodegroup.present:
- cluster_name: idem-test-cluster
- node_group_arn: arn:aws:eks:us-west-2:000000000000:nodegroup/prod/idem-test-instance-e1a573b9-4e74-48f0-85c4-c214a8ec8ada/id123
- version: '1.21'
- release_version: 1.21.5-20220123
- resource_id: 'idem-test-cluster-node-group'
- status: ACTIVE
- capacity_type: ON_DEMAND
- instance_types:
- t1.micro
- subnets:
- subnet-31813031
- ami_type: AL2_x86_64
- node_role: arn:aws:iam::000000000000:role/idem-test-role-2075a427-24c2-4021-abc3-9b542834addb
- disk_size: 20
- scaling_config:
desiredSize: 2
maxSize: 2
minSize: 2
- update_config:
maxUnavailable: 1
- tags:
- Name: idem-test-instance-e1a573b9-4e74-48f0-85c4-c214a8ec8ada
"""
result = dict(comment=[], old_state=None, new_state=None, name=name, result=True)
before = None
resource_updated = False
if resource_id:
result["comment"] = hub.tool.aws.comment_utils.already_exists_comment(
resource_type="aws.eks.nodegroup", name=name
)
before = await hub.exec.boto3.client.eks.describe_nodegroup(
ctx, clusterName=cluster_name, nodegroupName=resource_id
)
if not before["result"]:
if not ("ResourceNotFoundException" in before["comment"][0]):
result["result"] = False
result["comment"] += before["comment"]
return result
if "CREATE_FAILED" == before["ret"]["nodegroup"].get("status"):
result["result"] = False
result["comment"] += (
before["ret"]["nodegroup"].get("health", {}).get("issues")
)
return result
if before and before["ret"]:
try:
result[
"old_state"
] = hub.tool.aws.eks.conversion_utils.convert_raw_nodegroup_to_present(
raw_resource=before["ret"]["nodegroup"], idem_resource_name=name
)
plan_state = copy.deepcopy(result["old_state"])
client_request_token = result["old_state"].get("client_request_token", None)
# update nodegroup
update_ret = await hub.tool.aws.eks.nodegroup.update_nodegroup(
ctx=ctx,
name=name,
before=result["old_state"],
version=version,
release_version=release_version,
launch_template=launch_template,
labels=labels,
taints=taints,
scaling_config=scaling_config,
update_config=update_config,
client_request_token=client_request_token,
timeout=timeout,
)
result["comment"] += update_ret["comment"]
result["result"] = update_ret["result"]
resource_updated = bool(update_ret["ret"])
if update_ret["ret"] and ctx.get("test", False):
result["comment"] += [f"Would update aws.eks.nodegroup '{name}'"]
for nodegroup_param in update_ret["ret"]:
plan_state[nodegroup_param] = update_ret["ret"][nodegroup_param]
# update tags
update_tags_ret = await hub.tool.aws.eks.tag.update_eks_tags(
ctx=ctx,
resource_arn=result["old_state"].get("nodegroup_arn"),
old_tags=result["old_state"].get("tags"),
new_tags=tags,
)
result["comment"] += update_tags_ret["comment"]
result["result"] = result["result"] and update_tags_ret["result"]
resource_updated = resource_updated or bool(update_tags_ret["ret"])
if ctx.get("test", False) and update_tags_ret["ret"]:
plan_state["tags"] = update_tags_ret["ret"].get("tags")
except Exception as e1:
result["comment"] += [f"{e1.__class__.__name__}: {e1}"]
result["result"] = False
return result
else:
if ctx.get("test", False):
result["new_state"] = hub.tool.aws.test_state_utils.generate_test_state(
enforced_state={},
desired_state={
"name": name,
"cluster_name": cluster_name,
"subnets": subnets,
"node_role": node_role,
"scaling_config": scaling_config,
"disk_size": disk_size,
"instance_types": instance_types,
"ami_type": ami_type,
"remote_access": remote_access,
"labels": labels,
"taints": taints,
"tags": tags,
"client_request_token": client_request_token,
"launch_template": launch_template,
"update_config": update_config,
"capacity_type": capacity_type,
"version": version,
"release_version": release_version,
},
)
result["comment"] += hub.tool.aws.comment_utils.would_create_comment(
resource_type="aws.eks.nodegroup", name=name
)
return result
try:
ret = await hub.exec.boto3.client.eks.create_nodegroup(
ctx,
clusterName=cluster_name,
nodegroupName=name,
scalingConfig=scaling_config,
diskSize=disk_size,
subnets=subnets,
instanceTypes=instance_types,
amiType=ami_type,
remoteAccess=remote_access,
nodeRole=node_role,
labels=labels,
taints=taints,
tags=tags,
clientRequestToken=client_request_token,
launchTemplate=launch_template,
updateConfig=update_config,
capacityType=capacity_type,
version=version,
releaseVersion=release_version,
)
result["result"] = ret["result"]
if not result["result"]:
result["comment"] += ret["comment"]
return result
resource_id = ret["ret"]["nodegroup"]["nodegroupName"]
waiter_config = hub.tool.aws.waiter_utils.create_waiter_config(
default_delay=60,
default_max_attempts=40,
timeout_config=timeout.get("create") if timeout else None,
)
hub.log.debug(f"Waiting on creating aws.eks.nodegroup '{name}'")
try:
await hub.tool.boto3.client.wait(
ctx,
"eks",
"nodegroup_active",
nodegroupName=resource_id,
clusterName=cluster_name,
WaiterConfig=waiter_config,
)
except Exception as e:
result["comment"] += [str(e)]
result["result"] = False
result["comment"] += hub.tool.aws.comment_utils.create_comment(
resource_type="aws.eks.nodegroup", name=name
)
except hub.tool.boto3.exception.ClientError as e:
result["comment"] += [f"{e.__class__.__name__}: {e}"]
result["result"] = False
try:
if ctx.get("test", False):
result["new_state"] = plan_state
elif (not before) or resource_updated:
after = await hub.exec.boto3.client.eks.describe_nodegroup(
ctx, clusterName=cluster_name, nodegroupName=resource_id
)
result[
"new_state"
] = hub.tool.aws.eks.conversion_utils.convert_raw_nodegroup_to_present(
raw_resource=after["ret"]["nodegroup"], idem_resource_name=name
)
else:
result["new_state"] = copy.deepcopy(result["old_state"])
except Exception as e:
result["comment"] += [str(e)]
result["result"] = False
return result
[docs]async def absent(
hub,
ctx,
name: str,
cluster_name: str = None,
resource_id: str = None,
timeout: Dict = None,
) -> Dict[str, Any]:
"""
**Autogenerated function**
Deletes an Amazon EKS node group for a cluster.
Args:
name(str):
An Idem name of EKS Node group.
cluster_name(str, None):
The name of the cluster to create the node group in.
resource_id(str, Optional):
AWS EKS Node group name. Idem automatically considers this resource being absent
if this field is not specified.
timeout(dict, Optional):
Timeout configuration for creating or updating cluster.
* delete (dict):
Timeout configuration for deleting cluster
* delay: The amount of time in seconds to wait between attempts.
* max_attempts: Customized timeout configuration containing delay and max attempts.
Request Syntax:
.. code-block:: sls
[nodegroup-name]:
aws.eks.nodegroup.absent:
- cluster_name: 'string'
- resource_id: 'string'
Returns:
Dict[str, Any]
Examples:
.. code-block:: sls
nodes1:
aws.eks.nodegroup.absent:
- cluster_name: idem-test-cluster
- resource_id: idem-test-nodegroup
"""
result = dict(comment=[], old_state=None, new_state=None, name=name, result=True)
if not resource_id:
result["comment"] = hub.tool.aws.comment_utils.already_absent_comment(
resource_type="aws.eks.nodegroup", name=name
)
return result
if not cluster_name:
result["comment"] = hub.tool.aws.comment_utils.missing_args_for_absent_comment(
resource_type="aws.eks.nodegroup", name=name, args=["cluster_name"]
)
result["result"] = False
return result
before = await hub.exec.boto3.client.eks.describe_nodegroup(
ctx, clusterName=cluster_name, nodegroupName=resource_id
)
if not before["ret"]:
result["comment"] = hub.tool.aws.comment_utils.already_absent_comment(
resource_type="aws.eks.nodegroup", name=name
)
elif ctx.get("test", False):
result[
"old_state"
] = hub.tool.aws.eks.conversion_utils.convert_raw_nodegroup_to_present(
raw_resource=before["ret"]["nodegroup"], idem_resource_name=name
)
result["comment"] = hub.tool.aws.comment_utils.would_delete_comment(
resource_type="aws.eks.nodegroup", name=name
)
return result
else:
result[
"old_state"
] = hub.tool.aws.eks.conversion_utils.convert_raw_nodegroup_to_present(
raw_resource=before["ret"]["nodegroup"], idem_resource_name=name
)
try:
ret = await hub.exec.boto3.client.eks.delete_nodegroup(
ctx, clusterName=cluster_name, nodegroupName=resource_id
)
result["result"] = ret["result"]
if not result["result"]:
result["comment"] += ret["comment"]
result["result"] = False
return result
else:
waiter_config = hub.tool.aws.waiter_utils.create_waiter_config(
default_delay=60,
default_max_attempts=40,
timeout_config=timeout.get("delete") if timeout else None,
)
try:
await hub.tool.boto3.client.wait(
ctx,
"eks",
"nodegroup_deleted",
nodegroupName=resource_id,
clusterName=cluster_name,
WaiterConfig=waiter_config,
)
except Exception as e:
result["comment"] += [str(e)]
result["result"] = False
result["comment"] += hub.tool.aws.comment_utils.delete_comment(
resource_type="aws.eks.nodegroup", name=name
)
except Exception as e:
result["comment"] += [f"{e.__class__.__name__}: {e}"]
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
Lists the Amazon EKS managed node groups associated with the specified cluster in your Amazon Web Services
account in the specified Region. Self-managed node groups are not listed.
Returns:
Dict[str, Any]
Examples:
.. code-block:: bash
$ idem describe aws.eks.nodegroup
"""
result = {}
cluster_ret = await hub.exec.boto3.client.eks.list_clusters(ctx)
if not cluster_ret["result"]:
hub.log.warning(f"Could not describe cluster {cluster_ret['comment']}")
return {}
cluster_node_groups = []
try:
for name in cluster_ret["ret"]["clusters"]:
node_group_ret = await hub.exec.boto3.client.eks.list_nodegroups(
ctx, clusterName=name
)
if not node_group_ret["result"]:
result["comment"] = node_group_ret["comment"]
return result
for group in node_group_ret["ret"]["nodegroups"]:
cluster_node_groups.append({"name": name, "group": group})
except Exception as e:
result["comment"] = str(e)
result["result"] = False
return result
for node_groups in cluster_node_groups:
cluster_name = node_groups["name"]
node_group = node_groups["group"]
describe_ret = await hub.exec.boto3.client.eks.describe_nodegroup(
ctx, clusterName=cluster_name, nodegroupName=node_group
)
nodegroup = describe_ret["ret"]["nodegroup"]
resource_id = f"{nodegroup['clusterName']}-{nodegroup['nodegroupName']}"
resource_translated = (
hub.tool.aws.eks.conversion_utils.convert_raw_nodegroup_to_present(
raw_resource=nodegroup, idem_resource_name=resource_id
)
)
result[resource_id] = {
"aws.eks.nodegroup.present": [
{parameter_key: parameter_value}
for parameter_key, parameter_value in resource_translated.items()
]
}
return result