hetzner-k3s 0.5.0 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,13 +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'
6
- require "yaml"
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,364 @@ module Hetzner
16
17
  true
17
18
  end
18
19
 
19
- 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'
20
28
  def version
21
29
  puts Hetzner::K3s::VERSION
22
30
  end
23
31
 
24
- desc "create-cluster", "Create a k3s cluster in Hetzner Cloud"
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
- validate_config_file :create
29
- 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:
30
37
  end
31
38
 
32
- desc "delete-cluster", "Delete an existing k3s cluster in Hetzner Cloud"
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
- validate_config_file :delete
37
- 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:
38
44
  end
39
45
 
40
- 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'
41
47
  option :config_file, required: true
42
48
  option :new_k3s_version, required: true
43
- option :force, default: "false"
44
-
49
+ option :force, default: 'false'
45
50
  def upgrade_cluster
46
- validate_config_file :upgrade
47
- 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])
48
55
  end
49
56
 
50
- desc "releases", "List available k3s releases"
57
+ desc 'releases', 'List available k3s releases'
51
58
  def releases
52
- find_available_releases.each do |release|
59
+ available_releases.each do |release|
53
60
  puts release
54
61
  end
55
62
  end
56
63
 
57
64
  private
58
65
 
59
- attr_reader :configuration, :hetzner_client, :k3s_version
60
- attr_accessor :errors, :used_server_types
61
-
62
- def validate_config_file(action)
63
- config_file_path = options[:config_file]
64
-
65
- if File.exists?(config_file_path)
66
- begin
67
- @configuration = YAML.load_file(options[:config_file])
68
- unless configuration.is_a? Hash
69
- raise "Configuration is invalid"
70
- exit 1
71
- end
72
- rescue => e
73
- puts "Please ensure that the config file is a correct YAML manifest."
74
- exit 1
75
- end
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
- @errors = []
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
- errors.flatten!
86
+ return if errors.empty?
107
87
 
108
- unless errors.empty?
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
- exit 1
115
- end
90
+ errors.each do |error|
91
+ puts " - #{error}"
116
92
  end
117
93
 
118
- def valid_token?
119
- return @valid unless @valid.nil?
94
+ exit 1
95
+ end
120
96
 
121
- begin
122
- token = find_hetzner_token
123
- @hetzner_client = Hetzner::Client.new(token: token)
124
- response = hetzner_client.get("/locations")
125
- error_code = response.dig("error", "code")
126
- @valid = if error_code and error_code.size > 0
127
- false
128
- else
129
- true
130
- end
131
- rescue
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
- def validate_token
137
- errors << "Invalid Hetzner Cloud token" unless valid_token?
138
- end
111
+ def validate_token
112
+ errors << 'Invalid Hetzner Cloud token' unless valid_token?
113
+ end
139
114
 
140
- def validate_cluster_name
141
- errors << "Cluster name is an invalid format (only lowercase letters, digits and dashes are allowed)" unless configuration["cluster_name"] =~ /\A[a-z\d-]+\z/
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
- def validate_kubeconfig_path
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
- directory = File.dirname(path)
150
- errors << "Directory #{directory} doesn't exist" unless File.exists? directory
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
- def validate_public_ssh_key
156
- path = File.expand_path(configuration.dig("public_ssh_key_path"))
157
- errors << "Invalid Public SSH key path" and return unless File.exists? 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
158
126
 
159
- key = File.read(path)
160
- errors << "Public SSH key is invalid" unless ::SSHKey.valid_ssh_public_key?(key)
161
- rescue
162
- errors << "Invalid Public SSH key path"
163
- 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
164
132
 
