vagrant-ansible_auto 0.1.5 → 0.2.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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +26 -0
  3. data/Gemfile +2 -0
  4. data/Gemfile.lock +172 -0
  5. data/README.md +53 -12
  6. data/Rakefile +9 -7
  7. data/TODO.md +14 -0
  8. data/Vagrantfile +37 -15
  9. data/lib/vagrant/ansible_auto/cap/guest/posix/check_open_port.rb +22 -3
  10. data/lib/vagrant/ansible_auto/cap/guest/posix/executable_installed.rb +10 -2
  11. data/lib/vagrant/ansible_auto/cap/guest/posix/gateway_addresses.rb +8 -23
  12. data/lib/vagrant/ansible_auto/cap/guest/posix/private_key.rb +16 -1
  13. data/lib/vagrant/ansible_auto/cap/guest/posix/public_key.rb +18 -3
  14. data/lib/vagrant/ansible_auto/cap/guest/posix/ssh_server_address.rb +22 -12
  15. data/lib/vagrant/ansible_auto/cap/guest/posix.rb +16 -0
  16. data/lib/vagrant/ansible_auto/command/inventory.rb +37 -11
  17. data/lib/vagrant/ansible_auto/command/root.rb +34 -31
  18. data/lib/vagrant/ansible_auto/config.rb +74 -33
  19. data/lib/vagrant/ansible_auto/errors.rb +30 -1
  20. data/lib/vagrant/ansible_auto/host.rb +123 -34
  21. data/lib/vagrant/ansible_auto/inventory.rb +196 -34
  22. data/lib/vagrant/ansible_auto/plugin.rb +23 -8
  23. data/lib/vagrant/ansible_auto/provisioner.rb +121 -79
  24. data/lib/vagrant/ansible_auto/util/config.rb +61 -0
  25. data/lib/vagrant/ansible_auto/util/hash_with_indifferent_access.rb +58 -0
  26. data/lib/vagrant/ansible_auto/util/keys.rb +49 -0
  27. data/lib/vagrant/ansible_auto/util/shell_quote.rb +24 -0
  28. data/lib/vagrant/ansible_auto/version.rb +2 -1
  29. data/lib/vagrant/ansible_auto.rb +15 -0
  30. data/locales/en.yml +34 -0
  31. data/spec/spec_helper.rb +5 -85
  32. data/spec/support/context.rb +111 -0
  33. data/spec/support/matchers.rb +45 -0
  34. data/spec/unit/vagrant/ansible_auto/config_spec.rb +72 -0
  35. data/spec/unit/vagrant/ansible_auto/host_spec.rb +131 -0
  36. data/spec/unit/vagrant/ansible_auto/inventory_spec.rb +349 -0
  37. data/spec/unit/vagrant/ansible_auto/provisioner_spec.rb +248 -0
  38. data/spec/unit/vagrant/ansible_auto/util/config_spec.rb +63 -0
  39. data/spec/unit/vagrant/ansible_auto/util/keys_spec.rb +66 -0
  40. data/vagrant-ansible_auto.gemspec +6 -4
  41. data/vagrant-spec.config.rb +3 -0
  42. data/yard/extensions.rb +45 -0
  43. metadata +36 -11
  44. data/Vagrantfile2 +0 -4
  45. data/Vagrantfile3 +0 -8
  46. data/Vagrantfile4 +0 -31
  47. data/lib/vagrant/ansible_auto/cap/guest/posix/bash_installed.rb +0 -30
  48. data/lib/vagrant/ansible_auto/util.rb +0 -24
  49. data/spec/vagrant/ansible_auto/host_spec.rb +0 -43
  50. data/spec/vagrant/ansible_auto/inventory_spec.rb +0 -79
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'set'
3
4
 
4
5
  module VagrantPlugins
@@ -6,22 +7,32 @@ module VagrantPlugins
6
7
  module Cap
7
8
  module Guest
8
9
  module POSIX
