diff --git a/.github/workflows/mock-experiment.yml b/.github/workflows/mock-experiment.yml new file mode 100644 index 0000000..c701e12 --- /dev/null +++ b/.github/workflows/mock-experiment.yml @@ -0,0 +1,442 @@ +name: Mock Experiment + +permissions: + contents: read + +on: + workflow_run: + workflows: ["Provider Analysis"] + types: + - completed + push: + tags: + - mock-experiment* + +env: + STACKQL_CORE_REPOSITORY: ${{ vars.STACKQL_CORE_REPOSITORY != '' && vars.STACKQL_CORE_REPOSITORY || 'stackql/stackql' }} + STACKQL_CORE_REF: ${{ vars.STACKQL_CORE_REF != '' && vars.STACKQL_CORE_REF || 'main' }} + GOLANG_VERSION: 1.25.3 + PYTHON_VERSION: '3.12' + ANALYSIS_RESULTS_BUCKET: 'stackql-provider-analysis-results' + MOCK_PORT_BASE: 5050 + MAX_TEST_COUNT: 20000 + PARALLEL_JOBS: 20 + DEFAULT_JOB_TIMEOUT_MIN: ${{ vars.DEFAULT_JOB_TIMEOUT_MIN == '' && 80 || vars.DEFAULT_JOB_TIMEOUT_MIN }} + DEFAULT_MOCK_TIMEOUT_MIN: ${{ vars.DEFAULT_MOCK_TIMEOUT_MIN == '' && 60 || vars.DEFAULT_MOCK_TIMEOUT_MIN }} + +jobs: + + build-stackql: + if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' }} + runs-on: ubuntu-24.04 + + steps: + + - name: Set up golang + uses: actions/setup-go@v5 + with: + go-version: ^${{ env.GOLANG_VERSION }} + id: go + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Download core + uses: actions/checkout@v4 + with: + repository: ${{ env.STACKQL_CORE_REPOSITORY }} + ref: ${{ env.STACKQL_CORE_REF }} + token: ${{ secrets.CI_STACKQL_PACKAGE_DOWNLOAD_TOKEN }} + path: stackql-core + + - name: Build stackql from core source + working-directory: stackql-core + run: | + go get ./... + python3 cicd/python/build.py --build + + - name: Upload stackql binary + uses: actions/upload-artifact@v4 + with: + name: stackql_binary + path: stackql-core/build/stackql + + mock-test: + name: Mock E2E Tests + runs-on: ubuntu-24.04 + timeout-minutes: ${{ vars.DEFAULT_JOB_TIMEOUT_MIN == '' && 80 || vars.DEFAULT_JOB_TIMEOUT_MIN }} + needs: + - build-stackql + steps: + + - name: Check out code + uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Install Flask + run: pip install flask + + - name: Download stackql binary + uses: actions/download-artifact@v4 + with: + name: stackql_binary + path: build + + - name: Make stackql executable + run: | + chmod +x build/stackql + echo "${{ github.workspace }}/build" >> $GITHUB_PATH + + - name: Download Provider Analysis artifacts + run: | + set -e + RUN_ID=$(gh run list --workflow="Provider Analysis" --status=success --limit=1 --json databaseId --jq '.[0].databaseId') + echo "Downloading artifacts from run: ${RUN_ID}" + gh run download "$RUN_ID" --name auto-mocks --dir cicd/out/auto-mocks + gh run download "$RUN_ID" --name mock-queries --dir cicd/out/mock-queries + gh run download "$RUN_ID" --name mock-expectations --dir cicd/out/mock-expectations + gh run download "$RUN_ID" --name closures --dir cicd/out/closures + env: + GH_TOKEN: ${{ github.token }} + + - name: Verify downloaded artifacts + run: | + echo "Mocks: $(find cicd/out/auto-mocks -name 'mock_*.py' 2>/dev/null | wc -l)" + echo "Queries: $(find cicd/out/mock-queries -name 'query_*.txt' 2>/dev/null | wc -l)" + echo "Closures: $(find cicd/out/closures -name 'provider.yaml' 2>/dev/null | wc -l)" + + - name: Prepare test runner script + run: | + cat > /tmp/run_one_test.sh << 'SCRIPT' + #!/usr/bin/env bash + # Args: line_number provider_dir mock_filename provider service resource method + set -f + LINE_NUM="$1" + PROVIDER_DIR="$2" + MOCK_FILENAME="$3" + PROVIDER="$4" + SERVICE="$5" + RESOURCE="$6" + METHOD="$7" + + EVENTS_FILE="$EVENTS_DIR/events.jsonl" + PORT=$(( MOCK_PORT_BASE + (LINE_NUM % 500) )) + METHOD_KEY="${PROVIDER}_${SERVICE}_${RESOURCE}_${METHOD}" + + MOCK_FILE="cicd/out/auto-mocks/${PROVIDER_DIR}/${MOCK_FILENAME}" + QUERY_FILE="cicd/out/mock-queries/${PROVIDER_DIR}/query_${METHOD_KEY}.txt" + EXPECT_FILE="cicd/out/mock-expectations/${PROVIDER_DIR}/expect_${METHOD_KEY}.txt" + CLOSURE_DIR="cicd/out/closures/${PROVIDER_DIR}/${PROVIDER}_${SERVICE}_${RESOURCE}" + + emit() { + local status="$1" error_class="$2" detail="$3" http_code="$4" + printf '{"method_key":"%s","provider":"%s","service":"%s","resource":"%s","method":"%s","sql_verb":"%s","status":"%s","error_class":"%s","http_code":%s,"detail":"%s"}\n' \ + "$METHOD_KEY" "$PROVIDER" "$SERVICE" "$RESOURCE" "$METHOD" "$SQL_VERB" \ + "$status" "$error_class" "${http_code:-null}" "$detail" \ + | flock "$EVENTS_FILE.lock" tee -a "$EVENTS_FILE" > /dev/null + } + + # Determine SQL verb from query + SQL_VERB="unknown" + if [ -f "$QUERY_FILE" ]; then + first_word="$(head -c 20 "$QUERY_FILE" | awk '{print tolower($1)}')" + case "$first_word" in + select) SQL_VERB="select" ;; + insert) SQL_VERB="insert" ;; + delete) SQL_VERB="delete" ;; + update) SQL_VERB="update" ;; + exec) SQL_VERB="exec" ;; + esac + fi + + [ -f "$MOCK_FILE" ] || { emit "skip" "no_mock" "" ""; exit 0; } + [ -f "$QUERY_FILE" ] || { emit "skip" "no_query" "" ""; exit 0; } + [ -d "$CLOSURE_DIR" ] || { emit "skip" "no_closure" "" ""; exit 0; } + + QUERY="$(cat "$QUERY_FILE")" + REGISTRY_DIR="$(pwd)/${CLOSURE_DIR}" + + # Start mock, retry port if busy + for attempt in 1 2 3; do + python3 "$MOCK_FILE" --port "$PORT" & + MOCK_PID=$! + sleep 0.3 + if kill -0 $MOCK_PID 2>/dev/null; then + break + fi + PORT=$(( PORT + 500 )) + done + + if ! kill -0 $MOCK_PID 2>/dev/null; then + emit "fail" "mock_start_failed" "could not start mock on any port" "" + exit 0 + fi + + STDERR_FILE="/tmp/sq_stderr_${LINE_NUM}.txt" + RESPONSE="$(stackql \ + --tls.allowInsecure \ + --http.log.enabled \ + --registry "{ \"url\": \"file://${REGISTRY_DIR}\", \"localDocRoot\": \"${REGISTRY_DIR}\", \"verifyConfig\": { \"nopVerify\": true } }" \ + exec "${QUERY};" -o json 2>"$STDERR_FILE")" + + HTTP_CODE="$(grep 'http response status code:' "$STDERR_FILE" 2>/dev/null | head -1 | sed 's/.*status code: //' | sed 's/,.*//' | tr -d ' ')" + + kill $MOCK_PID 2>/dev/null + wait $MOCK_PID 2>/dev/null + + if [ "$HTTP_CODE" = "200" ]; then + # Check body match if expectation exists + if [ -f "$EXPECT_FILE" ] && [ -s "$EXPECT_FILE" ]; then + EXPECTED="$(cat "$EXPECT_FILE")" + if [ "$RESPONSE" = "$EXPECTED" ]; then + emit "pass" "status_ok_body_match" "" "$HTTP_CODE" + else + emit "fail" "body_mismatch" "" "$HTTP_CODE" + fi + else + emit "pass" "status_ok" "" "$HTTP_CODE" + fi + elif [ -n "$HTTP_CODE" ]; then + emit "fail" "status_${HTTP_CODE}" "" "$HTTP_CODE" + else + # No HTTP request made — classify from stderr + STDERR_FIRST="$(head -1 "$STDERR_FILE" 2>/dev/null | tr '"' "'" | head -c 200)" + if echo "$STDERR_FIRST" | grep -qi "syntax error"; then + emit "fail" "query_syntax_error" "$STDERR_FIRST" "" + elif echo "$STDERR_FIRST" | grep -qi "cannot resolve"; then + emit "fail" "registry_resolve_error" "$STDERR_FIRST" "" + elif echo "$STDERR_FIRST" | grep -qi "credentials"; then + emit "fail" "credentials_error" "$STDERR_FIRST" "" + else + emit "fail" "no_http_request" "$STDERR_FIRST" "" + fi + fi + + rm -f "$STDERR_FILE" + SCRIPT + chmod +x /tmp/run_one_test.sh + + - name: Run Mock E2E Tests + env: + AWS_SECRET_ACCESS_KEY: fake + AWS_ACCESS_KEY_ID: fake + GOOGLE_CREDENTIALS: '{}' + AZURE_CLIENT_ID: fake + AZURE_CLIENT_SECRET: fake + AZURE_TENANT_ID: fake + MOCK_PORT_BASE: ${{ env.MOCK_PORT_BASE }} + EVENTS_DIR: cicd/out + run: | + set +e + mkdir -p cicd/out + touch cicd/out/events.jsonl + touch cicd/out/events.jsonl.lock + + MAX_TESTS="${MAX_TEST_COUNT:-20000}" + # Unlimited for workflow_run trigger or mock-experiment-total* tags + if [ "${{ github.event_name }}" = "workflow_run" ]; then + MAX_TESTS=-1 + elif echo "${{ github.ref }}" | grep -q "refs/tags/mock-experiment-total"; then + MAX_TESTS=-1 + fi + + TIMEOUT_MIN="${DEFAULT_MOCK_TIMEOUT_MIN:-60}" + DEADLINE=$(($(date +%s) + TIMEOUT_MIN * 60)) + + # Build test list: prefer manifest, fall back to query files + TEST_LIST="/tmp/test_list.txt" + : > "$TEST_LIST" + + for manifest in cicd/out/auto-mocks/*/manifest.txt; do + [ -f "$manifest" ] || continue + provider_dir="$(basename "$(dirname "$manifest")")" + while IFS='|' read -r mock_filename provider service resource method; do + echo "${provider_dir}|${mock_filename}|${provider}|${service}|${resource}|${method}" + done < "$manifest" + done >> "$TEST_LIST" + + if [ ! -s "$TEST_LIST" ]; then + echo "No manifests found, deriving from query files" + for qf in cicd/out/mock-queries/*/query_*.txt; do + [ -f "$qf" ] || continue + provider_dir="$(basename "$(dirname "$qf")")" + key="$(basename "$qf" .txt)"; key="${key#query_}" + [ -f "cicd/out/auto-mocks/${provider_dir}/mock_${key}.py" ] || continue + echo "${provider_dir}|mock_${key}.py|${key}||||" + done >> "$TEST_LIST" + fi + + TOTAL_AVAILABLE="$(wc -l < "$TEST_LIST")" + if [ "$MAX_TESTS" -ge 0 ] 2>/dev/null && [ "$TOTAL_AVAILABLE" -gt "$MAX_TESTS" ]; then + echo "Limiting to ${MAX_TESTS} of ${TOTAL_AVAILABLE} tests" + head -n "$MAX_TESTS" "$TEST_LIST" > /tmp/test_list_limited.txt + mv /tmp/test_list_limited.txt "$TEST_LIST" + fi + + echo "Running $(wc -l < "$TEST_LIST") tests with ${PARALLEL_JOBS} parallel workers" + + export EVENTS_DIR MOCK_PORT_BASE DEADLINE + echo "Deadline: $(date -u -d "@${DEADLINE}" +%H:%M:%SZ 2>/dev/null || date -u -r "${DEADLINE}" +%H:%M:%SZ) (${TIMEOUT_MIN}m from now)" + + timeout --signal=TERM "${TIMEOUT_MIN}m" bash -c ' + nl -ba "$0" | tr "\t" "|" | \ + xargs -P "${PARALLEL_JOBS}" -I {} bash -c '\'' + IFS="|" read -r line_num provider_dir mock_filename provider service resource method <<< "{}" + /tmp/run_one_test.sh "$line_num" "$provider_dir" "$mock_filename" "$provider" "$service" "$resource" "$method" + '\'' + ' "$TEST_LIST" || true + + echo "Test run complete (or timed out). Events: $(wc -l < cicd/out/events.jsonl)" + + - name: Download prior results for trend comparison + if: always() + continue-on-error: true + run: | + gcloud storage cp "gs://${ANALYSIS_RESULTS_BUCKET}/mock-experiments/latest.json" /tmp/prior_latest.json 2>/dev/null || true + if [ -f /tmp/prior_latest.json ]; then + PRIOR_DIR="$(jq -r '.latest' /tmp/prior_latest.json)" + gcloud storage cp "${PRIOR_DIR}/mock-test-results.json" cicd/out/prior-results.json 2>/dev/null || true + fi + + - name: Generate Summary + if: always() + run: | + python3 << 'PYEOF' + import json, os + from collections import Counter + + events = [] + with open("cicd/out/events.jsonl") as f: + for line in f: + line = line.strip() + if line: + try: + events.append(json.loads(line)) + except json.JSONDecodeError: + pass + + total = len(events) + status_counts = Counter(e["status"] for e in events) + class_counts = Counter(e["error_class"] for e in events) + + # Score metrics + pass_count = status_counts.get("pass", 0) + fail_count = status_counts.get("fail", 0) + skip_count = status_counts.get("skip", 0) + tested = pass_count + fail_count + scores = { + "pass_rate": round(pass_count / tested, 4) if tested > 0 else 0, + "fail_rate": round(fail_count / tested, 4) if tested > 0 else 0, + "skip_rate": round(skip_count / total, 4) if total > 0 else 0, + "tested": tested, + } + + # Coverage: methods with mocks (have sample_response) — from events that aren't skips + methods_tested = set() + sql_verbs_seen = Counter() + for e in events: + if e["status"] != "skip": + methods_tested.add(e["method_key"]) + sql_verbs_seen[e.get("sql_verb", "unknown")] += 1 + coverage = { + "unique_methods_tested": len(methods_tested), + "by_sql_verb": dict(sql_verbs_seen.most_common()), + } + + # Intersection: sql_verb × error_class + verb_class = Counter((e.get("sql_verb", "unknown"), e["error_class"]) for e in events) + verb_class_summary = {f"{v}:{c}": n for (v, c), n in verb_class.most_common()} + + # Provider × status + provider_status = Counter((e.get("provider", "unknown"), e["status"]) for e in events) + provider_summary = {f"{p}:{s}": n for (p, s), n in provider_status.most_common()} + + summary = { + "total": total, + "scores": scores, + "coverage": coverage, + "by_status": dict(status_counts.most_common()), + "by_error_class": dict(class_counts.most_common()), + "by_verb_and_class": verb_class_summary, + "by_provider_and_status": provider_summary, + } + + # Trend comparison against prior run + prior_path = "cicd/out/prior-results.json" + if os.path.exists(prior_path): + try: + with open(prior_path) as f: + prior = json.load(f) + prior_scores = prior.get("scores", {}) + prior_pass = prior_scores.get("pass_rate", 0) + prior_tested = prior_scores.get("tested", 0) + delta_pass_rate = round(scores["pass_rate"] - prior_pass, 4) + delta_tested = scores["tested"] - prior_tested + + # Regressions: error classes that increased + prior_classes = prior.get("by_error_class", {}) + regressions = {} + improvements = {} + for cls, count in class_counts.items(): + prior_count = prior_classes.get(cls, 0) + if count > prior_count: + regressions[cls] = {"current": count, "prior": prior_count, "delta": count - prior_count} + elif count < prior_count: + improvements[cls] = {"current": count, "prior": prior_count, "delta": prior_count - count} + + summary["trend"] = { + "delta_pass_rate": delta_pass_rate, + "delta_tested": delta_tested, + "direction": "improving" if delta_pass_rate > 0 else "regressing" if delta_pass_rate < 0 else "stable", + "regressions": regressions, + "improvements": improvements, + } + except (json.JSONDecodeError, KeyError): + summary["trend"] = {"error": "could not parse prior results"} + else: + summary["trend"] = {"note": "no prior run available for comparison"} + + with open("cicd/out/mock-test-results.json", "w") as f: + json.dump(summary, f, indent=2) + + print(json.dumps(summary, indent=2)) + PYEOF + + - name: Upload Mock Test Results + uses: actions/upload-artifact@v4 + if: always() + with: + name: mock-test-results + path: | + cicd/out/mock-test-results.json + cicd/out/events.jsonl + + - name: Authenticate to Google Cloud + uses: google-github-actions/auth@v2 + if: always() + with: + credentials_json: ${{ secrets.ANALYSIS_UPLOAD_GCS }} + + - name: Set up Cloud SDK + uses: google-github-actions/setup-gcloud@v2 + if: always() + + - name: Upload Mock Test Results to GCS + if: always() + run: | + RUN_EPOCH="$(date -u +%s)" + RUN_TS="$(date -u -d "@${RUN_EPOCH}" +%Y-%m-%dT%H-%M-%SZ)" + DATE_PATH="$(date -u -d "@${RUN_EPOCH}" +%Y/%m/%d)" + DEST="gs://${ANALYSIS_RESULTS_BUCKET}/mock-experiments/${DATE_PATH}/${RUN_TS}" + + gcloud storage cp cicd/out/mock-test-results.json "${DEST}/mock-test-results.json" + gcloud storage cp cicd/out/events.jsonl "${DEST}/events.jsonl" + + echo "{\"latest\": \"${DEST}\"}" > latest.json + gcloud storage cp latest.json "gs://${ANALYSIS_RESULTS_BUCKET}/mock-experiments/latest.json" diff --git a/.github/workflows/provider-analysis.yml b/.github/workflows/provider-analysis.yml index 90f30af..77a0a2f 100644 --- a/.github/workflows/provider-analysis.yml +++ b/.github/workflows/provider-analysis.yml @@ -1,4 +1,4 @@ -name: Build +name: Provider Analysis permissions: contents: read @@ -144,7 +144,7 @@ jobs: ref: ${{ env.STACKQL_CORE_REF }} path: stackql-core - - name: Interrogate Providers via CLI and Generate Analysis + - name: Pull Providers run: | stackql exec "registry pull aws ${PROVIDER_VERSION_AWS};" stackql exec "registry pull google ${PROVIDER_VERSION_GOOGLE};" @@ -157,31 +157,87 @@ jobs: echo "DATE_PATH=${DATE_PATH}" echo "RUN_TS=${RUN_TS}" } >> "$GITHUB_ENV" - ./build/anysdk aot \ - ./.stackql \ - ./.stackql/src/aws/${PROVIDER_VERSION_AWS}/provider.yaml \ - -v \ - --schema-dir \ - cicd/schema-definitions > "cicd/out/${RUN_TS}-aws-${PROVIDER_VERSION_AWS}-summary.json" 2>"cicd/out/${RUN_TS}-aws-${PROVIDER_VERSION_AWS}-analysis.jsonl" || true - ./build/anysdk aot \ - ./.stackql \ - ./.stackql/src/azure/${PROVIDER_VERSION_AZURE}/provider.yaml \ - -v \ - --schema-dir \ - cicd/schema-definitions > "cicd/out/${RUN_TS}-azure-${PROVIDER_VERSION_AZURE}-summary.json" 2>"cicd/out/${RUN_TS}-azure-${PROVIDER_VERSION_AZURE}-analysis.jsonl" || true - ./build/anysdk aot \ - ./.stackql \ - ./.stackql/src/googleapis.com/${PROVIDER_VERSION_GOOGLE}/provider.yaml \ - -v \ - --schema-dir \ - cicd/schema-definitions > "cicd/out/${RUN_TS}-google-${PROVIDER_VERSION_GOOGLE}-summary.json" 2>"cicd/out/${RUN_TS}-google-${PROVIDER_VERSION_GOOGLE}-analysis.jsonl" || true + + - name: AOT Analysis and Mock Generation + run: | + for provider_cfg in \ + "aws|aws|${PROVIDER_VERSION_AWS}" \ + "azure|azure|${PROVIDER_VERSION_AZURE}" \ + "google|googleapis.com|${PROVIDER_VERSION_GOOGLE}"; do + + IFS='|' read -r provider handle version <<< "$provider_cfg" + + ./build/anysdk aot \ + ./.stackql \ + ./.stackql/src/${handle}/${version}/provider.yaml \ + -v \ + --mock-output-dir "cicd/out/auto-mocks/${provider}" \ + --mock-expectation-dir "cicd/out/mock-expectations/${provider}" \ + --mock-query-dir "cicd/out/mock-queries/${provider}" \ + --schema-dir cicd/schema-definitions \ + --stdout-file "cicd/out/aot/${RUN_TS}-${provider}-${version}-summary.json" \ + --stderr-file "cicd/out/aot/${RUN_TS}-${provider}-${version}-analysis.jsonl" || true + done + + - name: Generate Per-Resource Closures with Provider Docs + run: | + MOCK_PORT=5000 + + for provider_cfg in \ + "aws|aws|${PROVIDER_VERSION_AWS}" \ + "azure|azure|${PROVIDER_VERSION_AZURE}" \ + "google|googleapis.com|${PROVIDER_VERSION_GOOGLE}"; do + + IFS='|' read -r provider handle version <<< "$provider_cfg" + + for svc_file in .stackql/src/${handle}/${version}/services/*.yaml; do + svc_name="$(basename "$svc_file" .yaml)" + + ./build/anysdk closure \ + ./.stackql \ + ./.stackql/src/${handle}/${version}/provider.yaml \ + "$svc_name" \ + --provider "$provider" \ + --rewrite-url "http://localhost:${MOCK_PORT}" \ + --output-dir "cicd/out/closures/${provider}" \ + 2>/dev/null || true + done + done - name: Upload Provider Analysis results uses: actions/upload-artifact@v4 if: always() with: name: provider-analysis-results - path: cicd/out/*.json* + path: cicd/out/aot/ + + - name: Upload Auto Mocks + uses: actions/upload-artifact@v4 + if: always() + with: + name: auto-mocks + path: cicd/out/auto-mocks/ + + - name: Upload Mock Expectations + uses: actions/upload-artifact@v4 + if: always() + with: + name: mock-expectations + path: cicd/out/mock-expectations/ + + - name: Upload Mock Queries + uses: actions/upload-artifact@v4 + if: always() + with: + name: mock-queries + path: cicd/out/mock-queries/ + + - name: Upload Closures + uses: actions/upload-artifact@v4 + if: always() + with: + name: closures + path: cicd/out/closures/ - name: Authenticate to Google Cloud uses: google-github-actions/auth@v2 @@ -191,7 +247,7 @@ jobs: - name: Set up Cloud SDK uses: google-github-actions/setup-gcloud@v2 - - name: Upload analysis artifacts to GCS + - name: Upload all artifacts to GCS run: | set -e @@ -199,11 +255,13 @@ jobs: echo "Uploading to ${DEST}" - # upload everything - gcloud storage cp -r cicd/out "${DEST}/" + gcloud storage cp -r cicd/out/aot "${DEST}/" + gcloud storage cp -r cicd/out/auto-mocks "${DEST}/" + gcloud storage cp -r cicd/out/mock-expectations "${DEST}/" + gcloud storage cp -r cicd/out/mock-queries "${DEST}/" + gcloud storage cp -r cicd/out/closures "${DEST}/" - # optional: also write a pointer to latest - echo "{\"latest\": \"${DEST}/out\"}" > latest.json + echo "{\"latest\": \"${DEST}\"}" > latest.json gcloud storage cp latest.json "gs://${ANALYSIS_RESULTS_BUCKET}/provider-analysis/latest.json" diff --git a/.gitignore b/.gitignore index 13dfb8b..5867e35 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,7 @@ opint .DS_Store dist/ -.venv/ +*.venv/ __pycache__/ *.py[co] stackql-core/ diff --git a/cicd/.gitignore b/cicd/.gitignore new file mode 100644 index 0000000..f1a920f --- /dev/null +++ b/cicd/.gitignore @@ -0,0 +1,2 @@ +prior/ +post/ diff --git a/cicd/mock-testing-requirements.txt b/cicd/mock-testing-requirements.txt new file mode 100644 index 0000000..7fbcbc7 --- /dev/null +++ b/cicd/mock-testing-requirements.txt @@ -0,0 +1,10 @@ +PyYaml==6.0.2 +requests==2.32.3 +mistune==3.0.2 +psycopg2-binary==2.9.10 +psycopg[binary]>=3.1.16 +PyYaml==6.0.2 +requests==2.32.3 +robotframework==7.0.1 +sqlalchemy==1.4.44 +tabulate==0.9.0 diff --git a/cicd/out/.gitignore b/cicd/out/aot/.gitignore similarity index 100% rename from cicd/out/.gitignore rename to cicd/out/aot/.gitignore diff --git a/cicd/out/auto-mocks/.gitignore b/cicd/out/auto-mocks/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/cicd/out/auto-mocks/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/cicd/out/closures/.gitignore b/cicd/out/closures/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/cicd/out/closures/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/cicd/out/generic/.gitignore b/cicd/out/generic/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/cicd/out/generic/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/cicd/out/mock-expectations/aws/.gitignore b/cicd/out/mock-expectations/aws/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/cicd/out/mock-expectations/aws/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/cicd/out/mock-queries/.gitignore b/cicd/out/mock-queries/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/cicd/out/mock-queries/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/cicd/python/record_parser/README.md b/cicd/python/record_parser/README.md new file mode 100644 index 0000000..69735d8 --- /dev/null +++ b/cicd/python/record_parser/README.md @@ -0,0 +1,74 @@ +# record-parser + +Tiny example package. + +## Build with uv + +```bash +uv --directory cicd/python/record_parser build +``` + +## Install editable + +```bash +uv venv && uv pip install -e ./cicd/python/record_parser +``` + +## Use + +```python +from record_parser import parse_record, generate_flask_app, generate_flask_apps_from_file, generate_mocks_from_analysis_run + + +generate_mocks_from_analysis_run("test/assets/analysis-jsonl", "cicd/out/aot") + + +for line in generate_flask_apps_from_file("test/assets/analysis-jsonl/single-entry-observations.jsonl"): print(json.dumps(line)) + +``` + + + + +## De facto protocol + + + +```json +{ + "level": "warning", + "bin": "empty-response-unsafe", + "provider": "aws", + "service": "ec2", + "resource": "volumes_post_naively_presented", + "method": "describeVolumes", + "message": "response transform template accesses input directly without nil/empty guards — may fail on empty response bodies", + "prior_template": "{{ toJson . }}", + "fixed_template": "{{- if . -}}{{ toJson . }}{{- else -}}null{{- end -}}", + "empirical_tests": { + "results": [ + { + "input": "", + "ok": true + }, + { + "input": "", + "output": "{\"root\":\"\"}", + "ok": true + }, + { + "input": "", + "output": "{\"root\":\"\"}", + "ok": true + } + ] + }, + "sample_response": { + "pre_transform": "sample_stringsample_string0sample_stringfalsesample_stringsample_stringfalse00sample_stringsample_stringsample_stringsample_stringfalse", + "post_transform": "{\n \"DescribeVolumesResponse\": {\n \"NextToken\": \"sample_string\",\n \"Volumes\": [\n {\n \"Attachments\": [\n {}\n ],\n \"AvailabilityZone\": \"sample_string\",\n \"CreateTime\": \"sample_string\",\n \"Encrypted\": false,\n \"FastRestored\": false,\n \"Iops\": 0,\n \"KmsKeyId\": \"sample_string\",\n \"MultiAttachEnabled\": false,\n \"OutpostArn\": \"sample_string\",\n \"Size\": 0,\n \"SnapshotId\": \"sample_string\",\n \"State\": \"sample_string\",\n \"Tags\": [\n {}\n ],\n \"Throughput\": 0,\n \"VolumeId\": \"sample_string\",\n \"VolumeType\": \"sample_string\"\n }\n ]\n }\n}" + }, + "mock_route": "@app.route('/', methods=['POST'])\ndef aws_ec2_volumes_post_naively_presented_describevolumes():\n if request.form.get('Action') == 'DescribeVolumes':\n return Response(MOCK_RESPONSE_AWS_EC2_VOLUMES_POST_NAIVELY_PRESENTED_DESCRIBEVOLUMES, content_type='application/xml')", + "stackql_query": "SELECT * FROM aws.ec2.volumes_post_naively_presented WHERE region = 'dummy_region'", + "expected_response": "[\n {\n \"DescribeVolumesResponse\": {\n \"NextToken\": \"sample_string\",\n \"Volumes\": [\n {\n \"Attachments\": [\n {}\n ],\n \"AvailabilityZone\": \"sample_string\",\n \"CreateTime\": \"sample_string\",\n \"Encrypted\": false,\n \"FastRestored\": false,\n \"Iops\": 0,\n \"KmsKeyId\": \"sample_string\",\n \"MultiAttachEnabled\": false,\n \"OutpostArn\": \"sample_string\",\n \"Size\": 0,\n \"SnapshotId\": \"sample_string\",\n \"State\": \"sample_string\",\n \"Tags\": [\n {}\n ],\n \"Throughput\": 0,\n \"VolumeId\": \"sample_string\",\n \"VolumeType\": \"sample_string\"\n }\n ]\n }\n }\n]" +} +``` \ No newline at end of file diff --git a/cicd/python/record_parser/pyproject.toml b/cicd/python/record_parser/pyproject.toml new file mode 100644 index 0000000..b4a7af5 --- /dev/null +++ b/cicd/python/record_parser/pyproject.toml @@ -0,0 +1,12 @@ +[build-system] +requires = ["hatchling==1.27.0"] +build-backend = "hatchling.build" + +[project] +name = "record-parser" +version = "0.1.0" +requires-python = ">=3.11" +dependencies = [] + +[tool.hatch.build.targets.wheel] +packages = ["src/record_parser"] diff --git a/cicd/python/record_parser/src/record_parser/__init__.py b/cicd/python/record_parser/src/record_parser/__init__.py new file mode 100644 index 0000000..27d558c --- /dev/null +++ b/cicd/python/record_parser/src/record_parser/__init__.py @@ -0,0 +1,8 @@ +from .core import parse_record, generate_flask_app, generate_flask_apps_from_file, generate_mocks_from_analysis_run + +__all__ = [ + "parse_record", + "generate_flask_app", + "generate_flask_apps_from_file", + "generate_mocks_from_analysis_run", +] diff --git a/cicd/python/record_parser/src/record_parser/core.py b/cicd/python/record_parser/src/record_parser/core.py new file mode 100644 index 0000000..8bf8ac2 --- /dev/null +++ b/cicd/python/record_parser/src/record_parser/core.py @@ -0,0 +1,45 @@ +import json +import typing + +def parse_record(record: str) -> typing.Dict: + return json.loads(record) + +def generate_flask_app(parsed_record: typing.Dict) -> str: + app_code = f"""from flask import Flask, request, jsonify +app = Flask(__name__) + +{parsed_record['sample_response']['var_name']} = "{parsed_record['sample_response']['pre_transform']}" + +{parsed_record['mock_route']} + +""" + return app_code + + +def generate_flask_apps_from_file(file_path: str) -> typing.List[typing.Dict]: + apps = [] + with open(file_path, 'r') as f: + for line in f: + if line.strip(): # Skip empty lines + parsed_record = parse_record(line) + if not parsed_record.get('mock_route'): + continue # Skip records without a mock route + app_code = generate_flask_app(parsed_record) + apps.append({ + "parsed_record": parsed_record, + "app_code": app_code + }) + return apps + +def generate_mocks_from_analysis_run(src_dir: str, dest_dir: str): + import os + for filename in os.listdir(src_dir): + if filename.endswith('observations.jsonl'): + file_path = os.path.join(src_dir, filename) + apps = generate_flask_apps_from_file(file_path) + for app in apps: + mock_filename = f"{app['parsed_record']['provider']}_{app['parsed_record']['service']}_{app['parsed_record']['method']}_mock.py" + mock_file_path = os.path.join(dest_dir, mock_filename) + with open(mock_file_path, 'w') as f: + f.write(app['app_code']) + diff --git a/cicd/scripts/auto-mock/01-orchestrate-manual.sh b/cicd/scripts/auto-mock/01-orchestrate-manual.sh new file mode 100755 index 0000000..8b875b1 --- /dev/null +++ b/cicd/scripts/auto-mock/01-orchestrate-manual.sh @@ -0,0 +1,103 @@ +#!/usr/bin/env bash + +set -e + +provider="aws" +providerHandle="aws" +providerVersion="v26.02.00377" +service="ec2" +resource="volumes" +method="describe" +MOCK_PORT=5050 + +## Activate venv +if [ ! -d "mock.venv" ]; then + echo "Creating mock.venv..." + python3 -m venv mock.venv + mock.venv/bin/pip install -r cicd/mock-testing-requirements.txt +fi +source mock.venv/bin/activate + +## Generate artifacts (uncomment to regenerate) +# stackql exec "registry pull ${provider} ${providerVersion};" +# _now="$(date +%s)" && build/anysdk aot \ +# ./.stackql \ +# ./.stackql/src/${providerHandle}/${providerVersion}/provider.yaml \ +# -v \ +# --mock-output-dir "cicd/out/auto-mocks/${provider}" \ +# --mock-expectation-dir "cicd/out/mock-expectations/${provider}" \ +# --mock-query-dir "cicd/out/mock-queries/${provider}" \ +# --schema-dir cicd/schema-definitions \ +# --stdout-file "cicd/out/aot/${_now}-summary.json" \ +# --stderr-file "cicd/out/aot/${_now}-analysis.jsonl" + +## Generate closure with provider doc +build/anysdk closure \ + ./.stackql \ + ./.stackql/src/${providerHandle}/${providerVersion}/provider.yaml \ + "${service}" \ + --provider "${provider}" \ + --resource "${resource}" \ + --rewrite-url "http://localhost:${MOCK_PORT}" \ + --output-dir "cicd/out/closures/${provider}" + +## Resolve files +mock_file="cicd/out/auto-mocks/${provider}/mock_${provider}_${service}_${resource}_${method}.py" +query_file="cicd/out/mock-queries/${provider}/query_${provider}_${service}_${resource}_${method}.txt" +expect_file="cicd/out/mock-expectations/${provider}/expect_${provider}_${service}_${resource}_${method}.txt" +closure_dir="$(pwd)/cicd/out/closures/${provider}/${provider}_${service}_${resource}" + +query="$(cat "$query_file")" +expectation="" +[ -f "$expect_file" ] && [ -s "$expect_file" ] && expectation="$(cat "$expect_file")" + +echo "query: $query" +echo "expectation: ${expectation:-}" +echo "closure dir: $closure_dir" +echo "mock file: $(pwd)/$mock_file" + +## Kill any leftover mock on this port +lsof -ti ":${MOCK_PORT}" 2>/dev/null | xargs kill -9 2>/dev/null || true + +## Start mock +python3 "$mock_file" --port ${MOCK_PORT} & +MOCK_PID=$! +sleep 1 + +## Smoke test +echo "" +echo "=== Smoke test ===" +curl -s -X POST "http://localhost:${MOCK_PORT}/" -d "Action=DescribeVolumes" | head -100 +echo "" + +## Run StackQL +echo "" +echo "=== StackQL query ===" +response=$(AWS_SECRET_ACCESS_KEY=fake AWS_ACCESS_KEY_ID=fake stackql \ + --http.log.enabled \ + --tls.allowInsecure \ + --registry "{ \"url\": \"file://${closure_dir}\", \"localDocRoot\": \"${closure_dir}\", \"verifyConfig\": { \"nopVerify\": true } }" \ + exec "${query};" -o json 2>/tmp/stackql_stderr.txt) + +echo "response: $response" + +## Check result +http_status="$(grep 'http response status code:' /tmp/stackql_stderr.txt | head -1 | sed 's/.*status code: //' | sed 's/,.*//')" +echo "http status: ${http_status:-none}" + +if [ -n "$expectation" ]; then + if [ "$response" = "$expectation" ]; then + echo "RESULT: PASS (body match)" + else + echo "RESULT: FAIL (body mismatch)" + fi +elif [ "$http_status" = "200" ]; then + echo "RESULT: PASS (status 200)" +else + echo "RESULT: FAIL" + cat /tmp/stackql_stderr.txt | head -5 +fi + +## Cleanup +kill $MOCK_PID 2>/dev/null +wait $MOCK_PID 2>/dev/null diff --git a/cmd/argparse/aot.go b/cmd/argparse/aot.go index f895fe0..5d31dbf 100644 --- a/cmd/argparse/aot.go +++ b/cmd/argparse/aot.go @@ -140,6 +140,27 @@ func runAotCommand(rtCtx dto.RuntimeCtx, registryURL string, providerDoc string, // stdout: JSON summary fmt.Fprintln(os.Stdout, discovery.FormatSummaryJSON(allErrs, allWarnings, allAffirmatives, findings)) + // Optional: write individual Python mock files + if rtCtx.CLIMockOutputDir != "" && len(findings) > 0 { + if mockErr := discovery.WriteMockFiles(findings, rtCtx.CLIMockOutputDir); mockErr != nil { + fmt.Fprintf(os.Stderr, "warning: failed to write mock files: %v\n", mockErr) + } + } + + // Optional: write individual expected response files + if rtCtx.CLIMockExpectationDir != "" && len(findings) > 0 { + if expErr := discovery.WriteExpectationFiles(findings, rtCtx.CLIMockExpectationDir); expErr != nil { + fmt.Fprintf(os.Stderr, "warning: failed to write expectation files: %v\n", expErr) + } + } + + // Optional: write individual StackQL query files + if rtCtx.CLIMockQueryDir != "" && len(findings) > 0 { + if qErr := discovery.WriteQueryFiles(findings, rtCtx.CLIMockQueryDir); qErr != nil { + fmt.Fprintf(os.Stderr, "warning: failed to write query files: %v\n", qErr) + } + } + if analyisErr != nil { os.Exit(1) } diff --git a/cmd/argparse/argparse.go b/cmd/argparse/argparse.go index 6618bc1..703aba8 100644 --- a/cmd/argparse/argparse.go +++ b/cmd/argparse/argparse.go @@ -2,6 +2,8 @@ package argparse import ( "fmt" + "os" + "path/filepath" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -72,12 +74,19 @@ func init() { rootCmd.PersistentFlags().StringVar(&runtimeCtx.CLISchemaDir, "schema-dir", ``, "path to schema directory") rootCmd.PersistentFlags().BoolVar(&runtimeCtx.CLISkipSchemaValidation, "skip-schema-validation", false, "skip schema validation") rootCmd.PersistentFlags().BoolVarP(&runtimeCtx.VerboseFlag, dto.VerboseFlagKey, "v", false, "Verbose flag") + rootCmd.PersistentFlags().StringVar(&runtimeCtx.CLIMockOutputDir, "mock-output-dir", "", "output directory for individual Python mock files (aot only, empty to disable)") + rootCmd.PersistentFlags().StringVar(&runtimeCtx.CLIMockExpectationDir, "mock-expectation-dir", "", "output directory for expected response files (aot only, empty to disable)") + rootCmd.PersistentFlags().StringVar(&runtimeCtx.CLIMockQueryDir, "mock-query-dir", "", "output directory for StackQL query files (aot only, empty to disable)") + rootCmd.PersistentFlags().StringVar(&runtimeCtx.CLIStdoutFile, "stdout-file", "", "redirect stdout to this file (creates parent dirs, empty to use stdout)") + rootCmd.PersistentFlags().StringVar(&runtimeCtx.CLIStderrFile, "stderr-file", "", "redirect stderr to this file (creates parent dirs, empty to use stderr)") rootCmd.AddCommand(execCmd) rootCmd.AddCommand(constCmd) rootCmd.AddCommand(queryCmd) rootCmd.AddCommand(aotCmd) rootCmd.AddCommand(interrogateCmd) + rootCmd.AddCommand(closureCmd) + initClosureFlags() } @@ -93,6 +102,7 @@ func setLogLevel() { func initConfig() { setLogLevel() + setupOutputRedirects() viper.AutomaticEnv() // read in environment variables that match @@ -101,3 +111,30 @@ func initConfig() { fmt.Println("Using config file:", viper.ConfigFileUsed()) } } + +func setupOutputRedirects() { + if runtimeCtx.CLIStdoutFile != "" { + f, err := createFileWithDirs(runtimeCtx.CLIStdoutFile) + if err != nil { + log.Fatalf("cannot open stdout file: %v", err) + } + os.Stdout = f + } + if runtimeCtx.CLIStderrFile != "" { + f, err := createFileWithDirs(runtimeCtx.CLIStderrFile) + if err != nil { + log.Fatalf("cannot open stderr file: %v", err) + } + os.Stderr = f + } +} + +func createFileWithDirs(path string) (*os.File, error) { + dir := filepath.Dir(path) + if dir != "" && dir != "." { + if err := os.MkdirAll(dir, 0o755); err != nil { + return nil, err + } + } + return os.Create(path) +} diff --git a/cmd/argparse/closure.go b/cmd/argparse/closure.go new file mode 100644 index 0000000..7f7a970 --- /dev/null +++ b/cmd/argparse/closure.go @@ -0,0 +1,242 @@ +package argparse + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/spf13/cobra" + "gopkg.in/yaml.v3" + + "github.com/stackql/any-sdk/pkg/dto" + "github.com/stackql/any-sdk/public/closure" +) + +var closureCmd = &cobra.Command{ + Use: "closure ", + Short: "Generate a method closure for a StackQL resource", + Long: `Generate the minimal service document subset needed to action a specific resource. + +Usage: + closure --provider --resource [--rewrite-url ] + +The closure YAML is written to stdout.`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) < 3 { + cmd.Help() + os.Exit(0) + } + runClosureCommand(runtimeCtx, args[0], args[1], args[2]) + }, +} + +func initClosureFlags() { + closureCmd.Flags().StringVar(&runtimeCtx.CLIRewriteURL, "rewrite-url", "", "rewrite all server URLs to this base URL") + closureCmd.Flags().StringVar(&runtimeCtx.CLIProviderOut, "provider-out", "", "write a minimal provider YAML to this file (for use with the closure)") + closureCmd.Flags().StringVar(&runtimeCtx.CLIClosureOutputDir, "output-dir", "", "generate one closure per resource into this directory (each as a complete registry)") +} + +func runClosureCommand(rtCtx dto.RuntimeCtx, registryRoot string, providerDoc string, serviceName string) { + providerName := rtCtx.CLIProviderName + resourceName := rtCtx.CLIResourceStr + + if providerName == "" { + fmt.Fprintln(os.Stderr, "closure requires --provider flag") + os.Exit(1) + } + + // Read the provider doc to find the service $ref + serviceDocPath, err := resolveServiceDocPath(registryRoot, providerDoc, serviceName) + if err != nil { + fmt.Fprintf(os.Stderr, "failed to resolve service doc path: %v\n", err) + os.Exit(1) + } + + // Read raw service doc bytes + serviceDocBytes, err := os.ReadFile(serviceDocPath) + if err != nil { + fmt.Fprintf(os.Stderr, "failed to read service doc: %v\n", err) + os.Exit(1) + } + + // --output-dir mode: generate one closure per resource as a complete registry + if rtCtx.CLIClosureOutputDir != "" { + resourceNames, listErr := closure.ListResources(serviceDocBytes) + if listErr != nil { + fmt.Fprintf(os.Stderr, "failed to list resources: %v\n", listErr) + os.Exit(1) + } + provVersion := extractVersion(providerDoc) + provHandle := extractHandle(providerDoc) + for _, rsc := range resourceNames { + cfg := closure.ClosureConfig{ + ResourceName: rsc, + RewriteURL: rtCtx.CLIRewriteURL, + } + closureBytes, buildErr := closure.BuildClosure(serviceDocBytes, cfg) + if buildErr != nil { + fmt.Fprintf(os.Stderr, "warning: closure for %s/%s failed: %v\n", serviceName, rsc, buildErr) + continue + } + // Write closure as: output-dir/__/src///services/.yaml + registryBase := filepath.Join(rtCtx.CLIClosureOutputDir, + fmt.Sprintf("%s_%s_%s", providerName, serviceName, rsc), + "src", provHandle, provVersion) + svcDir := filepath.Join(registryBase, "services") + os.MkdirAll(svcDir, 0o755) + os.WriteFile(filepath.Join(svcDir, serviceName+".yaml"), closureBytes, 0o644) + + // Write provider doc + providerBytes, provErr := generateMinimalProviderDoc(providerDoc, providerName, serviceName, "") + if provErr == nil { + os.WriteFile(filepath.Join(registryBase, "provider.yaml"), providerBytes, 0o644) + } + } + return + } + + // Single resource mode: output closure to stdout + if resourceName == "" { + // No resource specified and no --output-dir: rewrite entire service doc + } + cfg := closure.ClosureConfig{ + ResourceName: resourceName, + RewriteURL: rtCtx.CLIRewriteURL, + } + + closureBytes, buildErr := closure.BuildClosure(serviceDocBytes, cfg) + if buildErr != nil { + fmt.Fprintf(os.Stderr, "failed to build closure: %v\n", buildErr) + os.Exit(1) + } + + os.Stdout.Write(closureBytes) + + // Optional: write a minimal provider doc alongside the closure + if rtCtx.CLIProviderOut != "" { + providerBytes, provErr := generateMinimalProviderDoc(providerDoc, providerName, serviceName, rtCtx.CLIProviderOut) + if provErr != nil { + fmt.Fprintf(os.Stderr, "warning: failed to generate provider doc: %v\n", provErr) + } else { + if wErr := os.MkdirAll(filepath.Dir(rtCtx.CLIProviderOut), 0o755); wErr != nil { + fmt.Fprintf(os.Stderr, "warning: failed to create provider output dir: %v\n", wErr) + } else if wErr := os.WriteFile(rtCtx.CLIProviderOut, providerBytes, 0o644); wErr != nil { + fmt.Fprintf(os.Stderr, "warning: failed to write provider doc: %v\n", wErr) + } + } + } +} + +// extractVersion extracts the version directory from a provider doc path like +// .stackql/src/aws/v26.02.00377/provider.yaml → v26.02.00377 +func extractVersion(providerDoc string) string { + dir := filepath.Dir(providerDoc) + return filepath.Base(dir) +} + +// extractHandle extracts the provider handle from a provider doc path like +// .stackql/src/aws/v26.02.00377/provider.yaml → aws +func extractHandle(providerDoc string) string { + dir := filepath.Dir(providerDoc) + return filepath.Base(filepath.Dir(dir)) +} + +// generateMinimalProviderDoc reads the original provider doc and produces a minimal +// version with only the specified service, referencing a closure service doc at the +// conventional path: //services/.yaml +func generateMinimalProviderDoc(originalProviderDoc string, providerName string, serviceName string, providerOutPath string) ([]byte, error) { + provBytes, err := os.ReadFile(originalProviderDoc) + if err != nil { + return nil, err + } + var prov map[string]interface{} + if err := yaml.Unmarshal(provBytes, &prov); err != nil { + return nil, err + } + + // Extract original service entry and auth config + services, _ := prov["providerServices"].(map[string]interface{}) + originalSvc, _ := services[serviceName].(map[string]interface{}) + + version, _ := prov["version"].(string) + if version == "" { + version = "v0.1.0" + } + + // Build the service ref path relative to the registry src/ dir + svcRefPath := fmt.Sprintf("%s/%s/services/%s.yaml", providerName, version, serviceName) + + svcEntry := map[string]interface{}{ + "id": fmt.Sprintf("%s:%s", serviceName, version), + "name": serviceName, + "preferred": true, + "service": map[string]interface{}{ + "$ref": svcRefPath, + }, + "title": serviceName, + "version": version, + } + // Preserve description from original if present + if originalSvc != nil { + if desc, ok := originalSvc["description"]; ok { + svcEntry["description"] = desc + } + if title, ok := originalSvc["title"]; ok { + svcEntry["title"] = title + } + } + + minimal := map[string]interface{}{ + "id": providerName, + "name": providerName, + "version": version, + "openapi": "3.0.0", + "providerServices": map[string]interface{}{ + serviceName: svcEntry, + }, + } + + // Copy auth config from original + if config, ok := prov["config"]; ok { + minimal["config"] = config + } + + return yaml.Marshal(minimal) +} + +// resolveServiceDocPath finds the service YAML file path by reading the +// provider doc and extracting the service.$ref for the given service name. +func resolveServiceDocPath(registryRoot string, providerDoc string, serviceName string) (string, error) { + provBytes, err := os.ReadFile(providerDoc) + if err != nil { + return "", fmt.Errorf("cannot read provider doc: %w", err) + } + + var prov map[string]interface{} + if err := yaml.Unmarshal(provBytes, &prov); err != nil { + return "", fmt.Errorf("cannot parse provider doc: %w", err) + } + + services, ok := prov["providerServices"].(map[string]interface{}) + if !ok { + return "", fmt.Errorf("provider doc has no providerServices") + } + + svc, ok := services[serviceName].(map[string]interface{}) + if !ok { + return "", fmt.Errorf("service '%s' not found in provider", serviceName) + } + + serviceRef, ok := svc["service"].(map[string]interface{}) + if !ok { + return "", fmt.Errorf("service '%s' has no service.$ref", serviceName) + } + + ref, ok := serviceRef["$ref"].(string) + if !ok { + return "", fmt.Errorf("service '%s' service.$ref is not a string", serviceName) + } + + // The ref is relative to the registry src/ directory + return filepath.Join(registryRoot, "src", ref), nil +} diff --git a/docs/automock_testing.md b/docs/automock_testing.md new file mode 100644 index 0000000..f6f61d7 --- /dev/null +++ b/docs/automock_testing.md @@ -0,0 +1,161 @@ + +# Automock testing + +The `anysdk` CLI is for testing purposes, so long as semver < 1. + + +## Setup + +You will need to install: + +- `stackql`, eg with `brew install stackql`. There are other alternatives on [the official install doco](https://stackql.io/docs/installing-stackql). +- `golang` >= `1.25.3`. +- `python3` >= `3.12`. + + +Once dependencies are in place, let us install the `anysdk` CLI. From the root of this repository: + +```bash +cicd/cli/build_cli.sh +``` + +Or if you want, cut out the middle man with `go build -o build/anysdk ./cmd/interrogate`. + +This creates an executable at the `.gitignore`d location `build/anysdk`. + +Set up the mock testing venv from the root of this repository: + +```bash +python3 -m venv mock.venv + +source mock.venv/bin/activate + +pip install -r cicd/mock-testing-requirements.txt + +``` + +Then, generate some auto mocks, for example `aws`, again from repository root: + +```bash + +stackql exec "registry pull aws v26.02.00377;" + +_now="$(date +%s)" && build/anysdk aot \ + ./.stackql \ + ./.stackql/src/aws/v26.02.00377/provider.yaml \ + -v \ + --mock-output-dir "cicd/out/auto-mocks/aws" \ + --mock-expectation-dir "cicd/out/mock-expectations/aws" \ + --mock-query-dir "cicd/out/mock-queries/aws" \ + --schema-dir \ + cicd/schema-definitions \ + --stdout-file "cicd/out/aot/${_now}-summary.json" \ + --stderr-file "cicd/out/aot/${_now}-analysis.jsonl" + +``` + +Pick a method, eg: `aws` `ec2` instances describe and generate the closure: + +```bash + +build/anysdk closure \ + ./.stackql \ + ./.stackql/src/aws/v26.02.00377/provider.yaml \ + ec2 \ + --provider aws \ + --resource instances \ + --rewrite-url http://localhost:5050 \ + > cicd/out/closures/closure_ec2_instances.yaml + +``` + +## Example run + +Let us perform an example run with reference material: + + +```bash + +source mock.venv/bin/activate + +# Start mock in background +python3 test/auto-mocks/reference/mock_aws_ec2_instances_describe.py --port 5050 & +MOCK_PID=$! +sleep 1 + +# Smoke test +curl -s -X POST http://localhost:5050/ -d "Action=DescribeInstances" + +# Run StackQL against the closure registry +response=$(AWS_SECRET_ACCESS_KEY=fake AWS_ACCESS_KEY_ID=fake stackql \ + --http.log.enabled \ + --tls.allowInsecure \ + --registry "{ \"url\": \"file://$(pwd)/test/auto-mocks/reference/registry\", \"localDocRoot\": \"$(pwd)/test/auto-mocks/reference/registry\", \"verifyConfig\": { \"nopVerify\": true } }" \ + exec "$(cat test/auto-mocks/reference/query_aws_ec2_instances_describe.txt);" -o json) + +echo "response: $response" + +if [ "$response" != "$(cat test/auto-mocks/reference/expect_aws_ec2_instances_describe.txt)" ]; then + echo "failed" +else + echo "success" +fi + +# Cleanup +kill $MOCK_PID 2>/dev/null + +``` + + +## Generalizing + +Let us perform a fully automated run: + + +```bash + +source mock.venv/bin/activate + +provider="aws" +providerHandle="aws" +providerVersion="v26.02.00377" +service="ec2" +resource="volumes" + +stackql exec "registry pull ${provider} ${providerVersion};" + +# Generate closure with provider doc +build/anysdk closure \ + ./.stackql \ + ./.stackql/src/${providerHandle}/${providerVersion}/provider.yaml \ + "${service}" \ + --provider "${provider}" \ + --resource "${resource}" \ + --rewrite-url http://localhost:5050 \ + --output-dir "cicd/out/closures/${provider}" + +closure_dir="cicd/out/closures/${provider}/${provider}_${service}_${resource}" +mock_file="cicd/out/auto-mocks/${provider}/mock_${provider}_${service}_${resource}_describe.py" +query="$(cat cicd/out/mock-queries/${provider}/query_${provider}_${service}_${resource}_describe.txt)" + +# Start mock +python3 "$mock_file" --port 5050 & +MOCK_PID=$! +sleep 1 + +# Smoke test +curl -s -X POST http://localhost:5050/ -d "Action=DescribeVolumes" + +# Run StackQL +response=$(AWS_SECRET_ACCESS_KEY=fake AWS_ACCESS_KEY_ID=fake stackql \ + --http.log.enabled \ + --tls.allowInsecure \ + --registry "{ \"url\": \"file://$(pwd)/${closure_dir}\", \"localDocRoot\": \"$(pwd)/${closure_dir}\", \"verifyConfig\": { \"nopVerify\": true } }" \ + exec "${query};" -o json) + +echo "response: $response" + +# Cleanup +kill $MOCK_PID 2>/dev/null + +``` diff --git a/docs/cli.md b/docs/cli.md index fc2b889..34ef3fe 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -178,8 +178,9 @@ _now="$(date +%s)" && build/anysdk aot \ ./.stackql \ ./.stackql/src/aws/v26.02.00377/provider.yaml \ -v \ + --mock-output-dir "cicd/out/auto-mocks/aws" \ --schema-dir \ - cicd/schema-definitions > "cicd/out/${_now}-summary.json" 2>"cicd/out/${_now}-analysis.jsonl" + cicd/schema-definitions > "cicd/out/aot/${_now}-summary.json" 2>"cicd/out/aot/${_now}-analysis.jsonl" ``` @@ -209,3 +210,99 @@ build/anysdk aot \ cicd/schema-definitions ``` + + +## Closure Generation + + +```bash + +build/anysdk closure \ + test/registry \ + test/registry/src/aws/v0.1.0/provider.yaml \ + ec2 \ + --provider aws \ + --resource volumes_post_naively_presented \ + --rewrite-url http://localhost:1091 \ + > cicd/out/aot/closure_ec2_volumes.yaml + + + +``` + + +## Auto-generated Flask mocks + +The AOT analysis produces structured findings that include `sample_response`, `mock_route`, and `stackql_query` attributes for each analyzed method. These can be composed into runnable Flask mock servers for end-to-end testing. + +### What the analysis emits per method + +Each finding with a response transform includes: + +| Field | Description | +|---|---| +| `sample_response.pre_transform` | Raw API response body (XML/JSON) derived from the provider's OpenAPI schema | +| `sample_response.post_transform` | Response after the provider's transform is applied | +| `mock_route` | Python string — a Flask route handler returning the mock response | +| `stackql_query` | The StackQL SQL query that exercises this endpoint | + +### Composing a mock server + +1. **Extract** — pull `mock_route` and `sample_response.pre_transform` from the JSONL analysis output +2. **Compose** — concatenate Flask boilerplate + all route strings with the mock response bodies injected: + +```python +from flask import Flask, request, Response + +app = Flask(__name__) + +# ... paste mock_route strings here, with sample_response.pre_transform as the body ... + +if __name__ == '__main__': + app.run(port=1091) +``` + +3. **Reroute** — use an existing registry rewrite or server override to point the provider's base URL at `localhost:` +4. **Test** — execute the `stackql_query` values against the rewritten registry: + +```bash +# start mock +python mock_aws_ec2.py & + +# run StackQL query from the analysis output +stackql exec \ + --registry='{"url": "file://./test/registry"}' \ + "SELECT * FROM aws.ec2.volumes_post_naively_presented WHERE region = 'us-east-1';" +``` + +This validates the full round-trip: StackQL sends a real request → Flask returns the schema-derived mock → the provider's response transform processes it → StackQL presents the result. + +## Leveraging CLI for mock testing + +For each closure, we can attach and instantiate corresponding method mocks from the appropriate generated mock file (when the parameter to persist these is populated with an output location). These mocks can be run in containers. Then we can test against these containers, verify, and terminate at the conclusion. + +How to do this? + +First, do somethig like this: + +```bash + +_now="$(date +%s)" && build/anysdk aot \ + ./.stackql \ + ./.stackql/src/aws/v26.02.00377/provider.yaml \ + -v \ + --mock-output-dir "cicd/out/auto-mocks/aws" \ + --schema-dir \ + cicd/schema-definitions > "cicd/out/aot/${_now}-summary.json" 2>"cicd/out/aot/${_now}-analysis.jsonl" + +``` + +Initial proposal is repository root level docker compose file: + +- Run +- Mounts python file or files in for example `cicd/out/auto-mocks/aws`. +- Run `stackql` against the complementary closure. +- Verify that the result is as expected. Confusingly, the expectation is still in a `jsonl` record. + + + diff --git a/docs/static_analysis.md b/docs/static_analysis.md new file mode 100644 index 0000000..10605e6 --- /dev/null +++ b/docs/static_analysis.md @@ -0,0 +1,28 @@ + +# Static Analysis + +This library contains normative static analysis tooling for provider documents. The cli exposes this at different granularities. + + +## Method closures + +For any collection of `stackql` methods (those referenced under `components.x-stackQL-resources..sqlVerbs` or implicityl defaulted to the "SQL" semantic `EXEC` under `components.x-stackQL-resources..methods`), there exists a `closure` object graph and corresponding document which is: + +- Necessary and sufficient to present and action these methods. +- A subset of the original document. + +There should exist a static analysis subcomponent that supports: + +- Derivation of closures for method collections. +- Serialization of closures into document form. +- Optional rewrite of aspects. + - Initially let us not be too sophisticated, scheme, host, port rewrite is the main initial use case. + - Some object that encapsulates rewriting semantics might be good so we can extend later. +- Also a capability for a workflow where whole documents or provider doc collections and ingested, rewritten and then serialized. +- This functionality should be available through the cli. + + + + + + diff --git a/pkg/dto/runtime_ctx.go b/pkg/dto/runtime_ctx.go index 391ecf4..5b3fb43 100644 --- a/pkg/dto/runtime_ctx.go +++ b/pkg/dto/runtime_ctx.go @@ -67,6 +67,14 @@ type RuntimeCtx struct { CLIMethodName string CLISchemaDir string CLISkipSchemaValidation bool + CLIRewriteURL string + CLIMockOutputDir string + CLIMockExpectationDir string + CLIMockQueryDir string + CLIProviderOut string + CLIClosureOutputDir string + CLIStdoutFile string + CLIStderrFile string } func setInt(iPtr *int, val string) error { diff --git a/public/closure/closure.go b/public/closure/closure.go new file mode 100644 index 0000000..06bb3bd --- /dev/null +++ b/public/closure/closure.go @@ -0,0 +1,323 @@ +package closure + +import ( + "fmt" + "strings" + + "gopkg.in/yaml.v3" +) + +// ClosureConfig configures the closure builder. +type ClosureConfig struct { + ResourceName string + RewriteURL string // optional: rewrite all server URLs to this base +} + +// BuildClosure produces the minimal service document YAML containing only +// the paths, schemas, parameters, and resource definitions needed for +// the specified resource. +func BuildClosure(serviceDocBytes []byte, cfg ClosureConfig) ([]byte, error) { + var doc map[string]interface{} + if err := yaml.Unmarshal(serviceDocBytes, &doc); err != nil { + return nil, fmt.Errorf("failed to parse service document: %w", err) + } + + // If no resource specified, rewrite the entire service doc (all resources) + if cfg.ResourceName == "" { + if cfg.RewriteURL != "" { + if servers, ok := doc["servers"]; ok { + if serverSlice, ok := servers.([]interface{}); ok { + doc["servers"] = RewriteServers(serverSlice, cfg.RewriteURL) + } + } + } + return yaml.Marshal(doc) + } + + // Locate target resource + resource, err := getResource(doc, cfg.ResourceName) + if err != nil { + return nil, err + } + + // Phase 1: Collect operation $refs from methods → path keys + pathKeys, methodHTTPMethods := collectPathKeysFromResource(resource) + + // Phase 2: Collect schema refs from methods (request/response schema_overrides) + schemaKeys := collectSchemaKeysFromResource(resource) + + // Phase 3: Collect schema + parameter refs from the referenced path operations + paramKeys := make(map[string]bool) + paths := getMap(doc, "paths") + for pathKey, httpMethod := range methodHTTPMethods { + pathItem := getMap(paths, pathKey) + if pathItem == nil { + continue + } + operation := getMap(pathItem, httpMethod) + if operation == nil { + continue + } + // Schemas from responses + for k := range collectSchemaKeys(operation) { + schemaKeys[k] = true + } + // Parameters from path item and operation + for k := range collectParameterKeys(pathItem) { + paramKeys[k] = true + } + for k := range collectParameterKeys(operation) { + paramKeys[k] = true + } + } + + // Phase 4: Transitively resolve schema refs + schemas := getMap(getMap(doc, "components"), "schemas") + if schemas != nil { + resolveTransitiveSchemas(schemas, schemaKeys) + } + + // Phase 5: Build closure document + out := buildClosureDoc(doc, cfg, resource, pathKeys, schemaKeys, paramKeys) + + return yaml.Marshal(out) +} + +func getResource(doc map[string]interface{}, name string) (map[string]interface{}, error) { + components := getMap(doc, "components") + if components == nil { + return nil, fmt.Errorf("service document has no components") + } + resources := getMap(components, "x-stackQL-resources") + if resources == nil { + return nil, fmt.Errorf("service document has no x-stackQL-resources") + } + resource := getMap(resources, name) + if resource == nil { + return nil, fmt.Errorf("resource '%s' not found in x-stackQL-resources", name) + } + return resource, nil +} + +func collectPathKeysFromResource(resource map[string]interface{}) (map[string]bool, map[string]string) { + pathKeys := make(map[string]bool) + methodHTTPMethods := make(map[string]string) // pathKey → httpMethod + + methods := getMap(resource, "methods") + if methods == nil { + return pathKeys, methodHTTPMethods + } + for _, methodDef := range methods { + m, ok := methodDef.(map[string]interface{}) + if !ok { + continue + } + op := getMap(m, "operation") + if op == nil { + continue + } + ref, ok := op["$ref"].(string) + if !ok { + continue + } + pathKey, httpMethod, ok := parseOperationRef(ref) + if ok { + pathKeys[pathKey] = true + methodHTTPMethods[pathKey] = httpMethod + } + } + return pathKeys, methodHTTPMethods +} + +func collectSchemaKeysFromResource(resource map[string]interface{}) map[string]bool { + keys := make(map[string]bool) + methods := getMap(resource, "methods") + if methods == nil { + return keys + } + for _, methodDef := range methods { + m, ok := methodDef.(map[string]interface{}) + if !ok { + continue + } + // response.schema_override.$ref + if resp := getMap(m, "response"); resp != nil { + if so := getMap(resp, "schema_override"); so != nil { + if ref, ok := so["$ref"].(string); ok { + if key, ok := parseComponentRef(ref, "schemas"); ok { + keys[key] = true + } + } + } + } + // request.schema_override.$ref + if req := getMap(m, "request"); req != nil { + if so := getMap(req, "schema_override"); so != nil { + if ref, ok := so["$ref"].(string); ok { + if key, ok := parseComponentRef(ref, "schemas"); ok { + keys[key] = true + } + } + } + } + } + return keys +} + +// resolveTransitiveSchemas expands the schemaKeys set by walking each collected +// schema for nested $ref values, iterating until stable. +func resolveTransitiveSchemas(schemas map[string]interface{}, schemaKeys map[string]bool) { + for { + added := false + for key := range schemaKeys { + schema, ok := schemas[key] + if !ok { + continue + } + for nested := range collectSchemaKeys(schema) { + if !schemaKeys[nested] { + schemaKeys[nested] = true + added = true + } + } + } + if !added { + break + } + } +} + +func buildClosureDoc( + doc map[string]interface{}, + cfg ClosureConfig, + resource map[string]interface{}, + pathKeys map[string]bool, + schemaKeys map[string]bool, + paramKeys map[string]bool, +) map[string]interface{} { + out := make(map[string]interface{}) + + // Copy top-level fields verbatim + for _, key := range []string{"openapi", "info", "security", "externalDocs", "x-hasEquivalentPaths"} { + if v, ok := doc[key]; ok { + out[key] = v + } + } + // Copy any remaining top-level x- keys + for k, v := range doc { + if strings.HasPrefix(k, "x-") { + out[k] = v + } + } + + // Servers — with optional rewrite + if servers, ok := doc["servers"]; ok { + if serverSlice, ok := servers.([]interface{}); ok && cfg.RewriteURL != "" { + out["servers"] = RewriteServers(serverSlice, cfg.RewriteURL) + } else { + out["servers"] = servers + } + } + + // Paths — only referenced paths + srcPaths := getMap(doc, "paths") + if srcPaths != nil { + filteredPaths := make(map[string]interface{}) + for key := range pathKeys { + if v, ok := srcPaths[key]; ok { + filteredPaths[key] = v + } + } + out["paths"] = filteredPaths + } + + // Components + srcComponents := getMap(doc, "components") + if srcComponents == nil { + return out + } + components := make(map[string]interface{}) + + // x-stackQL-resources — only the target resource + components["x-stackQL-resources"] = map[string]interface{}{ + cfg.ResourceName: resource, + } + + // schemas — only referenced + if srcSchemas := getMap(srcComponents, "schemas"); srcSchemas != nil { + filtered := make(map[string]interface{}) + for key := range schemaKeys { + if v, ok := srcSchemas[key]; ok { + filtered[key] = v + } + } + if len(filtered) > 0 { + components["schemas"] = filtered + } + } + + // parameters — only referenced + if srcParams := getMap(srcComponents, "parameters"); srcParams != nil { + filtered := make(map[string]interface{}) + for key := range paramKeys { + if v, ok := srcParams[key]; ok { + filtered[key] = v + } + } + if len(filtered) > 0 { + components["parameters"] = filtered + } + } + + // securitySchemes — copy if present + if ss, ok := srcComponents["securitySchemes"]; ok { + components["securitySchemes"] = ss + } + + // Copy all x- extensions from components (e.g., x-cloud-control-schemas) + for k, v := range srcComponents { + if strings.HasPrefix(k, "x-") && k != "x-stackQL-resources" { + components[k] = v + } + } + + out["components"] = components + return out +} + +// ListResources returns all resource names from the x-stackQL-resources section. +func ListResources(serviceDocBytes []byte) ([]string, error) { + var doc map[string]interface{} + if err := yaml.Unmarshal(serviceDocBytes, &doc); err != nil { + return nil, err + } + components := getMap(doc, "components") + if components == nil { + return nil, nil + } + resources := getMap(components, "x-stackQL-resources") + if resources == nil { + return nil, nil + } + names := make([]string, 0, len(resources)) + for k := range resources { + names = append(names, k) + } + return names, nil +} + +// getMap safely navigates to a nested map key. +func getMap(m map[string]interface{}, key string) map[string]interface{} { + if m == nil { + return nil + } + v, ok := m[key] + if !ok { + return nil + } + result, ok := v.(map[string]interface{}) + if !ok { + return nil + } + return result +} diff --git a/public/closure/refs.go b/public/closure/refs.go new file mode 100644 index 0000000..b86070b --- /dev/null +++ b/public/closure/refs.go @@ -0,0 +1,93 @@ +package closure + +import ( + "strings" +) + +// unescapeJSONPointer converts JSON Pointer escapes: ~1 → /, ~0 → ~ +func unescapeJSONPointer(s string) string { + s = strings.ReplaceAll(s, "~1", "/") + s = strings.ReplaceAll(s, "~0", "~") + return s +} + +// parseOperationRef parses an operation $ref like: +// +// #/paths/~1?__Action=DescribeVolumes&__Version=2016-11-15/post +// +// Returns the path key (unescaped) and HTTP method. +func parseOperationRef(ref string) (pathKey string, httpMethod string, ok bool) { + ref = strings.TrimPrefix(ref, "#/paths/") + if ref == "" { + return "", "", false + } + // The last segment after the final / is the HTTP method + lastSlash := strings.LastIndex(ref, "/") + if lastSlash < 0 { + return "", "", false + } + pathEncoded := ref[:lastSlash] + httpMethod = ref[lastSlash+1:] + pathKey = unescapeJSONPointer(pathEncoded) + return pathKey, strings.ToLower(httpMethod), true +} + +// parseComponentRef extracts the key from a $ref like #/components/schemas/Foo. +// The component parameter is the component type (e.g., "schemas", "parameters"). +func parseComponentRef(ref string, component string) (string, bool) { + prefix := "#/components/" + component + "/" + if !strings.HasPrefix(ref, prefix) { + return "", false + } + return ref[len(prefix):], true +} + +// collectAllRefs recursively walks a raw YAML node and returns all $ref string values. +func collectAllRefs(node interface{}) []string { + var refs []string + walkRefs(node, func(ref string) { + refs = append(refs, ref) + }) + return refs +} + +func walkRefs(node interface{}, fn func(string)) { + switch v := node.(type) { + case map[string]interface{}: + if ref, ok := v["$ref"]; ok { + if s, ok := ref.(string); ok { + fn(s) + } + } + for _, val := range v { + walkRefs(val, fn) + } + case []interface{}: + for _, item := range v { + walkRefs(item, fn) + } + } +} + +// collectSchemaKeys extracts all #/components/schemas/X references from a node, +// returning just the schema key names. +func collectSchemaKeys(node interface{}) map[string]bool { + keys := make(map[string]bool) + for _, ref := range collectAllRefs(node) { + if key, ok := parseComponentRef(ref, "schemas"); ok { + keys[key] = true + } + } + return keys +} + +// collectParameterKeys extracts all #/components/parameters/X references from a node. +func collectParameterKeys(node interface{}) map[string]bool { + keys := make(map[string]bool) + for _, ref := range collectAllRefs(node) { + if key, ok := parseComponentRef(ref, "parameters"); ok { + keys[key] = true + } + } + return keys +} diff --git a/public/closure/rewrite.go b/public/closure/rewrite.go new file mode 100644 index 0000000..1846e26 --- /dev/null +++ b/public/closure/rewrite.go @@ -0,0 +1,40 @@ +package closure + +import ( + "net/url" +) + +// RewriteServers replaces the URL in each server entry with rewriteURL, +// preserving template variables (e.g., {region}) if they appear in the path. +func RewriteServers(servers []interface{}, rewriteURL string) []interface{} { + if rewriteURL == "" { + return servers + } + target, err := url.Parse(rewriteURL) + if err != nil { + return servers + } + for _, s := range servers { + m, ok := s.(map[string]interface{}) + if !ok { + continue + } + rawURL, ok := m["url"].(string) + if !ok { + continue + } + // Parse the original to preserve any path suffix + orig, err := url.Parse(rawURL) + if err != nil { + m["url"] = rewriteURL + continue + } + // Replace scheme + host, keep path if target has no path + result := *target + if result.Path == "" || result.Path == "/" { + result.Path = orig.Path + } + m["url"] = result.String() + } + return servers +} diff --git a/public/discovery/analysis_classification.go b/public/discovery/analysis_classification.go index 9864f91..468ff18 100644 --- a/public/discovery/analysis_classification.go +++ b/public/discovery/analysis_classification.go @@ -21,11 +21,24 @@ func classifiedWarning(bin string, format string, args ...interface{}) string { return fmt.Sprintf("[%s] %s", bin, fmt.Sprintf(format, args...)) } +// ScoreMetrics provides aggregate pass rates and health scores. +type ScoreMetrics struct { + TotalMethods int `json:"total_methods"` + MethodsWithMocks int `json:"methods_with_mocks"` + MethodsWithTransforms int `json:"methods_with_transforms"` + MethodsClean int `json:"methods_clean"` + ErrorRate float64 `json:"error_rate"` + WarningRate float64 `json:"warning_rate"` + CleanRate float64 `json:"clean_rate"` + MockCoverage float64 `json:"mock_coverage"` +} + // AnalysisSummary is the JSON-serialisable top-level output of static analysis. type AnalysisSummary struct { TotalOK int `json:"total_ok"` TotalWarnings int `json:"total_warnings"` TotalErrors int `json:"total_errors"` + Scores *ScoreMetrics `json:"scores,omitempty"` Bins map[string]AnalysisBin `json:"bins"` Services map[string]ServiceSummary `json:"services"` Errors []string `json:"errors,omitempty"` @@ -33,9 +46,9 @@ type AnalysisSummary struct { // AnalysisBin holds the items for a single classification bin. type AnalysisBin struct { - Count int `json:"count"` - Errors []AnalysisFinding `json:"errors,omitempty"` - Warnings []AnalysisFinding `json:"warnings,omitempty"` + Count int `json:"count"` + Errors []string `json:"errors,omitempty"` + Warnings []string `json:"warnings,omitempty"` } // ServiceSummary aggregates error and warning counts per service. @@ -60,11 +73,15 @@ func FormatSummaryJSON(legacyErrors []error, legacyWarnings []string, affirmativ } ab := summary.Bins[bin] ab.Count++ + resourceRef := f.Resource + if f.Service != "" { + resourceRef = f.Service + "." + f.Resource + } if f.Level == "error" { - ab.Errors = append(ab.Errors, f) + ab.Errors = append(ab.Errors, resourceRef) summary.TotalErrors++ } else { - ab.Warnings = append(ab.Warnings, f) + ab.Warnings = append(ab.Warnings, resourceRef) summary.TotalWarnings++ } summary.Bins[bin] = ab @@ -80,6 +97,54 @@ func FormatSummaryJSON(legacyErrors []error, legacyWarnings []string, affirmativ } } + // Compute score metrics from findings + methodSet := make(map[string]bool) // all methods seen + methodHasMock := make(map[string]bool) // methods with sample_response + methodHasTransform := make(map[string]bool) // methods with prior_template (has transform) + methodHasIssue := make(map[string]bool) // methods with errors or warnings + for _, f := range findings { + mk := f.Provider + "." + f.Service + "." + f.Resource + "." + f.Method + methodSet[mk] = true + if f.SampleResponse != nil { + methodHasMock[mk] = true + } + if f.PriorTemplate != "" { + methodHasTransform[mk] = true + } + methodHasIssue[mk] = true + } + // Methods from affirmatives that had no findings are clean + totalMethods := len(methodSet) + if totalMethods == 0 { + totalMethods = summary.TotalOK // fallback to affirmative count + } + methodsClean := 0 + for mk := range methodHasMock { + if !methodHasIssue[mk] { + methodsClean++ + } + } + // For methods that only appear in affirmatives (no findings), count as clean + cleanFromAffirmatives := summary.TotalOK + if len(methodSet) > 0 { + cleanFromAffirmatives = 0 + } + totalForRate := totalMethods + if totalForRate == 0 { + totalForRate = 1 + } + scores := &ScoreMetrics{ + TotalMethods: totalMethods, + MethodsWithMocks: len(methodHasMock), + MethodsWithTransforms: len(methodHasTransform), + MethodsClean: methodsClean + cleanFromAffirmatives, + ErrorRate: float64(summary.TotalErrors) / float64(totalForRate), + WarningRate: float64(summary.TotalWarnings) / float64(totalForRate), + MockCoverage: float64(len(methodHasMock)) / float64(totalForRate), + } + scores.CleanRate = float64(scores.MethodsClean) / float64(totalForRate) + summary.Scores = scores + // Include legacy errors that aren't in findings (infrastructure errors) for _, e := range legacyErrors { bin, msg := parseWarningBin(e.Error()) diff --git a/public/discovery/analysis_finding.go b/public/discovery/analysis_finding.go index 2de4aae..0bce129 100644 --- a/public/discovery/analysis_finding.go +++ b/public/discovery/analysis_finding.go @@ -20,6 +20,9 @@ type AnalysisFinding struct { FixedTemplate string `json:"fixed_template,omitempty"` EmpiricalTests *stream_transform.EmpiricalTestSuite `json:"empirical_tests,omitempty"` SampleResponse *SampleResponsePair `json:"sample_response,omitempty"` + MockRoute string `json:"mock_route,omitempty"` + StackQLQuery string `json:"stackql_query,omitempty"` + ExpectedResponse string `json:"expected_response,omitempty"` } func (f AnalysisFinding) Error() string { diff --git a/public/discovery/method_analysis_checks.go b/public/discovery/method_analysis_checks.go new file mode 100644 index 0000000..30e9674 --- /dev/null +++ b/public/discovery/method_analysis_checks.go @@ -0,0 +1,263 @@ +package discovery + +import ( + "fmt" + "net/url" + "strings" + + "github.com/stackql/any-sdk/internal/anysdk" +) + +// Analysis bins for new static checks. +const ( + BinRequestParamUnroutable = "request-param-unroutable" + BinRefResolutionFailed = "ref-resolution-failed" + BinSQLVerbCoverage = "sql-verb-coverage" + BinServerURLInvalid = "server-url-invalid" + BinPaginationIncomplete = "pagination-incomplete" + BinTransformSchemaMismatch = "transform-schema-mismatch" +) + +// checkRequestParamRoutability validates that required parameters have +// valid locations and can be routed to the HTTP request. +func checkRequestParamRoutability( + actx AnalysisContext, + method anysdk.StandardOperationStore, +) []AnalysisFinding { + var findings []AnalysisFinding + params := method.GetRequiredParameters() + for key, param := range params { + if param == nil { + findings = append(findings, actx.NewWarning(BinRequestParamUnroutable, + fmt.Sprintf("required parameter '%s' is nil", key))) + continue + } + loc := param.GetLocation() + if loc == "" { + findings = append(findings, actx.NewWarning(BinRequestParamUnroutable, + fmt.Sprintf("required parameter '%s' has no location", key))) + } + } + return findings +} + +// checkRefResolution validates that the operation ref resolves to a non-nil operation. +func checkRefResolution( + actx AnalysisContext, + method anysdk.StandardOperationStore, +) []AnalysisFinding { + var findings []AnalysisFinding + opRef := method.GetOperationRef() + if opRef == nil { + findings = append(findings, actx.NewError(BinRefResolutionFailed, + "operation ref is nil")) + return findings + } + if opRef.Ref == "" && len(opRef.GetInline()) == 0 { + findings = append(findings, actx.NewError(BinRefResolutionFailed, + "operation ref has no $ref and no inline")) + } + // Check response schema ref resolves + resp, hasResp := method.GetResponse() + if hasResp { + schema := resp.GetSchema() + rawSchema := resp.GetRawSchema() + if schema == nil && rawSchema == nil { + findings = append(findings, actx.NewWarning(BinRefResolutionFailed, + "response has no resolved schema (raw or override)")) + } + } + return findings +} + +// checkSQLVerbCoverage validates that a resource has at least a SELECT method +// and that non-SELECT resources have appropriate parameters. +func checkSQLVerbCoverage( + actx AnalysisContext, + resource anysdk.Resource, +) []AnalysisFinding { + var findings []AnalysisFinding + methods := resource.GetMethods() + if len(methods) == 0 { + findings = append(findings, AnalysisFinding{ + Level: "warning", + Bin: BinSQLVerbCoverage, + Provider: actx.Provider, + Service: actx.Service, + Resource: actx.Resource, + Message: "resource has no methods", + }) + return findings + } + + hasSelect := false + for _, m := range methods { + if strings.ToLower(m.GetSQLVerb()) == "select" { + hasSelect = true + break + } + } + if !hasSelect { + findings = append(findings, AnalysisFinding{ + Level: "warning", + Bin: BinSQLVerbCoverage, + Provider: actx.Provider, + Service: actx.Service, + Resource: actx.Resource, + Message: "resource has no SELECT method", + }) + } + return findings +} + +// checkServerURLValidity validates server URL templates are well-formed +// and that template variables have defaults. +func checkServerURLValidity( + actx AnalysisContext, + method anysdk.StandardOperationStore, +) []AnalysisFinding { + var findings []AnalysisFinding + servers, hasServers := method.GetServers() + if !hasServers || len(servers) == 0 { + findings = append(findings, actx.NewWarning(BinServerURLInvalid, + "method has no server definitions")) + return findings + } + for _, srv := range servers { + if srv == nil || srv.URL == "" { + findings = append(findings, actx.NewWarning(BinServerURLInvalid, + "server entry has empty URL")) + continue + } + // Check URL is parseable (ignoring template vars) + testURL := srv.URL + // Replace {var} with placeholder for URL parsing + for varName := range srv.Variables { + testURL = strings.ReplaceAll(testURL, "{"+varName+"}", "placeholder") + } + if _, err := url.Parse(testURL); err != nil { + findings = append(findings, actx.NewWarning(BinServerURLInvalid, + fmt.Sprintf("server URL '%s' is not valid: %v", srv.URL, err))) + } + // Check template variables have defaults + for varName, varDef := range srv.Variables { + if varDef == nil { + findings = append(findings, actx.NewWarning(BinServerURLInvalid, + fmt.Sprintf("server variable '%s' has nil definition", varName))) + continue + } + if varDef.Default == "" { + findings = append(findings, actx.NewWarning(BinServerURLInvalid, + fmt.Sprintf("server variable '%s' has no default value", varName))) + } + } + } + return findings +} + +// checkPaginationCompleteness validates that pagination config has both +// request and response token semantics with valid keys. +func checkPaginationCompleteness( + actx AnalysisContext, + method anysdk.StandardOperationStore, +) []AnalysisFinding { + var findings []AnalysisFinding + reqToken, hasReqToken := method.GetPaginationRequestTokenSemantic() + respToken, hasRespToken := method.GetPaginationResponseTokenSemantic() + + // Only check if at least one token is configured (method claims to support pagination) + if !hasReqToken && !hasRespToken { + return nil + } + + if hasReqToken && !hasRespToken { + findings = append(findings, actx.NewWarning(BinPaginationIncomplete, + "pagination has request token but no response token")) + } + if hasRespToken && !hasReqToken { + findings = append(findings, actx.NewWarning(BinPaginationIncomplete, + "pagination has response token but no request token")) + } + if hasReqToken && reqToken.GetKey() == "" { + findings = append(findings, actx.NewWarning(BinPaginationIncomplete, + "pagination request token has empty key")) + } + if hasRespToken && respToken.GetKey() == "" { + findings = append(findings, actx.NewWarning(BinPaginationIncomplete, + "pagination response token has empty key")) + } + return findings +} + +// BinCrossResourceInconsistency is the bin for cross-resource consistency issues. +const BinCrossResourceInconsistency = "cross-resource-inconsistent" + +// checkCrossResourceConsistency checks that _list_only resources have a +// corresponding parent resource in the same service. +func checkCrossResourceConsistency( + actx AnalysisContext, + resourceKey string, + allResources map[string]anysdk.Resource, +) []AnalysisFinding { + var findings []AnalysisFinding + if !strings.HasSuffix(resourceKey, "_list_only") { + return nil + } + parentKey := strings.TrimSuffix(resourceKey, "_list_only") + if _, ok := allResources[parentKey]; !ok { + findings = append(findings, AnalysisFinding{ + Level: "warning", + Bin: BinCrossResourceInconsistency, + Provider: actx.Provider, + Service: actx.Service, + Resource: actx.Resource, + Message: fmt.Sprintf("list_only resource '%s' has no corresponding parent resource '%s'", resourceKey, parentKey), + }) + } + return findings +} + +// checkTransformSchemaConsistency validates that when a response has both +// a transform and a schema_override, the transform output type is consistent +// with the override media type. +func checkTransformSchemaConsistency( + actx AnalysisContext, + method anysdk.StandardOperationStore, +) []AnalysisFinding { + var findings []AnalysisFinding + resp, hasResp := method.GetResponse() + if !hasResp { + return nil + } + transform, hasTransform := resp.GetTransform() + if !hasTransform { + return nil + } + overrideMediaType := resp.GetOverrrideBodyMediaType() + rawMediaType := resp.GetBodyMediaType() + + // If there's a transform and override media type, check consistency + if overrideMediaType != "" && rawMediaType != "" && overrideMediaType != rawMediaType { + // Transform changes media type — check the transform type matches the output + tplType := transform.GetType() + if strings.Contains(tplType, "mxj") && !strings.Contains(overrideMediaType, "json") { + findings = append(findings, actx.NewWarning(BinTransformSchemaMismatch, + fmt.Sprintf("MXJ transform type '%s' but override media type is '%s' (expected application/json)", tplType, overrideMediaType))) + } + } + + // If there's a schema_override, check the override schema is non-nil + overrideSchema := resp.GetSchema() + if overrideMediaType != "" && overrideSchema == nil { + findings = append(findings, actx.NewWarning(BinTransformSchemaMismatch, + "response has override media type but no resolved override schema")) + } + + // Check transform body is non-empty + if transform.GetBody() == "" { + findings = append(findings, actx.NewWarning(BinTransformSchemaMismatch, + "response transform has empty body")) + } + + return findings +} diff --git a/public/discovery/mock_file_writer.go b/public/discovery/mock_file_writer.go new file mode 100644 index 0000000..25e4f92 --- /dev/null +++ b/public/discovery/mock_file_writer.go @@ -0,0 +1,119 @@ +package discovery + +import ( + "fmt" + "os" + "path/filepath" + "strings" +) + +// WriteMockFiles writes individual Python mock files for each finding +// that has a mock_route and sample_response. Each file is a standalone +// Flask app that can be run directly. +// WriteMockFiles writes individual Python mock files and a manifest mapping +// each filename to its provider/service/resource/method components. +func WriteMockFiles(findings []AnalysisFinding, outputDir string) error { + if outputDir == "" { + return nil + } + if err := os.MkdirAll(outputDir, 0o755); err != nil { + return fmt.Errorf("failed to create mock output dir: %w", err) + } + var manifest strings.Builder + for _, f := range findings { + if f.MockRoute == "" || f.SampleResponse == nil || f.SampleResponse.PreTransform == "" { + continue + } + filename := mockFileName(f.Provider, f.Service, f.Resource, f.Method) + path := filepath.Join(outputDir, filename) + content := buildMockPythonFile(f) + if err := os.WriteFile(path, []byte(content), 0o644); err != nil { + return fmt.Errorf("failed to write mock file %s: %w", path, err) + } + manifest.WriteString(fmt.Sprintf("%s|%s|%s|%s|%s\n", filename, f.Provider, f.Service, f.Resource, f.Method)) + } + if manifest.Len() > 0 { + if err := os.WriteFile(filepath.Join(outputDir, "manifest.txt"), []byte(manifest.String()), 0o644); err != nil { + return fmt.Errorf("failed to write manifest: %w", err) + } + } + return nil +} + +func mockFileName(provider, service, resource, method string) string { + base := fmt.Sprintf("mock_%s_%s_%s_%s", provider, service, resource, method) + base = strings.ReplaceAll(base, ".", "_") + base = strings.ReplaceAll(base, "-", "_") + return strings.ToLower(base) + ".py" +} + +// WriteExpectationFiles writes individual expected response files for each finding +// that has an expected_response. Each file is a plain text file containing the +// expected JSON output from `stackql exec -o json`. +func WriteExpectationFiles(findings []AnalysisFinding, outputDir string) error { + if outputDir == "" { + return nil + } + if err := os.MkdirAll(outputDir, 0o755); err != nil { + return fmt.Errorf("failed to create expectation output dir: %w", err) + } + for _, f := range findings { + if f.ExpectedResponse == "" { + continue + } + base := fmt.Sprintf("expect_%s_%s_%s_%s", f.Provider, f.Service, f.Resource, f.Method) + base = strings.ReplaceAll(base, ".", "_") + base = strings.ReplaceAll(base, "-", "_") + filename := strings.ToLower(base) + ".txt" + path := filepath.Join(outputDir, filename) + if err := os.WriteFile(path, []byte(f.ExpectedResponse), 0o644); err != nil { + return fmt.Errorf("failed to write expectation file %s: %w", path, err) + } + } + return nil +} + +// WriteQueryFiles writes individual StackQL query files for each finding +// that has a stackql_query. Each file is a plain text file containing the query. +func WriteQueryFiles(findings []AnalysisFinding, outputDir string) error { + if outputDir == "" { + return nil + } + if err := os.MkdirAll(outputDir, 0o755); err != nil { + return fmt.Errorf("failed to create query output dir: %w", err) + } + for _, f := range findings { + if f.StackQLQuery == "" { + continue + } + base := fmt.Sprintf("query_%s_%s_%s_%s", f.Provider, f.Service, f.Resource, f.Method) + base = strings.ReplaceAll(base, ".", "_") + base = strings.ReplaceAll(base, "-", "_") + filename := strings.ToLower(base) + ".txt" + path := filepath.Join(outputDir, filename) + if err := os.WriteFile(path, []byte(f.StackQLQuery), 0o644); err != nil { + return fmt.Errorf("failed to write query file %s: %w", path, err) + } + } + return nil +} + +func buildMockPythonFile(f AnalysisFinding) string { + varName := f.SampleResponse.VarName + if varName == "" { + varName = MockResponseVarName(f.Provider, f.Service, f.Resource, f.Method) + } + + // Escape the response body for Python triple-quoted string + body := strings.ReplaceAll(f.SampleResponse.PreTransform, `\`, `\\`) + body = strings.ReplaceAll(body, `"""`, `\"\"\"`) + + var sb strings.Builder + sb.WriteString("from flask import Flask, request, Response\n\n") + sb.WriteString("app = Flask(__name__)\n\n") + sb.WriteString(fmt.Sprintf("%s = \"\"\"%s\"\"\"\n\n", varName, body)) + sb.WriteString(f.MockRoute) + sb.WriteString("\n\n\nif __name__ == '__main__':\n import argparse as ap\n p = ap.ArgumentParser()\n p.add_argument('--port', type=int, default=5000)\n app.run(host='0.0.0.0', port=p.parse_args().port)\n") + + return sb.String() +} diff --git a/public/discovery/mock_route_generator.go b/public/discovery/mock_route_generator.go new file mode 100644 index 0000000..b455cac --- /dev/null +++ b/public/discovery/mock_route_generator.go @@ -0,0 +1,342 @@ +package discovery + +import ( + "encoding/json" + "fmt" + "regexp" + "sort" + "strings" + + "github.com/stackql/any-sdk/internal/anysdk" +) + +var openAPIParamPattern = regexp.MustCompile(`\{([^}]+)\}`) + +// GenerateMockRoute produces a Python Flask route handler string for a given method. +// The returned string is a complete route decorator + function that returns a stub response placeholder. +// resolveHTTPVerb returns the HTTP verb, falling back to parsing it from +// the method name prefix (e.g., "POST_DescribeVolumes" → "POST"). +func resolveHTTPVerb(httpVerb string, methodName string) string { + if httpVerb != "" { + return strings.ToUpper(httpVerb) + } + // Method names like POST_DescribeVolumes, GET_List encode the verb as prefix + if idx := strings.Index(methodName, "_"); idx > 0 { + candidate := strings.ToUpper(methodName[:idx]) + switch candidate { + case "GET", "POST", "PUT", "DELETE", "PATCH": + return candidate + } + } + return "GET" +} + +func GenerateMockRoute( + providerName string, + serviceName string, + resourceName string, + methodName string, + httpVerb string, + operationName string, + parameterizedPath string, + responseMediaType string, + requiredParams map[string]anysdk.Addressable, +) string { + funcName := sanitizePythonName(fmt.Sprintf("%s_%s_%s_%s", providerName, serviceName, resourceName, methodName)) + httpVerb = resolveHTTPVerb(httpVerb, operationName) + if responseMediaType == "" { + responseMediaType = "application/json" + } + + // Action query style: POST to root with Action discrimination + if isActionQueryStyle(parameterizedPath) { + action := deriveAction(operationName, parameterizedPath) + return fmt.Sprintf( + "@app.route('/', methods=['POST'])\n"+ + "def %s():\n"+ + " body = request.get_data(as_text=True)\n"+ + " if 'Action=%s' in body or request.form.get('Action') == '%s':\n"+ + " return Response(MOCK_RESPONSE_%s, content_type='%s')\n"+ + " return Response('Action not matched', status=404)", + funcName, action, action, strings.ToUpper(funcName), responseMediaType) + } + + // REST pattern: unique path with parameterized segments + flaskPath := openAPIParamPattern.ReplaceAllString(parameterizedPath, "<$1>") + if flaskPath == "" { + flaskPath = "/" + } + return fmt.Sprintf( + "@app.route('%s', methods=['%s'])\n"+ + "def %s():\n"+ + " return Response(MOCK_RESPONSE_%s, content_type='%s')", + flaskPath, httpVerb, funcName, strings.ToUpper(funcName), responseMediaType) +} + +// GenerateStackQLQuery produces a StackQL SQL query that exercises the given method. +// It partitions parameters by location: server/path/query params go in WHERE, +// requestBody params go in column/value lists for INSERT or SET for UPDATE. +func GenerateStackQLQuery( + providerName string, + serviceName string, + resourceName string, + sqlVerb string, + requiredParams map[string]anysdk.Addressable, + requestBodyAttrs map[string]anysdk.Addressable, +) string { + fqResource := fmt.Sprintf("%s.%s.%s", providerName, serviceName, resourceName) + sqlVerb = strings.ToLower(sqlVerb) + + // WHERE params = required non-body params + whereParams := make(map[string]anysdk.Addressable) + for k, p := range requiredParams { + loc := "" + if p != nil { + loc = p.GetLocation() + } + if loc != "requestBody" && loc != "body" { + whereParams[k] = p + } + } + // Body params = explicit request body attributes (preferred) or body-located required params + bodyParams := make(map[string]anysdk.Addressable) + if len(requestBodyAttrs) > 0 { + for k, p := range requestBodyAttrs { + bodyParams[k] = p + } + } else { + for k, p := range requiredParams { + if p != nil && (p.GetLocation() == "requestBody" || p.GetLocation() == "body") { + bodyParams[k] = p + } + } + } + + whereClause := buildWhereClause(whereParams) + + switch sqlVerb { + case "select": + if whereClause != "" { + return fmt.Sprintf("SELECT * FROM %s WHERE %s", fqResource, whereClause) + } + return fmt.Sprintf("SELECT * FROM %s", fqResource) + + case "insert": + // StackQL INSERT: all params (body + where) go as columns/values + allInsertParams := make(map[string]anysdk.Addressable) + for k, v := range bodyParams { + allInsertParams[k] = v + } + for k, v := range whereParams { + allInsertParams[k] = v + } + cols, vals := buildInsertColumnsAndValues(allInsertParams) + return fmt.Sprintf("INSERT INTO %s (%s) VALUES (%s)", fqResource, cols, vals) + + case "delete": + if whereClause != "" { + return fmt.Sprintf("DELETE FROM %s WHERE %s", fqResource, whereClause) + } + return fmt.Sprintf("DELETE FROM %s", fqResource) + + case "update": + setClause := buildSetClause(bodyParams) + q := fmt.Sprintf("UPDATE %s SET %s", fqResource, setClause) + if whereClause != "" { + q += fmt.Sprintf(" WHERE %s", whereClause) + } + return q + + case "exec": + if whereClause != "" { + return fmt.Sprintf("EXEC %s WHERE %s", fqResource, whereClause) + } + return fmt.Sprintf("EXEC %s", fqResource) + + default: + if whereClause != "" { + return fmt.Sprintf("SELECT * FROM %s WHERE %s", fqResource, whereClause) + } + return fmt.Sprintf("SELECT * FROM %s", fqResource) + } +} + +func buildInsertColumnsAndValues(params map[string]anysdk.Addressable) (string, string) { + if len(params) == 0 { + return "dummy_col", "'dummy_val'" + } + keys := sortedKeys(params) + cols := make([]string, 0, len(keys)) + vals := make([]string, 0, len(keys)) + for _, k := range keys { + cols = append(cols, k) + vals = append(vals, fmt.Sprintf("'%s'", dummyValue(params[k], k))) + } + return strings.Join(cols, ", "), strings.Join(vals, ", ") +} + +func buildSetClause(params map[string]anysdk.Addressable) string { + if len(params) == 0 { + return "dummy_col = 'dummy_val'" + } + keys := sortedKeys(params) + clauses := make([]string, 0, len(keys)) + for _, k := range keys { + clauses = append(clauses, fmt.Sprintf("%s = '%s'", k, dummyValue(params[k], k))) + } + return strings.Join(clauses, ", ") +} + +func sortedKeys(params map[string]anysdk.Addressable) []string { + keys := make([]string, 0, len(params)) + for k := range params { + keys = append(keys, k) + } + sort.Strings(keys) + return keys +} + +func buildWhereClause(params map[string]anysdk.Addressable) string { + if len(params) == 0 { + return "" + } + // Sort for deterministic output + keys := make([]string, 0, len(params)) + for k := range params { + keys = append(keys, k) + } + sort.Strings(keys) + + clauses := make([]string, 0, len(keys)) + for _, k := range keys { + p := params[k] + clauses = append(clauses, fmt.Sprintf("%s = '%s'", k, dummyValue(p, k))) + } + return strings.Join(clauses, " AND ") +} + +func dummyValue(p anysdk.Addressable, key string) (rv string) { + defer func() { + if r := recover(); r != nil { + rv = "dummy_" + key + } + }() + if p == nil { + return "dummy_" + key + } + switch strings.ToLower(p.GetType()) { + case "integer", "number": + return "0" + case "boolean": + return "true" + default: + return "dummy_" + p.GetName() + } +} + +// isActionQueryStyle detects the query API pattern where the path has an Action= +// parameter (e.g., "/?Action=DescribeVolumes&Version=..."). These APIs use POST +// to a root path with Action discrimination in the form body at runtime, +// regardless of what the OpenAPI spec says about the HTTP method. +func isActionQueryStyle(path string) bool { + return strings.Contains(path, "Action=") +} + +// deriveAction extracts the AWS Action name from the operation name or parameterized path. +// Operation names like "GET_DescribeVolumes" → "DescribeVolumes". +// Paths like "/?Action=DescribeVolumes&Version=..." → "DescribeVolumes". +func deriveAction(operationName string, parameterizedPath string) string { + // Try extracting from path query string: ?Action=Xyz or ?__Action=Xyz + if idx := strings.Index(parameterizedPath, "Action="); idx >= 0 { + action := parameterizedPath[idx+len("Action="):] + if ampIdx := strings.Index(action, "&"); ampIdx >= 0 { + action = action[:ampIdx] + } + if action != "" { + return action + } + } + // Fall back to operation name: strip HTTP verb prefix (e.g., "GET_DescribeVolumes" → "DescribeVolumes") + if idx := strings.Index(operationName, "_"); idx >= 0 { + candidate := operationName[idx+1:] + if candidate != "" { + return candidate + } + } + return operationName +} + +// GenerateExpectedResponse extracts the items array from the post-transform JSON +// using the selectItemsKey, and wraps it as a JSON array — matching `stackql exec -o json` output. +// Returns empty string if selectItemsKey is absent (expected response cannot be reliably predicted). +func GenerateExpectedResponse(postTransform string, selectItemsKey string) string { + if postTransform == "" || selectItemsKey == "" { + return "" + } + var parsed interface{} + if err := json.Unmarshal([]byte(postTransform), &parsed); err != nil { + return "" + } + + // Navigate dot-separated or $. prefixed key path, e.g. "$.items" or "items" + target := parsed + keyPath := strings.TrimPrefix(selectItemsKey, "$.") + keyPath = strings.TrimPrefix(keyPath, "$") + if keyPath != "" { + for _, seg := range strings.Split(keyPath, ".") { + if seg == "" { + continue + } + m, ok := target.(map[string]interface{}) + if !ok { + return "" + } + next, exists := m[seg] + if !exists { + return "" + } + target = next + } + } + + // If target is already an array, marshal it directly + if arr, ok := target.([]interface{}); ok { + out, err := json.MarshalIndent(arr, "", " ") + if err != nil { + return "" + } + return string(out) + } + // Single item — wrap as array + out, err := json.MarshalIndent([]interface{}{target}, "", " ") + if err != nil { + return "" + } + return string(out) +} + +// MockResponseVarName returns the Python variable name for the mock response body constant. +func MockResponseVarName(providerName, serviceName, resourceName, methodName string) string { + return "MOCK_RESPONSE_" + strings.ToUpper(sanitizePythonName( + fmt.Sprintf("%s_%s_%s_%s", providerName, serviceName, resourceName, methodName))) +} + +// InferMediaType determines the content type from the response body content. +// If the body starts with '<', it's XML; otherwise fall back to the provided default. +func InferMediaType(body string, fallback string) string { + trimmed := strings.TrimSpace(body) + if len(trimmed) > 0 && trimmed[0] == '<' { + return "application/xml" + } + if fallback != "" { + return fallback + } + return "application/json" +} + +func sanitizePythonName(s string) string { + s = strings.ReplaceAll(s, ".", "_") + s = strings.ReplaceAll(s, "-", "_") + s = strings.ReplaceAll(s, "/", "_") + return strings.ToLower(s) +} diff --git a/public/discovery/response_sample_generator.go b/public/discovery/response_sample_generator.go index 72381e7..6f86803 100644 --- a/public/discovery/response_sample_generator.go +++ b/public/discovery/response_sample_generator.go @@ -10,6 +10,7 @@ import ( // SampleResponsePair holds pre-transform and post-transform sample responses. type SampleResponsePair struct { + VarName string `json:"var_name,omitempty"` PreTransform string `json:"pre_transform"` PostTransform string `json:"post_transform"` } @@ -67,10 +68,29 @@ func GenerateSampleResponse(schema anysdk.Schema, mediaType string) string { } // GenerateSampleXMLResponse produces a minimal XML sample from the schema. +// If the schema is an object with properties, each top-level property becomes +// a root XML element — matching real API responses that don't wrap in an +// artificial envelope. func GenerateSampleXMLResponse(schema anysdk.Schema, rootElement string) string { if schema == nil { return "" } + // If the schema is an object, emit each top-level property as its own + // XML element — no artificial wrapper. This matches real API behavior + // (e.g., ...). + schemaType := schema.GetType() + if (schemaType == "object" || schemaType == "") && rootElement == "" { + props, err := schema.GetProperties() + if err == nil && len(props) > 0 { + var sb strings.Builder + for key, propSchema := range props { + sb.WriteString(fmt.Sprintf("<%s>", key)) + generateSampleXML(&sb, propSchema, 1) + sb.WriteString(fmt.Sprintf("", key)) + } + return sb.String() + } + } if rootElement == "" { rootElement = "Response" } diff --git a/public/discovery/static_analyzer.go b/public/discovery/static_analyzer.go index 2728dba..68c6050 100644 --- a/public/discovery/static_analyzer.go +++ b/public/discovery/static_analyzer.go @@ -4,7 +4,6 @@ import ( "fmt" "path/filepath" "strings" - "sync" "github.com/stackql/any-sdk/internal/anysdk" "github.com/stackql/any-sdk/pkg/client" @@ -1055,8 +1054,6 @@ func (osa *genericStaticAnalyzer) Analyze() error { } // --- END DOCVAL ANALYSIS --- providerServices := provider.GetProviderServices() - var wg sync.WaitGroup - serviceAnalyzers := make(map[string]StaticAnalyzer, len(providerServices)) for k, providerService := range providerServices { serviceLevelStaticAnalyzer := NewServiceLevelStaticAnalyzer( osa.cfg, @@ -1067,15 +1064,7 @@ func (osa *genericStaticAnalyzer) Analyze() error { k, osa.registryAPI, ) - serviceAnalyzers[k] = serviceLevelStaticAnalyzer - wg.Add(1) - go func(k string) { - defer wg.Done() - serviceLevelStaticAnalyzer.Analyze() - }(k) - } - wg.Wait() - for k, serviceLevelStaticAnalyzer := range serviceAnalyzers { + serviceLevelStaticAnalyzer.Analyze() serviceErrors := serviceLevelStaticAnalyzer.GetErrors() if len(serviceErrors) > 0 { osa.errors = append(osa.errors, fmt.Errorf("static analysis found errors for service %s, error count %d", k, len(serviceErrors))) @@ -1087,7 +1076,6 @@ func (osa *genericStaticAnalyzer) Analyze() error { osa.findings = append(osa.findings, fa.GetFindings()...) } } - wg.Wait() if len(osa.errors) > 0 { return fmt.Errorf("static analysis found errors, error count %d", len(osa.errors)) } @@ -1318,7 +1306,8 @@ func (osa *serviceLevelStaticAnalyzer) GetRegistryAPI() (anysdk.RegistryAPI, boo } func (osa *serviceLevelStaticAnalyzer) Analyze() error { - anysdk.OpenapiFileRoot = osa.cfg.GetRegistryRootDir() + // NOTE: OpenapiFileRoot must be set by the caller before concurrent invocation. + // Do not set it here — concurrent goroutines would race on the global. protocolType, protocolTypeErr := osa.provider.GetProtocolType() if protocolTypeErr != nil { return protocolTypeErr @@ -1399,6 +1388,10 @@ func (osa *serviceLevelStaticAnalyzer) Analyze() error { osa.findings = append(osa.findings, mar.findings...) } osa.affirmatives = append(osa.affirmatives, fmt.Sprintf("successfully dereferenced resource = '%s' with attendant service fragment for svc name = '%s'", resourceKey, k)) + // Resource-level checks + rctx := AnalysisContext{Provider: providerName, Service: k, Resource: resourceKey} + osa.findings = append(osa.findings, checkSQLVerbCoverage(rctx, resource)...) + osa.findings = append(osa.findings, checkCrossResourceConsistency(rctx, resourceKey, resources)...) } if len(osa.errors) > 0 { @@ -1442,6 +1435,13 @@ func analyzeMethod( result.errors = append(result.errors, fmt.Errorf("static analysis found errors for method %s on resource %s: %v", actx.Method, actx.Resource, methodAnalyzerErr)) } + // Static analysis checks (provider-agnostic, schema-driven) + result.findings = append(result.findings, checkRequestParamRoutability(actx, method)...) + result.findings = append(result.findings, checkRefResolution(actx, method)...) + result.findings = append(result.findings, checkServerURLValidity(actx, method)...) + result.findings = append(result.findings, checkPaginationCompleteness(actx, method)...) + result.findings = append(result.findings, checkTransformSchemaConsistency(actx, method)...) + switch protocolType { case client.HTTP: graphQL := method.GetGraphQL() @@ -1497,6 +1497,95 @@ func analyzeMethod( default: // placeholder for fine grained protocol type analysis } + + // Enrich findings that have sample responses with mock route and StackQL query + reqParams := method.GetRequiredParameters() + bodyAttrs, _ := method.GetRequestBodyAttributesNoRename() + // responseMediaType will be refined per-finding based on the actual pre-transform content + _, responseMediaType, _ := method.GetResponseBodySchemaAndMediaType() + operationPath := "" + if opRef := method.GetOperationRef(); opRef != nil { + operationPath = opRef.ExtractPathItem() + } + hasMock := false + for i := range result.findings { + if result.findings[i].SampleResponse != nil { + hasMock = true + varName := MockResponseVarName(actx.Provider, actx.Service, actx.Resource, actx.Method) + result.findings[i].SampleResponse.VarName = varName + mockMediaType := InferMediaType(result.findings[i].SampleResponse.PreTransform, responseMediaType) + result.findings[i].MockRoute = GenerateMockRoute( + actx.Provider, + actx.Service, + actx.Resource, + actx.Method, + method.GetAPIMethod(), + method.GetName(), + operationPath, + mockMediaType, + reqParams, + ) + result.findings[i].StackQLQuery = GenerateStackQLQuery( + actx.Provider, + actx.Service, + actx.Resource, + method.GetSQLVerb(), + reqParams, + bodyAttrs, + ) + result.findings[i].ExpectedResponse = GenerateExpectedResponse( + result.findings[i].SampleResponse.PostTransform, + method.GetSelectItemsKey(), + ) + } + } + + // For methods that didn't produce a SampleResponse through transform analysis, + // generate a mock-only finding directly from the response schema. + if !hasMock { + responseSchema, mediaType, _ := method.GetFinalResponseBodySchemaAndMediaType() + if responseSchema != nil { + sampleResponse := GenerateSampleResponsePair(responseSchema, mediaType, responseSchema, mediaType) + if sampleResponse != nil && sampleResponse.PreTransform != "" { + varName := MockResponseVarName(actx.Provider, actx.Service, actx.Resource, actx.Method) + sampleResponse.VarName = varName + f := AnalysisFinding{ + Level: "info", + Provider: actx.Provider, + Service: actx.Service, + Resource: actx.Resource, + Method: actx.Method, + Message: "mock generated from response schema", + SampleResponse: sampleResponse, + MockRoute: GenerateMockRoute( + actx.Provider, + actx.Service, + actx.Resource, + actx.Method, + method.GetAPIMethod(), + method.GetName(), + operationPath, + mediaType, + reqParams, + ), + StackQLQuery: GenerateStackQLQuery( + actx.Provider, + actx.Service, + actx.Resource, + method.GetSQLVerb(), + reqParams, + bodyAttrs, + ), + ExpectedResponse: GenerateExpectedResponse( + sampleResponse.PostTransform, + method.GetSelectItemsKey(), + ), + } + result.findings = append(result.findings, f) + } + } + } + return result } diff --git a/test/assets/analysis-jsonl/single-entry-observations.jsonl b/test/assets/analysis-jsonl/single-entry-observations.jsonl new file mode 100644 index 0000000..73cb523 --- /dev/null +++ b/test/assets/analysis-jsonl/single-entry-observations.jsonl @@ -0,0 +1 @@ +{"level":"warning","bin":"empty-response-unsafe","provider":"aws","service":"ec2","resource":"volumes_post_naively_presented","method":"describeVolumes","message":"response transform template accesses input directly without nil/empty guards — may fail on empty response bodies","prior_template":"{{ toJson . }}","fixed_template":"{{- if . -}}{{ toJson . }}{{- else -}}null{{- end -}}","empirical_tests":{"results":[{"input":"","ok":true},{"input":"\u003croot/\u003e","output":"{\"root\":\"\"}","ok":true},{"input":"\u003croot\u003e\u003c/root\u003e","output":"{\"root\":\"\"}","ok":true}]},"sample_response":{"var_name":"MOCK_RESPONSE_AWS_EC2_VOLUMES_POST_NAIVELY_PRESENTED_DESCRIBEVOLUMES","pre_transform":"\u003cResponse\u003e\u003cDescribeVolumesResponse\u003e\u003cNextToken\u003esample_string\u003c/NextToken\u003e\u003cVolumes\u003e\u003citem\u003e\u003cAttachments\u003e\u003citem\u003e\u003c/item\u003e\u003c/Attachments\u003e\u003cSnapshotId\u003esample_string\u003c/SnapshotId\u003e\u003cThroughput\u003e0\u003c/Throughput\u003e\u003cKmsKeyId\u003esample_string\u003c/KmsKeyId\u003e\u003cIops\u003e0\u003c/Iops\u003e\u003cVolumeType\u003esample_string\u003c/VolumeType\u003e\u003cFastRestored\u003efalse\u003c/FastRestored\u003e\u003cState\u003esample_string\u003c/State\u003e\u003cMultiAttachEnabled\u003efalse\u003c/MultiAttachEnabled\u003e\u003cAvailabilityZone\u003esample_string\u003c/AvailabilityZone\u003e\u003cOutpostArn\u003esample_string\u003c/OutpostArn\u003e\u003cEncrypted\u003efalse\u003c/Encrypted\u003e\u003cVolumeId\u003esample_string\u003c/VolumeId\u003e\u003cCreateTime\u003esample_string\u003c/CreateTime\u003e\u003cSize\u003e0\u003c/Size\u003e\u003cTags\u003e\u003citem\u003e\u003c/item\u003e\u003c/Tags\u003e\u003c/item\u003e\u003c/Volumes\u003e\u003c/DescribeVolumesResponse\u003e\u003c/Response\u003e","post_transform":"{\n \"DescribeVolumesResponse\": {\n \"NextToken\": \"sample_string\",\n \"Volumes\": [\n {\n \"Attachments\": [\n {}\n ],\n \"AvailabilityZone\": \"sample_string\",\n \"CreateTime\": \"sample_string\",\n \"Encrypted\": false,\n \"FastRestored\": false,\n \"Iops\": 0,\n \"KmsKeyId\": \"sample_string\",\n \"MultiAttachEnabled\": false,\n \"OutpostArn\": \"sample_string\",\n \"Size\": 0,\n \"SnapshotId\": \"sample_string\",\n \"State\": \"sample_string\",\n \"Tags\": [\n {}\n ],\n \"Throughput\": 0,\n \"VolumeId\": \"sample_string\",\n \"VolumeType\": \"sample_string\"\n }\n ]\n }\n}"},"mock_route":"@app.route('/', methods=['POST'])\ndef aws_ec2_volumes_post_naively_presented_describevolumes():\n if request.form.get('Action') == 'DescribeVolumes':\n return Response(MOCK_RESPONSE_AWS_EC2_VOLUMES_POST_NAIVELY_PRESENTED_DESCRIBEVOLUMES, content_type='application/xml')","stackql_query":"SELECT * FROM aws.ec2.volumes_post_naively_presented WHERE region = 'dummy_region'","expected_response":"[\n {\n \"DescribeVolumesResponse\": {\n \"NextToken\": \"sample_string\",\n \"Volumes\": [\n {\n \"Attachments\": [\n {}\n ],\n \"AvailabilityZone\": \"sample_string\",\n \"CreateTime\": \"sample_string\",\n \"Encrypted\": false,\n \"FastRestored\": false,\n \"Iops\": 0,\n \"KmsKeyId\": \"sample_string\",\n \"MultiAttachEnabled\": false,\n \"OutpostArn\": \"sample_string\",\n \"Size\": 0,\n \"SnapshotId\": \"sample_string\",\n \"State\": \"sample_string\",\n \"Tags\": [\n {}\n ],\n \"Throughput\": 0,\n \"VolumeId\": \"sample_string\",\n \"VolumeType\": \"sample_string\"\n }\n ]\n }\n }\n]"} \ No newline at end of file diff --git a/test/auto-mocks/reference/expect_aws_ec2_instances_describe.txt b/test/auto-mocks/reference/expect_aws_ec2_instances_describe.txt new file mode 100644 index 0000000..3244f6a --- /dev/null +++ b/test/auto-mocks/reference/expect_aws_ec2_instances_describe.txt @@ -0,0 +1,23 @@ +[ + { + "availability_zone": "sample_string", + "block_device_mappings": {}, + "iam_instance_profile": {}, + "image_id": "sample_string", + "instance_id": "sample_string", + "instance_type": "sample_string", + "key_name": "sample_string", + "launch_time": "sample_string", + "monitoring": {}, + "network_interfaces": {}, + "private_dns_name": "sample_string", + "private_ip_address": "sample_string", + "public_dns_name": "sample_string", + "public_ip_address": "sample_string", + "security_groups": {}, + "state": {}, + "subnet_id": "sample_string", + "tag_set": {}, + "vpc_id": "sample_string" + } +] \ No newline at end of file diff --git a/test/auto-mocks/reference/mock_aws_ec2_instances_describe.py b/test/auto-mocks/reference/mock_aws_ec2_instances_describe.py new file mode 100644 index 0000000..56b8c98 --- /dev/null +++ b/test/auto-mocks/reference/mock_aws_ec2_instances_describe.py @@ -0,0 +1,16 @@ +from flask import Flask, request, Response + +app = Flask(__name__) + +MOCK_RESPONSE_AWS_EC2_INSTANCES_DESCRIBE = """sample_stringsample_stringsample_stringsample_stringsample_stringsample_stringsample_stringsample_stringsample_stringsample_stringsample_stringsample_stringsample_string""" + +@app.route('/', methods=['POST']) +def aws_ec2_instances_describe(): + return Response(MOCK_RESPONSE_AWS_EC2_INSTANCES_DESCRIBE, content_type='application/json') + + +if __name__ == '__main__': + import argparse as ap + p = ap.ArgumentParser() + p.add_argument('--port', type=int, default=5000) + app.run(host='0.0.0.0', port=p.parse_args().port) diff --git a/test/auto-mocks/reference/query_aws_ec2_instances_describe.txt b/test/auto-mocks/reference/query_aws_ec2_instances_describe.txt new file mode 100644 index 0000000..fb95f8e --- /dev/null +++ b/test/auto-mocks/reference/query_aws_ec2_instances_describe.txt @@ -0,0 +1 @@ +SELECT * FROM aws.ec2.instances WHERE region = 'dummy_region' \ No newline at end of file diff --git a/test/auto-mocks/reference/registry/src/aws/v0.1.0/provider.yaml b/test/auto-mocks/reference/registry/src/aws/v0.1.0/provider.yaml new file mode 100644 index 0000000..ec33c2e --- /dev/null +++ b/test/auto-mocks/reference/registry/src/aws/v0.1.0/provider.yaml @@ -0,0 +1,19 @@ +id: aws +name: aws +version: v0.1.0 +providerServices: + ec2: + description: ec2 + id: ec2:v0.1.0 + name: ec2 + preferred: true + service: + $ref: aws/v0.1.0/services/closure_ec2_instances.yaml + title: EC2 API + version: v0.1.0 +openapi: 3.0.0 +config: + auth: + type: "aws_signing_v4" + credentialsenvvar: "AWS_SECRET_ACCESS_KEY" + keyIDenvvar: "AWS_ACCESS_KEY_ID" diff --git a/test/auto-mocks/reference/registry/src/aws/v0.1.0/services/closure_ec2_instances.yaml b/test/auto-mocks/reference/registry/src/aws/v0.1.0/services/closure_ec2_instances.yaml new file mode 100644 index 0000000..20e19ea --- /dev/null +++ b/test/auto-mocks/reference/registry/src/aws/v0.1.0/services/closure_ec2_instances.yaml @@ -0,0 +1,3345 @@ +components: + schemas: + ArchitectureValues: + enum: + - i386 + - x86_64 + - arm64 + - x86_64_mac + type: string + AttachmentStatus: + enum: + - attaching + - attached + - detaching + - detached + type: string + Blob: + type: string + Boolean: + type: boolean + BootModeValues: + enum: + - legacy-bios + - uefi + type: string + BundleInstanceRequest: + description: Contains the parameters for BundleInstance. + properties: + dryRun: + allOf: + - $ref: '#/components/schemas/Boolean' + - description: Checks whether you have the required permissions for the action, without actually making the request, and provides an error response. If you have the required permissions, the error response is DryRunOperation. Otherwise, it is UnauthorizedOperation. + undefined: + allOf: + - $ref: '#/components/schemas/Storage' + - description: The bucket in which to store the AMI. You can specify a bucket that you already own or a new bucket that Amazon EC2 creates on your behalf. If you specify a bucket that belongs to someone else, Amazon EC2 returns an error. + required: + - InstanceId + - Storage + title: BundleInstanceRequest + type: object + BundleInstanceResponseWrapper: + properties: + BundleInstanceResponse: + $ref: '#/components/schemas/BundleInstanceResult' + type: object + BundleInstanceResult: + description: Contains the output of BundleInstance. + properties: + bundleInstanceTask: + allOf: + - $ref: '#/components/schemas/BundleTask' + - description: Information about the bundle task. + type: object + BundleTask: + description: Describes a bundle task. + properties: + bundleId: + allOf: + - $ref: '#/components/schemas/String' + - description: The ID of the bundle task. + error: + allOf: + - $ref: '#/components/schemas/BundleTaskError' + - description: If the task fails, a description of the error. + instanceId: + allOf: + - $ref: '#/components/schemas/String' + - description: The ID of the instance associated with this bundle task. + progress: + allOf: + - $ref: '#/components/schemas/String' + - description: The level of task completion, as a percent (for example, 20%). + startTime: + allOf: + - $ref: '#/components/schemas/DateTime' + - description: The time this task started. + state: + allOf: + - $ref: '#/components/schemas/BundleTaskState' + - description: The state of the task. + storage: + allOf: + - $ref: '#/components/schemas/Storage' + - description: The Amazon S3 storage locations. + updateTime: + allOf: + - $ref: '#/components/schemas/DateTime' + - description: The time of the most recent update for the task. + type: object + BundleTaskError: + description: Describes an error for BundleInstance. + properties: + code: + allOf: + - $ref: '#/components/schemas/String' + - description: The error code. + message: + allOf: + - $ref: '#/components/schemas/String' + - description: The error message. + type: object + BundleTaskState: + enum: + - pending + - waiting-for-shutdown + - bundling + - storing + - cancelling + - complete + - failed + type: string + CapacityReservationPreference: + enum: + - open + - none + type: string + CapacityReservationSpecificationResponse: + description: Describes the instance's Capacity Reservation targeting preferences. The action returns the capacityReservationPreference response element if the instance is configured to run in On-Demand capacity, or if it is configured in run in any open Capacity Reservation that has matching attributes (instance type, platform, Availability Zone). The action returns the capacityReservationTarget response element if the instance explicily targets a specific Capacity Reservation or Capacity Reservation group. + properties: + capacityReservationPreference: + allOf: + - $ref: '#/components/schemas/CapacityReservationPreference' + - description:

