From 7c5e9e9b57d09964d72b3520860ec8749c90d58b Mon Sep 17 00:00:00 2001 From: Tortue Torche Date: Fri, 10 May 2019 16:33:49 +0200 Subject: [PATCH 1/4] Add custom stack environment variables Prior to this pull request `psu` script act like this: Stack env vars are set in the `deploy()` function. When a new stack is deployed it gets no env vars, and when an existing one is updated its envvars are reused (extracted from its stack definition into the stack_envvars variable and set back again). For the first case this pull request load the content of the env vars file and transform it into JSON using a `jq` command and set it as the `stack_envvars` value. For the second case, though, the script update the current stack env vars rather than setting them from scratch, keeping any value not previously set in the env file. The environment variables file path is customizable with the environment variable `$ENVIRONMENT_VARIABLES_FILE` or the `-g` flag, like this: ```bash export ACTION="deploy" export PORTAINER_USER="admin" export PORTAINER_PASSWORD="password" export PORTAINER_URL="http://portainer.local" export PORTAINER_STACK_NAME="mystack" export DOCKER_COMPOSE_FILE="/path/to/docker-compose.yml" export ENVIRONMENT_VARIABLES_FILE="/path/to/env_vars_file" ./psu ``` Or with flags: ```bash ./psu -a deploy -u admin -p password -l http://portainer.local -n mystack -c /path/to/docker-compose.yml -g /path/to/env_vars_file ``` close #7 --- psu | 120 +++++++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 78 insertions(+), 42 deletions(-) diff --git a/psu b/psu index c68fd7d..65a21d4 100755 --- a/psu +++ b/psu @@ -66,26 +66,27 @@ main() { exit 1 } -########################## -# Set globals # -# Globals: # -# ACTION # -# PORTAINER_USER # -# PORTAINER_PASSWORD # -# PORTAINER_URL # -# PORTAINER_STACK_NAME # -# DOCKER_COMPOSE_FILE # -# PORTAINER_ENDPOINT # -# PORTAINER_PRUNE # -# HTTPIE_VERIFY_SSL # -# VERBOSE_MODE # -# DEBUG_MODE # -# STRICT_MODE # -# Arguments: # -# None # -# Returns: # -# None # -########################## +################################ +# Set globals # +# Globals: # +# ACTION # +# PORTAINER_USER # +# PORTAINER_PASSWORD # +# PORTAINER_URL # +# PORTAINER_STACK_NAME # +# DOCKER_COMPOSE_FILE # +# ENVIRONMENT_VARIABLES_FILE # +# PORTAINER_ENDPOINT # +# PORTAINER_PRUNE # +# HTTPIE_VERIFY_SSL # +# VERBOSE_MODE # +# DEBUG_MODE # +# STRICT_MODE # +# Arguments: # +# None # +# Returns: # +# None # +################################ set_globals() { # Set arguments through envvars ACTION=${ACTION} @@ -94,6 +95,7 @@ set_globals() { PORTAINER_URL=${PORTAINER_URL} PORTAINER_STACK_NAME=${PORTAINER_STACK_NAME} DOCKER_COMPOSE_FILE=${DOCKER_COMPOSE_FILE} + ENVIRONMENT_VARIABLES_FILE=${ENVIRONMENT_VARIABLES_FILE} PORTAINER_ENDPOINT=${PORTAINER_ENDPOINT:-"1"} PORTAINER_PRUNE=${PORTAINER_PRUNE:-"false"} HTTPIE_VERIFY_SSL=${HTTPIE_VERIFY_SSL:-"yes"} @@ -102,7 +104,7 @@ set_globals() { STRICT_MODE=${STRICT_MODE:-"false"} # Set arguments through flags (overwrite envvars) - while getopts a:u:p:l:n:c:e:rsvdt option; do + while getopts a:u:p:l:n:c:e:g:rsvdt option; do case "${option}" in a) ACTION=${OPTARG} ;; u) PORTAINER_USER=${OPTARG} ;; @@ -111,6 +113,7 @@ set_globals() { n) PORTAINER_STACK_NAME=${OPTARG} ;; c) DOCKER_COMPOSE_FILE=${OPTARG} ;; e) PORTAINER_ENDPOINT=${OPTARG} ;; + g) ENVIRONMENT_VARIABLES_FILE=${OPTARG} ;; r) PORTAINER_PRUNE="true" ;; s) HTTPIE_VERIFY_SSL="no" ;; v) VERBOSE_MODE="true" ;; @@ -130,6 +133,7 @@ set_globals() { echo_debug "PORTAINER_URL -> $PORTAINER_URL" echo_debug "PORTAINER_STACK_NAME -> $PORTAINER_STACK_NAME" echo_debug "DOCKER_COMPOSE_FILE -> $DOCKER_COMPOSE_FILE" + echo_debug "ENVIRONMENT_VARIABLES_FILE -> $ENVIRONMENT_VARIABLES_FILE" echo_debug "PORTAINER_ENDPOINT -> $PORTAINER_ENDPOINT" echo_debug "PORTAINER_PRUNE -> $PORTAINER_PRUNE" echo_debug "HTTPIE_VERIFY_SSL -> $HTTPIE_VERIFY_SSL" @@ -145,6 +149,10 @@ set_globals() { check_argument "$PORTAINER_STACK_NAME" "portainer stack name" "PORTAINER_STACK_NAME" "n" if [ $ACTION == "deploy" ]; then check_argument "$DOCKER_COMPOSE_FILE" "docker compose file" "DOCKER_COMPOSE_FILE" "c" + if [ -n "$ENVIRONMENT_VARIABLES_FILE" ] && [[ ! -f "$ENVIRONMENT_VARIABLES_FILE" ]]; then + echo_error "Error: File path \"$ENVIRONMENT_VARIABLES_FILE\" not found for \"ENVIRONMENT_VARIABLES_FILE\" environment variable or the \"-g\" flag." + exit 1 + fi fi } @@ -254,21 +262,22 @@ echo_debug() { fi } -########################## -# Create/update a stack # -# Globals: # -# STACK # -# DOCKER_COMPOSE_FILE # -# PORTAINER_STACK_NAME # -# PORTAINER_URL # -# HTTPIE_VERIFY_SSL # -# PORTAINER_ENDPOINT # -# AUTH_TOKEN # -# Arguments: # -# None # -# Returns: # -# None # -########################## +################################ +# Create/update a stack # +# Globals: # +# STACK # +# DOCKER_COMPOSE_FILE # +# PORTAINER_STACK_NAME # +# PORTAINER_URL # +# ENVIRONMENT_VARIABLES_FILE # +# HTTPIE_VERIFY_SSL # +# PORTAINER_ENDPOINT # +# AUTH_TOKEN # +# Arguments: # +# None # +# Returns: # +# None # +################################ deploy() { # Read docker-compose file content local docker_compose_file_content @@ -304,14 +313,19 @@ deploy() { local swarm_id swarm_id=$(echo $docker_info | jq -r ".Swarm.Cluster.ID // empty") echo_debug "Swarm ID -> $swarm_id" - + # If there is no swarm ID if [ -z "$swarm_id" ];then echo_verbose "Swarm cluster not found." echo_verbose "Preparing stack JSON..." + local stack_envvars + stack_envvars="[]" + if [ -n "$ENVIRONMENT_VARIABLES_FILE" ]; then + stack_envvars=$(env_file_to_json) + fi local data_prefix="{\"Name\":\"$PORTAINER_STACK_NAME\",\"StackFileContent\":\"" - local data_suffix="\"}" + local data_suffix="\",\"Env\":"$stack_envvars"}" echo "$data_prefix$docker_compose_file_content$data_suffix" > json.tmp echo_debug "Stack JSON -> $(echo $data_prefix$docker_compose_file_content$data_suffix | jq -C .)" @@ -335,8 +349,13 @@ deploy() { echo_verbose "Swarm cluster found." echo_verbose "Preparing stack JSON..." + local stack_envvars + stack_envvars="[]" + if [ -n "$ENVIRONMENT_VARIABLES_FILE" ]; then + stack_envvars=$(env_file_to_json) + fi local data_prefix="{\"Name\":\"$PORTAINER_STACK_NAME\",\"SwarmID\":\"$swarm_id\",\"StackFileContent\":\"" - local data_suffix="\"}" + local data_suffix="\",\"Env\":"$stack_envvars"}" echo "$data_prefix$docker_compose_file_content$data_suffix" > json.tmp echo_debug "Stack JSON -> $(echo $data_prefix$docker_compose_file_content$data_suffix | jq -C .)" @@ -370,12 +389,16 @@ deploy() { local stack_id stack_id="$(echo "$STACK" | jq -j ".Id")" local stack_envvars - stack_envvars="$(echo -n "$STACK"| jq ".Env" -jc)" + stack_envvars="$(echo -n "$STACK" | jq ".Env" -jc)" + if [ -n "$ENVIRONMENT_VARIABLES_FILE" ]; then + new_stack_envvars=$(env_file_to_json) + stack_envvars="$(echo -n "${new_stack_envvars}${stack_envvars}" | jq -sjc 'add | unique_by(.name)')" + fi local data_prefix="{\"Id\":\"$stack_id\",\"StackFileContent\":\"" local data_suffix="\",\"Env\":"$stack_envvars",\"Prune\":$PORTAINER_PRUNE}" echo "$data_prefix$docker_compose_file_content$data_suffix" > json.tmp echo_debug "Stack JSON -> $(echo $data_prefix$docker_compose_file_content$data_suffix | jq -C .)" - + # Update stack echo_verbose "Updating stack $PORTAINER_STACK_NAME..." local update @@ -390,7 +413,7 @@ deploy() { @json.tmp) check_for_errors $? "$update" echo_debug "Update action response -> $(echo $update | jq -C .)" - + rm json.tmp fi } @@ -436,4 +459,17 @@ undeploy() { echo_debug "Delete action response -> $(echo $delete | jq -C .)" } +################################################### +# Convert environment variables from file to JSON # +# Globals: # +# ENVIRONMENT_VARIABLES_FILE # +# Arguments: # +# None # +# Returns: # +# JSON string # +################################################### +env_file_to_json() { + echo "$(env -i $(cat $ENVIRONMENT_VARIABLES_FILE) jq -n 'env | to_entries | map({name: .key, value: .value})')" +} + main "$@" From b0b59385f13e0329c3cb3e605623c1d5dbed4371 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Carlos=20Mej=C3=ADas=20Rodr=C3=ADguez?= Date: Fri, 10 May 2019 20:02:07 -0700 Subject: [PATCH 2/4] Make new_stack_envvars local --- psu | 1 + 1 file changed, 1 insertion(+) diff --git a/psu b/psu index 65a21d4..021f402 100755 --- a/psu +++ b/psu @@ -391,6 +391,7 @@ deploy() { local stack_envvars stack_envvars="$(echo -n "$STACK" | jq ".Env" -jc)" if [ -n "$ENVIRONMENT_VARIABLES_FILE" ]; then + local new_stack_envvars new_stack_envvars=$(env_file_to_json) stack_envvars="$(echo -n "${new_stack_envvars}${stack_envvars}" | jq -sjc 'add | unique_by(.name)')" fi From a47e653faeb952ac09769029002882128faaae88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Carlos=20Mej=C3=ADas=20Rodr=C3=ADguez?= Date: Fri, 10 May 2019 20:32:32 -0700 Subject: [PATCH 3/4] Add documentation for stack environment variables --- README.md | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f372c0d..ed470dc 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,7 @@ This is particularly useful for CI/CD pipelines. - `PORTAINER_URL` (string, required): URL to Portainer - `PORTAINER_STACK_NAME` (string, required): Stack name - `DOCKER_COMPOSE_FILE` (string, required if action=deploy): Path to doker-compose file +- `ENVIRONMENT_VARIABLES_FILE` (string, optional, only used when action=deploy or action=update): Path to file with environment variables to be used by the stack. See [stack environment variables](#stack-environment-variables) below. - `PORTAINER_PRUNE` ("true" or "false", optional): Whether to prune unused containers or not. Defaults to `"false"`. - `PORTAINER_ENDPOINT` (int, optional): Which endpoint to use. Defaults to `1`. - `HTTPIE_VERIFY_SSL` ("yes" or "no", optional): Whether to verify SSL certificate or not. Defaults to `"yes"`. @@ -60,6 +61,7 @@ export PORTAINER_PASSWORD="password" export PORTAINER_URL="http://portainer.local" export PORTAINER_STACK_NAME="mystack" export DOCKER_COMPOSE_FILE="/path/to/docker-compose.yml" +export ENVIRONMENT_VARIABLES_FILE="/path/to/env_vars_file" ./psu ``` @@ -84,6 +86,7 @@ This is more suitable for standalone script usage. - `-l` (string, required): URL to Portainer - `-n` (string, required): Stack name - `-c` (string, required if action=deploy): Path to doker-compose file +- `-g` (string, optional, only used when action=deploy or action=update): Path to file with environment variables to be used by the stack. See [stack environment variables](#stack-environment-variables) below. - `-r` ("true" or "false", optional): Whether to prune unused containers or not. Defaults to `"false"`. - `-e` (int, optional): Which endpoint to use. Defaults to `1`. - `-s` ("yes" or "no", optional): Whether to verify SSL certificate or not. Defaults to `"yes"`. @@ -94,13 +97,26 @@ This is more suitable for standalone script usage. #### Examples ```bash -./psu -a deploy -u admin -p password -l http://portainer.local -n mystack -c /path/to/docker-compose.yml +./psu -a deploy -u admin -p password -l http://portainer.local -n mystack -c /path/to/docker-compose.yml -g /path/to/env_vars_file ``` ```bash ./psu -a undeploy -u admin -p password -l http://portainer.local -n mystack ``` +### Stack environment variables + +There can be set environment variables for each stack, be it a new deployment or an update. For example: + +```bash +touch .env +echo "MYSQL_ROOT_PASSWORD=agoodpassword" >> .env +echo "ALLOWED_HOSTS=*" >> .env +./psu -a deploy -u admin -p password -l http://portainer.local -n django-stack -c /path/to/docker-compose.yml -g env_vars +``` + +Stack environment variables can be enabled through [ENVIRONMENT_VARIABLES_FILE envvar](#with-envvars) or [-g flag](#with-flags). + ### Verbose mode In verbose mode the script prints execution steps. From a692212a9f38bd191202864c08c424dc9a6c3dbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Carlos=20Mej=C3=ADas=20Rodr=C3=ADguez?= Date: Fri, 10 May 2019 20:39:15 -0700 Subject: [PATCH 4/4] Add Dockerfile ENV for stack environment variables --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index 348d617..4e60329 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,6 +10,7 @@ ENV LANG="en_US.UTF-8" \ PORTAINER_URL="" \ PORTAINER_STACK_NAME="" \ DOCKER_COMPOSE_FILE="" \ + ENVIRONMENT_VARIABLES_FILE="" \ PORTAINER_PRUNE="false" \ PORTAINER_ENDPOINT="1" \ HTTPIE_VERIFY_SSL="yes" \