10
+ # Attempt to find the IP address and port of an active SSH server
11
+ # @todo test not merely that a host and port combo is open, but that
12
+ # there's an SSH server listening there
9
13
  class SSHServerAddress
10
14
  class << self
15
+ # @param [Vagrant::Machine] machine a guest machine
16
+ # @param [Vagrant::Machine,nil] target_machine a guest machine
17
+ # @return [Array<(String, Integer)>] the host and port on which
18
+ # an SSH server may be listening
11
19
  def ssh_server_address(machine, target_machine = nil)
12
20
  with_open_ports(machine, target_machine).first
13
21
  end
14
22
 
15
- private
16
-
23
+ # @param (see #ssh_server_address)
24
+ # @return [Array<Array<(String, Integer)>>] a list of host and
25
+ # port pairs on which an SSH server may be listening
17
26
  def ssh_server_addresses(machine, target_machine = nil)
18
27
  with_open_ports(machine, target_machine).to_a
19
28
  end
20
29
 
30
+ private
31
+
21
32
  def with_open_ports(machine, target_machine = nil)
22
33
  return enum_for(__method__, machine, target_machine) unless block_given?
23
34
 
24
- return unless machine.guest.capability?(:check_open_port)
35
+ return unless machine.guest.capability?(:port_open?)
25
36
 
26
37
  target_machine ||= machine
27
38
  ssh_info = target_machine.ssh_info
@@ -30,13 +41,13 @@ module VagrantPlugins
30
41
  with_candidate_addresses(target_machine) do |host, port|
31
42
  port ||= default_port
32
43
 
33
- if machine.guest.capability(:check_open_port, host, port)
44
+ if machine.guest.capability(:port_open?, host, port)
34
45
  yield host, port
35
46
  end
36
47
  end
37
48
  end
38
49
 
39
- network_type_precedence_map = Hash[[:forwarded_port, :public_network, :private_network].each_with_index.map { |type, i| [type, i] }]
50
+ network_type_precedence_map = Hash[%i[forwarded_port public_network private_network].each_with_index.map { |type, i| [type, i] }]
40
51
  define_method(:network_type_precedence) do |type|
41
52
  network_type_precedence_map[type]
42
53
  end
@@ -62,18 +73,17 @@ module VagrantPlugins
62
73
  machine.config.vm.networks.sort_by { |(type, _)| network_type_precedence(type) }.each do |type, info|
63
74
  case type
64
75
  when :private_network, :public_network
65
- has_routable_ip = true
76
+ has_routable_ip = true
66
77
 
67
- yield_unseen_candidate.call([info[:ip]]) if info.key?(:ip)
78
+ yield_unseen_candidate.call([info[:ip]]) if info.key?(:ip)
68
79
  when :forwarded_port
69
- # TODO: the `:id' restriction might not be right.
70
- if info[:protocol] == 'tcp' and info[:id] == 'ssh'
71
- yield_unseen_candidate.call([info[:host_ip], info[:host]])
72
- end
80
+ if info[:protocol] == 'tcp' && info[:id] == 'ssh'
81
+ yield_unseen_candidate.call([info[:host_ip], info[:host]])
82
+ end
73
83
  end
74
84
  end
75
85
 
76
- return if has_routable_ip or !machine.guest.capability?(:gateway_addresses)
86
+ return if has_routable_ip || !machine.guest.capability?(:gateway_addresses)
77
87
 
78
88
  machine.guest.capability(:gateway_addresses).each do |gateway_address|
79
89
  yield_unseen_candidate.call(gateway_address)
@@ -0,0 +1,16 @@
1
+
2
+ # Here for documentation purposes only
3
+
4
+ module VagrantPlugins
5
+ module AnsibleAuto
6
+ # Capabilities on guests and hosts
7
+ module Cap
8
+ # Capabilities on guests
9
+ module Guest
10
+ # Capabilities on POSIX hosts
11
+ module POSIX
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -1,44 +1,70 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'optparse'
3
4
 