Describes the instance's Capacity Reservation preferences. Possible preferences include:

  • open - The instance can run in any open Capacity Reservation that has matching attributes (instance type, platform, Availability Zone).

  • none - The instance avoids running in a Capacity Reservation even if one is available. The instance runs in On-Demand capacity.

+ capacityReservationTarget: + allOf: + - $ref: '#/components/schemas/CapacityReservationTargetResponse' + - description: Information about the targeted Capacity Reservation or Capacity Reservation group. + type: object + CapacityReservationTargetResponse: + description: Describes a target Capacity Reservation or Capacity Reservation group. + properties: + capacityReservationId: + allOf: + - $ref: '#/components/schemas/String' + - description: The ID of the targeted Capacity Reservation. + capacityReservationResourceGroupArn: + allOf: + - $ref: '#/components/schemas/String' + - description: The ARN of the targeted Capacity Reservation group. + type: object + ConversionTask: + description: Describes a conversion task. + properties: + conversionTaskId: + allOf: + - $ref: '#/components/schemas/String' + - description: The ID of the conversion task. + expirationTime: + allOf: + - $ref: '#/components/schemas/String' + - description: The time when the task expires. If the upload isn't complete before the expiration time, we automatically cancel the task. + importInstance: + allOf: + - $ref: '#/components/schemas/ImportInstanceTaskDetails' + - description: If the task is for importing an instance, this contains information about the import instance task. + importVolume: + allOf: + - $ref: '#/components/schemas/ImportVolumeTaskDetails' + - description: If the task is for importing a volume, this contains information about the import volume task. + state: + allOf: + - $ref: '#/components/schemas/ConversionTaskState' + - description: The state of the conversion task. + statusMessage: + allOf: + - $ref: '#/components/schemas/String' + - description: The status message related to the conversion task. + tagSet: + allOf: + - $ref: '#/components/schemas/TagList' + - description: Any tags assigned to the task. + type: object + ConversionTaskState: + enum: + - active + - cancelling + - cancelled + - completed + type: string + CpuOptions: + description: The CPU options for the instance. + properties: + coreCount: + allOf: + - $ref: '#/components/schemas/Integer' + - description: The number of CPU cores for the instance. + threadsPerCore: + allOf: + - $ref: '#/components/schemas/Integer' + - description: The number of threads per CPU core. + type: object + DateTime: + format: date-time + type: string + DescribeInstancesRequest: + properties: + Filter: + allOf: + - $ref: '#/components/schemas/FilterList' + - description: '

