Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/c7n/resources/eks.py: 49%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

187 statements  

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.resources.aws import shape_schema 

8from c7n import tags, query 

9from c7n.query import QueryResourceManager, TypeInfo, DescribeSource, \ 

10 ChildResourceManager, ChildDescribeSource 

11from c7n.utils import local_session, type_schema, get_retry 

12from botocore.waiter import WaiterModel, create_waiter_with_client 

13from .aws import shape_validate 

14from .ecs import ContainerConfigSource 

15from c7n.filters.kms import KmsRelatedFilter 

16 

17 

18@query.sources.register('describe-eks-nodegroup') 

19class NodeGroupDescribeSource(ChildDescribeSource): 

20 

21 def get_query(self): 

22 return super().get_query(capture_parent_id=True) 

23 

24 def augment(self, resources): 

25 results = [] 

26 client = local_session(self.manager.session_factory).client('eks') 

27 for cluster_name, nodegroup_name in resources: 

28 nodegroup = client.describe_nodegroup( 

29 clusterName=cluster_name, 

30 nodegroupName=nodegroup_name)['nodegroup'] 

31 if 'tags' in nodegroup: 

32 nodegroup['Tags'] = [{'Key': k, 'Value': v} for k, v in nodegroup['tags'].items()] 

33 results.append(nodegroup) 

34 return results 

35 

36 

37@resources.register('eks-nodegroup') 

38class NodeGroup(ChildResourceManager): 

39 

40 class resource_type(TypeInfo): 

41 

42 service = 'eks' 

43 arn = 'nodegroupArn' 

44 arn_type = 'nodegroup' 

45 id = 'nodegroupArn' 

46 name = 'nodegroupName' 

47 enum_spec = ('list_nodegroups', 'nodegroups', None) 

48 parent_spec = ('eks', 'clusterName', None) 

49 permissions_enum = ('eks:DescribeNodegroup',) 

50 date = 'createdAt' 

51 

52 source_mapping = { 

53 'describe-child': NodeGroupDescribeSource, 

54 'describe': NodeGroupDescribeSource, 

55 } 

56 

57 

58@NodeGroup.action_registry.register('delete') 

59class DeleteNodeGroup(Action): 

60 """Delete node group(s).""" 

61 

62 schema = type_schema('delete') 

63 permissions = ('eks:DeleteNodegroup',) 

64 

65 def process(self, resources): 

66 client = local_session(self.manager.session_factory).client('eks') 

67 retry = get_retry(('Throttling',)) 

68 for r in resources: 

69 try: 

70 retry(client.delete_nodegroup, 

71 clusterName=r['clusterName'], 

72 nodegroupName=r['nodegroupName']) 

73 except client.exceptions.ResourceNotFoundException: 

74 continue 

75 

76 

77class EKSDescribeSource(DescribeSource): 

78 

79 def augment(self, resources): 

80 resources = super().augment(resources) 

81 for r in resources: 

82 if 'tags' not in r: 

83 continue 

84 r['Tags'] = [{'Key': k, 'Value': v} for k, v in r['tags'].items()] 

85 return resources 

86 

87 

88class EKSConfigSource(ContainerConfigSource): 

89 mapped_keys = {'certificateAuthorityData': 'certificateAuthority'} 

90 

91 

92@resources.register('eks') 

93class EKS(QueryResourceManager): 

94 

95 class resource_type(TypeInfo): 

96 service = 'eks' 

97 enum_spec = ('list_clusters', 'clusters', None) 

98 arn = 'arn' 

99 arn_type = 'cluster' 

100 detail_spec = ('describe_cluster', 'name', None, 'cluster') 

101 id = name = 'name' 

102 date = 'createdAt' 

103 config_type = cfn_type = 'AWS::EKS::Cluster' 

104 

105 source_mapping = { 

106 'config': EKSConfigSource, 

107 'describe': EKSDescribeSource 

108 } 

109 

110 

111@EKS.filter_registry.register('subnet') 

112class EKSSubnetFilter(SubnetFilter): 

113 

