Breaking Change: HTTPie is replaced by cURL

For smaller Docker images, faster execution and to be more portable
Running concurrently 'psu' commands should work now, by creating unique
temporary file names
This commit is contained in:
Tortue Torche 2021-09-14 13:41:15 +02:00
parent f9530c46fa
commit 54b0f3854a
8 changed files with 198 additions and 123 deletions

View File

@ -6,6 +6,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
### Changed
- **Breaking Change**: [HTTPie](https://httpie.io/) is replaced by [cURL](https://curl.se), for smaller Docker images, faster execution and to be more portable
### Fixed
- Running concurrently `psu` commands should work now, by creating unique temporary file names
## [1.2.0] - 2021-09-14 ## [1.2.0] - 2021-09-14
### Added ### Added
- Add tests for `actions`, `containers` and `services` actions - Add tests for `actions`, `containers` and `services` actions
@ -151,7 +157,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Debug mode - Debug mode
- Strict mode - Strict mode
[Unreleased]: https://gitlab.com/psuapp/psu/compare/v1.2.0...1-2-stable [Unreleased]: https://gitlab.com/psuapp/psu/compare/v1.2.0...1-3-next
[1.2.0]: https://gitlab.com/psuapp/psu/-/tags/v1.2.0 [1.2.0]: https://gitlab.com/psuapp/psu/-/tags/v1.2.0
[1.2.0-beta.1]: https://gitlab.com/psuapp/psu/-/tags/v1.2.0-beta.1 [1.2.0-beta.1]: https://gitlab.com/psuapp/psu/-/tags/v1.2.0-beta.1
[1.2.0-alpha]: https://gitlab.com/psuapp/psu/-/tags/v1.2.0-alpha [1.2.0-alpha]: https://gitlab.com/psuapp/psu/-/tags/v1.2.0-alpha

View File

@ -2,7 +2,7 @@ FROM alpine:3.14
RUN set -e; \ RUN set -e; \
apk add --no-cache \ apk add --no-cache \
bash ca-certificates docker-compose gettext httpie jq; \ bash ca-certificates curl docker-compose gettext jq; \
rm -rf $(find / -regex '.*\.py[co]') rm -rf $(find / -regex '.*\.py[co]')
ENV LANG="en_US.UTF-8" \ ENV LANG="en_US.UTF-8" \

View File

@ -2,8 +2,7 @@ FROM alpine:3.14
RUN set -e; \ RUN set -e; \
apk add --no-cache \ apk add --no-cache \
bash ca-certificates gettext httpie jq py3-setuptools; \ bash ca-certificates curl gettext jq
rm -rf $(find / -regex '.*\.py[co]')
ENV LANG="en_US.UTF-8" \ ENV LANG="en_US.UTF-8" \
LC_ALL="C.UTF-8" \ LC_ALL="C.UTF-8" \

View File

@ -5,12 +5,11 @@ ARG DOCKER_COMPOSE_VERSION=1.29.2
RUN set -e; \ RUN set -e; \
apt-get update -yqq; \ apt-get update -yqq; \
apt-get install \ apt-get install \
ca-certificates gettext-base httpie jq curl -yqq; \ ca-certificates curl gettext-base jq -yqq; \
\ \
curl -fL "https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose; \ curl --fail --silent --location "https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose; \
chmod +x /usr/local/bin/docker-compose; \ chmod +x /usr/local/bin/docker-compose; \
\ \
apt-get purge curl -y; \
apt-get clean; \ apt-get clean; \
rm -rf /var/lib/apt/lists/*; \ rm -rf /var/lib/apt/lists/*; \
rm -rf $(find / -regex '.*\.py[co]') rm -rf $(find / -regex '.*\.py[co]')

View File

@ -3,11 +3,10 @@ FROM debian:11-slim
RUN set -e; \ RUN set -e; \
apt-get update -yqq; \ apt-get update -yqq; \
apt-get install \ apt-get install \
ca-certificates gettext-base httpie jq -yqq; \ ca-certificates curl gettext-base jq -yqq; \
\ \
apt-get clean; \ apt-get clean; \
rm -rf /var/lib/apt/lists/*; \ rm -rf /var/lib/apt/lists/*
rm -rf $(find / -regex '.*\.py[co]')
ENV LANG="en_US.UTF-8" \ ENV LANG="en_US.UTF-8" \
LC_ALL="C.UTF-8" \ LC_ALL="C.UTF-8" \

View File

@ -49,12 +49,15 @@ For detailed instructions, see [How to use](#how-to-use) section.
You will need these dependencies installed: You will need these dependencies installed:
- [bash](https://www.gnu.org/software/bash/) - [bash](https://www.gnu.org/software/bash/)<sup title="required">*</sup> <small>(>= 5.0.3)</small>
- [httpie](https://httpie.org/) - [curl](https://curl.se/)<sup title="required">*</sup> <small>(>= 7.64.0, but >= 7.76.0 is recommended)</small>
- [jq](https://stedolan.github.io/jq/) - [jq](https://stedolan.github.io/jq/)<sup title="required">*</sup> <small>(>= 1.5.1)</small>
- [timeout](https://man7.org/linux/man-pages/man1/timeout.1.html) <small>For macOS run: `brew install coreutils`</small> - [timeout](https://man7.org/linux/man-pages/man1/timeout.1.html)<sup title="required">*</sup> <small>For macOS run: `brew install coreutils`</small>
- [uuidgen](https://man7.org/linux/man-pages/man1/uuidgen.1.html) only for some <abbr title="Operating System">OS</abbr> <small>(Debian and Alpine work fine without it)</small>
For Debian and similar apt-powered systems: `apt install bash httpie jq` <sup>*</sup> = required
For Debian and similar apt-powered systems: `apt install bash curl jq`
### Docker image and variants ### Docker image and variants

264
psu
View File

@ -115,12 +115,10 @@ main() {
# Get list of all stacks # Get list of all stacks
echo_verbose "Getting stack $PORTAINER_STACK_NAME..." echo_verbose "Getting stack $PORTAINER_STACK_NAME..."
STACKS=$(http \ STACKS=$(curl_wrapper \
--check-status \ --request GET \
--ignore-stdin \ --header "Authorization: Bearer ${PORTAINER_AUTH_TOKEN}" \
--verify=$HTTPIE_VERIFY_SSL \ "${PORTAINER_URL}/api/stacks")
"$PORTAINER_URL/api/stacks" \
"Authorization: Bearer $PORTAINER_AUTH_TOKEN")
check_for_errors $? "$STACKS" check_for_errors $? "$STACKS"
echo_debug_safe_json "Get stacks response -> $(echo $STACKS | jq -C .)" "$STACKS" echo_debug_safe_json "Get stacks response -> $(echo $STACKS | jq -C .)" "$STACKS"
@ -187,8 +185,7 @@ main() {
# or desired state == 'shutdown' and state == 'complete' # or desired state == 'shutdown' and state == 'complete'
# Tasks should not have one of these states: # Tasks should not have one of these states:
# 'failed', 'orphaned', 'remove' # 'failed', 'orphaned', 'remove'
timeout "$TIMEOUT" bash -c "until (export PORTAINER_SERVICE_NAME=$PORTAINER_SERVICE_NAME && psu --action=tasks:healthy --quiet --debug=false --verbose=false) >/dev/null 2>&1; do echo -n \$(if [ \"\$VERBOSE_MODE\" == \"true\" ]; then echo -n .; fi) && sleep 1; done;"
timeout $TIMEOUT bash -c "until (export PORTAINER_SERVICE_NAME=$PORTAINER_SERVICE_NAME && export DEBUG_MODE=false && export VERBOSE_MODE=false && psu -a tasks:healthy -q) >/dev/null 2>&1; do echo -n \$(if [ \"\$VERBOSE_MODE\" == \"true\" ]; then echo -n .; fi) && sleep 1; done;"
status=$? status=$?
if $(exit $status); then if $(exit $status); then
@ -460,8 +457,10 @@ inputs() {
-C=*|-F=*|--compose-file-base64=*|--file-base64=*|-C|-F|--compose-file-base64|--file-base64) -C=*|-F=*|--compose-file-base64=*|--file-base64=*|-C|-F|--compose-file-base64|--file-base64)
local docker_compose_file_base64 local docker_compose_file_base64
docker_compose_file_base64=$(input_option "$1" "$2") docker_compose_file_base64=$(input_option "$1" "$2")
echo "$docker_compose_file_base64" | base64 -d > docker_compose_file_from_base64.yml local docker_compose_file_from_base64_path
DOCKER_COMPOSE_FILE=docker_compose_file_from_base64.yml docker_compose_file_from_base64_path="$(unique_temp_file_path)"
echo "$docker_compose_file_base64" | base64 -d > "$docker_compose_file_from_base64_path"
DOCKER_COMPOSE_FILE="$docker_compose_file_from_base64_path"
if [ -n "$2" ] && [[ ! $2 =~ ^-.+$ ]] ; then if [ -n "$2" ] && [[ ! $2 =~ ^-.+$ ]] ; then
# When the second argument is the value of the current option # When the second argument is the value of the current option
shift shift
@ -484,8 +483,10 @@ inputs() {
-G=*|--env-file-base64=*|-G|--env-file-base64) -G=*|--env-file-base64=*|-G|--env-file-base64)
local env_file_base64 local env_file_base64
env_file_base64=$(input_option "$1" "$2") env_file_base64=$(input_option "$1" "$2")
echo "$env_file_base64" | base64 -d > env_file_from_base64 local env_file_from_base64_path
ENVIRONMENT_VARIABLES_FILE=env_file_from_base64 env_file_from_base64_path="$(unique_temp_file_path)"
echo "$env_file_base64" | base64 -d > "$env_file_from_base64_path"
ENVIRONMENT_VARIABLES_FILE="$env_file_from_base64_path"
if [ -n "$2" ] && [[ ! $2 =~ ^-.+$ ]] ; then if [ -n "$2" ] && [[ ! $2 =~ ^-.+$ ]] ; then
# When the second argument is the value of the current option # When the second argument is the value of the current option
shift shift
@ -741,6 +742,7 @@ echo_error() {
local error_message="$@" local error_message="$@"
local red='\033[0;31m' local red='\033[0;31m'
local nc='\033[0m' local nc='\033[0m'
echo -e "${red}[$(date +'%Y-%m-%dT%H:%M:%S%z')]: ${error_message}${nc}" >&2 echo -e "${red}[$(date +'%Y-%m-%dT%H:%M:%S%z')]: ${error_message}${nc}" >&2
} }
@ -785,18 +787,15 @@ check_for_errors() {
local response=$2 local response=$2
if [ $exit_code -ne 0 ]; then if [ $exit_code -ne 0 ]; then
case $exit_code in case $exit_code in
2) echo_error 'Request timed out!' ;; 22)
3) echo_error 'Unexpected HTTP 3xx Redirection!' ;; echo_error 'HTTP 4xx or 5xx Client Error!'
4) echo_error "$response"
echo_error 'HTTP 4xx Client Error!' ;;
echo_error $response 28) echo_error 'Request timed out!' ;;
47) echo_error 'Exceeded --max-redirs <n> redirects!' ;;
*)
echo_error 'Unholy Error!'
;; ;;
5)
echo_error 'HTTP 5xx Server Error!'
echo_error $response
;;
6) echo_error 'Exceeded --max-redirects=<n> redirects!' ;;
*) echo_error 'Unholy Error!' ;;
esac esac
exit 1 exit 1
fi fi
@ -819,10 +818,10 @@ echo_safe_json() {
# If the $json variable has an '.Env' entry # If the $json variable has an '.Env' entry
# We parse its content to mask sensitive values # We parse its content to mask sensitive values
if [ "$MASKED_VARIABLES" == "extended" ] && [ -n "$(echo "$json" | jq -j 'if type == "array" then .[] else . end | .Env // ""')" ]; then if [ "$MASKED_VARIABLES" == "extended" ] && [ -n "$(echo "$json" | jq -j 'if type == "array" then .[] else . end | .Env // ""')" ]; then
temp_file=".stack_envs-$(echo "$(date +%s%3N)")" temp_file="$(unique_temp_file_path)"
( (
echo "$json" | jq -r 'if type == "array" then .[] else . end | .Env | map(.name = .name + "=" + .value) | map (.name) | .[]' | sed "s/\"/\\\\\"/g" | sed "s/^\([^=]\{1,\}=\)\(.* .*\)$/\1\"\2\"/" > $temp_file && \ echo "$json" | jq -r 'if type == "array" then .[] else . end | .Env | map(.name = .name + "=" + .value) | map (.name) | .[]' | sed "s/\"/\\\\\"/g" | sed "s/^\([^=]\{1,\}=\)\(.* .*\)$/\1\"\2\"/" > "$temp_file" && \
. $temp_file && rm -f $temp_file && \ . "$temp_file" && rm -f "$temp_file" && \
if [ "$type" == "debug" ]; then \ if [ "$type" == "debug" ]; then \
echo_debug "$message"; \ echo_debug "$message"; \
elif [ "$type" == "verbose" ]; then \ elif [ "$type" == "verbose" ]; then \
@ -906,6 +905,75 @@ mask_variables() {
echo "$message" echo "$message"
} }
unique_temp_file_path() {
local file_prefix="psu"
local fallback_temp_path
local temp_path
local temp_file_path
fallback_temp_path="$(if [ -w "/tmp" ]; then echo "/tmp"; else pwd; fi)"
temp_path="${TMPDIR:-${TMP:-${TEMP:-$fallback_temp_path}}}"
if [ -w "$temp_path" ]; then
local file_uuid
# Generate universally unique identifier (UUID)
file_uuid=$(if [ -x "$(command -v uuidgen)" ]; then uuidgen; else cat /proc/sys/kernel/random/uuid; fi)
if [ -z "$file_uuid" ]; then
echo_error "You must install the 'uuidgen' program, to generate universally unique identifier"
exit 1
fi
# Unique temp file path
temp_file_path="${temp_path}/${file_prefix}_${file_uuid}.tmp"
else
echo_error "'${temp_path}' path is NOT WRITABLE!"
exit 1
fi
echo "$temp_file_path"
}
curl_wrapper() {
local result
# Use --fail-with-body cURL option if available
# And use environment variable to cache result
CURL_HAS_FAIL_WITH_BODY="${CURL_HAS_FAIL_WITH_BODY:-$(curl --help all | grep -w '\-\-fail\-with\-body' || true)}"
export CURL_HAS_FAIL_WITH_BODY
# Otherwise fallback to a temp result file storing
# Borrowed from: https://stackoverflow.com/a/55434980
local result_response_file
if [ -z "$CURL_HAS_FAIL_WITH_BODY" ]; then
result_response_file="$(unique_temp_file_path)"
fi
local curl_fail_params
curl_fail_params=$(if [ -n "$CURL_HAS_FAIL_WITH_BODY" ]; then echo --fail-with-body; else echo --output "$result_response_file" --write-out %\{http_code\}; fi)
result="$(curl \
$curl_fail_params \
--header "Content-Type: application/json" \
--silent \
$(if [ "$HTTPIE_VERIFY_SSL" == "no" ]; then echo --insecure; else $(if [ -f "$HTTPIE_VERIFY_SSL" ]; then echo --cacert "$HTTPIE_VERIFY_SSL"; else echo ''; fi); fi) \
"$@")"
result_exit_code="$?"
if [ -n "$result_response_file" ]; then
local response
response="$(cat "$result_response_file")"
rm -f "$result_response_file"
local response_result
response_result="$result"
result="$response"
echo "$result"
if [ "$response_result" -ge 400 ] && [ "$response_result" -le 599 ]; then
exit 22
fi
exit "$result_exit_code"
else
echo "$result"
exit "$result_exit_code"
fi
}
################################################### ###################################################
############### actions section ################### ############### actions section ###################
################################################### ###################################################
@ -938,6 +1006,9 @@ deploy() {
echo_debug "DOCKER_COMPOSE_FILE -> $DOCKER_COMPOSE_FILE" echo_debug "DOCKER_COMPOSE_FILE -> $DOCKER_COMPOSE_FILE"
echo_debug "docker_compose_file_content -> $docker_compose_file_content" echo_debug "docker_compose_file_content -> $docker_compose_file_content"
local json_temp_path
json_temp_path="$(unique_temp_file_path)"
# If the stack does not exist # If the stack does not exist
if [ -z "$STACK" ]; then if [ -z "$STACK" ]; then
echo_verbose "Stack $PORTAINER_STACK_NAME does not exist." echo_verbose "Stack $PORTAINER_STACK_NAME does not exist."
@ -967,23 +1038,19 @@ deploy() {
fi fi
local data_prefix="{\"Name\":\"$PORTAINER_STACK_NAME\"," local data_prefix="{\"Name\":\"$PORTAINER_STACK_NAME\","
local data_suffix=",\"Env\":"$stack_envvars"}" local data_suffix=",\"Env\":"$stack_envvars"}"
echo "$data_prefix$docker_compose_file_content$data_suffix" > json.tmp echo "$data_prefix$docker_compose_file_content$data_suffix" > "$json_temp_path"
echo_debug_safe_json "Stack JSON -> $(echo $data_prefix$docker_compose_file_content$data_suffix | jq -C .)" "$data_prefix$docker_compose_file_content$data_suffix" echo_debug_safe_json "Stack JSON -> $(echo $data_prefix$docker_compose_file_content$data_suffix | jq -C .)" "$data_prefix$docker_compose_file_content$data_suffix"
# Create stack for single Docker instance # Create stack for single Docker instance
echo_verbose "Creating stack $PORTAINER_STACK_NAME..." echo_verbose "Creating stack $PORTAINER_STACK_NAME..."
local create local create
create=$(http \ create=$(curl_wrapper \
--check-status \ --max-time 300 \
--ignore-stdin \ --request POST \
--verify=$HTTPIE_VERIFY_SSL \ --header "Authorization: Bearer ${PORTAINER_AUTH_TOKEN}" \
--timeout=300 \ --data "@${json_temp_path}" \
"$PORTAINER_URL/api/stacks" \ "${PORTAINER_URL}/api/stacks?type=2&method=string&endpointId=${PORTAINER_ENDPOINT}")
"Authorization: Bearer $PORTAINER_AUTH_TOKEN" \
type==2 \
method==string \
endpointId==$PORTAINER_ENDPOINT \
@json.tmp)
check_for_errors $? "$create" check_for_errors $? "$create"
echo_debug "Create action response -> $(echo $create | jq -C .)" echo_debug "Create action response -> $(echo $create | jq -C .)"
else else
@ -997,28 +1064,23 @@ deploy() {
fi fi
local data_prefix="{\"Name\":\"$PORTAINER_STACK_NAME\",\"SwarmID\":\"$swarm_id\"," local data_prefix="{\"Name\":\"$PORTAINER_STACK_NAME\",\"SwarmID\":\"$swarm_id\","
local data_suffix=",\"Env\":"$stack_envvars"}" local data_suffix=",\"Env\":"$stack_envvars"}"
echo "$data_prefix$docker_compose_file_content$data_suffix" > json.tmp echo "$data_prefix$docker_compose_file_content$data_suffix" > "$json_temp_path"
echo_debug_safe_json "Stack JSON -> $(echo $data_prefix$docker_compose_file_content$data_suffix | jq -C .)" "$data_prefix$docker_compose_file_content$data_suffix" echo_debug_safe_json "Stack JSON -> $(echo $data_prefix$docker_compose_file_content$data_suffix | jq -C .)" "$data_prefix$docker_compose_file_content$data_suffix"
# Create stack for Docker swarm # Create stack for Docker swarm
echo_verbose "Creating stack $PORTAINER_STACK_NAME..." echo_verbose "Creating stack $PORTAINER_STACK_NAME..."
local create local create
create=$(http \ create=$(curl_wrapper \
--check-status \ --max-time 300 \
--ignore-stdin \ --request POST \
--verify=$HTTPIE_VERIFY_SSL \ --header "Authorization: Bearer ${PORTAINER_AUTH_TOKEN}" \
--timeout=300 \ --data "@${json_temp_path}" \
"$PORTAINER_URL/api/stacks" \ "${PORTAINER_URL}/api/stacks?type=1&method=string&endpointId=${PORTAINER_ENDPOINT}")
"Authorization: Bearer $PORTAINER_AUTH_TOKEN" \
type==1 \
method==string \
endpointId==$PORTAINER_ENDPOINT \
@json.tmp)
check_for_errors $? "$create" check_for_errors $? "$create"
echo_debug "Create action response -> $(echo $create | jq -C .)" echo_debug "Create action response -> $(echo $create | jq -C .)"
fi fi
rm json.tmp rm -f "$json_temp_path"
else else
if [ $STRICT_MODE == "true" ]; then if [ $STRICT_MODE == "true" ]; then
echo_error "Error: Stack $PORTAINER_STACK_NAME already exists." echo_error "Error: Stack $PORTAINER_STACK_NAME already exists."
@ -1038,25 +1100,22 @@ deploy() {
fi fi
local data_prefix="{\"Id\":\"$stack_id\"," local data_prefix="{\"Id\":\"$stack_id\","
local data_suffix=",\"Env\":"$stack_envvars",\"Prune\":$PORTAINER_PRUNE}" local data_suffix=",\"Env\":"$stack_envvars",\"Prune\":$PORTAINER_PRUNE}"
echo "$data_prefix$docker_compose_file_content$data_suffix" > json.tmp echo "$data_prefix$docker_compose_file_content$data_suffix" > "$json_temp_path"
echo_debug_safe_json "Stack JSON -> $(echo $data_prefix$docker_compose_file_content$data_suffix | jq -C .)" "$data_prefix$docker_compose_file_content$data_suffix" echo_debug_safe_json "Stack JSON -> $(echo $data_prefix$docker_compose_file_content$data_suffix | jq -C .)" "$data_prefix$docker_compose_file_content$data_suffix"
# Update stack # Update stack
echo_verbose "Updating stack $PORTAINER_STACK_NAME..." echo_verbose "Updating stack $PORTAINER_STACK_NAME..."
local update local update
update=$(http \ update=$(curl_wrapper \
--check-status \ --max-time 300 \
--ignore-stdin \ --request PUT \
--verify=$HTTPIE_VERIFY_SSL \ --header "Authorization: Bearer ${PORTAINER_AUTH_TOKEN}" \
--timeout=300 \ --data "@${json_temp_path}" \
PUT "$PORTAINER_URL/api/stacks/$stack_id" \ "${PORTAINER_URL}/api/stacks/${stack_id}?endpointId=${PORTAINER_ENDPOINT}")
"Authorization: Bearer $PORTAINER_AUTH_TOKEN" \
endpointId==$PORTAINER_ENDPOINT \
@json.tmp)
check_for_errors $? "$update" check_for_errors $? "$update"
echo_debug "Update action response -> $(echo $update | jq -C .)" echo_debug "Update action response -> $(echo $update | jq -C .)"
rm json.tmp rm -f "$json_temp_path"
fi fi
} }
@ -1091,13 +1150,10 @@ undeploy() {
echo_verbose "Deleting stack $PORTAINER_STACK_NAME..." echo_verbose "Deleting stack $PORTAINER_STACK_NAME..."
local delete local delete
delete=$(http \ delete=$(curl_wrapper \
--check-status \ --request DELETE \
--ignore-stdin \ --header "Authorization: Bearer ${PORTAINER_AUTH_TOKEN}" \
--verify=$HTTPIE_VERIFY_SSL \ "${PORTAINER_URL}/api/stacks/${stack_id}?endpointId=${PORTAINER_ENDPOINT}")
DELETE "$PORTAINER_URL/api/stacks/$stack_id" \
"Authorization: Bearer $PORTAINER_AUTH_TOKEN" \
endpointId==$PORTAINER_ENDPOINT)
check_for_errors $? "$delete" check_for_errors $? "$delete"
echo_debug "Delete action response -> $(echo $delete | jq -C .)" echo_debug "Delete action response -> $(echo $delete | jq -C .)"
} }
@ -1106,13 +1162,10 @@ undeploy() {
login() { login() {
echo_verbose "Getting auth token..." echo_verbose "Getting auth token..."
local auth_token_json local auth_token_json
auth_token_json=$(http \ auth_token_json=$(curl_wrapper \
--check-status \ --request POST \
--ignore-stdin \ --data "{\"username\":\"${PORTAINER_USER}\",\"password\":\"${PORTAINER_PASSWORD}\"}" \
--verify=$HTTPIE_VERIFY_SSL \ "${PORTAINER_URL}/api/auth")
$PORTAINER_URL/api/auth \
username=$PORTAINER_USER \
password=$PORTAINER_PASSWORD)
check_for_errors $? "$auth_token_json" check_for_errors $? "$auth_token_json"
PORTAINER_AUTH_TOKEN=$(echo $auth_token_json | jq -r .jwt) PORTAINER_AUTH_TOKEN=$(echo $auth_token_json | jq -r .jwt)
echo_debug "Get auth token response -> $(echo $auth_token_json | jq -C .)" echo_debug "Get auth token response -> $(echo $auth_token_json | jq -C .)"
@ -1126,12 +1179,10 @@ login() {
# Get Docker info # Get Docker info
docker_info() { docker_info() {
local docker_info local docker_info
docker_info=$(http \ docker_info=$(curl_wrapper \
--check-status \ --request GET \
--ignore-stdin \ --header "Authorization: Bearer ${PORTAINER_AUTH_TOKEN}" \
--verify=$HTTPIE_VERIFY_SSL \ "${PORTAINER_URL}/api/endpoints/${PORTAINER_ENDPOINT}/docker/info")
"$PORTAINER_URL/api/endpoints/$PORTAINER_ENDPOINT/docker/info" \
"Authorization: Bearer $PORTAINER_AUTH_TOKEN")
check_for_errors $? "$docker_info" check_for_errors $? "$docker_info"
echo "$docker_info" echo "$docker_info"
@ -1151,13 +1202,12 @@ tasks() {
local filters local filters
filters="{$filter_service$(if [ -n "$filter_desired_state" ]; then echo ",$filter_desired_state"; fi)}" filters="{$filter_service$(if [ -n "$filter_desired_state" ]; then echo ",$filter_desired_state"; fi)}"
local tasks local tasks
tasks=$(http \ tasks=$(curl_wrapper \
--check-status \ --request GET \
--ignore-stdin \ --get \
--verify=$HTTPIE_VERIFY_SSL \ --header "Authorization: Bearer ${PORTAINER_AUTH_TOKEN}" \
"$PORTAINER_URL/api/endpoints/$PORTAINER_ENDPOINT/docker/tasks" \ --data-urlencode "filters=${filters}" \
filters=="$filters" \ "${PORTAINER_URL}/api/endpoints/${PORTAINER_ENDPOINT}/docker/tasks")
"Authorization: Bearer $PORTAINER_AUTH_TOKEN")
check_for_errors $? "$tasks" check_for_errors $? "$tasks"
if [ -n "$state" ]; then if [ -n "$state" ]; then
local filter_status local filter_status
@ -1192,14 +1242,15 @@ tasks_healthy() {
services() { services() {
local services local services
local filter_service local filter_service
local filters
filter_service="\"label\":{\"com.docker.stack.namespace=$PORTAINER_STACK_NAME\":true}" filter_service="\"label\":{\"com.docker.stack.namespace=$PORTAINER_STACK_NAME\":true}"
services=$(http \ filters="{${filter_service}}"
--check-status \ services=$(curl_wrapper \
--ignore-stdin \ --request GET \
--verify=$HTTPIE_VERIFY_SSL \ --get \
"$PORTAINER_URL/api/endpoints/$PORTAINER_ENDPOINT/docker/services" \ --header "Authorization: Bearer ${PORTAINER_AUTH_TOKEN}" \
filters=="{$filter_service}" \ --data-urlencode "filters=${filters}" \
"Authorization: Bearer $PORTAINER_AUTH_TOKEN") "${PORTAINER_URL}/api/endpoints/${PORTAINER_ENDPOINT}/docker/services")
check_for_errors $? "$services" check_for_errors $? "$services"
local filter_mode local filter_mode
@ -1223,13 +1274,12 @@ containers() {
filter_service="\"label\":{\"com.docker.swarm.service.name=$service_name\":true}" filter_service="\"label\":{\"com.docker.swarm.service.name=$service_name\":true}"
fi fi
filters="{$filter_stack$(if [ -n "$filter_service" ]; then echo ",$filter_service"; fi),$filter_task}" filters="{$filter_stack$(if [ -n "$filter_service" ]; then echo ",$filter_service"; fi),$filter_task}"
containers=$(http \ containers=$(curl_wrapper \
--check-status \ --request GET \
--ignore-stdin \ --get \
--verify=$HTTPIE_VERIFY_SSL \ --header "Authorization: Bearer ${PORTAINER_AUTH_TOKEN}" \
"$PORTAINER_URL/api/endpoints/$PORTAINER_ENDPOINT/docker/containers/json" \ --data-urlencode "filters=${filters}" \
filters=="$filters" \ "${PORTAINER_URL}/api/endpoints/${PORTAINER_ENDPOINT}/docker/containers/json")
"Authorization: Bearer $PORTAINER_AUTH_TOKEN")
check_for_errors $? "$containers" check_for_errors $? "$containers"
echo "$containers" echo "$containers"
} }
@ -1241,11 +1291,13 @@ lint() {
if [ -x "$(command -v docker-compose)" ]; then if [ -x "$(command -v docker-compose)" ]; then
echo_verbose "Linting Docker compose/stack file..." echo_verbose "Linting Docker compose/stack file..."
docker_stack_error="error_docker_stack_is_invalid" docker_stack_error="error_docker_stack_is_invalid"
docker_stack_validation=$(docker-compose -f "$DOCKER_COMPOSE_FILE" config -q > docker_stack_validation_report 2>&1 || echo $docker_stack_error) local docker_stack_validation_report
docker_stack_validation_report="$(unique_temp_file_path)"
docker_stack_validation=$(docker-compose -f "$DOCKER_COMPOSE_FILE" config -q > "$docker_stack_validation_report" 2>&1 || echo $docker_stack_error)
if [[ $docker_stack_validation =~ $docker_stack_error$ ]]; then if [[ $docker_stack_validation =~ $docker_stack_error$ ]]; then
echo_error "Error: The '$DOCKER_COMPOSE_FILE' Docker compose/stack file is invalid:" echo_error "Error: The '$DOCKER_COMPOSE_FILE' Docker compose/stack file is invalid:"
echo_error "$(cat docker_stack_validation_report)" echo_error "$(cat $docker_stack_validation_report)"
rm -f docker_stack_validation_report rm -f "$docker_stack_validation_report"
exit 1 exit 1
else else
if [ "$ACTION" == "lint" ]; then if [ "$ACTION" == "lint" ]; then

View File

@ -118,7 +118,16 @@ PSU_AUTH_TOKEN=$(psu_wrapper login --user $PSU_USER --password $PSU_PASSWORD --u
# Add GitLab Docker registry access to Portainer # Add GitLab Docker registry access to Portainer
envsubst '$CI_REGISTRY_USER,$CI_REGISTRY_PASSWORD' < gitlab-registry.json > gitlab-registry-final.json envsubst '$CI_REGISTRY_USER,$CI_REGISTRY_PASSWORD' < gitlab-registry.json > gitlab-registry-final.json
http --check-status --ignore-stdin --verify=no --timeout=10 POST "$PSU_URL/api/registries" "Authorization: Bearer $PSU_AUTH_TOKEN" @gitlab-registry-final.json curl \
--fail \
--silent \
--insecure \
--max-time 10 \
--request POST \
--header "Authorization: Bearer ${PSU_AUTH_TOKEN}" \
--header "Content-Type: application/json" \
--data "@gitlab-registry-final.json" \
"${PSU_URL}/api/registries"
# Add local endpoint to the Portainer instance # Add local endpoint to the Portainer instance
end_point_type_param=EndpointType end_point_type_param=EndpointType
@ -126,7 +135,15 @@ if [[ "$PORTAINER_VERSION" == "latest" ]] || [ "$(version $PORTAINER_VERSION)" -
# See: https://github.com/portainer/portainer/issues/4602#issuecomment-746819197 # See: https://github.com/portainer/portainer/issues/4602#issuecomment-746819197
end_point_type_param=EndpointCreationType end_point_type_param=EndpointCreationType
fi fi
http --check-status --ignore-stdin --verify=no --timeout=10 POST "$PSU_URL/api/endpoints" "Authorization: Bearer $PSU_AUTH_TOKEN" Name==local $end_point_type_param==1 curl \
--fail \
--silent \
--insecure \
--max-time 10 \
--request POST \
--header "Authorization: Bearer ${PSU_AUTH_TOKEN}" \
--header "Content-Type: application/json" \
"${PSU_URL}/api/endpoints?Name=local&${end_point_type_param}=1"
# Docker system info from Portainer test # Docker system info from Portainer test
docker_info=$(psu_wrapper system:info --user $PSU_USER --password $PSU_PASSWORD --url $PSU_URL --insecure --debug false --verbose false) docker_info=$(psu_wrapper system:info --user $PSU_USER --password $PSU_PASSWORD --url $PSU_URL --insecure --debug false --verbose false)