The filters.

  • affinity - The affinity setting for an instance running on a Dedicated Host (default | host).

  • architecture - The instance architecture (i386 | x86_64 | arm64).

  • availability-zone - The Availability Zone of the instance.

  • block-device-mapping.attach-time - The attach time for an EBS volume mapped to the instance, for example, 2010-09-15T17:15:20.000Z.

  • block-device-mapping.delete-on-termination - A Boolean that indicates whether the EBS volume is deleted on instance termination.

  • block-device-mapping.device-name - The device name specified in the block device mapping (for example, /dev/sdh or xvdh).

  • block-device-mapping.status - The status for the EBS volume (attaching | attached | detaching | detached).

  • block-device-mapping.volume-id - The volume ID of the EBS volume.

  • capacity-reservation-id - The ID of the Capacity Reservation into which the instance was launched.

  • client-token - The idempotency token you provided when you launched the instance.

  • dns-name - The public DNS name of the instance.

  • group-id - The ID of the security group for the instance. EC2-Classic only.

  • group-name - The name of the security group for the instance. EC2-Classic only.

  • hibernation-options.configured - A Boolean that indicates whether the instance is enabled for hibernation. A value of true means that the instance is enabled for hibernation.

  • host-id - The ID of the Dedicated Host on which the instance is running, if applicable.

  • hypervisor - The hypervisor type of the instance (ovm | xen). The value xen is used for both Xen and Nitro hypervisors.

  • iam-instance-profile.arn - The instance profile associated with the instance. Specified as an ARN.

  • image-id - The ID of the image used to launch the instance.

  • instance-id - The ID of the instance.

  • instance-lifecycle - Indicates whether this is a Spot Instance or a Scheduled Instance (spot | scheduled).

  • instance-state-code - The state of the instance, as a 16-bit unsigned integer. The high byte is used for internal purposes and should be ignored. The low byte is set based on the state represented. The valid values are: 0 (pending), 16 (running), 32 (shutting-down), 48 (terminated), 64 (stopping), and 80 (stopped).

  • instance-state-name - The state of the instance (pending | running | shutting-down | terminated | stopping | stopped).

  • instance-type - The type of instance (for example, t2.micro).

  • instance.group-id - The ID of the security group for the instance.

  • instance.group-name - The name of the security group for the instance.

  • ip-address - The public IPv4 address of the instance.

  • kernel-id - The kernel ID.

  • key-name - The name of the key pair used when the instance was launched.

  • launch-index - When launching multiple instances, this is the index for the instance in the launch group (for example, 0, 1, 2, and so on).

  • launch-time - The time when the instance was launched, in the ISO 8601 format in the UTC time zone (YYYY-MM-DDThh:mm:ss.sssZ), for example, 2021-09-29T11:04:43.305Z. You can use a wildcard (*), for example, 2021-09-29T*, which matches an entire day.

  • metadata-options.http-tokens - The metadata request authorization state (optional | required)

  • metadata-options.http-put-response-hop-limit - The http metadata request put response hop limit (integer, possible values 1 to 64)

  • metadata-options.http-endpoint - Enable or disable metadata access on http endpoint (enabled | disabled)

  • monitoring-state - Indicates whether detailed monitoring is enabled (disabled | enabled).

  • network-interface.addresses.private-ip-address - The private IPv4 address associated with the network interface.

  • network-interface.addresses.primary - Specifies whether the IPv4 address of the network interface is the primary private IPv4 address.

  • network-interface.addresses.association.public-ip - The ID of the association of an Elastic IP address (IPv4) with a network interface.

  • network-interface.addresses.association.ip-owner-id - The owner ID of the private IPv4 address associated with the network interface.

  • network-interface.association.public-ip - The address of the Elastic IP address (IPv4) bound to the network interface.

  • network-interface.association.ip-owner-id - The owner of the Elastic IP address (IPv4) associated with the network interface.

  • network-interface.association.allocation-id - The allocation ID returned when you allocated the Elastic IP address (IPv4) for your network interface.

  • network-interface.association.association-id - The association ID returned when the network interface was associated with an IPv4 address.

  • network-interface.attachment.attachment-id - The ID of the interface attachment.

  • network-interface.attachment.instance-id - The ID of the instance to which the network interface is attached.

  • network-interface.attachment.instance-owner-id - The owner ID of the instance to which the network interface is attached.

  • network-interface.attachment.device-index - The device index to which the network interface is attached.

  • network-interface.attachment.status - The status of the attachment (attaching | attached | detaching | detached).

  • network-interface.attachment.attach-time - The time that the network interface was attached to an instance.

  • network-interface.attachment.delete-on-termination - Specifies whether the attachment is deleted when an instance is terminated.

  • network-interface.availability-zone - The Availability Zone for the network interface.

  • network-interface.description - The description of the network interface.

  • network-interface.group-id - The ID of a security group associated with the network interface.

  • network-interface.group-name - The name of a security group associated with the network interface.

  • network-interface.ipv6-addresses.ipv6-address - The IPv6 address associated with the network interface.

  • network-interface.mac-address - The MAC address of the network interface.

  • network-interface.network-interface-id - The ID of the network interface.

  • network-interface.owner-id - The ID of the owner of the network interface.

  • network-interface.private-dns-name - The private DNS name of the network interface.

  • network-interface.requester-id - The requester ID for the network interface.

  • network-interface.requester-managed - Indicates whether the network interface is being managed by Amazon Web Services.

  • network-interface.status - The status of the network interface (available) | in-use).

  • network-interface.source-dest-check - Whether the network interface performs source/destination checking. A value of true means that checking is enabled, and false means that checking is disabled. The value must be false for the network interface to perform network address translation (NAT) in your VPC.

  • network-interface.subnet-id - The ID of the subnet for the network interface.

  • network-interface.vpc-id - The ID of the VPC for the network interface.

  • outpost-arn - The Amazon Resource Name (ARN) of the Outpost.

  • owner-id - The Amazon Web Services account ID of the instance owner.

  • placement-group-name - The name of the placement group for the instance.

  • placement-partition-number - The partition in which the instance is located.

  • platform - The platform. To list only Windows instances, use windows.

  • private-dns-name - The private IPv4 DNS name of the instance.

  • private-ip-address - The private IPv4 address of the instance.

  • product-code - The product code associated with the AMI used to launch the instance.

  • product-code.type - The type of product code (devpay | marketplace).

  • ramdisk-id - The RAM disk ID.

  • reason - The reason for the current state of the instance (for example, shows "User Initiated [date]" when you stop or terminate the instance). Similar to the state-reason-code filter.

  • requester-id - The ID of the entity that launched the instance on your behalf (for example, Amazon Web Services Management Console, Auto Scaling, and so on).

  • reservation-id - The ID of the instance''s reservation. A reservation ID is created any time you launch an instance. A reservation ID has a one-to-one relationship with an instance launch request, but can be associated with more than one instance if you launch multiple instances using the same launch request. For example, if you launch one instance, you get one reservation ID. If you launch ten instances using the same launch request, you also get one reservation ID.

  • root-device-name - The device name of the root device volume (for example, /dev/sda1).

  • root-device-type - The type of the root device volume (ebs | instance-store).

  • source-dest-check - Indicates whether the instance performs source/destination checking. A value of true means that checking is enabled, and false means that checking is disabled. The value must be false for the instance to perform network address translation (NAT) in your VPC.

  • spot-instance-request-id - The ID of the Spot Instance request.

  • state-reason-code - The reason code for the state change.

  • state-reason-message - A message that describes the state change.

  • subnet-id - The ID of the subnet for the instance.

  • tag:<key> - The key/value combination of a tag assigned to the resource. Use the tag key in the filter name and the tag value as the filter value. For example, to find all resources that have a tag with the key Owner and the value TeamA, specify tag:Owner for the filter name and TeamA for the filter value.

  • tag-key - The key of a tag assigned to the resource. Use this filter to find all resources that have a tag with a specific key, regardless of the tag value.

  • tenancy - The tenancy of an instance (dedicated | default | host).

  • virtualization-type - The virtualization type of the instance (paravirtual | hvm).

  • vpc-id - The ID of the VPC that the instance is running in.

