← all scripts

docs/client-examples/curl_sigv4_proxy_example.sh

81 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.

17#!/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
810set -euo pipefail
9
1040SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
1130PROJECT_ROOT=$(cd "${SCRIPT_DIR}/../.." && pwd)
12
1338for command_name in aws aws-sigv4-proxy curl jq; do
1438 if ! command -v "$command_name" >/dev/null 2>&1; then
151 echo "Error: required command '$command_name' is not installed" >&2
161 exit 1
17 fi
18done
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.
24context_value() {
259 local jq_filter=$1
269 local fallback=$2
2711 jq -er "${jq_filter} // empty" "${PROJECT_ROOT}/cdk.json" 2>/dev/null || printf '%s\n' "$fallback"
28}
29
3010API_REGION=${API_REGION:-$(context_value '.context.deployment_regions.api_gateway' 'us-east-2')}
3117PROJECT_NAME=${PROJECT_NAME:-$(context_value '.context.project_name' 'gco')}
329STACK_NAME=${STACK_NAME:-${PROJECT_NAME}-api-gateway}
339PROXY_PORT=${PROXY_PORT:-8080}
34
359GREEN='\033[0;32m'
369BLUE='\033[0;34m'
379YELLOW='\033[1;33m'
389RED='\033[0;31m'
399NC='\033[0m'
40
419echo -e "${BLUE}=== GCO API Gateway - aws-sigv4-proxy examples ===${NC}\n"
429aws sts get-caller-identity >/dev/null
43
44# shellcheck disable=SC2016
4518API_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)
509API_ENDPOINT=${API_ENDPOINT%/}
51
5218if [[ -z "$API_ENDPOINT" || "$API_ENDPOINT" == "None" ]]; then
531 echo -e "${RED}Error: ApiEndpoint was not found in stack ${STACK_NAME}${NC}" >&2
541 exit 1
55fi
56
578API_WITHOUT_SCHEME=${API_ENDPOINT#*://}
588API_HOST=${API_WITHOUT_SCHEME%%/*}
598API_STAGE_PATH=${API_WITHOUT_SCHEME#"$API_HOST"}
608LOCAL_API_BASE="http://localhost:${PROXY_PORT}${API_STAGE_PATH}"
61
628echo "API endpoint: ${API_ENDPOINT}"
638echo "Signing region: ${API_REGION}"
64
6516if command -v lsof >/dev/null 2>&1 && lsof -Pi :"$PROXY_PORT" -sTCP:LISTEN -t >/dev/null 2>&1; then
661 echo -e "${RED}Error: port ${PROXY_PORT} is already in use; choose another PROXY_PORT${NC}" >&2
671 exit 1
68fi
69
707PROXY_PID=""
7114PAYLOAD_FILE=$(mktemp "${TMPDIR:-/tmp}/gco-manifest.XXXXXX")
72cleanup() {
737 rm -f "$PAYLOAD_FILE"
747 if [[ -n "$PROXY_PID" ]]; then
757 echo -e "\n${GREEN}Stopping aws-sigv4-proxy...${NC}"
768 kill "$PROXY_PID" 2>/dev/null || true
778 wait "$PROXY_PID" 2>/dev/null || true
78 fi
79}
807trap cleanup EXIT INT TERM
81
827echo -e "${GREEN}Starting aws-sigv4-proxy on port ${PROXY_PORT}...${NC}"
837aws-sigv4-proxy \
84 --name execute-api \
85 --region "$API_REGION" \
86 --port "$PROXY_PORT" \
87 --upstream-url-scheme https \
88 --log-level info &
897PROXY_PID=$!
907sleep 2
917if ! kill -0 "$PROXY_PID" 2>/dev/null; then
921 echo -e "${RED}Error: aws-sigv4-proxy failed to start${NC}" >&2
931 exit 1
94fi
95
96perform_request() {
9722 local response
9844 response=$(curl -sS "$@" -w $'\nHTTP_STATUS:%{http_code}')
9922 HTTP_STATUS=${response##*$'\nHTTP_STATUS:'}
10022 BODY=${response%$'\nHTTP_STATUS:'*}
10122 echo "HTTP status: ${HTTP_STATUS}"
10245 echo "$BODY" | jq '.' 2>/dev/null || echo "$BODY"
103}
104
1056echo -e "\n${BLUE}Example 1: submit a Job manifest${NC}"
1066cat >"$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}
138EOF
1396jq '.' "$PAYLOAD_FILE"
1406perform_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
1466if [[ "$HTTP_STATUS" != "200" ]]; then
1471 echo -e "${RED}Manifest submission failed${NC}" >&2
1481 exit 1
149fi
150
1515echo -e "\n${BLUE}Example 2: get Job status${NC}"
1525perform_request \
153 "${LOCAL_API_BASE}/api/v1/jobs/gco-jobs/curl-example-job" \
154 -H "Host: ${API_HOST}"
155
1565echo -e "\n${BLUE}Example 3: list Jobs${NC}"
1575perform_request \
158 "${LOCAL_API_BASE}/api/v1/jobs?namespace=gco-jobs&limit=20" \
159 -H "Host: ${API_HOST}"
160
1615echo -e "\n${BLUE}Example 4: optional Job deletion${NC}"
1625read -r -p "Delete gco-jobs/curl-example-job? (y/N): " REPLY
1635if [[ "$REPLY" =~ ^[Yy]$ ]]; then
1641 perform_request \
165 -X DELETE "${LOCAL_API_BASE}/api/v1/jobs/gco-jobs/curl-example-job" \
166 -H "Host: ${API_HOST}"
167else
1684 echo "Skipping deletion."
169fi
170
1715echo -e "\n${BLUE}Example 5: verify unsigned requests are rejected${NC}"
1725perform_request "${API_ENDPOINT}/api/v1/jobs?limit=1"
1735if [[ "$HTTP_STATUS" == "403" ]]; then
1744 echo -e "${GREEN}Unsigned request correctly rejected.${NC}"
175else
1761 echo -e "${YELLOW}Expected HTTP 403, received ${HTTP_STATUS}.${NC}"
177fi
178
1795echo -e "\n${BLUE}=== Examples complete ===${NC}"
1805echo "The local URL includes the API Gateway stage path (${API_STAGE_PATH})."
1815echo "aws-sigv4-proxy signed requests with the active AWS credential chain."