Skip to content

Commit

Permalink
feat: admyral CLI up/down command with docker-compose
Browse files Browse the repository at this point in the history
  • Loading branch information
danielgrittner committed Aug 20, 2024
1 parent ba227e7 commit 347d653
Show file tree
Hide file tree
Showing 8 changed files with 227 additions and 81 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ __pycache__
.env

.ruff_cache

deploy/docker-compose/volumes
4 changes: 1 addition & 3 deletions admyral/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
action,
push as action_push,
)
from admyral.cli.server import up, down, show, logs
from admyral.cli.server import up, down
from admyral.cli.setup import init
from admyral.cli.workflow import workflow, push as workflow_push, trigger
from admyral.cli.secret import secret, set, delete, list
Expand All @@ -12,8 +12,6 @@
"action_push",
"up",
"down",
"show",
"logs",
"init",
"workflow",
"workflow_push",
Expand Down
202 changes: 141 additions & 61 deletions admyral/cli/server.py
Original file line number Diff line number Diff line change
@@ -1,87 +1,167 @@
import click
import asyncio
import os
import subprocess

from admyral.cli.cli import cli
from admyral.services.admyral import (
launch_admyral_blocking,
launch_admyral_daemon,
destroy_admyral_daemon,
show_logs,
show_status,
from admyral.utils.docker_utils import (
is_docker_running,
get_docker_compose_cmd,
list_running_docker_containers,
)
from admyral.config.config import get_local_storage_path


WELCOME_MESSAGE = """
_ __ __ __
| | / /__ / /________ ____ ___ ___ / /_____
| | /| / / _ \\/ / ___/ __ \\/ __ `__ \\/ _ \\ / __/ __ \\
| |/ |/ / __/ / /__/ /_/ / / / / / / __/ / /_/ /_/ /
|__/|__/\\___/_/\\___/\\____/_/ /_/ /_/\\___/ _\\__/\\____/
/ | ____/ /___ ___ __ ___________ _/ /
/ /| |/ __ / __ `__ \\/ / / / ___/ __ `/ /
/ ___ / /_/ / / / / / / /_/ / / / /_/ / /
/_/ |_\\__,_/_/ /_/ /_/\\__, /_/ \\__,_/_/
/____/
"""


def _get_docker_compose_dir_path() -> str:
if os.path.exists(os.path.join(os.path.dirname(__file__), "docker_compose")):
docker_compose_dir_path = os.path.join(
os.path.dirname(__file__), "docker_compose"
)
else:
docker_compose_dir_path = os.path.join(
os.path.dirname(__file__), "..", "..", "deploy", "docker-compose"
)
assert os.path.exists(docker_compose_dir_path)
return docker_compose_dir_path


@cli.command("up", help="Start Admyral locally.")
@click.option(
"--docker",
"-d",
is_flag=True,
default=False,
type=click.BOOL,
help="Run dockerized Admyral instead of as a local process.",
)
@click.option(
"--blocking",
"-b",
is_flag=True,
default=False,
help="Run Admyral in blocking mode.",
)
def up(docker: bool, blocking: bool) -> None:
def up() -> None:
"""
Launches Admyral services locally.
Args:
docker: Run Admyral as docker containers.
blocking: Run Admyral in blocking mode.
"""
if docker:
# Run Admyral as docker containers
# TODO: add docker support
click.echo("Docker mode is not yet implemented.")
click.echo(WELCOME_MESSAGE)

if not is_docker_running():
click.echo(
"Docker daemon is not running. Please make sure that Docker is installed and running."
)
return

if not blocking:
# Run Admyral as a daemon
launch_admyral_daemon()
# check if container is already running
running_containers = list_running_docker_containers()
if set(running_containers) & {
"admyral-web",
"admyral-worker",
"admyral-api",
"temporal-admin-tools",
"temporal-ui",
"temporal",
"postgresql",
"temporal-elasticsearch",
}:
click.echo("Admyral is already running.")
click.echo(
"You can access the Admyral UI at http://localhost:3000 or use the Admyral CLI.\n"
)
return

# figure out the path of the docker-compose directory
docker_compose_dir_path = _get_docker_compose_dir_path()

command = get_docker_compose_cmd()
command.append("up")
command.append("-d")

env = os.environ.copy()
if openai_api_key := os.environ.get("OPENAI_API_KEY"):
env["OPENAI_API_KEY"] = openai_api_key
else:
click.echo(
'Warning: OPENAI_API_KEY environment variable is not set. The "ai_action" action will not work!'
)
if (
os.environ.get("RESEND_API_KEY") is None
or os.environ.get("RESEND_EMAIL") is None
):
click.echo(
'Warning: RESEND_API_KEY or RESEND_EMAIL environment variables are not set. The "send_email" action will not work!'
)
else:
env["RESEND_API_KEY"] = os.environ.get("RESEND_API_KEY")
env["RESEND_EMAIL"] = os.environ.get("RESEND_EMAIL")

# Set persistance path
env["POSTGRES_VOLUME_PATH"] = os.path.join(get_local_storage_path(), "postgres")

click.echo("\nStarting Admyral...\n")

try:
subprocess.run(command, check=True, cwd=docker_compose_dir_path, env=env)
except subprocess.CalledProcessError as e:
click.echo(f"Command failed with error: {e}")
return

# Run Admyral in blocking mode
asyncio.run(launch_admyral_blocking())
click.echo("\nAdmyral is up and running.")
click.echo(
"You can access the Admyral UI at http://localhost:3000 or use the Admyral CLI.\n"
)


@cli.command("down", help="Stop Admyral locally.")
def down() -> None:
"""
Tears down Admyral services locally.
"""
# TODO: add docker support
destroy_admyral_daemon()
if not is_docker_running():
click.echo(
"Docker daemon is not running. Please make sure that Docker is installed and running."
)
return

docker_compose_dir_path = _get_docker_compose_dir_path()
command = get_docker_compose_cmd()
command.append("down")

@cli.command("show", help="Show information about Admyral.")
def show() -> None:
"""
Show information about Admyral.
"""
show_status()
click.echo("\nShutting Admyral down...\n")

try:
subprocess.run(command, check=True, cwd=docker_compose_dir_path)
except subprocess.CalledProcessError as e:
click.echo(f"Command failed with error: {e}")
return

click.echo("\nAdmyral is shut down.\n")

@cli.command("logs", help="Display logs for Admyral.")
@click.option("--follow", "-f", is_flag=True, help="Follow the logs.")
@click.option(
"--tail",
"-t",
type=click.INT,
help="Number of lines to display from the end of the logs.",
)
def logs(follow: bool, tail: int) -> None:
"""
Display logs for Admyral.

Args:
follow: Follow the logs.
tail: Number of lines to display from the end of the logs.
"""
for log in show_logs(follow, tail):
click.echo(log)
# @cli.command("show", help="Show information about Admyral.")
# def show() -> None:
# """
# Show information about Admyral.
# """
# show_status()


# @cli.command("logs", help="Display logs for Admyral.")
# @click.option("--follow", "-f", is_flag=True, help="Follow the logs.")
# @click.option(
# "--tail",
# "-t",
# type=click.INT,
# help="Number of lines to display from the end of the logs.",
# )
# def logs(follow: bool, tail: int) -> None:
# """
# Display logs for Admyral.

# Args:
# follow: Follow the logs.
# tail: Number of lines to display from the end of the logs.
# """
# for log in show_logs(follow, tail):
# click.echo(log)
29 changes: 29 additions & 0 deletions admyral/utils/docker_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from docker import DockerClient
import shutil


def is_docker_running() -> bool:
"""
Check for running Docker daemon.
Returns:
bool: True if Docker daemon is running, False otherwise.
"""
try:
docker_client = DockerClient.from_env()
docker_client.ping()
return True
except Exception:
return False


def get_docker_compose_cmd() -> list[str]:
if shutil.which("docker-compose") is not None:
return ["docker-compose"]
return ["docker", "compose"]


def list_running_docker_containers() -> list[str]:
docker_client = DockerClient.from_env()
containers = docker_client.containers.list()
return [container.name for container in containers]
1 change: 1 addition & 0 deletions deploy/docker-compose/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ POSTGRES_DB=postgres
POSTGRES_PORT=5432
POSTGRES_USER=postgres

POSTGRES_VOLUME_PATH=./volumes/db

TEMPORAL_VERSION=latest
TEMPORAL_ADMINTOOLS_VERSION=latest
Expand Down
32 changes: 16 additions & 16 deletions deploy/docker-compose/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ services:
postgresql:
container_name: postgresql
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_USER: ${POSTGRES_USER}
image: postgres:${POSTGRES_VERSION}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-your-super-secret-and-long-postgres-password}
POSTGRES_USER: ${POSTGRES_USER:-postgres}
image: postgres:${POSTGRES_VERSION:-16.4-bookworm}
healthcheck:
test: ["CMD-SHELL", "pg_isready", "-d", "temporal"]
interval: 30s
Expand All @@ -23,7 +23,7 @@ services:
expose:
- 5432
volumes:
- /var/lib/postgresql/data
- ${POSTGRES_VOLUME_PATH}:/var/lib/postgresql/data

