Contributing

Prerequisites

  • Go 1.26+
  • Python 3.10+ with pip (compatibility tests)
  • Docker (optional — Lambda runtime and integration tests)
  • pre-commit (optional but recommended)
  • Hugo extended 0.165+ (optional — only to preview this documentation as a site)

No C toolchain and no SQLite headers: the SQLite driver is pure Go, which is why everything here builds with CGO_ENABLED=0.

Setup

git clone https://github.com/skyoo2003/devcloud.git
cd devcloud
make build   # builds dist/devcloud and dist/codegen
make run     # starts the server on port 4747

Make Targets

CommandDescription
make buildBuild both binaries into dist/ — devcloud and codegen
make runRun the server from source (go run ./cmd/devcloud)
make testRun all Go tests with CGO_ENABLED=0 and -v
make test-compatInstall test/compatibility/requirements.txt and run the boto3 suite
make codegenRegenerate internal/generated/ from every model in api/smithy/
make codegen-s3Same, restricted to S3 — fast loop while editing templates
make docker-buildBuild the image from build/package/Dockerfile as devcloud/devcloud
make docker-runRun that image on port 4747 with ./data mounted
make docs-servePreview this documentation locally with Hugo
make docs-buildBuild the documentation site and check every internal link
make cleanDelete dist/, data/, and the Hugo output in public/ and resources/
make changelog VERSION=vX.Y.ZBatch and merge Changie fragments; VERSION is required
make statsPrint registered service and operation counts

Testing

make test          # Go tests, CGO_ENABLED=0 — the same mode releases ship
make test-compat   # Python/boto3 suite in test/compatibility/

You do not need a server running first. The devcloud_server session fixture in conftest.py starts one via go run, on a free port, against a temporary data directory it removes afterwards. Three environment variables change that:

VariableEffect
DEVCLOUD_EXTERNAL=1Connect to a server already running (e.g. in Docker) instead of starting one
DEVCLOUD_BIN=<path>Run a pre-built binary instead of go run — much faster
DEVCLOUD_PORT=<port>Use a fixed port instead of an arbitrary free one

To run the suite directly:

cd test/compatibility
pip install -r requirements.txt
pytest -v

Code Generation

Never edit files in internal/generated/ — codegen overwrites them.

make codegen      # all services
make codegen-s3   # one service
PieceWhere
Modelsapi/smithy/*.json
Sourcesinternal/codegen/source.go — ModelSource per format; SmithySource today
Generatorinternal/codegen/generator.go + internal/codegen/templates/
Outputinternal/generated/{service}/ — types.go, router.go, errors.go, base_provider.go

Providers parse the raw *http.Request themselves, so there is no generated serializer or deserializer. See architecture.md.

Reviewing the weekly model sync

smithy-sync.yml refreshes all 420 vendored models every Monday and opens a pull request. The diff is whole-tree — a refresh measured at 194 models moved 93 of them and 134 generated files, and the vendored set has more than doubled since — so do not try to read it. Read the PR body instead: it is generated by scripts/model_churn.py and lists which services gained or lost operations.

Three things to check, in order:

  1. Operations added or removed. These are the only changes that alter what DevCloud serves. Everything else is upstream reshaping traits or docs.
  2. The published figures, already re-derived. New operations move the fidelity manifest, so docs/coverage.md must move with them — the sync does that arithmetic itself and commits it. Check the before→after table at the top of the PR body against item 1: the figures should move for the same reason the operations did. A Published figures: failure means the change needs a sentence the tool will not invent — write it before merging, and never relax the gate to make it pass.
  3. codegen-drift and compat. These must be green on their own. A red codegen-drift means the committed output does not match the models; a red compat means a real behavioural regression.

The in-job test result is printed at the top of the PR body, and a failure there with a clean codegen-drift almost always means the published-figure gate fired on an operation that moved — which is the signal to look, not work to do. The PR’s own ci runs that gate again against the corrected figures, so it is green unless something is genuinely wrong.

Re-derive by hand, for a coverage change that did not come from a sync:

DEVCLOUD_UPDATE_DOCS=1 go test ./cmd/devcloud/ -run TestUpdatePublishedFigures

Adding a New AWS Service

  1. Add the Smithy model to api/smithy/.
  2. Run make codegen — generates types, router, errors and stubs.
  3. Implement the provider in internal/services/<service>/provider.go. Start with the most commonly used operations; the generated base provider makes everything else return NotImplementedError.
  4. Implement the store in store.go — SQLite (internal/storage/sqlite) for anything persistent, in-memory for ephemeral, filesystem for blobs. SQLite is the only embedded database in the tree; adding a second one needs a reason in the PR.
  5. Register the plugin from an init() in register.go, and blank-import the package in cmd/devcloud/imports.go. The interface contract, error convention and config keys are in plugin-api.md.
  6. Wire startup and config — cmd/devcloud/main.go and internal/config/default.yaml.
  7. Write tests — Go unit tests plus boto3 tests in test/compatibility/.
  8. Document it — a page under docs/services/ for a core service.
internal/
├── generated/{service}/    # Auto-generated (DO NOT EDIT)
│   ├── types.go            # Request/response structs
│   ├── router.go           # Operation routing
│   ├── errors.go           # Error types
│   └── base_provider.go    # Stub (NotImplementedError)
│
└── services/{service}/     # Your implementation
    ├── provider.go         # Service logic
    ├── store.go            # Storage backend
    └── register.go         # Plugin registration

Documentation

Everything under docs/ is read two ways: as plain markdown on github.com, and as the site at https://skyoo2003.github.io/devcloud/. Hugo mounts the directory rather than copying it, so both come from the same files.

That dual audience sets one rule: no front matter. GitHub renders a YAML block as a metadata table above the page, so titles come from each document’s H1 instead, and links stay plain and relative ([coverage](coverage.md#tiers)) rather than Hugo ref shortcodes.

Adding a page takes two steps:

  1. Write docs/<name>.md starting with an # H1 — that becomes its title.
  2. Add it to [menu.before] in hugo.toml. The sidebar is defined there because Hugo would otherwise sort pages alphabetically, and every entry needs its own identifier — entries without one collapse into a single link.

Then run make docs-build. Links that leave docs/ — to source files or to root files like CONTRIBUTING.md — are rewritten to point at GitHub, which also means an unresolvable link fails silently; scripts/check-site-links.py, which make docs-build runs, is what catches it.

Code Style

  • Standard Go conventions (gofmt, go vet)
  • One responsibility per file; use existing services as patterns
  • Errors follow the AWS shape (Code, Message, StatusCode) — see plugin-api.md
  • New Go files start with // SPDX-License-Identifier: Apache-2.0. Generated files keep the DO NOT EDIT marker on line 1 and the SPDX header on line 2. The go-spdx-header pre-commit hook adds it if missing.

Pre-commit hooks

pre-commit install
pre-commit run --all-files   # optional: check the whole tree now

.pre-commit-config.yaml runs go-spdx-header, gofmt -l -w, go vet and go build on Go files — the last two with CGO_ENABLED=0, so a contributor without a C toolchain still gets them. Python under test/ and scripts/ is handled by ruff. internal/generated/ and api/smithy/ are excluded throughout.

Linting

The full linter is not in pre-commit; CI runs it separately (lint.yml). Run it before opening a PR:

golangci-lint run --timeout=5m

License of Contributions

DevCloud is licensed under the Apache License, Version 2.0. By submitting a contribution you agree that it will be licensed under the same terms. See LICENSE and NOTICE.