FAQ | This is a LIVE service | Changelog

Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • uis/devops/lib/ucam-faas-python
1 result
Show changes
# This provider runs in the context of the person invoking Terraform (i.e. your personal @cam.ac.uk account).
# This is simply used to create tokens to impersonate other, more powerful, service accounts.
provider "google" {
alias = "impersonation"
}
# Generate a token for the terraform-deploy service account for the current workspace.
data "google_service_account_access_token" "terraform_deploy" {
target_service_account = local.workspace_config.terraform_sa_email
scopes = ["cloud-platform"]
provider = google.impersonation
}
provider "google" {
access_token = data.google_service_account_access_token.terraform_deploy.access_token
project = local.project
region = local.region
}
provider "google-beta" {
access_token = data.google_service_account_access_token.terraform_deploy.access_token
project = local.project
region = local.region
}
# A GitLab bot user access token is available in a secret manager secret.
data "google_secret_manager_secret_version" "gitlab_access_token" {
secret = local.gitlab_access_token_secret_name
project = local.gitlab_access_token_secret_project
}
# A GitLab provider is used to interact with projects on the Developer Hub.
provider "gitlab" {
token = data.google_secret_manager_secret_version.gitlab_access_token.secret_data
base_url = local.gitlab_base_url
}
# versions.tf specifies minimum versions for providers and terraform.
terraform {
required_version = "~> 1.7"
# Specify the required providers, their version restrictions and where to get them.
required_providers {
gitlab = {
source = "gitlabhq/gitlab"
version = "~> 16.11"
}
google = {
source = "hashicorp/google"
version = "~> 5.0"
}
google-beta = {
source = "hashicorp/google-beta"
version = "~> 5.0"
}
random = {
source = "hashicorp/random"
version = "~> 3.6"
}
}
}
......@@ -9,20 +9,55 @@ import gunicorn.app.base
import structlog
from cloudevents.http.event import CloudEvent
from gunicorn.config import get_default_config_file
from ucam_observe import get_structlog_logger
from .exceptions import UCAMFAASException
logger = get_structlog_logger(__name__)
def _common_function_wrapper(function):
def _common_function_wrapper_internal(*args, **kwargs):
try:
return function(*args, **kwargs)
except UCAMFAASException as exception:
exception_name = exception.__class__.__name__
logger.warning("function_failed_gracefully", exception_name=exception_name)
return flask.jsonify(details=f"The function raised {exception_name}."), 500
except Exception as exception:
exception_name = exception.__class__.__name__
logger.error("function_failed_uncaught_exception", exception_name=exception_name)
# FIXME dump stack trace into logs for unhandled exception
raise exception
return _common_function_wrapper_internal
def raw_event(function):
@_common_function_wrapper
def _raw_event_internal(request: flask.Request) -> flask.typing.ResponseReturnValue:
return function(request.data)
return_value = function(request.data)
if return_value is not None:
return return_value
return "", 200
_raw_event_internal.__name__ = function.__name__
_raw_event_internal = functions_framework.http(_raw_event_internal)
_raw_event_internal.__wrapped__ = function
return _raw_event_internal
def cloud_event(function):
@_common_function_wrapper
def _cloud_event_internal(event: CloudEvent) -> None:
return function(event.data)
......
class UCAMFAASException(Exception):
pass
class UCAMFAASCouldNotProcess(UCAMFAASException):
pass
......@@ -4,7 +4,7 @@ try:
from pytest import fixture
@fixture
def event_app_client():
def event_app_test_client_factory():
def _event_app_client(target, source=None):
test_app = _initialize_ucam_faas_app(target, source, True)
return test_app.test_client()
......
......@@ -2,7 +2,23 @@ from unittest.mock import patch
from flask import Flask
from ucam_faas import FaaSGunicornApplication
from ucam_faas import FaaSGunicornApplication, raw_event
from ucam_faas.exceptions import UCAMFAASCouldNotProcess
@raw_event
def example_no_exception(raw_event):
pass
@raw_event
def example_handled_exception(raw_event):
raise UCAMFAASCouldNotProcess
@raw_event
def example_unhandled_exception(raw_event):
raise Exception("Did not expect this")
def test_faas_gunicorn_application_bind():
......@@ -12,3 +28,27 @@ def test_faas_gunicorn_application_bind():
with patch("gunicorn.app.base.BaseApplication.run") as mock_run:
application.run()
mock_run.assert_called_once() # Ensures that the server's run method was indeed called
def test_exceptions(event_app_test_client_factory):
# No exception
test_client = event_app_test_client_factory(
target="example_no_exception", source="ucam_faas/tests/test_ucam_faas.py"
)
response = test_client.get("/")
assert response.status_code == 200
# Handle exception
test_client = event_app_test_client_factory(
target="example_handled_exception", source="ucam_faas/tests/test_ucam_faas.py"
)
response = test_client.get("/")
assert response.status_code == 500
assert "UCAMFAASCouldNotProcess" in response.json["details"]
# Unhandles exception
test_client = event_app_test_client_factory(
target="example_unhandled_exception", source="ucam_faas/tests/test_ucam_faas.py"
)
response = test_client.get("/")
assert response.status_code == 500