' + InstanceId: + allOf: + - $ref: '#/components/schemas/InstanceIdStringList' + - description: '

The instance IDs.

Default: Describes all your instances.

' + MaxResults: + maximum: 1000 + minimum: 5 + type: integer + dryRun: + allOf: + - $ref: '#/components/schemas/Boolean' + - description: Checks whether you have the required permissions for the action, without actually making the request, and provides an error response. If you have the required permissions, the error response is DryRunOperation. Otherwise, it is UnauthorizedOperation. + maxResults: + allOf: + - $ref: '#/components/schemas/Integer' + - description: The maximum number of results to return in a single call. To retrieve the remaining results, make another call with the returned NextToken value. This value can be between 5 and 1000. You cannot specify this parameter and the instance IDs parameter in the same call. + nextToken: + allOf: + - $ref: '#/components/schemas/String' + - description: The token to request the next page of results. + title: DescribeInstancesRequest + type: object + DescribeInstancesResponseWrapper: + properties: + DescribeInstancesResponse: + $ref: '#/components/schemas/DescribeInstancesResult' + type: object + DescribeInstancesResult: + example: {} + properties: + nextToken: + allOf: + - $ref: '#/components/schemas/String' + - description: The token to use to retrieve the next page of results. This value is null when there are no more results to return. + reservationSet: + allOf: + - $ref: '#/components/schemas/ReservationList' + - description: Information about the reservations. + type: object + DeviceType: + enum: + - ebs + - instance-store + type: string + DiskImage: + description: Describes a disk image. + properties: + undefined: + allOf: + - $ref: '#/components/schemas/VolumeDetail' + - description: Information about the volume. + type: object + DiskImageDescription: + description: Describes a disk image. + properties: + checksum: + allOf: + - $ref: '#/components/schemas/String' + - description: The checksum computed for the disk image. + format: + allOf: + - $ref: '#/components/schemas/DiskImageFormat' + - description: The disk image format. + importManifestUrl: + allOf: + - $ref: '#/components/schemas/String' + - description:

A presigned URL for the import manifest stored in Amazon S3. For information about creating a presigned URL for an Amazon S3 object, read the "Query String Request Authentication Alternative" section of the Authenticating REST Requests topic in the Amazon Simple Storage Service Developer Guide.

For information about the import manifest referenced by this API action, see VM Import Manifest.

+ size: + allOf: + - $ref: '#/components/schemas/Long' + - description: The size of the disk image, in GiB. + type: object + DiskImageFormat: + enum: + - VMDK + - RAW + - VHD + type: string + DiskImageList: + items: + $ref: '#/components/schemas/DiskImage' + type: array + DiskImageVolumeDescription: + description: Describes a disk image volume. + properties: + id: + allOf: + - $ref: '#/components/schemas/String' + - description: The volume identifier. + size: + allOf: + - $ref: '#/components/schemas/Long' + - description: The size of the volume, in GiB. + type: object + DisplayInstancesSchema: + properties: + line_items: + items: + properties: + availability_zone: + nullable: true + type: string + block_device_mappings: + nullable: true + type: object + iam_instance_profile: + nullable: true + type: object + image_id: + nullable: true + type: string + instance_id: + nullable: true + type: string + instance_type: + nullable: true + type: string + key_name: + nullable: true + type: string + launch_time: + nullable: true + type: string + monitoring: + nullable: true + type: object + network_interfaces: + nullable: true + type: object + private_dns_name: + nullable: true + type: string + private_ip_address: + nullable: true + type: string + public_dns_name: + nullable: true + type: string + public_ip_address: + nullable: true + type: string + security_groups: + nullable: true + type: object + state: + nullable: true + type: object + subnet_id: + nullable: true + type: string + tag_set: + nullable: true + type: object + vpc_id: + nullable: true + type: string + type: object + type: array + next_page_token: + nullable: true + type: string + type: object + EbsInstanceBlockDevice: + description: Describes a parameter used to set up an EBS volume in a block device mapping. + properties: + attachTime: + allOf: + - $ref: '#/components/schemas/DateTime' + - description: The time stamp when the attachment initiated. + deleteOnTermination: + allOf: + - $ref: '#/components/schemas/Boolean' + - description: Indicates whether the volume is deleted on instance termination. + status: + allOf: + - $ref: '#/components/schemas/AttachmentStatus' + - description: The attachment state. + volumeId: + allOf: + - $ref: '#/components/schemas/String' + - description: The ID of the EBS volume. + type: object + ElasticGpuAssociation: + description: Describes the association between an instance and an Elastic Graphics accelerator. + properties: + elasticGpuAssociationId: + allOf: + - $ref: '#/components/schemas/String' + - description: The ID of the association. + elasticGpuAssociationState: + allOf: + - $ref: '#/components/schemas/String' + - description: The state of the association between the instance and the Elastic Graphics accelerator. + elasticGpuAssociationTime: + allOf: + - $ref: '#/components/schemas/String' + - description: The time the Elastic Graphics accelerator was associated with the instance. + elasticGpuId: + allOf: + - $ref: '#/components/schemas/ElasticGpuId' + - description: The ID of the Elastic Graphics accelerator. + type: object + ElasticGpuAssociationList: + items: + allOf: + - $ref: '#/components/schemas/ElasticGpuAssociation' + - xml: + name: item + type: array + ElasticGpuId: + type: string + ElasticInferenceAccelerator: + description: ' Describes an elastic inference accelerator. ' + properties: + undefined: + allOf: + - $ref: '#/components/schemas/ElasticInferenceAcceleratorCount' + - description: '

The number of elastic inference accelerators to attach to the instance.

Default: 1

' + required: + - Type + type: object + ElasticInferenceAcceleratorAssociation: + description: ' Describes the association between an instance and an elastic inference accelerator. ' + properties: + elasticInferenceAcceleratorArn: + allOf: + - $ref: '#/components/schemas/String' + - description: ' The Amazon Resource Name (ARN) of the elastic inference accelerator. ' + elasticInferenceAcceleratorAssociationId: + allOf: + - $ref: '#/components/schemas/String' + - description: ' The ID of the association. ' + elasticInferenceAcceleratorAssociationState: + allOf: + - $ref: '#/components/schemas/String' + - description: ' The state of the elastic inference accelerator. ' + elasticInferenceAcceleratorAssociationTime: + allOf: + - $ref: '#/components/schemas/DateTime' + - description: ' The time at which the elastic inference accelerator is associated with an instance. ' + type: object + ElasticInferenceAcceleratorAssociationList: + items: + allOf: + - $ref: '#/components/schemas/ElasticInferenceAcceleratorAssociation' + - xml: + name: item + type: array + ElasticInferenceAcceleratorCount: + minimum: 1 + type: integer + ElasticInferenceAccelerators: + items: + allOf: + - $ref: '#/components/schemas/ElasticInferenceAccelerator' + - xml: + name: item + type: array + EnclaveOptions: + description: Indicates whether the instance is enabled for Amazon Web Services Nitro Enclaves. + properties: + enabled: + allOf: + - $ref: '#/components/schemas/Boolean' + - description: If this parameter is set to true, the instance is enabled for Amazon Web Services Nitro Enclaves; otherwise, it is not enabled for Amazon Web Services Nitro Enclaves. + type: object + Filter: + description:

A filter name and value pair that is used to return a more specific list of results from a describe operation. Filters can be used to match a set of resources by specific criteria, such as tags, attributes, or IDs.

If you specify multiple filters, the filters are joined with an AND, and the request returns only results that match all of the specified filters.

+ properties: + Value: + allOf: + - $ref: '#/components/schemas/ValueStringList' + - description: The filter values. Filter values are case-sensitive. If you specify multiple values for a filter, the values are joined with an OR, and the request returns all results that match any of the specified values. + undefined: + allOf: + - $ref: '#/components/schemas/String' + - description: The name of the filter. Filter names are case-sensitive. + type: object + FilterList: + items: + allOf: + - $ref: '#/components/schemas/Filter' + - xml: + name: Filter + type: array + GroupIdentifier: + description: Describes a security group. + properties: + groupId: + allOf: + - $ref: '#/components/schemas/String' + - description: The ID of the security group. + groupName: + allOf: + - $ref: '#/components/schemas/String' + - description: The name of the security group. + type: object + GroupIdentifierList: + items: + allOf: + - $ref: '#/components/schemas/GroupIdentifier' + - xml: + name: item + type: array + HibernationOptions: + description: Indicates whether your instance is configured for hibernation. This parameter is valid only if the instance meets the hibernation prerequisites. For more information, see Hibernate your instance in the Amazon EC2 User Guide. + properties: + configured: + allOf: + - $ref: '#/components/schemas/Boolean' + - description: If this parameter is set to true, your instance is enabled for hibernation; otherwise, it is not enabled for hibernation. + type: object + HibernationOptionsRequest: + description: Indicates whether your instance is configured for hibernation. This parameter is valid only if the instance meets the hibernation prerequisites. For more information, see Hibernate your instance in the Amazon EC2 User Guide. + properties: + undefined: + allOf: + - $ref: '#/components/schemas/Boolean' + - description: '

If you set this parameter to true, your instance is enabled for hibernation.

Default: false

' + type: object + HostnameType: + enum: + - ip-name + - resource-name + type: string + HttpTokensState: + enum: + - optional + - required + type: string + HypervisorType: + enum: + - ovm + - xen + type: string + IamInstanceProfile: + description: Describes an IAM instance profile. + properties: + arn: + allOf: + - $ref: '#/components/schemas/String' + - description: The Amazon Resource Name (ARN) of the instance profile. + id: + allOf: + - $ref: '#/components/schemas/String' + - description: The ID of the instance profile. + type: object + ImportInstanceLaunchSpecification: + description: Describes the launch specification for VM import. + properties: + GroupId: + allOf: + - $ref: '#/components/schemas/SecurityGroupIdStringList' + - description: The security group IDs. + GroupName: + allOf: + - $ref: '#/components/schemas/SecurityGroupStringList' + - description: The security group names. + additionalInfo: + allOf: + - $ref: '#/components/schemas/String' + - description: Reserved. + architecture: + allOf: + - $ref: '#/components/schemas/ArchitectureValues' + - description: The architecture of the instance. + instanceInitiatedShutdownBehavior: + allOf: + - $ref: '#/components/schemas/ShutdownBehavior' + - description: Indicates whether an instance stops or terminates when you initiate shutdown from the instance (using the operating system command for system shutdown). + instanceType: + allOf: + - $ref: '#/components/schemas/InstanceType' + - description: The instance type. For more information about the instance types that you can import, see Instance Types in the VM Import/Export User Guide. + monitoring: + allOf: + - $ref: '#/components/schemas/Boolean' + - description: Indicates whether monitoring is enabled. + placement: + allOf: + - $ref: '#/components/schemas/Placement' + - description: The placement information for the instance. + privateIpAddress: + allOf: + - $ref: '#/components/schemas/String' + - description: '[EC2-VPC] An available IP address from the IP address range of the subnet.' + subnetId: + allOf: + - $ref: '#/components/schemas/SubnetId' + - description: '[EC2-VPC] The ID of the subnet in which to launch the instance.' + userData: + allOf: + - $ref: '#/components/schemas/UserData' + - description: The Base64-encoded user data to make available to the instance. + type: object + ImportInstanceRequest: + properties: + description: + allOf: + - $ref: '#/components/schemas/String' + - description: A description for the instance being imported. + diskImage: + allOf: + - $ref: '#/components/schemas/DiskImageList' + - description: The disk image. + dryRun: + allOf: + - $ref: '#/components/schemas/Boolean' + - description: Checks whether you have the required permissions for the action, without actually making the request, and provides an error response. If you have the required permissions, the error response is DryRunOperation. Otherwise, it is UnauthorizedOperation. + launchSpecification: + allOf: + - $ref: '#/components/schemas/ImportInstanceLaunchSpecification' + - description: The launch specification. + platform: + allOf: + - $ref: '#/components/schemas/PlatformValues' + - description: The instance operating system. + required: + - Platform + title: ImportInstanceRequest + type: object + ImportInstanceResponseWrapper: + properties: + ImportInstanceResponse: + $ref: '#/components/schemas/ImportInstanceResult' + type: object + ImportInstanceResult: + properties: + conversionTask: + allOf: + - $ref: '#/components/schemas/ConversionTask' + - description: Information about the conversion task. + type: object + ImportInstanceTaskDetails: + description: Describes an import instance task. + properties: + description: + allOf: + - $ref: '#/components/schemas/String' + - description: A description of the task. + instanceId: + allOf: + - $ref: '#/components/schemas/String' + - description: The ID of the instance. + platform: + allOf: + - $ref: '#/components/schemas/PlatformValues' + - description: The instance operating system. + volumes: + allOf: + - $ref: '#/components/schemas/ImportInstanceVolumeDetailSet' + - description: The volumes. + type: object + ImportInstanceVolumeDetailItem: + description: Describes an import volume task. + properties: + availabilityZone: + allOf: + - $ref: '#/components/schemas/String' + - description: The Availability Zone where the resulting instance will reside. + bytesConverted: + allOf: + - $ref: '#/components/schemas/Long' + - description: The number of bytes converted so far. + description: + allOf: + - $ref: '#/components/schemas/String' + - description: A description of the task. + image: + allOf: + - $ref: '#/components/schemas/DiskImageDescription' + - description: The image. + status: + allOf: + - $ref: '#/components/schemas/String' + - description: The status of the import of this particular disk image. + statusMessage: + allOf: + - $ref: '#/components/schemas/String' + - description: The status information or errors related to the disk image. + volume: + allOf: + - $ref: '#/components/schemas/DiskImageVolumeDescription' + - description: The volume. + type: object + ImportInstanceVolumeDetailSet: + items: + allOf: + - $ref: '#/components/schemas/ImportInstanceVolumeDetailItem' + - xml: + name: item + type: array + ImportVolumeTaskDetails: + description: Describes an import volume task. + properties: + availabilityZone: + allOf: + - $ref: '#/components/schemas/String' + - description: The Availability Zone where the resulting volume will reside. + bytesConverted: + allOf: + - $ref: '#/components/schemas/Long' + - description: The number of bytes converted so far. + description: + allOf: + - $ref: '#/components/schemas/String' + - description: The description you provided when starting the import volume task. + image: + allOf: + - $ref: '#/components/schemas/DiskImageDescription' + - description: The image. + volume: + allOf: + - $ref: '#/components/schemas/DiskImageVolumeDescription' + - description: The volume. + type: object + Instance: + description: Describes an instance. + properties: + amiLaunchIndex: + allOf: + - $ref: '#/components/schemas/Integer' + - description: The AMI launch index, which can be used to find this instance in the launch group. + architecture: + allOf: + - $ref: '#/components/schemas/ArchitectureValues' + - description: The architecture of the image. + blockDeviceMapping: + allOf: + - $ref: '#/components/schemas/InstanceBlockDeviceMappingList' + - description: Any block device mapping entries for the instance. + bootMode: + allOf: + - $ref: '#/components/schemas/BootModeValues' + - description: The boot mode of the instance. For more information, see Boot modes in the Amazon EC2 User Guide. + capacityReservationId: + allOf: + - $ref: '#/components/schemas/String' + - description: The ID of the Capacity Reservation. + capacityReservationSpecification: + allOf: + - $ref: '#/components/schemas/CapacityReservationSpecificationResponse' + - description: Information about the Capacity Reservation targeting option. + clientToken: + allOf: + - $ref: '#/components/schemas/String' + - description: The idempotency token you provided when you launched the instance, if applicable. + cpuOptions: + allOf: + - $ref: '#/components/schemas/CpuOptions' + - description: The CPU options for the instance. + dnsName: + allOf: + - $ref: '#/components/schemas/String' + - description: (IPv4 only) The public DNS name assigned to the instance. This name is not available until the instance enters the running state. For EC2-VPC, this name is only available if you've enabled DNS hostnames for your VPC. + ebsOptimized: + allOf: + - $ref: '#/components/schemas/Boolean' + - description: Indicates whether the instance is optimized for Amazon EBS I/O. This optimization provides dedicated throughput to Amazon EBS and an optimized configuration stack to provide optimal I/O performance. This optimization isn't available with all instance types. Additional usage charges apply when using an EBS Optimized instance. + elasticGpuAssociationSet: + allOf: + - $ref: '#/components/schemas/ElasticGpuAssociationList' + - description: The Elastic GPU associated with the instance. + elasticInferenceAcceleratorAssociationSet: + allOf: + - $ref: '#/components/schemas/ElasticInferenceAcceleratorAssociationList' + - description: ' The elastic inference accelerator associated with the instance.' + enaSupport: + allOf: + - $ref: '#/components/schemas/Boolean' + - description: Specifies whether enhanced networking with ENA is enabled. + enclaveOptions: + allOf: + - $ref: '#/components/schemas/EnclaveOptions' + - description: Indicates whether the instance is enabled for Amazon Web Services Nitro Enclaves. + groupSet: + allOf: + - $ref: '#/components/schemas/GroupIdentifierList' + - description: The security groups for the instance. + hibernationOptions: + allOf: + - $ref: '#/components/schemas/HibernationOptions' + - description: Indicates whether the instance is enabled for hibernation. + hypervisor: + allOf: + - $ref: '#/components/schemas/HypervisorType' + - description: The hypervisor type of the instance. The value xen is used for both Xen and Nitro hypervisors. + iamInstanceProfile: + allOf: + - $ref: '#/components/schemas/IamInstanceProfile' + - description: The IAM instance profile associated with the instance, if applicable. + imageId: + allOf: + - $ref: '#/components/schemas/String' + - description: The ID of the AMI used to launch the instance. + instanceId: + allOf: + - $ref: '#/components/schemas/String' + - description: The ID of the instance. + instanceLifecycle: + allOf: + - $ref: '#/components/schemas/InstanceLifecycleType' + - description: Indicates whether this is a Spot Instance or a Scheduled Instance. + instanceState: + allOf: + - $ref: '#/components/schemas/InstanceState' + - description: The current state of the instance. + instanceType: + allOf: + - $ref: '#/components/schemas/InstanceType' + - description: The instance type. + ipAddress: + allOf: + - $ref: '#/components/schemas/String' + - description:

The public IPv4 address, or the Carrier IP address assigned to the instance, if applicable.

A Carrier IP address only applies to an instance launched in a subnet associated with a Wavelength Zone.

+ ipv6Address: + allOf: + - $ref: '#/components/schemas/String' + - description: The IPv6 address assigned to the instance. + kernelId: + allOf: + - $ref: '#/components/schemas/String' + - description: The kernel associated with this instance, if applicable. + keyName: + allOf: + - $ref: '#/components/schemas/String' + - description: The name of the key pair, if this instance was launched with an associated key pair. + launchTime: + allOf: + - $ref: '#/components/schemas/DateTime' + - description: The time the instance was launched. + licenseSet: + allOf: + - $ref: '#/components/schemas/LicenseList' + - description: The license configurations for the instance. + maintenanceOptions: + allOf: + - $ref: '#/components/schemas/InstanceMaintenanceOptions' + - description: Provides information on the recovery and maintenance options of your instance. + metadataOptions: + allOf: + - $ref: '#/components/schemas/InstanceMetadataOptionsResponse' + - description: The metadata options for the instance. + monitoring: + allOf: + - $ref: '#/components/schemas/Monitoring' + - description: The monitoring for the instance. + networkInterfaceSet: + allOf: + - $ref: '#/components/schemas/InstanceNetworkInterfaceList' + - description: '[EC2-VPC] The network interfaces for the instance.' + outpostArn: + allOf: + - $ref: '#/components/schemas/String' + - description: The Amazon Resource Name (ARN) of the Outpost. + placement: + allOf: + - $ref: '#/components/schemas/Placement' + - description: The location where the instance launched, if applicable. + platform: + allOf: + - $ref: '#/components/schemas/PlatformValues' + - description: The value is Windows for Windows instances; otherwise blank. + platformDetails: + allOf: + - $ref: '#/components/schemas/String' + - description: The platform details value for the instance. For more information, see AMI billing information fields in the Amazon EC2 User Guide. + privateDnsName: + allOf: + - $ref: '#/components/schemas/String' + - description:

(IPv4 only) The private DNS hostname name assigned to the instance. This DNS hostname can only be used inside the Amazon EC2 network. This name is not available until the instance enters the running state.

[EC2-VPC] The Amazon-provided DNS server resolves Amazon-provided private DNS hostnames if you've enabled DNS resolution and DNS hostnames in your VPC. If you are not using the Amazon-provided DNS server in your VPC, your custom domain name servers must resolve the hostname as appropriate.

