Compare commits

...

19 Commits

Author SHA1 Message Date
e885d5dc27 Correcting url used for portainer ping 2024-04-30 15:20:56 +02:00
10a4c05912 Correcting type in put method to get uri 2024-04-25 11:08:49 +02:00
02f73bcf27 Merge branch 'master' of git.dorfsvald.net:ewandor/portainer_updater 2024-04-25 11:00:52 +02:00
7dedfdb5d7 Adding function to wait for server to be back online after core element upgrade 2024-04-25 10:54:06 +02:00
f234ef2015 Adding generic requests method 2024-04-25 10:49:47 +02:00
8a84a70c59 Adding dynamic interpreter selector and real script path finding 2024-02-15 10:55:53 +01:00
aa60dd2529 Adding endpoint separation to allow image purging per endpoint 2023-12-02 01:50:01 +01:00
3b6f13abc4 Changing local updater sequence 2023-12-01 20:45:06 +01:00
9b5a45290a Improving logging further 2023-12-01 20:40:10 +01:00
a64fc40a16 Merge remote-tracking branch 'origin/master' 2023-12-01 20:34:47 +01:00
5b3b02980c Adding logs in the process 2023-12-01 17:18:53 +01:00
bbbf4ce497 Using Portainer API to update images 2023-12-01 17:12:03 +01:00
3b79e59076 Adding quotes in bash 2023-11-24 21:04:05 +01:00
91b8d35220 Modifying success logs 2023-10-03 13:40:34 +02:00
6dd7f5bff7 Adding logs printing 2023-10-03 13:37:24 +02:00
335f0c6f50 Correcting script updater name 2023-10-03 13:26:22 +02:00
38f3fba33c Merge branch 'master' of git.dorfsvald.net:ewandor/portainer_updater 2023-10-03 13:25:15 +02:00
400d4231d0 Exit if return value diff than 0 2023-10-03 13:24:50 +02:00
59ea60576f Calling the repository updater method 2023-10-03 13:20:10 +02:00
3 changed files with 157 additions and 25 deletions

View File

@@ -5,6 +5,6 @@ set -e
PROJECT_NAME=$1
COMPOSE_FILE=$2
docker compose -p $PROJECT_NAME -f $COMPOSE_FILE stop
docker compose -p $PROJECT_NAME -f $COMPOSE_FILE pull
docker compose -p $PROJECT_NAME -f $COMPOSE_FILE up -d
docker compose -p "$PROJECT_NAME" -f "$COMPOSE_FILE" pull
docker compose -p "$PROJECT_NAME" -f "$COMPOSE_FILE" down
docker compose -p "$PROJECT_NAME" -f "$COMPOSE_FILE" up -d

168
main.py Normal file → Executable file
View File

@@ -1,38 +1,170 @@
#!/usr/bin/env python3
import os
import json
from time import sleep
import requests
import config
DOCKER_COMPOSE_PATH = config.portainer_compose_dir + '/{}/docker-compose.yml'
BASH_COMPANION_SCRIPT = os.getcwd() + '/docker_updater.sh'
REPO_UPDATER_SCRIPT = os.getcwd() + '/update_repo.sh'
SCRIPT_DIR = os.path.dirname(__file__)
BASH_COMPANION_SCRIPT = f'{SCRIPT_DIR}/docker_updater.sh'
REPO_UPDATER_SCRIPT = f'{SCRIPT_DIR}/update_repos.sh'
class PortainerClient:
def __init__(self, portainer_url, access_token):
self.portainer_url = portainer_url
self.access_token = access_token
self.api_url = f"{self.portainer_url}/api"
def _get_headers(self):
return {
"accept": "application/json",
"X-API-Key": self.access_token
}
def _get(self, uri):
response = requests.get(f"{self.api_url}/{uri}", headers=self._get_headers())
return response.json()
def list_endpoints(self):
return self._get("endpoints")
def list_stacks(self, endpoint_id=None):
filters = f'?filters={{"EndpointID":{endpoint_id}}}' if endpoint_id else ""
return self._get(f'stacks{filters}')
def get_stack(self, stack_id):
return self._get(f'stacks/{stack_id}')
def get_stack_file(self, stack_id):
return self._get(f'stacks/{stack_id}/file')
def _put(self, uri, payload):
response = requests.put(
f"{self.api_url}/{uri}",
headers=self._get_headers(),
json=payload
)
return response.json()
def upgrade_stack(self, stack):
file_response = self.get_stack_file(stack['Id'])
payload = {
"pullImage": True,
"prune": True,
"StackFileContent": file_response["StackFileContent"],
"Env": stack["Env"]
}
return self._put(f"stacks/{stack['Id']}?endpointId={stack['EndpointId']}", payload)
def list_stack_containers(self, endpoint, stack_name):
filters = '{"label":["com.docker.compose.project=' + stack_name + '"]}'
return self._get(f'endpoints/{endpoint}/docker/containers/json?all=1&filters={filters}')
def update_image(self, endpoint, image):
response = requests.post(
f"{self.api_url}/endpoints/{endpoint}/docker/images/create?fromImage={image}",
headers=self._get_headers()
)
try:
status = json.loads(response.text.strip().split('\r\n')[-1])['status']
except Exception:
return False
return f"Downloaded newer image for {image}" in status
def delete_image(self, endpoint, image):
response = requests.delete(
f"{self.api_url}/endpoints/{endpoint}/docker/images/{image}?force=false",
headers=self._get_headers()
)
def ping_server(self):
try:
r = requests.get(f"{self.portainer_url}/", headers=self._get_headers())
except requests.exceptions.ConnectionError:
return False
return r.status_code == 200
def update_repositories():
for repo_path in config.git_repositories:
os.system(f"{REPO_UPDATER_SCRIPT} {repo_path}")
ret = os.system(f"{REPO_UPDATER_SCRIPT} {repo_path}")
if ret > 0:
exit(ret)
def update_stack(stack):
def update_stack_images(endpoint, stack_name):
containers = client.list_stack_containers(endpoint, stack_name)
updated_images = []
for c in containers:
if client.update_image(endpoint, c['Image']):
print(f"{c['Image']} was updated.")
updated_images.append(c['ImageID'])
return updated_images
def upgrade_local_stack(stack):
project_name = stack['Name']
print(f"Updating stack: {project_name}")
compose_file = DOCKER_COMPOSE_PATH.format(stack['Id'])
os.system(f"{BASH_COMPANION_SCRIPT} {project_name} {compose_file}")
ret = os.system(f"{BASH_COMPANION_SCRIPT} {project_name} {compose_file}")
if ret > 0:
exit(ret)
print(f"Stack {project_name} updated!\n")
def upgrade_endpoint_stack(client, endpoint):
endpoint_id = endpoint['Id']
endpoint_updated_images = []
for stack in client.list_stacks(endpoint_id):
if stack['Status'] == 1:
stack_name = stack['Name']
print(f"Checking {stack_name} for updates.")
updated_images = update_stack_images(endpoint_id, stack_name)
if updated_images:
endpoint_updated_images += updated_images
print(f"{stack_name} was updated. Restarting...")
if endpoint_id == 2 and stack_name in ('portainer', 'traefik'):
upgrade_local_stack(stack)
while not client.ping_server():
print("Waiting for server to be back online...")
sleep(1)
else:
client.upgrade_stack(stack)
print(f"{stack_name} restarted.")
else:
print(f"{stack_name} is up to date. Skipping.")
if endpoint_updated_images:
print("Deleting obsolete images")
for image_id in endpoint_updated_images:
client.delete_image(endpoint_id, image_id)
else:
print("No obsolete image to delete")
if __name__ == '__main__':
headers = {
"accept": "application/json",
"X-API-Key": config.access_token
}
update_repositories()
api_url = f"{config.portainer_url}/api/stacks"
response = requests.get(api_url, headers=headers)
stack_list = response.json()
client = PortainerClient(portainer_url=config.portainer_url, access_token=config.access_token)
for s in stack_list:
if s['Status'] == 1:
update_stack(s)
os.system('docker image prune -fa')
for e in client.list_endpoints():
upgrade_endpoint_stack(client, e)

View File

@@ -1,6 +1,6 @@
REPO_PATH=$1
git -C $REPO_PATH fetch --tags
LAST_HASH=`git -C $REPO_PATH rev-list --tags --max-count=1`
LAST_TAG=`git -C $REPO_PATH describe --tags $LAST_HASH`
git -C $REPO_PATH checkout $LAST_TAG
git -C "$REPO_PATH" fetch --tags
LAST_HASH=$(git -C "$REPO_PATH" rev-list --tags --max-count=1)
LAST_TAG=$(git -C "$REPO_PATH" describe --tags "$LAST_HASH")
git -C "$REPO_PATH" checkout "$LAST_TAG"