114 RelatedIdsExpression = "resourcesVpcConfig.subnetIds[]" 

115 

116 

117@EKS.filter_registry.register('security-group') 

118class EKSSGFilter(SecurityGroupFilter): 

119 

120 RelatedIdsExpression = "resourcesVpcConfig.securityGroupIds[]" 

121 

122 

123EKS.filter_registry.register('network-location', net_filters.NetworkLocation) 

124 

125 

126@EKS.filter_registry.register('vpc') 

127class EKSVpcFilter(VpcFilter): 

128 

129 RelatedIdsExpression = 'resourcesVpcConfig.vpcId' 

130 

131 

132@EKS.filter_registry.register('kms-key') 

133class KmsFilter(KmsRelatedFilter): 

134 RelatedIdsExpression = 'encryptionConfig[].provider.keyArn' 

135 

136 

137@EKS.action_registry.register('tag') 

138class EKSTag(tags.Tag): 

139 

140 permissions = ('eks:TagResource',) 

141 

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 

151 

152 

153EKS.filter_registry.register('marked-for-op', tags.TagActionFilter) 

154EKS.action_registry.register('mark-for-op', tags.TagDelayedAction) 

155 

156 

157@EKS.action_registry.register('remove-tag') 

158class EKSRemoveTag(tags.RemoveTag): 

159 

160 permissions = ('eks:UntagResource',) 

161 

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 

170 

171 

172@EKS.action_registry.register('update-config') 

173class UpdateConfig(Action): 

174 

175 schema = type_schema('update-config', 

176 **shape_schema( 

177 'eks', 'UpdateClusterConfigRequest', drop_fields=('name')) 

178 ) 

179 

180 permissions = ('eks:UpdateClusterConfig',) 

181 shape = 'UpdateClusterConfigRequest' 

182 

183 def validate(self): 

184 cfg = dict(self.data) 

185 cfg['name'] = 'validate' 

186 cfg.pop('type') 

187 return shape_validate( 

188 cfg, self.shape, self.manager.resource_type.service) 

189 

190 def process(self, resources): 

191 client = local_session(self.manager.session_factory).client('eks') 

192 state_filtered = 0 

193 params = dict(self.data) 

194 params.pop('type') 

195 for r in resources: 

196 if r['status'] != 'ACTIVE': 

197 state_filtered += 1 

198 continue 

199 client.update_cluster_config(name=r['name'], **params) 

200 if state_filtered: 

201 self.log.warning( 

202 "Filtered %d of %d clusters due to state", state_filtered, len(resources)) 

203 

204 

205@EKS.action_registry.register('associate-encryption-config') 

206class AssociateEncryptionConfig(Action): 

207 """ 

208 Action that adds an encryption configuration to an EKS cluster. 

209 

210 :example: 

211 

212 This policy will find all EKS clusters that do not have Secrets encryption set and 

213 associate encryption config with the specified keyArn. 

214 

215 .. code-block:: yaml 

216 

217 policies: 

218 - name: associate-encryption-config 

219 resource: aws.eks 

220 filters: 

221 - type: value 

222 key: encryptionConfig[].provider.keyArn 

223 value: absent 

224 actions: 

225 - type: associate-encryption-config 

226 encryptionConfig: 

227 - provider: 

228 keyArn: alias/eks 

229 resources: 

230 - secrets 

231 """ 

232 schema = { 

233 'type': 'object', 

234 'additionalProperties': False, 

235 'properties': { 

236 'type': {'enum': ['associate-encryption-config']}, 

237 'encryptionConfig': { 

238 'type': 'array', 

239 'properties': { 

240 'type': 'object', 

241 'properties': { 

242 'provider': { 

243 'type': 'object', 

244 'properties': { 

245 'keyArn': {'type': 'string'} 

246 } 

247 }, 

248 'resources': { 

249 'type': 'array', 

250 'properties': { 

251 'enum': 'secrets' 

252 } 

253 } 

254 } 

255 } 

256 } 

257 } 

258 } 

259 

260 permissions = ('eks:AssociateEncryptionConfig', 'kms:DescribeKey',) 

