1# Copyright The Cloud Custodian Authors.
2# SPDX-License-Identifier: Apache-2.0
3import re
4
5from c7n_gcp.provider import resources
6from c7n_gcp.query import (QueryResourceManager, TypeInfo, ChildTypeInfo,
7 ChildResourceManager)
8from c7n.utils import type_schema, local_session
9from c7n_gcp.actions import MethodAction
10from c7n_gcp.utils import get_firewall_port_ranges
11
12from c7n.filters import ValueFilter
13
14
15@resources.register('gke-cluster')
16class KubernetesCluster(QueryResourceManager):
17 """GCP resource:
18 https://cloud.google.com/kubernetes-engine/docs/reference/rest/v1/projects.locations.clusters
19 """
20
21 class resource_type(TypeInfo):
22 service = 'container'
23 version = 'v1'
24 component = 'projects.locations.clusters'
25 enum_spec = ('list', 'clusters[]', None)
26 scope = 'project'
27 scope_key = 'parent'
28 scope_template = "projects/{}/locations/-"
29 name = id = "name"
30 default_report_fields = [
31 'name', 'description', 'status', 'currentMasterVersion', 'currentNodeVersion',
32 'currentNodeCount', 'location']
33 asset_type = 'container.googleapis.com/Cluster'
34 scc_type = 'google.container.Cluster'
35 metric_key = 'resource.labels.cluster_name'
36 urn_component = 'cluster'
37 labels = True
38 labels_op = 'setResourceLabels'
39 urn_zonal = True
40
41 @staticmethod
42 def get(client, resource_info):
43 return client.execute_query(
44 'get', verb_arguments={
45 'name': 'projects/{}/locations/{}/clusters/{}'.format(
46 resource_info['project_id'],
47 resource_info['location'],
48 resource_info['cluster_name'])})
49
50 @staticmethod
51 def get_label_params(resource, all_labels):
52 location_str = "locations"
53 if resource['selfLink'].find(location_str) < 0:
54 location_str = "zones"
55 path_param_re = re.compile(
56 "%s%s%s" % (
57 '.*?/projects/(.*?)/', location_str, '/(.*?)/clusters/(.*)'
58 )
59 )
60 project, zone, cluster_name = path_param_re.match(
61 resource['selfLink']).groups()
62 return {'name': "%s%s%s%s%s%s" % (
63 'projects/', project, '/locations/', zone, '/clusters/', cluster_name),
64 'body': {
65 'resourceLabels': all_labels,
66 'labelFingerprint': resource['labelFingerprint']
67 }}
68
69 @classmethod
70 def refresh(cls, client, resource):
71 project_id = resource['selfLink'].split("/")[5]
72 return cls.get(
73 client,
74 {
75 'project_id': project_id,
76 'location': resource['zone'],
77 'cluster_name': resource['name']
78 }
79 )
80
81 def augment(self, resources):
82 if not resources:
83 return []
84 for r in resources:
85 if r.get('resourceLabels'):
86 r['labels'] = r['resourceLabels']
87 return resources
88
89
90@KubernetesCluster.filter_registry.register('effective-firewall')
91class EffectiveFirewall(ValueFilter):
92 """Filters gke clusters by their effective firewall rules.
93 See `getEffectiveFirewalls
94 https://cloud.google.com/workflows/docs/reference/googleapis/compute/v1/networks/getEffectiveFirewalls`_
95 for valid fields.
96
97 :example:
98
99 Filter all gke clusters that have a firewall rule that allows public
100 access
101
102 .. code-block:: yaml
103
104 policies:
105 - name: find-publicly-accessable-clusters
106 resource: gcp.gke-cluster
107 filters:
108 - type: effective-firewall
109 key: sourceRanges[]
110 op: contains
111 value: "0.0.0.0/0"
112 """
113
114 schema = type_schema('effective-firewall', rinherit=ValueFilter.schema)
115 permissions = ('compute.instances.getEffectiveFirewalls',)
116 annotation_key = "c7n:firewall"
117
118 def get_firewalls(self, client, p, r):
119 if self.annotation_key not in r:
120 firewalls = client.execute_command('getEffectiveFirewalls',
121 verb_arguments={'project': p, 'network': r['network']}).get('firewalls', [])
122
123 r[self.annotation_key] = get_firewall_port_ranges(firewalls)
124 return super(EffectiveFirewall, self).process(r[self.annotation_key], None)
125
126 def process(self, resources, event=None):
127 session = local_session(self.manager.session_factory)
128 project = session.get_default_project()
129 client = session.client(
130 "compute", "v1", "networks"
131 )
132 resource_list = [r for r in resources
133 if self.get_firewalls(client, project, r)]
134 return resource_list
135
136
137@resources.register('gke-nodepool')
138class KubernetesClusterNodePool(ChildResourceManager):
139 """GCP resource:
140 https://cloud.google.com/kubernetes-engine/docs/reference/rest/v1/projects.zones.clusters.nodePools
141 """
142
143 def _get_parent_resource_info(self, child_instance):
144 project_param_re = re.compile(
145 '.*?/projects/(.*?)/zones/(.*?)/clusters/(.*?)/nodePools/(.*?)'
146 )
147 parent_values = re.match(project_param_re, child_instance['selfLink']).groups()
148 parent_info = dict(
149 zip(('project_id', 'location', 'cluster_name', 'node_name'), parent_values)
150 )
151
152 return parent_info
153
154 def _get_child_enum_args(self, parent_instance):
155 return {
156 'parent': 'projects/{}/locations/{}/clusters/{}'.format(
157 local_session(self.session_factory).get_default_project(),
158 parent_instance['location'],
159 parent_instance['name']
160 )
161 }
162
163 class resource_type(ChildTypeInfo):
164 service = 'container'
165 version = 'v1'
166 component = 'projects.locations.clusters.nodePools'
167 enum_spec = ('list', 'nodePools[]', None)
168 scope = 'global'
169 name = id = 'name'
170 parent_spec = {'resource': 'gke-cluster'}
171 asset_type = 'container.googleapis.com/NodePool'
172 default_report_fields = ['name', 'status', 'version']
173 permissions = ('container.nodes.list',)
174 urn_component = 'cluster-node-pool'
175 urn_zonal = True
176
177 @staticmethod
178 def get(client, resource_info):
179 cluster_name = resource_info['cluster_name']
180 name = re.match(
181 r".*{}-(.*)-[^-]+-[^-]?".format(cluster_name),
182 resource_info['resourceName']).group(1)
183
184 return client.execute_command(
185 'get', verb_arguments={
186 'name': 'projects/{}/locations/{}/clusters/{}/nodePools/{}'.format(
187 resource_info['project_id'],
188 resource_info['location'],
189 resource_info['cluster_name'],
190 name)}
191 )
192
193 @classmethod
194 def _get_location(cls, resource):
195 "Get the region from the parent - the cluster"
196 return super()._get_location(cls.get_parent(resource))
197
198
199@KubernetesCluster.filter_registry.register('server-config')
200@KubernetesClusterNodePool.filter_registry.register('server-config')
201class ServerConfig(ValueFilter):
202 """Filters kubernetes clusters or nodepools by their server config.
203 See `getServerConfig
204 https://cloud.google.com/kubernetes-engine/docs/reference/rest/v1/projects.locations/getServerConfig`
205 for valid fields.
206
207 :example:
208
209 Filter all clusters that is not running a supported version
210
211 .. code-block:: yaml
212
213 policies:
214 - name: find-unsupported-cluster-version
215 resource: gcp.gke-cluster
216 filters:
217 - type: server-config
218 key: contains(serverConfig.validMasterVersions, resource.currentMasterVersion)
219 value: false
220
221 Filter all nodepools that is not running a supported version
222
223 .. code-block:: yaml
224
225 policies:
226 - name: find-unsupported-cluster-nodepools-version
227 resource: gcp.gke-nodepool
228 filters:
229 - type: server-config
230 key: contains(serverConfig.validNodeVersions, resource.version)
231 value: false
232 """
233
234 schema = type_schema('server-config', rinherit=ValueFilter.schema)
235 permissions = ('container.nodes.get', 'container.clusters.get')
236 annotation_key = "c7n:config"
237
238 def _get_location(self, r):
239 return r["location"] if "location" in r else r['selfLink'].split('/')[-5]
240
241 def get_config(self, client, project, resource):
242 if self.annotation_key in resource:
243 return
244 location = self._get_location(resource)
245 resource[self.annotation_key] = client.execute_command(
246 'getServerConfig', verb_arguments={
247 'name': 'projects/{}/locations/{}'.format(project, location)}
248 )
249
250 def __call__(self, r):
251 return super().__call__({"serverConfig": r[self.annotation_key], "resource": r})
252
253 def process(self, resources, event=None):
254 session = local_session(self.manager.session_factory)
255 project = session.get_default_project()
256 client = session.client("container", "v1", "projects.locations")
257 for r in resources:
258 self.get_config(client, project, r)
259 return super().process(resources)
260
261
262@KubernetesCluster.action_registry.register('delete')
263class Delete(MethodAction):
264 """Action to delete GKE clusters
265
266 It is recommended to use a filter to avoid unwanted deletion of GKE clusters
267
268 :example:
269
270 .. code-block:: yaml
271
272 policies:
273 - name: gcp-delete-testing-gke-clusters
274 resource: gcp.gke-cluster
275 filters:
276 - type: value
277 key: name
278 op: regex
279 value: '^(test-|demo-)*'
280 actions:
281 - type: delete
282 """
283
284 schema = type_schema('delete')
285 method_spec = {'op': 'delete'}
286
287 def get_resource_params(self, model, resource_info):
288 project = local_session(self.manager.source.query.session_factory).get_default_project()
289
290 return {'name': 'projects/{}/locations/{}/clusters/{}'.format(
291 project,
292 resource_info['location'],
293 resource_info['name'])}