vagrant-hetznercloud 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +9 -0
  3. data/LICENSE +201 -0
  4. data/README.md +111 -0
  5. data/Rakefile +6 -0
  6. data/example_box/metadata.json +3 -0
  7. data/examples/Vagrantfile +63 -0
  8. data/hetznercloud.box +0 -0
  9. data/lib/vagrant-hetznercloud.rb +18 -0
  10. data/lib/vagrant-hetznercloud/action.rb +189 -0
  11. data/lib/vagrant-hetznercloud/action/connect_hetznercloud.rb +33 -0
  12. data/lib/vagrant-hetznercloud/action/create_server.rb +92 -0
  13. data/lib/vagrant-hetznercloud/action/destroy_server.rb +33 -0
  14. data/lib/vagrant-hetznercloud/action/is_created.rb +18 -0
  15. data/lib/vagrant-hetznercloud/action/is_stopped.rb +18 -0
  16. data/lib/vagrant-hetznercloud/action/list_images.rb +23 -0
  17. data/lib/vagrant-hetznercloud/action/message_already_created.rb +16 -0
  18. data/lib/vagrant-hetznercloud/action/message_not_created.rb +16 -0
  19. data/lib/vagrant-hetznercloud/action/message_will_not_destroy.rb +16 -0
  20. data/lib/vagrant-hetznercloud/action/read_ssh_info.rb +51 -0
  21. data/lib/vagrant-hetznercloud/action/read_state.rb +36 -0
  22. data/lib/vagrant-hetznercloud/action/start_server.rb +90 -0
  23. data/lib/vagrant-hetznercloud/action/stop_server.rb +25 -0
  24. data/lib/vagrant-hetznercloud/action/warn_networks.rb +19 -0
  25. data/lib/vagrant-hetznercloud/command/images.rb +20 -0
  26. data/lib/vagrant-hetznercloud/command/root.rb +61 -0
  27. data/lib/vagrant-hetznercloud/config.rb +145 -0
  28. data/lib/vagrant-hetznercloud/errors.rb +21 -0
  29. data/lib/vagrant-hetznercloud/plugin.rb +77 -0
  30. data/lib/vagrant-hetznercloud/provider.rb +48 -0
  31. data/lib/vagrant-hetznercloud/util/timer.rb +17 -0
  32. data/lib/vagrant-hetznercloud/version.rb +5 -0
  33. data/locales/en.yml +90 -0
  34. data/vagrant-hetznercloud.gemspec +30 -0
  35. metadata +147 -0
