diff --git a/AGENTS.md b/AGENTS.md index d16ee6e..cd194b8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,67 +1,58 @@ # AGENTS.md ## Goal -Build a Terraform provider for Dynu DNS and domains. +Build and maintain a standalone Terraform provider for Dynu DNS and domains. -## Scope for current phase -Read-only only. Implement provider configuration and data sources first. -Do not implement any writable Terraform resources in the first phase. +## Scope +Current phase is **read-only**. -## Language and framework -- Language: Go -- Terraform provider framework: HashiCorp Terraform Plugin Framework -- Do not use the legacy SDK unless explicitly requested. - -## API usage -- Use Dynu's public API. -- Prefer stable, documented endpoints. -- Build a small internal API client package rather than scattering HTTP calls across resources/data sources. -- Add clear handling for pagination, 4xx/5xx responses, and malformed responses. -- Never log secrets. - -## Provider design Implement: - Provider configuration - Environment variable support for credentials - Read-only data sources: - - dynu_domains - - dynu_domain - - dynu_dns_records + - `dynu_domains` + - `dynu_domain` + - `dynu_dns_records` -Do not implement: -- resource_dynu_dns_record -- any create/update/delete flows -- any speculative unsupported resources +Do not implement in this phase: +- Any Terraform resources with create/update/delete +- Any write HTTP methods -## Safety -- Read-only phase only. -- No write HTTP methods in this phase. -- Acceptance tests must only cover provider auth and read-only data sources. -- Any future write support must be added in a separate phase. +## Language and framework +- Language: Go +- Framework: HashiCorp Terraform Plugin Framework +- Do not use the legacy Terraform Plugin SDK unless explicitly requested -## Code quality -- Keep code modular and idiomatic Go. -- Use strong typing for API models. -- Prefer explicit schema definitions with clear descriptions. -- Return actionable diagnostics. -- Keep public documentation accurate to actual implementation. +## API usage +- Use Dynu public API endpoints +- Prefer stable, documented endpoints +- Keep HTTP logic in a small internal client package +- Handle pagination and API errors clearly +- Never log secrets or credentials + +## Portability requirements +- Keep the repository generic and standalone +- Do not reference personal infrastructure, private domains, homelab tooling, or external repo scripts +- Use neutral placeholders in docs and examples (for example: `example.com`, `my-test-domain.example`, `var.dynu_api_key`) +- Avoid hardcoded local filesystem paths ## Testing -- Add unit tests where practical. -- Add acceptance tests gated behind environment variables. -- Do not require live credentials for normal unit tests. +- Add unit tests where practical +- Gate acceptance tests behind generic environment variables + - `TF_ACC=1` + - `DYNU_API_KEY` + - optional `DYNU_DOMAIN` for domain-specific acceptance coverage +- Unit tests must not require live credentials ## Documentation -- Add examples for every implemented data source. -- Update README with provider configuration and environment variables. -- Document limitations and unsupported areas clearly. +- Keep README and examples aligned with actual provider behavior +- Document current read-only limitations clearly +- Include build, test, and acceptance test instructions for any contributor + +## Developer scripts +- `scripts/setup-dev.sh`: validate local toolchain requirements +- `scripts/check.sh`: run formatting and unit checks +- `scripts/testacc.sh`: run acceptance tests using generic env vars ## Output expectations -Create a provider skeleton that can compile and expose the provider plus read-only data sources. - -## Codex environment -- Use `codex/setup.sh` to prepare the repository-local Codex environment. -- Use `codex/maintain.sh` to refresh and validate the environment. -- Use `codex/doctor.sh` for troubleshooting. -- Do not install global packages unless explicitly required. -- Prefer repo-local state under `.codex/`. +Changes should keep the provider idiomatic, easy to contribute to, and ready for public/open-source usage. diff --git a/README.md b/README.md index 94c5720..22742d4 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,28 @@ # terraform-provider-dynu -Terraform provider for Dynu DNS using the Terraform Plugin Framework. +A standalone Terraform provider for [Dynu](https://www.dynu.com/) DNS data. -> Current phase is **read-only**: this provider implements provider configuration and data sources only. +> Current phase: **read-only**. This provider supports provider configuration and data sources only. -## Features +## Feature scope -- Provider configuration with API key authentication. -- Environment variable support (`DYNU_API_KEY`). +Implemented: +- Provider authentication via API key +- Environment variable support (`DYNU_API_KEY`) - Read-only data sources: - `dynu_domains` - `dynu_domain` - `dynu_dns_records` +Not implemented in this phase: +- Terraform resources +- Any create/update/delete operations + ## Requirements -- Terraform >= 1.5 -- Go >= 1.22 (for building) -- A Dynu API key +- [Terraform](https://developer.hashicorp.com/terraform/downloads) `>= 1.5` +- [Go](https://go.dev/dl/) `>= 1.23` (for building and testing) +- Dynu API key ## Provider configuration @@ -25,24 +30,29 @@ Terraform provider for Dynu DNS using the Terraform Plugin Framework. provider "dynu" { api_key = var.dynu_api_key } + +variable "dynu_api_key" { + type = string + sensitive = true +} ``` -You can also omit `api_key` and set `DYNU_API_KEY` in your environment. +You can omit `api_key` in Terraform configuration and set the environment variable instead: -## Data sources +```bash +export DYNU_API_KEY="your-dynu-api-key" +``` + +## Data source usage ### dynu_domains -Returns all DNS domains associated with the account. - ```hcl data "dynu_domains" "all" {} ``` ### dynu_domain -Resolves the root domain from a hostname, then returns full domain details. - ```hcl data "dynu_domain" "selected" { hostname = "www.example.com" @@ -51,34 +61,52 @@ data "dynu_domain" "selected" { ### dynu_dns_records -Resolves the root domain from a hostname, then returns DNS records for that domain. - ```hcl data "dynu_dns_records" "records" { hostname = "www.example.com" } ``` -## Development +## Build ```bash -./codex/setup.sh -./codex/maintain.sh +go build ./... ``` -Run tests: +## Test + +Run formatting and unit tests: + +```bash +./scripts/check.sh +``` + +Or run unit tests directly: ```bash go test ./... ``` -Acceptance tests (requires live Dynu credentials): +## Acceptance tests + +Acceptance tests are opt-in and require live Dynu credentials. ```bash -TF_ACC=1 DYNU_API_KEY=... go test ./internal/provider -run TestAcc +TF_ACC=1 DYNU_API_KEY="your-dynu-api-key" ./scripts/testacc.sh ``` +Optional environment variable: +- `DYNU_DOMAIN` (for future domain-specific acceptance test cases) + +## Developer workflow + +- `./scripts/setup-dev.sh` – validates required local tools +- `./scripts/check.sh` – runs formatting and unit checks +- `./scripts/testacc.sh` – runs acceptance tests + +Repository-local Codex helpers are also available under `codex/` for agent-oriented workflows. + ## Limitations -- No Terraform resources are implemented in this phase. -- No write operations are supported by provider code in this phase. +- Read-only provider phase only +- No writable Terraform resources yet diff --git a/codex/doctor.sh b/codex/doctor.sh index ca17d58..46a5846 100755 --- a/codex/doctor.sh +++ b/codex/doctor.sh @@ -13,7 +13,7 @@ echo "time: $(date -u +%FT%TZ)" echo echo "--- tools ---" -for cmd in bash git python3 docker; do +for cmd in bash git go terraform; do if have "$cmd"; then echo "$cmd: $(command -v "$cmd")" else @@ -23,17 +23,14 @@ done echo echo "--- repo ---" -[[ -f services-up.sh ]] && echo "services-up.sh: present" || echo "services-up.sh: missing" -[[ -d core ]] && echo "core/: present" || true -[[ -d apps ]] && echo "apps/: present" || true -[[ -d monitoring ]] && echo "monitoring/: present" || true +for path in README.md go.mod main.go internal/provider/provider.go; do + if [[ -f "$path" ]]; then + echo "$path: present" + else + echo "$path: missing" + fi +done echo echo "--- git status ---" git status --short || true -echo - -if have docker && docker compose version >/dev/null 2>&1; then - echo "--- docker compose ---" - docker compose version || true -fi diff --git a/codex/maintain.sh b/codex/maintain.sh index 4e41d77..ff4e916 100755 --- a/codex/maintain.sh +++ b/codex/maintain.sh @@ -7,80 +7,15 @@ source "$SCRIPT_DIR/lib.sh" cd_repo_root -log "Starting Codex environment setup in $(pwd)" +log "Refreshing Codex environment and running repository checks" -if ! have git; then - die "git is required" -fi +"$SCRIPT_DIR/setup.sh" -if ! have bash; then - die "bash is required" -fi - -# Keep all Codex-local state inside the repo or user home. -ensure_dir .codex -ensure_dir .codex/bin -ensure_dir .codex/cache -ensure_dir .codex/tmp -ensure_dir .codex/logs - -# Useful environment defaults for repeatable non-interactive runs. -cat > .codex/env <<'EOF' -export CI=1 -export TERM=xterm-256color -export PYTHONDONTWRITEBYTECODE=1 -export PIP_DISABLE_PIP_VERSION_CHECK=1 -export PIP_NO_INPUT=1 -export GIT_PAGER=cat -EOF - -# Optional Python venv for helper tooling. -if have python3; then - log "python3 detected" - if [[ ! -d .codex/venv ]]; then - python3 -m venv .codex/venv - fi - # shellcheck disable=SC1091 - source .codex/venv/bin/activate - python -m pip install --upgrade pip setuptools wheel >/dev/null - - # Install only lightweight tooling that is broadly useful. - python -m pip install \ - pyyaml \ - requests \ - jinja2 \ - >/dev/null +if [[ -x scripts/check.sh ]]; then + ./scripts/check.sh else - warn "python3 not found; skipping virtualenv setup" + log "scripts/check.sh not found; running go test ./..." + go test ./... fi -# Basic repo checks based on your environment style. -if [[ -f services-up.sh ]]; then - log "Found services-up.sh" - chmod +x services-up.sh || true -else - warn "services-up.sh not found at repo root" -fi - -# Make codex scripts executable. -find codex -maxdepth 1 -type f -name '*.sh' -exec chmod +x {} + - -# Create a simple local ignore file for Codex-generated junk if needed. -touch .git/info/exclude -append_if_missing ".codex/" .git/info/exclude -append_if_missing ".pytest_cache/" .git/info/exclude -append_if_missing "__pycache__/" .git/info/exclude - -# Helpful summary file for Codex tasks. -cat > .codex/README.md <<'EOF' -This directory contains local, disposable Codex environment state. - -Contents: -- env: shell exports for non-interactive work -- venv: optional Python virtual environment -- cache/: tool cache -- tmp/: temporary files -- logs/: script logs -EOF - -log "Setup complete" +log "Maintenance complete" diff --git a/codex/setup.sh b/codex/setup.sh index 4e41d77..9172950 100755 --- a/codex/setup.sh +++ b/codex/setup.sh @@ -7,34 +7,24 @@ source "$SCRIPT_DIR/lib.sh" cd_repo_root -log "Starting Codex environment setup in $(pwd)" +log "Preparing repository-local Codex environment in $(pwd)" -if ! have git; then - die "git is required" -fi +for cmd in bash git; do + have "$cmd" || die "$cmd is required" +done -if ! have bash; then - die "bash is required" -fi - -# Keep all Codex-local state inside the repo or user home. ensure_dir .codex ensure_dir .codex/bin ensure_dir .codex/cache ensure_dir .codex/tmp ensure_dir .codex/logs -# Useful environment defaults for repeatable non-interactive runs. -cat > .codex/env <<'EOF' +cat > .codex/env <<'ENVEOF' export CI=1 export TERM=xterm-256color -export PYTHONDONTWRITEBYTECODE=1 -export PIP_DISABLE_PIP_VERSION_CHECK=1 -export PIP_NO_INPUT=1 export GIT_PAGER=cat -EOF +ENVEOF -# Optional Python venv for helper tooling. if have python3; then log "python3 detected" if [[ ! -d .codex/venv ]]; then @@ -43,44 +33,15 @@ if have python3; then # shellcheck disable=SC1091 source .codex/venv/bin/activate python -m pip install --upgrade pip setuptools wheel >/dev/null - - # Install only lightweight tooling that is broadly useful. - python -m pip install \ - pyyaml \ - requests \ - jinja2 \ - >/dev/null else - warn "python3 not found; skipping virtualenv setup" + warn "python3 not found; skipping optional venv setup" fi -# Basic repo checks based on your environment style. -if [[ -f services-up.sh ]]; then - log "Found services-up.sh" - chmod +x services-up.sh || true -else - warn "services-up.sh not found at repo root" -fi - -# Make codex scripts executable. find codex -maxdepth 1 -type f -name '*.sh' -exec chmod +x {} + -# Create a simple local ignore file for Codex-generated junk if needed. touch .git/info/exclude append_if_missing ".codex/" .git/info/exclude append_if_missing ".pytest_cache/" .git/info/exclude append_if_missing "__pycache__/" .git/info/exclude -# Helpful summary file for Codex tasks. -cat > .codex/README.md <<'EOF' -This directory contains local, disposable Codex environment state. - -Contents: -- env: shell exports for non-interactive work -- venv: optional Python virtual environment -- cache/: tool cache -- tmp/: temporary files -- logs/: script logs -EOF - log "Setup complete" diff --git a/internal/provider/provider_acc_test.go b/internal/provider/provider_acc_test.go index 56cc54a..fa499e8 100644 --- a/internal/provider/provider_acc_test.go +++ b/internal/provider/provider_acc_test.go @@ -6,10 +6,13 @@ import ( ) func TestAccScaffold(t *testing.T) { - if os.Getenv("TF_ACC") == "" || os.Getenv("DYNU_API_KEY") == "" { + if os.Getenv("TF_ACC") != "1" || os.Getenv("DYNU_API_KEY") == "" { t.Skip("set TF_ACC=1 and DYNU_API_KEY to enable acceptance tests") } + // Optional: DYNU_DOMAIN may be used by future acceptance test cases. + _ = os.Getenv("DYNU_DOMAIN") + // Acceptance tests for read-only data sources are intentionally scaffolded in phase 1. // Add terraform-plugin-testing based test cases in a follow-up with stable fixtures. } diff --git a/scripts/check.sh b/scripts/check.sh new file mode 100755 index 0000000..e9beb14 --- /dev/null +++ b/scripts/check.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +set -euo pipefail + +echo "[check] Running gofmt" +files="$(git ls-files '*.go')" +if [[ -n "$files" ]]; then + # shellcheck disable=SC2086 + gofmt -w $files +fi + +echo "[check] Running go test" +go test ./... + +echo "[check] Completed" diff --git a/scripts/setup-dev.sh b/scripts/setup-dev.sh new file mode 100755 index 0000000..e482353 --- /dev/null +++ b/scripts/setup-dev.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +set -euo pipefail + +echo "[setup-dev] Validating local toolchain" + +for cmd in bash git go; do + if ! command -v "$cmd" >/dev/null 2>&1; then + echo "[setup-dev][error] Missing required command: $cmd" >&2 + exit 1 + fi + echo "[setup-dev] $cmd: $(command -v "$cmd")" +done + +if command -v terraform >/dev/null 2>&1; then + echo "[setup-dev] terraform: $(command -v terraform)" +else + echo "[setup-dev][warn] terraform not found (only required for terraform validate and acceptance workflows)" +fi + +echo "[setup-dev] Toolchain validation complete" diff --git a/scripts/testacc.sh b/scripts/testacc.sh new file mode 100755 index 0000000..5bb482a --- /dev/null +++ b/scripts/testacc.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [[ "${TF_ACC:-}" != "1" ]]; then + echo "[testacc][error] TF_ACC must be set to 1" >&2 + exit 1 +fi + +if [[ -z "${DYNU_API_KEY:-}" ]]; then + echo "[testacc][error] DYNU_API_KEY must be set" >&2 + exit 1 +fi + +if [[ -z "${DYNU_DOMAIN:-}" ]]; then + echo "[testacc][info] DYNU_DOMAIN is not set; running baseline acceptance scaffolding" +fi + +echo "[testacc] Running acceptance tests" +go test ./internal/provider -run TestAcc -count=1