[goldctl] add --bucket flag to override GCS bucket

This removes the formulaic transforms for skia (which
does not currently use goldctl) and adds tests for
URL overriding and bucket overriding.

Change-Id: I3e56b84daadb63a0d304d673fc620b523bb79bab
Reviewed-on: https://skia-review.googlesource.com/c/buildbot/+/368237
Reviewed-by: Leandro Lovisolo <lovisolo@google.com>
diff --git a/gold-client/cmd/goldctl/cmd_dump_test.go b/gold-client/cmd/goldctl/cmd_dump_test.go
index 738ce58..cb1a695 100644
--- a/gold-client/cmd/goldctl/cmd_dump_test.go
+++ b/gold-client/cmd/goldctl/cmd_dump_test.go
@@ -13,7 +13,7 @@
 	workDir := t.TempDir()
 	setupAuthWithGSUtil(t, workDir)
 
-	mh := mockRPCResponses().Positive("pixel-tests", blankDigest).
+	mh := mockRPCResponses("https://my-instance-gold.skia.org").Positive("pixel-tests", blankDigest).
 		Negative("other-test", blankDigest).
 		Known("11111111111111111111111111111111").Build()
 
diff --git a/gold-client/cmd/goldctl/cmd_imgtest.go b/gold-client/cmd/goldctl/cmd_imgtest.go
index 6e033d2..002bb05 100644
--- a/gold-client/cmd/goldctl/cmd_imgtest.go
+++ b/gold-client/cmd/goldctl/cmd_imgtest.go
@@ -19,18 +19,19 @@
 // Specifically, it houses the flags.
 type imgTest struct {
 	// Common flags.
+	bucketOverride              string
+	changelistID                string
 	codeReviewSystem            string
-	continuousIntegrationSystem string
 	commitHash                  string
+	continuousIntegrationSystem string
 	corpus                      string
 	failureFile                 string
 	instanceID                  string
-	changelistID                string
-	tryJobID                    string
 	keysFile                    string
 	passFailStep                bool
-	patchsetOrder               int
 	patchsetID                  string
+	patchsetOrder               int
+	tryJobID                    string
 	uploadOnly                  bool
 	urlOverride                 string
 	workDir                     string
@@ -118,8 +119,9 @@
 	imgTestCheckCmd.Flags().StringVar(&env.pngFile, "png-file", "", "Path to the PNG file that contains the test results.")
 	imgTestCheckCmd.Flags().StringVar(&env.instanceID, "instance", "", "ID of the Gold instance.")
 
+	imgTestCheckCmd.Flags().StringVar(&env.bucketOverride, "bucket", "", "GCS Bucket to use. If empty the URL will be derived from the value of 'instance'")
 	imgTestCheckCmd.Flags().StringVar(&env.changelistID, "changelist", "", "If provided, the ChangelistExpectations matching this will apply.")
-	imgTestCheckCmd.Flags().StringVar(&env.urlOverride, "url", "", "URL of the Gold instance. Used for testing, if empty the URL will be derived from the value of 'instance'")
+	imgTestCheckCmd.Flags().StringVar(&env.urlOverride, "url", "", "URL of the Gold instance. If empty the URL will be derived from the value of 'instance'")
 
 	must(imgTestCheckCmd.MarkFlagRequired(fstrWorkDir))
 	must(imgTestCheckCmd.MarkFlagRequired("test-name"))
@@ -142,6 +144,7 @@
 	cmd.Flags().BoolVar(&i.passFailStep, "passfail", false, "Whether the 'add' call returns a pass/fail for each test.")
 	cmd.Flags().BoolVar(&i.uploadOnly, "upload-only", false, "Skip reading expectations from the server. Incompatible with passfail=true.")
 
+	cmd.Flags().StringVar(&i.bucketOverride, "bucket", "", "GCS Bucket to write to. If empty the URL will be derived from the value of 'instance'")
 	cmd.Flags().StringVar(&i.changelistID, "changelist", "", "Changelist ID if this is run as a TryJob.")
 	cmd.Flags().StringVar(&i.codeReviewSystem, "crs", "", "CodeReviewSystem, if any (e.g. 'gerrit', 'github')")
 	cmd.Flags().StringVar(&i.commitHash, "commit", "", "Git commit hash")
@@ -152,7 +155,7 @@
 	cmd.Flags().IntVar(&i.patchsetOrder, "patchset", 0, "Patchset number if this is run as a TryJob.")
 	cmd.Flags().StringVar(&i.patchsetID, "patchset_id", "", "Patchset id (e.g. githash) if this is run as a TryJob.")
 	cmd.Flags().StringVar(&i.tryJobID, "jobid", "", "TryJob ID if this is a TryJob run.")
-	cmd.Flags().StringVar(&i.urlOverride, "url", "", "URL of the Gold instance. Used for testing, if empty the URL will be derived from the value of 'instance'")
+	cmd.Flags().StringVar(&i.urlOverride, "url", "", "URL of the Gold instance. If empty the URL will be derived from the value of 'instance'")
 
 	cmd.Flags().StringVar(&i.changelistID, "issue", "", "[deprecated] Gerrit issue if this is trybot run. ")
 	must(cmd.MarkFlagRequired(fstrWorkDir))
@@ -192,9 +195,10 @@
 	if err != nil {
 		logErrf(ctx, "Could not load existing run, trying to initialize %s\n%s\n", i.workDir, err)
 		config := goldclient.GoldClientConfig{
-			WorkDir:         i.workDir,
 			InstanceID:      i.instanceID,
+			OverrideBucket:  i.bucketOverride,
 			OverrideGoldURL: i.urlOverride,
+			WorkDir:         i.workDir,
 		}
 		goldClient, err = goldclient.NewCloudClient(config)
 		ifErrLogExit(ctx, err)
@@ -250,6 +254,7 @@
 	config := goldclient.GoldClientConfig{
 		FailureFile:     i.failureFile,
 		InstanceID:      i.instanceID,
+		OverrideBucket:  i.bucketOverride,
 		OverrideGoldURL: i.urlOverride,
 		PassFailStep:    i.passFailStep,
 		UploadOnly:      i.uploadOnly,
@@ -317,6 +322,7 @@
 		config := goldclient.GoldClientConfig{
 			FailureFile:     i.failureFile,
 			InstanceID:      i.instanceID,
+			OverrideBucket:  i.bucketOverride,
 			OverrideGoldURL: i.urlOverride,
 			PassFailStep:    i.passFailStep,
 			UploadOnly:      i.uploadOnly,
diff --git a/gold-client/cmd/goldctl/cmd_imgtest_test.go b/gold-client/cmd/goldctl/cmd_imgtest_test.go
index 7ac6ed5..77bd8c0 100644
--- a/gold-client/cmd/goldctl/cmd_imgtest_test.go
+++ b/gold-client/cmd/goldctl/cmd_imgtest_test.go
@@ -56,7 +56,7 @@
 	keysFile := filepath.Join(workDir, "keys.json")
 	require.NoError(t, ioutil.WriteFile(keysFile, []byte(`{"os": "Android"}`), 0644))
 
-	mh := mockRPCResponses().Positive("pixel-tests", blankDigest).
+	mh := mockRPCResponses("https://my-instance-gold.skia.org").Positive("pixel-tests", blankDigest).
 		Negative("other-test", blankDigest).
 		Known("11111111111111111111111111111111").Build()
 
@@ -92,7 +92,7 @@
 	td, err := testutils.TestDataDir()
 	require.NoError(t, err)
 
-	mh := mockRPCResponses().Build()
+	mh := mockRPCResponses("https://my-instance-gold.skia.org").Build()
 
 	// Call imgtest init with the following flags. We expect it to load the baseline expectations
 	// and the known hashes (both empty).
@@ -161,6 +161,85 @@
 	assert.Contains(t, string(fb), "https://my-instance-gold.skia.org/detail?test=pixel-tests&digest=00000000000000000000000000000000")
 }
 
+func TestImgTest_InitAdd_OverwriteBucketAndURL_ProperLinks(t *testing.T) {
+	unittest.MediumTest(t)
+
+	workDir := t.TempDir()
+	setupAuthWithGSUtil(t, workDir)
+	td, err := testutils.TestDataDir()
+	require.NoError(t, err)
+
+	mh := mockRPCResponses("https://my-custom-gold-url.example.com").Build()
+
+	// Call imgtest init with the following flags. We expect it to load the baseline expectations
+	// and the known hashes (both empty).
+	ctx, output, exit := testContext(nil, mh, nil, nil)
+	env := imgTest{
+		bucketOverride:  "my-custom-bucket",
+		commitHash:      "1234567890123456789012345678901234567890",
+		corpus:          "my_corpus",
+		instanceID:      "my-instance",
+		passFailStep:    true,
+		failureFile:     filepath.Join(workDir, "failures.txt"),
+		workDir:         workDir,
+		testKeysStrings: []string{"os:Android"},
+		urlOverride:     "https://my-custom-gold-url.example.com",
+	}
+	runUntilExit(t, func() {
+		env.Init(ctx)
+	})
+	exit.AssertWasCalledWithCode(t, 0, output.String())
+
+	mg := &mocks.GCSUploader{}
+	resultsMatcher := mock.MatchedBy(func(results jsonio.GoldResults) bool {
+		assert.Equal(t, jsonio.GoldResults{
+			GitHash: "1234567890123456789012345678901234567890",
+			Key: map[string]string{
+				"os":          "Android",
+				"source_type": "my_corpus",
+			},
+			Results: []*jsonio.Result{{
+				Key:     map[string]string{"name": "pixel-tests", "device": "angler"},
+				Options: map[string]string{"some_option": "is optional", "ext": "png"},
+				Digest:  blankDigest,
+			}},
+		}, results)
+		return true
+	})
+	mg.On("UploadJSON", testutils.AnyContext, resultsMatcher, mock.Anything,
+		`my-custom-bucket/dm-json-v1/2021/01/23/22/1234567890123456789012345678901234567890/waterfall/dm-1611440480000000019.json`).Return(nil)
+	bytesMatcher := mock.MatchedBy(func(b []byte) bool {
+		assert.Len(t, b, 78) // spot check length
+		return true
+	})
+	mg.On("UploadBytes", testutils.AnyContext, bytesMatcher, mock.Anything,
+		`gs://my-custom-bucket/dm-images-v1/00000000000000000000000000000000.png`).Return(nil)
+
+	// Now call imgtest add with the following flags. This is simulating a test uploading a single
+	// result for a test called pixel-tests.
+	ctx, output, exit = testContext(mg, nil, nil, mockTime(timeOne))
+	env = imgTest{
+		workDir:                 workDir,
+		testName:                "pixel-tests",
+		pngFile:                 filepath.Join(td, "00000000000000000000000000000000.png"),
+		pngDigest:               blankDigest,
+		testKeysStrings:         []string{"device:angler"},
+		testOptionalKeysStrings: []string{"some_option:is optional"},
+	}
+	runUntilExit(t, func() {
+		env.Add(ctx)
+	})
+	logs := output.String()
+	exit.AssertWasCalledWithCode(t, 1, logs)
+	mg.AssertExpectations(t)
+	assert.Contains(t, logs, `Untriaged or negative image: https://my-custom-gold-url.example.com/detail?test=pixel-tests&digest=00000000000000000000000000000000`)
+	assert.Contains(t, logs, `Test: pixel-tests FAIL`)
+
+	fb, err := ioutil.ReadFile(filepath.Join(workDir, "failures.txt"))
+	require.NoError(t, err)
+	assert.Contains(t, string(fb), "https://my-custom-gold-url.example.com/detail?test=pixel-tests&digest=00000000000000000000000000000000")
+}
+
 func TestImgTest_InitAdd_StreamingPassFail_MatchesExpectations_ZeroExitCode(t *testing.T) {
 	unittest.MediumTest(t)
 
@@ -169,7 +248,7 @@
 	td, err := testutils.TestDataDir()
 	require.NoError(t, err)
 
-	mh := mockRPCResponses().Positive("pixel-tests", blankDigest).Build()
+	mh := mockRPCResponses("https://my-instance-gold.skia.org").Positive("pixel-tests", blankDigest).Build()
 
 	// Call imgtest init with the following flags. We expect it to load the baseline expectations
 	// and the known hashes.
@@ -238,7 +317,7 @@
 	td, err := testutils.TestDataDir()
 	require.NoError(t, err)
 
-	mh := mockRPCResponses().Positive("pixel-tests", blankDigest).Build()
+	mh := mockRPCResponses("https://my-instance-gold.skia.org").Positive("pixel-tests", blankDigest).Build()
 
 	// Call imgtest init with the following flags. We expect it to load the baseline expectations
 	// and the known hashes.
@@ -342,7 +421,7 @@
 	keysFile := filepath.Join(workDir, "keys.json")
 	require.NoError(t, ioutil.WriteFile(keysFile, []byte(`{"os": "Android"}`), 0644))
 
-	mh := mockRPCResponses().Positive("pixel-tests", blankDigest).Build()
+	mh := mockRPCResponses("https://my-instance-gold.skia.org").Positive("pixel-tests", blankDigest).Build()
 
 	mg := &mocks.GCSUploader{}
 	resultsMatcher := mock.MatchedBy(func(results jsonio.GoldResults) bool {
@@ -399,7 +478,7 @@
 	td, err := testutils.TestDataDir()
 	require.NoError(t, err)
 
-	mh := mockRPCResponses().Positive("pixel-tests", blankDigest).Build()
+	mh := mockRPCResponses("https://my-instance-gold.skia.org").Positive("pixel-tests", blankDigest).Build()
 
 	// Call imgtest init with the following flags. We expect it to load the baseline expectations
 	// and the known hashes.
@@ -493,7 +572,7 @@
 	td, err := testutils.TestDataDir()
 	require.NoError(t, err)
 
-	mh := mockRPCResponses().Build()
+	mh := mockRPCResponses("https://my-instance-gold.skia.org").Build()
 
 	// Call imgtest init with the following flags. We expect it to load the baseline expectations
 	// and the known hashes.
@@ -599,7 +678,7 @@
 	td, err := testutils.TestDataDir()
 	require.NoError(t, err)
 
-	mh := mockRPCResponses().Positive("pixel-tests", a01Digest).
+	mh := mockRPCResponses("https://my-instance-gold.skia.org").Positive("pixel-tests", a01Digest).
 		LatestPositive(a01Digest, paramtools.Params{
 			"device": "bullhead", "name": "pixel-tests", "source_type": "my-instance",
 		}).Build()
@@ -639,7 +718,7 @@
 	td, err := testutils.TestDataDir()
 	require.NoError(t, err)
 
-	mh := mockRPCResponses().Positive("pixel-tests", a01Digest).
+	mh := mockRPCResponses("https://my-instance-gold.skia.org").Positive("pixel-tests", a01Digest).
 		LatestPositive(a01Digest, paramtools.Params{
 			"device": "bullhead", "name": "pixel-tests", "source_type": "my-instance",
 		}).BuildForCL("gerritHub", "cl_1234")
@@ -709,11 +788,13 @@
 	knownDigests    []string
 	exp             *expectations.Expectations
 	latestPositives map[tiling.TraceID]types.Digest
+	urlBase         string
 }
 
-func mockRPCResponses() *rpcResponsesBuilder {
+func mockRPCResponses(instanceURL string) *rpcResponsesBuilder {
 	return &rpcResponsesBuilder{
-		exp: &expectations.Expectations{},
+		exp:     &expectations.Expectations{},
+		urlBase: instanceURL,
 	}
 }
 
@@ -746,7 +827,7 @@
 func (r *rpcResponsesBuilder) Build() *mocks.HTTPClient {
 	mh := &mocks.HTTPClient{}
 	knownResp := strings.Join(r.knownDigests, "\n")
-	mh.On("Get", "https://my-instance-gold.skia.org/json/v1/hashes").Return(httpResponse(knownResp, "200 OK", http.StatusOK), nil)
+	mh.On("Get", r.urlBase+"/json/v1/hashes").Return(httpResponse(knownResp, "200 OK", http.StatusOK), nil)
 
 	exp, err := json.Marshal(baseline.Baseline{
 		MD5:          "somemd5",
@@ -755,7 +836,7 @@
 	if err != nil {
 		panic(err)
 	}
-	mh.On("Get", "https://my-instance-gold.skia.org/json/v2/expectations").Return(
+	mh.On("Get", r.urlBase+"/json/v2/expectations").Return(
 		httpResponse(string(exp), "200 OK", http.StatusOK), nil)
 
 	for traceID, digest := range r.latestPositives {
@@ -763,7 +844,7 @@
 		if err != nil {
 			panic(err)
 		}
-		url := string("https://my-instance-gold.skia.org/json/v1/latestpositivedigest/" + traceID)
+		url := r.urlBase + "/json/v1/latestpositivedigest/" + string(traceID)
 		mh.On("Get", url).Return(
 			httpResponse(string(j), "200 OK", http.StatusOK), nil)
 	}
@@ -774,7 +855,7 @@
 func (r *rpcResponsesBuilder) BuildForCL(crs, clID string) *mocks.HTTPClient {
 	mh := &mocks.HTTPClient{}
 	knownResp := strings.Join(r.knownDigests, "\n")
-	mh.On("Get", "https://my-instance-gold.skia.org/json/v1/hashes").Return(httpResponse(knownResp, "200 OK", http.StatusOK), nil)
+	mh.On("Get", r.urlBase+"/json/v1/hashes").Return(httpResponse(knownResp, "200 OK", http.StatusOK), nil)
 
 	exp, err := json.Marshal(baseline.Baseline{
 		MD5:              "somemd5",
@@ -785,7 +866,7 @@
 	if err != nil {
 		panic(err)
 	}
-	url := fmt.Sprintf("https://my-instance-gold.skia.org/json/v2/expectations?issue=%s&crs=%s", clID, crs)
+	url := fmt.Sprintf("%s/json/v2/expectations?issue=%s&crs=%s", r.urlBase, clID, crs)
 	mh.On("Get", url).Return(
 		httpResponse(string(exp), "200 OK", http.StatusOK), nil)
 
@@ -794,7 +875,7 @@
 		if err != nil {
 			panic(err)
 		}
-		url := string("https://my-instance-gold.skia.org/json/v1/latestpositivedigest/" + traceID)
+		url := r.urlBase + "/json/v1/latestpositivedigest/" + string(traceID)
 		mh.On("Get", url).Return(
 			httpResponse(string(j), "200 OK", http.StatusOK), nil)
 	}
@@ -805,7 +886,7 @@
 func TestRPCResponsesBuilder_Default_ReturnsBlankValues(t *testing.T) {
 	unittest.SmallTest(t)
 
-	mh := mockRPCResponses().Build()
+	mh := mockRPCResponses("https://my-instance-gold.skia.org").Build()
 	resp, err := mh.Get("https://my-instance-gold.skia.org/json/v1/hashes")
 	require.NoError(t, err)
 	b, err := ioutil.ReadAll(resp.Body)
@@ -822,7 +903,7 @@
 func TestRPCResponsesBuilder_WithValues_ReturnsValidListsAndJSON(t *testing.T) {
 	unittest.SmallTest(t)
 
-	mh := mockRPCResponses().
+	mh := mockRPCResponses("http://my-custom-url.example.com").
 		Known("first_digest").
 		Positive("alpha test", "second_digest").
 		Positive("beta test", "third_digest").
@@ -830,7 +911,7 @@
 		LatestPositive("third_digest", paramtools.Params{"alpha": "beta", "gamma": "delta epsilon"}).
 		Known("fifth_digest").
 		Build()
-	resp, err := mh.Get("https://my-instance-gold.skia.org/json/v1/hashes")
+	resp, err := mh.Get("http://my-custom-url.example.com/json/v1/hashes")
 	require.NoError(t, err)
 	b, err := ioutil.ReadAll(resp.Body)
 	require.NoError(t, err)
@@ -840,13 +921,13 @@
 fourth_digest
 fifth_digest`, string(b))
 
-	resp, err = mh.Get("https://my-instance-gold.skia.org/json/v2/expectations")
+	resp, err = mh.Get("http://my-custom-url.example.com/json/v2/expectations")
 	require.NoError(t, err)
 	b, err = ioutil.ReadAll(resp.Body)
 	require.NoError(t, err)
 	assert.Equal(t, `{"md5":"somemd5","primary":{"alpha test":{"fourth_digest":"negative","second_digest":"positive"},"beta test":{"third_digest":"positive"}}}`, string(b))
 
-	resp, err = mh.Get("https://my-instance-gold.skia.org/json/v1/latestpositivedigest/,alpha=beta,gamma=delta epsilon,")
+	resp, err = mh.Get("http://my-custom-url.example.com/json/v1/latestpositivedigest/,alpha=beta,gamma=delta epsilon,")
 	require.NoError(t, err)
 	b, err = ioutil.ReadAll(resp.Body)
 	require.NoError(t, err)
diff --git a/gold-client/go/goldclient/common.go b/gold-client/go/goldclient/common.go
index 9942e1b..fc09d65 100644
--- a/gold-client/go/goldclient/common.go
+++ b/gold-client/go/goldclient/common.go
@@ -17,18 +17,6 @@
 
 const maxAttempts = 5
 
-// GetGoldInstanceURL returns the URL for a given Gold instance id.
-// This is usually a formulaic transform, but there are some special cases.
-func GetGoldInstanceURL(instanceID string) string {
-	if instanceID == instanceIDSkiaLegacy {
-		return hostSkiaLegacy
-	}
-	if instanceID == instanceIDFuchsia {
-		return hostFuchsiaCorp
-	}
-	return fmt.Sprintf(goldHostTemplate, instanceID)
-}
-
 // getWithRetries makes a GET request with retries to work around the rare unexpected EOF error.
 // See https://crbug.com/skia/9108.
 //
diff --git a/gold-client/go/goldclient/goldclient.go b/gold-client/go/goldclient/goldclient.go
index 81c2878..29fd9bd 100644
--- a/gold-client/go/goldclient/goldclient.go
+++ b/gold-client/go/goldclient/goldclient.go
@@ -160,9 +160,12 @@
 	// any failures. Only written to if PassFailStep is true
 	FailureFile string
 
-	// OverrideGoldURL is optional and allows to override the GoldURL for testing.
+	// OverrideGoldURL is optional and overrides the formulaic GoldURL (typically legacy instances).
 	OverrideGoldURL string
 
+	// OverrideBucket is optional and overrides the formulaic GCS Bucket.
+	OverrideBucket string
+
 	// UploadOnly is a mode where we don't check expectations against the server - i.e.
 	// we just operate in upload mode.
 	UploadOnly bool
@@ -261,10 +264,11 @@
 		WorkDir: c.workDir,
 	}
 	if c.resultState != nil {
-		existingConfig.InstanceID = c.resultState.InstanceID
-		existingConfig.PassFailStep = c.resultState.PerTestPassFail
 		existingConfig.FailureFile = c.resultState.FailureFile
+		existingConfig.InstanceID = c.resultState.InstanceID
+		existingConfig.OverrideBucket = c.resultState.Bucket
 		existingConfig.OverrideGoldURL = c.resultState.GoldURL
+		existingConfig.PassFailStep = c.resultState.PerTestPassFail
 		existingConfig.UploadOnly = c.resultState.UploadOnly
 	}
 	c.resultState = newResultState(sharedConfig, &existingConfig)
diff --git a/gold-client/go/goldclient/resultstate.go b/gold-client/go/goldclient/resultstate.go
index 28856f6..90b98c6 100644
--- a/gold-client/go/goldclient/resultstate.go
+++ b/gold-client/go/goldclient/resultstate.go
@@ -30,13 +30,6 @@
 	// bucketTemplate constructs the name of the ingestion bucket from the instance id
 	bucketTemplate = "skia-gold-%s"
 
-	// Skia's naming conventions are old and don't follow the patterns that
-	// newer clients do. One day, it might be nice to align the skia names
-	// to match the rest.
-	bucketSkiaLegacy     = "skia-infra-gm"
-	hostSkiaLegacy       = "https://gold.skia.org"
-	instanceIDSkiaLegacy = "skia"
-
 	hostFuchsiaCorp   = "https://fuchsia-gold.corp.goog"
 	instanceIDFuchsia = "fuchsia"
 )
@@ -64,7 +57,11 @@
 func newResultState(sharedConfig jsonio.GoldResults, config *GoldClientConfig) *resultState {
 	goldURL := config.OverrideGoldURL
 	if goldURL == "" {
-		goldURL = GetGoldInstanceURL(config.InstanceID)
+		goldURL = getGoldInstanceURL(config.InstanceID)
+	}
+	bucket := config.OverrideBucket
+	if bucket == "" {
+		bucket = getBucket(config.InstanceID)
 	}
 
 	ret := &resultState{
@@ -74,18 +71,24 @@
 		InstanceID:      config.InstanceID,
 		UploadOnly:      config.UploadOnly,
 		GoldURL:         goldURL,
-		Bucket:          getBucket(config.InstanceID),
+		Bucket:          bucket,
 	}
 
 	return ret
 }
 
-// getBucket returns the bucket name for a given instance id.
+// getGoldInstanceURL returns the URL for a given Gold instance id.
 // This is usually a formulaic transform, but there are some special cases.
-func getBucket(instanceID string) string {
-	if instanceID == instanceIDSkiaLegacy {
-		return bucketSkiaLegacy
+func getGoldInstanceURL(instanceID string) string {
+	if instanceID == instanceIDFuchsia {
+		return hostFuchsiaCorp
 	}
+	return fmt.Sprintf(goldHostTemplate, instanceID)
+}
+
+// getBucket returns the formulaic bucket name for a given instance id. Legacy instances may
+// have a different naming scheme and would override the bucket.
+func getBucket(instanceID string) string {
 	return fmt.Sprintf(bucketTemplate, instanceID)
 }