165
- def validate_private_ssh_key
166
- return unless (private_ssh_key_path = configuration.dig("private_ssh_key_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
167
136
 
168
- path = File.expand_path(private_ssh_key_path)
169
- errors << "Invalid Private SSH key path" and return unless File.exists?(path)
170
- rescue
171
- errors << "Invalid Private SSH key path"
172
- 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
173
142
 
174
- def validate_kubeconfig_path_must_exist
175
- path = File.expand_path configuration.dig("kubeconfig_path")
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
- def server_types
183
- return [] unless valid_token?
184
- @server_types ||= hetzner_client.get("/server_types")["server_types"].map{ |server_type| server_type["name"] }
185
- rescue
186
- @errors << "Cannot fetch server types with Hetzner API, please try again later"
187
- false
188
- end
146
+ return unless private_ssh_key_path
189
147
 
190
- def locations
191
- return [] unless valid_token?
192
- @locations ||= hetzner_client.get("/locations")["locations"].map{ |location| location["name"] }
193
- rescue
194
- @errors << "Cannot fetch locations with Hetzner API, please try again later"
195
- []
196
- 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
197
153
 
198
- def validate_location
199
- return if locations.empty? && !valid_token?
200
- 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")
201
- 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
202
157
 
203
- def find_available_releases
204
- @available_releases ||= begin
205
- response = HTTP.get("https://api.github.com/repos/k3s-io/k3s/tags?per_page=999").body
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"
210
- end
158
+ errors << 'kubeconfig path cannot be a directory' if File.directory? path
159
+ rescue StandardError
160
+ errors << 'Invalid kubeconfig path'
161
+ end
211
162
 
212
- def validate_k3s_version
213
- k3s_version = configuration.dig("k3s_version")
214
- available_releases = find_available_releases
215
- errors << "Invalid k3s version" unless available_releases.include? k3s_version
216
- end
163
+ def server_types
164
+ return [] unless valid_token?
217
165
 
218
- def validate_new_k3s_version
219
- new_k3s_version = options[:new_k3s_version]
220
- available_releases = find_available_releases
221
- errors << "The new k3s version is invalid" unless available_releases.include? new_k3s_version
222
- 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
223
171
 
224
- def validate_masters
225
- masters_pool = nil
172
+ def locations
173
+ return [] unless valid_token?
226
174
 
227
- begin
228
- masters_pool = configuration.dig("masters")
229
- rescue
230
- errors << "Invalid masters configuration"
231
- return
232
- 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
233
180
 
234
- if masters_pool.nil?
235
- errors << "Invalid masters configuration"
236
- return
237
- end
181
+ def validate_location
182
+ return if locations.empty? && !valid_token?
183
+ return if locations.include? configuration['location']
238
184
 
239
- validate_instance_group masters_pool, workers: false
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'] }
240
192
  end
193
+ rescue StandardError
194
+ errors << 'Cannot fetch the releases with Hetzner API, please try again later'
195
+ end
241
196
 
242
- def validate_worker_node_pools
243
- worker_node_pools = 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
244
201
 
245
- begin
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
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
253
206
 
254
- if worker_node_pools.nil? && schedule_workloads_on_masters?
255
- return
256
- end
207
+ def validate_masters
208
+ masters_pool = nil
257
209
 
258
- if !worker_node_pools.is_a? Array
259
- errors << "Invalid node pools configuration"
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
210
+ begin
211
+ masters_pool = configuration['masters']
212
+ rescue StandardError
213
+ errors << 'Invalid masters configuration'
214
+ return
271
215
  end
272
216
 
273
- def schedule_workloads_on_masters?
274
- schedule_workloads_on_masters = configuration.dig("schedule_workloads_on_masters")
275
- schedule_workloads_on_masters ? !!schedule_workloads_on_masters : false
217
+ if masters_pool.nil?
218
+ errors << 'Invalid masters configuration'
219
+ return
276
220
  end
277
221
 
278
- def validate_instance_group(instance_group, workers: true)
279
- instance_group_errors = []
222
+ validate_instance_group masters_pool, workers: false
223
+ end
280
224
 
281
- instance_group_type = workers ? "Worker mode pool #{instance_group["name"]}" : "Masters pool"
225
+ def validate_worker_node_pools
226
+ worker_node_pools = configuration['worker_node_pools'] || []
282
227
 
283
- unless !workers || instance_group["name"] =~ /\A([A-Za-z0-9\-\_]+)\Z/
284
- instance_group_errors << "#{instance_group_type} has an invalid name"
285
- end
228
+ unless worker_node_pools.size.positive? || schedule_workloads_on_masters?
229
+ errors << 'Invalid node pools configuration'
230
+ return
231
+ end
286
232
 
287
- unless instance_group.is_a? Hash
288
- instance_group_errors << "#{instance_group_type} is in an invalid format"
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
289
244
  end
245
+ end
246
+ end
290
247
 
291
- unless !valid_token? or server_types.include?(instance_group["instance_type"])
292
- instance_group_errors << "#{instance_group_type} has an invalid instance type"
293
- end
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
294
252
 
295
- if instance_group["instance_count"].is_a? Integer
296
- if instance_group["instance_count"] < 1
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
253
+ def validate_instance_group(instance_group, workers: true)
254
+ instance_group_errors = []
304
255
 
305
- used_server_types << instance_group["instance_type"]
256
+ instance_group_type = workers ? "Worker mode pool '#{instance_group['name']}'" : 'Masters pool'
306
257
 
307
- errors << instance_group_errors
308
- end
258
+ instance_group_errors << "#{instance_group_type} has an invalid name" unless !workers || instance_group['name'] =~ /\A([A-Za-z0-9\-_]+)\Z/
309
259
 
310
- def validate_verify_host_key
311
- return unless [true, false].include?(configuration.fetch("public_ssh_key_path", false))
312
- errors << "Please set the verify_host_key option to either true or false"
313
- end
260
+ instance_group_errors << "#{instance_group_type} is in an invalid format" unless instance_group.is_a? Hash
314
261
 
315
- def find_hetzner_token
316
- @token = ENV["HCLOUD_TOKEN"]
317
- return @token if @token
318
- @token = configuration.dig("hetzner_token")
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?
271
+ end
272
+ else
273
+ instance_group_errors << "#{instance_group_type} has an invalid instance count"
319
274
  end
320
275
 
321
- def validate_ssh_allowed_networks
322
- networks ||= configuration.dig("ssh_allowed_networks")
276
+ used_server_types << instance_group['instance_type']
323
277
 
324
- if networks.nil? or networks.empty?
325
- errors << "At least one network/IP range must be specified for SSH access"
326
- return
327
- end
278
+ errors << instance_group_errors
279
+ end
328
280
 
329
- invalid_networks = networks.reject do |network|
330
- IPAddr.new(network) rescue false
331
- end
281
+ def validate_verify_host_key
282
+ return unless [true, false].include?(configuration.fetch('public_ssh_key_path', false))
332
283
 
333
- unless invalid_networks.empty?
334
- invalid_networks.each do |network|
335
- errors << "The network #{network} is an invalid range"
336
- end
337
- end
284
+ errors << 'Please set the verify_host_key option to either true or false'
285
+ end
338
286
 
339
- invalid_ranges = networks.reject do |network|
340
- network.include? "/"
341
- end
287
+ def hetzner_token
288
+ @token = ENV['HCLOUD_TOKEN']
289
+ return @token if @token
342
290
 
343
- unless invalid_ranges.empty?
344
- invalid_ranges.each do |network|
345
- errors << "Please use the CIDR notation for the networks to avoid ambiguity"
346
- end
347
- end
291
+ @token = configuration['hetzner_token']
292
+ end
348
293
 
349
- return unless invalid_networks.empty?
294
+ def validate_ssh_allowed_networks
295
+ networks ||= configuration['ssh_allowed_networks']
350
296
 
351
- current_ip = URI.open('http://whatismyip.akamai.com').read
297
+ if networks.nil? || networks.empty?
298
+ errors << 'At least one network/IP range must be specified for SSH access'
299
+ return
300
+ end
352
301
 
353
- current_ip_networks = networks.detect do |network|
354
- IPAddr.new(network).include?(current_ip) rescue false
302
+ invalid_networks = networks.reject do |network|
303
+ IPAddr.new(network)
304
+ rescue StandardError
305
+ false
306
+ end
307
+
308
+ unless invalid_networks.empty?
309
+ invalid_networks.each do |network|
310
+ errors << "The network #{network} is an invalid range"
355
311
  end
312
+ end
313
+
314
+ invalid_ranges = networks.reject do |network|
315
+ network.include? '/'
316
+ end
356
317
 
357
- unless current_ip_networks
358
- 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"
318
+ unless invalid_ranges.empty?
319
+ invalid_ranges.each do |_network|
320
+ errors << 'Please use the CIDR notation for the networks to avoid ambiguity'
359
321
  end
360
322
  end
361
323
 
362
- def validate_additional_packages
363
- additional_packages = configuration.dig("additional_packages")
364
- errors << "Invalid additional packages configuration - it should be an array" if additional_packages && !additional_packages.is_a?(Array)
324
+ return unless invalid_networks.empty?
325
+
326
+ current_ip = URI.open('http://whatismyip.akamai.com').read
327
+
328
+ current_ip_networks = networks.detect do |network|
329
+ IPAddr.new(network).include?(current_ip)
330
+ rescue StandardError
331
+ false
365
332
  end
366
333
 
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
336
+
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
341
+
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
353
+
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]
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
372
+ end
373
+ else
374
+ puts 'Please specify a correct path for the config file.'
375
+ exit 1
376
+ end
377
+ end
367
378
  end
368
379
  end
369
380
  end