@@ -0,0 +1,33 @@
1
+ require 'fog/hetznercloud'
2
+
3
+ module VagrantPlugins
4
+ module Hetznercloud
5
+ module Action
6
+ # This action connects to Hetznercloud, verifies credentials work, and
7
+ # puts the Hetznercloud connection object into the `:hetznercloud_compute` key
8
+ # in the environment.
9
+ class ConnectHetznercloud
10
+ def initialize(app, _env)
11
+ @app = app
12
+ @logger = Log4r::Logger.new('vagrant_hetznercloud::action::connect_hetznercloud')
13
+ end
14
+
15
+ def call(env)
16
+ # Get the configs
17
+ provider_config = env[:machine].provider_config
18
+
19
+ # Build the fog config
20
+ fog_config = {
21
+ provider: :hetznercloud,
22
+ hetznercloud_token: provider_config.token
23
+ }
24
+
25
+ @logger.info('Connecting to Hetznercloud...')
26
+ env[:hetznercloud_compute] = Fog::Compute.new(fog_config)
27
+
28
+ @app.call(env)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,92 @@
1
+ require 'vagrant/util/retryable'
2
+ require 'vagrant-hetznercloud/util/timer'
3
+
4
+ module VagrantPlugins
5
+ module Hetznercloud
6
+ module Action
7
+ # This creates the configured server.
8
+ class CreateServer
9
+ include Vagrant::Util::Retryable
10
+
11
+ def initialize(app, _env)
12
+ @app = app
13
+ @logger = Log4r::Logger.new('vagrant_hetznercloud::action::create_server')
14
+ end
15
+
16
+ def call(env)
17
+ # Initialize metrics if they haven't been
18
+ env[:metrics] ||= {}
19
+
20
+ config = env[:machine].provider_config
21
+
22
+ server_type = config.server_type
23
+ image = config.image
24
+ name = config.name
25
+ ssh_keys = config.ssh_keys
26
+ location = config.location
27
+ datacenter = config.datacenter
28
+ user_data = config.user_data
29
+
30
+ env[:ui].info(I18n.t('vagrant_hetznercloud.creating_server'))
31
+ env[:ui].info(" -- Type: #{server_type}")
32
+ env[:ui].info(" -- Image: #{image}")
33
+ env[:ui].info(" -- Name: #{name}")
34
+ env[:ui].info(" -- SSH Keys: #{ssh_keys}")
35
+ env[:ui].info(" -- location: #{location}")
36
+ env[:ui].info(" -- datacenter: #{datacenter}")
37
+ env[:ui].info(" -- user_data: #{user_data}")
38
+
39
+ options = {
40
+ name: name,
41
+ image: image,
42
+ server_type: server_type,
43
+ ssh_keys: ssh_keys
44
+ }
45
+
46
+ option[:location] = location unless location.nil?
47
+ option[:datacenter] = datacenter unless datacenter.nil?
48
+ option[:user_data] = user_data unless user_data.nil?
49
+
50
+ begin
51
+ server = env[:hetznercloud_compute].servers.create(options)
52
+ rescue Fog::Hetznercloud::Compute::Error => e
53
+ raise Errors::FogError, message: e.message
54
+ rescue Excon::Errors::HTTPStatusError => e
55
+ raise Errors::InternalFogError,
56
+ error: e.message,
57
+ response: e.response.body
58
+ end
59
+
60
+ env[:ui].info(" -- Server IP: #{server.public_ip_address}")
61
+
62
+ @logger.info("Machine '#{name}' created.")
63
+
64
+ # Immediately save the ID since it is created at this point.
65
+ env[:machine].id = server.id
66
+
67
+ # destroy the server if we were interrupted
68
+ destroy(env) if env[:interrupted]
69
+
70
+ @logger.info("Waiting for Machine '#{name}' with IP #{server.public_ip_address} to be ready.")
71
+ server.wait_for { ready? }
72
+
73
+ @app.call(env)
74
+ end
75
+
76
+ def recover(env)
77
+ return if env['vagrant.error'].is_a?(Vagrant::Errors::VagrantError)
78
+
79
+ destroy(env) if env[:machine].provider.state.id != :not_created
80
+ end
81
+
82
+ def destroy(env)
83
+ destroy_env = env.dup
84
+ destroy_env.delete(:interrupted)
85
+ destroy_env[:config_validate] = false
86
+ destroy_env[:force_confirm_destroy] = true
87
+ env[:action_runner].run(Action.action_destroy, destroy_env)
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,33 @@
1
+ module VagrantPlugins
2
+ module Hetznercloud
3
+ module Action
4
+ # This destroys the running server.
5
+ class DestroyServer
6
+ def initialize(app, _env)
7
+ @app = app
8
+ end
9
+
10
+ def call(env)
11
+ server = env[:hetznercloud_compute].servers.get(env[:machine].id)
12
+
13
+ # Destroy the server and remove the tracking ID
14
+ env[:ui].info(I18n.t('vagrant_hetznercloud.destroying'))
15
+
16
+ begin
17
+ server.destroy
18
+ rescue Fog::Hetznercloud::Compute::InvalidRequestError => e
19
+ if e.message =~ /server should be stopped/
20
+ server.terminate(async: false)
21
+ else
22
+ raise
23
+ end
24
+ end
25
+
26
+ env[:machine].id = nil
27
+
28
+ @app.call(env)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,18 @@
1
+ module VagrantPlugins
2
+ module Hetznercloud
3
+ module Action
4
+ # This can be used with "Call" built-in to check if the machine
5
+ # is created and branch in the middleware.
6
+ class IsCreated
7
+ def initialize(app, _env)
8
+ @app = app
9
+ end
10
+
11
+ def call(env)
12
+ env[:result] = env[:machine].state.id != :not_created
13
+ @app.call(env)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ module VagrantPlugins
2
+ module Hetznercloud
3
+ module Action
4
+ # This can be used with "Call" built-in to check if the machine
5
+ # is stopped and branch in the middleware.
6
+ class IsStopped
7
+ def initialize(app, _env)
8
+ @app = app
9
+ end
10
+
11
+ def call(env)
12
+ env[:result] = env[:machine].state.id == :off
13
+ @app.call(env)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,23 @@
1
+ module VagrantPlugins
2
+ module Hetznercloud
3
+ module Action
4
+ class ListImages
5
+ def initialize(app, _env)
6
+ @app = app
7
+ end
8
+
9
+ def call(env)
10
+ compute = env[:hetznercloud_compute]
11
+
12
+ env[:ui].info(format('%-37s %-26s %-7s %-36s %s', 'Image ID', 'Created At', 'Type', 'Description', 'Image Name'), prefix: false)
13
+ compute.images.sort_by(&:name).each do |image|
14
+ created_at = Time.parse(image.created)
15
+ env[:ui].info(format('%-37s %-26s %-7s %-36s %s', image.identity, created_at, image.type, image.description, image.name), prefix: false)
16
+ end
17
+
18
+ @app.call(env)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,16 @@
1
+ module VagrantPlugins
2
+ module Hetznercloud
3
+ module Action
4
+ class MessageAlreadyCreated
5
+ def initialize(app, _env)
6
+ @app = app
7
+ end
8
+
9
+ def call(env)
10
+ env[:ui].info(I18n.t('vagrant_hetznercloud.already_status', status: 'created'))
11
+ @app.call(env)
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ module VagrantPlugins
2
+ module Hetznercloud
3
+ module Action
4
+ class MessageNotCreated
5
+ def initialize(app, _env)
6
+ @app = app
7
+ end
8
+
9
+ def call(env)
10
+ env[:ui].info(I18n.t('vagrant_hetznercloud.not_created'))
11
+ @app.call(env)
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ module VagrantPlugins
2
+ module Hetznercloud
3
+ module Action
4
+ class MessageWillNotDestroy
5
+ def initialize(app, _env)
6
+ @app = app
7
+ end
8
+
9
+ def call(env)
10
+ env[:ui].info(I18n.t('vagrant_hetznercloud.will_not_destroy', name: env[:machine].name))
11
+ @app.call(env)
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,51 @@
1
+ module VagrantPlugins
2
+ module Hetznercloud
3
+ module Action
4
+ # This action reads the SSH info for the machine and puts it into the
5
+ # `:machine_ssh_info` key in the environment.
6
+ class ReadSSHInfo
7
+ def initialize(app, _env)
8
+ @app = app
9
+ @logger = Log4r::Logger.new('vagrant_hetznercloud::action::read_ssh_info')
10
+ end
11
+
12
+ def call(env)
13
+ env[:machine_ssh_info] = read_ssh_info(env[:hetznercloud_compute], env[:machine])
14
+
15
+ @app.call(env)
16
+ end
17
+
18
+ def read_ssh_info(hetznercloud, machine)
19
+ return nil if machine.id.nil?
20
+
21
+ # Find the machine
22
+ server = hetznercloud.servers.get(machine.id)
23
+ if server.nil?
24
+ # The machine can't be found
25
+ @logger.info("Machine couldn't be found, assuming it got destroyed.")
26
+ machine.id = nil
27
+ return nil
28
+ end
29
+
30
+ # read attribute override
31
+ ssh_host_attribute = machine.provider_config.ssh_host_attribute
32
+ # default host attributes to try. NOTE: Order matters!
33
+ ssh_attrs = %i[public_ip_address]
34
+ ssh_attrs = (Array(ssh_host_attribute) + ssh_attrs).uniq if ssh_host_attribute
35
+ # try each attribute, get out on first value
36
+ host = nil
37
+ while !host && (attr = ssh_attrs.shift)
38
+ begin
39
+ host = server.send(attr)
40
+ rescue NoMethodError
41
+ @logger.info("SSH host attribute not found #{attr}")
42
+ end
43
+ end
44
+
45
+ { host: host, port: 22, username: 'root' }
46
+
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,36 @@
1
+ module VagrantPlugins
2
+ module Hetznercloud
3
+ module Action
4
+ # This action reads the state of the machine and puts it in the
5
+ # `:machine_state_id` key in the environment.
6
+ class ReadState
7
+ def initialize(app, _env)
8
+ @app = app
9
+ @logger = Log4r::Logger.new('vagrant_hetznercloud::action::read_state')
10
+ end
11
+
12
+ def call(env)
13
+ env[:machine_state_id] = read_state(env[:hetznercloud_compute], env[:machine])
14
+
15
+ @app.call(env)
16
+ end
17
+
18
+ def read_state(hetznercloud, machine)
19
+ return :not_created if machine.id.nil?
20
+
21
+ # Find the machine
22
+ server = hetznercloud.servers.get(machine.id)
23
+ if server.nil?
24
+ # The machine can't be found
25
+ @logger.info('Machine not found, assuming it got destroyed.')
26
+ machine.id = nil
27
+ return :not_created
28
+ end
29
+
30
+ # Return the state
31
+ server.status.to_sym
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,90 @@
1
+ require 'vagrant/util/retryable'
2
+ require 'vagrant-hetznercloud/util/timer'
3
+
4
+ module VagrantPlugins
5
+ module Hetznercloud
6
+ module Action
7
+ # This starts a stopped server.
8
+ class StartServer
9
+ include Vagrant::Util::Retryable
10
+
11
+ def initialize(app, _env)
12
+ @app = app
13
+ @logger = Log4r::Logger.new('vagrant_hetznercloud::action::start_server')
14
+ end
15
+
16
+ def call(env)
17
+ # Initialize metrics if they haven't been
18
+ env[:metrics] ||= {}
19
+
20
+ config = env[:machine].provider_config
21
+
22
+ server = env[:hetznercloud_compute].servers.get(env[:machine].id)
23
+
24
+ env[:ui].info(I18n.t('vagrant_hetznercloud.starting'))
25
+
26
+ begin
27
+ server.poweron
28
+ rescue Fog::Hetznercloud::Compute::Error => e
29
+ raise Errors::FogError, message: e.message
30
+ end
31
+
32
+ # Wait for the server to be ready first
33
+ env[:metrics]['server_ready_time'] = Util::Timer.time do
34
+ tries = config.server_ready_timeout / 2
35
+
36
+ env[:ui].info(I18n.t('vagrant_hetznercloud.waiting_for_ready'))
37
+ begin
38
+ retryable(on: Fog::Errors::TimeoutError, tries: tries) do
39
+ # If we're interrupted don't worry about waiting
40
+ next if env[:interrupted]
41
+
42
+ # Wait for the server to be ready
43
+ server.wait_for(2, config.server_check_interval) { ready? }
44
+ end
45
+ rescue Fog::Errors::TimeoutError
46
+ # Notify the user
47
+ raise Errors::ServerReadyTimeout,
48
+ timeout: config.server_ready_timeout
49
+ end
50
+ end
51
+
52
+ @logger.info("Time to server ready: #{env[:metrics]['server_ready_time']}")
53
+
54
+ unless env[:interrupted]
55
+ env[:metrics]['server_ssh_time'] = Util::Timer.time do
56
+ # Wait for SSH to be ready.
57
+ env[:ui].info(I18n.t('vagrant_hetznercloud.waiting_for_ssh'))
58
+ network_ready_retries = 0
59
+ network_ready_retries_max = 10
60
+ loop do
61
+ # If we're interrupted then just back out
62
+ break if env[:interrupted]
63
+ # When a server comes up, it's networking may not be ready by
64
+ # the time we connect.
65
+ begin
66
+ break if env[:machine].communicate.ready?
67
+ rescue
68
+ if network_ready_retries < network_ready_retries_max
69
+ network_ready_retries += 1
70
+ @logger.warn(I18n.t('vagrant_hetznercloud.waiting_for_ssh, retrying'))
71
+ else
72
+ raise
73
+ end
74
+ end
75
+ sleep 2
76
+ end
77
+ end
78
+
79
+ @logger.info("Time for SSH ready: #{env[:metrics]['server_ssh_time']}")
80
+
81
+ # Ready and booted!
82
+ env[:ui].info(I18n.t('vagrant_hetznercloud.ready'))
83
+ end
84
+
85
+ @app.call(env)
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end