From 36f8f468e73f7fce2746c6466c1f8d2d4dd00122 Mon Sep 17 00:00:00 2001 From: unkin-agent Date: Sat, 17 Oct 2026 15:17:02 +1100 Subject: [PATCH] Register resources per file and split resource docs - add registry with per-file init() registration, sorted by type name - give each remote kind its own resource_remote_.go - replace hardcoded resource tables with registry/examples/docs invariants - move per-resource README sections into docs/resources and docs/data-sources --- README.md | 216 +----------------- docs/data-sources/remote.md | 9 + docs/data-sources/virtual.md | 9 + docs/resources/local_alpine.md | 11 + docs/resources/local_deb.md | 11 + docs/resources/local_docker.md | 21 ++ docs/resources/local_generic.md | 11 + docs/resources/local_helm.md | 13 ++ docs/resources/local_npm.md | 13 ++ docs/resources/local_pypi.md | 11 + docs/resources/local_rpm.md | 11 + docs/resources/local_terraform.md | 11 + docs/resources/remote_alpine.md | 53 +++++ docs/resources/remote_cargo.md | 30 +++ docs/resources/remote_deb.md | 53 +++++ docs/resources/remote_docker.md | 52 +++++ docs/resources/remote_gems.md | 30 +++ docs/resources/remote_generic.md | 30 +++ docs/resources/remote_github_alpine.md | 49 ++++ docs/resources/remote_github_deb.md | 49 ++++ docs/resources/remote_github_rpm.md | 46 ++++ docs/resources/remote_goproxy.md | 30 +++ docs/resources/remote_helm.md | 30 +++ docs/resources/remote_npm.md | 30 +++ docs/resources/remote_puppet.md | 30 +++ docs/resources/remote_pypi.md | 30 +++ docs/resources/remote_rpm.md | 53 +++++ docs/resources/remote_terraform.md | 36 +++ docs/resources/virtual.md | 23 ++ internal/provider/datasource_remote.go | 2 + internal/provider/datasource_virtual.go | 2 + internal/provider/provider.go | 34 +-- internal/provider/provider_test.go | 145 ++++-------- internal/provider/registry.go | 41 ++++ internal/provider/resource_local_alpine.go | 2 + internal/provider/resource_local_deb.go | 2 + internal/provider/resource_local_docker.go | 2 + internal/provider/resource_local_generic.go | 2 + internal/provider/resource_local_helm.go | 2 + internal/provider/resource_local_npm.go | 2 + internal/provider/resource_local_pypi.go | 2 + internal/provider/resource_local_rpm.go | 2 + internal/provider/resource_local_terraform.go | 2 + internal/provider/resource_remote.go | 19 -- internal/provider/resource_remote_alpine.go | 3 + internal/provider/resource_remote_cargo.go | 3 + internal/provider/resource_remote_deb.go | 3 + internal/provider/resource_remote_docker.go | 3 + internal/provider/resource_remote_gems.go | 3 + internal/provider/resource_remote_generic.go | 3 + .../provider/resource_remote_github_alpine.go | 3 + .../provider/resource_remote_github_deb.go | 3 + .../provider/resource_remote_github_rpm.go | 3 + internal/provider/resource_remote_goproxy.go | 3 + internal/provider/resource_remote_helm.go | 3 + internal/provider/resource_remote_npm.go | 3 + internal/provider/resource_remote_puppet.go | 3 + internal/provider/resource_remote_pypi.go | 3 + internal/provider/resource_remote_rpm.go | 3 + .../provider/resource_remote_terraform.go | 3 + internal/provider/resource_remote_test.go | 102 ++------- internal/provider/resource_virtual.go | 2 + 62 files changed, 977 insertions(+), 437 deletions(-) create mode 100644 docs/data-sources/remote.md create mode 100644 docs/data-sources/virtual.md create mode 100644 docs/resources/local_alpine.md create mode 100644 docs/resources/local_deb.md create mode 100644 docs/resources/local_docker.md create mode 100644 docs/resources/local_generic.md create mode 100644 docs/resources/local_helm.md create mode 100644 docs/resources/local_npm.md create mode 100644 docs/resources/local_pypi.md create mode 100644 docs/resources/local_rpm.md create mode 100644 docs/resources/local_terraform.md create mode 100644 docs/resources/remote_alpine.md create mode 100644 docs/resources/remote_cargo.md create mode 100644 docs/resources/remote_deb.md create mode 100644 docs/resources/remote_docker.md create mode 100644 docs/resources/remote_gems.md create mode 100644 docs/resources/remote_generic.md create mode 100644 docs/resources/remote_github_alpine.md create mode 100644 docs/resources/remote_github_deb.md create mode 100644 docs/resources/remote_github_rpm.md create mode 100644 docs/resources/remote_goproxy.md create mode 100644 docs/resources/remote_helm.md create mode 100644 docs/resources/remote_npm.md create mode 100644 docs/resources/remote_puppet.md create mode 100644 docs/resources/remote_pypi.md create mode 100644 docs/resources/remote_rpm.md create mode 100644 docs/resources/remote_terraform.md create mode 100644 docs/resources/virtual.md create mode 100644 internal/provider/registry.go create mode 100644 internal/provider/resource_remote_alpine.go create mode 100644 internal/provider/resource_remote_cargo.go create mode 100644 internal/provider/resource_remote_deb.go create mode 100644 internal/provider/resource_remote_docker.go create mode 100644 internal/provider/resource_remote_gems.go create mode 100644 internal/provider/resource_remote_generic.go create mode 100644 internal/provider/resource_remote_github_alpine.go create mode 100644 internal/provider/resource_remote_github_deb.go create mode 100644 internal/provider/resource_remote_github_rpm.go create mode 100644 internal/provider/resource_remote_goproxy.go create mode 100644 internal/provider/resource_remote_helm.go create mode 100644 internal/provider/resource_remote_npm.go create mode 100644 internal/provider/resource_remote_puppet.go create mode 100644 internal/provider/resource_remote_pypi.go create mode 100644 internal/provider/resource_remote_rpm.go create mode 100644 internal/provider/resource_remote_terraform.go diff --git a/README.md b/README.md index d7250ce..6721c2f 100644 --- a/README.md +++ b/README.md @@ -46,219 +46,11 @@ provider "artifactapi" { | `endpoint` | Yes | ArtifactAPI server endpoint URL | | `token` | No | Bearer token sent as `Authorization` header; falls back to `ARTIFACTAPI_TOKEN` | -## Resources +## Resources and Data Sources -### Remote Resources - -Per-type remote resources manage upstream repository proxies. Each type applies its own mutability classification rules automatically (e.g., Docker classifies tag manifests as mutable and blobs as immutable; Helm classifies `index.yaml` as mutable). - -Available resource types: - -- `artifactapi_remote_generic` -- `artifactapi_remote_docker` -- `artifactapi_remote_helm` -- `artifactapi_remote_pypi` -- `artifactapi_remote_npm` -- `artifactapi_remote_rpm` -- `artifactapi_remote_deb` -- `artifactapi_remote_alpine` -- `artifactapi_remote_puppet` -- `artifactapi_remote_terraform` -- `artifactapi_remote_goproxy` -- `artifactapi_remote_cargo` -- `artifactapi_remote_gems` -- `artifactapi_remote_github_rpm` -- `artifactapi_remote_github_deb` -- `artifactapi_remote_github_alpine` - -#### Common Attributes - -| Attribute | Required | Default | Description | -|----------------------|----------|---------|-------------------------------------------------------------------| -| `name` | Yes | | Unique name (forces replacement on change) | -| `base_url` | Yes | | Upstream repository URL | -| `description` | No | `""` | Human-readable description | -| `username` | No | `""` | Upstream auth username (sensitive) | -| `password` | No | `""` | Upstream auth password (sensitive) | -| `immutable_ttl` | No | `0` | TTL in seconds for immutable artifacts (0 = cache forever) | -| `mutable_ttl` | No | `3600` | TTL in seconds for mutable artifacts | -| `check_mutable` | No | `true` | Enable conditional revalidation for mutable artifacts | -| `patterns` | No | | Allowlist of path patterns to proxy (empty = all) | -| `blocklist` | No | | Paths to always deny (checked before patterns) | -| `mutable_patterns` | No | | Override: treat matching paths as mutable | -| `immutable_patterns` | No | | Override: treat matching paths as immutable | -| `quarantine_enabled` | No | `false` | Enable quarantine for new artifacts | -| `quarantine_days` | No | `3` | Days to quarantine new artifacts | -| `stale_on_error` | No | `true` | Serve stale cache when upstream is unreachable | -| `upstream_dial_timeout` | No | `0` | Upstream TCP connect timeout in seconds (0 = server default) | -| `upstream_tls_timeout` | No | `0` | Upstream TLS handshake timeout in seconds (0 = server default) | -| `upstream_response_header_timeout` | No | `0` | Upstream response-header timeout in seconds (0 = server default) | - -#### rpm / deb / alpine-specific Attributes - -| Attribute | Default | Description | -|-------------------|---------------|-------------------------------------------------------------------------------------------------| -| `mirrorlist` | | Extra upstream mirror base URLs. Requests are load-balanced with failover across `base_url` + `mirrorlist`. | -| `mirror_strategy` | `round_robin` | Mirror load-balancing strategy across `base_url` + `mirrorlist`. One of `round_robin` or `least_conn`. | - -Only valid on the `artifactapi_remote_rpm`, `artifactapi_remote_deb`, and -`artifactapi_remote_alpine` resources. Setting either on any other remote type is -rejected at plan time. - -```hcl -resource "artifactapi_remote_rpm" "epel" { - name = "epel-9" - base_url = "https://download.example.com/pub/epel/9/Everything/x86_64" - mirrorlist = [ - "https://mirror-a.example.net/epel/9/Everything/x86_64", - "https://mirror-b.example.org/epel/9/Everything/x86_64", - ] - mirror_strategy = "least_conn" -} -``` - -#### Docker-specific Attributes - -| Attribute | Default | Description | -|--------------------|---------|----------------------------| -| `ban_tags_enabled` | `false` | Enable tag banning | -| `ban_tags` | | List of tags to ban | - -#### Terraform / github_rpm / github_deb / github_alpine-specific Attributes - -| Attribute | Default | Description | -|-------------------|---------|----------------------------------------------------------| -| `releases_remote` | `""` | Name of a backend remote that serves the download bytes. Terraform uses it for download URL rewriting; `github_rpm`/`github_deb`/`github_alpine` 302-redirect `.rpm`/`.deb`/`.apk` downloads to it. | - -#### github_rpm-specific notes - -`artifactapi_remote_github_rpm` exposes a GitHub repo's releases as a `dnf`/`yum` -repository without precaching packages. Set `base_url` to the releases API root -(`https://api.github.com/repos/{owner}/{repo}`) and `releases_remote` to a -generic `github.com` remote that streams the `.rpm` bytes. `patterns` filters -which release assets become packages (regex on asset filename). `password` may -hold a token for private repos or higher API rate limits. See -`examples/resources/artifactapi_remote_github_rpm/main.tf`. - -#### github_deb-specific notes - -`artifactapi_remote_github_deb` is the Debian/apt analog of `github_rpm`: it -exposes a GitHub repo's release `.deb` assets as an `apt` repository, synthesizing -the apt index (`Packages`/`Release`) from release metadata without precaching -packages. Set `base_url` to the releases API root -(`https://api.github.com/repos/{owner}/{repo}`) and `releases_remote` to a -generic `github.com` remote that streams the `.deb` bytes; `.deb` downloads -302-redirect there. `patterns` filters which release assets become packages -(regex on asset filename). `password` may hold a token for private repos or -higher API rate limits. See -`examples/resources/artifactapi_remote_github_deb/main.tf`. - -#### github_alpine-specific notes - -`artifactapi_remote_github_alpine` is the Alpine/apk analog of `github_rpm`: it -exposes a GitHub repo's release `.apk` assets as an `apk` repository, synthesizing -the apk index (`APKINDEX.tar.gz`) from release metadata without precaching -packages. Set `base_url` to the releases API root -(`https://api.github.com/repos/{owner}/{repo}`) and `releases_remote` to a -generic `github.com` remote that streams the `.apk` bytes; `.apk` downloads -302-redirect there. `patterns` filters which release assets become packages -(regex on asset filename). `password` may hold a token for private repos or -higher API rate limits. See -`examples/resources/artifactapi_remote_github_alpine/main.tf`. - -#### Example - -```hcl -resource "artifactapi_remote_docker" "dockerhub" { - name = "dockerhub" - base_url = "https://registry-1.docker.io" - - immutable_ttl = 0 - mutable_ttl = 300 - ban_tags_enabled = true - ban_tags = ["latest"] - - patterns = [ - "^library/postgres", - "^library/redis", - ] -} -``` - -### Local Resources - -Local resources manage repositories that ArtifactAPI hosts directly (rather than -proxying an upstream) — each is a real registry for its package type. - -Available resource types: - -- `artifactapi_local_docker` — a container registry (Docker Registry HTTP API V2, push and pull) -- `artifactapi_local_pypi` -- `artifactapi_local_npm` — an npm registry (`npm publish` and `npm install`) -- `artifactapi_local_rpm` -- `artifactapi_local_deb` -- `artifactapi_local_alpine` -- `artifactapi_local_helm` — a chart repository (index.yaml generated from uploaded charts) -- `artifactapi_local_terraform` - -Each takes just `name` (required, forces replacement) and an optional -`description`. - -```hcl -resource "artifactapi_local_docker" "internal" { - name = "docker-internal" - description = "Internal container image registry" -} -``` - -Images push and pull against `//:`, e.g. -`docker push artifactapi.example.com/docker-internal/myapp:latest`. - -### Virtual Resources - -Virtual repositories merge multiple remotes of the same package type into a single endpoint. - -```hcl -resource "artifactapi_virtual" "helm" { - name = "helm" - package_type = "helm" - description = "All helm repos merged" - - members = [ - artifactapi_remote_helm.jetstack.name, - artifactapi_remote_helm.hashicorp_helm.name, - ] -} -``` - -| Attribute | Required | Description | -|----------------|----------|-------------------------------------------| -| `name` | Yes | Unique name (forces replacement on change)| -| `package_type` | Yes | Package type of member remotes | -| `description` | No | Human-readable description | -| `members` | Yes | List of remote names to include | - -## Data Sources - -### `artifactapi_remote` - -Read an existing remote's configuration. - -```hcl -data "artifactapi_remote" "dockerhub" { - name = "dockerhub" -} -``` - -### `artifactapi_virtual` - -Read an existing virtual repository's configuration. - -```hcl -data "artifactapi_virtual" "helm" { - name = "helm" -} -``` +Each resource is documented in [`docs/resources/`](docs/resources/) and each data +source in [`docs/data-sources/`](docs/data-sources/), with a matching example under +[`examples/`](examples/). ## Import diff --git a/docs/data-sources/remote.md b/docs/data-sources/remote.md new file mode 100644 index 0000000..86890c9 --- /dev/null +++ b/docs/data-sources/remote.md @@ -0,0 +1,9 @@ +# `artifactapi_remote` (Data Source) + +Read an existing remote's configuration. + +```hcl +data "artifactapi_remote" "dockerhub" { + name = "dockerhub" +} +``` diff --git a/docs/data-sources/virtual.md b/docs/data-sources/virtual.md new file mode 100644 index 0000000..8465708 --- /dev/null +++ b/docs/data-sources/virtual.md @@ -0,0 +1,9 @@ +# `artifactapi_virtual` (Data Source) + +Read an existing virtual repository's configuration. + +```hcl +data "artifactapi_virtual" "helm" { + name = "helm" +} +``` diff --git a/docs/resources/local_alpine.md b/docs/resources/local_alpine.md new file mode 100644 index 0000000..ace4bcd --- /dev/null +++ b/docs/resources/local_alpine.md @@ -0,0 +1,11 @@ +# `artifactapi_local_alpine` + +Local resources manage repositories that ArtifactAPI hosts directly (rather than +proxying an upstream) — each is a real registry for its package type. + +Each takes just `name` (required, forces replacement) and an optional +`description`. + +## Example + +See `examples/resources/artifactapi_local_alpine/main.tf`. diff --git a/docs/resources/local_deb.md b/docs/resources/local_deb.md new file mode 100644 index 0000000..0db1aed --- /dev/null +++ b/docs/resources/local_deb.md @@ -0,0 +1,11 @@ +# `artifactapi_local_deb` + +Local resources manage repositories that ArtifactAPI hosts directly (rather than +proxying an upstream) — each is a real registry for its package type. + +Each takes just `name` (required, forces replacement) and an optional +`description`. + +## Example + +See `examples/resources/artifactapi_local_deb/main.tf`. diff --git a/docs/resources/local_docker.md b/docs/resources/local_docker.md new file mode 100644 index 0000000..48984c3 --- /dev/null +++ b/docs/resources/local_docker.md @@ -0,0 +1,21 @@ +# `artifactapi_local_docker` + +Local resources manage repositories that ArtifactAPI hosts directly (rather than +proxying an upstream) — each is a real registry for its package type. + +A container registry (Docker Registry HTTP API V2, push and pull). + +Each takes just `name` (required, forces replacement) and an optional +`description`. + +## Example + +```hcl +resource "artifactapi_local_docker" "internal" { + name = "docker-internal" + description = "Internal container image registry" +} +``` + +Images push and pull against `//:`, e.g. +`docker push artifactapi.example.com/docker-internal/myapp:latest`. diff --git a/docs/resources/local_generic.md b/docs/resources/local_generic.md new file mode 100644 index 0000000..212a45f --- /dev/null +++ b/docs/resources/local_generic.md @@ -0,0 +1,11 @@ +# `artifactapi_local_generic` + +Local resources manage repositories that ArtifactAPI hosts directly (rather than +proxying an upstream) — each is a real registry for its package type. + +Each takes just `name` (required, forces replacement) and an optional +`description`. + +## Example + +See `examples/resources/artifactapi_local_generic/main.tf`. diff --git a/docs/resources/local_helm.md b/docs/resources/local_helm.md new file mode 100644 index 0000000..5fa2f1a --- /dev/null +++ b/docs/resources/local_helm.md @@ -0,0 +1,13 @@ +# `artifactapi_local_helm` + +Local resources manage repositories that ArtifactAPI hosts directly (rather than +proxying an upstream) — each is a real registry for its package type. + +A chart repository (index.yaml generated from uploaded charts). + +Each takes just `name` (required, forces replacement) and an optional +`description`. + +## Example + +See `examples/resources/artifactapi_local_helm/main.tf`. diff --git a/docs/resources/local_npm.md b/docs/resources/local_npm.md new file mode 100644 index 0000000..0373b00 --- /dev/null +++ b/docs/resources/local_npm.md @@ -0,0 +1,13 @@ +# `artifactapi_local_npm` + +Local resources manage repositories that ArtifactAPI hosts directly (rather than +proxying an upstream) — each is a real registry for its package type. + +An npm registry (`npm publish` and `npm install`). + +Each takes just `name` (required, forces replacement) and an optional +`description`. + +## Example + +See `examples/resources/artifactapi_local_npm/main.tf`. diff --git a/docs/resources/local_pypi.md b/docs/resources/local_pypi.md new file mode 100644 index 0000000..1a8952f --- /dev/null +++ b/docs/resources/local_pypi.md @@ -0,0 +1,11 @@ +# `artifactapi_local_pypi` + +Local resources manage repositories that ArtifactAPI hosts directly (rather than +proxying an upstream) — each is a real registry for its package type. + +Each takes just `name` (required, forces replacement) and an optional +`description`. + +## Example + +See `examples/resources/artifactapi_local_pypi/main.tf`. diff --git a/docs/resources/local_rpm.md b/docs/resources/local_rpm.md new file mode 100644 index 0000000..f0a2a79 --- /dev/null +++ b/docs/resources/local_rpm.md @@ -0,0 +1,11 @@ +# `artifactapi_local_rpm` + +Local resources manage repositories that ArtifactAPI hosts directly (rather than +proxying an upstream) — each is a real registry for its package type. + +Each takes just `name` (required, forces replacement) and an optional +`description`. + +## Example + +See `examples/resources/artifactapi_local_rpm/main.tf`. diff --git a/docs/resources/local_terraform.md b/docs/resources/local_terraform.md new file mode 100644 index 0000000..d3986bf --- /dev/null +++ b/docs/resources/local_terraform.md @@ -0,0 +1,11 @@ +# `artifactapi_local_terraform` + +Local resources manage repositories that ArtifactAPI hosts directly (rather than +proxying an upstream) — each is a real registry for its package type. + +Each takes just `name` (required, forces replacement) and an optional +`description`. + +## Example + +See `examples/resources/artifactapi_local_terraform/main.tf`. diff --git a/docs/resources/remote_alpine.md b/docs/resources/remote_alpine.md new file mode 100644 index 0000000..2f6833f --- /dev/null +++ b/docs/resources/remote_alpine.md @@ -0,0 +1,53 @@ +# `artifactapi_remote_alpine` + +Per-type remote resources manage upstream repository proxies. Each type applies its own mutability classification rules automatically (e.g., Docker classifies tag manifests as mutable and blobs as immutable; Helm classifies `index.yaml` as mutable). + +## Common Attributes + +| Attribute | Required | Default | Description | +|----------------------|----------|---------|-------------------------------------------------------------------| +| `name` | Yes | | Unique name (forces replacement on change) | +| `base_url` | Yes | | Upstream repository URL | +| `description` | No | `""` | Human-readable description | +| `username` | No | `""` | Upstream auth username (sensitive) | +| `password` | No | `""` | Upstream auth password (sensitive) | +| `immutable_ttl` | No | `0` | TTL in seconds for immutable artifacts (0 = cache forever) | +| `mutable_ttl` | No | `3600` | TTL in seconds for mutable artifacts | +| `check_mutable` | No | `true` | Enable conditional revalidation for mutable artifacts | +| `patterns` | No | | Allowlist of path patterns to proxy (empty = all) | +| `blocklist` | No | | Paths to always deny (checked before patterns) | +| `mutable_patterns` | No | | Override: treat matching paths as mutable | +| `immutable_patterns` | No | | Override: treat matching paths as immutable | +| `quarantine_enabled` | No | `false` | Enable quarantine for new artifacts | +| `quarantine_days` | No | `3` | Days to quarantine new artifacts | +| `stale_on_error` | No | `true` | Serve stale cache when upstream is unreachable | +| `upstream_dial_timeout` | No | `0` | Upstream TCP connect timeout in seconds (0 = server default) | +| `upstream_tls_timeout` | No | `0` | Upstream TLS handshake timeout in seconds (0 = server default) | +| `upstream_response_header_timeout` | No | `0` | Upstream response-header timeout in seconds (0 = server default) | + +## rpm / deb / alpine-specific Attributes + +| Attribute | Default | Description | +|-------------------|---------------|-------------------------------------------------------------------------------------------------| +| `mirrorlist` | | Extra upstream mirror base URLs. Requests are load-balanced with failover across `base_url` + `mirrorlist`. | +| `mirror_strategy` | `round_robin` | Mirror load-balancing strategy across `base_url` + `mirrorlist`. One of `round_robin` or `least_conn`. | + +Only valid on the `artifactapi_remote_rpm`, `artifactapi_remote_deb`, and +`artifactapi_remote_alpine` resources. Setting either on any other remote type is +rejected at plan time. + +```hcl +resource "artifactapi_remote_rpm" "epel" { + name = "epel-9" + base_url = "https://download.example.com/pub/epel/9/Everything/x86_64" + mirrorlist = [ + "https://mirror-a.example.net/epel/9/Everything/x86_64", + "https://mirror-b.example.org/epel/9/Everything/x86_64", + ] + mirror_strategy = "least_conn" +} +``` + +## Example + +See `examples/resources/artifactapi_remote_alpine/main.tf`. diff --git a/docs/resources/remote_cargo.md b/docs/resources/remote_cargo.md new file mode 100644 index 0000000..63a9282 --- /dev/null +++ b/docs/resources/remote_cargo.md @@ -0,0 +1,30 @@ +# `artifactapi_remote_cargo` + +Per-type remote resources manage upstream repository proxies. Each type applies its own mutability classification rules automatically (e.g., Docker classifies tag manifests as mutable and blobs as immutable; Helm classifies `index.yaml` as mutable). + +## Common Attributes + +| Attribute | Required | Default | Description | +|----------------------|----------|---------|-------------------------------------------------------------------| +| `name` | Yes | | Unique name (forces replacement on change) | +| `base_url` | Yes | | Upstream repository URL | +| `description` | No | `""` | Human-readable description | +| `username` | No | `""` | Upstream auth username (sensitive) | +| `password` | No | `""` | Upstream auth password (sensitive) | +| `immutable_ttl` | No | `0` | TTL in seconds for immutable artifacts (0 = cache forever) | +| `mutable_ttl` | No | `3600` | TTL in seconds for mutable artifacts | +| `check_mutable` | No | `true` | Enable conditional revalidation for mutable artifacts | +| `patterns` | No | | Allowlist of path patterns to proxy (empty = all) | +| `blocklist` | No | | Paths to always deny (checked before patterns) | +| `mutable_patterns` | No | | Override: treat matching paths as mutable | +| `immutable_patterns` | No | | Override: treat matching paths as immutable | +| `quarantine_enabled` | No | `false` | Enable quarantine for new artifacts | +| `quarantine_days` | No | `3` | Days to quarantine new artifacts | +| `stale_on_error` | No | `true` | Serve stale cache when upstream is unreachable | +| `upstream_dial_timeout` | No | `0` | Upstream TCP connect timeout in seconds (0 = server default) | +| `upstream_tls_timeout` | No | `0` | Upstream TLS handshake timeout in seconds (0 = server default) | +| `upstream_response_header_timeout` | No | `0` | Upstream response-header timeout in seconds (0 = server default) | + +## Example + +See `examples/resources/artifactapi_remote_cargo/main.tf`. diff --git a/docs/resources/remote_deb.md b/docs/resources/remote_deb.md new file mode 100644 index 0000000..f197e96 --- /dev/null +++ b/docs/resources/remote_deb.md @@ -0,0 +1,53 @@ +# `artifactapi_remote_deb` + +Per-type remote resources manage upstream repository proxies. Each type applies its own mutability classification rules automatically (e.g., Docker classifies tag manifests as mutable and blobs as immutable; Helm classifies `index.yaml` as mutable). + +## Common Attributes + +| Attribute | Required | Default | Description | +|----------------------|----------|---------|-------------------------------------------------------------------| +| `name` | Yes | | Unique name (forces replacement on change) | +| `base_url` | Yes | | Upstream repository URL | +| `description` | No | `""` | Human-readable description | +| `username` | No | `""` | Upstream auth username (sensitive) | +| `password` | No | `""` | Upstream auth password (sensitive) | +| `immutable_ttl` | No | `0` | TTL in seconds for immutable artifacts (0 = cache forever) | +| `mutable_ttl` | No | `3600` | TTL in seconds for mutable artifacts | +| `check_mutable` | No | `true` | Enable conditional revalidation for mutable artifacts | +| `patterns` | No | | Allowlist of path patterns to proxy (empty = all) | +| `blocklist` | No | | Paths to always deny (checked before patterns) | +| `mutable_patterns` | No | | Override: treat matching paths as mutable | +| `immutable_patterns` | No | | Override: treat matching paths as immutable | +| `quarantine_enabled` | No | `false` | Enable quarantine for new artifacts | +| `quarantine_days` | No | `3` | Days to quarantine new artifacts | +| `stale_on_error` | No | `true` | Serve stale cache when upstream is unreachable | +| `upstream_dial_timeout` | No | `0` | Upstream TCP connect timeout in seconds (0 = server default) | +| `upstream_tls_timeout` | No | `0` | Upstream TLS handshake timeout in seconds (0 = server default) | +| `upstream_response_header_timeout` | No | `0` | Upstream response-header timeout in seconds (0 = server default) | + +## rpm / deb / alpine-specific Attributes + +| Attribute | Default | Description | +|-------------------|---------------|-------------------------------------------------------------------------------------------------| +| `mirrorlist` | | Extra upstream mirror base URLs. Requests are load-balanced with failover across `base_url` + `mirrorlist`. | +| `mirror_strategy` | `round_robin` | Mirror load-balancing strategy across `base_url` + `mirrorlist`. One of `round_robin` or `least_conn`. | + +Only valid on the `artifactapi_remote_rpm`, `artifactapi_remote_deb`, and +`artifactapi_remote_alpine` resources. Setting either on any other remote type is +rejected at plan time. + +```hcl +resource "artifactapi_remote_rpm" "epel" { + name = "epel-9" + base_url = "https://download.example.com/pub/epel/9/Everything/x86_64" + mirrorlist = [ + "https://mirror-a.example.net/epel/9/Everything/x86_64", + "https://mirror-b.example.org/epel/9/Everything/x86_64", + ] + mirror_strategy = "least_conn" +} +``` + +## Example + +See `examples/resources/artifactapi_remote_deb/main.tf`. diff --git a/docs/resources/remote_docker.md b/docs/resources/remote_docker.md new file mode 100644 index 0000000..23b0439 --- /dev/null +++ b/docs/resources/remote_docker.md @@ -0,0 +1,52 @@ +# `artifactapi_remote_docker` + +Per-type remote resources manage upstream repository proxies. Each type applies its own mutability classification rules automatically (e.g., Docker classifies tag manifests as mutable and blobs as immutable; Helm classifies `index.yaml` as mutable). + +## Common Attributes + +| Attribute | Required | Default | Description | +|----------------------|----------|---------|-------------------------------------------------------------------| +| `name` | Yes | | Unique name (forces replacement on change) | +| `base_url` | Yes | | Upstream repository URL | +| `description` | No | `""` | Human-readable description | +| `username` | No | `""` | Upstream auth username (sensitive) | +| `password` | No | `""` | Upstream auth password (sensitive) | +| `immutable_ttl` | No | `0` | TTL in seconds for immutable artifacts (0 = cache forever) | +| `mutable_ttl` | No | `3600` | TTL in seconds for mutable artifacts | +| `check_mutable` | No | `true` | Enable conditional revalidation for mutable artifacts | +| `patterns` | No | | Allowlist of path patterns to proxy (empty = all) | +| `blocklist` | No | | Paths to always deny (checked before patterns) | +| `mutable_patterns` | No | | Override: treat matching paths as mutable | +| `immutable_patterns` | No | | Override: treat matching paths as immutable | +| `quarantine_enabled` | No | `false` | Enable quarantine for new artifacts | +| `quarantine_days` | No | `3` | Days to quarantine new artifacts | +| `stale_on_error` | No | `true` | Serve stale cache when upstream is unreachable | +| `upstream_dial_timeout` | No | `0` | Upstream TCP connect timeout in seconds (0 = server default) | +| `upstream_tls_timeout` | No | `0` | Upstream TLS handshake timeout in seconds (0 = server default) | +| `upstream_response_header_timeout` | No | `0` | Upstream response-header timeout in seconds (0 = server default) | + +## Docker-specific Attributes + +| Attribute | Default | Description | +|--------------------|---------|----------------------------| +| `ban_tags_enabled` | `false` | Enable tag banning | +| `ban_tags` | | List of tags to ban | + +## Example + +```hcl +resource "artifactapi_remote_docker" "dockerhub" { + name = "dockerhub" + base_url = "https://registry-1.docker.io" + + immutable_ttl = 0 + mutable_ttl = 300 + ban_tags_enabled = true + ban_tags = ["latest"] + + patterns = [ + "^library/postgres", + "^library/redis", + ] +} +``` diff --git a/docs/resources/remote_gems.md b/docs/resources/remote_gems.md new file mode 100644 index 0000000..56fcf71 --- /dev/null +++ b/docs/resources/remote_gems.md @@ -0,0 +1,30 @@ +# `artifactapi_remote_gems` + +Per-type remote resources manage upstream repository proxies. Each type applies its own mutability classification rules automatically (e.g., Docker classifies tag manifests as mutable and blobs as immutable; Helm classifies `index.yaml` as mutable). + +## Common Attributes + +| Attribute | Required | Default | Description | +|----------------------|----------|---------|-------------------------------------------------------------------| +| `name` | Yes | | Unique name (forces replacement on change) | +| `base_url` | Yes | | Upstream repository URL | +| `description` | No | `""` | Human-readable description | +| `username` | No | `""` | Upstream auth username (sensitive) | +| `password` | No | `""` | Upstream auth password (sensitive) | +| `immutable_ttl` | No | `0` | TTL in seconds for immutable artifacts (0 = cache forever) | +| `mutable_ttl` | No | `3600` | TTL in seconds for mutable artifacts | +| `check_mutable` | No | `true` | Enable conditional revalidation for mutable artifacts | +| `patterns` | No | | Allowlist of path patterns to proxy (empty = all) | +| `blocklist` | No | | Paths to always deny (checked before patterns) | +| `mutable_patterns` | No | | Override: treat matching paths as mutable | +| `immutable_patterns` | No | | Override: treat matching paths as immutable | +| `quarantine_enabled` | No | `false` | Enable quarantine for new artifacts | +| `quarantine_days` | No | `3` | Days to quarantine new artifacts | +| `stale_on_error` | No | `true` | Serve stale cache when upstream is unreachable | +| `upstream_dial_timeout` | No | `0` | Upstream TCP connect timeout in seconds (0 = server default) | +| `upstream_tls_timeout` | No | `0` | Upstream TLS handshake timeout in seconds (0 = server default) | +| `upstream_response_header_timeout` | No | `0` | Upstream response-header timeout in seconds (0 = server default) | + +## Example + +See `examples/resources/artifactapi_remote_gems/main.tf`. diff --git a/docs/resources/remote_generic.md b/docs/resources/remote_generic.md new file mode 100644 index 0000000..42fce9d --- /dev/null +++ b/docs/resources/remote_generic.md @@ -0,0 +1,30 @@ +# `artifactapi_remote_generic` + +Per-type remote resources manage upstream repository proxies. Each type applies its own mutability classification rules automatically (e.g., Docker classifies tag manifests as mutable and blobs as immutable; Helm classifies `index.yaml` as mutable). + +## Common Attributes + +| Attribute | Required | Default | Description | +|----------------------|----------|---------|-------------------------------------------------------------------| +| `name` | Yes | | Unique name (forces replacement on change) | +| `base_url` | Yes | | Upstream repository URL | +| `description` | No | `""` | Human-readable description | +| `username` | No | `""` | Upstream auth username (sensitive) | +| `password` | No | `""` | Upstream auth password (sensitive) | +| `immutable_ttl` | No | `0` | TTL in seconds for immutable artifacts (0 = cache forever) | +| `mutable_ttl` | No | `3600` | TTL in seconds for mutable artifacts | +| `check_mutable` | No | `true` | Enable conditional revalidation for mutable artifacts | +| `patterns` | No | | Allowlist of path patterns to proxy (empty = all) | +| `blocklist` | No | | Paths to always deny (checked before patterns) | +| `mutable_patterns` | No | | Override: treat matching paths as mutable | +| `immutable_patterns` | No | | Override: treat matching paths as immutable | +| `quarantine_enabled` | No | `false` | Enable quarantine for new artifacts | +| `quarantine_days` | No | `3` | Days to quarantine new artifacts | +| `stale_on_error` | No | `true` | Serve stale cache when upstream is unreachable | +| `upstream_dial_timeout` | No | `0` | Upstream TCP connect timeout in seconds (0 = server default) | +| `upstream_tls_timeout` | No | `0` | Upstream TLS handshake timeout in seconds (0 = server default) | +| `upstream_response_header_timeout` | No | `0` | Upstream response-header timeout in seconds (0 = server default) | + +## Example + +See `examples/resources/artifactapi_remote_generic/main.tf`. diff --git a/docs/resources/remote_github_alpine.md b/docs/resources/remote_github_alpine.md new file mode 100644 index 0000000..281cf5c --- /dev/null +++ b/docs/resources/remote_github_alpine.md @@ -0,0 +1,49 @@ +# `artifactapi_remote_github_alpine` + +Per-type remote resources manage upstream repository proxies. Each type applies its own mutability classification rules automatically (e.g., Docker classifies tag manifests as mutable and blobs as immutable; Helm classifies `index.yaml` as mutable). + +## Common Attributes + +| Attribute | Required | Default | Description | +|----------------------|----------|---------|-------------------------------------------------------------------| +| `name` | Yes | | Unique name (forces replacement on change) | +| `base_url` | Yes | | Upstream repository URL | +| `description` | No | `""` | Human-readable description | +| `username` | No | `""` | Upstream auth username (sensitive) | +| `password` | No | `""` | Upstream auth password (sensitive) | +| `immutable_ttl` | No | `0` | TTL in seconds for immutable artifacts (0 = cache forever) | +| `mutable_ttl` | No | `3600` | TTL in seconds for mutable artifacts | +| `check_mutable` | No | `true` | Enable conditional revalidation for mutable artifacts | +| `patterns` | No | | Allowlist of path patterns to proxy (empty = all) | +| `blocklist` | No | | Paths to always deny (checked before patterns) | +| `mutable_patterns` | No | | Override: treat matching paths as mutable | +| `immutable_patterns` | No | | Override: treat matching paths as immutable | +| `quarantine_enabled` | No | `false` | Enable quarantine for new artifacts | +| `quarantine_days` | No | `3` | Days to quarantine new artifacts | +| `stale_on_error` | No | `true` | Serve stale cache when upstream is unreachable | +| `upstream_dial_timeout` | No | `0` | Upstream TCP connect timeout in seconds (0 = server default) | +| `upstream_tls_timeout` | No | `0` | Upstream TLS handshake timeout in seconds (0 = server default) | +| `upstream_response_header_timeout` | No | `0` | Upstream response-header timeout in seconds (0 = server default) | + +## Terraform / github_rpm / github_deb / github_alpine-specific Attributes + +| Attribute | Default | Description | +|-------------------|---------|----------------------------------------------------------| +| `releases_remote` | `""` | Name of a backend remote that serves the download bytes. Terraform uses it for download URL rewriting; `github_rpm`/`github_deb`/`github_alpine` 302-redirect `.rpm`/`.deb`/`.apk` downloads to it. | + +## Notes + +`artifactapi_remote_github_alpine` is the Alpine/apk analog of `github_rpm`: it +exposes a GitHub repo's release `.apk` assets as an `apk` repository, synthesizing +the apk index (`APKINDEX.tar.gz`) from release metadata without precaching +packages. Set `base_url` to the releases API root +(`https://api.github.com/repos/{owner}/{repo}`) and `releases_remote` to a +generic `github.com` remote that streams the `.apk` bytes; `.apk` downloads +302-redirect there. `patterns` filters which release assets become packages +(regex on asset filename). `password` may hold a token for private repos or +higher API rate limits. See +`examples/resources/artifactapi_remote_github_alpine/main.tf`. + +## Example + +See `examples/resources/artifactapi_remote_github_alpine/main.tf`. diff --git a/docs/resources/remote_github_deb.md b/docs/resources/remote_github_deb.md new file mode 100644 index 0000000..8c4f51b --- /dev/null +++ b/docs/resources/remote_github_deb.md @@ -0,0 +1,49 @@ +# `artifactapi_remote_github_deb` + +Per-type remote resources manage upstream repository proxies. Each type applies its own mutability classification rules automatically (e.g., Docker classifies tag manifests as mutable and blobs as immutable; Helm classifies `index.yaml` as mutable). + +## Common Attributes + +| Attribute | Required | Default | Description | +|----------------------|----------|---------|-------------------------------------------------------------------| +| `name` | Yes | | Unique name (forces replacement on change) | +| `base_url` | Yes | | Upstream repository URL | +| `description` | No | `""` | Human-readable description | +| `username` | No | `""` | Upstream auth username (sensitive) | +| `password` | No | `""` | Upstream auth password (sensitive) | +| `immutable_ttl` | No | `0` | TTL in seconds for immutable artifacts (0 = cache forever) | +| `mutable_ttl` | No | `3600` | TTL in seconds for mutable artifacts | +| `check_mutable` | No | `true` | Enable conditional revalidation for mutable artifacts | +| `patterns` | No | | Allowlist of path patterns to proxy (empty = all) | +| `blocklist` | No | | Paths to always deny (checked before patterns) | +| `mutable_patterns` | No | | Override: treat matching paths as mutable | +| `immutable_patterns` | No | | Override: treat matching paths as immutable | +| `quarantine_enabled` | No | `false` | Enable quarantine for new artifacts | +| `quarantine_days` | No | `3` | Days to quarantine new artifacts | +| `stale_on_error` | No | `true` | Serve stale cache when upstream is unreachable | +| `upstream_dial_timeout` | No | `0` | Upstream TCP connect timeout in seconds (0 = server default) | +| `upstream_tls_timeout` | No | `0` | Upstream TLS handshake timeout in seconds (0 = server default) | +| `upstream_response_header_timeout` | No | `0` | Upstream response-header timeout in seconds (0 = server default) | + +## Terraform / github_rpm / github_deb / github_alpine-specific Attributes + +| Attribute | Default | Description | +|-------------------|---------|----------------------------------------------------------| +| `releases_remote` | `""` | Name of a backend remote that serves the download bytes. Terraform uses it for download URL rewriting; `github_rpm`/`github_deb`/`github_alpine` 302-redirect `.rpm`/`.deb`/`.apk` downloads to it. | + +## Notes + +`artifactapi_remote_github_deb` is the Debian/apt analog of `github_rpm`: it +exposes a GitHub repo's release `.deb` assets as an `apt` repository, synthesizing +the apt index (`Packages`/`Release`) from release metadata without precaching +packages. Set `base_url` to the releases API root +(`https://api.github.com/repos/{owner}/{repo}`) and `releases_remote` to a +generic `github.com` remote that streams the `.deb` bytes; `.deb` downloads +302-redirect there. `patterns` filters which release assets become packages +(regex on asset filename). `password` may hold a token for private repos or +higher API rate limits. See +`examples/resources/artifactapi_remote_github_deb/main.tf`. + +## Example + +See `examples/resources/artifactapi_remote_github_deb/main.tf`. diff --git a/docs/resources/remote_github_rpm.md b/docs/resources/remote_github_rpm.md new file mode 100644 index 0000000..39eae13 --- /dev/null +++ b/docs/resources/remote_github_rpm.md @@ -0,0 +1,46 @@ +# `artifactapi_remote_github_rpm` + +Per-type remote resources manage upstream repository proxies. Each type applies its own mutability classification rules automatically (e.g., Docker classifies tag manifests as mutable and blobs as immutable; Helm classifies `index.yaml` as mutable). + +## Common Attributes + +| Attribute | Required | Default | Description | +|----------------------|----------|---------|-------------------------------------------------------------------| +| `name` | Yes | | Unique name (forces replacement on change) | +| `base_url` | Yes | | Upstream repository URL | +| `description` | No | `""` | Human-readable description | +| `username` | No | `""` | Upstream auth username (sensitive) | +| `password` | No | `""` | Upstream auth password (sensitive) | +| `immutable_ttl` | No | `0` | TTL in seconds for immutable artifacts (0 = cache forever) | +| `mutable_ttl` | No | `3600` | TTL in seconds for mutable artifacts | +| `check_mutable` | No | `true` | Enable conditional revalidation for mutable artifacts | +| `patterns` | No | | Allowlist of path patterns to proxy (empty = all) | +| `blocklist` | No | | Paths to always deny (checked before patterns) | +| `mutable_patterns` | No | | Override: treat matching paths as mutable | +| `immutable_patterns` | No | | Override: treat matching paths as immutable | +| `quarantine_enabled` | No | `false` | Enable quarantine for new artifacts | +| `quarantine_days` | No | `3` | Days to quarantine new artifacts | +| `stale_on_error` | No | `true` | Serve stale cache when upstream is unreachable | +| `upstream_dial_timeout` | No | `0` | Upstream TCP connect timeout in seconds (0 = server default) | +| `upstream_tls_timeout` | No | `0` | Upstream TLS handshake timeout in seconds (0 = server default) | +| `upstream_response_header_timeout` | No | `0` | Upstream response-header timeout in seconds (0 = server default) | + +## Terraform / github_rpm / github_deb / github_alpine-specific Attributes + +| Attribute | Default | Description | +|-------------------|---------|----------------------------------------------------------| +| `releases_remote` | `""` | Name of a backend remote that serves the download bytes. Terraform uses it for download URL rewriting; `github_rpm`/`github_deb`/`github_alpine` 302-redirect `.rpm`/`.deb`/`.apk` downloads to it. | + +## Notes + +`artifactapi_remote_github_rpm` exposes a GitHub repo's releases as a `dnf`/`yum` +repository without precaching packages. Set `base_url` to the releases API root +(`https://api.github.com/repos/{owner}/{repo}`) and `releases_remote` to a +generic `github.com` remote that streams the `.rpm` bytes. `patterns` filters +which release assets become packages (regex on asset filename). `password` may +hold a token for private repos or higher API rate limits. See +`examples/resources/artifactapi_remote_github_rpm/main.tf`. + +## Example + +See `examples/resources/artifactapi_remote_github_rpm/main.tf`. diff --git a/docs/resources/remote_goproxy.md b/docs/resources/remote_goproxy.md new file mode 100644 index 0000000..0257737 --- /dev/null +++ b/docs/resources/remote_goproxy.md @@ -0,0 +1,30 @@ +# `artifactapi_remote_goproxy` + +Per-type remote resources manage upstream repository proxies. Each type applies its own mutability classification rules automatically (e.g., Docker classifies tag manifests as mutable and blobs as immutable; Helm classifies `index.yaml` as mutable). + +## Common Attributes + +| Attribute | Required | Default | Description | +|----------------------|----------|---------|-------------------------------------------------------------------| +| `name` | Yes | | Unique name (forces replacement on change) | +| `base_url` | Yes | | Upstream repository URL | +| `description` | No | `""` | Human-readable description | +| `username` | No | `""` | Upstream auth username (sensitive) | +| `password` | No | `""` | Upstream auth password (sensitive) | +| `immutable_ttl` | No | `0` | TTL in seconds for immutable artifacts (0 = cache forever) | +| `mutable_ttl` | No | `3600` | TTL in seconds for mutable artifacts | +| `check_mutable` | No | `true` | Enable conditional revalidation for mutable artifacts | +| `patterns` | No | | Allowlist of path patterns to proxy (empty = all) | +| `blocklist` | No | | Paths to always deny (checked before patterns) | +| `mutable_patterns` | No | | Override: treat matching paths as mutable | +| `immutable_patterns` | No | | Override: treat matching paths as immutable | +| `quarantine_enabled` | No | `false` | Enable quarantine for new artifacts | +| `quarantine_days` | No | `3` | Days to quarantine new artifacts | +| `stale_on_error` | No | `true` | Serve stale cache when upstream is unreachable | +| `upstream_dial_timeout` | No | `0` | Upstream TCP connect timeout in seconds (0 = server default) | +| `upstream_tls_timeout` | No | `0` | Upstream TLS handshake timeout in seconds (0 = server default) | +| `upstream_response_header_timeout` | No | `0` | Upstream response-header timeout in seconds (0 = server default) | + +## Example + +See `examples/resources/artifactapi_remote_goproxy/main.tf`. diff --git a/docs/resources/remote_helm.md b/docs/resources/remote_helm.md new file mode 100644 index 0000000..6986783 --- /dev/null +++ b/docs/resources/remote_helm.md @@ -0,0 +1,30 @@ +# `artifactapi_remote_helm` + +Per-type remote resources manage upstream repository proxies. Each type applies its own mutability classification rules automatically (e.g., Docker classifies tag manifests as mutable and blobs as immutable; Helm classifies `index.yaml` as mutable). + +## Common Attributes + +| Attribute | Required | Default | Description | +|----------------------|----------|---------|-------------------------------------------------------------------| +| `name` | Yes | | Unique name (forces replacement on change) | +| `base_url` | Yes | | Upstream repository URL | +| `description` | No | `""` | Human-readable description | +| `username` | No | `""` | Upstream auth username (sensitive) | +| `password` | No | `""` | Upstream auth password (sensitive) | +| `immutable_ttl` | No | `0` | TTL in seconds for immutable artifacts (0 = cache forever) | +| `mutable_ttl` | No | `3600` | TTL in seconds for mutable artifacts | +| `check_mutable` | No | `true` | Enable conditional revalidation for mutable artifacts | +| `patterns` | No | | Allowlist of path patterns to proxy (empty = all) | +| `blocklist` | No | | Paths to always deny (checked before patterns) | +| `mutable_patterns` | No | | Override: treat matching paths as mutable | +| `immutable_patterns` | No | | Override: treat matching paths as immutable | +| `quarantine_enabled` | No | `false` | Enable quarantine for new artifacts | +| `quarantine_days` | No | `3` | Days to quarantine new artifacts | +| `stale_on_error` | No | `true` | Serve stale cache when upstream is unreachable | +| `upstream_dial_timeout` | No | `0` | Upstream TCP connect timeout in seconds (0 = server default) | +| `upstream_tls_timeout` | No | `0` | Upstream TLS handshake timeout in seconds (0 = server default) | +| `upstream_response_header_timeout` | No | `0` | Upstream response-header timeout in seconds (0 = server default) | + +## Example + +See `examples/resources/artifactapi_remote_helm/main.tf`. diff --git a/docs/resources/remote_npm.md b/docs/resources/remote_npm.md new file mode 100644 index 0000000..347618b --- /dev/null +++ b/docs/resources/remote_npm.md @@ -0,0 +1,30 @@ +# `artifactapi_remote_npm` + +Per-type remote resources manage upstream repository proxies. Each type applies its own mutability classification rules automatically (e.g., Docker classifies tag manifests as mutable and blobs as immutable; Helm classifies `index.yaml` as mutable). + +## Common Attributes + +| Attribute | Required | Default | Description | +|----------------------|----------|---------|-------------------------------------------------------------------| +| `name` | Yes | | Unique name (forces replacement on change) | +| `base_url` | Yes | | Upstream repository URL | +| `description` | No | `""` | Human-readable description | +| `username` | No | `""` | Upstream auth username (sensitive) | +| `password` | No | `""` | Upstream auth password (sensitive) | +| `immutable_ttl` | No | `0` | TTL in seconds for immutable artifacts (0 = cache forever) | +| `mutable_ttl` | No | `3600` | TTL in seconds for mutable artifacts | +| `check_mutable` | No | `true` | Enable conditional revalidation for mutable artifacts | +| `patterns` | No | | Allowlist of path patterns to proxy (empty = all) | +| `blocklist` | No | | Paths to always deny (checked before patterns) | +| `mutable_patterns` | No | | Override: treat matching paths as mutable | +| `immutable_patterns` | No | | Override: treat matching paths as immutable | +| `quarantine_enabled` | No | `false` | Enable quarantine for new artifacts | +| `quarantine_days` | No | `3` | Days to quarantine new artifacts | +| `stale_on_error` | No | `true` | Serve stale cache when upstream is unreachable | +| `upstream_dial_timeout` | No | `0` | Upstream TCP connect timeout in seconds (0 = server default) | +| `upstream_tls_timeout` | No | `0` | Upstream TLS handshake timeout in seconds (0 = server default) | +| `upstream_response_header_timeout` | No | `0` | Upstream response-header timeout in seconds (0 = server default) | + +## Example + +See `examples/resources/artifactapi_remote_npm/main.tf`. diff --git a/docs/resources/remote_puppet.md b/docs/resources/remote_puppet.md new file mode 100644 index 0000000..6b268c2 --- /dev/null +++ b/docs/resources/remote_puppet.md @@ -0,0 +1,30 @@ +# `artifactapi_remote_puppet` + +Per-type remote resources manage upstream repository proxies. Each type applies its own mutability classification rules automatically (e.g., Docker classifies tag manifests as mutable and blobs as immutable; Helm classifies `index.yaml` as mutable). + +## Common Attributes + +| Attribute | Required | Default | Description | +|----------------------|----------|---------|-------------------------------------------------------------------| +| `name` | Yes | | Unique name (forces replacement on change) | +| `base_url` | Yes | | Upstream repository URL | +| `description` | No | `""` | Human-readable description | +| `username` | No | `""` | Upstream auth username (sensitive) | +| `password` | No | `""` | Upstream auth password (sensitive) | +| `immutable_ttl` | No | `0` | TTL in seconds for immutable artifacts (0 = cache forever) | +| `mutable_ttl` | No | `3600` | TTL in seconds for mutable artifacts | +| `check_mutable` | No | `true` | Enable conditional revalidation for mutable artifacts | +| `patterns` | No | | Allowlist of path patterns to proxy (empty = all) | +| `blocklist` | No | | Paths to always deny (checked before patterns) | +| `mutable_patterns` | No | | Override: treat matching paths as mutable | +| `immutable_patterns` | No | | Override: treat matching paths as immutable | +| `quarantine_enabled` | No | `false` | Enable quarantine for new artifacts | +| `quarantine_days` | No | `3` | Days to quarantine new artifacts | +| `stale_on_error` | No | `true` | Serve stale cache when upstream is unreachable | +| `upstream_dial_timeout` | No | `0` | Upstream TCP connect timeout in seconds (0 = server default) | +| `upstream_tls_timeout` | No | `0` | Upstream TLS handshake timeout in seconds (0 = server default) | +| `upstream_response_header_timeout` | No | `0` | Upstream response-header timeout in seconds (0 = server default) | + +## Example + +See `examples/resources/artifactapi_remote_puppet/main.tf`. diff --git a/docs/resources/remote_pypi.md b/docs/resources/remote_pypi.md new file mode 100644 index 0000000..596e570 --- /dev/null +++ b/docs/resources/remote_pypi.md @@ -0,0 +1,30 @@ +# `artifactapi_remote_pypi` + +Per-type remote resources manage upstream repository proxies. Each type applies its own mutability classification rules automatically (e.g., Docker classifies tag manifests as mutable and blobs as immutable; Helm classifies `index.yaml` as mutable). + +## Common Attributes + +| Attribute | Required | Default | Description | +|----------------------|----------|---------|-------------------------------------------------------------------| +| `name` | Yes | | Unique name (forces replacement on change) | +| `base_url` | Yes | | Upstream repository URL | +| `description` | No | `""` | Human-readable description | +| `username` | No | `""` | Upstream auth username (sensitive) | +| `password` | No | `""` | Upstream auth password (sensitive) | +| `immutable_ttl` | No | `0` | TTL in seconds for immutable artifacts (0 = cache forever) | +| `mutable_ttl` | No | `3600` | TTL in seconds for mutable artifacts | +| `check_mutable` | No | `true` | Enable conditional revalidation for mutable artifacts | +| `patterns` | No | | Allowlist of path patterns to proxy (empty = all) | +| `blocklist` | No | | Paths to always deny (checked before patterns) | +| `mutable_patterns` | No | | Override: treat matching paths as mutable | +| `immutable_patterns` | No | | Override: treat matching paths as immutable | +| `quarantine_enabled` | No | `false` | Enable quarantine for new artifacts | +| `quarantine_days` | No | `3` | Days to quarantine new artifacts | +| `stale_on_error` | No | `true` | Serve stale cache when upstream is unreachable | +| `upstream_dial_timeout` | No | `0` | Upstream TCP connect timeout in seconds (0 = server default) | +| `upstream_tls_timeout` | No | `0` | Upstream TLS handshake timeout in seconds (0 = server default) | +| `upstream_response_header_timeout` | No | `0` | Upstream response-header timeout in seconds (0 = server default) | + +## Example + +See `examples/resources/artifactapi_remote_pypi/main.tf`. diff --git a/docs/resources/remote_rpm.md b/docs/resources/remote_rpm.md new file mode 100644 index 0000000..309d946 --- /dev/null +++ b/docs/resources/remote_rpm.md @@ -0,0 +1,53 @@ +# `artifactapi_remote_rpm` + +Per-type remote resources manage upstream repository proxies. Each type applies its own mutability classification rules automatically (e.g., Docker classifies tag manifests as mutable and blobs as immutable; Helm classifies `index.yaml` as mutable). + +## Common Attributes + +| Attribute | Required | Default | Description | +|----------------------|----------|---------|-------------------------------------------------------------------| +| `name` | Yes | | Unique name (forces replacement on change) | +| `base_url` | Yes | | Upstream repository URL | +| `description` | No | `""` | Human-readable description | +| `username` | No | `""` | Upstream auth username (sensitive) | +| `password` | No | `""` | Upstream auth password (sensitive) | +| `immutable_ttl` | No | `0` | TTL in seconds for immutable artifacts (0 = cache forever) | +| `mutable_ttl` | No | `3600` | TTL in seconds for mutable artifacts | +| `check_mutable` | No | `true` | Enable conditional revalidation for mutable artifacts | +| `patterns` | No | | Allowlist of path patterns to proxy (empty = all) | +| `blocklist` | No | | Paths to always deny (checked before patterns) | +| `mutable_patterns` | No | | Override: treat matching paths as mutable | +| `immutable_patterns` | No | | Override: treat matching paths as immutable | +| `quarantine_enabled` | No | `false` | Enable quarantine for new artifacts | +| `quarantine_days` | No | `3` | Days to quarantine new artifacts | +| `stale_on_error` | No | `true` | Serve stale cache when upstream is unreachable | +| `upstream_dial_timeout` | No | `0` | Upstream TCP connect timeout in seconds (0 = server default) | +| `upstream_tls_timeout` | No | `0` | Upstream TLS handshake timeout in seconds (0 = server default) | +| `upstream_response_header_timeout` | No | `0` | Upstream response-header timeout in seconds (0 = server default) | + +## rpm / deb / alpine-specific Attributes + +| Attribute | Default | Description | +|-------------------|---------------|-------------------------------------------------------------------------------------------------| +| `mirrorlist` | | Extra upstream mirror base URLs. Requests are load-balanced with failover across `base_url` + `mirrorlist`. | +| `mirror_strategy` | `round_robin` | Mirror load-balancing strategy across `base_url` + `mirrorlist`. One of `round_robin` or `least_conn`. | + +Only valid on the `artifactapi_remote_rpm`, `artifactapi_remote_deb`, and +`artifactapi_remote_alpine` resources. Setting either on any other remote type is +rejected at plan time. + +```hcl +resource "artifactapi_remote_rpm" "epel" { + name = "epel-9" + base_url = "https://download.example.com/pub/epel/9/Everything/x86_64" + mirrorlist = [ + "https://mirror-a.example.net/epel/9/Everything/x86_64", + "https://mirror-b.example.org/epel/9/Everything/x86_64", + ] + mirror_strategy = "least_conn" +} +``` + +## Example + +See `examples/resources/artifactapi_remote_rpm/main.tf`. diff --git a/docs/resources/remote_terraform.md b/docs/resources/remote_terraform.md new file mode 100644 index 0000000..de6bcc3 --- /dev/null +++ b/docs/resources/remote_terraform.md @@ -0,0 +1,36 @@ +# `artifactapi_remote_terraform` + +Per-type remote resources manage upstream repository proxies. Each type applies its own mutability classification rules automatically (e.g., Docker classifies tag manifests as mutable and blobs as immutable; Helm classifies `index.yaml` as mutable). + +## Common Attributes + +| Attribute | Required | Default | Description | +|----------------------|----------|---------|-------------------------------------------------------------------| +| `name` | Yes | | Unique name (forces replacement on change) | +| `base_url` | Yes | | Upstream repository URL | +| `description` | No | `""` | Human-readable description | +| `username` | No | `""` | Upstream auth username (sensitive) | +| `password` | No | `""` | Upstream auth password (sensitive) | +| `immutable_ttl` | No | `0` | TTL in seconds for immutable artifacts (0 = cache forever) | +| `mutable_ttl` | No | `3600` | TTL in seconds for mutable artifacts | +| `check_mutable` | No | `true` | Enable conditional revalidation for mutable artifacts | +| `patterns` | No | | Allowlist of path patterns to proxy (empty = all) | +| `blocklist` | No | | Paths to always deny (checked before patterns) | +| `mutable_patterns` | No | | Override: treat matching paths as mutable | +| `immutable_patterns` | No | | Override: treat matching paths as immutable | +| `quarantine_enabled` | No | `false` | Enable quarantine for new artifacts | +| `quarantine_days` | No | `3` | Days to quarantine new artifacts | +| `stale_on_error` | No | `true` | Serve stale cache when upstream is unreachable | +| `upstream_dial_timeout` | No | `0` | Upstream TCP connect timeout in seconds (0 = server default) | +| `upstream_tls_timeout` | No | `0` | Upstream TLS handshake timeout in seconds (0 = server default) | +| `upstream_response_header_timeout` | No | `0` | Upstream response-header timeout in seconds (0 = server default) | + +## Terraform / github_rpm / github_deb / github_alpine-specific Attributes + +| Attribute | Default | Description | +|-------------------|---------|----------------------------------------------------------| +| `releases_remote` | `""` | Name of a backend remote that serves the download bytes. Terraform uses it for download URL rewriting; `github_rpm`/`github_deb`/`github_alpine` 302-redirect `.rpm`/`.deb`/`.apk` downloads to it. | + +## Example + +See `examples/resources/artifactapi_remote_terraform/main.tf`. diff --git a/docs/resources/virtual.md b/docs/resources/virtual.md new file mode 100644 index 0000000..b2654df --- /dev/null +++ b/docs/resources/virtual.md @@ -0,0 +1,23 @@ +# `artifactapi_virtual` + +Virtual repositories merge multiple remotes of the same package type into a single endpoint. + +```hcl +resource "artifactapi_virtual" "helm" { + name = "helm" + package_type = "helm" + description = "All helm repos merged" + + members = [ + artifactapi_remote_helm.jetstack.name, + artifactapi_remote_helm.hashicorp_helm.name, + ] +} +``` + +| Attribute | Required | Description | +|----------------|----------|-------------------------------------------| +| `name` | Yes | Unique name (forces replacement on change)| +| `package_type` | Yes | Package type of member remotes | +| `description` | No | Human-readable description | +| `members` | Yes | List of remote names to include | diff --git a/internal/provider/datasource_remote.go b/internal/provider/datasource_remote.go index 872a922..d4a8663 100644 --- a/internal/provider/datasource_remote.go +++ b/internal/provider/datasource_remote.go @@ -15,6 +15,8 @@ type remoteDataSource struct { client *apiClient } +func init() { registerDataSource(NewRemoteDataSource) } + func NewRemoteDataSource() datasource.DataSource { return &remoteDataSource{} } diff --git a/internal/provider/datasource_virtual.go b/internal/provider/datasource_virtual.go index 0c6762f..9f47f2a 100644 --- a/internal/provider/datasource_virtual.go +++ b/internal/provider/datasource_virtual.go @@ -15,6 +15,8 @@ type virtualDataSource struct { client *apiClient } +func init() { registerDataSource(NewVirtualDataSource) } + func NewVirtualDataSource() datasource.DataSource { return &virtualDataSource{} } diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 6f3c73e..c2287f3 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -67,39 +67,9 @@ func (p *ArtifactAPIProvider) Configure(ctx context.Context, req provider.Config } func (p *ArtifactAPIProvider) Resources(_ context.Context) []func() resource.Resource { - return []func() resource.Resource{ - newRemoteResource("generic"), - newRemoteResource("docker"), - newRemoteResource("helm"), - newRemoteResource("pypi"), - newRemoteResource("npm"), - newRemoteResource("rpm"), - newRemoteResource("deb"), - newRemoteResource("alpine"), - newRemoteResource("puppet"), - newRemoteResource("terraform"), - newRemoteResource("goproxy"), - newRemoteResource("cargo"), - newRemoteResource("gems"), - newRemoteResource("github_rpm"), - newRemoteResource("github_deb"), - newRemoteResource("github_alpine"), - NewVirtualResource, - NewLocalTerraformResource, - NewLocalPyPIResource, - NewLocalNPMResource, - NewLocalRPMResource, - NewLocalDebResource, - NewLocalAlpineResource, - NewLocalHelmResource, - NewLocalDockerResource, - NewLocalGenericResource, - } + return sortedByTypeName(resourceRegistry, resourceTypeName) } func (p *ArtifactAPIProvider) DataSources(_ context.Context) []func() datasource.DataSource { - return []func() datasource.DataSource{ - NewRemoteDataSource, - NewVirtualDataSource, - } + return sortedByTypeName(dataSourceRegistry, dataSourceTypeName) } diff --git a/internal/provider/provider_test.go b/internal/provider/provider_test.go index f5aa9af..1eba749 100644 --- a/internal/provider/provider_test.go +++ b/internal/provider/provider_test.go @@ -2,11 +2,13 @@ package provider import ( "context" + "os" + "path/filepath" + "slices" + "strings" "testing" - "github.com/hashicorp/terraform-plugin-framework/datasource" fwprovider "github.com/hashicorp/terraform-plugin-framework/provider" - "github.com/hashicorp/terraform-plugin-framework/resource" ) func TestNew_ReturnsFactory(t *testing.T) { @@ -62,114 +64,65 @@ func TestProvider_Schema(t *testing.T) { } } -func TestProvider_Resources(t *testing.T) { +func TestProvider_Resources_Invariants(t *testing.T) { p := &ArtifactAPIProvider{version: "1.0.0"} - resources := p.Resources(context.Background()) - - // 16 remote resource types + 1 virtual + 9 local = 26 - expectedCount := 26 - if len(resources) != expectedCount { - t.Fatalf("expected %d resources, got %d", expectedCount, len(resources)) - } - - // Verify each factory produces a valid resource - for i, factory := range resources { - r := factory() - if r == nil { - t.Errorf("resource factory %d returned nil", i) - } + var names []string + for _, f := range p.Resources(context.Background()) { + names = append(names, resourceTypeName(f)) } + checkRegistered(t, names, "resources") } -func TestProvider_Resources_ContainsExpectedTypes(t *testing.T) { +func TestProvider_DataSources_Invariants(t *testing.T) { p := &ArtifactAPIProvider{version: "1.0.0"} - resources := p.Resources(context.Background()) - - // Collect type names by calling Metadata on each - typeNames := make(map[string]bool) - for _, factory := range resources { - r := factory() - req := resource.MetadataRequest{ProviderTypeName: "artifactapi"} - var resp resource.MetadataResponse - r.Metadata(context.Background(), req, &resp) - typeNames[resp.TypeName] = true - } - - expected := []string{ - "artifactapi_remote_generic", - "artifactapi_remote_docker", - "artifactapi_remote_helm", - "artifactapi_remote_pypi", - "artifactapi_remote_npm", - "artifactapi_remote_rpm", - "artifactapi_remote_deb", - "artifactapi_remote_alpine", - "artifactapi_remote_puppet", - "artifactapi_remote_terraform", - "artifactapi_remote_goproxy", - "artifactapi_remote_cargo", - "artifactapi_remote_gems", - "artifactapi_remote_github_rpm", - "artifactapi_remote_github_deb", - "artifactapi_remote_github_alpine", - "artifactapi_virtual", - "artifactapi_local_terraform", - "artifactapi_local_pypi", - "artifactapi_local_npm", - "artifactapi_local_rpm", - "artifactapi_local_deb", - "artifactapi_local_alpine", - "artifactapi_local_helm", - "artifactapi_local_docker", - "artifactapi_local_generic", - } - - for _, name := range expected { - if !typeNames[name] { - t.Errorf("missing expected resource type: %s", name) - } + var names []string + for _, f := range p.DataSources(context.Background()) { + names = append(names, dataSourceTypeName(f)) } + checkRegistered(t, names, "data-sources") } -func TestProvider_DataSources(t *testing.T) { - p := &ArtifactAPIProvider{version: "1.0.0"} - dataSources := p.DataSources(context.Background()) - - expectedCount := 2 - if len(dataSources) != expectedCount { - t.Fatalf("expected %d data sources, got %d", expectedCount, len(dataSources)) +func checkRegistered(t *testing.T, names []string, kind string) { + t.Helper() + if len(names) == 0 { + t.Fatalf("no %s registered", kind) } - - // Verify each factory produces a valid data source - for i, factory := range dataSources { - ds := factory() - if ds == nil { - t.Errorf("data source factory %d returned nil", i) + if !slices.IsSorted(names) { + t.Errorf("%s not sorted by type name: %v", kind, names) + } + seen := map[string]bool{} + for _, n := range names { + if !strings.HasPrefix(n, "artifactapi_") { + t.Errorf("%s: type name %q lacks artifactapi_ prefix", kind, n) + } + if seen[n] { + t.Errorf("%s: duplicate type name %q", kind, n) + } + seen[n] = true + if _, err := os.Stat(filepath.Join("../../examples", kind, n, "main.tf")); err != nil { + t.Errorf("%s: %s has no example: %v", kind, n, err) + } + if _, err := os.Stat(filepath.Join("../../docs", kind, strings.TrimPrefix(n, "artifactapi_")+".md")); err != nil { + t.Errorf("%s: %s has no docs page: %v", kind, n, err) } } -} -func TestProvider_DataSources_ContainsExpectedTypes(t *testing.T) { - p := &ArtifactAPIProvider{version: "1.0.0"} - dataSources := p.DataSources(context.Background()) - - typeNames := make(map[string]bool) - for _, factory := range dataSources { - ds := factory() - req := datasource.MetadataRequest{ProviderTypeName: "artifactapi"} - var resp datasource.MetadataResponse - ds.Metadata(context.Background(), req, &resp) - typeNames[resp.TypeName] = true + dirs, err := os.ReadDir(filepath.Join("../../examples", kind)) + if err != nil { + t.Fatal(err) } - - expected := []string{ - "artifactapi_remote", - "artifactapi_virtual", + for _, d := range dirs { + if !seen[d.Name()] { + t.Errorf("examples/%s/%s does not match a registered type", kind, d.Name()) + } } - - for _, name := range expected { - if !typeNames[name] { - t.Errorf("missing expected data source type: %s", name) + docs, err := filepath.Glob(filepath.Join("../../docs", kind, "*.md")) + if err != nil { + t.Fatal(err) + } + for _, d := range docs { + if n := "artifactapi_" + strings.TrimSuffix(filepath.Base(d), ".md"); !seen[n] { + t.Errorf("docs/%s/%s does not match a registered type", kind, filepath.Base(d)) } } } diff --git a/internal/provider/registry.go b/internal/provider/registry.go new file mode 100644 index 0000000..9d56130 --- /dev/null +++ b/internal/provider/registry.go @@ -0,0 +1,41 @@ +package provider + +import ( + "context" + "slices" + "strings" + + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/resource" +) + +var ( + resourceRegistry []func() resource.Resource + dataSourceRegistry []func() datasource.DataSource +) + +func registerResource(f func() resource.Resource) { + resourceRegistry = append(resourceRegistry, f) +} + +func registerDataSource(f func() datasource.DataSource) { + dataSourceRegistry = append(dataSourceRegistry, f) +} + +func resourceTypeName(f func() resource.Resource) string { + var resp resource.MetadataResponse + f().Metadata(context.Background(), resource.MetadataRequest{ProviderTypeName: "artifactapi"}, &resp) + return resp.TypeName +} + +func dataSourceTypeName(f func() datasource.DataSource) string { + var resp datasource.MetadataResponse + f().Metadata(context.Background(), datasource.MetadataRequest{ProviderTypeName: "artifactapi"}, &resp) + return resp.TypeName +} + +func sortedByTypeName[T any](fs []T, name func(T) string) []T { + out := slices.Clone(fs) + slices.SortFunc(out, func(a, b T) int { return strings.Compare(name(a), name(b)) }) + return out +} diff --git a/internal/provider/resource_local_alpine.go b/internal/provider/resource_local_alpine.go index 26c1453..cf76990 100644 --- a/internal/provider/resource_local_alpine.go +++ b/internal/provider/resource_local_alpine.go @@ -27,6 +27,8 @@ type localAlpineResourceModel struct { Description types.String `tfsdk:"description"` } +func init() { registerResource(NewLocalAlpineResource) } + func NewLocalAlpineResource() resource.Resource { return &localAlpineResource{} } diff --git a/internal/provider/resource_local_deb.go b/internal/provider/resource_local_deb.go index 445d9fb..c64e997 100644 --- a/internal/provider/resource_local_deb.go +++ b/internal/provider/resource_local_deb.go @@ -27,6 +27,8 @@ type localDebResourceModel struct { Description types.String `tfsdk:"description"` } +func init() { registerResource(NewLocalDebResource) } + func NewLocalDebResource() resource.Resource { return &localDebResource{} } diff --git a/internal/provider/resource_local_docker.go b/internal/provider/resource_local_docker.go index 52b473c..cf43163 100644 --- a/internal/provider/resource_local_docker.go +++ b/internal/provider/resource_local_docker.go @@ -27,6 +27,8 @@ type localDockerResourceModel struct { Description types.String `tfsdk:"description"` } +func init() { registerResource(NewLocalDockerResource) } + func NewLocalDockerResource() resource.Resource { return &localDockerResource{} } diff --git a/internal/provider/resource_local_generic.go b/internal/provider/resource_local_generic.go index f66dbfa..4e86989 100644 --- a/internal/provider/resource_local_generic.go +++ b/internal/provider/resource_local_generic.go @@ -27,6 +27,8 @@ type localGenericResourceModel struct { Description types.String `tfsdk:"description"` } +func init() { registerResource(NewLocalGenericResource) } + func NewLocalGenericResource() resource.Resource { return &localGenericResource{} } diff --git a/internal/provider/resource_local_helm.go b/internal/provider/resource_local_helm.go index 5c0eacf..315a52a 100644 --- a/internal/provider/resource_local_helm.go +++ b/internal/provider/resource_local_helm.go @@ -27,6 +27,8 @@ type localHelmResourceModel struct { Description types.String `tfsdk:"description"` } +func init() { registerResource(NewLocalHelmResource) } + func NewLocalHelmResource() resource.Resource { return &localHelmResource{} } diff --git a/internal/provider/resource_local_npm.go b/internal/provider/resource_local_npm.go index 3d23acb..3f517eb 100644 --- a/internal/provider/resource_local_npm.go +++ b/internal/provider/resource_local_npm.go @@ -27,6 +27,8 @@ type localNPMResourceModel struct { Description types.String `tfsdk:"description"` } +func init() { registerResource(NewLocalNPMResource) } + func NewLocalNPMResource() resource.Resource { return &localNPMResource{} } diff --git a/internal/provider/resource_local_pypi.go b/internal/provider/resource_local_pypi.go index 7109d92..f7518e7 100644 --- a/internal/provider/resource_local_pypi.go +++ b/internal/provider/resource_local_pypi.go @@ -27,6 +27,8 @@ type localPyPIResourceModel struct { Description types.String `tfsdk:"description"` } +func init() { registerResource(NewLocalPyPIResource) } + func NewLocalPyPIResource() resource.Resource { return &localPyPIResource{} } diff --git a/internal/provider/resource_local_rpm.go b/internal/provider/resource_local_rpm.go index bcd8172..725d686 100644 --- a/internal/provider/resource_local_rpm.go +++ b/internal/provider/resource_local_rpm.go @@ -27,6 +27,8 @@ type localRPMResourceModel struct { Description types.String `tfsdk:"description"` } +func init() { registerResource(NewLocalRPMResource) } + func NewLocalRPMResource() resource.Resource { return &localRPMResource{} } diff --git a/internal/provider/resource_local_terraform.go b/internal/provider/resource_local_terraform.go index dac2d6b..a43b4da 100644 --- a/internal/provider/resource_local_terraform.go +++ b/internal/provider/resource_local_terraform.go @@ -27,6 +27,8 @@ type localTerraformResourceModel struct { Description types.String `tfsdk:"description"` } +func init() { registerResource(NewLocalTerraformResource) } + func NewLocalTerraformResource() resource.Resource { return &localTerraformResource{} } diff --git a/internal/provider/resource_remote.go b/internal/provider/resource_remote.go index 0476854..bd4807b 100644 --- a/internal/provider/resource_remote.go +++ b/internal/provider/resource_remote.go @@ -71,25 +71,6 @@ func newRemoteResource(packageType string) func() resource.Resource { } } -func NewRemoteGeneric() resource.Resource { return &remoteResource{packageType: "generic"} } -func NewRemoteDocker() resource.Resource { return &remoteResource{packageType: "docker"} } -func NewRemoteHelm() resource.Resource { return &remoteResource{packageType: "helm"} } -func NewRemotePyPI() resource.Resource { return &remoteResource{packageType: "pypi"} } -func NewRemoteNPM() resource.Resource { return &remoteResource{packageType: "npm"} } -func NewRemoteRPM() resource.Resource { return &remoteResource{packageType: "rpm"} } -func NewRemoteDeb() resource.Resource { return &remoteResource{packageType: "deb"} } -func NewRemoteAlpine() resource.Resource { return &remoteResource{packageType: "alpine"} } -func NewRemotePuppet() resource.Resource { return &remoteResource{packageType: "puppet"} } -func NewRemoteTerraform() resource.Resource { return &remoteResource{packageType: "terraform"} } -func NewRemoteGoProxy() resource.Resource { return &remoteResource{packageType: "goproxy"} } -func NewRemoteCargo() resource.Resource { return &remoteResource{packageType: "cargo"} } -func NewRemoteGems() resource.Resource { return &remoteResource{packageType: "gems"} } -func NewRemoteGitHubRPM() resource.Resource { return &remoteResource{packageType: "github_rpm"} } -func NewRemoteGitHubDeb() resource.Resource { return &remoteResource{packageType: "github_deb"} } -func NewRemoteGitHubAlpine() resource.Resource { - return &remoteResource{packageType: "github_alpine"} -} - func (r *remoteResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { resp.TypeName = req.ProviderTypeName + "_remote_" + r.packageType } diff --git a/internal/provider/resource_remote_alpine.go b/internal/provider/resource_remote_alpine.go new file mode 100644 index 0000000..d79e438 --- /dev/null +++ b/internal/provider/resource_remote_alpine.go @@ -0,0 +1,3 @@ +package provider + +func init() { registerResource(newRemoteResource("alpine")) } diff --git a/internal/provider/resource_remote_cargo.go b/internal/provider/resource_remote_cargo.go new file mode 100644 index 0000000..d4ff785 --- /dev/null +++ b/internal/provider/resource_remote_cargo.go @@ -0,0 +1,3 @@ +package provider + +func init() { registerResource(newRemoteResource("cargo")) } diff --git a/internal/provider/resource_remote_deb.go b/internal/provider/resource_remote_deb.go new file mode 100644 index 0000000..1c7880f --- /dev/null +++ b/internal/provider/resource_remote_deb.go @@ -0,0 +1,3 @@ +package provider + +func init() { registerResource(newRemoteResource("deb")) } diff --git a/internal/provider/resource_remote_docker.go b/internal/provider/resource_remote_docker.go new file mode 100644 index 0000000..a65448e --- /dev/null +++ b/internal/provider/resource_remote_docker.go @@ -0,0 +1,3 @@ +package provider + +func init() { registerResource(newRemoteResource("docker")) } diff --git a/internal/provider/resource_remote_gems.go b/internal/provider/resource_remote_gems.go new file mode 100644 index 0000000..86c8fae --- /dev/null +++ b/internal/provider/resource_remote_gems.go @@ -0,0 +1,3 @@ +package provider + +func init() { registerResource(newRemoteResource("gems")) } diff --git a/internal/provider/resource_remote_generic.go b/internal/provider/resource_remote_generic.go new file mode 100644 index 0000000..815086c --- /dev/null +++ b/internal/provider/resource_remote_generic.go @@ -0,0 +1,3 @@ +package provider + +func init() { registerResource(newRemoteResource("generic")) } diff --git a/internal/provider/resource_remote_github_alpine.go b/internal/provider/resource_remote_github_alpine.go new file mode 100644 index 0000000..c9ce2e2 --- /dev/null +++ b/internal/provider/resource_remote_github_alpine.go @@ -0,0 +1,3 @@ +package provider + +func init() { registerResource(newRemoteResource("github_alpine")) } diff --git a/internal/provider/resource_remote_github_deb.go b/internal/provider/resource_remote_github_deb.go new file mode 100644 index 0000000..1a3f729 --- /dev/null +++ b/internal/provider/resource_remote_github_deb.go @@ -0,0 +1,3 @@ +package provider + +func init() { registerResource(newRemoteResource("github_deb")) } diff --git a/internal/provider/resource_remote_github_rpm.go b/internal/provider/resource_remote_github_rpm.go new file mode 100644 index 0000000..1e8d56c --- /dev/null +++ b/internal/provider/resource_remote_github_rpm.go @@ -0,0 +1,3 @@ +package provider + +func init() { registerResource(newRemoteResource("github_rpm")) } diff --git a/internal/provider/resource_remote_goproxy.go b/internal/provider/resource_remote_goproxy.go new file mode 100644 index 0000000..0b36fde --- /dev/null +++ b/internal/provider/resource_remote_goproxy.go @@ -0,0 +1,3 @@ +package provider + +func init() { registerResource(newRemoteResource("goproxy")) } diff --git a/internal/provider/resource_remote_helm.go b/internal/provider/resource_remote_helm.go new file mode 100644 index 0000000..fa62e11 --- /dev/null +++ b/internal/provider/resource_remote_helm.go @@ -0,0 +1,3 @@ +package provider + +func init() { registerResource(newRemoteResource("helm")) } diff --git a/internal/provider/resource_remote_npm.go b/internal/provider/resource_remote_npm.go new file mode 100644 index 0000000..dfeeebf --- /dev/null +++ b/internal/provider/resource_remote_npm.go @@ -0,0 +1,3 @@ +package provider + +func init() { registerResource(newRemoteResource("npm")) } diff --git a/internal/provider/resource_remote_puppet.go b/internal/provider/resource_remote_puppet.go new file mode 100644 index 0000000..fb18bd8 --- /dev/null +++ b/internal/provider/resource_remote_puppet.go @@ -0,0 +1,3 @@ +package provider + +func init() { registerResource(newRemoteResource("puppet")) } diff --git a/internal/provider/resource_remote_pypi.go b/internal/provider/resource_remote_pypi.go new file mode 100644 index 0000000..c27e63d --- /dev/null +++ b/internal/provider/resource_remote_pypi.go @@ -0,0 +1,3 @@ +package provider + +func init() { registerResource(newRemoteResource("pypi")) } diff --git a/internal/provider/resource_remote_rpm.go b/internal/provider/resource_remote_rpm.go new file mode 100644 index 0000000..29df51a --- /dev/null +++ b/internal/provider/resource_remote_rpm.go @@ -0,0 +1,3 @@ +package provider + +func init() { registerResource(newRemoteResource("rpm")) } diff --git a/internal/provider/resource_remote_terraform.go b/internal/provider/resource_remote_terraform.go new file mode 100644 index 0000000..fdc9b5c --- /dev/null +++ b/internal/provider/resource_remote_terraform.go @@ -0,0 +1,3 @@ +package provider + +func init() { registerResource(newRemoteResource("terraform")) } diff --git a/internal/provider/resource_remote_test.go b/internal/provider/resource_remote_test.go index 2b4a6d0..3eb23b4 100644 --- a/internal/provider/resource_remote_test.go +++ b/internal/provider/resource_remote_test.go @@ -150,26 +150,9 @@ func TestModelToAPI_NullLists(t *testing.T) { func TestModelToAPI_PackageTypeFromResource(t *testing.T) { ctx := context.Background() - tests := []struct { - pkgType string - }{ - {"generic"}, - {"docker"}, - {"helm"}, - {"pypi"}, - {"npm"}, - {"rpm"}, - {"alpine"}, - {"puppet"}, - {"terraform"}, - {"goproxy"}, - {"cargo"}, - {"gems"}, - } - - for _, tt := range tests { - t.Run(tt.pkgType, func(t *testing.T) { - r := &remoteResource{packageType: tt.pkgType} + for _, pkgType := range registeredRemoteKinds(t) { + t.Run(pkgType, func(t *testing.T) { + r := &remoteResource{packageType: pkgType} model := remoteResourceModel{ Name: types.StringValue("test"), BaseURL: types.StringValue("https://example.com"), @@ -191,8 +174,8 @@ func TestModelToAPI_PackageTypeFromResource(t *testing.T) { ReleasesRemote: types.StringValue(""), } api := r.modelToAPI(ctx, model) - if api.PackageType != tt.pkgType { - t.Errorf("expected package_type %s, got %s", tt.pkgType, api.PackageType) + if api.PackageType != pkgType { + t.Errorf("expected package_type %s, got %s", pkgType, api.PackageType) } }) } @@ -404,33 +387,33 @@ func TestModelToAPI_RoundTrip(t *testing.T) { } func TestRemoteResource_Metadata(t *testing.T) { - tests := []struct { - pkgType string - expected string - }{ - {"generic", "artifactapi_remote_generic"}, - {"docker", "artifactapi_remote_docker"}, - {"helm", "artifactapi_remote_helm"}, - {"pypi", "artifactapi_remote_pypi"}, - {"npm", "artifactapi_remote_npm"}, - {"github_rpm", "artifactapi_remote_github_rpm"}, - {"github_deb", "artifactapi_remote_github_deb"}, - {"github_alpine", "artifactapi_remote_github_alpine"}, - } - - for _, tt := range tests { - t.Run(tt.pkgType, func(t *testing.T) { - r := &remoteResource{packageType: tt.pkgType} + for _, pkgType := range registeredRemoteKinds(t) { + t.Run(pkgType, func(t *testing.T) { + r := &remoteResource{packageType: pkgType} req := resource.MetadataRequest{ProviderTypeName: "artifactapi"} var resp resource.MetadataResponse r.Metadata(context.Background(), req, &resp) - if resp.TypeName != tt.expected { - t.Errorf("expected %s, got %s", tt.expected, resp.TypeName) + if expected := "artifactapi_remote_" + pkgType; resp.TypeName != expected { + t.Errorf("expected %s, got %s", expected, resp.TypeName) } }) } } +func registeredRemoteKinds(t *testing.T) []string { + t.Helper() + var kinds []string + for _, f := range resourceRegistry { + if rr, ok := f().(*remoteResource); ok { + kinds = append(kinds, rr.packageType) + } + } + if len(kinds) == 0 { + t.Fatal("no remote kinds registered") + } + return kinds +} + func TestModelToAPI_GitHubRPM(t *testing.T) { ctx := context.Background() r := &remoteResource{packageType: "github_rpm"} @@ -510,43 +493,6 @@ func TestRemoteResource_Schema(t *testing.T) { } } -func TestNewRemoteResource_Constructors(t *testing.T) { - tests := []struct { - name string - fn func() resource.Resource - expected string - }{ - {"generic", func() resource.Resource { return NewRemoteGeneric() }, "generic"}, - {"docker", func() resource.Resource { return NewRemoteDocker() }, "docker"}, - {"helm", func() resource.Resource { return NewRemoteHelm() }, "helm"}, - {"pypi", func() resource.Resource { return NewRemotePyPI() }, "pypi"}, - {"npm", func() resource.Resource { return NewRemoteNPM() }, "npm"}, - {"rpm", func() resource.Resource { return NewRemoteRPM() }, "rpm"}, - {"alpine", func() resource.Resource { return NewRemoteAlpine() }, "alpine"}, - {"puppet", func() resource.Resource { return NewRemotePuppet() }, "puppet"}, - {"terraform", func() resource.Resource { return NewRemoteTerraform() }, "terraform"}, - {"goproxy", func() resource.Resource { return NewRemoteGoProxy() }, "goproxy"}, - {"cargo", func() resource.Resource { return NewRemoteCargo() }, "cargo"}, - {"gems", func() resource.Resource { return NewRemoteGems() }, "gems"}, - {"github_rpm", func() resource.Resource { return NewRemoteGitHubRPM() }, "github_rpm"}, - {"github_deb", func() resource.Resource { return NewRemoteGitHubDeb() }, "github_deb"}, - {"github_alpine", func() resource.Resource { return NewRemoteGitHubAlpine() }, "github_alpine"}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - r := tt.fn() - rr, ok := r.(*remoteResource) - if !ok { - t.Fatal("expected *remoteResource") - } - if rr.packageType != tt.expected { - t.Errorf("expected packageType %s, got %s", tt.expected, rr.packageType) - } - }) - } -} - func TestModelToAPI_Mirrorlist(t *testing.T) { ctx := context.Background() mirrors := []string{"https://mirror-a.example.com/rpm", "https://mirror-b.example.com/rpm"} diff --git a/internal/provider/resource_virtual.go b/internal/provider/resource_virtual.go index 92a11f2..c973764 100644 --- a/internal/provider/resource_virtual.go +++ b/internal/provider/resource_virtual.go @@ -29,6 +29,8 @@ type virtualResourceModel struct { Members types.List `tfsdk:"members"` } +func init() { registerResource(NewVirtualResource) } + func NewVirtualResource() resource.Resource { return &virtualResource{} } -- 2.47.3