Skip to content

Commit 62d5072

Browse files
committed
feat: add initial flask app with cloud run deployment pipeline
- Set up basic Flask application to retrieve GCP service account info - Add Dockerfile and requirements for containerization - Implement GitHub Actions workflow for automated deployment to Cloud Run - Include security scanning with Trivy as part of CI/CD pipeline
0 parents  commit 62d5072

File tree

4 files changed

+215
-0
lines changed

4 files changed

+215
-0
lines changed

.github/workflows/deploy.yml

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
name: Cloud Run Pipeline
2+
3+
on:
4+
push:
5+
branches:
6+
- 'prod-*' # Matches all production branches
7+
8+
env:
9+
# Define the configuration map once as an environment variable
10+
CONFIG_MAP: >
11+
{
12+
"purbalingga": {"project_id": "${{ vars.GCP_PURBALINGGA_ID }}", "secret_name": "GCP_PURBALINGGA_SA_KEY"},
13+
"lombokbarat": {"project_id": "${{ vars.GCP_LOMBOKBARAT_ID }}", "secret_name": "GCP_LOMBOKBARAT_SA_KEY"}
14+
}
15+
16+
jobs:
17+
setup:
18+
runs-on: ubuntu-latest
19+
outputs:
20+
project_id: ${{ steps.set-env.outputs.project_id }}
21+
repo_name: ${{ steps.set-env.outputs.repo_name }}
22+
environment: ${{ steps.set-env.outputs.environment }}
23+
secret_name: ${{ steps.set-env.outputs.secret_name }}
24+
25+
steps:
26+
- name: Extract environment name from branch
27+
id: extract-env
28+
run: |
29+
BRANCH_NAME=${GITHUB_REF#refs/heads/}
30+
# Extract environment name after 'prod-' prefix
31+
ENV_NAME=${BRANCH_NAME#prod-}
32+
echo "Environment name: $ENV_NAME"
33+
echo "environment=$ENV_NAME" >> "$GITHUB_OUTPUT"
34+
35+
- name: Set environment variables based on branch
36+
id: set-env
37+
run: |
38+
ENV_NAME="${{ steps.extract-env.outputs.environment }}"
39+
40+
# Extract project ID and secret name for this environment
41+
PROJECT_ID=$(echo $CONFIG_MAP | jq -r --arg env "$ENV_NAME" '.[$env].project_id')
42+
SECRET_NAME=$(echo $CONFIG_MAP | jq -r --arg env "$ENV_NAME" '.[$env].secret_name')
43+
44+
if [ "$PROJECT_ID" == "null" ]; then
45+
echo "Error: No configuration found for environment $ENV_NAME"
46+
exit 1
47+
fi
48+
49+
echo "project_id=$PROJECT_ID" >> "$GITHUB_OUTPUT"
50+
echo "environment=$ENV_NAME" >> "$GITHUB_OUTPUT"
51+
echo "secret_name=$SECRET_NAME" >> "$GITHUB_OUTPUT"
52+
echo "repo_name=$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_OUTPUT"
53+
54+
build_and_push:
55+
needs: setup
56+
runs-on: ubuntu-latest
57+
58+
steps:
59+
- name: Checkout code
60+
uses: actions/checkout@v4
61+
62+
- name: Set up Docker Buildx
63+
uses: docker/setup-buildx-action@v3
64+
65+
- name: Log in to GitHub Container Registry
66+
uses: docker/login-action@v3
67+
with:
68+
registry: ghcr.io
69+
username: ${{ secrets.GHCR_USERNAME }}
70+
password: ${{ secrets.GHCR_TOKEN }}
71+
72+
- name: Build and push Docker image
73+
uses: docker/build-push-action@v5
74+
with:
75+
context: .
76+
push: true
77+
tags: ghcr.io/${{ needs.setup.outputs.repo_name }}:${{ github.ref_name }}
78+
79+
deploy_cloud_run:
80+
needs: [setup, build_and_push]
81+
runs-on: ubuntu-latest
82+
83+
steps:
84+
- name: Checkout code
85+
uses: actions/checkout@v4
86+
87+
- name: Authenticate to Google Cloud
88+
id: 'auth'
89+
uses: 'google-github-actions/auth@v2'
90+
with:
91+
credentials_json: ${{ secrets[needs.setup.outputs.secret_name] }}
92+
project_id: ${{ needs.setup.outputs.project_id }}
93+
create_credentials_file: true
94+
export_environment_variables: true
95+
96+
- name: 'Deploy to Cloud Run'
97+
uses: 'google-github-actions/deploy-cloudrun@v2'
98+
with:
99+
service: '${{ vars.CR_SERVICE }}'
100+
image: '${{ vars.CR_REGION }}-docker.pkg.dev/${{ needs.setup.outputs.project_id }}/${{ vars.AR_REPO }}/${{ needs.setup.outputs.repo_name }}:${{ github.ref_name }}'
101+
region: '${{ vars.CR_REGION }}'
102+
env_vars: |
103+
PROJECT_ID=${{ needs.setup.outputs.project_id }}
104+
105+
trivy_scan:
106+
needs: [setup, build_and_push]
107+
runs-on: ubuntu-latest
108+
109+
steps:
110+
- name: Checkout code
111+
uses: actions/checkout@v4
112+
113+
- name: Log in to GitHub Container Registry
114+
uses: docker/login-action@v3
115+
with:
116+
registry: ghcr.io
117+
username: ${{ secrets.GHCR_USERNAME }}
118+
password: ${{ secrets.GHCR_TOKEN }}
119+
120+
- name: Pull image for scanning
121+
run: docker pull ghcr.io/${{ needs.setup.outputs.repo_name }}:${{ github.ref_name }}
122+
123+
- name: Run Trivy vulnerability scan
124+
uses: aquasecurity/trivy-action@0.30.0
125+
with:
126+
image-ref: ghcr.io/${{ needs.setup.outputs.repo_name }}:${{ github.ref_name }}
127+
format: table
128+
exit-code: 0
129+
ignore-unfixed: true
130+
vuln-type: os,library
131+
132+
- name: Generate scan info
133+
id: scan-info
134+
run: |
135+
echo "timestamp=$(date +%Y-%m-%d_%H-%M)" >> $GITHUB_OUTPUT
136+
echo "branch=${GITHUB_REF#refs/heads/}" >> $GITHUB_OUTPUT
137+
138+
- name: Save Trivy scan log
139+
run: |
140+
trivy image ghcr.io/${{ needs.setup.outputs.repo_name }}:${{ github.ref_name }} > "security-scan_${{ steps.scan-info.outputs.branch }}_${{ steps.scan-info.outputs.timestamp }}.log"
141+
142+
- name: Upload Trivy scan log
143+
uses: actions/upload-artifact@v4.6.2
144+
with:
145+
name: security-scan_${{ steps.scan-info.outputs.branch }}_${{ steps.scan-info.outputs.timestamp }}
146+
path: security-scan_${{ steps.scan-info.outputs.branch }}_${{ steps.scan-info.outputs.timestamp }}.log

Dockerfile

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Use the official Python image
2+
FROM python:3.9-slim
3+
4+
# Set working directory
5+
WORKDIR /app
6+
7+
# Copy requirements file
8+
COPY requirements.txt .
9+
10+
# Install dependencies
11+
RUN pip install --no-cache-dir -r requirements.txt
12+
13+
# Copy application code
14+
COPY app.py .
15+
16+
# Set environment variables
17+
ENV PORT=8080
18+
19+
# Expose the port
20+
EXPOSE 8080
21+
22+
# Use gunicorn as the production server
23+
CMD exec gunicorn --bind :$PORT --workers 1 --threads 8 --timeout 0 app:app

main.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
from flask import Flask, jsonify
2+
from google.auth import default
3+
from google.auth.transport.requests import Request
4+
import os
5+
6+
app = Flask(__name__)
7+
8+
@app.route('/', methods=['GET'])
9+
def get_service_account_info():
10+
try:
11+
# Get credentials from the environment
12+
credentials, project_id = default(scopes=['https://www.googleapis.com/auth/cloud-healthcare'])
13+
14+
# Get service account email
15+
service_account_email = credentials.service_account_email
16+
17+
# Request an access token
18+
request = Request()
19+
credentials.refresh(request)
20+
token = credentials.token
21+
22+
# Return all information in one response
23+
return jsonify({
24+
"service_account": service_account_email,
25+
"project_id": project_id,
26+
"token": token,
27+
"success": True
28+
})
29+
except Exception as e:
30+
return jsonify({
31+
"service_account": "Unknown",
32+
"project_id": "Unknown",
33+
"token": "Error retrieving token",
34+
"error": str(e),
35+
"success": False
36+
}), 500
37+
38+
if __name__ == '__main__':
39+
# Get port from environment variable or default to 8080
40+
port = int(os.environ.get('PORT', 8080))
41+
# Run the app, listening on all interfaces
42+
app.run(host='0.0.0.0', port=port)

requirements.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
flask==2.3.3
2+
google-auth==2.22.0
3+
requests==2.31.0
4+
gunicorn==21.2.0

0 commit comments

Comments
 (0)