Ansible: enforce pipefail - bash

Earlier today, we experienced a nasty issue that was caused by the following shell pipe:
- name: get remote branches
shell: git ls-remote -h git#bitbucket.org:orga/repo.git | sed 's_.*refs/heads/__g'
register: branches_remote
The git command fails, but the return code of the entire pipe is 0. This is default bash/sh behavior.
To fix this, in sh/bash, you can set -o pipefail or set -e. Is it possible to do that in ansible, preferably globally for all my shell commands?

In general you should try to use the shell commands as a last resort as they tend to be a bit brittle. If you need to use the shell module with any shell options, simply submit it as part of your command pipeline as shown below. The executable parameter forces the use of bash shell.
[user#ansible ~]$ ansible myhost -m shell -a "executable=/bin/bash set -o pipefail && false | echo hello there"
myhost | FAILED | rc=1 >>
hello there
[user#ansible ~]$ ansible myhost -m shell -a "executable=/bin/bash set -o pipefail && true | echo hello there"
myhost | success | rc=0 >>
hello there

Bash accepts set -o pipefail, but the default executable is /bin/sh which, on platforms such as Debian, is not guaranteed to support this, probably for a good reason (dash is a pure-posix shell).
You can configure /bin/bash as the executable in ansible.cfg:
[defaults]
executable = /bin/bash
The same can be done in molecule.yml
provisioner:
name: ansible
config_options:
defaults:
executable: /bin/bash
There is no configuration executable_flags available, so you should adjust the actions yourself:
- name: pipes that fail should fail the action
shell: |
set -e -o pipefail
git ls-remote -h git#bitbucket.org:orga/repo.git | sed 's_.*refs/heads/__g'
register: branches_remote

You can set the executable for the shell module, for example:
- name: get remote branches
shell: |
set -e -o pipefail
git ls-remote -h git#bitbucket.org:orga/repo.git | sed 's_.*refs/heads/__g'
args:
executable: /usr/bin/bash
register: branches_remote

Related

Drop from root to user preserving ALL environment variables

The following bash command works to drop down to user privileges, and preserve an environment for the most part:
root#machine:/root# DOLPHIN=1 sudo -E -u someuser bash -c 'echo $DOLPHIN'
1
However, this does not work for all variables, such as PATH, and LD_LIBRARY_PATH:
root#machine:/root# echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games
root#machine:/root# sudo -E -u someuser bash -c 'echo $PATH'
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin
Notice the PATH is different ^
Why is this happening?
Must be some bash mechanics I don't understand...
Looks like this is a workable option:
root#machine:/root# sudo PATH=$PATH LD_LIBRARY_PATH=$LD_LIBRARY_PATH -E -u someuser bash -c 'echo $PATH'
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games

Container date won't update in entrypoint script

I built a container using the docker-compose script below:
services:
client:
image: alpine
environment:
- BACKUP_ENABLED=1
- BACKUP_INTERVAL=60
- BACKUP_PATH=/data
- BACKUP_FILENAME=db_backup
networks:
- dbnet
entrypoint: |
sh -c 'sh -s << EOF
apk add --no-cache mysql-client
while true
do
then
sleep $$BACKUP_INTERVAL
echo "$$(date +%FT%H.%M) - Making Backup to : $$BACKUP_PATH/$$(date +%F)/$$BACKUP_FILENAME-$$(date +%FT%H.%M).sql.gz"
mysqldump -u root -ppassword -h dblb --all-databases | gzip > $$BACKUP_PATH/$$(date +%F)/$$BACKUP_FILENAME-$$(date +%FT%H.%M).sql.gz
done
EOF'
But I encounter an issue where the date won't be updated and causes the loop keep backup to the same created file.
Every 60s the log will some the same date value. Here the container's log:
The same thing happened when I tried to manually write the script inside the container:
The timestamp always displays correctly when I only type date inside the container console.
Why won't the date update? What did I miss in the script?
Why won't the date update?
Because it is expanded by outer shell. Compare a shell script:
#!/bin/sh
# in a script
# this is running inside a shell
cat <<EOF # cat just prints data
$(date) # not cat, **but the shell**, expands $(date)
EOF
vs:
sh -c '
# this is running inside a shell
sh -s <<EOF # sh -s executes input data
echo $(date) # not sh -s, but **the outer shell**, expands $(date). ONCE
EOF
'
That sh -c sh -s and entrypoint is all unorthodox, just run the command that you want to run.
command:
- sh
- -c
- |
apk add --no-cache mysql-client
while sleep $$BACKUP_INTERVAL; do
echo "$$(date +%FT%H.%M) - Making Backup to : $$BACKUP_PATH/$$(date +%F)/$$BACKUP_FILENAME-$$(date +%FT%H.%M).sql.gz"
done

Environment variable overrides command

I set the EC2_IP_ADDRESS variable
$ export EC2_IP_ADDRESS="`docker run -it -v $PWD/infrastructure:/terraform -v $PWD/data:/data terraform sh -c "terraform init; terraform state show module.aws_ec2.aws_eip.aws_instance_eip" | grep public_ip | awk '{print $3}'`"
And then I'm trying to copy some files into the EC2 instance:
$ scp -i key.pem -r src/* ec2-user#$EC2_IP_ADDRESS:/home/ec2-user/src/
But the output is an error: : nodename nor servname provided, or not known
Output of $ echo "scp -i key.pem -r src/* ec2-user#$EC2_IP_ADDRESS:/home/ec2-user/src/"
:/home/ec2-user/src/c/* ec2-user#X.X.X.X
It seems that anything after the variable EC2_IP_ADDRESS goes to the beginning of the string, overriding the command.
Any ideas on how to fix this?
It seems the variable contains $'\r' at the end. Remove it with
EC2_IP_ADDRESS=${EC2_IP_ADDRESS%$'\r'}

Using shell expansion with Ansible

I'm trying to execute a remote command via Ansible which requires gathering the PID of the process:
ansible webserver -m shell -a 'jstack -l $(pgrep -f java)'
However it seems Ansible is not able to expand the shell command contained in parenthesis (tried as well with grave accent):
127.0.0.1 | FAILED | rc=1 >>
Usage:
jstack [-l] <pid>
Executing just the command in the expansion reveals that expansion does not take place:
ansible webserver -a 'echo $(pgrep -f java)'
192.168.0.1 | success | rc=0 >>
$(pgrep -f java)
You'll want to escape the $ dollar sign, like so:
ansible all -i inventories/prod/hosts -m shell -a "echo \$(pgrep -f java)"

shell script to run multiple scripts from different shells

I want to run 2 different scripts from a single master shell script.
The first one uses the following command "rosh -n -l abcd" (It will log me in to the server with the user abcd and on the same shell I need to run the other script#2 and script#3 ...etc.)
Script#2- From there I need to change user using su - xyz and provide a password (it is fine if I can hardcode this in the file) (Script name is logintoServer)
Script#3- Run some script in the same shell to verify start of stop of server...
I have done the following but failed
I have one script which has rosh -n <servername> -l abcd /bin/sh -c "su - xyz" (I have to run this command in the same shell)
The below are the errors:
I am getting error while executing "standard in must be a tty"
I have tried to create 2 different scripts and run, but the problem is once the first script is run it does not run the 2nd script till I exit the script. (I need to run the 2nd script from the sub-shell created by the 1st script....)
I don't have rosh and I don't have a man page for rosh but a similar problem exists with ssh:
ssh localhost /bin/bash -c 'echo x' # (prints nothing)
ssh localhost "/bin/bash -c 'echo x'" # x
ssh localhost "/bin/bash -c 'tty'" # not a tty
ssh -t localhost "/bin/bash -c 'tty'" # /dev/pts/12\nConnection to localhost closed.
ssh localhost "/bin/bash -c 'su - $USER'" # su: must be run from a terminal
ssh -t localhost "/bin/bash -c 'su - $USER'"
the last asked for a password and then gave me a shell, so that would be 2 of 3 steps.
so one idea is to see if rosh has the -t option, too and the other is to enclose /bin/bash... with quotes, too (will require some escaping for the 3rd level).
What does rosh say with equivalent commands?
UPDATE
latest state:
rosh -n $host -l abcd -t "/bin/sh -c 'su - $user'"
Next I would save one step by saying /bin/su - xyz instead /bin/sh -c 'su - xyz', then you can use single quotes later, e.g.
rosh -n $host -l abcd -t "/bin/su - $user -c 'echo $PATH'"
this should print $PATH as seen by the echo command. Apparently it doesn't contain java. try man su, which java, man which.
su ... -c cmd runs cmd with the shell specified in /etc/passwd, so say </etc/passwd grep $user on the remote machine to find out which shell is used. if it's bash you can change $PATH in .bashrc or so, for other shells I don't know exactly.
Or specify an absolute path when launching java.
regarding password: with ssh I managed to use private key / public key and ssh-agent. For rosh I don't know if that works, too.

Resources