This commit is contained in:
2025-12-10 18:41:19 +01:00
parent 154fac3a30
commit 53031f1b8b
2 changed files with 84 additions and 10 deletions

78
port.py
View File

@@ -12,7 +12,10 @@ import base64
import tabulate import tabulate
from git import Repo from git import Repo
import requests import requests
from prompt_toolkit import prompt
from prompt_toolkit.completion import WordCompleter
from prompt_toolkit.shortcuts import checkboxlist_dialog
from prompt_toolkit.shortcuts import radiolist_dialog
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -156,6 +159,14 @@ class Portainer:
resp = requests.post(url, headers=headers, json=json, timeout=timeout) resp = requests.post(url, headers=headers, json=json, timeout=timeout)
return resp.text return resp.text
def _api_put(self, path, json="", timeout=120):
url = f"{self.base_url.rstrip('/')}{path}"
headers = {"X-API-Key": f"{self.token}"}
# print(url)
# print(json)
resp = requests.put(url, headers=headers, json=json, timeout=timeout)
return resp.text
def _api_post_file(self, path, endpoint_id, name, envs, file, timeout=120): def _api_post_file(self, path, endpoint_id, name, envs, file, timeout=120):
# input("API POST2 called. Press Enter to continue.") # input("API POST2 called. Press Enter to continue.")
"""Example authenticated GET request to Portainer API.""" """Example authenticated GET request to Portainer API."""
@@ -261,6 +272,13 @@ class Portainer:
# input(json.dumps(self.stacks_all,indent=2)) # input(json.dumps(self.stacks_all,indent=2))
return stcks return stcks
def get_services(self, endpoint, stack, timeout=30):
'''Get a list of services for a specific stack on an endpoint.'''
path = f"/endpoints/{self.all_data['endpoints']['by_name'][endpoint]}/docker/services"
#path += f'?filters={{"label": ["com.docker.compose.project={stack}"]}}'
services = self._api_get(path, timeout=timeout)
return services
def update_status(self, endpoint, stack): def update_status(self, endpoint, stack):
'''Get the update status of a specific stack on an endpoint.''' '''Get the update status of a specific stack on an endpoint.'''
path = f"/stacks/{self.all_data['stacks'][endpoint]['by_name'][stack]}/images_status?refresh=true" path = f"/stacks/{self.all_data['stacks'][endpoint]['by_name'][stack]}/images_status?refresh=true"
@@ -480,15 +498,13 @@ class Portainer:
return 1 return 1
def create_stack( def create_stack(
self, self,
endpoint, endpoint,
stacks=None, stacks=None,
mode="git", mode="git",
autostart=False, autostart=False,
stack_mode="swarm", stack_mode="swarm",
): ):
for stack in stacks: for stack in stacks:
if stack_mode == "swarm": if stack_mode == "swarm":
swarm_id = self.get_swarm_id(endpoint) swarm_id = self.get_swarm_id(endpoint)
@@ -705,6 +721,7 @@ class Portainer:
} }
self._api_post_file(path, self.endpoint_id, stack, envs, file) self._api_post_file(path, self.endpoint_id, stack, envs, file)
def print_stacks(self, endpoint="all"): def print_stacks(self, endpoint="all"):
"""Print a table of stacks, optionally filtered by endpoint.""" """Print a table of stacks, optionally filtered by endpoint."""
stacks = self.get_stacks() stacks = self.get_stacks()
@@ -739,6 +756,47 @@ class Portainer:
print(f"Total stacks: {count}") print(f"Total stacks: {count}")
# print(sorted(stack_names)) # print(sorted(stack_names))
def restart_service(self, endpoint_id, service_id):
stacks = [(s["Id"], s["Name"]) for s in self.get_stacks(endpoint_id)]
stack_id = radiolist_dialog(
title="Select one service",
text="Choose a service:",
values=stacks
).run()
service_dict = dict(stacks)
services = self.get_services(self.endpoint_name, stack_id)
svc_name = service_dict.get(stack_id)
stack_svcs = []
svc_menu = []
for s in services:
try:
if svc_name in s['Spec']['Name']:
stack_svcs.append([s['Version']['Index'], s['Spec']['Name']])
svc_menu.append([s['ID'], s['Spec']['Name']])
except KeyError as e:
print(e)
service_id = radiolist_dialog(
title="Select one service",
text="Choose a service:",
values=svc_menu
).run()
"""Restart a service on an endpoint."""
path = f"/endpoints/{self.endpoint_id}/forceupdateservice"
params={"serviceID": service_id, "pullImage": False}
try:
resp = self._api_put(path, json=params, timeout=20)
print(resp)
except ValueError as e:
print(f"Error restarting service: {e}")
return []
print(f"Service {service_id} : restarted")
return True
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."""
if endpoint_id is not None: if endpoint_id is not None:

View File

@@ -392,6 +392,7 @@ if __name__ == "__main__":
("delete_stack","delete_stack"), ("delete_stack","delete_stack"),
("stop_stack","stop_stack"), ("stop_stack","stop_stack"),
("start_stack","start_stack"), ("start_stack","start_stack"),
("restart_service","restart_service"),
("list_stacks","list_stacks"), ("list_stacks","list_stacks"),
("update_stack","update_stack"), ("update_stack","update_stack"),
("secrets","secrets"), ("secrets","secrets"),
@@ -526,6 +527,18 @@ if __name__ == "__main__":
por.start_stack(args.stack, args.endpoint_id) por.start_stack(args.stack, args.endpoint_id)
sys.exit() 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()
if args.action == "list_stacks": if args.action == "list_stacks":
args = prompt_missing_args( args = prompt_missing_args(
args, args,
@@ -544,6 +557,9 @@ if __name__ == "__main__":
por.get_containers(args.endpoint_id, args.stack) por.get_containers(args.endpoint_id, args.stack)
sys.exit() sys.exit()
if args.action == "update_stack": if args.action == "update_stack":
print("Updating stacks") print("Updating stacks")
por.update_stack(args.endpoint_id, args.stack, args.autostart) por.update_stack(args.endpoint_id, args.stack, args.autostart)