-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #12 from openearth/feature/pdf-cloud-function
Feature/pdf-cloud-function
- Loading branch information
Showing
47 changed files
with
9,049 additions
and
799 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
# This workflow build and push a Docker container to Google Artifact Registry and deploy it on Cloud Run when a commit is pushed to the "main" branch | ||
# | ||
# Overview: | ||
# | ||
# 1. Authenticate to Google Cloud | ||
# 2. Authenticate Docker to Artifact Registry | ||
# 3. Build a docker container | ||
# 4. Publish it to Google Artifact Registry | ||
# 5. Deploy it to Cloud Run | ||
# | ||
# To configure this workflow: | ||
# | ||
# 1. Ensure the required Google Cloud APIs are enabled: | ||
# | ||
# Cloud Run run.googleapis.com | ||
# Artifact Registry artifactregistry.googleapis.com | ||
# | ||
# 2. Create and configure Workload Identity Federation for GitHub (https://github.com/google-github-actions/auth#setting-up-workload-identity-federation) | ||
# | ||
# 3. Ensure the required IAM permissions are granted | ||
# | ||
# Cloud Run | ||
# roles/run.admin | ||
# roles/iam.serviceAccountUser (to act as the Cloud Run runtime service account) | ||
# | ||
# Artifact Registry | ||
# roles/artifactregistry.admin (project or repository level) | ||
# | ||
# NOTE: You should always follow the principle of least privilege when assigning IAM roles | ||
# | ||
# 4. Create GitHub secrets for WIF_PROVIDER and WIF_SERVICE_ACCOUNT | ||
# | ||
# 5. Change the values for the GAR_LOCATION, SERVICE and REGION environment variables (below). | ||
# | ||
# NOTE: To use Google Container Registry instead, replace ${{ env.GAR_LOCATION }}-docker.pkg.dev with gcr.io | ||
# | ||
# For more support on how to run this workflow, please visit https://github.com/marketplace/actions/deploy-to-cloud-run | ||
# | ||
# Further reading: | ||
# Cloud Run IAM permissions - https://cloud.google.com/run/docs/deploying | ||
# Artifact Registry IAM permissions - https://cloud.google.com/artifact-registry/docs/access-control#roles | ||
# Container Registry vs Artifact Registry - https://cloud.google.com/blog/products/application-development/understanding-artifact-registry-vs-container-registry | ||
# Principle of least privilege - https://cloud.google.com/blog/products/identity-security/dont-get-pwned-practicing-the-principle-of-least-privilege | ||
|
||
name: Build and Deploy Report function to Cloud Run | ||
|
||
on: | ||
push: | ||
branches: | ||
- "main" | ||
- feature/pdf-cloud-function | ||
|
||
env: | ||
PROJECT_ID: dgds-i1000482-002 | ||
GAR_LOCATION: europe-west3 | ||
SERVICE: gca-report-function | ||
REPOSITORY: gca-artifacts | ||
REGION: europe-west3 | ||
|
||
jobs: | ||
deploy: | ||
# Add 'id-token' with the intended permissions for workload identity federation | ||
permissions: | ||
contents: "read" | ||
id-token: "write" | ||
|
||
runs-on: ubuntu-latest | ||
steps: | ||
- name: Checkout | ||
uses: actions/checkout@v2 | ||
|
||
# NOTE: Alternative option - authentication via credentials json | ||
- name: Google Auth | ||
id: auth | ||
uses: "google-github-actions/auth@v2" | ||
with: | ||
credentials_json: "${{ secrets.GCP_FUNCTION_CREDENTIALS }}" | ||
|
||
# BEGIN - Docker auth and build (NOTE: If you already have a container image, these Docker steps can be omitted) | ||
- name: "Set up Cloud SDK" | ||
uses: "google-github-actions/setup-gcloud@v2" | ||
with: | ||
version: ">= 450.0.0" | ||
|
||
# Authenticate Docker to Google Cloud Artifact Registry | ||
- name: Docker Auth | ||
id: docker-auth | ||
run: |- | ||
gcloud auth print-access-token | docker login -u oauth2accesstoken --password-stdin ${{ env.GAR_LOCATION }}-docker.pkg.dev | ||
- name: Build and Push Container | ||
working-directory: App/functions/report-python-cloud-run | ||
run: |- | ||
docker build -t "${{ env.GAR_LOCATION }}-docker.pkg.dev/${{ env.PROJECT_ID }}/${{ env.REPOSITORY }}/${{ env.SERVICE }}:${{ github.sha }}" . | ||
docker push "${{ env.GAR_LOCATION }}-docker.pkg.dev/${{ env.PROJECT_ID }}/${{ env.REPOSITORY }}/${{ env.SERVICE }}:${{ github.sha }}" | ||
# END - Docker auth and build | ||
|
||
- name: Deploy to Cloud Run | ||
id: deploy | ||
uses: google-github-actions/deploy-cloudrun@v2 | ||
with: | ||
service: ${{ env.SERVICE }} | ||
region: ${{ env.REGION }} | ||
# NOTE: If using a pre-built image, update the image name here | ||
image: ${{ env.GAR_LOCATION }}-docker.pkg.dev/${{ env.PROJECT_ID }}/${{ env.REPOSITORY }}/${{ env.SERVICE }}:${{ github.sha }} | ||
|
||
# If required, use the Cloud Run url output in later steps | ||
- name: Show Output | ||
run: echo ${{ steps.deploy.outputs.url }} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
# Copyright 2020 Google, LLC. | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
# Use the official lightweight Python image. | ||
# https://hub.docker.com/_/python | ||
FROM python:3.11-slim | ||
|
||
# Allow statements and log messages to immediately appear in the Knative logs | ||
ENV PYTHONUNBUFFERED True | ||
|
||
# Copy local code to the container image. | ||
ENV APP_HOME /app | ||
WORKDIR $APP_HOME | ||
COPY . ./ | ||
|
||
# Install production dependencies. | ||
RUN pip install -r requirements.txt | ||
|
||
# Run the web service on container startup. Here we use the gunicorn | ||
# webserver, with one worker process and 8 threads. | ||
# For environments with multiple CPU cores, increase the number of workers | ||
# to be equal to the cores available. | ||
CMD exec gunicorn --bind :$PORT --workers 1 --threads 8 --timeout 0 main:app |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
# Developing the report-python-cloud-run Function | ||
|
||
## Prerequisites | ||
|
||
Create a virtual environment and install the dependencies: | ||
|
||
```bash | ||
pip install -r requirements.txt | ||
``` | ||
|
||
## Testing | ||
|
||
Run the report function locally: | ||
|
||
```bash | ||
python report.py | ||
``` | ||
|
||
## Deploying | ||
|
||
Deploying to Cloud run is done using github actions. The workflow is defined in `.github/workflows/deploy_function.yml`. The workflow is triggered on push to the `main` branch. |
Empty file.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
import json | ||
import os | ||
|
||
from shapely import Polygon # type: ignore | ||
from shapely.geometry import shape # type: ignore | ||
from flask import Flask, make_response, render_template_string, request | ||
|
||
from report.report import ( | ||
create_report_html, | ||
create_report_pdf, | ||
POLYGON_DEFAULT, | ||
STAC_ROOT_DEFAULT, | ||
) | ||
|
||
app = Flask(__name__) | ||
|
||
|
||
@app.route("/", methods=["GET"]) | ||
def return_report(): | ||
"""Return a report for the given polygon""" | ||
polygon_str = request.args.get("polygon") | ||
|
||
if not polygon_str: | ||
polygon_str = POLYGON_DEFAULT | ||
|
||
origin = request.headers.get("Referer") | ||
print(f"detected origin: {origin}") | ||
|
||
# For now we pin the stac_root on a default because we | ||
# don't have a way to pass it in from the client and cant handle the password | ||
# protected preview deployments | ||
stac_root = STAC_ROOT_DEFAULT | ||
|
||
polygon = shape(json.loads(polygon_str)) | ||
if not isinstance(polygon, Polygon): | ||
raise ValueError("Invalid polygon") | ||
|
||
web_page_content = create_report_html(polygon=polygon, stac_root=stac_root) | ||
pdf_object = create_report_pdf(web_page_content) | ||
|
||
response = make_response(pdf_object.getvalue()) | ||
response.headers["Content-Type"] = "application/pdf" | ||
response.headers["Content-Disposition"] = "inline; filename=coastal_report.pdf" | ||
response.headers["Access-Control-Allow-Origin"] = "*" # CORS | ||
return response | ||
|
||
|
||
@app.route("/html") | ||
def return_html(): | ||
"""Return a report for the given polygon""" | ||
polygon_str = request.args.get("polygon") | ||
|
||
if not polygon_str: | ||
polygon_str = POLYGON_DEFAULT | ||
|
||
origin = request.headers.get("Referer") | ||
print(f"detected origin: {origin}") | ||
|
||
# For now we pin the stac_root on a default because we | ||
# don't have a way to pass it in from the client and cant handle the password | ||
# protected preview deployments | ||
stac_root = STAC_ROOT_DEFAULT | ||
|
||
polygon = shape(json.loads(polygon_str)) | ||
if not isinstance(polygon, Polygon): | ||
raise ValueError("Invalid polygon") | ||
|
||
web_page_content = create_report_html(polygon=polygon, stac_root=stac_root) | ||
|
||
response = make_response(render_template_string(web_page_content)) | ||
response.headers["Access-Control-Allow-Origin"] = "*" # CORS | ||
return response | ||
|
||
|
||
if __name__ == "__main__": | ||
app.run(debug=True, host="0.0.0.0", port=int(os.environ.get("PORT", 8080))) |
Empty file.
Empty file.
13 changes: 13 additions & 0 deletions
13
App/functions/report-python-cloud-run/report/datasets/base_dataset.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
from typing import Optional | ||
import xarray as xr | ||
|
||
from .datasetcontent import DatasetContent | ||
from .esl import get_esl_content | ||
|
||
|
||
def get_dataset_content(dataset_id: str, xarr: xr.Dataset) -> Optional[DatasetContent]: | ||
match dataset_id: | ||
case "esl_gwl": | ||
return get_esl_content(xarr) | ||
case _: | ||
return None |
11 changes: 11 additions & 0 deletions
11
App/functions/report-python-cloud-run/report/datasets/datasetcontent.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
from dataclasses import dataclass | ||
from typing import Optional | ||
|
||
|
||
@dataclass | ||
class DatasetContent: | ||
dataset_id: str | ||
title: str | ||
text: str | ||
image_base64: Optional[str] = None | ||
image_svg: Optional[str] = None |
Oops, something went wrong.