Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file added examples/__init__.py
Empty file.
15 changes: 15 additions & 0 deletions examples/aptible.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
PROCFILE = """
web: exec bundle exec unicorn -c config/unicorn.rb -p $PORT
crons: exec supercronic -passthrough-logs crontabs/crontab
sidekiq: exec bundle exec sidekiq -q default
"""

APTIBLE_YAML = """
before_deploy:
- if [ -n "$DB_CREATE" ]; then bundle exec rake db:create; fi
- if [ -n "$RUN_MIGRATIONS" ]; then bundle exec rake db:migrate; fi
after_deploy_success:
- ./alert_slack_success.sh
after_deploy_failure:
- ./alert_slack_failure.sh
"""
191 changes: 191 additions & 0 deletions examples/github_actions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
DEPROVISION_APP = """
jobs:
deprovision-app:
name: Deprovision Review App and Databases
runs-on: ubuntu-24.04
steps:
- name: Install aptible CLI
run: |
PKG="$(mktemp)"
curl -fsSL "${{ env.CLI_URL }}" > "$PKG"
sudo dpkg -i "$PKG"
rm "$PKG"
aptible login --email "${{ secrets.APTIBLE_ROBOT_USERNAME }}" --password "${{ secrets.APTIBLE_ROBOT_PASSWORD }}"

- name: Deprovision app
run: |
aptible apps:deprovision --app ${{ vars.APTIBLE_APP_NAME }} --environment ${{ vars.APTIBLE_ENVIRONMENT_NAME }} || exit 0
"""

PROVISION_APP = """
jobs:
deprovision-app:
name: Deprovision Review App and Databases
runs-on: ubuntu-24.04
steps:
- name: Install aptible CLI
run: |
PKG="$(mktemp)"
curl -fsSL "${{ env.CLI_URL }}" > "$PKG"
sudo dpkg -i "$PKG"
rm "$PKG"
aptible login --email "${{ secrets.APTIBLE_ROBOT_USERNAME }}" --password "${{ secrets.APTIBLE_ROBOT_PASSWORD }}"
- name: Find or create API App
run: aptible apps:create --environment ${{ vars.APTIBLE_ENVIRONMENT_NAME }} ${{ vars.APTIBLE_APP_NAME }} || exit 0
"""

CONFIGURE_APP = """
jobs:
deprovision-app:
name: Deprovision Review App and Databases
runs-on: ubuntu-24.04
steps:
- name: Install aptible CLI
run: |
PKG="$(mktemp)"
curl -fsSL "${{ env.CLI_URL }}" > "$PKG"
sudo dpkg -i "$PKG"
rm "$PKG"
aptible login --email "${{ secrets.APTIBLE_ROBOT_USERNAME }}" --password "${{ secrets.APTIBLE_ROBOT_PASSWORD }}"
- name: Configure app
run: |
aptible config:set --environment ${{ vars.APTIBLE_ENVIRONMENT_NAME }} --app ${{ env.APTIBLE_API_APP_NAME }} \
DATABASE_URL="${{ env.PG_DATABASE_URL }}" \
CELERY_BROKER_URL="${{ env.REDIS_DATABASE_URL }}" \
FORCE_SSL=true \
SECRET_KEY=${{ secrets.SECRET_KEY }} \
FIELD_ENCRYPTION_KEYS=${{ secrets.FIELD_ENCRYPTION_KEYS }} \
IDLE_TIMEOUT=90
"""

DEPROVISION_DATABASE = """
jobs:
deprovision-app:
name: Deprovision Review App and Databases
runs-on: ubuntu-24.04
steps:
- name: Install aptible CLI
run: |
PKG="$(mktemp)"
curl -fsSL "${{ env.CLI_URL }}" > "$PKG"
sudo dpkg -i "$PKG"
rm "$PKG"
aptible login --email "${{ secrets.APTIBLE_ROBOT_USERNAME }}" --password "${{ secrets.APTIBLE_ROBOT_PASSWORD }}"
- name: Deprovision database
run: |
aptible db:deprovision ${{ vars.DATABASE_NAME }} --environment ${{ vars.APTIBLE_ENVIRONMENT_NAME }} || exit 0
"""

