Skip to content

Commit

Permalink
Refactor (#42)
Browse files Browse the repository at this point in the history
* feat: move to fastapi and validation fixes

* feat: pick secrets from default path

* feat: update requirements file

* feat: bump requests
  • Loading branch information
tushar5526 authored May 21, 2024
1 parent 369381f commit be08d71
Show file tree
Hide file tree
Showing 10 changed files with 243 additions and 119 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ COPY . .

EXPOSE 5000

CMD [ "flask", "run", "--host=0.0.0.0", "--port=5000"]
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "5000"]
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ local-dev:
echo "\nDEPLOYMENT_HOST='localhost' # DO NOT EDIT THIS" >> .env
bash setup-vault.sh docker-compose-local.yml
echo "\nVAULT_BASE_URL='http://localhost:8200'" >> .env
mkdir deployments nginx-confs
mkdir -p deployments nginx-confs
docker-compose -f docker-compose-local.yml up -d nginx

.PHONY: sarthi
Expand Down
94 changes: 56 additions & 38 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,67 +4,85 @@

import jwt
from dotenv import load_dotenv
from flask import Flask, jsonify, request
from flask_httpauth import HTTPTokenAuth
from fastapi import Depends, FastAPI, HTTPException, Request
from fastapi.responses import JSONResponse
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer

import server.constants as constants
from server.deployer import Deployer, DeploymentConfig

load_dotenv()

if (os.environ.get("ENV") or "local").lower() == "local":
logging.basicConfig(level=logging.NOTSET)
app = FastAPI()
security = HTTPBearer()
app.config = {"SECRET_TEXT": os.environ.get("SECRET_TEXT")}

env = os.environ.get("ENV").upper() == constants.LOCAL
logging.basicConfig(level=logging.DEBUG if env else logging.INFO)

app = Flask(__name__)
auth = HTTPTokenAuth("Bearer")
app.config["SECRET_TEXT"] = os.environ.get("SECRET_TEXT")


@auth.verify_token
def verify_token(token):
async def verify_token(credentials: HTTPAuthorizationCredentials = Depends(security)):
token = credentials.credentials
try:
data = jwt.decode(token, app.config["SECRET_TEXT"], algorithms=["HS256"])
logging.debug(f"Authenticated successfully {data}")
except Exception as e: # noqa: E722
logging.debug(f"Error while authenticating {e}")
return False
return True


# Your deployment endpoint
@app.route("/deploy", methods=["POST", "DELETE"])
@auth.login_required
def deploy():
data = request.get_json()

# Create DeploymentConfig object
project_name = urlparse(data.get("project_git_url")).path[
:-4
] # remove .git from the end
except Exception as e:
logging.info(f"Error while authenticating {e}")
raise HTTPException(status_code=401, detail="Invalid token")
return data


@app.post("/deploy")
@app.delete("/deploy")
async def deploy(request: Request, token: dict = Depends(verify_token)):
data = await request.json()

try:
project_git_url = urlparse(data.get("project_git_url")).path
except Exception as e:
logging.error(e)
return JSONResponse(
status_code=400,
content={"message": f"Bad Project Git URL: {str(e)}"},
)

if not project_git_url or not project_git_url.endswith(".git"):
return JSONResponse(
status_code=400,
content={"message": "Project URL should not be empty and end with .git"},
)

project_name = project_git_url[:-4] # remove .git from the end
config = DeploymentConfig(
project_name=project_name,
branch_name=data.get("branch"),
project_git_url=data.get("project_git_url"),
compose_file_location=data.get("compose_file_location") or "docker-compose.yml",
compose_file_location=data.get("compose_file_location"),
rest_action=request.method,
)

deployer = Deployer(config)
if request.method == "POST":

