mirror of
https://gitlab.sectorq.eu/jaydee/portainer.git
synced 2026-05-04 18:49:50 +02:00
Compare commits
202 Commits
v1.0.0
...
64c3615705
| Author | SHA1 | Date | |
|---|---|---|---|
| 64c3615705 | |||
| f5c883964a | |||
| bc984f05d2 | |||
| a9a4de2038 | |||
| 9205e0c8f7 | |||
| 062176a875 | |||
| db7005b304 | |||
| 0e8fcaa530 | |||
| df151c4c7f | |||
| eba757bf25 | |||
| 1e1f82e658 | |||
| 5e36820f88 | |||
| 546f695a0d | |||
| 4a3609ef27 | |||
| 9986c1bc03 | |||
| 18afddee86 | |||
| c5c5a5fafd | |||
| 654c244f82 | |||
| c6bba8c9a5 | |||
| 06b281ccc8 | |||
| 13563428b6 | |||
| f35d6e794d | |||
| b7130b835d | |||
| e911eee76b | |||
| 86a18e66f7 | |||
| ec27800d62 | |||
| 30e66ce890 | |||
| f022223da3 | |||
| 94e84d03c2 | |||
| 9915863650 | |||
| 88c41f2bcf | |||
| 9ac830a505 | |||
| e85fe9c98e | |||
| ce2460b33a | |||
| 9007376ad3 | |||
| cc91ef474c | |||
| e3e05a6430 | |||
| 4a422730b1 | |||
| 233d5c4ff8 | |||
| 6907785036 | |||
| 8c911c27a9 | |||
| bd138dd5aa | |||
| f7ebd259e1 | |||
| faa4c90e98 | |||
| 2421b93730 | |||
| 83d4514356 | |||
| 2cc8b6171d | |||
| 51081cf879 | |||
| 9d3fd8ef3e | |||
| 40b1d5d967 | |||
| 12036631be | |||
| cdbd508f95 | |||
| d1302c58a7 | |||
| 42ebef5f45 | |||
| 1a2f57a81f | |||
| eee10f8338 | |||
| 6d30afc469 | |||
| 02db7ce43b | |||
| 5cbeb664f3 | |||
| 2cf4b2b67d | |||
| 6aecddef8a | |||
| 2283143aca | |||
| 37a6b1548b | |||
| 3add3a4362 | |||
| 8bba5974fd | |||
| e7851cf976 | |||
| 27feb31606 | |||
| ce71433620 | |||
| d98024a696 | |||
| 19dae69fab | |||
| 67d9bef57d | |||
| 39a0175111 | |||
| 29ca908c75 | |||
| a16188a110 | |||
| 90f9b19f5e | |||
| 48708436df | |||
| 0c2be26cf0 | |||
| 8de33d2d92 | |||
| 475e49d4e9 | |||
| f770cbc926 | |||
| 8ba22f79b9 | |||
| ddeb67750f | |||
| 12ff88f8e8 | |||
| 9d27e804a5 | |||
| 1f0a19b7b1 | |||
| 5adfbbcf3d | |||
| 0dda82be87 | |||
| 1a54c1e341 | |||
| 8d4bd382ee | |||
| 96068d4fb3 | |||
| de37276ab6 | |||
| 2dc800f7f9 | |||
| ae387a794c | |||
| a3518ec0bb | |||
| 3e86a75502 | |||
| 11cd76215a | |||
| 4bbe283211 | |||
| fc3fe7b837 | |||
| 3152014ca3 | |||
| e411c81224 | |||
| 8ae696a96a | |||
| abd989a0db | |||
| bb8ef3bdb8 | |||
| 99aa451620 | |||
| fd1fcf90a4 | |||
| 135447d7aa | |||
| 164252534e | |||
| 807437c47e | |||
| 08a15f3bb9 | |||
| 1f4319f4dd | |||
| 9800b01ea2 | |||
| 4cd9cfce20 | |||
| 12a095e169 | |||
| 162c270c02 | |||
| 0c4a91d7ae | |||
| 36cb83694c | |||
| f97cd105ba | |||
| 954d5b2dd7 | |||
| 73a68a0f1b | |||
| 4598caca89 | |||
| 4d22e77689 | |||
| c2a1a7d115 | |||
| 9a79910428 | |||
| bc69ff6223 | |||
| 4e610eea32 | |||
| 4e8c0ab3a0 | |||
| b057dfcce4 | |||
| ab15e7c8ea | |||
| 74269b0368 | |||
| 45c97d1791 | |||
| c341c2332f | |||
| d0f2cfc75f | |||
| c22287f53b | |||
| f618476534 | |||
| 5865df9abc | |||
| f83ee560c1 | |||
| 5ce8573013 | |||
| e8191802b1 | |||
| e546d0cf3f | |||
| daf219329a | |||
| b601ecc0c3 | |||
| 1a8e532a02 | |||
| d6e4db6dd4 | |||
| 78012cec65 | |||
| ba098499f5 | |||
| ce24b5c00d | |||
| 46143a7c12 | |||
| c731fbe0de | |||
| d878a2baa0 | |||
| e280ea67f7 | |||
| 260eb63262 | |||
| 41d6ec9914 | |||
| 15e442d49b | |||
| 14c31575af | |||
| 406513b4b8 | |||
| 0c1b624972 | |||
| db5209e3fb | |||
| 111c70ef00 | |||
| 8bba4d1d18 | |||
| db45a48106 | |||
| 53438a3fb0 | |||
| 5646e0692d | |||
| 6634cc20fa | |||
| fc9f25a203 | |||
| 8351e9f1b1 | |||
| d007510704 | |||
| 3e202c9fd8 | |||
| d6e5c4087d | |||
| f70ebecc49 | |||
| b8264994c5 | |||
| 6081c44d4c | |||
| abb5fc7708 | |||
| a9eecac96d | |||
| e7bcee762d | |||
| e3fed3304a | |||
| 338aa66565 | |||
| 058554a0ea | |||
| b28a7c8273 | |||
| e464c498ff | |||
| b028a48fc0 | |||
| b2373f7016 | |||
| 1b50b3337c | |||
| ac68b5be6f | |||
| 035abffeab | |||
| 6097d2b442 | |||
| 55bdff1745 | |||
| b385f3db12 | |||
| 94a3ccfd23 | |||
| b0c570d7ba | |||
| 4339a7d769 | |||
| 039078191f | |||
| f5d76d87e0 | |||
| f561508d2e | |||
| 974966fdd8 | |||
| 63e158899e | |||
| 9336b56f96 | |||
| 66fba7b994 | |||
| 7804dbb117 | |||
| fb1763e14d | |||
| 829891d1ba | |||
| 174aab4faa | |||
| 9c6445ee03 |
+52
-11
@@ -1,13 +1,30 @@
|
|||||||
|
|
||||||
stages: # List of stages for jobs, and their order of execution
|
stages: # List of stages for jobs, and their order of execution
|
||||||
|
- notify1
|
||||||
- lint
|
- lint
|
||||||
|
- test
|
||||||
|
- security
|
||||||
|
- dast
|
||||||
- build
|
- build
|
||||||
- clean
|
- clean
|
||||||
- notify
|
- notify
|
||||||
variables:
|
variables:
|
||||||
GIT_SSH_COMMAND: "ssh -i /home/gitlab-runner/.ssh/id_rsa -o IdentitiesOnly=yes"
|
GIT_SSH_COMMAND: "ssh -i /home/gitlab-runner/.ssh/id_rsa -o IdentitiesOnly=yes"
|
||||||
|
CS_IMAGE: r.sectorq.eu/jaydee/builder:amd64
|
||||||
|
|
||||||
|
notify1:
|
||||||
|
stage: notify1 # Should be in a later stage than the job that might fail
|
||||||
|
when: on_success # <-- This is the key keyword
|
||||||
|
script:
|
||||||
|
- column=':'
|
||||||
|
- echo "${flow_id}"
|
||||||
|
- curl -XPOST http://192.168.77.101:8123/api/webhook/voice-notifications-tC_8YKxMJIAaQRV5riKuC7Zl --data-raw 'message=portainer build job started'
|
||||||
|
- rm -rf /home/gitlab-runner/builds/1fLwHSKm2/0/jaydee/portainer.tmp
|
||||||
|
rules:
|
||||||
|
- if: '$CI_COMMIT_MESSAGE =~ /build/'
|
||||||
lint:
|
lint:
|
||||||
stage: lint
|
stage: lint
|
||||||
image: r.sectorq.eu/jaydee/builder-portainer:latest
|
image: r.sectorq.eu/jaydee/builder:latest
|
||||||
before_script:
|
before_script:
|
||||||
- python3 -m pip install --break-system-packages flake8 black pylint tabulate prompt_toolkit hvac
|
- python3 -m pip install --break-system-packages flake8 black pylint tabulate prompt_toolkit hvac
|
||||||
- export PATH="$PATH:/home/gitlab-runner/.local/bin"
|
- export PATH="$PATH:/home/gitlab-runner/.local/bin"
|
||||||
@@ -19,17 +36,23 @@ lint:
|
|||||||
- rm -rf /home/gitlab-runner/builds/1fLwHSKm2/0/jaydee/portainer.tmp
|
- rm -rf /home/gitlab-runner/builds/1fLwHSKm2/0/jaydee/portainer.tmp
|
||||||
rules:
|
rules:
|
||||||
- if: '$CI_COMMIT_MESSAGE =~ /lint/'
|
- if: '$CI_COMMIT_MESSAGE =~ /lint/'
|
||||||
|
|
||||||
|
|
||||||
|
include:
|
||||||
|
- template: Security/SAST.gitlab-ci.yml
|
||||||
|
- template: Security/Dependency-Scanning.gitlab-ci.yml
|
||||||
|
- template: Security/Secret-Detection.gitlab-ci.yml
|
||||||
|
- template: Security/DAST.gitlab-ci.yml
|
||||||
|
#- template: Security/Container-Scanning.gitlab-ci.yml
|
||||||
|
|
||||||
build-job: # This job runs in the build stage, which runs first.
|
build-job: # This job runs in the build stage, which runs first.
|
||||||
stage: build
|
stage: build
|
||||||
image: r.sectorq.eu/jaydee/builder-portainer:latest
|
image: r.sectorq.eu/jaydee/builder:amd64
|
||||||
script:
|
script:
|
||||||
- mkdir -p ~/.ssh
|
- pyinstaller --onefile --clean -n portainer main.py
|
||||||
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' > ~/.ssh/id_rsa
|
|
||||||
- chmod 600 ~/.ssh/id_rsa
|
|
||||||
- pyinstaller --onefile portainer.py
|
|
||||||
#- scp -o ConnectTimeout=5 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null dist/portainer jd@192.168.80.222:/myapps/bin/ || true
|
#- scp -o ConnectTimeout=5 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null dist/portainer jd@192.168.80.222:/myapps/bin/ || true
|
||||||
- scp -o ConnectTimeout=5 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null dist/portainer jd@192.168.77.12:/myapps/bin/ || true
|
- scp -o ConnectTimeout=5 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null dist/portainer jd@192.168.77.12:/myapps/bin/ || true
|
||||||
- scp -o ConnectTimeout=5 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null dist/portainer jd@192.168.77.101:/myapps/bin/ || true
|
- curl -F "file=@dist/portainer" https://myapps.sectorq.eu/
|
||||||
- rm -rf /home/gitlab-runner/builds/1fLwHSKm2/0/jaydee/portainer.tmp
|
- rm -rf /home/gitlab-runner/builds/1fLwHSKm2/0/jaydee/portainer.tmp
|
||||||
artifacts:
|
artifacts:
|
||||||
paths:
|
paths:
|
||||||
@@ -40,19 +63,37 @@ build-job: # This job runs in the build stage, which runs first.
|
|||||||
# - curl -X POST https://kestra.sectorq.eu/api/v1/executions/webhook/jaydee/ansible-all/${flow_id} -d '{"tag":["proxmox"],"target":["servers"]}' -H "Content-Type${column} application/json"
|
# - curl -X POST https://kestra.sectorq.eu/api/v1/executions/webhook/jaydee/ansible-all/${flow_id} -d '{"tag":["proxmox"],"target":["servers"]}' -H "Content-Type${column} application/json"
|
||||||
rules:
|
rules:
|
||||||
- if: '$CI_COMMIT_MESSAGE =~ /build/'
|
- if: '$CI_COMMIT_MESSAGE =~ /build/'
|
||||||
|
build-job-arm: # This job runs in the build stage, which runs first.
|
||||||
|
stage: build
|
||||||
|
image: r.sectorq.eu/jaydee/builder:arm64
|
||||||
|
script:
|
||||||
|
- pyinstaller --onefile --clean -n portainer_arm main.py
|
||||||
|
#- scp -o ConnectTimeout=5 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null dist/portainer jd@192.168.80.222:/myapps/bin/ || true
|
||||||
|
- scp -o ConnectTimeout=5 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null dist/portainer_arm jd@192.168.77.12:/myapps/bin/ || true
|
||||||
|
- curl -F "file=@dist/portainer_arm" https://myapps.sectorq.eu/
|
||||||
|
- rm -rf /home/gitlab-runner/builds/1fLwHSKm2/0/jaydee/portainer.tmp
|
||||||
|
artifacts:
|
||||||
|
paths:
|
||||||
|
- dist/
|
||||||
|
expire_in: 1 week
|
||||||
|
# - column=":"
|
||||||
|
# - echo "${flow_id}"
|
||||||
|
# - curl -X POST https://kestra.sectorq.eu/api/v1/executions/webhook/jaydee/ansible-all/${flow_id} -d '{"tag":["proxmox"],"target":["servers"]}' -H "Content-Type${column} application/json"
|
||||||
|
rules:
|
||||||
|
- if: '$CI_COMMIT_MESSAGE =~ /build-all/'
|
||||||
|
|
||||||
|
|
||||||
clean-job: # This job runs in the build stage, which runs first.
|
clean-job: # This job runs in the build stage, which runs first.
|
||||||
stage: clean
|
stage: clean
|
||||||
script:
|
script:
|
||||||
- rm -rf /home/gitlab-runner/builds/1fLwHSKm2/0/jaydee/portainer.tmp
|
- rm -rf /home/gitlab-runner/builds/1fLwHSKm2/0/jaydee/portainer.tmp
|
||||||
rules:
|
rules:
|
||||||
- if: '$CI_COMMIT_MESSAGE =~ /build/'
|
- if: '$CI_COMMIT_MESSAGE =~ /build/'
|
||||||
|
|
||||||
cleanup_on_failure_job:
|
cleanup_on_failure_job:
|
||||||
stage: clean # Should be in a later stage than the job that might fail
|
stage: clean # Should be in a later stage than the job that might fail
|
||||||
when: on_failure # <-- This is the key keyword
|
when: on_failure # <-- This is the key keyword
|
||||||
script:
|
script:
|
||||||
- rm -rf /home/gitlab-runner/builds/1fLwHSKm2/0/jaydee/portainer.tmp
|
- rm -rf /home/gitlab-runner/builds/1fLwHSKm2/0/jaydee/portainer.tmp
|
||||||
|
|
||||||
notify:
|
notify:
|
||||||
stage: notify # Should be in a later stage than the job that might fail
|
stage: notify # Should be in a later stage than the job that might fail
|
||||||
when: on_success # <-- This is the key keyword
|
when: on_success # <-- This is the key keyword
|
||||||
@@ -61,7 +102,8 @@ notify:
|
|||||||
- echo "${flow_id}"
|
- echo "${flow_id}"
|
||||||
- curl -XPOST http://192.168.77.101:8123/api/webhook/voice-notifications-tC_8YKxMJIAaQRV5riKuC7Zl --data-raw 'message=portainer build job completed'
|
- curl -XPOST http://192.168.77.101:8123/api/webhook/voice-notifications-tC_8YKxMJIAaQRV5riKuC7Zl --data-raw 'message=portainer build job completed'
|
||||||
- rm -rf /home/gitlab-runner/builds/1fLwHSKm2/0/jaydee/portainer.tmp
|
- rm -rf /home/gitlab-runner/builds/1fLwHSKm2/0/jaydee/portainer.tmp
|
||||||
|
rules:
|
||||||
|
- if: '$CI_COMMIT_MESSAGE =~ /build/'
|
||||||
notify2:
|
notify2:
|
||||||
stage: notify # Should be in a later stage than the job that might fail
|
stage: notify # Should be in a later stage than the job that might fail
|
||||||
when: on_failure # <-- This is the key keyword
|
when: on_failure # <-- This is the key keyword
|
||||||
@@ -72,4 +114,3 @@ notify2:
|
|||||||
- rm -rf /home/gitlab-runner/builds/1fLwHSKm2/0/jaydee/portainer.tmp
|
- rm -rf /home/gitlab-runner/builds/1fLwHSKm2/0/jaydee/portainer.tmp
|
||||||
rules:
|
rules:
|
||||||
- if: '$CI_COMMIT_MESSAGE =~ /build/'
|
- if: '$CI_COMMIT_MESSAGE =~ /build/'
|
||||||
|
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
[semgrep]
|
||||||
|
exclude = ["bandit.B101", "java_deserialization_rule-JacksonUnsafeDeserialization"]
|
||||||
Vendored
+3
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"python.defaultInterpreterPath": "../../venvs/portainer/bin/python"
|
||||||
|
}
|
||||||
Binary file not shown.
+174
-65
@@ -5,41 +5,51 @@ This module provides a wrapper for interacting with the Portainer API
|
|||||||
to manage endpoints, stacks, and containers.
|
to manage endpoints, stacks, and containers.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# !/myapps/venvs/portainer/bin/python3
|
|
||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
import signal
|
import signal
|
||||||
import sys
|
import sys
|
||||||
import json
|
import json
|
||||||
import argparse
|
import argparse
|
||||||
import tty
|
|
||||||
import termios
|
|
||||||
import hvac
|
import hvac
|
||||||
|
import time
|
||||||
|
import base64
|
||||||
|
import shutil
|
||||||
|
import requests
|
||||||
|
import tempfile
|
||||||
|
import subprocess
|
||||||
|
from portainer.api import PortainerApi
|
||||||
|
from git import Repo
|
||||||
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
from tabulate import tabulate
|
from tabulate import tabulate
|
||||||
from port import Portainer
|
|
||||||
from prompt_toolkit import prompt
|
from prompt_toolkit import prompt
|
||||||
from prompt_toolkit.completion import WordCompleter
|
from prompt_toolkit.completion import WordCompleter
|
||||||
from prompt_toolkit.shortcuts import checkboxlist_dialog
|
from prompt_toolkit.shortcuts import checkboxlist_dialog
|
||||||
from prompt_toolkit.shortcuts import radiolist_dialog
|
from prompt_toolkit.shortcuts import radiolist_dialog
|
||||||
|
|
||||||
VAULT_ADDR = os.environ.get("VAULT_ADDR", "http://192.168.77.101:8200")
|
|
||||||
try:
|
def setup_vault():
|
||||||
|
# VAULT_ADDR = os.environ.get("VAULT_ADDR", "http://192.168.77.101:8200")
|
||||||
|
VAULT_ADDR = os.environ.get("VAULT_ADDR", "https://vault.sectorq.eu")
|
||||||
|
try:
|
||||||
VAULT_TOKEN = os.environ.get("VAULT_TOKEN")
|
VAULT_TOKEN = os.environ.get("VAULT_TOKEN")
|
||||||
if VAULT_TOKEN is None:
|
if VAULT_TOKEN is None:
|
||||||
raise KeyError
|
raise KeyError
|
||||||
except KeyError:
|
except KeyError:
|
||||||
VAULT_TOKEN = prompt("Valult root token : ", is_password=True)
|
VAULT_TOKEN = prompt("Valult root token : ", is_password=True)
|
||||||
os.environ["VAULT_TOKEN"] = VAULT_TOKEN
|
os.environ["VAULT_TOKEN"] = VAULT_TOKEN
|
||||||
|
|
||||||
client = hvac.Client(url=VAULT_ADDR, token=VAULT_TOKEN)
|
vclient = hvac.Client(url=VAULT_ADDR, token=VAULT_TOKEN)
|
||||||
# Check if connected
|
# Check if connected
|
||||||
if client.is_authenticated():
|
if vclient.is_authenticated():
|
||||||
print("Connected to Vault")
|
print("Connected to Vault")
|
||||||
else:
|
else:
|
||||||
raise Exception("Failed to authenticate with Vault")
|
raise Exception("Failed to authenticate with Vault")
|
||||||
# Specify the mount point of your KV engine
|
# Specify the mount point of your KV engine
|
||||||
|
return vclient
|
||||||
|
|
||||||
|
VERSION = "0.1.76"
|
||||||
|
|
||||||
VERSION = "0.1.16"
|
|
||||||
|
|
||||||
defaults = {
|
defaults = {
|
||||||
"endpoint_id": "vm01",
|
"endpoint_id": "vm01",
|
||||||
@@ -83,10 +93,6 @@ def load_config(defaults=defaults):
|
|||||||
print("Configuration written to /myapps/portainer.conf")
|
print("Configuration written to /myapps/portainer.conf")
|
||||||
return cur_config
|
return cur_config
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
a = load_config(defaults)
|
a = load_config(defaults)
|
||||||
|
|
||||||
# ENV_VARS = [
|
# ENV_VARS = [
|
||||||
@@ -109,7 +115,11 @@ def update_configs(cur_config):
|
|||||||
print("Configuration written to /myapps/portainer.conf")
|
print("Configuration written to /myapps/portainer.conf")
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description="Portainer helper - use env vars or pass credentials."
|
description=f"""\
|
||||||
|
Portainer helper - use env vars or pass credentials."
|
||||||
|
version: {VERSION}
|
||||||
|
""",
|
||||||
|
formatter_class=argparse.RawTextHelpFormatter,
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--base",
|
"--base",
|
||||||
@@ -132,21 +142,31 @@ parser.add_argument(
|
|||||||
default=None,
|
default=None,
|
||||||
help="Service ID to limit service operations",
|
help="Service ID to limit service operations",
|
||||||
)
|
)
|
||||||
parser.add_argument("--stack", "-s", type=str, default=None, nargs="+", help="Stack ID for operations")
|
parser.add_argument("--stack", "-s", type=str, default=None, help="Stack ID for operations")
|
||||||
parser.add_argument("--action", "-a", type=str, default=None, help="Action to perform")
|
parser.add_argument("--action", "-a", type=str, default=None, help="Action to perform")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--autostart", "-Z", action="store_true", help="Auto-start created stacks"
|
"--autostart", "-Z", action="store_true", help="Auto-start created stacks"
|
||||||
)
|
)
|
||||||
parser.add_argument("--update", "-u", action="store_true", help="Update service if it exists")
|
parser.add_argument("--update", "-u", action="store_true", help="Update service if it exists")
|
||||||
parser.add_argument("--debug", "-D", action="store_true")
|
parser.add_argument("--debug", "-D", action="store_true")
|
||||||
|
parser.add_argument("--launcher", "-L", action="store_true")
|
||||||
parser.add_argument("--gpu", "-g", action="store_true")
|
parser.add_argument("--gpu", "-g", action="store_true")
|
||||||
parser.add_argument("--timeout", type=int, default=10, help="Request timeout seconds")
|
parser.add_argument("--timeout", type=int, default=10, help="Request timeout seconds")
|
||||||
parser.add_argument("--deploy-mode", "-m", type=str, default="git", help="Deploy mode")
|
parser.add_argument("--deploy-mode", "-m", type=str, default="git", help="Deploy mode")
|
||||||
parser.add_argument("--stack-mode", "-w", default=None, help="Stack mode")
|
parser.add_argument("--stack-mode", "-w", default=None, help="Stack mode")
|
||||||
|
parser.add_argument("--print-command", "-P", action="store_true", help="Print quick command from action")
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"-E", "--excluded",
|
||||||
|
nargs="+",
|
||||||
|
default=["portainer", "nginx"],
|
||||||
|
help="Exclude from update"
|
||||||
|
)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
print("Running version:", VERSION)
|
print("Running version:", VERSION)
|
||||||
print("Environment:", args.site)
|
print("Environment:", args.site)
|
||||||
args.client = client
|
|
||||||
|
args.client = setup_vault()
|
||||||
if args.site is not None:
|
if args.site is not None:
|
||||||
cur_config["PORTAINER_SITE"] = args.site
|
cur_config["PORTAINER_SITE"] = args.site
|
||||||
if args.endpoint_id is not None:
|
if args.endpoint_id is not None:
|
||||||
@@ -206,6 +226,41 @@ def wl(msg):
|
|||||||
if args.debug:
|
if args.debug:
|
||||||
print(msg)
|
print(msg)
|
||||||
|
|
||||||
|
def run(cmd, cwd=None):
|
||||||
|
result = subprocess.run(cmd, cwd=cwd, capture_output=True, text=True)
|
||||||
|
if result.returncode != 0:
|
||||||
|
raise RuntimeError(result.stderr)
|
||||||
|
return result.stdout.strip()
|
||||||
|
|
||||||
|
def get_compose_files():
|
||||||
|
#git clone --depth=1 --filter=blob:none --no-checkout https://github.com/user/repo.git
|
||||||
|
with tempfile.TemporaryDirectory() as tmpdir:
|
||||||
|
repo_path = os.path.join(tmpdir, "repo")
|
||||||
|
|
||||||
|
# Clone with minimal data (no checkout, no blobs)
|
||||||
|
run([
|
||||||
|
"git", "clone",
|
||||||
|
"--depth=1",
|
||||||
|
"--filter=blob:none",
|
||||||
|
"--no-checkout",
|
||||||
|
"git@gitlab.sectorq.eu:/home/docker-compose.git",
|
||||||
|
repo_path
|
||||||
|
])
|
||||||
|
|
||||||
|
# List files in HEAD
|
||||||
|
output = run([
|
||||||
|
"git", "ls-tree",
|
||||||
|
"-r",
|
||||||
|
"HEAD",
|
||||||
|
"--name-only"
|
||||||
|
], cwd=repo_path)
|
||||||
|
folders = []
|
||||||
|
for line in output.splitlines():
|
||||||
|
if "/" in line and line.split("/")[0] != "__swarm":
|
||||||
|
folders.append(line.split("/")[0])
|
||||||
|
|
||||||
|
return list(dict.fromkeys(folders))
|
||||||
|
|
||||||
|
|
||||||
def prompt_missing_args(args_in, defaults_in, fields, action=None,stacks=None):
|
def prompt_missing_args(args_in, defaults_in, fields, action=None,stacks=None):
|
||||||
"""
|
"""
|
||||||
@@ -241,19 +296,23 @@ def prompt_missing_args(args_in, defaults_in, fields, action=None,stacks=None):
|
|||||||
elif field == "stack":
|
elif field == "stack":
|
||||||
if args.action == "create_stack":
|
if args.action == "create_stack":
|
||||||
# input(json.dumps(stacks, indent=2))
|
# input(json.dumps(stacks, indent=2))
|
||||||
commands = [
|
commands = get_compose_files()
|
||||||
'authentik', 'bitwarden', 'bookstack', 'dockermon', 'fail2ban', 'gitea', 'gitlab', 'grafana',
|
# commands = [
|
||||||
'hashicorp', 'home-assistant', 'homepage', 'immich', 'influxdb', 'jupyter', 'kestra', 'mailu3',
|
# 'api_server', 'authentik', 'bitwarden', 'bookstack', 'databasus', 'dockermon', 'duplicati', 'fail2ban', 'filebrowser', 'gitea', 'gitlab', 'grafana', 'grocy',
|
||||||
'mealie', 'mediacenter', 'mosquitto', 'motioneye', 'n8n', 'nebula', 'nextcloud', 'nginx',
|
# 'hashicorp', 'home-assistant', 'homebox','homepage', 'immich', 'influxdb', 'jupyter', 'kestra', 'kopia', 'linkding', 'linkwarden', 'mailu3',
|
||||||
'node-red', 'octoprint', 'ollama', 'onlyoffice', 'paperless-ngx', 'pihole', 'portainer-ce', 'rancher', 'registry',
|
# 'mealie', 'mediacenter', 'mosquitto', 'motioneye', 'n8n', 'nebula', 'nextcloud', 'nginx',
|
||||||
'regsync', 'semaphore', 'unifibrowser', 'uptime-kuma', 'watchtower', 'wazuh', 'webhub', 'wordpress',
|
# 'node-red', 'octoprint', 'ollama', 'onlyoffice', 'paperless-ngx', 'pihole', 'portainer-ce','portainerce', 'puppet', 'puppet-agent', 'rancher', 'registry',
|
||||||
'wud', 'zabbix-server']
|
# 'regsync', 'repo_mirror', 'searxng','semaphore', 'unifibrowser', 'uptime-kuma', 'watchtower', 'wazuh', 'webhub', 'wordpress',
|
||||||
|
# 'wud', 'zabbix-server']
|
||||||
try:
|
try:
|
||||||
print(por.all_data['stacks'][defaults_in[f"PORTAINER_ENDPOINT_ID".upper()]]['by_name'].keys())
|
print(por.all_data['stacks'][defaults_in[f"PORTAINER_ENDPOINT_ID".upper()]]['by_name'].keys())
|
||||||
for s in por.all_data['stacks'][defaults_in[f"PORTAINER_ENDPOINT_ID".upper()]]['by_name'].keys():
|
for s in por.all_data['stacks'][defaults_in[f"PORTAINER_ENDPOINT_ID".upper()]]['by_name'].keys():
|
||||||
|
print(s)
|
||||||
#print(s)
|
try:
|
||||||
commands.remove(s)
|
commands.remove(s)
|
||||||
|
except ValueError as e:
|
||||||
|
print(e)
|
||||||
|
|
||||||
except KeyError:
|
except KeyError:
|
||||||
print("No stacks found for endpoint", defaults_in[f"PORTAINER_ENDPOINT_ID".upper()])
|
print("No stacks found for endpoint", defaults_in[f"PORTAINER_ENDPOINT_ID".upper()])
|
||||||
|
|
||||||
@@ -283,6 +342,7 @@ def prompt_missing_args(args_in, defaults_in, fields, action=None,stacks=None):
|
|||||||
commands.sort()
|
commands.sort()
|
||||||
commands_tuples = [(cmd, cmd) for cmd in commands]
|
commands_tuples = [(cmd, cmd) for cmd in commands]
|
||||||
commands_tuples.insert(0, ("__ALL__", "[Select ALL]"))
|
commands_tuples.insert(0, ("__ALL__", "[Select ALL]"))
|
||||||
|
commands_tuples.insert(0, ("mandatory", "[Mandatory]"))
|
||||||
value_in = checkboxlist_dialog(
|
value_in = checkboxlist_dialog(
|
||||||
title="Select Services",
|
title="Select Services",
|
||||||
text="Choose one or more services:",
|
text="Choose one or more services:",
|
||||||
@@ -295,12 +355,14 @@ def prompt_missing_args(args_in, defaults_in, fields, action=None,stacks=None):
|
|||||||
elif "__ALL__" in value_in:
|
elif "__ALL__" in value_in:
|
||||||
# User selected "Select ALL"
|
# User selected "Select ALL"
|
||||||
value_in = commands # all real commands
|
value_in = commands # all real commands
|
||||||
|
elif "mandatory" in value_in:
|
||||||
|
# User selected "Select ALL"
|
||||||
|
value_in = ['pihole', 'nginx', 'authentik', 'hashicorp', 'mosquitto','homepage', 'mailu3', 'home-assistant', 'mediacenter' ] # all real commands
|
||||||
|
|
||||||
value_in.sort()
|
value_in.sort()
|
||||||
|
|
||||||
if "pihole" in value_in:
|
if "pihole" in value_in:
|
||||||
if action == "delete_stack":
|
if args.action in ["delete_stack","stop_stack"]:
|
||||||
value_in.remove("pihole")
|
value_in.remove("pihole")
|
||||||
value_in.append("pihole")
|
value_in.append("pihole")
|
||||||
else:
|
else:
|
||||||
@@ -372,7 +434,7 @@ def prompt_missing_args(args_in, defaults_in, fields, action=None,stacks=None):
|
|||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# Example usage: set PORTAINER_USER and PORTAINER_PASS in env, or pass literals below.
|
# Example usage: set PORTAINER_USER and PORTAINER_PASS in env, or pass literals below.
|
||||||
# token = get_portainer_token(base,"admin","l4c1j4yd33Du5lo") # or get_portainer_token(base, "admin", "secret")
|
|
||||||
def signal_handler(sig, frame):
|
def signal_handler(sig, frame):
|
||||||
logger.warning("Killed manually %s, %s", sig, frame)
|
logger.warning("Killed manually %s, %s", sig, frame)
|
||||||
print("\nTerminated by user")
|
print("\nTerminated by user")
|
||||||
@@ -390,6 +452,8 @@ if __name__ == "__main__":
|
|||||||
("update_service","update_service"),
|
("update_service","update_service"),
|
||||||
("update_containers","update_containers"),
|
("update_containers","update_containers"),
|
||||||
("list_stacks","list_stacks"),
|
("list_stacks","list_stacks"),
|
||||||
|
("list_all_stacks", "list_all_stacks"),
|
||||||
|
("delete_ophaned_stacks", "delete_ophaned_stacks"),
|
||||||
("update_stack","update_stack"),
|
("update_stack","update_stack"),
|
||||||
("secrets","secrets"),
|
("secrets","secrets"),
|
||||||
("print_all_data","print_all_data"),
|
("print_all_data","print_all_data"),
|
||||||
@@ -403,18 +467,13 @@ if __name__ == "__main__":
|
|||||||
]
|
]
|
||||||
|
|
||||||
selected_action = radiolist_dialog(
|
selected_action = radiolist_dialog(
|
||||||
title="Select one service",
|
title=f"Select one service - version: {VERSION}",
|
||||||
text="Choose a service:",
|
text="Choose a service:",
|
||||||
values=actions
|
values=actions
|
||||||
).run()
|
).run()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
print("Selected:", selected_action)
|
print("Selected:", selected_action)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# print("Possible actions: \n")
|
# print("Possible actions: \n")
|
||||||
# i = 1
|
# i = 1
|
||||||
# for a in actions:
|
# for a in actions:
|
||||||
@@ -426,7 +485,7 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
os.system("cls" if os.name == "nt" else "clear")
|
os.system("cls" if os.name == "nt" else "clear")
|
||||||
# Example: list endpoints
|
# Example: list endpoints
|
||||||
por = Portainer(cur_config["PORTAINER_SITE"], args)
|
por = PortainerApi(cur_config["PORTAINER_SITE"], args)
|
||||||
por.set_defaults(cur_config)
|
por.set_defaults(cur_config)
|
||||||
if args.debug:
|
if args.debug:
|
||||||
por._debug = True
|
por._debug = True
|
||||||
@@ -450,7 +509,6 @@ if __name__ == "__main__":
|
|||||||
for key, value in secrets.items():
|
for key, value in secrets.items():
|
||||||
res = por.create_secret(key, value, args.endpoint_id, args.timeout)
|
res = por.create_secret(key, value, args.endpoint_id, args.timeout)
|
||||||
print(res)
|
print(res)
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
if args.action == "delete_stack":
|
if args.action == "delete_stack":
|
||||||
args = prompt_missing_args(
|
args = prompt_missing_args(
|
||||||
@@ -471,7 +529,6 @@ if __name__ == "__main__":
|
|||||||
args.endpoint_id,
|
args.endpoint_id,
|
||||||
args.stack,
|
args.stack,
|
||||||
)
|
)
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
if args.action == "create_stack":
|
if args.action == "create_stack":
|
||||||
por.action = "create_stack"
|
por.action = "create_stack"
|
||||||
@@ -496,7 +553,7 @@ if __name__ == "__main__":
|
|||||||
args.autostart,
|
args.autostart,
|
||||||
args.stack_mode,
|
args.stack_mode,
|
||||||
)
|
)
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
if args.action == "stop_stack":
|
if args.action == "stop_stack":
|
||||||
args = prompt_missing_args(
|
args = prompt_missing_args(
|
||||||
@@ -510,7 +567,6 @@ if __name__ == "__main__":
|
|||||||
)
|
)
|
||||||
|
|
||||||
por.stop_stack(args.stack, args.endpoint_id)
|
por.stop_stack(args.stack, args.endpoint_id)
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
if args.action == "start_stack":
|
if args.action == "start_stack":
|
||||||
args = prompt_missing_args(
|
args = prompt_missing_args(
|
||||||
@@ -523,7 +579,7 @@ if __name__ == "__main__":
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
por.start_stack(args.stack, args.endpoint_id)
|
por.start_stack(args.stack, args.endpoint_id)
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
if args.action == "restart_service":
|
if args.action == "restart_service":
|
||||||
args = prompt_missing_args(
|
args = prompt_missing_args(
|
||||||
@@ -535,7 +591,7 @@ if __name__ == "__main__":
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
por.restart_service(args.endpoint_id, "lala")
|
por.restart_service(args.endpoint_id, "lala")
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
if args.action == "update_service":
|
if args.action == "update_service":
|
||||||
args = prompt_missing_args(
|
args = prompt_missing_args(
|
||||||
@@ -547,7 +603,9 @@ if __name__ == "__main__":
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
por.update_service()
|
por.update_service()
|
||||||
sys.exit()
|
if args.launcher:
|
||||||
|
input("\nPress ENTER to continue...")
|
||||||
|
|
||||||
|
|
||||||
if args.action == "update_containers":
|
if args.action == "update_containers":
|
||||||
|
|
||||||
@@ -562,7 +620,6 @@ if __name__ == "__main__":
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
por.update_containers()
|
por.update_containers()
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
if args.action == "list_stacks":
|
if args.action == "list_stacks":
|
||||||
args = prompt_missing_args(
|
args = prompt_missing_args(
|
||||||
@@ -574,13 +631,37 @@ if __name__ == "__main__":
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
por.print_stacks(args)
|
por.print_stacks(args)
|
||||||
|
if args.launcher:
|
||||||
|
input("Press ENTER to continue...")
|
||||||
# print(json.dumps(por.all_data, indent=2))
|
# print(json.dumps(por.all_data, indent=2))
|
||||||
sys.exit()
|
|
||||||
|
if args.action == "list_all_stacks":
|
||||||
|
por.get_stacks_failed()
|
||||||
|
if args.launcher:
|
||||||
|
input("Press ENTER to continue...")
|
||||||
|
# print(json.dumps(por.all_data, indent=2))
|
||||||
|
|
||||||
|
|
||||||
|
if args.action == "delete_ophaned_stacks":
|
||||||
|
por.delete_failed_stack()
|
||||||
|
if args.launcher:
|
||||||
|
input("Press ENTER to continue...")
|
||||||
|
# print(json.dumps(por.all_data, indent=2))
|
||||||
|
|
||||||
|
|
||||||
if args.action == "list_containers":
|
if args.action == "list_containers":
|
||||||
print("Getting containers")
|
print("Getting containers")
|
||||||
print(por.get_containers())
|
args = prompt_missing_args(
|
||||||
sys.exit()
|
args,
|
||||||
|
cur_config,
|
||||||
|
[
|
||||||
|
("site", "Site"),
|
||||||
|
("endpoint_id", "Endpoint ID"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
print("\n".join(por.get_containers()))
|
||||||
|
if args.launcher:
|
||||||
|
input("\nPress ENTER to continue...")
|
||||||
|
|
||||||
if args.action == "update_stack":
|
if args.action == "update_stack":
|
||||||
args = prompt_missing_args(
|
args = prompt_missing_args(
|
||||||
@@ -593,14 +674,18 @@ if __name__ == "__main__":
|
|||||||
)
|
)
|
||||||
|
|
||||||
por.update_stack(args)
|
por.update_stack(args)
|
||||||
sys.exit()
|
if args.launcher:
|
||||||
|
input("\nPress ENTER to continue...")
|
||||||
|
|
||||||
|
|
||||||
if args.action == "print_all_data":
|
if args.action == "print_all_data":
|
||||||
print(json.dumps(por.all_data, indent=2))
|
print(json.dumps(por.all_data, indent=2))
|
||||||
sys.exit()
|
if args.launcher:
|
||||||
|
input("\nPress ENTER to continue...")
|
||||||
|
|
||||||
|
|
||||||
if args.action == "update_status":
|
if args.action == "update_status":
|
||||||
por.update_status(args.endpoint_id, args.stack)
|
por.update_status(args.endpoint_id, args.stack)
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
if args.action == "list_endpoints":
|
if args.action == "list_endpoints":
|
||||||
eps = por.get_endpoints(args)
|
eps = por.get_endpoints(args)
|
||||||
@@ -609,20 +694,30 @@ if __name__ == "__main__":
|
|||||||
export_data.append([i, eps["by_id"][i]])
|
export_data.append([i, eps["by_id"][i]])
|
||||||
headers = ["EndpointId", "Name"]
|
headers = ["EndpointId", "Name"]
|
||||||
print(tabulate(export_data, headers=headers, tablefmt="github"))
|
print(tabulate(export_data, headers=headers, tablefmt="github"))
|
||||||
|
if args.launcher:
|
||||||
|
input("\nPress ENTER to continue...")
|
||||||
|
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
if args.action == "stop_containers":
|
if args.action == "stop_containers":
|
||||||
|
# TODO: does not work
|
||||||
|
args = prompt_missing_args(
|
||||||
|
args,
|
||||||
|
cur_config,
|
||||||
|
[
|
||||||
|
("site", "Site"),
|
||||||
|
("endpoint_id", "Endpoint ID"),
|
||||||
|
],
|
||||||
|
)
|
||||||
if por.all_data["endpoints_status"][args.endpoint_id] != 1:
|
if por.all_data["endpoints_status"][args.endpoint_id] != 1:
|
||||||
print(f"Endpoint {por.get_endpoint_name(args.endpoint_id)} is offline")
|
print(f"Endpoint {por.get_endpoint_name(args.endpoint_id)} is offline")
|
||||||
sys.exit()
|
|
||||||
print(f"Stopping containers on {por.get_endpoint_name(args.endpoint_id)}")
|
print(f"Stopping containers on {por.get_endpoint_name(args.endpoint_id)}")
|
||||||
cont = []
|
cont = []
|
||||||
for c in por.all_data["containers"][args.endpoint_id]:
|
for c in por.all_data["containers"][args.endpoint_id]:
|
||||||
if args.stack in (c, "all"):
|
if args.stack in (c, "all"):
|
||||||
cont += por.all_data["containers"][args.endpoint_id][c]
|
cont += por.all_data["containers"][args.endpoint_id][c]
|
||||||
por.stop_containers(args.endpoint_id, cont)
|
por.stop_containers(args.endpoint_id, cont)
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
if args.action == "start_containers":
|
if args.action == "start_containers":
|
||||||
print("Starting containers")
|
print("Starting containers")
|
||||||
@@ -632,7 +727,7 @@ if __name__ == "__main__":
|
|||||||
if args.stack in (c, "all"):
|
if args.stack in (c, "all"):
|
||||||
cont += por.all_data["containers"][args.endpoint_id][c]
|
cont += por.all_data["containers"][args.endpoint_id][c]
|
||||||
por.start_containers(args.endpoint_id, cont)
|
por.start_containers(args.endpoint_id, cont)
|
||||||
sys.exit()
|
|
||||||
if args.action == "start_containers":
|
if args.action == "start_containers":
|
||||||
print("Starting containers")
|
print("Starting containers")
|
||||||
cont = []
|
cont = []
|
||||||
@@ -641,14 +736,28 @@ if __name__ == "__main__":
|
|||||||
if args.stack in (c, "all"):
|
if args.stack in (c, "all"):
|
||||||
cont += por.all_data["containers"][args.endpoint_id][c]
|
cont += por.all_data["containers"][args.endpoint_id][c]
|
||||||
por.start_containers(args.endpoint_id, cont)
|
por.start_containers(args.endpoint_id, cont)
|
||||||
sys.exit()
|
|
||||||
if args.action == "refresh_environment":
|
if args.action == "refresh_environment":
|
||||||
cont = por.refresh()
|
cont = por.refresh()
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
if args.action == "refresh_status":
|
if args.action == "refresh_status":
|
||||||
if args.stack == "all":
|
por.refresh_status(args)
|
||||||
print("Stopping all stacks...")
|
|
||||||
stcks = por.get_stacks(endpoint_id=args.endpoint_id)
|
if args.print_command:
|
||||||
else:
|
one_time_command = "portainer"
|
||||||
por.refresh_status(args.stack_id)
|
if args.action:
|
||||||
|
one_time_command += f" --action={args.action}"
|
||||||
|
if por.endpoint_name:
|
||||||
|
one_time_command += f" --endpoint-id={por.endpoint_name}"
|
||||||
|
if por.site:
|
||||||
|
one_time_command += f" --site={por.site}"
|
||||||
|
if args.stack:
|
||||||
|
if type(args.stack) == list:
|
||||||
|
args.stack = ",".join(args.stack)
|
||||||
|
one_time_command += f" --stack={args.stack}"
|
||||||
|
width = shutil.get_terminal_size().columns
|
||||||
|
input(width)
|
||||||
|
print("#"*width)
|
||||||
|
print(f"COMMAND : {one_time_command}")
|
||||||
|
print("#"*width)
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+158
-52
@@ -13,6 +13,8 @@ import tabulate
|
|||||||
from git import Repo
|
from git import Repo
|
||||||
import requests
|
import requests
|
||||||
import hvac
|
import hvac
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
from prompt_toolkit import prompt
|
from prompt_toolkit import prompt
|
||||||
from prompt_toolkit.completion import WordCompleter
|
from prompt_toolkit.completion import WordCompleter
|
||||||
from prompt_toolkit.shortcuts import checkboxlist_dialog
|
from prompt_toolkit.shortcuts import checkboxlist_dialog
|
||||||
@@ -21,7 +23,7 @@ from prompt_toolkit.shortcuts import radiolist_dialog
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Portainer:
|
class PortainerApi:
|
||||||
"""
|
"""
|
||||||
Simple wrapper around the module-level Portainer helper functions.
|
Simple wrapper around the module-level Portainer helper functions.
|
||||||
Instantiate with base_url and optional token/timeout and call methods
|
Instantiate with base_url and optional token/timeout and call methods
|
||||||
@@ -37,6 +39,7 @@ class Portainer:
|
|||||||
self.timeout = timeout
|
self.timeout = timeout
|
||||||
self.git_url = "git@gitlab.sectorq.eu:home/docker-compose.git"
|
self.git_url = "git@gitlab.sectorq.eu:home/docker-compose.git"
|
||||||
self.stack_name = None
|
self.stack_name = None
|
||||||
|
self.stack_names = []
|
||||||
self.stacks_all = {}
|
self.stacks_all = {}
|
||||||
self.stack_id = None
|
self.stack_id = None
|
||||||
self.stack_ids = []
|
self.stack_ids = []
|
||||||
@@ -122,6 +125,7 @@ class Portainer:
|
|||||||
self.cur_config = config
|
self.cur_config = config
|
||||||
|
|
||||||
def get_site(self, site):
|
def get_site(self, site):
|
||||||
|
self.site = site
|
||||||
if site == "portainer":
|
if site == "portainer":
|
||||||
self.base_url = os.getenv(
|
self.base_url = os.getenv(
|
||||||
"PORTAINER_URL", "https://portainer.sectorq.eu/api"
|
"PORTAINER_URL", "https://portainer.sectorq.eu/api"
|
||||||
@@ -141,6 +145,13 @@ class Portainer:
|
|||||||
self.get_endpoints()
|
self.get_endpoints()
|
||||||
self.get_stacks()
|
self.get_stacks()
|
||||||
|
|
||||||
|
def refresh_status(self, args):
|
||||||
|
for s in self.all_data['stacks']['m-s']['by_id']:
|
||||||
|
path = f'/stacks/{s}/images_status?refresh=true'
|
||||||
|
|
||||||
|
print(path)
|
||||||
|
res = self._api_get(path, timeout=args.timeout)
|
||||||
|
|
||||||
def _is_number(self, s):
|
def _is_number(self, s):
|
||||||
"""Check if the input string is a number."""
|
"""Check if the input string is a number."""
|
||||||
try:
|
try:
|
||||||
@@ -159,7 +170,7 @@ class Portainer:
|
|||||||
response = requests.post(
|
response = requests.post(
|
||||||
"https://gotify.sectorq.eu/message",
|
"https://gotify.sectorq.eu/message",
|
||||||
data=payload,
|
data=payload,
|
||||||
headers={"X-Gotify-Key": "ASn_fIAd5OVjm8c"}
|
headers={"X-Gotify-Key": "A1krRuo8GIW-fpY"}
|
||||||
)
|
)
|
||||||
logger.debug(response.text)
|
logger.debug(response.text)
|
||||||
# print("Status:", response.status_code)
|
# print("Status:", response.status_code)
|
||||||
@@ -230,6 +241,34 @@ class Portainer:
|
|||||||
self.get_containers(self)
|
self.get_containers(self)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def get_stacks_failed(self, timeout=20):
|
||||||
|
'''Get a list of stacks for a specific endpoint or all endpoints.'''
|
||||||
|
path = "/stacks"
|
||||||
|
stcks = []
|
||||||
|
stacks = self._api_get(path, timeout=timeout)
|
||||||
|
self.stacks_all = {}
|
||||||
|
fail_endponts = [20, 39, 41, 32, 43]
|
||||||
|
# print(json.dumps(stacks,indent=2))
|
||||||
|
for s in stacks:
|
||||||
|
stcks.append((s["EndpointId"], s["Id"]))
|
||||||
|
print(f"{s['EndpointId']} : {s['Id']}")
|
||||||
|
return stcks
|
||||||
|
|
||||||
|
def delete_failed_stack(self, timeout=20):
|
||||||
|
'''Get a list of stacks for a specific endpoint or all endpoints.'''
|
||||||
|
path = "/stacks"
|
||||||
|
stcks = []
|
||||||
|
stacks = self._api_get(path, timeout=timeout)
|
||||||
|
|
||||||
|
# print(json.dumps(stacks,indent=2))
|
||||||
|
eid = input("Enter endpoint : ")
|
||||||
|
for s in stacks:
|
||||||
|
# print(s)
|
||||||
|
# print(f"{s['EndpointId']} : {s['Id']}")
|
||||||
|
if int(eid) == s["EndpointId"]:
|
||||||
|
print(f"deleting stack {s['Id']}")
|
||||||
|
self._delete_single_stack(s["Id"], "50")
|
||||||
|
|
||||||
def get_stacks(self, endpoint_id="all", timeout=20):
|
def get_stacks(self, endpoint_id="all", timeout=20):
|
||||||
'''Get a list of stacks for a specific endpoint or all endpoints.'''
|
'''Get a list of stacks for a specific endpoint or all endpoints.'''
|
||||||
if endpoint_id != "all":
|
if endpoint_id != "all":
|
||||||
@@ -238,7 +277,7 @@ class Portainer:
|
|||||||
stcks = []
|
stcks = []
|
||||||
stacks = self._api_get(path, timeout=timeout)
|
stacks = self._api_get(path, timeout=timeout)
|
||||||
self.stacks_all = {}
|
self.stacks_all = {}
|
||||||
fail_endponts = [20, 39, 41]
|
fail_endponts = [20, 39, 41, 32, 43]
|
||||||
# print(json.dumps(stacks,indent=2))
|
# print(json.dumps(stacks,indent=2))
|
||||||
webhooks = {}
|
webhooks = {}
|
||||||
for s in stacks:
|
for s in stacks:
|
||||||
@@ -315,6 +354,7 @@ class Portainer:
|
|||||||
|
|
||||||
def get_endpoint_id(self):
|
def get_endpoint_id(self):
|
||||||
'''Get endpoint ID from either ID or name input.'''
|
'''Get endpoint ID from either ID or name input.'''
|
||||||
|
# input(self.args.endpoint_id)
|
||||||
if self._is_number(self.args.endpoint_id):
|
if self._is_number(self.args.endpoint_id):
|
||||||
self.endpoint_id = self.args.endpoint_id
|
self.endpoint_id = self.args.endpoint_id
|
||||||
self.endpoint_name = self.endpoints["by_id"][self.args.endpoint_id]
|
self.endpoint_name = self.endpoints["by_id"][self.args.endpoint_id]
|
||||||
@@ -342,7 +382,6 @@ class Portainer:
|
|||||||
# print(stack)
|
# print(stack)
|
||||||
cont = []
|
cont = []
|
||||||
data = {}
|
data = {}
|
||||||
|
|
||||||
eps = [ep for ep in self.all_data['endpoints']['by_id'].keys()]
|
eps = [ep for ep in self.all_data['endpoints']['by_id'].keys()]
|
||||||
#input(eps)
|
#input(eps)
|
||||||
for endpoint in eps:
|
for endpoint in eps:
|
||||||
@@ -361,11 +400,15 @@ class Portainer:
|
|||||||
print(f"failed to get containers from {path}: {e}")
|
print(f"failed to get containers from {path}: {e}")
|
||||||
continue
|
continue
|
||||||
contr = []
|
contr = []
|
||||||
|
# print(f"Containers: {containers}")
|
||||||
try:
|
try:
|
||||||
for c in containers:
|
for c in containers:
|
||||||
#input(c)
|
# print(c)
|
||||||
|
try:
|
||||||
cont.append([c["Names"][0].replace("/", ""),c["Id"], c['Image']])
|
cont.append([c["Names"][0].replace("/", ""),c["Id"], c['Image']])
|
||||||
contr.append([c["Names"][0].replace("/", ""), c["Id"], c['Image']])
|
contr.append([c["Names"][0].replace("/", ""), c["Id"], c['Image']])
|
||||||
|
except:
|
||||||
|
print("Unable to parse container info")
|
||||||
if self.all_data["endpoints"]["by_id"][endpoint] in data:
|
if self.all_data["endpoints"]["by_id"][endpoint] in data:
|
||||||
data[self.all_data["endpoints"]["by_id"][endpoint]] = contr
|
data[self.all_data["endpoints"]["by_id"][endpoint]] = contr
|
||||||
data[endpoint] = contr
|
data[endpoint] = contr
|
||||||
@@ -394,14 +437,13 @@ class Portainer:
|
|||||||
else:
|
else:
|
||||||
|
|
||||||
eps = [self.get_endpoint_id()]
|
eps = [self.get_endpoint_id()]
|
||||||
#input(eps)
|
|
||||||
for endpoint in eps:
|
|
||||||
|
|
||||||
# print(s)
|
for endpoint in eps:
|
||||||
#print(self.args.stack)
|
#print(self.args.stack)
|
||||||
if self.args.stack in ["all", None]:
|
if self.args.stack in ["all", None]:
|
||||||
# input([id for id in self.all_data["stacks"][endpoint]['by_id'].keys()])
|
# input([id for id in self.all_data["stacks"][endpoint]['by_id'].keys()])
|
||||||
for s in [id for id in self.all_data["stacks"][endpoint]['by_id'].keys()]:
|
for e in [id for id in self.all_data["stacks"][endpoint]['by_name'].keys()]:
|
||||||
|
#input(e)
|
||||||
# if s not in self.all_data["stacks"]:
|
# if s not in self.all_data["stacks"]:
|
||||||
# continue
|
# continue
|
||||||
#input(self.all_data)
|
#input(self.all_data)
|
||||||
@@ -409,7 +451,7 @@ class Portainer:
|
|||||||
# print(f"Endpoint {self.all_data["endpoints"]["by_id"][s]} is offline")
|
# print(f"Endpoint {self.all_data["endpoints"]["by_id"][s]} is offline")
|
||||||
continue
|
continue
|
||||||
# input(self.all_data["stacks"][endpoint]["by_name"])
|
# input(self.all_data["stacks"][endpoint]["by_name"])
|
||||||
for e in self.all_data["stacks"][endpoint]["by_name"]:
|
|
||||||
#input(e)
|
#input(e)
|
||||||
path = (
|
path = (
|
||||||
f"/endpoints/{endpoint}/docker/containers/json"
|
f"/endpoints/{endpoint}/docker/containers/json"
|
||||||
@@ -489,7 +531,7 @@ class Portainer:
|
|||||||
stacks_tuples.append((s['Webhook'],s['Name']))
|
stacks_tuples.append((s['Webhook'],s['Name']))
|
||||||
# print(s['Name'], " : ", s['Webhook'])
|
# print(s['Name'], " : ", s['Webhook'])
|
||||||
stacks_dict = dict(stacks_tuples)
|
stacks_dict = dict(stacks_tuples)
|
||||||
print(stacks_dict)
|
# print(stacks_dict)
|
||||||
#input(stacks_tuples)
|
#input(stacks_tuples)
|
||||||
# stacks_tuples = [(s['AutoUpdate']['Webhook'], s['Name']) for s in stacks if "Webhook" in s['AutoUpdate'] ]
|
# stacks_tuples = [(s['AutoUpdate']['Webhook'], s['Name']) for s in stacks if "Webhook" in s['AutoUpdate'] ]
|
||||||
|
|
||||||
@@ -514,7 +556,7 @@ class Portainer:
|
|||||||
values=stacks_tuples
|
values=stacks_tuples
|
||||||
).run()
|
).run()
|
||||||
stcs = []
|
stcs = []
|
||||||
input(stack_ids)
|
#input(stack_ids)
|
||||||
|
|
||||||
if args.stack == "all":
|
if args.stack == "all":
|
||||||
for s in stack_dict:
|
for s in stack_dict:
|
||||||
@@ -524,18 +566,18 @@ class Portainer:
|
|||||||
if s in stack_ids:
|
if s in stack_ids:
|
||||||
stcs.append([s, stack_dict[s]])
|
stcs.append([s, stack_dict[s]])
|
||||||
|
|
||||||
print(stcs)
|
# print(stcs)
|
||||||
with ThreadPoolExecutor(max_workers=10) as exe:
|
with ThreadPoolExecutor(max_workers=10) as exe:
|
||||||
list(exe.map(update, stcs))
|
list(exe.map(update, stcs))
|
||||||
|
|
||||||
input('UPDATED')
|
#input('UPDATED')
|
||||||
if not args.autostart:
|
if not args.autostart:
|
||||||
time.sleep(120)
|
time.sleep(120)
|
||||||
cont = []
|
cont = []
|
||||||
for c in self.all_data["containers"][endpoint]:
|
for c in self.all_data["containers"][args.endpoint_id]:
|
||||||
if stack == c or stack == "all":
|
if args.stack == c or args.stack == "all":
|
||||||
cont += self.all_data["containers"][endpoint][c]
|
cont += self.all_data["containers"][args.endpoint_id][c]
|
||||||
self.stop_containers(endpoint, cont)
|
self.stop_containers(args.endpoint_id, cont)
|
||||||
|
|
||||||
def get_endpoints(self, timeout=10):
|
def get_endpoints(self, timeout=10):
|
||||||
'''Get a list of all endpoints.'''
|
'''Get a list of all endpoints.'''
|
||||||
@@ -618,7 +660,18 @@ class Portainer:
|
|||||||
autostart=False,
|
autostart=False,
|
||||||
stack_mode="swarm",
|
stack_mode="swarm",
|
||||||
):
|
):
|
||||||
|
diff_stacks = ['mediacenter']
|
||||||
for stack in stacks:
|
for stack in stacks:
|
||||||
|
server = ""
|
||||||
|
print("Stack:", stack)
|
||||||
|
print("Endpoint:", endpoint)
|
||||||
|
if stack in diff_stacks:
|
||||||
|
if endpoint == "nas":
|
||||||
|
server = "_nas"
|
||||||
|
elif endpoint == "m-s":
|
||||||
|
server = "_m-server"
|
||||||
|
|
||||||
|
|
||||||
if stack_mode == "swarm":
|
if stack_mode == "swarm":
|
||||||
swarm_id = self.get_swarm_id(endpoint)
|
swarm_id = self.get_swarm_id(endpoint)
|
||||||
p = "swarm"
|
p = "swarm"
|
||||||
@@ -710,8 +763,8 @@ class Portainer:
|
|||||||
},
|
},
|
||||||
"repositoryURL": "https://gitlab.sectorq.eu/home/docker-compose.git",
|
"repositoryURL": "https://gitlab.sectorq.eu/home/docker-compose.git",
|
||||||
"ReferenceName": "refs/heads/main",
|
"ReferenceName": "refs/heads/main",
|
||||||
"composeFile": f"{stack}/docker-compose.yml",
|
"composeFile": f"{stack}/docker-compose{server}.yml",
|
||||||
"ConfigFilePath": f"{stack}/docker-compose.yml",
|
"ConfigFilePath": f"{stack}/docker-compose{server}.yml",
|
||||||
"repositoryAuthentication": True,
|
"repositoryAuthentication": True,
|
||||||
"repositoryUsername": "jaydee",
|
"repositoryUsername": "jaydee",
|
||||||
"repositoryPassword": "glpat-uj-n-eEfTY398PE4vKSS",
|
"repositoryPassword": "glpat-uj-n-eEfTY398PE4vKSS",
|
||||||
@@ -834,7 +887,7 @@ class Portainer:
|
|||||||
}
|
}
|
||||||
self._api_post_file(path, self.endpoint_id, stack, envs, file)
|
self._api_post_file(path, self.endpoint_id, stack, envs, file)
|
||||||
|
|
||||||
def print_stacks(self, endpoint="all"):
|
def print_stacks(self, args):
|
||||||
"""Print a table of stacks, optionally filtered by endpoint."""
|
"""Print a table of stacks, optionally filtered by endpoint."""
|
||||||
stacks = self.get_stacks()
|
stacks = self.get_stacks()
|
||||||
count = 0
|
count = 0
|
||||||
@@ -842,11 +895,11 @@ class Portainer:
|
|||||||
stack_names = []
|
stack_names = []
|
||||||
for stack in stacks:
|
for stack in stacks:
|
||||||
# print(stack)
|
# print(stack)
|
||||||
if endpoint is not None:
|
if args.endpoint_id is not None:
|
||||||
if not stack["EndpointId"] in self.endpoints["by_id"]:
|
if not stack["EndpointId"] in self.endpoints["by_id"]:
|
||||||
continue
|
continue
|
||||||
if endpoint != "all":
|
if args.endpoint_id != "all":
|
||||||
if self.endpoints["by_name"][endpoint] != stack["EndpointId"]:
|
if self.endpoints["by_name"][args.endpoint_id] != stack["EndpointId"]:
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
stack_names.append(stack["Name"])
|
stack_names.append(stack["Name"])
|
||||||
@@ -873,7 +926,7 @@ class Portainer:
|
|||||||
def update_containers(self):
|
def update_containers(self):
|
||||||
all_containers = self.all_data["containers"][self.args.endpoint_id]
|
all_containers = self.all_data["containers"][self.args.endpoint_id]
|
||||||
#input(all_containers)
|
#input(all_containers)
|
||||||
service_tuples = [(s[1], s[0]) for s in all_containers if "." not in s[0]]
|
service_tuples = [(s[1], s[0]) for s in all_containers if "." not in s[0] and not s[0].startswith("runner-")]
|
||||||
service_tuples = sorted(service_tuples, key=lambda x: x[1])
|
service_tuples = sorted(service_tuples, key=lambda x: x[1])
|
||||||
service_dict = dict(service_tuples)
|
service_dict = dict(service_tuples)
|
||||||
# input(service_tuples)
|
# input(service_tuples)
|
||||||
@@ -913,9 +966,12 @@ class Portainer:
|
|||||||
#print(longest)
|
#print(longest)
|
||||||
ok = "\033[92m✔\033[0m"
|
ok = "\033[92m✔\033[0m"
|
||||||
err = "\033[91m✖\033[0m"
|
err = "\033[91m✖\033[0m"
|
||||||
|
updates = []
|
||||||
for service_id in service_ids:
|
for service_id in service_ids:
|
||||||
# print(self.all_data["containers"][self.args.endpoint_id])
|
# print(self.all_data["containers"][self.args.endpoint_id])
|
||||||
|
if service_dict[service_id] in self.args.excluded:
|
||||||
|
print(f"App {service_dict[service_id]} excluded")
|
||||||
|
continue
|
||||||
print("\033[?25l", end="")
|
print("\033[?25l", end="")
|
||||||
print(f"{service_dict[service_id]:<{longest}} ", end="", flush=True)
|
print(f"{service_dict[service_id]:<{longest}} ", end="", flush=True)
|
||||||
path = f"/docker/{self.get_endpoint_id()}/containers/{service_id}/image_status?refresh=true"
|
path = f"/docker/{self.get_endpoint_id()}/containers/{service_id}/image_status?refresh=true"
|
||||||
@@ -930,10 +986,10 @@ class Portainer:
|
|||||||
print("?")
|
print("?")
|
||||||
elif resp['Status'] == "outdated":
|
elif resp['Status'] == "outdated":
|
||||||
if pull:
|
if pull:
|
||||||
print("Recreate")
|
#print("Recreate")
|
||||||
self.recreate_container(service_id, pull)
|
self.recreate_container(service_id, pull)
|
||||||
#print(f"Service {service_dict[service_id]:<{longest}} : updated")
|
#print(f"Service {service_dict[service_id]:<{longest}} : updated")
|
||||||
self.gotify_message(f"Service {service_dict[service_id]} updated")
|
updates.append(service_dict[service_id])
|
||||||
print(ok, end=" ")
|
print(ok, end=" ")
|
||||||
for name, hash_, image in self.all_data["containers"][self.args.endpoint_id]:
|
for name, hash_, image in self.all_data["containers"][self.args.endpoint_id]:
|
||||||
if name.startswith(service_dict[service_id]):
|
if name.startswith(service_dict[service_id]):
|
||||||
@@ -941,7 +997,7 @@ class Portainer:
|
|||||||
else:
|
else:
|
||||||
print(f"\r\033[4m{service_dict[service_id]:<{longest}}\033[0m ", end="", flush=True)
|
print(f"\r\033[4m{service_dict[service_id]:<{longest}}\033[0m ", end="", flush=True)
|
||||||
#print(f"\033[4m{service_dict[service_id]:<{longest}} {err}\033[0m")
|
#print(f"\033[4m{service_dict[service_id]:<{longest}} {err}\033[0m")
|
||||||
self.gotify_message(f"Service update available for {service_dict[service_id]}")
|
updates.append(service_dict[service_id])
|
||||||
print(err, end=" ")
|
print(err, end=" ")
|
||||||
for name, hash_, image in self.all_data["containers"][self.args.endpoint_id]:
|
for name, hash_, image in self.all_data["containers"][self.args.endpoint_id]:
|
||||||
if name.startswith(service_dict[service_id]):
|
if name.startswith(service_dict[service_id]):
|
||||||
@@ -951,12 +1007,24 @@ class Portainer:
|
|||||||
for name, hash_, image in self.all_data["containers"][self.args.endpoint_id]:
|
for name, hash_, image in self.all_data["containers"][self.args.endpoint_id]:
|
||||||
if name.startswith(service_dict[service_id]):
|
if name.startswith(service_dict[service_id]):
|
||||||
print(image)
|
print(image)
|
||||||
|
if len(updates) > 0:
|
||||||
|
if pull:
|
||||||
|
self.gotify_message(f"Services updated: {', '.join(updates)}")
|
||||||
|
else:
|
||||||
|
self.gotify_message(f"Services updates available: {', '.join(updates)}")
|
||||||
print("\033[?25h", end="")
|
print("\033[?25h", end="")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def update_service(self):
|
def update_service(self):
|
||||||
all_services = self.get_services(self.get_endpoint_id())
|
all_services = self.get_services(self.get_endpoint_id())
|
||||||
#input(all_services)
|
if self.args.debug:
|
||||||
|
print(all_services)
|
||||||
|
if all_services == 503:
|
||||||
|
print("No services found on this endpoint.")
|
||||||
|
return False
|
||||||
|
if len(all_services) == 0:
|
||||||
|
print("No services found on this endpoint.")
|
||||||
|
return False
|
||||||
service_tuples = [(s['ID'], s['Spec']['Name']) for s in all_services]
|
service_tuples = [(s['ID'], s['Spec']['Name']) for s in all_services]
|
||||||
service_tuples = sorted(service_tuples, key=lambda x: x[1])
|
service_tuples = sorted(service_tuples, key=lambda x: x[1])
|
||||||
service_dict = dict(service_tuples)
|
service_dict = dict(service_tuples)
|
||||||
@@ -1000,6 +1068,7 @@ class Portainer:
|
|||||||
#print(longest)
|
#print(longest)
|
||||||
ok = "\033[92m✔\033[0m"
|
ok = "\033[92m✔\033[0m"
|
||||||
err = "\033[91m✖\033[0m"
|
err = "\033[91m✖\033[0m"
|
||||||
|
service_to_update = []
|
||||||
for service_id in service_ids:
|
for service_id in service_ids:
|
||||||
print("\033[?25l", end="")
|
print("\033[?25l", end="")
|
||||||
print(f"{service_dict[service_id]:<{longest}} ", end="", flush=True)
|
print(f"{service_dict[service_id]:<{longest}} ", end="", flush=True)
|
||||||
@@ -1012,18 +1081,23 @@ class Portainer:
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
if resp['Status'] == "outdated":
|
if resp['Status'] == "outdated":
|
||||||
|
service_to_update.append(service_dict[service_id])
|
||||||
if pull:
|
if pull:
|
||||||
|
if service_dict[service_id] in self.args.excluded:
|
||||||
|
continue
|
||||||
self.restart_srv(service_id, pull)
|
self.restart_srv(service_id, pull)
|
||||||
#print(f"Service {service_dict[service_id]:<{longest}} : updated")
|
#print(f"Service {service_dict[service_id]:<{longest}} : updated")
|
||||||
self.gotify_message(f"Service {service_dict[service_id]} updated")
|
print(f"{ok} updated")
|
||||||
print(ok)
|
|
||||||
else:
|
else:
|
||||||
print(f"\r\033[4m{service_dict[service_id]:<{longest}}\033[0m ", end="", flush=True)
|
print(f"\r\033[4m{service_dict[service_id]:<{longest}}\033[0m ", end="", flush=True)
|
||||||
#print(f"\033[4m{service_dict[service_id]:<{longest}} {err}\033[0m")
|
#print(f"\033[4m{service_dict[service_id]:<{longest}} {err}\033[0m")
|
||||||
self.gotify_message(f"Service update available for {service_dict[service_id]}")
|
|
||||||
print(err)
|
print(err)
|
||||||
else:
|
else:
|
||||||
print(ok)
|
print(ok)
|
||||||
|
if pull:
|
||||||
|
self.gotify_message(f"Services updated: {', '.join(service_to_update)}")
|
||||||
|
else:
|
||||||
|
self.gotify_message(f"Service update available: {', '.join(service_to_update)}")
|
||||||
print("\033[?25h", end="")
|
print("\033[?25h", end="")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -1094,10 +1168,10 @@ class Portainer:
|
|||||||
def recreate_container(self,service_id, pull=False):
|
def recreate_container(self,service_id, pull=False):
|
||||||
"""Restart a service on an endpoint."""
|
"""Restart a service on an endpoint."""
|
||||||
path = f"/docker/{self.endpoint_id}/containers/{service_id}/recreate"
|
path = f"/docker/{self.endpoint_id}/containers/{service_id}/recreate"
|
||||||
print(path)
|
# print(path)
|
||||||
params={"pullImage": pull}
|
params={"pullImage": pull}
|
||||||
try:
|
try:
|
||||||
resp = self._api_post(path, json=params, timeout=20)
|
resp = self._api_post(path, json=params, timeout=120)
|
||||||
#print(resp)
|
#print(resp)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
print(f"Error restarting service: {e}")
|
print(f"Error restarting service: {e}")
|
||||||
@@ -1108,7 +1182,7 @@ class Portainer:
|
|||||||
path = f"/endpoints/{self.endpoint_id}/forceupdateservice"
|
path = f"/endpoints/{self.endpoint_id}/forceupdateservice"
|
||||||
params={"serviceID": service_id, "pullImage": pool}
|
params={"serviceID": service_id, "pullImage": pool}
|
||||||
try:
|
try:
|
||||||
resp = self._api_put(path, json=params, timeout=20)
|
resp = self._api_put(path, json=params, timeout=120)
|
||||||
# print(resp)
|
# print(resp)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
print(f"Error restarting service: {e}")
|
print(f"Error restarting service: {e}")
|
||||||
@@ -1149,44 +1223,75 @@ class Portainer:
|
|||||||
|
|
||||||
def start_stack(self, stack=None, endpoint_id=None):
|
def start_stack(self, stack=None, endpoint_id=None):
|
||||||
"""Start one stack or all stacks on an endpoint."""
|
"""Start one stack or all stacks on an endpoint."""
|
||||||
|
ok = "\033[92m✔\033[0m"
|
||||||
|
ok2 = "\033[93m✔\033[0m"
|
||||||
|
err = "\033[91m✖\033[0m"
|
||||||
if endpoint_id is not None:
|
if endpoint_id is not None:
|
||||||
print("Getting endpoint")
|
print("Getting endpoint")
|
||||||
self.get_endpoint(endpoint_id)
|
self.get_endpoint(endpoint_id)
|
||||||
|
size = 0
|
||||||
if stack is not None:
|
if stack is not None:
|
||||||
|
if type(stack) == str:
|
||||||
|
stack = stack.split(",")
|
||||||
for s in stack:
|
for s in stack:
|
||||||
self.stack_ids = [self._resolve_stack_id(s, endpoint_id)]
|
if len(s) > size:
|
||||||
|
size = len(s)
|
||||||
|
self.stack_ids.append(self._resolve_stack_id(s, endpoint_id))
|
||||||
|
size = size + 5
|
||||||
for stck in self.stack_ids:
|
for stck in self.stack_ids:
|
||||||
|
print(
|
||||||
|
f"Starting stack {self.stacks_all[self.endpoint_id]['by_id'][stck][:size].ljust(size)}",
|
||||||
|
end="", flush=True
|
||||||
|
)
|
||||||
path = f"/stacks/{stck}/start"
|
path = f"/stacks/{stck}/start"
|
||||||
if self.endpoint_id is not None:
|
if self.endpoint_id is not None:
|
||||||
path += f"?endpointId={self.endpoint_id}"
|
path += f"?endpointId={self.endpoint_id}"
|
||||||
try:
|
try:
|
||||||
resp = self._api_post_no_body(path, timeout=20)
|
resp = self._api_post_no_body(path, timeout=120)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
print(f"Error stoping stack: {e}")
|
print(f"Error starting stack: {e}")
|
||||||
return []
|
return []
|
||||||
if "Id" in json.loads(resp):
|
if "Id" in json.loads(resp):
|
||||||
print(
|
print(ok)
|
||||||
f"Stack {self.stacks_all[self.endpoint_id]['by_id'][stck]} : started"
|
elif "already running" in json.loads(resp)['message']:
|
||||||
)
|
print(ok2)
|
||||||
else:
|
else:
|
||||||
print(
|
print(
|
||||||
f"Stack {self.stacks_all[self.endpoint_id]['by_id'][stck]} : {json.loads(resp)['message']}"
|
f"Stack {self.stacks_all[self.endpoint_id]['by_id'][stck]} : {json.loads(resp)['message']}"
|
||||||
)
|
)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def stop_stack(self, stack, endpoint_id):
|
def stop_stack(self, stack, endpoint_id):
|
||||||
|
|
||||||
"""Stop one stack or all stacks on an endpoint."""
|
"""Stop one stack or all stacks on an endpoint."""
|
||||||
print(f"Stopping stack {stack}")
|
# print(f"Stopping stack {stack}")
|
||||||
|
protected_stack = ['hashicorp','nginx','pihole',]
|
||||||
|
ok = "\033[92m✔\033[0m"
|
||||||
|
ok2 = "\033[93m✔\033[0m"
|
||||||
|
err = "\033[91m✖\033[0m"
|
||||||
if endpoint_id is not None:
|
if endpoint_id is not None:
|
||||||
self.get_endpoint(endpoint_id)
|
self.get_endpoint(endpoint_id)
|
||||||
|
size = 0
|
||||||
if stack is not None:
|
if stack is not None:
|
||||||
|
if type(stack) == str:
|
||||||
|
stack = stack.split(",")
|
||||||
for s in stack:
|
for s in stack:
|
||||||
self.stack_ids = [self._resolve_stack_id(s, endpoint_id)]
|
if size < len(s):
|
||||||
# print(self.stack_ids)
|
size = len(s)
|
||||||
|
self.stack_ids.append(self._resolve_stack_id(s, endpoint_id))
|
||||||
|
size = size + 5
|
||||||
|
self.stack_ids = list(dict.fromkeys(self.stack_ids))
|
||||||
for stck in self.stack_ids:
|
for stck in self.stack_ids:
|
||||||
|
if self.stacks_all[self.endpoint_id]['by_id'] in protected_stack:
|
||||||
|
ans = input(f"Really stop {self.stacks_all[self.endpoint_id]['by_id'][stck]} ? ") or "n"
|
||||||
|
if ans != "y":
|
||||||
|
continue
|
||||||
|
print(
|
||||||
|
f"Stopping stack {self.stacks_all[self.endpoint_id]['by_id'][stck][:size].ljust(size)}",
|
||||||
|
end="",
|
||||||
|
flush=True
|
||||||
|
)
|
||||||
path = f"/stacks/{stck}/stop"
|
path = f"/stacks/{stck}/stop"
|
||||||
# print(path)
|
# print(path)
|
||||||
if self.endpoint_id is not None:
|
if self.endpoint_id is not None:
|
||||||
@@ -1197,9 +1302,9 @@ class Portainer:
|
|||||||
print(f"Error stopping stack: {e}")
|
print(f"Error stopping stack: {e}")
|
||||||
return []
|
return []
|
||||||
if "Id" in json.loads(resp):
|
if "Id" in json.loads(resp):
|
||||||
print(
|
print(ok)
|
||||||
f"Stack {self.stacks_all[self.endpoint_id]['by_id'][stck]} : stopped"
|
elif "already inactive" in json.loads(resp)['message']:
|
||||||
)
|
print(ok2)
|
||||||
else:
|
else:
|
||||||
print(
|
print(
|
||||||
f"Stack {self.stacks_all[self.endpoint_id]['by_id'][stck]} : {json.loads(resp)['message']}"
|
f"Stack {self.stacks_all[self.endpoint_id]['by_id'][stck]} : {json.loads(resp)['message']}"
|
||||||
@@ -1224,8 +1329,8 @@ class Portainer:
|
|||||||
return "all"
|
return "all"
|
||||||
|
|
||||||
if not self._is_number(stack):
|
if not self._is_number(stack):
|
||||||
result = self.get_stack(stack, endpoint_id)
|
result = self.stacks_all[endpoint_id]['by_name'][stack]
|
||||||
return result["Id"]
|
return result
|
||||||
|
|
||||||
return int(stack)
|
return int(stack)
|
||||||
|
|
||||||
@@ -1351,4 +1456,5 @@ class Portainer:
|
|||||||
path = f"/endpoints/{endpoint_id}/docker/secrets/create"
|
path = f"/endpoints/{endpoint_id}/docker/secrets/create"
|
||||||
encoded = base64.b64encode(value.encode()).decode()
|
encoded = base64.b64encode(value.encode()).decode()
|
||||||
data = {"Name": name, "Data": encoded}
|
data = {"Name": name, "Data": encoded}
|
||||||
|
|
||||||
return self._api_post(path, data, timeout=timeout)
|
return self._api_post(path, data, timeout=timeout)
|
||||||
@@ -5,3 +5,6 @@ tabulate
|
|||||||
flake8
|
flake8
|
||||||
pylint
|
pylint
|
||||||
black
|
black
|
||||||
|
docker
|
||||||
|
hvac
|
||||||
|
prompt_toolkit
|
||||||
Reference in New Issue
Block a user