369 lines
11 KiB
Go
369 lines
11 KiB
Go
package k8s
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
"testing"
|
|
|
|
"k8s.io/apimachinery/pkg/util/yaml"
|
|
)
|
|
|
|
func toInt64(v interface{}) int64 {
|
|
switch n := v.(type) {
|
|
case int64:
|
|
return n
|
|
case float64:
|
|
return int64(n)
|
|
case int:
|
|
return int64(n)
|
|
default:
|
|
return 0
|
|
}
|
|
}
|
|
|
|
func forgejoYAMLPath() string {
|
|
_, thisFile, _, _ := runtime.Caller(0)
|
|
return filepath.Join(filepath.Dir(thisFile), "..", "..", "..", "..", "k8s", "dev-infra", "forgejo.yaml")
|
|
}
|
|
|
|
func readForgejoDocuments(t *testing.T) []map[string]interface{} {
|
|
t.Helper()
|
|
data, err := os.ReadFile(forgejoYAMLPath())
|
|
if err != nil {
|
|
t.Fatalf("reading forgejo.yaml: %v", err)
|
|
}
|
|
|
|
var docs []map[string]interface{}
|
|
parts := strings.Split(string(data), "\n---")
|
|
for _, part := range parts {
|
|
trimmed := strings.TrimSpace(part)
|
|
if trimmed == "" {
|
|
continue
|
|
}
|
|
// Strip leading comment lines (file-level comments before first doc)
|
|
lines := strings.Split(trimmed, "\n")
|
|
var contentLines []string
|
|
for _, line := range lines {
|
|
stripped := strings.TrimSpace(line)
|
|
if len(contentLines) == 0 && (stripped == "" || strings.HasPrefix(stripped, "#")) {
|
|
continue
|
|
}
|
|
contentLines = append(contentLines, line)
|
|
}
|
|
content := strings.Join(contentLines, "\n")
|
|
if content == "" {
|
|
continue
|
|
}
|
|
var doc map[string]interface{}
|
|
if err := yaml.NewYAMLOrJSONDecoder(strings.NewReader(content), 4096).Decode(&doc); err != nil {
|
|
t.Fatalf("decoding YAML document: %v\n---\n%s", err, content[:min(200, len(content))])
|
|
}
|
|
docs = append(docs, doc)
|
|
}
|
|
return docs
|
|
}
|
|
|
|
func findDoc(docs []map[string]interface{}, kind, name string) map[string]interface{} {
|
|
for _, doc := range docs {
|
|
if doc["kind"] == kind {
|
|
meta, ok := doc["metadata"].(map[string]interface{})
|
|
if ok && meta["name"] == name {
|
|
return doc
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func TestForgejoYAMLExists(t *testing.T) {
|
|
path := forgejoYAMLPath()
|
|
if _, err := os.Stat(path); os.IsNotExist(err) {
|
|
t.Fatalf("forgejo.yaml does not exist at %s", path)
|
|
}
|
|
}
|
|
|
|
func TestForgejoYAMLDocumentCount(t *testing.T) {
|
|
docs := readForgejoDocuments(t)
|
|
// Expected: Secret (admin), ConfigMap, PVC, Deployment, Service, IngressRoute, Middleware (basic-auth), Secret (htpasswd), Middleware (trailing-slash)
|
|
if len(docs) < 9 {
|
|
kinds := make([]string, len(docs))
|
|
for i, d := range docs {
|
|
kinds[i] = d["kind"].(string)
|
|
}
|
|
t.Errorf("expected at least 9 YAML documents, got %d: %v", len(docs), kinds)
|
|
}
|
|
}
|
|
|
|
func TestForgejoAdminSecret(t *testing.T) {
|
|
docs := readForgejoDocuments(t)
|
|
doc := findDoc(docs, "Secret", "forgejo-admin-secret")
|
|
if doc == nil {
|
|
t.Fatal("missing forgejo-admin-secret Secret")
|
|
}
|
|
meta := doc["metadata"].(map[string]interface{})
|
|
if meta["namespace"] != "dev-infra" {
|
|
t.Errorf("expected namespace dev-infra, got %v", meta["namespace"])
|
|
}
|
|
}
|
|
|
|
func TestForgejoConfigMap(t *testing.T) {
|
|
docs := readForgejoDocuments(t)
|
|
doc := findDoc(docs, "ConfigMap", "forgejo-config")
|
|
if doc == nil {
|
|
t.Fatal("missing forgejo-config ConfigMap")
|
|
}
|
|
|
|
meta := doc["metadata"].(map[string]interface{})
|
|
if meta["namespace"] != "dev-infra" {
|
|
t.Errorf("expected namespace dev-infra, got %v", meta["namespace"])
|
|
}
|
|
|
|
data := doc["data"].(map[string]interface{})
|
|
appIni, ok := data["app.ini"].(string)
|
|
if !ok {
|
|
t.Fatal("missing app.ini in ConfigMap data")
|
|
}
|
|
|
|
requiredSettings := []string{
|
|
"ROOT_URL = https://spinoff.dev/forgejo/",
|
|
"HTTP_PORT = 3000",
|
|
"DB_TYPE = sqlite3",
|
|
"ENABLED = true",
|
|
"DISABLE_REGISTRATION = true",
|
|
"OFFLINE_MODE = true",
|
|
"DEFAULT_BRANCH = main",
|
|
}
|
|
for _, s := range requiredSettings {
|
|
if !strings.Contains(appIni, s) {
|
|
t.Errorf("app.ini missing required setting: %s", s)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestForgejoPVC(t *testing.T) {
|
|
docs := readForgejoDocuments(t)
|
|
doc := findDoc(docs, "PersistentVolumeClaim", "forgejo-storage")
|
|
if doc == nil {
|
|
t.Fatal("missing forgejo-storage PVC")
|
|
}
|
|
|
|
meta := doc["metadata"].(map[string]interface{})
|
|
if meta["namespace"] != "dev-infra" {
|
|
t.Errorf("expected namespace dev-infra, got %v", meta["namespace"])
|
|
}
|
|
|
|
spec := doc["spec"].(map[string]interface{})
|
|
storageClass, ok := spec["storageClassName"].(string)
|
|
if !ok || storageClass != "longhorn" {
|
|
t.Errorf("expected storageClassName longhorn, got %v", storageClass)
|
|
}
|
|
|
|
resources := spec["resources"].(map[string]interface{})
|
|
requests := resources["requests"].(map[string]interface{})
|
|
if requests["storage"] != "5Gi" {
|
|
t.Errorf("expected 5Gi storage, got %v", requests["storage"])
|
|
}
|
|
}
|
|
|
|
func TestForgejoDeployment(t *testing.T) {
|
|
docs := readForgejoDocuments(t)
|
|
doc := findDoc(docs, "Deployment", "forgejo")
|
|
if doc == nil {
|
|
t.Fatal("missing forgejo Deployment")
|
|
}
|
|
|
|
meta := doc["metadata"].(map[string]interface{})
|
|
if meta["namespace"] != "dev-infra" {
|
|
t.Errorf("expected namespace dev-infra, got %v", meta["namespace"])
|
|
}
|
|
|
|
labels := meta["labels"].(map[string]interface{})
|
|
if labels["app"] != "forgejo" {
|
|
t.Errorf("expected label app=forgejo, got %v", labels["app"])
|
|
}
|
|
|
|
spec := doc["spec"].(map[string]interface{})
|
|
|
|
strategy := spec["strategy"].(map[string]interface{})
|
|
if strategy["type"] != "Recreate" {
|
|
t.Errorf("expected Recreate strategy, got %v", strategy["type"])
|
|
}
|
|
|
|
template := spec["template"].(map[string]interface{})
|
|
podSpec := template["spec"].(map[string]interface{})
|
|
|
|
t.Run("init_containers", func(t *testing.T) {
|
|
initContainers, ok := podSpec["initContainers"].([]interface{})
|
|
if !ok || len(initContainers) < 2 {
|
|
t.Fatal("expected at least 2 init containers (setup-config and create-admin)")
|
|
}
|
|
setupC := initContainers[0].(map[string]interface{})
|
|
if setupC["name"] != "setup-config" {
|
|
t.Errorf("expected first init container name setup-config, got %v", setupC["name"])
|
|
}
|
|
initC := initContainers[1].(map[string]interface{})
|
|
if initC["name"] != "create-admin" {
|
|
t.Errorf("expected second init container name create-admin, got %v", initC["name"])
|
|
}
|
|
image, _ := initC["image"].(string)
|
|
if !strings.HasPrefix(image, "codeberg.org/forgejo/forgejo:") {
|
|
t.Errorf("expected forgejo image, got %v", image)
|
|
}
|
|
})
|
|
|
|
t.Run("containers", func(t *testing.T) {
|
|
containers := podSpec["containers"].([]interface{})
|
|
if len(containers) != 2 {
|
|
t.Fatalf("expected 2 containers (forgejo + ipip-sidecar), got %d", len(containers))
|
|
}
|
|
|
|
forgejo := containers[0].(map[string]interface{})
|
|
if forgejo["name"] != "forgejo" {
|
|
t.Errorf("expected container name forgejo, got %v", forgejo["name"])
|
|
}
|
|
image, _ := forgejo["image"].(string)
|
|
if !strings.HasPrefix(image, "codeberg.org/forgejo/forgejo:") {
|
|
t.Errorf("expected forgejo image, got %v", image)
|
|
}
|
|
|
|
ports := forgejo["ports"].([]interface{})
|
|
if len(ports) != 1 {
|
|
t.Errorf("expected 1 port, got %d", len(ports))
|
|
}
|
|
port := ports[0].(map[string]interface{})
|
|
containerPort := toInt64(port["containerPort"])
|
|
if containerPort != 3000 {
|
|
t.Errorf("expected port 3000, got %v", port["containerPort"])
|
|
}
|
|
|
|
ipip := containers[1].(map[string]interface{})
|
|
if ipip["name"] != "ipip-sidecar" {
|
|
t.Errorf("expected container name ipip-sidecar, got %v", ipip["name"])
|
|
}
|
|
})
|
|
|
|
t.Run("volumes", func(t *testing.T) {
|
|
volumes := podSpec["volumes"].([]interface{})
|
|
if len(volumes) < 2 {
|
|
t.Fatalf("expected at least 2 volumes (storage + config), got %d", len(volumes))
|
|
}
|
|
volumeNames := make(map[string]bool)
|
|
for _, v := range volumes {
|
|
vol := v.(map[string]interface{})
|
|
volumeNames[vol["name"].(string)] = true
|
|
}
|
|
if !volumeNames["storage"] {
|
|
t.Error("missing storage volume")
|
|
}
|
|
if !volumeNames["config"] {
|
|
t.Error("missing config volume")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestForgejoService(t *testing.T) {
|
|
docs := readForgejoDocuments(t)
|
|
doc := findDoc(docs, "Service", "forgejo")
|
|
if doc == nil {
|
|
t.Fatal("missing forgejo Service")
|
|
}
|
|
|
|
meta := doc["metadata"].(map[string]interface{})
|
|
if meta["namespace"] != "dev-infra" {
|
|
t.Errorf("expected namespace dev-infra, got %v", meta["namespace"])
|
|
}
|
|
|
|
spec := doc["spec"].(map[string]interface{})
|
|
if spec["type"] != "ClusterIP" {
|
|
t.Errorf("expected ClusterIP, got %v", spec["type"])
|
|
}
|
|
|
|
ports := spec["ports"].([]interface{})
|
|
if len(ports) != 1 {
|
|
t.Fatalf("expected 1 port, got %d", len(ports))
|
|
}
|
|
port := ports[0].(map[string]interface{})
|
|
portNum := toInt64(port["port"])
|
|
if portNum != 3000 {
|
|
t.Errorf("expected port 3000, got %v", port["port"])
|
|
}
|
|
|
|
selector := spec["selector"].(map[string]interface{})
|
|
if selector["app"] != "forgejo" {
|
|
t.Errorf("expected selector app=forgejo, got %v", selector["app"])
|
|
}
|
|
}
|
|
|
|
func TestForgejoIngressRoute(t *testing.T) {
|
|
docs := readForgejoDocuments(t)
|
|
doc := findDoc(docs, "IngressRoute", "forgejo-ingress")
|
|
if doc == nil {
|
|
t.Fatal("missing forgejo-ingress IngressRoute")
|
|
}
|
|
|
|
meta := doc["metadata"].(map[string]interface{})
|
|
if meta["namespace"] != "dev-infra" {
|
|
t.Errorf("expected namespace dev-infra, got %v", meta["namespace"])
|
|
}
|
|
|
|
spec := doc["spec"].(map[string]interface{})
|
|
entryPoints := spec["entryPoints"].([]interface{})
|
|
if len(entryPoints) != 2 {
|
|
t.Errorf("expected 2 entryPoints (web, websecure), got %d", len(entryPoints))
|
|
}
|
|
|
|
routes := spec["routes"].([]interface{})
|
|
if len(routes) < 2 {
|
|
t.Fatalf("expected at least 2 routes (main + trailing-slash redirect), got %d", len(routes))
|
|
}
|
|
|
|
mainRoute := routes[0].(map[string]interface{})
|
|
matchRule, _ := mainRoute["match"].(string)
|
|
if !strings.Contains(matchRule, "PathPrefix(`/forgejo/`)") {
|
|
t.Errorf("expected PathPrefix /forgejo/ in main route, got %v", matchRule)
|
|
}
|
|
|
|
middlewares := mainRoute["middlewares"].([]interface{})
|
|
if len(middlewares) < 1 {
|
|
t.Fatal("expected at least 1 middleware on main route")
|
|
}
|
|
mw := middlewares[0].(map[string]interface{})
|
|
if mw["name"] != "forgejo-basic-auth" {
|
|
t.Errorf("expected forgejo-basic-auth middleware, got %v", mw["name"])
|
|
}
|
|
}
|
|
|
|
func TestForgejoBasicAuthMiddleware(t *testing.T) {
|
|
docs := readForgejoDocuments(t)
|
|
doc := findDoc(docs, "Middleware", "forgejo-basic-auth")
|
|
if doc == nil {
|
|
t.Fatal("missing forgejo-basic-auth Middleware")
|
|
}
|
|
|
|
meta := doc["metadata"].(map[string]interface{})
|
|
if meta["namespace"] != "dev-infra" {
|
|
t.Errorf("expected namespace dev-infra, got %v", meta["namespace"])
|
|
}
|
|
|
|
spec := doc["spec"].(map[string]interface{})
|
|
basicAuth := spec["basicAuth"].(map[string]interface{})
|
|
if basicAuth["secret"] != "forgejo-basic-auth-secret" {
|
|
t.Errorf("expected secret forgejo-basic-auth-secret, got %v", basicAuth["secret"])
|
|
}
|
|
}
|
|
|
|
func TestForgejoTrailingSlashMiddleware(t *testing.T) {
|
|
docs := readForgejoDocuments(t)
|
|
doc := findDoc(docs, "Middleware", "forgejo-add-trailing-slash")
|
|
if doc == nil {
|
|
t.Fatal("missing forgejo-add-trailing-slash Middleware")
|
|
}
|
|
|
|
spec := doc["spec"].(map[string]interface{})
|
|
redirect := spec["redirectRegex"].(map[string]interface{})
|
|
if redirect["permanent"] != true {
|
|
t.Error("expected permanent redirect")
|
|
}
|
|
}
|