mirror of
https://gitlab.sectorq.eu/jaydee/portainer.git
synced 2025-12-14 10:44:52 +01:00
build
This commit is contained in:
88
port.py
88
port.py
@@ -5,6 +5,9 @@ import uuid
|
||||
import shutil
|
||||
import time
|
||||
import logging
|
||||
import base64
|
||||
import tabulate
|
||||
from git import Repo
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
|
||||
|
||||
@@ -185,8 +188,8 @@ class Portainer:
|
||||
try:
|
||||
webhooks[s['EndpointId']] = {"webhook": {}}
|
||||
webhooks[self.endpoints["by_id"][s['EndpointId']]] = {"webhook": {}}
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
logger.debug(f"Exception while getting webhooks for endpoint {s['EndpointId']}: {e}")
|
||||
if not s['EndpointId'] in self.stacks_all:
|
||||
self.stacks_all[s['EndpointId']] = {"by_id": {}, "by_name": {}}
|
||||
self.stacks_all[self.endpoints["by_id"][s['EndpointId']]] = {"by_id": {}, "by_name": {}}
|
||||
@@ -198,7 +201,7 @@ class Portainer:
|
||||
# print(s)
|
||||
|
||||
if "AutoUpdate" in s and s["AutoUpdate"] is not None:
|
||||
if type(s["AutoUpdate"]) == dict and "Webhook" in s["AutoUpdate"]:
|
||||
if type(s["AutoUpdate"]) is dict and "Webhook" in s["AutoUpdate"]:
|
||||
# print(self.endpoints["by_id"][s['EndpointId']], s['Name'], s["AutoUpdate"]['Webhook'])
|
||||
# print("WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW")
|
||||
webhooks[s['EndpointId']][s['Name']] = s['AutoUpdate']['Webhook']
|
||||
@@ -255,24 +258,25 @@ class Portainer:
|
||||
cont = []
|
||||
data = {}
|
||||
if endpoint == "all":
|
||||
|
||||
for s in self.all_data["endpoints"]["by_id"]:
|
||||
# print(s)
|
||||
if stack == "all":
|
||||
if not s in self.all_data["stacks"]:
|
||||
if s not in self.all_data["stacks"]:
|
||||
continue
|
||||
if self.all_data["endpoints_status"][s] != 1:
|
||||
# print(f"Endpoint {self.all_data["endpoints"]["by_id"][s]} is offline")
|
||||
continue
|
||||
for e in self.all_data["stacks"][s]["by_name"]:
|
||||
path = f"/endpoints/{s}/docker/containers/json?all=1&filters={{\"label\": [\"com.docker.compose.project={e}\"]}}"
|
||||
path = (
|
||||
f"/endpoints/{s}/docker/containers/json"
|
||||
f"?all=1&filters={{\"label\": [\"com.docker.compose.project={e}\"]}}"
|
||||
)
|
||||
logging.info(f"request : {path}")
|
||||
try:
|
||||
containers = self.api_get(path)
|
||||
except:
|
||||
print(f"failed to get containers from {path}")
|
||||
except Exception as e:
|
||||
print(f"failed to get containers from {path}: {e}")
|
||||
continue
|
||||
|
||||
contr = []
|
||||
try:
|
||||
for c in containers:
|
||||
@@ -282,8 +286,11 @@ class Portainer:
|
||||
data[self.all_data["endpoints"]["by_id"][s]][e] = contr
|
||||
else:
|
||||
data[self.all_data["endpoints"]["by_id"][s]] = {e: contr}
|
||||
except:
|
||||
print("lalalal")
|
||||
except Exception as e:
|
||||
logger.debug(
|
||||
f"Exception while getting containers for stack {e} ",
|
||||
f"on endpoint {self.all_data['endpoints']['by_id'][s]}: {e}"
|
||||
)
|
||||
# print(data)
|
||||
self.all_data["containers"] = data
|
||||
else:
|
||||
@@ -291,13 +298,13 @@ class Portainer:
|
||||
|
||||
for i in self.all_data["containers"][endpoint][stack]:
|
||||
cont.append(i)
|
||||
|
||||
return cont
|
||||
|
||||
def stop_containers(self, endpoint, containers, timeout=130):
|
||||
if self.all_data["endpoints_status"][endpoint] != 1:
|
||||
print(f"Endpoint {self.get_endpoint_name(endpoint)} is offline")
|
||||
ep_id = self.endpoints["by_name"][endpoint]
|
||||
|
||||
def stop(c):
|
||||
print(f" > Stopping {c}")
|
||||
self.api_post_no_body(
|
||||
@@ -309,12 +316,11 @@ class Portainer:
|
||||
# for c in containers:
|
||||
# print(f" > Stopping {c}")
|
||||
# self.api_post_no_body(f"/endpoints/{self.endpoints["by_name"][endpoint]}/docker/containers/{c}/stop")
|
||||
|
||||
|
||||
# return 0
|
||||
|
||||
def start_containers(self, endpoint, containers, timeout=130):
|
||||
ep_id = self.endpoints["by_name"][endpoint]
|
||||
|
||||
def stop(c):
|
||||
print(f" > Starting {c}")
|
||||
self.api_post_no_body(
|
||||
@@ -337,6 +343,8 @@ class Portainer:
|
||||
ans = self.api_post_no_body(
|
||||
f"/stacks/webhooks/{c[1]}"
|
||||
)
|
||||
logger.debug(f"Update response for stack {c[0]} on endpoint {endpoint}: {ans}")
|
||||
|
||||
def stop():
|
||||
cont = []
|
||||
for c in self.all_data["containers"][endpoint]:
|
||||
@@ -390,7 +398,6 @@ class Portainer:
|
||||
|
||||
def get_stack(self, stack=None, endpoint_id=None, timeout=None):
|
||||
self.get_stacks(endpoint_id)
|
||||
|
||||
if not self.is_number(endpoint_id):
|
||||
endpoint_id = int(self.endpoints["by_name"][endpoint_id])
|
||||
self.stack_id = []
|
||||
@@ -403,11 +410,25 @@ class Portainer:
|
||||
else:
|
||||
for s in self.stacks:
|
||||
# print(s)
|
||||
if (stack is not None and s.get("Id") == stack and endpoint_id == s.get("EndpointId")) or str(s.get("Name")) == str(stack) and endpoint_id == int(s.get("EndpointId")):
|
||||
match_by_id = (
|
||||
stack is not None
|
||||
and s.get("Id") == stack
|
||||
and endpoint_id == s.get("EndpointId")
|
||||
)
|
||||
|
||||
match_by_name = (
|
||||
str(s.get("Name")) == str(stack)
|
||||
and endpoint_id == int(s.get("EndpointId")) # Ensure types match for comparison
|
||||
)
|
||||
|
||||
if match_by_id or match_by_name:
|
||||
# if (stack is not None and s.get("Id") == stack and endpoint_id == s.get("EndpointId"))
|
||||
# or str(s.get("Name")) == str(stack) and endpoint_id == int(s.get("EndpointId")):
|
||||
self.stack_id = s.get("Id")
|
||||
self.stack_name = s.get("Name")
|
||||
self.stack_ids.append(s.get("Id"))
|
||||
return s
|
||||
|
||||
raise ValueError(f"Stack not found: {stack}")
|
||||
|
||||
def create_stack(self, endpoint, stack=None, mode="git", autostart=False, swarm=False, timeout=None):
|
||||
@@ -427,11 +448,9 @@ class Portainer:
|
||||
Repo.clone_from(self.git_url, self.repo_dir)
|
||||
if mode == "git":
|
||||
path = f"/stacks/create/{p}/repository"
|
||||
|
||||
if self.endpoint_id is not None:
|
||||
path += f"?endpointId={self.endpoint_id}"
|
||||
|
||||
|
||||
if stack == "all":
|
||||
if self.endpoint_name == "rack":
|
||||
stacks = self.rack_stacks
|
||||
@@ -447,17 +466,21 @@ class Portainer:
|
||||
# input(json.dumps(self.stacks_all, indent=2))
|
||||
for stack in stacks:
|
||||
if self.endpoint_id in self.stacks_all:
|
||||
if stack in self.stacks_all[self.endpoint_id]['by_id'] or stack in self.stacks_all[self.endpoint_id]['by_name']:
|
||||
|
||||
# Check if the stack exists by ID or name
|
||||
stack_check = (
|
||||
stack in self.stacks_all[self.endpoint_id]['by_id']
|
||||
or stack in self.stacks_all[self.endpoint_id]['by_name']
|
||||
)
|
||||
if stack_check:
|
||||
print(f"Stack {stack} already exist")
|
||||
continue
|
||||
print(f"Working on {stack}")
|
||||
|
||||
|
||||
envs = []
|
||||
if os.path.exists(f"{env_path}"):
|
||||
f = open(f"{env_path}", "r")
|
||||
env_vars = f.read().splitlines()
|
||||
|
||||
for ev in env_vars:
|
||||
if ev.startswith("#") or ev.strip() == "":
|
||||
continue
|
||||
@@ -484,9 +507,6 @@ class Portainer:
|
||||
else:
|
||||
e['value'] = "syslog"
|
||||
|
||||
|
||||
|
||||
|
||||
uid = uuid.uuid4()
|
||||
# print(uid)
|
||||
req = {
|
||||
@@ -540,7 +560,7 @@ class Portainer:
|
||||
try:
|
||||
# print(self.endpoint_id)
|
||||
# print(stack)
|
||||
stck2 = self.get_stack(stack, self.endpoint_id)
|
||||
self.get_stack(stack, self.endpoint_id)
|
||||
created = True
|
||||
break
|
||||
except Exception as e:
|
||||
@@ -550,6 +570,7 @@ class Portainer:
|
||||
if tries > 50:
|
||||
print(f"Error retrieving stack {stack} after creation: {self.endpoint_name}")
|
||||
break
|
||||
logger.debug(f"Exception while getting stack {stack}: {e}")
|
||||
|
||||
if created:
|
||||
if stack != "pihole":
|
||||
@@ -618,10 +639,9 @@ class Portainer:
|
||||
def print_stacks(self, endpoint="all"):
|
||||
stacks = self.get_stacks()
|
||||
count = 0
|
||||
lst = []
|
||||
data = []
|
||||
for stack in stacks:
|
||||
if endpoint != None:
|
||||
if endpoint is not None:
|
||||
if not stack['EndpointId'] in self.endpoints['by_id']:
|
||||
continue
|
||||
if endpoint != "all":
|
||||
@@ -631,6 +651,7 @@ class Portainer:
|
||||
data.append([stack['Id'], stack['Name'], self.endpoints['by_id'][stack['EndpointId']]])
|
||||
except KeyError as e:
|
||||
data.append([stack['Id'], stack['Name'], "?"])
|
||||
logger.debug(f"KeyError getting endpoint name for stack {stack['Name']}: {e}")
|
||||
count += 1
|
||||
|
||||
headers = ["StackID", "Name", "Endpoint"]
|
||||
@@ -638,10 +659,10 @@ class Portainer:
|
||||
print(f"Total stacks: {count}")
|
||||
|
||||
def start_stack(self, stack=None, endpoint_id=None):
|
||||
if endpoint_id != None:
|
||||
if endpoint_id is not None:
|
||||
print("Getting endpoint")
|
||||
self.get_endpoint(endpoint_id)
|
||||
if stack != None:
|
||||
if stack is not None:
|
||||
self.get_stack(stack, endpoint_id)
|
||||
for stack in self.stack_ids:
|
||||
path = f"/stacks/{stack}/start"
|
||||
@@ -660,12 +681,12 @@ class Portainer:
|
||||
|
||||
def stop_stack(self, stack, endpoint_id):
|
||||
print(f"Stopping stack {stack}")
|
||||
if endpoint_id != None:
|
||||
if endpoint_id is not None:
|
||||
self.get_endpoint(endpoint_id)
|
||||
if stack == "all":
|
||||
self.get_stack(stack, endpoint_id)
|
||||
else:
|
||||
if stack != None:
|
||||
if stack is not None:
|
||||
self.stack_ids = [self.get_stack(stack, endpoint_id)["Id"]]
|
||||
for stack in self.stack_ids:
|
||||
path = f"/stacks/{stack}/stop"
|
||||
@@ -674,7 +695,7 @@ class Portainer:
|
||||
try:
|
||||
resp = self.api_post_no_body(path, timeout=120)
|
||||
except NameError as e:
|
||||
print(f"Error stoping stack: {e}")
|
||||
print(f"Error stopping stack: {e}")
|
||||
return []
|
||||
if "Id" in json.loads(resp):
|
||||
print(f"Stack {self.stacks_all[self.endpoint_id]['by_id'][stack]} : stopped")
|
||||
@@ -694,7 +715,6 @@ class Portainer:
|
||||
self.endpoint_name = endpoint_id
|
||||
self.endpoint_id = self.endpoints["by_name"][endpoint_id]
|
||||
|
||||
|
||||
if not self.is_number(endpoint_id):
|
||||
endpoint_id = int(self.endpoints["by_name"][endpoint_id])
|
||||
|
||||
@@ -716,9 +736,11 @@ class Portainer:
|
||||
path += f"?endpointId={endpoint_id}&removeVolumes=true"
|
||||
paths.append([self.get_endpoint_name(endpoint_id), s['Name'], path])
|
||||
# input(paths)
|
||||
|
||||
def delete(c):
|
||||
print(f"Delete stack {c[1]} from {c[0]} ")
|
||||
out = self.api_delete(c[2])
|
||||
logger.debug(f"Deleted stack {c[1]} from {c[0]}: {out}")
|
||||
|
||||
with ThreadPoolExecutor(max_workers=10) as exe:
|
||||
exe.map(delete, paths)
|
||||
|
||||
87
portainer.py
87
portainer.py
@@ -3,12 +3,8 @@ 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
|
||||
VERSION = "0.0.1"
|
||||
@@ -24,8 +20,11 @@ defaults = {
|
||||
|
||||
|
||||
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("--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("--refresh-environment", "-R", action="store_true", help="List endpoints")
|
||||
@@ -33,9 +32,9 @@ parser.add_argument("--list-endpoints","-E", action="store_true", help="List end
|
||||
parser.add_argument("--list-stacks", "-l", action="store_true", help="List stacks")
|
||||
parser.add_argument("--print-all-data", "-A", action="store_true", help="List stacks")
|
||||
parser.add_argument("--list-containers", "-c", action="store_true", help="List containers")
|
||||
parser.add_argument("--update-stack", "-U", action="store_true", help="Update stacls")
|
||||
parser.add_argument("--update-stack", "-U", action="store_true", help="Update stacks")
|
||||
parser.add_argument("--stop-containers", "-O", action="store_true", help="Stop containers")
|
||||
parser.add_argument("--start-containers", "-X", action="store_true", help="Stop containers")
|
||||
parser.add_argument("--start-containers", "-X", action="store_true", help="Start containers")
|
||||
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")
|
||||
parser.add_argument("--action", "-a", type=str, default=None, help="Action to perform")
|
||||
@@ -62,16 +61,36 @@ print("Environment:", args.site)
|
||||
_LOG_LEVEL = "INFO"
|
||||
LOG_FILE = "/tmp/portainer.log"
|
||||
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.debug('using debug loging')
|
||||
logging.basicConfig(
|
||||
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":
|
||||
logging.basicConfig(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 loging')
|
||||
logging.basicConfig(
|
||||
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":
|
||||
logging.basicConfig(filename=LOG_FILE, level=logging.DEBUG, format='%(asctime)s : %(levelname)s : %(message)s', datefmt='%m/%d/%Y %I:%M:%S %p')
|
||||
logging.info('using error loging')
|
||||
logging.basicConfig(
|
||||
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:
|
||||
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(
|
||||
filename=LOG_FILE,
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s : %(levelname)s : %(message)s',
|
||||
datefmt='%m/%d/%Y %I:%M:%S %p'
|
||||
)
|
||||
logging.info("script started")
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -83,9 +102,12 @@ else:
|
||||
base = os.getenv("PORTAINER_URL", "https://port.sectorq.eu/api")
|
||||
portainer_api_key = "ptr_/5RkMCT/j3BTaL32vMSDtXFi76yOXRKVFOrUtzMsl5Y="
|
||||
|
||||
|
||||
def wl(msg):
|
||||
if args.debug:
|
||||
print(msg)
|
||||
|
||||
|
||||
def is_number(s):
|
||||
"""Check if the input string is a number."""
|
||||
try:
|
||||
@@ -95,7 +117,6 @@ def is_number(s):
|
||||
return False
|
||||
|
||||
|
||||
|
||||
def get_portainer_token(base_url, username=None, password=None, timeout=10):
|
||||
"""
|
||||
Authenticate to Portainer and return a JWT token.
|
||||
@@ -114,6 +135,8 @@ def get_portainer_token(base_url, username=None, password=None, timeout=10):
|
||||
if not token:
|
||||
raise ValueError(f"No token found in response: {data}")
|
||||
return token
|
||||
|
||||
|
||||
def prompt_missing_args(args, defaults, fields):
|
||||
"""
|
||||
fields = [("arg_name", "Prompt text")]
|
||||
@@ -132,11 +155,27 @@ def prompt_missing_args(args, defaults, fields):
|
||||
setattr(args, field, value)
|
||||
|
||||
return args
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 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")
|
||||
if args.action == None:
|
||||
actions = ["delete_stack","create_stack","stop_stack","start_stack","list_stacks","update_stack","secrets","print_all_data","list_endpoints","list_containers","stop_containers","start_containers","refresh_environment","refresh_status","update_status"]
|
||||
if args.action is None:
|
||||
actions = ["delete_stack",
|
||||
"create_stack",
|
||||
"stop_stack",
|
||||
"start_stack",
|
||||
"list_stacks",
|
||||
"update_stack",
|
||||
"secrets",
|
||||
"print_all_data",
|
||||
"list_endpoints",
|
||||
"list_containers",
|
||||
"stop_containers",
|
||||
"start_containers",
|
||||
"refresh_environment",
|
||||
"refresh_status",
|
||||
"update_status"]
|
||||
print("Possible actions: ")
|
||||
i = 1
|
||||
for a in actions:
|
||||
@@ -150,7 +189,7 @@ if __name__ == "__main__":
|
||||
por = Portainer(base, token)
|
||||
|
||||
if args.action == "secrets":
|
||||
if args.endpoint_id == None:
|
||||
if args.endpoint_id is None:
|
||||
args.endpoint_id = input("Endpoint ID is required for creating secrets : ")
|
||||
|
||||
secrets = {
|
||||
@@ -182,23 +221,21 @@ if __name__ == "__main__":
|
||||
("stack_mode", "Stack mode (swarm or compose)"),
|
||||
("deploy_mode", "Deploy mode (git or upload)")
|
||||
])
|
||||
|
||||
|
||||
por.create_stack(args.endpoint_id, args.stack, args.deploy_mode, args.autostart, args.stack_mode)
|
||||
sys.exit()
|
||||
|
||||
if args.action == "stop_stack":
|
||||
if args.endpoint_id == None:
|
||||
if args.endpoint_id is None:
|
||||
args.endpoint_id = input("Endpoint ID is required for stopping stacks : ")
|
||||
if args.stack == None:
|
||||
if args.stack is None:
|
||||
args.stack = input("Stack name or ID is required for stopping stacks : ")
|
||||
por.stop_stack(args.stack, args.endpoint_id)
|
||||
sys.exit()
|
||||
|
||||
if args.action == "start_stack":
|
||||
if args.endpoint_id == None:
|
||||
if args.endpoint_id is None:
|
||||
args.endpoint_id = input("Endpoint ID is required for starting stacks : ")
|
||||
if args.stack == None:
|
||||
if args.stack is None:
|
||||
args.stack = input("Stack name or ID is required for starting stacks : ")
|
||||
por.start_stack(args.stack, args.endpoint_id)
|
||||
sys.exit()
|
||||
|
||||
Reference in New Issue
Block a user