5 Commits

Author SHA1 Message Date
unkinben 502053abef feat: manage local docker registries
ci/woodpecker/pr/pre-commit Pipeline failed
ci/woodpecker/pr/plan Pipeline failed
ArtifactAPI now serves local docker repos as real container registries and the
provider exposes an artifactapi_local_docker resource, but this config had no
way to declare one.

- Add a local_docker variable, module resource, config loader wiring, and
  terragrunt input, mirroring the other local_* kinds.
- Declare a docker-internal registry.
- Expand the README to cover the local_* and virtual kinds.

Depends on a terraform-provider-artifactapi release exposing
artifactapi_local_docker (and a matching .terraform.lock.hcl bump) before apply.
2026-07-04 22:41:12 +10:00
benvin 38ab457ac1 Merge pull request 'feat: initial commit' (#3) from benvin/add-missing-registries into main
ci/woodpecker/push/apply Pipeline was successful
Reviewed-on: #3
2026-07-03 23:35:30 +10:00
unkinben 9351ea30a9 fix: declare provider requirement in the artifactapi module
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/plan Pipeline was successful
pre-commit runs `tofu validate`/tflint on modules/artifactapi standalone, where
no required_providers was declared, so OpenTofu inferred hashicorp/artifactapi
and failed (and tflint flagged missing required_version/version constraint).

- add modules/artifactapi/versions.tf declaring required_version and the
  artifactapi provider (source = the registry, version >= 0.1.2)
- drop the now-duplicate required_providers from root.hcl's generated backend.tf;
  keep the provider config block + backend. Declaring it in both the module and
  the generated root would be a "Duplicate required providers" error at runtime.

Also revert the earlier init lock-cleanup: the CI failure was stale provider
references in state (fixed with `tofu state replace-provider`), not the lock.
2026-07-03 23:34:19 +10:00
unkinben 292392a024 fix(ci): clear stale provider locks before init
ci/woodpecker/pr/pre-commit Pipeline failed
ci/woodpecker/pr/plan Pipeline failed
The provider source moved from git.unkin.net/unkin/artifactapi to
artifactapi.k8s.syd1.au.unkin.net/terraform-unkin/artifactapi. .terraform.lock.hcl
is gitignored, so a CI runner reusing a workspace/cache keeps a lock pinning the
old source; some OpenTofu versions then try to re-resolve git.unkin.net (which
isn't a registry) instead of dropping it, failing init.

Delete any .terraform.lock.hcl before `terragrunt run --all init -- -upgrade` so
providers resolve purely from config.
2026-07-03 21:48:57 +10:00
unkinben 9058eb3f59 feat: initial commit
ci/woodpecker/pr/pre-commit Pipeline failed
ci/woodpecker/pr/plan Pipeline was successful
- add modules
- add config
- add environments
- add .gitignore
- add makefile

Wire up config.hcl, variables, and terragrunt inputs for all new types.
2026-07-03 21:29:02 +10:00
33 changed files with 261 additions and 47 deletions
+24
View File
@@ -0,0 +1,24 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
hooks:
- id: end-of-file-fixer
types: [yaml]
- id: trailing-whitespace
types: [yaml]
- repo: https://github.com/gruntwork-io/pre-commit
rev: v0.1.30
hooks:
- id: tofu-fmt
- id: tofu-validate
- id: tflint
- id: terragrunt-hcl-fmt
- repo: https://github.com/adrienverge/yamllint.git
rev: v1.37.1
hooks:
- id: yamllint
args:
[
"-d {extends: relaxed, rules: {line-length: disable}, ignore: chart}",
"-s",
]
+1 -1
View File
@@ -1,6 +1,6 @@
when:
- event: push
branch: master
branch: main
steps:
- name: apply
+9 -1
View File
@@ -1,3 +1,11 @@
# terraform-artifactapi
Terraform configuration for managing ArtifactAPI remote and virtual repositories
Terraform configuration for managing ArtifactAPI remote, local, and virtual repositories.
Repositories are declared as YAML files under `config/<kind>/<name>.yaml`, where the
filename is the repository name. `make plan` / `make apply` (via Terragrunt) reconcile
them against the ArtifactAPI instance.
Kinds: `remote_*` (caching proxies of an upstream), `local_*` (real registries hosted
directly — `local_docker`, `local_pypi`, `local_rpm`, `local_terraform`), and `virtual`
(merged views over members of one package type).
+25
View File
@@ -32,11 +32,36 @@ locals {
trimsuffix(basename(file_path), ".yaml") => content
if startswith(file_path, "remote_helm/")
}
remote_pypi = {
for file_path, content in local.all_configs :
trimsuffix(basename(file_path), ".yaml") => content
if startswith(file_path, "remote_pypi/")
}
remote_rpm = {
for file_path, content in local.all_configs :
trimsuffix(basename(file_path), ".yaml") => content
if startswith(file_path, "remote_rpm/")
}
local_terraform = {
for file_path, content in local.all_configs :
trimsuffix(basename(file_path), ".yaml") => content
if startswith(file_path, "local_terraform/")
}
local_pypi = {
for file_path, content in local.all_configs :
trimsuffix(basename(file_path), ".yaml") => content
if startswith(file_path, "local_pypi/")
}
local_rpm = {
for file_path, content in local.all_configs :
trimsuffix(basename(file_path), ".yaml") => content
if startswith(file_path, "local_rpm/")
}
local_docker = {
for file_path, content in local.all_configs :
trimsuffix(basename(file_path), ".yaml") => content
if startswith(file_path, "local_docker/")
}
virtual = {
for file_path, content in local.all_configs :
trimsuffix(basename(file_path), ".yaml") => content
+1
View File
@@ -0,0 +1 @@
description: "Internal container image registry"
+1
View File
@@ -0,0 +1 @@
description: "Internal PyPI packages"
+1
View File
@@ -0,0 +1 @@
description: "Internal RPM packages for Enterprise Linux 10 (RHEL/AlmaLinux/Rocky 10)"
+1
View File
@@ -0,0 +1 @@
description: "Internal RPM packages for Enterprise Linux 8 (RHEL/AlmaLinux/Rocky 8)"
+1
View File
@@ -0,0 +1 @@
description: "Internal RPM packages for Enterprise Linux 9 (RHEL/AlmaLinux/Rocky 9)"
+1
View File
@@ -0,0 +1 @@
description: "Internal RPM packages for Fedora 42"
+1
View File
@@ -0,0 +1 @@
description: "Internal RPM packages for Fedora 43"
+1
View File
@@ -0,0 +1 @@
description: "Internal RPM packages for Fedora 44"
+1
View File
@@ -0,0 +1 @@
description: "Internal RPM packages (distribution-agnostic)"
@@ -0,0 +1 @@
description: "Unkin Terraform modules"
+7
View File
@@ -3,7 +3,9 @@ description: Docker Hub registry
immutable_ttl: 0
mutable_ttl: 300
patterns:
- "^alpine/"
- "^library/almalinux"
- "^library/alpine"
- "^library/busybox"
- "^library/debian"
- "^library/fedora"
@@ -16,10 +18,15 @@ patterns:
- "^emberstack/kubernetes-reflector"
- "^hashicorp/consul"
- "^hashicorp/vault"
- "^intel/"
- "^internetsystemsconsortium/bind9"
- "^jfrog/"
- "^jpgouin/"
- "^kanidm/"
- "^osixia/"
- "^rancher/"
- "^rspamd/rspamd"
- "^tiredofit/"
- "^tozd/postfix"
- "^traefik/"
- "^valkey/valkey"
+6
View File
@@ -0,0 +1,6 @@
base_url: https://public.ecr.aws
description: Amazon ECR Public registry
immutable_ttl: 0
mutable_ttl: 300
patterns:
- "^docker/library/"
+9 -1
View File
@@ -3,10 +3,18 @@ description: GitHub Container Registry
immutable_ttl: 0
mutable_ttl: 300
patterns:
- "^cloudnative-pg/cloudnative-pg"
- "^cloudnative-pg/"
- "^dexidp/"
- "^emberstack/helm-charts"
- "^fallenbagel/"
- "^goauthentik/"
- "^home-operations/"
- "^jellyfin/"
- "^onedr0p/"
- "^open-webui/open-webui"
- "^openvoxproject/"
- "^paperclipai/"
- "^plexguide/"
- "^stakater/reloader"
- "^stalwartlabs/stalwart"
- "^voxpupuli/puppetboard"
+6
View File
@@ -0,0 +1,6 @@
base_url: https://docker.litellm.ai
description: LiteLLM container registry
immutable_ttl: 0
mutable_ttl: 300
patterns:
- "^berriai/"
+1
View File
@@ -3,6 +3,7 @@ description: Quay.io container registry
immutable_ttl: 0
mutable_ttl: 300
patterns:
- "^argoproj/"
- "^brancz/kube-rbac-proxy"
- "^cephcsi/cephcsi"
- "^jetstack/cert-manager-"
@@ -4,5 +4,6 @@ immutable_ttl: 0
mutable_ttl: 7200
patterns:
- "argoproj/argo-cd/.*.yaml$"
- "yannh/kubernetes-json-schema/master/.*.json$"
- "datreeio/CRDs-catalog/main/.*.json$"
- "kubernetes/kubernetes/.*.json$"
- "yannh/kubernetes-json-schema/master/.*.json$"
+7
View File
@@ -0,0 +1,7 @@
base_url: https://charts.goauthentik.io
description: Authentik Charts
immutable_ttl: 0
mutable_ttl: 3600
check_mutable: true
immutable_patterns:
- "\\.tgz$"
+7
View File
@@ -0,0 +1,7 @@
base_url: https://grafana.github.io/helm-charts
description: Grafana Helm charts
immutable_ttl: 0
mutable_ttl: 3600
check_mutable: true
immutable_patterns:
- "\\.tgz$"
+5
View File
@@ -0,0 +1,5 @@
base_url: https://pypi.org
description: "PyPI — Python Package Index"
immutable_ttl: 0
mutable_ttl: 3600
stale_on_error: true
+6
View File
@@ -0,0 +1,6 @@
base_url: "https://downloads.claude.ai/claude-code/rpm/stable"
description: "Claude Code RPM repository"
immutable_patterns:
- ".*/x86_64/.*\\.rpm$"
- ".*/noarch/.*\\.rpm$"
- ".*/repodata/.*\\.xml.*$"
+2
View File
@@ -1,10 +1,12 @@
package_type: helm
description: "Virtual repository merging all helm remotes — member order is priority order for duplicate chart+version"
members:
- authentik
- ceph-csi
- cnpg
- elastic-helm
- external-dns
- grafana
- hashicorp-helm
- intel-helm
- jetstack
+5
View File
@@ -0,0 +1,5 @@
package_type: pypi
description: "Virtual PyPI repository merging internal packages and upstream PyPI"
members:
- pypi-internal
- pypi
@@ -0,0 +1,32 @@
include "root" {
path = find_in_parent_folders("root.hcl")
expose = true
}
include "config" {
path = "${get_repo_root()}/config/config.hcl"
expose = true
}
locals {
config = include.config.locals.config
}
terraform {
source = "../../modules/artifactapi"
}
inputs = {
remote_alpine = local.config.remote_alpine
remote_docker = local.config.remote_docker
remote_generic = local.config.remote_generic
remote_goproxy = local.config.remote_goproxy
remote_helm = local.config.remote_helm
remote_pypi = local.config.remote_pypi
remote_rpm = local.config.remote_rpm
local_terraform = local.config.local_terraform
local_pypi = local.config.local_pypi
local_rpm = local.config.local_rpm
local_docker = local.config.local_docker
virtual = local.config.virtual
}
-27
View File
@@ -1,27 +0,0 @@
include "root" {
path = find_in_parent_folders("root.hcl")
expose = true
}
include "config" {
path = "${get_repo_root()}/config/config.hcl"
expose = true
}
locals {
config = include.config.locals.config
}
terraform {
source = "../../../modules/artifactapi"
}
inputs = {
remote_alpine = local.config.remote_alpine
remote_docker = local.config.remote_docker
remote_generic = local.config.remote_generic
remote_goproxy = local.config.remote_goproxy
remote_helm = local.config.remote_helm
remote_rpm = local.config.remote_rpm
virtual = local.config.virtual
}
+1 -7
View File
@@ -3,7 +3,7 @@ generate "backend" {
if_exists = "overwrite"
contents = <<EOF
provider "artifactapi" {
endpoint = "https://artifactapi.k8s.syd1.au.unkin.net"
endpoint = "https://${path_relative_to_include()}"
}
terraform {
@@ -15,12 +15,6 @@ terraform {
ca_file = "/etc/pki/tls/certs/ca-bundle.crt"
}
required_version = ">= 1.10"
required_providers {
artifactapi = {
source = "git.unkin.net/unkin/artifactapi"
version = "0.0.1"
}
}
}
EOF
}
+41
View File
@@ -76,6 +76,47 @@ resource "artifactapi_remote_rpm" "this" {
stale_on_error = each.value.stale_on_error
}
resource "artifactapi_remote_pypi" "this" {
for_each = var.remote_pypi
name = each.key
base_url = each.value.base_url
description = each.value.description
immutable_ttl = each.value.immutable_ttl
mutable_ttl = each.value.mutable_ttl
patterns = each.value.patterns
mutable_patterns = each.value.mutable_patterns
stale_on_error = each.value.stale_on_error
}
resource "artifactapi_local_terraform" "this" {
for_each = var.local_terraform
name = each.key
description = each.value.description
}
resource "artifactapi_local_pypi" "this" {
for_each = var.local_pypi
name = each.key
description = each.value.description
}
resource "artifactapi_local_rpm" "this" {
for_each = var.local_rpm
name = each.key
description = each.value.description
}
resource "artifactapi_local_docker" "this" {
for_each = var.local_docker
name = each.key
description = each.value.description
}
resource "artifactapi_virtual" "this" {
for_each = var.virtual
-9
View File
@@ -1,9 +0,0 @@
terraform {
required_version = ">= 1.10"
required_providers {
artifactapi = {
source = "git.unkin.net/unkin/artifactapi"
version = "0.0.1"
}
}
}
+46
View File
@@ -82,6 +82,52 @@ variable "remote_rpm" {
default = {}
}
variable "remote_pypi" {
description = "Map of PyPI remote repositories"
type = map(object({
base_url = string
description = optional(string, "")
immutable_ttl = optional(number, 0)
mutable_ttl = optional(number, 3600)
patterns = optional(list(string), [])
mutable_patterns = optional(list(string), [])
stale_on_error = optional(bool, true)
}))
default = {}
}
variable "local_pypi" {
description = "Map of local PyPI repositories"
type = map(object({
description = optional(string, "")
}))
default = {}
}
variable "local_rpm" {
description = "Map of local RPM repositories"
type = map(object({
description = optional(string, "")
}))
default = {}
}
variable "local_terraform" {
description = "Map of local Terraform repositories"
type = map(object({
description = optional(string, "")
}))
default = {}
}
variable "local_docker" {
description = "Map of local Docker repositories (real container registries)"
type = map(object({
description = optional(string, "")
}))
default = {}
}
variable "virtual" {
description = "Map of virtual repositories"
type = map(object({
+9
View File
@@ -0,0 +1,9 @@
terraform {
required_version = ">= 1.10"
required_providers {
artifactapi = {
source = "artifactapi.k8s.syd1.au.unkin.net/terraform-unkin/artifactapi"
version = ">= 0.1.2"
}
}
}