Merge GitLab integration

# Conflicts:
#	CHANGELOG.md
#	Dockerfile
#	README.md
#	psu
This commit is contained in:
Tortue Torche 2019-12-04 15:23:08 +01:00 committed by Tortue Torche
parent edde3e86c7
commit aca3e69669
27 changed files with 1831 additions and 111 deletions

162
.gitlab-ci.yml Normal file
View File

@ -0,0 +1,162 @@
image: $CI_REGISTRY/$CI_PROJECT_NAMESPACE/hub/auto-deploy-image:latest
variables:
DOCKER_DRIVER: overlay2
TRAEFIK_VERSION: 1.7-alpine
PORTAINER_VERSION: latest
PSU_IMAGE: ${CI_REGISTRY_IMAGE}/builds
PSU_TAG: $CI_COMMIT_SHA
PSU_TAG_CORE: core-${CI_COMMIT_SHA}
DOCKER_REGISTRY_IMAGE: $DOCKER_REGISTRY/$CI_PROJECT_PATH
stages:
- build
- test
- deploy
.build_template: &build_definition
stage: build
services:
- docker:dind
except:
refs:
- gitlab-pages
variables:
- $CI_COMMIT_TAG == "dev"
script:
- setup_docker
- registry_login
- cd ${CONTAINER_FOLDER:-.}
- docker pull $CONTAINER_IMAGE:${CONTAINER_VERSION:-latest} || true
- docker build --pull $(if [[ -n "$DOCKER_CACHE_DISABLED" ]]; then echo "--no-cache "; else echo "--cache-from $CONTAINER_IMAGE:${CONTAINER_VERSION:-latest}"; fi) --file ${DOCKER_FILE:-Dockerfile} --tag $CONTAINER_IMAGE:${CONTAINER_COMMIT_SHA:-$CI_COMMIT_SHA} --tag $CONTAINER_IMAGE:${CONTAINER_VERSION:-latest} --build-arg HTTP_PROXY="$HTTP_PROXY" --build-arg http_proxy="$http_proxy" --build-arg HTTPS_PROXY="$HTTPS_PROXY" --build-arg https_proxy="$https_proxy" --build-arg FTP_PROXY="$FTP_PROXY" --build-arg ftp_proxy="$ftp_proxy" --build-arg NO_PROXY="$NO_PROXY" --build-arg no_proxy="$no_proxy" .
- docker push $CONTAINER_IMAGE:${CONTAINER_COMMIT_SHA:-$CI_COMMIT_SHA}
- docker push $CONTAINER_IMAGE:${CONTAINER_VERSION:-latest}
build:main:
<<: *build_definition
variables:
CONTAINER_NAME: $CI_PROJECT_NAME
CONTAINER_VERSION: $CI_COMMIT_REF_SLUG-build
CONTAINER_IMAGE: ${CI_REGISTRY_IMAGE}/builds
build:debian:
<<: *build_definition
variables:
CONTAINER_NAME: $CI_PROJECT_NAME
CONTAINER_VERSION: $CI_COMMIT_REF_SLUG-build-debian
CONTAINER_COMMIT_SHA: debian-$CI_COMMIT_SHA
DOCKER_FILE: Dockerfile.debian
CONTAINER_IMAGE: ${CI_REGISTRY_IMAGE}/builds
build:core:
<<: *build_definition
variables:
CONTAINER_NAME: $CI_PROJECT_NAME
CONTAINER_VERSION: $CI_COMMIT_REF_SLUG-build-core
CONTAINER_COMMIT_SHA: core-$CI_COMMIT_SHA
DOCKER_FILE: Dockerfile.core
CONTAINER_IMAGE: ${CI_REGISTRY_IMAGE}/builds
build:debian-core:
<<: *build_definition
variables:
CONTAINER_NAME: $CI_PROJECT_NAME
CONTAINER_VERSION: $CI_COMMIT_REF_SLUG-build-debian-core
CONTAINER_COMMIT_SHA: debian-core-$CI_COMMIT_SHA
DOCKER_FILE: Dockerfile.debian-core
CONTAINER_IMAGE: ${CI_REGISTRY_IMAGE}/builds
.test_template: &test_definition
stage: test
services:
- name: docker:dind
alias: cluster
except:
refs:
- gitlab-pages
variables:
- $TEST_DISABLED
- $CI_COMMIT_TAG == "dev"
script:
- setup_docker
- registry_login
- bash scripts/test.sh
test:portainer-1.19.2:
<<: *test_definition
variables:
PORTAINER_VERSION: 1.19.2
test:portainer-1.20.2:
<<: *test_definition
variables:
PORTAINER_VERSION: 1.20.2
test:portainer-latest:
<<: *test_definition
test:portainer-latest:debian:
<<: *test_definition
variables:
PSU_TAG: debian-${CI_COMMIT_SHA}
PSU_TAG_CORE: debian-core-${CI_COMMIT_SHA}
release:
stage: deploy
services:
- docker:dind
except:
refs:
- gitlab-pages
variables:
- $CI_COMMIT_TAG == "dev"
script:
- setup_docker
- registry_login
- external_registry_login
# Tagging git repository and Docker images on successful builds and tests
# First argument is the variant(s) of the Docker image
# e.g. "core"
# or for multiple variants: "core debian debian-core"
- bash scripts/release.sh "core debian debian-core"
generate-docs:
stage: deploy
script:
- mkdir -p public/$CI_COMMIT_REF_SLUG
- cp -r docs/. public/$CI_COMMIT_REF_SLUG
- mkdir public/$CI_COMMIT_REF_SLUG/repo
- cp *.md public/$CI_COMMIT_REF_SLUG/repo
- bash scripts/push-to-gitlab-pages.sh
only:
variables:
- $CI_COMMIT_REF_NAME == "master" || $CI_COMMIT_REF_NAME =~ /^.+-stable$/ && $CI_COMMIT_REF_PROTECTED == "true"
except:
variables:
- $CI_PIPELINE_SOURCE == "schedule"
pages:
stage: deploy
before_script:
- echo ""
script:
# Gzip all static files before publishing GitLab Pages
# Source: https://www.queryxchange.com/q/5_119670/how-do-you-serve-jekyll-pages-with-gzip-compression-on-gitlab-pages/
- find public \( -name '*.html' -o -name '*.css' -o -name '*.js' -o -name '*.md' \) -print0 | xargs -0 gzip -9 -kv
artifacts:
paths:
- public
only:
- gitlab-pages
except:
variables:
- $CI_PIPELINE_SOURCE == "schedule"
before_script:
- set -e
- source scripts/helpers.sh
# Used for updating the Docker images, but not the psu code
# Via Scheduling Pipelines
# See: https://gitlab.com/help/user/project/pipelines/schedules
- git_reset_from_tag
# Used for updating Docker images, on release/stable branches, but not the psu code
- git_reset_from_last_stable_tag

View File

