Skip to content

Commit e44b70f

Browse files
fix(cli): sync inference org credentials on repo enrollment
Provision GCP secrets and region at org scope during install and refresh selected-repository access on admin enable/disable, matching FULLSEND_MINT_URL visibility for workflow_call dispatch from enrolled repos. Co-authored-by: Cursor <cursoragent@cursor.com> Signed-off-by: Barak Korren <bkorren@redhat.com>
1 parent 2b0cc38 commit e44b70f

4 files changed

Lines changed: 370 additions & 73 deletions

File tree

internal/cli/admin.go

Lines changed: 44 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1681,7 +1681,7 @@ func runUninstall(ctx context.Context, client forge.Client, printer *ui.Printer,
16811681
layers.NewConfigRepoLayer(org, client, emptyCfg, printer, false),
16821682
layers.NewWorkflowsLayer(org, client, printer, ""),
16831683
layers.NewSecretsLayer(org, client, nil, printer),
1684-
layers.NewInferenceLayer(org, client, nil, printer),
1684+
layers.NewInferenceLayer(org, client, nil, nil, printer),
16851685
dispatchLayer,
16861686
layers.NewEnrollmentLayer(org, client, nil, nil, printer),
16871687
)
@@ -1848,7 +1848,7 @@ func buildLayerStack(
18481848
layers.NewWorkflowsLayer(org, client, printer, user),
18491849
layers.NewVendorBinaryLayer(org, forge.ConfigRepoName, client, printer, vendorBinary, vendorFn),
18501850
layers.NewSecretsLayer(org, client, agentCreds, printer).WithOIDCMode(),
1851-
layers.NewInferenceLayer(org, client, inferenceProvider, printer),
1851+
layers.NewInferenceLayer(org, client, inferenceProvider, enrolledRepoIDs, printer),
18521852
dispatchLayer,
18531853
layers.NewEnrollmentLayer(org, client, enabledRepos, disabledRepos, printer),
18541854
)
@@ -2256,12 +2256,9 @@ func runEnableRepos(ctx context.Context, client forge.Client, printer *ui.Printe
22562256
}
22572257
}
22582258

2259-
// Sync org variable visibility so enrolled repos can read dispatch
2260-
// variables like FULLSEND_MINT_URL. Runs even when changed == 0 to
2261-
// reconcile a previously failed best-effort sync on re-run.
2262-
if cfg.Dispatch.Mode == "oidc-mint" {
2263-
syncOrgVariableVisibility(ctx, client, printer, org, cfg, allOrgRepos)
2264-
}
2259+
// Sync org secret/variable visibility for enrolled repos. Runs even when
2260+
// changed == 0 to reconcile a previously failed best-effort sync on re-run.
2261+
syncEnrolledRepoCredentialVisibility(ctx, client, printer, org, cfg, allOrgRepos)
22652262

