Skip to content

Instantly share code, notes, and snippets.

@BluSyn
Last active June 29, 2018 19:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save BluSyn/71a2040d610e06bf8ee06a9480d17fd3 to your computer and use it in GitHub Desktop.
Save BluSyn/71a2040d610e06bf8ee06a9480d17fd3 to your computer and use it in GitHub Desktop.
Update docker swarm secrets
#!/bin/bash
## Update specific secret by name
## (Seriously, this is annoying and should be baked in docker stack deploy)
## Call with env + secret file path, exactly as defined in docker-compose.yml
## Eg: ./update-secret.sh master ./secrets/worker/worker.json
## Dependencies: yq and jq
## yq - pip install yq
## jq - pacman -Sy jq
ENV=$1
SFILE=$2
if [[ -z $ENV || -z $SFILE ]]; then
echo "Must specify deployment environment and secret file."
echo "Example: update-secret.sh master ./secrets/worker/worker.conf"
exit
fi
COMPOSE="./docker-compose.yml"
# Connects local docker to swarm
# Replace with "docker-machine env <SWARM>" in most setups
source ~/.deploy_profile
SECRET=`yq 'path(.secrets[] | select(.file == "'$SFILE'"))[1]' $COMPOSE | tr -d '"'`
SERVICES=`docker service ls --format '{{.Name}}'`
LIST=()
TARGET=""
if [ -z $SECRET ]; then
echo "Could not find secret using that file"
exit
fi
SECRETNAME="${ENV}_${SECRET}"
echo "Found secret: $SECRET"
echo "Probing service list for secret..."
for SERVICE in ${SERVICES[@]}
do
SPEC=`docker service inspect $SERVICE | jq '.[].Spec.TaskTemplate.ContainerSpec.Secrets[]? | select(.SecretName | startswith("'$SECRETNAME'"))'`
TARGET=`echo "$SPEC" | jq '.File.Name' | head -n1 | tr -d '"'`
if [[ $TARGET ]]; then
echo "Found: $SERVICE"
LIST+=("$SERVICE:$TARGET")
# SecretName may have been changed due to previous update
SECRETNAME=`echo "$SPEC" | jq '.SecretName' | head -n1 | tr -d '"'`
fi
done
if [ -z $LIST ]; then
echo "Could not find any services using this secret. Nothing to do."
exit
fi
# Randomly generate a secret name for duplicate secret
NEWSECRET="${ENV}_${SECRET}_${RANDOM}"
echo "Creating duplicate secret... ($NEWSECRET)"
docker secret create $NEWSECRET $SFILE
for INSTANCE in ${LIST[@]}
do
readarray -td ':' SPLIT <<< "$INSTANCE"
declare SPLIT
echo "Updating service ${SPLIT[0]}"
docker service update --update-parallelism 0 --secret-rm $SECRETNAME --secret-add source=$NEWSECRET,target=${SPLIT[1]} ${SPLIT[0]}
done
# Must delete old secret or future calls to "docker stack deploy" will fail
docker secret rm $SECRETNAME
@jamiejackson
Copy link

jamiejackson commented Jun 24, 2018

Thanks for the contribution. I haven't tried this yet, but some thoughts:

This seems to make some assumptions (but which aren't always true):

  1. There is only one compose file.
  2. The compose file is named docker-compose.yml.
  3. The source of the secret is a file.

However, to work around 1 & 2, I supposed one could:

docker-compose -f a.yml -f b.yml -f c.yml config 2>/dev/null > docker-compose.yml
./update-secret.sh stack_name ./path/on/master/to/secret/file

Also, consider tweaking the example command so that the argument meanings are more self-evident. (My example, above, attempts to do so.)

@BluSyn
Copy link
Author

BluSyn commented Jun 29, 2018

Agreed this does make a lot of assumptions based around my current setup. Could easily be made more generic for other setups as you mentioned. The idea was to be able to run the cmd with as few params as possible so updates could be done with little effort.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment