#!/usr/bin/env poetry run python import subprocess from pathlib import Path from typing import Optional import docker # type: ignore import portainer # type: ignore import tomllib import typer # type: ignore def deploy_core(stack_name: Optional[str] = "core"): """Simply deploys the core services""" subprocess.run(["docker", "stack", "deploy", "-c", "docker-compose.yaml", stack_name]) def fetch_repository_url() -> str: """Fetches the repository url from the pyproject.toml file""" with open("pyproject.toml", "rb") as f: return tomllib.load(f)["tool"]["poetry"]["repository"] def portainer_deploy_stack(stack_file: str, stacks: portainer.api.stacks_api.StacksApi, endpoint_id: int, stack_name: Optional[str] = None, **kwargs) -> None: """Deploys the volumes for the stack""" valid_extensions = [".yaml", ".yml"] stack_path = Path(stack_file) if stack_path.suffix not in valid_extensions: for file in (stack_path.with_suffix(ext) for ext in valid_extensions): if file.exists(): stack_path = file break else: raise FileNotFoundError(f"Could not find stack file {stack_file}") stack_name = stack_name or stack_path.stem repository_url = fetch_repository_url() print(f"Deploying stack {stack_name} from {stack_path} @ {repository_url}") scoped_kwargs = { # "auto_update": portainer.PortainerAutoUpdateSettings( # interval="60m", # ), "name": stack_name, "compose_file": str(stack_path), "swarm_id": docker.from_env().swarm.id, "repository_url": repository_url, } scoped_kwargs.update(kwargs) stacks.stack_create_docker_swarm_repository( endpoint_id=endpoint_id, body = portainer.StacksSwarmStackFromGitRepositoryPayload( **scoped_kwargs, ) ) print(f"Stack {stack_name} deployed") def deploy_stack(username: str, password: str, stack_name: Optional[str] = "stack"): """Deploys the stack using the portainer api from the github repo. This allows portainer to have full control over the stack""" print("Deploying stack") print("Logging in to portainer") # Create an API client client = portainer.ApiClient() client.configuration.host = "http://127.0.0.1:9000/api" # Authenticate the client auth = portainer.AuthApi(client) auth_token = auth.authenticate_user( portainer.AuthAuthenticatePayload( username=username, password=password, ), ) client.configuration.api_key["Authorization"] = auth_token.jwt client.configuration.api_key_prefix["Authorization"] = "Bearer" # Get the endpoint ID for the local docker endpoint endpoints = portainer.EndpointsApi(client) endpoint_id = next(filter(lambda e: e.name == "local", endpoints.endpoint_list())).id # Initialize a stacks API stacks = portainer.StacksApi(client) # Then, deploy the substacks using the API print("Deploying substacks via portainer API") # portainer_deploy_stack("secrets", stacks, endpoint_id) portainer_deploy_stack("networks", stacks, endpoint_id) # portainer_deploy_stack("volumes", stacks, endpoint_id) # portainer_deploy_stack("backend", stacks, endpoint_id) # portainer_deploy_stack("frontend", stacks, endpoint_id) print("Stack deployed!") def deploy_all(username: str, password: str, core_name: Optional[str] = "core", stack_name: Optional[str] = "stack"): """Deploys the core services and the stack""" # deploy_core(core_name) deploy_stack(username, password, stack_name) if __name__ == "__main__": typer.run(deploy_all)