mirror of
https://github.com/Cian-H/I-Form_Research_Server_Stack.git
synced 2025-12-23 06:32:07 +00:00
Automated portainer deployment implemented
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -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
49
deploy
@@ -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)
|
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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()
|
|
||||||
@@ -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"]}
|
|
||||||
@@ -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
|
|
||||||
@@ -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"
|
|
||||||
Reference in New Issue
Block a user