hetzner-k3s 0.4.8 → 0.5.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,11 +1,14 @@
1
- require "thor"
2
- require "http"
3
- require "sshkey"
1
+ # frozen_string_literal: true
2
+
3
+ require 'thor'
4
+ require 'http'
5
+ require 'sshkey'
4
6
  require 'ipaddr'
5
7
  require 'open-uri'
8
+ require 'yaml'
6
9
 
7
- require_relative "cluster"
8
- require_relative "version"
10
+ require_relative 'cluster'
11
+ require_relative 'version'
9
12
 
10
13
  module Hetzner
11
14
  module K3s
@@ -14,388 +17,364 @@ module Hetzner
14
17
  true
15
18
  end
16
19
 
17
- desc "version", "Print the version"
20
+ def initialize(*args)
21
+ @errors = []
22
+ @used_server_types = []
23
+
24
+ super
25
+ end
26
+
27
+ desc 'version', 'Print the version'
18
28
  def version
19
29
  puts Hetzner::K3s::VERSION
20
30
  end
21
31
 
22
- desc "create-cluster", "Create a k3s cluster in Hetzner Cloud"
32
+ desc 'create-cluster', 'Create a k3s cluster in Hetzner Cloud'
23
33
  option :config_file, required: true
24
-
25
34
  def create_cluster
26
- validate_config_file :create
27
-
28
- Cluster.new(hetzner_client: hetzner_client, hetzner_token: find_hetzner_token).create configuration: configuration
35
+ validate_configuration :create
36
+ Cluster.new(hetzner_client:, hetzner_token:).create configuration:
29
37
  end
30
38
 
31
- desc "delete-cluster", "Delete an existing k3s cluster in Hetzner Cloud"
39
+ desc 'delete-cluster', 'Delete an existing k3s cluster in Hetzner Cloud'
32
40
  option :config_file, required: true
33
-
34
41
  def delete_cluster
35
- validate_config_file :delete
36
- Cluster.new(hetzner_client: hetzner_client, hetzner_token: find_hetzner_token).delete configuration: configuration
42
+ validate_configuration :delete
43
+ Cluster.new(hetzner_client:, hetzner_token:).delete configuration:
37
44
  end
38
45
 
39
- desc "upgrade-cluster", "Upgrade an existing k3s cluster in Hetzner Cloud to a new version"
46
+ desc 'upgrade-cluster', 'Upgrade an existing k3s cluster in Hetzner Cloud to a new version'
40
47
  option :config_file, required: true
41
48
  option :new_k3s_version, required: true
42
- option :force, default: "false"
43
-
49
+ option :force, default: 'false'
44
50
  def upgrade_cluster
45
- validate_config_file :upgrade
46
- Cluster.new(hetzner_client: hetzner_client, hetzner_token: find_hetzner_token).upgrade configuration: configuration, new_k3s_version: options[:new_k3s_version], config_file: options[:config_file]
51
+ validate_configuration :upgrade
52
+
53
+ Cluster.new(hetzner_client:, hetzner_token:)
54
+ .upgrade(configuration:, new_k3s_version: options[:new_k3s_version], config_file: options[:config_file])
47
55
  end
48
56
 
49
- desc "releases", "List available k3s releases"
57
+ desc 'releases', 'List available k3s releases'
50
58
  def releases
51
- find_available_releases.each do |release|
59
+ available_releases.each do |release|
52
60
  puts release
53
61
  end
54
62
  end
55
63
 
56
64
  private
57
65
 
58
- attr_reader :configuration, :hetzner_client, :k3s_version
59
- attr_accessor :errors, :used_server_types
66
+ attr_reader :configuration, :hetzner_client, :k3s_version
67
+ attr_accessor :errors, :used_server_types
68
+
69
+ def validate_configuration(action)
70
+ validate_configuration_file
71
+ validate_token
72
+ validate_cluster_name
73
+ validate_kubeconfig_path
74
+
75
+ case action
76
+ when :create
77
+ validate_create
78
+ when :delete
79
+ validate_kubeconfig_path_must_exist
80
+ when :upgrade
81
+ validate_upgrade
82
+ end
60
83
 