4
5
  module VagrantPlugins
5
6
  module AnsibleAuto
6
7
  module Command
8
+ # Vagrant +ansible inventory+ subcommand
7
9
  class Inventory < Vagrant.plugin(2, :command)
10
+ # @return [String] summary of the +ansible inventory+ command
8
11
  def self.synopsis
9
12
  'dynamic ansible inventory'
10
13
  end
11
14
 
15
+ # Print the Ansible inventory for the current Vagrantfile.
16
+ #
17
+ # By default, the inventory is printed in Ansible's static INI
18
+ # inventory style. When the +-l/--list+ flag is present, the inventory
19
+ # is printed in Ansible's dynamic JSON inventory style.
20
+ #
21
+ # @return [Integer] the exit status of the command
12
22
  def execute
23
+ operation = :as_ini
24
+
13
25
  opts = OptionParser.new do |op|
14
26
  op.banner = 'Usage: vagrant ansible inventory [<options>]'
15
27
  op.separator ''
16
28
  op.separator 'Available options:'
17
29
 
18
- op.on('-l', '--list', 'List all hosts as json') do |_target|
19
- @env.ui.info inventory.to_json, prefix: false
20
- return 0
30
+ op.on('--ini', 'List hosts as INI (default)') do
31
+ operation = :as_ini
21
32
  end
22
33
 
23
- op.on('-h', '--help', 'Show this message') do
24
- @env.ui.info opts.help, prefix: false
25
- return 0
34
+ op.on('--json', 'List all hosts as JSON') do
35
+ operation = :as_json
36
+ end
37
+
38
+ op.on('--pretty', 'List all hosts as pretty JSON') do
39
+ operation = :as_pretty_json
26
40
  end
27
41
  end
28
42
 
29
- @argv = parse_options(opts)
43
+ machines = parse_options(opts)
30
44
 
31
- @env.ui.info inventory.to_ini, prefix: false
45
+ @env.ui.info send(operation, machines), prefix: false
32
46
 
33
47
  0
34
48
  end
35
49
 
36
50
  private
37
51
 
38
- def inventory
39
- @inventory = with_target_vms(@argv) {}.each_with_object(AnsibleAuto::Inventory.new) do |machine, inventory|
52
+ def as_ini(machines)
53
+ build_inventory(machines).to_ini
54
+ end
55
+
56
+ def as_json(machines)
57
+ build_inventory(machines).to_json
58
+ end
59
+
60
+ def as_pretty_json(machines)
61
+ JSON.pretty_generate(build_inventory(machines))
62
+ end
63
+
64
+ def build_inventory(machines)
65
+ with_target_vms(machines) {}.each_with_object(AnsibleAuto::Inventory.new) do |machine, inventory|
40
66
  unless machine.state.id == :running
41
- @env.ui.warn "machine #{machine.name} is not running; falling back to default hostvar values"
67
+ @env.ui.warn "machine #{machine.name} is not running; falling back to default hostvar values", channel: :error
42
68
  end
43
69
  inventory.merge!(machine.config.ansible.inventory)
44
70
  inventory.add_host(machine)
@@ -1,57 +1,60 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'optparse'
3
4
 
5
+ require 'vagrant/ansible_auto/errors'
6
+
4
7
  module VagrantPlugins
5
8
  module AnsibleAuto
9
+ # Vagrant +ansible+ subcommand
6
10
  module Command
11
+ # Command for creating a static Ansible inventory from the machines
12
+ # defined in a Vagrantfile
7
13
  class Root < Vagrant.plugin(2, :command)
14
+ # @return [String] summary of the +ansible+ command
8
15
  def self.synopsis
9
16
  'build ansible inventory'
10
17
  end
11
18
 
