Skip to content

Commit dc373e4

Browse files
fix(provisioner/ansible): Allow for selection of inventory arg based on the ansible-core version
1 parent 8867704 commit dc373e4

File tree

5 files changed

+123
-18
lines changed

5 files changed

+123
-18
lines changed

plugins/provisioners/ansible/provisioner/base.rb

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,16 +53,25 @@ def initialize(machine, config)
5353
@gathered_version_stdout = nil
5454
@gathered_version_major = nil
5555
@gathered_version = nil
56+
57+
@ansible_package_version_map = {}
5658
end
5759

5860
def set_and_check_compatibility_mode
5961
begin
60-
set_gathered_ansible_version(gather_ansible_version)
62+
set_gathered_ansible_version(gather_ansible_version("ansible"))
6163
rescue StandardError => e
6264
# Nothing to do here, as the fallback on safe compatibility_mode is done below
6365
@logger.error("Error while gathering the ansible version: #{e.to_s}")
6466
end
6567

68+
begin
69+
set_gathered_ansible_package_version("ansible-core", gather_ansible_version("ansible-core"))
70+
rescue StandardError => e
71+
@logger.error("Error while gathering the ansible-core version: #{e.to_s}")
72+
end
73+
74+
6675
if @gathered_version_major
6776
if config.compatibility_mode == Ansible::COMPATIBILITY_MODE_AUTO
6877
detect_compatibility_mode
@@ -164,7 +173,7 @@ def prepare_common_command_arguments
164173
@command_arguments << "--limit=#{@machine.name}"
165174
end
166175

167-
@command_arguments << "--inventory=#{inventory_path}"
176+
@command_arguments << get_inventory_argument(inventory_path)
168177
@command_arguments << "--extra-vars=#{extra_vars_argument}" if config.extra_vars
169178
@command_arguments << "--#{@lexicon[:become]}" if config.become
170179
@command_arguments << "--#{@lexicon[:become_user]}=#{config.become_user}" if config.become_user
@@ -175,6 +184,22 @@ def prepare_common_command_arguments
175184
@command_arguments << "--start-at-task=#{config.start_at_task}" if config.start_at_task
176185
end
177186

187+
def get_inventory_argument(inventory_path)
188+
ansible_core_version = @ansible_package_version_map["ansible-core"]
189+
# Default to --inventory-file if version info is not available
190+
return "--inventory-file=#{inventory_path}" if ansible_core_version.nil? || ansible_core_version.empty?
191+
192+
major = ansible_core_version[:major].to_i
193+
minor = ansible_core_version[:minor].to_i
194+
195+
# Use --inventory for ansible-core >= 2.19
196+
if major > 2 || (major == 2 && minor >= 19)
197+
"--inventory=#{inventory_path}"
198+
else
199+
"--inventory-file=#{inventory_path}"
200+
end
201+
end
202+
178203
def prepare_common_environment_variables
179204
# Ensure Ansible output isn't buffered so that we receive output
180205
# on a task-by-task basis.
@@ -399,6 +424,26 @@ def set_gathered_ansible_version(stdout_output)
399424
end
400425
end
401426

