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,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
@@ -0,0 +1,5 @@
1
+ module VagrantPlugins
2
+ module Hetznercloud
3
+ VERSION = '0.0.1'
4
+ end
5
+ end