61
- def validate_config_file(action)
62
- config_file_path = options[:config_file]
84
+ errors.flatten!
63
85
 
64
- if File.exists?(config_file_path)
65
- begin
66
- @configuration = YAML.load_file(options[:config_file])
67
- raise "invalid" unless configuration.is_a? Hash
68
- rescue
69
- puts "Please ensure that the config file is a correct YAML manifest."
70
- return
71
- end
72
- else
73
- puts "Please specify a correct path for the config file."
74
- return
75
- end
86
+ return if errors.empty?
76
87
 
77
- @errors = []
78
- @used_server_types = []
79
-
80
- validate_token
81
- validate_cluster_name
82
- validate_kubeconfig_path
83
-
84
- case action
85
- when :create
86
- validate_public_ssh_key
87
- validate_private_ssh_key
88
- validate_ssh_allowed_networks
89
- validate_location
90
- validate_k3s_version
91
- validate_masters
92
- validate_worker_node_pools
93
- validate_verify_host_key
94
- when :delete
95
- validate_kubeconfig_path_must_exist
96
- when :upgrade
97
- validate_kubeconfig_path_must_exist
98
- validate_new_k3s_version
99
- validate_new_k3s_version_must_be_more_recent
100
- end
88
+ puts 'Some information in the configuration file requires your attention:'
101
89
 
102
- errors.flatten!
90
+ errors.each do |error|
91
+ puts " - #{error}"
92
+ end
103
93
 
104
- unless errors.empty?
105
- puts "Some information in the configuration file requires your attention:"
106
- errors.each do |error|
107
- puts " - #{error}"
108
- end
94
+ exit 1
95
+ end
109
96
 
110
- exit 1
111
- end
97
+ def valid_token?
98
+ return @valid unless @valid.nil?
99
+
100
+ begin
101
+ token = hetzner_token
102
+ @hetzner_client = Hetzner::Client.new(token:)
103
+ response = hetzner_client.get('/locations')
104
+ error_code = response.dig('error', 'code')
105
+ @valid = error_code&.size != 0
106
+ rescue StandardError
107
+ @valid = false
112
108
  end
109
+ end
113
110
 
114
- def valid_token?
115
- return @valid unless @valid.nil?
111
+ def validate_token
112
+ errors << 'Invalid Hetzner Cloud token' unless valid_token?
113
+ end
116
114
 
117
- begin
118
- token = find_hetzner_token
119
- @hetzner_client = Hetzner::Client.new(token: token)
120
- response = hetzner_client.get("/locations")
121
- error_code = response.dig("error", "code")
122
- @valid = if error_code and error_code.size > 0
123
- false
124
- else
125
- true
126
- end
127
- rescue
128
- @valid = false
129
- end
130
- end
115
+ def validate_cluster_name
116
+ errors << 'Cluster name is an invalid format (only lowercase letters, digits and dashes are allowed)' unless configuration['cluster_name'] =~ /\A[a-z\d-]+\z/
131
117
 
132
- def validate_token
133
- errors << "Invalid Hetzner Cloud token" unless valid_token?
134
- end
118
+ return if configuration['cluster_name'] =~ /\A[a-z]+.*\z/
135
119
 
136
- def validate_cluster_name
137
- errors << "Cluster name is an invalid format (only lowercase letters, digits and dashes are allowed)" unless configuration["cluster_name"] =~ /\A[a-z\d-]+\z/
138
- errors << "Ensure that the cluster name starts with a normal letter" unless configuration["cluster_name"] =~ /\A[a-z]+.*\z/
139
- end
120
+ errors << 'Ensure that the cluster name starts with a normal letter'
121
+ end
140
122
 