427+
def set_gathered_ansible_package_version(ansible_package, stdout_output)
428+
if !stdout_output.empty?
429+
first_line = stdout_output.lines[0]
430+
ansible_version_pattern = first_line.match(/(^#{ansible_package}\s+)(.+)$/)
431+
if ansible_version_pattern
432+
_, gathered_version, _ = ansible_version_pattern.captures
433+
gathered_version.strip!
434+
if gathered_version
435+
gathered_version_major = gathered_version.match(/(\d+)\..+$/).captures[0].to_i
436+
gathered_version_minor = gathered_version.match(/\d+\.(\d+)\..+$/).captures[0].to_i
437+
@ansible_package_version_map[ansible_package] = {
438+
version: gathered_version,
439+
major: gathered_version_major,
440+
minor: gathered_version_minor
441+
}
442+
end
443+
end
444+
end
445+
end
446+
402447
end
403448
end
404449
end

plugins/provisioners/ansible/provisioner/guest.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,11 +74,11 @@ def check_and_install_ansible
7474
end
7575
end
7676

77-
def gather_ansible_version
77+
def gather_ansible_version(package = 'ansible')
7878
raw_output = ""
7979

8080
result = @machine.communicate.execute(
81-
"python3 -c \"import importlib.metadata; print('ansible ' + importlib.metadata.version('ansible'))\"",
81+
"python3 -c \"import importlib.metadata; print('#{package} ' + importlib.metadata.version('#{package}'))\"",
8282
error_class: Ansible::Errors::AnsibleNotFoundOnGuest,
8383
error_key: :ansible_not_found_on_guest) do |type, output|
8484
if type == :stdout && output.lines[0]

plugins/provisioners/ansible/provisioner/host.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,10 +110,10 @@ def execute_command_from_host(command)
110110
end
111111
end
112112

113-
def gather_ansible_version
113+
def gather_ansible_version(package = 'ansible')
114114
raw_output = ''
115115
command = ['python3', '-c',
116-
"import importlib.metadata; print('ansible ' + importlib.metadata.version('ansible'))"]
116+
"import importlib.metadata; print('#{package} ' + importlib.metadata.version('#{package}'))"]
117117

118118
command << {
119119
notify: [:stdout, :stderr]

test/unit/plugins/provisioners/ansible/provisioner_test.rb

Lines changed: 71 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,17 @@ def self.it_should_check_ansible_version
8585
end
8686
end
8787

88+
def self.it_should_check_ansible_core_version
89+
it "execute 'Python ansible-core version check before executing 'ansible-playbook'" do
90+
expect(Vagrant::Util::Subprocess).to receive(:execute)
91+
.once.with('python3', '-c', "import importlib.metadata; print('ansible-core ' + importlib.metadata.version('ansible-core'))", { notify: %i[
92+
stdout stderr
93+
] })
94+
expect(Vagrant::Util::Subprocess).to receive(:execute)
95+
.once.with('ansible-playbook', any_args)
96+
end
97+
end
98+
8899
def self.it_should_set_arguments_and_environment_variables(
89100
expected_args_count = 5,
90101
expected_vars_count = 4,
@@ -96,7 +107,7 @@ def self.it_should_set_arguments_and_environment_variables(
96107
expect(args[1]).to eq("--connection=ssh")
97108
expect(args[2]).to eq("--timeout=30")
98109

99-
inventory_count = args.count { |x| x.match(/^--inventory=.+$/) if x.is_a?(String) }
110+
inventory_count = args.count { |x| x.match(/^--inventory-file=.+$/) if x.is_a?(String) }
100111
expect(inventory_count).to be > 0
101112

102113
expect(args[args.length-2]).to eq("playbook.yml")
@@ -200,9 +211,9 @@ def self.it_should_create_and_use_generated_inventory(with_user = true)
200211

201212
it "sets as ansible inventory the directory containing the auto-generated inventory file" do
202213
expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
203-
inventory_index = args.rindex("--inventory=#{generated_inventory_dir}")
214+
inventory_index = args.rindex("--inventory-file=#{generated_inventory_dir}")
204215
expect(inventory_index).to be > 0
205-
expect(find_last_argument_after(inventory_index, args, /--inventory=\w+/)).to be(false)
216+
expect(find_last_argument_after(inventory_index, args, /--inventory-file=\w+/)).to be(false)
206217
}.and_return(default_execute_result)
207218
end
208219
end
@@ -286,6 +297,7 @@ def ensure_that_config_is_valid
286297

287298
describe "with default options" do
288299
it_should_check_ansible_version
300+
it_should_check_ansible_core_version
289301
it_should_set_arguments_and_environment_variables
290302
it_should_create_and_use_generated_inventory
291303

@@ -383,6 +395,7 @@ def ensure_that_config_is_valid
383395
end
384396

385397
it_should_check_ansible_version
398+
it_should_check_ansible_core_version
386399
it_should_create_and_use_generated_inventory
387400

388401
it "doesn't warn about compatibility mode auto-detection" do
@@ -621,7 +634,7 @@ def ensure_that_config_is_valid
621634
"-l localhost",
622635
"--limit=foo",
623636
"--limit=bar",
624-
"--inventory=/forget/it/my/friend",
637+
"--inventory-file=/forget/it/my/friend",
625638
"--user=lion",
626639
"--new-arg=yeah"]
627640
end
@@ -639,7 +652,7 @@ def ensure_that_config_is_valid
639652
it "sets raw arguments after arguments related to supported options" do
640653
expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
641654
expect(args.index("--user=lion")).to be > args.index("--user=testuser")
642-
expect(args.index("--inventory=/forget/it/my/friend")).to be > args.index("--inventory=#{generated_inventory_dir}")
655+
expect(args.index("--inventory-file=/forget/it/my/friend")).to be > args.index("--inventory-file=#{generated_inventory_dir}")
643656
expect(args.index("--limit=bar")).to be > args.index("--limit=all")
644657
expect(args.index("--skip-tags=ignored")).to be > args.index("--skip-tags=foo,bar")
645658
}.and_return(default_execute_result)
@@ -736,8 +749,8 @@ def ensure_that_config_is_valid
736749

737750
it "does not generate the inventory and uses given inventory path instead" do
738751
expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
739-
expect(args).to include("--inventory=#{existing_file}")
740-
expect(args).not_to include("--inventory=#{generated_inventory_file}")
752+
expect(args).to include("--inventory-file=#{existing_file}")
753+
expect(args).not_to include("--inventory-file=#{generated_inventory_file}")
741754
expect(File.exist?(generated_inventory_file)).to be(false)
742755
}.and_return(default_execute_result)
743756
end
@@ -912,7 +925,7 @@ def ensure_that_config_is_valid
912925

