gitea-reverse-terraform_2025-10-13T12-40-00 #5

Merged
Trez.One merged 2 commits from gitea-reverse-terraform_2025-10-13T12-40-00 into main 2025-10-17 19:17:35 -04:00
6 changed files with 536 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
GITEA_TOKEN=""
+6
View File
@@ -0,0 +1,6 @@
// Generated by gitea-terraforming (OpenTofu compatible)
# source id=52
resource "gitea_organization" "org_trez" {
name = "Trez"
}
+121
View File
@@ -0,0 +1,121 @@
// Generated by gitea-terraforming (OpenTofu compatible)
# source id=5
resource "gitea_repository" "repo_trez_rinoa-docker" {
owner = "Trez"
name = "rinoa-docker"
private = false
description = ""
}
# source id=9
resource "gitea_repository" "repo_trez_meraki-naemon" {
owner = "Trez"
name = "meraki-naemon"
private = false
description = ""
}
# source id=13
resource "gitea_repository" "repo_trez_benedikta-ovos" {
owner = "Trez"
name = "benedikta-ovos"
private = false
description = ""
}
# source id=16
resource "gitea_repository" "repo_trez_rikku-home-assistant" {
owner = "Trez"
name = "rikku-home-assistant"
private = false
description = ""
}
# source id=17
resource "gitea_repository" "repo_trez_tar-valon-terraform" {
owner = "Trez"
name = "tar-valon-terraform"
private = true
description = ""
}
# source id=18
resource "gitea_repository" "repo_trez_hugo_it-services" {
owner = "Trez"
name = "hugo_it-services"
private = false
description = ""
}
# source id=19
resource "gitea_repository" "repo_trez_docker-mods-uptime-kuma-timeout-fix" {
owner = "Trez"
name = "docker-mods-uptime-kuma-timeout-fix"
private = false
description = "Documentation and Examples of base container modifications"
}
# source id=21
resource "gitea_repository" "repo_trez_tar-valon-ansible" {
owner = "Trez"
name = "tar-valon-ansible"
private = false
description = ""
}
# source id=22
resource "gitea_repository" "repo_trez_congo-hindi-gujarati" {
owner = "Trez"
name = "congo-hindi-gujarati"
private = false
description = "A powerful, lightweight theme for Hugo built with Tailwind CSS."
}
# source id=26
resource "gitea_repository" "repo_trez_action-home-assistant" {
owner = "Trez"
name = "action-home-assistant"
private = false
description = "🚀 Frenck's GitHub Action for running a Home Assistant Core configuration check"
}
# source id=27
resource "gitea_repository" "repo_trez_renovate-config" {
owner = "Trez"
name = "renovate-config"
private = false
description = ""
}
# source id=31
resource "gitea_repository" "repo_trez_hc-vault-env" {
owner = "Trez"
name = "hc-vault-env"
private = false
description = ""
}
# source id=32
resource "gitea_repository" "repo_trez_docker-select-image-pull" {
owner = "Trez"
name = "docker-select-image-pull"
private = false
description = ""
}
# source id=33
resource "gitea_repository" "repo_trez_gitea-auto-pr" {
owner = "Trez"
name = "gitea-auto-pr"
private = false
description = ""
}
# source id=34
resource "gitea_repository" "repo_trez_ultima-ai" {
owner = "Trez"
name = "ultima-ai"
private = true
description = ""
}
+274
View File
@@ -0,0 +1,274 @@
#!/usr/bin/env python3
"""
gitea-terraforming: Reverse Terraform for Gitea (OpenTofu compatible)
Generates Terraform HCL for:
- Users
- Organizations
- Repositories (user & org)
- Branch protections
Output files are automatically split per resource type:
gitea-<resource-type>.tf
Supports import generation:
- Modern import blocks (--modern-import-block)
- Shell script terraform import
Usage example:
python gitea_terraforming.py --api https://gitea.example.com --token <ADMIN_TOKEN> --out-dir ./gitea_tf
"""
import argparse, os, sys, time, json, re
from typing import Any, Dict, List, Optional
from datetime import datetime
import requests
def slugify(s: str) -> str:
s = re.sub(r'[^0-9a-zA-Z_-]', '_', s)
return re.sub('_+', '_', s).strip('_').lower()
class GiteaClient:
def __init__(self, api_base: str, token: str, verify: bool = True):
self.base = api_base.rstrip('/')
self.s = requests.Session()
self.s.headers.update({
'Authorization': f'token {token}',
'Accept': 'application/json',
'User-Agent': 'gitea-terraforming/0.1'
})
self.verify = verify
def _get(self, path: str, params: Optional[dict] = None):
url = f"{self.base}{path}"
out = []
page = 1
while True:
qp = params.copy() if params else {}
qp.update({'page': page, 'limit': 100})
resp = self.s.get(url, params=qp, verify=self.verify, timeout=30)
if resp.status_code == 404:
return []
if resp.status_code == 429:
retry = int(resp.headers.get('Retry-After', '5'))
time.sleep(retry)
continue
resp.raise_for_status()
data = resp.json()
if isinstance(data, list):
out.extend(data)
if len(data) < 100:
break
page += 1
else:
return data
return out
def list_orgs(self) -> List[dict]:
return self._get("/api/v1/orgs")
def list_users(self) -> List[dict]:
return self._get("/api/v1/admin/users")
def list_user_repos(self, user: str) -> List[dict]:
return self._get(f"/api/v1/users/{user}/repos")
def list_org_repos(self, org: str) -> List[dict]:
return self._get(f"/api/v1/orgs/{org}/repos")
def list_branch_protections(self, owner: str, repo: str) -> List[dict]:
return self._get(f"/api/v1/repos/{owner}/{repo}/branch_protections")
def hcl_block(resource_type: str, name: str, attrs: dict, comment: Optional[str] = None) -> str:
lines = []
if comment:
lines.append(f"# {comment}")
lines.append(f'resource "{resource_type}" "{name}" ' + "{")
for k, v in attrs.items():
if v is None:
continue
if isinstance(v, bool):
lines.append(f" {k} = {str(v).lower()}")
elif isinstance(v, (int, float)):
lines.append(f" {k} = {v}")
elif isinstance(v, str):
safe = v.replace('"', '\\"')
lines.append(f' {k} = "{safe}"')
elif isinstance(v, list):
joined = ", ".join([f'"{x}"' for x in v])
lines.append(f" {k} = [{joined}]")
else:
lines.append(f' # {k} = {json.dumps(v)}')
lines.append("}\n")
return "\n".join(lines)
def modern_import_block(to: str, ident: str) -> str:
return f'import {{\n to = {to}\n id = "{ident}"\n}}\n'
def generate(api: str, token: str, out_dir: str, modern: bool = False, dry: bool = False):
client = GiteaClient(api, token)
os.makedirs(out_dir, exist_ok=True)
imports = []
files: dict = {}
orgs = client.list_orgs()
org_buf = []
for o in orgs:
uname = o.get("username") or o.get("user_name") or o.get("name")
rname = f"org_{slugify(uname)}"
attrs = {"name": uname}
org_buf.append(hcl_block("gitea_organization", rname, attrs, comment=f"source id={o.get('id')}"))
imports.append((f"gitea_organization.{rname}", uname))
if org_buf:
files["orgs"] = "\n".join(org_buf)
users = []
try:
users = client.list_users()
except Exception as e:
print(f"Warning: cannot list users: {e}", file=sys.stderr)
user_buf = []
for u in users:
uname = u.get("login") or u.get("username")
if not uname:
continue
rname = f"user_{slugify(uname)}"
attrs = {
"username": uname,
"email": u.get("email"),
"full_name": u.get("full_name"),
"is_admin": u.get("is_admin")
}
user_buf.append(hcl_block("gitea_user", rname, attrs, comment=f"source id={u.get('id')}"))
imports.append((f"gitea_user.{rname}", uname))
if user_buf:
files["users"] = "\n".join(user_buf)
repo_buf = []
for u in users:
uname = u.get("login") or u.get("username")
if not uname:
continue
try:
repos = client.list_user_repos(uname)
except Exception:
repos = []
for r in repos:
rname = f"repo_{slugify(uname)}_{slugify(r['name'])}"
attrs = {
"owner": uname,
"name": r["name"],
"private": r.get("private", False),
"description": r.get("description")
}
repo_buf.append(hcl_block("gitea_repository", rname, attrs, comment=f"source id={r.get('id')}"))
imports.append((f"gitea_repository.{rname}", f"{uname}/{r['name']}"))
for o in orgs:
uname = o.get("username") or o.get("user_name") or o.get("name")
if not uname:
continue
try:
repos = client.list_org_repos(uname)
except Exception:
repos = []
for r in repos:
rname = f"repo_{slugify(uname)}_{slugify(r['name'])}"
attrs = {
"owner": uname,
"name": r["name"],
"private": r.get("private", False),
"description": r.get("description")
}
repo_buf.append(hcl_block("gitea_repository", rname, attrs, comment=f"source id={r.get('id')}"))
imports.append((f"gitea_repository.{rname}", f"{uname}/{r['name']}"))
if repo_buf:
files["repos"] = "\n".join(repo_buf)
bp_buf = []
for to, ident in imports:
if not to.startswith("gitea_repository."):
continue
owner, repo = ident.split("/", 1)
try:
bps = client.list_branch_protections(owner, repo)
except Exception:
bps = []
for bp in bps:
branch_name = bp.get("branch_name") or bp.get("branch") or bp.get("name")
if branch_name is None:
continue
bn = f"branch_protection_{slugify(owner)}_{slugify(repo)}_{slugify(branch_name)}"
attrs = {
"repository": repo,
"owner": owner,
"branch": branch_name,
"enable_status_check": bp.get("enable_status_check", False),
"required_approvals": bp.get("required_approvals", 0),
"enable_merge_whitelist": bp.get("enable_merge_whitelist", False),
}
bp_buf.append(hcl_block("gitea_branch_protection", bn, attrs, comment=f"protect branch {branch_name}"))
imports.append((f"gitea_branch_protection.{bn}", f"{owner}/{repo}/{branch_name}"))
if bp_buf:
files["branches"] = "\n".join(bp_buf)
for rtype, content in files.items():
fname = f"gitea-{rtype}.tf"
fpath = os.path.join(out_dir, fname)
if dry:
print(f"--- {fpath} ---")
print(content)
else:
with open(fpath, "w", encoding="utf-8") as f:
f.write(f"// Generated by gitea-terraforming (OpenTofu compatible)\n\n")
f.write(content)
print(f"Wrote {fname}", file=sys.stderr)
if modern:
imps = "\n".join([modern_import_block(to, ident) for to, ident in imports])
imppath = os.path.join(out_dir, "imports.tf")
if dry:
print("--- imports.tf ---")
print(imps)
else:
with open(imppath, "w", encoding="utf-8") as f:
f.write("// Import blocks\n\n")
f.write(imps)
print(f"Wrote imports.tf", file=sys.stderr)
else:
lines = ["#!/usr/bin/env bash", "set -euo pipefail"]
lines += [f'terraform import {to} "{ident}"' for to, ident in imports]
imppath = os.path.join(out_dir, "terraform_imports.sh")
if dry:
print("--- terraform_imports.sh ---")
print("\n".join(lines))
else:
with open(imppath, "w", encoding="utf-8") as f:
f.write("\n".join(lines) + "\n")
os.chmod(imppath, 0o755)
print(f"Wrote terraform_imports.sh", file=sys.stderr)
def main():
parser = argparse.ArgumentParser(
description="gitea-terraforming: Reverse Terraform for Gitea (OpenTofu compatible)\n\n"
"Generates Terraform HCL for users, organizations, repositories, and branch protections.\n"
"Output files are split per resource type and timestamped.\n\n"
"Example usage:\n"
" python gitea_terraforming.py --api https://gitea.example.com --token <ADMIN_TOKEN> --out-dir ./gitea_tf\n",
formatter_class=argparse.RawTextHelpFormatter
)
parser.add_argument("--api", required=True, help="Gitea API URL (e.g., https://gitea.example.com)")
parser.add_argument("--token", default=os.environ.get("GITEA_TOKEN"),
help="Gitea admin token (or set GITEA_TOKEN environment variable)")
parser.add_argument("--out-dir", default="./gitea_tf", help="Directory to write Terraform files")
parser.add_argument("--modern-import-block", action="store_true",
help="Generate modern OpenTofu import blocks (imports.tf) instead of shell script")
parser.add_argument("--dry-run", action="store_true", help="Print output instead of writing files")
args = parser.parse_args()
if not args.token:
parser.error("Missing Gitea token: provide --token or set GITEA_TOKEN environment variable")
generate(args.api, args.token, args.out_dir, modern=args.modern_import_block, dry=args.dry_run)
if __name__ == "__main__":
main()
+33
View File
@@ -0,0 +1,33 @@
// Generated by gitea-terraforming (OpenTofu compatible)
# source id=3
resource "gitea_user" "user_gitea-sonarqube-bot" {
username = "gitea-sonarqube-bot"
email = "trezone@vivaldi.net"
full_name = ""
is_admin = false
}
# source id=51
resource "gitea_user" "user_renovate-bot" {
username = "renovate-bot"
email = "charish2k1@gmail.com"
full_name = ""
is_admin = false
}
# source id=1
resource "gitea_user" "user_root" {
username = "root"
email = "noreply@trez.wtf"
full_name = ""
is_admin = true
}
# source id=2
resource "gitea_user" "user_trez_one" {
username = "Trez.One"
email = "charish.patel@trez.wtf"
full_name = ""
is_admin = false
}
+101
View File
@@ -0,0 +1,101 @@
// Import blocks
import {
to = gitea_organization.org_trez
id = "Trez"
}
import {
to = gitea_user.user_gitea-sonarqube-bot
id = "gitea-sonarqube-bot"
}
import {
to = gitea_user.user_renovate-bot
id = "renovate-bot"
}
import {
to = gitea_user.user_root
id = "root"
}
import {
to = gitea_user.user_trez_one
id = "Trez.One"
}
import {
to = gitea_repository.repo_trez_rinoa-docker
id = "Trez/rinoa-docker"
}
import {
to = gitea_repository.repo_trez_meraki-naemon
id = "Trez/meraki-naemon"
}
import {
to = gitea_repository.repo_trez_benedikta-ovos
id = "Trez/benedikta-ovos"
}
import {
to = gitea_repository.repo_trez_rikku-home-assistant
id = "Trez/rikku-home-assistant"
}
import {
to = gitea_repository.repo_trez_tar-valon-terraform
id = "Trez/tar-valon-terraform"
}
import {
to = gitea_repository.repo_trez_hugo_it-services
id = "Trez/hugo_it-services"
}
import {
to = gitea_repository.repo_trez_docker-mods-uptime-kuma-timeout-fix
id = "Trez/docker-mods-uptime-kuma-timeout-fix"
}
import {
to = gitea_repository.repo_trez_tar-valon-ansible
id = "Trez/tar-valon-ansible"
}
import {
to = gitea_repository.repo_trez_congo-hindi-gujarati
id = "Trez/congo-hindi-gujarati"
}
import {
to = gitea_repository.repo_trez_action-home-assistant
id = "Trez/action-home-assistant"
}
import {
to = gitea_repository.repo_trez_renovate-config
id = "Trez/renovate-config"
}
import {
to = gitea_repository.repo_trez_hc-vault-env
id = "Trez/hc-vault-env"
}
import {
to = gitea_repository.repo_trez_docker-select-image-pull
id = "Trez/docker-select-image-pull"
}
import {
to = gitea_repository.repo_trez_gitea-auto-pr
id = "Trez/gitea-auto-pr"
}
import {
to = gitea_repository.repo_trez_ultima-ai
id = "Trez/ultima-ai"
}