141
- def validate_kubeconfig_path
142
- path = File.expand_path(configuration.dig("kubeconfig_path"))
143
- errors << "kubeconfig path cannot be a directory" and return if File.directory? path
123
+ def validate_kubeconfig_path
124
+ path = File.expand_path(configuration['kubeconfig_path'])
125
+ errors << 'kubeconfig path cannot be a directory' and return if File.directory? path
144
126
 
145
- directory = File.dirname(path)
146
- errors << "Directory #{directory} doesn't exist" unless File.exists? directory
147
- rescue
148
- errors << "Invalid path for the kubeconfig"
149
- end
127
+ directory = File.dirname(path)
128
+ errors << "Directory #{directory} doesn't exist" unless File.exist? directory
129
+ rescue StandardError
130
+ errors << 'Invalid path for the kubeconfig'
131
+ end
150
132
 
151
- def validate_public_ssh_key
152
- path = File.expand_path(configuration.dig("public_ssh_key_path"))
153
- errors << "Invalid Public SSH key path" and return unless File.exists? path
133
+ def validate_public_ssh_key
134
+ path = File.expand_path(configuration['public_ssh_key_path'])
135
+ errors << 'Invalid Public SSH key path' and return unless File.exist? path
154
136
 
155
- key = File.read(path)
156
- errors << "Public SSH key is invalid" unless ::SSHKey.valid_ssh_public_key?(key)
157
- rescue
158
- errors << "Invalid Public SSH key path"
159
- end
137
+ key = File.read(path)
138
+ errors << 'Public SSH key is invalid' unless ::SSHKey.valid_ssh_public_key?(key)
139
+ rescue StandardError
140
+ errors << 'Invalid Public SSH key path'
141
+ end
160
142
 
161
- def validate_private_ssh_key
162
- return unless (private_ssh_key_path = configuration.dig("private_ssh_key_path"))
143
+ def validate_private_ssh_key
144
+ private_ssh_key_path = configuration['private_ssh_key_path']
163
145
 
164
- path = File.expand_path(private_ssh_key_path)
165
- errors << "Invalid Private SSH key path" and return unless File.exists?(path)
166
- rescue
167
- errors << "Invalid Private SSH key path"
168
- end
146
+ return unless private_ssh_key_path
169
147
 
170
- def validate_kubeconfig_path_must_exist
171
- path = File.expand_path configuration.dig("kubeconfig_path")
172
- errors << "kubeconfig path is invalid" and return unless File.exists? path
173
- errors << "kubeconfig path cannot be a directory" if File.directory? path
174
- rescue
175
- errors << "Invalid kubeconfig path"
176
- end
148
+ path = File.expand_path(private_ssh_key_path)
149
+ errors << 'Invalid Private SSH key path' and return unless File.exist?(path)
150
+ rescue StandardError
151
+ errors << 'Invalid Private SSH key path'
152
+ end
177
153
 
178
- def server_types
179
- return [] unless valid_token?
180
- @server_types ||= hetzner_client.get("/server_types")["server_types"].map{ |server_type| server_type["name"] }
181
- rescue
182
- @errors << "Cannot fetch server types with Hetzner API, please try again later"
183
- false
184
- end
154
+ def validate_kubeconfig_path_must_exist
155
+ path = File.expand_path configuration['kubeconfig_path']
156
+ errors << 'kubeconfig path is invalid' and return unless File.exist? path
185
157
 
186
- def locations
187
- return [] unless valid_token?
188
- @locations ||= hetzner_client.get("/locations")["locations"].map{ |location| location["name"] }
189
- rescue
190
- @errors << "Cannot fetch locations with Hetzner API, please try again later"
191
- []
192
- end
158
+ errors << 'kubeconfig path cannot be a directory' if File.directory? path
159
+ rescue StandardError
160
+ errors << 'Invalid kubeconfig path'
161
+ end
193
162
 