913926
it "shows the ansible-playbook command and set verbosity to '-#{verbose_option}' level" do
914927
expect(machine.env.ui).to receive(:detail)
915-
.with("PYTHONUNBUFFERED=1 ANSIBLE_FORCE_COLOR=true ANSIBLE_HOST_KEY_CHECKING=false ANSIBLE_SSH_ARGS='-o UserKnownHostsFile=/dev/null -o IdentitiesOnly=yes -o ControlMaster=auto -o ControlPersist=60s' ansible-playbook --connection=ssh --timeout=30 --limit=\"machine1\" --inventory=#{generated_inventory_dir} -#{verbose_option} playbook.yml")
928+
.with("PYTHONUNBUFFERED=1 ANSIBLE_FORCE_COLOR=true ANSIBLE_HOST_KEY_CHECKING=false ANSIBLE_SSH_ARGS='-o UserKnownHostsFile=/dev/null -o IdentitiesOnly=yes -o ControlMaster=auto -o ControlPersist=60s' ansible-playbook --connection=ssh --timeout=30 --limit=\"machine1\" --inventory-file=#{generated_inventory_dir} -#{verbose_option} playbook.yml")
916929
end
917930
end
918931

@@ -926,7 +939,7 @@ def ensure_that_config_is_valid
926939

