hetzner-k3s 0.5.0 → 0.5.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +121 -0
- data/Dockerfile +4 -1
- data/Gemfile +5 -3
- data/Gemfile.lock +24 -3
- data/README.md +24 -90
- data/Rakefile +5 -3
- data/bin/build.sh +3 -3
- data/bin/{console → console.sh} +3 -3
- data/bin/{setup → setup.sh} +0 -0
- data/hetzner-k3s.gemspec +25 -21
- data/lib/hetzner/infra/client.rb +17 -15
- data/lib/hetzner/infra/firewall.rb +92 -90
- data/lib/hetzner/infra/load_balancer.rb +62 -59
- data/lib/hetzner/infra/network.rb +31 -30
- data/lib/hetzner/infra/placement_group.rb +25 -21
- data/lib/hetzner/infra/server.rb +34 -33
- data/lib/hetzner/infra/ssh_key.rb +34 -35
- data/lib/hetzner/infra.rb +5 -1
- data/lib/hetzner/k3s/cli.rb +337 -264
- data/lib/hetzner/k3s/cluster.rb +503 -405
- data/lib/hetzner/k3s/version.rb +3 -1
- data/lib/hetzner/utils.rb +42 -38
- data/lib/hetzner.rb +2 -0
- metadata +26 -10
data/lib/hetzner/k3s/cli.rb
CHANGED
@@ -1,13 +1,14 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'thor'
|
4
|
+
require 'http'
|
5
|
+
require 'sshkey'
|
4
6
|
require 'ipaddr'
|
5
7
|
require 'open-uri'
|
6
|
-
require
|
7
|
-
|
8
|
-
require_relative "cluster"
|
9
|
-
require_relative "version"
|
8
|
+
require 'yaml'
|
10
9
|
|
10
|
+
require_relative 'cluster'
|
11
|
+
require_relative 'version'
|
11
12
|
|
12
13
|
module Hetzner
|
13
14
|
module K3s
|
@@ -16,354 +17,426 @@ module Hetzner
|
|
16
17
|
true
|
17
18
|
end
|
18
19
|
|
19
|
-
|
20
|
+
def initialize(*args)
|
21
|
+
@errors = []
|
22
|
+
@used_server_types = []
|
23
|
+
|
24
|
+
super
|
25
|
+
end
|
26
|
+
|
27
|
+
desc 'version', 'Print the version'
|
20
28
|
def version
|
21
29
|
puts Hetzner::K3s::VERSION
|
22
30
|
end
|
23
31
|
|
24
|
-
desc
|
32
|
+
desc 'create-cluster', 'Create a k3s cluster in Hetzner Cloud'
|
25
33
|
option :config_file, required: true
|
26
|
-
|
27
34
|
def create_cluster
|
28
|
-
|
29
|
-
Cluster.new(hetzner_client
|
35
|
+
validate_configuration :create
|
36
|
+
Cluster.new(hetzner_client:, hetzner_token:).create configuration:
|
30
37
|
end
|
31
38
|
|
32
|
-
desc
|
39
|
+
desc 'delete-cluster', 'Delete an existing k3s cluster in Hetzner Cloud'
|
33
40
|
option :config_file, required: true
|
34
|
-
|
35
41
|
def delete_cluster
|
36
|
-
|
37
|
-
Cluster.new(hetzner_client
|
42
|
+
validate_configuration :delete
|
43
|
+
Cluster.new(hetzner_client:, hetzner_token:).delete configuration:
|
38
44
|
end
|
39
45
|
|
40
|
-
desc
|
46
|
+
desc 'upgrade-cluster', 'Upgrade an existing k3s cluster in Hetzner Cloud to a new version'
|
41
47
|
option :config_file, required: true
|
42
48
|
option :new_k3s_version, required: true
|
43
|
-
option :force, default:
|
44
|
-
|
49
|
+
option :force, default: 'false'
|
45
50
|
def upgrade_cluster
|
46
|
-
|
47
|
-
|
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])
|
48
55
|
end
|
49
56
|
|
50
|
-
desc
|
57
|
+
desc 'releases', 'List available k3s releases'
|
51
58
|
def releases
|
52
|
-
|
59
|
+
available_releases.each do |release|
|
53
60
|
puts release
|
54
61
|
end
|
55
62
|
end
|
56
63
|
|
57
64
|
private
|
58
65
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
else
|
77
|
-
puts "Please specify a correct path for the config file."
|
78
|
-
exit 1
|
79
|
-
end
|
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
|
80
83
|
|
81
|
-
|
82
|
-
@used_server_types = []
|
83
|
-
|
84
|
-
validate_token
|
85
|
-
validate_cluster_name
|
86
|
-
validate_kubeconfig_path
|
87
|
-
|
88
|
-
case action
|
89
|
-
when :create
|
90
|
-
validate_public_ssh_key
|
91
|
-
validate_private_ssh_key
|
92
|
-
validate_ssh_allowed_networks
|
93
|
-
validate_location
|
94
|
-
validate_k3s_version
|
95
|
-
validate_masters
|
96
|
-
validate_worker_node_pools
|
97
|
-
validate_verify_host_key
|
98
|
-
validate_additional_packages
|
99
|
-
when :delete
|
100
|
-
validate_kubeconfig_path_must_exist
|
101
|
-
when :upgrade
|
102
|
-
validate_kubeconfig_path_must_exist
|
103
|
-
validate_new_k3s_version
|
104
|
-
end
|
84
|
+
errors.flatten!
|
105
85
|
|
106
|
-
|
86
|
+
return if errors.empty?
|
107
87
|
|
108
|
-
|
109
|
-
puts "Some information in the configuration file requires your attention:"
|
110
|
-
errors.each do |error|
|
111
|
-
puts " - #{error}"
|
112
|
-
end
|
88
|
+
puts 'Some information in the configuration file requires your attention:'
|
113
89
|
|
114
|
-
|
115
|
-
|
90
|
+
errors.each do |error|
|
91
|
+
puts " - #{error}"
|
116
92
|
end
|
117
93
|
|
118
|
-
|
119
|
-
|
94
|
+
exit 1
|
95
|
+
end
|
120
96
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
@valid = false
|
133
|
-
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
|
134
108
|
end
|
109
|
+
end
|
135
110
|
|
136
|
-
|
137
|
-
|
138
|
-
|
111
|
+
def validate_token
|
112
|
+
errors << 'Invalid Hetzner Cloud token' unless valid_token?
|
113
|
+
end
|
139
114
|
|
140
|
-
|
141
|
-
|
142
|
-
errors << "Ensure that the cluster name starts with a normal letter" unless configuration["cluster_name"] =~ /\A[a-z]+.*\z/
|
143
|
-
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/
|
144
117
|
|
145
|
-
|
146
|
-
path = File.expand_path(configuration.dig("kubeconfig_path"))
|
147
|
-
errors << "kubeconfig path cannot be a directory" and return if File.directory? path
|
118
|
+
return if configuration['cluster_name'] =~ /\A[a-z]+.*\z/
|
148
119
|
|
149
|
-
|
150
|
-
|
151
|
-
rescue
|
152
|
-
errors << "Invalid path for the kubeconfig"
|
153
|
-
end
|
120
|
+
errors << 'Ensure that the cluster name starts with a normal letter'
|
121
|
+
end
|
154
122
|
|
155
|
-
|
156
|
-
|
157
|
-
|
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
|
158
126
|
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
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
|
164
132
|
|
165
|
-
|
166
|
-
|
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
|
167
136
|
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
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
|
173
142
|
|
174
|
-
|
175
|
-
|
176
|
-
errors << "kubeconfig path is invalid" and return unless File.exists? path
|
177
|
-
errors << "kubeconfig path cannot be a directory" if File.directory? path
|
178
|
-
rescue
|
179
|
-
errors << "Invalid kubeconfig path"
|
180
|
-
end
|
143
|
+
def validate_private_ssh_key
|
144
|
+
private_ssh_key_path = configuration['private_ssh_key_path']
|
181
145
|
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
146
|
+
return unless private_ssh_key_path
|
147
|
+
|
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
|
153
|
+
|
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
|
157
|
+
|
158
|
+
errors << 'kubeconfig path cannot be a directory' if File.directory? path
|
159
|
+
rescue StandardError
|
160
|
+
errors << 'Invalid kubeconfig path'
|
161
|
+
end
|
189
162
|
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
163
|
+
def server_types
|
164
|
+
return [] unless valid_token?
|
165
|
+
|
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
|
171
|
+
|
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 valid_location?(location)
|
182
|
+
return if locations.empty? && !valid_token?
|
183
|
+
|
184
|
+
locations.include? location
|
185
|
+
end
|
186
|
+
|
187
|
+
def validate_masters_location
|
188
|
+
return if valid_location?(configuration['location'])
|
189
|
+
|
190
|
+
errors << 'Invalid location for master nodes - valid locations: nbg1 (Nuremberg, Germany), fsn1 (Falkenstein, Germany), hel1 (Helsinki, Finland) or ash (Ashburn, Virginia, USA)'
|
191
|
+
end
|
192
|
+
|
193
|
+
def available_releases
|
194
|
+
@available_releases ||= begin
|
195
|
+
response = HTTP.get('https://api.github.com/repos/k3s-io/k3s/tags?per_page=999').body
|
196
|
+
JSON.parse(response).map { |hash| hash['name'] }
|
196
197
|
end
|
198
|
+
rescue StandardError
|
199
|
+
errors << 'Cannot fetch the releases with Hetzner API, please try again later'
|
200
|
+
end
|
201
|
+
|
202
|
+
def validate_k3s_version
|
203
|
+
k3s_version = configuration['k3s_version']
|
204
|
+
errors << 'Invalid k3s version' unless available_releases.include? k3s_version
|
205
|
+
end
|
197
206
|
|
198
|
-
|
199
|
-
|
200
|
-
|
207
|
+
def validate_new_k3s_version
|
208
|
+
new_k3s_version = options[:new_k3s_version]
|
209
|
+
errors << 'The new k3s version is invalid' unless available_releases.include? new_k3s_version
|
210
|
+
end
|
211
|
+
|
212
|
+
def validate_masters
|
213
|
+
masters_pool = nil
|
214
|
+
|
215
|
+
begin
|
216
|
+
masters_pool = configuration['masters']
|
217
|
+
rescue StandardError
|
218
|
+
errors << 'Invalid masters configuration'
|
219
|
+
return
|
201
220
|
end
|
202
221
|
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
JSON.parse(response).map { |hash| hash["name"] }
|
207
|
-
end
|
208
|
-
rescue
|
209
|
-
errors << "Cannot fetch the releases with Hetzner API, please try again later"
|
222
|
+
if masters_pool.nil?
|
223
|
+
errors << 'Invalid masters configuration'
|
224
|
+
return
|
210
225
|
end
|
211
226
|
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
227
|
+
validate_instance_group masters_pool, workers: false
|
228
|
+
end
|
229
|
+
|
230
|
+
def validate_worker_node_pools
|
231
|
+
worker_node_pools = configuration['worker_node_pools'] || []
|
232
|
+
|
233
|
+
unless worker_node_pools.size.positive? || schedule_workloads_on_masters?
|
234
|
+
errors << 'Invalid node pools configuration'
|
235
|
+
return
|
216
236
|
end
|
217
237
|
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
errors <<
|
238
|
+
return if worker_node_pools.size.zero? && schedule_workloads_on_masters?
|
239
|
+
|
240
|
+
if !worker_node_pools.is_a? Array
|
241
|
+
errors << 'Invalid node pools configuration'
|
242
|
+
elsif worker_node_pools.size.zero?
|
243
|
+
errors << 'At least one node pool is required in order to schedule workloads' unless schedule_workloads_on_masters?
|
244
|
+
elsif worker_node_pools.map { |worker_node_pool| worker_node_pool['name'] }.uniq.size != worker_node_pools.size
|
245
|
+
errors << 'Each node pool must have an unique name'
|
246
|
+
elsif server_types
|
247
|
+
worker_node_pools.each do |worker_node_pool|
|
248
|
+
validate_instance_group worker_node_pool
|
249
|
+
end
|
222
250
|
end
|
251
|
+
end
|
223
252
|
|
224
|
-
|
225
|
-
|
253
|
+
def schedule_workloads_on_masters?
|
254
|
+
schedule_workloads_on_masters = configuration['schedule_workloads_on_masters']
|
255
|
+
schedule_workloads_on_masters ? !!schedule_workloads_on_masters : false
|
256
|
+
end
|
226
257
|
|
227
|
-
|
228
|
-
|
229
|
-
rescue
|
230
|
-
errors << "Invalid masters configuration"
|
231
|
-
return
|
232
|
-
end
|
258
|
+
def validate_instance_group(instance_group, workers: true)
|
259
|
+
instance_group_errors = []
|
233
260
|
|
234
|
-
|
235
|
-
errors << "Invalid masters configuration"
|
236
|
-
return
|
237
|
-
end
|
261
|
+
instance_group_type = workers ? "Worker mode pool '#{instance_group['name']}'" : 'Masters pool'
|
238
262
|
|
239
|
-
|
240
|
-
end
|
263
|
+
instance_group_errors << "#{instance_group_type} has an invalid name" unless !workers || instance_group['name'] =~ /\A([A-Za-z0-9\-_]+)\Z/
|
241
264
|
|
242
|
-
|
243
|
-
worker_node_pools = nil
|
265
|
+
instance_group_errors << "#{instance_group_type} is in an invalid format" unless instance_group.is_a? Hash
|
244
266
|
|
245
|
-
|
246
|
-
worker_node_pools = configuration.dig("worker_node_pools")
|
247
|
-
rescue
|
248
|
-
unless schedule_workloads_on_masters?
|
249
|
-
errors << "Invalid node pools configuration"
|
250
|
-
return
|
251
|
-
end
|
252
|
-
end
|
267
|
+
instance_group_errors << "#{instance_group_type} has an invalid instance type" unless !valid_token? || server_types.include?(instance_group['instance_type'])
|
253
268
|
|
254
|
-
|
255
|
-
|
256
|
-
|
269
|
+
if workers
|
270
|
+
location = instance_group.fetch('location', configuration['location'])
|
271
|
+
instance_group_errors << "#{instance_group_type} has an invalid location - valid locations: nbg1 (Nuremberg, Germany), fsn1 (Falkenstein, Germany), hel1 (Helsinki, Finland) or ash (Ashburn, Virginia, USA)" unless valid_location?(location)
|
257
272
|
|
258
|
-
|
259
|
-
|
260
|
-
elsif worker_node_pools.size == 0
|
261
|
-
unless schedule_workloads_on_masters?
|
262
|
-
errors << "At least one node pool is required in order to schedule workloads"
|
263
|
-
end
|
264
|
-
elsif worker_node_pools.map{ |worker_node_pool| worker_node_pool["name"]}.uniq.size != worker_node_pools.size
|
265
|
-
errors << "Each node pool must have an unique name"
|
266
|
-
elsif server_types
|
267
|
-
worker_node_pools.each do |worker_node_pool|
|
268
|
-
validate_instance_group worker_node_pool
|
269
|
-
end
|
270
|
-
end
|
273
|
+
in_network_zone = configuration['location'] == 'ash' ? location == 'ash' : location != 'ash'
|
274
|
+
instance_group_errors << "#{instance_group_type} must be in the same network zone as the masters. If the masters are located in Ashburn, all the node pools must be located in Ashburn too, otherwise none of the node pools should be located in Ashburn." unless in_network_zone
|
271
275
|
end
|
272
276
|
|
273
|
-
|
274
|
-
|
275
|
-
|
277
|
+
if instance_group['instance_count'].is_a? Integer
|
278
|
+
if instance_group['instance_count'] < 1
|
279
|
+
instance_group_errors << "#{instance_group_type} must have at least one node"
|
280
|
+
elsif instance_group['instance_count'] > 10
|
281
|
+
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."
|
282
|
+
elsif !workers
|
283
|
+
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?
|
284
|
+
end
|
285
|
+
else
|
286
|
+
instance_group_errors << "#{instance_group_type} has an invalid instance count"
|
276
287
|
end
|
277
288
|
|
278
|
-
|
279
|
-
instance_group_errors = []
|
289
|
+
used_server_types << instance_group['instance_type']
|
280
290
|
|
281
|
-
|
291
|
+
errors << instance_group_errors
|
292
|
+
end
|
282
293
|
|
283
|
-
|
284
|
-
|
285
|
-
end
|
294
|
+
def validate_verify_host_key
|
295
|
+
return unless [true, false].include?(configuration.fetch('public_ssh_key_path', false))
|
286
296
|
|
287
|
-
|
288
|
-
|
289
|
-
end
|
297
|
+
errors << 'Please set the verify_host_key option to either true or false'
|
298
|
+
end
|
290
299
|
|
291
|
-
|
292
|
-
|
293
|
-
|
300
|
+
def hetzner_token
|
301
|
+
@token = ENV['HCLOUD_TOKEN']
|
302
|
+
return @token if @token
|
294
303
|
|
295
|
-
|
296
|
-
|
297
|
-
instance_group_errors << "#{instance_group_type} must have at least one node"
|
298
|
-
elsif !workers
|
299
|
-
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?
|
300
|
-
end
|
301
|
-
else
|
302
|
-
instance_group_errors << "#{instance_group_type} has an invalid instance count"
|
303
|
-
end
|
304
|
+
@token = configuration['hetzner_token']
|
305
|
+
end
|
304
306
|
|
305
|
-
|
307
|
+
def validate_ssh_allowed_networks
|
308
|
+
networks ||= configuration['ssh_allowed_networks']
|
306
309
|
|
307
|
-
|
310
|
+
if networks.nil? || networks.empty?
|
311
|
+
errors << 'At least one network/IP range must be specified for SSH access'
|
312
|
+
return
|
308
313
|
end
|
309
314
|
|
310
|
-
|
311
|
-
|
312
|
-
|
315
|
+
invalid_networks = networks.reject do |network|
|
316
|
+
IPAddr.new(network)
|
317
|
+
rescue StandardError
|
318
|
+
false
|
313
319
|
end
|
314
320
|
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
321
|
+
unless invalid_networks.empty?
|
322
|
+
invalid_networks.each do |network|
|
323
|
+
errors << "The network #{network} is an invalid range"
|
324
|
+
end
|
319
325
|
end
|
320
326
|
|
321
|
-
|
322
|
-
|
327
|
+
invalid_ranges = networks.reject do |network|
|
328
|
+
network.include? '/'
|
329
|
+
end
|
323
330
|
|
324
|
-
|
325
|
-
|
326
|
-
|
331
|
+
unless invalid_ranges.empty?
|
332
|
+
invalid_ranges.each do |_network|
|
333
|
+
errors << 'Please use the CIDR notation for the networks to avoid ambiguity'
|
327
334
|
end
|
335
|
+
end
|
328
336
|
|
329
|
-
|
330
|
-
IPAddr.new(network) rescue false
|
331
|
-
end
|
337
|
+
return unless invalid_networks.empty?
|
332
338
|
|
333
|
-
|
334
|
-
invalid_networks.each do |network|
|
335
|
-
errors << "The network #{network} is an invalid range"
|
336
|
-
end
|
337
|
-
end
|
339
|
+
current_ip = URI.open('http://whatismyip.akamai.com').read
|
338
340
|
|
339
|
-
|
340
|
-
|
341
|
-
|
341
|
+
current_ip_networks = networks.detect do |network|
|
342
|
+
IPAddr.new(network).include?(current_ip)
|
343
|
+
rescue StandardError
|
344
|
+
false
|
345
|
+
end
|
342
346
|
|
343
|
-
|
344
|
-
|
345
|
-
errors << "Please use the CIDR notation for the networks to avoid ambiguity"
|
346
|
-
end
|
347
|
-
end
|
347
|
+
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
|
348
|
+
end
|
348
349
|
|
349
|
-
|
350
|
+
def validate_additional_packages
|
351
|
+
additional_packages = configuration['additional_packages']
|
352
|
+
errors << 'Invalid additional packages configuration - it should be an array' if additional_packages && !additional_packages.is_a?(Array)
|
353
|
+
end
|
350
354
|
|
351
|
-
|
355
|
+
def validate_create
|
356
|
+
validate_public_ssh_key
|
357
|
+
validate_private_ssh_key
|
358
|
+
validate_ssh_allowed_networks
|
359
|
+
validate_masters_location
|
360
|
+
validate_k3s_version
|
361
|
+
validate_masters
|
362
|
+
validate_worker_node_pools
|
363
|
+
validate_verify_host_key
|
364
|
+
validate_additional_packages
|
365
|
+
validate_kube_api_server_args
|
366
|
+
validate_kube_scheduler_args
|
367
|
+
validate_kube_controller_manager_args
|
368
|
+
validate_kube_cloud_controller_manager_args
|
369
|
+
validate_kubelet_args
|
370
|
+
validate_kube_proxy_args
|
371
|
+
validate_network_zone
|
372
|
+
end
|
352
373
|
|
353
|
-
|
354
|
-
|
355
|
-
|
374
|
+
def validate_upgrade
|
375
|
+
validate_kubeconfig_path_must_exist
|
376
|
+
validate_new_k3s_version
|
377
|
+
end
|
356
378
|
|
357
|
-
|
358
|
-
|
379
|
+
def validate_configuration_file
|
380
|
+
config_file_path = options[:config_file]
|
381
|
+
|
382
|
+
if File.exist?(config_file_path)
|
383
|
+
begin
|
384
|
+
@configuration = YAML.load_file(options[:config_file])
|
385
|
+
unless configuration.is_a? Hash
|
386
|
+
puts 'Configuration is invalid'
|
387
|
+
exit 1
|
388
|
+
end
|
389
|
+
rescue StandardError
|
390
|
+
puts 'Please ensure that the config file is a correct YAML manifest.'
|
391
|
+
exit 1
|
359
392
|
end
|
393
|
+
else
|
394
|
+
puts 'Please specify a correct path for the config file.'
|
395
|
+
exit 1
|
360
396
|
end
|
397
|
+
end
|
361
398
|
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
end
|
399
|
+
def validate_kube_api_server_args
|
400
|
+
kube_api_server_args = configuration['kube_api_server_args']
|
401
|
+
return unless kube_api_server_args
|
366
402
|
|
403
|
+
errors << 'kube_api_server_args must be an array of arguments' unless kube_api_server_args.is_a? Array
|
404
|
+
end
|
405
|
+
|
406
|
+
def validate_kube_scheduler_args
|
407
|
+
kube_scheduler_args = configuration['kube_scheduler_args']
|
408
|
+
return unless kube_scheduler_args
|
409
|
+
|
410
|
+
errors << 'kube_scheduler_args must be an array of arguments' unless kube_scheduler_args.is_a? Array
|
411
|
+
end
|
412
|
+
|
413
|
+
def validate_kube_controller_manager_args
|
414
|
+
kube_controller_manager_args = configuration['kube_controller_manager_args']
|
415
|
+
return unless kube_controller_manager_args
|
416
|
+
|
417
|
+
errors << 'kube_controller_manager_args must be an array of arguments' unless kube_controller_manager_args.is_a? Array
|
418
|
+
end
|
419
|
+
|
420
|
+
def validate_kube_cloud_controller_manager_args
|
421
|
+
kube_cloud_controller_manager_args = configuration['kube_cloud_controller_manager_args']
|
422
|
+
return unless kube_cloud_controller_manager_args
|
423
|
+
|
424
|
+
errors << 'kube_cloud_controller_manager_args must be an array of arguments' unless kube_cloud_controller_manager_args.is_a? Array
|
425
|
+
end
|
426
|
+
|
427
|
+
def validate_kubelet_args
|
428
|
+
kubelet_args = configuration['kubelet_args']
|
429
|
+
return unless kubelet_args
|
430
|
+
|
431
|
+
errors << 'kubelet_args must be an array of arguments' unless kubelet_args.is_a? Array
|
432
|
+
end
|
433
|
+
|
434
|
+
def validate_kube_proxy_args
|
435
|
+
kube_proxy_args = configuration['kube_proxy_args']
|
436
|
+
return unless kube_proxy_args
|
437
|
+
|
438
|
+
errors << 'kube_proxy_args must be an array of arguments' unless kube_proxy_args.is_a? Array
|
439
|
+
end
|
367
440
|
end
|
368
441
|
end
|
369
442
|
end
|