194
- def validate_location
195
- return if locations.empty? && !valid_token?
196
- errors << "Invalid location - available locations: nbg1 (Nuremberg, Germany), fsn1 (Falkenstein, Germany), hel1 (Helsinki, Finland) or ash (Ashburn, Virginia, USA)" unless locations.include? configuration.dig("location")
197
- end
163
+ def server_types
164
+ return [] unless valid_token?
198
165
 
199
- def find_available_releases
200
- @available_releases ||= begin
201
- response = HTTP.get("https://api.github.com/repos/k3s-io/k3s/tags?per_page=999").body
202
- JSON.parse(response).map { |hash| hash["name"] }
203
- end
204
- rescue
205
- errors << "Cannot fetch the releases with Hetzner API, please try again later"
206
- end
166
+ @server_types ||= hetzner_client.get('/server_types')['server_types'].map { |server_type| server_type['name'] }
167
+ rescue StandardError
168
+ @errors << 'Cannot fetch server types with Hetzner API, please try again later'
169
+ false
170
+ end
207
171
 
208
- def validate_k3s_version
209
- k3s_version = configuration.dig("k3s_version")
210
- available_releases = find_available_releases
211
- errors << "Invalid k3s version" unless available_releases.include? k3s_version
212
- end
172
+ def locations
173
+ return [] unless valid_token?
174
+
175
+ @locations ||= hetzner_client.get('/locations')['locations'].map { |location| location['name'] }
176
+ rescue StandardError
177
+ @errors << 'Cannot fetch locations with Hetzner API, please try again later'
178
+ []
179
+ end
180
+
181
+ def validate_location
182
+ return if locations.empty? && !valid_token?
183
+ return if locations.include? configuration['location']
184
+
185
+ errors << 'Invalid location - available locations: nbg1 (Nuremberg, Germany), fsn1 (Falkenstein, Germany), hel1 (Helsinki, Finland) or ash (Ashburn, Virginia, USA)'
186
+ end
213
187
 
214
- def validate_new_k3s_version
215
- new_k3s_version = options[:new_k3s_version]
216
- available_releases = find_available_releases
217
- errors << "The new k3s version is invalid" unless available_releases.include? new_k3s_version
188
+ def available_releases
189
+ @available_releases ||= begin
190
+ response = HTTP.get('https://api.github.com/repos/k3s-io/k3s/tags?per_page=999').body
191
+ JSON.parse(response).map { |hash| hash['name'] }
218
192
  end
193
+ rescue StandardError
194
+ errors << 'Cannot fetch the releases with Hetzner API, please try again later'
195
+ end
219
196
 
220
- def validate_masters
221
- masters_pool = nil
197
+ def validate_k3s_version
198
+ k3s_version = configuration['k3s_version']
199
+ errors << 'Invalid k3s version' unless available_releases.include? k3s_version
200
+ end
222
201
 
223
- begin
224
- masters_pool = configuration.dig("masters")
225
- rescue
226
- errors << "Invalid masters configuration"
227
- return
228
- end
202
+ def validate_new_k3s_version
203
+ new_k3s_version = options[:new_k3s_version]
204
+ errors << 'The new k3s version is invalid' unless available_releases.include? new_k3s_version
205
+ end
229
206
 
230
- if masters_pool.nil?
231
- errors << "Invalid masters configuration"
232
- return
233
- end
207
+ def validate_masters
208
+ masters_pool = nil
234
209
 
235
- validate_instance_group masters_pool, workers: false
210
+ begin
211
+ masters_pool = configuration['masters']
212
+ rescue StandardError
213
+ errors << 'Invalid masters configuration'
214
+ return
236
215
  end
237
216
 
238
- def validate_worker_node_pools
239
- worker_node_pools = nil
217
+ if masters_pool.nil?
218
+ errors << 'Invalid masters configuration'
219
+ return
220
+ end
240
221
 
241
- begin
242
- worker_node_pools = configuration.dig("worker_node_pools")
243
- rescue
244
- unless schedule_workloads_on_masters?
245
- errors << "Invalid node pools configuration"
246
- return
247
- end
248
- end
222
+ validate_instance_group masters_pool, workers: false
223
+ end
249
224
 