927940
it "shows the ansible-playbook command and set verbosity to '-#{verbose_option}' level" do
928941
expect(machine.env.ui).to receive(:detail)
929-
.with("PYTHONUNBUFFERED=1 ANSIBLE_FORCE_COLOR=true ANSIBLE_HOST_KEY_CHECKING=false ANSIBLE_SSH_ARGS='-o UserKnownHostsFile=/dev/null -o IdentitiesOnly=yes -o ControlMaster=auto -o ControlPersist=60s' ansible-playbook --connection=ssh --timeout=30 --limit=\"machine1\" --inventory=#{generated_inventory_dir} -#{verbose_option} playbook.yml")
942+
.with("PYTHONUNBUFFERED=1 ANSIBLE_FORCE_COLOR=true ANSIBLE_HOST_KEY_CHECKING=false ANSIBLE_SSH_ARGS='-o UserKnownHostsFile=/dev/null -o IdentitiesOnly=yes -o ControlMaster=auto -o ControlPersist=60s' ansible-playbook --connection=ssh --timeout=30 --limit=\"machine1\" --inventory-file=#{generated_inventory_dir} -#{verbose_option} playbook.yml")
930943
end
931944
end
932945
end
@@ -941,7 +954,7 @@ def ensure_that_config_is_valid
941954

942955
it "shows the ansible-playbook command and set verbosity to '-v' level" do
943956
expect(machine.env.ui).to receive(:detail)
944-
.with("PYTHONUNBUFFERED=1 ANSIBLE_FORCE_COLOR=true ANSIBLE_HOST_KEY_CHECKING=false ANSIBLE_SSH_ARGS='-o UserKnownHostsFile=/dev/null -o IdentitiesOnly=yes -o ControlMaster=auto -o ControlPersist=60s' ansible-playbook --connection=ssh --timeout=30 --limit=\"machine1\" --inventory=#{generated_inventory_dir} -v playbook.yml")
957+
.with("PYTHONUNBUFFERED=1 ANSIBLE_FORCE_COLOR=true ANSIBLE_HOST_KEY_CHECKING=false ANSIBLE_SSH_ARGS='-o UserKnownHostsFile=/dev/null -o IdentitiesOnly=yes -o ControlMaster=auto -o ControlPersist=60s' ansible-playbook --connection=ssh --timeout=30 --limit=\"machine1\" --inventory-file=#{generated_inventory_dir} -v playbook.yml")
945958
end
946959
end
947960

@@ -1172,7 +1185,7 @@ def ensure_that_config_is_valid
11721185

