Vagrant 1.8, ansible_local `extra_vars` being ignored
elsom25 opened this issue ยท 20 comments
Porting applications to use the lovely new ansible_local
, and running into a few issues.
In my Vagrantfile, I have:
config.vm.provision "ansible_local" do |ansible|
ansible.provisioning_path = "/my-app/playbooks"
ansible.playbook = "site.yml"
ansible.inventory_path = "development"
ansible.extra_vars = {
is_vagrant: true,
should_seed: true,
source: "/my-app"
}
ansible.verbose = "vvvv"
end
Values have been scrubbed and simplified. Many of them actually inspect environment variables to determine there value, preventing me from moving them to my inventory group_var file.
default values defined in group_vars/all
is is_vagrant=false
and should_seed=false
which generates (what looks correctly):
cd /my-app/playbooks && PYTHONUNBUFFERED=1 ANSIBLE_FORCE_COLOR=true ansible-playbook --limit='default' --inventory-file=development --extra-vars={"is_vagrant":true,"should_seed":true,"source":"/cirro-legacy"} -vvvv site.yml
But then I get (in a dummy task created to display variable output):
TASK: [common | display vars] *************************************************
<default> REMOTE_MODULE command echo "is_vagrant=False should_seed=False source=/cirro-legacy" #USE_SHELL
I can work around this temporarily, but something seems amiss.
Hi @elsom25, thanks for your problem report. This looks strange, but likely to be an Ansible (usage) issue.
Could you please log into the machine (vagrant ssh
), and manually run the same command
cd /my-app/playbooks && PYTHONUNBUFFERED=1 ANSIBLE_FORCE_COLOR=true ansible-playbook --limit='default' --inventory-file=development --extra-vars={"is_vagrant":true,"should_seed":true,"source":"/my-app"} -vvvv site.yml
I'd expect you get the same result...
Hey @gildegoma: as you suspect, it fails the same way.
Looking through ansible docs for --extra-vars, I can't find anything that suggests there's support for a raw hash, but rather, it seems it expects quoted json.
Testing this out by changing the command to:
cd /my-app/playbooks && PYTHONUNBUFFERED=1 ANSIBLE_FORCE_COLOR=true ansible-playbook --limit='default' --inventory-file=development --extra-vars='{"is_vagrant":true,"should_seed":true,"source":"/my-app"}' -vvvv site.yml
Leads to the values being set correctly.
@elsom25 thanks for checking. It's indeed a bug in ansible_local
handling of extra_vars
in hash format. Sorry for that!
Until it's fixed, you can store these extra variables in a YAML or JSON file, and use something like ansible.extra_vars = '/my-app/vagrant-vars.yml'
.
Note that the ansible
provisioner is not affected (quoting is not required by command calls via childprocess
library).
i was about to open a pull request... but since @gildegoma mentions it is not necessary for the ansible
provisioner my quick fix might break something there.
since the ansible documentation uses single quotes around the extra_vars
JSON (http://docs.ansible.com/ansible/playbooks_variables.html#passing-variables-on-the-command-line) i hacked string formatting into plugins/provisioners/ansible/provisioner/base.rb
:
def extra_vars_argument
if config.extra_vars.kind_of?(String) and config.extra_vars =~ /^@.+$/
# A JSON or YAML file is referenced.
config.extra_vars
else
# Expected to be a Hash after config validation.
"'#{config.extra_vars.to_json}'"
end
end
and then the ruby hash is properly passed on to ansible as a single quoted JSON string.
another workaround is "escaped double quoting":
ansible.extra_vars = {
"\"service_name\"": "\"djboot\""
}
EDIT: that does not work when using more than one element in the hash. then bash interprets the {}
as array.
the resulting --extra-vars={"\"service_name\"":"\"foo\""}
then does not loose the escaped doublequotes. otherwise the shell (bash, sh) eats the doublequotes:
$ echo {"service_name":"foo"}
{service_name:foo}
I've just stumbled upon the same issue and dared to open a PR that fixes it.
@gildegoma looks like childprocess doesn't mind such quoting because I was able to provision my machine with both ansible
and ansible-local
. Could you take a look at #6820?
+1 for this fix from me, I am trying to pass proxy settings through the ansible provisioner
ansible.extra_vars = {
proxy_env: {
http_proxy: ENV['http_proxy'] || "",
https_proxy: ENV['http_proxy'] || ""
}
}
but all what comes out in the command line arg is unquoted
web: Running ansible-playbook...
PYTHONUNBUFFERED=1 \
ANSIBLE_FORCE_COLOR=true \
ANSIBLE_HOST_KEY_CHECKING=false \
ansible-playbook --connection=ssh --timeout=30 \
--extra-vars=ansible_ssh_user='vagrant' \
--limit='web.vagrant.dev' \
--inventory-file=./inventory \
--extra-vars={"proxy_env":{"http_proxy":"http://proxy:8888","https_proxy":"http://proxy:8888"}} \
--sudo site.yml
whilst --extra-vars
must be quoted in single quotes to work
web: Running ansible-playbook...
PYTHONUNBUFFERED=1 \
ANSIBLE_FORCE_COLOR=true \
ANSIBLE_HOST_KEY_CHECKING=false \
ansible-playbook --connection=ssh --timeout=30 \
--extra-vars=ansible_ssh_user='vagrant' \
--limit='web.vagrant.dev' \
--inventory-file=./inventory \
--extra-vars='{"proxy_env":{"http_proxy":"http://proxy:8888","https_proxy":"http://proxy:8888"}}' \
--sudo site.yml
@fdemmer I modified your approach slightly to also escape the quotes after the extra_vars hash is converted to JSON. Now it does work properly for me.
def extra_vars_argument
if config.extra_vars.kind_of?(String) and config.extra_vars =~ /^@.+$/
# A JSON or YAML file is referenced.
config.extra_vars
else
# Expected to be a Hash after config validation.
"'#{config.extra_vars.to_json.gsub("\"", %q(\\\"))}'"
end
end
I've worked around this issue by using raw arguments.
This doesn't work:
ansible.extra_vars = {
provider: "virtualbox"
}
This does:
ansible.raw_arguments = [
"--extra-vars",
"provider=virtualbox"
]
I hope it can be fixed before 1.8.2.
Is this issue holding up the 1.8.2 release? I noticed it's not in the 1.8.2 milestone, though one other ticket in that milestone mentioned this ticket as a dependency...
@geerlingguy no, there is no hold up ;-)
Of course, I hope this issue will also be fixed for the next Vagrant release ๐
@kamazee I actually started to review #6820 some days ago, and found some problems (which I don't remember right now). I'll comment directly onto the PR (but no commitment about the deadline, sorry).
@gildegoma can you please double-check about those comments? i'd like to help, but don't see any comments in the code or conversation of pr #6820 besides the "needs-review" label and #6800 also only mentions "needs review", left the checkbox open, but still merged.
@fdemmer #6820 will be reviewed (I haven't started yet, sorry). If you want to help, you chan also check it out and bring your feedbacks on the PR thread. That would be great :)
For the records: I initially thought to address this issue via #6800. But I finally decided to merge this "growing beast" to get all these other fixes into master branch (I usually prefer to keep one PR focused on one Issue). I updated #6800 description, hoping to make things more clear...
Let's flag it for Milestone 1.8.2...
My workaround (Vagrant 1.8.1, ansible_local 1.9.5):
ansible.raw_arguments = [
"--extra-vars",
"\"a=b c=d\""
]
fixed with #7103 imho.
sorry, for being annoying, but is there an eta for 1.8.2? would help a fellow developer on windows a lot...
Below a simple test to verify the fix.
Vagrant version
Vagrant 1.8.1
Ansible version (also affects 1.9.x in exact same way)
ansible-playbook 2.0.1.0
config file =
configured module search path = Default w/o overrides
Host operating system
OSX 10.11.4
Guest operating system
Centos7 / any linux that can be targeted by ansible really
Vagrantfile
# -*- mode: ruby -*-
# vi: set ft=ruby :
Vagrant.configure(2) do |config|
config.vm.box = "centos/7"
if Vagrant.has_plugin?("vagrant-vbguest")
config.vbguest.auto_update = false
end
# provisioning ansible
config.vm.provision "ansible" do |ansible|
ansible.playbook = "play.yml"
ansible.verbose = "vvvv"
ansible.extra_vars = {
# send a string of generated keycloak servers to the haproxy
extra1: Array.new(3){ |n| "bla#{(1 + n).to_s.rjust(2,'0')}" },
extra2: true
}
end # ansible provisioning
end
play.yml
---
- hosts: all
# strategy: debug
gather_facts: no
tasks:
- debug: var=extra1
- fail: msg="extra1 arg not set"
when: extra1 is undefined
Debug output
$ vagrant provision
==> default: Running provisioner: ansible...
default: Running ansible-playbook...
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='default' \
--inventory-file=/Users/fred/workspaces/extraargs/.vagrant/provisioners/ansible/inventory \
--extra-vars='{"extra1":["bla01","bla02","bla03"],"extra2":true}'\
-vvvv play.yml
No config file found; using defaults
Loaded callback default of type stdout, v2.0
1 plays in play.yml
PLAY ***************************************************************************
TASK [debug] *******************************************************************
task path: /Users/fred/workspaces/extraargs/play.yml:6
ok: [default] => {
"extra1": "VARIABLE IS NOT DEFINED!"
}
PLAY RECAP *********************************************************************
default : ok=1 changed=0 unreachable=0 failed=1
Expected behavior
Extra args should be passed properly to the ansible playbook executable:
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='default' \
--inventory-file=./.vagrant/provisioners/ansible/inventory \
--extra-vars='{"extra1":["bla01","bla02","bla03"],"extra2":true}' \
-vvvv play.yml
No config file found; using defaults
Loaded callback default of type stdout, v2.0
1 plays in play.yml
PLAY ***************************************************************************
TASK [debug] *******************************************************************
task path: /Users/fred/workspaces/extraargs/play.yml:6
ok: [default] => {
"extra1": [
"bla01",
"bla02",
"bla03"
]
}
PLAY RECAP *********************************************************************
default : ok=1 changed=0 unreachable=0 failed=0
Actual behavior
What actually happened?
extra_args
are not passed properly by vagrant to the ansible-playbook process
Steps to reproduce
- copy
Vagrantfile
from above to empty folder - copy
play.yml
file from above into same folder - run vagrant up and watch ansible provisioning step fail
@bertramn thank you very much for your test case. I really appreciate to get more people involved in the "quality assurance" process ๐ ๐
From your report, I think that you are testing it with a wrong fix (maybe #6820), as your output is single quoted, not double quoted. If you run it with master
codebase (where #7103 has been merged), you should get a successful ansible run, like illustrated below:
with ansible
remote provisioner:
==> machine1: Running provisioner: ansible...
machine1: Running ansible-playbook...
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=/.../.vagrant/provisioners/ansible/inventory \
--extra-vars="{\"extra1\":[\"bla01\",\"bla02\",\"bla03\"],\"extra2\":true}" \
-vvvv check.yml
PLAY [all] ********************************************************************
TASK: [debug var=extra1] ******************************************************
<127.0.0.1> ESTABLISH CONNECTION FOR USER: vagrant
ok: [machine1] => {
"var": {
"extra1": [
"bla01",
"bla02",
"bla03"
]
}
}
TASK: [fail msg="extra1 arg not set"] *****************************************
skipping: [machine1]
PLAY RECAP ********************************************************************
machine1 : ok=1 changed=0 unreachable=0 failed=0
with ansible_local
provisioner:
==> machine1: Running provisioner: ansible_local...
machine1: Running ansible-playbook...
cd /vagrant && PYTHONUNBUFFERED=1 ANSIBLE_FORCE_COLOR=true ansible-playbook \
--limit="machine1" --inventory-file=/tmp/vagrant-ansible/inventory \
--extra-vars="{\"extra1\":[\"bla01\",\"bla02\",\"bla03\"],\"extra2\":true}" \
-vvvv check.yml
Using /vagrant/ansible.cfg as config file
Loaded callback default of type stdout, v2.0
PLAYBOOK: check.yml ************************************************************
1 plays in check.yml
PLAY [all] *********************************************************************
TASK [debug] *******************************************************************
task path: /vagrant/check.yml:5
ok: [machine1] => {
"extra1": [
"bla01",
"bla02",
"bla03"
]
}
TASK [fail] ********************************************************************
task path: /vagrant/check.yml:7
skipping: [machine1] => {"changed": false, "skip_reason": "Conditional check failed", "skipped": true}
PLAY RECAP *********************************************************************
machine1 : ok=1 changed=0 unreachable=0 failed=0
I also must stress out that I am very surprised that you have produce such an error when running the ansible
provisioner, since this bug only affects ansible_local
(only the verbose output of the ansible
provisioner is wrong and displays an invalid ansible-playbook
command, but the effective command executed by Vagrant 1.8.1 under the hood is correct).
Can you please give it a try with git master
? Thanks again!
Hi @gildegoma, glad to help out. This test case was actually executed against vagrant 1.8.1 release. I did have problems with ansible extra-vars on both OSX and windows. The ansible documentation suggests to single quote --extra-vars
arguments when they are passed as json arguments. I think they do some magic to work out if its a comma separated list of vars, a yaml file to load or a JSON structure.
As of Ansible 1.2, you can also pass in extra vars as quoted JSON, like so:
--extra-vars '{"pacman":"mrs","ghosts":["inky","pinky","clyde","sue"]}'
I am not that familiar with ruby and have not been able to run the latest master (will have to setup a rbenv first before breaking my system ruby install ).
However I did look through the ansible provisioner code while tracking the same problem on a windows machine and it looks like the "command" is properly constructed but the subprocess utility does mingle it up and constructs the os process incorrectly (as in stripping out characters required by ansible). Its all good until about here:
plugins/provisioners/ansible/provisioner/host.rb
line 76 but then goes wrong in Vagrant::Util::Subprocess.execute(*command)
. I could see it during debug with a little wrapper I wrote to get vagrant and ansible to work on windows (hack) https://github.com/bertramn/ansible-win-wrapper and the exact same problem also exists on OSX (as per above test). Reading through all the other issues on --extra-args
no one mentioned Vagrant::Util::Subprocess
as a possible culprit which I suspect causes this failure. Hope this gives you guys some ideas.