Skip to content

Commit 7d0c3dc

Browse files
committed
refactor(repos): extract per-repo install logic into internal/repos package
Signed-off-by: Claude <noreply@anthropic.com> Signed-off-by: Greg Allen <gallen@redhat.com>
1 parent 6cfeae9 commit 7d0c3dc

4 files changed

Lines changed: 1420 additions & 58 deletions

File tree

internal/cli/admin.go

Lines changed: 168 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +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/scaffold"
32+
"github.com/fullsend-ai/fullsend/internal/repos"
3333
"github.com/fullsend-ai/fullsend/internal/ui"
3434
)
3535

@@ -655,35 +655,7 @@ func runPerRepoInstall(ctx context.Context, c perRepoInstallConfig) error {
655655
printer.StepWarn("Using provided WIF provider value — skipping inference provider auto-provisioning")
656656
}
657657

658-
cfg := config.NewPerRepoConfig(roles, repoFullName)
659-
if err := cfg.Validate(); err != nil {
660-
return fmt.Errorf("invalid config: %w", err)
661-
}
662-
663-
cfgYAML, err := cfg.Marshal()
664-
if err != nil {
665-
return fmt.Errorf("marshaling per-repo config: %w", err)
666-
}
667-
668658
upstreamRef, upstreamTag := resolveUpstreamRef()
669-
installFiles, err := scaffold.CollectPerRepoInstallFiles(vendor, upstreamRef, upstreamTag)
670-
if err != nil {
671-
return fmt.Errorf("collecting per-repo scaffold files: %w", err)
672-
}
673-
674-
var files []forge.TreeFile
675-
for _, f := range installFiles {
676-
files = append(files, forge.TreeFile{
677-
Path: f.Path,
678-
Content: f.Content,
679-
Mode: f.Mode,
680-
})
681-
}
682-
files = append(files, forge.TreeFile{
683-
Path: ".fullsend/config.yaml",
684-
Content: cfgYAML,
685-
Mode: "100644",
686-
})
687659

688660
needsWIFProvision := inferenceWIFProvider == ""
689661

@@ -823,7 +795,14 @@ func runPerRepoInstall(ctx context.Context, c perRepoInstallConfig) error {
823795
printer.StepInfo(fmt.Sprintf(" Repo restriction: %s/%s", owner, repo))
824796
printer.Blank()
825797
}
826-
for _, f := range files {
798+
dryRunFiles, dryRunErr := repos.BuildScaffoldFiles(repos.InstallConfig{
799+
Owner: owner, Repo: repo, Roles: roles,
800+
VendorBinary: vendor, UpstreamRef: upstreamRef, UpstreamTag: upstreamTag,
801+
})
802+
if dryRunErr != nil {
803+
return fmt.Errorf("generating scaffold files for dry run: %w", dryRunErr)
804+
}
805+
for _, f := range dryRunFiles {
827806
printer.StepDone(fmt.Sprintf("Would write: %s (%d bytes)", f.Path, len(f.Content)))
828807
}
829808
printer.Blank()
@@ -970,46 +949,125 @@ func runPerRepoInstall(ctx context.Context, c perRepoInstallConfig) error {
970949
printer.StepDone("Mint validated and org registered")
971950
}
972951

952+
// Delegate WIF provisioning, scaffold commit, and variable/secret
953+
// writes to the reusable repos.Install function. This enables the
954+
// future `fullsend repos install` command to share the same logic.
955+
var wifProvisioner repos.WIFProvisioner
973956
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)
957+
wifProvisioner = &gcfWIFAdapter{
958+
provisioner: gcf.NewProvisioner(gcf.Config{
959+
ProjectID: inferenceProject,
960+
GitHubOrgs: []string{owner},
961+
Repo: owner + "/" + repo,
962+
WIFPoolName: gcf.DefaultInferencePool,
963+
}, gcf.NewLiveGCFClient(inferenceProject)),
964+
}
965+
}
966+
967+
// Scaffold commit function wrapping layers.CommitScaffoldFiles, which
968+
// provides retry on non-fast-forward errors, branch-protection fallback
969+
// to PR delivery, and fork-based PR support for non-owner users.
970+
scaffoldCommitFn := func(ctx context.Context, owner, repo string, files []forge.TreeFile, direct bool) error {
971+
targetRepo, repoErr := client.GetRepo(ctx, owner, repo)
972+
if repoErr != nil {
973+
return fmt.Errorf("getting repo info: %w", repoErr)
974+
}
975+
commitMsg := fmt.Sprintf("chore: initialize fullsend-%s per-repo installation", version)
976+
prTitle := "chore: initialize fullsend per-repo installation"
977+
prBody := "This PR adds the fullsend scaffold files for per-repo installation.\n\n" +
978+
"Merge this PR to activate fullsend workflows."
979+
if direct {
980+
printer.StepStart(fmt.Sprintf("Committing scaffold files to %s/%s (%s branch)",
981+
owner, repo, targetRepo.DefaultBranch))
982+
} else {
983+
printer.StepStart(fmt.Sprintf("Creating scaffold PR for %s/%s (target: %s)",
984+
owner, repo, targetRepo.DefaultBranch))
986985
}
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")
986+
_, err := layers.CommitScaffoldFiles(ctx, client, printer, owner, repo,
987+
targetRepo.DefaultBranch, commitMsg, prTitle, prBody, files, direct, os.Stdin)
988+
return err
990989
}
991990

