Skip to content

Commit c1b1e2c

Browse files
committed
refactor(repos): extract per-repo install logic into internal/repos package
Move the core per-repo installation flow (guard check, WIF provisioning, scaffold commit, variable/secret writes) from internal/cli/admin.go into a reusable internal/repos package. This enables the future `fullsend repos install` command (ADR 0057) to share the same logic without duplicating the admin CLI's implementation. The refactored Install() function accepts an InstallConfig struct and a WIFProvisioner interface, decoupling the install logic from CLI concerns (spinners, prompts, flag parsing) and from the concrete GCF provisioner. admin.go delegates to repos.Install() via a gcfWIFAdapter that bridges the gcf.Provisioner to the WIFProvisioner interface. Test coverage on the new package is 89.2% (17 tests covering fresh install, guard check, WIF provisioning/failure, scaffold commit/failure, mint discovery, and PR-based delivery). Signed-off-by: Greg Allen <gallen@redhat.com> Signed-off-by: Claude <noreply@anthropic.com> Signed-off-by: Greg Allen <gallen@redhat.com>
1 parent 6cfeae9 commit c1b1e2c

3 files changed

Lines changed: 1044 additions & 26 deletions

File tree

internal/cli/admin.go

Lines changed: 135 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
"github.com/fullsend-ai/fullsend/internal/inference/vertex"
3030
"github.com/fullsend-ai/fullsend/internal/layers"
3131
"github.com/fullsend-ai/fullsend/internal/mintcore"
32+
"github.com/fullsend-ai/fullsend/internal/repos"
3233
"github.com/fullsend-ai/fullsend/internal/scaffold"
3334
"github.com/fullsend-ai/fullsend/internal/ui"
3435
)
@@ -970,46 +971,104 @@ func runPerRepoInstall(ctx context.Context, c perRepoInstallConfig) error {
970971
printer.StepDone("Mint validated and org registered")
971972
}
972973