12
- def initialize(argv, env)
13
- super
14
-
15
- @main_args, @sub_command, @sub_args = split_main_and_subcommand(argv)
16
-
17
- @subcommands = Vagrant::Registry.new
18
-
19
- @subcommands.register(:inventory) do
20
- require_relative 'inventory'
21
- Inventory
22
- end
23
- end
24
-
19
+ # Execute the +ansible+ command
20
+ # @return [Integer] the exit status of the command
25
21
  def execute
26
- if @main_args.include?('-h') || @main_args.include?('--help')
27
- return help
22
+ @argv, subcommand_name, subcommand_argv = split_main_and_subcommand(@argv)
23
+
24
+ if subcommand_name.nil?
25
+ @argv = ['-h'] if @argv.empty?
26
+ return parse_options(prepare_options)
27
+ elsif subcommands.key? subcommand_name.to_sym
28
+ return subcommands.get(subcommand_name.to_sym).new(subcommand_argv, @env.dup).execute
29
+ else
30
+ raise Errors::UnrecognizedCommandError, command: subcommand_name
28
31
  end
29
-
30
- command_class = @subcommands.get(@sub_command.to_sym) if @sub_command
31
- return help if !command_class || !@sub_command
32
- @logger.debug("Invoking command class: #{command_class} #{@sub_args.inspect}")
33
-
34
- command_class.new(@sub_args, @env).execute
35
32
  end
36
33
 
37
- def help
38
- opts = OptionParser.new do |o|
34
+ private
35
+
36
+ def prepare_options
37
+ OptionParser.new do |o|
39
38
  o.banner = 'Usage: vagrant ansible <subcommand> [<options>]'
40
39
  o.separator ''
41
40
  o.separator 'Available subcommands:'
42
41
 
43
- keys = []
44
- @subcommands.each { |key, _value| keys << key.to_s }
45
-
46
- keys.sort.each do |key|
47
- o.separator " #{key}"
42
+ subcommands.keys.sort.each do |k|
43
+ o.separator " #{k}"
48
44
  end
49
45
 
50
- o.separator ""
51
- o.separator "For help on any individual subcommand run `vagrant ansible <subcommand> -h`"
46
+ o.separator ''
47
+ o.separator 'For help on any individual subcommand run `vagrant ansible <subcommand> -h`'
52
48
  end
49
+ end
53
50
 
54
- @env.ui.info(opts.help, prefix: false)
51
+ def subcommands
52
+ @subcommands ||= Vagrant::Registry.new.tap do |r|
53
+ r.register(:inventory) do
54
+ require_relative 'inventory'
55
+ Inventory
56
+ end
57
+ end
55
58
  end
56
59
  end
57
60
  end
@@ -1,56 +1,109 @@
1
1
  # frozen_string_literal: true
2
- require 'vagrant/ansible_auto/inventory'
3
- require 'vagrant/ansible_auto/errors'
4
- require 'vagrant/util/deep_merge'
5
2
 
6
3
  require 'vagrant'
7
4
  require Vagrant.source_root + 'plugins/provisioners/ansible/config/guest'
8
5
 
6
+ require 'vagrant/ansible_auto/inventory'
7
+ require 'vagrant/ansible_auto/errors'
8
+ require 'vagrant/ansible_auto/util/config'
9
+
9
10
  module VagrantPlugins
10
11
  module AnsibleAuto
12
+ # Configuration for the +ansible_auto+ provisioner and +ansible+ command
11
13
  class Config < VagrantPlugins::Ansible::Config::Guest
14
+ include Util::Config
15
+
12
16
  attr_accessor :inventory, :groups, :vars, :children,
13
- :strict_host_key_checking, :host_connect_tries, :host_connect_sleep
17
+ :strict_host_key_checking, :host_connect_tries,
18
+ :host_connect_sleep, :insert_control_machine_public_key,
19
+ :upload_inventory_host_private_keys
14
20
 
15
21
  protected(:inventory=)
16
22
 
