diff --git a/docker/.dockerignore b/docker/.dockerignore deleted file mode 100644 index b512c09..0000000 --- a/docker/.dockerignore +++ /dev/null @@ -1 +0,0 @@ -node_modules \ No newline at end of file diff --git a/docker/Dockerfile b/docker/Dockerfile deleted file mode 100644 index 9b82e8d..0000000 --- a/docker/Dockerfile +++ /dev/null @@ -1,16 +0,0 @@ -FROM node:10 -LABEL org.opencontainers.image.source="https://github.com/podaac/hydrocron-api" -RUN npm install forever -g - -ENV project_dir /project -ENV app_dir ${project_dir}/app -ENV config_dir ${project_dir}/config - -RUN mkdir ${project_dir} ${app_dir} ${config_dir} -WORKDIR ${app_dir} - -COPY package*.json ./ -RUN npm install -COPY . . - -CMD ${app_dir}/docker/docker-start-command \ No newline at end of file diff --git a/docker/build-docker.sh b/docker/build-docker.sh deleted file mode 100755 index 497ccd3..0000000 --- a/docker/build-docker.sh +++ /dev/null @@ -1,74 +0,0 @@ -#!/usr/bin/env bash - -# This script is intended to be run by the CI/CD pipeline to build a specific version of this application. - -set -Eeo pipefail - -LOCAL_BUILD=false - -POSITIONAL=() -while [[ $# -gt 0 ]] -do -key="$1" - -case $key in - -n|--service-name) - service_name="$2" - shift # past argument - shift # past value - ;; - -v|--service-version) - service_version="$2" - shift # past argument - shift # past value - ;; - --local) - LOCAL_BUILD=true - shift # past argument - ;; - *) # unknown option - POSITIONAL+=("$1") # save it in an array for later - shift # past argument - ;; -esac -done -set -- "${POSITIONAL[@]}" # restore positional parameters - -USAGE="USAGE: build-docker.sh -n|--service-name service_name -v|--service-version service_version [--local]" - -# shellcheck disable=SC2154 -if [[ -z "${service_name}" ]]; then - echo "service_name required. Name of the service as found in pyproject.toml (e.g. podaac-staging)" >&2 - echo "$USAGE" >&2 - exit 1 -fi - -# shellcheck disable=SC2154 -if [[ -z "${service_version}" ]]; then - echo "service_version required. Version of software to install (e.g. 0.1.0-a1+12353)." >&2 - echo "$USAGE" >&2 - exit 1 -fi - -set -u - -SCRIPTPATH="$( cd "$(dirname "$0")" ; pwd -P )" -PROJECT_DIR="$(dirname "${SCRIPTPATH}")" -DIST_PATH="dist/" - -app_path=$( cd ${SCRIPTPATH}/.. && echo $(pwd) ) - -repositoryName=ghcr.io/podaac/${service_name} - -# Docker tags can't include '+' https://github.com/docker/distribution/issues/1201 -dockerTagVersion=$(echo "${service_version}" | tr "+" _) - -# Build the image -if [ "$LOCAL_BUILD" = true ] ; then - wheel_filename="$(echo "${service_name}" | tr "-" _)-${service_version}-py3-none-any.whl" - docker build -t "${repositoryName}":"${dockerTagVersion}" --build-arg DIST_PATH="${DIST_PATH}" --build-arg SOURCE="${DIST_PATH}${wheel_filename}" -f "$SCRIPTPATH"/Dockerfile "$PROJECT_DIR" 1>&2 -else - docker build -t "${repositoryName}":"${dockerTagVersion}" --build-arg SOURCE="${service_name}==${service_version}" -f "$SCRIPTPATH"/Dockerfile "$app_path" 1>&2 -fi - -echo "${repositoryName}":"${dockerTagVersion}" \ No newline at end of file diff --git a/docker/docker-start-command b/docker/docker-start-command deleted file mode 100755 index 10430d6..0000000 --- a/docker/docker-start-command +++ /dev/null @@ -1,27 +0,0 @@ -#!/bin/bash - -outside_config_dir=/config -work_dir=/work -app_dir=/app - -if [ -e "/config/server.crt" ]; then - echo "server.crt is here" - cp /config/server.crt /project/config/server.crt -fi; - -if [ -e "/config/server.key" ]; then - echo "server.key is here" - cp /config/server.key /project/config/server.key -fi; - -if [ -e "/config/config.js" ]; then - echo "config.js is here" - cp /config/config.js /project/config/config.js -fi; - -if [ -e "/config/private-config.js" ]; then - echo "private-config.js is here" - cp /config/private-config.js /project/config/private-config.js -fi; - -forever server/server.js diff --git a/docker/push-docker-artifactory.sh b/docker/push-docker-artifactory.sh deleted file mode 100755 index c84d2cd..0000000 --- a/docker/push-docker-artifactory.sh +++ /dev/null @@ -1,66 +0,0 @@ -#!/usr/bin/env bash - -# This script is intended to be run by the CI/CD pipeline to push a docker tag previously built by build-docker.sh - -set -Eeo pipefail - -POSITIONAL=() -while [[ $# -gt 0 ]] -do -key="$1" - -case $key in - -t|--docker-tag) - docker_tag="$2" - shift # past argument - shift # past value - ;; - -r|--registry) - ARTIFACTORY_DOCKER_REGISTRY="$2" - shift # past argument - shift # past value - ;; - -u|--artifactory-username) - ARTIFACTORY_USER="$2" - shift # past argument - shift # past value - ;; - -p|--artifactory-password) - ARTIFACTORY_PASSWORD="$2" - shift # past argument - shift # past value - ;; - *) # unknown option - POSITIONAL+=("$1") # save it in an array for later - shift # past argument - ;; -esac -done -set -- "${POSITIONAL[@]}" # restore positional parameters - -USAGE="push-docker-artifactory.sh -t|--docker-tag docker_tag -u|--artifactory-username ARTIFACTORY_USER -p|--artifactory-password ARTIFACTORY_PASSWORD" - -# shellcheck disable=SC2154 -if [[ -z "${docker_tag}" ]]; then - echo "docker_tag required." >&2 - echo "$USAGE" >&2 - exit 1 -fi - -# shellcheck disable=SC2154 -if [[ -z "${ARTIFACTORY_USER}" ]]; then - echo "ARTIFACTORY_USER required." >&2 - echo "$USAGE" >&2 - exit 1 -fi - -# shellcheck disable=SC2154 -if [[ -z "${ARTIFACTORY_PASSWORD}" ]]; then - echo "ARTIFACTORY_PASSWORD required." >&2 - echo "$USAGE" >&2 - exit 1 -fi - -echo "${ARTIFACTORY_PASSWORD}" | docker login --username "${ARTIFACTORY_USER}" --password-stdin "${ARTIFACTORY_DOCKER_REGISTRY}" -docker tag "${docker_tag}" "${ARTIFACTORY_DOCKER_REGISTRY}/${docker_tag}" -docker push "${ARTIFACTORY_DOCKER_REGISTRY}/${docker_tag}" \ No newline at end of file diff --git a/docker/push-docker-ecr.sh b/docker/push-docker-ecr.sh deleted file mode 100755 index 54ab71c..0000000 --- a/docker/push-docker-ecr.sh +++ /dev/null @@ -1,78 +0,0 @@ -#!/usr/bin/env bash - -# This script is intended to be run by the CI/CD pipeline to push a docker tag previously built by build-docker.sh - -set -Eeo pipefail - -POSITIONAL=() -while [[ $# -gt 0 ]] -do -key="$1" - -case $key in - -t|--docker-tag) - docker_tag="$2" - shift # past argument - shift # past value - ;; - -v|--tf-venue) - tf_venue="$2" - case $tf_venue in - ngap-service-sit|ngap-service-uat|ngap-service-ops|ngap-cumulus-swot-sit|ngap-cumulus-sit|ngap-cumulus-swot-uat|ngap-cumulus-uat|ngap-cumulus-ops|ngap-cumulus-sndbx) ;; - *) - echo "tf_venue must be sit, uat, or ops" - exit 1;; - esac - shift # past argument - shift # past value - ;; - *) # unknown option - POSITIONAL+=("$1") # save it in an array for later - shift # past argument - ;; -esac -done -set -- "${POSITIONAL[@]}" # restore positional parameters - -USAGE="push-docker-ecr.sh -t|--docker-tag docker_tag -v|--tf-venue tf_venue" - -# shellcheck disable=SC2154 -if [[ -z "${tf_venue}" ]]; then - echo "tf_venue required. One of sit, uat, ops" >&2 - echo "$USAGE" >&2 - exit 1 -fi - -# shellcheck disable=SC2154 -if [[ -z "${docker_tag}" ]]; then - echo "docker_tag required." >&2 - echo "$USAGE" >&2 - exit 1 -fi - -set -u - -repositoryName=$(echo "${docker_tag}" | awk -F':' '{print $1}') -tf_profile="${tf_venue}" - -# Get the AWS Account ID for this venue/profile -# shellcheck disable=SC2154 -aws_acct=$(aws sts get-caller-identity --profile "$tf_profile" | python -c "import sys, json; print(json.load(sys.stdin)['Account'])") - -# Create repository if needed -aws ecr create-repository --repository-name "${repositoryName}" --profile "$tf_profile" || echo "No need to create, repository ${repositoryName} already exists" - -# Login to ECR -set +x -$(aws ecr get-login --no-include-email --region us-west-2 --profile "$tf_profile" 2> /dev/null) || \ -docker login --username AWS --password "$(aws ecr get-login-password --region us-west-2 --profile "$tf_profile")" "$aws_acct".dkr.ecr.us-west-2.amazonaws.com -set -x - -# Tag the image for this venue's ECR -docker tag "${docker_tag}" "$aws_acct".dkr.ecr.us-west-2.amazonaws.com/"${docker_tag}" - -# Push the tag -docker push "$aws_acct".dkr.ecr.us-west-2.amazonaws.com/"${docker_tag}" - -# Clean up docker -docker rmi "$aws_acct".dkr.ecr.us-west-2.amazonaws.com/"${docker_tag}" || true diff --git a/terraform/.terraform.lock.hcl b/terraform/.terraform.lock.hcl index 7ac277c..8f586e0 100644 --- a/terraform/.terraform.lock.hcl +++ b/terraform/.terraform.lock.hcl @@ -2,37 +2,38 @@ # Manual edits may be lost in future updates. provider "registry.terraform.io/hashicorp/aws" { - version = "3.58.0" + version = "3.51.0" hashes = [ - "h1:kx8p0xV/mGa+7vnGOk02AenU/ZKxF08yT/0jynZgSQI=", - "zh:47118f7a69c8962d74ff67be3c13cac76ba99c900941eed4750c347252a0986d", - "zh:4f3322399bef25cdf43e62a00248d991c5e1ef9d744e9b7cd2f8c398bb60bef3", - "zh:55c2223b32f2f114ce5a0ccadbf1ffb8a892bb3862365a0dc4c8ea1248ff1814", - "zh:5a79cfdfde4c132544173fde8d8672659fd6e1bebdacdac856f103915238b1e4", - "zh:67fbddb0c184c25283fa66d4299b458079a3ed2d82189dc3338fda796b6be14c", - "zh:78ad660c2b965e5817c06aadf267d311e2320e7f6d3faad8061c1e29ef4b60ef", - "zh:a20eace4dbb0fb168f6152599b7ce75a9cf419c85f9ae26d8463baf14abe7ddc", - "zh:a31a99c9b924d98e9a5bf173ee8fe3aee73c9a92cd63b2b10c6f350d8b5c5e49", - "zh:a9210d9b0b65324bbf712318044e89f959a2f9aaff6f69c0a91bb294aff7101e", - "zh:aa885bc6647d218c8b73fcfe33cfee20c11d88078655aa1e194b013fde17a204", - "zh:c5a5122e5dcb1c9e9d8fde2c994bab7ba08d63904cabff61129e139cbf97332f", + "h1:SG9CoqWGJgsJz27G88kBxMjQ/Pl8QGVpcXOU/tDEO84=", + "h1:cFui6662XBswZnkxmCGcalxr2ltnL/5lKMnTLHq/RRw=", + "zh:35a28a35a106c5a4b00294f747f85aaf8feab07ece64f393e109318ca375428a", + "zh:9185a2e3a145abbb791b0cf98e8cdc6134d683e84f6eacae2157b8f0bdb3dbfa", + "zh:92897adf8e510911d6e161b34de5e05375356918137a46718f80edf86744c9e1", + "zh:9c2a84222a02ab661f9b7912243245c57a249d53387773b37f80a8e9e59f8866", + "zh:a05fbdd49ad6112d5f7bc102b5a8486d9724ab6d831b52eca69400f161e6b745", + "zh:ba5472f506bb6a3fb3214c7dfbaab01892f84c1f2c591619924e704a70c01a3e", + "zh:bd12d009f21b52f4e24b1d395922273350b97868110fe2eaf6ab3bee21c424d6", + "zh:be9c7b38c303d997c83649392db3588d637d80985b4c610f2bbc3f11b3a8bce8", + "zh:d6f7fb8378d0cd9cbbdcfdb9f80e99e5fcede08af4afca3b97916747c130278b", + "zh:e32963e4e3d22868ccf94f89e30649a24025b68c78b6c5e91e3aa3bb9b9c4aea", + "zh:eba96e9ffa19941c139326d3c942449a5d0e937ccbfcbe10b55a3c20105d6320", ] } -provider "registry.terraform.io/hashicorp/random" { - version = "3.1.0" +provider "registry.terraform.io/hashicorp/template" { + version = "2.2.0" hashes = [ - "h1:rKYu5ZUbXwrLG1w81k7H3nce/Ys6yAxXhWcbtk36HjY=", - "zh:2bbb3339f0643b5daa07480ef4397bd23a79963cc364cdfbb4e86354cb7725bc", - "zh:3cd456047805bf639fbf2c761b1848880ea703a054f76db51852008b11008626", - "zh:4f251b0eda5bb5e3dc26ea4400dba200018213654b69b4a5f96abee815b4f5ff", - "zh:7011332745ea061e517fe1319bd6c75054a314155cb2c1199a5b01fe1889a7e2", - "zh:738ed82858317ccc246691c8b85995bc125ac3b4143043219bd0437adc56c992", - "zh:7dbe52fac7bb21227acd7529b487511c91f4107db9cc4414f50d04ffc3cab427", - "zh:a3a9251fb15f93e4cfc1789800fc2d7414bbc18944ad4c5c98f466e6477c42bc", - "zh:a543ec1a3a8c20635cf374110bd2f87c07374cf2c50617eee2c669b3ceeeaa9f", - "zh:d9ab41d556a48bd7059f0810cf020500635bfc696c9fc3adab5ea8915c1d886b", - "zh:d9e13427a7d011dbd654e591b0337e6074eef8c3b9bb11b2e39eaaf257044fd7", - "zh:f7605bd1437752114baf601bdf6931debe6dc6bfe3006eb7e9bb9080931dca8a", + "h1:0wlehNaxBX7GJQnPfQwTNvvAf38Jm0Nv7ssKGMaG6Og=", + "h1:94qn780bi1qjrbC3uQtjJh3Wkfwd5+tTtJHOb7KTg9w=", + "zh:01702196f0a0492ec07917db7aaa595843d8f171dc195f4c988d2ffca2a06386", + "zh:09aae3da826ba3d7df69efeb25d146a1de0d03e951d35019a0f80e4f58c89b53", + "zh:09ba83c0625b6fe0a954da6fbd0c355ac0b7f07f86c91a2a97849140fea49603", + "zh:0e3a6c8e16f17f19010accd0844187d524580d9fdb0731f675ffcf4afba03d16", + "zh:45f2c594b6f2f34ea663704cc72048b212fe7d16fb4cfd959365fa997228a776", + "zh:77ea3e5a0446784d77114b5e851c970a3dde1e08fa6de38210b8385d7605d451", + "zh:8a154388f3708e3df5a69122a23bdfaf760a523788a5081976b3d5616f7d30ae", + "zh:992843002f2db5a11e626b3fc23dc0c87ad3729b3b3cff08e32ffb3df97edbde", + "zh:ad906f4cebd3ec5e43d5cd6dc8f4c5c9cc3b33d2243c89c5fc18f97f7277b51d", + "zh:c979425ddb256511137ecd093e23283234da0154b7fa8b21c2687182d9aea8b2", ] } diff --git a/terraform/README.md b/terraform/README.md new file mode 100644 index 0000000..06cd4a6 --- /dev/null +++ b/terraform/README.md @@ -0,0 +1,53 @@ + +# Deploying the HYDROCRON + + +## Dependencies +There are a handful of dependencies needed to deploy the entire Feature Translation Service + +* Terraform - deployment technology. >= Terraform v0.12.7 +* AWS CLI - Amazon Web Service command line interface. >= aws-cli/1.11.120 +* python3 environment - tested with python 3.7, needed for packaging the lambda functions. + +## Soft dependencies +These are dependencies for deploying the entire Feature Translation Service that exist outside of this build. + +* access key for NGAP environment +* Terraform variables defined below + +## Terraform Variables + +| variable | Defined In | Example | Description | +| --------------- | ------------ | ------------------------------------------------------- | ----------- | +| stage | tf_vars | sit | staging environment to which we are deploying | +| app_name | tf_vars | HYDROCRON | Name of the application being deployed - same for all environments| +| credentials | command line | ~/.aws/credentials | AWS credential file to use for authentication | +| profile | command line | ngap-services-sit | AWS Profile to use for authentication | +| docker_tag | command line | podaac/podaac-cloud/podaac-hydrocron:1.0.0-alpha.3 | Name of docker image and tag as returned from `docker/build-docker.sh`. | +| vpc_id | tf_vars | vpc-04d8fc64e8ce5cca8 | VPC Id for use. This is predefined by NGAP. | +| private_subnets | tf_vars | ["subnet-0d15606f25bd4047b","subnet-0adee3417fedb7f05"] | private subnets for use within VPC. This is defined by NGAP | + + +## Building the lambda images +The lambda code needs to be built into a deployable image and uploaded to ECR before running terraform. Normally CI/CD handles this task but if you are trying to run terraform locally it needs to be done manually. + +Follow the instructions in the [docker README](../docker/README.md) to build the image + + +## Build and deploy the application +We use a pre-built docker container to do the deployment (Please do not use local terraform!) + +From the project root directory: +``` +export tf_venue=sit +docker run -v ~/.aws:/home/dockeruser/.aws:ro -v ${PWD}:/home/dockeruser -w /home/dockeruser/terraform cae-artifactory.jpl.nasa.gov:16003/podaac/service/deploy-terraform-1.0.3:latest bash bin/deploy.sh -v ${tf_venue} -t ${docker_tag} +``` + +## Destroying the Application +Similarly, use the pre-built docker container to do the destroy (Please do not use local terraform!) + +From the project root directory: +``` +docker run -v ~/.aws:/home/dockeruser/.aws:ro -v ${PWD}:/home/dockeruser cae-artifactory.jpl.nasa.gov:16003/podaac/service/deploy-terraform-1.0.3:latest bash bin/destroy.sh -v ${tf_venue} -t ${docker_tag} +``` +This will take anywhere from 3-10 minutes. diff --git a/terraform/alb.tf b/terraform/alb.tf deleted file mode 100644 index b13faa7..0000000 --- a/terraform/alb.tf +++ /dev/null @@ -1,38 +0,0 @@ -data "aws_lb" "alb" { - name = var.load_balancer_name -} - - -data "aws_security_group" "alb_sg" { - name = var.load_balancer_sg_name -} - -data "aws_acm_certificate" "issued" { - domain = local.certificate_name -} - -resource "aws_lb_listener" "hydrocron_api_alb_listener" { - load_balancer_arn = data.aws_lb.alb.arn - port = 443 - protocol = "HTTPS" - ssl_policy = "ELBSecurityPolicy-2016-08" - certificate_arn = data.aws_acm_certificate.issued.arn - - default_action { - type = "forward" - target_group_arn = aws_lb_target_group.fargate_service_tg.arn - } -} - -resource "aws_lb_target_group" "fargate_service_tg" { - name = "${local.ec2_resources_name}-tg" - port = 80 - protocol = "HTTP" - target_type = "ip" - vpc_id = data.aws_vpc.default.id - - health_check { - path = "/hydrocron/api/session/user" - matcher = "200-299" - } -} diff --git a/terraform/api-specification-templates/fts_aws_api.yml b/terraform/api-specification-templates/fts_aws_api.yml new file mode 100644 index 0000000..58ea293 --- /dev/null +++ b/terraform/api-specification-templates/fts_aws_api.yml @@ -0,0 +1,956 @@ +openapi: 3.0.1 +info: + title: podaac-hydrocron + version: 1.0.0 + license: + name: Apache 2.0 + url: 'https://www.apache.org/licenses/LICENSE-2.0.html' +servers: + - url: 'https://hydrocron.podaac.earthdata.nasa.gov/' + description: Production + - url: 'https://hydrocron.podaac.uat.earthdata.nasa.gov/' + description: User Acceptance +paths: + '/huc/{huc}': + summary: HUCs + description: Interact with HUC objects by ID + get: + deprecated: true + summary: Get HUC (deprecated) + description: Get one or more HUC by ID + parameters: + - $ref: '#/components/parameters/huc_param' + - $ref: '#/components/parameters/exact_param' + - $ref: '#/components/parameters/page_number_param' + - $ref: '#/components/parameters/page_size_param' + responses: + '200': + $ref: '#/components/responses/Success' + '400': + $ref: '#/components/responses/ClientError' + '404': + $ref: '#/components/responses/NotFound' + '413': + $ref: '#/components/responses/ClientError' + '500': + $ref: '#/components/responses/ServerError' + x-amazon-apigateway-integration: + uri: ${hydrocronapi_v021_lambda_arn} + responses: + default: + statusCode: "200" + responseTemplates: + application/json: | + #set($inputRoot = $input.path('$')) + #if($inputRoot.toString().contains('206 PARTIAL CONTENT')) + #set($context.responseOverride.status = 206) + #end + $input.json('$') + ^400.*: + statusCode: "400" + responseTemplates: + application/json: |- + { + "error" : "$input.path('$.errorMessage')" + } + ^404.*: + statusCode: "404" + responseTemplates: + application/json: |- + { + "error" : "$input.path('$.errorMessage')" + } + ^413.*: + statusCode: "413" + responseTemplates: + application/json: |- + { + "error" : "$input.path('$.errorMessage')" + } + ^[^1-5].*: + statusCode: "500" + responseTemplates: + application/json: |- + { + "error" : "$input.path('$.errorMessage')" + } + requestTemplates: + application/json: |- + { + "body": { + "exact":"$input.params('exact')", + "HUC": "$input.params('huc')", + "page_number": "$input.params('page_number')" , + "page_size": "$input.params('page_size')" , + "polygon_format": "$input.params('polygon_format')" + } + } + passthroughBehavior: when_no_templates + httpMethod: POST + contentHandling: CONVERT_TO_TEXT + type: aws + '/region/{region}': + summary: Regions + description: Interact with HUC objects by Region + get: + deprecated: true + summary: Get Region (deprecated) + description: Get one or more HUC by region + parameters: + - $ref: '#/components/parameters/region_param' + - $ref: '#/components/parameters/exact_param' + - $ref: '#/components/parameters/page_number_param' + - $ref: '#/components/parameters/page_size_param' + responses: + '200': + $ref: '#/components/responses/Success' + '400': + $ref: '#/components/responses/ClientError' + '404': + $ref: '#/components/responses/NotFound' + '413': + $ref: '#/components/responses/ClientError' + '500': + $ref: '#/components/responses/ServerError' + x-amazon-apigateway-integration: + uri: ${hydrocronapi_v021_lambda_arn} + responses: + default: + statusCode: "200" + responseTemplates: + application/json: | + #set($inputRoot = $input.path('$')) + #if($inputRoot.toString().contains('206 PARTIAL CONTENT')) + #set($context.responseOverride.status = 206) + #end + $input.json('$') + ^400.*: + statusCode: "400" + responseTemplates: + application/json: |- + { + "error" : "$input.path('$.errorMessage')" + } + ^404.*: + statusCode: "404" + responseTemplates: + application/json: |- + { + "error" : "$input.path('$.errorMessage')" + } + ^413.*: + statusCode: "413" + responseTemplates: + application/json: |- + { + "error" : "$input.path('$.errorMessage')" + } + ^[^1-5].*: + statusCode: "500" + responseTemplates: + application/json: |- + { + "error" : "$input.path('$.errorMessage')" + } + requestTemplates: + application/json: |- + { + "body": { + "exact":"$input.params('exact')", + "region": "$input.params('region')", + "page_number": "$input.params('page_number')" , + "page_size": "$input.params('page_size')" , + "polygon_format": "$input.params('polygon_format')" + } + } + passthroughBehavior: when_no_templates + httpMethod: POST + contentHandling: CONVERT_TO_TEXT + type: aws + '/rivers/reach/{reach}': + summary: River Reach + description: Interact with River Reach objects by ID + get: + deprecated: true + summary: Get River Reach (deprecated) + description: Get one or more river reach(es) by ID + parameters: + - $ref: '#/components/parameters/reach_param' + - $ref: '#/components/parameters/exact_param' + - $ref: '#/components/parameters/page_number_param' + - $ref: '#/components/parameters/page_size_param' + responses: + '200': + $ref: '#/components/responses/Success' + '400': + $ref: '#/components/responses/ClientError' + '404': + $ref: '#/components/responses/NotFound' + '413': + $ref: '#/components/responses/ClientError' + '500': + $ref: '#/components/responses/ServerError' + x-amazon-apigateway-integration: + uri: ${hydrocronapi_v021_lambda_arn} + responses: + default: + statusCode: "200" + responseTemplates: + application/json: | + #set($inputRoot = $input.path('$')) + #if($inputRoot.toString().contains('206 PARTIAL CONTENT')) + #set($context.responseOverride.status = 206) + #end + $input.json('$') + ^400.*: + statusCode: "400" + responseTemplates: + application/json: |- + { + "error" : "$input.path('$.errorMessage')" + } + ^404.*: + statusCode: "404" + responseTemplates: + application/json: |- + { + "error" : "$input.path('$.errorMessage')" + } + ^413.*: + statusCode: "413" + responseTemplates: + application/json: |- + { + "error" : "$input.path('$.errorMessage')" + } + ^[^1-5].*: + statusCode: "500" + responseTemplates: + application/json: |- + { + "error" : "$input.path('$.errorMessage')" + } + requestTemplates: + application/json: |- + { + "body": { + "exact":"$input.params('exact')", + "reach": "$input.params('reach')", + "page_number": "$input.params('page_number')" , + "page_size": "$input.params('page_size')" , + "polygon_format": "$input.params('polygon_format')" + } + } + passthroughBehavior: when_no_templates + httpMethod: POST + contentHandling: CONVERT_TO_TEXT + type: aws + '/rivers/node/{node}': + summary: River Node + description: Interact with River Node objects by ID + get: + deprecated: true + summary: Get River Node (deprecated) + description: Get one or more river node(s) by ID + parameters: + - $ref: '#/components/parameters/node_param' + - $ref: '#/components/parameters/exact_param' + - $ref: '#/components/parameters/page_number_param' + - $ref: '#/components/parameters/page_size_param' + responses: + '200': + $ref: '#/components/responses/Success' + '400': + $ref: '#/components/responses/ClientError' + '404': + $ref: '#/components/responses/NotFound' + '413': + $ref: '#/components/responses/ClientError' + '500': + $ref: '#/components/responses/ServerError' + x-amazon-apigateway-integration: + uri: ${hydrocronapi_v021_lambda_arn} + responses: + default: + statusCode: "200" + responseTemplates: + application/json: | + #set($inputRoot = $input.path('$')) + #if($inputRoot.toString().contains('206 PARTIAL CONTENT')) + #set($context.responseOverride.status = 206) + #end + $input.json('$') + ^400.*: + statusCode: "400" + responseTemplates: + application/json: |- + { + "error" : "$input.path('$.errorMessage')" + } + ^404.*: + statusCode: "404" + responseTemplates: + application/json: |- + { + "error" : "$input.path('$.errorMessage')" + } + ^413.*: + statusCode: "413" + responseTemplates: + application/json: |- + { + "error" : "$input.path('$.errorMessage')" + } + ^[^1-5].*: + statusCode: "500" + responseTemplates: + application/json: |- + { + "error" : "$input.path('$.errorMessage')" + } + requestTemplates: + application/json: |- + { + "body": { + "exact":"$input.params('exact')", + "node": "$input.params('node')", + "page_number": "$input.params('page_number')" , + "page_size": "$input.params('page_size')" , + "polygon_format": "$input.params('polygon_format')" + } + } + passthroughBehavior: when_no_templates + httpMethod: POST + contentHandling: CONVERT_TO_TEXT + type: aws + '/v1/huc/{huc}': + summary: HUCs + description: Interact with HUC objects by ID + get: + summary: Get HUC + description: Get one or more HUC by ID + parameters: + - $ref: '#/components/parameters/huc_param' + - $ref: '#/components/parameters/exact_param' + - $ref: '#/components/parameters/page_number_param' + - $ref: '#/components/parameters/page_size_param' + responses: + '200': + $ref: '#/components/responses/SuccessV1' + '400': + $ref: '#/components/responses/ClientError' + '404': + $ref: '#/components/responses/NotFound' + '413': + $ref: '#/components/responses/ClientError' + '500': + $ref: '#/components/responses/ServerError' + x-amazon-apigateway-integration: + uri: ${hydrocronapi_lambda_arn} + responses: + default: + statusCode: "200" + responseTemplates: + application/json: | + #set($inputRoot = $input.path('$')) + #if($inputRoot.toString().contains('206 PARTIAL CONTENT')) + #set($context.responseOverride.status = 206) + #end + $input.json('$') + ^400.*: + statusCode: "400" + responseTemplates: + application/json: |- + { + "error" : "$input.path('$.errorMessage')" + } + ^404.*: + statusCode: "404" + responseTemplates: + application/json: |- + { + "error" : "$input.path('$.errorMessage')" + } + ^413.*: + statusCode: "413" + responseTemplates: + application/json: |- + { + "error" : "$input.path('$.errorMessage')" + } + ^[^1-5].*: + statusCode: "500" + responseTemplates: + application/json: |- + { + "error" : "$input.path('$.errorMessage')" + } + requestTemplates: + application/json: |- + { + "body": { + "exact":"$input.params('exact')", + "HUC": "$input.params('huc')", + "page_number": "$input.params('page_number')" , + "page_size": "$input.params('page_size')" , + "polygon_format": "$input.params('polygon_format')" + } + } + passthroughBehavior: when_no_templates + httpMethod: POST + contentHandling: CONVERT_TO_TEXT + type: aws + '/v1/region/{region}': + summary: Regions + description: Interact with HUC objects by Region + get: + summary: Get Region + description: Get one or more HUC by region + parameters: + - $ref: '#/components/parameters/region_param' + - $ref: '#/components/parameters/exact_param' + - $ref: '#/components/parameters/page_number_param' + - $ref: '#/components/parameters/page_size_param' + responses: + '200': + $ref: '#/components/responses/SuccessV1' + '400': + $ref: '#/components/responses/ClientError' + '404': + $ref: '#/components/responses/NotFound' + '413': + $ref: '#/components/responses/ClientError' + '500': + $ref: '#/components/responses/ServerError' + x-amazon-apigateway-integration: + uri: ${hydrocronapi_lambda_arn} + responses: + default: + statusCode: "200" + responseTemplates: + application/json: | + #set($inputRoot = $input.path('$')) + #if($inputRoot.toString().contains('206 PARTIAL CONTENT')) + #set($context.responseOverride.status = 206) + #end + $input.json('$') + ^400.*: + statusCode: "400" + responseTemplates: + application/json: |- + { + "error" : "$input.path('$.errorMessage')" + } + ^404.*: + statusCode: "404" + responseTemplates: + application/json: |- + { + "error" : "$input.path('$.errorMessage')" + } + ^413.*: + statusCode: "413" + responseTemplates: + application/json: |- + { + "error" : "$input.path('$.errorMessage')" + } + ^[^1-5].*: + statusCode: "500" + responseTemplates: + application/json: |- + { + "error" : "$input.path('$.errorMessage')" + } + requestTemplates: + application/json: |- + { + "body": { + "exact":"$input.params('exact')", + "region": "$input.params('region')", + "page_number": "$input.params('page_number')" , + "page_size": "$input.params('page_size')" , + "polygon_format": "$input.params('polygon_format')" + } + } + passthroughBehavior: when_no_templates + httpMethod: POST + contentHandling: CONVERT_TO_TEXT + type: aws + '/v1/rivers/{name}': + get: + parameters: + - $ref: '#/components/parameters/river_name_param' + - $ref: '#/components/parameters/include_reaches_option_param' + - $ref: '#/components/parameters/include_nodes_option_param' + - $ref: '#/components/parameters/exact_param' + - $ref: '#/components/parameters/page_number_param' + - $ref: '#/components/parameters/page_size_param' + responses: + '200': + $ref: '#/components/responses/SuccessV1' + '400': + $ref: '#/components/responses/ClientError' + '404': + $ref: '#/components/responses/NotFound' + '413': + $ref: '#/components/responses/ClientError' + '500': + $ref: '#/components/responses/ServerError' + x-amazon-apigateway-integration: + uri: ${hydrocronapi_lambda_arn} + responses: + default: + statusCode: "200" + responseTemplates: + application/json: | + #set($inputRoot = $input.path('$')) + #if($inputRoot.toString().contains('206 PARTIAL CONTENT')) + #set($context.responseOverride.status = 206) + #end + $input.json('$') + ^400.*: + statusCode: "400" + responseTemplates: + application/json: |- + { + "error" : "$input.path('$.errorMessage')" + } + ^404.*: + statusCode: "404" + responseTemplates: + application/json: |- + { + "error" : "$input.path('$.errorMessage')" + } + ^413.*: + statusCode: "413" + responseTemplates: + application/json: |- + { + "error" : "$input.path('$.errorMessage')" + } + ^[^1-5].*: + statusCode: "500" + responseTemplates: + application/json: |- + { + "error" : "$input.path('$.errorMessage')" + } + requestTemplates: + application/json: |- + { + "body": { + "exact":"$input.params('exact')", + "name": "$input.params('name')", + "reaches": "$input.params('reaches')", + "nodes": "$input.params('nodes')", + "page_number": "$input.params('page_number')" , + "page_size": "$input.params('page_size')" , + "polygon_format": "$input.params('polygon_format')" + } + } + passthroughBehavior: when_no_templates + httpMethod: POST + contentHandling: CONVERT_TO_TEXT + type: aws + '/v1/rivers/reach/{reach}': + summary: River Reach + description: Interact with River Reach objects by ID + get: + summary: Get River Reach + description: Get one or more river reach(es) by ID + parameters: + - $ref: '#/components/parameters/reach_param' + - $ref: '#/components/parameters/river_name_option_param' + - $ref: '#/components/parameters/exact_param' + - $ref: '#/components/parameters/page_number_param' + - $ref: '#/components/parameters/page_size_param' + responses: + '200': + $ref: '#/components/responses/SuccessV1' + '400': + $ref: '#/components/responses/ClientError' + '404': + $ref: '#/components/responses/NotFound' + '413': + $ref: '#/components/responses/ClientError' + '500': + $ref: '#/components/responses/ServerError' + x-amazon-apigateway-integration: + uri: ${hydrocronapi_lambda_arn} + responses: + default: + statusCode: "200" + responseTemplates: + application/json: | + #set($inputRoot = $input.path('$')) + #if($inputRoot.toString().contains('206 PARTIAL CONTENT')) + #set($context.responseOverride.status = 206) + #end + $input.json('$') + ^400.*: + statusCode: "400" + responseTemplates: + application/json: |- + { + "error" : "$input.path('$.errorMessage')" + } + ^404.*: + statusCode: "404" + responseTemplates: + application/json: |- + { + "error" : "$input.path('$.errorMessage')" + } + ^413.*: + statusCode: "413" + responseTemplates: + application/json: |- + { + "error" : "$input.path('$.errorMessage')" + } + ^[^1-5].*: + statusCode: "500" + responseTemplates: + application/json: |- + { + "error" : "$input.path('$.errorMessage')" + } + requestTemplates: + application/json: |- + { + "body": { + "exact":"$input.params('exact')", + "reach": "$input.params('reach')", + "river_name": "$input.params('river_name')", + "page_number": "$input.params('page_number')" , + "page_size": "$input.params('page_size')" , + "polygon_format": "$input.params('polygon_format')" + } + } + passthroughBehavior: when_no_templates + httpMethod: POST + contentHandling: CONVERT_TO_TEXT + type: aws + '/v1/rivers/node/{node}': + summary: River Node + description: Interact with River Node objects by ID + get: + summary: Get River Node + description: Get one or more river node(s) by ID + parameters: + - $ref: '#/components/parameters/node_param' + - $ref: '#/components/parameters/river_name_option_param' + - $ref: '#/components/parameters/exact_param' + - $ref: '#/components/parameters/page_number_param' + - $ref: '#/components/parameters/page_size_param' + responses: + '200': + $ref: '#/components/responses/SuccessV1' + '400': + $ref: '#/components/responses/ClientError' + '404': + $ref: '#/components/responses/NotFound' + '413': + $ref: '#/components/responses/ClientError' + '500': + $ref: '#/components/responses/ServerError' + x-amazon-apigateway-integration: + uri: ${hydrocronapi_lambda_arn} + responses: + default: + statusCode: "200" + responseTemplates: + application/json: | + #set($inputRoot = $input.path('$')) + #if($inputRoot.toString().contains('206 PARTIAL CONTENT')) + #set($context.responseOverride.status = 206) + #end + $input.json('$') + ^400.*: + statusCode: "400" + responseTemplates: + application/json: |- + { + "error" : "$input.path('$.errorMessage')" + } + ^404.*: + statusCode: "404" + responseTemplates: + application/json: |- + { + "error" : "$input.path('$.errorMessage')" + } + ^413.*: + statusCode: "413" + responseTemplates: + application/json: |- + { + "error" : "$input.path('$.errorMessage')" + } + ^[^1-5].*: + statusCode: "500" + responseTemplates: + application/json: |- + { + "error" : "$input.path('$.errorMessage')" + } + requestTemplates: + application/json: |- + { + "body": { + "exact":"$input.params('exact')", + "node": "$input.params('node')", + "river_name": "$input.params('river_name')", + "page_number": "$input.params('page_number')" , + "page_size": "$input.params('page_size')" , + "polygon_format": "$input.params('polygon_format')" + } + } + passthroughBehavior: when_no_templates + httpMethod: POST + contentHandling: CONVERT_TO_TEXT + type: aws +components: + parameters: + huc_param: + name: huc + in: path + required: true + schema: + type: string + region_param: + name: region + in: path + required: true + schema: + type: string + river_name_param: + name: name + in: path + required: true + schema: + type: string + reach_param: + name: reach + in: path + required: true + schema: + type: string + node_param: + name: node + in: path + required: true + schema: + type: string + exact_param: + name: exact + in: query + example: true + schema: + type: string + polygon_format_param: + name: polygon_format + in: query + schema: + type: string + page_number_param: + name: page_number + in: query + schema: + type: integer + page_size_param: + name: page_size + in: query + schema: + type: integer + river_name_option_param: + name: river_name + in: query + schema: + type: string + include_reaches_option_param: + name: reaches + description: Include river reaches in results + example: true + in: query + schema: + type: string + include_nodes_option_param: + name: nodes + description: Include river nodes in results + example: false + in: query + schema: + type: string + responses: + Success: + description: Success Response + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessResponse' + SuccessV1: + description: V1 Success Response + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessV1Response' + ClientError: + description: 400 response + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + NotFound: + description: 404 response + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + ServerError: + description: 500 response + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + schemas: + Empty: + title: Empty Schema + type: object + Error: + type: object + properties: + error: + type: string + SuccessResponse: + title: Success Response Body + type: object + properties: + status: + type: string + description: HTTP Status code returned by backend + time: + type: string + description: Time in milliseconds to complete request + hits: + type: integer + description: Total number of results + search on: + $ref: '#/components/schemas/SearchParameters' + results_count: + type: integer + description: Number of result entries returned in this request. Only appears if hits > page_size + results: + type: object + description: Single object where each property of the object is a result + SuccessV1Response: + title: SuccessV1 Response Body + type: object + properties: + status: + type: string + description: HTTP Status code returned by backend + time: + type: string + description: Time in milliseconds to complete request + hits: + type: integer + description: Total number of results + search on: + $ref: '#/components/schemas/SearchV1Parameters' + results_count: + type: integer + description: Number of result entries returned in this request. Only appears if hits > page_size + results: + type: array + description: List of result objects. List can contain objects of type [HUC, RiverReach, RiverNode], or a merge of two or more types (e.g. RiverReach and RiverNode) depending on the resource being queried. (e.g. if requesting /huc or /region, a list of HUC objects will be returned, and if requesting /rivers/name, a list of RiverReach and RiverNodes [merged] will be returned) + items: + type: object + anyOf: + - $ref: '#/components/schemas/HUC' + - $ref: '#/components/schemas/RiverReach' + - $ref: '#/components/schemas/RiverNode' + SearchParameters: + title: Search Parameters + type: object + properties: + parameter: + type: string + exact: + type: boolean + polygon_format: + type: string + page_number: + type: integer + page_size: + type: integer + SearchV1Parameters: + title: Search Parameters + type: object + properties: + parameter: + type: string + river_name: + type: string + exact: + type: boolean + polygon_format: + type: string + page_number: + type: integer + page_size: + type: integer + HUC: + type: object + properties: + Region Name: + type: string + HUC: + type: string + USGS Polygon: + type: object + properties: + Object URL: + type: string + Source: + type: string + Bounding Box: + type: string + Convex Hull Polygon: + type: string + Visvalingam Polygon: + type: string + RiverReach: + title: River Reach + type: object + properties: + reach_id: + type: string + geojson: + type: object + additionalProperties: true + RiverNode: + title: River Node + type: object + properties: + node_id: + type: string + geojson: + type: object + additionalProperties: true +x-amazon-apigateway-policy: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: '*' + Action: 'execute-api:Invoke' + Resource: '*' + - Effect: Deny + Principal: '*' + Action: 'execute-api:Invoke' + Resource: '*' + Condition: + StringNotEquals: + 'aws:SourceVpc': ${vpc_id} diff --git a/terraform/bin/config.sh b/terraform/bin/config.sh deleted file mode 100644 index 95d3b29..0000000 --- a/terraform/bin/config.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env bash -set -eo pipefail - -if [ ! $# -eq 1 ] -then - echo "usage: $(caller | cut -d' ' -f2) venue" - exit 1 -fi - -VENUE=$1 -source "$(dirname $BASH_SOURCE)/../environments/$VENUE.env" - -export TF_IN_AUTOMATION=true # https://www.terraform.io/cli/config/environment-variables#tf_in_automation -export TF_INPUT=false # https://www.terraform.io/cli/config/environment-variables#tf_input - -export TF_VAR_region="$REGION" -export TF_VAR_stage="$VENUE" - -terraform init -reconfigure -backend-config="bucket=$BUCKET" -backend-config="region=$REGION" \ No newline at end of file diff --git a/terraform/bin/deploy.sh b/terraform/bin/deploy.sh new file mode 100755 index 0000000..f678a6c --- /dev/null +++ b/terraform/bin/deploy.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash + +set -Eexo pipefail + +# Read in args from command line + +POSITIONAL=() +while [[ $# -gt 0 ]] +do +key="$1" + +case $key in + -t|--docker-tag) + docker_tag="$2" + shift # past argument + shift # past value + ;; + -v|--tf-venue) + tf_venue="$2" + case $tf_venue in + sit|uat|ops) ;; + *) + echo "tf_venue must be sit, uat, or ops" + exit 1;; + esac + shift # past argument + shift # past value + ;; + *) # unknown option + POSITIONAL+=("$1") # save it in an array for later + shift # past argument + ;; +esac +done +set -- "${POSITIONAL[@]}" # restore positional parameters + +# https://www.terraform.io/docs/commands/environment-variables.html#tf_in_automation +TF_IN_AUTOMATION=true + +# Terraform initialization +terraform init -reconfigure -input=false -backend-config="bucket=podaac-services-${tf_venue}-terraform" -backend-config="profile=ngap-service-${tf_venue}" + +terraform plan -input=false -var-file=tfvars/"${tf_venue}".tfvars -var="credentials=~/.aws/credentials" -var="docker_tag=${docker_tag}" -var="profile=ngap-service-${tf_venue}" -out="tfplan" + +# Apply the plan that was created +terraform apply -input=false -auto-approve tfplan \ No newline at end of file diff --git a/terraform/bin/destroy.sh b/terraform/bin/destroy.sh new file mode 100755 index 0000000..1e1961c --- /dev/null +++ b/terraform/bin/destroy.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash + +set -Eexo pipefail + +POSITIONAL=() +while [[ $# -gt 0 ]] +do +key="$1" + +case $key in + -t|--docker-tag) + docker_tag="$2" + shift # past argument + shift # past value + ;; + -v|--tf-venue) + tf_venue="$2" + case $tf_venue in + sit|uat|ops) ;; + *) + echo "tf_venue must be sit, uat, or ops" + exit 1;; + esac + shift # past argument + shift # past value + ;; + *) # unknown option + POSITIONAL+=("$1") # save it in an array for later + shift # past argument + ;; +esac +done +set -- "${POSITIONAL[@]}" # restore positional parameters + +# https://www.terraform.io/docs/commands/environment-variables.html#tf_in_automation +TF_IN_AUTOMATION=true + +# Terraform initialization +terraform init -reconfigure -input=false -backend-config="bucket=podaac-services-${tf_venue}-terraform" -backend-config="profile=ngap-service-${tf_venue}" + + +terraform destroy -auto-approve -var-file=tfvars/"${tf_venue}".tfvars -var="credentials=~/.aws/credentials" -var="profile=ngap-service-${tf_venue}" -var="docker_tag=${docker_tag}" diff --git a/terraform/db.tf b/terraform/db.tf deleted file mode 100644 index 7f065e0..0000000 --- a/terraform/db.tf +++ /dev/null @@ -1,121 +0,0 @@ - -resource "aws_security_group" "db" { - description = "controls access to the database" - - vpc_id = data.aws_vpc.default.id - name = "${local.ec2_resources_name}-db-sg" - tags = local.default_tags -} -resource "aws_security_group_rule" "allow_self_in" { - type = "ingress" - security_group_id = aws_security_group.db.id - protocol = "tcp" - from_port = 3306 - to_port = 3306 - self = true -} -resource "aws_security_group_rule" "allow_all_out" { - type = "egress" - security_group_id = aws_security_group.db.id - from_port = 0 - to_port = 0 - protocol = "-1" - cidr_blocks = [ - "0.0.0.0/0", - ] -} - - -resource "aws_db_subnet_group" "default" { - name = "${local.ec2_resources_name}-subnet" - subnet_ids = data.aws_subnets.private.ids - - tags = local.default_tags -} - - -resource "random_password" "db_admin_pass" { - length = 16 - special = false -} -resource "random_password" "db_user_pass" { - length = 16 - special = false -} - - -## RDS Database -resource "aws_db_instance" "database" { - identifier = "${local.ec2_resources_name}-rds" - allocated_storage = 20 - storage_type = "gp2" - engine = "mysql" - engine_version = "5.7" - instance_class = "db.t2.micro" - name = "hydrocronapidb" - username = "hydrocronapiadmin" - password = random_password.db_admin_pass.result - parameter_group_name = "default.mysql5.7" - multi_az = "true" - vpc_security_group_ids = [aws_security_group.db.id] - db_subnet_group_name = aws_db_subnet_group.default.id - skip_final_snapshot = true - tags = local.default_tags -} - - -resource "aws_ssm_parameter" "db_admin" { - name = "${local.ec2_resources_name}-db-admin" - type = "String" - value = aws_db_instance.database.username - tags = local.default_tags - overwrite = true -} - -resource "aws_ssm_parameter" "db_admin_pass" { - name = "${local.ec2_resources_name}-db-admin-pass" - type = "SecureString" - value = aws_db_instance.database.password - tags = local.default_tags - overwrite = true -} - -resource "aws_ssm_parameter" "db_user" { - name = "${local.ec2_resources_name}-db-user" - type = "String" - value = "hydrocronapiuser" - tags = local.default_tags - overwrite = true -} - -resource "aws_ssm_parameter" "db_user_pass" { - name = "${local.ec2_resources_name}-db-user-pass" - type = "SecureString" - value = random_password.db_user_pass.result - tags = local.default_tags - overwrite = true -} - -resource "aws_ssm_parameter" "db_host" { - name = "${local.ec2_resources_name}-db-host" - type = "String" - value = aws_db_instance.database.address - tags = local.default_tags - overwrite = true -} - -resource "aws_ssm_parameter" "db_name" { - name = "${local.ec2_resources_name}-db_name" - type = "String" - value = aws_db_instance.database.name - tags = local.default_tags - overwrite = true -} - -resource "aws_ssm_parameter" "db_sg" { - name = "${local.ec2_resources_name}-db-sg" - type = "String" - value = aws_security_group.db.id - tags = local.default_tags - overwrite = true -} diff --git a/terraform/environments/ops.env b/terraform/environments/ops.env deleted file mode 100644 index c36075e..0000000 --- a/terraform/environments/ops.env +++ /dev/null @@ -1,2 +0,0 @@ -export REGION=us-west-2 -export BUCKET=podaac-services-ops-terraform \ No newline at end of file diff --git a/terraform/environments/sit.env b/terraform/environments/sit.env deleted file mode 100644 index 6aae1b2..0000000 --- a/terraform/environments/sit.env +++ /dev/null @@ -1,2 +0,0 @@ -export REGION=us-west-2 -export BUCKET=podaac-services-sit-terraform \ No newline at end of file diff --git a/terraform/environments/uat.env b/terraform/environments/uat.env deleted file mode 100644 index 5ded9f8..0000000 --- a/terraform/environments/uat.env +++ /dev/null @@ -1,2 +0,0 @@ -export REGION=us-west-2 -export BUCKET=podaac-services-uat-terraform \ No newline at end of file diff --git a/terraform/fargate-iam.tf b/terraform/fargate-iam.tf deleted file mode 100644 index a11047e..0000000 --- a/terraform/fargate-iam.tf +++ /dev/null @@ -1,170 +0,0 @@ -#----- Fargate Task Execution Role -------- -resource "aws_iam_role" "fargate_task_execution_role" { - name = "${local.ec2_resources_name}-fargate-task-execution-role" - permissions_boundary = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:policy/NGAPShRoleBoundary" - tags = local.default_tags - assume_role_policy = jsonencode({ - Version = "2012-10-17" - Statement = [ - { - Action = "sts:AssumeRole" - Principal = { - Service = "ecs-tasks.amazonaws.com" - } - Effect = "Allow" - Sid = "" - } - ] - }) -} - - -resource "aws_iam_role_policy" "fargate_policy" { - name = "${local.ec2_resources_name}-fargate-policy" - role = aws_iam_role.fargate_task_execution_role.id - policy = jsonencode({ - Version = "2012-10-17" - Statement = [ - { - Effect = "Allow" - Action = [ - "cloudwatch:GetMetricStatistics", - "logs:CreateLogGroup", - "logs:CreateLogStream", - "logs:DescribeLogStreams", - "logs:PutLogEvents", - "ssm:GetParameter", - "ec2:CreateNetworkInterface", - "ec2:DescribeNetworkInterfaces", - "ec2:DeleteNetworkInterface", - "ecr:GetDownloadUrlForLayer", - "ecr:BatchGetImage", - "ecr:GetAuthorizationToken", - "ssm:GetParameters" - ] - Resource = "*" - }, - { - Effect = "Allow" - Action = [ - "s3:ListBucket*" - ] - Resource = [ - "arn:aws:s3:::podaac-services-${var.stage}-deploy", - "arn:aws:s3:::podaac-services-${var.stage}-deploy/*" - ] - } - ] - }) -} - -#----- Fargate Task Role-------- -resource "aws_iam_role" "ecs_task_role" { - name = "${local.ec2_resources_name}-fargate-task-role" - permissions_boundary = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:policy/NGAPShRoleBoundary" - tags = local.default_tags - assume_role_policy = jsonencode({ - Version = "2012-10-17" - Statement = [ - { - Action = "sts:AssumeRole" - Principal = { - Service = "ecs-tasks.amazonaws.com" - } - Effect = "Allow", - Sid = "" - } - ] - }) -} - -resource "aws_iam_role_policy" "ecs_task_role_policy" { - name = "${local.ec2_resources_name}-fargate-task-role-policy" - role = aws_iam_role.ecs_task_role.id - policy = jsonencode({ - Version = "2012-10-17" - Statement = [ - { - Effect = "Allow" - Action = [ - "s3:Get*", - "s3:List*" - ] - Resource = [ - "arn:aws:s3:::podaac-services-${var.stage}-deploy", - "arn:aws:s3:::podaac-services-${var.stage}-deploy/*" - ] - } - ] - }) -} - - -#----- CloudWatch Events - Role Management-------- -resource "aws_iam_role" "cloudwatch_events_role" { - name = "${local.ec2_resources_name}-fargate-task-events" - permissions_boundary = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:policy/NGAPShRoleBoundary" - tags = local.default_tags - assume_role_policy = jsonencode({ - Version = "2012-10-17" - Statement = [ - { - Action = "sts:AssumeRole" - Principal = { - Service = "events.amazonaws.com" - } - Effect = "Allow" - Sid = "" - } - ] - }) -} - -data "aws_iam_policy_document" "cloudwatch_events_role_assume_policy" { - statement { - actions = ["sts:AssumeRole"] - - principals { - type = "Service" - identifiers = ["events.amazonaws.com"] - } - } -} - -resource "aws_iam_role_policy" "cloudwatch_events_role_run_task" { - name = "${aws_ecs_task_definition.fargate_task.family}-events-ecs" - role = aws_iam_role.cloudwatch_events_role.id - policy = data.aws_iam_policy_document.cloudwatch_events_role_run_task_policy.json -} - -data "aws_iam_policy_document" "cloudwatch_events_role_run_task_policy" { - statement { - effect = "Allow" - actions = ["ecs:RunTask"] - resources = ["arn:aws:ecs:${var.region}:${data.aws_caller_identity.current.account_id}:task-definition/${aws_ecs_task_definition.fargate_task.family}:*"] - - condition { - test = "StringLike" - variable = "ecs:cluster" - values = [aws_ecs_cluster.fargate_cluster.arn] - } - } -} - -resource "aws_iam_role_policy" "cloudwatch_events_role_pass_role" { - name = "${aws_ecs_task_definition.fargate_task.family}-events-ecs-pass-role" - role = aws_iam_role.cloudwatch_events_role.id - policy = data.aws_iam_policy_document.cloudwatch_events_role_pass_role_policy.json -} - -data "aws_iam_policy_document" "cloudwatch_events_role_pass_role_policy" { - statement { - effect = "Allow" - actions = ["iam:PassRole"] - - resources = [ - aws_iam_role.fargate_task_execution_role.arn, - aws_iam_role.ecs_task_role.arn, - ] - } -} diff --git a/terraform/fargate.tf b/terraform/fargate.tf deleted file mode 100644 index 90343f8..0000000 --- a/terraform/fargate.tf +++ /dev/null @@ -1,214 +0,0 @@ - - - -resource "random_string" "session_cookie_secret" { - length = 16 - special = true -} - -resource "aws_ssm_parameter" "session_cookie_secret" { - name = "${local.ec2_resources_name}-session-cookie-secret" - type = "SecureString" - value = random_string.session_cookie_secret.result - tags = local.default_tags - overwrite = true -} - -resource "aws_ssm_parameter" "earth_data_login_password" { - name = "${local.ec2_resources_name}-earth-data-login-password" - type = "SecureString" - value = var.EARTH_DATA_LOGIN_PASSWORD - tags = local.default_tags - overwrite = true -} - -resource "aws_security_group" "hydrocron_api" { - name = "${local.ec2_resources_name}-sg" - description = "Control traffic for the hydrocron-api api" - vpc_id = data.aws_vpc.default.id - - ingress = [ - { - description = "From ALB" - from_port = 8080 - to_port = 8080 - protocol = "tcp" - security_groups = [] - cidr_blocks = ["0.0.0.0/0"] - ipv6_cidr_blocks = [] - prefix_list_ids = [] - self : false - } - ] - - egress = [ - { - description = "Outbound traffic" - prefix_list_ids = [] - security_groups = [] - self = false - from_port = 0 - to_port = 0 - protocol = "-1" - cidr_blocks = ["0.0.0.0/0"] - ipv6_cidr_blocks = ["::/0"] - } - ] - - tags = local.default_tags -} - - -#----- AWS ECS Cluster-------- -resource "aws_ecs_cluster" "fargate_cluster" { - name = "${local.ec2_resources_name}-fargate-cluster" - tags = local.default_tags -} - -#----- ECS Services-------- -resource "aws_cloudwatch_log_group" "fargate_task_log_group" { - name = "${local.ec2_resources_name}-fargate-worker" - retention_in_days = var.logs_retention_days - tags = local.default_tags -} - -#----- Fargate Task Definition -------- -resource "aws_ecs_task_definition" "fargate_task" { - family = "${local.ec2_resources_name}-fargate-task" - requires_compatibilities = ["FARGATE"] - container_definitions = jsonencode([ - { - name = "${local.ec2_resources_name}-fargate-task" - image = var.hydrocron_api_docker_image - command = ["/bin/bash", "-c", "node mysql/setup-db.js && docker/docker-start-command"] - secrets = [ - { - name = "DATABASE_PASSWORD" - valueFrom = "${aws_ssm_parameter.db_user_pass.arn}" - }, - { - name = "DATABASE_ADMIN_PASSWORD" - valueFrom = "${aws_ssm_parameter.db_admin_pass.arn}" - }, - { - name = "EARTH_DATA_LOGIN_PASSWORD" - valueFrom = "${aws_ssm_parameter.earth_data_login_password.arn}" - }, - { - name = "SESSION_COOKIE_SECRET" - valueFrom = "${aws_ssm_parameter.session_cookie_secret.arn}" - } - ] - environment = [ - { - name = "DATABASE_HOST" - value = "${aws_ssm_parameter.db_host.value}" - }, - { - name = "DATABASE_PORT" - value = "3306" - }, - { - name = "DATABASE_NAME" - value = "${aws_ssm_parameter.db_name.value}" - }, - { - name = "DATABASE_USERNAME" - value = "${aws_ssm_parameter.db_user.value}" - }, - { - name = "DATABASE_ADMIN" - value = "${aws_ssm_parameter.db_admin.value}" - }, - { - name = "EARTH_DATA_LOGIN_CLIENT_ID" - value = "${var.EARTH_DATA_LOGIN_CLIENT_ID}" - }, - { - name = "EARTH_DATA_LOGIN_AUTH_CODE_REQUEST_URI" - value = "${var.earth_data_login_base_url}/oauth/authorize" - }, - { - name = "EARTH_DATA_LOGIN_TOKEN_REQUEST_URI" - value = "${var.earth_data_login_base_url}/oauth/token" - }, - { - name = "EARTH_DATA_LOGIN_TOKEN_REFRESH_URI" - value = "${var.earth_data_login_base_url}/oauth/token" - }, - { - name = "EARTH_DATA_LOGIN_USER_INFO_URI" - value = "${var.earth_data_login_base_url}" - }, - { - name = "L2SS_SUBSET_SUBMIT_REQUEST_URI" - value = "${var.l2ss_base_url}/subset/submit" - }, - { - name = "L2SS_SUBSET_STATUS_REQUEST_URI" - value = "${var.l2ss_base_url}/subset/status" - }, - { - name = "HARMONY_BASE_URL" - value = "${var.harmony_base_url}" - }, - { - name = "SECURE_COOKIE" - value = "true" - }, - { - name = "LIST_OF_AUTHORIZED_CORS_REQUESTER_ORIGINS" - value = "${var.LIST_OF_AUTHORIZED_CORS_REQUESTER_ORIGINS}" - }, - { - name = "NUM_PROXY_SERVERS_FOR_API" - value = "1" - } - ] - portMappings = [{ - containerPort = 8080 - }] - logConfiguration = { - logDriver = "awslogs" - options = { - "awslogs-region" = "us-west-2" - "awslogs-group" = "${aws_cloudwatch_log_group.fargate_task_log_group.name}" - "awslogs-stream-prefix" = "${aws_cloudwatch_log_group.fargate_task_log_group.name}" - } - } - } - ]) - - network_mode = "awsvpc" - cpu = var.task_cpu - memory = var.task_memory - - execution_role_arn = aws_iam_role.fargate_task_execution_role.arn - task_role_arn = aws_iam_role.ecs_task_role.arn - tags = local.default_tags -} - -resource "aws_ecs_service" "hydrocron_api" { - name = "${local.ec2_resources_name}-ecs-service" - cluster = aws_ecs_cluster.fargate_cluster.arn - task_definition = aws_ecs_task_definition.fargate_task.arn - launch_type = "FARGATE" - desired_count = 1 - - network_configuration { - subnets = data.aws_subnets.private.ids - security_groups = [aws_ssm_parameter.db_sg.value, aws_security_group.hydrocron_api.id] - } - - load_balancer { - target_group_arn = aws_lb_target_group.fargate_service_tg.arn - container_name = "${local.ec2_resources_name}-fargate-task" - container_port = 8080 - } - - tags = local.default_tags - - depends_on = [ - aws_ecs_cluster.fargate_cluster - ] -} diff --git a/terraform/feature-translation-service-lambda-iam.tf b/terraform/feature-translation-service-lambda-iam.tf new file mode 100644 index 0000000..b9f6137 --- /dev/null +++ b/terraform/feature-translation-service-lambda-iam.tf @@ -0,0 +1,100 @@ +#IAM roles + +resource "aws_iam_instance_profile" "hydrocron-service-profile" { + name = "${local.ec2_resources_name}-instance-profile" + role = aws_iam_role.hydrocron-service-role.name +} + +resource "aws_iam_policy" "hydrocron-service-policy" { + name = "${local.ec2_resources_name}-service-policy" + path = "/" + policy = <