Skip to content

Commit cca1fcc

Browse files
committed
Validate Forgejo migration candidates
1 parent f349f3d commit cca1fcc

8 files changed

Lines changed: 764 additions & 18 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
- Added best-effort live Datadog integration installation metadata collection.
2828
- Added a reproducible Datadog example refresh target and checked-in full example output.
2929
- Added fixture integration evidence refs and validation coverage for integration evidence.
30+
- Added Forgejo migration candidate validation for the GitHub Enterprise assessment path.
3031
- Updated CI and release workflows to Node.js 24-native GitHub Actions.
3132

3233
## 0.1.0 - 2026-05-24

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ Included in the current implementation:
116116
- OpenTelemetry Collector sketch.
117117
- ArgoCD starter manifest.
118118
- Typed migration plan manifest and phase-gate Markdown plan.
119-
- Validation report with embedded JSON Schema checks, Grafana dashboard, Prometheus alert, OpenTelemetry collector, and ArgoCD candidate checks, YAML/JSON parsing, evidence ref checks, secret scan, and optional `promtool`/`kubeconform` checks.
119+
- Validation report with embedded JSON Schema checks, Grafana dashboard, Prometheus alert, OpenTelemetry collector, ArgoCD, and Forgejo migration candidate checks, YAML/JSON parsing, evidence ref checks, secret scan, and optional `promtool`/`kubeconform` checks.
120120
- Evidence bundle export.
121121
- Evidence bundle path-safety checks that reject symlinks in exported project sections.
122122
- No-op assist provider and explicit opt-in LiteLLM assist.
@@ -158,7 +158,7 @@ The release checklist lives in `docs/release.md`. A release build should pass `m
158158

159159
## Assessment Paths
160160

161-
GitHub Enterprise to Forgejo collects repository, team, branch protection, Actions workflow, secret metadata, runner, deploy key, and GitHub App installation metadata from live GitHub/GitHub Enterprise APIs or local fixtures. It generates Forgejo migration assessment, CI compatibility, branch protection mapping, runner migration, repository ownership reports, and a Forgejo migration candidate YAML.
161+
GitHub Enterprise to Forgejo collects repository, team, branch protection, Actions workflow, secret metadata, runner, deploy key, and GitHub App installation metadata from live GitHub/GitHub Enterprise APIs or local fixtures. It generates Forgejo migration assessment, CI compatibility, branch protection mapping, runner migration, repository ownership reports, and a validated Forgejo migration candidate YAML.
162162

163163
Okta/Auth0 to Keycloak/Zitadel collects applications, SAML/OIDC client metadata, groups, policies, MFA settings, redirect URIs, owners, and break-glass account metadata from live Okta/Auth0 APIs or local fixtures. It generates identity migration risk, realm/client candidate config, break-glass, cutover, and rollback artifacts.
164164

docs/cli.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ The CI test suite also runs this definition-of-done pipeline against the fixture
2727

2828
The checked-in Datadog example can be refreshed with `make example VERSION=0.1.0-dev`. CI also runs `make example-smoke` as part of `make verify` to ensure the example fixture still completes the full pipeline.
2929

30-
`openexit validate` performs typed consistency checks, embedded JSON Schema validation, Grafana dashboard candidate validation, Prometheus alert-rule candidate validation, OpenTelemetry collector candidate validation, ArgoCD candidate validation, YAML/JSON parse checks, evidence reference checks, secret scanning, and optional external tool checks when `promtool` or `kubeconform` are installed.
30+
`openexit validate` performs typed consistency checks, embedded JSON Schema validation, Grafana dashboard candidate validation, Prometheus alert-rule candidate validation, OpenTelemetry collector candidate validation, ArgoCD candidate validation, Forgejo migration candidate validation, YAML/JSON parse checks, evidence reference checks, secret scanning, and optional external tool checks when `promtool` or `kubeconform` are installed.
3131

3232
`openexit export` refuses to package symlinks from exported project sections, even with `--force`, so evidence bundles cannot accidentally include files from outside the project tree.
3333

docs/github-enterprise-to-forgejo.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,6 @@ Generated target candidate:
4343

4444
- `generated-config/forgejo/migration-candidate.yaml`
4545

46+
Validation checks the Forgejo migration candidate for source/target metadata, non-production safety flags, absence of credentials, repository/team/branch-protection/workflow/secret/runner/deploy-key/app coverage, preserved evidence refs, expected runner/deploy-key target actions, and non-low manual-review findings.
47+
4648
The assessment flags branch protection gaps, GitHub-hosted runner dependency, GitHub-specific Actions usage, unknown secret consumers, offline runners, write-capable deploy keys, GitHub Pages/Packages/Discussions usage, and GitHub App webhook review.

docs/release.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ Release-blocking v0.1 requirements:
1616
- Markdown handover artifacts.
1717
- Grafana, Prometheus, OpenTelemetry, and ArgoCD candidate generators.
1818
- Typed migration plan manifest with assessment, pilot, shadow, and cutover phase gates.
19-
- Validation engine with embedded JSON Schema checks, Grafana dashboard, Prometheus alert, OpenTelemetry collector, and ArgoCD candidate checks, YAML/JSON parsing, evidence refs, secret scan, optional promtool, and optional kubeconform.
19+
- Validation engine with embedded JSON Schema checks, Grafana dashboard, Prometheus alert, OpenTelemetry collector, ArgoCD, and Forgejo migration candidate checks, YAML/JSON parsing, evidence refs, secret scan, optional promtool, and optional kubeconform.
2020
- Evidence bundle export with checksums and OpenExit version metadata.
2121
- No-op AI assist and optional external assist behind explicit opt-in.
2222
- Documentation, examples, CI, release draft workflow, and reproducible release artifacts.
@@ -51,6 +51,7 @@ The AI provider path is complete for local fixture assessment workflows and incl
5151
- [ ] `validation/validation-report.md` includes `prometheus-rule-candidates: passed` for the Datadog definition-of-done pipeline.
5252
- [ ] `validation/validation-report.md` includes `opentelemetry-candidate: passed` for the Datadog definition-of-done pipeline.
5353
- [ ] `validation/validation-report.md` includes `argocd-candidate: passed` for the Datadog definition-of-done pipeline.
54+
- [ ] `validation/validation-report.md` includes `forgejo-migration-candidate: passed` for the GitHub Enterprise to Forgejo pipeline.
5455
- [ ] `CHANGELOG.md` has a `0.1.0` section.
5556
- [ ] Exported bundle README includes version, commit, build date, bundle timestamp, and candidate warning.
5657
- [ ] Export refuses symlinks in exported project sections, including when `--force` is used.

internal/app/e2e_test.go

Lines changed: 79 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -413,20 +413,7 @@ func TestAssessRejectsMismatchedProjectTarget(t *testing.T) {
413413

414414
func TestGitHubEnterpriseForgejoFixturePipeline(t *testing.T) {
415415
projectDir := filepath.Join(t.TempDir(), "ghe-demo")
416-
fixturePath := filepath.Join("..", "..", "testdata", "github-enterprise", "small.json")
417-
418-
commands := [][]string{
419-
{"init", projectDir, "--source", "github-enterprise", "--target", "forgejo"},
420-
{"collect", "github-fixture", "--project", projectDir, "--input", fixturePath},
421-
{"assess", "--project", projectDir, "--target", "forgejo"},
422-
{"generate", "--project", projectDir, "--all"},
423-
{"validate", "--project", projectDir},
424-
}
425-
for _, args := range commands {
426-
if err := executeForTest(args...); err != nil {
427-
t.Fatalf("openexit %s failed: %v", strings.Join(args, " "), err)
428-
}
429-
}
416+
runGitHubEnterpriseFixturePipeline(t, projectDir)
430417
for _, rel := range []string{
431418
"inventory/openexit.inventory.yaml",
432419
"mapping/openexit.mapping.yaml",
@@ -464,6 +451,67 @@ func TestGitHubEnterpriseForgejoFixturePipeline(t *testing.T) {
464451
t.Fatalf("expected assessment finding %s", id)
465452
}
466453
}
454+
report, err := os.ReadFile(filepath.Join(projectDir, "validation", "validation-report.md"))
455+
if err != nil {
456+
t.Fatal(err)
457+
}
458+
if !strings.Contains(string(report), "forgejo-migration-candidate: passed") {
459+
t.Fatalf("expected Forgejo candidate validation marker, got:\n%s", string(report))
460+
}
461+
}
462+
463+
func TestValidationRejectsProductionReadyForgejoCandidate(t *testing.T) {
464+
projectDir := filepath.Join(t.TempDir(), "ghe-demo")
465+
runGitHubEnterpriseFixturePipeline(t, projectDir)
466+
candidatePath := filepath.Join(projectDir, "generated-config", "forgejo", "migration-candidate.yaml")
467+
data, err := os.ReadFile(candidatePath)
468+
if err != nil {
469+
t.Fatal(err)
470+
}
471+
corrupt := strings.Replace(string(data), "productionReady: false", "productionReady: true", 1)
472+
if corrupt == string(data) {
473+
t.Fatal("test fixture did not contain productionReady marker")
474+
}
475+
if err := os.WriteFile(candidatePath, []byte(corrupt), 0o644); err != nil {
476+
t.Fatal(err)
477+
}
478+
if err := executeForTest("validate", "--project", projectDir); err == nil {
479+
t.Fatal("expected validation to fail when Forgejo candidate is marked production ready")
480+
}
481+
report, err := os.ReadFile(filepath.Join(projectDir, "validation", "validation-report.md"))
482+
if err != nil {
483+
t.Fatal(err)
484+
}
485+
if !strings.Contains(string(report), "forgejo-migration-candidate") || !strings.Contains(string(report), "productionReady") {
486+
t.Fatalf("expected Forgejo candidate productionReady failure in validation report, got:\n%s", string(report))
487+
}
488+
}
489+
490+
func TestValidationRejectsBrokenForgejoRepositoryCandidate(t *testing.T) {
491+
projectDir := filepath.Join(t.TempDir(), "ghe-demo")
492+
runGitHubEnterpriseFixturePipeline(t, projectDir)
493+
candidatePath := filepath.Join(projectDir, "generated-config", "forgejo", "migration-candidate.yaml")
494+
data, err := os.ReadFile(candidatePath)
495+
if err != nil {
496+
t.Fatal(err)
497+
}
498+
corrupt := strings.Replace(string(data), "targetRepository: platform/api", "targetRepository: platform/api-drifted", 1)
499+
if corrupt == string(data) {
500+
t.Fatal("test fixture did not contain targetRepository metadata")
501+
}
502+
if err := os.WriteFile(candidatePath, []byte(corrupt), 0o644); err != nil {
503+
t.Fatal(err)
504+
}
505+
if err := executeForTest("validate", "--project", projectDir); err == nil {
506+
t.Fatal("expected validation to fail when Forgejo repository candidate drifts from inventory")
507+
}
508+
report, err := os.ReadFile(filepath.Join(projectDir, "validation", "validation-report.md"))
509+
if err != nil {
510+
t.Fatal(err)
511+
}
512+
if !strings.Contains(string(report), "forgejo-migration-candidate") || !strings.Contains(string(report), "targetRepository") {
513+
t.Fatalf("expected Forgejo candidate targetRepository failure in validation report, got:\n%s", string(report))
514+
}
467515
}
468516

469517
func TestIdentityFixturePipeline(t *testing.T) {
@@ -643,6 +691,23 @@ func TestExportRefusesInvalidProjectWithoutForce(t *testing.T) {
643691
}
644692
}
645693

694+
func runGitHubEnterpriseFixturePipeline(t *testing.T, projectDir string) {
695+
t.Helper()
696+
fixturePath := filepath.Join("..", "..", "testdata", "github-enterprise", "small.json")
697+
commands := [][]string{
698+
{"init", projectDir, "--source", "github-enterprise", "--target", "forgejo"},
699+
{"collect", "github-fixture", "--project", projectDir, "--input", fixturePath},
700+
{"assess", "--project", projectDir, "--target", "forgejo"},
701+
{"generate", "--project", projectDir, "--all"},
702+
{"validate", "--project", projectDir},
703+
}
704+
for _, args := range commands {
705+
if err := executeForTest(args...); err != nil {
706+
t.Fatalf("openexit %s failed: %v", strings.Join(args, " "), err)
707+
}
708+
}
709+
}
710+
646711
func executeForTest(args ...string) error {
647712
cmd := NewRootCommand()
648713
cmd.SetArgs(args)

0 commit comments

Comments
 (0)