250
- if worker_node_pools.nil? && schedule_workloads_on_masters?
251
- return
252
- end
225
+ def validate_worker_node_pools
226
+ worker_node_pools = configuration['worker_node_pools'] || []
253
227
 
254
- if !worker_node_pools.is_a? Array
255
- errors << "Invalid node pools configuration"
256
- elsif worker_node_pools.size == 0
257
- unless schedule_workloads_on_masters?
258
- errors << "At least one node pool is required in order to schedule workloads"
259
- end
260
- elsif worker_node_pools.map{ |worker_node_pool| worker_node_pool["name"]}.uniq.size != worker_node_pools.size
261
- errors << "Each node pool must have an unique name"
262
- elsif server_types
263
- worker_node_pools.each do |worker_node_pool|
264
- validate_instance_group worker_node_pool
265
- end
266
- end
228
+ unless worker_node_pools.size.positive? || schedule_workloads_on_masters?
229
+ errors << 'Invalid node pools configuration'
230
+ return
267
231
  end
268
232
 
269
- def schedule_workloads_on_masters?
270
- schedule_workloads_on_masters = configuration.dig("schedule_workloads_on_masters")
271
- schedule_workloads_on_masters ? !!schedule_workloads_on_masters : false
233
+ return if worker_node_pools.size.zero? && schedule_workloads_on_masters?
234
+
235
+ if !worker_node_pools.is_a? Array
236
+ errors << 'Invalid node pools configuration'
237
+ elsif worker_node_pools.size.zero?
238
+ errors << 'At least one node pool is required in order to schedule workloads' unless schedule_workloads_on_masters?
239
+ elsif worker_node_pools.map { |worker_node_pool| worker_node_pool['name'] }.uniq.size != worker_node_pools.size
240
+ errors << 'Each node pool must have an unique name'
241
+ elsif server_types
242
+ worker_node_pools.each do |worker_node_pool|
243
+ validate_instance_group worker_node_pool
244
+ end
272
245
  end
246
+ end
273
247
 
274
- def validate_new_k3s_version_must_be_more_recent
275
- return if options[:force] == "true"
276
- return unless kubernetes_client
248
+ def schedule_workloads_on_masters?
249
+ schedule_workloads_on_masters = configuration['schedule_workloads_on_masters']
250
+ schedule_workloads_on_masters ? !!schedule_workloads_on_masters : false
251
+ end
277
252
 
278
- begin
279
- Timeout::timeout(5) do
280
- servers = kubernetes_client.api("v1").resource("nodes").list
253
+ def validate_instance_group(instance_group, workers: true)
254
+ instance_group_errors = []
281
255
 
282
- if servers.size == 0
283
- errors << "The cluster seems to have no nodes, nothing to upgrade"
284
- else
285
- available_releases = find_available_releases
256
+ instance_group_type = workers ? "Worker mode pool '#{instance_group['name']}'" : 'Masters pool'
286
257
 
287
- current_k3s_version = servers.first.dig(:status, :nodeInfo, :kubeletVersion)
288
- current_k3s_version_index = available_releases.index(current_k3s_version) || 1000
258
+ instance_group_errors << "#{instance_group_type} has an invalid name" unless !workers || instance_group['name'] =~ /\A([A-Za-z0-9\-_]+)\Z/
289
259
 
290
- new_k3s_version = options[:new_k3s_version]
291
- new_k3s_version_index = available_releases.index(new_k3s_version) || 1000
260
+ instance_group_errors << "#{instance_group_type} is in an invalid format" unless instance_group.is_a? Hash
292
261
 
293
- unless new_k3s_version_index < current_k3s_version_index
294
- errors << "The new k3s version must be more recent than the current one"
295
- end
296
- end
297
- end
262
+ instance_group_errors << "#{instance_group_type} has an invalid instance type" unless !valid_token? || server_types.include?(instance_group['instance_type'])
298
263
 