23
+ BOOLEAN = %I[
24
+ strict_host_key_checking
25
+ insert_control_machine_public_key
26
+ upload_inventory_host_private_keys
27
+ ].freeze
28
+
29
+ INTEGER = %I[
30
+ host_connect_tries
31
+ ].freeze
32
+
33
+ NUMBER = %I[
34
+ host_connect_sleep
35
+ ].freeze
36
+
17
37
  def initialize
18
38
  super
19
- @inventory = Inventory.new
20
- @groups = UNSET_VALUE
21
- @vars = UNSET_VALUE
22
- @children = UNSET_VALUE
23
- @strict_host_key_checking = UNSET_VALUE
24
- @host_connect_tries = UNSET_VALUE
25
- @host_connect_sleep = UNSET_VALUE
26
- @__errors = []
39
+ @inventory = Inventory.new
40
+ @groups = UNSET_VALUE
41
+ @vars = UNSET_VALUE
42
+ @children = UNSET_VALUE
43
+ @strict_host_key_checking = UNSET_VALUE
44
+ @host_connect_tries = UNSET_VALUE
45
+ @host_connect_sleep = UNSET_VALUE
46
+ @insert_control_machine_public_key = UNSET_VALUE
47
+ @upload_inventory_host_private_keys = UNSET_VALUE
48
+ @__errors = []
27
49
  end
28
50
 
51
+ # Set default configuration values at the end of constructing the Vagrant
52
+ # environment
53
+ # @api private
54
+ # @return [void]
29
55
  def finalize!
30
56
  super
31
- @inventory.groups = @groups unless @groups == UNSET_VALUE
32
- @inventory.vars = @vars unless @vars == UNSET_VALUE
33
- @inventory.children = @children unless @children == UNSET_VALUE
34
- @strict_host_key_checking = false if @strict_host_key_checking == UNSET_VALUE
35
- @host_connect_tries = 10 if @host_connect_tries == UNSET_VALUE
36
- @host_connect_sleep = 2 if @host_connect_sleep == UNSET_VALUE
57
+ @inventory.groups = @groups unless @groups == UNSET_VALUE
58
+ @inventory.vars = @vars unless @vars == UNSET_VALUE
59
+ @inventory.children = @children unless @children == UNSET_VALUE
60
+ @strict_host_key_checking = false if @strict_host_key_checking == UNSET_VALUE
61
+ @host_connect_tries = 10 if @host_connect_tries == UNSET_VALUE
62
+ @host_connect_sleep = 2 if @host_connect_sleep == UNSET_VALUE
63
+ @insert_control_machine_public_key = true if @insert_control_machine_public_key == UNSET_VALUE
64
+ @upload_inventory_host_private_keys = !@insert_control_machine_public_key if @upload_inventory_host_private_keys == UNSET_VALUE
65
+
66
+ # NOTE @limit is defined in core Vagrant's Ansible config.
67
+ @limit = '*' if @limit == UNSET_VALUE
37
68
  rescue Errors::InventoryError => e
38
69
  @__errors << e.message
39
70
  end
40
71
 
72
+ # Ensure that the configuration is in a valid state
73
+ # @api private
74
+ # @return [Hash{String=>Array}] a structure containing a list of errors
75
+ # under the +ansible_auto+ key
41
76
  def validate(machine)
42
77
  super
43
78
 
44
79
  errors = _detected_errors + @__errors
45
80
 
46
- # TODO: -- test that `host_wait...' values are integers
47
- unless @strict_host_key_checking == true or @strict_host_key_checking == false
48
- errors << "strict_host_key_checking must be either true or false"
81
+ BOOLEAN.each do |o|
82
+ unless bool? instance_variable_get(:"@#{o}")
83
+ errors << "#{o} must be either true or false"
84
+ end
85
+ end
86
+
87
+ INTEGER.each do |o|
88
+ unless int? instance_variable_get(:"@#{o}")
89
+ errors << "#{o} must be an integer"
90
+ end
91
+ end
92
+
93
+ NUMBER.each do |o|
94
+ unless num? instance_variable_get(:"@#{o}")
95
+ errors << "#{o} must be a number"
96
+ end
49
97
  end
