hetzner-k3s 0.4.9 → 0.5.3
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 +4 -4
- data/.rubocop.yml +121 -0
- data/Dockerfile +5 -2
- data/Gemfile +5 -3
- data/Gemfile.lock +24 -3
- data/README.md +7 -85
- 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 +37 -31
- data/lib/hetzner/infra/ssh_key.rb +34 -35
- data/lib/hetzner/infra.rb +5 -1
- data/lib/hetzner/k3s/cli.rb +279 -262
- data/lib/hetzner/k3s/cluster.rb +448 -397
- 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,348 +17,364 @@ 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
|
-
when :delete
|
|
99
|
-
validate_kubeconfig_path_must_exist
|
|
100
|
-
when :upgrade
|
|
101
|
-
validate_kubeconfig_path_must_exist
|
|
102
|
-
validate_new_k3s_version
|
|
103
|
-
end
|
|
84
|
+
errors.flatten!
|
|
104
85
|
|
|
105
|
-
|
|
86
|
+
return if errors.empty?
|
|
106
87
|
|
|
107
|
-
|
|
108
|
-
puts "Some information in the configuration file requires your attention:"
|
|
109
|
-
errors.each do |error|
|
|
110
|
-
puts " - #{error}"
|
|
111
|
-
end
|
|
88
|
+
puts 'Some information in the configuration file requires your attention:'
|
|
112
89
|
|
|
113
|
-
|
|
114
|
-
|
|
90
|
+
errors.each do |error|
|
|
91
|
+
puts " - #{error}"
|
|
115
92
|
end
|
|
116
93
|
|
|
117
|
-
|
|
118
|
-
|
|
94
|
+
exit 1
|
|
95
|
+
end
|
|
119
96
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
@valid = false
|
|
132
|
-
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
|
|
133
108
|
end
|
|
109
|
+
end
|
|
134
110
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
111
|
+
def validate_token
|
|
112
|
+
errors << 'Invalid Hetzner Cloud token' unless valid_token?
|
|
113
|
+
end
|
|
138
114
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
errors << "Ensure that the cluster name starts with a normal letter" unless configuration["cluster_name"] =~ /\A[a-z]+.*\z/
|
|
142
|
-
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/
|
|
143
117
|
|
|
144
|
-
|
|
145
|
-
path = File.expand_path(configuration.dig("kubeconfig_path"))
|
|
146
|
-
errors << "kubeconfig path cannot be a directory" and return if File.directory? path
|
|
118
|
+
return if configuration['cluster_name'] =~ /\A[a-z]+.*\z/
|
|
147
119
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
rescue
|
|
151
|
-
errors << "Invalid path for the kubeconfig"
|
|
152
|
-
end
|
|
120
|
+
errors << 'Ensure that the cluster name starts with a normal letter'
|
|
121
|
+
end
|
|
153
122
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
|
157
126
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
|
163
132
|
|
|
164
|
-
|
|
165
|
-
|
|
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
|
|
166
136
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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
|
|
172
142
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
errors << "kubeconfig path is invalid" and return unless File.exists? path
|
|
176
|
-
errors << "kubeconfig path cannot be a directory" if File.directory? path
|
|
177
|
-
rescue
|
|
178
|
-
errors << "Invalid kubeconfig path"
|
|
179
|
-
end
|
|
143
|
+
def validate_private_ssh_key
|
|
144
|
+
private_ssh_key_path = configuration['private_ssh_key_path']
|
|
180
145
|
|
|
181
|
-
|
|
182
|
-
return [] unless valid_token?
|
|
183
|
-
@server_types ||= hetzner_client.get("/server_types")["server_types"].map{ |server_type| server_type["name"] }
|
|
184
|
-
rescue
|
|
185
|
-
@errors << "Cannot fetch server types with Hetzner API, please try again later"
|
|
186
|
-
false
|
|
187
|
-
end
|
|
146
|
+
return unless private_ssh_key_path
|
|
188
147
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
[]
|
|
195
|
-
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
|
|
196
153
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
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
|
|
201
157
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
end
|
|
207
|
-
rescue
|
|
208
|
-
errors << "Cannot fetch the releases with Hetzner API, please try again later"
|
|
209
|
-
end
|
|
158
|
+
errors << 'kubeconfig path cannot be a directory' if File.directory? path
|
|
159
|
+
rescue StandardError
|
|
160
|
+
errors << 'Invalid kubeconfig path'
|
|
161
|
+
end
|
|
210
162
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
available_releases = find_available_releases
|
|
214
|
-
errors << "Invalid k3s version" unless available_releases.include? k3s_version
|
|
215
|
-
end
|
|
163
|
+
def server_types
|
|
164
|
+
return [] unless valid_token?
|
|
216
165
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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
|
|
222
171
|
|
|
223
|
-
|
|
224
|
-
|
|
172
|
+
def locations
|
|
173
|
+
return [] unless valid_token?
|
|
225
174
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
end
|
|
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
|
|
232
180
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
end
|
|
181
|
+
def validate_location
|
|
182
|
+
return if locations.empty? && !valid_token?
|
|
183
|
+
return if locations.include? configuration['location']
|
|
237
184
|
|
|
238
|
-
|
|
185
|
+
errors << 'Invalid location - available locations: nbg1 (Nuremberg, Germany), fsn1 (Falkenstein, Germany), hel1 (Helsinki, Finland) or ash (Ashburn, Virginia, USA)'
|
|
186
|
+
end
|
|
187
|
+
|
|
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'] }
|
|
239
192
|
end
|
|
193
|
+
rescue StandardError
|
|
194
|
+
errors << 'Cannot fetch the releases with Hetzner API, please try again later'
|
|
195
|
+
end
|
|
240
196
|
|
|
241
|
-
|
|
242
|
-
|
|
197
|
+
def validate_k3s_version
|
|
198
|
+
k3s_version = configuration['k3s_version']
|
|
199
|
+
errors << 'Invalid k3s version' unless available_releases.include? k3s_version
|
|
200
|
+
end
|
|
243
201
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
errors << "Invalid node pools configuration"
|
|
249
|
-
return
|
|
250
|
-
end
|
|
251
|
-
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
|
|
252
206
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
end
|
|
207
|
+
def validate_masters
|
|
208
|
+
masters_pool = nil
|
|
256
209
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
end
|
|
263
|
-
elsif worker_node_pools.map{ |worker_node_pool| worker_node_pool["name"]}.uniq.size != worker_node_pools.size
|
|
264
|
-
errors << "Each node pool must have an unique name"
|
|
265
|
-
elsif server_types
|
|
266
|
-
worker_node_pools.each do |worker_node_pool|
|
|
267
|
-
validate_instance_group worker_node_pool
|
|
268
|
-
end
|
|
269
|
-
end
|
|
210
|
+
begin
|
|
211
|
+
masters_pool = configuration['masters']
|
|
212
|
+
rescue StandardError
|
|
213
|
+
errors << 'Invalid masters configuration'
|
|
214
|
+
return
|
|
270
215
|
end
|
|
271
216
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
217
|
+
if masters_pool.nil?
|
|
218
|
+
errors << 'Invalid masters configuration'
|
|
219
|
+
return
|
|
275
220
|
end
|
|
276
221
|
|
|
277
|
-
|
|
278
|
-
|
|
222
|
+
validate_instance_group masters_pool, workers: false
|
|
223
|
+
end
|
|
279
224
|
|
|
280
|
-
|
|
225
|
+
def validate_worker_node_pools
|
|
226
|
+
worker_node_pools = configuration['worker_node_pools'] || []
|
|
281
227
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
228
|
+
unless worker_node_pools.size.positive? || schedule_workloads_on_masters?
|
|
229
|
+
errors << 'Invalid node pools configuration'
|
|
230
|
+
return
|
|
231
|
+
end
|
|
285
232
|
|
|
286
|
-
|
|
287
|
-
|
|
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
|
|
288
244
|
end
|
|
245
|
+
end
|
|
246
|
+
end
|
|
289
247
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
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
|
|
293
252
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
253
|
+
def validate_instance_group(instance_group, workers: true)
|
|
254
|
+
instance_group_errors = []
|
|
255
|
+
|
|
256
|
+
instance_group_type = workers ? "Worker mode pool '#{instance_group['name']}'" : 'Masters pool'
|
|
257
|
+
|
|
258
|
+
instance_group_errors << "#{instance_group_type} has an invalid name" unless !workers || instance_group['name'] =~ /\A([A-Za-z0-9\-_]+)\Z/
|
|
259
|
+
|
|
260
|
+
instance_group_errors << "#{instance_group_type} is in an invalid format" unless instance_group.is_a? Hash
|
|
261
|
+
|
|
262
|
+
instance_group_errors << "#{instance_group_type} has an invalid instance type" unless !valid_token? || server_types.include?(instance_group['instance_type'])
|
|
263
|
+
|
|
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?
|
|
302
271
|
end
|
|
272
|
+
else
|
|
273
|
+
instance_group_errors << "#{instance_group_type} has an invalid instance count"
|
|
274
|
+
end
|
|
303
275
|
|
|
304
|
-
|
|
276
|
+
used_server_types << instance_group['instance_type']
|
|
305
277
|
|
|
306
|
-
|
|
278
|
+
errors << instance_group_errors
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
def validate_verify_host_key
|
|
282
|
+
return unless [true, false].include?(configuration.fetch('public_ssh_key_path', false))
|
|
283
|
+
|
|
284
|
+
errors << 'Please set the verify_host_key option to either true or false'
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
def hetzner_token
|
|
288
|
+
@token = ENV['HCLOUD_TOKEN']
|
|
289
|
+
return @token if @token
|
|
290
|
+
|
|
291
|
+
@token = configuration['hetzner_token']
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
def validate_ssh_allowed_networks
|
|
295
|
+
networks ||= configuration['ssh_allowed_networks']
|
|
296
|
+
|
|
297
|
+
if networks.nil? || networks.empty?
|
|
298
|
+
errors << 'At least one network/IP range must be specified for SSH access'
|
|
299
|
+
return
|
|
307
300
|
end
|
|
308
301
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
302
|
+
invalid_networks = networks.reject do |network|
|
|
303
|
+
IPAddr.new(network)
|
|
304
|
+
rescue StandardError
|
|
305
|
+
false
|
|
312
306
|
end
|
|
313
307
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
308
|
+
unless invalid_networks.empty?
|
|
309
|
+
invalid_networks.each do |network|
|
|
310
|
+
errors << "The network #{network} is an invalid range"
|
|
311
|
+
end
|
|
318
312
|
end
|
|
319
313
|
|
|
320
|
-
|
|
321
|
-
|
|
314
|
+
invalid_ranges = networks.reject do |network|
|
|
315
|
+
network.include? '/'
|
|
316
|
+
end
|
|
322
317
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
318
|
+
unless invalid_ranges.empty?
|
|
319
|
+
invalid_ranges.each do |_network|
|
|
320
|
+
errors << 'Please use the CIDR notation for the networks to avoid ambiguity'
|
|
326
321
|
end
|
|
322
|
+
end
|
|
327
323
|
|
|
328
|
-
|
|
329
|
-
IPAddr.new(network) rescue false
|
|
330
|
-
end
|
|
324
|
+
return unless invalid_networks.empty?
|
|
331
325
|
|
|
332
|
-
|
|
333
|
-
invalid_networks.each do |network|
|
|
334
|
-
errors << "The network #{network} is an invalid range"
|
|
335
|
-
end
|
|
336
|
-
end
|
|
326
|
+
current_ip = URI.open('http://whatismyip.akamai.com').read
|
|
337
327
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
328
|
+
current_ip_networks = networks.detect do |network|
|
|
329
|
+
IPAddr.new(network).include?(current_ip)
|
|
330
|
+
rescue StandardError
|
|
331
|
+
false
|
|
332
|
+
end
|
|
341
333
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
errors << "Please use the CIDR notation for the networks to avoid ambiguity"
|
|
345
|
-
end
|
|
346
|
-
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
|
|
347
336
|
|
|
348
|
-
|
|
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
|
|
349
341
|
|
|
350
|
-
|
|
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
|
|
351
353
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
354
|
+
def validate_upgrade
|
|
355
|
+
validate_kubeconfig_path_must_exist
|
|
356
|
+
validate_new_k3s_version
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
def validate_configuration_file
|
|
360
|
+
config_file_path = options[:config_file]
|
|
355
361
|
|
|
356
|
-
|
|
357
|
-
|
|
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
|
|
358
372
|
end
|
|
373
|
+
else
|
|
374
|
+
puts 'Please specify a correct path for the config file.'
|
|
375
|
+
exit 1
|
|
359
376
|
end
|
|
360
|
-
|
|
377
|
+
end
|
|
361
378
|
end
|
|
362
379
|
end
|
|
363
380
|
end
|