Conditional in Docker argument in Bash - bash

In my bash file I've something like this
docker run -d \
--network=host \
--name my-service \
--log-driver="$LOGGING" \
if [[ "$LOGGING" == 'splunk' ]]; then
echo "--log-opt tag={{.ImageName}}/{{.Name}}/{{.ID}} \\";
echo "--log-opt env=NODE_ENV \\";
fi
But shellcheck complains by showing the following result. Any idea?
https://github.com/koalaman/shellcheck/wiki/SC1089

Build the argument list first (in an array), then call docker. This has the additional benefit of getting rid of the ugly line continuation characters.
docker_opts=(
-d
--network=host
--name my-service
--log-driver="$LOGGING"
--log-opt="$log_opt"
)
if [[ $LOGGING == splunk ]]; then
docker_opts+=(
--log-opt "tag={{.ImageName}}/{{.Name}}/{{.ID}} \\"
--log-opt "env=NODE_ENV \\"
)
fi
docker run "${docker_opts[#]}"
The main idea, though, is to keep the conditional code as small as possible and keep it separate from the unconditional code.

I suggest to use $(if ..; then ...; fi):
docker run -d \
--network=host \
--name my-service \
--log-driver="$LOGGING" \
$(if [[ "$LOGGING" == 'splunk' ]]; then
echo "--log-opt tag={{.ImageName}}/{{.Name}}/{{.ID}}"
echo "--log-opt env=NODE_ENV"
fi)

Related

Using option flag with condition (shell script)

I have this command inside my shell script file:
docker exec dev-wordpress $phpunitPath \
--configuration $configurationPath \
--testsuit $testsuit \
--group $group \
--testdox
It is working if I set the 'testsuit' and the 'group' as command line options.
The 'testsuit' and 'group' options should be used only if those variables has value.
Same issue with 'testdox' is solved with 'if-else' but it is not a good way when I want to do the same with 3 different options.
How can I avoid the '--group' option if I don't have value in $group variable?
#!/bin/zsh
phpunit="/var/www/html/wp-content/plugins/irea/api/src/vendor/bin/phpunit"
configuration="/var/www/html/wp-content/plugins/irea/api/tests/phpunit.xml"
testdox=
filter=
testsuite=
group=
while [ "$1" != "" ]; do
case $1 in
--group ) shift
group="--group $1"
;;
--testsuite ) shift
testsuite="--testsuite $1"
;;
--filter ) shift
filter="--filter $1"
;;
--testdox ) testdox="--testdox"
;;
esac
shift
done
docker exec irea-wordpress $phpunit \
--configuration $configuration \
$testsuite \
$group \
$filter \
$testdox
You can use parameter expansion :
docker exec dev-wordpress "$phpunitPath" \
--configuration "$configurationPath" \
${testsuit:+--testsuit "$testsuit"} \
${group:+--group "$group"} \
--testdox
This script should work well for $testsuit and $group.
I didn't notice you may have problem for the other two variable.
I updated the script, maybe you can try again.
I need to build up the command as a string first then run it with eval.
eval "docker exec irea-wordpress $phpunit --configuration $configuration $testsuite $group $filter $testdox"

Unable to execute command inside double square brackets inside my Makefile

