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.
- checksums.yaml +7 -0
- data/Gemfile +9 -0
- data/LICENSE +201 -0
- data/README.md +111 -0
- data/Rakefile +6 -0
- data/example_box/metadata.json +3 -0
- data/examples/Vagrantfile +63 -0
- data/hetznercloud.box +0 -0
- data/lib/vagrant-hetznercloud.rb +18 -0
- data/lib/vagrant-hetznercloud/action.rb +189 -0
- data/lib/vagrant-hetznercloud/action/connect_hetznercloud.rb +33 -0
- data/lib/vagrant-hetznercloud/action/create_server.rb +92 -0
- data/lib/vagrant-hetznercloud/action/destroy_server.rb +33 -0
- data/lib/vagrant-hetznercloud/action/is_created.rb +18 -0
- data/lib/vagrant-hetznercloud/action/is_stopped.rb +18 -0
- data/lib/vagrant-hetznercloud/action/list_images.rb +23 -0
- data/lib/vagrant-hetznercloud/action/message_already_created.rb +16 -0
- data/lib/vagrant-hetznercloud/action/message_not_created.rb +16 -0
- data/lib/vagrant-hetznercloud/action/message_will_not_destroy.rb +16 -0
- data/lib/vagrant-hetznercloud/action/read_ssh_info.rb +51 -0
- data/lib/vagrant-hetznercloud/action/read_state.rb +36 -0
- data/lib/vagrant-hetznercloud/action/start_server.rb +90 -0
- data/lib/vagrant-hetznercloud/action/stop_server.rb +25 -0
- data/lib/vagrant-hetznercloud/action/warn_networks.rb +19 -0
- data/lib/vagrant-hetznercloud/command/images.rb +20 -0
- data/lib/vagrant-hetznercloud/command/root.rb +61 -0
- data/lib/vagrant-hetznercloud/config.rb +145 -0
- data/lib/vagrant-hetznercloud/errors.rb +21 -0
- data/lib/vagrant-hetznercloud/plugin.rb +77 -0
- data/lib/vagrant-hetznercloud/provider.rb +48 -0
- data/lib/vagrant-hetznercloud/util/timer.rb +17 -0
- data/lib/vagrant-hetznercloud/version.rb +5 -0
- data/locales/en.yml +90 -0
- data/vagrant-hetznercloud.gemspec +30 -0
- 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
|