Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Deployment] Automating Building and Deployment #31

Merged
merged 34 commits into from
Jan 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
8f87f5a
[OS] ARM64 Docker Builder Push
TheManWhoLikesToCode Jan 9, 2024
f384671
Added ENV Variable
TheManWhoLikesToCode Jan 9, 2024
2a4af15
Add Error Catching to Google Drive Authentication
TheManWhoLikesToCode Jan 10, 2024
69b660f
Additional Attempt to get Creds
TheManWhoLikesToCode Jan 10, 2024
3eae669
OS Client ID and Secret
TheManWhoLikesToCode Jan 10, 2024
2fc1a7a
Update requirements.txt
TheManWhoLikesToCode Jan 10, 2024
9922cf2
Revert "Update requirements.txt"
TheManWhoLikesToCode Jan 10, 2024
9742ed8
Client Secret Test
TheManWhoLikesToCode Jan 10, 2024
f02df94
Update docker-image.yml
TheManWhoLikesToCode Jan 10, 2024
dc5589c
Added Secrets
TheManWhoLikesToCode Jan 10, 2024
d0ea606
Merge branch 'TheManWhoLikesToCode-patch-2' of https://github.com/The…
TheManWhoLikesToCode Jan 10, 2024
c8d9d42
Load environmental variables
TheManWhoLikesToCode Jan 10, 2024
622fb59
Removed unused settings
TheManWhoLikesToCode Jan 10, 2024
dce0983
Update requirements.txt
TheManWhoLikesToCode Jan 10, 2024
3faeef0
Refresh Auth Token
TheManWhoLikesToCode Jan 10, 2024
da86ec1
Credentials in GH Secrets
TheManWhoLikesToCode Jan 10, 2024
1312a67
CLIENT_CREDENTIALS_JSON
TheManWhoLikesToCode Jan 10, 2024
590224e
Update Dockerfile
TheManWhoLikesToCode Jan 10, 2024
b186ce0
Update docker-image.yml
TheManWhoLikesToCode Jan 11, 2024
b331532
ports
TheManWhoLikesToCode Jan 11, 2024
2c8eab5
Update docker-image.yml
TheManWhoLikesToCode Jan 11, 2024
c73c7e9
Update docker-image.yml
TheManWhoLikesToCode Jan 11, 2024
8505392
removed port from config
TheManWhoLikesToCode Jan 11, 2024
aa23a7d
fixed name tags
TheManWhoLikesToCode Jan 11, 2024
0080ebb
Updated Flask Ports
TheManWhoLikesToCode Jan 11, 2024
ed59247
Updating Pathing and Ports
TheManWhoLikesToCode Jan 12, 2024
27372a4
Switched port to env
TheManWhoLikesToCode Jan 12, 2024
7acda80
Test ENV pass
TheManWhoLikesToCode Jan 12, 2024
daa2fa4
spelling PAIN
TheManWhoLikesToCode Jan 12, 2024
3b9e227
Expose ports
TheManWhoLikesToCode Jan 12, 2024
0044281
Update requirements.txt
TheManWhoLikesToCode Jan 12, 2024
87b2187
Update Dockerfile
TheManWhoLikesToCode Jan 12, 2024
11d8421
House Keeping
TheManWhoLikesToCode Jan 12, 2024
2d22fb8
Update Urls Based on Host
TheManWhoLikesToCode Jan 12, 2024
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
73 changes: 52 additions & 21 deletions .github/workflows/docker-image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,37 +8,68 @@ on:
release:
types: [published]

env:
PROD_BACKEND_PORT: 5001
DEV_BACKEND_PORT: 5003
PROD_FRONTEND_PORT: 5002
DEV_FRONTEND_PORT: 5004

jobs:
backend-build:
runs-on: ubuntu-latest
steps:
- name: Checkout backend code
uses: actions/checkout@v3
uses: actions/checkout@v4