299
- rescue Timeout::Error
300
- puts "Cannot upgrade: Unable to fetch nodes from Kubernetes API. Is the cluster online?"
264
+ if instance_group['instance_count'].is_a? Integer
265
+ if instance_group['instance_count'] < 1
266
+ instance_group_errors << "#{instance_group_type} must have at least one node"
267
+ elsif instance_group['instance_count'] > 10
268
+ instance_group_errors << "#{instance_group_type} cannot have more than 10 nodes due to a limitation with the Hetzner placement groups. You can add more node pools if you need more nodes."
269
+ elsif !workers
270
+ instance_group_errors << 'Masters count must equal to 1 for non-HA clusters or an odd number (recommended 3) for an HA cluster' unless instance_group['instance_count'].odd?
301
271
  end
272
+ else
273
+ instance_group_errors << "#{instance_group_type} has an invalid instance count"
302
274
  end
303
275
 
304
- def validate_instance_group(instance_group, workers: true)
305
- instance_group_errors = []
276
+ used_server_types << instance_group['instance_type']
306
277
 
307
- instance_group_type = workers ? "Worker mode pool #{instance_group["name"]}" : "Masters pool"
278
+ errors << instance_group_errors
279
+ end
308
280
 
309
- unless !workers || instance_group["name"] =~ /\A([A-Za-z0-9\-\_]+)\Z/
310
- instance_group_errors << "#{instance_group_type} has an invalid name"
311
- end
281
+ def validate_verify_host_key
282
+ return unless [true, false].include?(configuration.fetch('public_ssh_key_path', false))
312
283
 
313
- unless instance_group.is_a? Hash
314
- instance_group_errors << "#{instance_group_type} is in an invalid format"
315
- end
284
+ errors << 'Please set the verify_host_key option to either true or false'
285
+ end
316
286
 
317
- unless !valid_token? or server_types.include?(instance_group["instance_type"])
318
- instance_group_errors << "#{instance_group_type} has an invalid instance type"
319
- end
287
+ def hetzner_token
288
+ @token = ENV['HCLOUD_TOKEN']
289
+ return @token if @token
320
290
 
321
- if instance_group["instance_count"].is_a? Integer
322
- if instance_group["instance_count"] < 1
323
- instance_group_errors << "#{instance_group_type} must have at least one node"
324
- elsif !workers
325
- instance_group_errors << "Masters count must equal to 1 for non-HA clusters or an odd number (recommended 3) for an HA cluster" unless instance_group["instance_count"].odd?
326
- end
327
- else
328
- instance_group_errors << "#{instance_group_type} has an invalid instance count"
329
- end
291
+ @token = configuration['hetzner_token']
292
+ end
330
293
 
331
- used_server_types << instance_group["instance_type"]
294
+ def validate_ssh_allowed_networks
295
+ networks ||= configuration['ssh_allowed_networks']
332
296
 
333
- errors << instance_group_errors
297
+ if networks.nil? || networks.empty?
298
+ errors << 'At least one network/IP range must be specified for SSH access'
299
+ return
334
300
  end
335
301
 
336
- def kubernetes_client
337
- return @kubernetes_client if @kubernetes_client
338
-
339
- config_hash = YAML.load_file(File.expand_path(configuration["kubeconfig_path"]))
340
- config_hash['current-context'] = configuration["cluster_name"]
341
- @kubernetes_client = K8s::Client.config(K8s::Config.new(config_hash))
342
- rescue
343
- errors << "Cannot connect to the Kubernetes cluster"
302
+ invalid_networks = networks.reject do |network|
303
+ IPAddr.new(network)
304
+ rescue StandardError
344
305
  false
345
306
  end
346
307
 
