mirror of
https://gitlab.com/psuapp/psu.git
synced 2024-08-30 18:12:34 +00:00
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:
parent
f9530c46fa
commit
54b0f3854a
@ -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
|
||||||
|
@ -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" \
|
||||||
|
@ -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" \
|
||||||
|
@ -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]')
|
||||||
|
@ -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" \
|
||||||
|
13
README.md
13
README.md
@ -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
264
psu
@ -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
|
||||||
|
21
tests/run.sh
21
tests/run.sh
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user