admyral-api:
container_name: admyral-api
Expand All @@ -48,11 +48,11 @@ services:
environment:
# DB
- ADMYRAL_DATABASE_TYPE=postgres
- ADMYRAL_DATABASE_URL=postgresql+asyncpg://postgres:${POSTGRES_PASSWORD}@postgresql:5432/admyral
- ADMYRAL_DATABASE_URL=postgresql+asyncpg://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-your-super-secret-and-long-postgres-password}@postgresql:5432/admyral
- ADMYRAL_SECRETS_MANAGER_TYPE=sql
# App Secrets
- ADMYRAL_WEBHOOK_SIGNING_SECRET=${ADMYRAL_WEBHOOK_SIGNING_SECRET}
- ADMYRAL_SECRETS_ENCRYPTION_KEY=${ADMYRAL_SECRETS_ENCRYPTION_KEY}
- ADMYRAL_WEBHOOK_SIGNING_SECRET=${ADMYRAL_WEBHOOK_SIGNING_SECRET:-e179017a-62b0-4996-8a38-e91aa9f1}
- ADMYRAL_SECRETS_ENCRYPTION_KEY=${ADMYRAL_SECRETS_ENCRYPTION_KEY:-0123456789abcdef0123456789abcdef}
# Temporal
- ADMYRAL_TEMPORAL_HOST=temporal:7233

