Skip to content

Commit bb98af2

Browse files
committed
Merge branch 'dev'
2 parents 7bdeb3c + 4117736 commit bb98af2

10 files changed

Lines changed: 575 additions & 0 deletions

File tree

.dockerignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.env

.github/workflows/ci.yml

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
name: Build and Deploy
2+
3+
env:
4+
APP_NAME: CMS-BACKEND-CRON
5+
PROJECT_NAME: CMS-BACKEND-CRON
6+
DOCKER_COMPOSE_PATH: /root/app/docker-compose.yml
7+
REGISTRY: ghcr.io
8+
DOCKER_REGISTRY: ghcr.io/code4govtech/dmp-cms-backend-cron
9+
DOT_ENV_FILE_NAME: env.dmp-cms-backend-cron
10+
11+
12+
on:
13+
workflow_dispatch:
14+
push:
15+
branches:
16+
- dev
17+
- main
18+
19+
permissions:
20+
contents: write
21+
packages: write
22+
23+
24+
jobs:
25+
set_vars:
26+
name: Set Environment Variables
27+
runs-on: ubuntu-latest
28+
outputs:
29+
TAG_LATEST: ${{ steps.tag_values.outputs.TAG_LATEST }}
30+
TAG_ENV_COMMIT: ${{ steps.tag_values.outputs.TAG_ENV_COMMIT }}
31+
APP_ENV: ${{ steps.tag_values.outputs.APP_ENV }}
32+
steps:
33+
- name: Set Docker Image Tags
34+
id: tag_values
35+
run: |
36+
case "${{ github.ref }}" in
37+
'refs/heads/main')
38+
echo "TAG_LATEST=prod-latest" >> $GITHUB_OUTPUT
39+
echo "TAG_ENV_COMMIT=prod-${GITHUB_SHA:0:5}" >> $GITHUB_OUTPUT
40+
echo "APP_ENV=PROD" >> $GITHUB_OUTPUT
41+
;;
42+
'refs/heads/devops')
43+
echo "TAG_LATEST=dev-latest" >> $GITHUB_OUTPUT
44+
echo "TAG_ENV_COMMIT=dev-${GITHUB_SHA:0:5}" >> $GITHUB_OUTPUT
45+
echo "APP_ENV=DEV" >> $GITHUB_OUTPUT
46+
;;
47+
'refs/heads/dev')
48+
echo "TAG_LATEST=dev-latest" >> $GITHUB_OUTPUT
49+
echo "TAG_ENV_COMMIT=dev-${GITHUB_SHA:0:5}" >> $GITHUB_OUTPUT
50+
echo "APP_ENV=DEV" >> $GITHUB_OUTPUT
51+
;;
52+
esac
53+
54+
build:
55+
name: Build
56+
runs-on: ubuntu-latest
57+
needs: [set_vars]
58+
permissions:
59+
contents: read
60+
packages: write
61+
env:
62+
TAG_LATEST: ${{ needs.set_vars.outputs.TAG_LATEST }}
63+
TAG_ENV_COMMIT: ${{ needs.set_vars.outputs.TAG_ENV_COMMIT }}
64+
GITHUB_TOKEN: ${{ secrets[format('APP_{0}_GITHUB_TOKEN', needs.set_vars.outputs.APP_ENV)] }}
65+
TARGET_DATE: ${{ vars[format('APP_{0}_TARGET_DATE', needs.set_vars.outputs.APP_ENV)] }}
66+
SUPABASE_KEY: ${{ secrets[format('APP_{0}_SUPABASE_KEY', needs.set_vars.outputs.APP_ENV)] }}
67+
SUPABASE_URL: ${{ vars[format('APP_{0}_SUPABASE_URL', needs.set_vars.outputs.APP_ENV)] }}
68+
SCHEDULER_DELAY_IN_MINS: ${{ vars[format('APP_{0}_SCHEDULER_DELAY_IN_MINS', needs.set_vars.outputs.APP_ENV)] }}
69+
steps:
70+
- name: Checkout code
71+
uses: actions/checkout@v2
72+
73+
- name: Log in to the Container registry
74+
uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1
75+
with:
76+
registry: ${{ env.REGISTRY }}
77+
username: ${{ github.actor }}
78+
password: ${{ secrets.GITHUB_TOKEN }}
79+
80+
- name: Set Docker Tags
81+
uses: actions/setup-node@v2
82+
83+
- name: Read Secrets
84+
run: |
85+
echo "GITHUB_TOKEN=${GITHUB_TOKEN}" >> .env
86+
echo "TARGET_DATE=${TARGET_DATE}" >> .env
87+
echo "SUPABASE_URL=${SUPABASE_URL}" >> .env
88+
echo "SUPABASE_KEY=${SUPABASE_KEY}" >> .env
89+
echo "SCHEDULER_DELAY_IN_MINS=${SCHEDULER_DELAY_IN_MINS}" >> .env
90+
91+
mv .env ${{ env.DOT_ENV_FILE_NAME }}
92+
93+
- name: Copy env file to DEV Server
94+
uses: appleboy/scp-action@v0.1.7
95+
if: needs.set_vars.outputs.APP_ENV == 'DEV'
96+
with:
97+
host: ${{ vars.DEV_SERVER_HOST }}
98+
username: ${{ vars.DEV_SERVER_USERNAME }}
99+
key: ${{ secrets.DEV_SSH_PRIVATE_KEY }}
100+
port: ${{ vars.DEV_SERVER_PORT }}
101+
source: "${{ env.DOT_ENV_FILE_NAME }}"
102+
target: /root/app/
103+
104+
- name: Build ${{ env.APP_NAME }} Docker image
105+
run: |
106+
docker build -t ${{ env.DOCKER_REGISTRY }}:${{ env.TAG_LATEST }} .
107+
108+
- name: Add tag to Docker image
109+
run: |
110+
echo ${{ github.sha }}
111+
docker tag ${{ env.DOCKER_REGISTRY }}:${{ env.TAG_LATEST }} ${{ env.DOCKER_REGISTRY }}:${{ env.TAG_ENV_COMMIT }}
112+
113+
- name: Push Docker image to GitHub Packages
114+
run: |
115+
docker push ${{ env.DOCKER_REGISTRY }}:${{ env.TAG_LATEST }}
116+
docker push ${{ env.DOCKER_REGISTRY }}:${{ env.TAG_ENV_COMMIT }}
117+
118+
deploy:
119+
name: Deployment
120+
runs-on: ubuntu-latest
121+
needs: build
122+
if: github.event_name == 'push' && github.ref_type == 'branch'
123+
124+
steps:
125+
- name: Deploy to DevOps/Dev Environment
126+
if: github.ref == 'refs/heads/devops' || github.ref == 'refs/heads/dev'
127+
uses: appleboy/ssh-action@v1.0.3
128+
env:
129+
DOCKER_COMPOSE_PATH: ${{ env.DOCKER_COMPOSE_PATH }}
130+
APP_NAME: ${{ env.APP_NAME }}
131+
DOCKER_REGISTRY: ${{ env.DOCKER_REGISTRY }}
132+
with:
133+
host: ${{ vars.DEV_SERVER_HOST }}
134+
username: ${{ vars.DEV_SERVER_USERNAME }}
135+
key: ${{ secrets.DEV_SSH_PRIVATE_KEY }}
136+
port: ${{ vars.DEV_SERVER_PORT }}
137+
allenvs: true
138+
script_stop: true
139+
envs: DOCKER_COMPOSE_PATH,APP_NAME,DOCKER_REGISTRY
140+
script: |
141+
echo "Docker Compose Path $DOCKER_COMPOSE_PATH"
142+
docker compose -f $DOCKER_COMPOSE_PATH pull
143+
docker compose -f $DOCKER_COMPOSE_PATH up -d
144+
145+
- name: Deploy to Prod environment
146+
if: github.ref == 'refs/heads/main'
147+
run: echo "Deploying to Kubernetes"