974+
// Delegate WIF provisioning, scaffold commit, and variable/secret
975+
// writes to the reusable repos.Install function. This enables the
976+
// future `fullsend repos install` command to share the same logic.
977+
var wifProv repos.WIFProvisioner
973978
if needsWIFProvision {
974-
printer.StepStart("Provisioning WIF infrastructure")
975-
provisioner := gcf.NewProvisioner(gcf.Config{
976-
ProjectID: inferenceProject,
977-
GitHubOrgs: []string{owner},
978-
Repo: owner + "/" + repo,
979-
WIFPoolName: gcf.DefaultInferencePool,
980-
}, gcf.NewLiveGCFClient(inferenceProject))
981-
var provErr error
982-
inferenceWIFProvider, provErr = provisioner.ProvisionWIF(ctx)
983-
if provErr != nil {
984-
printer.StepFail("WIF provisioning failed")
985-
return fmt.Errorf("provisioning WIF: %w", provErr)
979+
wifProv = &gcfWIFAdapter{
980+
wifProvisioner: gcf.NewProvisioner(gcf.Config{
981+
ProjectID: inferenceProject,
982+
GitHubOrgs: []string{owner},
983+
Repo: owner + "/" + repo,
984+
WIFPoolName: gcf.DefaultInferencePool,
985+
}, gcf.NewLiveGCFClient(inferenceProject)),
986+
}
987+
}
988+
989+
installCfg := repos.InstallConfig{
990+
Owner: owner,
991+
Repo: repo,
992+
Roles: roles,
993+
MintURL: mintURL,
994+
InferenceProject: inferenceProject,
995+
InferenceRegion: inferenceRegion,
996+
UpstreamRef: upstreamRef,
997+
UpstreamTag: upstreamTag,
998+
SkipMintCheck: true, // already handled above
999+
SkipAppSetup: true, // already handled above
1000+
SkipGuardCheck: true, // admin.go handles guard check itself (lines 691-704)
1001+
SkipWIF: !needsWIFProvision,
1002+
WIFProvider: inferenceWIFProvider,
1003+
VendorBinary: vendor,
1004+
Direct: c.Direct,
1005+
}
1006+
1007+
progressFn := func(_ string, phase, msg string) {
1008+
switch phase {
1009+
case "wif":
1010+
if strings.Contains(msg, "Provisioning") {
1011+
printer.StepStart(msg)
1012+
} else if strings.Contains(msg, "ready") {
1013+
printer.StepDone(msg)
1014+
printer.StepInfo("IAM policy changes may take up to 7 minutes to propagate")
1015+
printer.StepInfo("Agent workflows that authenticate via WIF may fail until propagation completes")
1016+
}
1017+
case "scaffold":
1018+
if strings.Contains(msg, "Committing") || strings.Contains(msg, "Generating") {
1019+
printer.StepStart(msg)
1020+
} else {
1021+
printer.StepDone(msg)
1022+
}
1023+
case "vars":
1024+
if strings.Contains(msg, "Configuring") {
1025+
printer.StepStart(msg)
1026+
} else {
1027+
printer.StepDone(msg)
1028+
}
1029+
case "secrets":
1030+
if strings.Contains(msg, "Configuring") {
1031+
printer.StepStart(msg)
1032+
} else {
1033+
printer.StepDone(msg)
1034+
}
9861035
}
987-
printer.StepDone("WIF infrastructure ready")
988-
printer.StepInfo("IAM policy changes may take up to 7 minutes to propagate")
989-
printer.StepInfo("Agent workflows that authenticate via WIF may fail until propagation completes")
9901036
}
9911037

992-
repoVars := map[string]string{
993-
"FULLSEND_MINT_URL": mintURL,
994-
"FULLSEND_GCP_REGION": inferenceRegion,
995-
forge.PerRepoGuardVar: "true",
1038+
installResult, installErr := repos.Install(ctx, installCfg, client, wifProv, progressFn)
1039+
if installErr != nil {
1040+
return installErr
9961041
}
9971042

998-
repoSecrets := map[string]string{
999-
"FULLSEND_GCP_PROJECT_ID": inferenceProject,
1000-
"FULLSEND_GCP_WIF_PROVIDER": inferenceWIFProvider,
1043+
if installResult.WIFProvider != "" {
1044+
inferenceWIFProvider = installResult.WIFProvider
10011045
}
10021046

10031047
if vendor {
10041048
var vendorErr error
1049+
scaffoldFiles, buildErr := repos.BuildScaffoldFiles(installCfg)
1050+
if buildErr != nil {
1051+
return fmt.Errorf("building scaffold files for vendor: %w", buildErr)
1052+
}
1053+
files = scaffoldFiles
10051054
files, _, vendorErr = appendVendorTreeFiles(printer, owner, repo, files, vendor, fullsendBinary, fullsendSource)
10061055
if vendorErr != nil {
10071056
return fmt.Errorf("collecting vendored assets: %w", vendorErr)
10081057
}
1009-
}
1010-
1011-
if err := applyPerRepoScaffold(ctx, client, printer, owner, repo, files, repoVars, repoSecrets, c.Direct); err != nil {
1012-
return err
1058+
// Vendor files need a separate commit since they weren't included
1059+
// in the repos.Install scaffold commit.
1060+
repoVars := map[string]string{
1061+
"FULLSEND_MINT_URL": mintURL,
1062+
"FULLSEND_GCP_REGION": inferenceRegion,
1063+
forge.PerRepoGuardVar: "true",
1064+
}
1065+
repoSecrets := map[string]string{
1066+
"FULLSEND_GCP_PROJECT_ID": inferenceProject,
1067+
"FULLSEND_GCP_WIF_PROVIDER": inferenceWIFProvider,
1068+
}
1069+
if err := applyPerRepoScaffold(ctx, client, printer, owner, repo, files, repoVars, repoSecrets, c.Direct); err != nil {
1070+
return err
1071+
}
10131072
}
10141073

10151074
if !vendor {
@@ -1023,6 +1082,56 @@ func runPerRepoInstall(ctx context.Context, c perRepoInstallConfig) error {
10231082
return nil
10241083
}
10251084

1085+
// gcfWIFAdapter wraps a gcf.Provisioner to implement repos.WIFProvisioner.
1086+
type gcfWIFAdapter struct {
1087+
discoverer *gcf.Provisioner
1088+
wifProvisioner *gcf.Provisioner
1089+
}
1090+
1091+
func (a *gcfWIFAdapter) DiscoverMint(ctx context.Context) (*repos.MintDiscovery, error) {
1092+
prov := a.discoverer
1093+
if prov == nil {
1094+
return nil, repos.ErrMintNotFound
1095+
}
1096+
d, err := prov.DiscoverMint(ctx)
1097+
if err != nil {
1098+
return nil, err
1099+
}
1100+
return &repos.MintDiscovery{
1101+
URL: d.URL,
1102+
RoleAppIDs: d.RoleAppIDs,
1103+
PerRepoWIFRepos: d.PerRepoWIFRepos,
1104+
}, nil
1105+
}
1106+
1107+
func (a *gcfWIFAdapter) ProvisionWIF(ctx context.Context) (string, error) {
1108+
if a.wifProvisioner == nil {
1109+
return "", fmt.Errorf("WIF provisioner not configured")
1110+
}
1111+
return a.wifProvisioner.ProvisionWIF(ctx)
1112+
}
1113+
1114+
func (a *gcfWIFAdapter) RegisterPerRepoWIF(ctx context.Context, repo string) error {
1115+
if a.wifProvisioner == nil {
1116+
return fmt.Errorf("WIF provisioner not configured")
1117+
}
1118+
return a.wifProvisioner.RegisterPerRepoWIF(ctx, repo)
1119+
}
1120+
1121+
func (a *gcfWIFAdapter) EnsureOrgInMint(ctx context.Context, expectedURL string, org string) error {
1122+
if a.wifProvisioner == nil {
1123+
return fmt.Errorf("WIF provisioner not configured")
1124+
}
1125+
return a.wifProvisioner.EnsureOrgInMint(ctx, expectedURL, org)
1126+
}
1127+
1128+
func (a *gcfWIFAdapter) DeletePerRepoWIF(ctx context.Context, repo string) error {
1129+
if a.wifProvisioner == nil {
1130+
return fmt.Errorf("WIF provisioner not configured")
1131+
}
1132+
return a.wifProvisioner.RemoveRepoFromMint(ctx, repo)
1133+
}
1134+
10261135
// applyPerRepoScaffold commits scaffold files to the repo's default branch
10271136
// and configures the repository variables and secrets needed for fullsend.
10281137
func applyPerRepoScaffold(ctx context.Context, client forge.Client, printer *ui.Printer,

0 commit comments

Comments
 (0)