clean:
#for container_name in ${NEW_DJANGO_IMAGE_NAME} \
${NEW_MSQL_IMAGE_NAME} \
${NEW_NGINX_IMAGE_NAME} \
${NEW_REDIS_IMAGE_NAME}; \
do if [[ 'a' == 'a' ]]; then echo 'fdfdf'; fi; done;
If I do something like this it works. Now instead of this silly line
do if [[ 'a' == 'a' ]]; then echo 'fdfdf'; fi; done;
I want to write the following:
do if [[ docker ps --filter "name=^/$$container_name$$" --format '{{.Names}}' == $$container_name ]]; then echo 'fdfdf'; fi; done;
The idea is that I iterate over a number of docker containers and if it happens that some of them are running I want to stop them. So in the place of echo 'fdfdf' I want to see this line: docker container stop <CONTAINER_NAME>;
Looks as simple as hell but I can't get it to work in the Makefile...What am I doing wrong?
You apparently think that [[ cmd == "string" ]] executes cmd before performing the test. This is not the case. Use:
[[ `cmd` == "string" ]]
instead. In your case it would look like this:
do if [[ `docker ps --filter "name=^/$$container_name$$" --format '{{.Names}}'` == $$container_name ]]; then docker container stop $$container_name; fi; done;
Or, a bit more readable, maybe:
IMAGES := $(NEW_DJANGO_IMAGE_NAME) $(NEW_MSQL_IMAGE_NAME) \
$(NEW_NGINX_IMAGE_NAME) $(NEW_REDIS_IMAGE_NAME)
clean:
#for cn in $(IMAGES); do \
tmp=`docker ps --filter "name=^/$$cn$$" --format '{{.Names}}'`; \
if [ -n "$$tmp" ]; then \
docker container stop $$cn; \
fi; \
done
Note that, in this last version, we use the bourne shell test commmand ([) instead of the bash-only conditional expression ([[...]]).

Trouble implementing chained variables in Makefile

I'm looking to use the previous variable to look up the next variable (these should all be set as envvars) I have this working in a smaller extent with just profile, but when i try chaining variables together they fall flat...
Expected output is that make build has TF_VARS_* set as envvars and that they are able to be used as regular variables for the next variable input. I assume that there should be an export of TF_VAR_aws_profile TF_VAR_aws_vpc and TF_VAR_aws_net into global scope, this seemed to work fine for when I was JUST using TF_VAR_aws_profile and was able to confirm with echo $TF_VAR_aws_profile
export TF_VAR_aws_profile TF_VAR_aws_vpc TF_VAR_aws_net
# An implicit guard target, used by other targets to ensure
# that environment variables are set before beginning tasks
assert-%:
# if [ "${${*}}" = "" ] ; then \
echo "Environment variable $* not set" ; \
exit 1 ; \
fi
vault:
## This is the problem section here....
# read -p "Enter AWS Profile Name: " profile ; \
vpc=$(shell aws --profile "$${profile}" --region us-west-2 ec2 describe-vpcs |jq -r '.[] | first | .VpcId') ; \
net=$(shell aws --profile "$${profile}" --region us-west-2 ec2 describe-subnets --filters "Name=vpc-id,Values=$${vpc}" |jq -r '.[] | first | .SubnetId') ; \
TF_VAR_aws_profile=$$profile TF_VAR_aws_vpc=$$vpc TF_VAR_aws_net=$$net make build && \
TF_VAR_aws_profile=$$profile make keypair && \
TF_VAR_aws_profile=$$profile make plan && \
TF_VAR_aws_profile=$$profile make apply
build: require-packer
aws-vault exec $(TF_VAR_aws_profile) --assume-role-ttl=60m -- \
"/usr/local/bin/packer" "build" \
"-var" "builder_subnet_id=$(TF_VAR_aws_net)" \
"-var" "builder_vpc_id=$(TF_VAR_aws_vpc)" \
"packer/vault.json"
require-packer: assert-TF_VAR_aws_vpc assert-TF_VAR_aws_net
# echo "[info] VPC: $(TF_VAR_aws_vpc)" ## Not set
# echo "[info] NET: $(TF_VAR_aws_net)" ## Not set
packer --version &> /dev/null
UPDATE
Looks like it might be something to do with line termination on the vault expression, added ; \ to continue the lines
Got it, it was a combination of TWO things, one running as $(shell \$CMD) does not evaluate the inner expression variables, I needed to revert to ticks instead to evaluate variables (keep in mind, variables STILL need $$ in order to evaluate inside of Makefiles) as well as the lines needed to be continued, so terminating only with ; is not effective, we need to use ; \.
Please note that I updated my variable names from above, they are now $$subnet and $$prvnet because I'm a spacing nazi...
export TF_VAR_aws_profile TF_VAR_aws_prvnet TF_VAR_aws_subnet
# An implicit guard target, used by other targets to ensure
# that environment variables are set before beginning tasks
assert-%:
# if [ "${${*}}" = "" ] ; then \
echo "Environment variable $* not set" ; \
exit 1 ; \
fi
vault:
# read -p "Enter AWS Profile Name: " profile ; \
prvnet=`aws --profile "$${profile}" --region us-west-2 ec2 describe-vpcs |jq -r '.[] | first | .VpcId'` ; \
subnet=`aws --profile "$${profile}" --region us-west-2 ec2 describe-subnets --filters "Name=vpc-id,Values=$${prvnet}" |jq -r '.[] | first | .SubnetId'` ; \
TF_VAR_aws_profile=$$profile TF_VAR_aws_prvnet=$$prvnet TF_VAR_aws_subnet=$$subnet make build && \
TF_VAR_aws_profile=$$profile make keypair && \
TF_VAR_aws_profile=$$profile make plan && \
TF_VAR_aws_profile=$$profile make apply
build: require-packer
aws-vault exec $(TF_VAR_aws_profile) --assume-role-ttl=60m -- \
"/usr/local/bin/packer" "build" \
"-var" "builder_subnet_id=$(TF_VAR_aws_net)" \
"-var" "builder_vpc_id=$(TF_VAR_aws_vpc)" \
"packer/vault.json"
require-packer: assert-TF_VAR_aws_prvnet assert-TF_VAR_aws_subnet
# echo "[info] VPC: $(TF_VAR_aws_prvnet)" ;
# echo "[info] NET: $(TF_VAR_aws_subnet)" ;
packer --version &> /dev/null

sh conditionally pass an option to command

I want to do something like:
#!/bin/sh
[ -f "/tmp/nodes" ]
[[ $? -eq 0 ]] && VAL=$? ||
geth --datadir /root/.ethereum \
${VAL+"--nodekey \"/root/nodekey.txt\""} \
--networkid 1999 \
--rpc \
--rpcaddr "0.0.0.0" \
I want the option --nodekey "/root/nodekey.txt" to be passed if the file /tmp/nodes exists. How can that be done more elegantly than an if with two nearly identical commands?
--EDIT--
This is the best I've been able to get working so far:
if [ $VAL -eq 0 ]; then
/geth --datadir /root/.ethereum \
--nodekey "/root/nodekey.txt" \
# No dice
# Would be nice if this worked so I didn't need the if
# ${VAL+ --nodekey "/root/nodekey.txt" } \
--networkid 1999 \
--rpc \
--rpcaddr "0.0.0.0"
else
/geth --datadir /root/.ethereum \
--networkid 1999 \
--rpc \
--rpcaddr "0.0.0.0" \
fi
This is another line in the file and works fine:
ENODE_URL=$(/geth --datadir /root/.ethereum ${VAL+ --nodekey "/root/nodekey.txt"} --exec "${JS}" console 2>/dev/null | sed -e 's/^"\(.*\)"$/\1/')
There's a bashism here, but it's [[ $? -eq 0 ]], as [[ is a ksh extension adopted by bash. There's no point to using $? at all here, since you can just directly perform your assignment based on whether the test -f succeeds:
touch /tmp/nodes # set us up for the truthy path
if test -f /tmp/nodes; then tmp_nodes_exists=1; else unset tmp_nodes_exists; fi
printf '%s\n' /tmp/nodes ${tmp_nodes_exists+"REALLY EXISTS" "(yes, really)"}
...properly emits as output (as run with dash, perhaps the most common minimal /bin/sh interpreter):
/tmp/nodes
REALLY EXISTS
(yes, really)
By contrast, to demonstrate that the other path fails as it should:
rm -f -- /tmp/nodes # set us up for the falsey path
if test -f /tmp/nodes; then tmp_nodes_exists=1; else unset tmp_nodes_exists; fi
printf '%s\n' /tmp/nodes ${tmp_nodes_exists+"REALLY EXISTS" "(yes, really)"}
emits as output only:
/tmp/nodes

How to securely quote an optional flag?

If $FOO is set, I want to run:
cd "$OUTPUTDIR" && fpm -s dir -t rpm \
-a x86_64 \
--epoch "${PKGEPOCH}" \
-n "${PACKAGENAME}" \
--version "${PKGVERSION}" \
--iteration "${PKGRELEASE}" \
-C "$OUTPUTDIR/installroot" \
--description="${PKGDESCRIPTION}" \
.
If $FOO is not set, I don't want to include the flag at all.
The program fails if --description= (empty).
However, sometimes descriptions include quotes and other special characters so I don't want to do:
if [[ -z "PKGDESCRIPTION" ]]; then
D=--description="${PKGDESCRIPTION}"
fi
cd "$OUTPUTDIR" && fpm -s dir -t rpm \
-a x86_64 \
--epoch "${PKGEPOCH}" \
-n "${PACKAGENAME}" \
--version "${PKGVERSION}" \
--iteration "${PKGRELEASE}" \
-C "$OUTPUTDIR/installroot" \
$D
.
If I put quotes around $D then it becomes an additional (blank) arg.
Is there a way to do this that won't be a security problem if $PKGDESCRIPTION includes special chars AND doesn't generate a blank arg?
Using an array is the only sane way to do this:
options=( -a x86_64 -C "$OUTPUTDIR/installroot" )
[[ $PKGEPOCH ]] && options+=( --epoch "$PGKEPOCH" )
[[ $PACKAGENAME ]] && options+=( -n "$PACKAGENAME" )
[[ $PKGVERSION ]] && options+=( --version "$PKGVERSION" )
[[ $PKGRELEASE ]] && options+=( --iteration "$PKGRELEASE" )
[[ $PKGDESCRIPTION ]] && options+=( --description="$PKGDESCRIPTION" )
cd "$OUTPUTDIR" && fpm -s dir -t rpm "${options[#]}"
See also http://mywiki.wooledge.org/BashFAQ/050
If PKGDESCRIPTION is the only argument that needs this conditional treatment, you can use an "alternate value" expansion:
[...] && fpm -s dir -t rpm \
[...] \
${PKGDESCRIPTION:+ --description="${PKGDESCRIPTION}"} \
.
Explanation: the :+ means this will expand to nothing at all unless PKGDESCRIPTION is set to a non-null value; if it is set to something non-null, it expands to --description="${PKGDESCRIPTION}", and the double-quotes make it ignore special characters in PKGDESCRIPTION's value. Note that the space in :+ -- isn't needed, but does no harm and makes it at least slightly easier to read.
BTW, if more than one argument needs this treatment, I'd go with #glenn jackman's approach.

Resources