From 9ab540719e1237672d610bb06f997d46bd923916 Mon Sep 17 00:00:00 2001 From: unkin-agent Date: Sat, 19 Sep 2026 15:55:12 +1000 Subject: [PATCH 1/2] Add fork_from to repository config Express a fork as an optional fork_from key on the repository yaml, so a forked repo is declared the same way as any other repo. - add fork_from "/" to the repository module and object type - create gitea_fork instead of gitea_repository when fork_from is set - validate the source repo name matches the config file name, since a fork inherits the source name - move gitea_repository.this to index 0 for the count refactor - index existing repository import targets to match --- modules/gitea_instance/imports.tf | 68 +++++++++---------- modules/gitea_instance/main.tf | 1 + .../gitea_instance/modules/repository/main.tf | 19 ++++++ .../modules/repository/outputs.tf | 2 +- .../modules/repository/variables.tf | 16 +++++ modules/gitea_instance/variables.tf | 1 + 6 files changed, 72 insertions(+), 35 deletions(-) diff --git a/modules/gitea_instance/imports.tf b/modules/gitea_instance/imports.tf index 9446369..cfe2959 100644 --- a/modules/gitea_instance/imports.tf +++ b/modules/gitea_instance/imports.tf @@ -4,167 +4,167 @@ import { } import { - to = module.repository["git.unkin.net/unkin/puppet-prod"].gitea_repository.this + to = module.repository["git.unkin.net/unkin/puppet-prod"].gitea_repository.this[0] id = "2" } import { - to = module.repository["git.unkin.net/unkin/puppet-r10k"].gitea_repository.this + to = module.repository["git.unkin.net/unkin/puppet-r10k"].gitea_repository.this[0] id = "3" } import { - to = module.repository["git.unkin.net/unkin/rpmbuild-gonic"].gitea_repository.this + to = module.repository["git.unkin.net/unkin/rpmbuild-gonic"].gitea_repository.this[0] id = "23" } import { - to = module.repository["git.unkin.net/unkin/docker-almalinux-base"].gitea_repository.this + to = module.repository["git.unkin.net/unkin/docker-almalinux-base"].gitea_repository.this[0] id = "24" } import { - to = module.repository["git.unkin.net/unkin/rpmbuild-internal-ca-certificates"].gitea_repository.this + to = module.repository["git.unkin.net/unkin/rpmbuild-internal-ca-certificates"].gitea_repository.this[0] id = "27" } import { - to = module.repository["git.unkin.net/unkin/rpmbuild-template"].gitea_repository.this + to = module.repository["git.unkin.net/unkin/rpmbuild-template"].gitea_repository.this[0] id = "29" } import { - to = module.repository["git.unkin.net/unkin/rpmbuild-jellyfin-web"].gitea_repository.this + to = module.repository["git.unkin.net/unkin/rpmbuild-jellyfin-web"].gitea_repository.this[0] id = "31" } import { - to = module.repository["git.unkin.net/unkin/rpmbuild-proxlb"].gitea_repository.this + to = module.repository["git.unkin.net/unkin/rpmbuild-proxlb"].gitea_repository.this[0] id = "33" } import { - to = module.repository["git.unkin.net/unkin/docker-almalinux-buildrunner"].gitea_repository.this + to = module.repository["git.unkin.net/unkin/docker-almalinux-buildrunner"].gitea_repository.this[0] id = "36" } import { - to = module.repository["git.unkin.net/unkin/docker-template"].gitea_repository.this + to = module.repository["git.unkin.net/unkin/docker-template"].gitea_repository.this[0] id = "38" } import { - to = module.repository["git.unkin.net/unkin/terraform-vault"].gitea_repository.this + to = module.repository["git.unkin.net/unkin/terraform-vault"].gitea_repository.this[0] id = "39" } import { - to = module.repository["git.unkin.net/unkin/docker-almalinux-jupyterinstance"].gitea_repository.this + to = module.repository["git.unkin.net/unkin/docker-almalinux-jupyterinstance"].gitea_repository.this[0] id = "40" } import { - to = module.repository["git.unkin.net/unkin/rpmbuilder"].gitea_repository.this + to = module.repository["git.unkin.net/unkin/rpmbuilder"].gitea_repository.this[0] id = "41" } import { - to = module.repository["git.unkin.net/unkin/docker-almalinux-runnerdnd"].gitea_repository.this + to = module.repository["git.unkin.net/unkin/docker-almalinux-runnerdnd"].gitea_repository.this[0] id = "43" } import { - to = module.repository["git.unkin.net/unkin/initbuilder"].gitea_repository.this + to = module.repository["git.unkin.net/unkin/initbuilder"].gitea_repository.this[0] id = "47" } import { - to = module.repository["git.unkin.net/unkin/puppetapi"].gitea_repository.this + to = module.repository["git.unkin.net/unkin/puppetapi"].gitea_repository.this[0] id = "50" } import { - to = module.repository["git.unkin.net/unkin/terraform-nomad"].gitea_repository.this + to = module.repository["git.unkin.net/unkin/terraform-nomad"].gitea_repository.this[0] id = "53" } import { - to = module.repository["git.unkin.net/unkin/packer-images"].gitea_repository.this + to = module.repository["git.unkin.net/unkin/packer-images"].gitea_repository.this[0] id = "59" } import { - to = module.repository["git.unkin.net/unkin/app-sudaporn-research-normalised"].gitea_repository.this + to = module.repository["git.unkin.net/unkin/app-sudaporn-research-normalised"].gitea_repository.this[0] id = "60" } import { - to = module.repository["git.unkin.net/unkin/app-sudaporn-research-individual"].gitea_repository.this + to = module.repository["git.unkin.net/unkin/app-sudaporn-research-individual"].gitea_repository.this[0] id = "63" } import { - to = module.repository["git.unkin.net/unkin/terraform-incus"].gitea_repository.this + to = module.repository["git.unkin.net/unkin/terraform-incus"].gitea_repository.this[0] id = "66" } import { - to = module.repository["git.unkin.net/unkin/artifactapi"].gitea_repository.this + to = module.repository["git.unkin.net/unkin/artifactapi"].gitea_repository.this[0] id = "67" } import { - to = module.repository["git.unkin.net/unkin/argocd-apps"].gitea_repository.this + to = module.repository["git.unkin.net/unkin/argocd-apps"].gitea_repository.this[0] id = "100" } import { - to = module.repository["git.unkin.net/unkin/certmanager"].gitea_repository.this + to = module.repository["git.unkin.net/unkin/certmanager"].gitea_repository.this[0] id = "101" } import { - to = module.repository["git.unkin.net/unkin/node-lookup"].gitea_repository.this + to = module.repository["git.unkin.net/unkin/node-lookup"].gitea_repository.this[0] id = "102" } import { - to = module.repository["git.unkin.net/unkin/container-devcompute"].gitea_repository.this + to = module.repository["git.unkin.net/unkin/container-devcompute"].gitea_repository.this[0] id = "135" } import { - to = module.repository["git.unkin.net/unkin/streamstack"].gitea_repository.this + to = module.repository["git.unkin.net/unkin/streamstack"].gitea_repository.this[0] id = "136" } import { - to = module.repository["git.unkin.net/unkin/terraform-provider-artifactapi"].gitea_repository.this + to = module.repository["git.unkin.net/unkin/terraform-provider-artifactapi"].gitea_repository.this[0] id = "137" } import { - to = module.repository["git.unkin.net/unkin/forgebot"].gitea_repository.this + to = module.repository["git.unkin.net/unkin/forgebot"].gitea_repository.this[0] id = "139" } import { - to = module.repository["git.unkin.net/unkin/forgebot-skills"].gitea_repository.this + to = module.repository["git.unkin.net/unkin/forgebot-skills"].gitea_repository.this[0] id = "140" } import { - to = module.repository["git.unkin.net/unkin/container-agent-base"].gitea_repository.this + to = module.repository["git.unkin.net/unkin/container-agent-base"].gitea_repository.this[0] id = "141" } import { - to = module.repository["git.unkin.net/unkin/container-agent-dev"].gitea_repository.this + to = module.repository["git.unkin.net/unkin/container-agent-dev"].gitea_repository.this[0] id = "142" } import { - to = module.repository["git.unkin.net/unkin/container-agent-infra"].gitea_repository.this + to = module.repository["git.unkin.net/unkin/container-agent-infra"].gitea_repository.this[0] id = "143" } @@ -199,7 +199,7 @@ import { } import { - to = module.repository["git.unkin.net/unkin/terraform-git"].gitea_repository.this + to = module.repository["git.unkin.net/unkin/terraform-git"].gitea_repository.this[0] id = "144" } diff --git a/modules/gitea_instance/main.tf b/modules/gitea_instance/main.tf index dc84eff..a83fb94 100644 --- a/modules/gitea_instance/main.tf +++ b/modules/gitea_instance/main.tf @@ -19,6 +19,7 @@ module "repository" { name = each.value.name organisation = each.value.organisation + fork_from = each.value.fork_from description = each.value.description private = each.value.private default_branch = each.value.default_branch diff --git a/modules/gitea_instance/modules/repository/main.tf b/modules/gitea_instance/modules/repository/main.tf index 9949454..d3749cd 100644 --- a/modules/gitea_instance/modules/repository/main.tf +++ b/modules/gitea_instance/modules/repository/main.tf @@ -1,4 +1,10 @@ +locals { + fork_source = var.fork_from == null ? null : split("/", var.fork_from) +} + resource "gitea_repository" "this" { + count = var.fork_from == null ? 1 : 0 + username = var.organisation name = var.name description = var.description @@ -33,3 +39,16 @@ resource "gitea_repository" "this" { ignore_changes = [migration_mirror_interval] } } + +moved { + from = gitea_repository.this + to = gitea_repository.this[0] +} + +resource "gitea_fork" "this" { + count = var.fork_from == null ? 0 : 1 + + owner = local.fork_source[0] + repo = local.fork_source[1] + organization = var.organisation +} diff --git a/modules/gitea_instance/modules/repository/outputs.tf b/modules/gitea_instance/modules/repository/outputs.tf index c344e7f..901df6c 100644 --- a/modules/gitea_instance/modules/repository/outputs.tf +++ b/modules/gitea_instance/modules/repository/outputs.tf @@ -1,3 +1,3 @@ output "id" { - value = gitea_repository.this.id + value = one(concat(gitea_repository.this[*].id, gitea_fork.this[*].id)) } diff --git a/modules/gitea_instance/modules/repository/variables.tf b/modules/gitea_instance/modules/repository/variables.tf index 7a91820..da2f1da 100644 --- a/modules/gitea_instance/modules/repository/variables.tf +++ b/modules/gitea_instance/modules/repository/variables.tf @@ -6,6 +6,22 @@ variable "organisation" { type = string } +variable "fork_from" { + description = "Source repository to fork, as \"/\". When set, a fork is created instead of a new repository." + type = string + default = null + + validation { + condition = var.fork_from == null || can(regex("^[^/]+/[^/]+$", var.fork_from)) + error_message = "fork_from must be \"/\"." + } + + validation { + condition = var.fork_from == null || try(split("/", var.fork_from)[1], null) == var.name + error_message = "fork_from source repository must be named \"${var.name}\": a fork inherits the source name, so it cannot differ from the config file name." + } +} + variable "description" { type = string default = null diff --git a/modules/gitea_instance/variables.tf b/modules/gitea_instance/variables.tf index a67f810..909ba71 100644 --- a/modules/gitea_instance/variables.tf +++ b/modules/gitea_instance/variables.tf @@ -17,6 +17,7 @@ variable "repository" { type = map(object({ name = string organisation = string + fork_from = optional(string) description = optional(string) private = optional(bool) default_branch = optional(string) -- 2.47.3 From 8bc04e2c7d57f325a8cddf9e0d2d334efbf17bed Mon Sep 17 00:00:00 2001 From: unkin-agent Date: Sat, 19 Sep 2026 16:03:44 +1000 Subject: [PATCH 2/2] Reject fork_from alongside unsupported repository keys gitea_fork can only express the source repo and owning organisation, so any other setting in a fork's yaml is silently dropped. - validate fork_from entries carry no other repository attributes, naming the offending config key and attributes - note that gitea_fork has no archive_on_destroy --- modules/gitea_instance/modules/repository/main.tf | 1 + modules/gitea_instance/variables.tf | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/modules/gitea_instance/modules/repository/main.tf b/modules/gitea_instance/modules/repository/main.tf index d3749cd..80c8e03 100644 --- a/modules/gitea_instance/modules/repository/main.tf +++ b/modules/gitea_instance/modules/repository/main.tf @@ -45,6 +45,7 @@ moved { to = gitea_repository.this[0] } +# gitea_fork has no archive_on_destroy, so removing a fork from the config deletes the repository instead of archiving it. resource "gitea_fork" "this" { count = var.fork_from == null ? 0 : 1 diff --git a/modules/gitea_instance/variables.tf b/modules/gitea_instance/variables.tf index 909ba71..c7ce6b7 100644 --- a/modules/gitea_instance/variables.tf +++ b/modules/gitea_instance/variables.tf @@ -45,6 +45,19 @@ variable "repository" { migration_releases = optional(bool) })) default = {} + + validation { + condition = alltrue([ + for key, repo in var.repository : + length([for attribute, value in repo : attribute if value != null && !contains(["name", "organisation", "fork_from"], attribute)]) == 0 + if repo.fork_from != null + ]) + error_message = "gitea_fork only takes the source repository and the owning organisation, so fork_from cannot be combined with other repository settings: ${join("; ", [ + for key, repo in var.repository : + "${key} also sets ${join(", ", [for attribute, value in repo : attribute if value != null && !contains(["name", "organisation", "fork_from"], attribute)])}" + if repo.fork_from != null && length([for attribute, value in repo : attribute if value != null && !contains(["name", "organisation", "fork_from"], attribute)]) > 0 + ])}." + } } variable "branch_protection" { -- 2.47.3