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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/prod-web-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ jobs:
name: Build Docusaurus
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
with:
fetch-depth: 0

- uses: actions/setup-node@v4
- uses: actions/setup-node@v7
with:
node-version: 20
cache: yarn
Expand All @@ -31,7 +31,7 @@ jobs:
working-directory: website

- name: Upload Build Artifact
uses: actions/upload-pages-artifact@v3
uses: actions/upload-pages-artifact@v5
with:
path: website/build # Ensure the path is correctly set to the Docusaurus build output

Expand All @@ -53,6 +53,6 @@ jobs:
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
uses: actions/deploy-pages@v5
with:
working-directory: website/build # Ensures the correct directory is used for deployment
42 changes: 0 additions & 42 deletions .github/workflows/star-check.yml

This file was deleted.

4 changes: 2 additions & 2 deletions .github/workflows/test-web-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ jobs:
name: Test deployment
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
with:
fetch-depth: 0

- uses: actions/setup-node@v4
- uses: actions/setup-node@v7
with:
node-version: 20
cache: yarn
Expand Down
56 changes: 56 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# CLAUDE.md

Guidance for working in this repo. It generates the `linode` StackQL provider from the Linode OpenAPI spec and builds the provider docs microsite (Docusaurus, GitHub Pages at linode-provider.stackql.io).

## Layout

- `provider-dev/downloaded/` - raw + preprocessed OpenAPI spec (`make download`)
- `provider-dev/source/` - per-service split specs (`make split`, `make normalize`)
- `provider-dev/config/all_services.csv` - **master mapping document**: one row per operation mapping to StackQL resource/method/verb. Never regenerate from scratch; `make mappings` preserves existing rows and appends new operations
- `provider-dev/config/service-config.json` - pagination config injected as `x-stackQL-config` into every service
- `provider-dev/openapi/src/linode/v00.00.00000/` - the generated provider (the publishable artifact)
- `bin/` - pipeline wrappers, server scripts, `test-meta-routes.cjs`, `smoke-test.mjs`
- `website/` - Docusaurus 3.10 microsite; `website/docs/` is generated by `make docs`, do not hand-edit
- `examples/stackql-deploy/linode-demo/` - example stackql-deploy stack
- `Makefile` - the whole pipeline; `make help` lists targets, `make all` runs everything

## Toolchain

- Tooling: `@stackql/provider-utils` (CLI bins under `node_modules/@stackql/provider-utils/bin/`), invoked via npm scripts / make targets
- Make targets run under a POSIX shell. On this Windows machine, use WSL (`make`, `node`, `python3` and the Linux `stackql` binary are on the WSL PATH)
- `LINODE_TOKEN` lives in `.env` (gitignored); make targets source it for live work

## Mapping conventions (all_services.csv)

- `stackql_verb`: `select`/`insert`/`replace` (PUT)/`update` (PATCH)/`delete`, action-style POSTs (boot, clone, resize, ...) are `exec`
- `stackql_method_name` is user-facing API surface (EXEC statements, SHOW METHODS, docs) - **no HTTP plumbing or resource-name repetition**. Convention (matches the openai provider): selects are `get` (single) / `list` (collection, `list_by_<scope>` for variants), `create`, `update` (PUT), `patch`, `delete`, and exec methods are bare action verbs (`boot`, `shutdown`, `resize`, `reset_credentials`). Example: `EXEC linode.linode.instances.shutdown @linodeId = '123'` - never `post_shutdown_linode_instance`
- `stackql_object_key`: set `$.data` for select methods returning the standard Linode envelope `{data, page, pages, results}`; leave empty for single-object responses. **Without `$.data`, list queries return the envelope as one useless row** - this was the pre-2026 provider's behavior and was fixed in the August 2026 uplift
- The `path`/`verb` columns must match the current split specs; rows are matched by `filename` + `operationId` during generate, but stale paths cause "not found in manifest" errors - rebuild them from the source specs if the upstream API renames path params

## Provider design decisions

