merge main
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful

This commit is contained in:
2026-10-18 20:51:46 +11:00
97 changed files with 1981 additions and 459 deletions
+7 -213
View File
@@ -23,13 +23,15 @@ make install
This places the binary at `~/.terraform.d/plugins/git.unkin.net/unkin/artifactapi/<version>/<os_arch>/`.
A `make install` build is referenced with `source = "git.unkin.net/unkin/artifactapi"` (the provider address, matching that path); the `artifactapi.example.com/terraform-unkin/artifactapi` source below is for installing from an artifactapi registry.
## Provider Configuration
```hcl
terraform {
required_providers {
artifactapi = {
source = "git.unkin.net/unkin/artifactapi"
source = "artifactapi.example.com/terraform-unkin/artifactapi"
version = "0.0.1"
}
}
@@ -46,219 +48,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_terraform`
- `artifactapi_local_goproxy` — a Go module proxy (GOPROXY protocol) for private modules
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 `<endpoint>/<name>/<image>:<tag>`, 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
+9
View File
@@ -0,0 +1,9 @@
# `artifactapi_remote` (Data Source)
Read an existing remote's configuration.
```hcl
data "artifactapi_remote" "dockerhub" {
name = "dockerhub"
}
```
+9
View File
@@ -0,0 +1,9 @@
# `artifactapi_virtual` (Data Source)
Read an existing virtual repository's configuration.
```hcl
data "artifactapi_virtual" "helm" {
name = "helm"
}
```
+11
View File
@@ -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`.
+13
View File
@@ -0,0 +1,13 @@
# `artifactapi_local_ansible`
Local resources manage repositories that ArtifactAPI hosts directly (rather than
proxying an upstream) — each is a real registry for its package type.
A Galaxy v3 server for collections (`ansible-galaxy collection publish` and `install`).
Each takes just `name` (required, forces replacement) and an optional
`description`.
## Example
See `examples/resources/artifactapi_local_ansible/main.tf`.
+11
View File
@@ -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`.
+21
View File
@@ -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 `<endpoint>/<name>/<image>:<tag>`, e.g.
`docker push artifactapi.example.com/docker-internal/myapp:latest`.
+13
View File
@@ -0,0 +1,13 @@
# `artifactapi_local_gems`
Local resources manage repositories that ArtifactAPI hosts directly (rather than
proxying an upstream) — each is a real registry for its package type.
A RubyGems source (`gem push`, `gem yank`, `gem install` and `bundle install`).
Each takes just `name` (required, forces replacement) and an optional
`description`.
## Example
See `examples/resources/artifactapi_local_gems/main.tf`.
+11
View File
@@ -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`.
+13
View File
@@ -0,0 +1,13 @@
# `artifactapi_local_goproxy`
Local resources manage repositories that ArtifactAPI hosts directly (rather than
proxying an upstream) — each is a real registry for its package type.
A Go module proxy for private modules (GOPROXY protocol: `go get`, `go mod download`).
Each takes just `name` (required, forces replacement) and an optional
`description`.
## Example
See `examples/resources/artifactapi_local_goproxy/main.tf`.
+13
View File
@@ -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`.
+13
View File
@@ -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`.
+11
View File
@@ -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`.
+11
View File
@@ -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`.
+11
View File
@@ -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`.
+53
View File
@@ -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`.
+30
View File
@@ -0,0 +1,30 @@
# `artifactapi_remote_ansible`
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_ansible/main.tf`.
+30
View File
@@ -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`.
+53
View File
@@ -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`.
+52
View File
@@ -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",
]
}
```
+30
View File
@@ -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`.
+30
View File
@@ -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`.
+49
View File
@@ -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`.
+49
View File
@@ -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`.
+46
View File
@@ -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`.
+30
View File
@@ -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`.
+30
View File
@@ -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`.
+30
View File
@@ -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`.
+30
View File
@@ -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`.
+30
View File
@@ -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`.
+53
View File
@@ -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`.
+36
View File
@@ -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`.
+23
View File
@@ -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 |
@@ -1,7 +1,7 @@
terraform {
required_providers {
artifactapi = {
source = "git.unkin.net/unkin/artifactapi"
source = "artifactapi.example.com/terraform-unkin/artifactapi"
version = "0.0.1"
}
}
@@ -1,7 +1,7 @@
terraform {
required_providers {
artifactapi = {
source = "git.unkin.net/unkin/artifactapi"
source = "artifactapi.example.com/terraform-unkin/artifactapi"
version = "0.0.1"
}
}
+2 -2
View File
@@ -1,14 +1,14 @@
terraform {
required_providers {
artifactapi = {
source = "git.unkin.net/unkin/artifactapi"
source = "artifactapi.example.com/terraform-unkin/artifactapi"
version = "0.0.1"
}
}
}
provider "artifactapi" {
endpoint = "https://artifactapi.k8s.syd1.au.unkin.net"
endpoint = "https://artifactapi.example.com"
# token is read from ARTIFACTAPI_TOKEN when unset
}
@@ -0,0 +1,4 @@
resource "artifactapi_local_ansible" "internal" {
name = "ansible-internal"
description = "Internal Ansible Galaxy collections"
}
@@ -0,0 +1,4 @@
resource "artifactapi_local_gems" "internal" {
name = "gems-internal"
description = "Internal RubyGems repository"
}
@@ -0,0 +1,4 @@
resource "artifactapi_local_helm" "internal" {
name = "helm-internal"
description = "Internal Helm chart repository"
}
@@ -1,7 +1,7 @@
terraform {
required_providers {
artifactapi = {
source = "git.unkin.net/unkin/artifactapi"
source = "artifactapi.example.com/terraform-unkin/artifactapi"
version = "0.0.1"
}
}
@@ -0,0 +1,23 @@
terraform {
required_providers {
artifactapi = {
source = "artifactapi.example.com/terraform-unkin/artifactapi"
version = "0.0.1"
}
}
}
provider "artifactapi" {
endpoint = "https://artifactapi.example.com"
}
# Ansible remote proxies the Galaxy v3 collections API.
# The provider knows JSON metadata is mutable; collection tarballs are immutable.
resource "artifactapi_remote_ansible" "galaxy" {
name = "galaxy"
base_url = "https://galaxy.ansible.com"
description = "Ansible Galaxy"
immutable_ttl = 0
mutable_ttl = 300
}
@@ -1,7 +1,7 @@
terraform {
required_providers {
artifactapi = {
source = "git.unkin.net/unkin/artifactapi"
source = "artifactapi.example.com/terraform-unkin/artifactapi"
version = "0.0.1"
}
}
@@ -1,7 +1,7 @@
terraform {
required_providers {
artifactapi = {
source = "git.unkin.net/unkin/artifactapi"
source = "artifactapi.example.com/terraform-unkin/artifactapi"
version = "0.0.1"
}
}
@@ -1,7 +1,7 @@
terraform {
required_providers {
artifactapi = {
source = "git.unkin.net/unkin/artifactapi"
source = "artifactapi.example.com/terraform-unkin/artifactapi"
version = "0.0.1"
}
}
@@ -1,7 +1,7 @@
terraform {
required_providers {
artifactapi = {
source = "git.unkin.net/unkin/artifactapi"
source = "artifactapi.example.com/terraform-unkin/artifactapi"
version = "0.0.1"
}
}
@@ -1,7 +1,7 @@
terraform {
required_providers {
artifactapi = {
source = "git.unkin.net/unkin/artifactapi"
source = "artifactapi.example.com/terraform-unkin/artifactapi"
version = "0.0.1"
}
}
@@ -1,7 +1,7 @@
terraform {
required_providers {
artifactapi = {
source = "git.unkin.net/unkin/artifactapi"
source = "artifactapi.example.com/terraform-unkin/artifactapi"
version = "0.0.1"
}
}
@@ -1,7 +1,7 @@
terraform {
required_providers {
artifactapi = {
source = "git.unkin.net/unkin/artifactapi"
source = "artifactapi.example.com/terraform-unkin/artifactapi"
version = "0.0.1"
}
}
@@ -1,7 +1,7 @@
terraform {
required_providers {
artifactapi = {
source = "git.unkin.net/unkin/artifactapi"
source = "artifactapi.example.com/terraform-unkin/artifactapi"
version = "0.0.1"
}
}
@@ -1,7 +1,7 @@
terraform {
required_providers {
artifactapi = {
source = "git.unkin.net/unkin/artifactapi"
source = "artifactapi.example.com/terraform-unkin/artifactapi"
version = "0.0.1"
}
}
@@ -1,7 +1,7 @@
terraform {
required_providers {
artifactapi = {
source = "git.unkin.net/unkin/artifactapi"
source = "artifactapi.example.com/terraform-unkin/artifactapi"
version = "0.0.1"
}
}
@@ -1,7 +1,7 @@
terraform {
required_providers {
artifactapi = {
source = "git.unkin.net/unkin/artifactapi"
source = "artifactapi.example.com/terraform-unkin/artifactapi"
version = "0.0.1"
}
}
@@ -1,7 +1,7 @@
terraform {
required_providers {
artifactapi = {
source = "git.unkin.net/unkin/artifactapi"
source = "artifactapi.example.com/terraform-unkin/artifactapi"
version = "0.0.1"
}
}
@@ -1,7 +1,7 @@
terraform {
required_providers {
artifactapi = {
source = "git.unkin.net/unkin/artifactapi"
source = "artifactapi.example.com/terraform-unkin/artifactapi"
version = "0.0.1"
}
}
@@ -1,7 +1,7 @@
terraform {
required_providers {
artifactapi = {
source = "git.unkin.net/unkin/artifactapi"
source = "artifactapi.example.com/terraform-unkin/artifactapi"
version = "0.0.1"
}
}
@@ -1,7 +1,7 @@
terraform {
required_providers {
artifactapi = {
source = "git.unkin.net/unkin/artifactapi"
source = "artifactapi.example.com/terraform-unkin/artifactapi"
version = "0.0.1"
}
}
@@ -1,7 +1,7 @@
terraform {
required_providers {
artifactapi = {
source = "git.unkin.net/unkin/artifactapi"
source = "artifactapi.example.com/terraform-unkin/artifactapi"
version = "0.0.1"
}
}
+2
View File
@@ -15,6 +15,8 @@ type remoteDataSource struct {
client *apiClient
}
func init() { registerDataSource(NewRemoteDataSource) }
func NewRemoteDataSource() datasource.DataSource {
return &remoteDataSource{}
}
+2
View File
@@ -15,6 +15,8 @@ type virtualDataSource struct {
client *apiClient
}
func init() { registerDataSource(NewVirtualDataSource) }
func NewVirtualDataSource() datasource.DataSource {
return &virtualDataSource{}
}
+2 -32
View File
@@ -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,
NewLocalDockerResource,
NewLocalGenericResource,
NewLocalGoProxyResource,
}
return sortedByTypeName(resourceRegistry, resourceTypeName)
}
func (p *ArtifactAPIProvider) DataSources(_ context.Context) []func() datasource.DataSource {
return []func() datasource.DataSource{
NewRemoteDataSource,
NewVirtualDataSource,
}
return sortedByTypeName(dataSourceRegistry, dataSourceTypeName)
}
+49 -96
View File
@@ -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_docker",
"artifactapi_local_generic",
"artifactapi_local_goproxy",
}
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))
}
}
}
+41
View File
@@ -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
}
@@ -27,6 +27,8 @@ type localAlpineResourceModel struct {
Description types.String `tfsdk:"description"`
}
func init() { registerResource(NewLocalAlpineResource) }
func NewLocalAlpineResource() resource.Resource {
return &localAlpineResource{}
}
+166
View File
@@ -0,0 +1,166 @@
package provider
import (
"context"
"fmt"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/types"
)
var (
_ resource.Resource = &localAnsibleResource{}
_ resource.ResourceWithImportState = &localAnsibleResource{}
)
type localAnsibleResource struct {
client *apiClient
}
type localAnsibleResourceModel struct {
Name types.String `tfsdk:"name"`
Description types.String `tfsdk:"description"`
}
func init() { registerResource(NewLocalAnsibleResource) }
func NewLocalAnsibleResource() resource.Resource {
return &localAnsibleResource{}
}
func (r *localAnsibleResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_local_ansible"
}
func (r *localAnsibleResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
Description: "Manages a local ArtifactAPI Ansible Galaxy repository for hosting collections directly.",
Attributes: map[string]schema.Attribute{
"name": schema.StringAttribute{
Description: "Unique name of the local ansible repository.",
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
"description": schema.StringAttribute{
Description: "Human-readable description.",
Optional: true,
Computed: true,
Default: stringdefault.StaticString(""),
},
},
}
}
func (r *localAnsibleResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
if req.ProviderData == nil {
return
}
client, ok := req.ProviderData.(*apiClient)
if !ok {
resp.Diagnostics.AddError("unexpected provider data type", fmt.Sprintf("got %T", req.ProviderData))
return
}
r.client = client
}
func (r *localAnsibleResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
var plan localAnsibleResourceModel
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
if resp.Diagnostics.HasError() {
return
}
api := localAnsibleModelToAPI(plan)
api.ManagedBy = "terraform"
var created remoteAPI
if err := r.client.post(ctx, "/api/v2/remotes", api, &created); err != nil {
resp.Diagnostics.AddError("create local ansible failed", err.Error())
return
}
state := localAnsibleAPIToModel(created)
resp.Diagnostics.Append(resp.State.Set(ctx, state)...)
}
func (r *localAnsibleResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
var state localAnsibleResourceModel
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}
var remote remoteAPI
err := r.client.get(ctx, "/api/v2/remotes/"+state.Name.ValueString(), &remote)
if err != nil {
if isNotFound(err) {
resp.State.RemoveResource(ctx)
return
}
resp.Diagnostics.AddError("read local ansible failed", err.Error())
return
}
newState := localAnsibleAPIToModel(remote)
resp.Diagnostics.Append(resp.State.Set(ctx, newState)...)
}
func (r *localAnsibleResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
var plan localAnsibleResourceModel
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
if resp.Diagnostics.HasError() {
return
}
api := localAnsibleModelToAPI(plan)
api.ManagedBy = "terraform"
var updated remoteAPI
if err := r.client.put(ctx, "/api/v2/remotes/"+plan.Name.ValueString(), api, &updated); err != nil {
resp.Diagnostics.AddError("update local ansible failed", err.Error())
return
}
state := localAnsibleAPIToModel(updated)
resp.Diagnostics.Append(resp.State.Set(ctx, state)...)
}
func (r *localAnsibleResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
var state localAnsibleResourceModel
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}
if err := r.client.del(ctx, "/api/v2/remotes/"+state.Name.ValueString()); err != nil {
resp.Diagnostics.AddError("delete local ansible failed", err.Error())
return
}
}
func (r *localAnsibleResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
resource.ImportStatePassthroughID(ctx, path.Root("name"), req, resp)
}
func localAnsibleModelToAPI(m localAnsibleResourceModel) remoteAPI {
return remoteAPI{
Name: m.Name.ValueString(),
PackageType: "ansible",
RepoType: "local",
Description: m.Description.ValueString(),
}
}
func localAnsibleAPIToModel(api remoteAPI) localAnsibleResourceModel {
return localAnsibleResourceModel{
Name: types.StringValue(api.Name),
Description: types.StringValue(api.Description),
}
}
@@ -0,0 +1,125 @@
package provider
import (
"context"
"testing"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/types"
)
func TestLocalAnsibleModelToAPI(t *testing.T) {
model := localAnsibleResourceModel{
Name: types.StringValue("ansible-internal"),
Description: types.StringValue("Internal ansible repository"),
}
api := localAnsibleModelToAPI(model)
if api.Name != "ansible-internal" {
t.Errorf("Name: expected ansible-internal, got %s", api.Name)
}
if api.PackageType != "ansible" {
t.Errorf("PackageType: expected ansible, got %s", api.PackageType)
}
if api.RepoType != "local" {
t.Errorf("RepoType: expected local, got %s", api.RepoType)
}
if api.Description != "Internal ansible repository" {
t.Errorf("Description: expected 'Internal ansible repository', got %s", api.Description)
}
}
func TestLocalAnsibleModelToAPI_EmptyDescription(t *testing.T) {
model := localAnsibleResourceModel{
Name: types.StringValue("ansible-empty"),
Description: types.StringValue(""),
}
api := localAnsibleModelToAPI(model)
if api.Name != "ansible-empty" {
t.Errorf("Name: expected ansible-empty, got %s", api.Name)
}
if api.Description != "" {
t.Errorf("Description: expected empty string, got %s", api.Description)
}
if api.PackageType != "ansible" {
t.Errorf("PackageType: expected ansible, got %s", api.PackageType)
}
if api.RepoType != "local" {
t.Errorf("RepoType: expected local, got %s", api.RepoType)
}
}
func TestLocalAnsibleAPIToModel(t *testing.T) {
api := remoteAPI{
Name: "ansible-internal",
PackageType: "ansible",
RepoType: "local",
Description: "Internal ansible repository",
ManagedBy: "terraform",
}
model := localAnsibleAPIToModel(api)
if model.Name.ValueString() != "ansible-internal" {
t.Errorf("Name: expected ansible-internal, got %s", model.Name.ValueString())
}
if model.Description.ValueString() != "Internal ansible repository" {
t.Errorf("Description: expected 'Internal ansible repository', got %s", model.Description.ValueString())
}
}
func TestLocalAnsibleRoundTrip(t *testing.T) {
original := localAnsibleResourceModel{
Name: types.StringValue("roundtrip-ansible"),
Description: types.StringValue("Round trip test"),
}
api := localAnsibleModelToAPI(original)
result := localAnsibleAPIToModel(api)
if result.Name.ValueString() != original.Name.ValueString() {
t.Errorf("Name: expected %s, got %s", original.Name.ValueString(), result.Name.ValueString())
}
if result.Description.ValueString() != original.Description.ValueString() {
t.Errorf("Description: expected %s, got %s", original.Description.ValueString(), result.Description.ValueString())
}
}
func TestLocalAnsibleResource_Metadata(t *testing.T) {
r := NewLocalAnsibleResource()
req := resource.MetadataRequest{ProviderTypeName: "artifactapi"}
var resp resource.MetadataResponse
r.Metadata(context.Background(), req, &resp)
if resp.TypeName != "artifactapi_local_ansible" {
t.Errorf("expected artifactapi_local_ansible, got %s", resp.TypeName)
}
}
func TestLocalAnsibleResource_Schema(t *testing.T) {
r := NewLocalAnsibleResource()
req := resource.SchemaRequest{}
var resp resource.SchemaResponse
r.Schema(context.Background(), req, &resp)
expectedAttrs := []string{"name", "description"}
for _, attr := range expectedAttrs {
if _, ok := resp.Schema.Attributes[attr]; !ok {
t.Errorf("missing expected attribute: %s", attr)
}
}
if len(resp.Schema.Attributes) != len(expectedAttrs) {
t.Errorf("expected %d attributes, got %d", len(expectedAttrs), len(resp.Schema.Attributes))
}
}
func TestNewLocalAnsibleResource_Type(t *testing.T) {
r := NewLocalAnsibleResource()
_, ok := r.(*localAnsibleResource)
if !ok {
t.Error("expected *localAnsibleResource")
}
}
+2
View File
@@ -27,6 +27,8 @@ type localDebResourceModel struct {
Description types.String `tfsdk:"description"`
}
func init() { registerResource(NewLocalDebResource) }
func NewLocalDebResource() resource.Resource {
return &localDebResource{}
}
@@ -27,6 +27,8 @@ type localDockerResourceModel struct {
Description types.String `tfsdk:"description"`
}
func init() { registerResource(NewLocalDockerResource) }
func NewLocalDockerResource() resource.Resource {
return &localDockerResource{}
}
+166
View File
@@ -0,0 +1,166 @@
package provider
import (
"context"
"fmt"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/types"
)
var (
_ resource.Resource = &localGemsResource{}
_ resource.ResourceWithImportState = &localGemsResource{}
)
type localGemsResource struct {
client *apiClient
}
type localGemsResourceModel struct {
Name types.String `tfsdk:"name"`
Description types.String `tfsdk:"description"`
}
func init() { registerResource(NewLocalGemsResource) }
func NewLocalGemsResource() resource.Resource {
return &localGemsResource{}
}
func (r *localGemsResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_local_gems"
}
func (r *localGemsResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
Description: "Manages a local ArtifactAPI RubyGems repository for hosting gems directly.",
Attributes: map[string]schema.Attribute{
"name": schema.StringAttribute{
Description: "Unique name of the local gems repository.",
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
"description": schema.StringAttribute{
Description: "Human-readable description.",
Optional: true,
Computed: true,
Default: stringdefault.StaticString(""),
},
},
}
}
func (r *localGemsResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
if req.ProviderData == nil {
return
}
client, ok := req.ProviderData.(*apiClient)
if !ok {
resp.Diagnostics.AddError("unexpected provider data type", fmt.Sprintf("got %T", req.ProviderData))
return
}
r.client = client
}
func (r *localGemsResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
var plan localGemsResourceModel
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
if resp.Diagnostics.HasError() {
return
}
api := localGemsModelToAPI(plan)
api.ManagedBy = "terraform"
var created remoteAPI
if err := r.client.post(ctx, "/api/v2/remotes", api, &created); err != nil {
resp.Diagnostics.AddError("create local gems failed", err.Error())
return
}
state := localGemsAPIToModel(created)
resp.Diagnostics.Append(resp.State.Set(ctx, state)...)
}
func (r *localGemsResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
var state localGemsResourceModel
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}
var remote remoteAPI
err := r.client.get(ctx, "/api/v2/remotes/"+state.Name.ValueString(), &remote)
if err != nil {
if isNotFound(err) {
resp.State.RemoveResource(ctx)
return
}
resp.Diagnostics.AddError("read local gems failed", err.Error())
return
}
newState := localGemsAPIToModel(remote)
resp.Diagnostics.Append(resp.State.Set(ctx, newState)...)
}
func (r *localGemsResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
var plan localGemsResourceModel
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
if resp.Diagnostics.HasError() {
return
}
api := localGemsModelToAPI(plan)
api.ManagedBy = "terraform"
var updated remoteAPI
if err := r.client.put(ctx, "/api/v2/remotes/"+plan.Name.ValueString(), api, &updated); err != nil {
resp.Diagnostics.AddError("update local gems failed", err.Error())
return
}
state := localGemsAPIToModel(updated)
resp.Diagnostics.Append(resp.State.Set(ctx, state)...)
}
func (r *localGemsResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
var state localGemsResourceModel
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}
if err := r.client.del(ctx, "/api/v2/remotes/"+state.Name.ValueString()); err != nil {
resp.Diagnostics.AddError("delete local gems failed", err.Error())
return
}
}
func (r *localGemsResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
resource.ImportStatePassthroughID(ctx, path.Root("name"), req, resp)
}
func localGemsModelToAPI(m localGemsResourceModel) remoteAPI {
return remoteAPI{
Name: m.Name.ValueString(),
PackageType: "gems",
RepoType: "local",
Description: m.Description.ValueString(),
}
}
func localGemsAPIToModel(api remoteAPI) localGemsResourceModel {
return localGemsResourceModel{
Name: types.StringValue(api.Name),
Description: types.StringValue(api.Description),
}
}
@@ -0,0 +1,125 @@
package provider
import (
"context"
"testing"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/types"
)
func TestLocalGemsModelToAPI(t *testing.T) {
model := localGemsResourceModel{
Name: types.StringValue("gems-internal"),
Description: types.StringValue("Internal gems repository"),
}
api := localGemsModelToAPI(model)
if api.Name != "gems-internal" {
t.Errorf("Name: expected gems-internal, got %s", api.Name)
}
if api.PackageType != "gems" {
t.Errorf("PackageType: expected gems, got %s", api.PackageType)
}
if api.RepoType != "local" {
t.Errorf("RepoType: expected local, got %s", api.RepoType)
}
if api.Description != "Internal gems repository" {
t.Errorf("Description: expected 'Internal gems repository', got %s", api.Description)
}
}
func TestLocalGemsModelToAPI_EmptyDescription(t *testing.T) {
model := localGemsResourceModel{
Name: types.StringValue("gems-empty"),
Description: types.StringValue(""),
}
api := localGemsModelToAPI(model)
if api.Name != "gems-empty" {
t.Errorf("Name: expected gems-empty, got %s", api.Name)
}
if api.Description != "" {
t.Errorf("Description: expected empty string, got %s", api.Description)
}
if api.PackageType != "gems" {
t.Errorf("PackageType: expected gems, got %s", api.PackageType)
}
if api.RepoType != "local" {
t.Errorf("RepoType: expected local, got %s", api.RepoType)
}
}
func TestLocalGemsAPIToModel(t *testing.T) {
api := remoteAPI{
Name: "gems-internal",
PackageType: "gems",
RepoType: "local",
Description: "Internal gems repository",
ManagedBy: "terraform",
}
model := localGemsAPIToModel(api)
if model.Name.ValueString() != "gems-internal" {
t.Errorf("Name: expected gems-internal, got %s", model.Name.ValueString())
}
if model.Description.ValueString() != "Internal gems repository" {
t.Errorf("Description: expected 'Internal gems repository', got %s", model.Description.ValueString())
}
}
func TestLocalGemsRoundTrip(t *testing.T) {
original := localGemsResourceModel{
Name: types.StringValue("roundtrip-gems"),
Description: types.StringValue("Round trip test"),
}
api := localGemsModelToAPI(original)
result := localGemsAPIToModel(api)
if result.Name.ValueString() != original.Name.ValueString() {
t.Errorf("Name: expected %s, got %s", original.Name.ValueString(), result.Name.ValueString())
}
if result.Description.ValueString() != original.Description.ValueString() {
t.Errorf("Description: expected %s, got %s", original.Description.ValueString(), result.Description.ValueString())
}
}
func TestLocalGemsResource_Metadata(t *testing.T) {
r := NewLocalGemsResource()
req := resource.MetadataRequest{ProviderTypeName: "artifactapi"}
var resp resource.MetadataResponse
r.Metadata(context.Background(), req, &resp)
if resp.TypeName != "artifactapi_local_gems" {
t.Errorf("expected artifactapi_local_gems, got %s", resp.TypeName)
}
}
func TestLocalGemsResource_Schema(t *testing.T) {
r := NewLocalGemsResource()
req := resource.SchemaRequest{}
var resp resource.SchemaResponse
r.Schema(context.Background(), req, &resp)
expectedAttrs := []string{"name", "description"}
for _, attr := range expectedAttrs {
if _, ok := resp.Schema.Attributes[attr]; !ok {
t.Errorf("missing expected attribute: %s", attr)
}
}
if len(resp.Schema.Attributes) != len(expectedAttrs) {
t.Errorf("expected %d attributes, got %d", len(expectedAttrs), len(resp.Schema.Attributes))
}
}
func TestNewLocalGemsResource_Type(t *testing.T) {
r := NewLocalGemsResource()
_, ok := r.(*localGemsResource)
if !ok {
t.Error("expected *localGemsResource")
}
}
@@ -27,6 +27,8 @@ type localGenericResourceModel struct {
Description types.String `tfsdk:"description"`
}
func init() { registerResource(NewLocalGenericResource) }
func NewLocalGenericResource() resource.Resource {
return &localGenericResource{}
}
@@ -27,6 +27,8 @@ type localGoProxyResourceModel struct {
Description types.String `tfsdk:"description"`
}
func init() { registerResource(NewLocalGoProxyResource) }
func NewLocalGoProxyResource() resource.Resource {
return &localGoProxyResource{}
}
+166
View File
@@ -0,0 +1,166 @@
package provider
import (
"context"
"fmt"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/types"
)
var (
_ resource.Resource = &localHelmResource{}
_ resource.ResourceWithImportState = &localHelmResource{}
)
type localHelmResource struct {
client *apiClient
}
type localHelmResourceModel struct {
Name types.String `tfsdk:"name"`
Description types.String `tfsdk:"description"`
}
func init() { registerResource(NewLocalHelmResource) }
func NewLocalHelmResource() resource.Resource {
return &localHelmResource{}
}
func (r *localHelmResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_local_helm"
}
func (r *localHelmResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
Description: "Manages a local ArtifactAPI Helm repository for hosting Helm charts directly.",
Attributes: map[string]schema.Attribute{
"name": schema.StringAttribute{
Description: "Unique name of the local Helm repository.",
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
"description": schema.StringAttribute{
Description: "Human-readable description.",
Optional: true,
Computed: true,
Default: stringdefault.StaticString(""),
},
},
}
}
func (r *localHelmResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
if req.ProviderData == nil {
return
}
client, ok := req.ProviderData.(*apiClient)
if !ok {
resp.Diagnostics.AddError("unexpected provider data type", fmt.Sprintf("got %T", req.ProviderData))
return
}
r.client = client
}
func (r *localHelmResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
var plan localHelmResourceModel
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
if resp.Diagnostics.HasError() {
return
}
api := localHelmModelToAPI(plan)
api.ManagedBy = "terraform"
var created remoteAPI
if err := r.client.post(ctx, "/api/v2/remotes", api, &created); err != nil {
resp.Diagnostics.AddError("create local helm failed", err.Error())
return
}
state := localHelmAPIToModel(created)
resp.Diagnostics.Append(resp.State.Set(ctx, state)...)
}
func (r *localHelmResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
var state localHelmResourceModel
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}
var remote remoteAPI
err := r.client.get(ctx, "/api/v2/remotes/"+state.Name.ValueString(), &remote)
if err != nil {
if isNotFound(err) {
resp.State.RemoveResource(ctx)
return
}
resp.Diagnostics.AddError("read local helm failed", err.Error())
return
}
newState := localHelmAPIToModel(remote)
resp.Diagnostics.Append(resp.State.Set(ctx, newState)...)
}
func (r *localHelmResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
var plan localHelmResourceModel
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
if resp.Diagnostics.HasError() {
return
}
api := localHelmModelToAPI(plan)
api.ManagedBy = "terraform"
var updated remoteAPI
if err := r.client.put(ctx, "/api/v2/remotes/"+plan.Name.ValueString(), api, &updated); err != nil {
resp.Diagnostics.AddError("update local helm failed", err.Error())
return
}
state := localHelmAPIToModel(updated)
resp.Diagnostics.Append(resp.State.Set(ctx, state)...)
}
func (r *localHelmResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
var state localHelmResourceModel
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}
if err := r.client.del(ctx, "/api/v2/remotes/"+state.Name.ValueString()); err != nil {
resp.Diagnostics.AddError("delete local helm failed", err.Error())
return
}
}
func (r *localHelmResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
resource.ImportStatePassthroughID(ctx, path.Root("name"), req, resp)
}
func localHelmModelToAPI(m localHelmResourceModel) remoteAPI {
return remoteAPI{
Name: m.Name.ValueString(),
PackageType: "helm",
RepoType: "local",
Description: m.Description.ValueString(),
}
}
func localHelmAPIToModel(api remoteAPI) localHelmResourceModel {
return localHelmResourceModel{
Name: types.StringValue(api.Name),
Description: types.StringValue(api.Description),
}
}
@@ -0,0 +1,125 @@
package provider
import (
"context"
"testing"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/types"
)
func TestLocalHelmModelToAPI(t *testing.T) {
model := localHelmResourceModel{
Name: types.StringValue("helm-internal"),
Description: types.StringValue("Internal Helm repository"),
}
api := localHelmModelToAPI(model)
if api.Name != "helm-internal" {
t.Errorf("Name: expected helm-internal, got %s", api.Name)
}
if api.PackageType != "helm" {
t.Errorf("PackageType: expected helm, got %s", api.PackageType)
}
if api.RepoType != "local" {
t.Errorf("RepoType: expected local, got %s", api.RepoType)
}
if api.Description != "Internal Helm repository" {
t.Errorf("Description: expected 'Internal Helm repository', got %s", api.Description)
}
}
func TestLocalHelmModelToAPI_EmptyDescription(t *testing.T) {
model := localHelmResourceModel{
Name: types.StringValue("helm-empty"),
Description: types.StringValue(""),
}
api := localHelmModelToAPI(model)
if api.Name != "helm-empty" {
t.Errorf("Name: expected helm-empty, got %s", api.Name)
}
if api.Description != "" {
t.Errorf("Description: expected empty string, got %s", api.Description)
}
if api.PackageType != "helm" {
t.Errorf("PackageType: expected helm, got %s", api.PackageType)
}
if api.RepoType != "local" {
t.Errorf("RepoType: expected local, got %s", api.RepoType)
}
}
func TestLocalHelmAPIToModel(t *testing.T) {
api := remoteAPI{
Name: "helm-internal",
PackageType: "helm",
RepoType: "local",
Description: "Internal Helm repository",
ManagedBy: "terraform",
}
model := localHelmAPIToModel(api)
if model.Name.ValueString() != "helm-internal" {
t.Errorf("Name: expected helm-internal, got %s", model.Name.ValueString())
}
if model.Description.ValueString() != "Internal Helm repository" {
t.Errorf("Description: expected 'Internal Helm repository', got %s", model.Description.ValueString())
}
}
func TestLocalHelmRoundTrip(t *testing.T) {
original := localHelmResourceModel{
Name: types.StringValue("roundtrip-helm"),
Description: types.StringValue("Round trip test"),
}
api := localHelmModelToAPI(original)
result := localHelmAPIToModel(api)
if result.Name.ValueString() != original.Name.ValueString() {
t.Errorf("Name: expected %s, got %s", original.Name.ValueString(), result.Name.ValueString())
}
if result.Description.ValueString() != original.Description.ValueString() {
t.Errorf("Description: expected %s, got %s", original.Description.ValueString(), result.Description.ValueString())
}
}
func TestLocalHelmResource_Metadata(t *testing.T) {
r := NewLocalHelmResource()
req := resource.MetadataRequest{ProviderTypeName: "artifactapi"}
var resp resource.MetadataResponse
r.Metadata(context.Background(), req, &resp)
if resp.TypeName != "artifactapi_local_helm" {
t.Errorf("expected artifactapi_local_helm, got %s", resp.TypeName)
}
}
func TestLocalHelmResource_Schema(t *testing.T) {
r := NewLocalHelmResource()
req := resource.SchemaRequest{}
var resp resource.SchemaResponse
r.Schema(context.Background(), req, &resp)
expectedAttrs := []string{"name", "description"}
for _, attr := range expectedAttrs {
if _, ok := resp.Schema.Attributes[attr]; !ok {
t.Errorf("missing expected attribute: %s", attr)
}
}
if len(resp.Schema.Attributes) != len(expectedAttrs) {
t.Errorf("expected %d attributes, got %d", len(expectedAttrs), len(resp.Schema.Attributes))
}
}
func TestNewLocalHelmResource_Type(t *testing.T) {
r := NewLocalHelmResource()
_, ok := r.(*localHelmResource)
if !ok {
t.Error("expected *localHelmResource")
}
}
+2
View File
@@ -27,6 +27,8 @@ type localNPMResourceModel struct {
Description types.String `tfsdk:"description"`
}
func init() { registerResource(NewLocalNPMResource) }
func NewLocalNPMResource() resource.Resource {
return &localNPMResource{}
}
+2
View File
@@ -27,6 +27,8 @@ type localPyPIResourceModel struct {
Description types.String `tfsdk:"description"`
}
func init() { registerResource(NewLocalPyPIResource) }
func NewLocalPyPIResource() resource.Resource {
return &localPyPIResource{}
}
+2
View File
@@ -27,6 +27,8 @@ type localRPMResourceModel struct {
Description types.String `tfsdk:"description"`
}
func init() { registerResource(NewLocalRPMResource) }
func NewLocalRPMResource() resource.Resource {
return &localRPMResource{}
}
@@ -27,6 +27,8 @@ type localTerraformResourceModel struct {
Description types.String `tfsdk:"description"`
}
func init() { registerResource(NewLocalTerraformResource) }
func NewLocalTerraformResource() resource.Resource {
return &localTerraformResource{}
}
-19
View File
@@ -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
}
@@ -0,0 +1,3 @@
package provider
func init() { registerResource(newRemoteResource("alpine")) }
@@ -0,0 +1,3 @@
package provider
func init() { registerResource(newRemoteResource("ansible")) }
@@ -0,0 +1,3 @@
package provider
func init() { registerResource(newRemoteResource("cargo")) }
+3
View File
@@ -0,0 +1,3 @@
package provider
func init() { registerResource(newRemoteResource("deb")) }
@@ -0,0 +1,3 @@
package provider
func init() { registerResource(newRemoteResource("docker")) }
@@ -0,0 +1,3 @@
package provider
func init() { registerResource(newRemoteResource("gems")) }
@@ -0,0 +1,3 @@
package provider
func init() { registerResource(newRemoteResource("generic")) }
@@ -0,0 +1,3 @@
package provider
func init() { registerResource(newRemoteResource("github_alpine")) }
@@ -0,0 +1,3 @@
package provider
func init() { registerResource(newRemoteResource("github_deb")) }
@@ -0,0 +1,3 @@
package provider
func init() { registerResource(newRemoteResource("github_rpm")) }
@@ -0,0 +1,3 @@
package provider
func init() { registerResource(newRemoteResource("goproxy")) }
@@ -0,0 +1,3 @@
package provider
func init() { registerResource(newRemoteResource("helm")) }
+3
View File
@@ -0,0 +1,3 @@
package provider
func init() { registerResource(newRemoteResource("npm")) }
@@ -0,0 +1,3 @@
package provider
func init() { registerResource(newRemoteResource("puppet")) }
@@ -0,0 +1,3 @@
package provider
func init() { registerResource(newRemoteResource("pypi")) }
+3
View File
@@ -0,0 +1,3 @@
package provider
func init() { registerResource(newRemoteResource("rpm")) }
@@ -0,0 +1,3 @@
package provider
func init() { registerResource(newRemoteResource("terraform")) }
+24 -78
View File
@@ -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"}
+2
View File
@@ -29,6 +29,8 @@ type virtualResourceModel struct {
Members types.List `tfsdk:"members"`
}
func init() { registerResource(NewVirtualResource) }
func NewVirtualResource() resource.Resource {
return &virtualResource{}
}