261 

262 def process(self, resources): 

263 client = local_session(self.manager.session_factory).client('eks') 

264 error = None 

265 params = dict(self.data) 

266 params.pop('type') 

267 # associate_encryption_config does not accept kms key aliases, if provided 

268 # with an alias find the key arn with kms:DescribeKey first. 

269 key_arn = params['encryptionConfig'][0]['provider']['keyArn'] 

270 if 'alias' in key_arn: 

271 try: 

272 kms_client = local_session(self.manager.session_factory).client('kms') 

273 _key_arn = kms_client.describe_key(KeyId=key_arn)['KeyMetadata']['Arn'] 

274 params['encryptionConfig'][0]['provider']['keyArn'] = _key_arn 

275 except kms_client.exceptions.NotFoundException as e: 

276 self.log.error( 

277 "The following error was received for kms:DescribeKey: " 

278 f"{e.response['Error']['Message']}" 

279 ) 

280 raise e 

281 for r in self.filter_resources(resources, 'status', ('ACTIVE',)): 

282 try: 

283 client.associate_encryption_config( 

284 clusterName=r['name'], 

285 encryptionConfig=params['encryptionConfig'] 

286 ) 

287 except client.exceptions.InvalidParameterException as e: 

288 error = e 

289 self.log.error( 

290 "The following error was received for cluster " 

291 f"{r['name']}: {e.response['Error']['Message']}" 

292 ) 

293 continue 

294 if error: 

295 raise error 

296 

297 

298@EKS.action_registry.register('delete') 

299class Delete(Action): 

300 

301 schema = type_schema('delete') 

302 permissions = ('eks:DeleteCluster',) 

303 

304 def process(self, resources): 

305 client = local_session(self.manager.session_factory).client('eks') 

306 for r in resources: 

307 try: 

308 self.delete_associated(r, client) 

309 client.delete_cluster(name=r['name']) 

310 except client.exceptions.ResourceNotFoundException: 

311 continue 

312 

313 def delete_associated(self, r, client): 

314 nodegroups = client.list_nodegroups(clusterName=r['name'])['nodegroups'] 

315 fargate_profiles = client.list_fargate_profiles( 

316 clusterName=r['name'])['fargateProfileNames'] 

317 waiters = [] 

318 if nodegroups: 

319 for nodegroup in nodegroups: 

320 self.manager.retry( 

321 client.delete_nodegroup, clusterName=r['name'], nodegroupName=nodegroup) 

322 # Nodegroup supports parallel delete so process in parallel, check these later on 

323 waiters.append({"clusterName": r['name'], "nodegroupName": nodegroup}) 

324 if fargate_profiles: 

325 waiter = self.fargate_delete_waiter(client) 

326 for profile in fargate_profiles: 

327 self.manager.retry( 

328 client.delete_fargate_profile, 

329 clusterName=r['name'], fargateProfileName=profile) 

330 # Fargate profiles don't support parallel deletes so process serially 

331 waiter.wait( 

332 clusterName=r['name'], fargateProfileName=profile) 

333 if waiters: 

334 waiter = client.get_waiter('nodegroup_deleted') 

335 for w in waiters: 

336 waiter.wait(**w) 

337 

338 def fargate_delete_waiter(self, client): 

339 # Fargate profiles seem to delete faster @ roughly 2 minutes each so keeping defaults 

340 config = { 

341 'version': 2, 

342 'waiters': { 

343 "FargateProfileDeleted": { 

344 'operation': 'DescribeFargateProfile', 

345 'delay': 30, 

346 'maxAttempts': 40, 

347 'acceptors': [ 

348 { 

349 "expected": "DELETE_FAILED", 

350 "matcher": "path", 

351 "state": "failure", 

352 "argument": "fargateprofile.status" 

353 }, 

354 { 

355 "expected": "ResourceNotFoundException", 

356 "matcher": "error", 

357 "state": "success" 

358 } 

359 ] 

360 } 

361 } 

362 } 

363 return create_waiter_with_client("FargateProfileDeleted", WaiterModel(config), client)