docs/client-examples/curl_sigv4_proxy_example.sh81 of 81 statements covered (100.00%).
coveredmissednever traced by Bash (not counted)A line ending in … continues the statement above it and shares its fate.
| 1 | 7 | #!/bin/bash |
| 2 | # Example: call the GCO API Gateway through aws-sigv4-proxy. | |
| 3 | # | |
| 4 | # Requirements: AWS CLI, aws-sigv4-proxy, curl, jq, and (optionally) lsof. | |
| 5 | # The proxy uses the normal AWS credential provider chain, so AWS_PROFILE, | |
| 6 | # temporary session credentials, SSO, web identity, and IAM roles are supported. | |
| 7 | ||
| 8 | 10 | set -euo pipefail |
| 9 | ||
| 10 | 40 | SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) |
| 11 | 30 | PROJECT_ROOT=$(cd "${SCRIPT_DIR}/../.." && pwd) |
| 12 | ||
| 13 | 38 | for command_name in aws aws-sigv4-proxy curl jq; do |
| 14 | 38 | if ! command -v "$command_name" >/dev/null 2>&1; then |
| 15 | 1 | echo "Error: required command '$command_name' is not installed" >&2 |
| 16 | 1 | exit 1 |
| 17 | fi | |
| 18 | done | |
| 19 | ||
| 20 | # Reads one value from the checkout's cdk.json, or prints the fallback when the | |
| 21 | # file is absent (the example was copied out of the checkout) or the key is | |
| 22 | # not set. jq fails on both, and its stderr is silenced because either is | |
| 23 | # an expected condition here, not an error. | |
| 24 | context_value() { | |
| 25 | 9 | local jq_filter=$1 |
| 26 | 9 | local fallback=$2 |
| 27 | 11 | jq -er "${jq_filter} // empty" "${PROJECT_ROOT}/cdk.json" 2>/dev/null || printf '%s\n' "$fallback" |
| 28 | } | |
| 29 | ||
| 30 | 10 | API_REGION=${API_REGION:-$(context_value '.context.deployment_regions.api_gateway' 'us-east-2')} |
| 31 | 17 | PROJECT_NAME=${PROJECT_NAME:-$(context_value '.context.project_name' 'gco')} |
| 32 | 9 | STACK_NAME=${STACK_NAME:-${PROJECT_NAME}-api-gateway} |
| 33 | 9 | PROXY_PORT=${PROXY_PORT:-8080} |
| 34 | ||
| 35 | 9 | GREEN='\033[0;32m' |
| 36 | 9 | BLUE='\033[0;34m' |
| 37 | 9 | YELLOW='\033[1;33m' |
| 38 | 9 | RED='\033[0;31m' |
| 39 | 9 | NC='\033[0m' |
| 40 | ||
| 41 | 9 | echo -e "${BLUE}=== GCO API Gateway - aws-sigv4-proxy examples ===${NC}\n" |
| 42 | 9 | aws sts get-caller-identity >/dev/null |
| 43 | ||
| 44 | # shellcheck disable=SC2016 | |
| 45 | 18 | API_ENDPOINT=$(aws cloudformation describe-stacks \ |
| 46 | --stack-name "$STACK_NAME" \ | |
| 47 | --region "$API_REGION" \ | |
| 48 | --query 'Stacks[0].Outputs[?OutputKey==`ApiEndpoint`].OutputValue' \ | |
| 49 | --output text) | |
| 50 | 9 | API_ENDPOINT=${API_ENDPOINT%/} |
| 51 | ||
| 52 | 18 | if [[ -z "$API_ENDPOINT" || "$API_ENDPOINT" == "None" ]]; then |
| 53 | 1 | echo -e "${RED}Error: ApiEndpoint was not found in stack ${STACK_NAME}${NC}" >&2 |
| 54 | 1 | exit 1 |
| 55 | fi | |
| 56 | ||
| 57 | 8 | API_WITHOUT_SCHEME=${API_ENDPOINT#*://} |
| 58 | 8 | API_HOST=${API_WITHOUT_SCHEME%%/*} |
| 59 | 8 | API_STAGE_PATH=${API_WITHOUT_SCHEME#"$API_HOST"} |
| 60 | 8 | LOCAL_API_BASE="http://localhost:${PROXY_PORT}${API_STAGE_PATH}" |
| 61 | ||
| 62 | 8 | echo "API endpoint: ${API_ENDPOINT}" |
| 63 | 8 | echo "Signing region: ${API_REGION}" |
| 64 | ||
| 65 | 16 | if command -v lsof >/dev/null 2>&1 && lsof -Pi :"$PROXY_PORT" -sTCP:LISTEN -t >/dev/null 2>&1; then |
| 66 | 1 | echo -e "${RED}Error: port ${PROXY_PORT} is already in use; choose another PROXY_PORT${NC}" >&2 |
| 67 | 1 | exit 1 |
| 68 | fi | |
| 69 | ||
| 70 | 7 | PROXY_PID="" |
| 71 | 14 | PAYLOAD_FILE=$(mktemp "${TMPDIR:-/tmp}/gco-manifest.XXXXXX") |
| 72 | cleanup() { | |
| 73 | 7 | rm -f "$PAYLOAD_FILE" |
| 74 | 7 | if [[ -n "$PROXY_PID" ]]; then |
| 75 | 7 | echo -e "\n${GREEN}Stopping aws-sigv4-proxy...${NC}" |
| 76 | 8 | kill "$PROXY_PID" 2>/dev/null || true |
| 77 | 8 | wait "$PROXY_PID" 2>/dev/null || true |
| 78 | fi | |
| 79 | } | |
| 80 | 7 | trap cleanup EXIT INT TERM |
| 81 | ||
| 82 | 7 | echo -e "${GREEN}Starting aws-sigv4-proxy on port ${PROXY_PORT}...${NC}" |
| 83 | 7 | aws-sigv4-proxy \ |
| 84 | --name execute-api \ | |
| 85 | --region "$API_REGION" \ | |
| 86 | --port "$PROXY_PORT" \ | |
| 87 | --upstream-url-scheme https \ | |
| 88 | --log-level info & | |
| 89 | 7 | PROXY_PID=$! |
| 90 | 7 | sleep 2 |
| 91 | 7 | if ! kill -0 "$PROXY_PID" 2>/dev/null; then |
| 92 | 1 | echo -e "${RED}Error: aws-sigv4-proxy failed to start${NC}" >&2 |
| 93 | 1 | exit 1 |
| 94 | fi | |
| 95 | ||
| 96 | perform_request() { | |
| 97 | 22 | local response |
| 98 | 44 | response=$(curl -sS "$@" -w $'\nHTTP_STATUS:%{http_code}') |
| 99 | 22 | HTTP_STATUS=${response##*$'\nHTTP_STATUS:'} |
| 100 | 22 | BODY=${response%$'\nHTTP_STATUS:'*} |
| 101 | 22 | echo "HTTP status: ${HTTP_STATUS}" |
| 102 | 45 | echo "$BODY" | jq '.' 2>/dev/null || echo "$BODY" |
| 103 | } | |
| 104 | ||
| 105 | 6 | echo -e "\n${BLUE}Example 1: submit a Job manifest${NC}" |
| 106 | 6 | cat >"$PAYLOAD_FILE" <<'EOF' |
| 107 | { | |
| 108 | "manifests": [ | |
| 109 | { | |
| 110 | "apiVersion": "batch/v1", | |
| 111 | "kind": "Job", | |
| 112 | "metadata": { | |
| 113 | "name": "curl-example-job", | |
| 114 | "namespace": "gco-jobs", | |
| 115 | "labels": { | |
| 116 | "app": "curl-example", | |
| 117 | "submitted-by": "curl-sigv4-proxy" | |
| 118 | } | |
| 119 | }, | |
| 120 | "spec": { | |
| 121 | "template": { | |
| 122 | "spec": { | |
| 123 | "containers": [ | |
| 124 | { | |
| 125 | "name": "example", | |
| 126 | "image": "busybox:1.38.0", | |
| 127 | "command": ["sh", "-c", "echo 'Hello from GCO!' && sleep 10"] | |
| 128 | } | |
| 129 | ], | |
| 130 | "restartPolicy": "Never" | |
| 131 | } | |
| 132 | }, | |
| 133 | "backoffLimit": 2 | |
| 134 | } | |
| 135 | } | |
| 136 | ] | |
| 137 | } | |
| 138 | EOF | |
| 139 | 6 | jq '.' "$PAYLOAD_FILE" |
| 140 | 6 | perform_request \ |
| 141 | -X POST "${LOCAL_API_BASE}/api/v1/manifests" \ | |
| 142 | -H "Host: ${API_HOST}" \ | |
| 143 | -H "Content-Type: application/json" \ | |
| 144 | --data-binary "@${PAYLOAD_FILE}" | |
| 145 | ||
| 146 | 6 | if [[ "$HTTP_STATUS" != "200" ]]; then |
| 147 | 1 | echo -e "${RED}Manifest submission failed${NC}" >&2 |
| 148 | 1 | exit 1 |
| 149 | fi | |
| 150 | ||
| 151 | 5 | echo -e "\n${BLUE}Example 2: get Job status${NC}" |
| 152 | 5 | perform_request \ |
| 153 | "${LOCAL_API_BASE}/api/v1/jobs/gco-jobs/curl-example-job" \ | |
| 154 | -H "Host: ${API_HOST}" | |
| 155 | ||
| 156 | 5 | echo -e "\n${BLUE}Example 3: list Jobs${NC}" |
| 157 | 5 | perform_request \ |
| 158 | "${LOCAL_API_BASE}/api/v1/jobs?namespace=gco-jobs&limit=20" \ | |
| 159 | -H "Host: ${API_HOST}" | |
| 160 | ||
| 161 | 5 | echo -e "\n${BLUE}Example 4: optional Job deletion${NC}" |
| 162 | 5 | read -r -p "Delete gco-jobs/curl-example-job? (y/N): " REPLY |
| 163 | 5 | if [[ "$REPLY" =~ ^[Yy]$ ]]; then |
| 164 | 1 | perform_request \ |
| 165 | -X DELETE "${LOCAL_API_BASE}/api/v1/jobs/gco-jobs/curl-example-job" \ | |
| 166 | -H "Host: ${API_HOST}" | |
| 167 | else | |
| 168 | 4 | echo "Skipping deletion." |
| 169 | fi | |
| 170 | ||
| 171 | 5 | echo -e "\n${BLUE}Example 5: verify unsigned requests are rejected${NC}" |
| 172 | 5 | perform_request "${API_ENDPOINT}/api/v1/jobs?limit=1" |
| 173 | 5 | if [[ "$HTTP_STATUS" == "403" ]]; then |
| 174 | 4 | echo -e "${GREEN}Unsigned request correctly rejected.${NC}" |
| 175 | else | |
| 176 | 1 | echo -e "${YELLOW}Expected HTTP 403, received ${HTTP_STATUS}.${NC}" |
| 177 | fi | |
| 178 | ||
| 179 | 5 | echo -e "\n${BLUE}=== Examples complete ===${NC}" |
| 180 | 5 | echo "The local URL includes the API Gateway stage path (${API_STAGE_PATH})." |
| 181 | 5 | echo "aws-sigv4-proxy signed requests with the active AWS credential chain." |