linode provider for stackql
This repository is used to generate and document the linode provider for StackQL, allowing you to query and manipulate Linode resources using SQL. The provider is built from the Linode OpenAPI specification using the @stackql/provider-utils package.
- Node.js >= 20 and
npm(andyarnfor the docs site) - Python 3 (spec preprocessing)
- GNU make and a POSIX shell (WSL, Linux or macOS)
- A
stackqlbinary in the repo root or on thePATH - A Linode API token in
LINODE_TOKEN(put it in.env- see below) for live tests
Install dependencies:
make depsCreate a .env file in the repo root for anything that talks to the live API:
LINODE_TOKEN=your-token-here
make all runs the entire pipeline (deps, download, split, normalize, mappings, provider, meta-route tests, docs, website build). The individual steps are described below.
make downloadDownloads the latest spec from the development branch of linode/linode-api-docs and preprocesses it (provider-dev/scripts/remove_api_version.py removes the {apiVersion} path parameter and strips CLI/OAuth boilerplate from descriptions), producing provider-dev/downloaded/openapi_api_version_removed.json.
make splitSplits the monolithic spec into per-service specs in provider-dev/source/, discriminating on the first path segment (with network_transfer folded into networking).
make normalizeFlattens allOf compositions, renames anonymous oneOf/anyOf variants, lifts path-level parameters and marks bare-array responses. This step supersedes the hand-written schema patch scripts (update_linode_interfaces.py, update_managed_stats.py) that earlier versions of this repo carried.
make mappingsRegenerates provider-dev/config/all_services.csv, preserving all existing mappings and appending any new (unmapped) operations. For new rows, assign:
stackql_resource_name- the resource the operation belongs tostackql_method_name- unique method name within the resourcestackql_verb-select|insert|update|replace|delete|execstackql_object_key- set to$.dataforselectmethods whose response is the standard Linode envelope ({data, page, pages, results}); leave empty for single-object responses
Method names are user-facing (they surface in EXEC statements, SHOW METHODS and the docs), so they carry no HTTP plumbing and no resource-name repetition: selects are get/list (plus list_by_<scope> variants), mutations are create/update/patch/delete, and lifecycle operations are bare action verbs - boot, shutdown, resize, reset_credentials:
EXEC linode.linode.instances.shutdown @linodeId = '12345678';
EXECparameter values are always quoted strings, including numeric ids.
This CSV is the master mapping document - the intent over time is to add new services/resources/ops and remove deprecated ones, never to regenerate it from scratch.
make providerGenerates the StackQL provider extension docs into provider-dev/openapi/src/linode/v00.00.00000/, wiring in:
- Servers and auth -
https://api.linode.com/v4, bearer auth fromLINODE_TOKEN - Pagination (
provider-dev/config/service-config.json, injected asx-stackQL-configin every service) - Linode'spage/pagesenvelope drives thepage_numberpagination algorithm, so multi-page collections are traversed automatically - Naive request body translation (
--naive-req-body-translate) -INSERT/UPDATE/REPLACEcolumns are the native Linode API body property names (label,region,type, ...), notdata__-prefixed
The target then runs provider-dev/scripts/patch_provider_output.py, which patches the generated docs to make EXEC lifecycle methods work with current stackql/any-sdk: integer path params are retyped to strings (any-sdk's EXEC validation does not accept the integer schema type), and the opaque type: string responses on action operations are rewritten to minimal object schemas (the EXEC planner needs a tabulatable response and resolves every supplied parameter against it). See the script docstring for details.
Predicate pushdown: not wired in for this provider. Linode implements server-side filtering via the
X-FilterHTTP request header, and any-sdk'squeryParamPushdowncan only emit query parameters (with OData filter syntax).LIMITpushdown topage_sizeis also omitted because Linode rejectspage_size < 25, which would break smallLIMITqueries. Predicates are evaluated locally by StackQL, which is functionally equivalent.
make testStarts a local StackQL server against the generated provider (file:// registry over provider-dev/openapi), tests every meta route (services, resources, methods, describe), and stops the server.
The server holds parsed provider docs in memory - always restart it (which
make testdoes) after regenerating the provider.
For interactive exploration:
make start-server
stackql shell # or connect any postgres client to localhost:5444
make stop-servermake smoke-test # locally generated provider
make smoke-test-live # latest published provider from the public registryRuns bin/smoke-test.mjs against the live Linode API using LINODE_TOKEN from .env. It covers the critical read paths (account, regions, types, images, kernels - which also proves multi-page pagination - instances, volumes, object storage, firewalls) plus a full compute lifecycle - create a g6-nanode-1 running Debian 12, wait for it to boot, EXEC linode.linode.instances.shutdown, wait for offline, EXEC linode.linode.instances.boot, wait for running, then delete - and a create/verify/delete cycle for a minimum-size 10GB volume. Resources exist for a few minutes; worst-case spend is well under USD 0.05 per run, and all mutations are cleaned up and verified in finally blocks.
To publish the provider push the linode dir to providers/src in a feature branch of the stackql-provider-registry. Follow the registry release flow.
Test the published provider from the dev registry:
export DEV_REG="{ \"url\": \"https://registry-dev.stackql.app/providers\" }"
stackql --registry="${DEV_REG}" shellregistry pull linode;make docs # generate docs from the provider into website/
make website # build the Docusaurus siteThe microsite is Docusaurus 3.10 using the shared StackQL provider-site configuration (stackql/docusaurus-config), vendored into website/.shared-config/ at build time - see website/README.md. Provider identity lives in website/provider.js; header content for the generated index page lives in provider-dev/docgen/provider-data/.
Deployment to linode-provider.stackql.io is via GitHub Pages on push to main (.github/workflows/prod-web-deploy.yml).
-- Get account information
SELECT company, country, balance, active_since
FROM linode.account.account;
-- List all Linode instances
SELECT id, label, region, status, type, ipv4
FROM linode.linode.instances;
-- Region capability audit
SELECT id, country, status
FROM linode.regions.regions;
-- Create a nanode (columns are native API body properties)
INSERT INTO linode.linode.instances (label, region, type, image, root_pass)
SELECT 'my-vm', 'us-ord', 'g6-nanode-1', 'linode/debian12', 'S3cureP@ssw0rd!';
-- Stop and start it (lifecycle operations are EXEC methods)
EXEC linode.linode.instances.shutdown @linodeId = '12345678';
EXEC linode.linode.instances.boot @linodeId = '12345678';
-- Delete it
DELETE FROM linode.linode.instances WHERE linodeId = 12345678;An example stackql-deploy stack (compute instance + block storage volume, with per-environment sizing and full lifecycle anchors) is in examples/stackql-deploy/linode-demo.
MIT
Contributions are welcome! Please feel free to submit a Pull Request.