Expand Down Expand Up @@ -88,17 +88,17 @@ services:
# https://stackoverflow.com/questions/39663096/docker-compose-creating-multiple-instances-for-the-same-image
deploy:
mode: replicated
replicas: ${ADMYRAL_WORKER_REPLICAS}
replicas: ${ADMYRAL_WORKER_REPLICAS:-1}
environment:
# Integrations
- OPENAI_API_KEY=${OPENAI_API_KEY}
- RESEND_API_KEY=${RESEND_API_KEY}
# DB
- ADMYRAL_DATABASE_TYPE=postgres
- ADMYRAL_DATABASE_URL=postgresql+asyncpg://postgres:${POSTGRES_PASSWORD}@postgresql:5432/admyral
- ADMYRAL_DATABASE_URL=postgresql+asyncpg://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-your-super-secret-and-long-postgres-password}@postgresql:5432/admyral
- ADMYRAL_SECRETS_MANAGER_TYPE=sql
# App Secrets
- ADMYRAL_SECRETS_ENCRYPTION_KEY=${ADMYRAL_SECRETS_ENCRYPTION_KEY}
- ADMYRAL_SECRETS_ENCRYPTION_KEY=${ADMYRAL_SECRETS_ENCRYPTION_KEY:-0123456789abcdef0123456789abcdef}
# Temporal
- ADMYRAL_TEMPORAL_HOST=temporal:7233

Expand All @@ -116,7 +116,7 @@ services:
- discovery.type=single-node
- ES_JAVA_OPTS=-Xms256m -Xmx256m
- xpack.security.enabled=false
image: elasticsearch:${ELASTICSEARCH_VERSION}
image: elasticsearch:${ELASTICSEARCH_VERSION:-latest}
healthcheck:
test: curl -s http://localhost:9200 >/dev/null || exit 1
interval: 30s
Expand All @@ -134,14 +134,14 @@ services:
environment:
- DB=postgres12
- DB_PORT=5432
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PWD=${POSTGRES_PASSWORD}
- POSTGRES_USER=${POSTGRES_USER:-postgres}
- POSTGRES_PWD=${POSTGRES_PASSWORD:-your-super-secret-and-long-postgres-password}
- POSTGRES_SEEDS=postgresql
- DYNAMIC_CONFIG_FILE_PATH=config/dynamicconfig/development-sql.yml
- ENABLE_ES=true
- ES_SEEDS=elasticsearch
- ES_VERSION=v7
image: temporalio/auto-setup:${TEMPORAL_VERSION}
image: temporalio/auto-setup:${TEMPORAL_VERSION:-latest}
healthcheck:
test: ["CMD", "tctl", "--address", "temporal:7233", "workflow", "list"]
interval: 1s
Expand Down Expand Up @@ -169,7 +169,7 @@ services:
environment:
- TEMPORAL_ADDRESS=temporal:7233
- TEMPORAL_CLI_ADDRESS=temporal:7233
image: temporalio/admin-tools:${TEMPORAL_ADMINTOOLS_VERSION}
image: temporalio/admin-tools:${TEMPORAL_ADMINTOOLS_VERSION:-latest}
networks:
- admyral-network
stdin_open: true
Expand All @@ -183,7 +183,7 @@ services:
environment:
- TEMPORAL_ADDRESS=temporal:7233
- TEMPORAL_CORS_ORIGINS=http://localhost:3000
image: temporalio/ui:${TEMPORAL_UI_VERSION}
image: temporalio/ui:${TEMPORAL_UI_VERSION:-latest}
networks:
- admyral-network
ports:
Expand Down
Loading

0 comments on commit 347d653

Please sign in to comment.