992-
repoVars := map[string]string{
993-
"FULLSEND_MINT_URL": mintURL,
994-
"FULLSEND_GCP_REGION": inferenceRegion,
995-
forge.PerRepoGuardVar: "true",
991+
installCfg := repos.InstallConfig{
992+
Owner: owner,
993+
Repo: repo,
994+
Roles: roles,
995+
MintURL: mintURL,
996+
InferenceProject: inferenceProject,
997+
InferenceRegion: inferenceRegion,
998+
UpstreamRef: upstreamRef,
999+
UpstreamTag: upstreamTag,
1000+
SkipMintCheck: true, // already handled above
1001+
SkipAppSetup: true, // already handled above
1002+
SkipGuardCheck: true, // admin.go handles guard check itself
1003+
SkipWIF: !needsWIFProvision,
1004+
WIFProvider: inferenceWIFProvider,
1005+
VendorBinary: vendor,
1006+
Direct: c.Direct,
1007+
SkipScaffoldAndConfig: vendor, // vendor path commits scaffold+vendor atomically below
1008+
}
1009+
1010+
progressFn := func(_ string, phase, msg string) {
1011+
switch phase {
1012+
case "wif":
1013+
if strings.Contains(msg, "Provisioning") {
1014+
printer.StepStart(msg)
1015+
} else if strings.Contains(msg, "ready") {
1016+
printer.StepDone(msg)
1017+
printer.StepInfo("IAM policy changes may take up to 7 minutes to propagate")
1018+
printer.StepInfo("Agent workflows that authenticate via WIF may fail until propagation completes")
1019+
}
1020+
case "scaffold":
1021+
if strings.Contains(msg, "Committing") || strings.Contains(msg, "Generating") {
1022+
printer.StepStart(msg)
1023+
} else {
1024+
printer.StepDone(msg)
1025+
}
1026+
case "vars":
1027+
if strings.Contains(msg, "Configuring") {
1028+
printer.StepStart(msg)
1029+
} else {
1030+
printer.StepDone(msg)
1031+
}
1032+
case "secrets":
1033+
if strings.Contains(msg, "Configuring") {
1034+
printer.StepStart(msg)
1035+
} else {
1036+
printer.StepDone(msg)
1037+
}
1038+
}
9961039
}
9971040

998-
repoSecrets := map[string]string{
999-
"FULLSEND_GCP_PROJECT_ID": inferenceProject,
1000-
"FULLSEND_GCP_WIF_PROVIDER": inferenceWIFProvider,
1041+
installResult, installErr := repos.Install(ctx, installCfg, client, wifProvisioner, scaffoldCommitFn, progressFn)
1042+
if installErr != nil {
1043+
return installErr
1044+
}
1045+
1046+
if installResult.WIFProvider != "" {
1047+
inferenceWIFProvider = installResult.WIFProvider
10011048
}
10021049

10031050
if vendor {
1004-
var vendorErr error
1005-
files, _, vendorErr = appendVendorTreeFiles(printer, owner, repo, files, vendor, fullsendBinary, fullsendSource)
1051+
scaffoldFiles, buildErr := repos.BuildScaffoldFiles(installCfg)
1052+
if buildErr != nil {
1053+
return fmt.Errorf("building scaffold files for vendor: %w", buildErr)
1054+
}
1055+
vendorFiles, _, vendorErr := appendVendorTreeFiles(printer, owner, repo, scaffoldFiles, vendor, fullsendBinary, fullsendSource)
10061056
if vendorErr != nil {
10071057
return fmt.Errorf("collecting vendored assets: %w", vendorErr)
10081058
}
1009-
}
1010-
1011-
if err := applyPerRepoScaffold(ctx, client, printer, owner, repo, files, repoVars, repoSecrets, c.Direct); err != nil {
1012-
return err
1059+
repoVars := map[string]string{
1060+
"FULLSEND_MINT_URL": mintURL,
1061+
"FULLSEND_GCP_REGION": inferenceRegion,
1062+
forge.PerRepoGuardVar: "true",
1063+
}
1064+
repoSecrets := map[string]string{
1065+
"FULLSEND_GCP_PROJECT_ID": inferenceProject,
1066+
"FULLSEND_GCP_WIF_PROVIDER": inferenceWIFProvider,
1067+
}
1068+
if err := applyPerRepoScaffold(ctx, client, printer, owner, repo, vendorFiles, repoVars, repoSecrets, c.Direct); err != nil {
1069+
return err
1070+
}
10131071
}
10141072

10151073
if !vendor {
@@ -1023,6 +1081,58 @@ func runPerRepoInstall(ctx context.Context, c perRepoInstallConfig) error {
10231081
return nil
10241082
}
10251083

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

0 commit comments

Comments
 (0)