50
98
 
51
99
  { 'ansible_auto' => errors }
52
100
  end
53
101
 
102
+ # Merge two configurations
103
+ # @api private
104
+ # @param [Config] other the configuration to merge into this
105
+ # configuration
106
+ # @return [Config] the merged configuration
54
107
  def merge(other)
55
108
  return super if other.nil?
56
109
 
@@ -61,18 +114,6 @@ module VagrantPlugins
61
114
  result.inventory = inventory.merge(other.inventory)
62
115
  end
63
116
  end
64
-
65
- private
66
-
67
- def conditional_merge(a, b)
68
- if b.nil? or b == UNSET_VALUE
69
- a
70
- elsif a.nil? or a == UNSET_VALUE
71
- b
72
- else
73
- Vagrant::Util::DeepMerge.deep_merge(a, b)
74
- end
75
- end
76
117
  end
77
118
  end
78
119
  end
@@ -1,14 +1,43 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module VagrantPlugins
3
4
  module AnsibleAuto
5
+ # Error classes for the +ansible_auto+ provisioner and +ansible+ command
4
6
  module Errors
7
+ # An error representing an {Inventory} misconfiguration
5
8
  class InventoryError < Vagrant::Errors::VagrantError
6
- error_namespace('vagrant.provisioners.ansible_auto')
9
+ error_namespace('vagrant.ansible_auto.errors.inventory')
7
10
  end
8
11
 
12
+ # Raised when an Ansible inventory group is expected to exist but doesn't
9
13
  class MissingGroupError < InventoryError
10
14
  error_key(:missing_group)
11
15
  end
16
+
17
+ # Raised when a group specifies child groups that do not exist
18
+ class GroupMissingChildError < MissingGroupError
19
+ error_key(:group_missing_child)
20
+ end
21
+
22
+ # Raised when provided data can't be converted to a {Host} object
23
+ class InvalidHostTypeError < InventoryError
24
+ error_key(:invalid_host_type)
25
+ end
26
+
27
+ # Raised when a group name is disallowed
28
+ class InvalidGroupNameError < InventoryError
29
+ error_key(:invalid_group_name)
30
+ end
31
+
32
+ # Class representing {Command} errors
33
+ class CommandError < Vagrant::Errors::VagrantError
34
+ error_namespace('vagrant.ansible_auto.errors.command')
35
+ end
36
+
37
+ # Raised on receipt of an unrecognized +vagrant ansible+ subcommand
38
+ class UnrecognizedCommandError < CommandError
39
+ error_key('unrecognized_command')
40
+ end
12
41
  end
13
42
  end
14
43
  end
@@ -1,85 +1,174 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'vagrant'
3
- require 'vagrant/ansible_auto/util'
4
+
5
+ require 'vagrant/ansible_auto/util/hash_with_indifferent_access'
6
+ require 'vagrant/ansible_auto/util/keys'
4
7
 
5
8
  module VagrantPlugins
6
9
  module AnsibleAuto
10
+ # Class representing a single host in an Ansible inventory
7
11
  class Host
8
- include VagrantPlugins::AnsibleAuto::Util
9
-
10
- ANSIBLE_HOSTVARS = [:ssh_user, :ssh_host, :ssh_port, :ssh_private_key_file, :connection].freeze
11
-
12
- attr_writer(*ANSIBLE_HOSTVARS)
12
+ include Util::Keys
13
13
 
14
+ # @param [#to_s] name the name of the {Host} as it should appear in an
15
+ # {Inventory}
16
+ # @param [Hash] hostvars variables to associate with the host
14
17
  def initialize(name, hostvars = {})
15
18
  @name = name
16
19
 