.gitignore

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#all files relating to python vitual env
2+
/.env
3+
#environment variables and cache
4+
.env
5+
*.pem
6+
*.pyc
7+
*/__pycache__/*
8+
9+
/__pycache__
10+
env/*
11+

Dockerfile

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Use an official Python runtime as a parent image
2+
FROM python:3.12-slim
3+
4+
# Set the working directory in the container
5+
WORKDIR /app
6+
7+
# Copy the current directory contents into the container at /app
8+
COPY . /app
9+
10+
# Install any needed packages specified in requirements.txt
11+
RUN pip install --no-cache-dir -r requirements.txt
12+
13+
EXPOSE 5000
14+
15+
# Run app.py when the container launches
16+
CMD ["python", "app.py"]

app.py

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
# app.py
2+
from quart import Quart
3+
import httpx
4+
import os,markdown2
5+
from db import SupabaseInterface
6+
from apscheduler.schedulers.asyncio import AsyncIOScheduler
7+
from dotenv import load_dotenv
8+
from datetime import datetime
9+
10+
from utils import handle_week_data, parse_issue_description
11+
12+
# Load environment variables from .env file
13+
load_dotenv()
14+
delay_mins: str = os.getenv("SCHEDULER_DELAY_IN_MINS")
15+
16+
app = Quart(__name__)
17+
18+
scheduler = AsyncIOScheduler()
19+
20+
21+
@app.route('/')
22+
async def index():
23+
return 'Hello, World!'
24+
25+
26+
def define_issue_description_update(val):
27+
try:
28+
parsed_body = parse_issue_description(val['body'])
29+
# Get contributor from assignee
30+
assignee = val['assignee']
31+
if assignee is not None:
32+
contributor = assignee['login']
33+
else:
34+
contributor = ''
35+
issue_update = {
36+
"mentor_username": parsed_body['mentor'],
37+
"contributor_username": contributor,
38+
"title": val['title'],
39+
"description": parsed_body['description']
40+
}
41+
return issue_update
42+
except Exception as e:
43+
print(e)
44+
return {}
45+
46+
47+
def define_issue_update(val, dmp_id):
48+
try:
49+
issue_update = {
50+
"dmp_id": dmp_id,
51+
"body_text": val['body'],
52+
"comment_id": val['id'],
53+
"comment_updated_at": val['updated_at'],
54+
"comment_link": val['html_url'],
55+
"comment_api": val['comments_url'] if 'comments_url' in val else val['url'],
56+
"created_by": val['user']['login']
57+
}
58+
return issue_update
59+
except Exception as e:
60+
print(e)
61+
return {}
62+
63+
64+
def define_pr_update(pr_val, dmp_id):
65+
try:
66+
pr_data = {
67+
"dmp_id": dmp_id,
68+
"pr_id": pr_val['id'],
69+
"pr_updated_at": pr_val['updated_at'],
70+
"status": pr_val['state'],
71+
"merged_at": pr_val['merged_at'],
72+
"closed_at": pr_val['closed_at'],
73+
"created_at": pr_val['created_at'],
74+
"title": pr_val['title'],
75+
"link": pr_val['html_url']
76+
}
77+
78+
return pr_data
79+
except Exception as e:
80+
print(e)
81+
return {}
82+
83+
84+
@app.route('/dmp_updates')
85+
async def dmp_updates():
86+
print(
87+
f"Issue description, comments and PR job started --- {datetime.now()}")
88+
GITHUB_TOKEN = os.getenv('GITHUB_TOKEN')
89+
try:
90+
TARGET_DATE = os.getenv('TARGET_DATE')
91+
db = SupabaseInterface().get_instance()
92+
93+
# Loop through all dmp issues
94+
dmp_tickets = db.get_dmp_issues()
95+
96+
for dmp in dmp_tickets:
97+
dmp_id = dmp['id']
98+
issue_number = dmp['issue_number']
99+
repo = dmp['repo']
100+
owner = dmp['dmp_orgs']['repo_owner']
101+
102+
app.logger.info("DMP_ID: "+str(dmp_id))
103+
104+
# # Make the HTTP request to GitHub API
105+
headers = {
106+
"Accept": "application/vnd.github+json",
107+
"Authorization": f"Bearer {GITHUB_TOKEN}",
108+
"X-GitHub-Api-Version": "2022-11-28"
109+
}
110+
111+
# 1. Read & Update Description of the ticket
112+
GITHUB_ISSUE_URL = "https://api.github.com/repos/{owner}/{repo}/issues/{issue_number}"
113+
description_url = GITHUB_ISSUE_URL.format(
114+
owner=owner, repo=repo, issue_number=issue_number)
115+
async with httpx.AsyncClient() as client:
116+
issue_response = await client.get(description_url, headers=headers)
117+
if issue_response.status_code == 200:
118+
# Parse issue discription
119+
issue_update = define_issue_description_update(
120+
issue_response.json())
121+
122+
issue_update['mentor_username'] = dmp['mentor_username'] #get from db
123+
issue_update['contributor_username'] = dmp['contributor_username'] #get from db
124+
125+
app.logger.info('Decription from remote: ', issue_update)
126+
update_data = db.update_data(
127+
issue_update, 'dmp_issues', 'id', dmp_id)
128+
app.logger.info(update_data)
129+
else:
130+
app.logger.error("Description API failed: " +
131+
str(issue_response.status_code) + " for dmp_id: "+str(dmp_id))
132+
133+
# 2. Read & Update comments of the ticket
134+
GITHUB_COMMENTS_URL = "https://api.github.com/repos/{owner}/{repo}/issues/{issue_number}/comments"
135+
comments_url = GITHUB_COMMENTS_URL.format(
136+
owner=owner, repo=repo, issue_number=issue_number)
137+
async with httpx.AsyncClient() as client:
138+
comments_response = await client.get(comments_url, headers=headers)
139+
if comments_response.status_code == 200:
140+
week_update_status = False
141+
# Loop through comments
142+
for val in comments_response.json():
143+
# Handle if any of the comments are week data
144+
plain_text_body = markdown2.markdown(val['body'])
145+
if "Weekly Goals" in plain_text_body and not week_update_status:
146+
week_update_status = handle_week_data(val, dmp['issue_url'], dmp_id, issue_update['mentor_username'])
147+
148+
# Parse comments
149+
comment_update = define_issue_update(
150+
val, dmp_id=dmp_id)
151+
app.logger.info(
152+
'Comment from remote: ', comment_update)
153+
upsert_comments = db.upsert_data(
154+
comment_update, 'dmp_issue_updates')
155+
app.logger.info(upsert_comments)
156+
else:
157+
app.logger.error("Comments API failed: " +
158+
str(issue_response.status_code) + " for dmp_id: "+str(dmp_id))
159+
160+
# 3. Read & Update PRs of the ticket
161+
GITHUB_PR_URL = "https://api.github.com/repos/{owner}/{repo}/pulls"
162+
pr_url = GITHUB_PR_URL.format(owner=owner, repo=repo)
163+
async with httpx.AsyncClient() as client:
164+
pr_response = await client.get(pr_url, headers=headers)
165+
if pr_response.status_code == 200:
166+
for pr_val in pr_response.json():
167+
# Select only those prs which have the issue number in ticket
168+
if "#"+str(issue_number) not in pr_val['title']:
169+
continue
170+
pr_created_at = pr_val['created_at']
171+
if (pr_created_at >= TARGET_DATE) or 1 == 1:
172+
pr_data = define_pr_update(pr_val, dmp_id)
173+
upsert_pr = db.upsert_data(
174+
pr_data, 'dmp_pr_updates')
175+
app.logger.info(upsert_pr)
176+
else:
177+
app.logger.error("PR API failed: " +
178+
str(issue_response.status_code) + " for dmp_id: "+str(dmp_id))
179+
return "success"
180+
except Exception as e:
181+
print(e)
182+
return "Server Error"
183+
184+
185+
@app.before_serving
186+
async def start_scheduler():
187+
app.logger.info(
188+
"Scheduling dmp_updates_job to run every "+delay_mins+" mins")
189+
scheduler.add_job(dmp_updates, 'interval', minutes=int(delay_mins))
190+
scheduler.start()
191+
192+
if __name__ == '__main__':
193+
app.run(host='0.0.0.0')

0 commit comments

Comments
 (0)