11731186
it "shows the ansible-playbook command, with additional quotes when required" do
11741187
expect(machine.env.ui).to receive(:detail)
1175-
.with(%Q(PYTHONUNBUFFERED=1 ANSIBLE_FORCE_COLOR=true ANSIBLE_ROLES_PATH='/up/to the stars' ANSIBLE_CONFIG='#{existing_file}' ANSIBLE_HOST_KEY_CHECKING=true ANSIBLE_SSH_ARGS='-o IdentitiesOnly=yes -o IdentityFile=/my/key1 -o IdentityFile=/my/key2 -o ForwardAgent=yes -o ControlMaster=no -o ControlMaster=auto -o ControlPersist=60s' ansible-playbook --connection=ssh --timeout=30 --ask-sudo-pass --ask-vault-pass --limit="machine*:&vagrant:!that_one" --inventory=#{generated_inventory_dir} --extra-vars=\\{\\"var1\\":\\"string\\ with\\ \\'apo\\$trophe\\$\\',\\ \\\\\\\\,\\ \\\\\\"\\ and\\ \\=\\",\\"var2\\":\\{\\"x\\":42\\}\\} --sudo --sudo-user=deployer -vvv --vault-password-file=#{existing_file} --tags=db,www --skip-tags=foo,bar --start-at-task="joe's awesome task" --why-not --su-user=foot --ask-su-pass --limit=all --private-key=./myself.key --extra-vars='{\"var3\":\"foo\"}' playbook.yml))
1188+
.with(%Q(PYTHONUNBUFFERED=1 ANSIBLE_FORCE_COLOR=true ANSIBLE_ROLES_PATH='/up/to the stars' ANSIBLE_CONFIG='#{existing_file}' ANSIBLE_HOST_KEY_CHECKING=true ANSIBLE_SSH_ARGS='-o IdentitiesOnly=yes -o IdentityFile=/my/key1 -o IdentityFile=/my/key2 -o ForwardAgent=yes -o ControlMaster=no -o ControlMaster=auto -o ControlPersist=60s' ansible-playbook --connection=ssh --timeout=30 --ask-sudo-pass --ask-vault-pass --limit="machine*:&vagrant:!that_one" --inventory-file=#{generated_inventory_dir} --extra-vars=\\{\\"var1\\":\\"string\\ with\\ \\'apo\\$trophe\\$\\',\\ \\\\\\\\,\\ \\\\\\"\\ and\\ \\=\\",\\"var2\\":\\{\\"x\\":42\\}\\} --sudo --sudo-user=deployer -vvv --vault-password-file=#{existing_file} --tags=db,www --skip-tags=foo,bar --start-at-task="joe's awesome task" --why-not --su-user=foot --ask-su-pass --limit=all --private-key=./myself.key --extra-vars='{\"var3\":\"foo\"}' playbook.yml))
11761189
end
11771190
end
11781191

@@ -1262,5 +1275,52 @@ def ensure_that_config_is_valid
12621275
}.and_return(default_execute_result)
12631276
end
12641277
end
1278+
1279+
describe '#get_inventory_argument' do
1280+
context 'when ansible version is not detected' do
1281+
before do
1282+
config.version = nil
1283+
allow(subject).to receive(:gather_ansible_version).and_return("ansible #{config.version}\n...\n")
1284+
allow(subject).to receive(:gather_ansible_version).with("ansible-core").and_return("ansible #{config.version}\n...\n")
1285+
end
1286+
1287+
it 'returns the default inventory command' do
1288+
expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
1289+
expect(args).to include("--inventory-file=#{generated_inventory_dir}")
1290+
}.and_return(default_execute_result)
1291+
end
1292+
end
1293+
1294+
context 'when ansible version is detected' do
1295+
describe 'version >= 2.19' do
1296+
before do
1297+
config.version = "2.19.0"
1298+
allow(subject).to receive(:gather_ansible_version).with("ansible").and_return("ansible #{config.version}\n...\n")
1299+
allow(subject).to receive(:gather_ansible_version).with("ansible-core").and_return("ansible-core #{config.version}\n...\n")
1300+
end
1301+
1302+
it 'returns --inventory as the inventory command' do
1303+
expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
1304+
expect(args).to include("--inventory=#{generated_inventory_dir}")
1305+
}.and_return(default_execute_result)
1306+
end
1307+
end
1308+
1309+
describe 'version < 2.19' do
1310+
before do
1311+
config.version = "2.18.5"
1312+
allow(subject).to receive(:gather_ansible_version).with("ansible").and_return("ansible #{config.version}\n...\n")
1313+
allow(subject).to receive(:gather_ansible_version).with("ansible-core").and_return("ansible-core #{config.version}\n...\n")
1314+
end
1315+
1316+
it 'returns --inventory-file as the inventory command' do
1317+
expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
1318+
expect(args).to include("--inventory-file=#{generated_inventory_dir}")
1319+
}.and_return(default_execute_result)
1320+
end
1321+
end
1322+
end
1323+
end
1324+
12651325
end
12661326
end

test/unit/vagrant/action/builtin/box_add_test.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -410,7 +410,7 @@ def with_web_server(path, **opts)
410410
}.and_return(box)
411411

412412
allow(env[:ui]).to receive(:detail).and_call_original
413-
expect(env[:ui]).to receive(:detail).with(%r{.*http://(?!#{username}).+?:(?!#{password}).+?@127\.0\.0\.1:#{port}/#{box_path.basename}.*}).
413+
expect(env[:ui]).to receive(:detail).with(/.*http:\/\/\*+(?::\*+)?@127\.0\.0\.1:#{port}\/#{box_path.basename}.*/).
414414
and_call_original
415415
expect(app).to receive(:call).with(env)
416416

0 commit comments

Comments
 (0)