22662263
if changed == 0 {
22672264
return nil
@@ -2281,17 +2278,11 @@ func runEnableRepos(ctx context.Context, client forge.Client, printer *ui.Printe
22812278
// dispatch layer, derived from the gcf provisioner to stay in sync automatically.
22822279
var dispatchOrgVariableNames = gcf.NewProvisioner(gcf.Config{}, nil).OrgVariableNames()
22832280

2284-
// syncOrgVariableVisibility updates the "selected" repository list for each
2285-
// dispatch org variable so that all currently enrolled repos (plus the config
2286-
// repo) can read them. This is best-effort: failures are logged as warnings
2287-
// but do not fail the enable command, because the repo-maintenance workflow
2288-
// can reconcile this later.
2289-
func syncOrgVariableVisibility(ctx context.Context, client forge.Client, printer *ui.Printer, org string, cfg *config.OrgConfig, allOrgRepos []forge.Repository) {
2290-
// Collect IDs for all enabled repos.
2281+
// enrolledRepoIDsForConfig returns GitHub repo IDs for all enabled repos plus
2282+
// the .fullsend config repo (which needs access to org-scoped credentials).
2283+
func enrolledRepoIDsForConfig(cfg *config.OrgConfig, allOrgRepos []forge.Repository) []int64 {
22912284
enrolledRepoIDs := collectEnrolledRepoIDs(allOrgRepos, cfg.EnabledRepos())
22922285

2293-
// Ensure the config repo (.fullsend) is included — it needs access
2294-
// to dispatch variables for its own workflows.
22952286
seen := make(map[int64]bool, len(enrolledRepoIDs))
22962287
for _, id := range enrolledRepoIDs {
22972288
seen[id] = true
@@ -2302,6 +2293,28 @@ func syncOrgVariableVisibility(ctx context.Context, client forge.Client, printer
23022293
break
23032294
}
23042295
}
2296+
return enrolledRepoIDs
2297+
}
2298+
2299+
// syncEnrolledRepoCredentialVisibility updates org-level dispatch variables and
2300+
// inference secrets/variables so only currently enrolled repos (plus .fullsend)
2301+
// can read them. Best-effort: failures are warnings, not command errors.
2302+
func syncEnrolledRepoCredentialVisibility(ctx context.Context, client forge.Client, printer *ui.Printer, org string, cfg *config.OrgConfig, allOrgRepos []forge.Repository) {
2303+
if cfg.Dispatch.Mode == "oidc-mint" {
2304+
syncOrgVariableVisibility(ctx, client, printer, org, cfg, allOrgRepos)
2305+
}
2306+
if cfg.Inference.Provider != "" {
2307+
syncInferenceCredentialVisibility(ctx, client, printer, org, cfg, allOrgRepos)
2308+
}
2309+
}
2310+
2311+
// syncOrgVariableVisibility updates the "selected" repository list for each
2312+
// dispatch org variable so that all currently enrolled repos (plus the config
2313+
// repo) can read them. This is best-effort: failures are logged as warnings
2314+
// but do not fail the enable command, because the repo-maintenance workflow
2315+
// can reconcile this later.
2316+
func syncOrgVariableVisibility(ctx context.Context, client forge.Client, printer *ui.Printer, org string, cfg *config.OrgConfig, allOrgRepos []forge.Repository) {
2317+
enrolledRepoIDs := enrolledRepoIDsForConfig(cfg, allOrgRepos)
23052318

23062319
for _, varName := range dispatchOrgVariableNames {
23072320
exists, checkErr := client.OrgVariableExists(ctx, org, varName)
@@ -2323,6 +2336,14 @@ func syncOrgVariableVisibility(ctx context.Context, client forge.Client, printer
23232336
}
23242337
}
23252338

2339+
// syncInferenceCredentialVisibility updates org-level inference secret and
2340+
// variable visibility for enrolled repos. Best-effort like dispatch sync.
2341+
func syncInferenceCredentialVisibility(ctx context.Context, client forge.Client, printer *ui.Printer, org string, cfg *config.OrgConfig, allOrgRepos []forge.Repository) {
2342+
repoIDs := enrolledRepoIDsForConfig(cfg, allOrgRepos)
2343+
layer := layers.NewInferenceLayer(org, client, nil, nil, printer)
2344+
layer.SyncEnrolledRepoAccess(ctx, repoIDs)
2345+
}
2346+
23262347
// runDisableRepos disables the specified repositories from fullsend enrollment.
23272348
func runDisableRepos(ctx context.Context, client forge.Client, printer *ui.Printer, org string, repos []string, all bool, yolo bool) error {
23282349
printer.Banner(Version())
@@ -2416,14 +2437,12 @@ func runDisableRepos(ctx context.Context, client forge.Client, printer *ui.Print
24162437
return err
24172438
}
24182439

2419-
// Sync org variable visibility to revoke access for disabled repos.
2420-
if cfg.Dispatch.Mode == "oidc-mint" {
2421-
allOrgRepos, listErr := client.ListOrgRepos(ctx, org)
2422-
if listErr != nil {
2423-
printer.StepWarn(fmt.Sprintf("could not list org repos for variable sync: %v", listErr))
2424-
} else {
2425-
syncOrgVariableVisibility(ctx, client, printer, org, cfg, allOrgRepos)
2426-
}
2440+
// Sync org secret/variable visibility to revoke access for disabled repos.
2441+
allOrgRepos, listErr := client.ListOrgRepos(ctx, org)
2442+
if listErr != nil {
2443+
printer.StepWarn(fmt.Sprintf("could not list org repos for credential sync: %v", listErr))
2444+
} else {
2445+
syncEnrolledRepoCredentialVisibility(ctx, client, printer, org, cfg, allOrgRepos)
24272446
}
24282447

24292448
printer.Blank()

internal/cli/admin_test.go

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -810,6 +810,38 @@ func TestRunEnableRepos_VariableSyncErrorDoesNotBlockEnable(t *testing.T) {
810810
require.NoError(t, err, "enable should succeed even when variable sync fails")
811811
}
812812

813+
func TestRunEnableRepos_SyncsInferenceOrgSecretsWhenConfigured(t *testing.T) {
814+
cfg := setupTestConfig(map[string]bool{
815+
"web-app": false,
816+
"api": true,
817+
})
818+
cfg.Dispatch.Mode = "oidc-mint"
819+
cfg.Inference.Provider = "vertex"
820+
821+
client := setupTestClient("testorg", cfg, []string{"web-app", "api"})
822+
client.Repos[0].ID = 1
823+
client.Repos[1].ID = 10
824+
client.Repos[2].ID = 20
825+
client.OrgSecrets = map[string]bool{
826+
"testorg/FULLSEND_GCP_WIF_PROVIDER": true,
827+
"testorg/FULLSEND_GCP_PROJECT_ID": true,
828+
}
829+
client.OrgVariables = map[string]bool{"testorg/FULLSEND_GCP_REGION": true}
830+
client.VariableValues["testorg/.fullsend/FULLSEND_GCP_REGION"] = "global"
831+
client.VariablesExist["testorg/.fullsend/FULLSEND_GCP_REGION"] = true
832+
833+
printer := ui.New(&discardWriter{})
834+
835+
err := runEnableRepos(context.Background(), client, printer, "testorg", []string{"web-app"}, false, true)
836+
require.NoError(t, err)
837+
838+
// Enabled repos: web-app + api + .fullsend
839+
wantIDs := []int64{10, 20, 1}
840+
assert.Equal(t, wantIDs, client.OrgSecretRepoIDs["testorg/FULLSEND_GCP_WIF_PROVIDER"])
841+
assert.Equal(t, wantIDs, client.OrgSecretRepoIDs["testorg/FULLSEND_GCP_PROJECT_ID"])
842+
assert.Equal(t, wantIDs, client.OrgVariableRepoIDs["testorg/FULLSEND_GCP_REGION"])
843+
}
844+
813845
func TestRunEnableRepos_SkipsVariableSyncWhenVariableNotExists(t *testing.T) {
814846
// When the org variable doesn't exist yet (mint not provisioned),
815847
// sync should skip gracefully.
@@ -1197,7 +1229,7 @@ func TestCheckInstallScopes_SyncWithLayers(t *testing.T) {
11971229
layers.NewConfigRepoLayer("test-org", nil, emptyCfg, ui.New(&discardWriter{}), false),
11981230
layers.NewWorkflowsLayer("test-org", nil, ui.New(&discardWriter{}), ""),
11991231
layers.NewSecretsLayer("test-org", nil, nil, ui.New(&discardWriter{})),
1200-
layers.NewInferenceLayer("test-org", nil, nil, ui.New(&discardWriter{})),
1232+
layers.NewInferenceLayer("test-org", nil, nil, nil, ui.New(&discardWriter{})),
12011233
layers.NewOIDCDispatchLayer("test-org", nil, nil, nil, ui.New(&discardWriter{})),
12021234
layers.NewEnrollmentLayer("test-org", nil, nil, nil, ui.New(&discardWriter{})),
12031235
layers.NewVendorBinaryLayer("test-org", ".fullsend", nil, ui.New(&discardWriter{}), false, nil),

0 commit comments

Comments
 (0)