generated from coulomb/repo-seed
feat: implement T16, T17 — Keycloak realm import transformer, LDIF generator
- T16: canonical → Keycloak realm JSON (profile-safe: no identity brokering, implicit flow always false) - T17: canonical → LDIF for openldap/389ds/ad targets with pre-validation 27 migration tests pass. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -3,11 +3,73 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"keycape/internal/migration/lldapexport"
|
||||
"keycape/internal/migration/tokeycloak"
|
||||
"keycape/internal/server/telemetry"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Fprintln(os.Stderr, "keycape-to-keycloak: not yet implemented (T06+)")
|
||||
os.Exit(1)
|
||||
inputFile := flag.String("input", "", "Path to canonical-export.yaml (required)")
|
||||
outputFile := flag.String("output", "keycloak-realm.json", "Path to write Keycloak realm import JSON")
|
||||
realmName := flag.String("realm", "netkingdom", "Keycloak realm name")
|
||||
issuer := flag.String("issuer", "", "OIDC issuer URL")
|
||||
flag.Parse()
|
||||
|
||||
if *inputFile == "" {
|
||||
fmt.Fprintln(os.Stderr, "keycape-to-keycloak: -input is required")
|
||||
flag.Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(*inputFile)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "keycape-to-keycloak: read %q: %v\n", *inputFile, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
var export lldapexport.ExportResult
|
||||
if err := yaml.Unmarshal(data, &export); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "keycape-to-keycloak: parse YAML: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
log := zerolog.New(os.Stderr).With().Timestamp().Logger()
|
||||
em := telemetry.NewLogEmitter(log)
|
||||
tr := tokeycloak.New(tokeycloak.Config{
|
||||
RealmName: *realmName,
|
||||
Issuer: *issuer,
|
||||
}, em)
|
||||
|
||||
realm, err := tr.Transform(&export)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "keycape-to-keycloak: transform: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Print validation report to stderr.
|
||||
report := tr.ValidationReport(&export, realm)
|
||||
for _, issue := range report {
|
||||
fmt.Fprintf(os.Stderr, "WARNING: %s\n", issue)
|
||||
}
|
||||
|
||||
out, err := json.MarshalIndent(realm, "", " ")
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "keycape-to-keycloak: marshal JSON: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(*outputFile, out, 0o644); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "keycape-to-keycloak: write %q: %v\n", *outputFile, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Printf("keycape-to-keycloak: wrote %s (%d bytes)\n", *outputFile, len(out))
|
||||
}
|
||||
|
||||
@@ -3,11 +3,79 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"keycape/internal/migration/lldapexport"
|
||||
"keycape/internal/migration/toldap"
|
||||
"keycape/internal/server/telemetry"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Fprintln(os.Stderr, "lldap-to-ldap: not yet implemented (T06+)")
|
||||
os.Exit(1)
|
||||
inputFile := flag.String("input", "", "Path to canonical-export.yaml (required)")
|
||||
outputFile := flag.String("output", "export.ldif", "Path to write LDIF output")
|
||||
baseDN := flag.String("basedn", "dc=netkingdom,dc=local", "LDAP base DN")
|
||||
targetStr := flag.String("target", "openldap", "LDAP target: openldap | 389ds | ad")
|
||||
flag.Parse()
|
||||
|
||||
if *inputFile == "" {
|
||||
fmt.Fprintln(os.Stderr, "lldap-to-ldap: -input is required")
|
||||
flag.Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
target, err := parseTarget(*targetStr)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "lldap-to-ldap: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(*inputFile)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "lldap-to-ldap: read %q: %v\n", *inputFile, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
var export lldapexport.ExportResult
|
||||
if err := yaml.Unmarshal(data, &export); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "lldap-to-ldap: parse YAML: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
log := zerolog.New(os.Stderr).With().Timestamp().Logger()
|
||||
em := telemetry.NewLogEmitter(log)
|
||||
gen := toldap.New(toldap.Config{
|
||||
BaseDN: *baseDN,
|
||||
Target: target,
|
||||
}, em)
|
||||
|
||||
ldif, err := gen.Generate(&export)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "lldap-to-ldap: generate: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(*outputFile, []byte(ldif), 0o644); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "lldap-to-ldap: write %q: %v\n", *outputFile, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Printf("lldap-to-ldap: wrote %s (%d bytes)\n", *outputFile, len(ldif))
|
||||
}
|
||||
|
||||
func parseTarget(s string) (toldap.Target, error) {
|
||||
switch s {
|
||||
case "openldap":
|
||||
return toldap.TargetOpenLDAP, nil
|
||||
case "389ds":
|
||||
return toldap.Target389DS, nil
|
||||
case "ad":
|
||||
return toldap.TargetAD, nil
|
||||
default:
|
||||
return "", fmt.Errorf("unknown target %q: must be one of openldap, 389ds, ad", s)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user