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 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("--user", "-u", default=os.getenv("PORTAINER_USER"), help="Portainer username (ENV: PORTAINER_USER)") parser.add_argument("--password", "-p", default=os.getenv("PORTAINER_PASS"), help="Portainer password (ENV: PORTAINER_PASS)") parser.add_argument("--endpoint-id", "-e", type=str, help="Endpoint ID to limit stack operations") parser.add_argument("--list-endpoints", action="store_true", help="List endpoints") parser.add_argument("--list-stacks", "-l", action="store_true", help="List stacks") parser.add_argument("--delete-stack", "-d", action="store_true", help="Delete stack") parser.add_argument("--get-stack", metavar="NAME_OR_ID", help="Get stack by name or numeric id") parser.add_argument("--autostart", "-a", action="store_true", help="Auto-start created stacks") parser.add_argument("--start-stack", "-x", action='store_true') parser.add_argument("--stop-stack", "-o", action='store_true') parser.add_argument("--debug", "-D", action='store_true') parser.add_argument("--create-stack1","-c", action='store_true') parser.add_argument("--create-stack","-n", action='store_true') parser.add_argument("--create-stack_new2","-N", action='store_true') parser.add_argument("--gpu","-g", action='store_true') parser.add_argument("--create-stacks","-C", action='store_true') parser.add_argument("--refresh-status","-r", action='store_true') parser.add_argument("--stack", "-s", type=str, help="Stack ID for operations") parser.add_argument("--token-only", action="store_true", help="Print auth token and exit") 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") args = parser.parse_args() portainer_api_key = "ptr_QoHE/e6kfqIirE3fhzYMRFRxK7eL42wtCxo5P18i1zQ=" def wl(msg): if args.debug: print(msg) def is_number(s): """Check if the input string is a number.""" try: float(s) return True except ValueError: return False def get_portainer_token(base_url, username=None, password=None, timeout=10): """ Authenticate to Portainer and return a JWT token. 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" resp = requests.post(url, json={"Username": username, "Password": password}, timeout=timeout) resp.raise_for_status() data = resp.json() token = data.get("jwt") or data.get("JWT") or data.get("token") if not token: raise ValueError(f"No token found in response: {data}") return token def api_get(base_url, path, token, timeout=10): """Example authenticated GET request to Portainer API.""" url = f"{base_url.rstrip('/')}{path}" headers = {"Authorization": f"Bearer {token}"} headers = {"X-API-Key": f"{token}"} resp = requests.get(url, headers=headers, timeout=timeout) resp.raise_for_status() return resp.json() def api_post(base_url, path, token, data, timeout=240): """Example authenticated GET request to Portainer API.""" url = f"{base_url.rstrip('/')}{path}" headers = {"Authorization": f"Bearer {token}"} headers = {"X-API-Key": f"{token}"} resp = requests.post(url, headers=headers, json=data, timeout=timeout) resp.raise_for_status() return resp.json() def api_post2(base_url, path, token, endpoint_id, name, envs, file, timeout=240): #input("API POST2 called. Press Enter to continue.") """Example authenticated GET request to Portainer API.""" url = f"{base_url.rstrip('/')}{path}" headers = {"Authorization": f"Bearer {token}"} headers = {"X-API-Key": f"{token}"} data = { "EndpointId": endpoint_id, "Name": name, "Env": json.dumps(envs) } print(data) resp = requests.post(url, headers=headers, files=file, data=data, timeout=timeout) resp.raise_for_status() return resp.json() def api_post_no_body(base_url, path, token, timeout=120): """Example authenticated GET request to Portainer API.""" url = f"{base_url.rstrip('/')}{path}" headers = {"Authorization": f"Bearer {token}"} headers = {"X-API-Key": f"{token}"} resp = requests.post(url, headers=headers, timeout=timeout) resp.raise_for_status() return resp.json() def api_delete(base_url, path, token, timeout=20): """Example authenticated DELETE request to Portainer API.""" wl("Deleting stack via API...") url = f"{base_url.rstrip('/')}{path}" headers = {"Authorization": f"Bearer {token}"} headers = {"X-API-Key": f"{token}"} resp = requests.delete(url, headers=headers, timeout=timeout) wl(resp) resp.raise_for_status() wl(resp.status_code) return resp.status_code def refresh_status(base_url, token, stack, timeout=20): path = f"/api/stacks/{stack}/images_status?refresh=true" wl(path) stacks = api_get(base_url, path, token, timeout=timeout) wl(json.dumps(stacks, indent=2)) if stcks is None: return [] return stcks def get_stacks(base_url, token, endpoint_id=None, timeout=10): """ Return a list of stacks. If endpoint_id is provided, it will be added as a query param. """ path = "/api/stacks" stcks = [] stacks = api_get(base_url, path, token, timeout=timeout) for s in stacks: if s['EndpointId'] == endpoint_id: stcks.append(s) wl(json.dumps(stacks, indent=2)) if stcks is None: return [] return stcks def get_stack(base_url, identifier, token, endpoint_id=None, timeout=10): wl("get_stack") """ Retrieve a single stack by numeric Id or by Name. Identifier may be an int (Id) or a string (Name). Raises ValueError if not found. """ #print(endpoint_id) stacks = get_stacks(base_url, token, endpoint_id=endpoint_id, timeout=timeout) # Normalize identifier #input(stacks) ident_id = None #print(identifier) try: ident_id = int(identifier) except (TypeError, ValueError): pass #wl(stacks) for s in stacks: # Many Portainer responses use 'Id' and 'Name' keys if ident_id is not None and s.get("Id") == ident_id and endpoint_id == s.get("EndpointId"): return s if str(s.get("Name")) == str(identifier) and endpoint_id == s.get("EndpointId"): return s raise ValueError(f"Stack not found: {identifier}") def create_stack(base_url, token, endpoint_id=None, data={}, timeout=300): """ Return a list of stacks. If endpoint_id is provided, it will be added as a query param. """ path = "/api/stacks/create/standalone/repository" if endpoint_id is not None: path += f"?endpointId={endpoint_id}" wl(path) try: stacks = api_post(base_url, path, token, data, timeout=timeout) except Exception as e: #print(f"Error creating stack: {e}") if "Conflict for url" in str(e): print("Stack with this name may already exist.") else: print(f"Error creating stack: {e}") wl(json.dumps(data, indent=2)) return [] if stacks is None: return [] return stacks def create_stack2(base_url, token, endpoint_id=None, name="", file="", envs=[], timeout=300): #input("Creating stack from file... Press Enter to continue.") """ Return a list of stacks. If endpoint_id is provided, it will be added as a query param. """ path = "/api/stacks/create/standalone/file" if endpoint_id is not None: path += f"?endpointId={endpoint_id}&name={name}" wl(path) try: stacks = api_post2(base_url, path, token, endpoint_id, name=name, envs=envs, file=file, timeout=timeout) except Exception as e: print(f"Error creating stack: {e}") if "Conflict for url" in str(e): print("Stack with this name may already exist.") else: print(json.dumps(envs, indent=2)) return [] if stacks is None: return [] return stacks def stop_stack(base_url, token, endpoint_id=None, stack=None, timeout=120): path = f"/api/stacks/{stack}/stop" if endpoint_id is not None: path += f"?endpointId={endpoint_id}" try: api_post_no_body(base_url, path, token, timeout=timeout) except Exception as e: print(f"Error stoping stack: {e}") return [] return True def start_stack(base_url, token, endpoint_id=None, stack=None, timeout=120): path = f"/api/stacks/{stack}/start" if endpoint_id is not None: path += f"?endpointId={endpoint_id}" try: stacks = api_post_no_body(base_url, path, token, timeout=timeout) except Exception as e: print(f"Error starting stack: {e}") return [] if stacks is None: return [] return stacks def delete_stack(base_url, token, endpoint_id=None, stack=None, timeout=120): """ Return a list of stacks. If endpoint_id is provided, it will be added as a query param. """ if stack == "all": stacks = get_stacks(base_url, token, endpoint_id=endpoint_id, timeout=timeout) for s in stacks: #print(s['EndpointId'], endpoint_id) if int(s['EndpointId']) != int(endpoint_id): continue print("Deleting stack:", s['Name']) path = f"/api/stacks/{s['Id']}" if endpoint_id is not None: path += f"?endpointId={endpoint_id}&removeVolumes=true" if args.debug: print(path) out = api_delete(base_url, path, token, timeout=timeout) return "Done" else: path = f"/api/stacks/{stack}" if endpoint_id is not None: path += f"?endpointId={endpoint_id}&removeVolumes=true" # print(path) try: # print(path) # print(base_url) # print(token) stacks = api_delete(base_url, path, token, timeout=timeout) except Exception as e: #print(f"Error creating stack: {e}") if "Conflict for url" in str(e): print("Stack with this name may already exist.") else: print(f"Error deleting stack: {e}") #print(stacks) return [] if stacks is None: return [] return stacks def print_stacks(base, token, endpoint=None,endpoints={}): stacks = get_stacks(base, token, endpoint) count = 0 lst = [] data = [] #print(stacks) for stack in stacks: #print(endpoint) #print(stack['EndpointId']) #print(stack) if endpoint != None: if not stack['EndpointId'] in endpoints['by_id']: continue if endpoint != stack['EndpointId']: continue if not stack['Name'] in basic_stacks: lst.append(stack['Name']) try: #print(f"Stack ID: {stack['Id']}, Name: {stack['Name']}, EndpointName: {eps[stack['EndpointId']]}") data.append([stack['Id'], stack['Name'], endpoints['by_id'][stack['EndpointId']]]) except KeyError as e: #print(f"Stack ID: {stack['Id']}, Name: {stack['Name']}, EndpointName: ?") data.append([stack['Id'], stack['Name'], "?"]) count += 1 headers = ["StackID", "Name", "Endpoint"] print(tabulate(data, headers=headers, tablefmt="github")) print(f"Total stacks: {count}") def resolve_endpoins(base,token): base = os.getenv("PORTAINER_URL", "https://portainer.sectorq.eu") endpoints = api_get(base, "/api/endpoints", token) eps = {"by_id":{}, "by_name":{}} for ep in endpoints: eps['by_id'][ep['Id']] = ep['Name'] eps['by_name'][ep['Name']] = ep['Id'] return eps if __name__ == "__main__": # 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 = portainer_api_key # Example: list endpoints por = Portainer(base, token) if args.delete_stack: por.delete_stack(args.endpoint_id,args.stack,) sys.exit() if args.create_stack: por.create_stack(args.endpoint_id,args.stack, args.deploy_mode, args.autostart) sys.exit() #print(por.base_url) if args.stop_stack: por.stop_stack(args.stack,args.endpoint_id) sys.exit() #print(por.base_url) if args.start_stack: por.start_stack(args.stack,args.endpoint_id) sys.exit() #print(por.base_url) endpoints = resolve_endpoins(base, token) wl(endpoints) if is_number(args.endpoint_id): install_endpoint_id = int(args.endpoint_id) install_endpoint_name = endpoints['by_id'][install_endpoint_id] else: install_endpoint_id = endpoints['by_name'][args.endpoint_id] install_endpoint_name = args.endpoint_id wl(install_endpoint_name) wl(install_endpoint_id) if args.list_stacks: print_stacks(base, token, install_endpoint_id,endpoints) if args.create_stack_new21: print("Creating new stack from git repo...") if not args.stack_id: args.stack_id = input("Stack name? : ") if install_endpoint_id == None: install_endpoint_id = endpoints['by_id'][input("Endpoint name? : ")] if is_number(install_endpoint_id): install_endpoint_id = int(install_endpoint_id) else: install_endpoint_id = endpoints['by_name'][install_endpoint_id] git_url = "https://gitlab.sectorq.eu/home/docker-compose.git" git_url = "git@gitlab.sectorq.eu:home/docker-compose.git" repo_dir = "/tmp/docker-compose" # Check if folder exists if os.path.exists(repo_dir): shutil.rmtree(repo_dir) print(f"Folder '{repo_dir}' has been removed.") else: print(f"Folder '{repo_dir}' does not exist.") Repo.clone_from(git_url, repo_dir) wl(args.stack_id) wl(install_endpoint_id) if args.stack_id == "all": wl("All stacks selected") if install_endpoint_name == "nas": args.stack_id = nas_stacks elif install_endpoint_name == "rpi5": print("RPI5 stacks selected") args.stack_id = rpi5_stacks elif install_endpoint_name == "rack": args.stack_id = rack_stacks else: args.stack_id = [args.stack_id] for s in args.stack_id: print(f"Processing stack: {s}") if os.path.exists(f"{repo_dir}/{s}/.env"): f = open(f"{repo_dir}/{s}/.env","r") env_vars = f.read().splitlines() envs = [] for ev in env_vars: if ev.startswith("#") or ev.strip() == "": continue if "=" in ev: name, value = ev.split("=",1) envs.append({"name": name, "value": value}) f.close() #wl(envs) for e in envs: wl(f"Env: {e['name']} = {e['value']}") HWS = ["HW_MODE","HW_MODE1","HW_MODE2"] if e['name'] == "RESTART" and args.endpoint_id == "m-server": e['value'] = "always" if e['name'] in HWS: wl("Found HW_MODE env var.") if args.gpu: e['value'] = "hw" else: e['value'] = "cpu" if e['name'] == "LOGGING": wl("Found LOGGING env var.") if args.gpu: e['value'] = "journald" else: e['value'] = "syslog" req = { "Name": s, "Env": envs, "AdditionalFiles": [], "AutoUpdate": None, "repositoryURL": "https://gitlab.sectorq.eu/home/docker-compose.git", "ReferenceName": "refs/heads/main", "composeFile": f"{s}/docker-compose.yml", "ConfigFilePath": f"{s}/docker-compose.yml", "repositoryAuthentication": True, "repositoryUsername": "jaydee", "repositoryPassword": "glpat-uj-n-eEfTY398PE4vKSS", "AuthorizationType": 0, "TLSSkipVerify": False, "supportRelativePath": True, "repositoryAuthentication": True, "fromAppTemplate": False, "registries": [6,3], "FromAppTemplate": False, "Namespace": "", "CreatedByUserId": "", "Webhook": "", "filesystemPath": "/share/docker_data/portainer/portainer-data/", "RegistryID": 4, "isDetachedFromGit": True } #wl(json.dumps(req, indent=2)) create_stack(base, token, install_endpoint_id, data=req) if not args.autostart and s != "pihole": tries = 0 while True: try: stck2 = get_stack(base, s, token, endpoint_id=install_endpoint_id) break except Exception as e: print(f"Waiting for stack {s} to be created...") time.sleep(10) tries += 1 if tries > 20: print(f"Error retrieving stack {s} after creation: {e}") break try: print(f"Stopping stack: ID {stck2['Id']}, Name: {stck2['Name']}") stop_stack(base, token, install_endpoint_id, stck2['Id']) except Exception as e: print(f"Error stopping stack {s}: {e}") if args.create_stack_new2: print("Creating new stack from file...") if not args.stack_id: args.stack_id = input("Stack name? : ") if install_endpoint_id == None: install_endpoint_id = endpoints['by_id'][input("Endpoint name? : ")] if is_number(install_endpoint_id): install_endpoint_id = int(install_endpoint_id) else: install_endpoint_id = endpoints['by_name'][install_endpoint_id] git_url = "https://gitlab.sectorq.eu/home/docker-compose.git" git_url = "git@gitlab.sectorq.eu:home/docker-compose.git" repo_dir = "/tmp/docker-compose" # Check if folder exists if os.path.exists(repo_dir): shutil.rmtree(repo_dir) print(f"Folder '{repo_dir}' has been removed.") else: print(f"Folder '{repo_dir}' does not exist.") Repo.clone_from(git_url, repo_dir) wl(args.stack_id) wl(install_endpoint_id) if args.stack_id == "all": wl("All stacks selected") if install_endpoint_name == "nas": args.stack_id = nas_stacks elif install_endpoint_name == "rpi5": print("RPI5 stacks selected") args.stack_id = rpi5_stacks elif install_endpoint_name == "rack": args.stack_id = rack_stacks else: args.stack_id = [args.stack_id] for s in args.stack_id: # file = f"/tmp/docker-compose/{s}/docker-compose.yml" print(f"Processing stack: {s}") file = { # ("filename", file_object) "file": ("docker-compose.yml", open(f"/tmp/docker-compose/{s}/docker-compose.yml", "rb")), } print(file) if os.path.exists(f"{repo_dir}/{s}/.env"): f = open(f"{repo_dir}/{s}/.env","r") env_vars = f.read().splitlines() envs = [] for ev in env_vars: if ev.startswith("#") or ev.strip() == "": continue if "=" in ev: name, value = ev.split("=",1) envs.append({"name": name, "value": value}) f.close() #wl(envs) for e in envs: wl(f"Env: {e['name']} = {e['value']}") HWS = ["HW_MODE","HW_MODE1","HW_MODE2"] if e['name'] in HWS: wl("Found HW_MODE env var.") if args.gpu: e['value'] = "hw" else: e['value'] = "cpu" if e['name'] == "LOGGING": wl("Found LOGGING env var.") if args.gpu: e['value'] = "journald" else: e['value'] = "syslog" create_stack2(base, token, install_endpoint_id, name=s, file=file, envs=envs) if not args.autostart and s != "pihole": tries = 0 while True: try: stck2 = get_stack(base, s, token, endpoint_id=install_endpoint_id) break except Exception as e: print(f"Waiting for stack {s} to be created...") time.sleep(2) tries += 1 if tries > 5: print(f"Error retrieving stack {s} after creation: {e}") break try: print(f"Stopping stack: ID {stck2['Id']}, Name: {stck2['Name']}") stop_stack(base, token, install_endpoint_id, stck2['Id']) except Exception as e: print(f"Error stopping stack {s}: {e}") #print(json.dumps(req, indent=2)) if args.create_stack: if not args.stack_id: input("Stack name?") stck = get_stack(base, args.stack_id, token) print(f"Found stack: ID {stck['Id']}, Name: {stck['Name']}") print(json.dumps(stck, indent=2)) for e in stck["Env"]: print(f"Env: {e['name']} = {e['value']}") HWS = ["HW_MODE","HW_MODE1","HW_MODE2"] if e['name'] in HWS: print("Found HW_MODE env var.") if args.gpu: e['value'] = "hw" else: e['value'] = "cpu" if e['name'] == "LOGGING": print("Found LOGGING env var.") if args.gpu: e['value'] = "journald" else: e['value'] = "syslog" #print(json.dumps(stck, indent=2)) uid = str(uuid.uuid4()) try: stck["AutoUpdate"]["Webhook"] = uid except: stck["AutoUpdate"] = None try: req = { "Name": stck["Name"], "Env": stck["Env"], "AdditionalFiles": stck["AdditionalFiles"], "AutoUpdate": stck["AutoUpdate"], "repositoryURL": stck["GitConfig"]["URL"], "ReferenceName": "refs/heads/main", "composeFile": f"{stck['Name']}/docker-compose.yml", "ConfigFilePath": f"{stck['Name']}/docker-compose.yml", "repositoryAuthentication": True, "repositoryUsername": "jaydee", "repositoryPassword": "glpat-uj-n-eEfTY398PE4vKSS", "AuthorizationType": 0, "TLSSkipVerify": False, "supportRelativePath": True, "repositoryAuthentication": True, "fromAppTemplate": False, "registries": [6,3], "FromAppTemplate": False, "Namespace": "", "CreatedByUserId": "", "Webhook": "", "filesystemPath": "/share/docker_data/portainer/portainer-data/stacks", "RegistryID": 4 } except: req = { "Name": stck["Name"], "Env": stck["Env"], "AdditionalFiles": stck["AdditionalFiles"], "AutoUpdate": None, "repositoryURL": "https://gitlab.sectorq.eu/home/docker-compose.git", "ReferenceName": "refs/heads/main", "composeFile": f"{stck['Name']}/docker-compose.yml", "ConfigFilePath": f"{stck['Name']}/docker-compose.yml", "repositoryAuthentication": True, "repositoryUsername": "jaydee", "repositoryPassword": "glpat-uj-n-eEfTY398PE4vKSS", "AuthorizationType": 0, "TLSSkipVerify": False, "supportRelativePath": True, "repositoryAuthentication": True, "fromAppTemplate": False, "registries": [6,3], "FromAppTemplate": False, "Namespace": "", "CreatedByUserId": "", "Webhook": "", "filesystemPath": "/share/docker_data/portainer/portainer-data/stacks", "RegistryID": 6 } print(json.dumps(req, indent=2)) create_stack(base, token, install_endpoint_id, data=req) if args.create_stacks: if args.endpoint_id == "nas": s = nas_stacks elif args.endpoint_id == "rpi5": s = rpi5_stacks elif args.endpoint_id == "rack": s = rack_stacks wl(s) for ns in s: wl(f"Processing stack: {ns}") stck = get_stack(base, ns, token) print(f"Found stack: ID {stck['Id']}, Name: {stck['Name']}") #print(json.dumps(stck, indent=2)) for e in stck["Env"]: #print(f"Env: {e['name']} = {e['value']}") if e['name'] == "RESTART" and stck["Env"] == install_endpoint_id: e['value'] = "always" HWS = ["HW_MODE","HW_MODE1","HW_MODE2"] if e['name'] in HWS: print("Found HW_MODE env var.") if args.gpu: e['value'] = "hw" else: e['value'] = "cpu" if e['name'] == "LOGGING": print("Found LOGGING env var.") if args.gpu: e['value'] = "journald" else: e['value'] = "syslog" #print(stck["Env"]) uid = str(uuid.uuid4()) try: stck["AutoUpdate"]["Webhook"] = uid except: stck["AutoUpdate"] = None try: req = { "Name": stck["Name"], "Env": stck["Env"], "AdditionalFiles": stck["AdditionalFiles"], "AutoUpdate": stck["AutoUpdate"], "repositoryURL": stck["GitConfig"]["URL"], "ReferenceName": "refs/heads/main", "composeFile": f"{stck['Name']}/docker-compose.yml", "ConfigFilePath": f"{stck['Name']}/docker-compose.yml", "repositoryAuthentication": True, "repositoryUsername": "jaydee", "repositoryPassword": "glpat-uj-n-eEfTY398PE4vKSS", "AuthorizationType": 0, "TLSSkipVerify": False, "supportRelativePath": True, "repositoryAuthentication": True, "fromAppTemplate": False, "registries": [6,3], "FromAppTemplate": False, "Namespace": "", "CreatedByUserId": "", "Webhook": "", "filesystemPath": "/share/docker_data/portainer/portainer-data/stacks", "RegistryID": 4 } except: req = { "Name": stck["Name"], "Env": stck["Env"], "AdditionalFiles": stck["AdditionalFiles"], "AutoUpdate": None, "repositoryURL": "https://gitlab.sectorq.eu/home/docker-compose.git", "ReferenceName": "refs/heads/main", "composeFile": f"{stck['Name']}/docker-compose.yml", "ConfigFilePath": f"{stck['Name']}/docker-compose.yml", "repositoryAuthentication": True, "repositoryUsername": "jaydee", "repositoryPassword": "glpat-uj-n-eEfTY398PE4vKSS", "AuthorizationType": 0, "TLSSkipVerify": False, "supportRelativePath": True, "repositoryAuthentication": True, "fromAppTemplate": False, "registries": [6,3], "FromAppTemplate": False, "Namespace": "", "CreatedByUserId": "", "Webhook": "", "filesystemPath": "/share/docker_data/portainer/portainer-data/stacks", "RegistryID": 6 } print(f"Creating stack: {ns}") create_stack(base, token, install_endpoint_id, data=req) if not args.autostart: stck2 = get_stack(base, ns, token, endpoint_id=install_endpoint_id) print(print_stacks(base, token, install_endpoint_id,endpoints)) print(f"Stopping stack: ID {stck2['Id']}, Name: {stck2['Name']}") stop_stack(base, token, install_endpoint_id, stck2['Id']) if args.delete_stack22: print(f"Delete stack {args.stack_id}") if not is_number(args.stack_id) and args.stack_id != "all": args.stack_id = get_stack(base, args.stack_id, token, install_endpoint_id)['Id'] #print(args.stack_id) #print(install_endpoint_id) delete_stack(base, token, install_endpoint_id, args.stack_id) if args.stop_stack: if args.stack_id == "all": print("Stopping all stacks...") stcks = get_stacks(base, token, endpoint_id=install_endpoint_id) # stcks = get_stack(base, sta, token, endpoint_id=install_endpoint_id) else: stcks = [get_stack(base, args.stack_id, token, endpoint_id=install_endpoint_id)] for stck in stcks: print(f"Stopping stack {stck['Name']}") stop_stack(base, token, install_endpoint_id, stck['Id']) if args.start_stack: if args.stack_id == "all": print("Starting all stacks...") stcks = get_stacks(base, token, endpoint_id=install_endpoint_id) # stcks = get_stack(base, sta, token, endpoint_id=install_endpoint_id) else: stcks = [get_stack(base, args.stack_id, token, endpoint_id=install_endpoint_id)] for stck in stcks: print(f"Starting stack {stck['Name']}") start_stack(base, token, install_endpoint_id, stck['Id']) if args.refresh_status: if args.stack_id == "all": print("Stopping all stacks...") stcks = get_stacks(base, token, endpoint_id=install_endpoint_id) # stcks = get_stack(base, sta, token, endpoint_id=install_endpoint_id) else: refresh_status(base, args.stack_id, token)