├── terraform
├── wms_api
│ ├── cog_renderer
│ │ ├── .gitignore
│ │ ├── src
│ │ │ ├── styles
│ │ │ │ ├── aerosols.csv
│ │ │ │ └── cloud_moisture.csv
│ │ │ └── main.py
│ │ ├── iam.tf
│ │ └── main.tf
│ ├── list_layers
│ │ ├── .gitignore
│ │ ├── iam.tf
│ │ ├── main.tf
│ │ └── src
│ │ │ └── main.py
│ ├── main.tf
│ ├── api.tf
│ ├── iam.tf
│ └── api.yaml
├── netcdf_datasource
│ ├── process_netcdf
│ │ ├── .gitignore
│ │ ├── src
│ │ │ ├── main.py
│ │ │ └── netcdf_to_geotiff.py
│ │ ├── main.tf
│ │ └── iam.tf
│ ├── filter_subscription
│ │ ├── .gitignore
│ │ ├── src
│ │ │ └── main.py
│ │ ├── iam.tf
│ │ └── main.tf
│ ├── sns.tf
│ └── main.tf
├── .gitignore
├── variables.tf
├── s3
│ └── main.tf
├── main.tf
└── webui
│ └── index.html
├── .flake8
└── README.md
/terraform/wms_api/cog_renderer/.gitignore:
--------------------------------------------------------------------------------
1 | src.zip
2 |
--------------------------------------------------------------------------------
/terraform/wms_api/list_layers/.gitignore:
--------------------------------------------------------------------------------
1 | src.zip
2 |
--------------------------------------------------------------------------------
/terraform/netcdf_datasource/process_netcdf/.gitignore:
--------------------------------------------------------------------------------
1 | src.zip
2 |
--------------------------------------------------------------------------------
/terraform/netcdf_datasource/filter_subscription/.gitignore:
--------------------------------------------------------------------------------
1 | src.zip
2 |
--------------------------------------------------------------------------------
/terraform/.gitignore:
--------------------------------------------------------------------------------
1 | .terraform/
2 | terraform.tfvars
3 | terraform.tfstate.backup
4 |
--------------------------------------------------------------------------------
/terraform/wms_api/cog_renderer/src/styles/aerosols.csv:
--------------------------------------------------------------------------------
1 | nv,0,0,0,0
2 | 0,0,0,0,0
3 | 0.25,100,100,100,100
4 | 0.5,175,175,175,255
5 | 0.75,200,200,200,255
6 | 1.10,255,255,255,255
7 |
--------------------------------------------------------------------------------
/terraform/wms_api/cog_renderer/src/styles/cloud_moisture.csv:
--------------------------------------------------------------------------------
1 | nv,0,0,0,0
2 | 300,0,0,0,0
3 | 240,175,175,175,100
4 | 230,255,255,255,200
5 | 210,31,144,184,255
6 | 180,0,0,255,255
7 |
--------------------------------------------------------------------------------
/.flake8:
--------------------------------------------------------------------------------
1 | [flake8]
2 | max-line-length = 88
3 | docstring-convention = google
4 | docstring-quotes = "
5 | inline-quotes = "
6 | enable-extensions = G, SC, M
7 | ignore = D212, D300,
8 | import-order-style = smarkets
9 |
--------------------------------------------------------------------------------
/terraform/netcdf_datasource/sns.tf:
--------------------------------------------------------------------------------
1 | resource "aws_sns_topic_subscription" "new_goes_object" {
2 | topic_arn = var.sns_topic_arn
3 | protocol = "lambda"
4 | endpoint = module.filter_subscription.lambda_arn
5 | }
6 |
--------------------------------------------------------------------------------
/terraform/wms_api/main.tf:
--------------------------------------------------------------------------------
1 | /*
2 | * The purpose of this module is to implement a WMS API
3 | */
4 |
5 | # Name and Project Tags
6 | variable "name" {}
7 | variable "project" {}
8 |
9 | # Parameters
10 | variable "region" {}
11 | variable "source_bucket" {}
12 |
13 | # Lambda functions
14 | module "cog_renderer" {
15 | source = "./cog_renderer"
16 | name = "${var.name}_cog_renderer"
17 | project = var.project
18 | source_bucket = var.source_bucket
19 | api_gateway_arn = aws_api_gateway_rest_api.tile.execution_arn
20 | }
21 |
22 | module "list_layers" {
23 | source = "./list_layers"
24 | name = "${var.name}_list_layers"
25 | project = var.project
26 | source_bucket = var.source_bucket
27 | api_gateway_arn = aws_api_gateway_rest_api.tile.execution_arn
28 | }
29 |
--------------------------------------------------------------------------------
/terraform/variables.tf:
--------------------------------------------------------------------------------
1 | # AWS Credentials
2 | variable "access_key" {
3 | type = string
4 | description = "AWS Access Key"
5 | }
6 | variable "secret_key" {
7 | type = string
8 | description = "AWS Secret Key"
9 | }
10 | variable "aws_region" {
11 | type = string
12 | description = "AWS Region"
13 | default = "us-east-1"
14 | }
15 |
16 | # Name and Project Tags
17 | variable "name" {
18 | type = string
19 | description = "Name prefix used by all resources"
20 | }
21 | variable "project" {
22 | type = string
23 | description = "A 'project' tag is added to all resources to track cost"
24 | }
25 |
26 | # Main S3 Bucket name
27 | variable "s3_bucket_name" {
28 | type = string
29 | description = "Name for the S3 bucket where data is stored. S3 bucket names must be unique"
30 | }
31 |
--------------------------------------------------------------------------------
/terraform/wms_api/api.tf:
--------------------------------------------------------------------------------
1 | # Tiling API
2 |
3 | resource "aws_api_gateway_rest_api" "tile" {
4 | name = var.name
5 | body = data.template_file.openapi.rendered
6 | }
7 |
8 | # Open API definition
9 | data "template_file" "openapi" {
10 | template = file("${path.module}/api.yaml")
11 |
12 | vars = {
13 | name = var.name
14 | cog_renderer = module.cog_renderer.lambda_arn
15 | list_layers = module.list_layers.lambda_arn
16 | region = var.region
17 | credentials = aws_iam_role.api_gw.arn
18 | }
19 | }
20 |
21 | # API Deployment
22 | resource "aws_api_gateway_deployment" "demo" {
23 | rest_api_id = aws_api_gateway_rest_api.tile.id
24 | stage_name = "demo"
25 |
26 | # Set the description to a hash of the OpenAPI file to force updates on changes
27 | stage_description = md5(file("${path.module}/api.yaml"))
28 | }
29 |
--------------------------------------------------------------------------------
/terraform/s3/main.tf:
--------------------------------------------------------------------------------
1 | /*
2 | * The purpose of this module is to manage all S3 resources
3 | */
4 |
5 | # Name and Project Tags
6 | variable "name" {}
7 | variable "project" {}
8 |
9 | # S3 Bucket Name
10 | variable "s3_bucket_name" {}
11 |
12 | # Main S3 bucket
13 | resource "aws_s3_bucket" "data" {
14 | bucket = var.s3_bucket_name
15 | acl = "private"
16 | tags = {
17 | Name = var.name
18 | Project = var.project
19 | }
20 |
21 | # Apply default encryption for all objects
22 | server_side_encryption_configuration {
23 | rule {
24 | apply_server_side_encryption_by_default {
25 | sse_algorithm = "AES256"
26 | }
27 | }
28 | }
29 |
30 | # Expire items older than 1 day
31 | lifecycle_rule {
32 | id = "expire"
33 | enabled = true
34 |
35 | expiration {
36 | days = 1
37 | }
38 | }
39 | }
40 |
41 | # block all public access
42 | resource "aws_s3_bucket_public_access_block" "data" {
43 | bucket = aws_s3_bucket.data.id
44 |
45 | block_public_acls = true
46 | block_public_policy = true
47 | ignore_public_acls = true
48 | restrict_public_buckets = true
49 | }
50 |
--------------------------------------------------------------------------------
/terraform/netcdf_datasource/process_netcdf/src/main.py:
--------------------------------------------------------------------------------
1 | """Ingest a new NetCDF File."""
2 |
3 | # System Imports
4 | import logging
5 | import os
6 |
7 | # External Imports
8 | import boto3
9 | import netcdf_to_geotiff
10 |
11 | # Set environment variables for GDAL and Proj
12 | os.environ["PROJ_LIB"] = "/opt/share/proj"
13 | os.environ["GDAL_DATA"] = "/opt/share/gdal"
14 |
15 | # Configure Logging
16 | logger = logging.getLogger()
17 | logger.setLevel(logging.INFO)
18 |
19 | # Get S3 Client
20 | client = boto3.client("s3")
21 |
22 | # Get environment variables
23 | SOURCE_BUCKET = os.environ["source_bucket"]
24 | DEST_BUCKET = os.environ["dest_bucket"]
25 |
26 |
27 | def handler(event, context):
28 | """AWS Lambda handler."""
29 | logger.info(f"Processing File: {event['key']}")
30 |
31 | data = netcdf_to_geotiff.convert(
32 | SOURCE_BUCKET,
33 | event["key"],
34 | event["definition"]["parameter_name"],
35 | event["definition"]["band"],
36 | client,
37 | )
38 |
39 | # Upload to S3
40 | client.upload_file(data["local_file"], DEST_BUCKET, data["key"])
41 |
42 | # Cleanup
43 | os.remove(data["local_file"])
44 |
--------------------------------------------------------------------------------
/terraform/wms_api/iam.tf:
--------------------------------------------------------------------------------
1 | /*
2 | * IAM Role assumed by API Gateway
3 | */
4 |
5 | # Role
6 | resource "aws_iam_role" "api_gw" {
7 | name = "${var.name}_api_gw"
8 | path = "/"
9 | assume_role_policy = data.aws_iam_policy_document.api_gw.json
10 |
11 | tags = {
12 | Name = "${var.name}_api_gw"
13 | Project = var.project
14 | }
15 | }
16 |
17 | # Service using role
18 | data "aws_iam_policy_document" "api_gw" {
19 | statement {
20 | effect = "Allow"
21 | actions = ["sts:AssumeRole"]
22 | principals {
23 | type = "Service"
24 | identifiers = ["apigateway.amazonaws.com"]
25 | }
26 | }
27 | }
28 |
29 | # In-line policy attachment
30 | resource "aws_iam_role_policy" "api_gw" {
31 | name = "${var.name}_api_gw"
32 | role = aws_iam_role.api_gw.id
33 | policy = data.aws_iam_policy_document.lambda_invoke.json
34 | }
35 |
36 | # In-line policy definition
37 | data "aws_iam_policy_document" "lambda_invoke" {
38 | statement {
39 | sid = "InvokeLambda"
40 | effect = "Allow"
41 | actions = [
42 | "lambda:InvokeFunction"
43 | ]
44 | resources = [
45 | module.cog_renderer.lambda_arn,
46 | module.list_layers.lambda_arn
47 | ]
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/terraform/main.tf:
--------------------------------------------------------------------------------
1 | # Specify the provider and access details
2 | provider "aws" {
3 | access_key = var.access_key
4 | secret_key = var.secret_key
5 | region = var.aws_region
6 | }
7 |
8 | # Configure terraform to store its state on S3
9 | terraform {
10 | backend "s3" {}
11 | }
12 |
13 | # Modules
14 | module "geos16" {
15 | source = "./netcdf_datasource"
16 | name = "${var.name}_goes16"
17 | project = var.project
18 | sns_topic_arn = "arn:aws:sns:us-east-1:123901341784:NewGOES16Object"
19 | source_bucket = "noaa-goes16"
20 | dest_bucket = var.s3_bucket_name
21 | data_definitions = [{
22 | filter_regex = "ABI-L2-CMIPF\\/[0-9]{4}\\/[0-9]{3}/[0-9]{2}/OR_ABI-L2-CMIPF-M6C09.*.nc"
23 | parameter_name = "CMI"
24 | band = 1
25 | }, {
26 | filter_regex = "ABI-L2-CMIPC\\/[0-9]{4}\\/[0-9]{3}/[0-9]{2}/OR_ABI-L2-CMIPC-M6C09.*.nc"
27 | parameter_name = "CMI"
28 | band = 1
29 | }]
30 | }
31 |
32 | module "s3" {
33 | source = "./s3"
34 | name = var.name
35 | project = var.project
36 | s3_bucket_name = var.s3_bucket_name
37 | }
38 |
39 | module "wms_api" {
40 | source = "./wms_api"
41 | name = var.name
42 | project = var.project
43 | source_bucket = var.s3_bucket_name
44 | region = var.aws_region
45 | }
46 |
--------------------------------------------------------------------------------
/terraform/wms_api/list_layers/iam.tf:
--------------------------------------------------------------------------------
1 | /*
2 | * IAM Role used by this lambda function
3 | */
4 |
5 | # Role
6 | resource "aws_iam_role" "lambda" {
7 | name = "${var.name}_lambda"
8 | path = "/"
9 | assume_role_policy = data.aws_iam_policy_document.lambda.json
10 |
11 | tags = {
12 | Name = "${var.name}_lambda"
13 | Project = var.project
14 | }
15 | }
16 |
17 | # Service using role
18 | data "aws_iam_policy_document" "lambda" {
19 | statement {
20 | effect = "Allow"
21 | actions = ["sts:AssumeRole"]
22 | principals {
23 | type = "Service"
24 | identifiers = ["lambda.amazonaws.com"]
25 | }
26 | }
27 | }
28 |
29 | # In-line policy attachment
30 | resource "aws_iam_role_policy" "s3" {
31 | name = "${var.name}_s3"
32 | role = aws_iam_role.lambda.id
33 | policy = data.aws_iam_policy_document.s3.json
34 | }
35 |
36 | # In-line policy definition
37 | data "aws_iam_policy_document" "s3" {
38 | statement {
39 | sid = "S3ListBucket"
40 | effect = "Allow"
41 | actions = [
42 | "s3:ListBucket"
43 | ]
44 | resources = ["arn:aws:s3:::${var.source_bucket}"]
45 | }
46 | }
47 |
48 | # AWS CloudWatch policy attachment
49 | resource "aws_iam_role_policy_attachment" "cloudwatch" {
50 | role = aws_iam_role.lambda.id
51 | policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
52 | }
53 |
--------------------------------------------------------------------------------
/terraform/netcdf_datasource/filter_subscription/src/main.py:
--------------------------------------------------------------------------------
1 | """Parse SNS event notification."""
2 |
3 | # System Imports
4 | import json
5 | import logging
6 | import os
7 | import re
8 |
9 | # External Imports
10 | import boto3
11 |
12 | # Configure Logging
13 | logger = logging.getLogger()
14 | logger.setLevel(logging.INFO)
15 |
16 | # Get environment variables
17 | PROCESSING_FUNCTION = os.environ["processing_function"]
18 | DATA_DEFINITIONS = json.loads(os.environ["data_definitions"])
19 |
20 | # Get Lambda Client
21 | client = boto3.client("lambda")
22 |
23 |
24 | def handler(event, context):
25 | """AWS Lambda handler."""
26 | sns_record = event["Records"][0]["Sns"]
27 |
28 | # Pull the fine name
29 | goes16_event = json.loads(sns_record["Message"])
30 | file_name = goes16_event["Records"][0]["s3"]["object"]["key"]
31 | logger.info(file_name)
32 |
33 | for definition in DATA_DEFINITIONS:
34 | file_pattern = re.compile(definition["filter_regex"])
35 | if file_pattern.fullmatch(file_name):
36 | logger.info(f"Processing: {file_name}")
37 | client = boto3.client("lambda")
38 | client.invoke(
39 | FunctionName=PROCESSING_FUNCTION,
40 | InvocationType="Event",
41 | Payload=json.dumps({"key": file_name, "definition": definition}),
42 | )
43 |
44 | return
45 |
--------------------------------------------------------------------------------
/terraform/netcdf_datasource/filter_subscription/iam.tf:
--------------------------------------------------------------------------------
1 | /*
2 | * IAM Role used by this lambda function
3 | */
4 |
5 | # Role
6 | resource "aws_iam_role" "lambda" {
7 | name = "${var.name}_lambda"
8 | path = "/"
9 | assume_role_policy = data.aws_iam_policy_document.lambda.json
10 |
11 | tags = {
12 | Name = "${var.name}_lambda"
13 | Project = var.project
14 | }
15 | }
16 |
17 | # Service using role
18 | data "aws_iam_policy_document" "lambda" {
19 | statement {
20 | effect = "Allow"
21 | actions = ["sts:AssumeRole"]
22 | principals {
23 | type = "Service"
24 | identifiers = ["lambda.amazonaws.com"]
25 | }
26 | }
27 | }
28 |
29 | # In-line policy attachment
30 | resource "aws_iam_role_policy" "lambda" {
31 | name = "${var.name}_lambda"
32 | role = aws_iam_role.lambda.id
33 | policy = data.aws_iam_policy_document.lambda_invoke.json
34 | }
35 |
36 | # In-line policy definition
37 | data "aws_iam_policy_document" "lambda_invoke" {
38 | statement {
39 | sid = "InvokeLambda"
40 | effect = "Allow"
41 | actions = [
42 | "lambda:InvokeFunction"
43 | ]
44 | resources = [var.processing_function]
45 | }
46 | }
47 |
48 | # AWS CloudWatch policy attachment
49 | resource "aws_iam_role_policy_attachment" "cloudwatch" {
50 | role = aws_iam_role.lambda.id
51 | policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
52 | }
53 |
--------------------------------------------------------------------------------
/terraform/webui/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
11 |
12 | OpenLayers example
13 |
14 |
15 |
16 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/terraform/netcdf_datasource/process_netcdf/main.tf:
--------------------------------------------------------------------------------
1 | /*
2 | * Lambda function that converts a NetCDF file to a COG
3 | */
4 |
5 | # Name and Project Tags
6 | variable "name" {type = string}
7 | variable "project" {type = string}
8 |
9 | # Parameters
10 | variable "source_bucket" {type = string}
11 | variable "dest_bucket" {type = string}
12 |
13 | # Archive Source Code
14 | data "archive_file" "process_netcdf" {
15 | type = "zip"
16 | source_dir = "${path.module}/src/"
17 | output_path = "${path.module}/src.zip"
18 | }
19 |
20 | resource "aws_lambda_function" "process_netcdf" {
21 | filename = "${path.module}/src.zip"
22 | source_code_hash = data.archive_file.process_netcdf.output_base64sha256
23 | layers = [
24 | "arn:aws:lambda:us-east-1:552188055668:layer:geolambda-python:3",
25 | "arn:aws:lambda:us-east-1:552188055668:layer:geolambda:4"
26 | ]
27 | function_name = var.name
28 | description = "Convert a NetCDF file to a COG"
29 | role = aws_iam_role.lambda.arn
30 | handler = "main.handler"
31 | runtime = "python3.7"
32 | timeout = 900
33 | memory_size = 384
34 |
35 | environment {
36 | variables = {
37 | source_bucket = var.source_bucket
38 | dest_bucket = var.dest_bucket
39 | }
40 | }
41 |
42 | tags = {
43 | Name = var.name
44 | Project = var.project
45 | }
46 | }
47 |
48 | # Return Lambda ARN
49 | output "lambda_arn" {
50 | value = aws_lambda_function.process_netcdf.arn
51 | }
52 |
--------------------------------------------------------------------------------
/terraform/netcdf_datasource/main.tf:
--------------------------------------------------------------------------------
1 | /*
2 | * The purpose of this module is to define a goes16 datasource
3 | */
4 |
5 | # Name and Project Tags
6 | variable "name" {type = string}
7 | variable "project" {type = string}
8 |
9 | # Parameters
10 | variable "sns_topic_arn" {
11 | type = string
12 | default = "arn:aws:sns:us-east-1:123901341784:NewGOES16Object"
13 | }
14 | variable "source_bucket" {
15 | type = string
16 | default = "noaa-goes16"
17 | }
18 | variable "dest_bucket" {type = string}
19 | variable "data_definitions" {
20 | type = list(object({
21 | filter_regex = string
22 | parameter_name = string
23 | band = number
24 | }))
25 | default = [
26 | {
27 | filter_regex = "ABI-L2-CMIPF\\/[0-9]{4}\\/[0-9]{3}/[0-9]{2}/OR_ABI-L2-CMIPF-M6C09.*.nc"
28 | parameter_name = "CMI"
29 | band = 1
30 | }
31 | ]
32 | }
33 |
34 | # Lambda functions
35 | module "filter_subscription" {
36 | source = "./filter_subscription"
37 | name = "${var.name}_filter_subscription"
38 | project = var.project
39 | sns_topic_arn = var.sns_topic_arn
40 | processing_function = module.process_netcdf.lambda_arn
41 | data_definitions = var.data_definitions
42 | }
43 |
44 | module "process_netcdf" {
45 | source = "./process_netcdf"
46 | name = "${var.name}_process_netcdf"
47 | project = var.project
48 | source_bucket = var.source_bucket
49 | dest_bucket = var.dest_bucket
50 | }
51 |
--------------------------------------------------------------------------------
/terraform/wms_api/cog_renderer/iam.tf:
--------------------------------------------------------------------------------
1 | /*
2 | * IAM Role used by this lambda function
3 | */
4 |
5 | # Role
6 | resource "aws_iam_role" "lambda" {
7 | name = "${var.name}_lambda"
8 | path = "/"
9 | assume_role_policy = data.aws_iam_policy_document.lambda.json
10 |
11 | tags = {
12 | Name = "${var.name}_lambda"
13 | Project = var.project
14 | }
15 | }
16 |
17 | # Service using role
18 | data "aws_iam_policy_document" "lambda" {
19 | statement {
20 | effect = "Allow"
21 | actions = ["sts:AssumeRole"]
22 | principals {
23 | type = "Service"
24 | identifiers = ["lambda.amazonaws.com"]
25 | }
26 | }
27 | }
28 |
29 | # In-line policy attachment
30 | resource "aws_iam_role_policy" "s3" {
31 | name = "${var.name}_s3"
32 | role = aws_iam_role.lambda.id
33 | policy = data.aws_iam_policy_document.s3.json
34 | }
35 |
36 | # In-line policy definition
37 | data "aws_iam_policy_document" "s3" {
38 | statement {
39 | sid = "S3ListBucket"
40 | effect = "Allow"
41 | actions = [
42 | "s3:ListBucket"
43 | ]
44 | resources = ["arn:aws:s3:::${var.source_bucket}"]
45 | }
46 |
47 | statement {
48 | sid = "S3GetObject"
49 | effect = "Allow"
50 | actions = [
51 | "s3:GetObject"
52 | ]
53 | resources = ["arn:aws:s3:::${var.source_bucket}/*"]
54 | }
55 | }
56 |
57 | # AWS CloudWatch policy attachment
58 | resource "aws_iam_role_policy_attachment" "cloudwatch" {
59 | role = aws_iam_role.lambda.id
60 | policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
61 | }
62 |
--------------------------------------------------------------------------------
/terraform/wms_api/list_layers/main.tf:
--------------------------------------------------------------------------------
1 | /*
2 | * Lambda function that lists available layers
3 | */
4 |
5 | # Name and Project Tags
6 | variable "name" {}
7 | variable "project" {}
8 |
9 | # Parameters
10 | variable "source_bucket" {}
11 | variable "api_gateway_arn" {}
12 |
13 | # Archive Source Code
14 | data "archive_file" "list_layers" {
15 | type = "zip"
16 | source_dir = "${path.module}/src/"
17 | output_path = "${path.module}/src.zip"
18 | }
19 |
20 | resource "aws_lambda_function" "list_layers" {
21 | filename = "${path.module}/src.zip"
22 | source_code_hash = data.archive_file.list_layers.output_base64sha256
23 |
24 | function_name = var.name
25 | description = "Lists layers in an S3 Bucket"
26 | role = aws_iam_role.lambda.arn
27 | handler = "main.handler"
28 | runtime = "python3.7"
29 | timeout = 300
30 | memory_size = 128
31 |
32 | environment {
33 | variables = {
34 | source_bucket = var.source_bucket
35 | }
36 | }
37 |
38 | tags = {
39 | Name = var.name
40 | Project = var.project
41 | }
42 | }
43 |
44 | # Allow API Gateway to invoke this lambda function
45 | resource "aws_lambda_permission" "allow_API_GW" {
46 | statement_id = "AllowExecutionFromAPIGW"
47 | action = "lambda:InvokeFunction"
48 | function_name = aws_lambda_function.list_layers.function_name
49 | principal = "apigateway.amazonaws.com"
50 | source_arn = "${var.api_gateway_arn}/*/GET/layers"
51 | }
52 |
53 | # Return Lambda ARN
54 | output "lambda_arn" {
55 | value = aws_lambda_function.list_layers.arn
56 | }
57 |
--------------------------------------------------------------------------------
/terraform/netcdf_datasource/process_netcdf/iam.tf:
--------------------------------------------------------------------------------
1 | /*
2 | * IAM Role used by this lambda function
3 | */
4 |
5 | # Role
6 | resource "aws_iam_role" "lambda" {
7 | name = "${var.name}_lambda"
8 | path = "/"
9 | assume_role_policy = data.aws_iam_policy_document.lambda.json
10 |
11 | tags = {
12 | Name = "${var.name}_lambda"
13 | Project = var.project
14 | }
15 | }
16 |
17 | # Service using role
18 | data "aws_iam_policy_document" "lambda" {
19 | statement {
20 | effect = "Allow"
21 | actions = ["sts:AssumeRole"]
22 | principals {
23 | type = "Service"
24 | identifiers = ["lambda.amazonaws.com"]
25 | }
26 | }
27 | }
28 |
29 | # In-line policy attachments
30 | resource "aws_iam_role_policy" "s3" {
31 | name = "${var.name}_s3"
32 | role = aws_iam_role.lambda.id
33 | policy = data.aws_iam_policy_document.s3.json
34 | }
35 |
36 | # In-line policy definition
37 | data "aws_iam_policy_document" "s3" {
38 | statement {
39 | sid = "S3ListBucket"
40 | effect = "Allow"
41 | actions = [
42 | "s3:ListBucket"
43 | ]
44 | resources = ["arn:aws:s3:::${var.source_bucket}"]
45 | }
46 |
47 | statement {
48 | sid = "S3GetObject"
49 | effect = "Allow"
50 | actions = [
51 | "s3:GetObject"
52 | ]
53 | resources = ["arn:aws:s3:::${var.source_bucket}/*"]
54 | }
55 |
56 | statement {
57 | sid = "S3PutObject"
58 | effect = "Allow"
59 | actions = [
60 | "s3:PutObject"
61 | ]
62 | resources = ["arn:aws:s3:::${var.dest_bucket}/*"]
63 | }
64 | }
65 |
66 | # AWS CloudWatch policy attachment
67 | resource "aws_iam_role_policy_attachment" "cloudwatch" {
68 | role = aws_iam_role.lambda.id
69 | policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
70 | }
71 |
--------------------------------------------------------------------------------
/terraform/wms_api/cog_renderer/main.tf:
--------------------------------------------------------------------------------
1 | /*
2 | * Lambda function that converts a COG to an image
3 | */
4 |
5 | # Name and Project Tags
6 | variable "name" {}
7 | variable "project" {}
8 |
9 | # Parameters
10 | variable "source_bucket" {}
11 | variable "api_gateway_arn" {}
12 |
13 | # Archive Source Code
14 | data "archive_file" "cog_renderer" {
15 | type = "zip"
16 | source_dir = "${path.module}/src/"
17 | output_path = "${path.module}/src.zip"
18 | }
19 |
20 | resource "aws_lambda_function" "cog_renderer" {
21 | filename = "${path.module}/src.zip"
22 | source_code_hash = data.archive_file.cog_renderer.output_base64sha256
23 | layers = [
24 | "arn:aws:lambda:us-east-1:710449791192:layer:geolambda-python:3",
25 | "arn:aws:lambda:us-east-1:710449791192:layer:geolambda:9"
26 | ]
27 | function_name = var.name
28 | description = "Convert a COG to an image"
29 | role = aws_iam_role.lambda.arn
30 | handler = "main.handler"
31 | runtime = "python3.8"
32 | timeout = 300
33 | memory_size = 128
34 |
35 | environment {
36 | variables = {
37 | source_bucket = var.source_bucket
38 | }
39 | }
40 |
41 | tags = {
42 | Name = var.name
43 | Project = var.project
44 | }
45 | }
46 |
47 | # Allow API Gateway to invoke this lambda function
48 | resource "aws_lambda_permission" "allow_API_GW" {
49 | statement_id = "AllowExecutionFromAPIGW"
50 | action = "lambda:InvokeFunction"
51 | function_name = aws_lambda_function.cog_renderer.function_name
52 | principal = "apigateway.amazonaws.com"
53 | source_arn = "${var.api_gateway_arn}/*/GET/wms"
54 | }
55 |
56 | # Return Lambda ARN
57 | output "lambda_arn" {
58 | value = aws_lambda_function.cog_renderer.arn
59 | }
60 |
--------------------------------------------------------------------------------
/terraform/netcdf_datasource/filter_subscription/main.tf:
--------------------------------------------------------------------------------
1 | /*
2 | * Lambda function filters an SNS subscription
3 | */
4 |
5 | # Name and Project Tags
6 | variable "name" {type = string}
7 | variable "project" {type = string}
8 |
9 | # Parameters
10 | variable "sns_topic_arn" {type = string}
11 | variable "processing_function" {type = string}
12 | variable "data_definitions" {
13 | type = list(object({
14 | filter_regex = string
15 | parameter_name = string
16 | band = number
17 | }))
18 | }
19 |
20 | # Archive Source Code
21 | data "archive_file" "filter_subscription" {
22 | type = "zip"
23 | source_dir = "${path.module}/src/"
24 | output_path = "${path.module}/src.zip"
25 | }
26 |
27 | resource "aws_lambda_function" "filter_subscription" {
28 | filename = "${path.module}/src.zip"
29 | source_code_hash = data.archive_file.filter_subscription.output_base64sha256
30 | function_name = var.name
31 | description = "Filter the sns topic"
32 | role = aws_iam_role.lambda.arn
33 | handler = "main.handler"
34 | runtime = "python3.7"
35 | timeout = 120
36 |
37 | environment {
38 | variables = {
39 | processing_function = var.processing_function
40 | data_definitions = jsonencode(var.data_definitions)
41 | }
42 | }
43 |
44 | tags = {
45 | Name = var.name
46 | Project = var.project
47 | }
48 | }
49 |
50 | # Allow SNS to invoke this lambda function
51 | resource "aws_lambda_permission" "allow_SNS" {
52 | statement_id = "AllowExecutionFromSNS"
53 | action = "lambda:InvokeFunction"
54 | function_name = aws_lambda_function.filter_subscription.function_name
55 | principal = "sns.amazonaws.com"
56 | source_arn = var.sns_topic_arn
57 | }
58 |
59 | # Return Lambda ARN
60 | output "lambda_arn" {
61 | value = aws_lambda_function.filter_subscription.arn
62 | }
63 |
--------------------------------------------------------------------------------
/terraform/wms_api/cog_renderer/src/main.py:
--------------------------------------------------------------------------------
1 | """Convert a COG to png."""
2 | # System Imports
3 | import base64
4 | import logging
5 | import os
6 | import sys
7 |
8 | # update path to gdal libraries
9 | sys.path.append("/opt/python/osgeo")
10 |
11 | # External Imports
12 | from osgeo import gdal
13 |
14 | # Set environment variables for GDAL and Proj
15 | os.environ["PROJ_LIB"] = "/opt/share/proj"
16 | os.environ["GDAL_DATA"] = "/opt/share/gdal"
17 |
18 | # Configure Logging
19 | logger = logging.getLogger()
20 | logger.setLevel(logging.INFO)
21 |
22 | # Get environment variables
23 | SOURCE_BUCKET = os.environ["source_bucket"]
24 |
25 |
26 | def handler(event, context):
27 | """AWS Lambda handler."""
28 | layername = event["queryStringParameters"]["LAYERS"]
29 | width = event["queryStringParameters"]["WIDTH"]
30 | height = event["queryStringParameters"]["HEIGHT"]
31 | bounding_box = event["queryStringParameters"]["BBOX"].split(",")
32 | style = event["queryStringParameters"]["STYLES"]
33 |
34 | # Convert to GeoTIFF
35 | gdal_raster = gdal.Translate(
36 | "",
37 | f"/vsis3/{SOURCE_BUCKET}/{layername}",
38 | format="MEM",
39 | width=width,
40 | height=height,
41 | projWin=[bounding_box[0], bounding_box[3], bounding_box[2], bounding_box[1]],
42 | projWinSRS="EPSG:3857",
43 | scaleParams=[],
44 | )
45 |
46 | # Add color
47 | gdal.DEMProcessing(
48 | os.path.join("/tmp", "file.png"),
49 | gdal_raster,
50 | "color-relief",
51 | addAlpha=True,
52 | colorFilename=os.path.join("styles", f"{style}.csv"),
53 | )
54 | gdal_raster = None
55 |
56 | with open(os.path.join("/tmp", "file.png"), "rb") as image_file:
57 | encoded_image = base64.b64encode(image_file.read()).decode("utf-8")
58 |
59 | return {
60 | "isBase64Encoded": True,
61 | "statusCode": 200,
62 | "headers": {
63 | "Content-Type": "image/png",
64 | "Access-Control-Allow-Headers": "Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,access-control-allow-origin",
65 | "Access-Control-Allow-Methods": "*",
66 | "Access-Control-Allow-Origin": "*",
67 | },
68 | "body": encoded_image,
69 | }
70 |
--------------------------------------------------------------------------------
/terraform/wms_api/list_layers/src/main.py:
--------------------------------------------------------------------------------
1 | """List layers in an S3 Bucket."""
2 | # System Imports
3 | import json
4 | import logging
5 | import os
6 |
7 | # External Imports
8 | import boto3
9 |
10 | # Configure Logging
11 | logger = logging.getLogger()
12 | logger.setLevel(logging.INFO)
13 |
14 | # Get environment variables
15 | SOURCE_BUCKET = os.environ["source_bucket"]
16 |
17 | # Get S3 Client
18 | client = boto3.client("s3")
19 |
20 |
21 | def handler(event, context):
22 | """AWS Lambda handler."""
23 | layers = list_bucket(SOURCE_BUCKET, "", continuation_token=None, objects=None)
24 |
25 | return {
26 | "statusCode": 200,
27 | "headers": {
28 | "Content-Type": "application/json",
29 | "Access-Control-Allow-Headers": "Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,access-control-allow-origin",
30 | "Access-Control-Allow-Methods": "*",
31 | "Access-Control-Allow-Origin": "*",
32 | },
33 | "body": json.dumps(layers),
34 | }
35 |
36 |
37 | def list_bucket(bucket, prefix, continuation_token=None, objects=None):
38 | """
39 | Recursively lists contents on an S3 bucket.
40 |
41 | Args:
42 | bucket: S3 Bucket Name
43 | prefix: Prefix to use for objects
44 | continuation_token: used for pagination
45 | objects: running list of objects
46 | Returns:
47 | list of objects in the bucket with the specified prefix
48 |
49 | """
50 | if objects is None:
51 | objects = []
52 |
53 | if not continuation_token:
54 | response = client.list_objects_v2(Bucket=bucket, MaxKeys=1000, Prefix=prefix)
55 | else:
56 | response = client.list_objects_v2(
57 | Bucket=bucket,
58 | MaxKeys=1000,
59 | Prefix=prefix,
60 | ContinuationToken=continuation_token,
61 | )
62 |
63 | if "Contents" in response:
64 | objects.extend(
65 | [
66 | s3_object["Key"]
67 | for s3_object in response["Contents"]
68 | if ".tif" in s3_object["Key"]
69 | ]
70 | )
71 |
72 | if "NextContinuationToken" in response:
73 | return list_bucket(
74 | bucket,
75 | prefix,
76 | continuation_token=response["NextContinuationToken"],
77 | objects=objects,
78 | )
79 |
80 | return objects
81 |
--------------------------------------------------------------------------------
/terraform/netcdf_datasource/process_netcdf/src/netcdf_to_geotiff.py:
--------------------------------------------------------------------------------
1 | """Convert NetCDF to a geoTIFF."""
2 |
3 | # System Imports
4 | import logging
5 | import os
6 | import uuid
7 |
8 | # External Imports
9 | from osgeo import gdal
10 |
11 | # Configure Logging
12 | logger = logging.getLogger()
13 | logger.setLevel(logging.INFO)
14 |
15 |
16 | def convert(netcdf_bucket, netcdf_key, parameter_name, band, s3_client):
17 | """
18 | Convert a NetCDF File to a Cloud Optimzed GeoTIFF (COG).
19 |
20 | Args:
21 | netcdf_bucket: Name of the NetCDF source S3 Bucket
22 | netcdf_key: Key to the NetCDF File
23 | parameter_name: Name of the NetCDF Parameter to extract
24 | band: Band number to extract
25 | s3_client: Boto3 S3 client
26 |
27 | Returns:
28 | data: Object containing information about the new COG
29 |
30 | """
31 | input_file = os.path.join("/tmp", "file.nc")
32 |
33 | # Download file
34 | s3_client.download_file(netcdf_bucket, netcdf_key, input_file)
35 |
36 | # Convert to GeoTIFF
37 | temp_file_1 = os.path.join("/tmp", f"{uuid.uuid1()}_1.tif")
38 | gdal.Translate(
39 | temp_file_1,
40 | f"NETCDF:{input_file}:{parameter_name}",
41 | format="GTiff",
42 | bandList=[band],
43 | outputType=gdal.GDT_Float32,
44 | creationOptions=["COMPRESS=DEFLATE"],
45 | unscale=True, # GDAL warp cannot handle scaled values
46 | )
47 | os.remove(input_file)
48 |
49 | # Warp to EPSG:3857
50 | temp_file_2 = os.path.join("/tmp", f"{uuid.uuid1()}_2.tif")
51 | gdal.Warp(
52 | temp_file_2,
53 | temp_file_1,
54 | format="GTiff",
55 | dstSRS="EPSG:3857",
56 | workingType=gdal.GDT_Float32,
57 | outputType=gdal.GDT_Float32,
58 | creationOptions=["COMPRESS=DEFLATE"],
59 | resampleAlg="cubicspline",
60 | )
61 | os.remove(temp_file_1)
62 |
63 | # Apply compression and tiling
64 | output_file = os.path.join("/tmp", f"{uuid.uuid1()}_final.tif")
65 | dataset = gdal.Translate(
66 | output_file,
67 | temp_file_2,
68 | format="GTiff",
69 | creationOptions=["TILED=YES", "COMPRESS=DEFLATE"],
70 | )
71 | os.remove(temp_file_2)
72 |
73 | # Add overviews
74 | dataset.BuildOverviews("NEAREST", [2, 4, 8, 16, 32, 64])
75 |
76 | # Close dataset
77 | dataset = None
78 |
79 | return {
80 | "local_file": output_file,
81 | "key": f'{netcdf_key.split(".")[0]}.tif',
82 | "bbox": [0, 0, 0, 0],
83 | "band": band,
84 | }
85 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # WMS Serverless
2 |
3 | A Serverless implementation of WMS on AWS.
4 |
5 | This repository contains a [terraform](https://www.terraform.io/) project that implements a
6 | serverless version of [WMS](https://www.opengeospatial.org/standards/wms) on
7 | [AWS](https://aws.amazon.com/).
8 |
9 | A live demo can be found here: https://nathancalandra.cloud/goes16-serverless
10 |
11 | ## Components
12 |
13 | This project consists of three modules.
14 |
15 | ### S3
16 |
17 | This modules contains the main S3 bucket used in this project. It's purpose is store layers that
18 | will be served by the API. The data source module can be used to automatically add files to this
19 | bucket. To prevent storing large amounts of data, a life-cycle policy is applied to delete any
20 | object older than one day.
21 |
22 | ### NetCDF Data Source
23 |
24 | This module contains lambda functions and an SNS subscription that are used to convert NetCDF files
25 | on the `noaa-goes16` public S3 bucket to [COGs](https://www.cogeo.org/). It can be configured to
26 | pull as much data as desired from the source bucket. It's default configuration pulls a single
27 | product. To add more products edit the `data_definitions` variable in `terraform/main.tf`. For
28 | example, the following configuration processes bands 2 and 9 from the Cloud Moisture Imagery (CMI)
29 | product. See the [documentation](https://docs.opendata.aws/noaa-goes16/cics-readme.html) on the
30 | public goes16 S3 bucket for a description of the available data.
31 |
32 | ```hcl
33 | [{
34 | filter_regex = "ABI-L2-CMIPF\\/[0-9]{4}\\/[0-9]{3}/[0-9]{2}/OR_ABI-L2-CMIPF-M6C09.*.nc"
35 | parameter_name = "CMI"
36 | band = 1
37 | }, {
38 | filter_regex = "ABI-L2-CMIPF\\/[0-9]{4}\\/[0-9]{3}/[0-9]{2}/OR_ABI-L2-CMIPF-M6C02.*.nc"
39 | parameter_name = "CMI"
40 | band = 1
41 | }]
42 | ```
43 |
44 | ### WMS API
45 |
46 | The WMS API module uses API Gateway and lambda functions to operate on files stored in the S3
47 | bucket. Currently there are two endpoints:
48 |
49 | #### wms
50 |
51 | This is endpoint supports a partial implementation of the WMS "GetMap" request.
52 |
53 | Supported parameters:
54 |
55 | | Name | Supported (yes/no) | Notes |
56 | |-------------|:--------------------:|--------------------------------------------------------|
57 | | VERSION | yes | Only 1.3.0 |
58 | | REQUEST | yes | Only "GetMap" |
59 | | LAYERS | yes | Only one layer at a time |
60 | | STYLES | yes | Name of a csv file in the cog renderer lambda function |
61 | | CRS | no | |
62 | | BBOX | yes | |
63 | | WIDTH | yes | |
64 | | HEIGHT | yes | |
65 | | FORMAT | yes | Only PNG |
66 | | TRANSPARENT | yes | |
67 | | BGCOLOR | no | |
68 | | EXCEPTIONS | no | |
69 | | TIME | no | |
70 | | ELEVATION | no | |
71 |
72 | #### layers
73 |
74 | This endpoint simply lists layers in the main S3 bucket. It is a placeholder until WMS
75 | "GetCapabilities" is supported.
76 |
77 | ## Installation
78 |
79 | 1. Install [terraform](https://www.terraform.io/downloads.html)
80 | 1. Open a terminal inside the terraform directory
81 | 1. Run `terraform init`
82 | 1. Provide variables for s3 backend storage
83 | 1. Run `terraform apply`
84 |
85 | ## AWS Services used by this project
86 |
87 | - [S3](https://aws.amazon.com/s3/)
88 | - [Lambda](https://aws.amazon.com/lambda/)
89 | - [SNS](https://aws.amazon.com/sns/)
90 | - [API Gateway](https://aws.amazon.com/api-gateway/)
91 |
92 |
93 | ## Lambda, GDAL, and Python
94 |
95 | https://github.com/developmentseed/geolambda
96 |
--------------------------------------------------------------------------------
/terraform/wms_api/api.yaml:
--------------------------------------------------------------------------------
1 | openapi: 3.0.2
2 | info:
3 | title: ${name}
4 | description: WMS API
5 | version: 0.0.0
6 | servers:
7 | - url: 'localhost'
8 | description: 'tester'
9 | x-amazon-apigateway-request-validators:
10 | all:
11 | validateRequestBody: true
12 | validateRequestParameters: true
13 | validateRequestHeaders: true
14 | params-only:
15 | validateRequestBody: false
16 | validateRequestParameters: true
17 | validateRequestHeaders: true
18 | x-amazon-apigateway-binary-media-types:
19 | - image/png
20 | - image/jpeg
21 | - image/webp
22 | paths:
23 | /layers:
24 | get:
25 | summary: Get Available Layers
26 | description: Returns a list of available layers
27 | tags:
28 | - Info
29 | responses:
30 | 200:
31 | description: OK
32 | headers:
33 | Access-Control-Allow-Headers:
34 | type: string
35 | Access-Control-Allow-Methods:
36 | type: string
37 | Access-Control-Allow-Origin:
38 | type: string
39 | content:
40 | application/json:
41 | $ref: '#/components/schemas/layers'
42 | x-amazon-apigateway-integration:
43 | type: aws_proxy
44 | uri: arn:aws:apigateway:${region}:lambda:path/2015-03-31/functions/${list_layers}/invocations
45 | httpMethod: POST
46 | credentials: ${credentials}
47 | responses:
48 | default:
49 | statusCode: 200
50 | options:
51 | summary: Enable CORS
52 | description: Enable CORS by returning the correct headers
53 | responses:
54 | 200:
55 | description: Default response for CORS method
56 | headers:
57 | Access-Control-Allow-Headers:
58 | type: string
59 | Access-Control-Allow-Methods:
60 | type: string
61 | Access-Control-Allow-Origin:
62 | type: string
63 | x-amazon-apigateway-integration:
64 | type: mock
65 | responses:
66 | default:
67 | statusCode: 200
68 | responseParameters:
69 | method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,access-control-allow-origin'"
70 | method.response.header.Access-Control-Allow-Methods: "'*'"
71 | method.response.header.Access-Control-Allow-Origin: "'*'"
72 | responseTemplates:
73 | application/json: |
74 | {}
75 | passthroughBehavior: when_no_match
76 | requestTemplates:
77 | application/json: |
78 | {"statusCode" : 200}
79 | /wms:
80 | get:
81 | summary: Web Map Service
82 | description: OGC compliant WMS feed
83 | tags:
84 | - Tile
85 | x-amazon-apigateway-request-validator: params-only
86 | parameters:
87 | - name: VERSION
88 | description: WMS Version (1.3.0)
89 | in: query
90 | required: true
91 | schema:
92 | type: string
93 | example: 1.3.0
94 | - name: REQUEST
95 | description: WMS request type
96 | in: query
97 | required: true
98 | schema:
99 | type: string
100 | example: GetMap
101 | - name: LAYERS
102 | description: layer name
103 | in: query
104 | required: true
105 | schema:
106 | type: string
107 | example: layername
108 | - name: STYLES
109 | description: style name
110 | in: query
111 | required: true
112 | schema:
113 | type: string
114 | example: style
115 | - name: CRS
116 | description: Coordinate Reference System (CRS)
117 | in: query
118 | required: true
119 | schema:
120 | type: string
121 | example: epsg:4326
122 | - name: BBOX
123 | description: Boundingbox corners (minx,miny,maxx,maxy)
124 | in: query
125 | required: true
126 | schema:
127 | type: array
128 | items:
129 | type: number
130 | example: [0,0,0,0]
131 | - name: WIDTH
132 | description: Output image width
133 | in: query
134 | required: true
135 | schema:
136 | type: integer
137 | example: 256
138 | - name: HEIGHT
139 | description: Output image height
140 | in: query
141 | required: true
142 | schema:
143 | type: integer
144 | example: 256
145 | - name: FORMAT
146 | description: Output image format
147 | in: query
148 | required: true
149 | schema:
150 | type: string
151 | example: image/png
152 | - name: TRANSPARENT
153 | description: Background map transparency
154 | in: query
155 | required: false
156 | schema:
157 | type: boolean
158 | default: false
159 | example: false
160 | - name: BGCOLOR
161 | description: Background color
162 | in: query
163 | required: false
164 | schema:
165 | type: string
166 | default: "0xFFFFFF"
167 | example: "0xFFFFFF"
168 | - name: EXCEPTIONS
169 | description: exception format
170 | in: query
171 | required: false
172 | schema:
173 | type: string
174 | default: json
175 | example: json
176 | - name: TIME
177 | description: Time value of the layer
178 | in: query
179 | required: false
180 | schema:
181 | type: string
182 | example: "2019-11-01T01:15:07Z"
183 | - name: ELEVATION
184 | description: Elevation value of the layer
185 | in: query
186 | required: false
187 | schema:
188 | type: string
189 | example: "0"
190 | responses:
191 | 200:
192 | description: OK
193 | headers:
194 | Access-Control-Allow-Headers:
195 | type: string
196 | Access-Control-Allow-Methods:
197 | type: string
198 | Access-Control-Allow-Origin:
199 | type: string
200 | content:
201 | image/png:
202 | $ref: '#/components/schemas/binary'
203 | x-amazon-apigateway-integration:
204 | type: aws_proxy
205 | uri: arn:aws:apigateway:${region}:lambda:path/2015-03-31/functions/${cog_renderer}/invocations
206 | httpMethod: POST
207 | credentials: ${credentials}
208 | requestParameters:
209 | # TODO: maybe not needed?
210 | integration.request.header.Accept: method.request.querystring.FORMAT
211 | responses:
212 | default:
213 | statusCode: 200
214 | options:
215 | summary: Enable CORS
216 | description: Enable CORS by returning the correct headers
217 | responses:
218 | 200:
219 | description: Default response for CORS method
220 | headers:
221 | Access-Control-Allow-Headers:
222 | type: string
223 | Access-Control-Allow-Methods:
224 | type: string
225 | Access-Control-Allow-Origin:
226 | type: string
227 | x-amazon-apigateway-integration:
228 | type: mock
229 | responses:
230 | default:
231 | statusCode: 200
232 | responseParameters:
233 | method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,access-control-allow-origin'"
234 | method.response.header.Access-Control-Allow-Methods: "'*'"
235 | method.response.header.Access-Control-Allow-Origin: "'*'"
236 | responseTemplates:
237 | application/json: |
238 | {}
239 | passthroughBehavior: when_no_match
240 | requestTemplates:
241 | application/json: |
242 | {"statusCode" : 200}
243 | components:
244 | schemas:
245 | binary:
246 | type: string
247 | format: binary
248 | layers:
249 | type: array
250 | items:
251 | type: string
252 |
--------------------------------------------------------------------------------