347
- def validate_verify_host_key
348
- return unless [true, false].include?(configuration.fetch("public_ssh_key_path", false))
349
- errors << "Please set the verify_host_key option to either true or false"
308
+ unless invalid_networks.empty?
309
+ invalid_networks.each do |network|
310
+ errors << "The network #{network} is an invalid range"
311
+ end
350
312
  end
351
313
 
352
- def find_hetzner_token
353
- @token = ENV["HCLOUD_TOKEN"]
354
- return @token if @token
355
- @token = configuration.dig("hetzner_token")
314
+ invalid_ranges = networks.reject do |network|
315
+ network.include? '/'
356
316
  end
357
317
 
358
- def validate_ssh_allowed_networks
359
- networks ||= configuration.dig("ssh_allowed_networks")
360
-
361
- if networks.nil? or networks.empty?
362
- errors << "At least one network/IP range must be specified for SSH access"
363
- return
318
+ unless invalid_ranges.empty?
319
+ invalid_ranges.each do |_network|
320
+ errors << 'Please use the CIDR notation for the networks to avoid ambiguity'
364
321
  end
322
+ end
365
323
 
366
- invalid_networks = networks.reject do |network|
367
- IPAddr.new(network) rescue false
368
- end
324
+ return unless invalid_networks.empty?
369
325
 
370
- unless invalid_networks.empty?
371
- invalid_networks.each do |network|
372
- errors << "The network #{network} is an invalid range"
373
- end
374
- end
326
+ current_ip = URI.open('http://whatismyip.akamai.com').read
375
327
 
376
- invalid_ranges = networks.reject do |network|
377
- network.include? "/"
378
- end
328
+ current_ip_networks = networks.detect do |network|
329
+ IPAddr.new(network).include?(current_ip)
330
+ rescue StandardError
331
+ false
332
+ end
379
333
 
380
- unless invalid_ranges.empty?
381
- invalid_ranges.each do |network|
382
- errors << "Please use the CIDR notation for the networks to avoid ambiguity"
383
- end
384
- end
334
+ errors << "Your current IP #{current_ip} is not included into any of the networks you've specified, so we won't be able to SSH into the nodes" unless current_ip_networks
335
+ end
385
336
 
386
- return unless invalid_networks.empty?
337
+ def validate_additional_packages
338
+ additional_packages = configuration['additional_packages']
339
+ errors << 'Invalid additional packages configuration - it should be an array' if additional_packages && !additional_packages.is_a?(Array)
340
+ end
387
341
 
388
- current_ip = URI.open('http://whatismyip.akamai.com').read
342
+ def validate_create
343
+ validate_public_ssh_key
344
+ validate_private_ssh_key
345
+ validate_ssh_allowed_networks
346
+ validate_location
347
+ validate_k3s_version
348
+ validate_masters
349
+ validate_worker_node_pools
350
+ validate_verify_host_key
351
+ validate_additional_packages
352
+ end
389
353
 
390
- current_ip_networks = networks.detect do |network|
391
- IPAddr.new(network).include?(current_ip) rescue false
392
- end
354
+ def validate_upgrade
355
+ validate_kubeconfig_path_must_exist
356
+ validate_new_k3s_version
357
+ end
393
358
 
394
- unless current_ip_networks
395
- errors << "Your current IP #{current_ip} is not included into any of the networks you've specified, so we won't be able to SSH into the nodes"
359
+ def validate_configuration_file
360
+ config_file_path = options[:config_file]
361
+
362
+ if File.exist?(config_file_path)
363
+ begin
364
+ @configuration = YAML.load_file(options[:config_file])
365
+ unless configuration.is_a? Hash
366
+ puts 'Configuration is invalid'
367
+ exit 1
368
+ end
369
+ rescue StandardError
370
+ puts 'Please ensure that the config file is a correct YAML manifest.'
371
+ exit 1
396
372
  end
373
+ else
374
+ puts 'Please specify a correct path for the config file.'
375
+ exit 1
397
376
  end
398
-
377
+ end
399
378
  end
400
379
  end
401
380
  end