I have an Ansible role which I'm trying to migrate to macOS.
The following step fails:
- name: get go user details
command: sh -c 'echo $HOME'
become: true
become_user: "{{ go_user }}"
become_flags: -Hi
changed_when: false
register: go_user_check
The error emitted is:
TASK [default : get go user details] *******************************************
fatal: [127.0.0.1]: FAILED! => {"msg": "Failed to set permissions on the temporary files Ansible needs to create when becoming an unprivileged user (rc: 1, err: chown: /var/tmp/ansible-tmp-1533765311.28-97292171123102/: Operation not permitted\nchown: /var/tmp/ansible-tmp-1533765311.28-97292171123102/command.py: Operation not permitted\n}). For information on working around this, see https://docs.ansible.com/ansible/become.html#becoming-an-unprivileged-user"}
This same step works just fine on all the Linux distributions
I'm testing against. When I execute sudo -Hiu username sh -c 'echo $HOME', I get what I would expect: /Users/travis.
When I execute the following:
ansible -i 127.0.0.1, -c local --become --become-user travis \
-m command -a 'sh -c "echo $HOME"' all
I get exactly what I'd expect, /Users/travis.
I'm reading the documentation linked to by Ansible but I'm not seeing a workaround.
Is there a different way I should be executing this on OSX?
I was able to get it to proceed with the following modification:
diff --git a/tasks/discover/go.yml b/tasks/discover/go.yml
index 090ce7d..f0c2b89 100644
--- a/tasks/discover/go.yml
+++ b/tasks/discover/go.yml
## -3,9 +3,10 ##
command: sh -c 'echo $HOME'
become: true
become_user: "{{ go_user }}"
- become_flags: -Hi
changed_when: false
register: go_user_check
+ vars:
+ ansible_ssh_pipelining: true
- name: set go user home
set_fact: go_user_home="{{ go_user_check.stdout_lines[0] }}"
Pipelining appears to dodge the file modification that is failing. Unfortunately, I don't have the answer as to why it's failing, so other answers are still welcome as I'd like to get to what is actually happening.
There appear to be a lot of caveats to using Ansible on macOS, so I will probably have to have my tasks diverge completely based on whether it's Linux or macOS.
Related
I am having an issue learning Ansible and getting feedback from it.
Here is the code I am running
clear && sudo ansible-playbook patching.yml -u stackoverflow -k --ask-become-pass -v
Here is my patching.yml file
---
- hosts: all
gather_facts: no
become: yes
tasks:
- name: Patching
apt: update_cache=yes force_apt_get=yes
- name: Proof of Patching
shell: apt -q -y --ignore-hold --allow-change-held-packages --allow-unauthenticated -s dist-upgrade | /bin/grep ^Inst | wc -l
register: output
- debug: var=output
What I am trying to accomplish is one to learn Ansible and two to patch my various Linux machines with some sort of feedback that they have patched I am getting this feedback after reading stack overflow for some time. It is long and un-human-friendly I would like it to be just a # or even better hostname: #.
Thank You
-Jared
I would like to run a set of raw commands (to install python) if a given
operating system on the target host is OpenBSD
I have to run these checks as pre_tasks (because to run anything as a task Python must already be present on the target system)
What I do not understand is, whether any OS characteristics variables are available in pre_task stage (like ansible_os_family)
- name: Check for Python
raw: test -e /usr/bin/python
changed_when: false
failed_when: false
register: check_python
- name: Install Python on OpenBSD
# raw: test -e /usr/bin/apt && (apt -y update && apt install -y python-minimal) || (yum -y install python libselinux-python)
raw: pkg_add -r python%3
when: check_python.rc != 0
when: ansible_os_family == "OpenBSD" # <-- problem here
I seem to get a problem when trying to use ansible_os_family
Is there a way to enable the pre_tasks without me writing my own os family checks?
PS. I used the above python installation code, following recommendation here:
[1] https://relativkreativ.at/articles/how-to-install-python-with-ansible
Following your latest comments, as said earlier by #Vladimir and myself, you will not be able to use ansible's facts variables to detect the OS on hosts without python since it is impossible to gather facts (i.e. play the setup module). You need to do that job on your own for this first step.
I don't have a BSD system I can access. So the below is totally untested for your platform. But I believe it should work, or at least put you on track. Check the content of /etc/os-release on your system to adapt the regex if needed. The one I wrote gave a correct result on Ubuntu, Debian, Centos, RHEL and on RedHat UBI and Alpine docker images.
---
- name: Fix hosts with no python at all
hosts: all
gather_facts: false
tasks:
- name: Perform a dirty OS detection if python is not installed
raw: |-
if which python > /dev/null || which python3 > /dev/null; then
echo installed
else
sed -n "s/^NAME=\"\(.*\)\"/\\1/p" /etc/os-release
fi
changed_when: false
register: dirty_detect
- name: Print message when already installed
debug:
msg: Host {{ inventory_hostname }} already has python. Next will skip.
when: dirty_detect.stdout_lines[0] == 'installed'
- name: Install python on openbsd if needed
raw: |-
pkg_add -r python%3
become: true
when: dirty_detect.stdout_lines[0] == 'OpenBSD'
- name: Install python on Ubuntu if needed
raw: |-
apt install -y python3
become: true
when: dirty_detect.stdout_lines[0] == 'Ubuntu'
- name: Back to normal life with ansible
hosts: all
tasks:
- name: Now we gathered facts and we can use all facts vars
debug:
msg: Host {{ inventory_hostname }} is running {{ ansible_os_familly }}
- name: We might have left hosts with python 2.7 only above, make sure python3 is installed
package:
name: python3
become: true
Q: "Is there a way to enable the pre_tasks without me writing my own os family checks?"
A: No. It is not. Python is needed to collect the facts.
See the verbose output. For example the output of connecting FreeBSD 12 remote host from the Ubuntu controller
shell> ansible --version
ansible 2.9.6
...
python version = 3.8.2 (default, Apr 27 2020, 15:53:34) [GCC 9.3.0]
shell> ansible test_01 -m setup -vvv
ESTABLISH SSH CONNECTION FOR USER: admin
SSH: EXEC ssh -C -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o 'User="admin"' -o ConnectTimeout=30 -o ControlPath=/export/home/admin.config/.ansible/cp/5a3ab05cf7 test_01 '/bin/sh -c '"'"'/usr/local/bin/python3.7 && sleep 0'"'"''
Without Python installed the module will crash
(127, b'', b'/bin/sh: /usr/bin/python: not found\n')
Failed to connect to the host via ssh: /bin/sh: /usr/bin/python: not found
[WARNING]: No python interpreters found for host test_01 (tried ['/usr/bin/python',
'python3.7', 'python3.6', 'python3.5', 'python2.7', 'python2.6', '/usr/libexec/platform-
python', '/usr/bin/python3', 'python'])
test_01 | FAILED! => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"module_stderr": "/bin/sh: /usr/bin/python: not found\n",
"module_stdout": "",
"msg": "The module failed to execute correctly, you probably need to set the interpreter.\nSee stdout/stderr for the exact error",
"rc": 127
}
I've been searching for quite some time and tried many variants and similar answers without success. Hopefully this is something simple I am missing.
Ansible 2.9.6
I am creating many playbooks that all share a large set of custom Roles for my clients. I want to keep all logic out of the playbooks, and place that logic in the roles themselves to allow maximum re-usability. Basically, I just want to add boilerplate playbooks for simple "Role" runs via tags, with some vars overrides here and there.
My problem is that I can't seem to make some roles idempotent by using conditions - the conditionals don't work. The error I get is:
fatal: [localhost]: FAILED! => {"msg": "The conditional check 'homebrew_base.rc != 0' failed.
The error was: error while evaluating conditional (homebrew_bash.rc != 0): 'homebrew_bash' is undefined
The error appears to be in '/Users/eric/code/client1/provisioning/roles/bash/tasks/main.yaml': line 12, column 3, but may
be elsewhere in the file depending on the exact syntax problem.
The offending line appears to be:
- name: Change shell (chsh) for macOS to homebrew managed version
^ here
"}
Below is the boilerplate code for my playbooks:
# ./playbook.yaml
---
- name: Provisioning
hosts: localhost
connection: local
pre_tasks:
- include_vars: "{{ item }}"
with_fileglob:
- "{{ playbook_dir }}/vars/global.yaml"
tags: always
tasks:
- name: Use homebrew bash binary
include_role:
name: bash
tags: bash
The above is truncated quite a bit, but only thing missing are additional var files and a whole bunch of include_roles.
Below is my role file in the entirely though. They are largely untested because of the error I keep getting.
# ./roles/bash/tasks/main.yaml
---
- name: Check /etc/shells contains "/usr/local/bin/bash"
command: grep -Fxq "/usr/local/bin/bash" /etc/shells
register: homebrew_bash
ignore_errors: True
changed_when:
- homebrew_bash.rc != 0
- name: Check that homebrew installed /usr/local/bin/bash
stat:
path: /usr/local/bin/bash
register: homebrew_bash_binary
- name: Change shell (chsh) for macOS to homebrew managed versoin
tags: bash, chsh
shell: chsh -s /usr/local/bin/bash
become: yes
when:
- homebrew_bash.rc != 0
- homebrew_bash_binary.stat.exists = True
Ps: I do plan on abstracting those hardcoded paths into roles/bash/defaults/. But I need it working first before that.
Ps2: If there is a better way to use a contains filter (instead of the grep hack), I'm all ears.
I've tried:
making separate tasks/chsh.yaml, and using the include: command to call that task within the role. When I do this, I get an odd error telling me the variable is undefined - in the tasks/chsh.yaml - even though I am checking for the variable in tasks/main.yaml! That doesn't seem right.
using quotes in various places in the conditions
commenting out each condition: both give the same error, just differenet names.
Again, I am trying to keep this logic in the roles only - not in the playbook.
Thanks!
Figured it out. I was missing the "tags" on the conditionals!
# ./roles/bash/tasks/main.yaml
---
- name: Check /etc/shells contains "/usr/local/bin/bash"
tags: bash, chsh
command: grep -Fxq "/usr/local/bin/bash" /etc/shells
register: homebrew_bash
ignore_errors: True
changed_when:
- homebrew_bash.rc != 0
- name: Check that homebrew installed /usr/local/bin/bash
tags: bash, chsh
stat:
path: /usr/local/bin/bash
register: homebrew_bash_binary
- name: Change shell (chsh) for macOS to homebrew managed versoin
tags: bash, chsh
shell: chsh -s /usr/local/bin/bash
become: yes
when:
- homebrew_bash.rc != 0
- homebrew_bash_binary.stat.exists = True
ansible 192.168.1.115 -s -m shell -a "echo -e 'oldpassword\nnewpassword\nnewpassword' | passwd myuser" -u myuser --ask-sudo-pass
I would like to update existing user with new password, I had tried this command, but it doesn't work
appreciate any Tips !
You can leverage the user module to quickly change the password for desired account. Ansible doesn’t allow you to pass a cleartext password to user module so you have to install a password hashing library to be leveraged by Python.
To install the library:
sudo -H pip install passlib
Then simply exexute your command:
ansible 192.168.1.115 -s -m user -a "name=root update_password=always password={{ yourpassword | password_hash('sha512') }}" -u myuser --ask-sudo-pass
Hope that help you
Create your shadow password (linux) with
python -c 'import crypt; print crypt.crypt("YourPassword", "$6$random_salt")'
create
update_pass.yml
execute your ansible-playbook with sudoer (bash)
ansible-playbook update_pass.yml --become --become-method='sudo' --ask-become-pass
Update password for a list of hosts using dynamic variables:
In your inventory file set a variable (pass) as the following:
ip_1# ansible_user=xxxxxx ansible_ssh_pass=xxxx ansible_sudo_pass=xxx pass='aaaa'
ip_2# ansible_user=xxxxxx ansible_ssh_pass=xxxx ansible_sudo_pass=xxx pass='bbbb'
Now in the playbook we make a backup of the shadow file and set cron task to restore the shadow file in case something went wrong than we update the password:
- hosts: your_hosts
gather_facts: no
tasks:
- name: backup shadow file
copy:
src: /etc/shadow
dest: /etc/shadaw.org
become: yes
- name: set cron for backup
cron:
name: restore shadow
hour: 'AT LEAST GIVE YOURSELF ONE HOUR TO BE ABLE TO CALL THIS OFF'
minute: *
job: "yes | cp /tmp/shadow /etc/"
become: yes
- name: generate hash pass
delegate_to: localhost
command: python -c "from passlib.hash import sha512_crypt; import getpass; print sha512_crypt.encrypt('{{pass}}')"
register: hash
- debug:
var: hash.stdout
- name: update password
user:
name: xxxxxx
password: '{{hash.stdout}}'
become: yes
Now we create a new playbook to call off cron task we use the new password for authentication and if authentication failed cron will remain active and restore the old password.
hosts file:
ip_1# ansible_user=xxxxxx ansible_ssh_pass=aaaa ansible_sudo_pass=aaaa
ip_2# ansible_user=xxxxxx ansible_ssh_pass=bbbb ansible_sudo_pass=bbbb
the playbook:
- hosts: your_hosts
gather_facts: no
tasks:
- name: cancel cron task
cron:
name: restore shadow
state: absent
!!Remember:
pass variable contain your password so you may consider using vault.
Give yourself time when setting cron for backup to be able to call it of (second playbook).
In worst case cron will restore the original password.
You need to have passlib installed in your ansible server.
When I sudo as a user the ansible_env does not have the correct HOME variable set - "/root". However, if I echo the HOME env variable it is correct - "/var/lib/pgsql". Is there no other way to get the home directory of a sudo'ed user?
Also, I have already set "sudo_flags = -H" in ansible.cfg and I cannot login as postgres user.
- name: ansible_env->HOME
sudo: yes
sudo_user: postgres
debug: msg="{{ ansible_env.HOME }}"
- name: echo $HOME
sudo: yes
sudo_user: postgres
shell: "echo $HOME"
register: postgres_homedir
- name: postgres_homedir.stdout
sudo: yes
sudo_user: postgres
debug: msg="{{ postgres_homedir.stdout }}"
Result:
TASK: [PostgreSQL | ansible_env->HOME] ****************************************
ok: [postgres] => {
"msg": "/root"
}
TASK: [PostgreSQL | echo $HOME] ***********************************************
changed: [postgres]
TASK: [PostgreSQL | postgres_homedir.stdout] **********************************
ok: [postgres] => {
"msg": "/var/lib/pgsql"
}
I can replicate the output above by running the playbook as the root user (either locally with - hosts: localhost, or by SSHing as root). The facts gathered by Ansible are those of the root user.
If this is what you are doing, then your workaround seems to be the best way of getting the postgres user's $HOME variable.
Even though you add sudo_user: postgres to the ansible_env->HOME task, the fact will not change since it is gathered at the start of the play.
"~" expands as the non-root user if you use "become: no". In the example below I combine that with an explicit "sudo" to do something as root in the home directory:
- name: Setup | install
command: sudo make install chdir=~/git/emacs
become: no