Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
c6a2d0f
mock-generation
general-kroll-4-life Apr 3, 2026
a149611
- Amended robot test.
general-kroll-4-life Apr 3, 2026
a81f6bf
auto-mocks
general-kroll-4-life Apr 3, 2026
e38effe
more-tests
general-kroll-4-life Apr 3, 2026
807f63e
mock-coverage
general-kroll-4-life Apr 3, 2026
86459d7
stagin pint
general-kroll-4-life Apr 3, 2026
6ea3a20
mock-writer
general-kroll-4-life Apr 3, 2026
a9c639f
slow-progress
general-kroll-4-life Apr 4, 2026
c8555d9
slow-progress-mocking
general-kroll-4-life Apr 4, 2026
126e64b
valid-reference-example
general-kroll-4-life Apr 4, 2026
71b429e
crude example
general-kroll-4-life Apr 4, 2026
8d52ec0
progression
general-kroll-4-life Apr 5, 2026
96e137f
closer-to-automation
general-kroll-4-life Apr 5, 2026
a6f3988
crude-manual-opening
general-kroll-4-life Apr 5, 2026
cb0908d
getting closer
general-kroll-4-life Apr 5, 2026
c8718d5
another-step
general-kroll-4-life Apr 5, 2026
079ac99
slow progress
general-kroll-4-life Apr 5, 2026
93296b7
closer
general-kroll-4-life Apr 5, 2026
a221608
pretty good
general-kroll-4-life Apr 5, 2026
49e4d5b
better response
general-kroll-4-life Apr 5, 2026
456ac93
preparatory-step
general-kroll-4-life Apr 5, 2026
a8a530d
another attempt
general-kroll-4-life Apr 5, 2026
9b03e97
trial-expt
general-kroll-4-life Apr 5, 2026
11763df
fix-busted
general-kroll-4-life Apr 5, 2026
331e8b2
faster-download
general-kroll-4-life Apr 5, 2026
e192ccd
disambiguation
general-kroll-4-life Apr 5, 2026
c4e5c06
ffs
general-kroll-4-life Apr 5, 2026
9a0dd35
agai run
general-kroll-4-life Apr 5, 2026
e4c8f21
corner-case-handling
general-kroll-4-life Apr 5, 2026
fd934b2
attempt-parallel
general-kroll-4-life Apr 5, 2026
4a4173c
avoid-collisions
general-kroll-4-life Apr 5, 2026
9a10941
better QA
general-kroll-4-life Apr 5, 2026
95ea952
safe-duration-mocks
general-kroll-4-life Apr 5, 2026
3b4ad4b
better-logic
general-kroll-4-life Apr 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
442 changes: 442 additions & 0 deletions .github/workflows/mock-experiment.yml

Large diffs are not rendered by default.

110 changes: 84 additions & 26 deletions .github/workflows/provider-analysis.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Build
name: Provider Analysis

permissions:
contents: read
Expand Down Expand Up @@ -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};"
Expand All @@ -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
Expand All @@ -191,19 +247,21 @@ 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

DEST="gs://${ANALYSIS_RESULTS_BUCKET}/provider-analysis/${DATE_PATH}/${RUN_TS}"

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"


2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
opint
.DS_Store
dist/
.venv/
*.venv/
__pycache__/
*.py[co]
stackql-core/
Expand Down
2 changes: 2 additions & 0 deletions cicd/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
prior/
post/
10 changes: 10 additions & 0 deletions cicd/mock-testing-requirements.txt
Original file line number Diff line number Diff line change
@@ -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
File renamed without changes.
2 changes: 2 additions & 0 deletions cicd/out/auto-mocks/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*
!.gitignore
2 changes: 2 additions & 0 deletions cicd/out/closures/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*
!.gitignore
2 changes: 2 additions & 0 deletions cicd/out/generic/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*
!.gitignore
2 changes: 2 additions & 0 deletions cicd/out/mock-expectations/aws/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*
!.gitignore
2 changes: 2 additions & 0 deletions cicd/out/mock-queries/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*
!.gitignore
74 changes: 74 additions & 0 deletions cicd/python/record_parser/README.md
Original file line number Diff line number Diff line change
@@ -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": "<root/>",
"output": "{\"root\":\"\"}",
"ok": true
},
{
"input": "<root></root>",
"output": "{\"root\":\"\"}",
"ok": true
}
]
},
"sample_response": {
"pre_transform": "<Response><DescribeVolumesResponse><NextToken>sample_string</NextToken><Volumes><item><AvailabilityZone>sample_string</AvailabilityZone><Attachments><item></item></Attachments><Size>0</Size><State>sample_string</State><FastRestored>false</FastRestored><CreateTime>sample_string</CreateTime><SnapshotId>sample_string</SnapshotId><Encrypted>false</Encrypted><Iops>0</Iops><Throughput>0</Throughput><KmsKeyId>sample_string</KmsKeyId><OutpostArn>sample_string</OutpostArn><VolumeType>sample_string</VolumeType><Tags><item></item></Tags><VolumeId>sample_string</VolumeId><MultiAttachEnabled>false</MultiAttachEnabled></item></Volumes></DescribeVolumesResponse></Response>",
"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]"
}
```
12 changes: 12 additions & 0 deletions cicd/python/record_parser/pyproject.toml
Original file line number Diff line number Diff line change
@@ -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"]
8 changes: 8 additions & 0 deletions cicd/python/record_parser/src/record_parser/__init__.py
Original file line number Diff line number Diff line change
@@ -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",
]
45 changes: 45 additions & 0 deletions cicd/python/record_parser/src/record_parser/core.py
Original file line number Diff line number Diff line change
@@ -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'])

Loading
Loading