Manage Gitea users; add teabot personality bot accounts
Add a data-driven 'user' config kind so Gitea accounts are declared as config/git.unkin.net/user/<name>.yaml, mirroring the existing repository and team kinds. Wire the yaml into a new modules/user submodule that creates a gitea_user (provider go-gitea/gitea 0.7.0, already pinned). The provider's user resource requires a password; generate a per-user random_password so nothing sensitive is hardcoded and only a placeholder lives in state (tokens come later from vault-plugin-secrets-gitea). Provision teabot's implementer and reviewer personality accounts with a conservative posture: not site admins, no org creation, no repo creation, limited profile visibility. Claude-Session: https://claude.ai/code/session_015ur3i7D2azsMAWTSVABApv
This commit is contained in:
@@ -33,6 +33,14 @@ locals {
|
|||||||
})
|
})
|
||||||
if length(regexall("/team/", file_path)) > 0
|
if length(regexall("/team/", file_path)) > 0
|
||||||
}
|
}
|
||||||
|
user = {
|
||||||
|
for file_path, content in local.all_configs :
|
||||||
|
"${split("/", file_path)[0]}/${trimsuffix(basename(file_path), ".yaml")}" => merge(content, {
|
||||||
|
username = trimsuffix(basename(file_path), ".yaml")
|
||||||
|
gitea_url = split("/", file_path)[0]
|
||||||
|
})
|
||||||
|
if length(regexall("/user/", file_path)) > 0
|
||||||
|
}
|
||||||
branch_protection = merge([
|
branch_protection = merge([
|
||||||
for file_path, content in local.all_configs : {
|
for file_path, content in local.all_configs : {
|
||||||
for idx, rule in try(content.branch_protection, []) :
|
for idx, rule in try(content.branch_protection, []) :
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# teabot "implementer" personality account.
|
||||||
|
# Used by the teabot daemon (unkin/teabot) to open issues/PRs as a distinct
|
||||||
|
# identity so its work is attributable. Auth is via an API token issued
|
||||||
|
# out-of-band (vault-plugin-secrets-gitea / static KV) -- no token or usable
|
||||||
|
# password lives in this repo's state beyond the generated placeholder.
|
||||||
|
email: teabot-implementer@unkin.net
|
||||||
|
full_name: "Teabot Implementer"
|
||||||
|
description: "teabot implementer bot -- automated agent (unkin/teabot)"
|
||||||
|
# Conservative bot posture: not a site admin, cannot create orgs or repos,
|
||||||
|
# profile visible only to signed-in users.
|
||||||
|
visibility: limited
|
||||||
|
admin: false
|
||||||
|
allow_create_organization: false
|
||||||
|
max_repo_creation: 0
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# teabot "reviewer" personality account.
|
||||||
|
# Used by the teabot daemon (unkin/teabot) to review pull requests as a distinct
|
||||||
|
# identity so its reviews are attributable. Auth is via an API token issued
|
||||||
|
# out-of-band (vault-plugin-secrets-gitea / static KV) -- no token or usable
|
||||||
|
# password lives in this repo's state beyond the generated placeholder.
|
||||||
|
email: teabot-reviewer@unkin.net
|
||||||
|
full_name: "Teabot Reviewer"
|
||||||
|
description: "teabot reviewer bot -- automated agent (unkin/teabot)"
|
||||||
|
# Conservative bot posture: not a site admin, cannot create orgs or repos,
|
||||||
|
# profile visible only to signed-in users.
|
||||||
|
visibility: limited
|
||||||
|
admin: false
|
||||||
|
allow_create_organization: false
|
||||||
|
max_repo_creation: 0
|
||||||
@@ -21,4 +21,5 @@ inputs = {
|
|||||||
repository = local.config.repository
|
repository = local.config.repository
|
||||||
branch_protection = local.config.branch_protection
|
branch_protection = local.config.branch_protection
|
||||||
team = local.config.team
|
team = local.config.team
|
||||||
|
user = local.config.user
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,25 @@ module "repository" {
|
|||||||
depends_on = [module.organisation]
|
depends_on = [module.organisation]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
module "user" {
|
||||||
|
source = "./modules/user"
|
||||||
|
|
||||||
|
for_each = var.user
|
||||||
|
|
||||||
|
username = each.value.username
|
||||||
|
email = each.value.email
|
||||||
|
login_name = each.value.login_name
|
||||||
|
full_name = each.value.full_name
|
||||||
|
description = each.value.description
|
||||||
|
visibility = each.value.visibility
|
||||||
|
admin = each.value.admin
|
||||||
|
restricted = each.value.restricted
|
||||||
|
active = each.value.active
|
||||||
|
allow_create_organization = each.value.allow_create_organization
|
||||||
|
max_repo_creation = each.value.max_repo_creation
|
||||||
|
must_change_password = each.value.must_change_password
|
||||||
|
}
|
||||||
|
|
||||||
module "team" {
|
module "team" {
|
||||||
source = "./modules/team"
|
source = "./modules/team"
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# The gitea_user resource requires a password argument. These accounts
|
||||||
|
# authenticate via API tokens (issued out-of-band by the
|
||||||
|
# vault-plugin-secrets-gitea engine, or a static KV entry initially), so the
|
||||||
|
# password is never used interactively. It is generated here so nothing
|
||||||
|
# sensitive is hardcoded, and kept only in state.
|
||||||
|
resource "random_password" "this" {
|
||||||
|
length = 32
|
||||||
|
special = true
|
||||||
|
override_special = "!@#%^&*()-_=+"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "gitea_user" "this" {
|
||||||
|
username = var.username
|
||||||
|
login_name = coalesce(var.login_name, var.username)
|
||||||
|
email = var.email
|
||||||
|
password = random_password.this.result
|
||||||
|
|
||||||
|
full_name = var.full_name
|
||||||
|
description = var.description
|
||||||
|
visibility = var.visibility
|
||||||
|
|
||||||
|
admin = var.admin
|
||||||
|
restricted = var.restricted
|
||||||
|
active = var.active
|
||||||
|
allow_create_organization = var.allow_create_organization
|
||||||
|
max_repo_creation = var.max_repo_creation
|
||||||
|
must_change_password = var.must_change_password
|
||||||
|
|
||||||
|
# Do not e-mail the (possibly unmonitored) bot address on creation.
|
||||||
|
send_notification = false
|
||||||
|
|
||||||
|
lifecycle {
|
||||||
|
# The provider cannot read the password back from the API; ignore it so the
|
||||||
|
# account never shows perpetual drift once created.
|
||||||
|
ignore_changes = [password]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
terraform {
|
||||||
|
required_version = ">= 1.10"
|
||||||
|
required_providers {
|
||||||
|
gitea = {
|
||||||
|
source = "go-gitea/gitea"
|
||||||
|
version = "0.7.0"
|
||||||
|
}
|
||||||
|
random = {
|
||||||
|
source = "hashicorp/random"
|
||||||
|
version = ">= 3.5"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
variable "username" {
|
||||||
|
description = "Username of the Gitea account (immutable; changing it recreates the user)"
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "login_name" {
|
||||||
|
description = "Login name, may differ from username. Defaults to the username."
|
||||||
|
type = string
|
||||||
|
default = null
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "email" {
|
||||||
|
description = "E-Mail address of the user"
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "full_name" {
|
||||||
|
description = "Display / full name of the user"
|
||||||
|
type = string
|
||||||
|
default = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "description" {
|
||||||
|
description = "Profile description of the user"
|
||||||
|
type = string
|
||||||
|
default = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "visibility" {
|
||||||
|
description = "Profile visibility: public, limited or private"
|
||||||
|
type = string
|
||||||
|
default = "limited"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "admin" {
|
||||||
|
description = "Whether the user is a site administrator"
|
||||||
|
type = bool
|
||||||
|
default = false
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "restricted" {
|
||||||
|
description = "Restricted users can only access repositories/orgs they are explicitly added to"
|
||||||
|
type = bool
|
||||||
|
default = false
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "active" {
|
||||||
|
description = "Whether the account is active"
|
||||||
|
type = bool
|
||||||
|
default = true
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "allow_create_organization" {
|
||||||
|
description = "Whether the user may create organisations"
|
||||||
|
type = bool
|
||||||
|
default = false
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "max_repo_creation" {
|
||||||
|
description = "Maximum number of repositories the user may create (0 disallows creation)"
|
||||||
|
type = number
|
||||||
|
default = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "must_change_password" {
|
||||||
|
description = "Force a password change on first login"
|
||||||
|
type = bool
|
||||||
|
default = false
|
||||||
|
}
|
||||||
@@ -9,5 +9,9 @@ terraform {
|
|||||||
source = "Kichiyaki/woodpecker"
|
source = "Kichiyaki/woodpecker"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
}
|
}
|
||||||
|
random = {
|
||||||
|
source = "hashicorp/random"
|
||||||
|
version = ">= 3.5"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,6 +64,25 @@ variable "branch_protection" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
variable "user" {
|
||||||
|
description = "Map of Gitea user accounts to create"
|
||||||
|
type = map(object({
|
||||||
|
username = string
|
||||||
|
email = string
|
||||||
|
login_name = optional(string)
|
||||||
|
full_name = optional(string, "")
|
||||||
|
description = optional(string, "")
|
||||||
|
visibility = optional(string, "limited")
|
||||||
|
admin = optional(bool, false)
|
||||||
|
restricted = optional(bool, false)
|
||||||
|
active = optional(bool, true)
|
||||||
|
allow_create_organization = optional(bool, false)
|
||||||
|
max_repo_creation = optional(number, 0)
|
||||||
|
must_change_password = optional(bool, false)
|
||||||
|
}))
|
||||||
|
default = {}
|
||||||
|
}
|
||||||
|
|
||||||
variable "team" {
|
variable "team" {
|
||||||
description = "Map of teams to create"
|
description = "Map of teams to create"
|
||||||
type = map(object({
|
type = map(object({
|
||||||
|
|||||||
Reference in New Issue
Block a user