Automated portainer deployment implemented

This commit is contained in:
Cian Hughes
2024-01-10 17:01:18 +00:00
parent 794cbb0000
commit 5fb70cae60
7 changed files with 47 additions and 192 deletions

2
.gitignore vendored
View File

@@ -198,7 +198,7 @@ cython_debug/
# Ignore dynamically constructed swagger APIs # Ignore dynamically constructed swagger APIs
.swagger-codegen* .swagger-codegen*
src/portainer/* src/portainer_api/*
# These files do need to be included though: # These files do need to be included though:
!src/portainer/pyproject.toml !src/portainer/pyproject.toml
!src/portainer/build.py !src/portainer/build.py

49
deploy
View File

@@ -4,6 +4,9 @@ from typing import Optional
import typer # type: ignore import typer # type: ignore
import subprocess import subprocess
import portainer # type: ignore import portainer # type: ignore
import docker # type: ignore
import tomllib
from pathlib import Path
def docker_deploy_core(stack_name: Optional[str] = "core"): def docker_deploy_core(stack_name: Optional[str] = "core"):
@@ -11,19 +14,53 @@ def docker_deploy_core(stack_name: Optional[str] = "core"):
subprocess.run(["docker", "stack", "deploy", "-c", "docker-compose.yaml", stack_name]) 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 docker_deploy_stack(username: str, password: str, stack_name: Optional[str] = "stack"): def docker_deploy_stack(username: str, password: str, stack_name: Optional[str] = "stack"):
"""Deploys the stack using the portainer api, to allow for complete control over the stack""" """Deploys the stack using the portainer api, to allow for complete control over the stack"""
breakpoint() #! TODO: Implement way of using portainer api to deploy stack # 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
# Then, deploy the stack using the API
stacks = portainer.StacksApi(client)
stacks.stack_create_docker_swarm_repository(
endpoint_id=endpoint_id,
body = portainer.StacksSwarmStackFromGitRepositoryPayload(
auto_update=portainer.PortainerAutoUpdateSettings(
interval="60m",
),
name=stack_name,
compose_file="stack.yaml",
swarm_id=docker.from_env().swarm.id,
repository_url=fetch_repository_url(),
)
)
# breakpoint() #! TODO: Implement way of using portainer api to deploy stack
# subprocess.run(["docker", "stack", "deploy", "-c", "stack.yaml", stack_name]) # subprocess.run(["docker", "stack", "deploy", "-c", "stack.yaml", stack_name])
def docker_deploy_all(core_name: Optional[str] = "core", stack_name: Optional[str] = "stack"): def docker_deploy_all(username: str, password: str, core_name: Optional[str] = "core", stack_name: Optional[str] = "stack"):
"""Deploys the core services and the stack""" """Deploys the core services and the stack"""
docker_deploy_core(core_name) docker_deploy_core(core_name)
docker_deploy_stack(stack_name) docker_deploy_stack(username, password, stack_name)
if __name__ == "__main__": if __name__ == "__main__":
# typer.run(docker_deploy_all) typer.run(docker_deploy_all)
# docker_deploy_core("core")
typer.run(docker_deploy_stack)

View File

@@ -4,15 +4,16 @@ version = "0.1.0"
description = "" description = ""
authors = ["Cian Hughes <cian.hughes@dcu.ie>"] authors = ["Cian Hughes <cian.hughes@dcu.ie>"]
readme = "README.md" readme = "README.md"
repository = "https://github.com/Cian-H/I-Form_Research_Server_Stack"
packages = [ packages = [
{ include = "deploy", from = "." }, { include = "deploy", from = "." },
# { include = "portainer", from = "src" },
] ]
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = "^3.11" python = "^3.11"
typer = "^0.9.0" typer = "^0.9.0"
portainer = {path = "src/portainer"} portainer = {path = "src/portainer_api"}
docker = "^7.0.0"
[tool.poetry.group.dev.dependencies] [tool.poetry.group.dev.dependencies]
snoop = "^0.4.3" snoop = "^0.4.3"

View File

@@ -1,50 +0,0 @@
from pathlib import Path
import requests # type: ignore
import docker # type: ignore
def fetch_portainer_api_spec(version: str):
"""Gets and caches the portainer api spec for a given version"""
if not Path(f".cache/portainer_api/{version}/swagger.json").exists():
Path(f".cache/portainer_api/{version}").mkdir(parents=True, exist_ok=True)
json_request = requests.get(f"https://api.swaggerhub.com/apis/portainer/portainer-ce/{version}/swagger.json")
json_request.raise_for_status()
with open(f".cache/portainer_api/{version}/swagger.json", "w") as f:
f.write(json_request.text)
def build_portainer_api(version: str):
"""Builds the portainer api"""
# Path("src/portainer_api").mkdir(parents=True, exist_ok=True)
docker_client = docker.from_env()
docker_client.containers.run(
"swaggerapi/swagger-codegen-cli",
"generate -i /schemas/swagger.json -l python -o /local -D packageName=portainer",
mounts = [
docker.types.Mount(
target="/local",
source=str(Path().resolve()),
type="bind"
),
docker.types.Mount(
target="/schemas",
source=str(Path(f".cache/portainer_api/{version}").resolve()),
type="bind"
),
],
auto_remove=True,
)
def init_portainer_api():
c = docker.from_env().containers.run("portainer/portainer-ce:latest", detach=True, tty=True, remove=True)
portainer_version = c.exec_run("/portainer --version").output.decode().strip()
c.stop()
fetch_portainer_api_spec(portainer_version)
build_portainer_api(portainer_version)
def build():
init_portainer_api()
if __name__ == "__main__":
build()

View File

@@ -1,24 +0,0 @@
[tool.poetry]
name = "portainer"
version = "0.1.0"
description = ""
authors = ["Cian Hughes <cian.hughes@dcu.ie>"]
[tool.poetry.dependencies]
python = "^3.11"
requests = "^2.31.0"
docker = "^7.0.0"
[tool.poetry.build]
script = "build.py"
generate-setup-file = false
[build-system]
requires = ["poetry-core", "setuptools", "requests", "docker"]
build-backend = "poetry.core.masonry.api"
[project]
dynamic = ["dependencies"]
[tool.setuptools.dynamic]
dependencies = {file = ["requirements.txt"]}

View File

@@ -1,87 +0,0 @@
from typing import Dict, Any
from pathlib import Path
import requests # type: ignore
import docker # type: ignore
def portainer_login(username: str, password: str) -> Dict[str, Any]:
"""Logs into portainer and returns a session and the portainer version"""
auth_login = {"username": username, "password": password}
auth_response = requests.post("http://127.0.0.1:9000/api/auth", json=auth_login)
auth_response.raise_for_status()
auth_token = auth_response.json()["jwt"]
session = requests.Session()
session.headers.update({"Authorization": f"Bearer {auth_token}"})
portainer_response = session.get("http://127.0.0.1:9000/api/system/version")
portainer_response.raise_for_status()
portainer_version = portainer_response.json()["ServerVersion"]
return {"session": session, "version": portainer_version, "auth_token": auth_token}
def fetch_portainer_api_spec(version: str): # -> Dict[str, Any]:
"""Gets and caches the portainer api spec for a given version"""
if not Path(f".cache/portainer_api/{version}/swagger.json").exists():
Path(f".cache/portainer_api/{version}").mkdir(parents=True, exist_ok=True)
json_request = requests.get(f"https://api.swaggerhub.com/apis/portainer/portainer-ce/{version}/swagger.json")
json_request.raise_for_status()
with open(f".cache/portainer_api/{version}/swagger.json", "w") as f:
f.write(json_request.text)
# with open(f".cache/portainer_api/{version}/swagger.json") as f:
# return f.read()
# def init_portainer_api(version: str, auth_token: str): # -> openapi.Swagger:
# """
# Returns a swagger api for portainer which follows the schema outlined here:
# https://app.swaggerhub.com/apis/portainer/portainer-ce/2.19.4
# """
# # spec = get_portainer_api_spec(version)
# # api = openapi.loads(spec)
# # api.validate()
# # return api
# app = App.create(f"https://api.swaggerhub.com/apis/portainer/portainer-ce/{version}/swagger.json")
# auth = Security(app)
# auth.update_with("ApiKeyAuth", auth_token)
# client = Client(auth)
# return app, client
# def create_portainer_api(version: str, auth_token: str):
# spec = get_portainer_api_spec(version)
# api = openapi.loads(spec)
# api.validate()
# return api
def build_portainer_api(version: str):
"""Builds the portainer api"""
# Path("src/portainer_api").mkdir(parents=True, exist_ok=True)
docker_client = docker.from_env()
docker_client.containers.run(
"swaggerapi/swagger-codegen-cli",
"generate -i /schemas/swagger.json -l python -o /local -D packageName=portainer",
mounts = [
docker.types.Mount(
target="/local",
source=str(Path().resolve()),
type="bind"
),
docker.types.Mount(
target="/schemas",
source=str(Path(f".cache/portainer_api/{version}").resolve()),
type="bind"
),
],
auto_remove=True,
)
def init_portainer_api(username: str, password: str):
portainer_session = portainer_login(username, password)
fetch_portainer_api_spec(portainer_session["version"])
build_portainer_api(portainer_session["version"])
def build():
init_portainer_api("admin", "%a7DtJkb&^fG%aZKGNvga5V&yBU$#UYBfGjU*pu!v2HS288&kwbR7Gpd@A5MjWr2")
if __name__ == "__main__":
build()
# import portainer # noqa: F401

View File

@@ -1,22 +0,0 @@
[tool.poetry]
name = "portainer"
version = "0.1.0"
description = ""
authors = ["Cian Hughes <cian.hughes@dcu.ie>"]
# packages = [
# { include = "src" },
# ]
[tool.poetry.dependencies]
python = "^3.11"
requests = "^2.31.0"
docker = "^7.0.0"
six = "^1.16.0"
[tool.poetry.build]
script = "build.py"
generate-setup-file = false
[build-system]
requires = ["poetry-core", "setuptools", "requests", "docker"]
build-backend = "poetry.core.masonry.api"