diff --git a/.github/workflows/docker_cleanup.yml b/.github/workflows/docker_cleanup.yml new file mode 100644 index 00000000..1f2d5af6 --- /dev/null +++ b/.github/workflows/docker_cleanup.yml @@ -0,0 +1,23 @@ +name: Cleanup Untagged Images + +on: + # every sunday at 00:00 + schedule: + - cron: "0 0 * * SUN" + # or manually + workflow_dispatch: + +jobs: + delete-untagged-images: + name: Delete Untagged Images + runs-on: ubuntu-latest + steps: + - uses: bots-house/ghcr-delete-image-action@v1.1.0 # nosemgrep: yaml.github-actions.security.third-party-action-not-pinned-to-commit-sha.third-party-action-not-pinned-to-commit-sha + with: + # NOTE: at now only orgs is supported + owner: airtai + name: captn-google-auth-ads + + token: ${{ secrets.GITHUB_TOKEN }} + # Keep latest N untagged images + untagged-keep-latest: 1 diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml new file mode 100644 index 00000000..59879b5c --- /dev/null +++ b/.github/workflows/pipeline.yml @@ -0,0 +1,79 @@ +name: Pipeline +on: [push, workflow_dispatch] + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + PORT: ${{ vars.PORT }} + DOMAIN: ${{ vars.DOMAIN }} + DATABASE_URL: ${{ secrets.DATABASE_URL }} + CLIENT_SECRET: ${{ secrets.CLIENT_SECRET }} + +jobs: + docker_build_push: + runs-on: ubuntu-22.04 + permissions: + contents: read + packages: write + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - uses: actions/setup-node@v3 + with: + node-version: 18 + + - name: Install wasp + run: curl -sSL https://get.wasp-lang.dev/installer.sh | sh + + - name: Log in to the Container registry + uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - run: docker pull ghcr.io/$GITHUB_REPOSITORY:$GITHUB_REF_NAME || docker pull ghcr.io/$GITHUB_REPOSITORY || true + - run: docker build --build-arg PORT=$PORT -t ghcr.io/$GITHUB_REPOSITORY:$GITHUB_REF_NAME . + - name: Add tag latest if branch is main + if: github.ref_name == 'main' + run: docker tag ghcr.io/$GITHUB_REPOSITORY:$GITHUB_REF_NAME ghcr.io/$GITHUB_REPOSITORY:latest + - name: Push only if branch name is main + if: github.ref_name == 'main' + run: docker push ghcr.io/$GITHUB_REPOSITORY --all-tags + + deploy: + runs-on: ubuntu-22.04 + defaults: + run: + shell: bash + needs: [docker_build_push] + if: github.ref_name == 'main' + container: + image: python:3.7-stretch + env: + GITHUB_USERNAME: ${{ github.actor }} + GITHUB_PASSWORD: ${{ secrets.GITHUB_TOKEN }} + SSH_KEY: ${{ secrets.SSH_KEY }} + AZURE_OPENAI_API_KEY: ${{ secrets.AZURE_OPENAI_API_KEY }} + DEVELOPER_TOKEN: ${{ secrets.DEVELOPER_TOKEN }} + steps: + - uses: actions/checkout@v3 + # This is to fix GIT not liking owner of the checkout dir - https://github.com/actions/runner/issues/2033#issuecomment-1204205989 + - run: chown -R $(id -u):$(id -g) $PWD + - run: echo "TAG=latest" >> $GITHUB_ENV + # - run: if [[ $GITHUB_REF_NAME == "main" ]]; then printenv PROD_CONFIG > "$(pwd)/.env" ; else printenv STAGING_CONFIG > "$(pwd)/.env" ; fi; + - run: echo "PATH=$PATH:/github/home/.local/bin" >> $GITHUB_ENV + - run: 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client git -y )' + - run: eval $(ssh-agent -s) + - run: mkdir -p ~/.ssh + - run: chmod 700 ~/.ssh + - run: ssh-keyscan "$DOMAIN" >> ~/.ssh/known_hosts + - run: chmod 644 ~/.ssh/known_hosts + - run: echo "$SSH_KEY" | base64 --decode > key.pem + - run: chmod 600 key.pem + + # - run: if [[ $GITHUB_REF_NAME == "main" ]]; then echo "DOMAIN=api.airt.ai" >> $GITHUB_ENV ; else echo "DOMAIN=api.staging.airt.ai" >> $GITHUB_ENV ; fi; + - run: ssh -o StrictHostKeyChecking=no -i key.pem azureuser@"$DOMAIN" "docker images" + - run: sh scripts/deploy.sh + + - run: rm key.pem diff --git a/.gitignore b/.gitignore index ee1b5ca6..6cf1d5e6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ *.json venv/ +__pycache__/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..0be27915 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,35 @@ +ARG BASE_IMAGE=ubuntu:22.04 + +FROM $BASE_IMAGE + + +SHELL ["/bin/bash", "-c"] + + +# needed to suppress tons of debconf messages +ENV DEBIAN_FRONTEND noninteractive + +RUN apt update --fix-missing && apt upgrade --yes \ + && apt install -y software-properties-common apt-utils build-essential git wget curl \ + && add-apt-repository ppa:deadsnakes/ppa \ + && apt update \ + && apt install -y --no-install-recommends python3.10-dev python3.10-distutils python3-pip python3-apt \ + && apt purge --auto-remove \ + && apt clean \ + && rm -rf /var/lib/apt/lists/* + +# Install node and npm +RUN curl -fsSL https://deb.nodesource.com/setup_18.x | bash - && apt-get install -y --no-install-recommends nodejs \ + && apt purge --auto-remove && apt clean && rm -rf /var/lib/apt/lists/* + +# RUN update-alternatives --set python3 /usr/bin/python3.10 +RUN python3 -m pip install --upgrade pip + +COPY migrations ./migrations +COPY application.py scripts/* fastapi_requirements.txt schema.prisma ./ +RUN pip install -r fastapi_requirements.txt + +EXPOSE ${PORT} + +ENTRYPOINT [] +CMD [ "/usr/bin/bash", "-c", "./start_webservice.sh" ] diff --git a/application.py b/application.py index eb72dbb2..4689e635 100644 --- a/application.py +++ b/application.py @@ -126,7 +126,7 @@ async def load_user_credentials(user_id): def create_google_ads_client(user_credentials): # Create a dictionary with the required structure for GoogleAdsClient google_ads_credentials = { - "developer_token": "rQl20ooeSUSJsTIredWGFw", # Replace with your actual developer token + "developer_token": environ.get("DEVELOPER_TOKEN"), "use_proto_plus": False, "client_id": oauth2_settings["clientId"], "client_secret": oauth2_settings["clientSecret"], diff --git a/migrations/20231109102807_add_model/migration.sql b/migrations/20231109102807_add_model/migration.sql new file mode 100644 index 00000000..46c7cfd1 --- /dev/null +++ b/migrations/20231109102807_add_model/migration.sql @@ -0,0 +1,14 @@ +-- CreateTable +CREATE TABLE "GAuth" ( + "id" TEXT NOT NULL, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) NOT NULL, + "user_id" INTEGER NOT NULL, + "creds" JSONB NOT NULL, + "info" JSONB NOT NULL, + + CONSTRAINT "GAuth_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "GAuth_user_id_key" ON "GAuth"("user_id"); diff --git a/migrations/migration_lock.toml b/migrations/migration_lock.toml new file mode 100644 index 00000000..fbffa92c --- /dev/null +++ b/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (i.e. Git) +provider = "postgresql" \ No newline at end of file diff --git a/scripts/deploy.sh b/scripts/deploy.sh new file mode 100755 index 00000000..1887d7a7 --- /dev/null +++ b/scripts/deploy.sh @@ -0,0 +1,78 @@ +#!/bin/bash + + +if test -z "$TAG" +then + echo "ERROR: TAG variable must be defined, exiting" + exit -1 +fi + +if test -z "$GITHUB_USERNAME" +then + echo "ERROR: GITHUB_USERNAME variable must be defined, exiting" + exit -1 +fi + +if test -z "$GITHUB_PASSWORD" +then + echo "ERROR: GITHUB_PASSWORD variable must be defined, exiting" + exit -1 +fi + + +if [ ! -f key.pem ]; then + echo "ERROR: key.pem file not found" + exit -1 +fi + + +if test -z "$DOMAIN" +then + echo "ERROR: DOMAIN variable must be defined, exiting" + exit -1 +fi + +if test -z "$PORT" +then + echo "ERROR: PORT variable must be defined, exiting" + exit -1 +fi + +if test -z "$DATABASE_URL" +then + echo "ERROR: DATABASE_URL variable must be defined, exiting" + exit -1 +fi + +if test -z "$CLIENT_SECRET" +then + echo "ERROR: CLIENT_SECRET variable must be defined, exiting" + exit -1 +fi + +if test -z "$AZURE_OPENAI_API_KEY" +then + echo "ERROR: AZURE_OPENAI_API_KEY variable must be defined, exiting" + exit -1 +fi + +if test -z "$DEVELOPER_TOKEN" +then + echo "ERROR: DEVELOPER_TOKEN variable must be defined, exiting" + exit -1 +fi + +echo "INFO: stopping already running docker container" +ssh -o StrictHostKeyChecking=no -i key.pem azureuser@"$DOMAIN" "docker stop gads || echo 'No containers available to stop'" +ssh -o StrictHostKeyChecking=no -i key.pem azureuser@"$DOMAIN" "docker container prune -f || echo 'No stopped containers to delete'" + +echo "INFO: pulling docker image" +ssh -o StrictHostKeyChecking=no -i key.pem azureuser@"$DOMAIN" "echo $GITHUB_PASSWORD | docker login -u '$GITHUB_USERNAME' --password-stdin '$REGISTRY'" +ssh -o StrictHostKeyChecking=no -i key.pem azureuser@"$DOMAIN" "docker pull ghcr.io/$GITHUB_REPOSITORY:'$TAG'" +sleep 10 + +echo "Deleting old image" +ssh -o StrictHostKeyChecking=no -i key.pem azureuser@"$DOMAIN" "docker system prune -f || echo 'No images to delete'" + +echo "INFO: starting docker container" +ssh -o StrictHostKeyChecking=no -i key.pem azureuser@"$DOMAIN" "docker run --name gads -p $PORT:$PORT -e PORT='$PORT' -e DATABASE_URL='$DATABASE_URL' -e CLIENT_SECRET='$CLIENT_SECRET' -e DEVELOPER_TOKEN='$DEVELOPER_TOKEN' -e AZURE_OPENAI_API_KEY='$AZURE_OPENAI_API_KEY' -d ghcr.io/$GITHUB_REPOSITORY:$TAG" diff --git a/scripts/start_webservice.sh b/scripts/start_webservice.sh new file mode 100755 index 00000000..7e30ee63 --- /dev/null +++ b/scripts/start_webservice.sh @@ -0,0 +1,14 @@ +#!/usr/bin/bash + + +if [[ -z "${NUM_WORKERS}" ]]; then + NUM_WORKERS=2 +fi + +echo NUM_WORKERS set to $NUM_WORKERS + +cat <<< "$CLIENT_SECRET" > client_secrets.json + +prisma migrate deploy + +uvicorn application:app --port $PORT --host 0.0.0.0 --workers=$NUM_WORKERS --proxy-headers