diff --git a/docs/cli/repos.md b/docs/cli/repos.md index 3c3c8b9863..f43d0e74f4 100644 --- a/docs/cli/repos.md +++ b/docs/cli/repos.md @@ -256,7 +256,7 @@ fullsend repos set-default forge.github.mint_url "" # removes the key |-----|------|-------------| | `defaults.allowed_remote_resources` | comma-separated URLs | HTTPS URLs agents may fetch at runtime | | `forge.github.url` | URL | GitHub instance URL (default: `https://github.com`) | -| `forge.github.mint_url` | URL | Cloud Run endpoint URL for the token mint (defaults to `https://mint.fullsend.sh` in public mode) | +| `forge.github.mint_url` | URL | Token mint service URL (defaults to `https://mint.fullsend.sh` in public mode) | | `forge.github.mint_mode` | `public` or `private` | Controls the default mint URL: `public` defaults to `https://mint.fullsend.sh`; `private` requires an explicit `mint_url` (default: `public`) | | `forge.github.fullsend_ref` | ref string | Git ref to pin in scaffold workflow YAML | | `forge.gitlab.url` | URL | GitLab instance URL | diff --git a/docs/guides/getting-started/README.md b/docs/guides/getting-started/README.md index e7f71a0af7..7971bc2bf1 100644 --- a/docs/guides/getting-started/README.md +++ b/docs/guides/getting-started/README.md @@ -7,20 +7,6 @@ sidebar_position: 1 This section contains easy and to the point guides to help you set up Fullsend. These are intended to be read in a certain order: -1. **Mint enrollment** — before configuring anything, your org or repo - must be enrolled in a fullsend token mint service so the mint - accepts token requests from your GitHub Actions workflows. - The CLI defaults to the hosted mint. To enroll, contact the - fullsend team in the internal Slack channel with your GitHub org - name (for org mode) or `owner/repo` (for per-repo mode). To deploy - and manage your own mint instead, see the - [Mint administration](../infrastructure/mint-administration.md) guide. - - > **Note:** Self-service enrollment is not yet available. - > [Public mint mode](https://github.com/fullsend-ai/fullsend/pull/1580) - > will remove the need for per-org/repo enrollment, but is still - > in progress. - -2. [Getting Inference](getting-inference.md) -3. [Configuring GitHub](configuring-github.md) -4. [Organization Mode](org-mode.md) +1. [Getting Inference](getting-inference.md) +2. [Configuring GitHub](configuring-github.md) +3. [Organization Mode](org-mode.md) diff --git a/docs/guides/getting-started/configuring-github.md b/docs/guides/getting-started/configuring-github.md index c4dea0c860..1c3371f2cd 100644 --- a/docs/guides/getting-started/configuring-github.md +++ b/docs/guides/getting-started/configuring-github.md @@ -8,7 +8,6 @@ The goal of this document is that you configure Fullsend for your GitHub reposit ## Prerequisites -* Your org or repo is enrolled in a fullsend token mint service (see [Getting Started](README.md) step 1). * You have your WIF provider URL from [Getting Inference](getting-inference.md). * Download the latest [fullsend](https://github.com/fullsend-ai/fullsend/releases) CLI. * Download the latest [gh](https://cli.github.com/) CLI and authenticate with it. diff --git a/docs/guides/getting-started/operations.md b/docs/guides/getting-started/operations.md index 1e7c67b5f5..dfa7c2e113 100644 --- a/docs/guides/getting-started/operations.md +++ b/docs/guides/getting-started/operations.md @@ -69,7 +69,7 @@ To remove fullsend from a single repository: 1. Delete `.github/workflows/fullsend.yaml` and repo-level secrets/variables 2. Run `fullsend inference deprovision "$OWNER/$REPO"` to remove WIF access -3. Contact the fullsend team to unenroll the repo from the hosted mint +3. Remove the `FULLSEND_MINT_URL` repository variable (if set) — no separate unenrollment is needed for the hosted community mint **GitLab repos:** @@ -78,7 +78,7 @@ To remove fullsend from a single repository: 3. Revoke the `fullsend-bot` project access token (Settings → Access Tokens) 4. Delete fullsend pipeline schedules (`fullsend slash poll` and `fullsend event poll`) -If you manage your own self-hosted mint, run `fullsend mint unenroll "$OWNER/$REPO"` instead of GitHub step 3. See the [standalone commands](#standalone-commands) table for details. +If you manage your own self-hosted mint, run `fullsend mint unenroll "$OWNER/$REPO"` to remove the repo from the mint's allowlist. See the [standalone commands](#standalone-commands) table for details. ## Standalone commands @@ -115,7 +115,7 @@ For organizations that separate GCP and GitHub responsibilities across teams, fu | Developer | `fullsend agent update [sha]` | Re-pin a URL agent to a new commit SHA | | Developer | `fullsend agent remove ` | Unregister an agent from config | -The typical handoff: a GCP admin runs `mint deploy` + `mint enroll` + `inference provision`, then passes the mint URL and WIF provider resource name to a GitHub maintainer who runs `github setup --mint-url=... --inference-wif-provider=...`. +The typical handoff for self-managed mints: a GCP admin runs `mint deploy` + `mint enroll` + `inference provision`, then passes the mint URL and WIF provider resource name to a GitHub maintainer who runs `github setup --mint-url=... --inference-wif-provider=...`. For the hosted community mint, enrollment is automatic — install the shared Apps and use the CLI defaults. ### Per-command IAM role breakdown diff --git a/docs/guides/getting-started/repo-management.md b/docs/guides/getting-started/repo-management.md index 30c687239e..f1e5e0af4d 100644 --- a/docs/guides/getting-started/repo-management.md +++ b/docs/guides/getting-started/repo-management.md @@ -19,7 +19,7 @@ fullsend across an organization. Individual repo owners should use - **fullsend CLI** installed (see [releases](https://github.com/fullsend-ai/fullsend/releases)) - **GitHub access** — admin or write access to the target repositories - **`gh` CLI** authenticated with the required OAuth scopes (see [OAuth scope reference](../infrastructure/advanced-setup.md#oauth-scope-reference)) -- **GCP prerequisites** — GCP WIF provisioning (`fullsend inference provision`) and mint enrollment (`fullsend mint enroll`, GitHub only) must be completed separately before running `repos install`. When multiple repos share the same GCP project, existing inference secrets are reused automatically. See [Mint administration](../infrastructure/mint-administration.md) and [Advanced setup](../infrastructure/advanced-setup.md). +- **GCP prerequisites** — GCP WIF provisioning (`fullsend inference provision`) must be completed separately before running `repos install`. For self-managed mints, mint enrollment (`fullsend mint enroll`) is also required. The hosted community mint needs no enrollment — install the shared Apps and use the CLI defaults. When multiple repos share the same GCP project, existing inference secrets are reused automatically. See [Mint administration](../infrastructure/mint-administration.md) and [Advanced setup](../infrastructure/advanced-setup.md). ## Getting started @@ -139,9 +139,10 @@ Install runs in three phases: drift (synced automatically) and scaffold ref drift (upgraded automatically). -> **Prerequisite:** GCP infrastructure (WIF pools/providers, mint -> enrollment) must be provisioned separately before running install. -> See `fullsend inference provision` and `fullsend mint enroll`. +> **Prerequisite:** GCP WIF provisioning (`fullsend inference provision`) +> must be completed before running install. For self-managed mints, +> also run `fullsend mint enroll`. The hosted community mint needs no +> enrollment. > **Note:** When your token does not have direct push access to a target > repository, the install command creates a fork and submits the scaffold @@ -448,7 +449,7 @@ infrastructure, coordinate between roles: |------|------|---------| | 1 | Platform Admin | `fullsend repos uninstall "org/*" --yes` (forge-side cleanup + manifest removal) | | 2 | GCP Admin (Inference) | `fullsend inference deprovision ` (WIF cleanup) | -| 3 | GCP Admin (Mint) | `fullsend mint unenroll ` | +| 3 | GCP Admin (Mint) | `fullsend mint unenroll ` (self-hosted mints only; not needed for the hosted community mint) | Each `fullsend` command that prompts for confirmation accepts a skip flag: `--yes` for `repos` commands, `--yolo` for `github` and `mint` diff --git a/docs/guides/infrastructure/mint-administration.md b/docs/guides/infrastructure/mint-administration.md index 1b5310132c..c705b50014 100644 --- a/docs/guides/infrastructure/mint-administration.md +++ b/docs/guides/infrastructure/mint-administration.md @@ -20,7 +20,7 @@ This guide covers deploying and managing the fullsend token mint. The mint is th ## Hosted mint -The fullsend team operates a public hosted mint service. If your organization is enrolled, you can use it directly without deploying your own. +The fullsend team operates a public hosted community mint service. Install the shared public GitHub Apps and use the CLI defaults — no separate enrollment step is required. **Platform GCP project:** The hosted mint currently runs in GCP project `it-gcp-konflux-dev-fullsend` (region `us-central1`). @@ -30,7 +30,7 @@ The fullsend team operates a public hosted mint service. If your organization is https://mint.fullsend.sh ``` -Pass this URL as `--mint-url` when running `fullsend github setup`, or set the `FULLSEND_MINT_URL` repository/org variable in GitHub. If you are using the hosted mint, the rest of this guide (deploying, enrolling, troubleshooting) is handled by the fullsend team — you do not need to manage mint infrastructure yourself. +The CLI defaults to this URL. You can also set the `FULLSEND_MINT_URL` repository/org variable in GitHub explicitly. If you are using the hosted community mint, the rest of this guide (deploying, enrolling, troubleshooting) is for platform operators managing self-hosted mints — you do not need to manage mint infrastructure yourself. ## Prerequisites diff --git a/docs/guides/infrastructure/standalone-mint.md b/docs/guides/infrastructure/standalone-mint.md index 1f39eaba0b..25ccf5fb15 100644 --- a/docs/guides/infrastructure/standalone-mint.md +++ b/docs/guides/infrastructure/standalone-mint.md @@ -32,7 +32,7 @@ For roles without a local PEM, the optional fallback proxy forwards the request - **Go 1.26+** to build the binary (or use a pre-built release) - **A GitHub organization** where you will install your custom GitHub Apps - **The hosted mint URL** (optional, for fallback proxy): `https://mint.fullsend.sh` -- **Your organization enrolled in the hosted mint** (optional, for fallback proxy) — see [Mint service administration](mint-administration.md) +- **Shared public GitHub Apps installed** (optional, for fallback proxy) — see [Getting Started](../getting-started/) ## Step 1: Create a GitHub App @@ -264,7 +264,7 @@ When `FALLBACK_MINT_URL` is set, the standalone mint acts as a transparent proxy | `GET /health` | Always handled locally | | `GET /v1/status` | Always handled locally | -The proxy forwards the original OIDC bearer token and request body to the upstream mint, and returns the upstream response verbatim. The upstream mint performs its own OIDC validation — your organization must be enrolled on the upstream mint for proxied requests to succeed. +The proxy forwards the original OIDC bearer token and request body to the upstream mint, and returns the upstream response verbatim. The upstream mint performs its own OIDC validation — the shared public GitHub Apps must be installed in your organization for proxied requests to succeed. When `FALLBACK_MINT_URL` is not set, requests for roles without local PEMs are rejected with a `403 Forbidden` response. diff --git a/e2e/admin/admin_test.go b/e2e/admin/admin_test.go index 8d039e5f93..f7a94cee1c 100644 --- a/e2e/admin/admin_test.go +++ b/e2e/admin/admin_test.go @@ -93,7 +93,7 @@ func TestAdminInstallUninstall(t *testing.T) { "admin", "install", env.org, "--skip-app-setup", "--skip-mint-check", - "--mint-url", env.cfg.MintURL, + "--mint-url", e2etest.DefaultPoolOrgInstallMintURL, "--app-set", e2eAppSet, "--enroll-all", "--vendor", @@ -898,7 +898,7 @@ func TestVendorFromSubdirectory(t *testing.T) { "admin", "install", env.org, "--skip-app-setup", "--skip-mint-check", - "--mint-url", env.cfg.MintURL, + "--mint-url", e2etest.DefaultPoolOrgInstallMintURL, "--app-set", e2eAppSet, "--enroll-none", "--vendor", diff --git a/internal/cli/admin.go b/internal/cli/admin.go index 320a5a1e34..a2758cf9cc 100644 --- a/internal/cli/admin.go +++ b/internal/cli/admin.go @@ -38,7 +38,7 @@ import ( // DefaultMintURL is the hosted public mint URL used when --mint-url is not // explicitly provided. Users who self-host a mint can override this via // the --mint-url flag. -const DefaultMintURL = "https://fullsend-mint-gljhbkcloq-uc.a.run.app" +const DefaultMintURL = "https://mint.fullsend.sh" // adminMintDiscovery holds the results of a mint infrastructure discovery call. type adminMintDiscovery struct { @@ -197,6 +197,17 @@ func validateWIFProvider(raw string) error { return nil } +// IsHostedMintURL reports whether raw is the hosted community mint URL +// (mint.fullsend.sh). This check is also used by pkg/e2etest to resolve +// the GCP project for hosted-mint enrollment. +func IsHostedMintURL(raw string) bool { + parsed, err := url.Parse(raw) + if err != nil { + return false + } + return strings.EqualFold(parsed.Hostname(), "mint.fullsend.sh") +} + func validateMintURL(raw string) error { if err := validateMintURLHTTPS(raw); err != nil { return err @@ -206,11 +217,12 @@ func validateMintURL(raw string) error { return err } host := parsed.Hostname() - if !strings.HasSuffix(host, ".run.app") && - !strings.HasSuffix(host, ".cloudfunctions.net") { - return fmt.Errorf("--mint-url must be a Cloud Run URL (.run.app or .cloudfunctions.net), got host %q", host) + if strings.EqualFold(host, "mint.fullsend.sh") || + strings.HasSuffix(host, ".run.app") || + strings.HasSuffix(host, ".cloudfunctions.net") { + return nil } - return nil + return fmt.Errorf("--mint-url must be mint.fullsend.sh or a Cloud Run URL (.run.app or .cloudfunctions.net), got host %q", host) } func validateSkipMintCheck(mintURL string) error { diff --git a/internal/cli/admin_test.go b/internal/cli/admin_test.go index d75839f0ae..552d81f80c 100644 --- a/internal/cli/admin_test.go +++ b/internal/cli/admin_test.go @@ -191,7 +191,7 @@ func TestInstallCmd_PerRepoRejectsNonCloudRunMintURL(t *testing.T) { cmd.SetArgs([]string{"admin", "install", "acme/widget", "--mint-url", "https://evil.example.com", "--inference-project", "my-project"}) err := cmd.Execute() require.Error(t, err) - assert.Contains(t, err.Error(), "--mint-url must be a Cloud Run URL") + assert.Contains(t, err.Error(), "--mint-url must be mint.fullsend.sh or a Cloud Run URL") } func TestInstallCmd_PerRepoRejectsPerOrgFlags(t *testing.T) { @@ -1780,6 +1780,45 @@ func TestValidateSkipMintCheck(t *testing.T) { require.NoError(t, validateSkipMintCheck("https://mint.example.com/v1/token")) } +func TestValidateMintURL_AcceptsHostedCommunityMint(t *testing.T) { + require.NoError(t, validateMintURL("https://mint.fullsend.sh")) +} + +func TestValidateMintURL_AcceptsCloudRunURL(t *testing.T) { + require.NoError(t, validateMintURL("https://fullsend-mint-abc123.run.app")) +} + +func TestValidateMintURL_AcceptsCloudFunctionsURL(t *testing.T) { + require.NoError(t, validateMintURL("https://us-central1-my-project.cloudfunctions.net")) +} + +func TestValidateMintURL_RejectsArbitraryHosts(t *testing.T) { + err := validateMintURL("https://evil.example.com") + require.Error(t, err) + assert.Contains(t, err.Error(), "got host") +} + +func TestValidateMintURL_RejectsOtherFullsendSubdomains(t *testing.T) { + err := validateMintURL("https://evil.fullsend.sh") + require.Error(t, err) + assert.Contains(t, err.Error(), "got host") +} + +func TestDefaultMintURL_IsHostedCommunity(t *testing.T) { + assert.Equal(t, "https://mint.fullsend.sh", DefaultMintURL) +} + +func TestIsHostedMintURL(t *testing.T) { + assert.True(t, IsHostedMintURL("https://mint.fullsend.sh")) + assert.True(t, IsHostedMintURL("https://mint.fullsend.sh/v1/token")) + assert.True(t, IsHostedMintURL("https://mint.fullsend.sh:443")) + assert.True(t, IsHostedMintURL("https://Mint.Fullsend.SH")) + assert.False(t, IsHostedMintURL("https://evil.example.com")) + assert.False(t, IsHostedMintURL("https://fullsend-mint-abc123.run.app")) + assert.False(t, IsHostedMintURL("")) + assert.False(t, IsHostedMintURL("://")) +} + func TestValidateWIFProvider_Valid(t *testing.T) { valid := []string{ "projects/123456789/locations/global/workloadIdentityPools/fullsend-pool/providers/gh-acme-widget", diff --git a/internal/config/defaults.go b/internal/config/defaults.go index 864045ff9e..bf9a28fec0 100644 --- a/internal/config/defaults.go +++ b/internal/config/defaults.go @@ -8,7 +8,7 @@ package config const ( // DefaultPerRepoMintURL is the hosted public mint used when no // mint_url is configured in config.yaml or config.base.yaml. - DefaultPerRepoMintURL = "https://fullsend-mint-gljhbkcloq-uc.a.run.app" + DefaultPerRepoMintURL = "https://mint.fullsend.sh" // DefaultPerRepoInferenceProvider is the default inference backend. DefaultPerRepoInferenceProvider = "vertex" diff --git a/internal/dispatch/gcf/provisioner.go b/internal/dispatch/gcf/provisioner.go index 81448ef511..658db0e3c5 100644 --- a/internal/dispatch/gcf/provisioner.go +++ b/internal/dispatch/gcf/provisioner.go @@ -664,10 +664,15 @@ func (p *Provisioner) provisionWithExistingMint(ctx context.Context) (map[string } parsedURL, err := url.Parse(p.cfg.MintURL) - if err != nil || parsedURL.Scheme != "https" || - (!strings.HasSuffix(parsedURL.Host, ".run.app") && - !strings.HasSuffix(parsedURL.Host, ".cloudfunctions.net")) { - return nil, fmt.Errorf("MintURL %q must be a valid Cloud Run URL (.run.app or .cloudfunctions.net)", p.cfg.MintURL) + if err != nil { + return nil, fmt.Errorf("MintURL %q must be mint.fullsend.sh or a Cloud Run URL (.run.app or .cloudfunctions.net)", p.cfg.MintURL) + } + host := parsedURL.Hostname() + if parsedURL.Scheme != "https" || + (!strings.EqualFold(host, "mint.fullsend.sh") && + !strings.HasSuffix(host, ".run.app") && + !strings.HasSuffix(host, ".cloudfunctions.net")) { + return nil, fmt.Errorf("MintURL %q must be mint.fullsend.sh or a Cloud Run URL (.run.app or .cloudfunctions.net)", p.cfg.MintURL) } // Store new PEMs once per role (shared across orgs on the mint). @@ -1003,9 +1008,14 @@ func (p *Provisioner) provisionSelfManaged(ctx context.Context) (map[string]stri } parsedURL, err := url.Parse(mintURL) - if err != nil || parsedURL.Scheme != "https" || - (!strings.HasSuffix(parsedURL.Host, ".run.app") && - !strings.HasSuffix(parsedURL.Host, ".cloudfunctions.net")) { + if err != nil { + return nil, fmt.Errorf("function URL %q is not a valid Cloud Run URL", mintURL) + } + host := parsedURL.Hostname() + if parsedURL.Scheme != "https" || + (!strings.EqualFold(host, "mint.fullsend.sh") && + !strings.HasSuffix(host, ".run.app") && + !strings.HasSuffix(host, ".cloudfunctions.net")) { return nil, fmt.Errorf("function URL %q is not a valid Cloud Run URL", mintURL) } diff --git a/internal/dispatch/gcf/provisioner_test.go b/internal/dispatch/gcf/provisioner_test.go index 92f17de96c..36451ad066 100644 --- a/internal/dispatch/gcf/provisioner_test.go +++ b/internal/dispatch/gcf/provisioner_test.go @@ -693,6 +693,54 @@ func TestProvisioner_Provision_BundledMode(t *testing.T) { assert.Contains(t, fake.calls, "AddSecretVersion") } +func TestProvisioner_Provision_BundledMode_HostedMintURL(t *testing.T) { + fake := newFakeGCFClient() + fake.errs["GetSecret"] = ErrSecretNotFound + fake.functionInfo = &FunctionInfo{ + Name: "projects/shared-project/locations/us-central1/functions/fullsend-mint", + State: "ACTIVE", + URI: "https://mint.fullsend.sh", + EnvVars: map[string]string{ + "ALLOWED_ORGS": "test-org", + }, + } + + p := newTestProvisioner(Config{ + ProjectID: "shared-project", + GitHubOrgs: []string{"test-org"}, + AgentPEMs: singleRolePEMs(), + MintURL: "https://mint.fullsend.sh", + }, fake) + + vars, err := p.Provision(context.Background()) + require.NoError(t, err) + assert.Equal(t, "https://mint.fullsend.sh", vars["FULLSEND_MINT_URL"]) +} + +func TestProvisioner_Provision_BundledMode_CloudFunctionsURL(t *testing.T) { + fake := newFakeGCFClient() + fake.errs["GetSecret"] = ErrSecretNotFound + fake.functionInfo = &FunctionInfo{ + Name: "projects/shared-project/locations/us-central1/functions/fullsend-mint", + State: "ACTIVE", + URI: "https://us-central1-shared-project.cloudfunctions.net", + EnvVars: map[string]string{ + "ALLOWED_ORGS": "test-org", + }, + } + + p := newTestProvisioner(Config{ + ProjectID: "shared-project", + GitHubOrgs: []string{"test-org"}, + AgentPEMs: singleRolePEMs(), + MintURL: "https://us-central1-shared-project.cloudfunctions.net", + }, fake) + + vars, err := p.Provision(context.Background()) + require.NoError(t, err) + assert.Equal(t, "https://us-central1-shared-project.cloudfunctions.net", vars["FULLSEND_MINT_URL"]) +} + func TestProvisioner_Provision_BundledMode_PublicMintSkipsPerRepoWIF(t *testing.T) { fake := newFakeGCFClient() fake.functionInfo = &FunctionInfo{ @@ -739,6 +787,7 @@ func TestProvisioner_Provision_BundledMode_InvalidMintURL(t *testing.T) { {"HTTP not HTTPS", "http://mint.example.com"}, {"no scheme", "mint.example.com"}, {"empty host", "https://"}, + {"other fullsend subdomain", "https://evil.fullsend.sh"}, } for _, tc := range tests { @@ -752,7 +801,7 @@ func TestProvisioner_Provision_BundledMode_InvalidMintURL(t *testing.T) { _, err := p.Provision(context.Background()) require.Error(t, err) - assert.Contains(t, err.Error(), "must be a valid Cloud Run URL") + assert.Contains(t, err.Error(), "must be mint.fullsend.sh or a Cloud Run URL") }) } } diff --git a/pkg/e2etest/auth.go b/pkg/e2etest/auth.go index d2b56154d9..5184158fa5 100644 --- a/pkg/e2etest/auth.go +++ b/pkg/e2etest/auth.go @@ -3,6 +3,7 @@ package e2etest import ( "context" "fmt" + "net/url" "os" "os/exec" "strings" @@ -39,6 +40,32 @@ func runningInGitHubActions() bool { return os.Getenv("GITHUB_ACTIONS") == "true" } +// DefaultPoolOrgInstallMintURL is written into pool orgs as FULLSEND_MINT_URL by +// admin e2e install tests. Distinct from resolveMintURL() / cli.DefaultMintURL, +// which CI uses for cross-org e2e org locking. +// +// Admin e2e tests exercise per-org installation; workflows on the installed org +// mint against FULLSEND_MINT_URL. The community hosted mint (mint.fullsend.sh) +// runs in public mode and does not support per-org installs, so org-mode admin +// e2e must keep using the legacy per-org hosted dev mint until that changes. +const DefaultPoolOrgInstallMintURL = "https://fullsend-mint-gljhbkcloq-uc.a.run.app" + +// poolOrgMintHost is the hostname of DefaultPoolOrgInstallMintURL, parsed +// once so isPoolOrgMintURL can do a case-insensitive hostname comparison +// (same approach as cli.IsHostedMintURL) instead of exact string equality. +var poolOrgMintHost = func() string { + u, _ := url.Parse(DefaultPoolOrgInstallMintURL) + return u.Hostname() +}() + +func isPoolOrgMintURL(raw string) bool { + parsed, err := url.Parse(raw) + if err != nil { + return false + } + return strings.EqualFold(parsed.Hostname(), poolOrgMintHost) +} + // resolveMintURL returns the mint endpoint from FULLSEND_MINT_URL or the hosted // default (same as fullsend admin --mint-url). func resolveMintURL() string { @@ -63,7 +90,7 @@ func MintEnrollProjectID(cfg EnvConfig) string { if mintURL == "" { mintURL = cli.DefaultMintURL } - if mintURL == cli.DefaultMintURL { + if isPoolOrgMintURL(mintURL) || cli.IsHostedMintURL(mintURL) { return DefaultHostedMintGCPProject } return strings.TrimSpace(cfg.GCPProjectID) diff --git a/pkg/e2etest/auth_test.go b/pkg/e2etest/auth_test.go index 9f6f97c278..92974bb534 100644 --- a/pkg/e2etest/auth_test.go +++ b/pkg/e2etest/auth_test.go @@ -12,20 +12,39 @@ import ( func TestMintEnrollProjectID(t *testing.T) { t.Setenv("E2E_GCP_MINT_PROJECT_ID", "") + + // Pool-org install mint (DefaultPoolOrgInstallMintURL) → hosted project. cfg := EnvConfig{ - MintURL: cli.DefaultMintURL, + MintURL: DefaultPoolOrgInstallMintURL, GCPProjectID: "inference-only-project", } assert.Equal(t, DefaultHostedMintGCPProject, MintEnrollProjectID(cfg)) + // Pool-org URL with trailing slash still matches via hostname comparison. + cfg.MintURL = DefaultPoolOrgInstallMintURL + "/" + assert.Equal(t, DefaultHostedMintGCPProject, MintEnrollProjectID(cfg)) + + // Community mint (cli.DefaultMintURL / mint.fullsend.sh) → hosted project + // via IsHostedMintURL. + cfg.MintURL = cli.DefaultMintURL + assert.Equal(t, DefaultHostedMintGCPProject, MintEnrollProjectID(cfg)) + + // Env override takes precedence. t.Setenv("E2E_GCP_MINT_PROJECT_ID", "override-mint-project") assert.Equal(t, "override-mint-project", MintEnrollProjectID(cfg)) + // Custom (non-hosted) mint → inference project. t.Setenv("E2E_GCP_MINT_PROJECT_ID", "") cfg.MintURL = "https://mint.example.com" assert.Equal(t, "inference-only-project", MintEnrollProjectID(cfg)) } +func TestMintEnrollProjectID_EmptyMintURLFallsBackToHosted(t *testing.T) { + t.Setenv("E2E_GCP_MINT_PROJECT_ID", "") + cfg := EnvConfig{GCPProjectID: "custom-project"} + assert.Equal(t, DefaultHostedMintGCPProject, MintEnrollProjectID(cfg)) +} + func TestMintEnrollProjectID_EmptyWithoutHostedMint(t *testing.T) { t.Setenv("E2E_GCP_MINT_PROJECT_ID", "") cfg := EnvConfig{ @@ -35,9 +54,16 @@ func TestMintEnrollProjectID_EmptyWithoutHostedMint(t *testing.T) { assert.Empty(t, MintEnrollProjectID(cfg)) } +func TestIsPoolOrgMintURL(t *testing.T) { + assert.True(t, isPoolOrgMintURL(DefaultPoolOrgInstallMintURL)) + assert.True(t, isPoolOrgMintURL(DefaultPoolOrgInstallMintURL+"/")) + assert.False(t, isPoolOrgMintURL("https://other-mint.run.app")) + assert.False(t, isPoolOrgMintURL("://")) +} + func TestMintEnrollProjectID_RespectsEnvOverride(t *testing.T) { t.Setenv("E2E_GCP_MINT_PROJECT_ID", "from-env") - cfg := EnvConfig{MintURL: cli.DefaultMintURL} + cfg := EnvConfig{MintURL: DefaultPoolOrgInstallMintURL} assert.Equal(t, "from-env", MintEnrollProjectID(cfg)) _ = os.Unsetenv("E2E_GCP_MINT_PROJECT_ID") }