feat: implement T01-T04 — Go module, canonical model, LDAP validator, error taxonomy
- T01: Go module (keycape), full directory skeleton, Makefile, CI workflow
- T02: spec/canonical-model.yaml with 6 entities + Go domain types
- T03: spec/ldap-schema.yaml + validator binary with structural/semantic rules
- T04: Error taxonomy — 4 stable error types, JSON format, HTTP helpers
28 tests pass, go vet clean, go build clean.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 01:27:54 +01:00
|
|
|
// lldap-to-ldap migrates LLDAP directory data to standard LDAP (LDIF format).
|
|
|
|
|
// Part of the NetKingdom IAM migration contract.
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
|
|
import (
|
2026-03-13 02:13:04 +01:00
|
|
|
"flag"
|
feat: implement T01-T04 — Go module, canonical model, LDAP validator, error taxonomy
- T01: Go module (keycape), full directory skeleton, Makefile, CI workflow
- T02: spec/canonical-model.yaml with 6 entities + Go domain types
- T03: spec/ldap-schema.yaml + validator binary with structural/semantic rules
- T04: Error taxonomy — 4 stable error types, JSON format, HTTP helpers
28 tests pass, go vet clean, go build clean.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 01:27:54 +01:00
|
|
|
"fmt"
|
|
|
|
|
"os"
|
2026-03-13 02:13:04 +01:00
|
|
|
|
|
|
|
|
"github.com/rs/zerolog"
|
|
|
|
|
"gopkg.in/yaml.v3"
|
|
|
|
|
|
|
|
|
|
"keycape/internal/migration/lldapexport"
|
|
|
|
|
"keycape/internal/migration/toldap"
|
|
|
|
|
"keycape/internal/server/telemetry"
|
feat: implement T01-T04 — Go module, canonical model, LDAP validator, error taxonomy
- T01: Go module (keycape), full directory skeleton, Makefile, CI workflow
- T02: spec/canonical-model.yaml with 6 entities + Go domain types
- T03: spec/ldap-schema.yaml + validator binary with structural/semantic rules
- T04: Error taxonomy — 4 stable error types, JSON format, HTTP helpers
28 tests pass, go vet clean, go build clean.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 01:27:54 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func main() {
|
2026-03-13 02:13:04 +01:00
|
|
|
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)
|
|
|
|
|
}
|
feat: implement T01-T04 — Go module, canonical model, LDAP validator, error taxonomy
- T01: Go module (keycape), full directory skeleton, Makefile, CI workflow
- T02: spec/canonical-model.yaml with 6 entities + Go domain types
- T03: spec/ldap-schema.yaml + validator binary with structural/semantic rules
- T04: Error taxonomy — 4 stable error types, JSON format, HTTP helpers
28 tests pass, go vet clean, go build clean.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 01:27:54 +01:00
|
|
|
}
|