17
- # Convert keys to symbols
18
- @hostvars = hostvars.each_with_object({}) { |(k, v), acc| acc[k.to_sym] = v }
20
+ # # Convert keys to symbols
21
+ # @hostvars = hostvars.each_with_object({}) { |(k, v), acc| acc[k.to_sym] = v }
22
+ self.hostvars = hostvars
19
23
  end
20
24
 
25
+ # @return [String] the name of the {Host}, default +"default"+
21
26
  def name
22
27
  @name ||= 'default'
23
28
  end
24
29
 
25
- def ssh_user
26
- @ssh_user ||= @hostvars[:ssh_user] || 'vagrant'
30
+ # @return [String] the name of the machine as a string
31
+ def inventory_hostname
32
+ @inventory_hostname ||= name.to_s
33
+ end
34
+
35
+ # @param new_name [#to_s] the name to associate with this host in the
36
+ # generated inventory
37
+ # @return (see #inventory_hostname)
38
+ def inventory_hostname=(new_name)
39
+ @inventory_hostname = new_name.to_s
40
+ end
41
+
42
+ # @return [String] the SSH user for the {Host}, default +"vagrant"+
43
+ def ansible_ssh_user
44
+ hostvars[:ansible_ssh_user] ||= 'vagrant'
27
45
  end
28
46
 
29
- def ssh_host
30
- @ssh_host ||= @hostvars[:ssh_host] || '127.0.0.1'
47
+ # @return [String] the hostname of the {Host}, default +"127.0.0.1"+
48
+ def ansible_ssh_host
49
+ hostvars[:ansible_ssh_host] ||= '127.0.0.1'
31
50
  end
32
51
 
33
- def ssh_port
34
- @ssh_port ||= @hostvars[:ssh_port] || 22
52
+ # @return [Integer] the SSH port of the {Host}, default +22+
53
+ def ansible_ssh_port
54
+ hostvars[:ansible_ssh_port] ||= 22
35
55
  end
36
56
 
37
- def ssh_private_key_file
38
- @ssh_private_key_file ||= @hostvars[:ssh_private_key_file]
57
+ # The SSH private key file
58
+ # @return [String] if the SSH private key file is defined
59
+ # @return [nil] if no SSH private key file is defined
60
+ def ansible_ssh_private_key_file
61
+ hostvars[:ansible_ssh_private_key_file]
39
62
  end
40
63
 
41
- def connection
42
- @connection ||= @hostvars[:connection]
64
+ # The connection type
65
+ # @return [String] if the connection type is defined
66
+ # @return [nil] if no connection type is defined
67
+ def ansible_connection
68
+ hostvars[:ansible_connection]
43
69
  end
44
70
 
71
+ # @return [Hash] the {Host}'s attributes keyed to its attribute names
72
+ # @example
73
+ # host = Host.new("myhost", {ansible_ssh_user: 'me', ansible_ssh_port: 2200})
74
+ # host.hostvars #=> {
75
+ # # 'ansible_ssh_user' => 'me',
76
+ # # 'ansible_ssh_host' => '127.0.0.1',
77
+ # # 'ansible_ssh_port' => 2200,
78
+ # # }
45
79
  def hostvars
46
- ANSIBLE_HOSTVARS.each_with_object({}) do |m, acc|
47
- value = send(m)
48
- acc["ansible_#{m}"] = value unless value.nil?
49
- end
80
+ @hostvars ||= Util::HashWithIndifferentAccess.new
81
+ end
82
+
83
+ # @param [Hash] hostvars the variables to set on the host
84
+ # @return [Hash] the new hostvars
85
+ def hostvars=(hostvars)
86
+ raise ArgumentError, 'hostvars must be a hash' unless hostvars.is_a? Hash
87
+ @hostvars = Util::HashWithIndifferentAccess.new(hostvars)
50
88
  end
51
89
 
90
+ # @return [Hash{String=>Hash}] the {Host}'s {hostvars} keyed to its
91
+ # {name}
52
92
  def to_h
