@@ -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.
10281137func applyPerRepoScaffold (ctx context.Context , client forge.Client , printer * ui.Printer ,
0 commit comments