Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vagrant 1.8, ansible_local extra_vars being ignored #6726

Closed
elsom25 opened this issue Dec 23, 2015 · 20 comments · Fixed by #7103
Closed

Vagrant 1.8, ansible_local extra_vars being ignored #6726

elsom25 opened this issue Dec 23, 2015 · 20 comments · Fixed by #7103

Comments

@elsom25
Copy link

elsom25 commented Dec 23, 2015

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.

@elsom25 elsom25 changed the title Vagrant 18, ansible_local extra_vars being ignored Vagrant 1.8, ansible_local extra_vars being ignored Dec 23, 2015
@gildegoma
Copy link
Collaborator

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

@elsom25
Copy link
Author

elsom25 commented Dec 23, 2015

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.

@gildegoma
Copy link
Collaborator

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

@gildegoma gildegoma added bug and removed needs info labels Dec 24, 2015
@fdemmer
Copy link

fdemmer commented Jan 4, 2016

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}

fdemmer added a commit to fdemmer/django_bootstrap that referenced this issue Jan 4, 2016
The weird doublequoting is necessary due to a vagrant bug:
hashicorp/vagrant#6726
@kamazee
Copy link
Contributor

kamazee commented Jan 7, 2016

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?

@gildegoma
Copy link
Collaborator

@kamazee thanks a lot for the PR, I'll check it asap (i.e. not right now 😉)

codenrhoden added a commit to thecodeteam/ansible-role-rexray that referenced this issue Jan 13, 2016
Install REX-Ray into a CentOS 7 box using Ansible.

To prevent users from having to have Ansible installed locally, use
the ansible_local provisioner, which will run Ansible fromw within
the guest.  This requires Vagrant 1.8+, and at this time still
doesn't quite work until hashicorp/vagrant#6726
is fixed.
@bertramn
Copy link

+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

@bertramn
Copy link

@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

@warrenseine
Copy link

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.

@geerlingguy
Copy link
Contributor

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

@gildegoma
Copy link
Collaborator

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

@fdemmer
Copy link

fdemmer commented Feb 17, 2016

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

@gildegoma
Copy link
Collaborator

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

@gildegoma gildegoma added this to the 1.8.2 milestone Feb 17, 2016
@gildegoma
Copy link
Collaborator

Let's flag it for Milestone 1.8.2...

gildegoma added a commit that referenced this issue Mar 5, 2016
Without this change, the JSON string generated from the `extra_vars`
Ruby hash is passed without enclosing quotes and is then not parseable
by the ansible-playbook command when exectuted in a usual shell context.

In this changeset, the ansible (remote) unit test coverage is improved
to cover both usage of `extra_vars` (ansible_local unit tests are still
missing).

Additional Notes:

 - Double quotes are favored to single quotes in order to allow usage of
   any character for the variable values. For this reason additional
   escaping is appended to JSON-inner double quotes and backslashes.

 - This problem was not affecting the `ansible` remote provisioner
   (which is running the ansible-playbook command via the childprocess
   Ruby library). But with this change, the `verbose` output will also
   now be correct for a copy-paste reuse.

 - After this change, all the "--extra-vars" arguments (also a var
   file passed with the @-syntax or anything coming via the
   `raw_arguments` option) are "blindly" and systematically enclosed
   in double quoted and double-escaped.
   This is not optimal and can potentially break with peculiar values
   (e.g. a double quote character (") cannot be used in a json value
   when using `raw_arguments`). That said, I think that the current
   solution is a reasonable trade-off, since the official `extra_vars`
   option should now be able to cover a great majority of use cases.

Fix #6726
@SchulteMarkus
Copy link

My workaround (Vagrant 1.8.1, ansible_local 1.9.5):

ansible.raw_arguments = [
    "--extra-vars",
    "\"a=b c=d\""
]

@fdemmer
Copy link

fdemmer commented Apr 20, 2016

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

gildegoma added a commit that referenced this issue Apr 20, 2016
With cb80286, the helper function
stringify_ansible_playbook_command was also applied on the
`raw_arguments` content, which is not wanted. Given that users have used
the `raw_arguments` option as a workaround to avoid the bug GH-6726,
this new change ensure that any `--extra-vars` option passed as a raw
argument won't be additonally enquoted by the ansible_local
provisioner.

This change also improves the ansible remote provisioner verbose output,
but has no impact on its behaviour, which was already correct.

Note that this refactoring introduces some code duplications that are not
very elegant (see ansible_playbook_command_for_shell_execution in
host.rb and execute_ansible_playbook_from_host in base.rb). I hope we
can find a better implementation later, but it is good enough for now
since all these parts are covered by corresponding unit tests (the
`ansible_local` stuff being tested via the verbose output of the ansible
remote provisioner).
@bertramn
Copy link

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

  1. copy Vagrantfile from above to empty folder
  2. copy play.yml file from above into same folder
  3. run vagrant up and watch ansible provisioning step fail

@gildegoma
Copy link
Collaborator

gildegoma commented Apr 23, 2016

@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!

@bertramn
Copy link

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.

@gildegoma
Copy link
Collaborator

@bertramn Thanks for the additional information. Now it is very clear that the issue you're reporting is different, and shouldn't not be discussed here.

I filed it again as #7255, where I invite you to bring more information because I am not able to reproduce the described problem.

kahowell added a commit to candlepin/candlepin-jobs that referenced this issue Dec 2, 2016
CANDLEPIN_JENKINS_GITHUB_ORG when set will override what github org is
used as the base for the jobs.

This allows one to point the jobs at ones own repo, rather than at the
central repos.

Had to switch from ansible_local to ansible because of a [vagrant
bug](hashicorp/vagrant#6726). The bug is fixed
in 1.8.2, but f24 has 1.8.1, so I figure it's better to just switch for
now at least, even though I prefer ansible_local for having less deps on
the host machine.
kahowell added a commit to candlepin/candlepin-jobs that referenced this issue Dec 2, 2016
CANDLEPIN_JENKINS_GITHUB_ORG when set will override what github org is
used as the base for the jobs.

This allows one to point the jobs at ones own repo, rather than at the
central repos.

Had to switch from ansible_local to ansible because of a [vagrant
bug](hashicorp/vagrant#6726). The bug is fixed
in 1.8.2, but f24 has 1.8.1, so I figure it's better to just switch for
now at least, even though I prefer ansible_local for having less deps on
the host machine.
kahowell added a commit to candlepin/candlepin-jobs that referenced this issue Dec 2, 2016
CANDLEPIN_JENKINS_GITHUB_ORG when set will override what github org is
used as the base for the jobs.

This allows one to point the jobs at ones own repo, rather than at the
central repos.

Had to switch from ansible_local to ansible because of a [vagrant
bug](hashicorp/vagrant#6726). The bug is fixed
in 1.8.2, but f24 has 1.8.1, so I figure it's better to just switch for
now at least, even though I prefer ansible_local for having less deps on
the host machine.
@ghost ghost locked and limited conversation to collaborators Apr 4, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.