+ privateDnsNameOptions: + allOf: + - $ref: '#/components/schemas/PrivateDnsNameOptionsResponse' + - description: The options for the instance hostname. + privateIpAddress: + allOf: + - $ref: '#/components/schemas/String' + - description: The private IPv4 address assigned to the instance. + productCodes: + allOf: + - $ref: '#/components/schemas/ProductCodeList' + - description: The product codes attached to this instance, if applicable. + ramdiskId: + allOf: + - $ref: '#/components/schemas/String' + - description: The RAM disk associated with this instance, if applicable. + reason: + allOf: + - $ref: '#/components/schemas/String' + - description: The reason for the most recent state transition. This might be an empty string. + rootDeviceName: + allOf: + - $ref: '#/components/schemas/String' + - description: The device name of the root device volume (for example, /dev/sda1). + rootDeviceType: + allOf: + - $ref: '#/components/schemas/DeviceType' + - description: The root device type used by the AMI. The AMI can use an EBS volume or an instance store volume. + sourceDestCheck: + allOf: + - $ref: '#/components/schemas/Boolean' + - description: Indicates whether source/destination checking is enabled. + spotInstanceRequestId: + allOf: + - $ref: '#/components/schemas/String' + - description: If the request is a Spot Instance request, the ID of the request. + sriovNetSupport: + allOf: + - $ref: '#/components/schemas/String' + - description: Specifies whether enhanced networking with the Intel 82599 Virtual Function interface is enabled. + stateReason: + allOf: + - $ref: '#/components/schemas/StateReason' + - description: The reason for the most recent state transition. + subnetId: + allOf: + - $ref: '#/components/schemas/String' + - description: '[EC2-VPC] The ID of the subnet in which the instance is running.' + tagSet: + allOf: + - $ref: '#/components/schemas/TagList' + - description: Any tags assigned to the instance. + tpmSupport: + allOf: + - $ref: '#/components/schemas/String' + - description: If the instance is configured for NitroTPM support, the value is v2.0. For more information, see NitroTPM in the Amazon EC2 User Guide. + usageOperation: + allOf: + - $ref: '#/components/schemas/String' + - description: The usage operation value for the instance. For more information, see AMI billing information fields in the Amazon EC2 User Guide. + usageOperationUpdateTime: + allOf: + - $ref: '#/components/schemas/MillisecondDateTime' + - description: The time that the usage operation was last updated. + virtualizationType: + allOf: + - $ref: '#/components/schemas/VirtualizationType' + - description: The virtualization type of the instance. + vpcId: + allOf: + - $ref: '#/components/schemas/String' + - description: '[EC2-VPC] The ID of the VPC in which the instance is running.' + type: object + InstanceAutoRecoveryState: + enum: + - disabled + - default + type: string + InstanceBlockDeviceMapping: + description: Describes a block device mapping. + properties: + deviceName: + allOf: + - $ref: '#/components/schemas/String' + - description: The device name (for example, /dev/sdh or xvdh). + ebs: + allOf: + - $ref: '#/components/schemas/EbsInstanceBlockDevice' + - description: Parameters used to automatically set up EBS volumes when the instance is launched. + type: object + InstanceBlockDeviceMappingList: + items: + allOf: + - $ref: '#/components/schemas/InstanceBlockDeviceMapping' + - xml: + name: item + type: array + InstanceId: + type: string + InstanceIdStringList: + items: + allOf: + - $ref: '#/components/schemas/InstanceId' + - xml: + name: InstanceId + type: array + InstanceIpv4Prefix: + description: Information about an IPv4 prefix. + properties: + ipv4Prefix: + allOf: + - $ref: '#/components/schemas/String' + - description: One or more IPv4 prefixes assigned to the network interface. + type: object + InstanceIpv4PrefixList: + items: + allOf: + - $ref: '#/components/schemas/InstanceIpv4Prefix' + - xml: + name: item + type: array + InstanceIpv6Address: + description: Describes an IPv6 address. + properties: + ipv6Address: + allOf: + - $ref: '#/components/schemas/String' + - description: The IPv6 address. + type: object + InstanceIpv6AddressList: + items: + allOf: + - $ref: '#/components/schemas/InstanceIpv6Address' + - xml: + name: item + type: array + InstanceIpv6Prefix: + description: Information about an IPv6 prefix. + properties: + ipv6Prefix: + allOf: + - $ref: '#/components/schemas/String' + - description: One or more IPv6 prefixes assigned to the network interface. + type: object + InstanceIpv6PrefixList: + items: + allOf: + - $ref: '#/components/schemas/InstanceIpv6Prefix' + - xml: + name: item + type: array + InstanceLifecycleType: + enum: + - spot + - scheduled + type: string + InstanceList: + items: + allOf: + - $ref: '#/components/schemas/Instance' + - xml: + name: item + type: array + InstanceMaintenanceOptions: + description: The maintenance options for the instance. + properties: + autoRecovery: + allOf: + - $ref: '#/components/schemas/InstanceAutoRecoveryState' + - description: Provides information on the current automatic recovery behavior of your instance. + type: object + InstanceMaintenanceOptionsRequest: + description: The maintenance options for the instance. + properties: + undefined: + allOf: + - $ref: '#/components/schemas/InstanceAutoRecoveryState' + - description: Disables the automatic recovery behavior of your instance or sets it to default. For more information, see Simplified automatic recovery. + type: object + InstanceMetadataEndpointState: + enum: + - disabled + - enabled + type: string + InstanceMetadataOptionsResponse: + description: The metadata options for the instance. + properties: + httpEndpoint: + allOf: + - $ref: '#/components/schemas/InstanceMetadataEndpointState' + - description:

Indicates whether the HTTP metadata endpoint on your instances is enabled or disabled.

If the value is disabled, you cannot access your instance metadata.

+ httpProtocolIpv6: + allOf: + - $ref: '#/components/schemas/InstanceMetadataProtocolState' + - description: Indicates whether the IPv6 endpoint for the instance metadata service is enabled or disabled. + httpPutResponseHopLimit: + allOf: + - $ref: '#/components/schemas/Integer' + - description: '

The desired HTTP PUT response hop limit for instance metadata requests. The larger the number, the further instance metadata requests can travel.

Default: 1

Possible values: Integers from 1 to 64

' + httpTokens: + allOf: + - $ref: '#/components/schemas/HttpTokensState' + - description: '

The state of token usage for your instance metadata requests.

If the state is optional, you can choose to retrieve instance metadata with or without a signed token header on your request. If you retrieve the IAM role credentials without a token, the version 1.0 role credentials are returned. If you retrieve the IAM role credentials using a valid signed token, the version 2.0 role credentials are returned.

If the state is required, you must send a signed token header with any instance metadata retrieval requests. In this state, retrieving the IAM role credential always returns the version 2.0 credentials; the version 1.0 credentials are not available.

Default: optional

' + instanceMetadataTags: + allOf: + - $ref: '#/components/schemas/InstanceMetadataTagsState' + - description: Indicates whether access to instance tags from the instance metadata is enabled or disabled. For more information, see Work with instance tags using the instance metadata. + state: + allOf: + - $ref: '#/components/schemas/InstanceMetadataOptionsState' + - description:

The state of the metadata option changes.

pending - The metadata options are being updated and the instance is not ready to process metadata traffic with the new selection.

applied - The metadata options have been successfully applied on the instance.

+ type: object + InstanceMetadataOptionsState: + enum: + - pending + - applied + type: string + InstanceMetadataProtocolState: + enum: + - disabled + - enabled + type: string + InstanceMetadataTagsState: + enum: + - disabled + - enabled + type: string + InstanceMonitoring: + description: Describes the monitoring of an instance. + properties: + instanceId: + allOf: + - $ref: '#/components/schemas/String' + - description: The ID of the instance. + monitoring: + allOf: + - $ref: '#/components/schemas/Monitoring' + - description: The monitoring for the instance. + type: object + InstanceMonitoringList: + items: + allOf: + - $ref: '#/components/schemas/InstanceMonitoring' + - xml: + name: item + type: array + InstanceNetworkInterface: + description: Describes a network interface. + properties: + association: + allOf: + - $ref: '#/components/schemas/InstanceNetworkInterfaceAssociation' + - description: The association information for an Elastic IPv4 associated with the network interface. + attachment: + allOf: + - $ref: '#/components/schemas/InstanceNetworkInterfaceAttachment' + - description: The network interface attachment. + description: + allOf: + - $ref: '#/components/schemas/String' + - description: The description. + groupSet: + allOf: + - $ref: '#/components/schemas/GroupIdentifierList' + - description: One or more security groups. + interfaceType: + allOf: + - $ref: '#/components/schemas/String' + - description: '

The type of network interface.

Valid values: interface | efa | trunk

' + ipv4PrefixSet: + allOf: + - $ref: '#/components/schemas/InstanceIpv4PrefixList' + - description: The IPv4 delegated prefixes that are assigned to the network interface. + ipv6AddressesSet: + allOf: + - $ref: '#/components/schemas/InstanceIpv6AddressList' + - description: One or more IPv6 addresses associated with the network interface. + ipv6PrefixSet: + allOf: + - $ref: '#/components/schemas/InstanceIpv6PrefixList' + - description: The IPv6 delegated prefixes that are assigned to the network interface. + macAddress: + allOf: + - $ref: '#/components/schemas/String' + - description: The MAC address. + networkInterfaceId: + allOf: + - $ref: '#/components/schemas/String' + - description: The ID of the network interface. + ownerId: + allOf: + - $ref: '#/components/schemas/String' + - description: The ID of the Amazon Web Services account that created the network interface. + privateDnsName: + allOf: + - $ref: '#/components/schemas/String' + - description: The private DNS name. + privateIpAddress: + allOf: + - $ref: '#/components/schemas/String' + - description: The IPv4 address of the network interface within the subnet. + privateIpAddressesSet: + allOf: + - $ref: '#/components/schemas/InstancePrivateIpAddressList' + - description: One or more private IPv4 addresses associated with the network interface. + sourceDestCheck: + allOf: + - $ref: '#/components/schemas/Boolean' + - description: Indicates whether source/destination checking is enabled. + status: + allOf: + - $ref: '#/components/schemas/NetworkInterfaceStatus' + - description: The status of the network interface. + subnetId: + allOf: + - $ref: '#/components/schemas/String' + - description: The ID of the subnet. + vpcId: + allOf: + - $ref: '#/components/schemas/String' + - description: The ID of the VPC. + type: object + InstanceNetworkInterfaceAssociation: + description: Describes association information for an Elastic IP address (IPv4). + properties: + carrierIp: + allOf: + - $ref: '#/components/schemas/String' + - description: The carrier IP address associated with the network interface. + customerOwnedIp: + allOf: + - $ref: '#/components/schemas/String' + - description: The customer-owned IP address associated with the network interface. + ipOwnerId: + allOf: + - $ref: '#/components/schemas/String' + - description: The ID of the owner of the Elastic IP address. + publicDnsName: + allOf: + - $ref: '#/components/schemas/String' + - description: The public DNS name. + publicIp: + allOf: + - $ref: '#/components/schemas/String' + - description: The public IP address or Elastic IP address bound to the network interface. + type: object + InstanceNetworkInterfaceAttachment: + description: Describes a network interface attachment. + properties: + attachTime: + allOf: + - $ref: '#/components/schemas/DateTime' + - description: The time stamp when the attachment initiated. + attachmentId: + allOf: + - $ref: '#/components/schemas/String' + - description: The ID of the network interface attachment. + deleteOnTermination: + allOf: + - $ref: '#/components/schemas/Boolean' + - description: Indicates whether the network interface is deleted when the instance is terminated. + deviceIndex: + allOf: + - $ref: '#/components/schemas/Integer' + - description: The index of the device on the instance for the network interface attachment. + networkCardIndex: + allOf: + - $ref: '#/components/schemas/Integer' + - description: The index of the network card. + status: + allOf: + - $ref: '#/components/schemas/AttachmentStatus' + - description: The attachment state. + type: object + InstanceNetworkInterfaceList: + items: + allOf: + - $ref: '#/components/schemas/InstanceNetworkInterface' + - xml: + name: item + type: array + InstancePrivateIpAddress: + description: Describes a private IPv4 address. + properties: + association: + allOf: + - $ref: '#/components/schemas/InstanceNetworkInterfaceAssociation' + - description: The association information for an Elastic IP address for the network interface. + primary: + allOf: + - $ref: '#/components/schemas/Boolean' + - description: Indicates whether this IPv4 address is the primary private IP address of the network interface. + privateDnsName: + allOf: + - $ref: '#/components/schemas/String' + - description: The private IPv4 DNS name. + privateIpAddress: + allOf: + - $ref: '#/components/schemas/String' + - description: The private IPv4 address of the network interface. + type: object + InstancePrivateIpAddressList: + items: + allOf: + - $ref: '#/components/schemas/InstancePrivateIpAddress' + - xml: + name: item + type: array + InstanceState: + description: Describes the current state of an instance. + properties: + code: + allOf: + - $ref: '#/components/schemas/Integer' + - description: '

The state of the instance as a 16-bit unsigned integer.

The high byte is all of the bits between 2^8 and (2^16)-1, which equals decimal values between 256 and 65,535. These numerical values are used for internal purposes and should be ignored.

The low byte is all of the bits between 2^0 and (2^8)-1, which equals decimal values between 0 and 255.

The valid values for instance-state-code will all be in the range of the low byte and they are:

  • 0 : pending

  • 16 : running

  • 32 : shutting-down

  • 48 : terminated

  • 64 : stopping

  • 80 : stopped

You can ignore the high byte value by zeroing out all of the bits above 2^8 or 256 in decimal.

' + name: + allOf: + - $ref: '#/components/schemas/InstanceStateName' + - description: The current state of the instance. + type: object + InstanceStateChange: + description: Describes an instance state change. + properties: + currentState: + allOf: + - $ref: '#/components/schemas/InstanceState' + - description: The current state of the instance. + instanceId: + allOf: + - $ref: '#/components/schemas/String' + - description: The ID of the instance. + previousState: + allOf: + - $ref: '#/components/schemas/InstanceState' + - description: The previous state of the instance. + type: object + InstanceStateChangeList: + items: + allOf: + - $ref: '#/components/schemas/InstanceStateChange' + - xml: + name: item + type: array + InstanceStateName: + enum: + - pending + - running + - shutting-down + - terminated + - stopping + - stopped + type: string + InstanceType: + enum: + - a1.medium + - a1.large + - a1.xlarge + - a1.2xlarge + - a1.4xlarge + - a1.metal + - c1.medium + - c1.xlarge + - c3.large + - c3.xlarge + - c3.2xlarge + - c3.4xlarge + - c3.8xlarge + - c4.large + - c4.xlarge + - c4.2xlarge + - c4.4xlarge + - c4.8xlarge + - c5.large + - c5.xlarge + - c5.2xlarge + - c5.4xlarge + - c5.9xlarge + - c5.12xlarge + - c5.18xlarge + - c5.24xlarge + - c5.metal + - c5a.large + - c5a.xlarge + - c5a.2xlarge + - c5a.4xlarge + - c5a.8xlarge + - c5a.12xlarge + - c5a.16xlarge + - c5a.24xlarge + - c5ad.large + - c5ad.xlarge + - c5ad.2xlarge + - c5ad.4xlarge + - c5ad.8xlarge + - c5ad.12xlarge + - c5ad.16xlarge + - c5ad.24xlarge + - c5d.large + - c5d.xlarge + - c5d.2xlarge + - c5d.4xlarge + - c5d.9xlarge + - c5d.12xlarge + - c5d.18xlarge + - c5d.24xlarge + - c5d.metal + - c5n.large + - c5n.xlarge + - c5n.2xlarge + - c5n.4xlarge + - c5n.9xlarge + - c5n.18xlarge + - c5n.metal + - c6g.medium + - c6g.large + - c6g.xlarge + - c6g.2xlarge + - c6g.4xlarge + - c6g.8xlarge + - c6g.12xlarge + - c6g.16xlarge + - c6g.metal + - c6gd.medium + - c6gd.large + - c6gd.xlarge + - c6gd.2xlarge + - c6gd.4xlarge + - c6gd.8xlarge + - c6gd.12xlarge + - c6gd.16xlarge + - c6gd.metal + - c6gn.medium + - c6gn.large + - c6gn.xlarge + - c6gn.2xlarge + - c6gn.4xlarge + - c6gn.8xlarge + - c6gn.12xlarge + - c6gn.16xlarge + - c6i.large + - c6i.xlarge + - c6i.2xlarge + - c6i.4xlarge + - c6i.8xlarge + - c6i.12xlarge + - c6i.16xlarge + - c6i.24xlarge + - c6i.32xlarge + - c6i.metal + - cc1.4xlarge + - cc2.8xlarge + - cg1.4xlarge + - cr1.8xlarge + - d2.xlarge + - d2.2xlarge + - d2.4xlarge + - d2.8xlarge + - d3.xlarge + - d3.2xlarge + - d3.4xlarge + - d3.8xlarge + - d3en.xlarge + - d3en.2xlarge + - d3en.4xlarge + - d3en.6xlarge + - d3en.8xlarge + - d3en.12xlarge + - dl1.24xlarge + - f1.2xlarge + - f1.4xlarge + - f1.16xlarge + - g2.2xlarge + - g2.8xlarge + - g3.4xlarge + - g3.8xlarge + - g3.16xlarge + - g3s.xlarge + - g4ad.xlarge + - g4ad.2xlarge + - g4ad.4xlarge + - g4ad.8xlarge + - g4ad.16xlarge + - g4dn.xlarge + - g4dn.2xlarge + - g4dn.4xlarge + - g4dn.8xlarge + - g4dn.12xlarge + - g4dn.16xlarge + - g4dn.metal + - g5.xlarge + - g5.2xlarge + - g5.4xlarge + - g5.8xlarge + - g5.12xlarge + - g5.16xlarge + - g5.24xlarge + - g5.48xlarge + - g5g.xlarge + - g5g.2xlarge + - g5g.4xlarge + - g5g.8xlarge + - g5g.16xlarge + - g5g.metal + - hi1.4xlarge + - hpc6a.48xlarge + - hs1.8xlarge + - h1.2xlarge + - h1.4xlarge + - h1.8xlarge + - h1.16xlarge + - i2.xlarge + - i2.2xlarge + - i2.4xlarge + - i2.8xlarge + - i3.large + - i3.xlarge + - i3.2xlarge + - i3.4xlarge + - i3.8xlarge + - i3.16xlarge + - i3.metal + - i3en.large + - i3en.xlarge + - i3en.2xlarge + - i3en.3xlarge + - i3en.6xlarge + - i3en.12xlarge + - i3en.24xlarge + - i3en.metal + - im4gn.large + - im4gn.xlarge + - im4gn.2xlarge + - im4gn.4xlarge + - im4gn.8xlarge + - im4gn.16xlarge + - inf1.xlarge + - inf1.2xlarge + - inf1.6xlarge + - inf1.24xlarge + - is4gen.medium + - is4gen.large + - is4gen.xlarge + - is4gen.2xlarge + - is4gen.4xlarge + - is4gen.8xlarge + - m1.small + - m1.medium + - m1.large + - m1.xlarge + - m2.xlarge + - m2.2xlarge + - m2.4xlarge + - m3.medium + - m3.large + - m3.xlarge + - m3.2xlarge + - m4.large + - m4.xlarge + - m4.2xlarge + - m4.4xlarge + - m4.10xlarge + - m4.16xlarge + - m5.large + - m5.xlarge + - m5.2xlarge + - m5.4xlarge + - m5.8xlarge + - m5.12xlarge + - m5.16xlarge + - m5.24xlarge + - m5.metal + - m5a.large + - m5a.xlarge + - m5a.2xlarge + - m5a.4xlarge + - m5a.8xlarge + - m5a.12xlarge + - m5a.16xlarge + - m5a.24xlarge + - m5ad.large + - m5ad.xlarge + - m5ad.2xlarge + - m5ad.4xlarge + - m5ad.8xlarge + - m5ad.12xlarge + - m5ad.16xlarge + - m5ad.24xlarge + - m5d.large + - m5d.xlarge + - m5d.2xlarge + - m5d.4xlarge + - m5d.8xlarge + - m5d.12xlarge + - m5d.16xlarge + - m5d.24xlarge + - m5d.metal + - m5dn.large + - m5dn.xlarge + - m5dn.2xlarge + - m5dn.4xlarge + - m5dn.8xlarge + - m5dn.12xlarge + - m5dn.16xlarge + - m5dn.24xlarge + - m5dn.metal + - m5n.large + - m5n.xlarge + - m5n.2xlarge + - m5n.4xlarge + - m5n.8xlarge + - m5n.12xlarge + - m5n.16xlarge + - m5n.24xlarge + - m5n.metal + - m5zn.large + - m5zn.xlarge + - m5zn.2xlarge + - m5zn.3xlarge + - m5zn.6xlarge + - m5zn.12xlarge + - m5zn.metal + - m6a.large + - m6a.xlarge + - m6a.2xlarge + - m6a.4xlarge + - m6a.8xlarge + - m6a.12xlarge + - m6a.16xlarge + - m6a.24xlarge + - m6a.32xlarge + - m6a.48xlarge + - m6g.metal + - m6g.medium + - m6g.large + - m6g.xlarge + - m6g.2xlarge + - m6g.4xlarge + - m6g.8xlarge + - m6g.12xlarge + - m6g.16xlarge + - m6gd.metal + - m6gd.medium + - m6gd.large + - m6gd.xlarge + - m6gd.2xlarge + - m6gd.4xlarge + - m6gd.8xlarge + - m6gd.12xlarge + - m6gd.16xlarge + - m6i.large + - m6i.xlarge + - m6i.2xlarge + - m6i.4xlarge + - m6i.8xlarge + - m6i.12xlarge + - m6i.16xlarge + - m6i.24xlarge + - m6i.32xlarge + - m6i.metal + - mac1.metal + - p2.xlarge + - p2.8xlarge + - p2.16xlarge + - p3.2xlarge + - p3.8xlarge + - p3.16xlarge + - p3dn.24xlarge + - p4d.24xlarge + - r3.large + - r3.xlarge + - r3.2xlarge + - r3.4xlarge + - r3.8xlarge + - r4.large + - r4.xlarge + - r4.2xlarge + - r4.4xlarge + - r4.8xlarge + - r4.16xlarge + - r5.large + - r5.xlarge + - r5.2xlarge + - r5.4xlarge + - r5.8xlarge + - r5.12xlarge + - r5.16xlarge + - r5.24xlarge + - r5.metal + - r5a.large + - r5a.xlarge + - r5a.2xlarge + - r5a.4xlarge + - r5a.8xlarge + - r5a.12xlarge + - r5a.16xlarge + - r5a.24xlarge + - r5ad.large + - r5ad.xlarge + - r5ad.2xlarge + - r5ad.4xlarge + - r5ad.8xlarge + - r5ad.12xlarge + - r5ad.16xlarge + - r5ad.24xlarge + - r5b.large + - r5b.xlarge + - r5b.2xlarge + - r5b.4xlarge + - r5b.8xlarge + - r5b.12xlarge + - r5b.16xlarge + - r5b.24xlarge + - r5b.metal + - r5d.large + - r5d.xlarge + - r5d.2xlarge + - r5d.4xlarge + - r5d.8xlarge + - r5d.12xlarge + - r5d.16xlarge + - r5d.24xlarge + - r5d.metal + - r5dn.large + - r5dn.xlarge + - r5dn.2xlarge + - r5dn.4xlarge + - r5dn.8xlarge + - r5dn.12xlarge + - r5dn.16xlarge + - r5dn.24xlarge + - r5dn.metal + - r5n.large + - r5n.xlarge + - r5n.2xlarge + - r5n.4xlarge + - r5n.8xlarge + - r5n.12xlarge + - r5n.16xlarge + - r5n.24xlarge + - r5n.metal + - r6g.medium + - r6g.large + - r6g.xlarge + - r6g.2xlarge + - r6g.4xlarge + - r6g.8xlarge + - r6g.12xlarge + - r6g.16xlarge + - r6g.metal + - r6gd.medium + - r6gd.large + - r6gd.xlarge + - r6gd.2xlarge + - r6gd.4xlarge + - r6gd.8xlarge + - r6gd.12xlarge + - r6gd.16xlarge + - r6gd.metal + - r6i.large + - r6i.xlarge + - r6i.2xlarge + - r6i.4xlarge + - r6i.8xlarge + - r6i.12xlarge + - r6i.16xlarge + - r6i.24xlarge + - r6i.32xlarge + - r6i.metal + - t1.micro + - t2.nano + - t2.micro + - t2.small + - t2.medium + - t2.large + - t2.xlarge + - t2.2xlarge + - t3.nano + - t3.micro + - t3.small + - t3.medium + - t3.large + - t3.xlarge + - t3.2xlarge + - t3a.nano + - t3a.micro + - t3a.small + - t3a.medium + - t3a.large + - t3a.xlarge + - t3a.2xlarge + - t4g.nano + - t4g.micro + - t4g.small + - t4g.medium + - t4g.large + - t4g.xlarge + - t4g.2xlarge + - u-6tb1.56xlarge + - u-6tb1.112xlarge + - u-9tb1.112xlarge + - u-12tb1.112xlarge + - u-6tb1.metal + - u-9tb1.metal + - u-12tb1.metal + - u-18tb1.metal + - u-24tb1.metal + - vt1.3xlarge + - vt1.6xlarge + - vt1.24xlarge + - x1.16xlarge + - x1.32xlarge + - x1e.xlarge + - x1e.2xlarge + - x1e.4xlarge + - x1e.8xlarge + - x1e.16xlarge + - x1e.32xlarge + - x2iezn.2xlarge + - x2iezn.4xlarge + - x2iezn.6xlarge + - x2iezn.8xlarge + - x2iezn.12xlarge + - x2iezn.metal + - x2gd.medium + - x2gd.large + - x2gd.xlarge + - x2gd.2xlarge + - x2gd.4xlarge + - x2gd.8xlarge + - x2gd.12xlarge + - x2gd.16xlarge + - x2gd.metal + - z1d.large + - z1d.xlarge + - z1d.2xlarge + - z1d.3xlarge + - z1d.6xlarge + - z1d.12xlarge + - z1d.metal + - x2idn.16xlarge + - x2idn.24xlarge + - x2idn.32xlarge + - x2iedn.xlarge + - x2iedn.2xlarge + - x2iedn.4xlarge + - x2iedn.8xlarge + - x2iedn.16xlarge + - x2iedn.24xlarge + - x2iedn.32xlarge + - c6a.large + - c6a.xlarge + - c6a.2xlarge + - c6a.4xlarge + - c6a.8xlarge + - c6a.12xlarge + - c6a.16xlarge + - c6a.24xlarge + - c6a.32xlarge + - c6a.48xlarge + - c6a.metal + - m6a.metal + - i4i.large + - i4i.xlarge + - i4i.2xlarge + - i4i.4xlarge + - i4i.8xlarge + - i4i.16xlarge + - i4i.32xlarge + type: string + Integer: + type: integer + LicenseConfiguration: + description: Describes a license configuration. + properties: + licenseConfigurationArn: + allOf: + - $ref: '#/components/schemas/String' + - description: The Amazon Resource Name (ARN) of the license configuration. + type: object + LicenseList: + items: + allOf: + - $ref: '#/components/schemas/LicenseConfiguration' + - xml: + name: item + type: array + Long: + type: integer + MillisecondDateTime: + format: date-time + type: string + MonitorInstancesRequest: + properties: + InstanceId: + allOf: + - $ref: '#/components/schemas/InstanceIdStringList' + - description: The IDs of the instances. + dryRun: + allOf: + - $ref: '#/components/schemas/Boolean' + - description: Checks whether you have the required permissions for the action, without actually making the request, and provides an error response. If you have the required permissions, the error response is DryRunOperation. Otherwise, it is UnauthorizedOperation. + required: + - InstanceIds + title: MonitorInstancesRequest + type: object + MonitorInstancesResponseWrapper: + properties: + MonitorInstancesResponse: + $ref: '#/components/schemas/MonitorInstancesResult' + type: object + MonitorInstancesResult: + properties: + instancesSet: + allOf: + - $ref: '#/components/schemas/InstanceMonitoringList' + - description: The monitoring information. + type: object + Monitoring: + description: Describes the monitoring of an instance. + properties: + state: + allOf: + - $ref: '#/components/schemas/MonitoringState' + - description: Indicates whether detailed monitoring is enabled. Otherwise, basic monitoring is enabled. + type: object + MonitoringState: + enum: + - disabled + - disabling + - enabled + - pending + type: string + NetworkInterfaceStatus: + enum: + - available + - associated + - attaching + - in-use + - detaching + type: string + Placement: + description: Describes the placement of an instance. + properties: + affinity: + allOf: + - $ref: '#/components/schemas/String' + - description:

The affinity setting for the instance on the Dedicated Host. This parameter is not supported for the ImportInstance command.

This parameter is not supported by CreateFleet.

+ availabilityZone: + allOf: + - $ref: '#/components/schemas/String' + - description:

The Availability Zone of the instance.

If not specified, an Availability Zone will be automatically chosen for you based on the load balancing criteria for the Region.

This parameter is not supported by CreateFleet.

+ groupName: + allOf: + - $ref: '#/components/schemas/PlacementGroupName' + - description: The name of the placement group the instance is in. + hostId: + allOf: + - $ref: '#/components/schemas/String' + - description:

The ID of the Dedicated Host on which the instance resides. This parameter is not supported for the ImportInstance command.

This parameter is not supported by CreateFleet.

+ hostResourceGroupArn: + allOf: + - $ref: '#/components/schemas/String' + - description:

The ARN of the host resource group in which to launch the instances. If you specify a host resource group ARN, omit the Tenancy parameter or set it to host.

This parameter is not supported by CreateFleet.

+ partitionNumber: + allOf: + - $ref: '#/components/schemas/Integer' + - description:

The number of the partition that the instance is in. Valid only if the placement group strategy is set to partition.

This parameter is not supported by CreateFleet.

+ spreadDomain: + allOf: + - $ref: '#/components/schemas/String' + - description:

Reserved for future use.

This parameter is not supported by CreateFleet.

+ tenancy: + allOf: + - $ref: '#/components/schemas/Tenancy' + - description:

The tenancy of the instance (if the instance is running in a VPC). An instance with a tenancy of dedicated runs on single-tenant hardware. The host tenancy is not supported for the ImportInstance command.

This parameter is not supported by CreateFleet.

T3 instances that use the unlimited CPU credit option do not support host tenancy.

+ type: object + PlacementGroupName: + type: string + PlatformValues: + enum: + - Windows + type: string + PrivateDnsNameOptionsResponse: + description: Describes the options for instance hostnames. + properties: + enableResourceNameDnsAAAARecord: + allOf: + - $ref: '#/components/schemas/Boolean' + - description: Indicates whether to respond to DNS queries for instance hostnames with DNS AAAA records. + enableResourceNameDnsARecord: + allOf: + - $ref: '#/components/schemas/Boolean' + - description: Indicates whether to respond to DNS queries for instance hostnames with DNS A records. + hostnameType: + allOf: + - $ref: '#/components/schemas/HostnameType' + - description: The type of hostname to assign to an instance. + type: object + ProductCode: + description: Describes a product code. + properties: + productCode: + allOf: + - $ref: '#/components/schemas/String' + - description: The product code. + type: + allOf: + - $ref: '#/components/schemas/ProductCodeValues' + - description: The type of product code. + type: object + ProductCodeList: + items: + allOf: + - $ref: '#/components/schemas/ProductCode' + - xml: + name: item + type: array + ProductCodeValues: + enum: + - devpay + - marketplace + type: string + RamdiskId: + type: string + RebootInstancesRequest: + properties: + InstanceId: + allOf: + - $ref: '#/components/schemas/InstanceIdStringList' + - description: The instance IDs. + dryRun: + allOf: + - $ref: '#/components/schemas/Boolean' + - description: Checks whether you have the required permissions for the action, without actually making the request, and provides an error response. If you have the required permissions, the error response is DryRunOperation. Otherwise, it is UnauthorizedOperation. + required: + - InstanceIds + title: RebootInstancesRequest + type: object + Reservation: + description: Describes a launch request for one or more instances, and includes owner, requester, and security group information that applies to all instances in the launch request. + example: {} + properties: + groupSet: + allOf: + - $ref: '#/components/schemas/GroupIdentifierList' + - description: '[EC2-Classic only] The security groups.' + instancesSet: + allOf: + - $ref: '#/components/schemas/InstanceList' + - description: The instances. + ownerId: + allOf: + - $ref: '#/components/schemas/String' + - description: The ID of the Amazon Web Services account that owns the reservation. + requesterId: + allOf: + - $ref: '#/components/schemas/String' + - description: The ID of the requester that launched the instances on your behalf (for example, Amazon Web Services Management Console or Auto Scaling). + reservationId: + allOf: + - $ref: '#/components/schemas/String' + - description: The ID of the reservation. + type: object + ReservationList: + items: + allOf: + - $ref: '#/components/schemas/Reservation' + - xml: + name: item + type: array + RunInstancesRequest: + properties: + BlockDeviceMapping: + allOf: + - $ref: '#/components/schemas/Integer' + - description:

[EC2-VPC] The number of IPv6 addresses to associate with the primary network interface. Amazon EC2 chooses the IPv6 addresses from the range of your subnet. You cannot specify this option and the option to assign specific IPv6 addresses in the same request. You can specify this option if you've specified a minimum number of instances to launch.