@ -5,10 +5,27 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Added
- New actions: `ls`, `status`, `services`, `tasks`, `tasks:healthy`, `containers`, `login`, `lint`, `inspect`, `system:info`, `actions`, `help` and `version`
- New options: `--auth-token=[AUTH_TOKEN]`, `--compose-file-base64=[BASE64]`, `--env-file-base64=[BASE64]`, `--timeout=[SECONDS]`, `--detect-job=[true|false]`, `--service=[SERVICE_NAME]`, `--insecure`, `--masked-variables`, `--quiet`, `--lint`, `--help` and `--version`
- New flags: `-A`, `-C`, `-F`, `-G`, `-T`, `-j`, `-i`, `-S`, `-m`, `-q`, `-L`, `-h` and `-V`
- New environment variables: `PORTAINER_AUTH_TOKEN`, `TIMEOUT`, `AUTO_DETECT_JOB`, `PORTAINER_SERVICE_NAME`, `MASKED_VARIABLES`, `QUIET_MODE` and `DOCKER_COMPOSE_LINT`
- The Docker image include now `docker-compose` to be able to lint Docker compose/stack file
- The `core` Docker image variant doesn't include `docker-compose`, so it's a bit smaller. But you can't lint Docker compose/stack file before deploying a stack
- The `debian` and `debian-core` Docker image variants, use [Debian](https://www.debian.org) instead of [Alpine](https://alpinelinux.org/) as base image for `psu`
- Online documentation via [docsify](https://docsify.js.org)
- Tests who run automatically on each git push via [GitLab CI](https://docs.gitlab.com/ce/ci/)
### Changed
- The `undeploy` action is now an aliased action. You should use `rm` action instead
### Deprecated
- The `--secure=[yes|no]` option and `-s` flag are deprecated. Use the `--insecure` option instead (`psu <action> ... --inscure`)
- The `--action=[ACTION_NAME]` option and `-a` flag are deprecated. Use `<action>` argument instead (`psu <action> ...`)
## [0.1.1] - 2019-06-05
### Fixed
- Fixed error when environment variables loaded from file contain spaces in their values [#14](https://github.com/greenled/portainer-stack-utils/pull/14)
- Fixed error when environment variables loaded from file contain spaces in their values [#14](https://gitlab.com/psuapp/psu/merge_requests/14)
## [0.1.0] - 2019-05-24
### Added
@ -23,6 +40,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Debug mode
- Strict mode
[Unreleased]: https://github.com/greenled/portainer-stack-utils/compare/0.1.1...HEAD
[0.1.1]: https://github.com/greenled/portainer-stack-utils/releases/tag/0.1.1
[0.1.0]: https://github.com/greenled/portainer-stack-utils/releases/tag/0.1.0
[Unreleased]: https://gitlab.com/psuapp/psu/compare/v0.1.1...master
[0.1.1]: https://gitlab.com/psuapp/psu/-/tags/v0.1.1
[0.1.0]: https://gitlab.com/psuapp/psu/-/tags/v0.1.0

View File

@ -1,13 +1,13 @@
FROM alpine:3.10
RUN apk add --no-cache \
bash ca-certificates gettext httpie jq \
py3-pip python3-dev libc-dev libffi-dev openssl-dev gcc make; \
\
pip3 --no-cache-dir install docker-compose; \
\
apk del python3-dev libc-dev libffi-dev openssl-dev gcc make; \
rm -rf /tmp/src
bash ca-certificates gettext httpie jq \
py3-pip python3-dev libc-dev libffi-dev openssl-dev gcc make; \
\
pip3 --no-cache-dir install docker-compose; \
\
apk del python3-dev libc-dev libffi-dev openssl-dev gcc make; \
rm -rf /tmp/src
ENV LANG="en_US.UTF-8" \
LC_ALL="C.UTF-8" \

36
Dockerfile.core Normal file
View File

@ -0,0 +1,36 @@
FROM alpine:3.10
RUN apk add --no-cache \
bash ca-certificates gettext httpie jq
ENV LANG="en_US.UTF-8" \
LC_ALL="C.UTF-8" \
LANGUAGE="en_US.UTF-8" \
TERM="xterm" \
ACTION="" \
PORTAINER_USER="" \
PORTAINER_PASSWORD="" \
PORTAINER_AUTH_TOKEN="" \
PORTAINER_URL="" \
PORTAINER_STACK_NAME="" \
PORTAINER_SERVICE_NAME="" \
DOCKER_COMPOSE_FILE="" \
DOCKER_COMPOSE_LINT="false" \
ENVIRONMENT_VARIABLES_FILE="" \
PORTAINER_ENDPOINT="1" \
PORTAINER_PRUNE="false" \
TIMEOUT=100 \
AUTO_DETECT_JOB="true" \
HTTPIE_VERIFY_SSL="yes" \
VERBOSE_MODE="false" \
DEBUG_MODE="false" \
QUIET_MODE="false" \
STRICT_MODE="false" \
MASKED_VARIABLES="false" \
PSU_CORE_EDITION="true"
COPY psu /usr/local/bin/
RUN chmod +x /usr/local/bin/psu
ENTRYPOINT ["/usr/local/bin/psu"]

43
Dockerfile.debian Normal file
View File

@ -0,0 +1,43 @@
FROM debian:9-slim
RUN apt-get update -yqq; \
apt-get install \
ca-certificates gettext-base httpie jq curl -yqq; \
\
curl -fL "https://github.com/docker/compose/releases/download/1.24.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose; \
chmod +x /usr/local/bin/docker-compose; \
\
apt-get purge curl -y; \
apt-get clean; \
rm -rf /var/lib/apt/lists/*
ENV LANG="en_US.UTF-8" \
LC_ALL="C.UTF-8" \
LANGUAGE="en_US.UTF-8" \
TERM="xterm" \
ACTION="" \
PORTAINER_USER="" \
PORTAINER_PASSWORD="" \
PORTAINER_AUTH_TOKEN="" \
PORTAINER_URL="" \
PORTAINER_STACK_NAME="" \
PORTAINER_SERVICE_NAME="" \
DOCKER_COMPOSE_FILE="" \
DOCKER_COMPOSE_LINT="true" \
ENVIRONMENT_VARIABLES_FILE="" \
PORTAINER_ENDPOINT="1" \
PORTAINER_PRUNE="false" \
TIMEOUT=100 \
AUTO_DETECT_JOB="true" \
HTTPIE_VERIFY_SSL="yes" \
VERBOSE_MODE="false" \
DEBUG_MODE="false" \
QUIET_MODE="false" \
STRICT_MODE="false" \
MASKED_VARIABLES="false"
COPY psu /usr/local/bin/
RUN chmod +x /usr/local/bin/psu
ENTRYPOINT ["/usr/local/bin/psu"]

40
Dockerfile.debian-core Normal file
View File

@ -0,0 +1,40 @@
FROM debian:9-slim
RUN apt-get update -yqq; \
apt-get install \
ca-certificates gettext-base httpie jq -yqq; \
\
apt-get clean; \
rm -rf /var/lib/apt/lists/*
ENV LANG="en_US.UTF-8" \
LC_ALL="C.UTF-8" \
LANGUAGE="en_US.UTF-8" \
TERM="xterm" \
ACTION="" \
PORTAINER_USER="" \
PORTAINER_PASSWORD="" \
PORTAINER_AUTH_TOKEN="" \
PORTAINER_URL="" \
PORTAINER_STACK_NAME="" \
PORTAINER_SERVICE_NAME="" \
DOCKER_COMPOSE_FILE="" \
DOCKER_COMPOSE_LINT="false" \
ENVIRONMENT_VARIABLES_FILE="" \
PORTAINER_ENDPOINT="1" \
PORTAINER_PRUNE="false" \
TIMEOUT=100 \
AUTO_DETECT_JOB="true" \
HTTPIE_VERIFY_SSL="yes" \
VERBOSE_MODE="false" \
DEBUG_MODE="false" \
QUIET_MODE="false" \
STRICT_MODE="false" \
MASKED_VARIABLES="false" \
PSU_CORE_EDITION="true"
COPY psu /usr/local/bin/
RUN chmod +x /usr/local/bin/psu
ENTRYPOINT ["/usr/local/bin/psu"]

236
README.md
View File

@ -1,14 +1,31 @@
# Portainer Stack Utils
<h1>Portainer Stack Utils</h1>
<div class="docsify-hidden">
[![Docker Automated build](https://img.shields.io/docker/automated/greenled/portainer-stack-utils.svg)](https://hub.docker.com/r/greenled/portainer-stack-utils/)
[![Docker Pulls](https://img.shields.io/docker/pulls/greenled/portainer-stack-utils.svg)](https://hub.docker.com/r/greenled/portainer-stack-utils/)
[![Microbadger](https://images.microbadger.com/badges/image/greenled/portainer-stack-utils.svg)](http://microbadger.com/images/greenled/portainer-stack-utils "Image size")
[![Docker Pulls](https://img.shields.io/docker/pulls/psuapp/psu.svg)](https://hub.docker.com/r/psuapp/psu/)
[![Microbadger](https://images.microbadger.com/badges/image/psuapp/psu.svg)](http://microbadger.com/images/psuapp/psu "Image size")
[![pipeline status](https://gitlab.com/psuapp/psu/badges/master/pipeline.svg)](https://gitlab.com/psuapp/psu/commits/master)
Bash script to deploy/update/undeploy stacks in a [Portainer](https://portainer.io/) instance from a [docker-compose](https://docs.docker.com/compose) [yaml file](https://docs.docker.com/compose/compose-file). Based on previous work by [@vladbabii](https://github.com/vladbabii) on [docker-how-to/portainer-bash-scripts](https://github.com/docker-how-to/portainer-bash-scripts).
Bash script to deploy/update/remove stacks in a [Portainer](https://portainer.io/) instance from a [docker-compose](https://docs.docker.com/compose) [yaml file](https://docs.docker.com/compose/compose-file).
## Supported Portainer API
_Based on previous work by [@vladbabii](https://github.com/vladbabii) on [docker-how-to/portainer-bash-scripts](https://github.com/docker-how-to/portainer-bash-scripts)._
Script was created for the latest Portainer API, which at the time of writing is [1.19.2](https://app.swaggerhub.com/apis/deviantony/Portainer/1.19.2).
<h2>Table of contents</h2>
<!-- Generated by https://github.com/mcpride/atom-mdtoc -->
<!-- MDTOC maxdepth:2 firsth1:2 numbering:0 flatten:0 bullets:1 updateOnSave:1 -->
- [How to install](#how-to-install)
- [Standalone](#standalone)
- [Docker image and variants](#docker-image-and-variants)
- [How to use](#how-to-use)
- [With options](#with-options)
- [With flags](#with-flags)
- [With envvars](#with-envvars)
- [Documentation](#documentation)
- [Supported Portainer API](#supported-portainer-api)
- [License](#license)
<!-- /MDTOC -->
</div>
## How to install
@ -17,9 +34,9 @@ Script was created for the latest Portainer API, which at the time of writing is
Just clone the repo and use the script:
```bash
git clone https://github.com/greenled/portainer-stack-utils.git
cd portainer-stack-utils
./psu -a deploy ...
git clone https://gitlab.com/psuapp/psu.git
cd psu/
bash ./psu deploy ...
```
For detailed instructions, see [How to use](#how-to-use) section.
@ -34,48 +51,122 @@ You will need these dependencies installed:
For Debian and similar apt-powered systems: `apt install bash httpie jq`.
### Docker image
Use [the published Docker image](https://hub.docker.com/r/greenled/portainer-stack-utils/):
### Docker image and variants
If you don't want or can't install `psu` and its dependencies, you can run it with the default [published Docker image](https://hub.docker.com/r/psuapp/psu), like this:
```bash
docker run -e ACTION=deploy greenled/portainer-stack-utils ...
docker run psuapp/psu deploy ...
```
See the [With envvars](#with-envvars) section for a list of all supported environment variables.
For detailed instructions, see [How to use](#how-to-use) section.
#### Supported tags
#### Tags
Published Docker images are [tagged](https://hub.docker.com/r/psuapp/psu/tags) matching [GitLab tags](https://gitlab.com/psuapp/psu/-/tags):
Published images are [tagged](https://hub.docker.com/r/greenled/portainer-stack-utils/tags/) matching [GitHub releases](https://github.com/greenled/portainer-stack-utils/releases):
- `dev` -> [`dev`](https://gitlab.com/psuapp/psu/-/tags/dev)
<!-- - `1.1.0-alpha.1` -> [`v1.1.0-alpha.1`](https://gitlab.com/psuapp/psu/-/tags/v1.1.0-alpha.1) -->
<!-- - `1`, `1.0`, `1.0.0`, `latest` -> [`v1.0.0`](https://gitlab.com/psuapp/psu/-/tags/v1.0.0) -->
- `0.1.1`, `latest` -> [`v0.1.1`](https://gitlab.com/psuapp/psu/-/tags/v0.1.1)
- `0.1.0` -> [`v0.1.0`](https://gitlab.com/psuapp/psu/-/tags/v0.1.0)
- `latest`, `0.1.1` -> `0.1.1`
- `0.1.0` -> `0.1.0`
- ...
- `dev` -> `master`
##### Variants
The `core` variant doesn't include `docker-compose`, so it's a bit smaller.
But you can't lint Docker compose/stack file before deploying a stack.
- `dev-core` -> [`dev`](https://gitlab.com/psuapp/psu/-/tags/dev)
<!-- - `1-core`, `1.0-core`, `1.0.0-core`, `core` -> [`v1.0.0`](https://gitlab.com/psuapp/psu/-/tags/v1.0.0) -->
The `debian` and `debian-core` variants use [Debian](https://www.debian.org) instead of [Alpine](https://alpinelinux.org/) as base image for `psu`.
- `dev-debian` -> [`dev`](https://gitlab.com/psuapp/psu/-/tags/dev)
- `dev-debian-core` -> [`dev`](https://gitlab.com/psuapp/psu/-/tags/dev)
<!-- - `1-debian`, `1.0-debian`, `1.0.0-debian`, `debian` -> [`v1.0.0`](https://gitlab.com/psuapp/psu/-/tags/v1.0.0) -->
<!-- - `1-debian-core`, `1.0-debian-core`, `1.0.0-debian-core`, `debian-core` -> [`v1.0.0`](https://gitlab.com/psuapp/psu/-/tags/v1.0.0) -->
#### Testing/debugging:
For testing/debugging, you can use this Docker image in interactive mode, to run any commands inside the container:
```bash
docker run -v $(pwd)/docker-compose.yml:/docker-compose.yml -it --rm --entrypoint bash psuapp/psu
# Run any commands here! E.g.
$ psu --version
Portainer Stack Utils, version 1.0.0
License GPLv3: GNU GPL version 3
```
## How to use
The provided `psu` script allows to deploy/update/undeploy Portainer stacks. Settings can be passed through envvars and/or flags. Both envvars and flags can be mixed but flags will always overwrite envvar values. When deploying a stack, if it doesn't exist a new one is created, otherwise it's updated (unless strict mode is active).
The provided `psu` script allows to deploy/update/remove... Portainer stacks. Settings can be passed through envvars and/or options and/or flags. Both envvars, options and flags can be mixed but options or flags will always overwrite envvar values. When deploying a stack, if it doesn't exist a new one is created, otherwise it's updated (unless strict mode is active).
### With options
This is more suitable for standalone script usage.
- `<action>` ("deploy", "rm", "ls"..., required): Whether to deploy, remove, list... the stack, _not an option but an argument_
- `--user` (string, required): Username
- `--password` (string, required): Password
- `--url` (string, required): URL to Portainer
- `--name` (string, required): Stack name
- `--compose-file` (string, required if action=deploy): Path to docker-compose file
For detailed instructions, see the full [options list](docs/README.md#available-options).
#### Examples
```bash
bash ./psu deploy --user admin --password password --url https://portainer.local --name mystack --compose-file /path/to/docker-compose.yml --env-file /path/to/env_vars_file
```
```bash
bash ./psu rm --user admin --password password --url https://portainer.local --name mystack
```
**With Docker:**
```bash
docker run -v $(pwd)/docker-compose.yml:/docker-compose.yml -v $(pwd)/.env:/.env psuapp/psu deploy --user admin --password password --url https://portainer.local --name mystack --compose-file docker-compose.yml --env-file .env
```
### With flags
This is more suitable for standalone script usage.
- `<action>` ("deploy", "rm", "ls"..., required): Whether to deploy, remove, list... the stack, _not a flag but an argument_
- `-u` (string, required): Username
- `-p` (string, required): Password
- `-l` (string, required): URL to Portainer
- `-n` (string, required): Stack name
- `-c` (string, required if action=deploy): Path to docker-compose file
For detailed instructions, see the full [flags list](docs/README.md#available-options).
#### Examples
```bash
bash ./psu deploy -u admin -p password -l https://portainer.local -n mystack -c /path/to/docker-compose.yml -g /path/to/env_vars_file
```
```bash
bash ./psu rm -u admin -p password -l https://portainer.local -n mystack
```
**With Docker:**
```bash
docker run -v $(pwd)/docker-compose.yml:/docker-compose.yml -v $(pwd)/.env:/.env psuapp/psu deploy -u admin -p password -l https://portainer.local -n mystack -c docker-compose.yml -g .env
```
### With envvars
This is particularly useful for CI/CD pipelines using Docker containers.
This is particularly useful for [CI](https://en.wikipedia.org/wiki/Continuous_integration)/[CD](https://en.wikipedia.org/wiki/Continuous_deployment) pipelines using Docker containers.
- `ACTION` ("deploy" or "undeploy", required): Whether to deploy or undeploy the stack
- `ACTION` ("deploy", "rm", "ls"..., required): Whether to deploy, remove, list... the stack
- `PORTAINER_USER` (string, required): Username
- `PORTAINER_PASSWORD` (string, required): Password
- `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"`.
- `VERBOSE_MODE` ("true" or "false", optional): Whether to activate verbose output mode or not. Defaults to `"false"`. See [verbose mode](#verbose-mode) below.
- `DEBUG_MODE` ("true" or "false", optional): Whether to activate debug output mode or not. Defaults to `"false"`. See [debug mode](#debug-mode) below.
- `STRICT_MODE` ("true" or "false", optional): Whether to activate strict mode or not. Defaults to `"false"`. See [strict mode](#strict-mode) below.
For detailed instructions, see the full [envvars list](docs/README.md#available-environment-variables).
#### Examples
@ -83,95 +174,40 @@ This is particularly useful for CI/CD pipelines using Docker containers.
export ACTION="deploy"
export PORTAINER_USER="admin"
export PORTAINER_PASSWORD="password"
export PORTAINER_URL="http://portainer.local"
export PORTAINER_URL="https://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
bash ./psu
```
```bash
export ACTION="undeploy"
export ACTION="rm"
export PORTAINER_USER="admin"
export PORTAINER_PASSWORD="password"
export PORTAINER_URL="http://portainer.local"
export PORTAINER_URL="https://portainer.local"
export PORTAINER_STACK_NAME="mystack"
./psu
bash ./psu
```
### With flags
This is more suitable for standalone script usage.
- `-a` ("deploy" or "undeploy", required): Whether to deploy or undeploy the stack
- `-u` (string, required): Username
- `-p` (string, required): Password
- `-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"`.
- `-v` ("true" or "false", optional): Whether to activate verbose output mode or not. Defaults to `"false"`. See [verbose mode](#verbose-mode) below.
- `-d` ("true" or "false", optional): Whether to activate debug output mode or not. Defaults to `"false"`. See [debug mode](#debug-mode) below.
- `-t` ("true" or "false", optional): Whether to activate strict mode or not. Defaults to `"false"`. See [strict mode](#strict-mode) below.
#### Examples
**With Docker:**
```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
docker run -v $(pwd)/docker-compose.yml:/docker-compose.yml -v $(pwd)/.env:/.env -e ACTION="deploy" -e PORTAINER_USER="admin" -e PORTAINER_PASSWORD="password" -e PORTAINER_URL="https://portainer.local" -e PORTAINER_STACK_NAME="mystack" -e DOCKER_COMPOSE_FILE="docker-compose.yml" -e ENVIRONMENT_VARIABLES_FILE=".env" psuapp/psu
```
```bash
./psu -a undeploy -u admin -p password -l http://portainer.local -n mystack
```
## Documentation
### Stack environment variables
<div class="docsify-hidden">
For advanced usage, see the full <a href="https://psuapp.gitlab.io/psu/1-0-stable"><abbr title="Portainer Stack Utils">PSU</abbr> documentation</a>.
</div>
There can be set environment variables for each stack, be it a new deployment or an update. For example:
For detailed instructions, see the [CLI Commands](docs/README.md) documentation.
```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
```
## Supported Portainer API
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.
```text
Getting auth token...
Getting stack mystack...
Stack mystack not found.
Getting Docker info...
Getting swarm cluster (if any)...
Swarm cluster found.
Preparing stack JSON...
Creating stack mystack...
```
Verbose mode can be enabled through [VERBOSE_MODE envvar](#with-envvars) or [-v flag](#with-flags).
### Debug mode
In debug mode the script prints as much information as possible to help diagnosing a malfunction.
**WARNING**: Debug mode will print configuration values (with Portainer credentials) and Portainer API responses (with sensitive information like authentication token and stacks environment variables). Avoid using debug mode in CI/CD pipelines, as pipeline logs are usually recorded.
Debug mode can be enabled through [DEBUG_MODE envvar](#with-envvars) or [-d flag](#with-flags).
### Strict mode
In strict mode the script never updates an existent stack nor removes an unexistent one, and instead exits with an error.
Strict mode can be enabled through [STRICT_MODE envvar](#with-envvars) or [-t flag](#with-flags).
<abbr title="Portainer Stack Utils">PSU</abbr> was created for the latest versions of Portainer API, which at the time of writing are [1.19.2](https://app.swaggerhub.com/apis/deviantony/Portainer/1.19.2), [1.20.2](https://app.swaggerhub.com/apis/deviantony/Portainer/1.20.2) and [1.21.0](https://app.swaggerhub.com/apis/deviantony/Portainer/1.21.0).
## License

0
docs/.nojekyll Normal file
View File

664
docs/README.md Normal file
View File

@ -0,0 +1,664 @@
<h1>
<div class="docsify-hidden">Portainer Stack Utils Docs</div>
<abbr title="command-line interface">CLI</abbr> Commands
</h1>
<div class="docsify-hidden">
<h2>Table of contents</h2>
<!-- Generated by https://github.com/mcpride/atom-mdtoc -->
<!-- MDTOC maxdepth:2 firsth1:2 numbering:0 flatten:0 bullets:1 updateOnSave:1 -->
- [Available actions](#available-actions)
- [Aliased actions](#aliased-actions)
- [Available options](#available-options)
- [Available environment variables](#available-environment-variables)
- [Actions in details](#actions-in-details)
- [`deploy`](#deploy)
- [`rm`](#rm)
- [`ls`](#ls)
- [`status`](#status)
- [`services`](#services)
- [`tasks`](#tasks)
- [`tasks:healthy`](#taskshealthy)
- [`containers`](#containers)
- [`login`](#login)
- [`lint`](#lint)
- [`inspect`](#inspect)
- [`system:info`](#systeminfo)
- [`actions`](#actions)
- [`help`](#help)
- [`version`](#version)
- [Options in details](#options-in-details)
- [Stack environment variables](#stack-environment-variables)
- [Verbose mode](#verbose-mode)
- [Debug mode](#debug-mode)
- [Strict mode](#strict-mode)
- [Quiet mode](#quiet-mode)
<!-- /MDTOC -->
</div>
## Available actions
| Name | Description |
| --------------------------------- | ----------- |
| [`deploy`](#deploy) | Deploy/update the given stack. |
| [`rm`](#rm) | Remove/undeploy the given stack. |
| [`ls`](#ls) | List stacks already deployed. |
| [`status`](#status) | Check if the stack is running/deployed correctly. |
| [`services`](#services) | List services already deployed in the current stack. |
| [`tasks`](#tasks) | List tasks in the current stack. |
| [`tasks:healthy`](#taskshealthy) | List tasks who are running correctly in the current stack. |
| [`containers`](#containers) | List containers running in the current stack. |
| [`login`](#login) | Log in to a Portainer instance. |
| [`lint`](#lint) | Validate the Docker compose/stack file. |
| [`inspect`](#inspect) | Display low-level information of the current stack. |
| [`system:info`](#systeminfo) | Display Docker system-wide information. |
| [`actions`](#actions) | List available actions for this program. |
| [`help`](#help) | Display help message. |
| [`version`](#version) | Display this program version. |
### Aliased actions
| Aliased action | Equivalent action |
| ------------------ | ---------------------------------- |
| `auth` | [`login`](#login) |
| `docker:info` | [`system:info`](#systeminfo) |
| `list` | [`ls`](#ls) |
| `ps` | [`tasks`](#tasks) |
| `ps:healthy` | [`tasks:healthy`](#taskshealthy) |
| `remove` | [`rm`](#rm) |
| `undeploy` | [`rm`](#rm) |
| `update` | [`deploy`](#deploy) |
| `validate` | [`lint`](#lint) |
## Available options
<!-- [deploy](#deploy), [rm](#rm), [ls](#ls), [status](#status), [services](#services), [tasks](#tasks), [tasks:healthy](#taskshealthy), [containers](#containers), [login](#login), [lint](#lint), [inspect](#inspect), [system:info](#systeminfo), [actions](#actions), [help](#help), [version](#version) | -->
| Flag | Option | Description | Used in action(s) |
| -----------| -------------------------------- | ----------- | ----------------- |
| `-l` | `--url=URL` | URL of the Portainer instance | [deploy](#deploy), [rm](#rm), [ls](#ls), [status](#status), [services](#services), [tasks](#tasks), [tasks:healthy](#taskshealthy), [containers](#containers), [login](#login), [inspect](#inspect), [system:info](#systeminfo) |
| `-u` | `--user=USERNAME` | Username of the Portainer instance | [deploy](#deploy), [rm](#rm), [ls](#ls), [status](#status), [services](#services), [tasks](#tasks), [tasks:healthy](#taskshealthy), [containers](#containers), [login](#login), [inspect](#inspect), [system:info](#systeminfo) |
| `-p` | `--password=PASSWORD` | Password of the Portainer instance | [deploy](#deploy), [rm](#rm), [ls](#ls), [status](#status), [services](#services), [tasks](#tasks), [tasks:healthy](#taskshealthy), [containers](#containers), [login](#login), [inspect](#inspect), [system:info](#systeminfo) |
| `-A` | `--auth-token=[AUTH_TOKEN]` | Use a Portainer auth token instead of `--user` and `--password` options, you can get it with the `psu login` command. Defaults to `null` | [deploy](#deploy), [rm](#rm), [ls](#ls), [status](#status), [services](#services), [tasks](#tasks), [tasks:healthy](#taskshealthy), [containers](#containers), [login](#login), [inspect](#inspect), [system:info](#systeminfo) |
| `-n` | `--name=STACK_NAME` | Stack name | [deploy](#deploy), [rm](#rm), [status](#status), [services](#services), [tasks](#tasks), [tasks:healthy](#taskshealthy), [containers](#containers), [inspect](#inspect) |
| `-c`, `-f` | `--compose-file=FILE_PATH` | Path to docker compose/stack file (required if action=deploy) | [deploy](#deploy), [lint](#lint) |
| `-C`, `-F` | `--compose-file-base64=[BASE64]` | Content of docker compose/stack file, encoded in base64, useful with Docker in Docker (only used when action=deploy) | [deploy](#deploy), [lint](#lint) |
| `-g` | `--env-file=[FILE_PATH]` | Path to a file of environment variables, to be used by the stack (only used when action=deploy) | [deploy](#deploy) |
| `-G` | `--env-file-base64=[BASE64]` | Content of file with environment variables, encoded in base64, to be used by the stack, useful with Docker in Docker (only used when action=deploy) | [deploy](#deploy) |
| `-e` | `--endpoint=[ENDPOINT_ID]` | Which Docker endpoint to use. Defaults to `1` | [deploy](#deploy), [rm](#rm), [ls](#ls), [status](#status), [services](#services), [tasks](#tasks), [tasks:healthy](#taskshealthy), [containers](#containers), [login](#login), [inspect](#inspect), [system:info](#systeminfo) |
| `-r` | `--prune` | Whether to prune unused containers or not (only used when action=deploy). Defaults to `false` | [deploy](#deploy) |
| `-T` | `--timeout=[SECONDS]` | Timeout, number of seconds before thrown an error (only used when action=status\|tasks\|tasks:healthy). Defaults to `100` | [status](#status), [tasks](#tasks), [tasks:healthy](#taskshealthy) |
| `-j` | `--detect-job=[true\|false]` | Auto detect services who are jobs in the current stack. Defaults to `true` | [status](#status), [tasks](#tasks), [tasks:healthy](#taskshealthy) |
| `-S` | `--service=[SERVICE_NAME]` | Filtering by a service name of the current stack (only used when action=status\|tasks\|tasks:healthy\|containers) | [status](#status), [tasks](#tasks), [tasks:healthy](#taskshealthy), [containers](#containers) |
| `-i` | `--insecure` | Skip the host's SSL certificate verification, use at your own risk. Defaults to `false` | [deploy](#deploy), [rm](#rm), [ls](#ls), [status](#status), [services](#services), [tasks](#tasks), [tasks:healthy](#taskshealthy), [containers](#containers), [login](#login), [inspect](#inspect), [system:info](#systeminfo) |
| `-v` | `--verbose` | Increase the verbosity of messages. Defaults to `false` | [deploy](#deploy), [rm](#rm), [ls](#ls), [status](#status), [services](#services), [tasks](#tasks), [tasks:healthy](#taskshealthy), [containers](#containers), [login](#login), [lint](#lint), [inspect](#inspect), [system:info](#systeminfo), [actions](#actions) |
| `-d` | `--debug` | Print as much information as possible to help diagnosing a malfunction. Defaults to `false` | [deploy](#deploy), [rm](#rm), [ls](#ls), [status](#status), [services](#services), [tasks](#tasks), [tasks:healthy](#taskshealthy), [containers](#containers), [login](#login), [lint](#lint), [inspect](#inspect), [system:info](#systeminfo), [actions](#actions) |
| `-m` | `--masked-variables` | In debug/verbose mode, value of sensitive variables will be hidden, avoid leaking passwords/tokens in logs. Possible values: true\|extended\|false. Defaults to `false` | [deploy](#deploy), [rm](#rm), [ls](#ls), [status](#status), [services](#services), [tasks](#tasks), [tasks:healthy](#taskshealthy), [containers](#containers), [login](#login), [inspect](#inspect), [system:info](#systeminfo) |
| `-q` | `--quiet` | Display the minimum of information or nothing, UNIX/Linux friendly. Defaults to `false` | [ls](#ls), [services](#services), [tasks](#tasks), [tasks:healthy](#taskshealthy), [containers](#containers), [inspect](#inspect) |
| `-t` | `--strict` | Never updates an existent stack nor removes an inexistent one, and instead exits with an error. Defaults to `false` | [deploy](#deploy), [rm](#rm) |
| `-L` | `--lint=[true\|false]` | Validate the Docker compose/stack file before deploying the stack (only used when action=deploy). Defaults to `true` | [deploy](#deploy) |
| `-h` | `--help` | Display help message. To display help of a given action, run: `psu <action> --help` | [deploy](#deploy), [rm](#rm), [ls](#ls), [status](#status), [services](#services), [tasks](#tasks), [tasks:healthy](#taskshealthy), [containers](#containers), [login](#login), [lint](#lint), [inspect](#inspect), [system:info](#systeminfo), [actions](#actions), [version](#version) |
| `-V` | `--version` | Display the version of this program | |
| `-s` | `--secure=[yes\|no]` | **DEPRECATED**: Use the `--insecure` option instead. Enable or disable the host's SSL certificate verification. Defaults to `yes` | [deploy](#deploy), [rm](#rm), [ls](#ls), [status](#status), [services](#services), [tasks](#tasks), [tasks:healthy](#taskshealthy), [containers](#containers), [login](#login), [inspect](#inspect), [system:info](#systeminfo) |
| `-a` | `--action=[ACTION_NAME]` | **DEPRECATED**: Use `<action>` argument instead. The name of the action to execute | |
## Available environment variables
| Environment variable | Description | Used in action(s) |
| ------------------------------------------- | -------------- | ----------------- |
| `ACTION=ACTION_NAME` | The name of the action to execute | <abbr title="not applicable">n/a</abbr> |
| `PORTAINER_URL=URL` | URL of the Portainer instance | [deploy](#deploy), [rm](#rm), [ls](#ls), [status](#status), [services](#services), [tasks](#tasks), [tasks:healthy](#taskshealthy), [containers](#containers), [login](#login), [inspect](#inspect), [system:info](#systeminfo) |
| `PORTAINER_USER=USERNAME` | Username of the Portainer instance | [deploy](#deploy), [rm](#rm), [ls](#ls), [status](#status), [services](#services), [tasks](#tasks), [tasks:healthy](#taskshealthy), [containers](#containers), [login](#login), [inspect](#inspect), [system:info](#systeminfo) |
| `PORTAINER_PASSWORD=PASSWORD` | Password of the Portainer instance | [deploy](#deploy), [rm](#rm), [ls](#ls), [status](#status), [services](#services), [tasks](#tasks), [tasks:healthy](#taskshealthy), [containers](#containers), [login](#login), [inspect](#inspect), [system:info](#systeminfo) |
| `PORTAINER_AUTH_TOKEN=[AUTH_TOKEN]` | Use a Portainer auth token instead of `--user` and `--password` options, you can get it with the `psu login` command. Defaults to `null` | [deploy](#deploy), [rm](#rm), [ls](#ls), [status](#status), [services](#services), [tasks](#tasks), [tasks:healthy](#taskshealthy), [containers](#containers), [login](#login), [inspect](#inspect), [system:info](#systeminfo) |
| `PORTAINER_STACK_NAME=STACK_NAME` | Stack name | [deploy](#deploy), [rm](#rm), [status](#status), [services](#services), [tasks](#tasks), [tasks:healthy](#taskshealthy), [containers](#containers), [inspect](#inspect) |
| `DOCKER_COMPOSE_FILE=FILE_PATH` | Path to docker compose/stack file (required if action=deploy) | [deploy](#deploy), [lint](#lint) |
| `ENVIRONMENT_VARIABLES_FILE=[FILE_PATH]` | Path to a file of environment variables, to be used by the stack (only used when action=deploy) | [deploy](#deploy) |
| `PORTAINER_ENDPOINT=[ENDPOINT_ID]` | Which Docker endpoint to use. Defaults to `1` | [deploy](#deploy), [rm](#rm), [ls](#ls), [status](#status), [services](#services), [tasks](#tasks), [tasks:healthy](#taskshealthy), [containers](#containers), [login](#login), [inspect](#inspect), [system:info](#systeminfo) |
| `PORTAINER_PRUNE=[true\|false]` | Whether to prune unused containers or not (only used when action=deploy). Defaults to `false` | [deploy](#deploy) |
| `TIMEOUT=[SECONDS]` | Timeout, number of seconds before thrown an error (only used when action=status\|tasks\|tasks:healthy). Defaults to `100` | [status](#status), [tasks](#tasks), [tasks:healthy](#taskshealthy) |
| `AUTO_DETECT_JOB=[true\|false]` | Auto detect services who are jobs in the current stack. Defaults to `true` | [status](#status), [tasks](#tasks), [tasks:healthy](#taskshealthy) |
| `PORTAINER_SERVICE_NAME=[SERVICE_NAME]` | Filtering by a service name of the current stack (only used when action=status\|tasks\|tasks:healthy\|containers) | [status](#status), [tasks](#tasks), [tasks:healthy](#taskshealthy), [containers](#containers) |
| `HTTPIE_VERIFY_SSL=[yes\|no]` | Enable or disable the host's SSL certificate verification. Defaults to `yes` | [deploy](#deploy), [rm](#rm), [ls](#ls), [status](#status), [services](#services), [tasks](#tasks), [tasks:healthy](#taskshealthy), [containers](#containers), [login](#login), [inspect](#inspect), [system:info](#systeminfo) |
| `VERBOSE_MODE=[true\|false]` | Increase the verbosity of messages. Defaults to `false` | [deploy](#deploy), [rm](#rm), [ls](#ls), [status](#status), [services](#services), [tasks](#tasks), [tasks:healthy](#taskshealthy), [containers](#containers), [login](#login), [lint](#lint), [inspect](#inspect), [system:info](#systeminfo), [actions](#actions) |
| `DEBUG_MODE=[true\|false]` | Print as much information as possible to help diagnosing a malfunction. Defaults to `false` | [deploy](#deploy), [rm](#rm), [ls](#ls), [status](#status), [services](#services), [tasks](#tasks), [tasks:healthy](#taskshealthy), [containers](#containers), [login](#login), [lint](#lint), [inspect](#inspect), [system:info](#systeminfo), [actions](#actions) |
| `MASKED_VARIABLES=[true\|extended\|false]` | In debug/verbose mode, value of sensitive variables will be hidden, avoid leaking passwords/tokens in logs. Possible values: true\|extended\|false. Defaults to `false` | [deploy](#deploy), [rm](#rm), [ls](#ls), [status](#status), [services](#services), [tasks](#tasks), [tasks:healthy](#taskshealthy), [containers](#containers), [login](#login), [inspect](#inspect), [system:info](#systeminfo) |
| `QUIET_MODE=[true\|false]` | Display the minimum of information or nothing, UNIX/Linux friendly. Defaults to `false` | [ls](#ls), [services](#services), [tasks](#tasks), [tasks:healthy](#taskshealthy), [containers](#containers), [inspect](#inspect) |
| `STRICT_MODE=[true\|false]` | Never updates an existent stack nor removes an inexistent one, and instead exits with an error. Defaults to `false` | [deploy](#deploy), [rm](#rm) |
| `DOCKER_COMPOSE_LINT=[true\|false]` | Validate the Docker compose/stack file before deploying the stack (only used when action=deploy). Defaults to `true` | [deploy](#deploy) |
<!-- | `PORTAINER_INSECURE=[true\|false]` | Skip the host's SSL certificate verification, use at your own risk. Defaults to `false` | [deploy](#deploy), [rm](#rm), [ls](#ls), [status](#status), [services](#services), [tasks](#tasks), [tasks:healthy](#taskshealthy), [containers](#containers), [login](#login), [inspect](#inspect), [system:info](#systeminfo) | -->
<!-- | `DOCKER_COMPOSE_BASE64=[BASE64]` | Content of docker compose/stack file, encoded in base64, useful with Docker in Docker (only used when action=deploy) | [deploy](#deploy), [lint](#lint) | -->
<!-- | `ENVIRONMENT_VARIABLES_BASE64=[BASE64]` | Content of file with environment variables, encoded in base64, to be used by the stack, useful with Docker in Docker (only used when action=deploy) | [deploy](#deploy) | -->
## Actions in details
### `deploy`
Deploy/update the given stack.
#### Aliased action:
`update`
#### Usage:
`psu deploy [options]`
#### Required options:
| Flag | Option | Description |
| ---------- | -------------------------------- | ----------- |
| `-l` | `--url=URL` | URL of the Portainer instance |
| `-u` | `--user=USERNAME` | Username of the Portainer instance |
| `-p` | `--password=PASSWORD` | Password of the Portainer instance |
| `-n` | `--name=STACK_NAME` | Stack name |
| `-c`, `-f` | `--compose-file=FILE_PATH` | Path to docker compose/stack file (required if action=deploy) |
#### Optional options:
| Flag | Option | Description |
| ---------- | -------------------------------- | ----------- |
| `-A` | `--auth-token=[AUTH_TOKEN]` | Use a Portainer auth token instead of `--user` and `--password` options, you can get it with the `psu login` command. Defaults to `null` |
| `-e` | `--endpoint=[ENDPOINT_ID]` | Which Docker endpoint to use. Defaults to `1` |
| `-L` | `--lint=[true\|false]` | Validate the Docker compose/stack file before deploying the stack (only used when action=deploy). Defaults to `true` |
| `-C`, `-F` | `--compose-file-base64=[BASE64]` | Content of docker compose/stack file, encoded in base64, useful with Docker in Docker (only used when action=deploy) |
| `-g` | `--env-file=[FILE_PATH]` | Path to a file of environment variables, to be used by the stack (only used when action=deploy) |
| `-G` | `--env-file-base64=[BASE64]` | Content of file with environment variables, encoded in base64, to be used by the stack, useful with Docker in Docker (only used when action=deploy) |
| `-r` | `--prune` | Whether to prune unused containers or not (only used when action=deploy). Defaults to `false` |
| `-i` | `--insecure` | Skip the host's SSL certificate verification, use at your own risk. Defaults to `false` |
| `-v` | `--verbose` | Increase the verbosity of messages. Defaults to `false` |
| `-d` | `--debug` | Print as much information as possible to help diagnosing a malfunction. Defaults to `false` |
| `-m` | `--masked-variables` | In debug/verbose mode, value of sensitive variables will be hidden, avoid leaking passwords/tokens in logs. Possible values: true\|extended\|false. Defaults to `false` |
| `-t` | `--strict` | Never updates an existent stack nor removes an inexistent one, and instead exits with an error. Defaults to `false` |
| `-h` | `--help` | Display help message. To display help of a given action, run: `psu <action> --help` |
### `rm`
Remove/undeploy the given stack.
#### Aliased actions:
`remove`, `undeploy`
#### Usage:
`psu rm [options]`
#### Required options:
| Flag | Option | Description |
| ---------- | -------------------------------- | ----------- |
| `-l` | `--url=URL` | URL of the Portainer instance |
| `-u` | `--user=USERNAME` | Username of the Portainer instance |
| `-p` | `--password=PASSWORD` | Password of the Portainer instance |
| `-n` | `--name=STACK_NAME` | Stack name |
#### Optional options:
| Flag | Option | Description |
| ---------- | -------------------------------- | ----------- |
| `-A` | `--auth-token=[AUTH_TOKEN]` | Use a Portainer auth token instead of `--user` and `--password` options, you can get it with the `psu login` command. Defaults to `null` |
| `-e` | `--endpoint=[ENDPOINT_ID]` | Which Docker endpoint to use. Defaults to `1` |
| `-i` | `--insecure` | Skip the host's SSL certificate verification, use at your own risk. Defaults to `false` |
| `-v` | `--verbose` | Increase the verbosity of messages. Defaults to `false` |
| `-d` | `--debug` | Print as much information as possible to help diagnosing a malfunction. Defaults to `false` |
| `-m` | `--masked-variables` | In debug/verbose mode, value of sensitive variables will be hidden, avoid leaking passwords/tokens in logs. Possible values: true\|extended\|false. Defaults to `false` |
| `-t` | `--strict` | Never updates an existent stack nor removes an inexistent one, and instead exits with an error. Defaults to `false` |
| `-h` | `--help` | Display help message. To display help of a given action, run: `psu <action> --help` |
### `ls`
List stacks already deployed.
#### Aliased actions:
`list`
#### Usage:
`psu ls [options]`
#### Examples:
```bash
psu ls --user admin --password password --url https://portainer.local
```
Output a JSON array of the stacks already deployed:
```json
[
{
"Id": "mystack_jpofkc0i9uo9wtx1zesuk649w",
"Name": "mystack",
"Type": "1",
"EndpointID": "1",
"EntryPoint": "docker-compose.yml",
"SwarmID": "jpofkc0i9uo9wtx1zesuk649w",
"ProjectPath": "/data/compose/mystack_jpofkc0i9uo9wtx1zesuk649w",
"Env": [
{
"name": "MYSQL_ROOT_PASSWORD",
"value": "password"
}
]
},
{
"Id": "mysecondstack_i0649w9uo9wtx1zesujpofkck",
"Name": "mysecondstack",
"Type": "1",
"EndpointID": "1",
"EntryPoint": "docker-compose.yml",
"SwarmID": "i0649w9uo9wtx1zesujpofkck",
"ProjectPath": "/data/compose/mysecondstack_i0649w9uo9wtx1zesujpofkck"
},
{
"Id": "mythirdstack_w9uo9wtxi064ujpofkck91zes",
"Name": "mythirdstack",
"Type": "1",
"EndpointID": "1",
"EntryPoint": "docker-compose.yml",
"SwarmID": "mythirdstack_w9uo9wtxi064ujpofkck91zes",
"ProjectPath": "/data/compose/mythirdstack_mythirdstack_w9uo9wtxi064ujpofkck91zes"
},
]
```
**With the `quiet` mode enabled**:
```bash
psu ls --user admin --password password --url https://portainer.local --quiet
```
Output the name of the stacks already deployed:
```bash
mystack
mysecondstack
mythirdstack
```
#### Required options:
| Flag | Option | Description |
| ---------- | -------------------------------- | ----------- |
| `-l` | `--url=URL` | URL of the Portainer instance |
| `-u` | `--user=USERNAME` | Username of the Portainer instance |
| `-p` | `--password=PASSWORD` | Password of the Portainer instance |
#### Optional options:
| Flag | Option | Description |
| ---------- | -------------------------------- | ----------- |
| `-A` | `--auth-token=[AUTH_TOKEN]` | Use a Portainer auth token instead of `--user` and `--password` options, you can get it with the `psu login` command. Defaults to `null` |
| `-e` | `--endpoint=[ENDPOINT_ID]` | Which Docker endpoint to use. Defaults to `1` |
| `-q` | `--quiet` | Display the minimum of information or nothing, UNIX/Linux friendly. Defaults to `false` |
| `-i` | `--insecure` | Skip the host's SSL certificate verification, use at your own risk. Defaults to `false` |
| `-v` | `--verbose` | Increase the verbosity of messages. Defaults to `false` |
| `-d` | `--debug` | Print as much information as possible to help diagnosing a malfunction. Defaults to `false` |
| `-m` | `--masked-variables` | In debug/verbose mode, value of sensitive variables will be hidden, avoid leaking passwords/tokens in logs. Possible values: true\|extended\|false. Defaults to `false` |
| `-h` | `--help` | Display help message. To display help of a given action, run: `psu <action> --help` |
### `status`
Check if the stack is running/deployed correctly
#### Usage:
`psu status [options]`
#### Required options:
| Flag | Option | Description |
| ---------- | -------------------------------- | ----------- |
| `-l` | `--url=URL` | URL of the Portainer instance |
| `-u` | `--user=USERNAME` | Username of the Portainer instance |
| `-p` | `--password=PASSWORD` | Password of the Portainer instance |
| `-n` | `--name=STACK_NAME` | Stack name |
#### Optional options:
| Flag | Option | Description |
| ---------- | -------------------------------- | ----------- |
| `-A` | `--auth-token=[AUTH_TOKEN]` | Use a Portainer auth token instead of `--user` and `--password` options, you can get it with the `psu login` command. Defaults to `null` |
| `-e` | `--endpoint=[ENDPOINT_ID]` | Which Docker endpoint to use. Defaults to `1` |
| `-S` | `--service=[SERVICE_NAME]` | Filtering by a service name of the current stack (only used when action=status\|tasks\|tasks:healthy\|containers) |
| `-j` | `--detect-job=[true\|false]` | Auto detect services who are jobs in the current stack. Defaults to `true` |
| `-T` | `--timeout=[SECONDS]` | Timeout, number of seconds before thrown an error (only used when action=status\|tasks\|tasks:healthy). Defaults to `100` |
| `-i` | `--insecure` | Skip the host's SSL certificate verification, use at your own risk. Defaults to `false` |
| `-v` | `--verbose` | Increase the verbosity of messages. Defaults to `false` |
| `-d` | `--debug` | Print as much information as possible to help diagnosing a malfunction. Defaults to `false` |
| `-m` | `--masked-variables` | In debug/verbose mode, value of sensitive variables will be hidden, avoid leaking passwords/tokens in logs. Possible values: true\|extended\|false. Defaults to `false` |
| `-h` | `--help` | Display help message. To display help of a given action, run: `psu <action> --help` |
### `services`
List services already deployed in the current stack.
#### Usage:
`psu services [options]`
#### Required options:
| Flag | Option | Description |
| ---------- | -------------------------------- | ----------- |
| `-l` | `--url=URL` | URL of the Portainer instance |
| `-u` | `--user=USERNAME` | Username of the Portainer instance |
| `-p` | `--password=PASSWORD` | Password of the Portainer instance |
| `-n` | `--name=STACK_NAME` | Stack name |
#### Optional options:
| Flag | Option | Description |
| ---------- | -------------------------------- | ----------- |
| `-A` | `--auth-token=[AUTH_TOKEN]` | Use a Portainer auth token instead of `--user` and `--password` options, you can get it with the `psu login` command. Defaults to `null` |
| `-e` | `--endpoint=[ENDPOINT_ID]` | Which Docker endpoint to use. Defaults to `1` |
| `-q` | `--quiet` | Display the minimum of information or nothing, UNIX/Linux friendly. Defaults to `false` |
| `-i` | `--insecure` | Skip the host's SSL certificate verification, use at your own risk. Defaults to `false` |
| `-v` | `--verbose` | Increase the verbosity of messages. Defaults to `false` |
| `-d` | `--debug` | Print as much information as possible to help diagnosing a malfunction. Defaults to `false` |
| `-m` | `--masked-variables` | In debug/verbose mode, value of sensitive variables will be hidden, avoid leaking passwords/tokens in logs. Possible values: true\|extended\|false. Defaults to `false` |
| `-h` | `--help` | Display help message. To display help of a given action, run: `psu <action> --help` |
### `tasks`
List tasks in the current stack.
#### Usage:
`psu tasks [options]`
#### Aliased action:
`ps`
#### Required options:
| Flag | Option | Description |
| ---------- | -------------------------------- | ----------- |
| `-l` | `--url=URL` | URL of the Portainer instance |
| `-u` | `--user=USERNAME` | Username of the Portainer instance |
| `-p` | `--password=PASSWORD` | Password of the Portainer instance |
| `-n` | `--name=STACK_NAME` | Stack name |
#### Optional options:
| Flag | Option | Description |
| ---------- | -------------------------------- | ----------- |
| `-A` | `--auth-token=[AUTH_TOKEN]` | Use a Portainer auth token instead of `--user` and `--password` options, you can get it with the `psu login` command. Defaults to `null` |
| `-e` | `--endpoint=[ENDPOINT_ID]` | Which Docker endpoint to use. Defaults to `1` |
| `-S` | `--service=[SERVICE_NAME]` | Filtering by a service name of the current stack (only used when action=status\|tasks\|tasks:healthy\|containers) |
| `-j` | `--detect-job=[true\|false]` | Auto detect services who are jobs in the current stack. Defaults to `true` |
| `-T` | `--timeout=[SECONDS]` | Timeout, number of seconds before thrown an error (only used when action=status\|tasks\|tasks:healthy). Defaults to `100` |
| `-q` | `--quiet` | Display the minimum of information or nothing, UNIX/Linux friendly. Defaults to `false` |
| `-i` | `--insecure` | Skip the host's SSL certificate verification, use at your own risk. Defaults to `false` |
| `-v` | `--verbose` | Increase the verbosity of messages. Defaults to `false` |
| `-d` | `--debug` | Print as much information as possible to help diagnosing a malfunction. Defaults to `false` |
| `-m` | `--masked-variables` | In debug/verbose mode, value of sensitive variables will be hidden, avoid leaking passwords/tokens in logs. Possible values: true\|extended\|false. Defaults to `false` |
| `-h` | `--help` | Display help message. To display help of a given action, run: `psu <action> --help` |
### `tasks:healthy`
List tasks who are running correctly in the current stack.
#### Usage:
`psu tasks:healthy [options]`
#### Aliased action:
`ps:healthy`
#### Required options:
| Flag | Option | Description |
| ---------- | -------------------------------- | ----------- |
| `-l` | `--url=URL` | URL of the Portainer instance |
| `-u` | `--user=USERNAME` | Username of the Portainer instance |
| `-p` | `--password=PASSWORD` | Password of the Portainer instance |
| `-n` | `--name=STACK_NAME` | Stack name |
#### Optional options:
| Flag | Option | Description |
| ---------- | -------------------------------- | ----------- |
| `-A` | `--auth-token=[AUTH_TOKEN]` | Use a Portainer auth token instead of `--user` and `--password` options, you can get it with the `psu login` command. Defaults to `null` |
| `-e` | `--endpoint=[ENDPOINT_ID]` | Which Docker endpoint to use. Defaults to `1` |
| `-S` | `--service=[SERVICE_NAME]` | Filtering by a service name of the current stack (only used when action=status\|tasks\|tasks:healthy\|containers) |
| `-j` | `--detect-job=[true\|false]` | Auto detect services who are jobs in the current stack. Defaults to `true` |
| `-T` | `--timeout=[SECONDS]` | Timeout, number of seconds before thrown an error (only used when action=status\|tasks\|tasks:healthy). Defaults to `100` |
| `-q` | `--quiet` | Display the minimum of information or nothing, UNIX/Linux friendly. Defaults to `false` |
| `-i` | `--insecure` | Skip the host's SSL certificate verification, use at your own risk. Defaults to `false` |
| `-v` | `--verbose` | Increase the verbosity of messages. Defaults to `false` |
| `-d` | `--debug` | Print as much information as possible to help diagnosing a malfunction. Defaults to `false` |
| `-m` | `--masked-variables` | In debug/verbose mode, value of sensitive variables will be hidden, avoid leaking passwords/tokens in logs. Possible values: true\|extended\|false. Defaults to `false` |
| `-h` | `--help` | Display help message. To display help of a given action, run: `psu <action> --help` |
### `containers`
List containers running in the current stack.
#### Usage:
`psu containers [options]`
#### Required options:
| Flag | Option | Description |
| ---------- | -------------------------------- | ----------- |
| `-l` | `--url=URL` | URL of the Portainer instance |
| `-u` | `--user=USERNAME` | Username of the Portainer instance |
| `-p` | `--password=PASSWORD` | Password of the Portainer instance |
| `-n` | `--name=STACK_NAME` | Stack name |
#### Optional options:
| Flag | Option | Description |
| ---------- | -------------------------------- | ----------- |
| `-A` | `--auth-token=[AUTH_TOKEN]` | Use a Portainer auth token instead of `--user` and `--password` options, you can get it with the `psu login` command. Defaults to `null` |
| `-e` | `--endpoint=[ENDPOINT_ID]` | Which Docker endpoint to use. Defaults to `1` |
| `-S` | `--service=[SERVICE_NAME]` | Filtering by a service name of the current stack (only used when action=status\|tasks\|tasks:healthy\|containers) |
| `-q` | `--quiet` | Display the minimum of information or nothing, UNIX/Linux friendly. Defaults to `false` |
| `-i` | `--insecure` | Skip the host's SSL certificate verification, use at your own risk. Defaults to `false` |
| `-v` | `--verbose` | Increase the verbosity of messages. Defaults to `false` |
| `-d` | `--debug` | Print as much information as possible to help diagnosing a malfunction. Defaults to `false` |
| `-m` | `--masked-variables` | In debug/verbose mode, value of sensitive variables will be hidden, avoid leaking passwords/tokens in logs. Possible values: true\|extended\|false. Defaults to `false` |
| `-h` | `--help` | Display help message. To display help of a given action, run: `psu <action> --help` |
### `login`
Log in to a Portainer instance.
#### Usage:
`psu login [options]`
#### Aliased action:
`auth`
#### Required options:
| Flag | Option | Description |
| ---------- | -------------------------------- | ----------- |
| `-l` | `--url=URL` | URL of the Portainer instance |
| `-u` | `--user=USERNAME` | Username of the Portainer instance |
| `-p` | `--password=PASSWORD` | Password of the Portainer instance |
#### Optional options:
| Flag | Option | Description |
| ---------- | -------------------------------- | ----------- |
| `-i` | `--insecure` | Skip the host's SSL certificate verification, use at your own risk. Defaults to `false` |
| `-v` | `--verbose` | Increase the verbosity of messages. Defaults to `false` |
| `-d` | `--debug` | Print as much information as possible to help diagnosing a malfunction. Defaults to `false` |
| `-m` | `--masked-variables` | In debug/verbose mode, value of sensitive variables will be hidden, avoid leaking passwords/tokens in logs. Possible values: true\|extended\|false. Defaults to `false` |
| `-h` | `--help` | Display help message. To display help of a given action, run: `psu <action> --help` |
### `lint`
Validate the Docker compose/stack file.
#### Usage:
`psu lint [options]`
#### Aliased action:
`validate`
#### Required options:
| Flag | Option | Description |
| ---------- | -------------------------------- | ----------- |
| `-c`, `-f` | `--compose-file=FILE_PATH` | Path to docker compose/stack file (required if action=deploy) |
#### Optional options:
| Flag | Option | Description |
| ---------- | -------------------------------- | ----------- |
| `-C`, `-F` | `--compose-file-base64=[BASE64]` | Content of docker compose/stack file, encoded in base64, useful with Docker in Docker (only used when action=deploy) |
| `-v` | `--verbose` | Increase the verbosity of messages. Defaults to `false` |
| `-d` | `--debug` | Print as much information as possible to help diagnosing a malfunction. Defaults to `false` |
| `-h` | `--help` | Display help message. To display help of a given action, run: `psu <action> --help` |
### `inspect`
Display low-level information of the current stack.
#### Usage:
`psu inspect [options]`
#### Examples:
```bash
psu inspect --user admin --password password --url https://portainer.local --name mystack
```
Output the JSON object of the stack if it's already deployed:
```json
{
"Id": "mystack_jpofkc0i9uo9wtx1zesuk649w",
"Name": "mystack",
"Type": "1",
"EndpointID": "1",
"EntryPoint": "docker-compose.yml",
"SwarmID": "jpofkc0i9uo9wtx1zesuk649w",
"ProjectPath": "/data/compose/mystack_jpofkc0i9uo9wtx1zesuk649w",
"Env": [
{
"name": "MYSQL_ROOT_PASSWORD",
"value": "password"
}
]
}
```
**With the `quiet` mode enabled**:
```bash
psu inspect --user admin --password password --url https://portainer.local --name mystack --quiet
```
Output the stack name if it's already deployed:
```bash
mystack
```
**Check if the stack is already deployed**:
```bash
stack_exist=$(psu inspect --user admin --password password --url https://portainer.local --name mystack)
if [ -n "$stack_exist" ]; then
echo OK
else
echo KO
fi
## OR check the exit code of the last executed command:
stack_exist=$(psu inspect --user admin --password password --url https://portainer.local --name mystack)
status=$?
if $(exit $status); then
echo OK
else
echo KO
fi
```
#### Required options:
| Flag | Option | Description |
| ---------- | -------------------------------- | ----------- |
| `-l` | `--url=URL` | URL of the Portainer instance |
| `-u` | `--user=USERNAME` | Username of the Portainer instance |
| `-p` | `--password=PASSWORD` | Password of the Portainer instance |
| `-n` | `--name=STACK_NAME` | Stack name |
#### Optional options:
| Flag | Option | Description |
| ---------- | -------------------------------- | ----------- |
| `-A` | `--auth-token=[AUTH_TOKEN]` | Use a Portainer auth token instead of `--user` and `--password` options, you can get it with the `psu login` command. Defaults to `null` |
| `-e` | `--endpoint=[ENDPOINT_ID]` | Which Docker endpoint to use. Defaults to `1` |
| `-q` | `--quiet` | Display the minimum of information or nothing, UNIX/Linux friendly. Defaults to `false` |
| `-i` | `--insecure` | Skip the host's SSL certificate verification, use at your own risk. Defaults to `false` |
| `-v` | `--verbose` | Increase the verbosity of messages. Defaults to `false` |
| `-d` | `--debug` | Print as much information as possible to help diagnosing a malfunction. Defaults to `false` |
| `-m` | `--masked-variables` | In debug/verbose mode, value of sensitive variables will be hidden, avoid leaking passwords/tokens in logs. Possible values: true\|extended\|false. Defaults to `false` |
| `-h` | `--help` | Display help message. To display help of a given action, run: `psu <action> --help` |
### `system:info`
Display Docker system-wide information.
#### Usage:
`psu system:info [options]`
#### Aliased action:
`docker:info`
#### Required options:
| Flag | Option | Description |
| ---------- | -------------------------------- | ----------- |
| `-l` | `--url=URL` | URL of the Portainer instance |
| `-u` | `--user=USERNAME` | Username of the Portainer instance |
| `-p` | `--password=PASSWORD` | Password of the Portainer instance |
#### Optional options:
| Flag | Option | Description |
| ---------- | -------------------------------- | ----------- |
| `-A` | `--auth-token=[AUTH_TOKEN]` | Use a Portainer auth token instead of `--user` and `--password` options, you can get it with the `psu login` command. Defaults to `null` |
| `-e` | `--endpoint=[ENDPOINT_ID]` | Which Docker endpoint to use. Defaults to `1` |
| `-i` | `--insecure` | Skip the host's SSL certificate verification, use at your own risk. Defaults to `false` |
| `-v` | `--verbose` | Increase the verbosity of messages. Defaults to `false` |
| `-d` | `--debug` | Print as much information as possible to help diagnosing a malfunction. Defaults to `false` |
| `-m` | `--masked-variables` | In debug/verbose mode, value of sensitive variables will be hidden, avoid leaking passwords/tokens in logs. Possible values: true\|extended\|false. Defaults to `false` |
| `-h` | `--help` | Display help message. To display help of a given action, run: `psu <action> --help` |
### `actions`
List available actions for this program.
#### Optional options:
| Flag | Option | Description |
| ---------- | -------------------------------- | ----------- |
| `-v` | `--verbose` | Increase the verbosity of messages. Defaults to `false` |
| `-d` | `--debug` | Print as much information as possible to help diagnosing a malfunction. Defaults to `false` |
| `-h` | `--help` | Display help message. To display help of a given action, run: `psu <action> --help` |
### `help`
Display help message.
### `version`
Display this program version.
#### Optional options:
| Flag | Option | Description |
| ---------- | -------------------------------- | ----------- |
| `-h` | `--help` | Display help message. To display help of a given action, run: `psu <action> --help` |
## Options in details
### 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 deploy --user admin --password password --url https://portainer.local --name mystack --compose-file docker-compose.yml --env-file .env
```
Stack environment variables can be enabled through `ENVIRONMENT_VARIABLES_FILE` envvar or `--env-file` option or `-g` flag.
### Verbose mode
In verbose mode the script prints execution steps.
```text
Getting auth token...
Getting stack mystack...
Stack mystack not found.
Getting Docker info...
Getting swarm cluster (if any)...
Swarm cluster found.
Preparing stack JSON...
Creating stack mystack...
```
Verbose mode can be enabled through `VERBOSE_MODE` envvar or `--verbose` option or `-v` flag.
### Debug mode
In debug mode the script prints as much information as possible to help diagnosing a malfunction.
**WARNING**: Debug mode will print configuration values (with Portainer credentials) and Portainer API responses (with sensitive information like authentication token and stacks environment variables). Avoid using debug mode in CI/CD pipelines, as pipeline logs are usually recorded.
Debug mode can be enabled through `DEBUG_MODE` envvar or `--debug` option or `-d` flag.
### Strict mode
In strict mode the script never updates an existent stack nor removes an inexistent one, and instead exits with an error.
Strict mode can be enabled through `STRICT_MODE` envvar or `--strict` option or `-t` flag.
### Quiet mode
In quiet mode the script prints the minimum of informations or nothing.
It's inspired by the `--quiet` option of [`docker images`](https://docs.docker.com/engine/reference/commandline/images/#options) command.
Quiet mode can be enabled through `QUIET_MODE` envvar or `--quiet` option or `-q` flag.

6
docs/_coverpage.md Normal file
View File

@ -0,0 +1,6 @@
# Portainer Stack Utils Documentation
> Bash script to deploy/update/remove stacks in a [Portainer](https://portainer.io/) instance.
[GitLab](https://gitlab.com/psuapp/psu/ ":target=_blank View source on GitLab")
<a href="#../README" title="Read documentation">Getting Started</a>

3
docs/_sidebar.md Normal file
View File

@ -0,0 +1,3 @@
- [**Getting Started**](../README)
- [**<abbr title="command-line interface">CLI</abbr> Commands**](docs/README)
- [**Changelog**](CHANGELOG)

1
docs/assets/docsify.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
docs/assets/prism-bash.min.js vendored Normal file
View File

@ -0,0 +1 @@
!function(e){var a={variable:[{pattern:/\$?\(\([\s\S]+?\)\)/,inside:{variable:[{pattern:/(^\$\(\([\s\S]+)\)\)/,lookbehind:!0},/^\$\(\(/],number:/\b0x[\dA-Fa-f]+\b|(?:\b\d+\.?\d*|\B\.\d+)(?:[Ee]-?\d+)?/,operator:/--?|-=|\+\+?|\+=|!=?|~|\*\*?|\*=|\/=?|%=?|<<=?|>>=?|<=?|>=?|==?|&&?|&=|\^=?|\|\|?|\|=|\?|:/,punctuation:/\(\(?|\)\)?|,|;/}},{pattern:/\$\([^)]+\)|`[^`]+`/,greedy:!0,inside:{variable:/^\$\(|^`|\)$|`$/}},/\$(?:[\w#?*!@]+|\{[^}]+\})/i]};e.languages.bash={shebang:{pattern:/^#!\s*\/bin\/bash|^#!\s*\/bin\/sh/,alias:"important"},comment:{pattern:/(^|[^"{\\])#.*/,lookbehind:!0},string:[{pattern:/((?:^|[^<])<<\s*)["']?(\w+?)["']?\s*\r?\n(?:[\s\S])*?\r?\n\2/,lookbehind:!0,greedy:!0,inside:a},{pattern:/(["'])(?:\\[\s\S]|\$\([^)]+\)|`[^`]+`|(?!\1)[^\\])*\1/,greedy:!0,inside:a}],variable:a.variable,function:{pattern:/(^|[\s;|&])(?:add|alias|apropos|apt|apt-cache|apt-get|aptitude|aspell|automysqlbackup|awk|basename|bash|bc|bconsole|bg|builtin|bzip2|cal|cat|cd|cfdisk|chgrp|chkconfig|chmod|chown|chroot|cksum|clear|cmp|comm|command|cp|cron|crontab|csplit|curl|cut|date|dc|dd|ddrescue|debootstrap|df|diff|diff3|dig|dir|dircolors|dirname|dirs|dmesg|du|egrep|eject|enable|env|ethtool|eval|exec|expand|expect|export|expr|fdformat|fdisk|fg|fgrep|file|find|fmt|fold|format|free|fsck|ftp|fuser|gawk|getopts|git|gparted|grep|groupadd|groupdel|groupmod|groups|grub-mkconfig|gzip|halt|hash|head|help|hg|history|host|hostname|htop|iconv|id|ifconfig|ifdown|ifup|import|install|ip|jobs|join|kill|killall|less|link|ln|locate|logname|logout|logrotate|look|lpc|lpr|lprint|lprintd|lprintq|lprm|ls|lsof|lynx|make|man|mc|mdadm|mkconfig|mkdir|mke2fs|mkfifo|mkfs|mkisofs|mknod|mkswap|mmv|more|most|mount|mtools|mtr|mutt|mv|nano|nc|netstat|nice|nl|nohup|notify-send|npm|nslookup|op|open|parted|passwd|paste|pathchk|ping|pkill|pnpm|popd|pr|printcap|printenv|printf|ps|pushd|pv|pwd|quota|quotacheck|quotactl|ram|rar|rcp|read|readarray|readonly|reboot|remsync|rename|renice|rev|rm|rmdir|rpm|rsync|scp|screen|sdiff|sed|sendmail|seq|service|sftp|shift|shopt|shutdown|sleep|slocate|sort|source|split|ssh|stat|strace|su|sudo|sum|suspend|swapon|sync|tail|tar|tee|test|time|timeout|times|top|touch|tr|traceroute|trap|tsort|tty|type|ulimit|umask|umount|unalias|uname|unexpand|uniq|units|unrar|unshar|unzip|update-grub|uptime|useradd|userdel|usermod|users|uudecode|uuencode|vdir|vi|vim|virsh|vmstat|wait|watch|wc|wget|whereis|which|who|whoami|write|xargs|xdg-open|yarn|yes|zip|zypper)(?=$|[\s;|&])/,lookbehind:!0},keyword:{pattern:/(^|[\s;|&])(?:let|:|\.|if|then|else|elif|fi|for|break|continue|while|in|case|function|select|do|done|until|echo|exit|return|set|declare)(?=$|[\s;|&])/,lookbehind:!0},boolean:{pattern:/(^|[\s;|&])(?:true|false)(?=$|[\s;|&])/,lookbehind:!0},operator:/&&?|\|\|?|==?|!=?|<<<?|>>|<=?|>=?|=~/,punctuation:/\$?\(\(?|\)\)?|\.\.|[{}[\];]/};var t=a.variable[1].inside;t.string=e.languages.bash.string,t.function=e.languages.bash.function,t.keyword=e.languages.bash.keyword,t.boolean=e.languages.bash.boolean,t.operator=e.languages.bash.operator,t.punctuation=e.languages.bash.punctuation,e.languages.shell=e.languages.bash}(Prism);

1
docs/assets/prism-json.min.js vendored Normal file
View File

@ -0,0 +1 @@
Prism.languages.json={comment:/\/\/.*|\/\*[\s\S]*?(?:\*\/|$)/,property:{pattern:/"(?:\\.|[^\\"\r\n])*"(?=\s*:)/,greedy:!0},string:{pattern:/"(?:\\.|[^\\"\r\n])*"(?!\s*:)/,greedy:!0},number:/-?\d+\.?\d*(e[+-]?\d+)?/i,punctuation:/[{}[\],]/,operator:/:/,boolean:/\b(?:true|false)\b/,null:{pattern:/\bnull\b/,alias:"keyword"}};

89
docs/index.html Normal file
View File

@ -0,0 +1,89 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Portainer Stack Utils Documentation</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="description" content="Description">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<!-- <link rel="stylesheet" href="//unpkg.com/docsify/lib/themes/vue.css"> -->
<link rel="stylesheet" href="assets/docsify_themes_vue.css">
<style>
.docsify-hidden {
display: none;
}
/* source: https://gitlab-corners.bryce.io/ */
.gitlab-corner-wrapper{overflow:hidden;width:100px;height:100px;position:fixed;top:0;right:0}.gitlab-corner{position:absolute;top:-16px;right:-50px;transform:rotate(45deg);background:#548;border:44px solid #548;border-bottom:none;border-top:#548 solid 16px}.gitlab-corner svg{width:60px;height:60px;margin-bottom:-4px}.cls-1{fill:#fc6d26}.cls-2{fill:#e24329}.cls-3{fill:#fca326}.gitlab-corner:hover .cls-1{animation:cycle .6s}.gitlab-corner:hover .cls-2{animation:cycleMid .6s}.gitlab-corner:hover .cls-3{animation:cycleEnd .6s}@keyframes cycle{100%,15%,60%{fill:#fc6d26}30%,75%{fill:#e24329}45%,90%{fill:#fca326}}@keyframes cycleMid{100%,15%,60%{fill:#e24329}30%,75%{fill:#fca326}45%,90%{fill:#fc6d26}}@keyframes cycleEnd{100%,15%,60%{fill:#fca326}30%,75%{fill:#fc6d26}45%,90%{fill:#e24329}}@media (max-width:500px){.gitlab-corner:hover .cls-1,.gitlab-corner:hover .cls-2,.gitlab-corner:hover .cls-3{animation:none}.gitlab-corner .cls-1{animation:cycle .6s}.gitlab-corner .cls-2{animation:cycleMid .6s}.gitlab-corner .cls-3{animation:cycleEnd .6s}}
/* source: https://www.w3schools.com/howto/howto_js_scroll_to_top.asp */
#top-btn{display:none;position:fixed;bottom:15px;right:20px;z-index:99;border:none;outline:none;background-color:#548;color:#fff;cursor:pointer;padding:10px;border-radius:5px;font-size:18px}#top-btn:hover{background-color:#555}
</style>
</head>
<body>
<div id="app"></div>
<script>
window.$docsify = {
name: '<abbr title="Portainer Stack Utils">PSU</abbr> Docs ',
// repo: 'https://gitlab.com/psuapp/psu',
coverpage: true,
onlyCover: true,
search: 'auto',
maxLevel: 3,
loadSidebar: true,
subMaxLevel: 3,
alias: {
'/CHANGELOG': 'repo/CHANGELOG',
'/../README': 'repo/README',
'/docs/README': 'README',
'/.*/_sidebar.md': '/_sidebar.md'
},
search : {
paths: [
'/docs/README', // => /README.md
'/../README', // => /repo/README.md
'/CHANGELOG', // => /repo/CHANGELOG.md
],
depth: 3
},
}
</script>
<!-- <script src="//unpkg.com/docsify/lib/docsify.min.js"></script> -->
<!-- <script src="//unpkg.com/prismjs/components/prism-bash.min.js"></script> -->
<!-- <script src="//unpkg.com/prismjs/components/prism-json.min.js"></script> -->
<!-- <script src="//unpkg.com/docsify/lib/plugins/search.min.js"></script> -->
<script src="assets/docsify.min.js"></script>
<script src="assets/prism-bash.min.js"></script>
<script src="assets/prism-json.min.js"></script>
<script src="assets/docsify_plugins_search.min.js"></script>
<!-- source: https://www.w3schools.com/howto/howto_js_scroll_to_top.asp -->
<script>
(function () {
// When the user scrolls down 100px from the top of the document, show the button
var scrollFunction = function() {
if (document.body.scrollTop > 100 || document.documentElement.scrollTop > 100) {
document.getElementById("top-btn").style.display = "block";
} else {
document.getElementById("top-btn").style.display = "none";
}
}
// When the user clicks on the button, scroll to the top of the document
window.topFunction = function () {
window.scrollTo(0, 0);
}
window.addEventListener("scroll", function () {
scrollFunction();
}, false);
})();
</script>
<button onclick="topFunction()" id="top-btn" title="Go top"></button>
<!-- source: https://gitlab-corners.bryce.io/ -->
<div class="gitlab-corner-wrapper">
<a href="https://gitlab.com/psuapp/psu" class="gitlab-corner" aria-label="View source on GitLab" title="View source on GitLab" target="_blank" rel="noopener">
<svg id="logo_art" data-name="logo art" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 586 559">
<g id="g44"><path id="path46" class="cls-1" d="M461.17,301.83l-18.91-58.12L404.84,128.43a6.47,6.47,0,0,0-12.27,0L355.15,243.64H230.82L193.4,128.43a6.46,6.46,0,0,0-12.26,0L143.78,243.64l-18.91,58.19a12.88,12.88,0,0,0,4.66,14.39L293,435,456.44,316.22a12.9,12.9,0,0,0,4.73-14.39"/></g><g id="g48"><path id="path50" class="cls-2" d="M293,434.91h0l62.16-191.28H230.87L293,434.91Z"/></g><g id="g56"><path id="path58" class="cls-1" d="M293,434.91,230.82,243.63h-87L293,434.91Z"/></g><g id="g64"><path id="path66" class="cls-3" d="M143.75,243.69h0l-18.91,58.12a12.88,12.88,0,0,0,4.66,14.39L293,435,143.75,243.69Z"/></g><g id="g72"><path id="path74" class="cls-2" d="M143.78,243.69h87.11L193.4,128.49a6.47,6.47,0,0,0-12.27,0l-37.35,115.2Z"/></g><g id="g76"><path id="path78" class="cls-1" d="M293,434.91l62.16-191.28H442.3L293,434.91Z"/></g><g id="g80"><path id="path82" class="cls-3" d="M442.24,243.69h0l18.91,58.12a12.85,12.85,0,0,1-4.66,14.39L293,434.91l149.2-191.22Z"/></g><g id="g84"><path id="path86" class="cls-2" d="M442.28,243.69h-87.1l37.42-115.2a6.46,6.46,0,0,1,12.26,0l37.42,115.2Z"/></g>
</svg>
</a>
</div>
</body>
</html>

134
scripts/helpers.sh Normal file
View File

@ -0,0 +1,134 @@
#!/usr/bin/env bash
set -e
[[ "$TRACE" ]] && set -x
function registry_login() {
if [[ -n "$CI_REGISTRY_USER" ]]; then
echo "Logging to GitLab Container Registry with CI credentials..."
docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY"
echo ""
fi
}
function external_registry_login() {
if [[ -n "$DOCKER_USER" ]]; then
echo "Logging to External Registry..."
docker login -u "$DOCKER_USER" -p "$DOCKER_PASSWORD" "$DOCKER_REGISTRY"
echo ""
fi
}
function setup_docker() {
if ! docker info &>/dev/null; then
if [ -z "$DOCKER_HOST" -a "$KUBERNETES_PORT" ]; then
export DOCKER_HOST='tcp://localhost:2375'
fi
fi
}
function git_tag_on_success() {
local git_tag="${1:-dev}"
local target_branch="${2:-master}"
if (
[ "$CI_COMMIT_REF_NAME" == "$target_branch" ] &&
[ -n "$GITLAB_API_TOKEN" ] &&
[ -z "$GIT_RESET_TAG" ]
); then
# (re)write Protected Tag
# TODO: rewrite these 'wget' commands with 'curl' commands
wget -Y off -O response.txt --header='Accept-Charset: UTF-8' --header "PRIVATE-TOKEN: $GITLAB_API_TOKEN" --post-data '_method=delete' $CI_API_V4_URL/projects/$CI_PROJECT_ID/protected_tags/$git_tag || true
wget -Y off -O response.txt --header='Accept-Charset: UTF-8' --header "PRIVATE-TOKEN: $GITLAB_API_TOKEN" --post-data '_method=delete' $CI_API_V4_URL/projects/$CI_PROJECT_ID/repository/tags/$git_tag || true
wget -Y off -O response.txt --header='Accept-Charset: UTF-8' --header "PRIVATE-TOKEN: $GITLAB_API_TOKEN" --post-data "tag_name=$git_tag&ref=$CI_COMMIT_SHA" $CI_API_V4_URL/projects/$CI_PROJECT_ID/repository/tags
wget -Y off -O response.txt --header='Accept-Charset: UTF-8' --header "PRIVATE-TOKEN: $GITLAB_API_TOKEN" --post-data "name=$git_tag&create_access_level=0" $CI_API_V4_URL/projects/$CI_PROJECT_ID/protected_tags
else
echo WARNING: \$GITLAB_API_TOKEN variable is missing
fi
}
function registry_tag_on_success() {
local current_registry_tag="${1:-$CI_COMMIT_SHA}"
local target_registry_tag="${2:-dev}"
local target_branch="${3:-master}"
local current_registry_image="${4:-$CI_REGISTRY_IMAGE/builds}"
local target_registry_image="${5:-$CI_REGISTRY_IMAGE}"
local target_external_registry_image="${6:-$DOCKER_REGISTRY_IMAGE}"
if [ "$CI_COMMIT_REF_NAME" == "$target_branch" ]; then
docker pull "$current_registry_image:$current_registry_tag"
docker tag "$current_registry_image:$current_registry_tag" "$target_registry_image:$target_registry_tag"
docker push "$target_registry_image:$target_registry_tag"
if [ -n "$target_external_registry_image" ]; then
docker tag "$current_registry_image:$current_registry_tag" "$target_external_registry_image:$target_registry_tag"
docker push "$target_external_registry_image:$target_registry_tag"
fi
fi
}
# Reset the git repository to the target tag
#
# First argument pass to this function or the `GIT_RESET_TAG` CI variable
# must be set
# git_reset_from_tag dev
# or:
# GIT_RESET_TAG=dev
# git_reset_from_tag
function git_reset_from_tag() {
local git_target_tag="${1:-$GIT_RESET_TAG}"
if (
[ "$CI_PIPELINE_SOURCE" == "schedule" ] &&
[ -n "$git_target_tag" ] && [ "$GIT_STRATEGY" != "none" ] &&
[ -z "$CI_COMMIT_TAG" ]
); then
# Get specific tag
git reset --hard $git_target_tag
export CI_COMMIT_SHA=$(git rev-parse HEAD)
export CI_COMMIT_SHORT_SHA=$(git rev-parse --short HEAD)
else
echo NOTICE: Not a Scheduling Pipeline, skip the git tag reset stuff... # debug
fi
}
# Get latest stable semantic versioning git tag
# from a specific git branch
#
# First argument pass to this function or the `CI_COMMIT_REF_NAME` CI variable
# must be set
# get_git_last_stable_tag 1-0-stable
# -> "v1.0.3"
# or:
# CI_COMMIT_REF_NAME=1-0-stable
# get_git_last_stable_tag
# -> "v1.0.3"
#
# see: https://semver.org
function get_git_last_stable_tag() {
local target_branch="${1:-$CI_COMMIT_REF_NAME}"
git fetch origin $target_branch
git checkout -f -q $target_branch
echo "$(git tag --merged $target_branch | grep -w '^v[0-9]\+\.[0-9]\+\.[0-9]\+$' | sort -r -V | head -n 1)"
}
# Useful for updating Docker images, on release/stable branches, but not the psu code
# See: https://docs.gitlab.com/ce/workflow/gitlab_flow.html#release-branches-with-gitlab-flow
# You can create a scheduled pipeline with a targeted git branch ("master", "1-0-stable", ...)
# and the CI variables below:
# "GIT_RESET_LAST_STABLE_TAG=true"
# "DOCKER_CACHE_DISABLED=true"
# "TEST_DISABLED=" # no value to unset this variable
# See: https://gitlab.com/help/user/project/pipelines/schedules
function git_reset_from_last_stable_tag() {
if [ "$GIT_RESET_LAST_STABLE_TAG" == "true" ]; then
git_last_stable_tag="$(get_git_last_stable_tag)"
if [ -n "$git_last_stable_tag" ]; then
export CI_COMMIT_REF_PROTECTED="true"
export CI_COMMIT_TAG="$git_last_stable_tag"
export GIT_RESET_TAG="$git_last_stable_tag"
git_reset_from_tag
else
echo WARNING: Last stable git tag not found
fi
fi
}

View File

@ -0,0 +1,63 @@
#!/usr/bin/env bash
# Usage:
# bash push-to-gitlab-pages.sh <target_branch_name> <path_to_push_in_the_branch>
# bash push-to-gitlab-pages.sh
# bash push-to-gitlab-pages.sh "gitlab-pages" "public"
set -e
[[ "$TRACE" ]] && set -x
if [ -z "$GITLAB_WRITE_REPO_USER" ] || [ -z "$GITLAB_WRITE_REPO_TOKEN" ]; then
echo "ERROR: \$GITLAB_WRITE_REPO_USER and/or \$GITLAB_WRITE_REPO_TOKEN CI variables must be set!"
exit 1
fi
current_path=$PWD
project_path=$CI_PROJECT_DIR
# GitLab Pages branch name
branch=${1:-gitlab-pages}
# Path of the folder to push in GitLab Pages branch
source_path=${2:-$project_path/public}
repository_path=$project_path/$branch
# Path of the files to publish in GitLab Pages
pages_path=$repository_path/public
repository_url_path=$(echo $CI_PROJECT_URL | sed -E 's/^https?:\/\/(.+)$/\1/')
repository_url_protocol=$(echo $CI_PROJECT_URL | sed -E 's/^(https?:\/\/).+$/\1/')
repository_user=$GITLAB_WRITE_REPO_USER
repository_password=$GITLAB_WRITE_REPO_TOKEN
repository_url=${repository_url_protocol}${repository_user}:${repository_password}@${repository_url_path}
last_commit_message=$(git log -1 -z --format="%s%n%ncommit %H%nAuthor: %an <%ae>%nDate: %ad" HEAD)
if ! $(git clone --quiet --branch=$branch --single-branch $repository_url $repository_path); then
# Create $branch if needed
git clone --quiet --depth=1 --single-branch $repository_url $repository_path
cd $repository_path
git config user.name "$repository_user"
git config user.email "${repository_user}@users.noreply.gitlab.com"
git checkout --orphan $branch
git rm --quiet -rf .
cp -pa $project_path/.gitlab-ci.yml .gitlab-ci.yml
echo "Git branch dedicated to [GitLab Pages](https://docs.gitlab.com/ce/user/project/pages/).
DO NOT EDIT THIS BRANCH BY HAND PLEASE" > README.md
git add README.md .gitlab-ci.yml
git commit -m "Git branch dedicated to GitLab Pages (https://docs.gitlab.com/ce/user/project/pages/)."
fi
cd $current_path
if [ -d $source_path ]; then
cp -Rpa $source_path/. $pages_path
cd $repository_path
git config user.name "$repository_user"
git config user.email "${repository_user}@users.noreply.gitlab.com"
# Update git '$branch' and push changes to the current repository
git add --all
git diff-index --quiet HEAD || git commit -m "$last_commit_message"
git push --quiet origin $branch | true
fi
cd $current_path

113
scripts/release.sh Normal file
View File

@ -0,0 +1,113 @@
#!/usr/bin/env bash
set -e
[[ "$TRACE" ]] && set -x
source "$(dirname "$0")/helpers.sh"
# First argument is the variant(s) of the Docker image to be tagged
# e.g. "core"
# or "core debian debian-core" for multiple variants
variant_input="$1"
# Split space (" ") character to new line "\n"
variants="$(echo "$variant_input" | tr ' ' '\n')"
if [ "$GIT_RESET_LAST_STABLE_TAG" != "true" ]; then
git_tag_on_success "dev"
registry_tag_on_success $CI_COMMIT_SHA "dev"
for variant in $variants; do
registry_tag_on_success "${variant}-${CI_COMMIT_SHA}" "dev-${variant}"
done
fi
# Write current git tag to the Docker registry
# if it starts with the 'v' character, followed by a number, e.g 'v1.0.2'
# see: https://semver.org
# You should set a 'v*' protected tags, in your GitLab project
# see: https://gitlab.com/help/user/project/protected_tags#configuring-protected-tags
if (
[[ "$CI_COMMIT_TAG" =~ ^v[0-9][0-9a-z.\-]*$ ]] &&
[ "$CI_COMMIT_REF_PROTECTED" == "true" ]
); then
# Remove the first letter ('v' character), for tagging Docker images
target_registry_tag="${CI_COMMIT_TAG:1}"
registry_tag_on_success $CI_COMMIT_SHA $target_registry_tag $CI_COMMIT_REF_NAME
for variant in $variants; do
registry_tag_on_success "${variant}-${CI_COMMIT_SHA}" "${target_registry_tag}-${variant}" $CI_COMMIT_REF_NAME
done
# If current git tag is a stable semantic version.
# see: https://semver.org
if [[ "$target_registry_tag" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
git fetch --all
# e.g. " origin/master"
master_branch="$(git branch --no-color --remotes --list 'origin/master')"
# e.g. " origin/1-0-stable
# > origin/1-1-stable
# > origin/2-0-stable
# > origin/master"
commit_in_branches="$(git branch --no-color --remotes --contains $CI_COMMIT_SHA | grep -w ' origin/master\| origin/.*-stable')"
# e.g. " origin/2-0-stable
# > origin/1-1-stable
# > origin/1-0-stable"
stable_branches="$(echo "$commit_in_branches" | grep -vw ' origin/master' | sort -rV)"
# Extract the MAJOR and MAJOR.MINOR versions
# Then tag and push them to Docker registry.
# e.g. the "v1.3.7" git tag, will create/update "1" and "1.3" registry tags
# For MAJOR tag on git stable branches, we use the latest stable branch
# e.g. when there is 2 stable branches "1-0-stable" and "1-1-stable"
# If there is a new minor git tag "v1.0.3", only on the "1-0-stable".
# We don't have to update the registry tag "1".
# Because only git tags on the "1-1-stable" git branch can do it.
major_registry_tag=$(echo $target_registry_tag | sed -E 's/^([0-9]+)\.[0-9]+\.[0-9]+$/\1/')
minor_registry_tag=$(echo $target_registry_tag | sed -E 's/^[0-9]+\.([0-9]+)\.[0-9]+$/\1/')
major_minor_registry_tag=$major_registry_tag.$minor_registry_tag
target_stable_branch="${major_registry_tag}-${minor_registry_tag}-stable"
# e.g. "1-1-stable"
latest_major_branch="$(echo "$stable_branches"| grep -E "^ origin/$major_registry_tag-[0-9]+-stable$" | head -n 1 | sed -E 's/^ origin\/(.+)$/\1/')"
# If the git tag is only contained in master branch
# or if it's contained in the latest stable branch who has the same MAJOR version
# e.g. when there is 2 stable branches "1-0-stable" and "1-1-stable".
# If git tag is "v1.0.1", the MAJOR registry tag "1" is NOT written.
# Because the lastet stable branch is "1-1-stable".
#
# If git tag is "v1.1.3", the MAJOR registry tag "1" is written.
# Because the lastet stable branch is "1-1-stable".
if (
([ -n "$master_branch" ] && [ "$master_branch" == "$commit_in_branches" ]) ||
[ "$latest_major_branch" == "$target_stable_branch" ]
); then
registry_tag_on_success $CI_COMMIT_SHA $major_registry_tag $CI_COMMIT_REF_NAME
fi
registry_tag_on_success $CI_COMMIT_SHA $major_minor_registry_tag $CI_COMMIT_REF_NAME
for variant in $variants; do
if (
([ -n "$master_branch" ] && [ "$master_branch" == "$commit_in_branches" ]) ||
[ "$latest_major_branch" == "$target_stable_branch" ]
); then
registry_tag_on_success "${variant}-${CI_COMMIT_SHA}" "${major_registry_tag}-${variant}" $CI_COMMIT_REF_NAME
fi
registry_tag_on_success "${variant}-${CI_COMMIT_SHA}" "${major_minor_registry_tag}-${variant}" $CI_COMMIT_REF_NAME
done
# The latest stable semantic versioning git tag on the "master" branch
# is considered as the "latest" registry tag.
# Check if the current git commit is only present in the "master" branch
# and not in stable branches (e.g. "1-0-stable").
# Non stable branches are skipped (e.g. "feature-better-performance").
if [ -n "$master_branch" ] && [ "$master_branch" == "$commit_in_branches" ]; then
registry_tag_on_success $CI_COMMIT_SHA "latest" $CI_COMMIT_REF_NAME
for variant in $variants; do
registry_tag_on_success "${variant}-${CI_COMMIT_SHA}" "${variant}" $CI_COMMIT_REF_NAME
done
fi
fi
fi

5
scripts/test.sh Normal file
View File

@ -0,0 +1,5 @@
#!/usr/bin/env bash
set -e
[[ "$TRACE" ]] && set -x
bash "$(dirname "$0")/../tests/run.sh"

View File

@ -0,0 +1,43 @@
version: '3.6'
services:
portainer:
image: portainer/portainer:$PORTAINER_VERSION
command: --admin-password-file '/run/secrets/portainer-password'
labels:
- traefik.enable=true
- traefik.frontend.rule=Host:portainer.$BASE_DOMAIN
- traefik.frontend.redirect.entryPoint=https
- traefik.backend=portainer
- traefik.port=9000
networks:
- traefik-net
environment:
- HTTP_PROXY
- HTTPS_PROXY
- http_proxy
- https_proxy
- NO_PROXY
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- portainer:/data
secrets:
- portainer-password
deploy:
placement:
constraints:
- node.role == manager
update_config:
parallelism: 1
delay: 10s
networks:
traefik-net:
driver: overlay
external: true
secrets:
portainer-password:
external: true
volumes:
portainer:

View File

@ -0,0 +1,45 @@
version: '3.6'
services:
reverse-proxy:
image: traefik:$TRAEFIK_VERSION
# Enables the web UI and tells Traefik to listen to docker
command:
- --api
- --docker
- --docker.watch
- --defaultEntryPoints=http,https
# IMPORTANT: The `retry.attempts` param is required to allow zero (1s) downtime deployment
- --retry.attempts=10
- "--entryPoints=Name:http Address::80"
- "--entryPoints=Name:https Address::443 TLS WhiteList.UseXForwardedFor:true"
- --docker.exposedbydefault=false
environment:
- HTTP_PROXY
- HTTPS_PROXY
- http_proxy
- https_proxy
- NO_PROXY
ports:
- "80:80"
- 443:443
# The Web UI (enabled by --api)
- "8080:8080"
networks:
- traefik-net
volumes:
# So that Traefik can listen to the Docker events
- /var/run/docker.sock:/var/run/docker.sock
deploy:
mode: global
placement:
constraints:
- node.role == manager
update_config:
parallelism: 1
delay: 10s
networks:
traefik-net:
driver: overlay
name: traefik-net

View File

@ -0,0 +1,65 @@
version: '3.6'
services:
job:
image: $CI_REGISTRY/$CI_PROJECT_NAMESPACE/hub/testing/psu-php-app:latest
labels:
job-name: job-web-app
entrypoint: ["php"]
command: -r 'echo date("d-m-Y H:i:s").PHP_EOL;'
# command: -r 'sleep(20); echo date("d-m-Y H:i:s").PHP_EOL;'
environment:
- HTTP_PROXY
- HTTPS_PROXY
- http_proxy
- https_proxy
- NO_PROXY
deploy:
restart_policy:
condition: none
app:
image: $CI_REGISTRY/$CI_PROJECT_NAMESPACE/hub/testing/psu-php-app:latest
environment:
- HTTP_PROXY
- HTTPS_PROXY
- http_proxy
- https_proxy
- NO_PROXY
volumes:
- php-runner:/var/run/php
deploy:
update_config:
order: start-first
failure_action: continue
web:
image: $CI_REGISTRY/$CI_PROJECT_NAMESPACE/hub/testing/psu-apache2:latest
labels:
- traefik.enable=true
- "traefik.frontend.rule=Host:$PSU_STACK_NAME.$BASE_DOMAIN"
- traefik.frontend.redirect.entryPoint=https
- traefik.backend=web-php-app
- traefik.port=5000
environment:
- HTTP_PROXY
- HTTPS_PROXY
- http_proxy
- https_proxy
- NO_PROXY
volumes:
- php-runner:/var/run/php
stop_signal: SIGWINCH
deploy:
update_config:
failure_action: rollback
order: start-first
networks:
- traefik-net
networks:
traefik-net:
external: true
volumes:
php-runner:

View File

@ -0,0 +1,3 @@
FOO=bar
DB_MIGRATE="php artisan migrate --force"
ENVS="( ONE TWO=two THREE=\"php artisan\" )"

View File

@ -0,0 +1,8 @@
{
"Name": "gitlab-registry",
"Type": 1,
"URL": "registry.gitlab.com",
"Authentication": true,
"Username": "$CI_REGISTRY_USER",
"Password": "$CI_REGISTRY_PASSWORD"
}

140
tests/run.sh Normal file
View File

@ -0,0 +1,140 @@
#!/usr/bin/env bash
set -e
[[ "$TRACE" ]] && set -x
CLUSTER_IP=$(getent hosts cluster | awk '{ print $1 }')
export BASE_DOMAIN="$CLUSTER_IP.nip.io"
export PSU_STACK_NAME="web-app"
PSU_URL="https://portainer.$BASE_DOMAIN"
PSU_USER="admin"
PSU_PASSWORD="mypassword"
# Change working directory to 'tests/'
cd "$(dirname "$0")"
function psu_wrapper() {
docker run --rm $PSU_IMAGE:${PSU_TAG:-$CI_COMMIT_SHA} "$@"
}
function psu_core_wrapper() {
docker run --rm $PSU_IMAGE:${PSU_TAG_CORE:-core-$CI_COMMIT_SHA} "$@"
}
function application_exists() {
local stack_name="$1"
local stack_info
stack_info=$(psu_wrapper inspect \
--auth-token="$PSU_AUTH_TOKEN" \
--url="$PSU_URL" \
--name="$stack_name" \
--insecure \
--debug="false" \
--verbose="false") || true
if [ -n "$stack_info" ]; then
echo "true"
else
echo "false"
fi
}
# Init Docker Swarm
docker swarm init
# Deploy Traefik test
# Parse the Docker traefik stack file to deploy
envsubst '$TRAEFIK_VERSION' < dockerfiles/docker-stack-traefik.yml > dockerfiles/docker-stack-traefik-final.yml
docker stack deploy -c dockerfiles/docker-stack-traefik-final.yml traefik --with-registry-auth
bash -c "timeout 20 bash -c 'while ! (echo > /dev/tcp/cluster/443 && curl -fs --max-time 2 http://cluster:8080/dashboard/) >/dev/null 2>&1; do sleep 1; done;'"
# Deploy Portainer test
echo -n $PSU_PASSWORD | docker secret create portainer-password -
# Parse the Docker portainer stack file to deploy
envsubst '$PORTAINER_VERSION,$BASE_DOMAIN' < dockerfiles/docker-stack-portainer.yml > dockerfiles/docker-stack-portainer-final.yml
docker stack deploy -c dockerfiles/docker-stack-portainer-final.yml portainer --with-registry-auth
bash -c "timeout 20 bash -c 'while ! (curl -fkLs --max-time 2 $PSU_URL) >/dev/null 2>&1; do sleep 1; done;'"
# psu version test
psu_wrapper --version | grep -E 'version v?[0-9]+\.[0-9]+\.[0-9]+'
# psu help test
# Check if 4 terms present in the help message are visible when running the command
[ "$(psu_wrapper --help | grep -E 'Usage|Arguments|Options|Available actions' | wc -l)" == "4" ]
# TODO: test 'actions' action
# TODO: test 'services' action
# TODO: test 'containers' action
# Portainer login test
PSU_AUTH_TOKEN=$(psu_wrapper login --user $PSU_USER --password $PSU_PASSWORD --url $PSU_URL --insecure)
# Add GitLab Docker registry access to Portainer
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
# Add local endpoint to the Portainer instance
http --check-status --ignore-stdin --verify=no --timeout=10 POST "$PSU_URL/api/endpoints" "Authorization: Bearer $PSU_AUTH_TOKEN" Name==local EndpointType==1
# 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)
[ "$(echo "$docker_info" | jq -j ".DockerRootDir")" == "/var/lib/docker" ]
# Parse the Docker compose/stack file to deploy
envsubst '$CI_REGISTRY,$CI_PROJECT_NAMESPACE,$BASE_DOMAIN,$PSU_STACK_NAME' < dockerfiles/docker-stack-web-app.yml > dockerfiles/docker-stack-web-app-final.yml
# Convert docker compose/stack file in a base64 encoded string,
# due to some limitations with Docker in Docker and volumes
# see: https://stackoverflow.com/a/55481515
docker_compose_base64="$(cat dockerfiles/docker-stack-web-app-final.yml | base64)"
# Lint the Docker compose/stack file to be deployed
lint_result=$(psu_wrapper lint --compose-file-base64 "$docker_compose_base64" --debug false --verbose false)
[ "$lint_result" == "[OK]" ]
# Stack deploy test
# Check the 'web-app' stack isn't deployed yet
[ "$(application_exists $PSU_STACK_NAME)" == "false" ]
# Deploy the 'web-app' stack
psu_core_wrapper deploy --user $PSU_USER --password $PSU_PASSWORD --url $PSU_URL --name $PSU_STACK_NAME --compose-file-base64 "$docker_compose_base64" --insecure $PORTAINER_DEPLOY_EXTRA_ARGS
[ "$(application_exists $PSU_STACK_NAME)" == "true" ]
# Ensure the deployed stack is running correctly
psu_wrapper status --user=$PSU_USER --password=$PSU_PASSWORD --url=$PSU_URL --name=$PSU_STACK_NAME --insecure --timeout=30 $PORTAINER_DEPLOY_EXTRA_ARGS
now=$(date -u +"%Y-%m-%d")
curl -fks --max-time 6 https://$PSU_STACK_NAME.$BASE_DOMAIN | grep -w "OK<br>$now"
# Ensure the deployed stack has no environment variables
stack_info=$(psu_wrapper inspect --user=$PSU_USER --password=$PSU_PASSWORD --url=$PSU_URL --name=$PSU_STACK_NAME --debug=false --verbose=false --insecure)
stack_envvars="$(echo -n "$stack_info" | jq ".Env" -jc)"
[ "$stack_envvars" == "[]" ]
# List deployed stacks with quiet mode test
stack_list="$(psu_wrapper ls --user $PSU_USER --password $PSU_PASSWORD --url $PSU_URL --insecure --debug false --verbose false --quiet)"
[ "$stack_list" == "$PSU_STACK_NAME" ]
# Convert env file in a base64 encoded string,
# due to some limitations with Docker in Docker and volumes
# see: https://stackoverflow.com/a/55481515
env_file_base64="$(cat dockerfiles/web-app.env | base64)"
# Stack update test
psu_wrapper deploy --user $PSU_USER --password $PSU_PASSWORD --url $PSU_URL --name $PSU_STACK_NAME --compose-file-base64 "$docker_compose_base64" --env-file-base64 "$env_file_base64" --insecure $PORTAINER_DEPLOY_EXTRA_ARGS
# Check the 'web-app' stack is already deployed
[ "$(application_exists $PSU_STACK_NAME)" == "true" ]
# Ensure the updated stack is running correctly
psu_wrapper status --user=$PSU_USER --password=$PSU_PASSWORD --url=$PSU_URL --name=$PSU_STACK_NAME --insecure --timeout=30 $PORTAINER_DEPLOY_EXTRA_ARGS
now=$(date -u +"%Y-%m-%d")
curl -fks --max-time 6 https://$PSU_STACK_NAME.$BASE_DOMAIN | grep -w "OK<br>$now"
# Ensure the updated stack has environment variables corresponding to the env file
stack_info=$(psu_wrapper inspect --user=$PSU_USER --password=$PSU_PASSWORD --url=$PSU_URL --name=$PSU_STACK_NAME --debug=false --verbose=false --insecure)
stack_envvars="$(echo -n "$stack_info" | jq ".Env" -jc)"
[ "$stack_envvars" != "[]" ]
env_foo_from_stack="$(echo -n "$stack_envvars" | jq ".[] | select(.name == \"FOO\") | .value" -r)"
(. dockerfiles/web-app.env && [ "$FOO" == "$env_foo_from_stack" ])
env_db_migrate_from_stack="$(echo -n "$stack_envvars" | jq ".[] | select(.name == \"DB_MIGRATE\") | .value" -r)"
(. dockerfiles/web-app.env && [ "$DB_MIGRATE" == "$env_db_migrate_from_stack" ])
# Stack remove/undeploy test
psu_wrapper rm --user $PSU_USER --password $PSU_PASSWORD --url $PSU_URL --name $PSU_STACK_NAME --insecure $PORTAINER_DEPLOY_EXTRA_ARGS
[ "$(application_exists $PSU_STACK_NAME)" == "false" ]
# Ensure the reverse proxy Traefik free the URL of the removed stack
bash -c "timeout 20 bash -c 'while ! (curl -ks --max-time 2 https://$PSU_STACK_NAME.$BASE_DOMAIN | grep -w \"404 page not found\") >/dev/null 2>&1; do sleep 1; done;'"
curl -ks --max-time 6 https://$PSU_STACK_NAME.$BASE_DOMAIN