53
- { name => hostvars }
93
+ finalize!
94
+ { inventory_hostname => hostvars }
54
95
  end
55
96
 
97
+ # @return [String] the {Host} represented as an entry in an Ansible
98
+ # INI-style static inventory
99
+ # @example
100
+ # host = Host.new("myhost", {ssh_user: 'me', ssh_port: 2200})
101
+ # host.to_ini #=> "myhost ansible_ssh_user=me ansible_ssh_host=127.0.0.1 ansible_ssh_port=2200"
56
102
  def to_ini
57
- [name, *hostvars.reject { |_, value| value.nil? }.map { |key, value| "#{key}=#{value}" }].join(' ')
103
+ [inventory_hostname, *hostvars.sort.reject { |_, value| value.nil? }.map { |key, value| "#{key}=#{value}" }].join(' ')
104
+ end
105
+
106
+ # @return [Fixnum] hash key
107
+ def hash
108
+ to_h.hash
109
+ end
110
+
111
+ # @return [Boolean] whether two hosts are identical
112
+ def eql?(other)
113
+ to_h.eql?(other.to_h)
114
+ end
115
+
116
+ private
117
+
118
+ def finalize!
119
+ %i[ansible_connection ansible_ssh_user ansible_ssh_host ansible_ssh_port ansible_ssh_private_key_file].each do |m|
120
+ send(m)
121
+ end
122
+ end
123
+
124
+ def respond_to_missing?(method, _include_all = false)
125
+ method.to_s.start_with? 'ansible_'
126
+ end
127
+
128
+ def method_missing(method, *args, &block)
129
+ super unless respond_to_missing?(method)
130
+
131
+ if method[-1] == '='
132
+ hostvars[method[0..-2]] = args[0]
133
+ else
134
+ hostvars[method]
135
+ end
58
136
  end
59
137
  end
60
138
 
139
+ # An Ansible host initialized from a {Vagrant::Machine}
61
140
  class HostMachine < Host
141
+ # @param [Vagrant::Machine] machine a {Vagrant::Machine} objectj
142
+ # @param [Hash] hostvars the hostvars associated with the machine
62
143
  def initialize(machine, hostvars = {})
63
144
  super(machine.name, hostvars)
64
145
  @machine = machine
65
- @ssh_info = machine.ssh_info || {}
66
146
  end
67
147
 
68
- def ssh_user
69
- @ssh_user ||= @hostvars[:ssh_user] || @ssh_info[:username] || super
148
+ # @see Host#ssh_user
149
+ def ansible_ssh_user
150
+ hostvars[:ansible_ssh_user] ||= ssh_info[:username] || super
70
151
  end
71
152
 
72
- def ssh_host
73
- @ssh_host ||= @hostvars[:ssh_host] || @ssh_info[:host] || super
153
+ # @see Host#ssh_host
154
+ def ansible_ssh_host
155
+ hostvars[:ansible_ssh_host] ||= ssh_info[:host] || super
74
156
  end
75
157
 
76
- def ssh_port
77
- @ssh_port ||= @hostvars[:ssh_port] || @ssh_info[:port] || super
158
+ # @see Host#ssh_port
159
+ def ansible_ssh_port
160
+ hostvars[:ansible_ssh_port] ||= ssh_info[:port] || super
78
161
  end
79
162
 
80
- # TODO: better inference of which private key to use
81
- def ssh_private_key_file
82
- @ssh_private_key_file ||= @hostvars[:ssh_private_key_file] || fetch_private_key(@machine)
163
+ # @see Host#ssh_private_key_file
164
+ def ansible_ssh_private_key_file
165
+ hostvars[:ansible_ssh_private_key_file] ||= fetch_private_key(@machine)
166
+ end
167
+
168
+ private
169
+
170
+ def ssh_info
171
+ @machine.ssh_info || {}
83
172
  end
84
173
  end
85
174
  end