You cannot specify this option and the network interfaces option in the same request.

+ ElasticInferenceAccelerator: + allOf: + - $ref: '#/components/schemas/ElasticInferenceAccelerators' + - description:

An elastic inference accelerator to associate with the instance. Elastic inference accelerators are a resource you can attach to your Amazon EC2 instances to accelerate your Deep Learning (DL) inference workloads.

You cannot specify accelerators from different generations in the same request.

+ ImageId: + type: string + InstanceType: + type: string + Ipv6Address: + allOf: + - $ref: '#/components/schemas/RamdiskId' + - description:

The ID of the RAM disk to select. Some kernels require additional drivers at launch. Check the kernel requirements for information about whether you need to specify a RAM disk. To find kernel requirements, go to the Amazon Web Services Resource Center and search for the kernel ID.

We recommend that you use PV-GRUB instead of kernels and RAM disks. For more information, see PV-GRUB in the Amazon EC2 User Guide.

+ KeyName: + type: string + LicenseSpecification: + allOf: + - $ref: '#/components/schemas/InstanceMaintenanceOptionsRequest' + - description: The maintenance and recovery options for the instance. + SecurityGroup: + allOf: + - $ref: '#/components/schemas/RunInstancesUserData' + - description: The user data script to make available to the instance. For more information, see Run commands on your Linux instance at launch and Run commands on your Windows instance at launch. If you are using a command line tool, base64-encoding is performed for you, and you can load the text from a file. Otherwise, you must provide base64-encoded text. User data is limited to 16 KB. + SecurityGroupId: + allOf: + - $ref: '#/components/schemas/SecurityGroupIdStringList' + - description:

The IDs of the security groups. You can create a security group using CreateSecurityGroup.

If you specify a network interface, you must specify any security groups as part of the network interface.

+ TagSpecification: + allOf: + - $ref: '#/components/schemas/HibernationOptionsRequest' + - description:

Indicates whether an instance is enabled for hibernation. For more information, see Hibernate your instance in the Amazon EC2 User Guide.

You can't enable hibernation and Amazon Web Services Nitro Enclaves on the same instance.

+ associate_public_ip_address: + type: string + name: + type: string + security_group_id: + type: string + subnet_id: + type: string + required: [] + title: RunInstancesRequest + type: object + RunInstancesResponseWrapper: + properties: + RunInstancesResponse: + $ref: '#/components/schemas/Reservation' + type: object + RunInstancesUserData: + format: password + type: string + S3Storage: + description: Describes the storage parameters for Amazon S3 and Amazon S3 buckets for an instance store-backed AMI. + properties: + bucket: + allOf: + - $ref: '#/components/schemas/String' + - description: The bucket in which to store the AMI. You can specify a bucket that you already own or a new bucket that Amazon EC2 creates on your behalf. If you specify a bucket that belongs to someone else, Amazon EC2 returns an error. + prefix: + allOf: + - $ref: '#/components/schemas/String' + - description: The beginning of the file name of the AMI. + undefined: + allOf: + - $ref: '#/components/schemas/String' + - description: The access key ID of the owner of the bucket. Before you specify a value for your access key ID, review and follow the guidance in Best Practices for Managing Amazon Web Services Access Keys. + uploadPolicy: + allOf: + - $ref: '#/components/schemas/Blob' + - description: An Amazon S3 upload policy that gives Amazon EC2 permission to upload items into Amazon S3 on your behalf. + uploadPolicySignature: + allOf: + - $ref: '#/components/schemas/String' + - description: The signature of the JSON document. + type: object + SecurityGroupId: + type: string + SecurityGroupIdStringList: + items: + allOf: + - $ref: '#/components/schemas/SecurityGroupId' + - xml: + name: SecurityGroupId + type: array + SecurityGroupName: + type: string + SecurityGroupStringList: + items: + allOf: + - $ref: '#/components/schemas/SecurityGroupName' + - xml: + name: SecurityGroup + type: array + ShutdownBehavior: + enum: + - stop + - terminate + type: string + StartInstancesRequest: + properties: + InstanceId: + $ref: '#/components/schemas/InstanceIdStringList' + additionalInfo: + $ref: '#/components/schemas/String' + dryRun: + allOf: + - $ref: '#/components/schemas/Boolean' + - description: Checks whether you have the required permissions for the action, without actually making the request, and provides an error response. If you have the required permissions, the error response is DryRunOperation. Otherwise, it is UnauthorizedOperation. + required: + - InstanceIds + title: StartInstancesRequest + type: object + StartInstancesResponseWrapper: + properties: + StartInstancesResponse: + $ref: '#/components/schemas/StartInstancesResult' + type: object + StartInstancesResult: + example: + StartingInstances: + - CurrentState: + Code: 0 + Name: pending + InstanceId: i-1234567890abcdef0 + PreviousState: + Code: 80 + Name: stopped + properties: + instancesSet: + allOf: + - $ref: '#/components/schemas/InstanceStateChangeList' + - description: Information about the started instances. + type: object + StateReason: + description: Describes a state change. + properties: + code: + allOf: + - $ref: '#/components/schemas/String' + - description: The reason code for the state change. + message: + allOf: + - $ref: '#/components/schemas/String' + - description: '

The message for the state change.

  • Server.InsufficientInstanceCapacity: There was insufficient capacity available to satisfy the launch request.

  • Server.InternalError: An internal error caused the instance to terminate during launch.

  • Server.ScheduledStop: The instance was stopped due to a scheduled retirement.

  • Server.SpotInstanceShutdown: The instance was stopped because the number of Spot requests with a maximum price equal to or higher than the Spot price exceeded available capacity or because of an increase in the Spot price.

  • Server.SpotInstanceTermination: The instance was terminated because the number of Spot requests with a maximum price equal to or higher than the Spot price exceeded available capacity or because of an increase in the Spot price.

  • Client.InstanceInitiatedShutdown: The instance was shut down using the shutdown -h command from the instance.

  • Client.InstanceTerminated: The instance was terminated or rebooted during AMI creation.

  • Client.InternalError: A client error caused the instance to terminate during launch.

  • Client.InvalidSnapshot.NotFound: The specified snapshot was not found.

  • Client.UserInitiatedHibernate: Hibernation was initiated on the instance.

  • Client.UserInitiatedShutdown: The instance was shut down using the Amazon EC2 API.

  • Client.VolumeLimitExceeded: The limit on the number of EBS volumes or total storage was exceeded. Decrease usage or request an increase in your account limits.

' + type: object + StopInstancesRequest: + properties: + InstanceId: + allOf: + - $ref: '#/components/schemas/Boolean' + - description: '

Hibernates the instance if the instance was enabled for hibernation at launch. If the instance cannot hibernate successfully, a normal shutdown occurs. For more information, see Hibernate your instance in the Amazon EC2 User Guide.

Default: false

' + dryRun: + allOf: + - $ref: '#/components/schemas/Boolean' + - description: Checks whether you have the required permissions for the action, without actually making the request, and provides an error response. If you have the required permissions, the error response is DryRunOperation. Otherwise, it is UnauthorizedOperation. + force: + allOf: + - $ref: '#/components/schemas/Boolean' + - description: '

Forces the instances to stop. The instances do not have an opportunity to flush file system caches or file system metadata. If you use this option, you must perform file system check and repair procedures. This option is not recommended for Windows instances.

Default: false

' + required: + - InstanceIds + title: StopInstancesRequest + type: object + StopInstancesResponseWrapper: + properties: + StopInstancesResponse: + $ref: '#/components/schemas/StopInstancesResult' + type: object + StopInstancesResult: + example: + StoppingInstances: + - CurrentState: + Code: 64 + Name: stopping + InstanceId: i-1234567890abcdef0 + PreviousState: + Code: 16 + Name: running + properties: + instancesSet: + allOf: + - $ref: '#/components/schemas/InstanceStateChangeList' + - description: Information about the stopped instances. + type: object + Storage: + description: Describes the storage location for an instance store-backed AMI. + properties: + undefined: + allOf: + - $ref: '#/components/schemas/S3Storage' + - description: An Amazon S3 storage location. + type: object + String: + type: string + SubnetId: + type: string + Tag: + description: Describes a tag. + properties: + key: + allOf: + - $ref: '#/components/schemas/String' + - description: '

The key of the tag.

Constraints: Tag keys are case-sensitive and accept a maximum of 127 Unicode characters. May not begin with aws:.

' + value: + allOf: + - $ref: '#/components/schemas/String' + - description: '

The value of the tag.

Constraints: Tag values are case-sensitive and accept a maximum of 256 Unicode characters.

' + type: object + TagList: + items: + allOf: + - $ref: '#/components/schemas/Tag' + - xml: + name: item + type: array + Tenancy: + enum: + - default + - dedicated + - host + type: string + TerminateInstancesRequest: + properties: + InstanceId: + allOf: + - $ref: '#/components/schemas/InstanceIdStringList' + - description: '

The IDs of the instances.

Constraints: Up to 1000 instance IDs. We recommend breaking up this request into smaller batches.

' + dryRun: + allOf: + - $ref: '#/components/schemas/Boolean' + - description: Checks whether you have the required permissions for the action, without actually making the request, and provides an error response. If you have the required permissions, the error response is DryRunOperation. Otherwise, it is UnauthorizedOperation. + required: + - InstanceId + title: TerminateInstancesRequest + type: object + TerminateInstancesResponseWrapper: + properties: + TerminateInstancesResponse: + $ref: '#/components/schemas/TerminateInstancesResult' + type: object + TerminateInstancesResult: + example: + TerminatingInstances: + - CurrentState: + Code: 32 + Name: shutting-down + InstanceId: i-1234567890abcdef0 + PreviousState: + Code: 16 + Name: running + properties: + instancesSet: + allOf: + - $ref: '#/components/schemas/InstanceStateChangeList' + - description: Information about the terminated instances. + type: object + UnmonitorInstancesRequest: + properties: + InstanceId: + allOf: + - $ref: '#/components/schemas/InstanceIdStringList' + - description: The IDs of the instances. + dryRun: + allOf: + - $ref: '#/components/schemas/Boolean' + - description: Checks whether you have the required permissions for the action, without actually making the request, and provides an error response. If you have the required permissions, the error response is DryRunOperation. Otherwise, it is UnauthorizedOperation. + required: + - InstanceIds + title: UnmonitorInstancesRequest + type: object + UnmonitorInstancesResponseWrapper: + properties: + UnmonitorInstancesResponse: + $ref: '#/components/schemas/UnmonitorInstancesResult' + type: object + UnmonitorInstancesResult: + properties: + instancesSet: + allOf: + - $ref: '#/components/schemas/InstanceMonitoringList' + - description: The monitoring information. + type: object + UserData: + description: Describes the user data for an instance. + properties: + data: + allOf: + - $ref: '#/components/schemas/String' + - description: The user data. If you are using an Amazon Web Services SDK or command line tool, Base64-encoding is performed for you, and you can load the text from a file. Otherwise, you must provide Base64-encoded text. + type: object + ValueStringList: + items: + allOf: + - $ref: '#/components/schemas/String' + - xml: + name: item + type: array + VirtualizationType: + enum: + - hvm + - paravirtual + type: string + VolumeDetail: + description: Describes an EBS volume. + properties: + size: + description: The size of the volume, in GiB. + type: integer + required: + - Size + type: object + securitySchemes: + hmac: + description: Amazon Signature authorization v4 + in: header + name: Authorization + type: apiKey + x-amazon-apigateway-authtype: awsSigv4 + x-stackQL-resources: + instances: + id: aws.ec2.instances + methods: + bundle: + config: + requestBodyTranslate: + algorithm: naive + requestTranslate: + algorithm: drop_double_underscore_params + operation: + $ref: '#/paths/~1?__Action=BundleInstance&__Version=2016-11-15/post' + request: + mediaType: application/x-www-form-urlencoded + schema_override: + $ref: '#/components/schemas/BundleInstanceRequest' + transform: + body: |- + {{- $ctx := . -}} + {{- if or (not $ctx) (eq (len $ctx) 0) -}} + {{- $ctx = "{}" -}} + {{- end -}} + {{- $body := jsonMapFromString $ctx -}} + {{- $query := "Action=BundleInstance&Version=2016-11-15" -}} + {{- range $k, $v := $body -}} + {{- if eq (kindOf $v) "slice" -}} + {{- range $i, $sub := $v -}} + {{- $query = printf "%s&%s.%d=%s" $query $k (plus1 $i) (urlquery $sub) -}} + {{- end -}} + {{- else -}} + {{- $query = printf "%s&%s=%s" $query $k (urlquery $v) -}} + {{- end -}} + {{- end -}} + {{- $query -}} + type: golang_template_text_v0.3.0 + response: + mediaType: text/xml + openAPIDocKey: "200" + overrideMediaType: application/json + transform: + body: '{{ toJson . }}' + type: golang_template_mxj_v0.2.0 + describe: + config: + pagination: + requestToken: + key: NextToken + location: body + responseToken: + key: $.next_page_token + location: body + requestBodyTranslate: + algorithm: naive + requestTranslate: + algorithm: drop_double_underscore_params + operation: + $ref: '#/paths/~1?__Action=DescribeInstances&__Version=2016-11-15/post' + request: + mediaType: application/x-www-form-urlencoded + schema_override: + $ref: '#/components/schemas/DescribeInstancesRequest' + transform: + body: |- + {{- $ctx := . -}} + {{- if or (not $ctx) (eq (len $ctx) 0) -}} + {{- $ctx = "{}" -}} + {{- end -}} + {{- $body := jsonMapFromString $ctx -}} + {{- $query := "Action=DescribeInstances&Version=2016-11-15" -}} + {{- range $k, $v := $body -}} + {{- if eq (kindOf $v) "slice" -}} + {{- range $i, $sub := $v -}} + {{- $query = printf "%s&%s.%d=%s" $query $k (plus1 $i) (urlquery $sub) -}} + {{- end -}} + {{- else -}} + {{- $query = printf "%s&%s=%s" $query $k (urlquery $v) -}} + {{- end -}} + {{- end -}} + {{- $query -}} + type: golang_template_text_v0.3.0 + response: + mediaType: text/xml + objectKey: $.line_items + openAPIDocKey: "200" + overrideMediaType: application/json + schema_override: + $ref: '#/components/schemas/DisplayInstancesSchema' + transform: + body: | + { + {{- $resp := . -}} + {{- with index . "DescribeInstancesResponse" -}} + {{- $resp = . -}} + {{- end -}} + + "next_page_token": + {{- with index $resp "nextToken" -}}{{ toJson . }}{{- else -}}null{{- end -}}, + + "line_items": [ + {{- $first := true -}} + + {{- with index $resp "reservationSet" "item" -}} + + {{- if eq (printf "%T" .) "map[string]interface {}" -}} + {{- $res := . -}} + {{- with index $res "instancesSet" "item" -}} + + {{- if eq (printf "%T" .) "map[string]interface {}" -}} + {{- if not $first }},{{ end -}}{{- $first = false -}} + {{ template "item" . }} + {{- else -}} + {{- range . -}} + {{- if not $first }},{{ end -}}{{- $first = false -}} + {{ template "item" . }} + {{- end -}} + {{- end -}} + + {{- end -}} + {{- else -}} + {{- range . -}} + {{- $res := . -}} + {{- with index $res "instancesSet" "item" -}} + + {{- if eq (printf "%T" .) "map[string]interface {}" -}} + {{- if not $first }},{{ end -}}{{- $first = false -}} + {{ template "item" . }} + {{- else -}} + {{- range . -}} + {{- if not $first }},{{ end -}}{{- $first = false -}} + {{ template "item" . }} + {{- end -}} + {{- end -}} + + {{- end -}} + {{- end -}} + {{- end -}} + + {{- end -}} + ] + } + + {{- define "item" -}} + { + "instance_id": {{ with index . "instanceId" }}{{ toJson . }}{{ else }}null{{ end }}, + "reservation_id": {{ with index . "reservationId" }}{{ toJson . }}{{ else }}null{{ end }}, + "owner_id": {{ with index . "ownerId" }}{{ toJson . }}{{ else }}null{{ end }}, + + "image_id": {{ with index . "imageId" }}{{ toJson . }}{{ else }}null{{ end }}, + "instance_type": {{ with index . "instanceType" }}{{ toJson . }}{{ else }}null{{ end }}, + "ami_launch_index": {{ with index . "amiLaunchIndex" }}{{ toJson . }}{{ else }}null{{ end }}, + "key_name": {{ with index . "keyName" }}{{ toJson . }}{{ else }}null{{ end }}, + "client_token": {{ with index . "clientToken" }}{{ toJson . }}{{ else }}null{{ end }}, + + "state": {{ with index . "instanceState" }}{{ toJson . }}{{ else }}null{{ end }}, + "launch_time": {{ with index . "launchTime" }}{{ toJson . }}{{ else }}null{{ end }}, + + "private_dns_name": {{ with index . "privateDnsName" }}{{ toJson . }}{{ else }}null{{ end }}, + "public_dns_name": {{ with index . "dnsName" }}{{ toJson . }}{{ else }}null{{ end }}, + "private_ip_address": {{ with index . "privateIpAddress" }}{{ toJson . }}{{ else }}null{{ end }}, + "public_ip_address": {{ with index . "ipAddress" }}{{ toJson . }}{{ else }}null{{ end }}, + + "subnet_id": {{ with index . "subnetId" }}{{ toJson . }}{{ else }}null{{ end }}, + "vpc_id": {{ with index . "vpcId" }}{{ toJson . }}{{ else }}null{{ end }}, + + "availability_zone": {{ with index . "placement" "availabilityZone" }}{{ toJson . }}{{ else }}null{{ end }}, + "placement": {{ with index . "placement" }}{{ toJson . }}{{ else }}null{{ end }}, + + "monitoring": {{ with index . "monitoring" }}{{ toJson . }}{{ else }}null{{ end }}, + "source_dest_check": {{ with index . "sourceDestCheck" }}{{ toJson . }}{{ else }}null{{ end }}, + + "architecture": {{ with index . "architecture" }}{{ toJson . }}{{ else }}null{{ end }}, + "hypervisor": {{ with index . "hypervisor" }}{{ toJson . }}{{ else }}null{{ end }}, + "virtualization_type": {{ with index . "virtualizationType" }}{{ toJson . }}{{ else }}null{{ end }}, + "platform_details": {{ with index . "platformDetails" }}{{ toJson . }}{{ else }}null{{ end }}, + + "root_device_type": {{ with index . "rootDeviceType" }}{{ toJson . }}{{ else }}null{{ end }}, + "root_device_name": {{ with index . "rootDeviceName" }}{{ toJson . }}{{ else }}null{{ end }}, + + "ebs_optimized": {{ with index . "ebsOptimized" }}{{ toJson . }}{{ else }}null{{ end }}, + "ena_support": {{ with index . "enaSupport" }}{{ toJson . }}{{ else }}null{{ end }}, + + "cpu_options": {{ with index . "cpuOptions" }}{{ toJson . }}{{ else }}null{{ end }}, + "metadata_options": {{ with index . "metadataOptions" }}{{ toJson . }}{{ else }}null{{ end }}, + "maintenance_options": {{ with index . "maintenanceOptions" }}{{ toJson . }}{{ else }}null{{ end }}, + "hibernation_options": {{ with index . "hibernationOptions" }}{{ toJson . }}{{ else }}null{{ end }}, + "enclave_options": {{ with index . "enclaveOptions" }}{{ toJson . }}{{ else }}null{{ end }}, + "network_performance_options": {{ with index . "networkPerformanceOptions" }}{{ toJson . }}{{ else }}null{{ end }}, + "capacity_reservation_specification": {{ with index . "capacityReservationSpecification" }}{{ toJson . }}{{ else }}null{{ end }}, + + "security_groups": {{ with index . "groupSet" }}{{ toJson . }}{{ else }}null{{ end }}, + "block_device_mappings": {{ with index . "blockDeviceMapping" }}{{ toJson . }}{{ else }}null{{ end }}, + "network_interfaces": {{ with index . "networkInterfaceSet" }}{{ toJson . }}{{ else }}null{{ end }}, + + "tag_set": {{ with index . "tagSet" }}{{ toJson . }}{{ else }}null{{ end }} + } + {{- end -}} + type: golang_template_mxj_v0.2.0 + import: + config: + requestBodyTranslate: + algorithm: naive + requestTranslate: + algorithm: drop_double_underscore_params + operation: + $ref: '#/paths/~1?__Action=ImportInstance&__Version=2016-11-15/post' + request: + mediaType: application/x-www-form-urlencoded + schema_override: + $ref: '#/components/schemas/ImportInstanceRequest' + transform: + body: |- + {{- $ctx := . -}} + {{- if or (not $ctx) (eq (len $ctx) 0) -}} + {{- $ctx = "{}" -}} + {{- end -}} + {{- $body := jsonMapFromString $ctx -}} + {{- $query := "Action=ImportInstance&Version=2016-11-15" -}} + {{- range $k, $v := $body -}} + {{- if eq (kindOf $v) "slice" -}} + {{- range $i, $sub := $v -}} + {{- $query = printf "%s&%s.%d=%s" $query $k (plus1 $i) (urlquery $sub) -}} + {{- end -}} + {{- else -}} + {{- $query = printf "%s&%s=%s" $query $k (urlquery $v) -}} + {{- end -}} + {{- end -}} + {{- $query -}} + type: golang_template_text_v0.3.0 + response: + mediaType: text/xml + objectKey: $.line_items + openAPIDocKey: "200" + overrideMediaType: application/json + schema_override: + $ref: '#/components/schemas/DisplayInstancesSchema' + transform: + body: '{{ toJson . }}' + type: golang_template_mxj_v0.2.0 + monitor: + config: + requestBodyTranslate: + algorithm: naive + requestTranslate: + algorithm: drop_double_underscore_params + operation: + $ref: '#/paths/~1?__Action=MonitorInstances&__Version=2016-11-15/post' + request: + mediaType: application/x-www-form-urlencoded + schema_override: + $ref: '#/components/schemas/MonitorInstancesRequest' + transform: + body: |- + {{- $ctx := . -}} + {{- if or (not $ctx) (eq (len $ctx) 0) -}} + {{- $ctx = "{}" -}} + {{- end -}} + {{- $body := jsonMapFromString $ctx -}} + {{- $query := "Action=MonitorInstances&Version=2016-11-15" -}} + {{- range $k, $v := $body -}} + {{- if eq (kindOf $v) "slice" -}} + {{- range $i, $sub := $v -}} + {{- $query = printf "%s&%s.%d=%s" $query $k (plus1 $i) (urlquery $sub) -}} + {{- end -}} + {{- else -}} + {{- $query = printf "%s&%s=%s" $query $k (urlquery $v) -}} + {{- end -}} + {{- end -}} + {{- $query -}} + type: golang_template_text_v0.3.0 + response: + mediaType: text/xml + openAPIDocKey: "200" + overrideMediaType: application/json + transform: + body: '{{ toJson . }}' + type: golang_template_mxj_v0.2.0 + reboot: + config: + requestBodyTranslate: + algorithm: naive + requestTranslate: + algorithm: drop_double_underscore_params + operation: + $ref: '#/paths/~1?__Action=RebootInstances&__Version=2016-11-15/post' + request: + mediaType: application/x-www-form-urlencoded + schema_override: + $ref: '#/components/schemas/RebootInstancesRequest' + transform: + body: |- + {{- $ctx := . -}} + {{- if or (not $ctx) (eq (len $ctx) 0) -}} + {{- $ctx = "{}" -}} + {{- end -}} + {{- $body := jsonMapFromString $ctx -}} + {{- $query := "Action=RebootInstances&Version=2016-11-15" -}} + {{- range $k, $v := $body -}} + {{- if eq (kindOf $v) "slice" -}} + {{- range $i, $sub := $v -}} + {{- $query = printf "%s&%s.%d=%s" $query $k (plus1 $i) (urlquery $sub) -}} + {{- end -}} + {{- else -}} + {{- $query = printf "%s&%s=%s" $query $k (urlquery $v) -}} + {{- end -}} + {{- end -}} + {{- $query -}} + type: golang_template_text_v0.3.0 + response: + mediaType: text/xml + openAPIDocKey: "200" + overrideMediaType: application/json + transform: + body: '{{ toJson . }}' + type: golang_template_mxj_v0.2.0 + run: + config: + requestBodyTranslate: + algorithm: naive + requestTranslate: + algorithm: drop_double_underscore_params + operation: + $ref: '#/paths/~1?__Action=RunInstances&__Version=2016-11-15/post' + request: + mediaType: application/x-www-form-urlencoded + schema_override: + $ref: '#/components/schemas/RunInstancesRequest' + transform: + body: |- + {{- $ctx := . -}} + {{- if or (not $ctx) (eq (len $ctx) 0) -}} + {{- $ctx = "{}" -}} + {{- end -}} + {{- $body := jsonMapFromString $ctx -}} + {{- $query := "Action=RunInstances&Version=2016-11-15" -}} + {{- range $k, $v := $body -}} + {{- if eq (kindOf $v) "slice" -}} + {{- range $i, $sub := $v -}} + {{- $query = printf "%s&%s.%d=%s" $query $k (plus1 $i) (urlquery $sub) -}} + {{- end -}} + {{- else if and (ne $k "subnet_id") (ne $k "security_group_id") (ne $k "associate_public_ip_address") (ne $k "name") -}} + {{- $query = printf "%s&%s=%s" $query $k (urlquery $v) -}} + {{- end -}} + {{- end -}} + {{- if and (not (index $body "MaxCount")) (not (index $body "MinCount")) -}} + {{- $query = printf "%s&MaxCount=1&MinCount=1" $query -}} + {{- end -}} + {{- if and (index $body "subnet_id") (index $body "security_group_id") (index $body "associate_public_ip_address") -}} + {{- $query = printf "%s&NetworkInterface.1.DeviceIndex=0&NetworkInterface.1.SubnetId=%s&NetworkInterface.1.AssociatePublicIpAddress=%s&NetworkInterface.1.SecurityGroupId.1=%s" $query $body.subnet_id $body.associate_public_ip_address $body.security_group_id -}} + {{- end -}} + {{- if index $body "name" -}} + {{- $query = printf "%s&TagSpecification.1.ResourceType=instance&TagSpecification.1.Tag.1.Key=Name&TagSpecification.1.Tag.1.Value=%s" $query $body.name -}} + {{- end -}} + {{- $query -}} + type: golang_template_text_v0.3.0 + response: + mediaType: text/xml + openAPIDocKey: "200" + overrideMediaType: application/json + transform: + body: '{{ toJson . }}' + type: golang_template_mxj_v0.2.0 + start: + config: + requestBodyTranslate: + algorithm: naive + requestTranslate: + algorithm: drop_double_underscore_params + operation: + $ref: '#/paths/~1?__Action=StartInstances&__Version=2016-11-15/post' + request: + mediaType: application/x-www-form-urlencoded + schema_override: + $ref: '#/components/schemas/StartInstancesRequest' + transform: + body: |- + {{- $ctx := . -}} + {{- if or (not $ctx) (eq (len $ctx) 0) -}} + {{- $ctx = "{}" -}} + {{- end -}} + {{- $body := jsonMapFromString $ctx -}} + {{- $query := "Action=StartInstances&Version=2016-11-15" -}} + {{- range $k, $v := $body -}} + {{- if eq (kindOf $v) "slice" -}} + {{- range $i, $sub := $v -}} + {{- $query = printf "%s&%s.%d=%s" $query $k (plus1 $i) (urlquery $sub) -}} + {{- end -}} + {{- else -}} + {{- $query = printf "%s&%s=%s" $query $k (urlquery $v) -}} + {{- end -}} + {{- end -}} + {{- $query -}} + type: golang_template_text_v0.3.0 + response: + mediaType: text/xml + openAPIDocKey: "200" + overrideMediaType: application/json + transform: + body: '{{ toJson . }}' + type: golang_template_mxj_v0.2.0 + stop: + config: + requestBodyTranslate: + algorithm: naive + requestTranslate: + algorithm: drop_double_underscore_params + operation: + $ref: '#/paths/~1?__Action=StopInstances&__Version=2016-11-15/post' + request: + mediaType: application/x-www-form-urlencoded + schema_override: + $ref: '#/components/schemas/StopInstancesRequest' + transform: + body: |- + {{- $ctx := . -}} + {{- if or (not $ctx) (eq (len $ctx) 0) -}} + {{- $ctx = "{}" -}} + {{- end -}} + {{- $body := jsonMapFromString $ctx -}} + {{- $query := "Action=StopInstances&Version=2016-11-15" -}} + {{- range $k, $v := $body -}} + {{- if eq (kindOf $v) "slice" -}} + {{- range $i, $sub := $v -}} + {{- $query = printf "%s&%s.%d=%s" $query $k (plus1 $i) (urlquery $sub) -}} + {{- end -}} + {{- else -}} + {{- $query = printf "%s&%s=%s" $query $k (urlquery $v) -}} + {{- end -}} + {{- end -}} + {{- $query -}} + type: golang_template_text_v0.3.0 + response: + mediaType: text/xml + openAPIDocKey: "200" + overrideMediaType: application/json + transform: + body: '{{ toJson . }}' + type: golang_template_mxj_v0.2.0 + terminate: + config: + requestBodyTranslate: + algorithm: naive + requestTranslate: + algorithm: drop_double_underscore_params + operation: + $ref: '#/paths/~1?__Action=TerminateInstances&__Version=2016-11-15/post' + request: + mediaType: application/x-www-form-urlencoded + schema_override: + $ref: '#/components/schemas/TerminateInstancesRequest' + transform: + body: |- + {{- $ctx := . -}} + {{- if or (not $ctx) (eq (len $ctx) 0) -}} + {{- $ctx = "{}" -}} + {{- end -}} + {{- $body := jsonMapFromString $ctx -}} + {{- $query := "Action=TerminateInstances&Version=2016-11-15" -}} + {{- range $k, $v := $body -}} + {{- if eq (kindOf $v) "slice" -}} + {{- range $i, $sub := $v -}} + {{- $query = printf "%s&%s.%d=%s" $query $k (plus1 $i) (urlquery $sub) -}} + {{- end -}} + {{- else -}} + {{- $query = printf "%s&%s=%s" $query $k (urlquery $v) -}} + {{- end -}} + {{- end -}} + {{- $query -}} + type: golang_template_text_v0.3.0 + response: + mediaType: text/xml + openAPIDocKey: "200" + overrideMediaType: application/json + transform: + body: '{{ toJson . }}' + type: golang_template_mxj_v0.2.0 + unmonitor: + config: + requestBodyTranslate: + algorithm: naive + requestTranslate: + algorithm: drop_double_underscore_params + operation: + $ref: '#/paths/~1?__Action=UnmonitorInstances&__Version=2016-11-15/post' + request: + mediaType: application/x-www-form-urlencoded + schema_override: + $ref: '#/components/schemas/UnmonitorInstancesRequest' + transform: + body: |- + {{- $ctx := . -}} + {{- if or (not $ctx) (eq (len $ctx) 0) -}} + {{- $ctx = "{}" -}} + {{- end -}} + {{- $body := jsonMapFromString $ctx -}} + {{- $query := "Action=UnmonitorInstances&Version=2016-11-15" -}} + {{- range $k, $v := $body -}} + {{- if eq (kindOf $v) "slice" -}} + {{- range $i, $sub := $v -}} + {{- $query = printf "%s&%s.%d=%s" $query $k (plus1 $i) (urlquery $sub) -}} + {{- end -}} + {{- else -}} + {{- $query = printf "%s&%s=%s" $query $k (urlquery $v) -}} + {{- end -}} + {{- end -}} + {{- $query -}} + type: golang_template_text_v0.3.0 + response: + mediaType: text/xml + openAPIDocKey: "200" + overrideMediaType: application/json + transform: + body: '{{ toJson . }}' + type: golang_template_mxj_v0.2.0 + name: instances + sqlVerbs: + delete: + - $ref: '#/components/x-stackQL-resources/instances/methods/terminate' + exec: + - $ref: '#/components/x-stackQL-resources/instances/methods/bundle' + - $ref: '#/components/x-stackQL-resources/instances/methods/import' + - $ref: '#/components/x-stackQL-resources/instances/methods/monitor' + - $ref: '#/components/x-stackQL-resources/instances/methods/reboot' + - $ref: '#/components/x-stackQL-resources/instances/methods/start' + - $ref: '#/components/x-stackQL-resources/instances/methods/stop' + - $ref: '#/components/x-stackQL-resources/instances/methods/unmonitor' + insert: + - $ref: '#/components/x-stackQL-resources/instances/methods/run' + select: + - $ref: '#/components/x-stackQL-resources/instances/methods/describe' + update: [] + title: instances +externalDocs: + description: Amazon Web Services documentation + url: https://docs.aws.amazon.com/ec2/ +info: + contact: + email: mike.ralphson@gmail.com + name: Mike Ralphson + url: https://github.com/mermade/aws2openapi + x-twitter: PermittedSoc + description: Native EC2 API operations + license: + name: Apache 2.0 License + url: http://www.apache.org/licenses/ + termsOfService: https://aws.amazon.com/service-terms/ + title: ec2 + version: "2016-11-15" + x-apiClientRegistration: + url: https://portal.aws.amazon.com/gp/aws/developer/registration/index.html?nc2=h_ct + x-apisguru-categories: + - cloud + x-logo: + backgroundColor: '#FFFFFF' + url: https://twitter.com/awscloud/profile_image?size=original + x-origin: + - contentType: application/json + converter: + url: https://github.com/mermade/aws2openapi + version: 1.0.0 + url: https://raw.githubusercontent.com/aws/aws-sdk-js/master/apis/ec2-2016-11-15.normal.json + x-apisguru-driver: external + x-preferred: true + x-providerName: amazonaws.com + x-release: v4 + x-serviceName: ec2 +openapi: 3.0.0 +paths: + /?__Action=BundleInstance&__Version=2016-11-15: + post: + description:

Bundles an Amazon instance store-backed Windows instance.

During bundling, only the root device volume (C:\) is bundled. Data on other instance store volumes is not preserved.

This action is not applicable for Linux/Unix instances or Windows instances that are backed by Amazon EBS.

+ operationId: GET_BundleInstance + parameters: + - description: '

The ID of the instance to bundle.

Type: String

Default: None

Required: Yes

' + in: query + name: InstanceId + required: true + schema: + type: string + - description: The bucket in which to store the AMI. You can specify a bucket that you already own or a new bucket that Amazon EC2 creates on your behalf. If you specify a bucket that belongs to someone else, Amazon EC2 returns an error. + in: query + name: Storage + required: true + schema: + description: Describes the storage location for an instance store-backed AMI. + properties: + undefined: + allOf: + - $ref: '#/components/schemas/S3Storage' + - description: An Amazon S3 storage location. + type: object + - description: Checks whether you have the required permissions for the action, without actually making the request, and provides an error response. If you have the required permissions, the error response is DryRunOperation. Otherwise, it is UnauthorizedOperation. + in: query + name: DryRun + required: false + schema: + type: boolean + responses: + "200": + content: + text/xml: + schema: + $ref: '#/components/schemas/BundleInstanceResponseWrapper' + description: Success + x-aws-operation-name: BundleInstance + /?__Action=DescribeInstances&__Version=2016-11-15: + post: + description:

Describes the specified instances or all instances.

If you specify instance IDs, the output includes information for only the specified instances. If you specify filters, the output includes information for only those instances that meet the filter criteria. If you do not specify instance IDs or filters, the output includes information for all instances, which can affect performance. We recommend that you use pagination to ensure that the operation returns quickly and successfully.

If you specify an instance ID that is not valid, an error is returned. If you specify an instance that you do not own, it is not included in the output.

Recently terminated instances might appear in the returned results. This interval is usually less than one hour.

If you describe instances in the rare case where an Availability Zone is experiencing a service disruption and you specify instance IDs that are in the affected zone, or do not specify any instance IDs at all, the call fails. If you describe instances and specify only instance IDs that are in an unaffected zone, the call works normally.

+ operationId: GET_DescribeInstances + parameters: + - description: '

The filters.

  • affinity - The affinity setting for an instance running on a Dedicated Host (default | host).

  • architecture - The instance architecture (i386 | x86_64 | arm64).

  • availability-zone - The Availability Zone of the instance.

  • block-device-mapping.attach-time - The attach time for an EBS volume mapped to the instance, for example, 2010-09-15T17:15:20.000Z.

  • block-device-mapping.delete-on-termination - A Boolean that indicates whether the EBS volume is deleted on instance termination.

  • block-device-mapping.device-name - The device name specified in the block device mapping (for example, /dev/sdh or xvdh).

  • block-device-mapping.status - The status for the EBS volume (attaching | attached | detaching | detached).

  • block-device-mapping.volume-id - The volume ID of the EBS volume.

  • capacity-reservation-id - The ID of the Capacity Reservation into which the instance was launched.

  • client-token - The idempotency token you provided when you launched the instance.

  • dns-name - The public DNS name of the instance.

  • group-id - The ID of the security group for the instance. EC2-Classic only.

  • group-name - The name of the security group for the instance. EC2-Classic only.

  • hibernation-options.configured - A Boolean that indicates whether the instance is enabled for hibernation. A value of true means that the instance is enabled for hibernation.

  • host-id - The ID of the Dedicated Host on which the instance is running, if applicable.

  • hypervisor - The hypervisor type of the instance (ovm | xen). The value xen is used for both Xen and Nitro hypervisors.

  • iam-instance-profile.arn - The instance profile associated with the instance. Specified as an ARN.

  • image-id - The ID of the image used to launch the instance.

  • instance-id - The ID of the instance.

  • instance-lifecycle - Indicates whether this is a Spot Instance or a Scheduled Instance (spot | scheduled).

  • instance-state-code - The state of the instance, as a 16-bit unsigned integer. The high byte is used for internal purposes and should be ignored. The low byte is set based on the state represented. The valid values are: 0 (pending), 16 (running), 32 (shutting-down), 48 (terminated), 64 (stopping), and 80 (stopped).

  • instance-state-name - The state of the instance (pending | running | shutting-down | terminated | stopping | stopped).

  • instance-type - The type of instance (for example, t2.micro).

  • instance.group-id - The ID of the security group for the instance.

  • instance.group-name - The name of the security group for the instance.

  • ip-address - The public IPv4 address of the instance.

  • kernel-id - The kernel ID.

  • key-name - The name of the key pair used when the instance was launched.

  • launch-index - When launching multiple instances, this is the index for the instance in the launch group (for example, 0, 1, 2, and so on).

  • launch-time - The time when the instance was launched, in the ISO 8601 format in the UTC time zone (YYYY-MM-DDThh:mm:ss.sssZ), for example, 2021-09-29T11:04:43.305Z. You can use a wildcard (*), for example, 2021-09-29T*, which matches an entire day.

  • metadata-options.http-tokens - The metadata request authorization state (optional | required)

  • metadata-options.http-put-response-hop-limit - The http metadata request put response hop limit (integer, possible values 1 to 64)

  • metadata-options.http-endpoint - Enable or disable metadata access on http endpoint (enabled | disabled)

  • monitoring-state - Indicates whether detailed monitoring is enabled (disabled | enabled).

  • network-interface.addresses.private-ip-address - The private IPv4 address associated with the network interface.

  • network-interface.addresses.primary - Specifies whether the IPv4 address of the network interface is the primary private IPv4 address.

  • network-interface.addresses.association.public-ip - The ID of the association of an Elastic IP address (IPv4) with a network interface.

  • network-interface.addresses.association.ip-owner-id - The owner ID of the private IPv4 address associated with the network interface.

  • network-interface.association.public-ip - The address of the Elastic IP address (IPv4) bound to the network interface.

  • network-interface.association.ip-owner-id - The owner of the Elastic IP address (IPv4) associated with the network interface.

  • network-interface.association.allocation-id - The allocation ID returned when you allocated the Elastic IP address (IPv4) for your network interface.

  • network-interface.association.association-id - The association ID returned when the network interface was associated with an IPv4 address.

  • network-interface.attachment.attachment-id - The ID of the interface attachment.

  • network-interface.attachment.instance-id - The ID of the instance to which the network interface is attached.

  • network-interface.attachment.instance-owner-id - The owner ID of the instance to which the network interface is attached.

  • network-interface.attachment.device-index - The device index to which the network interface is attached.

  • network-interface.attachment.status - The status of the attachment (attaching | attached | detaching | detached).

  • network-interface.attachment.attach-time - The time that the network interface was attached to an instance.

  • network-interface.attachment.delete-on-termination - Specifies whether the attachment is deleted when an instance is terminated.

  • network-interface.availability-zone - The Availability Zone for the network interface.

  • network-interface.description - The description of the network interface.

  • network-interface.group-id - The ID of a security group associated with the network interface.

  • network-interface.group-name - The name of a security group associated with the network interface.

  • network-interface.ipv6-addresses.ipv6-address - The IPv6 address associated with the network interface.

  • network-interface.mac-address - The MAC address of the network interface.

  • network-interface.network-interface-id - The ID of the network interface.

  • network-interface.owner-id - The ID of the owner of the network interface.

  • network-interface.private-dns-name - The private DNS name of the network interface.

  • network-interface.requester-id - The requester ID for the network interface.

  • network-interface.requester-managed - Indicates whether the network interface is being managed by Amazon Web Services.

  • network-interface.status - The status of the network interface (available) | in-use).

  • network-interface.source-dest-check - Whether the network interface performs source/destination checking. A value of true means that checking is enabled, and false means that checking is disabled. The value must be false for the network interface to perform network address translation (NAT) in your VPC.

  • network-interface.subnet-id - The ID of the subnet for the network interface.

  • network-interface.vpc-id - The ID of the VPC for the network interface.

  • outpost-arn - The Amazon Resource Name (ARN) of the Outpost.

  • owner-id - The Amazon Web Services account ID of the instance owner.

  • placement-group-name - The name of the placement group for the instance.

  • placement-partition-number - The partition in which the instance is located.

  • platform - The platform. To list only Windows instances, use windows.

  • private-dns-name - The private IPv4 DNS name of the instance.

  • private-ip-address - The private IPv4 address of the instance.

  • product-code - The product code associated with the AMI used to launch the instance.

  • product-code.type - The type of product code (devpay | marketplace).

  • ramdisk-id - The RAM disk ID.

  • reason - The reason for the current state of the instance (for example, shows "User Initiated [date]" when you stop or terminate the instance). Similar to the state-reason-code filter.

  • requester-id - The ID of the entity that launched the instance on your behalf (for example, Amazon Web Services Management Console, Auto Scaling, and so on).

  • reservation-id - The ID of the instance''s reservation. A reservation ID is created any time you launch an instance. A reservation ID has a one-to-one relationship with an instance launch request, but can be associated with more than one instance if you launch multiple instances using the same launch request. For example, if you launch one instance, you get one reservation ID. If you launch ten instances using the same launch request, you also get one reservation ID.

  • root-device-name - The device name of the root device volume (for example, /dev/sda1).

  • root-device-type - The type of the root device volume (ebs | instance-store).

  • source-dest-check - Indicates whether the instance performs source/destination checking. A value of true means that checking is enabled, and false means that checking is disabled. The value must be false for the instance to perform network address translation (NAT) in your VPC.

  • spot-instance-request-id - The ID of the Spot Instance request.

  • state-reason-code - The reason code for the state change.

  • state-reason-message - A message that describes the state change.

  • subnet-id - The ID of the subnet for the instance.

  • tag:<key> - The key/value combination of a tag assigned to the resource. Use the tag key in the filter name and the tag value as the filter value. For example, to find all resources that have a tag with the key Owner and the value TeamA, specify tag:Owner for the filter name and TeamA for the filter value.

  • tag-key - The key of a tag assigned to the resource. Use this filter to find all resources that have a tag with a specific key, regardless of the tag value.

  • tenancy - The tenancy of an instance (dedicated | default | host).

  • virtualization-type - The virtualization type of the instance (paravirtual | hvm).

  • vpc-id - The ID of the VPC that the instance is running in.

' + in: query + name: Filter + required: false + schema: + items: + allOf: + - $ref: '#/components/schemas/Filter' + - xml: + name: Filter + type: array + - description: '

The instance IDs.

Default: Describes all your instances.

' + in: query + name: InstanceId + required: false + schema: + items: + allOf: + - $ref: '#/components/schemas/InstanceId' + - xml: + name: InstanceId + type: array + - description: Checks whether you have the required permissions for the action, without actually making the request, and provides an error response. If you have the required permissions, the error response is DryRunOperation. Otherwise, it is UnauthorizedOperation. + in: query + name: DryRun + required: false + schema: + type: boolean + - description: The maximum number of results to return in a single call. To retrieve the remaining results, make another call with the returned NextToken value. This value can be between 5 and 1000. You cannot specify this parameter and the instance IDs parameter in the same call. + in: query + name: MaxResults + required: false + schema: + type: integer + - description: The token to request the next page of results. + in: query + name: NextToken + required: false + schema: + type: string + responses: + "200": + content: + text/xml: + schema: + $ref: '#/components/schemas/DescribeInstancesResponseWrapper' + description: Success + x-aws-operation-name: DescribeInstances + /?__Action=ImportInstance&__Version=2016-11-15: + post: + description:

Creates an import instance task using metadata from the specified disk image.

This API action supports only single-volume VMs. To import multi-volume VMs, use ImportImage instead.

This API action is not supported by the Command Line Interface (CLI). For information about using the Amazon EC2 CLI, which is deprecated, see Importing a VM to Amazon EC2 in the Amazon EC2 CLI Reference PDF file.

For information about the import manifest referenced by this API action, see VM Import Manifest.

+ operationId: GET_ImportInstance + parameters: + - description: A description for the instance being imported. + in: query + name: Description + required: false + schema: + type: string + - description: The disk image. + in: query + name: DiskImage + required: false + schema: + items: + $ref: '#/components/schemas/DiskImage' + type: array + - description: Checks whether you have the required permissions for the action, without actually making the request, and provides an error response. If you have the required permissions, the error response is DryRunOperation. Otherwise, it is UnauthorizedOperation. + in: query + name: DryRun + required: false + schema: + type: boolean + - description: The launch specification. + in: query + name: LaunchSpecification + required: false + schema: + description: Describes the launch specification for VM import. + properties: + GroupId: + allOf: + - $ref: '#/components/schemas/SecurityGroupIdStringList' + - description: The security group IDs. + GroupName: + allOf: + - $ref: '#/components/schemas/SecurityGroupStringList' + - description: The security group names. + additionalInfo: + allOf: + - $ref: '#/components/schemas/String' + - description: Reserved. + architecture: + allOf: + - $ref: '#/components/schemas/ArchitectureValues' + - description: The architecture of the instance. + instanceInitiatedShutdownBehavior: + allOf: + - $ref: '#/components/schemas/ShutdownBehavior' + - description: Indicates whether an instance stops or terminates when you initiate shutdown from the instance (using the operating system command for system shutdown). + instanceType: + allOf: + - $ref: '#/components/schemas/InstanceType' + - description: The instance type. For more information about the instance types that you can import, see Instance Types in the VM Import/Export User Guide. + monitoring: + allOf: + - $ref: '#/components/schemas/Boolean' + - description: Indicates whether monitoring is enabled. + placement: + allOf: + - $ref: '#/components/schemas/Placement' + - description: The placement information for the instance. + privateIpAddress: + allOf: + - $ref: '#/components/schemas/String' + - description: '[EC2-VPC] An available IP address from the IP address range of the subnet.' + subnetId: + allOf: + - $ref: '#/components/schemas/SubnetId' + - description: '[EC2-VPC] The ID of the subnet in which to launch the instance.' + userData: + allOf: + - $ref: '#/components/schemas/UserData' + - description: The Base64-encoded user data to make available to the instance. + type: object + - description: The instance operating system. + in: query + name: Platform + required: true + schema: + enum: + - Windows + type: string + responses: + "200": + content: + text/xml: + schema: + $ref: '#/components/schemas/ImportInstanceResponseWrapper' + description: Success + x-aws-operation-name: ImportInstance + /?__Action=MonitorInstances&__Version=2016-11-15: + post: + description:

Enables detailed monitoring for a running instance. Otherwise, basic monitoring is enabled. For more information, see Monitor your instances using CloudWatch in the Amazon EC2 User Guide.

To disable detailed monitoring, see UnmonitorInstances.

+ operationId: GET_MonitorInstances + parameters: + - description: The IDs of the instances. + in: query + name: InstanceId + required: true + schema: + items: + allOf: + - $ref: '#/components/schemas/InstanceId' + - xml: + name: InstanceId + type: array + - description: Checks whether you have the required permissions for the action, without actually making the request, and provides an error response. If you have the required permissions, the error response is DryRunOperation. Otherwise, it is UnauthorizedOperation. + in: query + name: DryRun + required: false + schema: + type: boolean + responses: + "200": + content: + text/xml: + schema: + $ref: '#/components/schemas/MonitorInstancesResponseWrapper' + description: Success + x-aws-operation-name: MonitorInstances + /?__Action=RebootInstances&__Version=2016-11-15: + post: + description:

Requests a reboot of the specified instances. This operation is asynchronous; it only queues a request to reboot the specified instances. The operation succeeds if the instances are valid and belong to you. Requests to reboot terminated instances are ignored.

If an instance does not cleanly shut down within a few minutes, Amazon EC2 performs a hard reboot.

For more information about troubleshooting, see Troubleshoot an unreachable instance in the Amazon EC2 User Guide.

+ operationId: GET_RebootInstances + parameters: + - description: The instance IDs. + in: query + name: InstanceId + required: true + schema: + items: + allOf: + - $ref: '#/components/schemas/InstanceId' + - xml: + name: InstanceId + type: array + - description: Checks whether you have the required permissions for the action, without actually making the request, and provides an error response. If you have the required permissions, the error response is DryRunOperation. Otherwise, it is UnauthorizedOperation. + in: query + name: DryRun + required: false + schema: + type: boolean + responses: + "200": + description: Success + x-aws-operation-name: RebootInstances + /?__Action=RunInstances&__Version=2016-11-15: + post: + operationId: GET_RunInstances + parameters: [] + responses: + "200": + content: + text/xml: + schema: + $ref: '#/components/schemas/RunInstancesResponseWrapper' + description: Success + x-aws-operation-name: RunInstances + /?__Action=StartInstances&__Version=2016-11-15: + post: + description:

Starts an Amazon EBS-backed instance that you've previously stopped.

Instances that use Amazon EBS volumes as their root devices can be quickly stopped and started. When an instance is stopped, the compute resources are released and you are not billed for instance usage. However, your root partition Amazon EBS volume remains and continues to persist your data, and you are charged for Amazon EBS volume usage. You can restart your instance at any time. Every time you start your instance, Amazon EC2 charges a one-minute minimum for instance usage, and thereafter charges per second for instance usage.

Before stopping an instance, make sure it is in a state from which it can be restarted. Stopping an instance does not preserve data stored in RAM.

Performing this operation on an instance that uses an instance store as its root device returns an error.

If you attempt to start a T3 instance with host tenancy and the unlimted CPU credit option, the request fails. The unlimited CPU credit option is not supported on Dedicated Hosts. Before you start the instance, either change its CPU credit option to standard, or change its tenancy to default or dedicated.

For more information, see Stop and start your instance in the Amazon EC2 User Guide.

+ operationId: GET_StartInstances + parameters: + - description: The IDs of the instances. + in: query + name: InstanceId + required: true + schema: + items: + $ref: '#/components/schemas/InstanceId' + type: array + - description: Reserved. + in: query + name: AdditionalInfo + required: false + schema: + type: string + - description: Checks whether you have the required permissions for the action, without actually making the request, and provides an error response. If you have the required permissions, the error response is DryRunOperation. Otherwise, it is UnauthorizedOperation. + in: query + name: DryRun + required: false + schema: + type: boolean + responses: + "200": + content: + text/xml: + schema: + $ref: '#/components/schemas/StartInstancesResponseWrapper' + description: Success + x-aws-operation-name: StartInstances + /?__Action=StopInstances&__Version=2016-11-15: + post: + description:

Stops an Amazon EBS-backed instance. For more information, see Stop and start your instance in the Amazon EC2 User Guide.

You can use the Stop action to hibernate an instance if the instance is enabled for hibernation and it meets the hibernation prerequisites. For more information, see Hibernate your instance in the Amazon EC2 User Guide.

We don't charge usage for a stopped instance, or data transfer fees; however, your root partition Amazon EBS volume remains and continues to persist your data, and you are charged for Amazon EBS volume usage. Every time you start your instance, Amazon EC2 charges a one-minute minimum for instance usage, and thereafter charges per second for instance usage.

You can't stop or hibernate instance store-backed instances. You can't use the Stop action to hibernate Spot Instances, but you can specify that Amazon EC2 should hibernate Spot Instances when they are interrupted. For more information, see Hibernating interrupted Spot Instances in the Amazon EC2 User Guide.

When you stop or hibernate an instance, we shut it down. You can restart your instance at any time. Before stopping or hibernating an instance, make sure it is in a state from which it can be restarted. Stopping an instance does not preserve data stored in RAM, but hibernating an instance does preserve data stored in RAM. If an instance cannot hibernate successfully, a normal shutdown occurs.

Stopping and hibernating an instance is different to rebooting or terminating it. For example, when you stop or hibernate an instance, the root device and any other devices attached to the instance persist. When you terminate an instance, the root device and any other devices attached during the instance launch are automatically deleted. For more information about the differences between rebooting, stopping, hibernating, and terminating instances, see Instance lifecycle in the Amazon EC2 User Guide.

When you stop an instance, we attempt to shut it down forcibly after a short while. If your instance appears stuck in the stopping state after a period of time, there may be an issue with the underlying host computer. For more information, see Troubleshoot stopping your instance in the Amazon EC2 User Guide.

+ operationId: GET_StopInstances + parameters: + - description: The IDs of the instances. + in: query + name: InstanceId + required: true + schema: + items: + allOf: + - $ref: '#/components/schemas/InstanceId' + - xml: + name: InstanceId + type: array + - description: '

Hibernates the instance if the instance was enabled for hibernation at launch. If the instance cannot hibernate successfully, a normal shutdown occurs. For more information, see Hibernate your instance in the Amazon EC2 User Guide.

Default: false

' + in: query + name: Hibernate + required: false + schema: + type: boolean + - description: Checks whether you have the required permissions for the action, without actually making the request, and provides an error response. If you have the required permissions, the error response is DryRunOperation. Otherwise, it is UnauthorizedOperation. + in: query + name: DryRun + required: false + schema: + type: boolean + - description: '

Forces the instances to stop. The instances do not have an opportunity to flush file system caches or file system metadata. If you use this option, you must perform file system check and repair procedures. This option is not recommended for Windows instances.

Default: false

' + in: query + name: Force + required: false + schema: + type: boolean + responses: + "200": + content: + text/xml: + schema: + $ref: '#/components/schemas/StopInstancesResponseWrapper' + description: Success + x-aws-operation-name: StopInstances + /?__Action=TerminateInstances&__Version=2016-11-15: + post: + description: '

Shuts down the specified instances. This operation is idempotent; if you terminate an instance more than once, each call succeeds.

If you specify multiple instances and the request fails (for example, because of a single incorrect instance ID), none of the instances are terminated.

If you terminate multiple instances across multiple Availability Zones, and one or more of the specified instances are enabled for termination protection, the request fails with the following results:

  • The specified instances that are in the same Availability Zone as the protected instance are not terminated.

  • The specified instances that are in different Availability Zones, where no other specified instances are protected, are successfully terminated.

For example, say you have the following instances:

  • Instance A: us-east-1a; Not protected

  • Instance B: us-east-1a; Not protected

  • Instance C: us-east-1b; Protected

  • Instance D: us-east-1b; not protected

If you attempt to terminate all of these instances in the same request, the request reports failure with the following results:

  • Instance A and Instance B are successfully terminated because none of the specified instances in us-east-1a are enabled for termination protection.

  • Instance C and Instance D fail to terminate because at least one of the specified instances in us-east-1b (Instance C) is enabled for termination protection.

Terminated instances remain visible after termination (for approximately one hour).

By default, Amazon EC2 deletes all EBS volumes that were attached when the instance launched. Volumes attached after instance launch continue running.

You can stop, start, and terminate EBS-backed instances. You can only terminate instance store-backed instances. What happens to an instance differs if you stop it or terminate it. For example, when you stop an instance, the root device and any other devices attached to the instance persist. When you terminate an instance, any attached EBS volumes with the DeleteOnTermination block device mapping parameter set to true are automatically deleted. For more information about the differences between stopping and terminating instances, see Instance lifecycle in the Amazon EC2 User Guide.

For more information about troubleshooting, see Troubleshooting terminating your instance in the Amazon EC2 User Guide.

' + operationId: GET_TerminateInstances + parameters: [] + responses: + "200": + content: + text/xml: + schema: + $ref: '#/components/schemas/TerminateInstancesResponseWrapper' + description: Success + x-aws-operation-name: TerminateInstances + /?__Action=UnmonitorInstances&__Version=2016-11-15: + post: + description: Disables detailed monitoring for a running instance. For more information, see Monitoring your instances and volumes in the Amazon EC2 User Guide. + operationId: GET_UnmonitorInstances + parameters: + - description: The IDs of the instances. + in: query + name: InstanceId + required: true + schema: + items: + allOf: + - $ref: '#/components/schemas/InstanceId' + - xml: + name: InstanceId + type: array + - description: Checks whether you have the required permissions for the action, without actually making the request, and provides an error response. If you have the required permissions, the error response is DryRunOperation. Otherwise, it is UnauthorizedOperation. + in: query + name: DryRun + required: false + schema: + type: boolean + responses: + "200": + content: + text/xml: + schema: + $ref: '#/components/schemas/UnmonitorInstancesResponseWrapper' + description: Success + x-aws-operation-name: UnmonitorInstances +security: + - hmac: [] +servers: + - description: The Amazon EC2 multi-region endpoint + url: http://localhost:5000 + variables: + region: + default: us-east-1 + description: The AWS region + enum: + - us-east-1 + - us-east-2 + - us-west-1 + - us-west-2 + - us-gov-west-1 + - us-gov-east-1 + - ca-central-1 + - eu-north-1 + - eu-west-1 + - eu-west-2 + - eu-west-3 + - eu-central-1 + - eu-south-1 + - af-south-1 + - ap-northeast-1 + - ap-northeast-2 + - ap-northeast-3 + - ap-southeast-1 + - ap-southeast-2 + - ap-east-1 + - ap-south-1 + - sa-east-1 + - me-south-1 + - description: The general Amazon EC2 endpoint for US East (N. Virginia) + url: http://localhost:5000 + variables: {} + - description: The Amazon EC2 endpoint for China (Beijing) and China (Ningxia) + url: http://localhost:5000 + variables: + region: + default: cn-north-1 + description: The AWS region + enum: + - cn-north-1 + - cn-northwest-1 diff --git a/test/robot/auto-mocks/auto_mocks.robot b/test/robot/auto-mocks/auto_mocks.robot new file mode 100644 index 0000000..a1e5a8e --- /dev/null +++ b/test/robot/auto-mocks/auto_mocks.robot @@ -0,0 +1,3 @@ +*** Variables *** +${REPOSITORY_ROOT} ${CURDIR}${/}..${/}..${/}..${/}.. +${LOCAL_LIB_HOME} ${REPOSITORY_ROOT}${/}test${/}python \ No newline at end of file diff --git a/test/robot/cli/aot/aot_adhoc.robot b/test/robot/cli/aot/aot_adhoc.robot index 1a01bf7..5da5931 100644 --- a/test/robot/cli/aot/aot_adhoc.robot +++ b/test/robot/cli/aot/aot_adhoc.robot @@ -117,7 +117,7 @@ AOT Resource Level Analysis AWS EC2 volumes_post_naively_presented with CLI Should Contain ${result.stdout} ... "warning_count": 2 Should Contain ${result.stdout} - ... sample_response + ... ec2.volumes_post_naively_presented Should Be Equal As Strings ${result.rc} 0 AOT Method Level Analysis AWS EC2 volumes_post_naively_presented describeVolumes with CLI @@ -167,7 +167,58 @@ AOT Method Level Analysis AWS EC2 volumes_post_naively_presented describeVolumes Should Contain ${result.stdout} ... "missing-semantics" Should Contain ${result.stdout} - ... "method": "describeVolumes" + ... ec2.volumes_post_naively_presented + Should Be Equal As Strings ${result.rc} 0 + +Closure Generation AWS EC2 volumes_post_naively_presented with Rewrite + [Documentation] Test closure generation with server URL rewrite produces valid minimal service doc + [Tags] cli closure + ${result} = Run Process + ... ${CLI_EXE} + ... closure + ... \./test/registry + ... \./test/registry/src/aws/v0\.1\.0/provider\.yaml + ... ec2 + ... \-\-provider + ... aws + ... \-\-resource + ... volumes_post_naively_presented + ... \-\-rewrite-url + ... http://localhost:1091 + ... cwd=${CWD_FOR_EXEC} + ... stdout=${CURDIR}${/}/tmp${/}Closure-Generation-AWS-EC2-with-CLI.yaml + ... stderr=${CURDIR}${/}/tmp${/}Closure-Generation-AWS-EC2-with-CLI_stderr.txt + Log Stderr = ${result.stderr} + Log Stdout = ${result.stdout} + Log RC = ${result.rc} + # stdout: valid YAML closure with only the target resource + Should Contain ${result.stdout} + ... x-stackQL-resources + Should Contain ${result.stdout} + ... volumes_post_naively_presented + # Only the POST path for DescribeVolumes should be present + Should Contain ${result.stdout} + ... Action=DescribeVolumes + # Transitive schemas included + Should Contain ${result.stdout} + ... DescribeVolumesOutput + Should Contain ${result.stdout} + ... DescribeVolumesRequest + Should Contain ${result.stdout} + ... DescribeVolumesResult + # Server URLs rewritten + Should Contain ${result.stdout} + ... http://localhost:1091 + # Original server URLs absent (check the url: field pattern, not metadata like x-providerName) + Should Not Contain ${result.stdout} + ... ec2.{region}.amazonaws.com + # Other resources from ec2.yaml absent + Should Not Contain ${result.stdout} + ... volumes_presented + # Security schemes preserved + Should Contain ${result.stdout} + ... securitySchemes + # Method transform config survives round-trip Should Contain ${result.stdout} - ... sample_response + ... toJson Should Be Equal As Strings ${result.rc} 0