Skip to content

Commit f3eea58

Browse files
committed
Phase 6 (continued): Port ado2gh inventory-report command + CSV generators
Add inventory-report command for ado2gh that generates CSV reports for ADO orgs, team projects, repos, and pipelines. Includes: - pkg/ado/csvgen.go: 4 CSV generator functions (orgs, team projects, repos, pipelines) with thousand-separator formatting, C#-compatible date/boolean formatting, and minimal mode support - pkg/ado/csvgen_test.go: 15+ tests covering all generators and edge cases - cmd/ado2gh/inventory_report.go: Command handler with proper flag validation, inspector setup, and sequential CSV generation - cmd/ado2gh/inventory_report_test.go: 3+ handler tests - pkg/ado/inspector.go: Added SetOrgFilter/GetOrgFilter methods for single-org inventory scoping - Wired into cmd/ado2gh/main.go All ADO-specific commands now complete (11 + 8 shared = 19 total).
1 parent c14c2e2 commit f3eea58

6 files changed

Lines changed: 1289 additions & 1 deletion

File tree

cmd/ado2gh/inventory_report.go

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"os"
6+
7+
"github.com/github/gh-gei/pkg/ado"
8+
"github.com/github/gh-gei/pkg/env"
9+
"github.com/github/gh-gei/pkg/logger"
10+
"github.com/spf13/cobra"
11+
)
12+
13+
// ---------------------------------------------------------------------------
14+
// Consumer-defined interfaces
15+
// ---------------------------------------------------------------------------
16+
17+
// inventoryInspector defines the inspector methods needed by inventory-report.
18+
type inventoryInspector interface {
19+
ado.CSVInspector
20+
SetOrgFilter(string)
21+
GetOrgFilter() string
22+
GetTeamProjectCount(ctx context.Context) (int, error)
23+
GetRepoCount(ctx context.Context) (int, error)
24+
GetPipelineCount(ctx context.Context) (int, error)
25+
}
26+
27+
// inventoryAPI is the ADO API interface for inventory-report.
28+
type inventoryAPI = ado.CSVAdoAPI
29+
30+
// ---------------------------------------------------------------------------
31+
// Args struct
32+
// ---------------------------------------------------------------------------
33+
34+
type inventoryReportArgs struct {
35+
adoOrg string
36+
adoPAT string
37+
minimal bool
38+
}
39+
40+
// ---------------------------------------------------------------------------
41+
// Command constructor (testable)
42+
// ---------------------------------------------------------------------------
43+
44+
func newInventoryReportCmd(
45+
ins inventoryInspector,
46+
api inventoryAPI,
47+
log *logger.Logger,
48+
writeFile func(string, string) error,
49+
) *cobra.Command {
50+
var a inventoryReportArgs
51+
52+
cmd := &cobra.Command{
53+
Use: "inventory-report",
54+
Short: "Generates several CSV files containing lists of ADO orgs, team projects, repos, and pipelines",
55+
Long: "Generates several CSV files containing lists of ADO orgs, team projects, repos, and pipelines. Useful for planning large migrations.\n" +
56+
"Note: Expects ADO_PAT env variable or --ado-pat option to be set.",
57+
RunE: func(cmd *cobra.Command, _ []string) error {
58+
return runInventoryReport(cmd.Context(), ins, api, log, a, writeFile)
59+
},
60+
}
61+
62+
cmd.Flags().StringVar(&a.adoOrg, "ado-org", "", "If not provided will iterate over all orgs that ADO_PAT has access to.")
63+
cmd.Flags().StringVar(&a.adoPAT, "ado-pat", "", "")
64+
cmd.Flags().BoolVar(&a.minimal, "minimal", false, "Significantly speeds up the generation of the CSV files by including the bare minimum info.")
65+
66+
return cmd
67+
}
68+
69+
// ---------------------------------------------------------------------------
70+
// Production command constructor
71+
// ---------------------------------------------------------------------------
72+
73+
func newInventoryReportCmdLive() *cobra.Command {
74+
var a inventoryReportArgs
75+
76+
cmd := &cobra.Command{
77+
Use: "inventory-report",
78+
Short: "Generates several CSV files containing lists of ADO orgs, team projects, repos, and pipelines",
79+
Long: "Generates several CSV files containing lists of ADO orgs, team projects, repos, and pipelines. Useful for planning large migrations.\n" +
80+
"Note: Expects ADO_PAT env variable or --ado-pat option to be set.",
81+
RunE: func(cmd *cobra.Command, _ []string) error {
82+
log := getLogger(cmd)
83+
envProv := env.New()
84+
85+
adoPAT := a.adoPAT
86+
if adoPAT == "" {
87+
adoPAT = envProv.ADOPAT()
88+
}
89+
90+
client := ado.NewClient("https://dev.azure.com", adoPAT, log)
91+
ins := ado.NewInspector(log, client)
92+
93+
writeFile := func(path, content string) error {
94+
return os.WriteFile(path, []byte(content), 0o600)
95+
}
96+
97+
return runInventoryReport(cmd.Context(), ins, client, log, a, writeFile)
98+
},
99+
}
100+
101+
cmd.Flags().StringVar(&a.adoOrg, "ado-org", "", "If not provided will iterate over all orgs that ADO_PAT has access to.")
102+
cmd.Flags().StringVar(&a.adoPAT, "ado-pat", "", "")
103+
cmd.Flags().BoolVar(&a.minimal, "minimal", false, "Significantly speeds up the generation of the CSV files by including the bare minimum info.")
104+
105+
return cmd
106+
}
107+
108+
// ---------------------------------------------------------------------------
109+
// Runner
110+
// ---------------------------------------------------------------------------
111+
112+
func runInventoryReport(
113+
ctx context.Context,
114+
ins inventoryInspector,
115+
api inventoryAPI,
116+
log *logger.Logger,
117+
a inventoryReportArgs,
118+
writeFile func(string, string) error,
119+
) error {
120+
log.Info("Creating inventory report...")
121+
122+
if a.adoOrg != "" {
123+
ins.SetOrgFilter(a.adoOrg)
124+
}
125+
126+
// Populate caches and log counts
127+
orgs, err := ins.GetOrgs(ctx)
128+
if err != nil {
129+
return err
130+
}
131+
log.Info("Found %d orgs", len(orgs))
132+
133+
tpCount, err := ins.GetTeamProjectCount(ctx)
134+
if err != nil {
135+
return err
136+
}
137+
log.Info("Found %d team projects", tpCount)
138+
139+
repoCount, err := ins.GetRepoCount(ctx)
140+
if err != nil {
141+
return err
142+
}
143+
log.Info("Found %d repos", repoCount)
144+
145+
pipelineCount, err := ins.GetPipelineCount(ctx)
146+
if err != nil {
147+
return err
148+
}
149+
log.Info("Found %d pipelines", pipelineCount)
150+
151+
// Generate CSVs
152+
orgsCsv, err := ado.GenerateOrgsCsv(ctx, ins, api, a.minimal)
153+
if err != nil {
154+
return err
155+
}
156+
157+
tpCsv, err := ado.GenerateTeamProjectsCsv(ctx, ins, api, a.minimal)
158+
if err != nil {
159+
return err
160+
}
161+
162+
reposCsv, err := ado.GenerateReposCsv(ctx, ins, api, a.minimal)
163+
if err != nil {
164+
return err
165+
}
166+
167+
pipelinesCsv, err := ado.GeneratePipelinesCsv(ctx, ins, api)
168+
if err != nil {
169+
return err
170+
}
171+
172+
// Write files
173+
files := map[string]string{
174+
"orgs.csv": orgsCsv,
175+
"team-projects.csv": tpCsv,
176+
"repos.csv": reposCsv,
177+
"pipelines.csv": pipelinesCsv,
178+
}
179+
180+
for name, content := range files {
181+
if err := writeFile(name, content); err != nil {
182+
return err
183+
}
184+
log.Info("Wrote %s", name)
185+
}
186+
187+
return nil
188+
}

0 commit comments

Comments
 (0)