- name: Build and push the Docker image for backend
run: |
if [ ${{ github.event_name }} == 'pull_request' ]; then
DOCKER_IMAGE_TAG=development
else
DOCKER_IMAGE_TAG=production
fi
docker build ./backend -t themanwholikestocode/archive-me-prod:backend-$DOCKER_IMAGE_TAG
echo ${{ secrets.DOCKER_PASSWORD }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin
docker push themanwholikestocode/archive-me-prod:backend-$DOCKER_IMAGE_TAG
- name: Set up QEMU
uses: docker/setup-qemu-action@v3

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Login to DockerHub
run: echo ${{ secrets.DOCKER_PASSWORD }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin

- name: Build and push the Docker image for backend
uses: docker/build-push-action@v5
with:
context: ./backend
file: ./backend/Dockerfile
platforms: linux/arm64
push: true
tags: |
themanwholikestocode/archive-me-prod:backend-${{ github.event_name == 'pull_request' && 'development' || 'production' }}
build-args: |
CLIENT_CREDENTIALS_JSON=${{ secrets.CLIENT_CREDENTIALS_JSON }}
GOOGLE_CLIENT_SECRET=${{ secrets.GOOGLE_CLIENT_SECRET }}
GOOGLE_CLIENT_ID=${{ secrets.GOOGLE_CLIENT_ID }}
ENVIRONMENT=${{ github.event_name == 'pull_request' && 'dev' || 'prod' }}
PORT=${{ github.event_name != 'pull_request' && env.PROD_BACKEND_PORT || env.DEV_BACKEND_PORT }}

frontend-build:
runs-on: ubuntu-latest
steps:
- name: Checkout frontend code
uses: actions/checkout@v3


- name: Set up QEMU
uses: docker/setup-qemu-action@v3

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Login to DockerHub
run: echo ${{ secrets.DOCKER_PASSWORD }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin

- name: Build and push the Docker image for frontend
run: |
if [ ${{ github.event_name }} == 'pull_request' ]; then
DOCKER_IMAGE_TAG=development
else
DOCKER_IMAGE_TAG=production
fi
docker build ./frontend -t themanwholikestocode/archive-me-prod:frontend-$DOCKER_IMAGE_TAG
echo ${{ secrets.DOCKER_PASSWORD }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin
docker push themanwholikestocode/archive-me-prod:frontend-$DOCKER_IMAGE_TAG
uses: docker/build-push-action@v5
with:
context: ./frontend
file: ./frontend/Dockerfile
platforms: linux/arm64
push: true
tags: |
themanwholikestocode/archive-me-prod:frontend-${{ github.event_name == 'pull_request' && 'development' || 'production' }}
build-args: |
ENVIRONMENT=${{ github.event_name == 'pull_request' && 'dev' || 'prod' }}
PORT=${{ github.event_name != 'pull_request' && env.PROD_FRONTEND_PORT || env.DEV_FRONTEND_PORT }}
9 changes: 5 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@

backend/client_secrets.json
client_secrets.json
*.pyc
*.pyc
mycreds.txt
backend/settings.yaml
backend/.env
frontend/.env
*.pyc
*.DS_Store
backend/support/.DS_Store
20 changes: 17 additions & 3 deletions backend/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,24 @@ WORKDIR /backend

ADD . /backend

RUN pip install -r requirements.txt
ARG GOOGLE_CLIENT_SECRET
ENV GOOGLE_CLIENT_SECRET=$GOOGLE_CLIENT_SECRET

ARG GOOGLE_CLIENT_ID
ENV GOOGLE_CLIENT_ID=$GOOGLE_CLIENT_ID

ARG CLIENT_CREDENTIALS_JSON
ENV CLIENT_CREDENTIALS_JSON=$CLIENT_CREDENTIALS_JSON
RUN echo "$CLIENT_CREDENTIALS_JSON" > /backend/credentials.json

EXPOSE 5001
ARG ENVIRONMENT
ENV ENVIRONMENT=$ENVIRONMENT

CMD python ./app.py
ARG PORT
ENV PORT=$PORT

EXPOSE $PORT

RUN pip install -r requirements.txt

CMD python ./app.py
Binary file removed backend/__pycache__/app.cpython-311.pyc
Binary file not shown.
Binary file not shown.
Binary file removed backend/__pycache__/config.cpython-311.pyc
Binary file not shown.
Binary file not shown.
Binary file removed backend/__pycache__/pdf_compressor.cpython-311.pyc
Binary file not shown.
64 changes: 52 additions & 12 deletions backend/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,17 @@
import threading
import time
import uuid

from dotenv import load_dotenv
from flask import Flask, abort, after_this_request, jsonify, request, send_from_directory
from flask_cors import CORS, cross_origin
from flask_apscheduler import APScheduler
import yaml

from blackboard_scraper import BlackboardSession
from file_management import clean_up_session_files, delete_session_files, list_files_in_drive_folder, update_drive_directory, clean_up_docs_files
import config

from pydrive2.auth import GoogleAuth
from pydrive2.drive import GoogleDrive

Expand All @@ -22,6 +27,9 @@
# Initialize Logging
logging.basicConfig(level=logging.INFO)

# Import dot env variables
load_dotenv()


def is_file_valid(file_path):
return os.path.isfile(file_path) and not os.path.islink(file_path)
Expand All @@ -34,9 +42,10 @@ def remove_file_safely(file_path):
except OSError as error:
app.logger.error(f"Error removing file: {error}")


@scheduler.task('interval', id='clean_up', seconds=600)
def clean_up_and_upload_files_to_google_drive(file_path=None):

if file_path:
remove_file_safely(file_path)

Expand All @@ -50,8 +59,34 @@ def clean_up_and_upload_files_to_google_drive(file_path=None):


def authorize_drive():
gauth = GoogleAuth(settings_file='settings.yaml')
gauth.LocalWebserverAuth()
current_directory = os.getcwd()

if 'backend' in current_directory:
settings_path = 'settings.yaml'
elif 'Archive-Me' in current_directory:
settings_path = 'backend/settings.yaml'
else:
raise Exception("Unable to locate settings file.")

with open(settings_path, 'r') as file:
settings = yaml.safe_load(file)

settings['client_config']['client_id'] = os.environ.get('GOOGLE_CLIENT_ID')
settings['client_config']['client_secret'] = os.environ.get(
'GOOGLE_CLIENT_SECRET')

gauth = GoogleAuth(settings=settings)

if os.path.isfile("credentials.json"):
gauth.LoadCredentialsFile("credentials.json")
else:
gauth.LocalWebserverAuth()
gauth.SaveCredentialsFile("credentials.json")

if gauth.access_token_expired:
gauth.Refresh()
gauth.SaveCredentialsFile("credentials.json")

drive = GoogleDrive(gauth)
return drive

Expand Down Expand Up @@ -130,6 +165,7 @@ def delete_inactive_bb_sessions(inactivity_threshold_seconds=180):
def index():
return jsonify({'message': "Welcome to the ArchiveMe's Blackboard Scraper API"})


@app.route('/login', methods=['POST'])
@cross_origin()
def login():
Expand Down Expand Up @@ -220,9 +256,9 @@ def list_directory(path):
# Check if there's only one file returned
if len(items) == 1 and items[0][3] == 'FILE':
# Assuming 'file_id' and 'file_name' are available based on the user selection
file_id = items[0][2]
file_name = items[0][0]
file_id = items[0][2]
file_name = items[0][0]

# Update the session_files_path based on the current directory and create if it doesn't exist
current_dir = os.path.dirname(os.path.abspath(__file__))
if os.path.basename(current_dir) != 'backend':
Expand All @@ -235,9 +271,9 @@ def list_directory(path):
if not os.path.exists(session_files_path):
os.makedirs(session_files_path)
full_path = os.path.join(session_files_path, file_name)

file = drive.CreateFile({'id': file_id})
print('Downloading file %s from Google Drive' % file_name)
print('Downloading file %s from Google Drive' % file_name)
file.GetContentFile(full_path)

@after_this_request
Expand All @@ -246,9 +282,9 @@ def trigger_post_download_operations(response):
target=clean_up_and_upload_files_to_google_drive, args=(full_path,))
thread.start()
return response

return send_from_directory(session_files_path, file_name, as_attachment=True)

return jsonify(items)


Expand All @@ -258,12 +294,16 @@ def list_root_directory():


if __name__ == '__main__':

drive = authorize_drive()

if not drive:
app.logger.error("Error authorizing Google Drive")
exit(1)

team_drive_id = '0AFReXfsUal4rUk9PVA'

scheduler.init_app(app)
scheduler.start()

app.run(host='0.0.0.0', port=app.config['PORT'], debug=app.config['DEBUG'])
app.run(host='0.0.0.0', port=app.config['PORT'], debug=app.config['DEBUG'])
20 changes: 16 additions & 4 deletions backend/config.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
# config.py
from selenium.webdriver.chrome.options import Options

import os
from dotenv import load_dotenv
import sys
# Flask configuration
DEBUG = True # Set to False in production
PORT = 5001 # Port number for the Flask server
load_dotenv()

env = os.environ.get('ENVIRONMENT')

if env == 'dev':
PORT = 5003
DEBUG = True
elif env == 'prod':
PORT = 5001
DEBUG = False
else:
print("Environment not specified. Please provide a valid environment.")
sys.exit(1)
1 change: 1 addition & 0 deletions backend/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ gunicorn
flask_cors
flask_apscheduler
pydrive2
python-dotenv
13 changes: 13 additions & 0 deletions backend/settings.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
client_config_backend: settings
client_config:
client_id: ${GOOGLE_CLIENT_ID}
client_secret: ${GOOGLE_CLIENT_SECRET}

save_credentials: True
save_credentials_backend: file
save_credentials_file: "credentials.json"

get_refresh_token: True

oauth_scope:
- "https://www.googleapis.com/auth/drive"
8 changes: 7 additions & 1 deletion frontend/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ ADD . /frontend

RUN pip install -r requirements.txt

EXPOSE 5002
ARG ENVIRONMENT
ENV ENVIRONMENT=$ENVIRONMENT

ARG PORT
ENV PORT=$PORT

EXPOSE $PORT

CMD python ./app.py
Binary file removed frontend/__pycache__/app.cpython-311.pyc
Binary file not shown.
Binary file removed frontend/__pycache__/config.cpython-311.pyc
Binary file not shown.
Binary file removed frontend/__pycache__/wsgi.cpython-311.pyc
Binary file not shown.
7 changes: 1 addition & 6 deletions frontend/app.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from flask import Flask, render_template, request, jsonify, send_from_directory, abort
from flask import Flask, render_template
from flask_cors import CORS, cross_origin
import os
import logging
Expand Down Expand Up @@ -28,11 +28,6 @@ def demo():
def directory():
return render_template('directory.html')

# Add a login route for demonstration
@app.route('/login', methods=['POST'])
def login():
# Your login logic here
return jsonify(success=True)

if __name__ == '__main__':
app.run(host='0.0.0.0', port=app.config['PORT'], debug=app.config['DEBUG'])
6 changes: 6 additions & 0 deletions frontend/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"apiUrl":{
"dev":"http://localhost:5003",
"prod":"https://api.archive-me.net"
}
}
22 changes: 16 additions & 6 deletions frontend/config.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
# config.py
import os
from dotenv import load_dotenv
import sys

# Flask configuration
DEBUG = True # Set to False in production
PORT = 5002 # Port number for the Flask server
load_dotenv()

env = os.environ.get('ENVIRONMENT')

if env == 'dev':
PORT = 5004
DEBUG = True
elif env == 'prod':
PORT = 5002
DEBUG = False
else:
print("Environment not specified. Please provide a valid environment.")
sys.exit(1)

# CORS Configurations
CORS_HEADERS = 'Content-Type'

# Security configurations
# It's advisable to use environment variables for sensitive data
# Example: SECRET_KEY = os.environ.get('SECRET_KEY') or 'a-default-secret'
3 changes: 2 additions & 1 deletion frontend/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ ray
argparse
bs4
gunicorn
flask_cors
flask_cors
python-dotenv
Loading
Loading