PROVISION_DATABASE = """
jobs:
build-publish-deploy:
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Install aptible CLI
run: |
PKG="$(mktemp)"
curl -fsSL "${{ env.CLI_URL }}" > "$PKG"
sudo dpkg -i "$PKG"
rm "$PKG"
aptible login --email "${{ secrets.APTIBLE_ROBOT_USERNAME }}" --password "${{ secrets.APTIBLE_ROBOT_PASSWORD }}"
- name: Create PostgreSQL database
run: |
aptible db:create "${{ vars.DATABASE_NAME }}" --environment ${{ vars.APTIBLE_ENVIRONMENT_NAME }} --type postgresql || true
- name: Set PostgreSQL database env var
run: |
PG_DATABASE_URL=$(APTIBLE_OUTPUT_FORMAT=json aptible db:list --environment ${{ vars.APTIBLE_ENVIRONMENT_NAME }} | jq -r '.[] | select(.handle=="${{ vars.DATABASE_NAME }}")| .connection_url')
echo "PG_DATABASE_URL=$PG_DATABASE_URL" >> $GITHUB_ENV || exit 0
"""

RESTORE_FROM_BACKUP = """
jobs:
build-publish-deploy:
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Install aptible CLI
run: |
PKG="$(mktemp)"
curl -fsSL "${{ env.CLI_URL }}" > "$PKG"
sudo dpkg -i "$PKG"
rm "$PKG"
aptible login --email "${{ secrets.APTIBLE_ROBOT_USERNAME }}" --password "${{ secrets.APTIBLE_ROBOT_PASSWORD }}"

- name: Get most recent backup
run: |
BACKUP_ID=$(aptible backup:list ${{ vars.DATABASE_NAME }} --environment ${{ vars.APTIBLE_ENVIRONMENT_NAME }} | head -n 1 | awk -F ':' '{print $1}')
aptible backup:restore ${BACKUP_ID} --handle ${{ vars.DATABASE_NAME }}-restore
"""

PROVISION_ENDPOINT = """
jobs:
deprovision-app:
name: Deprovision Review App and Databases
runs-on: ubuntu-24.04
steps:
- name: Install aptible CLI
run: |
PKG="$(mktemp)"
curl -fsSL "${{ env.CLI_URL }}" > "$PKG"
sudo dpkg -i "$PKG"
rm "$PKG"
aptible login --email "${{ secrets.APTIBLE_ROBOT_USERNAME }}" --password "${{ secrets.APTIBLE_ROBOT_PASSWORD }}"
- name: Create app endpoint
run: |
aptible endpoints:https:create --environment ${{ vars.APTIBLE_ENVIRONMENT_NAME }} --app ${{ vars.APTIBLE_APP_NAME }} --default-domain "cmd" --ip-whitelist ${{ vars.SAFE_IP_1 }} ${{ vars.SAFE_IP_2 }} || exit 0
"""

BUILD_PUBLISH_DEPLOY = """
jobs:
build-publish-deploy:
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Install aptible CLI
run: |
PKG="$(mktemp)"
curl -fsSL "${{ env.CLI_URL }}" > "$PKG"
sudo dpkg -i "$PKG"
rm "$PKG"
aptible login --email "${{ secrets.APTIBLE_ROBOT_USERNAME }}" --password "${{ secrets.APTIBLE_ROBOT_PASSWORD }}"

- name: Log in to the Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha,prefix=sha-
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
- name: Deploy to Aptible
uses: aptible/aptible-deploy-action@v4
with:
username: ${{ secrets.APTIBLE_ROBOT_USERNAME }}
password: ${{ secrets.APTIBLE_ROBOT_PASSWORD }}
environment: ${{ vars.APTIBLE_ENVIRONMENT_NAME }}
app: ${{ vars.APTIBLE_APP_NAME }}
docker_img: ghcr.io/${{ github.repository }}:${{ github.ref_name }}
private_registry_username: ${{ github.actor }}
private_registry_password: ${{ secrets.GITHUB_TOKEN }}
"""
143 changes: 142 additions & 1 deletion main.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,19 @@
from typing import Any, Dict, List, Optional

from api_client import AptibleApiClient
from examples import aptible, github_actions
from models import (
AccountManager,
App,
AppManager,
Database,
DatabaseManager,
OperationManager,
StackManager,
VhostManager,
)
from models.service import Service, ServiceManager


mcp = FastMCP("aptible")


