Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2.6.11
2.6.12
5 changes: 3 additions & 2 deletions docker/docker-compose.deployment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ services:
limits:
cpus: '8' # 50%
memory: 19.2G # 60%


volumes:
- # Mount conf.d on host machine to the nginx conf.d on container
- "./entity-api/nginx/conf.d:/etc/nginx/conf.d"
4 changes: 3 additions & 1 deletion docker/docker-compose.development.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ services:
entity-api:
build:
context: ./entity-api
# Uncomment if tesitng against a specific branch of commons other than the PyPI package
# Uncomment if testing against a specific branch of commons other than the PyPI package
# Will also need to use the 'git+https://github.com/hubmapconsortium/commons.git@${COMMONS_BRANCH}#egg=hubmap-commons'
# in src/requirements.txt accordingly
args:
Expand All @@ -25,3 +25,5 @@ services:
- "../BUILD:/usr/src/app/BUILD"
# Mount the source code to container
- "../src:/usr/src/app/src"
- # Mount conf.d on host machine to the nginx conf.d on container
- "./entity-api/nginx/conf.d:/etc/nginx/conf.d"
33 changes: 33 additions & 0 deletions docker/docker-compose.localhost.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
services:

entity-api:
build:
context: ./entity-api
args:
- COMMONS_BRANCH=${COMMONS_BRANCH:-main}
image: hubmap/entity-api:${ENTITY_API_VERSION:?err}
environment:
- DEPLOY_MODE=localhost
volumes:
# Mount VERSION and BUILD files
- "../VERSION:/usr/src/app/VERSION"
- "../BUILD:/usr/src/app/BUILD"
## Mount source code for live development
#- "../src:/usr/src/app/src"
# Mount localhost-specific nginx config
- "${PWD}/entity-api/nginx/conf.d-localhost:/etc/nginx/conf.d"
healthcheck:
# Replaces base healthcheck - Check port 8080 inside container (nginx listening port)
test: [ "CMD", "curl", "--fail", "http://localhost:8080/status" ]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
logging:
# Override CloudWatch logging - use local json-file driver for localhost
driver: json-file
options:
max-size: "10m"
max-file: "3" # Keep 3 files, rotating oldest out
networks:
- gateway_hubmap # Same network as hubmap-auth for communication
4 changes: 1 addition & 3 deletions docker/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@ services:
- "../log:/usr/src/app/log"
# Mount the schema yaml file
- "../src/schema/provenance_schema.yaml:/usr/src/app/src/schema/provenance_schema.yaml"
# Mount conf.d on host machine to the nginx conf.d on container
- "./entity-api/nginx/conf.d:/etc/nginx/conf.d"
networks:
- gateway_hubmap
# Send docker logs to AWS CloudWatch
Expand All @@ -40,6 +38,6 @@ services:
awslogs-stream: ${LOG_STREAM}

networks:
# This is the network created by gateway to enable communicaton between multiple docker-compose projects
# This is the network created by gateway to enable communication between multiple docker-compose projects
gateway_hubmap:
external: true
148 changes: 148 additions & 0 deletions docker/docker-localhost.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
#!/bin/bash

# Print a new line and the banner
echo
echo "==================== Entity-API ===================="

function tier_check() {
# Get the script name and extract DEPLOY_TIER
SCRIPT_NAME=$(basename "${0}")

# Extract deploy tier from script name (docker-*.sh pattern)
if [[ ${SCRIPT_NAME} =~ docker-(.*)\.sh ]]; then
DEPLOY_TIER="${BASH_REMATCH[1]}"
else
echo "Error: Script name doesn't match pattern 'docker-*.sh'"
exit 1
fi
echo "Executing ${SCRIPT_NAME} to deploy in Docker on ${DEPLOY_TIER}"
}

# Chances are localhost development is not being done on an RHEL server with
# the environment variables set. Unset HOST_UID and HOST_GID to ensure
# docker-compose defaults (1001:1001) are used.
function export_host_ids() {
if [ -n "${HOST_UID}" ] || [ -n "${HOST_GID}" ]; then
echo "WARNING: HOST_UID and HOST_GID are set in your environment but will be ignored for localhost."
echo " Localhost development uses docker-compose.yml defaults."
fi
# Unset to ensure docker-compose defaults are used
unset HOST_UID
unset HOST_GID
}

