vagrant-ansible-fixed 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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