vagrant-ansible-fixed 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/Gemfile +9 -0
- data/README.md +37 -0
- data/Rakefile +3 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/lib/cap/guest/arch/ansible_install.rb +19 -0
- data/lib/cap/guest/debian/ansible_install.rb +28 -0
- data/lib/cap/guest/fedora/ansible_install.rb +26 -0
- data/lib/cap/guest/freebsd/ansible_install.rb +18 -0
- data/lib/cap/guest/posix/ansible_installed.rb +25 -0
- data/lib/cap/guest/redhat/ansible_install.rb +27 -0
- data/lib/cap/guest/suse/ansible_install.rb +18 -0
- data/lib/cap/guest/ubuntu/ansible_install.rb +22 -0
- data/lib/config/base.rb +119 -0
- data/lib/config/guest.rb +69 -0
- data/lib/config/host.rb +64 -0
- data/lib/errors.rb +27 -0
- data/lib/helpers.rb +43 -0
- data/lib/plugin.rb +80 -0
- data/lib/provisioner/base.rb +219 -0
- data/lib/provisioner/guest.rb +149 -0
- data/lib/provisioner/host.rb +257 -0
- data/lib/version.rb +5 -0
- data/vagrant-ansible-fixed.gemspec +23 -0
- metadata +96 -0
data/lib/plugin.rb
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
require "vagrant"
|
2
|
+
|
3
|
+
module VagrantPlugins
|
4
|
+
module Ansible_Fixed
|
5
|
+
class Plugin < Vagrant.plugin("2")
|
6
|
+
|
7
|
+
name "ansible_fixed"
|
8
|
+
description <<-DESC
|
9
|
+
Provides support for provisioning your virtual machines with Ansible
|
10
|
+
from the Vagrant host (`ansible`) or from the guests (`ansible_local`).
|
11
|
+
DESC with patch for Windows systems until version 1.8.2 is public
|
12
|
+
|
13
|
+
config("ansible_fixed", :provisioner) do
|
14
|
+
require_relative "config/host"
|
15
|
+
Config::Host
|
16
|
+
end
|
17
|
+
|
18
|
+
config("ansible_fixed_local", :provisioner) do
|
19
|
+
require_relative "config/guest"
|
20
|
+
Config::Guest
|
21
|
+
end
|
22
|
+
|
23
|
+
provisioner("ansible_fixed") do
|
24
|
+
require_relative "provisioner/host"
|
25
|
+
Provisioner::Host
|
26
|
+
end
|
27
|
+
|
28
|
+
provisioner("ansible_fixed_local") do
|
29
|
+
require_relative "provisioner/guest"
|
30
|
+
Provisioner::Guest
|
31
|
+
end
|
32
|
+
|
33
|
+
guest_capability(:linux, :ansible_installed) do
|
34
|
+
require_relative "cap/guest/posix/ansible_installed"
|
35
|
+
Cap::Guest::POSIX::AnsibleInstalled
|
36
|
+
end
|
37
|
+
|
38
|
+
guest_capability(:freebsd, :ansible_installed) do
|
39
|
+
require_relative "cap/guest/posix/ansible_installed"
|
40
|
+
Cap::Guest::POSIX::AnsibleInstalled
|
41
|
+
end
|
42
|
+
|
43
|
+
guest_capability(:arch, :ansible_install) do
|
44
|
+
require_relative "cap/guest/arch/ansible_install"
|
45
|
+
Cap::Guest::Arch::AnsibleInstall
|
46
|
+
end
|
47
|
+
|
48
|
+
guest_capability(:debian, :ansible_install) do
|
49
|
+
require_relative "cap/guest/debian/ansible_install"
|
50
|
+
Cap::Guest::Debian::AnsibleInstall
|
51
|
+
end
|
52
|
+
|
53
|
+
guest_capability(:ubuntu, :ansible_install) do
|
54
|
+
require_relative "cap/guest/ubuntu/ansible_install"
|
55
|
+
Cap::Guest::Ubuntu::AnsibleInstall
|
56
|
+
end
|
57
|
+
|
58
|
+
guest_capability(:fedora, :ansible_install) do
|
59
|
+
require_relative "cap/guest/fedora/ansible_install"
|
60
|
+
Cap::Guest::Fedora::AnsibleInstall
|
61
|
+
end
|
62
|
+
|
63
|
+
guest_capability(:redhat, :ansible_install) do
|
64
|
+
require_relative "cap/guest/redhat/ansible_install"
|
65
|
+
Cap::Guest::RedHat::AnsibleInstall
|
66
|
+
end
|
67
|
+
|
68
|
+
guest_capability(:suse, :ansible_install) do
|
69
|
+
require_relative "cap/guest/suse/ansible_install"
|
70
|
+
Cap::Guest::SUSE::AnsibleInstall
|
71
|
+
end
|
72
|
+
|
73
|
+
guest_capability(:freebsd, :ansible_install) do
|
74
|
+
require_relative "cap/guest/freebsd/ansible_install"
|
75
|
+
Cap::Guest::FreeBSD::AnsibleInstall
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,219 @@
|
|
1
|
+
require_relative "../errors"
|
2
|
+
require_relative "../helpers"
|
3
|
+
|
4
|
+
module VagrantPlugins
|
5
|
+
module Ansible_Fixed
|
6
|
+
module Provisioner
|
7
|
+
|
8
|
+
# This class is a base class where the common functionality shared between
|
9
|
+
# both Ansible provisioners are stored.
|
10
|
+
# This is **not an actual provisioner**.
|
11
|
+
# Instead, {Host} (ansible) or {Guest} (ansible_local) should be used.
|
12
|
+
|
13
|
+
class Base < Vagrant.plugin("2", :provisioner)
|
14
|
+
|
15
|
+
RANGE_PATTERN = %r{(?:\[[a-z]:[a-z]\]|\[[0-9]+?:[0-9]+?\])}.freeze
|
16
|
+
|
17
|
+
protected
|
18
|
+
|
19
|
+
def initialize(machine, config)
|
20
|
+
super
|
21
|
+
|
22
|
+
@command_arguments = []
|
23
|
+
@environment_variables = {}
|
24
|
+
@inventory_machines = {}
|
25
|
+
@inventory_path = nil
|
26
|
+
end
|
27
|
+
|
28
|
+
def prepare_common_command_arguments
|
29
|
+
# By default we limit by the current machine,
|
30
|
+
# but this can be overridden by the `limit` option.
|
31
|
+
if config.limit
|
32
|
+
@command_arguments << "--limit=#{Helpers::as_list_argument(config.limit)}"
|
33
|
+
else
|
34
|
+
@command_arguments << "--limit=#{@machine.name}"
|
35
|
+
end
|
36
|
+
|
37
|
+
@command_arguments << "--inventory-file=#{inventory_path}"
|
38
|
+
@command_arguments << "--extra-vars=#{extra_vars_argument}" if config.extra_vars
|
39
|
+
@command_arguments << "--sudo" if config.sudo
|
40
|
+
@command_arguments << "--sudo-user=#{config.sudo_user}" if config.sudo_user
|
41
|
+
@command_arguments << "#{verbosity_argument}" if verbosity_is_enabled?
|
42
|
+
@command_arguments << "--vault-password-file=#{config.vault_password_file}" if config.vault_password_file
|
43
|
+
@command_arguments << "--tags=#{Helpers::as_list_argument(config.tags)}" if config.tags
|
44
|
+
@command_arguments << "--skip-tags=#{Helpers::as_list_argument(config.skip_tags)}" if config.skip_tags
|
45
|
+
@command_arguments << "--start-at-task=#{config.start_at_task}" if config.start_at_task
|
46
|
+
|
47
|
+
# Finally, add the raw configuration options, which has the highest precedence
|
48
|
+
# and can therefore potentially override any other options of this provisioner.
|
49
|
+
@command_arguments.concat(Helpers::as_array(config.raw_arguments)) if config.raw_arguments
|
50
|
+
end
|
51
|
+
|
52
|
+
def prepare_common_environment_variables
|
53
|
+
# Ensure Ansible output isn't buffered so that we receive output
|
54
|
+
# on a task-by-task basis.
|
55
|
+
@environment_variables["PYTHONUNBUFFERED"] = 1
|
56
|
+
|
57
|
+
# When Ansible output is piped in Vagrant integration, its default colorization is
|
58
|
+
# automatically disabled and the only way to re-enable colors is to use ANSIBLE_FORCE_COLOR.
|
59
|
+
@environment_variables["ANSIBLE_FORCE_COLOR"] = "true" if @machine.env.ui.color?
|
60
|
+
# Setting ANSIBLE_NOCOLOR is "unnecessary" at the moment, but this could change in the future
|
61
|
+
# (e.g. local provisioner [GH-2103], possible change in vagrant/ansible integration, etc.)
|
62
|
+
@environment_variables["ANSIBLE_NOCOLOR"] = "true" if !@machine.env.ui.color?
|
63
|
+
end
|
64
|
+
|
65
|
+
# Auto-generate "safe" inventory file based on Vagrantfile,
|
66
|
+
# unless inventory_path is explicitly provided
|
67
|
+
def inventory_path
|
68
|
+
if config.inventory_path
|
69
|
+
config.inventory_path
|
70
|
+
else
|
71
|
+
@inventory_path ||= generate_inventory
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def get_inventory_host_vars_string(machine_name)
|
76
|
+
# In Ruby, Symbol and String values are different, but
|
77
|
+
# Vagrant has to unify them for better user experience.
|
78
|
+
vars = config.host_vars[machine_name.to_sym]
|
79
|
+
if !vars
|
80
|
+
vars = config.host_vars[machine_name.to_s]
|
81
|
+
end
|
82
|
+
s = nil
|
83
|
+
if vars.is_a?(Hash)
|
84
|
+
s = vars.each.collect{ |k, v| "#{k}=#{v}" }.join(" ")
|
85
|
+
elsif vars.is_a?(Array)
|
86
|
+
s = vars.join(" ")
|
87
|
+
elsif vars.is_a?(String)
|
88
|
+
s = vars
|
89
|
+
end
|
90
|
+
if s and !s.empty? then s else nil end
|
91
|
+
end
|
92
|
+
|
93
|
+
def generate_inventory
|
94
|
+
inventory = "# Generated by Vagrant\n\n"
|
95
|
+
|
96
|
+
# This "abstract" step must fill the @inventory_machines list
|
97
|
+
# and return the list of supported host(s)
|
98
|
+
inventory += generate_inventory_machines
|
99
|
+
|
100
|
+
inventory += generate_inventory_groups
|
101
|
+
|
102
|
+
# This "abstract" step must create the inventory file and
|
103
|
+
# return its location path
|
104
|
+
# TODO: explain possible race conditions, etc.
|
105
|
+
@inventory_path = ship_generated_inventory(inventory)
|
106
|
+
end
|
107
|
+
|
108
|
+
# Write out groups information.
|
109
|
+
# All defined groups will be included, but only supported
|
110
|
+
# machines and defined child groups will be included.
|
111
|
+
def generate_inventory_groups
|
112
|
+
groups_of_groups = {}
|
113
|
+
defined_groups = []
|
114
|
+
group_vars = {}
|
115
|
+
inventory_groups = ""
|
116
|
+
|
117
|
+
# Verify if host range patterns exist and warn
|
118
|
+
if config.groups.any? { |gm| gm.to_s[RANGE_PATTERN] }
|
119
|
+
@machine.ui.warn(I18n.t("vagrant.provisioners.ansible.ansible_host_pattern_detected"))
|
120
|
+
end
|
121
|
+
|
122
|
+
config.groups.each_pair do |gname, gmembers|
|
123
|
+
if gname.is_a?(Symbol)
|
124
|
+
gname = gname.to_s
|
125
|
+
end
|
126
|
+
|
127
|
+
if gmembers.is_a?(String)
|
128
|
+
gmembers = gmembers.split(/\s+/)
|
129
|
+
elsif gmembers.is_a?(Hash)
|
130
|
+
gmembers = gmembers.each.collect{ |k, v| "#{k}=#{v}" }
|
131
|
+
elsif !gmembers.is_a?(Array)
|
132
|
+
gmembers = []
|
133
|
+
end
|
134
|
+
|
135
|
+
if gname.end_with?(":children")
|
136
|
+
groups_of_groups[gname] = gmembers
|
137
|
+
defined_groups << gname.sub(/:children$/, '')
|
138
|
+
elsif gname.end_with?(":vars")
|
139
|
+
group_vars[gname] = gmembers
|
140
|
+
else
|
141
|
+
defined_groups << gname
|
142
|
+
inventory_groups += "\n[#{gname}]\n"
|
143
|
+
gmembers.each do |gm|
|
144
|
+
# TODO : Expand and validate host range patterns
|
145
|
+
# against @inventory_machines list before adding them
|
146
|
+
# otherwise abort with an error message
|
147
|
+
if gm[RANGE_PATTERN]
|
148
|
+
inventory_groups += "#{gm}\n"
|
149
|
+
end
|
150
|
+
inventory_groups += "#{gm}\n" if @inventory_machines.include?(gm.to_sym)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
defined_groups.uniq!
|
156
|
+
groups_of_groups.each_pair do |gname, gmembers|
|
157
|
+
inventory_groups += "\n[#{gname}]\n"
|
158
|
+
gmembers.each do |gm|
|
159
|
+
inventory_groups += "#{gm}\n" if defined_groups.include?(gm)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
group_vars.each_pair do |gname, gmembers|
|
164
|
+
if defined_groups.include?(gname.sub(/:vars$/, ""))
|
165
|
+
inventory_groups += "\n[#{gname}]\n" + gmembers.join("\n") + "\n"
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
return inventory_groups
|
170
|
+
end
|
171
|
+
|
172
|
+
def extra_vars_argument
|
173
|
+
if config.extra_vars.kind_of?(String) and config.extra_vars =~ /^@.+$/
|
174
|
+
# A JSON or YAML file is referenced.
|
175
|
+
config.extra_vars
|
176
|
+
else
|
177
|
+
# Expected to be a Hash after config validation.
|
178
|
+
config.extra_vars.to_json
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def get_galaxy_role_file(base_dir)
|
183
|
+
Helpers::expand_path_in_unix_style(config.galaxy_role_file, base_dir)
|
184
|
+
end
|
185
|
+
|
186
|
+
def get_galaxy_roles_path(base_dir)
|
187
|
+
if config.galaxy_roles_path
|
188
|
+
Helpers::expand_path_in_unix_style(config.galaxy_roles_path, base_dir)
|
189
|
+
else
|
190
|
+
playbook_path = Helpers::expand_path_in_unix_style(config.playbook, base_dir)
|
191
|
+
File.join(Pathname.new(playbook_path).parent, 'roles')
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def ui_running_ansible_command(name, command)
|
196
|
+
@machine.ui.detail I18n.t("vagrant.provisioners.ansible.running_#{name}")
|
197
|
+
if verbosity_is_enabled?
|
198
|
+
# Show the ansible command in use
|
199
|
+
@machine.env.ui.detail command
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
def verbosity_is_enabled?
|
204
|
+
config.verbose && !config.verbose.to_s.empty?
|
205
|
+
end
|
206
|
+
|
207
|
+
def verbosity_argument
|
208
|
+
if config.verbose.to_s =~ /^-?(v+)$/
|
209
|
+
"-#{$+}"
|
210
|
+
else
|
211
|
+
# safe default, in case input strays
|
212
|
+
'-v'
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
require 'tempfile'
|
2
|
+
|
3
|
+
require_relative "base"
|
4
|
+
|
5
|
+
module VagrantPlugins
|
6
|
+
module Ansible_Fixed
|
7
|
+
module Provisioner
|
8
|
+
class Guest < Base
|
9
|
+
|
10
|
+
def initialize(machine, config)
|
11
|
+
super
|
12
|
+
@logger = Log4r::Logger.new("vagrant::provisioners::ansible_guest")
|
13
|
+
end
|
14
|
+
|
15
|
+
def provision
|
16
|
+
check_and_install_ansible
|
17
|
+
execute_ansible_galaxy_on_guest if config.galaxy_role_file
|
18
|
+
execute_ansible_playbook_on_guest
|
19
|
+
end
|
20
|
+
|
21
|
+
protected
|
22
|
+
|
23
|
+
#
|
24
|
+
# This handles verifying the Ansible installation, installing it if it was
|
25
|
+
# requested, and so on. This method will raise exceptions if things are wrong.
|
26
|
+
#
|
27
|
+
# Current limitations:
|
28
|
+
# - The installation of a specific Ansible version is not supported.
|
29
|
+
# Such feature is difficult to systematically provide via package repositories (apt, yum, ...).
|
30
|
+
# Installing via pip python packaging or directly from github source would be appropriate,
|
31
|
+
# but these approaches require more dependency burden.
|
32
|
+
# - There is no guarantee that the automated installation will replace
|
33
|
+
# a previous Ansible installation.
|
34
|
+
#
|
35
|
+
def check_and_install_ansible
|
36
|
+
@logger.info("Checking for Ansible installation...")
|
37
|
+
|
38
|
+
# If the guest cannot check if Ansible is installed,
|
39
|
+
# print a warning and try to continue without any installation attempt...
|
40
|
+
if !@machine.guest.capability?(:ansible_installed)
|
41
|
+
@machine.ui.warn(I18n.t("vagrant.provisioners.ansible.cannot_detect"))
|
42
|
+
return
|
43
|
+
end
|
44
|
+
|
45
|
+
# Try to install Ansible (if needed and requested)
|
46
|
+
if config.install &&
|
47
|
+
(config.version.to_s.to_sym == :latest ||
|
48
|
+
!@machine.guest.capability(:ansible_installed, config.version))
|
49
|
+
@machine.ui.detail I18n.t("vagrant.provisioners.ansible.installing")
|
50
|
+
@machine.guest.capability(:ansible_install)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Check that ansible binaries are well installed on the guest,
|
54
|
+
@machine.communicate.execute(
|
55
|
+
"ansible-galaxy info --help && ansible-playbook --help",
|
56
|
+
:error_class => Ansible::Errors::AnsibleNotFoundOnGuest,
|
57
|
+
:error_key => :ansible_not_found_on_guest)
|
58
|
+
|
59
|
+
# Check if requested ansible version is available
|
60
|
+
if (!config.version.empty? &&
|
61
|
+
config.version.to_s.to_sym != :latest &&
|
62
|
+
!@machine.guest.capability(:ansible_installed, config.version))
|
63
|
+
raise Ansible::Errors::AnsibleVersionNotFoundOnGuest, required_version: config.version.to_s
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def execute_ansible_galaxy_on_guest
|
68
|
+
command_values = {
|
69
|
+
:role_file => get_galaxy_role_file(config.provisioning_path),
|
70
|
+
:roles_path => get_galaxy_roles_path(config.provisioning_path)
|
71
|
+
}
|
72
|
+
remote_command = config.galaxy_command % command_values
|
73
|
+
|
74
|
+
execute_ansible_command_on_guest "galaxy", remote_command
|
75
|
+
end
|
76
|
+
|
77
|
+
def execute_ansible_playbook_on_guest
|
78
|
+
prepare_common_command_arguments
|
79
|
+
prepare_common_environment_variables
|
80
|
+
|
81
|
+
command = (%w(ansible-playbook) << @command_arguments << config.playbook).flatten
|
82
|
+
remote_command = "cd #{config.provisioning_path} && #{Helpers::stringify_ansible_playbook_command(@environment_variables, command)}"
|
83
|
+
|
84
|
+
execute_ansible_command_on_guest "playbook", remote_command
|
85
|
+
end
|
86
|
+
|
87
|
+
def execute_ansible_command_on_guest(name, command)
|
88
|
+
ui_running_ansible_command name, command
|
89
|
+
|
90
|
+
result = execute_on_guest(command)
|
91
|
+
raise Ansible::Errors::AnsibleCommandFailed if result != 0
|
92
|
+
end
|
93
|
+
|
94
|
+
def execute_on_guest(command)
|
95
|
+
@machine.communicate.execute(command, :error_check => false) do |type, data|
|
96
|
+
if [:stderr, :stdout].include?(type)
|
97
|
+
@machine.env.ui.info(data, :new_line => false, :prefix => false)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def ship_generated_inventory(inventory_content)
|
103
|
+
inventory_basedir = File.join(config.tmp_path, "inventory")
|
104
|
+
inventory_path = File.join(inventory_basedir, "vagrant_ansible_local_inventory")
|
105
|
+
|
106
|
+
temp_inventory = Tempfile.new("vagrant_ansible_local_inventory_#{@machine.name}")
|
107
|
+
temp_inventory.write(inventory_content)
|
108
|
+
temp_inventory.close
|
109
|
+
|
110
|
+
create_and_chown_remote_folder(inventory_basedir)
|
111
|
+
@machine.communicate.tap do |comm|
|
112
|
+
comm.sudo("rm -f #{inventory_path}", error_check: false)
|
113
|
+
comm.upload(temp_inventory.path, inventory_path)
|
114
|
+
end
|
115
|
+
|
116
|
+
return inventory_basedir
|
117
|
+
end
|
118
|
+
|
119
|
+
def generate_inventory_machines
|
120
|
+
machines = ""
|
121
|
+
|
122
|
+
# TODO: Instead, why not loop over active_machines and skip missing guests, like in Host?
|
123
|
+
machine.env.machine_names.each do |machine_name|
|
124
|
+
begin
|
125
|
+
@inventory_machines[machine_name] = machine_name
|
126
|
+
if @machine.name == machine_name
|
127
|
+
machines += "#{machine_name} ansible_connection=local\n"
|
128
|
+
else
|
129
|
+
machines += "#{machine_name}\n"
|
130
|
+
end
|
131
|
+
host_vars = get_inventory_host_vars_string(machine_name)
|
132
|
+
machines.sub!(/\n$/, " #{host_vars}\n") if host_vars
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
return machines
|
137
|
+
end
|
138
|
+
|
139
|
+
def create_and_chown_remote_folder(path)
|
140
|
+
@machine.communicate.tap do |comm|
|
141
|
+
comm.sudo("mkdir -p #{path}")
|
142
|
+
comm.sudo("chown -h #{@machine.ssh_info[:username]} #{path}")
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|