mirror of
https://gitlab.sectorq.eu/jaydee/portainer.git
synced 2025-12-14 18:44:53 +01:00
Compare commits
147 Commits
b65680be59
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 04f5a059a4 | |||
| 24492e1ec9 | |||
| 54da7d2764 | |||
| 2d3ca53c08 | |||
| 87a088bfb0 | |||
| 92b24e472e | |||
| 42f82ef69e | |||
| 506aa0a903 | |||
| 6c4222ac16 | |||
| 9be4051720 | |||
| 2319a13554 | |||
| b434887ff9 | |||
| 4c8beff0c8 | |||
| d6842eab62 | |||
| 5864085ec3 | |||
| 04248ce279 | |||
| 101bfbc9a4 | |||
| 2e3c611c2a | |||
| fb7ebf01d0 | |||
| 53031f1b8b | |||
| 154fac3a30 | |||
| e96fc5dddc | |||
| d0ed999640 | |||
| ce33cd00c6 | |||
| 42b03a6fc7 | |||
| b30094ca9d | |||
| 191bde3f26 | |||
| b2cf21a7cd | |||
| 9f37755932 | |||
| 88e8ea7658 | |||
| 9218351ebc | |||
| 688cdb344d | |||
| ee2e84fbdd | |||
| 3986db7afc | |||
| e271e9bf81 | |||
| 15f8da3b64 | |||
| 23c5758e3c | |||
| bd185a22ce | |||
| e92f3b8467 | |||
| cf90c7b7b3 | |||
| 1f19fedbca | |||
| 7f043a61ab | |||
| 47e3a665b8 | |||
| 734ab945ab | |||
| 822626c5f3 | |||
| aecbc7731a | |||
| d52711e1f5 | |||
| 24a75740fe | |||
| 8fb39ddfce | |||
| 530be46a61 | |||
| 67638b2c1b | |||
| 41e5ff5709 | |||
| dfe7298bb2 | |||
| c790349b6b | |||
| bb57a3827d | |||
| 6f9b9c7605 | |||
| 276044614d | |||
| 45900138fc | |||
| 19a91c17c6 | |||
| aba67dc90d | |||
| bc64e5d023 | |||
| 269292a8d3 | |||
| cca115b33c | |||
| c96ffc4ee6 | |||
| cb196091c7 | |||
| 0ad11f1ae3 | |||
| 94bd230fd1 | |||
| 79ea47836b | |||
| 9f8adb8617 | |||
| d42f7dba0c | |||
| f06abe7abb | |||
| de9ce2a566 | |||
| 10299b61e6 | |||
| fb3d0b5998 | |||
| 7fc3aa25c0 | |||
| 78cba2b1e9 | |||
| 9ac6b48062 | |||
| ea5ae5f3a6 | |||
| 0af3938aee | |||
| 14932c182b | |||
| e1aaa72f13 | |||
| c937eb9f5f | |||
| 74739f459f | |||
| 493f16281c | |||
| f328118f65 | |||
| e9a3cb8dc4 | |||
| 6f05b2b93b | |||
| 91abc72e5c | |||
| cf5e2ce658 | |||
| 2719a91426 | |||
| 5b6a504258 | |||
| 6cc5bfb487 | |||
| e981a0dc1c | |||
| ee01577a6c | |||
| f8b0acdfcc | |||
| cce4c5eb9b | |||
| 90712be4b2 | |||
| 0ab5e9627b | |||
| ad5b61a44d | |||
| 1ae000292b | |||
| 5fb1e8ade4 | |||
| 0022673a7b | |||
| 48e8b36646 | |||
| 3f4ccd9d32 | |||
| 8e899ce382 | |||
| 39fb6a1366 | |||
| 313d6df99b | |||
| 8ecdeb95aa | |||
| 05aa7e362d | |||
| b111717e26 | |||
| 5be1560be6 | |||
| 28b745b2cc | |||
| 1a3e37ed8b | |||
| 92299e1b23 | |||
| c7d4623c2f | |||
| 6925199f87 | |||
| c722b5f723 | |||
| abefe7ebd7 | |||
| c1fcb94962 | |||
| e052fd7038 | |||
| 05aa62cbed | |||
| 327ce78259 | |||
| b38bf30b56 | |||
| dec2f81b05 | |||
| 615af18e12 | |||
| d25595b0db | |||
| 3c8b297dd5 | |||
| 0f3b6d9d99 | |||
| 90f5fe38b3 | |||
| 7bff9cc3b4 | |||
| de59da76df | |||
| d80d5298f3 | |||
| 54790febd2 | |||
| f654a483ea | |||
| 60d9b8e1c8 | |||
| d8283aa281 | |||
| f2a8c9ee29 | |||
| cdde724e4b | |||
| a98599beee | |||
| 0b058c955e | |||
| 5d31ab363a | |||
| 1895eff815 | |||
| 0a00c22d67 | |||
| 2320ff1b53 | |||
| 117da5fc5d | |||
| 8c517e66b0 | |||
| 3595f832f2 |
75
.gitlab-ci.yml
Normal file
75
.gitlab-ci.yml
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
stages: # List of stages for jobs, and their order of execution
|
||||||
|
- lint
|
||||||
|
- build
|
||||||
|
- clean
|
||||||
|
- notify
|
||||||
|
variables:
|
||||||
|
GIT_SSH_COMMAND: "ssh -i /home/gitlab-runner/.ssh/id_rsa -o IdentitiesOnly=yes"
|
||||||
|
lint:
|
||||||
|
stage: lint
|
||||||
|
image: r.sectorq.eu/jaydee/builder-portainer:latest
|
||||||
|
before_script:
|
||||||
|
- python3 -m pip install --break-system-packages flake8 black pylint tabulate prompt_toolkit hvac
|
||||||
|
- export PATH="$PATH:/home/gitlab-runner/.local/bin"
|
||||||
|
# - echo "PATH is now: $PATH"
|
||||||
|
script:
|
||||||
|
- flake8 .
|
||||||
|
- black --check .
|
||||||
|
- pylint portainer.py
|
||||||
|
- rm -rf /home/gitlab-runner/builds/1fLwHSKm2/0/jaydee/portainer.tmp
|
||||||
|
rules:
|
||||||
|
- if: '$CI_COMMIT_MESSAGE =~ /lint/'
|
||||||
|
build-job: # This job runs in the build stage, which runs first.
|
||||||
|
stage: build
|
||||||
|
image: r.sectorq.eu/jaydee/builder-portainer:latest
|
||||||
|
script:
|
||||||
|
- mkdir -p ~/.ssh
|
||||||
|
- 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.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
|
||||||
|
- 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/'
|
||||||
|
clean-job: # This job runs in the build stage, which runs first.
|
||||||
|
stage: clean
|
||||||
|
script:
|
||||||
|
- rm -rf /home/gitlab-runner/builds/1fLwHSKm2/0/jaydee/portainer.tmp
|
||||||
|
rules:
|
||||||
|
- if: '$CI_COMMIT_MESSAGE =~ /build/'
|
||||||
|
|
||||||
|
cleanup_on_failure_job:
|
||||||
|
stage: clean # Should be in a later stage than the job that might fail
|
||||||
|
when: on_failure # <-- This is the key keyword
|
||||||
|
script:
|
||||||
|
- rm -rf /home/gitlab-runner/builds/1fLwHSKm2/0/jaydee/portainer.tmp
|
||||||
|
|
||||||
|
notify:
|
||||||
|
stage: notify # 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 completed'
|
||||||
|
- rm -rf /home/gitlab-runner/builds/1fLwHSKm2/0/jaydee/portainer.tmp
|
||||||
|
|
||||||
|
notify2:
|
||||||
|
stage: notify # Should be in a later stage than the job that might fail
|
||||||
|
when: on_failure # <-- 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 failed'
|
||||||
|
- rm -rf /home/gitlab-runner/builds/1fLwHSKm2/0/jaydee/portainer.tmp
|
||||||
|
rules:
|
||||||
|
- if: '$CI_COMMIT_MESSAGE =~ /build/'
|
||||||
|
|
||||||
4
.pylintrc
Normal file
4
.pylintrc
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# In your .pylintrc or setup.cfg file
|
||||||
|
[FORMAT]
|
||||||
|
# The number of characters that a line should not exceed
|
||||||
|
max-line-length=120
|
||||||
728
portainer.py
Normal file → Executable file
728
portainer.py
Normal file → Executable file
@@ -1,189 +1,649 @@
|
|||||||
|
"""
|
||||||
|
Portainer API Client module.
|
||||||
|
|
||||||
|
This module provides a wrapper for interacting with the Portainer API
|
||||||
|
to manage endpoints, stacks, and containers.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# !/myapps/venvs/portainer/bin/python3
|
||||||
import os
|
import os
|
||||||
import sys
|
|
||||||
import requests
|
|
||||||
import json
|
|
||||||
import uuid
|
|
||||||
import argparse
|
|
||||||
import shutil
|
|
||||||
import time
|
|
||||||
from tabulate import tabulate
|
|
||||||
from git import Repo # pip install gitpython
|
|
||||||
from port import Portainer
|
|
||||||
import logging
|
import logging
|
||||||
parser = argparse.ArgumentParser(description="Portainer helper - use env vars or pass credentials.")
|
import signal
|
||||||
parser.add_argument("--base", "-b", default=os.getenv("PORTAINER_URL", "https://portainer.example.com"),
|
import sys
|
||||||
help="Base URL for Portainer (ENV: PORTAINER_URL)")
|
import json
|
||||||
parser.add_argument("--endpoint-id", "-e", type=str, default="all", help="Endpoint ID to limit stack operations")
|
import argparse
|
||||||
parser.add_argument("--refresh-environment", "-R", action="store_true", help="List endpoints")
|
import tty
|
||||||
parser.add_argument("--list-endpoints","-E", action="store_true", help="List endpoints")
|
import termios
|
||||||
parser.add_argument("--list-stacks", "-l", action="store_true", help="List stacks")
|
import hvac
|
||||||
parser.add_argument("--print-all-data", "-A", action="store_true", help="List stacks")
|
from tabulate import tabulate
|
||||||
parser.add_argument("--list-containers", "-c", action="store_true", help="List containers")
|
from port import Portainer
|
||||||
parser.add_argument("--update-stack", "-U", action="store_true", help="Update stacls")
|
from prompt_toolkit import prompt
|
||||||
parser.add_argument("--stop-containers", "-O", action="store_true", help="Stop containers")
|
from prompt_toolkit.completion import WordCompleter
|
||||||
parser.add_argument("--start-containers", "-X", action="store_true", help="Stop containers")
|
from prompt_toolkit.shortcuts import checkboxlist_dialog
|
||||||
parser.add_argument("--delete-stack", "-d", action="store_true", help="Delete stack")
|
from prompt_toolkit.shortcuts import radiolist_dialog
|
||||||
parser.add_argument("--update-status", "-S", action="store_true", help="Update status")
|
|
||||||
parser.add_argument("--get-stack", metavar="NAME_OR_ID", help="Get stack by name or numeric id")
|
VAULT_ADDR = os.environ.get("VAULT_ADDR", "http://192.168.77.101:8200")
|
||||||
parser.add_argument("--autostart", "-a", action="store_true", help="Auto-start created stacks")
|
try:
|
||||||
parser.add_argument("--start-stack", "-x", action='store_true')
|
VAULT_TOKEN = os.environ.get("VAULT_TOKEN")
|
||||||
parser.add_argument("--stop-stack", "-o", action='store_true')
|
if VAULT_TOKEN is None:
|
||||||
parser.add_argument("--debug", "-D", action='store_true')
|
raise KeyError
|
||||||
parser.add_argument("--create-stack","-n", action='store_true')
|
except KeyError:
|
||||||
parser.add_argument("--create-stack_new2","-N", action='store_true')
|
VAULT_TOKEN = prompt("Valult root token : ", is_password=True)
|
||||||
parser.add_argument("--gpu","-g", action='store_true')
|
os.environ["VAULT_TOKEN"] = VAULT_TOKEN
|
||||||
parser.add_argument("--create-stacks","-C", action='store_true')
|
|
||||||
parser.add_argument("--refresh-status","-r", action='store_true')
|
client = hvac.Client(url=VAULT_ADDR, token=VAULT_TOKEN)
|
||||||
parser.add_argument("--stack", "-s", type=str, help="Stack ID for operations")
|
# Check if connected
|
||||||
parser.add_argument("--token-only", action="store_true", help="Print auth token and exit")
|
if client.is_authenticated():
|
||||||
|
print("Connected to Vault")
|
||||||
|
else:
|
||||||
|
raise Exception("Failed to authenticate with Vault")
|
||||||
|
# Specify the mount point of your KV engine
|
||||||
|
|
||||||
|
VERSION = "0.1.14"
|
||||||
|
|
||||||
|
defaults = {
|
||||||
|
"endpoint_id": "vm01",
|
||||||
|
"stack": "my_stack",
|
||||||
|
"deploy_mode": "git",
|
||||||
|
"autostart": "True",
|
||||||
|
"stack_mode": "swarm",
|
||||||
|
"site": "portainer",
|
||||||
|
}
|
||||||
|
|
||||||
|
cur_config = {}
|
||||||
|
|
||||||
|
def load_config(defaults=defaults):
|
||||||
|
'''Load configuration from /myapps/portainer.conf if it exists, else from env vars or defaults.'''
|
||||||
|
if os.path.exists("/myapps/portainer.conf"):
|
||||||
|
with open("/myapps/portainer.conf", "r") as f:
|
||||||
|
conf_data = f.read()
|
||||||
|
for line in conf_data.split("\n"):
|
||||||
|
if line.startswith("#") or line.strip() == "":
|
||||||
|
continue
|
||||||
|
key, value = line.split("=", 1)
|
||||||
|
os.environ[key.strip()] = value.strip()
|
||||||
|
cur_config[key.strip()] = value.strip()
|
||||||
|
else:
|
||||||
|
print("No /myapps/portainer.conf file found, proceeding with env vars.")
|
||||||
|
os.makedirs("/myapps", exist_ok=True)
|
||||||
|
|
||||||
|
for field in defaults.keys():
|
||||||
|
value_in = os.getenv(f"PORTAINER_{field.upper()}")
|
||||||
|
if value_in is not None:
|
||||||
|
os.environ[f"PORTAINER_{field.upper()}"] = value_in
|
||||||
|
cur_config[f"PORTAINER_{field.upper()}"] = value_in
|
||||||
|
else:
|
||||||
|
os.environ[f"PORTAINER_{field.upper()}"] = defaults[field]
|
||||||
|
cur_config[f"PORTAINER_{field.upper()}"] = defaults[field]
|
||||||
|
|
||||||
|
conf_data = "\n".join(f"{k.upper()}={v}" for k, v in cur_config.items())
|
||||||
|
# print("Using the following configuration:")
|
||||||
|
with open("/myapps/portainer.conf", "w") as f:
|
||||||
|
f.write(conf_data)
|
||||||
|
print("Configuration written to /myapps/portainer.conf")
|
||||||
|
return cur_config
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
a = load_config(defaults)
|
||||||
|
|
||||||
|
# ENV_VARS = [
|
||||||
|
# "PORTAINER_URL",
|
||||||
|
# "PORTAINER_SITE",
|
||||||
|
# "PORTAINER_ENDPOINT_ID",
|
||||||
|
# "PORTAINER_STACK",
|
||||||
|
# "PORTAINER_DEPLOY_MODE",
|
||||||
|
# "PORTAINER_STACK_MODE",
|
||||||
|
# ]
|
||||||
|
|
||||||
|
|
||||||
|
def update_configs(cur_config):
|
||||||
|
'''Update defaults from environment variables if set.'''
|
||||||
|
conf_data = "\n".join(f"{k.upper()}={v}" for k, v in cur_config.items())
|
||||||
|
# print("Using the following configuration:")
|
||||||
|
# print(conf_data)
|
||||||
|
with open("/myapps/portainer.conf", "w") as f:
|
||||||
|
f.write(conf_data)
|
||||||
|
print("Configuration written to /myapps/portainer.conf")
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="Portainer helper - use env vars or pass credentials."
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--base",
|
||||||
|
"-b",
|
||||||
|
default=os.getenv("PORTAINER_URL", "https://portainer.example.com"),
|
||||||
|
help="Base URL for Portainer (ENV: PORTAINER_URL)",
|
||||||
|
)
|
||||||
|
parser.add_argument("--site", "-t", type=str, default=None, help="Site")
|
||||||
|
parser.add_argument(
|
||||||
|
"--endpoint-id",
|
||||||
|
"-e",
|
||||||
|
type=str,
|
||||||
|
default=None,
|
||||||
|
help="Endpoint ID to limit stack operations",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--service-id",
|
||||||
|
"-i",
|
||||||
|
type=str,
|
||||||
|
default=None,
|
||||||
|
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("--action", "-a", type=str, default=None, help="Action to perform")
|
||||||
|
parser.add_argument(
|
||||||
|
"--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("--debug", "-D", 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")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
print("Running version:", VERSION)
|
||||||
|
print("Environment:", args.site)
|
||||||
|
args.client = client
|
||||||
|
if args.site is not None:
|
||||||
|
cur_config["PORTAINER_SITE"] = args.site
|
||||||
|
if args.endpoint_id is not None:
|
||||||
|
cur_config["PORTAINER_ENDPOINT_ID"] = args.endpoint_id
|
||||||
|
if args.stack is not None:
|
||||||
|
cur_config["PORTAINER_STACK"] = args.stack
|
||||||
|
if args.deploy_mode is not None:
|
||||||
|
cur_config["PORTAINER_DEPLOY_MODE"] = args.deploy_mode
|
||||||
|
if args.stack_mode is not None:
|
||||||
|
cur_config["PORTAINER_STACK_MODE"] = args.stack_mode
|
||||||
|
|
||||||
|
update_configs(cur_config)
|
||||||
|
|
||||||
|
if args.debug:
|
||||||
|
input(cur_config)
|
||||||
|
|
||||||
_LOG_LEVEL = "INFO"
|
_LOG_LEVEL = "INFO"
|
||||||
LOG_FILE = "/tmp/portainer.log"
|
LOG_FILE = "/tmp/portainer.log"
|
||||||
if _LOG_LEVEL == "DEBUG":
|
if _LOG_LEVEL == "DEBUG":
|
||||||
logging.basicConfig(filename=LOG_FILE, level=logging.DEBUG, format='%(asctime)s : %(levelname)s : %(message)s', datefmt='%m/%d/%Y %I:%M:%S %p')
|
logging.basicConfig(
|
||||||
logging.debug('using debug loging')
|
filename=LOG_FILE,
|
||||||
|
level=logging.DEBUG,
|
||||||
|
format="%(asctime)s : %(levelname)s : %(message)s",
|
||||||
|
datefmt="%m/%d/%Y %I:%M:%S %p",
|
||||||
|
)
|
||||||
|
logging.debug("using debug logging")
|
||||||
elif _LOG_LEVEL == "ERROR":
|
elif _LOG_LEVEL == "ERROR":
|
||||||
logging.basicConfig(filename=LOG_FILE, level=logging.ERROR, format='%(asctime)s : %(levelname)s : %(message)s', datefmt='%m/%d/%Y %I:%M:%S %p')
|
logging.basicConfig(
|
||||||
logging.info('using error loging')
|
filename=LOG_FILE,
|
||||||
|
level=logging.ERROR,
|
||||||
|
format="%(asctime)s : %(levelname)s : %(message)s",
|
||||||
|
datefmt="%m/%d/%Y %I:%M:%S %p",
|
||||||
|
)
|
||||||
|
logging.info("using error logging")
|
||||||
elif _LOG_LEVEL == "SCAN":
|
elif _LOG_LEVEL == "SCAN":
|
||||||
logging.basicConfig(filename=LOG_FILE, level=logging.DEBUG, format='%(asctime)s : %(levelname)s : %(message)s', datefmt='%m/%d/%Y %I:%M:%S %p')
|
logging.basicConfig(
|
||||||
logging.info('using error loging')
|
filename=LOG_FILE,
|
||||||
|
level=logging.DEBUG,
|
||||||
|
format="%(asctime)s : %(levelname)s : %(message)s",
|
||||||
|
datefmt="%m/%d/%Y %I:%M:%S %p",
|
||||||
|
)
|
||||||
|
logging.info("using scan logging")
|
||||||
else:
|
else:
|
||||||
logging.basicConfig(filename=LOG_FILE, level=logging.INFO, format='%(asctime)s : %(levelname)s : %(message)s', datefmt='%m/%d/%Y %I:%M:%S %p')
|
logging.basicConfig(
|
||||||
logging.info("script started")
|
filename=LOG_FILE,
|
||||||
|
level=logging.INFO,
|
||||||
logger = logging.getLogger(__name__)
|
format="%(asctime)s : %(levelname)s : %(message)s",
|
||||||
|
datefmt="%m/%d/%Y %I:%M:%S %p",
|
||||||
|
)
|
||||||
|
logging.info("script started")
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
portainer_api_key = "ptr_GCNUoFcTOaXm7k8ZxPdQGmrFIamxZPTydbserYofMHc="
|
|
||||||
def wl(msg):
|
def wl(msg):
|
||||||
|
"""Write log message if debug is enabled."""
|
||||||
if args.debug:
|
if args.debug:
|
||||||
print(msg)
|
print(msg)
|
||||||
def is_number(s):
|
|
||||||
"""Check if the input string is a number."""
|
|
||||||
try:
|
|
||||||
float(s)
|
|
||||||
return True
|
|
||||||
except ValueError:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
|
def prompt_missing_args(args_in, defaults_in, fields, action=None,stacks=None):
|
||||||
def get_portainer_token(base_url, username=None, password=None, timeout=10):
|
|
||||||
"""
|
"""
|
||||||
Authenticate to Portainer and return a JWT token.
|
fields = [("arg_name", "Prompt text")]
|
||||||
Reads PORTAINER_USER / PORTAINER_PASS from environment if username/password are not provided.
|
|
||||||
"""
|
"""
|
||||||
username = username or os.getenv("PORTAINER_USER")
|
|
||||||
password = password or os.getenv("PORTAINER_PASS")
|
|
||||||
if not username or not password:
|
|
||||||
raise ValueError("Username and password must be provided (or set PORTAINER_USER / PORTAINER_PASS).")
|
|
||||||
|
|
||||||
url = f"{base_url.rstrip('/')}/api/auth"
|
longest = 0
|
||||||
resp = requests.post(url, json={"Username": username, "Password": password}, timeout=timeout)
|
for field, text in fields:
|
||||||
resp.raise_for_status()
|
a = text + " (default= " + cur_config["PORTAINER_" + field.upper()] + ")"
|
||||||
data = resp.json()
|
if len(a) > longest:
|
||||||
token = data.get("jwt") or data.get("JWT") or data.get("token")
|
longest = len(a)
|
||||||
if not token:
|
|
||||||
raise ValueError(f"No token found in response: {data}")
|
for field, text in fields:
|
||||||
return token
|
# print(field)
|
||||||
|
value_in = getattr(args_in, field)
|
||||||
|
default = defaults_in.get(f"PORTAINER_{field}".upper())
|
||||||
|
cur_site = defaults_in.get("PORTAINER_SITE".upper())
|
||||||
|
cur_env = defaults_in.get("PORTAINER_ENVIRONMENT_ID".upper())
|
||||||
|
|
||||||
|
# print(value_in)
|
||||||
|
if value_in is None:
|
||||||
|
if default is not None:
|
||||||
|
prompt_text = f"{text} (default={default}) : "
|
||||||
|
# value_in = input(prompt) or default
|
||||||
|
if field == "site":
|
||||||
|
commands = ["portainer", "port"]
|
||||||
|
elif field == "deploy_mode":
|
||||||
|
commands = ["git", "upload"]
|
||||||
|
elif field == "stack_mode":
|
||||||
|
commands = ["swarm", "compose"]
|
||||||
|
elif field == "endpoint_id":
|
||||||
|
commands = por.endpoints_names
|
||||||
|
elif field == "stack":
|
||||||
|
if args.action == "create_stack":
|
||||||
|
# input(json.dumps(stacks, indent=2))
|
||||||
|
commands = [
|
||||||
|
'authentik', 'bitwarden', 'bookstack', 'dockermon', 'fail2ban', 'gitea', 'gitlab', 'grafana',
|
||||||
|
'hashicorp', 'home-assistant', 'homepage', 'immich', 'influxdb', 'jupyter', 'kestra', 'mailu3',
|
||||||
|
'mealie', 'mediacenter', 'mosquitto', 'motioneye', 'n8n', 'nebula', 'nextcloud', 'nginx',
|
||||||
|
'node-red', 'octoprint', 'ollama', 'onlyoffice', 'paperless-ngx', 'pihole', 'portainer-ce', 'rancher', 'registry',
|
||||||
|
'regsync', 'semaphore', 'unifibrowser', 'uptime-kuma', 'watchtower', 'wazuh', 'webhub', 'wordpress',
|
||||||
|
'wud', 'zabbix-server']
|
||||||
|
try:
|
||||||
|
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():
|
||||||
|
|
||||||
|
#print(s)
|
||||||
|
commands.remove(s)
|
||||||
|
except KeyError:
|
||||||
|
print("No stacks found for endpoint", defaults_in[f"PORTAINER_ENDPOINT_ID".upper()])
|
||||||
|
|
||||||
|
else:
|
||||||
|
commands = []
|
||||||
|
if por._debug:
|
||||||
|
input(por.stacks_all)
|
||||||
|
# print(defaults_in[f"PORTAINER_ENDPOINT_ID".upper()])
|
||||||
|
try:
|
||||||
|
for s in por.stacks_all[
|
||||||
|
defaults_in[f"PORTAINER_ENDPOINT_ID".upper()]
|
||||||
|
]["by_name"].keys():
|
||||||
|
commands.append(s)
|
||||||
|
except KeyError:
|
||||||
|
print(
|
||||||
|
"No stacks found for endpoint",
|
||||||
|
defaults_in[f"PORTAINER_ENDPOINT_ID".upper()],
|
||||||
|
)
|
||||||
|
sys.exit(1)
|
||||||
|
else:
|
||||||
|
commands = []
|
||||||
|
completer = WordCompleter(
|
||||||
|
commands, ignore_case=True, match_middle=False
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
if field == "stack":
|
||||||
|
commands.sort()
|
||||||
|
commands_tuples = [(cmd, cmd) for cmd in commands]
|
||||||
|
commands_tuples.insert(0, ("__ALL__", "[Select ALL]"))
|
||||||
|
value_in = checkboxlist_dialog(
|
||||||
|
title="Select Services",
|
||||||
|
text="Choose one or more services:",
|
||||||
|
values=commands_tuples,
|
||||||
|
).run()
|
||||||
|
|
||||||
|
if value_in is None:
|
||||||
|
print("Cancelled.")
|
||||||
|
sys.exit(0)
|
||||||
|
elif "__ALL__" in value_in:
|
||||||
|
# User selected "Select ALL"
|
||||||
|
value_in = commands # all real commands
|
||||||
|
|
||||||
|
|
||||||
|
value_in.sort()
|
||||||
|
|
||||||
|
if "pihole" in value_in:
|
||||||
|
if action == "delete_stack":
|
||||||
|
value_in.remove("pihole")
|
||||||
|
value_in.append("pihole")
|
||||||
|
else:
|
||||||
|
value_in.remove("pihole")
|
||||||
|
value_in.insert(0, "pihole")
|
||||||
|
print(" >> Stacks :", ",".join(value_in))
|
||||||
|
else:
|
||||||
|
value_in = (
|
||||||
|
prompt(
|
||||||
|
f" >> {prompt_text}",
|
||||||
|
completer=completer,
|
||||||
|
placeholder=default,
|
||||||
|
)
|
||||||
|
or default
|
||||||
|
)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\n^C received — exiting cleanly.")
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
# value_in = input_with_default(prompt_text, default, longest+2)
|
||||||
|
|
||||||
|
else:
|
||||||
|
# value_in = input(f"{text}: ")
|
||||||
|
commands = ["start", "stop", "status", "restart", "reload", "exit"]
|
||||||
|
completer = WordCompleter(commands, ignore_case=True)
|
||||||
|
try:
|
||||||
|
value_in = (
|
||||||
|
prompt(
|
||||||
|
f" >> {text} {default}",
|
||||||
|
completer=completer,
|
||||||
|
placeholder=default,
|
||||||
|
)
|
||||||
|
or default
|
||||||
|
)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\n^C received — exiting cleanly.")
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
# value_in = input_with_default(text, default, longest+2)
|
||||||
|
value_in.sort()
|
||||||
|
if por._debug:
|
||||||
|
print("Value entered:", value_in)
|
||||||
|
defaults_in[f"PORTAINER_{field}".upper()] = value_in
|
||||||
|
setattr(args, field, value_in)
|
||||||
|
|
||||||
|
if field == "site" and value_in != cur_site:
|
||||||
|
por.get_site(value_in)
|
||||||
|
if value_in == "portainer":
|
||||||
|
defaults_in["PORTAINER_ENDPOINT_ID"] = "m-s"
|
||||||
|
elif value_in == "port":
|
||||||
|
defaults_in["PORTAINER_ENDPOINT_ID"] = "vm01"
|
||||||
|
if field == "stack" and value_in != cur_site:
|
||||||
|
os.environ[field] = ",".join(value_in)
|
||||||
|
else:
|
||||||
|
os.environ[field] = value_in
|
||||||
|
if por._debug:
|
||||||
|
print(f"{defaults_in} {field} {value_in}")
|
||||||
|
if field == "endpoint_id" and value_in != defaults_in.get(
|
||||||
|
"PORTAINER_ENDPOINT_ID".upper()
|
||||||
|
):
|
||||||
|
print("refreshing environment")
|
||||||
|
por.get_endpoints()
|
||||||
|
with open("/myapps/portainer.conf", "w") as f:
|
||||||
|
for k in defaults_in.keys():
|
||||||
|
f.write(f"{k}={defaults_in[k]}\n")
|
||||||
|
|
||||||
|
return args
|
||||||
|
|
||||||
|
|
||||||
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.
|
||||||
base = os.getenv("PORTAINER_URL", "https://portainer.sectorq.eu/api")
|
# token = get_portainer_token(base,"admin","l4c1j4yd33Du5lo") # or get_portainer_token(base, "admin", "secret")
|
||||||
#token = get_portainer_token(base,"admin","l4c1j4yd33Du5lo") # or get_portainer_token(base, "admin", "secret")
|
def signal_handler(sig, frame):
|
||||||
token = portainer_api_key
|
logger.warning("Killed manually %s, %s", sig, frame)
|
||||||
|
print("\nTerminated by user")
|
||||||
|
print("\033[?25h", end="")
|
||||||
|
sys.exit(0)
|
||||||
|
signal.signal(signal.SIGINT, signal_handler)
|
||||||
|
os.system("cls" if os.name == "nt" else "clear")
|
||||||
|
if args.action is None:
|
||||||
|
actions = [
|
||||||
|
("create_stack","create_stack"),
|
||||||
|
("delete_stack","delete_stack"),
|
||||||
|
("stop_stack","stop_stack"),
|
||||||
|
("start_stack","start_stack"),
|
||||||
|
("restart_service","restart_service"),
|
||||||
|
("update_service","update_service"),
|
||||||
|
("update_containers","update_containers"),
|
||||||
|
("list_stacks","list_stacks"),
|
||||||
|
("update_stack","update_stack"),
|
||||||
|
("secrets","secrets"),
|
||||||
|
("print_all_data","print_all_data"),
|
||||||
|
("list_endpoints","list_endpoints"),
|
||||||
|
("list_containers","list_containers"),
|
||||||
|
("stop_containers","stop_containers"),
|
||||||
|
("start_containers","start_containers"),
|
||||||
|
("refresh_environment","refresh_environment"),
|
||||||
|
("refresh_status","refresh_status"),
|
||||||
|
("update_status","update_status"),
|
||||||
|
]
|
||||||
|
|
||||||
|
selected_action = radiolist_dialog(
|
||||||
|
title="Select one service",
|
||||||
|
text="Choose a service:",
|
||||||
|
values=actions
|
||||||
|
).run()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
print("Selected:", selected_action)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# print("Possible actions: \n")
|
||||||
|
# i = 1
|
||||||
|
# for a in actions:
|
||||||
|
# print(f" > {i:>2}. {a}")
|
||||||
|
# i += 1
|
||||||
|
# ans = input("\nSelect action to perform: ")
|
||||||
|
args.action = selected_action
|
||||||
|
|
||||||
|
|
||||||
|
os.system("cls" if os.name == "nt" else "clear")
|
||||||
# Example: list endpoints
|
# Example: list endpoints
|
||||||
por = Portainer(base, token)
|
por = Portainer(cur_config["PORTAINER_SITE"], args)
|
||||||
|
por.set_defaults(cur_config)
|
||||||
if args.delete_stack:
|
if args.debug:
|
||||||
por.delete_stack(args.endpoint_id,args.stack,)
|
por._debug = True
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
if args.create_stack:
|
if args.action == "secrets":
|
||||||
por.create_stack(args.endpoint_id,args.stack, args.deploy_mode, args.autostart)
|
args = prompt_missing_args(
|
||||||
|
args,
|
||||||
|
cur_config,
|
||||||
|
[
|
||||||
|
("site", "Site"),
|
||||||
|
("endpoint_id", "Endpoint ID"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
secrets = {
|
||||||
|
"gitea_runner_registration_token": "8nmKqJhkvYwltmNfF2o9vs0tzo70ufHSQpVg6ymb",
|
||||||
|
"influxdb2-admin-token": "l4c1j4yd33Du5lo",
|
||||||
|
"ha_influxdb2_admin_token": "l4c1j4yd33Du5lo",
|
||||||
|
"wordpress_db_password": "wordpress",
|
||||||
|
"wordpress_root_db_password": "wordpress",
|
||||||
|
}
|
||||||
|
for key, value in secrets.items():
|
||||||
|
res = por.create_secret(key, value, args.endpoint_id, args.timeout)
|
||||||
|
print(res)
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
if args.stop_stack:
|
if args.action == "delete_stack":
|
||||||
por.stop_stack(args.stack,args.endpoint_id)
|
args = prompt_missing_args(
|
||||||
|
args,
|
||||||
|
cur_config,
|
||||||
|
[
|
||||||
|
("site", "Site"),
|
||||||
|
("endpoint_id", "Endpoint ID"),
|
||||||
|
("stack", "Stack name or ID"),
|
||||||
|
],
|
||||||
|
action="delete_stack",
|
||||||
|
)
|
||||||
|
|
||||||
|
input(
|
||||||
|
f"\nDelete stack {','.join(args.stack)} on endpoint {args.endpoint_id}. Press ENTER to continue..."
|
||||||
|
)
|
||||||
|
por.delete_stack(
|
||||||
|
args.endpoint_id,
|
||||||
|
args.stack,
|
||||||
|
)
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
if args.action == "create_stack":
|
||||||
|
por.action = "create_stack"
|
||||||
|
print(cur_config)
|
||||||
|
print(args)
|
||||||
|
args = prompt_missing_args(
|
||||||
|
args,
|
||||||
|
cur_config,
|
||||||
|
[
|
||||||
|
("site", "Site"),
|
||||||
|
("endpoint_id", "Endpoint ID"),
|
||||||
|
("stack", "Stack name or ID"),
|
||||||
|
("stack_mode", "Stack mode (swarm or compose)"),
|
||||||
|
("deploy_mode", "Deploy mode (git or upload)"),
|
||||||
|
],
|
||||||
|
por,
|
||||||
|
)
|
||||||
|
por.create_stack(
|
||||||
|
args.endpoint_id,
|
||||||
|
args.stack,
|
||||||
|
args.deploy_mode,
|
||||||
|
args.autostart,
|
||||||
|
args.stack_mode,
|
||||||
|
)
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
if args.action == "stop_stack":
|
||||||
|
args = prompt_missing_args(
|
||||||
|
args,
|
||||||
|
cur_config,
|
||||||
|
[
|
||||||
|
("site", "Site"),
|
||||||
|
("endpoint_id", "Endpoint ID"),
|
||||||
|
("stack", "Stack name or ID"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
por.stop_stack(args.stack, args.endpoint_id)
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
if args.action == "start_stack":
|
||||||
|
args = prompt_missing_args(
|
||||||
|
args,
|
||||||
|
cur_config,
|
||||||
|
[
|
||||||
|
("site", "Site"),
|
||||||
|
("endpoint_id", "Endpoint ID"),
|
||||||
|
("stack", "Stack name or ID"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
por.start_stack(args.stack, args.endpoint_id)
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
if args.action == "restart_service":
|
||||||
|
args = prompt_missing_args(
|
||||||
|
args,
|
||||||
|
cur_config,
|
||||||
|
[
|
||||||
|
("site", "Site"),
|
||||||
|
("endpoint_id", "Endpoint ID")
|
||||||
|
],
|
||||||
|
)
|
||||||
|
por.restart_service(args.endpoint_id, "lala")
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
if args.start_stack:
|
if args.action == "update_service":
|
||||||
por.start_stack(args.stack,args.endpoint_id)
|
|
||||||
|
|
||||||
|
|
||||||
|
args = prompt_missing_args(
|
||||||
|
args,
|
||||||
|
cur_config,
|
||||||
|
[
|
||||||
|
("site", "Site"),
|
||||||
|
("endpoint_id", "Endpoint ID")
|
||||||
|
],
|
||||||
|
)
|
||||||
|
por.update_service()
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
if args.list_stacks:
|
if args.action == "update_containers":
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
args = prompt_missing_args(
|
||||||
|
args,
|
||||||
|
cur_config,
|
||||||
|
[
|
||||||
|
("site", "Site"),
|
||||||
|
("endpoint_id", "Endpoint ID")
|
||||||
|
],
|
||||||
|
)
|
||||||
|
por.update_containers()
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
if args.action == "list_stacks":
|
||||||
|
args = prompt_missing_args(
|
||||||
|
args,
|
||||||
|
cur_config,
|
||||||
|
[
|
||||||
|
("site", "Site"),
|
||||||
|
("endpoint_id", "Endpoint ID"),
|
||||||
|
],
|
||||||
|
)
|
||||||
por.print_stacks(args.endpoint_id)
|
por.print_stacks(args.endpoint_id)
|
||||||
print(json.dumps(por.all_data,indent=2))
|
# print(json.dumps(por.all_data, indent=2))
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
if args.list_containers:
|
if args.action == "list_containers":
|
||||||
print("Getting containers")
|
print("Getting containers")
|
||||||
por.get_containers(args.endpoint_id,args.stack)
|
print(por.get_containers())
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
if args.update_stack:
|
|
||||||
|
if args.action == "update_stack":
|
||||||
print("Updating stacks")
|
print("Updating stacks")
|
||||||
autostart=True if args.autostart else False
|
por.update_stack(args.endpoint_id, args.stack, args.autostart)
|
||||||
por.update_stack(args.endpoint_id,args.stack,autostart)
|
sys.exit()
|
||||||
sys.exit()
|
if args.action == "print_all_data":
|
||||||
if args.print_all_data:
|
print(json.dumps(por.all_data, indent=2))
|
||||||
print(json.dumps(por.all_data,indent=2))
|
sys.exit()
|
||||||
sys.exit()
|
if args.action == "update_status":
|
||||||
if args.update_status:
|
por.update_status(args.endpoint_id, args.stack)
|
||||||
por.update_status(args.endpoint_id,args.stack)
|
sys.exit()
|
||||||
sys.exit()
|
|
||||||
|
if args.action == "list_endpoints":
|
||||||
if args.list_endpoints:
|
eps = por.get_endpoints(args)
|
||||||
eps = por.get_endpoints()
|
export_data = []
|
||||||
data = []
|
|
||||||
for i in eps["by_id"]:
|
for i in eps["by_id"]:
|
||||||
data.append([i,eps["by_id"][i]])
|
export_data.append([i, eps["by_id"][i]])
|
||||||
headers = ["EndpointId", "Name"]
|
headers = ["EndpointId", "Name"]
|
||||||
print(tabulate(data, headers=headers, tablefmt="github"))
|
print(tabulate(export_data, headers=headers, tablefmt="github"))
|
||||||
|
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
if args.stop_containers:
|
if args.action == "stop_containers":
|
||||||
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()
|
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 == c or args.stack == "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()
|
sys.exit()
|
||||||
|
|
||||||
if args.start_containers:
|
if args.action == "start_containers":
|
||||||
print("Starting containers")
|
print("Starting containers")
|
||||||
cont = []
|
cont = []
|
||||||
#input(json.dumps(por.all_data,indent=2))
|
# input(json.dumps(por.all_data, indent=2))
|
||||||
for c in por.all_data["containers"][args.endpoint_id]:
|
for c in por.all_data["containers"][args.endpoint_id]:
|
||||||
if args.stack == c or args.stack == "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()
|
sys.exit()
|
||||||
if args.start_containers:
|
if args.action == "start_containers":
|
||||||
print("Starting containers")
|
print("Starting containers")
|
||||||
cont = []
|
cont = []
|
||||||
#input(json.dumps(por.all_data,indent=2))
|
# input(json.dumps(por.all_data,indent=2))
|
||||||
for c in por.all_data["containers"][args.endpoint_id]:
|
for c in por.all_data["containers"][args.endpoint_id]:
|
||||||
if args.stack == c or args.stack == "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()
|
sys.exit()
|
||||||
if args.refresh_environment:
|
if args.action == "refresh_environment":
|
||||||
cont = por.refresh()
|
cont = por.refresh()
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
if args.refresh_status:
|
if args.action == "refresh_status":
|
||||||
if args.stack== "all":
|
if args.stack == "all":
|
||||||
print("Stopping all stacks...")
|
print("Stopping all stacks...")
|
||||||
stcks = por.get_stacks(base, token, endpoint_id=args.endpoint_id)
|
stcks = por.get_stacks(endpoint_id=args.endpoint_id)
|
||||||
# stcks = get_stack(base, sta, token, endpoint_id=install_endpoint_id)
|
|
||||||
else:
|
else:
|
||||||
por.refresh_status(base, args.stack_id, token)
|
por.refresh_status(args.stack_id)
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
requests
|
requests
|
||||||
gitpython
|
gitpython
|
||||||
tabulate
|
tabulate
|
||||||
|
# Other dev tools
|
||||||
|
flake8
|
||||||
|
pylint
|
||||||
|
black
|
||||||
Reference in New Issue
Block a user