Contributing to delstack

May 4, 2026 ยท View on GitHub

This guide covers what you need to start contributing. For the architectural overview (layered packages, deletion algorithm, mocking strategy), see CLAUDE.md.

Project Structure

cmd/
  delstack/       CLI entrypoint
internal/
  app/            Application layer (stack deletion orchestration)
  cdk/            CDK integration (synthesis, manifest parsing)
  operation/      Operators for force-deleting specific resource types
  preprocessor/   Pre-deletion processing (e.g., Lambda VPC detachment)
  resourcetype/   Resource type constants
  io/             Logger and I/O utilities
  version/        Version and revision info
pkg/
  client/         AWS SDK client wrappers with interfaces for testing
e2e/
  <scenario>/     Per-scenario E2E test environments (CDK + deploy script)

Package Dependency Rules

pkg/client            -> No internal dependencies (AWS SDK only)
internal/operation    -> pkg/client
internal/preprocessor -> pkg/client
internal/app          -> internal/operation, internal/preprocessor (NOT directly on pkg/client)

No circular dependencies. internal/app must not depend on pkg/client directly: use operation or preprocessor as intermediaries.

Development Setup

  • make run OPT="<options>": Run delstack locally
  • make test: Run all tests
  • make mockgen: Regenerate mocks
  • make lint: Run linter

Adding New Resource Support

When adding support for a new AWS resource, first decide whether it is an Operator or a Preprocessor:

  • Operator: force-deletes resources that cause DELETE_FAILED during stack deletion (e.g., emptying S3 buckets, deleting ECR images). Reference PR: #569 (Athena WorkGroup).
  • Preprocessor: runs before CloudFormation deletion. Either a Checker (fatal on failure, e.g., deletion-protection check) or a Modifier (best-effort, e.g., Lambda VPC detachment).

Detailed step-by-step procedures for each are in the Claude Code skills under .claude/skills/:

  • add-operator: all files to touch when adding an Operator (resourcetype, client, operator, factory, collection, tests, README, E2E).
  • add-preprocessor: all files to touch when adding a Preprocessor, plus the Checker vs Modifier decision.

Testing

Unit Tests

  • make test: Run all tests
  • make test_view: Run tests with coverage report
  • make mockgen: Regenerate mocks (driven by //go:generate mockgen comments in pkg/client/, which must be on line 1)

Two mocking patterns are used; do not mix them:

  • pkg/client/*_test.go: AWS SDK middleware mocks. Inject a middleware.FinalizeMiddlewareFunc via config.WithAPIOptions to intercept SDK calls. For paginated APIs, capture NextToken in an Initialize-stage middleware via middleware.WithStackValue / GetStackValue. See backup_test.go and s3_test.go. Background: Testing with AWS SDK for Go v2 without interface mocks.
  • internal/operation/*_test.go and internal/preprocessor/*_test.go: gomock against the I<Service> interfaces in pkg/client/, with mocks regenerated by make mockgen.

E2E Tests

E2E tests deploy real AWS resources and incur cost. Contributors are expected to update E2E code when needed, but the maintainer runs them before merging.

  • make testgen_<scenario>: deploy a test stack for the scenario.
  • make e2e_<scenario>: deploy and then run delstack against it. Auto-generates a unique stage name (STAGE=<name> to override; OPT="-p <profile>" for AWS profile).
  • make testgen_help / make e2e_help: list all available scenarios.

When adding a new target resource type, extend e2e/full/ (CDK constructor + post-deploy SDK setup in deploy.go); see the add-operator skill for the full file list. For new preprocessors or other logic, prefer a dedicated e2e/<name>/ directory.

Important: E2E tests must reproduce the DELETE_FAILED state. If a blocking dependency is created inside the same CloudFormation stack via CDK, CloudFormation resolves the dependency order and deletion succeeds, defeating the test. Blocking dependencies must be created outside of CloudFormation (via SDK calls in deploy.go). CDK should only create the base resource itself. If a dependency cannot feasibly be created outside CloudFormation (e.g., MFA requiring TOTP, FIDO/Passkey requiring physical devices), skip it in E2E and rely on unit tests.

When creating a new e2e/<name>/ directory, add a cdk/.gitignore that excludes cdk.context.json (CDK generates this file at deploy time with account-specific context).

Code Style & Conventions

  • Code comments in English, minimal.
  • var _ IXxx = (*Xxx)(nil) for compile-time interface implementation check (used in both pkg/client/ and internal/operation/).
  • Concurrency: errgroup + semaphore.NewWeighted(runtime.NumCPU()).
  • Errors: public methods in pkg/client wrap with ClientError. In internal/operation and internal/preprocessor, return client errors as-is (already wrapped); only wrap with ClientError for errors generated locally (e.g., ctx.Done(), validation).
  • Idempotency: check existence before deletion.
  • //go:generate mockgen comment must be on line 1 of the file.
  • When a client file already exists for a service (e.g., iam.go), extend its interface and struct rather than creating a new file.

Test Naming

Top-level test function: Test[ReceiverType]_[MethodName] (e.g., TestEcr_DeleteRepository, TestS3BucketOperator_DeleteS3Bucket).