hub-clusters-creator 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,264 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (C) 2019 Rohith Jayawardene <gambol99@gmail.com>
4
+ #
5
+ # This program is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU General Public License
7
+ # as published by the Free Software Foundation; either version 2
8
+ # of the License, or (at your option) any later version.
9
+ #
10
+ # This program is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
17
+ #
18
+ require 'google/apis/compute_v1'
19
+ require 'google/apis/container_v1beta1'
20
+ require 'google/apis/dns_v1'
21
+ require 'googleauth'
22
+
23
+ require 'hub-clusters-creator/errors'
24
+ require 'hub-clusters-creator/kube/kube'
25
+ require 'hub-clusters-creator/logging'
26
+ require 'hub-clusters-creator/providers/bootstrap'
27
+ require 'hub-clusters-creator/providers/gke/helpers'
28
+
29
+ # rubocop:disable Metrics/ClassLength,Metrics/LineLength,Metrics/MethodLength
30
+ module HubClustersCreator
31
+ module Providers
32
+ # GKE provides the GKE implmentation
33
+ class GKE
34
+ DEFAULT_PSP_CLUSTER_ROLE = <<~YAML
35
+ apiVersion: rbac.authorization.k8s.io/v1
36
+ kind: ClusterRole
37
+ metadata:
38
+ name: default:psp
39
+ rules:
40
+ - apiGroups:
41
+ - policy
42
+ resourceNames:
43
+ - gce.unprivileged-addon
44
+ resources:
45
+ - podsecuritypolicies
46
+ verbs:
47
+ - use
48
+ YAML
49
+
50
+ DEFAULT_PSP_CLUSTERROLE_BINDING = <<~YAML
51
+ apiVersion: rbac.authorization.k8s.io/v1
52
+ kind: ClusterRoleBinding
53
+ metadata:
54
+ name: default:psp
55
+ roleRef:
56
+ apiGroup: rbac.authorization.k8s.io
57
+ kind: ClusterRole
58
+ name: default:psp
59
+ subjects:
60
+ - apiGroup: rbac.authorization.k8s.io
61
+ kind: Group
62
+ name: system:authenticated
63
+ - apiGroup: rbac.authorization.k8s.io
64
+ kind: Group
65
+ name: system:serviceaccounts
66
+ YAML
67
+
68
+ # Compute are a collection of methods used to interact with GCP
69
+ include Errors
70
+ include GCP::Compute
71
+ include GCP::Containers
72
+ include Logging
73
+
74
+ Container = Google::Apis::ContainerV1beta1
75
+ Compute = Google::Apis::ComputeV1
76
+ Dns = Google::Apis::DnsV1
77
+
78
+ def initialize(provider)
79
+ @account = provider[:account]
80
+ @project = provider[:project]
81
+ @region = provider[:region]
82
+ @compute = Compute::ComputeService.new
83
+ @gke = Container::ContainerService.new
84
+ @dns = Dns::DnsService.new
85
+ @client = nil
86
+
87
+ @compute.authorization = authorize
88
+ @gke.authorization = authorize
89
+ @dns.authorization = authorize
90
+ end
91
+
92
+ # create is responsible for building the infrastructure
93
+ def create(name, config)
94
+ # @step: validate the configuration
95
+ begin
96
+ validate(config)
97
+ rescue StandardError => e
98
+ raise ConfigurationError, "invalid configuration, error: #{e}"
99
+ end
100
+
101
+ # @step: provision the infrastructure
102
+ begin
103
+ provision_gke(name, config)
104
+ rescue StandardError => e
105
+ raise InfrastructureError, "failed to provision cluster: '#{name}', error: #{e}"
106
+ end
107
+
108
+ # @step: initialize the cluster
109
+ begin
110
+ c = provision_cluster(name, config)
111
+ rescue StandardError => e
112
+ raise InitializerError, "failed to initialize the cluster: '#{name}', error: #{e}"
113
+ end
114
+
115
+ {
116
+ cluster: {
117
+ ca: c.master_auth.cluster_ca_certificate,
118
+ endpoint: "https://#{c.endpoint}",
119
+ token: @client.account('sysadmin')
120
+ },
121
+ config: config,
122
+ services: {
123
+ grafana: {
124
+ hostname: config[:grafana_hostname]
125
+ }
126
+ }
127
+ }
128
+ end
129
+
130
+ # destroy is used to kill off a cluster
131
+ def destroy(name)
132
+ @gke.delete_project_location_cluster("projects/#{@project}/locations/#{@region}/clusters/#{name}")
133
+ end
134
+
135
+ private
136
+
137
+ # provision_gke is responsible for provisioning the infrastucture
138
+ # rubocop:disable Metrics/AbcSize
139
+ def provision_gke(name, config)
140
+ info "checking if the gke cluster: '#{name}' exists"
141
+ if cluster?(name)
142
+ info "skipping the creation of cluster: '#{name}' as it already exists"
143
+ else
144
+ info "cluster: '#{name}' does not exist, creating now"
145
+ path = "projects/#{@project}/locations/#{@region}"
146
+ operation = @gke.create_project_location_cluster(path, cluster_spec(config))
147
+
148
+ info "waiting for the cluster: '#{name}' to be created, operation: '#{operation.name}'"
149
+ status = hold_for_operation(operation.name)
150
+ unless status.status_message.nil?
151
+ raise InfrastructureError, "operation: '#{x.operation_type}' failed, error: #{x.status_message}"
152
+ end
153
+ end
154
+ gke = cluster(name)
155
+
156
+ # @step: create a cloud-nat device if private networking enabled
157
+ # and nothing exists already
158
+ if config[:enable_private_network]
159
+ info 'checking if cloud-nat device has been created'
160
+ router('router') do |x|
161
+ unless x.nats
162
+ x.nats = default_cloud_nat('cloud-nat')
163
+ patch_router('router', x)
164
+ end
165
+ end
166
+ end
167
+
168
+ info "provisioning a dns entry for the master api = > #{gke.endpoint}"
169
+ # dns(kubeapi_name(config).to_s, gke.endpoint, config[:domain])
170
+ end
171
+ # rubocop:enable Metrics/AbcSize
172
+
173
+ # provision_cluster is responsible for kickstarting the cluster
174
+ # rubocop:disable Metrics/AbcSize
175
+ def provision_cluster(name, config)
176
+ info "waiting for the master api endpoint to be available on cluster: #{name}"
177
+ thing = cluster(name)
178
+ @client = HubClustersCreator::Kube.new(thing.endpoint, token: authorize.access_token)
179
+ @client.wait_for_kubeapi
180
+
181
+ # @step: if psp is enabled we need to add the roles and bindings
182
+ info 'creating the default psp binding to unpriviledged policy'
183
+ @client.kubectl(DEFAULT_PSP_CLUSTER_ROLE)
184
+ @client.kubectl(DEFAULT_PSP_CLUSTERROLE_BINDING)
185
+
186
+ # @step: bootstrap the cluster and wait
187
+ HubClustersCreator::Providers::Bootstrap.new(name, @client, config).bootstrap
188
+
189
+ ingress = @client.get('loki-grafana', 'loki', 'ingresses', version: 'extensions/v1beta1')
190
+ address = ingress.status.loadBalancer.ingress.first.ip
191
+
192
+ # @step: update the dns record for the ingress
193
+ unless (config[:grafana_hostname] || '').empty?
194
+ info "adding a dns record for #{config[:grafana_hostname]} => #{address}"
195
+ dns(config[:grafana_hostname].split('.').first, address, config[:domain])
196
+ end
197
+
198
+ cluster(name)
199
+ end
200
+ # rubocop:enable Metrics/AbcSize
201
+
202
+ # validate is responsible for validating the options for cluster creation
203
+ # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
204
+ def validate(config)
205
+ raise ConfigurationError, "domain: #{config[:domain]} does not exist within project" unless domain?(config[:domain])
206
+ raise ConfigurationError, 'disk size must be positive' unless config[:disk_size_gb].positive?
207
+ raise ConfigurationError, 'size must be positive' unless config[:size].positive?
208
+
209
+ # @check the networking options
210
+ raise ConfigurationError, 'the network does not exist' unless network?(config[:network])
211
+ raise ConfigurationError, 'the subnetwork does not exist' unless subnet?(config[:subnetwork], config[:network]) && !config[:create_subnetwork]
212
+
213
+ # @check if subnets exist - need to do something more clever
214
+ # and check for overlapping subnety really but i can't find a gem
215
+ network_checks = []
216
+ network_checks.push(config['cluster_ipv4_cidr']) if config['cluster_ipv4_cidr']
217
+ network_checks.push(config['master_ipv4_cidr_block']) if config['master_ipv4_cidr_block']
218
+ network_checks.push(config['services_ipv4_cidr']) if config['services_ipv4_cidr']
219
+
220
+ nets = networks
221
+ network_checks.each do |n|
222
+ nets.each { |x| raise ConfigurationError, "network: #{n} already exists" if n == x.cidr }
223
+ end
224
+
225
+ if config[:enable_private_network] && !config[:master_ipv4_cidr_block]
226
+ raise ConfigurationError, 'you must specify a master_ipv4_cidr_block'
227
+ end
228
+
229
+ config
230
+ end
231
+ # rubocop:enable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
232
+
233
+ # patch_router is a wrapper for the patch router
234
+ def patch_router(name, router)
235
+ @compute.patch_router(@project, @region, name, router)
236
+ end
237
+
238
+ # default_cloud_nat returns a default cloud nat configuration
239
+ def default_cloud_nat(name = 'cloud-nat')
240
+ [
241
+ Google::Apis::ComputeV1::RouterNat.new(
242
+ log_config: Google::Apis::ComputeV1::RouterNatLogConfig.new(enable: false, filter: 'ALL'),
243
+ name: name,
244
+ nat_ip_allocate_option: 'AUTO_ONLY',
245
+ source_subnetwork_ip_ranges_to_nat: 'ALL_SUBNETWORKS_ALL_IP_RANGES'
246
+ )
247
+ ]
248
+ end
249
+
250
+ # authorize is responsible for providing an access token to operate
251
+ def authorize(scopes = ['https://www.googleapis.com/auth/cloud-platform'])
252
+ if @authorizer.nil?
253
+ @authorizer = Google::Auth::ServiceAccountCredentials.make_creds(
254
+ json_key_io: StringIO.new(@account),
255
+ scope: scopes
256
+ )
257
+ @authorizer.fetch_access_token!
258
+ end
259
+ @authorizer
260
+ end
261
+ end
262
+ end
263
+ end
264
+ # rubocop:enable Metrics/ClassLength,Metrics/LineLength,Metrics/MethodLength
@@ -0,0 +1,364 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (C) 2019 Rohith Jayawardene <gambol99@gmail.com>
4
+ #
5
+ # This program is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU General Public License
7
+ # as published by the Free Software Foundation; either version 2
8
+ # of the License, or (at your option) any later version.
9
+ #
10
+ # This program is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
17
+ #
18
+
19
+ # rubocop:disable Metrics/LineLength,Metrics/MethodLength,Metrics/ModuleLength
20
+ module HubClustersCreator
21
+ module Providers
22
+ # GCP is the namespace
23
+ module GCP
24
+ # Containers is a GKE container methods
25
+ module Containers
26
+ private
27
+
28
+ # gke_locations returns a list of compute locations
29
+ def gke_locations
30
+ @gke.list_project_locations("projects/#{@project}").locations.select do |x|
31
+ x.name.start_with?("#{@region}-")
32
+ end.map(&:name)
33
+ end
34
+
35
+ # operation returns the current status of an operation
36
+ def operation(id)
37
+ @gke.get_project_location_operation("projects/#{@project}/locations/#{@region}/operations/*", operation_id: id)
38
+ end
39
+
40
+ # operations returns a list of all operations
41
+ def operations
42
+ list = @gke.list_project_location_operations("projects/#{@project}/locations/#{@region}").operations
43
+ list.each { |x| yield x } if block_given?
44
+ list
45
+ end
46
+
47
+ # operations_by_resource returns any operations filtered by the resource
48
+ def operations_by_resource(name, resource, operation_type = '')
49
+ operations.select do |x|
50
+ next unless x.target_link.end_with?("#{resource}/#{name}")
51
+ next if !operation_type.empty? && (!x.operation_type == operation_type)
52
+
53
+ true
54
+ end
55
+ end
56
+
57
+ # hold_for_operation is responisble for waiting for an operation to complete or error
58
+ # rubocop:disable Lint/RescueException
59
+ def hold_for_operation(id, interval = 10, timeout = 900)
60
+ max_attempts = timeout / interval
61
+ retries = attempts = 0
62
+
63
+ while attempts < max_attempts
64
+ begin
65
+ resp = operation(id)
66
+ return resp if !resp.nil? && resp.status == 'DONE'
67
+ rescue Exception => e
68
+ raise Exception, "failed waiting on operation: #{id}, error: #{e}" if retries > 10
69
+
70
+ retries += 1
71
+ end
72
+ sleep(interval)
73
+ attempts += 1
74
+ end
75
+
76
+ raise Exception, "operation: #{id} has timed out waiting to finish"
77
+ end
78
+ # rubocop:enable Lint/RescueException
79
+
80
+ # cluster returns a specific cluster
81
+ def cluster(name)
82
+ return nil unless cluster?(name)
83
+
84
+ clusters.select { |x| x.name = name }.first
85
+ end
86
+
87
+ # cluster? check if a gke cluster exists
88
+ def cluster?(name)
89
+ clusters.map(&:name).include?(name)
90
+ end
91
+
92
+ # clusters returns a list of clusters
93
+ def clusters
94
+ path = "projects/#{@project}/locations/#{@region}"
95
+ list = @gke.list_zone_clusters(nil, nil, parent: path).clusters || []
96
+ list.each { |x| yield x } if block_given?
97
+ list
98
+ end
99
+
100
+ # cluster_spec is responsible for generating a cluster specification from options
101
+ # rubocop:disable Metrics/AbcSize
102
+ def cluster_spec(options)
103
+ locations = gke_locations
104
+
105
+ request = Google::Apis::ContainerV1beta1::CreateClusterRequest.new(
106
+ parent: "projects/#{@project}/locations/#{@region}",
107
+ project_id: @project
108
+ )
109
+ request.cluster = Google::Apis::ContainerV1beta1::Cluster.new(
110
+ name: options[:name],
111
+ description: options[:description],
112
+ initial_cluster_version: options[:version],
113
+
114
+ #
115
+ ## Addons
116
+ #
117
+ addons_config: Google::Apis::ContainerV1beta1::AddonsConfig.new(
118
+ cloud_run_config: Google::Apis::ContainerV1beta1::CloudRunConfig.new(
119
+ disabled: !options[:enable_cloud_run]
120
+ ),
121
+ horizontal_pod_autoscaling: Google::Apis::ContainerV1beta1::HorizontalPodAutoscaling.new(
122
+ disabled: !options[:enable_horizontal_pod_autoscaler]
123
+ ),
124
+ http_load_balancing: Google::Apis::ContainerV1beta1::HttpLoadBalancing.new(
125
+ disabled: !options[:enable_http_loadbalancer]
126
+ ),
127
+ istio_config: Google::Apis::ContainerV1beta1::IstioConfig.new(
128
+ auth: 'AUTH_MUTUAL_TLS',
129
+ disabled: !options[:enable_istio]
130
+ ),
131
+ kubernetes_dashboard: Google::Apis::ContainerV1beta1::KubernetesDashboard.new(
132
+ disabled: true
133
+ ),
134
+ network_policy_config: Google::Apis::ContainerV1beta1::NetworkPolicyConfig.new(
135
+ disabled: false
136
+ )
137
+ ),
138
+
139
+ maintenance_policy: Google::Apis::ContainerV1beta1::MaintenancePolicy.new(
140
+ window: Google::Apis::ContainerV1beta1::MaintenanceWindow.new(
141
+ daily_maintenance_window: Google::Apis::ContainerV1beta1::DailyMaintenanceWindow.new(
142
+ start_time: options[:maintenance_window]
143
+ )
144
+ )
145
+ ),
146
+
147
+ #
148
+ ## Authentication
149
+ #
150
+ master_auth: Google::Apis::ContainerV1beta1::MasterAuth.new(
151
+ client_certificate_config: Google::Apis::ContainerV1beta1::ClientCertificateConfig.new(
152
+ issue_client_certificate: false
153
+ )
154
+ ),
155
+
156
+ #
157
+ ## Network
158
+ #
159
+ ip_allocation_policy: Google::Apis::ContainerV1beta1::IpAllocationPolicy.new(
160
+ cluster_ipv4_cidr_block: options[:cluster_ipv4_cidr],
161
+ create_subnetwork: options[:create_subnetwork],
162
+ services_ipv4_cidr_block: options[:services_ipv4_cidr],
163
+ subnetwork_name: options[:subnetwork],
164
+ use_ip_aliases: true
165
+ ),
166
+ locations: locations,
167
+
168
+ #
169
+ ## Features
170
+ #
171
+ monitoring_service: ('monitoring.googleapis.com/kubernetes' if options[:enable_monitoring]),
172
+ logging_service: ('logging.googleapis.com/kubernetes' if options[:enable_logging]),
173
+
174
+ binary_authorization: Google::Apis::ContainerV1beta1::BinaryAuthorization.new(
175
+ enabled: options[:enable_binary_authorization]
176
+ ),
177
+ legacy_abac: Google::Apis::ContainerV1beta1::LegacyAbac.new(
178
+ enabled: false
179
+ ),
180
+ network_policy: Google::Apis::ContainerV1beta1::NetworkPolicy.new(
181
+ enabled: options[:enable_network_policies]
182
+ ),
183
+ pod_security_policy_config: Google::Apis::ContainerV1beta1::PodSecurityPolicyConfig.new(
184
+ enabled: options[:enable_pod_security_policies]
185
+ ),
186
+
187
+ #
188
+ ## Node Pools
189
+ #
190
+ node_pools: [
191
+ Google::Apis::ContainerV1beta1::NodePool.new(
192
+ autoscaling: Google::Apis::ContainerV1beta1::NodePoolAutoscaling.new(
193
+ autoprovisioned: false,
194
+ enabled: options[:enable_autoscaler],
195
+ max_node_count: options[:max_size],
196
+ min_node_count: options[:size]
197
+ ),
198
+ config: Google::Apis::ContainerV1beta1::NodeConfig.new(
199
+ disk_size_gb: options[:disk_size_gb],
200
+ image_type: options[:image_type],
201
+ machine_type: options[:machine_type],
202
+ oauth_scopes: [
203
+ 'https://www.googleapis.com/auth/compute',
204
+ 'https://www.googleapis.com/auth/devstorage.read_only',
205
+ 'https://www.googleapis.com/auth/logging.write',
206
+ 'https://www.googleapis.com/auth/monitoring'
207
+ ],
208
+ preemptible: options[:preemptible]
209
+ ),
210
+ initial_node_count: options[:size],
211
+ locations: locations,
212
+ management: Google::Apis::ContainerV1beta1::NodeManagement.new(
213
+ auto_repair: options[:enable_autorepair],
214
+ auto_upgrade: options[:enable_autoupgrade]
215
+ ),
216
+ max_pods_constraint: Google::Apis::ContainerV1beta1::MaxPodsConstraint.new(
217
+ max_pods_per_node: 110
218
+ ),
219
+ name: 'compute',
220
+ version: options[:version]
221
+ )
222
+ ]
223
+ )
224
+
225
+ if options[:enable_private_network]
226
+ request.cluster.private_cluster = true
227
+ request.cluster.private_cluster_config = Google::Apis::ContainerV1beta1::PrivateClusterConfig.new(
228
+ enable_private_endpoint: options[:enable_private_endpoint],
229
+ enable_private_nodes: true,
230
+ master_ipv4_cidr_block: options[:master_ipv4_cidr_block]
231
+ )
232
+
233
+ # @step: do we have any authorized cidr's
234
+ if options[:authorized_master_cidrs].size.positive?
235
+ request.cluster.master_authorized_networks_config = Google::Apis::ContainerV1beta1::MasterAuthorizedNetworksConfig.new(
236
+ cidr_blocks: [],
237
+ enabled: true
238
+ )
239
+ options[:authorized_master_cidrs].each do |x|
240
+ block = Google::Apis::ContainerV1beta1::CidrBlock.new(
241
+ cidr_block: x[:cidr],
242
+ display_name: x[:name]
243
+ )
244
+
245
+ request.cluster.master_authorized_networks_config.cidr_blocks.push(block)
246
+ end
247
+ end
248
+ end
249
+ request
250
+ end
251
+ # rubocop:enable Metrics/AbcSize
252
+ end
253
+ end
254
+ end
255
+ # rubocop:enable Metrics/LineLength,Metrics/MethodLength
256
+ end
257
+
258
+ # rubocop:disable Metrics/LineLength,Metrics/MethodLength
259
+ module HubClustersCreator
260
+ module Providers
261
+ # GCP namespaces the GCP methods
262
+ module GCP
263
+ # Compute provides some helper methods / functions to the GCP agent
264
+ module Compute
265
+ # router returns a specfic router
266
+ def router(name)
267
+ r = routers.select { |x| x.name == name }.first
268
+ yield r if block_given?
269
+ r
270
+ end
271
+
272
+ # router? check if the router exists
273
+ def router?(name)
274
+ routers.map(&:name).include?(name)
275
+ end
276
+
277
+ # routers returns the list of routers
278
+ def routers
279
+ list = @compute.list_routers(@project, @region).items
280
+ list.each { |x| yield x } if block_given?
281
+ list
282
+ end
283
+
284
+ # network? checks if the network exists in the region and project
285
+ def network?(name)
286
+ networks.items.map(&:name).include?(name)
287
+ end
288
+
289
+ # networks returns a list of networks in the region and project
290
+ def networks
291
+ list = @compute.list_networks(@project)
292
+ list.each { |x| yield x } if block_given?
293
+ list
294
+ end
295
+
296
+ # subnet? checks if the subnet exists in the project, network and region
297
+ def subnet?(name, network)
298
+ subnets(network).include?(name)
299
+ end
300
+
301
+ # dns is responsible for adding / updating a dns record in a zone
302
+ def dns(src, dest, zone, record = 'A')
303
+ raise ArgumentError, "the managed zone: #{zone} does not exist" unless domain?(zone)
304
+
305
+ hostname = "#{src}.#{zone}."
306
+ change = Google::Apis::DnsV1::Change.new(
307
+ additions: [
308
+ Google::Apis::DnsV1::ResourceRecordSet.new(
309
+ kind: 'dns#resourceRecordSet',
310
+ name: hostname,
311
+ rrdatas: [dest],
312
+ ttl: 120,
313
+ type: record
314
+ )
315
+ ]
316
+ )
317
+
318
+ # @step: check a record already exists and if so add for deletion
319
+ dns_records(zone).rrsets.each do |x|
320
+ next unless x.name == hostname
321
+
322
+ change.deletions = [x]
323
+ end
324
+
325
+ managed_zone = domain(zone)
326
+ @dns.create_change(@project, managed_zone.name, change)
327
+ end
328
+
329
+ # dns_records returns a list of dns recordsets
330
+ def dns_records(zone)
331
+ raise ArgumentError, "the managed zone: #{zone} does not exist" unless domain?(zone)
332
+
333
+ managed_zone = domain(zone)
334
+ @dns.list_resource_record_sets(@project, managed_zone.name)
335
+ end
336
+
337
+ # domain? checks if the domain exists
338
+ def domain?(name)
339
+ domains.map { |x| x.dns_name.chomp('.') }.include?(name)
340
+ end
341
+
342
+ # domain returns a specific domain
343
+ def domain(name)
344
+ domains.select { |x| x.dns_name.chomp('.') == name }.first
345
+ end
346
+
347
+ # domains provides a list of domains
348
+ def domains
349
+ @dns.list_managed_zones(@project).managed_zones
350
+ end
351
+
352
+ # subnets returns a list of subnets in the network
353
+ def subnets(network)
354
+ list = @compute.list_subnetworks(@project, @region).items.select do |x|
355
+ x.network.end_with?(network)
356
+ end.map(&:name)
357
+ list.each { |x| yield x } if block_given?
358
+ list
359
+ end
360
+ end
361
+ end
362
+ end
363
+ # rubocop:enable Metrics/LineLength,Metrics/MethodLength,Metrics/ModuleLength
364
+ end