if request.method == constants.POST:
urls = deployer.deploy_preview_environment()
return jsonify(urls)
elif request.method == "DELETE":
return JSONResponse(content=urls)
elif request.method == constants.DELETE:
deployer.delete_preview_environment()
return (
jsonify({"message": "Removed preview environment"}),
200,
return JSONResponse(
status_code=200,
content={"message": "Removed preview environment"},
)
else:
return (
jsonify({"error": "Invalid HTTP method. Supported methods: POST, DELETE"}),
405,
return JSONResponse(
status_code=405,
content={"error": "Invalid HTTP method. Supported methods: POST, DELETE"},
)


if __name__ == "__main__":
app.run(debug=True, use_reloader=False)
import uvicorn

uvicorn.run(
app,
host="0.0.0.0",
port=5000,
)
4 changes: 2 additions & 2 deletions docker-compose-local.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ version: "3"

services:
nginx:
image: nginx:latest
image: nginx:1.26.0
restart: always
container_name: sarthi_nginx
ports:
Expand Down Expand Up @@ -35,7 +35,7 @@ services:
- vault

vault:
image: vault:1.12.3
image: hashicorp/vault:1.16
restart: always
ports:
- "8200:8200"
Expand Down
12 changes: 6 additions & 6 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ version: "3"

services:
nginx:
image: nginx:latest
image: nginx:1.26.0
restart: always
container_name: sarthi_nginx
ports:
Expand Down Expand Up @@ -41,7 +41,7 @@ services:
- vault

loki:
image: grafana/loki:latest
image: grafana/loki:main-8978ecf
restart: always
ports:
- 127.0.0.1:3100:3100
Expand All @@ -52,15 +52,15 @@ services:
- promtail

promtail:
image: grafana/promtail:latest
image: grafana/promtail:main-8978ecf
restart: always
volumes:
- /var/log:/var/log
- ./logging-config/promtail:/etc/promtail
command: -config.file=/etc/promtail/promtail-config.yaml

grafana:
image: grafana/grafana
image: grafana/grafana:10.1.10-ubuntu
restart: always
container_name: grafana
volumes:
Expand All @@ -71,7 +71,7 @@ services:
- loki

vault:
image: vault:1.12.3
image: hashicorp/vault:1.16
restart: always
volumes:
- ./vault/vault.json:/vault/config/vault.json
Expand All @@ -94,7 +94,7 @@ services:
retries: 3

portainer:
image: portainer/portainer-ce:latest
image: portainer/portainer-ce:2.20.2
volumes:
- portainer_data:/data
- /var/run/docker.sock:/var/run/docker.sock
Expand Down
8 changes: 4 additions & 4 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
pytest==7.4.4
pytest-mock==3.12.0
pre-commit==3.6.0
coverage==7.4.0
pytest~=7.4.4
pytest-mock~=3.12.0
pre-commit~=3.6.0
coverage~=7.4.0
14 changes: 7 additions & 7 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
pyyaml==6.0.1
flask==3.0.0
pyjwt==2.8.0
Flask-HTTPAuth==4.8.0
python-dotenv==1.0.0
requests==2.31.0
filelock==3.13.1
pyyaml~=6.0.1
pyjwt~=2.8.0
python-dotenv~=1.0.0
requests>=2.32.0
filelock~=3.13.1
fastapi~=0.111.0
uvicorn~=0.29.0
20 changes: 20 additions & 0 deletions server/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Can't be used as branch names for now
DEFAULT_SECRETS_PATH = ["default-dev-secrets"]
COMPOSE_FILE = "docker-compose.yml"

GET = "GET"
POST = "POST"
DELETE = "DELETE"
PUT = "PUT"

LOCAL = "LOCAL"
PROD = "PROD"

DOCKER_HOST_NETWORK_DOMAIN = "host.docker.internal"

DEFAULT_DEPLOYMENT_PORT_START = 15000
DEFAULT_DEPLOYMENT_PORT_END = 25000

LOCALHOST = "localhost"

SAMPLE_ENV_FILENAMES = [".env.sample", "env.sample", "sample.env"]
14 changes: 10 additions & 4 deletions server/deployer.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
import typing

import filelock
from fastapi.exceptions import HTTPException

import server.constants as constants

from .utils import ComposeHelper, DeploymentConfig, NginxHelper, SecretsHelper

Expand All @@ -28,12 +31,12 @@ def __init__(self, config: DeploymentConfig):
)

with self._lock:
if config.rest_action != "DELETE":
if config.rest_action != constants.DELETE:
self._setup_project()

self._compose_helper = ComposeHelper(
os.path.join(self._project_path, config.compose_file_location),
config.rest_action != "DELETE",
config.rest_action != constants.DELETE,
)
self._secrets_helper = SecretsHelper(
self._config.project_name, self._config.branch_name, self._project_path
Expand Down Expand Up @@ -62,14 +65,17 @@ def _clone_project(self):
stdout, stderr = process.communicate()
except subprocess.TimeoutExpired as e:
logger.error(f"Error cloning the repo {self._config} with {e}")
raise
raise HTTPException(500, e)
if process.returncode == 0:
logger.info("Git clone successful.")
else:
logger.error(f"Git clone failed. Return code: {process.returncode}")
logger.error(f"Standard Output: {stdout.decode()}")
logger.error(f"Standard Error: {stderr.decode()}")
raise Exception(f"Cloning the Git repo failed {self._config}")
raise HTTPException(
500,
f"Cloning the Git repo failed {self._config.project_git_url}:{self._config.branch_name} {stderr.decode()}",
)

def _setup_project(self):
if os.path.exists(self._project_path):
Expand Down
Loading

0 comments on commit be08d71

Please sign in to comment.