gclouder 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +104 -0
- data/bin/gclouder +7 -0
- data/lib/gclouder/config/arguments.rb +35 -0
- data/lib/gclouder/config/cli_args.rb +77 -0
- data/lib/gclouder/config/cluster.rb +66 -0
- data/lib/gclouder/config/defaults.rb +35 -0
- data/lib/gclouder/config/files/project.rb +24 -0
- data/lib/gclouder/config/project.rb +34 -0
- data/lib/gclouder/config/resource_representations.rb +31 -0
- data/lib/gclouder/config_loader.rb +25 -0
- data/lib/gclouder/config_section.rb +26 -0
- data/lib/gclouder/dependencies.rb +11 -0
- data/lib/gclouder/gcloud.rb +39 -0
- data/lib/gclouder/gsutil.rb +25 -0
- data/lib/gclouder/header.rb +9 -0
- data/lib/gclouder/helpers.rb +77 -0
- data/lib/gclouder/logging.rb +181 -0
- data/lib/gclouder/mappings/argument.rb +31 -0
- data/lib/gclouder/mappings/file.rb +31 -0
- data/lib/gclouder/mappings/property.rb +31 -0
- data/lib/gclouder/mappings/resource_representation.rb +31 -0
- data/lib/gclouder/monkey_patches/array.rb +19 -0
- data/lib/gclouder/monkey_patches/boolean.rb +12 -0
- data/lib/gclouder/monkey_patches/hash.rb +44 -0
- data/lib/gclouder/monkey_patches/ipaddr.rb +10 -0
- data/lib/gclouder/monkey_patches/string.rb +30 -0
- data/lib/gclouder/resource.rb +63 -0
- data/lib/gclouder/resource_cleaner.rb +58 -0
- data/lib/gclouder/resources/compute/addresses.rb +108 -0
- data/lib/gclouder/resources/compute/bgp-vpns.rb +220 -0
- data/lib/gclouder/resources/compute/disks.rb +99 -0
- data/lib/gclouder/resources/compute/firewall_rules.rb +82 -0
- data/lib/gclouder/resources/compute/instances.rb +147 -0
- data/lib/gclouder/resources/compute/networks/subnets.rb +104 -0
- data/lib/gclouder/resources/compute/networks.rb +110 -0
- data/lib/gclouder/resources/compute/project_info/ssh_keys.rb +171 -0
- data/lib/gclouder/resources/compute/routers.rb +83 -0
- data/lib/gclouder/resources/compute/vpns.rb +199 -0
- data/lib/gclouder/resources/container/clusters.rb +257 -0
- data/lib/gclouder/resources/container/node_pools.rb +193 -0
- data/lib/gclouder/resources/dns.rb +390 -0
- data/lib/gclouder/resources/logging/sinks.rb +98 -0
- data/lib/gclouder/resources/project/iam_policy_binding.rb +293 -0
- data/lib/gclouder/resources/project.rb +85 -0
- data/lib/gclouder/resources/project_id.rb +71 -0
- data/lib/gclouder/resources/pubsub/subscriptions.rb +100 -0
- data/lib/gclouder/resources/pubsub/topics.rb +95 -0
- data/lib/gclouder/resources/storagebuckets.rb +103 -0
- data/lib/gclouder/resources/validate/global.rb +27 -0
- data/lib/gclouder/resources/validate/local.rb +68 -0
- data/lib/gclouder/resources/validate/region.rb +28 -0
- data/lib/gclouder/resources/validate/remote.rb +78 -0
- data/lib/gclouder/resources.rb +148 -0
- data/lib/gclouder/shell.rb +71 -0
- data/lib/gclouder/version.rb +5 -0
- data/lib/gclouder.rb +278 -0
- metadata +102 -0
@@ -0,0 +1,257 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# NOTE
|
4
|
+
#
|
5
|
+
# table of equivalent commands for `gcloud container ...` commands..
|
6
|
+
#
|
7
|
+
# convert node-pool parameters to cluster create and cluster resize parameters..
|
8
|
+
#
|
9
|
+
# clusters create - clusters resize - nodepool create
|
10
|
+
#
|
11
|
+
# additional-zones - -
|
12
|
+
# async - async -
|
13
|
+
# cluster-ipv4-cidr - -
|
14
|
+
# disable-addons - -
|
15
|
+
# disk-size - - disk-size
|
16
|
+
# no-enable-cloud-endpoints - - enable-cloud-endpoints
|
17
|
+
# no-enable-cloud-logging - -
|
18
|
+
# no-enable-cloud-monitoring - -
|
19
|
+
# image-type - - image-type
|
20
|
+
# machine-type - - machine-type
|
21
|
+
# max-nodes-per-pool - -
|
22
|
+
# network - -
|
23
|
+
# num-nodes - size - num-nodes
|
24
|
+
# password - -
|
25
|
+
# scopes - - scopes
|
26
|
+
# subnetwork - -
|
27
|
+
# username - -
|
28
|
+
# wait - wait -
|
29
|
+
# zone - zone - zone
|
30
|
+
# - - - cluster
|
31
|
+
# - - node-pool - -
|
32
|
+
#
|
33
|
+
|
34
|
+
module GClouder
|
35
|
+
module Resources
|
36
|
+
module Container
|
37
|
+
module Clusters
|
38
|
+
include GClouder::Logging
|
39
|
+
include GClouder::Config::Project
|
40
|
+
include GClouder::Resource::Cleaner
|
41
|
+
|
42
|
+
module Config
|
43
|
+
def self.merged(config)
|
44
|
+
return unless config.key?("regions")
|
45
|
+
config["regions"].each do |region, region_config|
|
46
|
+
next unless region_config.key?("clusters")
|
47
|
+
|
48
|
+
region_config["clusters"].each_with_index do |cluster, cluster_index|
|
49
|
+
cluster_config = config["regions"][region]["clusters"][cluster_index]
|
50
|
+
config["regions"][region]["clusters"][cluster_index] = GClouder::Config::Cluster.new(cluster_config)
|
51
|
+
|
52
|
+
config["regions"][region]["clusters"][cluster_index]["node_pools"].each_with_index do |pool, pool_index|
|
53
|
+
pool_config = config["regions"][region]["clusters"][cluster_index]["node_pools"][pool_index]
|
54
|
+
config["regions"][region]["clusters"][cluster_index]["node_pools"][pool_index] = GClouder::Config::Cluster.new(pool_config)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
config
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.header(stage = :ensure)
|
64
|
+
info "[#{stage}] container / clusters", title: true
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.validate
|
68
|
+
return if Local.list.empty?
|
69
|
+
header :validate
|
70
|
+
Local.validate
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.ensure
|
74
|
+
return if Local.list.empty?
|
75
|
+
header
|
76
|
+
|
77
|
+
Local.list.each do |region, region_config|
|
78
|
+
info region, heading: true, indent: 2
|
79
|
+
region_config.each do |cluster|
|
80
|
+
info
|
81
|
+
Cluster.build(region, cluster)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
module Local
|
87
|
+
def self.list
|
88
|
+
Resources::Region.instances(path: section)
|
89
|
+
end
|
90
|
+
|
91
|
+
def self.section
|
92
|
+
["clusters"]
|
93
|
+
end
|
94
|
+
|
95
|
+
def self.validate
|
96
|
+
Resources::Validate::Region.instances(
|
97
|
+
list,
|
98
|
+
required_keys: GClouder::Config::Arguments.required(["container", "clusters"]),
|
99
|
+
permitted_keys: GClouder::Config::Arguments.permitted(["container", "clusters"]),
|
100
|
+
# FIXME: zone has wrong type in assets arguments file
|
101
|
+
# FIXME: num_nodes has wrong type in assets arguments file
|
102
|
+
ignore_keys: %w(node_pools zone num_nodes),
|
103
|
+
)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
module Remote
|
108
|
+
def self.list
|
109
|
+
Resources::Remote.instances(
|
110
|
+
path: %w(container clusters),
|
111
|
+
# FIXME: zone has wrong type in assets arguments file
|
112
|
+
# FIXME: num_nodes has wrong type in assets arguments file
|
113
|
+
ignore_keys: %w(node_pools zone num_nodes),
|
114
|
+
)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
module Cluster
|
119
|
+
include GClouder::Shell
|
120
|
+
include GClouder::Logging
|
121
|
+
include GClouder::Config::CLIArgs
|
122
|
+
include GClouder::Helpers
|
123
|
+
include GClouder::GCloud
|
124
|
+
|
125
|
+
def self.build(region, cluster)
|
126
|
+
unless cluster["zone"]
|
127
|
+
info "skipping cluster since no zone is set"
|
128
|
+
return
|
129
|
+
end
|
130
|
+
|
131
|
+
config = cluster.context(:create_cluster)
|
132
|
+
|
133
|
+
create(cluster["name"], config)
|
134
|
+
|
135
|
+
#check_immutable_conflicts(cluster)
|
136
|
+
end
|
137
|
+
|
138
|
+
#def self.check_immutable_conflicts(cluster)
|
139
|
+
# check_immutable_cluster_conflicts(cluster)
|
140
|
+
# check_immutable_nodepool_conflicts(cluster)
|
141
|
+
#end
|
142
|
+
|
143
|
+
#def self.check_immutable_cluster_conflicts(cluster)
|
144
|
+
# return unless Resource.resource?("container clusters", cluster["name"], silent: true)
|
145
|
+
|
146
|
+
# #info "checking cluster for immutable resource conflicts: #{cluster}"
|
147
|
+
|
148
|
+
# zone = cluster["zone"]
|
149
|
+
|
150
|
+
# remote_cluster_config = get_remote_cluster_config(cluster, zone)
|
151
|
+
|
152
|
+
# immutable_parameters = %w(cluster_ipv4_cidr current_node_count instance_group_urls locations
|
153
|
+
# node_pools services_ipv4_cidr username password name zone additional_zones network
|
154
|
+
# subnetwork node_config)
|
155
|
+
|
156
|
+
# cluster_config.each do |cluster_key, cluster_value|
|
157
|
+
# next if cluster_key == "node_pools"
|
158
|
+
|
159
|
+
# unless immutable_parameters.include?(cluster_key)
|
160
|
+
# #debug "skipping key since it isn't immutable: #{cluster_key}"
|
161
|
+
# next
|
162
|
+
# end
|
163
|
+
|
164
|
+
# if cluster_key == "additional_zones"
|
165
|
+
# remote_value = remote_cluster_config["locations"].sort
|
166
|
+
# cluster_value = [cluster_value + [cluster_config["zone"]]].flatten.sort
|
167
|
+
# else
|
168
|
+
# remote_value = remote_cluster_config[cluster_key.mixedcase]
|
169
|
+
# end
|
170
|
+
|
171
|
+
# check_values(cluster_key, remote_value, cluster_value)
|
172
|
+
# end
|
173
|
+
#end
|
174
|
+
|
175
|
+
#def self.check_immutable_nodepool_conflicts(cluster)
|
176
|
+
# intersection = nodepool_intersection(cluster)
|
177
|
+
|
178
|
+
# return if intersection.empty?
|
179
|
+
|
180
|
+
# #info "checking nodepools for immutable resource conflicts: #{intersection.join(", ")}"
|
181
|
+
|
182
|
+
# intersection.each do |pool|
|
183
|
+
# #debug "checking pool: #{pool}"
|
184
|
+
|
185
|
+
# zone = pool["zone"]
|
186
|
+
|
187
|
+
# pool.each do |pool_key, pool_value|
|
188
|
+
# immutable_parameters = %w(initial_node_count disk_size_gb service_account image_type machine_type scopes)
|
189
|
+
|
190
|
+
# # these keys are exposed through the pool resource but should be skipped..
|
191
|
+
# next if pool_key == "zone"
|
192
|
+
# next if pool_key == "additional_zones"
|
193
|
+
|
194
|
+
# unless immutable_parameters.include?(pool_key)
|
195
|
+
# #debug "skipping key since it isn't immutable: #{pool_key}"
|
196
|
+
# next
|
197
|
+
# end
|
198
|
+
|
199
|
+
# remote_nodepool_config = get_remote_nodepool_config(cluster["name"], pool["name"], zone)
|
200
|
+
|
201
|
+
# if pool_key == "scopes"
|
202
|
+
# check_values(pool_key, remote_nodepool_config["config"]["oauthScopes"], pool_value.sort)
|
203
|
+
# next
|
204
|
+
# end
|
205
|
+
|
206
|
+
# check_values(pool_key, remote_nodepool_config["config"][pool_key.mixedcase], pool_value)
|
207
|
+
# end
|
208
|
+
# end
|
209
|
+
#end
|
210
|
+
|
211
|
+
#def self.nodepool_intersection(cluster)
|
212
|
+
# zone = cluster["zone"]
|
213
|
+
# remote_nodepool_names(cluster["name"], zone) & local_nodepool_names(cluster)
|
214
|
+
#end
|
215
|
+
|
216
|
+
#def self.local_nodepool_names(cluster)
|
217
|
+
# cluster["node_pools"].map { |pool| pool["name"] }
|
218
|
+
#end
|
219
|
+
|
220
|
+
#def self.remote_nodepool_names(cluster_name, zone)
|
221
|
+
# gcloud("--format json container clusters describe #{cluster_name} --zone #{zone} | jq -r '.nodePools[].name'", force: true).split("\n")
|
222
|
+
#end
|
223
|
+
|
224
|
+
#def self.get_remote_nodepool_config(cluster_name, pool, zone)
|
225
|
+
# filter = "jq -r '.nodePools[] | select(.name == \"#{pool}\")'"
|
226
|
+
# gcloud("--format json container clusters describe #{cluster_name} --zone #{zone} | #{filter}", force: true)
|
227
|
+
#end
|
228
|
+
|
229
|
+
#def self.get_remote_cluster_config(cluster_name, zone)
|
230
|
+
# gcloud("--format json container clusters describe #{cluster_name} --zone #{zone}", force: true)
|
231
|
+
#end
|
232
|
+
|
233
|
+
#def self.check_values(key, remote_value, local_value)
|
234
|
+
# if remote_value != local_value
|
235
|
+
# fatal "error: remote config doesn't match local config: #{key} (#{remote_value} != #{local_value})"
|
236
|
+
# else
|
237
|
+
# #debug "local and remote keys have same value for param: #{key} = #{local_value}"
|
238
|
+
# true
|
239
|
+
# end
|
240
|
+
#end
|
241
|
+
|
242
|
+
def self.loop_until_cluster_exists(cluster_name)
|
243
|
+
until Resource.resource?("container clusters", cluster_name, silent: true)
|
244
|
+
sleep 1
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
def self.create(cluster_name, config)
|
249
|
+
args = hash_to_args(config)
|
250
|
+
Resource.ensure :"container clusters", cluster_name, args, indent: 3
|
251
|
+
loop_until_cluster_exists(cluster_name) if !cli_args[:dry_run]
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
@@ -0,0 +1,193 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
|
5
|
+
module GClouder
|
6
|
+
module Resources
|
7
|
+
module Container
|
8
|
+
module NodePools
|
9
|
+
include GClouder::Shell
|
10
|
+
include GClouder::Logging
|
11
|
+
include GClouder::GCloud
|
12
|
+
include GClouder::Helpers
|
13
|
+
|
14
|
+
def self.delete_default_nodepool
|
15
|
+
Resource.purge :"container node-pools", "default-pool"
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.validate
|
19
|
+
return if GClouder::Resources::Container::Clusters::Local.list.empty?
|
20
|
+
header :validate
|
21
|
+
|
22
|
+
GClouder::Resources::Container::Clusters::Local.list.each do |region, clusters|
|
23
|
+
info region, heading: true, indent: 2
|
24
|
+
clusters.each do |cluster|
|
25
|
+
next if cluster["node_pools"].empty?
|
26
|
+
info cluster["name"], heading: true, indent: 3
|
27
|
+
Local.validate(cluster)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.ensure
|
33
|
+
return if GClouder::Resources::Container::Clusters::Local.list.empty?
|
34
|
+
header
|
35
|
+
|
36
|
+
GClouder::Resources::Container::Clusters::Local.list.each do |region, clusters|
|
37
|
+
info region, heading: true, indent: 2
|
38
|
+
clusters.each do |cluster|
|
39
|
+
next if cluster["node_pools"].empty?
|
40
|
+
info cluster["name"], heading: true, indent: 3
|
41
|
+
cluster["node_pools"].each do |pool|
|
42
|
+
NodePool.create(cluster, pool)
|
43
|
+
NodePool.resize(cluster, pool) if Resource.resource?("container clusters", cluster["name"], silent: true)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.header(stage = :ensure)
|
50
|
+
info "[#{stage}] container / node-pools", title: true, indent: 1
|
51
|
+
end
|
52
|
+
|
53
|
+
# FIXME: create a collection then iterate through it to avoid printing
|
54
|
+
# messages when no clusters are undefined
|
55
|
+
def self.clean
|
56
|
+
return if GClouder::Resources::Container::Clusters::Local.list.empty?
|
57
|
+
header :clean
|
58
|
+
|
59
|
+
GClouder::Resources::Container::Clusters::Local.list.each do |region, clusters|
|
60
|
+
info region, heading: true, indent: 2
|
61
|
+
clusters.each do |cluster|
|
62
|
+
next if undefined(cluster).empty?
|
63
|
+
|
64
|
+
info cluster["name"], heading: true, indent: 3
|
65
|
+
undefined(cluster).each do |namespace, resources|
|
66
|
+
resources.each do |resource|
|
67
|
+
message = resource['name']
|
68
|
+
message += " (not defined locally)"
|
69
|
+
info
|
70
|
+
warning message, indent: 4
|
71
|
+
#resource_purge(namespace, user)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.undefined(cluster)
|
79
|
+
return {} unless Resource.resource?("container clusters", cluster["name"], silent: true)
|
80
|
+
self::Remote.list(cluster).each_with_object({}) do |(namespace, resources), collection|
|
81
|
+
resources.each do |resource|
|
82
|
+
namespace_resources = self::Local.list(cluster)[namespace]
|
83
|
+
|
84
|
+
next if namespace_resources && namespace_resources.select {|r| resource["name"] == r["name"] }.length > 0
|
85
|
+
|
86
|
+
collection[namespace] ||= []
|
87
|
+
collection[namespace] << resource
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
module Local
|
93
|
+
def self.list(cluster)
|
94
|
+
return {} unless cluster.key?("node_pools")
|
95
|
+
{ cluster["zone"].gsub(/-.$/, "") => cluster["node_pools"] }
|
96
|
+
end
|
97
|
+
|
98
|
+
def self.section
|
99
|
+
%w(container node-pools)
|
100
|
+
end
|
101
|
+
|
102
|
+
def self.validate(cluster)
|
103
|
+
Resources::Validate::Region.instances(
|
104
|
+
list(cluster),
|
105
|
+
required_keys: GClouder::Config::Arguments.required(section).merge({ "zone" => { "type" => "String", "required" => "true" }}),
|
106
|
+
permitted_keys: GClouder::Config::Arguments.permitted(section).merge({ "additional_zones" => { "type" => "Array", "required" => "false" } }),
|
107
|
+
# FIXME: num_nodes has wrong type in assets arguments file..
|
108
|
+
ignore_keys: ["size", "num_nodes"],
|
109
|
+
skip_region: true,
|
110
|
+
indent: 1,
|
111
|
+
)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
module Remote
|
116
|
+
def self.list(cluster)
|
117
|
+
Resources::Remote.instances(path: %w(container node-pools), args: "--cluster #{cluster['name']} --zone #{cluster['zone']}")
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
module NodePool
|
122
|
+
include GClouder::Shell
|
123
|
+
include GClouder::Logging
|
124
|
+
include GClouder::Config::CLIArgs
|
125
|
+
include GClouder::GCloud
|
126
|
+
include GClouder::Helpers
|
127
|
+
|
128
|
+
def self.create(cluster, pool)
|
129
|
+
parameters = hash_to_args(pool.context(:create_nodepool))
|
130
|
+
zone = pool["zone"]
|
131
|
+
|
132
|
+
if !check_exists?(cluster["name"], pool["name"], zone)
|
133
|
+
gcloud("alpha container node-pools create --cluster #{cluster["name"]} #{parameters} #{pool["name"]}")
|
134
|
+
if cli_args[:dry_run]
|
135
|
+
add pool["name"], indent: 4
|
136
|
+
else
|
137
|
+
sleep 1 until check_exists?(cluster["name"], pool["name"], zone)
|
138
|
+
end
|
139
|
+
else
|
140
|
+
good "#{pool["name"]}", indent: 4
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def self.resize(cluster, pool)
|
145
|
+
config = pool.context(:resize_cluster)
|
146
|
+
zone = pool["zone"]
|
147
|
+
|
148
|
+
if !check_exists?(cluster["name"], pool["name"], zone)
|
149
|
+
info "skipping resize for non-existant cluster/nodepool: #{cluster["name"]}/#{pool["name"]}", indent: 5
|
150
|
+
return
|
151
|
+
end
|
152
|
+
|
153
|
+
number_of_zones = calculate_number_of_zones(pool)
|
154
|
+
|
155
|
+
parameters = hash_to_args(config)
|
156
|
+
|
157
|
+
current_size = remote_size(cluster["name"], pool["name"], pool["zone"])
|
158
|
+
|
159
|
+
desired_size = if pool.key?("additional_zones")
|
160
|
+
config["size"] * number_of_zones
|
161
|
+
else
|
162
|
+
config["size"]
|
163
|
+
end
|
164
|
+
|
165
|
+
if desired_size != current_size
|
166
|
+
add "resizing pool (zones: #{number_of_zones}): #{current_size} -> #{desired_size}", indent: 5
|
167
|
+
gcloud("container clusters resize #{cluster["name"]} --node-pool #{pool["name"]} #{parameters}")
|
168
|
+
else
|
169
|
+
true
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def self.calculate_number_of_zones(pool)
|
174
|
+
pool.key?("additional_zones") ? (pool["additional_zones"].count + 1) : 1
|
175
|
+
end
|
176
|
+
|
177
|
+
def self.remote_size(cluster_name, pool_name, zone)
|
178
|
+
remote_pool_config = gcloud("--format json container clusters describe #{cluster_name} --zone #{zone} | jq --join-output -c '.nodePools[] | select(.name == \"#{pool_name}\")'", force: true)
|
179
|
+
instance_group_ids = remote_pool_config["instanceGroupUrls"].map { |url| data = url.split("/"); [ data[-1], data[-3] ] }.to_h
|
180
|
+
nodes = instance_group_ids.map do |instance_group_id, zone|
|
181
|
+
gcloud("--format json compute instance-groups describe --zone #{zone} #{instance_group_id} | jq '.size'", force: true).to_i
|
182
|
+
end
|
183
|
+
nodes.inject(0) { |sum, i| sum + i }
|
184
|
+
end
|
185
|
+
|
186
|
+
def self.check_exists?(cluster_name, nodepool, zone)
|
187
|
+
gcloud("--format json container node-pools list --zone #{zone} --cluster #{cluster_name} | jq 'select(.[].name == \"#{nodepool}\") | length'", force: true).to_i.nonzero?
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|