Expand All @@ -23,6 +24,7 @@
account_manager = AccountManager(api_client)
app_manager = AppManager(api_client)
database_manager = DatabaseManager(api_client)
operation_manager = OperationManager(api_client)
stack_manager = StackManager(api_client)
service_manager = ServiceManager(api_client)
vhost_manager = VhostManager(api_client)
Expand Down Expand Up @@ -412,5 +414,144 @@ async def list_service_vhosts(
return [vhost.model_dump() for vhost in vhosts]


@mcp.tool()
async def get_operations_for_app(
app_handle: str, account_handle: Optional[str] = None
) -> List[Dict[str, Any]]:
"""
Get recent operations for a specific app.
"""
app_data = await get_app(app_handle, account_handle)
if not app_data:
raise Exception(f"App {app_handle} not found.")

app = App.model_validate(app_data)
operations = await operation_manager.get_operations_for_app(app.id)
return operations


@mcp.tool()
async def get_operations_for_database(
database_handle: str, account_handle: Optional[str] = None
) -> List[Dict[str, Any]]:
"""
Get recent operations for a specific database.
"""
database_data = await get_database(database_handle, account_handle)
if not database_data:
raise Exception(f"Database {database_handle} not found.")

database = Database.model_validate(database_data)
operations = await operation_manager.get_operations_for_database(database.id)
return operations


@mcp.tool()
async def get_operations_for_vhost(vhost_id: int) -> List[Dict[str, Any]]:
"""
Get recent operations for a specific vhost/endpoint.
"""
operations = await operation_manager.get_operations_for_vhost(vhost_id)
return operations


@mcp.tool()
async def get_operation_logs(operation_id: int) -> str:
"""
Get logs for a specific operation.
"""
logs = await operation_manager.logs(operation_id)
return logs


@mcp.tool()
async def get_procfile_example() -> str:
"""
Gets an example Procfile for defining app processes.
Keep in mind that 1-off tasks like running migrations are better suited
for processes run via .aptible.yml, and do not belong in the Procfile.


The finalized Procfile file should be located in /.aptible/Procfile
in the build Docker image.
"""
return aptible.PROCFILE


@mcp.tool()
async def get_aptible_yaml_example() -> str:
"""
Gets an example aptible.yml configuration file for deploy hooks.

The finalized .aptible.yml file should be located in /.aptible/.aptible.yml
in the build Docker image.
"""
return aptible.APTIBLE_YAML


@mcp.tool()
async def get_endpoint_provision_example() -> str:
"""
Gets an example of a GitHub Action for provisioning an endpoint.
"""
return github_actions.PROVISION_ENDPOINT


@mcp.tool()
async def get_app_provision_example() -> str:
"""
Gets an example of a GitHub Action for provisioning an app.
"""
return github_actions.PROVISION_APP


@mcp.tool()
async def get_app_deprovision_example() -> str:
"""
Gets an example of a GitHub Action for deprovisioning an app.
"""
return github_actions.DEPROVISION_APP


@mcp.tool()
async def get_app_configure_example() -> str:
"""
Gets an example of a GitHub Action for configuring an app.
"""
return github_actions.CONFIGURE_APP


@mcp.tool()
async def get_database_provision_example() -> str:
"""
Gets an example of a GitHub Action for provisioning a database.
"""
return github_actions.PROVISION_DATABASE


@mcp.tool()
async def get_database_deprovision_example() -> str:
"""
Gets an example of a GitHub Action for deprovisioning a database.
"""
return github_actions.DEPROVISION_DATABASE


@mcp.tool()
async def get_database_restore_example() -> str:
"""
Gets an example of a GitHub Action for restoring a database from backup.
"""
return github_actions.RESTORE_FROM_BACKUP


@mcp.tool()
async def get_build_deploy_example() -> str:
"""
Gets an example of a GitHub Action for building, publishing, and deploying an app.
"""
return github_actions.BUILD_PUBLISH_DEPLOY


if __name__ == "__main__":
mcp.run(transport="stdio")
3 changes: 3 additions & 0 deletions models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from models.account import Account, AccountManager
from models.app import App, AppManager
from models.database import Database, DatabaseImage, DatabaseManager
from models.operation import Operation, OperationManager
from models.service import Service, ServiceManager
from models.stack import Stack, StackManager
from models.vhost import Vhost, VhostManager
Expand All @@ -14,6 +15,8 @@
"Database",
"DatabaseImage",
"DatabaseManager",
"Operation",
"OperationManager",
"Service",
"ServiceManager",
"Stack",
Expand Down
Loading