api-server: Initial PoC

This commit is contained in:
Kacper Donat 2022-11-14 19:41:46 +01:00
parent 19a8765938
commit fc94212d97
13 changed files with 240 additions and 3 deletions

View File

@ -5,6 +5,7 @@ end_of_line = lf
insert_final_newline = true insert_final_newline = true
indent_style = space indent_style = space
charset = utf-8 charset = utf-8
indent_size = 4
[*.{yaml,yml}] [*.{yaml,yml}]
indent_size = 2 indent_size = 2

51
.gitignore vendored
View File

@ -1 +1,52 @@
/.vagrant/ /.vagrant/
/docker-compose.override.yaml
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Environments
.virtualenv/

29
api/Dockerfile Normal file
View File

@ -0,0 +1,29 @@
FROM python:3.11-alpine
RUN apk --no-cache add ansible openssh-client tini su-exec socat
RUN adduser \
--disabled-password \
--gecos "" \
api-server
USER api-server
WORKDIR /opt/api-server
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
ENV API_PROJECT_DIR=/var/project \
API_GALAXY_REQUIREMENTS=/var/project/galaxy-requirements.yml \
API_PIP_REQUIREMENTS=/var/project/requirements.txt \
API_RUNAS=api-server \
PATH="/home/api-server/.local/bin:${PATH}"
VOLUME [ "${API_PROJECT_DIR}" ]
WORKDIR ${API_PROJECT_DIR}
# switch to root as it must be available
USER root
ENTRYPOINT [ "tini", "--", "/opt/api-server/bin/docker-entrypoint.sh" ]

53
api/api.py Normal file
View File

@ -0,0 +1,53 @@
import asyncio
from os import system
from time import sleep
from typing import Any, Dict
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
from pydantic import BaseModel
import ansible_runner
class DeployArgs(BaseModel):
extra_vars: Dict[str, Any]
app = FastAPI()
@app.post("/deploy/{service}")
async def deploy(service: str, args: DeployArgs):
finished = False
lines = []
def finish_callback(_):
nonlocal finished
finished = True
def event_callback(data: Dict):
if 'stdout' in data:
lines.append(data['stdout'])
async def logs():
nonlocal lines
while not finished:
for line in lines:
yield line.rstrip() + "\n"
lines = []
await asyncio.sleep(0.1)
ansible_runner.run_async(
playbook='deploy.yaml',
extravars={
'services': [service],
**args.extra_vars
},
private_data_dir='/home/api-server',
project_dir='/var/project',
inventory='inventory/m2.ini',
event_handler=event_callback,
finished_callback=finish_callback,
settings={
'suppress_ansible_output': True
}
)
return StreamingResponse(logs(), media_type='text/plain')

View File

@ -0,0 +1,6 @@
#!/bin/sh
if [ -z "$(ls -A ${API_PROJECT_DIR})" ]; then
echo "No files found in project dir, maybe you forgot to mount it?" >&2
exit 1
fi

View File

@ -0,0 +1,16 @@
#!/bin/sh
PROXIED_AUTH_SOCK="${PROXIED_AUTH_SOCK:-/var/run/proxied-ssh-auth.sock}"
if [ -S "${PROXIED_AUTH_SOCK}" ]; then
echo "Found previously not closed ssh auth socket, closing."
rm ${PROXIED_AUTH_SOCK}
fi
# This should be run only when run as is specified and SSH Agent socket is present
if [ -n "$API_RUNAS" ] && [ -S "$SSH_AUTH_SOCK" ]; then
echo "Proxying ${SSH_AUTH_SOCK} -> ${PROXIED_AUTH_SOCK} for ${API_RUNAS%%:*}"
socat UNIX-LISTEN:${PROXIED_AUTH_SOCK},fork,user=${API_RUNAS%%:*},mode=600 \
UNIX-CONNECT:${SSH_AUTH_SOCK} &
export SSH_AUTH_SOCK=/var/run/proxied-ssh-auth.sock
fi

View File

@ -0,0 +1,6 @@
#!/bin/sh
if [ -f "${API_GALAXY_REQUIREMENTS}" ]; then
echo "Installing galaxy stuff from ${API_GALAXY_REQUIREMENTS}"
run-user ansible-galaxy install -r ${API_GALAXY_REQUIREMENTS}
fi

View File

@ -0,0 +1,7 @@
#!/bin/sh
if [ -n "$ANSIBLE_VAULT_PASSWORD" ]; then
export ANSIBLE_VAULT_PASSWORD_FILE=/var/run/secrets/vault-password
echo "$ANSIBLE_VAULT_PASSWORD" > $ANSIBLE_VAULT_PASSWORD_FILE
unset ANSIBLE_VAULT_PASSWORD
fi

46
api/bin/docker-entrypoint.sh Executable file
View File

@ -0,0 +1,46 @@
#!/bin/sh
# fail on first error
set -e
function runuser {
EXEC=0
while true; do
case "$1" in
-E|--exec)
EXEC=1
shift
;;
--)
shift
break
;;
*)
break
;;
esac
done
if [ -n "$API_RUNAS" ]; then
set -- su-exec "$API_RUNAS" "$@"
fi
if [ $EXEC -eq 1 ]; then
exec "$@"
else
"$@"
fi
}
alias run-user=runuser
for part in $(dirname $0)/docker-entrypoint.d/*.sh; do
[ -x $part ] && source $part
done
if [ "${1#-}" != "$1" ] || [ $# -eq 0 ]; then
set -- uvicorn --app-dir /opt/api-server api:app --host ${API_HOST:-0.0.0.0} --port ${API_PORT:-8080} "$@"
fi
run-user --exec -- "$@"

4
api/requirements.txt Normal file
View File

@ -0,0 +1,4 @@
fastapi>=0.68.0,<0.69.0
pydantic>=1.8.0,<2.0.0
uvicorn>=0.15.0,<0.16.0
ansible-runner>=2.3.0,<2.4.0

15
docker-compose.yaml Normal file
View File

@ -0,0 +1,15 @@
version: '3.8'
services:
api:
build: api
image: registry.kadet.net/mgmt/api:${API_VERSION:-latest}
environment:
- SSH_AUTH_SOCK
ports:
- "8080:8080"
command: ['--reload', '--reload-dir', '/opt/api-server']
volumes:
- .:/var/project
- ./api:/opt/api-server
- ${SSH_AUTH_SOCK}:${SSH_AUTH_SOCK}

4
ping.yaml Normal file
View File

@ -0,0 +1,4 @@
- hosts:
- all
tasks:
- action: ping

View File

@ -1 +0,0 @@
docker