Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/c7n/resources/eks.py: 49%
187 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-08 06:51 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-08 06:51 +0000
1# Copyright The Cloud Custodian Authors.
2# SPDX-License-Identifier: Apache-2.0
3import c7n.filters.vpc as net_filters
4from c7n.actions import Action
5from c7n.filters.vpc import SecurityGroupFilter, SubnetFilter, VpcFilter
6from c7n.manager import resources
7from c7n import tags, query
8from c7n.query import QueryResourceManager, TypeInfo, DescribeSource, \
9 ChildResourceManager, ChildDescribeSource
10from c7n.utils import local_session, type_schema, get_retry
11from botocore.waiter import WaiterModel, create_waiter_with_client
12from .aws import shape_validate
13from .ecs import ContainerConfigSource
14from c7n.filters.kms import KmsRelatedFilter
17@query.sources.register('describe-eks-nodegroup')
18class NodeGroupDescribeSource(ChildDescribeSource):
20 def get_query(self):
21 query = super(NodeGroupDescribeSource, self).get_query()
22 query.capture_parent_id = True
23 return query
25 def augment(self, resources):
26 results = []
27 client = local_session(self.manager.session_factory).client('eks')
28 for cluster_name, nodegroup_name in resources:
29 nodegroup = client.describe_nodegroup(
30 clusterName=cluster_name,
31 nodegroupName=nodegroup_name)['nodegroup']
32 if 'tags' in nodegroup:
33 nodegroup['Tags'] = [{'Key': k, 'Value': v} for k, v in nodegroup['tags'].items()]
34 results.append(nodegroup)
35 return results
38@resources.register('eks-nodegroup')
39class NodeGroup(ChildResourceManager):
41 class resource_type(TypeInfo):
43 service = 'eks'
44 arn = 'nodegroupArn'
45 arn_type = 'nodegroup'
46 id = 'nodegroupArn'
47 name = 'nodegroupName'
48 enum_spec = ('list_nodegroups', 'nodegroups', None)
49 parent_spec = ('eks', 'clusterName', None)
50 permissions_enum = ('eks:DescribeNodegroup',)
51 date = 'createdAt'
53 source_mapping = {
54 'describe-child': NodeGroupDescribeSource,
55 'describe': NodeGroupDescribeSource,
56 }
59@NodeGroup.action_registry.register('delete')
60class DeleteNodeGroup(Action):
61 """Delete node group(s)."""
63 schema = type_schema('delete')
64 permissions = ('eks:DeleteNodegroup',)
66 def process(self, resources):
67 client = local_session(self.manager.session_factory).client('eks')
68 retry = get_retry(('Throttling',))
69 for r in resources:
70 try:
71 retry(client.delete_nodegroup,
72 clusterName=r['clusterName'],
73 nodegroupName=r['nodegroupName'])
74 except client.exceptions.ResourceNotFoundException:
75 continue
78class EKSDescribeSource(DescribeSource):
80 def augment(self, resources):
81 resources = super().augment(resources)
82 for r in resources:
83 if 'tags' not in r:
84 continue
85 r['Tags'] = [{'Key': k, 'Value': v} for k, v in r['tags'].items()]
86 return resources
89class EKSConfigSource(ContainerConfigSource):
90 mapped_keys = {'certificateAuthorityData': 'certificateAuthority'}
93@resources.register('eks')
94class EKS(QueryResourceManager):
96 class resource_type(TypeInfo):
97 service = 'eks'
98 enum_spec = ('list_clusters', 'clusters', None)
99 arn = 'arn'
100 arn_type = 'cluster'
101 detail_spec = ('describe_cluster', 'name', None, 'cluster')
102 id = name = 'name'
103 date = 'createdAt'
104 config_type = cfn_type = 'AWS::EKS::Cluster'
106 source_mapping = {
107 'config': EKSConfigSource,
108 'describe': EKSDescribeSource
109 }
112@EKS.filter_registry.register('subnet')
113class EKSSubnetFilter(SubnetFilter):
115 RelatedIdsExpression = "resourcesVpcConfig.subnetIds[]"
118@EKS.filter_registry.register('security-group')
119class EKSSGFilter(SecurityGroupFilter):
121 RelatedIdsExpression = "resourcesVpcConfig.securityGroupIds[]"
123EKS.filter_registry.register('network-location', net_filters.NetworkLocation)
126@EKS.filter_registry.register('vpc')
127class EKSVpcFilter(VpcFilter):
129 RelatedIdsExpression = 'resourcesVpcConfig.vpcId'
132@EKS.filter_registry.register('kms-key')
133class KmsFilter(KmsRelatedFilter):
134 RelatedIdsExpression = 'encryptionConfig[].provider.keyArn'
137@EKS.action_registry.register('tag')
138class EKSTag(tags.Tag):
140 permissions = ('eks:TagResource',)
142 def process_resource_set(self, client, resource_set, tags):
143 for r in resource_set:
144 try:
145 self.manager.retry(
146 client.tag_resource,
147 resourceArn=r['arn'],
148 tags={t['Key']: t['Value'] for t in tags})
149 except client.exceptions.ResourceNotFoundException:
150 continue
153EKS.filter_registry.register('marked-for-op', tags.TagActionFilter)
154EKS.action_registry.register('mark-for-op', tags.TagDelayedAction)
157@EKS.action_registry.register('remove-tag')
158class EKSRemoveTag(tags.RemoveTag):
160 permissions = ('eks:UntagResource',)
162 def process_resource_set(self, client, resource_set, tags):
163 for r in resource_set:
164 try:
165 self.manager.retry(
166 client.untag_resource,
167 resourceArn=r['arn'], tagKeys=tags)
168 except client.exceptions.ResourceNotFoundException:
169 continue
172@EKS.action_registry.register('update-config')
173class UpdateConfig(Action):
175 schema = {
176 'type': 'object',
177 'additionalProperties': False,
178 'oneOf': [
179 {'required': ['type', 'logging']},
180 {'required': ['type', 'resourcesVpcConfig']},
181 {'required': ['type', 'logging', 'resourcesVpcConfig']}],
182 'properties': {
183 'type': {'enum': ['update-config']},
184 'logging': {'type': 'object'},
185 'resourcesVpcConfig': {'type': 'object'}
186 }
187 }
189 permissions = ('eks:UpdateClusterConfig',)
190 shape = 'UpdateClusterConfigRequest'
192 def validate(self):
193 cfg = dict(self.data)
194 cfg['name'] = 'validate'
195 cfg.pop('type')
196 return shape_validate(
197 cfg, self.shape, self.manager.resource_type.service)
199 def process(self, resources):
200 client = local_session(self.manager.session_factory).client('eks')
201 state_filtered = 0
202 params = dict(self.data)
203 params.pop('type')
204 for r in resources:
205 if r['status'] != 'ACTIVE':
206 state_filtered += 1
207 continue
208 client.update_cluster_config(name=r['name'], **params)
209 if state_filtered:
210 self.log.warning(
211 "Filtered %d of %d clusters due to state", state_filtered, len(resources))
214@EKS.action_registry.register('associate-encryption-config')
215class AssociateEncryptionConfig(Action):
216 """
217 Action that adds an encryption configuration to an EKS cluster.
219 :example:
221 This policy will find all EKS clusters that do not have Secrets encryption set and
222 associate encryption config with the specified keyArn.
224 .. code-block:: yaml
226 policies:
227 - name: associate-encryption-config
228 resource: aws.eks
229 filters:
230 - type: value
231 key: encryptionConfig[].provider.keyArn
232 value: absent
233 actions:
234 - type: associate-encryption-config
235 encryptionConfig:
236 - provider:
237 keyArn: alias/eks
238 resources:
239 - secrets
240 """
241 schema = {
242 'type': 'object',
243 'additionalProperties': False,
244 'properties': {
245 'type': {'enum': ['associate-encryption-config']},
246 'encryptionConfig': {
247 'type': 'array',
248 'properties': {
249 'type': 'object',
250 'properties': {
251 'provider': {
252 'type': 'object',
253 'properties': {
254 'keyArn': {'type': 'string'}
255 }
256 },
257 'resources': {
258 'type': 'array',
259 'properties': {
260 'enum': 'secrets'
261 }
262 }
263 }
264 }
265 }
266 }
267 }
269 permissions = ('eks:AssociateEncryptionConfig', 'kms:DescribeKey',)
271 def process(self, resources):
272 client = local_session(self.manager.session_factory).client('eks')
273 error = None
274 params = dict(self.data)
275 params.pop('type')
276 # associate_encryption_config does not accept kms key aliases, if provided
277 # with an alias find the key arn with kms:DescribeKey first.
278 key_arn = params['encryptionConfig'][0]['provider']['keyArn']
279 if 'alias' in key_arn:
280 try:
281 kms_client = local_session(self.manager.session_factory).client('kms')
282 _key_arn = kms_client.describe_key(KeyId=key_arn)['KeyMetadata']['Arn']
283 params['encryptionConfig'][0]['provider']['keyArn'] = _key_arn
284 except kms_client.exceptions.NotFoundException as e:
285 self.log.error(
286 "The following error was received for kms:DescribeKey: " \
287 f"{e.response['Error']['Message']}"
288 )
289 raise e
290 for r in self.filter_resources(resources, 'status', ('ACTIVE',)):
291 try:
292 client.associate_encryption_config(
293 clusterName=r['name'],
294 encryptionConfig=params['encryptionConfig']
295 )
296 except client.exceptions.InvalidParameterException as e:
297 error = e
298 self.log.error(
299 "The following error was received for cluster " \
300 f"{r['name']}: {e.response['Error']['Message']}"
301 )
302 continue
303 if error:
304 raise error
307@EKS.action_registry.register('delete')
308class Delete(Action):
310 schema = type_schema('delete')
311 permissions = ('eks:DeleteCluster',)
313 def process(self, resources):
314 client = local_session(self.manager.session_factory).client('eks')
315 for r in resources:
316 try:
317 self.delete_associated(r, client)
318 client.delete_cluster(name=r['name'])
319 except client.exceptions.ResourceNotFoundException:
320 continue
322 def delete_associated(self, r, client):
323 nodegroups = client.list_nodegroups(clusterName=r['name'])['nodegroups']
324 fargate_profiles = client.list_fargate_profiles(
325 clusterName=r['name'])['fargateProfileNames']
326 waiters = []
327 if nodegroups:
328 for nodegroup in nodegroups:
329 self.manager.retry(
330 client.delete_nodegroup, clusterName=r['name'], nodegroupName=nodegroup)
331 # Nodegroup supports parallel delete so process in parallel, check these later on
332 waiters.append({"clusterName": r['name'], "nodegroupName": nodegroup})
333 if fargate_profiles:
334 waiter = self.fargate_delete_waiter(client)
335 for profile in fargate_profiles:
336 self.manager.retry(
337 client.delete_fargate_profile,
338 clusterName=r['name'], fargateProfileName=profile)
339 # Fargate profiles don't support parallel deletes so process serially
340 waiter.wait(
341 clusterName=r['name'], fargateProfileName=profile)
342 if waiters:
343 waiter = client.get_waiter('nodegroup_deleted')
344 for w in waiters:
345 waiter.wait(**w)
347 def fargate_delete_waiter(self, client):
348 # Fargate profiles seem to delete faster @ roughly 2 minutes each so keeping defaults
349 config = {
350 'version': 2,
351 'waiters': {
352 "FargateProfileDeleted": {
353 'operation': 'DescribeFargateProfile',
354 'delay': 30,
355 'maxAttempts': 40,
356 'acceptors': [
357 {
358 "expected": "DELETE_FAILED",
359 "matcher": "path",
360 "state": "failure",
361 "argument": "fargateprofile.status"
362 },
363 {
364 "expected": "ResourceNotFoundException",
365 "matcher": "error",
366 "state": "success"
367 }
368 ]
369 }
370 }
371 }
372 return create_waiter_with_client("FargateProfileDeleted", WaiterModel(config), client)