Compare commits

...

19 Commits

Author SHA1 Message Date
jaydee 5adb2c7c2e build 2026-03-29 20:18:13 +02:00
jaydee 8067ac8561 build 2026-03-28 12:33:16 +01:00
jaydee 79e9e708b4 build 2026-03-28 12:33:00 +01:00
ladislav.dusa 64c3615705 build 2026-03-25 14:56:28 +01:00
jaydee f5c883964a build 2026-03-24 23:19:58 +01:00
jaydee bc984f05d2 build 2026-03-24 12:26:15 +01:00
jaydee a9a4de2038 build 2026-03-24 12:16:29 +01:00
jaydee 9205e0c8f7 build 2026-03-23 16:56:46 +01:00
jaydee 062176a875 build 2026-03-22 15:28:56 +01:00
jaydee db7005b304 build 2026-03-21 22:46:02 +01:00
jaydee 0e8fcaa530 build 2026-03-21 21:53:36 +01:00
jaydee df151c4c7f build 2026-03-21 21:24:03 +01:00
jaydee eba757bf25 build 2026-03-21 21:19:48 +01:00
jaydee 1e1f82e658 build 2026-03-21 21:07:18 +01:00
jaydee 5e36820f88 build 2026-03-21 19:09:41 +01:00
jaydee 546f695a0d build 2026-03-21 11:33:26 +01:00
jaydee 4a3609ef27 build 2026-03-21 10:36:53 +01:00
jaydee 9986c1bc03 build 2026-03-20 22:11:03 +01:00
jaydee 18afddee86 build 2026-03-20 20:57:15 +01:00
9 changed files with 151 additions and 52 deletions
Binary file not shown.
+85 -32
View File
@@ -16,6 +16,8 @@ import time
import base64 import base64
import shutil import shutil
import requests import requests
import tempfile
import subprocess
from portainer.api import PortainerApi from portainer.api import PortainerApi
from git import Repo from git import Repo
from concurrent.futures import ThreadPoolExecutor from concurrent.futures import ThreadPoolExecutor
@@ -46,7 +48,7 @@ def setup_vault():
# Specify the mount point of your KV engine # Specify the mount point of your KV engine
return vclient return vclient
VERSION = "0.1.75" VERSION = "0.1.77"
defaults = { defaults = {
@@ -140,7 +142,7 @@ 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"
@@ -152,6 +154,8 @@ 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( parser.add_argument(
"-E", "--excluded", "-E", "--excluded",
nargs="+", nargs="+",
@@ -222,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):
""" """
@@ -257,13 +296,14 @@ 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()
'api_server', 'authentik', 'bitwarden', 'bookstack', 'databasus', 'dockermon', 'duplicati', 'fail2ban', 'filebrowser', 'gitea', 'gitlab', 'grafana', 'grocy', # commands = [
'hashicorp', 'home-assistant', 'homebox','homepage', 'immich', 'influxdb', 'jupyter', 'kestra', 'kopia', 'linkding', 'linkwarden', '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','portainerce', 'rancher', 'registry', # 'mealie', 'mediacenter', 'mosquitto', 'motioneye', 'n8n', 'nebula', 'nextcloud', 'nginx',
'regsync', 'repo_mirror', 'searxng','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():
@@ -302,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:",
@@ -314,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:
@@ -466,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(
@@ -487,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"
@@ -512,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(
@@ -526,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(
@@ -539,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(
@@ -551,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(
@@ -565,7 +605,7 @@ if __name__ == "__main__":
por.update_service() por.update_service()
if args.launcher: if args.launcher:
input("\nPress ENTER to continue...") input("\nPress ENTER to continue...")
sys.exit()
if args.action == "update_containers": if args.action == "update_containers":
@@ -580,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(
@@ -595,21 +634,19 @@ if __name__ == "__main__":
if args.launcher: if args.launcher:
input("Press ENTER to continue...") 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": if args.action == "list_all_stacks":
por.get_stacks_failed() por.get_stacks_failed()
if args.launcher: if args.launcher:
input("Press ENTER to continue...") 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 == "delete_ophaned_stacks": if args.action == "delete_ophaned_stacks":
por.delete_failed_stack() por.delete_failed_stack()
if args.launcher: if args.launcher:
input("Press ENTER to continue...") 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_containers": if args.action == "list_containers":
@@ -625,7 +662,6 @@ if __name__ == "__main__":
print("\n".join(por.get_containers())) print("\n".join(por.get_containers()))
if args.launcher: if args.launcher:
input("\nPress ENTER to continue...") input("\nPress ENTER to continue...")
sys.exit()
if args.action == "update_stack": if args.action == "update_stack":
args = prompt_missing_args( args = prompt_missing_args(
@@ -640,17 +676,16 @@ if __name__ == "__main__":
por.update_stack(args) por.update_stack(args)
if args.launcher: if args.launcher:
input("\nPress ENTER to continue...") input("\nPress ENTER to continue...")
sys.exit()
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))
if args.launcher: if args.launcher:
input("\nPress ENTER to continue...") input("\nPress ENTER to continue...")
sys.exit()
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)
@@ -661,7 +696,7 @@ if __name__ == "__main__":
print(tabulate(export_data, headers=headers, tablefmt="github")) print(tabulate(export_data, headers=headers, tablefmt="github"))
if args.launcher: if args.launcher:
input("\nPress ENTER to continue...") input("\nPress ENTER to continue...")
sys.exit()
if args.action == "stop_containers": if args.action == "stop_containers":
# TODO: does not work # TODO: does not work
@@ -675,14 +710,14 @@ if __name__ == "__main__":
) )
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")
@@ -692,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 = []
@@ -701,10 +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":
por.refresh_status(args) por.refresh_status(args)
if args.print_command:
one_time_command = "portainer"
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.
+64 -18
View File
@@ -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
@@ -37,6 +39,7 @@ class PortainerApi:
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 PortainerApi:
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"
@@ -176,7 +180,7 @@ class PortainerApi:
def _api_get(self, path, timeout=120): def _api_get(self, path, timeout=120):
url = f"{self.base_url.rstrip('/')}{path}" url = f"{self.base_url.rstrip('/')}{path}"
headers = {"X-API-Key": f"{self.token}"} headers = {"X-API-Key": f"{self.token}"}
resp = requests.get(url, headers=headers, timeout=timeout) resp = requests.get(url, headers=headers, timeout=120)
if resp.status_code != 200: if resp.status_code != 200:
return resp.status_code return resp.status_code
print(f"Error: {resp.status_code} - {resp.text}") print(f"Error: {resp.status_code} - {resp.text}")
@@ -273,7 +277,7 @@ class PortainerApi:
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, 32, 43] fail_endponts = [20, 39, 41, 32, 43, 44]
# print(json.dumps(stacks,indent=2)) # print(json.dumps(stacks,indent=2))
webhooks = {} webhooks = {}
for s in stacks: for s in stacks:
@@ -656,7 +660,18 @@ class PortainerApi:
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"
@@ -748,8 +763,8 @@ class PortainerApi:
}, },
"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",
@@ -1208,44 +1223,75 @@ class PortainerApi:
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:
if len(s) > size:
size = len(s)
self.stack_ids.append(self._resolve_stack_id(s, endpoint_id)) 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:
if size < len(s):
size = len(s)
self.stack_ids.append(self._resolve_stack_id(s, endpoint_id)) self.stack_ids.append(self._resolve_stack_id(s, endpoint_id))
# print(self.stack_ids) 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:
@@ -1256,9 +1302,9 @@ class PortainerApi:
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']}"
@@ -1283,8 +1329,8 @@ class PortainerApi:
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)