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

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 

15 

16 

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

18class NodeGroupDescribeSource(ChildDescribeSource): 

19 

20 def get_query(self): 

21 query = super(NodeGroupDescribeSource, self).get_query() 

22 query.capture_parent_id = True 

23 return query 

24 

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 

36 

37 

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

39class NodeGroup(ChildResourceManager): 

40 

41 class resource_type(TypeInfo): 

42 

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' 

52 

53 source_mapping = { 

54 'describe-child': NodeGroupDescribeSource, 

55 'describe': NodeGroupDescribeSource, 

56 } 

57 

58 

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

60class DeleteNodeGroup(Action): 

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

62 

63 schema = type_schema('delete') 

64 permissions = ('eks:DeleteNodegroup',) 

65 

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 

76 

77 

78class EKSDescribeSource(DescribeSource): 

79 

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 

87 

88 

89class EKSConfigSource(ContainerConfigSource): 

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

91 

92 

93@resources.register('eks') 

94class EKS(QueryResourceManager): 

95 

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' 

105 

106 source_mapping = { 

107 'config': EKSConfigSource, 

108 'describe': EKSDescribeSource 

109 } 

110 

111 

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

113class EKSSubnetFilter(SubnetFilter): 

114 

115 RelatedIdsExpression = "resourcesVpcConfig.subnetIds[]" 

116 

117 

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

119class EKSSGFilter(SecurityGroupFilter): 

120 

121 RelatedIdsExpression = "resourcesVpcConfig.securityGroupIds[]" 

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 = { 

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 } 

188 

189 permissions = ('eks:UpdateClusterConfig',) 

190 shape = 'UpdateClusterConfigRequest' 

191 

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) 

198 

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)) 

212 

213 

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

215class AssociateEncryptionConfig(Action): 

216 """ 

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

218 

219 :example: 

220 

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

222 associate encryption config with the specified keyArn. 

223 

224 .. code-block:: yaml 

225 

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 } 

268 

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

270 

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 

305 

306 

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

308class Delete(Action): 

309 

310 schema = type_schema('delete') 

311 permissions = ('eks:DeleteCluster',) 

312 

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 

321 

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) 

346 

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)