vagrant-ansible-fixed 0.1.0

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.
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