# The `absent_or_newer` checks if the copied src at docker/some-api/src directory exists
# and if the source src directory is newer.
# If both conditions are true `absent_or_newer` writes an error message
# and causes script to exit with an error code.
function absent_or_newer() {
if [ \( -e ${1} \) -a \( ${2} -nt ${1} \) ]; then
echo "${1} is out of date"
exit -1
fi
}

function get_dir_of_this_script() {
# This function sets DIR to the directory in which this script itself is found.
# Thank you https://stackoverflow.com/questions/59895/how-to-get-the-source-directory-of-a-bash-script-from-within-the-script-itself
SCRIPT_SOURCE="${BASH_SOURCE[0]}"
while [ -h "${SCRIPT_SOURCE}" ]; do # resolve $SCRIPT_SOURCE until the file is no longer a symlink
DIR="$( cd -P "$( dirname "${SCRIPT_SOURCE}" )" >/dev/null 2>&1 && pwd )"
SCRIPT_SOURCE="$(readlink "${SCRIPT_SOURCE}")"
[[ ${SCRIPT_SOURCE} != /* ]] && SCRIPT_SOURCE="${DIR}/${SCRIPT_SOURCE}" # if $SCRIPT_SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
done
DIR="$( cd -P "$( dirname "${SCRIPT_SOURCE}" )" >/dev/null 2>&1 && pwd )"
echo "DIR of script: ${DIR}"
}

# Generate the build version based on git branch name and short commit hash and write into BUILD file
function generate_build_version() {
GIT_BRANCH_NAME=$(git branch | sed -n -e 's/^\* \(.*\)/\1/p')
GIT_SHORT_COMMIT_HASH=$(git rev-parse --short HEAD)
# Clear the old BUILD version and write the new one
truncate -s 0 ../BUILD
# Note: echo to file appends newline
echo "${GIT_BRANCH_NAME}:${GIT_SHORT_COMMIT_HASH}" >> ../BUILD
# Remove the trailing newline character
truncate -s -1 ../BUILD
echo "BUILD(git branch name:short commit hash): ${GIT_BRANCH_NAME}:${GIT_SHORT_COMMIT_HASH}"
}

# Set the version environment variable for the docker build
# Version number is from the VERSION file
# Also remove newlines and leading/trailing slashes if present in that VERSION file
function export_version() {
export ENTITY_API_VERSION=$(tr -d "\n\r" < ../VERSION | xargs)
echo "ENTITY_API_VERSION: ${ENTITY_API_VERSION}"
}

if [[ "${1}" != "check" && "${1}" != "config" && "${1}" != "build" && "${1}" != "start" && "${1}" != "stop" && "${1}" != "down" ]]; then
echo "Unknown command '${1}', specify one of the following: check|config|build|start|stop|down"
else
# Echo this script name and the tier expected for Docker deployment
tier_check

# Always show the script dir
get_dir_of_this_script

# Always export and show the version
export_version

# Unset HOST_UID/HOST_GID for localhost to use defaults
export_host_ids

# Always show the build in case branch changed or new commits
generate_build_version

# Print empty line
echo

if [ "${1}" = "check" ]; then
# Bash array
config_paths=(
'../src/instance/app.cfg'
)

for pth in "${config_paths[@]}"; do
if [ ! -e ${pth} ]; then
echo "Missing file (relative path to DIR of script): ${pth}"
exit -1
fi
done

absent_or_newer entity-api/src ../src

echo 'Checks complete, all good :)'
elif [ "${1}" = "config" ]; then
docker compose -f docker-compose.yml -f docker-compose.${DEPLOY_TIER}.yml -p entity-api config
elif [ "${1}" = "build" ]; then
# Delete the copied source code dir if exists
if [ -d "entity-api/src" ]; then
rm -rf entity-api/src
fi

# Copy over the src folder
cp -r ../src entity-api/

# Delete old VERSION and BUILD files if found
if [ -f "entity-api/VERSION" ]; then
rm -rf entity-api/VERSION
fi

if [ -f "entity-api/BUILD" ]; then
rm -rf entity-api/BUILD
fi

# Copy over the VERSION and BUILD files
cp ../VERSION entity-api
cp ../BUILD entity-api

docker compose -f docker-compose.yml -f docker-compose.${DEPLOY_TIER}.yml -p entity-api build --no-cache
elif [ "${1}" = "start" ]; then
docker compose -f docker-compose.yml -f docker-compose.${DEPLOY_TIER}.yml -p entity-api up -d
elif [ "${1}" = "stop" ]; then
docker compose -f docker-compose.yml -f docker-compose.${DEPLOY_TIER}.yml -p entity-api stop
elif [ "${1}" = "down" ]; then
docker compose -f docker-compose.yml -f docker-compose.${DEPLOY_TIER}.yml -p entity-api down
fi
fi
82 changes: 82 additions & 0 deletions docker/entity-api/nginx/conf.d-localhost/entity-api.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
server {
# Only root can listen on ports below 1024, we use higher-numbered ports
# since nginx is running under non-root user hubmap
listen 8080;
server_name localhost;
root /usr/share/nginx/html;

# Docker's internal DNS resolver
resolver 127.0.0.11 valid=10s;
resolver_timeout 5s;

# We need this logging for inspecting auth requests from other internal services
# Logging to the mounted volume for outside container access
access_log /usr/src/app/log/nginx_access_entity-api.log;
error_log /usr/src/app/log/nginx_error_entity-api.log warn;

# Set payload size limit to 10M, default is 1M.
client_max_body_size 10M;

# Pass requests to the uWSGI server using the "uwsgi" protocol on port 5000
location / {
# Always enable CORS
# Response to preflight requests
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, OPTIONS' always;
# These are permitted headers to be used with the actual request
add_header 'Access-Control-Allow-Headers' 'Authorization, Cache-Control, Content-Type, X-Hubmap-Application' always;
# Cache the response to this preflight request in browser for the max age 86400 seconds (= 24 hours)
add_header 'Access-Control-Max-Age' 86400 always;
# No Content
return 204;
}

# Response to the original requests (HTTP methods are case-sensitive) with CORS enabled
if ($request_method ~ (POST|GET|PUT)) {
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Authorization, Cache-Control, Content-Type, X-Hubmap-Application' always;
}

# Capture original request details BEFORE auth_request subrequest
set $original_uri $request_uri;
set $original_method $request_method;

# Call hubmap-auth for authorization before passing to entity-api
auth_request /api_auth;

# Pass authorization headers from hubmap-auth response to the Flask app
auth_request_set $auth_user $upstream_http_x_hubmap_user;
auth_request_set $auth_groups $upstream_http_x_hubmap_groups;
uwsgi_param X-Hubmap-User $auth_user;
uwsgi_param X-Hubmap-Groups $auth_groups;

include uwsgi_params;
uwsgi_pass uwsgi://localhost:5000;
}

# Internal location for auth requests - calls hubmap-auth container
location = /api_auth {
internal;
# Use variable to enable runtime DNS resolution via Docker's internal DNS.
# This allows hubmap-auth to restart without requiring entity-api nginx reload.
set $hubmap_auth_backend "hubmap-auth:7777";
# Call hubmap-auth via Docker network using container hostname and port 7777
proxy_pass http://$hubmap_auth_backend/api_auth;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
#
# These headers match what hubmap-auth app.py expects.
#
# We need to hard-code the Host to the Docker service name on the
# Docker network gateway_hubmap, rather than $http_host, so that
# a value like localhost:3333 is not passed. The value must be a
# JSON Object key in the gateway repository's api_endpoints.localhost.json.
proxy_set_header Host "entity-api";
#proxy_set_header X-Original-URI $request_uri;
#proxy_set_header X-Original-Request-Method $request_method;
proxy_set_header X-Original-URI $original_uri;
proxy_set_header X-Original-Request-Method $original_method;
}
}
1 change: 1 addition & 0 deletions docker/entity-api/nginx/nginx.conf
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ http {
include /etc/nginx/mime.types;
default_type application/octet-stream;

# Extend nginx's default Combined Log Format with $http_x_forwarded_for
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
Expand Down