I'm using nvm (https://github.com/creationix/nvm), which is essentially a shell script that you source into your shell and then call, for example, nvm install [version]. But no matter how I try and call that function, ansible can't seem to find it.
I've tried using the command and shell modules. I've tried using become and become_user. I've tried using sudo -iu like in https://github.com/leonidas/ansible-nvm/blob/master/tasks/main.yml, but it doesn't work for me. It must be possible though since it works in that file.
How can I run any shell function in Ansible? In this case I've got a source nvm.sh in my .zshrc which allows me to execute nvm commands from the interactive shell fine.
You'll need to use the shell module, because you want to run shell commands, and you'll need to source in the nvm script into that environment. Something like:
- shell: |
source /path/to/nvm
nvm install ...
Whether or not you use become depends on whether or not you want to run the commands as root (or another user).
Here is my playbook for this:
- hosts: all
vars:
# https://github.com/nvm-sh/nvm/releases
nvm_version: "0.34.0"
# https://github.com/nodejs/node/releases
# "node" for latest version, "--lts" for latest long term support version,
# or provide a specific version, ex: "10.16.3"
node_version: "--lts"
tasks:
- name: Get_nvm_install_script | {{ role_name | basename }}
tags: Get_nvm_install_script
get_url:
url: https://raw.githubusercontent.com/nvm-sh/nvm/v{{ nvm_version }}/install.sh
dest: "{{ ansible_user_dir }}/nvm_install.sh"
force: true
- name: Install_or_update_nvm | {{ role_name | basename }}
tags: Install_or_update_nvm
command: bash {{ ansible_user_dir }}/nvm_install.sh
- name: Install_nodejs | {{ role_name | basename }}
tags: Install_nodejs
shell: |
source {{ ansible_user_dir }}/.nvm/nvm.sh
nvm install {{ node_version }}
args:
executable: /bin/bash
Note the use of executable: /bin/bash, as source command is not available in all shells, so we specify bash because it includes source
As an alternative to source you can use the dot:
- name: Install_nodejs | {{ role_name | basename }}
tags: Install_nodejs
shell: |
. {{ ansible_user_dir }}/.nvm/nvm.sh
nvm install {{ node_version }}
Related
The following command fails most likely because it fails to interpolate the shell variable packdir
- name: archive_artifacts.yml --> Clear git history from packs directories
command: 'for packdir in {{ packs_dir }}/*; do rm -rf {{ packs_dir }}/"${packdir}"/.git; done'
args:
chdir: "{{ temp_build_directory }}"
packs_dir is a variable in the defaults/main.yml of the specific role:
packs_dir: "packs"
Is there a way of having the command module substituting both ansible and shell variables?
Is there a way of having the command module substituting both ansible and shell variables?
Yes. Make sure the shell variable is present in the environment of the host, user and shell you run the command in.
- hosts: localhost
vars:
env_variable: SHELL
tasks:
- command: "echo ${{ env_variable }}"
register: result
- debug: msg="{{ result.stdout }}"
I want to create a virtualenv with virtualenvwrapper via ansible.
My default vars:
wrapper_bin: '/usr/local/bin/virtualenvwrapper.sh'
virtualenv_dir: '/home/user/.envs'
{{ project_name }} comes from extra variable
My task:
---
- name: Make a virtualenv
shell: "{{ wrapper_bin }} && mkvirtualenv {{ project_name }}"
args:
executable: /bin/bash
creates: "{{ virtualenv_dir}}/{{ project_name }}"
On a remote machine all works well.
which virtualenvwrapper.sh returns correct way /usr/local/bin/virtualenvwrapper.sh
But Ansible throw an error /bin/bash: mkvirtualenv: command not found
Add PATH to the environment, for example:
---
- name: Make a virtualenv
shell: "{{ wrapper_bin }} && mkvirtualenv {{ project_name }}"
args:
executable: /bin/bash
creates: "{{ virtualenv_dir}}/{{ project_name }}"
environment:
path: /usr/local/bin:/usr/bin:/bin
It is one of the most common problems with Ansible - it runs shell non-interactive, non-logon session which by shell design does not source all the rc-files. In result the PATH you have when logged on interactively and the PATH Ansible gets in its environment differ.
- name: Make a virtualenv
shell: export PATH=$PYENV_ROOT/bin:$PATH mkvirtualenv {{ project_name }}
If you use pyenv.
This works for me
- name: Create the required virtualenv
shell: |
export WORKON_HOME="{{ virtualenv_dir }}"
export VIRTUALENVWRAPPER_PYTHON=/usr/bin/python3
. /usr/local/bin/virtualenvwrapper.sh
mkvirtualenv "{{project_name}}" -p python3.6
args:
executable: /bin/bash
creates: "{{ virtualenv_dir}}/{{ project_name }}"
register: appServer_virtualenv
become_user: "{{sudo_user}}"
I know about Ansible's environment: command at the top of playbook, but I don't think that will work for me seeing how I don't know the variables value prior to the execution of the playbook. I'm trying to retrieve package versions and PHP Modules and log them to a file. I want to use regex to capture the version and store it to an environment variable. Then I want to write that variable equals that variable's value to an environment file with a shell command. I also want to pull an array from the environment and loop through that. Ansible doesn't seem to persist the shell environment and the environment variable gets wiped out between commands. This is simple in Bash. Is this possible in Ansible? I'm trying:
---
- hosts: all
become: yes
vars:
site_variables:
code_directory: /home/
dependency_versions:
WGET_VERSION: placeholder
PHP_MODULES: placeholder
tasks:
- name: Retrieve Environment
shell: export WGET_VERSION=$(wget --version | grep -o 'Wget [0-9]*.[0-9]*\+')
shell: export PHP_MODULES=$(php -m)
shell: echo "export {{ item }}={{ lookup('env', item ) }}" >> {{ site_variables.code_directory }}/.env.log
with_items:
- WGET_VERSION
- name: Write PHP Modules Out
shell: export PHP_MODULES=$(php -m)
shell: export PHP_MODULES=$(echo {{ lookup('env', 'PHP_MODULES') }} | sed 's/\[PHP Modules\]//g')
shell: export PHP_MODULES=$(echo {{ lookup('env', 'PHP_MODULES') }} | sed 's/\[Zend Modules\]//g')
shell: export PHP_MODULES=({{ lookup('env', 'PHP_MODULES') }})
shell: echo "# - {{ item.0 }}" >> {{ site_variables.code_directory }}/.env.log
with_items:
- "{{ lookup('env', 'PHP_MODULES') }}"
There's a lot going on here.
First, lookup always runs on the ansible control host, while the script that you pass to the shell module is running on the remote server. So you will never be able to get a remote environment variable using lookup.
For details: https://docs.ansible.com/ansible/playbooks_lookups.html
Secondly, environment variables don't propagate from a child to parent. If you have a script that does this...
export MYVARIABLE=foo
...and you run that script, your current environment will not suddenly have a variable named MYVARIABLE. This is just as true for processes spawned by Ansible as it is for processes spawned by your shell.
If you want to set an ansible variable, consider using the register keyword to get the value:
- hosts: localhost
gather_facts: false
tasks:
- name: get wget version
command: wget --version
register: wget_version_raw
- name: extract wget version
set_fact:
wget_version: "{{ wget_version_raw.stdout_lines[0].split()[2] }}"
- name: show wget version
debug:
msg: "wget version is: {{ wget_version }}"
I always wonder what is the good way to replace the following shell tasks using the "ansible way" (with get_url, etc.):
- name: Install oh-my-zsh
shell: wget -qO - https://raw.github.com/robbyrussell/oh-my-zsh/master/tools/install.sh | bash -
or
- name: Install nodesource repo
shell: curl -sL https://deb.nodesource.com/setup_5.x | bash -
This worked for me:
- name: Download zsh installer
get_url:
url: https://raw.github.com/robbyrussell/oh-my-zsh/master/tools/install.sh dest=/tmp/zsh-installer.sh
- name: Execute the zsh-installer.sh
shell: /tmp/zsh-installer.sh
- name: Remove the zsh-installer.sh
file:
path: /tmp/zsh-installer.sh
state: absent
#RaviTezu solution doesn't work because the file/script that you wish to execute must be on the machine where you execute your play/role.
As per the documentation here
The local script at path will be transferred to the remote node and then executed.
So one way to do it is by downloading the file locally and using a task like below:
- name: execute the script.sh
script: /local/path/to/script.sh
Or you can do this:
- name: download setup_5.x file to tmp dir
get_url:
url: https://deb.nodesource.com/setup_5.x
dest: /tmp/
mode: 0755
- name: execute setup_5.x script
shell: setup_5.x
args:
chdir: /tmp/
I would go for the first method if you are uploading your own script, the second method is more useful in your case because the script might gets updated in time so you are sure each time you execute it it uses the latest script.
For me, the following statement worked:
- name: "Execute Script"
shell: curl -sL https://rpm.nodesource.com/setup_6.x | bash -
Consider using the get_url or uri module rather than running curl.
For example:
- name: Download setup_8.x script
get_url: url=https://deb.nodesource.com/setup_8.x dest=/opt mode=755
- name: Setup Node.js
command: /opt/setup_8.x
- name: Install Node.js (JavaScript run-time environment)
apt: name=nodejs state=present
This playbook is what I've come up with. So far, it's as close to an idiomatic solution as I have come, and seems to be idempotent.
It will check for the existence of a command (in this case, starship) and if/when the test fails it will download the script.
---
- name: Check for Starship command
command: command -v starship >/dev/null 2>&1
register: installed
no_log: true
ignore_errors: yes
- name: Download Starship installer
get_url:
url: https://starship.rs/install.sh
dest: /tmp/starship-installer.sh
mode: 'u+rwx'
when: installed.rc != 0
register: download
- name: Run the install script
shell: /tmp/starship-installer.sh
when: download.changed
- name: Remove the starship-installer.sh
file:
path: /tmp/starship-installer.sh
state: absent
May be this basic example can help you to start:
---
- name: Installing Zsh and git
apt: pkg=zsh,git state=latest
register: installation
- name: Backing up existing ~/.zshrc
shell: if [ -f ~/.zshrc ]; then mv ~/.zshrc{,.orig}; fi
when: installation|success
sudo: no
- name: Cloning oh-my-zsh
git:
repo=https://github.com/robbyrussell/oh-my-zsh
dest=~/.oh-my-zsh
when: installation|success
register: cloning
sudo: no
- name: Creating new ~/.zshrc
copy:
src=~/.oh-my-zsh/templates/zshrc.zsh-template
dest=~/.zshrc
when: cloning|success
sudo: no
Note the: "force=yes", which will always download the script, overriding the old one.
Also note the "changed_when", which you can refine per your case.
- name: 'Download {{ helm.install_script_url }}'
environment:
http_proxy: '{{proxy_env.http_proxy | default ("") }}'
https_proxy: '{{proxy_env.https_proxy | default ("") }}'
no_proxy: '{{proxy_env.no_proxy | default ("") }}'
get_url: url={{ helm.install_script_url | default ("") }} dest=/tmp/helm_install_script force=yes mode="0755"
when: helm.install_script_url is defined
tags:
- helm_x
- name: Run {{ helm.install_script_url }}
environment:
http_proxy: '{{proxy_env.http_proxy | default ("") }}'
https_proxy: '{{proxy_env.https_proxy | default ("") }}'
no_proxy: '{{proxy_env.no_proxy | default ("") }}'
command: "/tmp/helm_install_script"
register: command_result
changed_when: "'is up-to-date' not in command_result.stdout"
when: helm.install_script_url is defined
args:
chdir: /tmp/
tags:
- helm_x
So I have an Ansible playbook and I'm trying to call a command for each item in a list, but also run that command over a fileglob. There is a "with_nested" in Ansible, and it can take variable names, but if I add a "with_fileglob," it just inserts "with_fileglob" as the filename instead of actually doing a glob.
vars:
repo_versions:
- version: trusty
distribution: Ubuntu
- version: wheezy
distribution: Debian
...
- command: reprepro -b /var/www/html includedeb {{ item[0].version }} {{ item[1] }}
with_nested:
- repo_versions
with_fileglob: /home/repoman/debs/*.deb
when: debs_available.stat.exists == True
I've tried a couple of different combinations and I can't seem to get it to process the command in a double for loop (for each .version, for each .deb file)
This should be what you are trying to accomplish.
I used the shell module to register the output of the fileglob, and then the stdout_lines property of the registerd variable in the loop. I have converted the task from my test to your actual commands and paths, so you might need to double check:
vars:
repo_versions:
- version: trusty
distribution: Ubuntu
- version: wheezy
distribution: Debian
tasks:
- shell: ls -1 /home/repoman/debs/*.deb
register: repo_list
- command: reprepro -b /var/www/html includedeb {{ item[0].version }} {{ item[1] }}
with_nested:
- repo_versions
- repo_list.stdout_lines