- **Pagination**: `page_number` algorithm (`requestToken: page` in query, `responseToken: $.page`, `responseTerminator: $.pages`) - uniform across all services via `--service-config`
- **Request bodies**: `--naive-req-body-translate` - INSERT/UPDATE columns are native API property names (no `data__` prefix). This is a breaking change vs the pre-2026 provider
- **Predicate pushdown**: deliberately not configured. Linode filters via the `X-Filter` HTTP header; any-sdk `queryParamPushdown` only emits query params (OData syntax for filters). `LIMIT`->`page_size` pushdown was also rejected because Linode errors on `page_size < 25`
- **Path params keep upstream camelCase** (`linodeId`, `regionId`) for continuity with the previous release - `--update-path-param-names` is not used
- The old python patch scripts (`update_linode_interfaces.py`, `update_managed_stats.py`) were deleted - provider-utils `normalize` flattens those unions now
- **`provider-dev/scripts/patch_provider_output.py` must run after generate** (the Makefile `provider` target does this). It works around two upstream any-sdk/stackql bugs that otherwise break EXEC lifecycle methods: (1) EXEC param validation accepts `string`/`int` schema types but not OpenAPI `integer`, so integer path params are retyped to string (same workaround the DigitalOcean provider ships); (2) the EXEC planner tabulates the response schema and resolves each supplied `@param` against it via FindByPath, where a miss returns a wrapped nil that panics the engine - Linode's opaque `type: string` action responses (wire body `{}`) are rewritten to objects carrying the operation's param names plus a `message` property (all always null). Both are worth upstream fixes in any-sdk

## Gotchas

- **Restart the local server after regenerating the provider** - it holds parsed docs in memory (stale docs silently serve old behavior). `make test` / `make smoke-test` handle this
- **Never `registry pull linode` when testing the local build** - even with a `file://` registry it can fall back to the public registry and shadow the local `v00.00.00000` with the published version (cached under `.stackql/src/linode/`). The local server serves straight from `localDocRoot` without a pull
- Single-row reads by path param (`WHERE volumeId = 123`) work via `stackql exec` but are unreliable through the pg wire server - poll via list queries in tests
- **EXEC params must be quoted strings**: `EXEC linode.linode.instances.shutdown @linodeId = '123'` - the unquoted integer form fails validation ("does NOT match SQL type 'IntVal'"), and through the pg wire server that failure can be silent
- An instance created without an image has no disks, cannot boot, and legitimately stays in `provisioning` status
- On Windows, always pass `encoding='utf-8'` when opening spec/provider files in python (emoji in descriptions break cp1252)

## Testing

- `make test` - meta routes (services/resources/methods/describe) against the local build; no creds needed
- `make smoke-test` - live API test (local provider); `make smoke-test-live` - same against the latest published provider. Creates and deletes a `g6-nanode-1` (no image) and a 10GB volume; costs under USD 0.05/run; cleanup is verified
- Meta-route baseline after the Aug 2026 update: 21 services, 142 resources, 449 methods, 217 selectable

## Publishing

Provider: copy `provider-dev/openapi/src/linode` into `providers/src` in a feature branch of `stackql/stackql-provider-registry` and follow the registry release flow. Website: push to `main` touching `website/**` triggers the GitHub Pages deploy workflow.
141 changes: 141 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
# Makefile for the linode StackQL provider.
#
# Run under a POSIX shell (WSL, Linux or macOS) with node >= 20, python3 and
# a `stackql` binary either in the repo root or on the PATH.
# `LINODE_TOKEN` is sourced from .env (see .env.example) for live targets.

SHELL := /bin/bash

PROVIDER_NAME := linode
SPEC_URL := https://github.com/linode/linode-api-docs/raw/refs/heads/development/openapi.json
DOWNLOAD_DIR := provider-dev/downloaded
SOURCE_DIR := provider-dev/source
CONFIG_DIR := provider-dev/config
OPENAPI_DIR := provider-dev/openapi
REGISTRY_ROOT := $(CURDIR)/$(OPENAPI_DIR)
SERVER_PORT := 5444

# stackql binary: repo root first, then PATH
STACKQL_BIN := $(shell [ -f ./stackql ] && echo ./stackql || command -v stackql)

LOAD_ENV = if [ -f .env ]; then set -a; . ./.env; set +a; fi

.DEFAULT_GOAL := help

.PHONY: help deps download split normalize mappings provider test docs website \
start-server stop-server server-status test-meta-routes \
smoke-test smoke-test-live clean all

