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,25 @@
|
|
|
1
|
+
module VagrantPlugins
|
|
2
|
+
module Hetznercloud
|
|
3
|
+
module Action
|
|
4
|
+
# This stops the running server.
|
|
5
|
+
class StopServer
|
|
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
|
+
if env[:machine].state.id == :off
|
|
14
|
+
env[:ui].info(I18n.t('vagrant_hetznercloud.already_status', status: env[:machine].state.id))
|
|
15
|
+
else
|
|
16
|
+
env[:ui].info(I18n.t('vagrant_hetznercloud.stopping'))
|
|
17
|
+
server.poweroff(async: false)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
@app.call(env)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module VagrantPlugins
|
|
2
|
+
module Hetznercloud
|
|
3
|
+
module Action
|
|
4
|
+
class WarnNetworks
|
|
5
|
+
def initialize(app, _env)
|
|
6
|
+
@app = app
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def call(env)
|
|
10
|
+
if env[:machine].config.vm.networks.length > 1
|
|
11
|
+
env[:ui].warn(I18n.t('vagrant_hetznercloud.warn_networks'))
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
@app.call(env)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module VagrantPlugins
|
|
2
|
+
module Hetznercloud
|
|
3
|
+
module Command
|
|
4
|
+
class Images < Vagrant.plugin('2', :command)
|
|
5
|
+
def execute
|
|
6
|
+
opts = OptionParser.new do |o|
|
|
7
|
+
o.banner = 'Usage: vagrant hetznercloud images [options]'
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
argv = parse_options(opts)
|
|
11
|
+
return unless argv
|
|
12
|
+
|
|
13
|
+
with_target_vms(argv, provider: :hetznercloud) do |machine|
|
|
14
|
+
machine.action('list_images')
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
require 'vagrant-hetznercloud/action'
|
|
2
|
+
|
|
3
|
+
module VagrantPlugins
|
|
4
|
+
module Hetznercloud
|
|
5
|
+
module Command
|
|
6
|
+
class Root < Vagrant.plugin('2', :command)
|
|
7
|
+
def self.synopsis
|
|
8
|
+
'queries Hetznercloud for available resources'
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def initialize(argv, env)
|
|
12
|
+
@main_args, @sub_command, @sub_args = split_main_and_subcommand(argv)
|
|
13
|
+
|
|
14
|
+
@subcommands = Vagrant::Registry.new
|
|
15
|
+
@subcommands.register(:images) do
|
|
16
|
+
require File.expand_path('../images', __FILE__)
|
|
17
|
+
Images
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
super(argv, env)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def execute
|
|
24
|
+
if @main_args.include?('-h') || @main_args.include?('--help')
|
|
25
|
+
# Print the help for all the hetznercloud commands.
|
|
26
|
+
return help
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
command_class = @subcommands.get(@sub_command.to_sym) if @sub_command
|
|
30
|
+
return help if !command_class || !@sub_command
|
|
31
|
+
@logger.debug("Invoking command class: #{command_class} #{@sub_args.inspect}")
|
|
32
|
+
|
|
33
|
+
# Initialize and execute the command class
|
|
34
|
+
command_class.new(@sub_args, @env).execute
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def help
|
|
38
|
+
opts = OptionParser.new do |o|
|
|
39
|
+
o.banner = 'Usage: vagrant hetznercloud <subcommand> [<args>]'
|
|
40
|
+
o.separator ''
|
|
41
|
+
o.separator 'Available subcommands:'
|
|
42
|
+
|
|
43
|
+
# Add the available subcommands as separators in order to print them
|
|
44
|
+
# out as well.
|
|
45
|
+
keys = []
|
|
46
|
+
@subcommands.each { |key, _value| keys << key.to_s }
|
|
47
|
+
|
|
48
|
+
keys.sort.each do |key|
|
|
49
|
+
o.separator " #{key}"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
o.separator ''
|
|
53
|
+
o.separator 'For help on any individual subcommand run `vagrant hetznercloud <subcommand> -h`'
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
@env.ui.info(opts.help, prefix: false)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
require 'toml'
|
|
2
|
+
|
|
3
|
+
module VagrantPlugins
|
|
4
|
+
module Hetznercloud
|
|
5
|
+
class Config < Vagrant.plugin('2', :config)
|
|
6
|
+
# The user_data file to use (e.g. for bootstrapping)
|
|
7
|
+
#
|
|
8
|
+
# @return [String]
|
|
9
|
+
attr_accessor :user_data
|
|
10
|
+
|
|
11
|
+
# The type of the server to launch, such as 'cx11'. Defaults to 'cx11'.
|
|
12
|
+
#
|
|
13
|
+
# @return [String]
|
|
14
|
+
attr_accessor :server_type
|
|
15
|
+
|
|
16
|
+
# The image ID or name like 'centos-7'.
|
|
17
|
+
#
|
|
18
|
+
# @return [String]
|
|
19
|
+
attr_accessor :image
|
|
20
|
+
|
|
21
|
+
# The name of the server. Optional. Can be auto generated like h-xxxxxx.
|
|
22
|
+
#
|
|
23
|
+
# @return [String]
|
|
24
|
+
attr_accessor :name
|
|
25
|
+
|
|
26
|
+
# The name of the Hetznercloud location to create the server in. It can also be
|
|
27
|
+
# configured with HETZNERCLOUD_LOCATION environment variable.
|
|
28
|
+
#
|
|
29
|
+
# @return [String]
|
|
30
|
+
attr_accessor :location
|
|
31
|
+
|
|
32
|
+
# The name of the Hetznercloud datacenter to create the server in. It can also be
|
|
33
|
+
# configured with HETZNERCLOUD_DATACENTER environment variable.
|
|
34
|
+
#
|
|
35
|
+
# @return [String]
|
|
36
|
+
attr_accessor :datacenter
|
|
37
|
+
|
|
38
|
+
# The interval to wait for checking a server's state. Defaults to 2
|
|
39
|
+
# seconds.
|
|
40
|
+
#
|
|
41
|
+
# @return [Fixnum]
|
|
42
|
+
attr_accessor :server_check_interval
|
|
43
|
+
|
|
44
|
+
# The timeout to wait for a server to become ready. Defaults to 120
|
|
45
|
+
# seconds.
|
|
46
|
+
#
|
|
47
|
+
# @return [Fixnum]
|
|
48
|
+
attr_accessor :server_ready_timeout
|
|
49
|
+
|
|
50
|
+
# Specifies which address to connect to with ssh.
|
|
51
|
+
# Must be one of:
|
|
52
|
+
# - :public_ip_address
|
|
53
|
+
# This attribute also accepts an array of symbols.
|
|
54
|
+
#
|
|
55
|
+
# @return [Symbol]
|
|
56
|
+
attr_accessor :ssh_host_attribute
|
|
57
|
+
|
|
58
|
+
# The API token to access Hetznercloud. It can also be configured with
|
|
59
|
+
# HETZNERCLOUD_TOKEN environment variable. If not specified it uses the
|
|
60
|
+
# hcloud cli config file (.config/hcloud/cli.toml)
|
|
61
|
+
#
|
|
62
|
+
# @return [String]
|
|
63
|
+
attr_accessor :token
|
|
64
|
+
|
|
65
|
+
# ssh keys as array (e.g. ['testkey'])
|
|
66
|
+
#
|
|
67
|
+
# @return [Array]
|
|
68
|
+
attr_accessor :ssh_keys
|
|
69
|
+
|
|
70
|
+
# active context in the hcloud config file
|
|
71
|
+
# If empty default context is used.
|
|
72
|
+
#
|
|
73
|
+
# @return [String]
|
|
74
|
+
attr_accessor :active_context
|
|
75
|
+
|
|
76
|
+
def initialize
|
|
77
|
+
@user_data = UNSET_VALUE
|
|
78
|
+
@server_type = UNSET_VALUE
|
|
79
|
+
@image = UNSET_VALUE
|
|
80
|
+
@name = UNSET_VALUE
|
|
81
|
+
@location = UNSET_VALUE
|
|
82
|
+
@datacenter = UNSET_VALUE
|
|
83
|
+
@server_check_interval = UNSET_VALUE
|
|
84
|
+
@server_ready_timeout = UNSET_VALUE
|
|
85
|
+
@ssh_host_attribute = UNSET_VALUE
|
|
86
|
+
@token = UNSET_VALUE
|
|
87
|
+
@ssh_keys = UNSET_VALUE
|
|
88
|
+
@active_context = UNSET_VALUE
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def finalize!
|
|
92
|
+
@user_data = nil if @user_data == UNSET_VALUE
|
|
93
|
+
@server_type = 'cx11' if @server_type == UNSET_VALUE
|
|
94
|
+
@image = 'centos-7' if @image == UNSET_VALUE
|
|
95
|
+
|
|
96
|
+
if @name == UNSET_VALUE
|
|
97
|
+
require 'securerandom'
|
|
98
|
+
@name = "h-#{SecureRandom.hex(3)}"
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
@location = (ENV['HETZNERCLOUD_LOCATION']) if @location == UNSET_VALUE
|
|
102
|
+
@datacenter = (ENV['HETZNERCLOUD_DATACENTER']) if @datacenter == UNSET_VALUE
|
|
103
|
+
@server_check_interval = 2 if @server_check_interval == UNSET_VALUE
|
|
104
|
+
@server_ready_timeout = 120 if @server_ready_timeout == UNSET_VALUE
|
|
105
|
+
@ssh_host_attribute = nil if @ssh_host_attribute == UNSET_VALUE
|
|
106
|
+
@active_context = nil if @active_context == UNSET_VALUE
|
|
107
|
+
@token = (ENV['HETZNERCLOUD_TOKEN'] || get_token(@active_context)) if @token == UNSET_VALUE
|
|
108
|
+
@ssh_keys = [] if @ssh_keys == UNSET_VALUE
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def validate(_machine)
|
|
112
|
+
errors = _detected_errors
|
|
113
|
+
|
|
114
|
+
errors << I18n.t('vagrant_hetznercloud.config.token_required') if @token.nil?
|
|
115
|
+
errors << I18n.t('vagrant_hetznercloud.config.ssh_keys_required') if @token.nil?
|
|
116
|
+
|
|
117
|
+
{ 'Hetznercloud Provider' => errors }
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Get the token from the hckoud cli config file (if it exists)
|
|
121
|
+
def get_token(active_context = nil)
|
|
122
|
+
|
|
123
|
+
# The config file
|
|
124
|
+
config_file = ENV['HOME'] + "/.config/hcloud/cli.toml"
|
|
125
|
+
return nil unless File.exists?(config_file)
|
|
126
|
+
|
|
127
|
+
# Load the file
|
|
128
|
+
config_data = TOML.load_file(config_file)
|
|
129
|
+
|
|
130
|
+
# default context if not specified
|
|
131
|
+
active_context = config_data["active_context"] if active_context.nil?
|
|
132
|
+
|
|
133
|
+
context = config_data["contexts"].find { |context|
|
|
134
|
+
context["name"] == active_context
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
# Not found
|
|
138
|
+
return nil if context.nil?
|
|
139
|
+
return nil unless context.has_key?('token')
|
|
140
|
+
return context["token"]
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module VagrantPlugins
|
|
2
|
+
module Hetznercloud
|
|
3
|
+
module Errors
|
|
4
|
+
class VagrantHetznercloudError < Vagrant::Errors::VagrantError
|
|
5
|
+
error_namespace('vagrant_hetznercloud.errors')
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
class FogError < VagrantHetznercloudError
|
|
9
|
+
error_key(:fog_error)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
class InternalFogError < VagrantHetznercloudError
|
|
13
|
+
error_key(:internal_fog_error)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
class ServerReadyTimeout < VagrantHetznercloudError
|
|
17
|
+
error_key(:server_ready_timeout)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
begin
|
|
2
|
+
require 'vagrant'
|
|
3
|
+
rescue LoadError
|
|
4
|
+
raise 'The Hetznercloud provider must be run within Vagrant.'
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
# This is a sanity check to make sure no one is attempting to install
|
|
8
|
+
# this into an early Vagrant version.
|
|
9
|
+
if Vagrant::VERSION < '1.5.0'
|
|
10
|
+
raise 'Hetznercloud provider is only compatible with Vagrant 1.5+'
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
module VagrantPlugins
|
|
14
|
+
module Hetznercloud
|
|
15
|
+
class Plugin < Vagrant.plugin('2')
|
|
16
|
+
name 'Hetznercloud'
|
|
17
|
+
description <<-DESC
|
|
18
|
+
This plugin installs a provider that allows Vagrant to manage
|
|
19
|
+
machines in Hetznercloud.
|
|
20
|
+
DESC
|
|
21
|
+
|
|
22
|
+
config(:hetznercloud, :provider) do
|
|
23
|
+
require_relative 'config'
|
|
24
|
+
Config
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
provider(:hetznercloud, parallel: true, box_optional: true) do
|
|
28
|
+
# Setup logging and i18n
|
|
29
|
+
setup_logging
|
|
30
|
+
setup_i18n
|
|
31
|
+
|
|
32
|
+
# Return the provider
|
|
33
|
+
require_relative 'provider'
|
|
34
|
+
Provider
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
command(:hetznercloud, primary: false) do
|
|
38
|
+
require_relative 'command/root'
|
|
39
|
+
Command::Root
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# This initializes the internationalization strings.
|
|
43
|
+
def self.setup_i18n
|
|
44
|
+
I18n.load_path << File.expand_path('locales/en.yml', Hetznercloud.source_root)
|
|
45
|
+
I18n.reload!
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# This sets up our log level to be whatever VAGRANT_LOG is.
|
|
49
|
+
def self.setup_logging
|
|
50
|
+
require 'log4r'
|
|
51
|
+
|
|
52
|
+
level = nil
|
|
53
|
+
begin
|
|
54
|
+
level = Log4r.const_get(ENV['VAGRANT_LOG'].upcase)
|
|
55
|
+
rescue NameError
|
|
56
|
+
# This means that the logging constant wasn't found,
|
|
57
|
+
# which is fine. We just keep `level` as `nil`. But
|
|
58
|
+
# we tell the user.
|
|
59
|
+
level = nil
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Some constants, such as "true" resolve to booleans, so the
|
|
63
|
+
# above error checking doesn't catch it. This will check to make
|
|
64
|
+
# sure that the log level is an integer, as Log4r requires.
|
|
65
|
+
level = nil unless level.is_a?(Integer)
|
|
66
|
+
|
|
67
|
+
# Set the logging level on all "vagrant" namespaced
|
|
68
|
+
# logs as long as we have a valid level.
|
|
69
|
+
if level
|
|
70
|
+
logger = Log4r::Logger.new('vagrant_hetznercloud')
|
|
71
|
+
logger.outputters = Log4r::Outputter.stderr
|
|
72
|
+
logger.level = level
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
module VagrantPlugins
|
|
2
|
+
module Hetznercloud
|
|
3
|
+
class Provider < Vagrant.plugin('2', :provider)
|
|
4
|
+
def initialize(machine)
|
|
5
|
+
@machine = machine
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def action(name)
|
|
9
|
+
# Attempt to get the action method from the Action class if it
|
|
10
|
+
# exists, otherwise return nil to show that we don't support the
|
|
11
|
+
# given action.
|
|
12
|
+
action_method = "action_#{name}"
|
|
13
|
+
return Action.send(action_method) if Action.respond_to?(action_method)
|
|
14
|
+
nil
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def ssh_info
|
|
18
|
+
# Run a custom action called "read_ssh_info" which does what it
|
|
19
|
+
# says and puts the resulting SSH info into the `:machine_ssh_info`
|
|
20
|
+
# key in the environment.
|
|
21
|
+
env = @machine.action('read_ssh_info', lock: false)
|
|
22
|
+
env[:machine_ssh_info]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def state
|
|
26
|
+
# Run a custom action we define called "read_state" which does
|
|
27
|
+
# what it says. It puts the state in the `:machine_state_id`
|
|
28
|
+
# key in the environment.
|
|
29
|
+
env = @machine.action('read_state', lock: false)
|
|
30
|
+
|
|
31
|
+
state_id = env[:machine_state_id]
|
|
32
|
+
|
|
33
|
+
# Get the short and long description
|
|
34
|
+
short = I18n.t("vagrant_hetznercloud.states.short_#{state_id}")
|
|
35
|
+
long = I18n.t("vagrant_hetznercloud.states.long_#{state_id}")
|
|
36
|
+
|
|
37
|
+
# Return the MachineState object
|
|
38
|
+
Vagrant::MachineState.new(state_id, short, long)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def to_s
|
|
42
|
+
id = @machine.id.nil? ? 'new' : @machine.id
|
|
43
|
+
name = @machine.name.nil? ? 'no name' : @machine.name
|
|
44
|
+
"Hetznercloud (#{id} with name #{name})"
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module VagrantPlugins
|
|
2
|
+
module Hetznercloud
|
|
3
|
+
module Util
|
|
4
|
+
class Timer
|
|
5
|
+
# A basic utility method that times the execution of the given
|
|
6
|
+
# block and returns it.
|
|
7
|
+
def self.time
|
|
8
|
+
start_time = Time.now.to_f
|
|
9
|
+
yield
|
|
10
|
+
end_time = Time.now.to_f
|
|
11
|
+
|
|
12
|
+
end_time - start_time
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|