merge main
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
# `artifactapi_remote` (Data Source)
|
||||
|
||||
Read an existing remote's configuration.
|
||||
|
||||
```hcl
|
||||
data "artifactapi_remote" "dockerhub" {
|
||||
name = "dockerhub"
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,9 @@
|
||||
# `artifactapi_virtual` (Data Source)
|
||||
|
||||
Read an existing virtual repository's configuration.
|
||||
|
||||
```hcl
|
||||
data "artifactapi_virtual" "helm" {
|
||||
name = "helm"
|
||||
}
|
||||
```
|
||||
@@ -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`.
|
||||
@@ -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`.
|
||||
@@ -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`.
|
||||
@@ -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`.
|
||||
@@ -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`.
|
||||
@@ -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`.
|
||||
@@ -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`.
|
||||
@@ -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`.
|
||||
@@ -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`.
|
||||
@@ -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`.
|
||||
@@ -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`.
|
||||
@@ -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`.
|
||||
@@ -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`.
|
||||
@@ -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`.
|
||||
@@ -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`.
|
||||
@@ -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`.
|
||||
@@ -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",
|
||||
]
|
||||
}
|
||||
```
|
||||
@@ -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`.
|
||||
@@ -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`.
|
||||
@@ -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`.
|
||||
@@ -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`.
|
||||
@@ -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`.
|
||||
@@ -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`.
|
||||
@@ -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`.
|
||||
@@ -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`.
|
||||
@@ -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`.
|
||||
@@ -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`.
|
||||
@@ -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`.
|
||||
@@ -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`.
|
||||
@@ -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
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@ type remoteDataSource struct {
|
||||
client *apiClient
|
||||
}
|
||||
|
||||
func init() { registerDataSource(NewRemoteDataSource) }
|
||||
|
||||
func NewRemoteDataSource() datasource.DataSource {
|
||||
return &remoteDataSource{}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@ type virtualDataSource struct {
|
||||
client *apiClient
|
||||
}
|
||||
|
||||
func init() { registerDataSource(NewVirtualDataSource) }
|
||||
|
||||
func NewVirtualDataSource() datasource.DataSource {
|
||||
return &virtualDataSource{}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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{}
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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{}
|
||||
}
|
||||
|
||||
@@ -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{}
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,8 @@ type localNPMResourceModel struct {
|
||||
Description types.String `tfsdk:"description"`
|
||||
}
|
||||
|
||||
func init() { registerResource(NewLocalNPMResource) }
|
||||
|
||||
func NewLocalNPMResource() resource.Resource {
|
||||
return &localNPMResource{}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,8 @@ type localPyPIResourceModel struct {
|
||||
Description types.String `tfsdk:"description"`
|
||||
}
|
||||
|
||||
func init() { registerResource(NewLocalPyPIResource) }
|
||||
|
||||
func NewLocalPyPIResource() resource.Resource {
|
||||
return &localPyPIResource{}
|
||||
}
|
||||
|
||||
@@ -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{}
|
||||
}
|
||||
|
||||
@@ -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")) }
|
||||
@@ -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")) }
|
||||
@@ -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")) }
|
||||
@@ -0,0 +1,3 @@
|
||||
package provider
|
||||
|
||||
func init() { registerResource(newRemoteResource("rpm")) }
|
||||
@@ -0,0 +1,3 @@
|
||||
package provider
|
||||
|
||||
func init() { registerResource(newRemoteResource("terraform")) }
|
||||
@@ -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"}
|
||||
|
||||
@@ -29,6 +29,8 @@ type virtualResourceModel struct {
|
||||
Members types.List `tfsdk:"members"`
|
||||
}
|
||||
|
||||
func init() { registerResource(NewVirtualResource) }
|
||||
|
||||
func NewVirtualResource() resource.Resource {
|
||||
return &virtualResource{}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user