help:
@echo "linode StackQL provider - make targets:"
@echo ""
@echo " make deps install node dependencies (repo + website)"
@echo " make download download and preprocess the Linode OpenAPI spec"
@echo " make split split the spec into service-level specs"
@echo " make normalize normalize the split specs (allOf flattening etc.)"
@echo " make mappings regenerate all_services.csv (preserves existing mappings)"
@echo " make provider generate the StackQL provider extension docs"
@echo " make test start server, test all meta routes, stop server"
@echo " make smoke-test live smoke test against the locally generated provider"
@echo " make smoke-test-live live smoke test against the latest published provider"
@echo " make docs generate the Docusaurus docs from the provider"
@echo " make website build the docs microsite"
@echo " make all everything: deps through website"
@echo ""
@echo " make start-server / stop-server / server-status"

deps:
npm install
cd website && yarn install

download:
rm -f $(DOWNLOAD_DIR)/*
curl -sL $(SPEC_URL) -o $(DOWNLOAD_DIR)/openapi.json
python3 provider-dev/scripts/remove_api_version.py

split:
rm -rf $(SOURCE_DIR)/*
npm run split -- \
--provider-name $(PROVIDER_NAME) \
--api-doc $(DOWNLOAD_DIR)/openapi_api_version_removed.json \
--svc-discriminator path \
--output-dir $(SOURCE_DIR) \
--overwrite \
--svc-name-overrides '{"network_transfer": "networking"}'

normalize:
npm run normalize -- --api-dir $(SOURCE_DIR)

mappings:
npm run generate-mappings -- \
--input-dir $(SOURCE_DIR) \
--output-dir $(CONFIG_DIR)
@echo ""
@echo "Review $(CONFIG_DIR)/all_services.csv - any new (unmapped) operations need"
@echo "stackql_resource_name / stackql_method_name / stackql_verb assigned, and"
@echo "enveloped list responses need stackql_object_key set to \$$.data"

provider:
rm -rf $(OPENAPI_DIR)/*
npm run generate-provider -- \
--provider-name $(PROVIDER_NAME) \
--input-dir $(SOURCE_DIR) \
--output-dir $(OPENAPI_DIR)/src/$(PROVIDER_NAME) \
--config-path $(CONFIG_DIR)/all_services.csv \
--servers '[{"url": "https://api.linode.com/v4"}]' \
--provider-config '{"auth": {"credentialsenvvar": "LINODE_TOKEN","type": "bearer"}}' \
--service-config $(CONFIG_DIR)/service-config.json \
--naive-req-body-translate \
--overwrite
python3 provider-dev/scripts/patch_provider_output.py

start-server:
@$(LOAD_ENV); ./bin/start-server.sh --provider $(PROVIDER_NAME) --registry "$(REGISTRY_ROOT)" --port $(SERVER_PORT)

stop-server:
@./bin/stop-server.sh --port $(SERVER_PORT) || true

server-status:
@./bin/server-status.sh

test-meta-routes:
node ./bin/test-meta-routes.cjs $(PROVIDER_NAME) --port $(SERVER_PORT)

# always restart the server around provider tests: a running server holds
# parsed provider docs in memory and will not see a regenerated provider
test: stop-server start-server
sleep 3
node ./bin/test-meta-routes.cjs $(PROVIDER_NAME) --port $(SERVER_PORT)
$(MAKE) stop-server

smoke-test: stop-server start-server
sleep 3
node ./bin/smoke-test.mjs --port $(SERVER_PORT) || ($(MAKE) stop-server; exit 1)
$(MAKE) stop-server

smoke-test-live: stop-server
@$(LOAD_ENV); \
$(STACKQL_BIN) exec "registry pull $(PROVIDER_NAME)" && \
nohup $(STACKQL_BIN) --pgsrv.port=$(SERVER_PORT) srv > stackql-server.log 2>&1 & \
sleep 3
node ./bin/smoke-test.mjs --live --port $(SERVER_PORT) || ($(MAKE) stop-server; exit 1)
$(MAKE) stop-server

docs:
rm -rf website/docs/*
npm run generate-docs -- \
--provider-name $(PROVIDER_NAME) \
--provider-dir ./$(OPENAPI_DIR)/src/$(PROVIDER_NAME)/v00.00.00000 \
--output-dir ./website \
--provider-data-dir ./provider-dev/docgen/provider-data

website:
cd website && yarn build

clean:
rm -rf $(SOURCE_DIR)/* $(OPENAPI_DIR)/* website/build website/.docusaurus .stackql stackql-server.log

all: deps download split normalize mappings provider test docs website
@echo ""
@echo "Provider and website generated and tested. Run 'make smoke-test' for the"
@echo "live API smoke test (creates and deletes a nanode and a 10GB volume)."
Loading