Distributing the Cookwala samples
Status (2026-10-06): published on PyPI, npm, Maven Central, NuGet, Homebrew and GHCR (tag samples-v0.3.0), with the single file and the .deb on GitHub Releases. Chocolatey is submitted and in moderation; RPM, pacman (AUR), apk, conda-forge and Snap have manifests ready but nothing submitted yet. JFrog Artifactory is a bring-your-own-instance channel — publish.sh pushes to whatever instance you point it at; there is no public one. Version: [VERSION](VERSION) (python packaging/build.py --check-versions fails CI if a manifest disagrees).
One payload, many channels #
The Python package is standard library only, so one file, cookwala-samples-<v>.pyz (a zipapp), runs on any Python 3.9+ on any OS. The OS package managers ship that file plus a one-line launcher; language registries ship their native port.
python samples/packaging/build.py builds the pyz, the wheel and sdist, the .deb, and renders every manifest below into samples/build/manifests/ with the version, URLs and sha256 filled in. Assets are expected at https://github.com/amado2k5/cookwala/releases/download/samples-v<v>/; pass --base-url to point them at another host, such as an Artifactory generic repository.
Channels #
| Channel | Install (once published) | Source in this repo | Published by | Needs |
|---|---|---|---|---|
| PyPI (pip, pipx, uv) | pip install cookwala-samples | python/pyproject.toml | CI on tag | PyPI trusted publisher for this workflow |
| npm | npm i -g @cookwala/samples · npx @cookwala/samples demo | js/package.json | CI on tag | NPM_TOKEN, the @cookwala npm scope |
| Maven Central (Maven, Gradle, sbt, Leiningen) | ai.cookwala:cookwala-samples:0.3.0 | java/pom.xml, java/build.gradle.kts | CI on tag | MAVEN_CENTRAL_USERNAME/PASSWORD, MAVEN_GPG_PRIVATE_KEY/PASSPHRASE, the ai.cookwala namespace |
| GitHub Packages (Maven) | as above, with the GitHub Packages repository | java/build.gradle.kts | CI on tag | nothing extra (GITHUB_TOKEN) |
| NuGet | dotnet add package Cookwala.Samples · dotnet tool install -g Cookwala.Samples.Tool | dotnet/Directory.Build.props, dotnet/src/*/*.csproj | CI on tag | a nuget.org trusted-publishing policy (owner amado2k5, repo cookwala, workflow samples.yml, environment release, pattern Cookwala.*); the package owner name is read from the NUGET_USER repository variable (default amado2026); or a NUGET_API_KEY secret |
| Homebrew (macOS, Linux) | brew install amado2k5/cookwala/cookwala-samples | packaging/homebrew/cookwala-samples.rb.in | CI on tag, to the tap repository | a tap repo amado2k5/homebrew-cookwala, HOMEBREW_TAP_TOKEN |
| Chocolatey (Windows) | choco install cookwala-samples | packaging/chocolatey/ | CI on tag (Windows runner) | CHOCO_API_KEY; community moderation before it is public |
| Scoop (Windows) | scoop bucket add cookwala https://github.com/amado2k5/scoop-cookwala · scoop install cookwala-samples | packaging/scoop/cookwala-samples.json.in | Excavator updates version/hash on each samples-v* release | live — bucket published, see packaging/scoop/SUBMIT.md |
| apt (Debian, Ubuntu) | sudo apt install ./cookwala-samples_0.3.0_all.deb, or from an apt repository | built by packaging/build.py | GitHub release asset; Artifactory Debian repo | an apt repository (Artifactory, Cloudsmith, a PPA) for apt install cookwala-samples |
| RPM (dnf, yum, zypper) | sudo dnf install cookwala-samples | packaging/rpm/cookwala-samples.spec.in | by hand: rpmbuild -ba, Fedora COPR or openSUSE OBS | a COPR or OBS project |
| pacman (Arch, AUR) | yay -S cookwala-samples | packaging/arch/PKGBUILD.in | by hand: push the rendered PKGBUILD to the AUR | an AUR account |
| apk (Alpine) | apk add cookwala-samples | packaging/alpine/APKBUILD.in | by hand: aports merge request | an aports maintainer |
| conda-forge (conda, mamba, pixi) | conda install -c conda-forge cookwala-samples | packaging/conda/meta.yaml.in (from the PyPI sdist) | by hand: staged-recipes pull request | the PyPI release first |
| Snap | sudo snap install cookwala-samples --edge | packaging/snap/snapcraft.yaml | by hand: snapcraft upload | a Snap Store account |
| OCI image (GHCR, Docker Hub, Quay, ECR, ACR) | docker run --rm -p 8080:8080 ghcr.io/amado2k5/cookwala-samples | packaging/docker/Containerfile | CI on tag (GHCR) | nothing extra (GITHUB_TOKEN) |
| Helm (Kubernetes) | helm install samples packaging/helm/cookwala-samples | packaging/helm/cookwala-samples/ | from the repository; an OCI chart push is one command (helm push) | a chart registry, if wanted |
| JFrog Artifactory | the native client of each type, pointed at your Artifactory | packaging/artifactory/publish.sh | CI on tag, or by hand | ARTIFACTORY_URL, ARTIFACTORY_USER, ARTIFACTORY_TOKEN (+ ARTIFACTORY_DOCKER_REGISTRY) |
| GitHub release | download the pyz, deb, wheel, sdist and SHA256SUMS | .github/workflows/samples.yml | CI on tag | nothing extra |
| Azure Functions | deploy | cloud/azure-functions/ | CI on demand (samples-cloud.yml, target=azure), or by hand (az, func) | an Azure subscription + cloud-test environment secrets |
| AWS Lambda | deploy | cloud/aws-lambda/ (SAM) | CI on demand (samples-cloud.yml, target=aws), or by hand (sam deploy) | an AWS account + cloud-test environment secret |
| Google Cloud Run functions | deploy | cloud/gcp-functions/ | CI on demand (samples-cloud.yml, target=gcp), or by hand (gcloud) | a GCP project + cloud-test environment secrets |
| OpenShift, OpenShift Serverless | deploy | cloud/openshift/ | by hand (oc) | a cluster |
Not provided, with the reason: winget needs a native Windows installer (exe or msi) and the samples ship a Python zipapp; Windows users have Chocolatey, Scoop, pip and npm. Go modules and crates.io have no sample port yet (the SDKs exist in sdk/go and sdk/rust).
Artifactory #
packaging/artifactory/publish.sh pushes each package type to a local repository of the matching type, with each ecosystem's own client: twine (PyPI), npm, Maven (-Partifactory) or Gradle, dotnet nuget push, Debian (with deb.distribution, deb.component, deb.architecture properties), RPM, Docker, and a generic repository for the pyz. Create the repositories first (default keys cookwala-<type>-local, overridable by environment variables listed in the script); then point remote or virtual repositories at them as usual.
Releasing #
- Bump
samples/VERSIONand every manifest (python samples/packaging/build.py --check-versionslists them); add a line to the Python, npm, Maven and NuGet changelogs if you keep them. - Regenerate the bundle if the vocabularies, limits or example recipes changed:
python samples/tools/build_bundle.py. - Merge, then tag:
git tag samples-v0.3.0 && git push origin samples-v0.3.0. - CI tests every port, builds every package, creates the GitHub release, and publishes to each registry whose secret is set. The other channels take the rendered manifests from the release's
samples-packagesartifact.
Verified in this repository (2026-10-05) #
Installed the way a user installs, from local stand-ins for each registry #
packaging/install-test.sh repeats all of this, and CI runs it on every change (install job). No registry is contacted: npm gets a Verdaccio registry, pip a PEP 503 index, Maven and Gradle a file repository, NuGet a folder feed, apt a repository made with apt-ftparchive, Homebrew a local tap and a local copy of the release asset.
| Channel | What ran | Where |
|---|---|---|
| npm | npx @cookwala/samples demo; npm i -g @cookwala/samples; import { demo } from '@cookwala/samples' in a new project | empty npm home and cache |
| PyPI | pip install cookwala-samples from the wheel and from the sdist; pipx install; uvx cookwala-samples | new virtual environments |
| single file | python3 cookwala-samples-0.3.0.pyz demo | any Python 3.9+ |
| Maven | a new project depending on ai.cookwala:cookwala-samples:0.3.0, compiled and run; java -jar on the artifact | empty local repository |
| Gradle | a new project with implementation("ai.cookwala:cookwala-samples:0.3.0"), gradle run | empty Gradle home |
| NuGet | dotnet add package Cookwala.Samples in a new console app, dotnet run; dotnet tool install -g Cookwala.Samples.Tool, then cookwala-samples | empty NuGet cache and tool home |
| apt | apt-get install cookwala-samples from an apt repository (pulls in python3), run, apt-get remove leaves nothing | Debian 12 container |
| RPM | rpmbuild -bb on the rendered spec (its %check runs the pyz), dnf install, run, dnf remove | Fedora 41 container |
| pacman | makepkg (checks the sha256 and runs check()), pacman -U, run, pacman -R | Arch Linux container |
| apk | abuild -r (sha256, check(), signed), apk add, run, apk del | Alpine 3.20 container |
| Homebrew | brew install from a tap (installs [email protected]), run, brew test passes | Homebrew's Linux container |
| conda | conda build on the rendered recipe from the sdist (its tests run), conda create, run | Miniforge container |
| Chocolatey | choco pack makes the .nupkg; the install and uninstall scripts run under PowerShell, download the asset, check its sha256, create and remove the command; Chocolatey's three helper functions are stood in | PowerShell on Linux; a real choco install needs Windows |
| Scoop | scoop install from the live bucket on windows-latest, then cookwala-samples demo — daily + on packaging changes | .github/workflows/samples-channel-scoop.yml |
| OCI image | runs the demo; serves HTTP as an arbitrary uid on a read-only root filesystem | Docker |
| AWS Lambda | the handler answers inside AWS's own public.ecr.aws/lambda/python:3.12 image (runtime interface emulator) | Docker |
| Azure Functions | func start with Azure Functions Core Tools 4 serves /api/health, /api/v1/samples/plan and the demo | local Functions host |
| Google Cloud functions | functions-framework --target samples serves every endpoint | local Functions Framework |
The first manual pass found two real bugs, now fixed and covered by tests: the JavaScript and Java run commands exited 0 when a job did not complete (Python and C# exited 1), and an unknown --fault kind was ignored instead of being a usage error. The four CLIs now give the same exit codes and the same reports.
Tests and builds #
| What | How |
|---|---|
| Python: 33 tests, including equality with the reference dry run for every example recipe and device, JSON Schema validation of every request, status, log and incident, the demo against the reference hub over HTTP, and every service endpoint with the network disabled | python -m unittest discover -s samples/python/tests |
| JavaScript: 42 tests (Node 18 and 22); Markdown and CSV demo reports byte-identical to Python | npm test in samples/js |
| Java: tests under both Maven and Gradle (Java 17 and 21); demo identical to Python; sources and javadoc jars for Maven Central | mvn -B package, gradle build in samples/java |
| .NET: 55 tests; demo identical to Python | dotnet test in samples/dotnet |
pyz, wheel and sdist build; twine check passes | packaging/build.py |
| the Helm chart lints and renders a Deployment, Service, Ingress and Route | Helm 3.16 |
the Helm chart installs into a throwaway kind cluster and the Knative service answers over Kourier, both serving /health and the demo report | .github/workflows/samples-cloud.yml kind job (kind, Knative Serving 1.23) |
the Azure Functions deploy runs on a real subscription: Bicep stack, zip publish, /health + /plan + demo report over the public URL, then the resource group is deleted | .github/workflows/samples-cloud.yml azure job (workflow_dispatch, cloud-test environment) |
Not run here #
The OpenShift template (needs a real cluster; Helm and Knative on a throwaway kind cluster are covered by the kind job). Everything else above runs in CI: snap install (samples-channel-snap.yml), choco install and scoop install on windows-latest (samples-channel-chocolatey.yml, samples-channel-scoop.yml), and all three cloud deploys — Azure Functions, AWS Lambda and Google Cloud Run functions — are proven against real accounts; see below.
Proven against real accounts: Azure Functions, AWS Lambda, Google Cloud #
Run 37443213472 (2026-10-06, samples-cloud.yml dispatched with target=azure behind the protected cloud-test environment):
- OIDC login via
azure/login— a Microsoft Entra app registration (cookwala-ci) with a federated credential scoped toenvironment:cloud-test; no stored client secret. az group create+az deployment group createdeployedsamples/cloud/azure-functions/main.bicep(storage account, Y1 consumption plan, Application Insights, Function App) toeastus— overridable via theAZURE_REGIONvariable, since new subscriptions reject some regions (RequestDisallowedByAzure/ "not accepting new customers").- Zip-deployed
samples/cloud/azure-functions/with a remote build. - Called the real endpoints:
GET /api/healthreturned{"ok": true, ...},POST /api/v1/samples/planranked the devices, andGET /api/v1/samples/demo?format=markdownproduced the report with3 completed. if: always()teardown deleted the resource group.
What the job actually did, from the run log:
$ az group create -n cw-samples-ci-37443213472 -l eastus --tags ttl=1d purpose=samples-ci
$ az deployment group create -g cw-samples-ci-37443213472 -f samples/cloud/azure-functions/main.bicep -p appName=cwci37443213472
$ az functionapp deployment source config-zip -g cw-samples-ci-37443213472 -n cwci37443213472 --src /tmp/app.zip --build-remote true
$ curl "https://cwci37443213472.azurewebsites.net/api/health?code=<key>"
{"ok": true, "service": "cookwala-samples", "version": "0.3.0", "core": "0.2.0"}
$ curl "https://cwci37443213472.azurewebsites.net/api/v1/samples/demo?format=markdown&code=<key>"
# Cookwala samples demo (offline, simulated kitchen) ... 3 completed ...
azure function demo verified
$ az group delete --yes --no-wait -n cw-samples-ci-37443213472Required secrets in the cloud-test environment: AZURE_CLIENT_ID, AZURE_TENANT_ID, AZURE_SUBSCRIPTION_ID. One setup detail worth noting: because this repository was renamed, GitHub emits the OIDC subject with numeric entity ids (repo:amado2k5@20147989/cookwala@1403544608:environment:cloud-test) — the federated credential's subject must match that form, not repo:amado2k5/cookwala:....
Run 37448439405 (2026-10-06, target=aws):
- OIDC role assumption via
configure-aws-credentials— an IAM OIDC provider fortoken.actions.githubusercontent.complus acookwala-cirole whose trust policy matchesrepo:amado2k5*/cookwala*:environment:cloud-test; no stored access keys. sam build+sam deploycreated the stackcw-samples-ci-37448439405ineu-west-1: the Lambda function, its execution role, and an HTTP API.- Called the public
HttpApiUrl:/healthreturned{"ok": true, ...}and/v1/samples/demo?format=markdownproduced the report with3 completed. if: always()teardown ransam delete.
Two findings this run exposed, both fixed in the same PR: the SAM template's AuthType: NONE needed an explicit AWS::Lambda::Permission for lambda:InvokeFunctionUrl, and anonymous Function-URL calls are blocked on brand-new AWS accounts regardless — so the workflow proves the deploy through the API Gateway HttpApiUrl output instead.
The blocked path and the working path, verified by hand against a real stack:
$ sam deploy --stack-name cw-debug --region eu-west-1 --parameter-overrides FunctionUrlAuth=NONE
Successfully created/updated stack - cw-debug
$ curl https://4yaogxj73phvz24wu7w3p6t5uu0hhhfh.lambda-url.eu-west-1.on.aws/health
{"Message":"Forbidden ..."} # 403 even with AuthType NONE + public policy (new-account block)
$ curl https://eh00djbagh.execute-api.eu-west-1.amazonaws.com/health
{"ok": true, "service": "cookwala-samples", "version": "0.3.0", "core": "0.2.0"}
$ curl "https://eh00djbagh.execute-api.eu-west-1.amazonaws.com/v1/samples/demo?format=markdown"
# Cookwala samples demo (offline, simulated kitchen) ... 3 completed ...
$ sam delete --no-prompts --stack-name cw-debug # Deleted successfullyRequired secret in the cloud-test environment: AWS_ROLE_TO_ASSUME (the role ARN).
Run 37450278019 (2026-10-06, target=gcp):
- OIDC via
google-github-actions/auth— a workload identity pool + provider on projectcookwala-ci-5604impersonating service accountgithub-ci@, conditioned on the numericrepository_id(1403544608), so the repo-rename subject quirk cannot apply. gcloud functions deploy --gen2createdcookwala-samples-ci-37450278019ineurope-west1(Cloud Run function,--allow-unauthenticated).- Called the public
.run.appURL:/healthreturned{"ok": true, ...}and/v1/samples/demo?format=markdownproduced the report with3 completed. if: always()teardown deleted the function.
What the job actually did, from the run log:
$ gcloud functions deploy cookwala-samples-ci-37450278019 --gen2 --runtime python312 \
--region europe-west1 --source samples/cloud/gcp-functions --entry-point samples \
--trigger-http --allow-unauthenticated --project cookwala-ci-5604
serviceConfig:
uri: https://cookwala-samples-ci-37450278019-wokqyuqweq-ew.a.run.app
$ curl https://cookwala-samples-ci-37450278019-wokqyuqweq-ew.a.run.app/health
{"ok": true, "service": "cookwala-samples", "version": "0.3.0", "core": "0.2.0"}
$ curl "https://cookwala-samples-ci-37450278019-wokqyuqweq-ew.a.run.app/v1/samples/demo?format=markdown"
# Cookwala samples demo (offline, simulated kitchen) ... 3 completed ...
gcp function demo verified
$ gcloud functions delete cookwala-samples-ci-37450278019 --gen2 --region europe-west1Required secrets in the cloud-test environment: GCP_PROJECT, GCP_SERVICE_ACCOUNT, GCP_WORKLOAD_IDENTITY_PROVIDER. Gen2 functions need an open billing account on the project — a fresh billing account's free trial plus the always-free 2M-invocation tier keeps CI cost at ~$0.