sigs.k8s.io/prow@v0.0.0-20240503223140-c5e374dc7eb1/pkg/spyglass/storageartifact_fetcher_test.go (about)

     1  /*
     2  Copyright 2018 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package spyglass
    18  
    19  import (
    20  	"context"
    21  	"encoding/base64"
    22  	"fmt"
    23  	"os"
    24  	"strings"
    25  	"testing"
    26  
    27  	"github.com/google/go-cmp/cmp"
    28  	"k8s.io/apimachinery/pkg/util/sets"
    29  
    30  	prowv1 "sigs.k8s.io/prow/pkg/apis/prowjobs/v1"
    31  	"sigs.k8s.io/prow/pkg/config"
    32  	"sigs.k8s.io/prow/pkg/io"
    33  )
    34  
    35  func TestNewGCSJobSource(t *testing.T) {
    36  	testCases := []struct {
    37  		name         string
    38  		src          string
    39  		exJobPrefix  string
    40  		exBucket     string
    41  		exName       string
    42  		exBuildID    string
    43  		exLinkPrefix string
    44  		exSource     string
    45  		expectedErr  error
    46  	}{
    47  		{
    48  			name:         "Test standard GCS link (old format)",
    49  			src:          "test-bucket/logs/example-ci-run/403",
    50  			exBucket:     "test-bucket",
    51  			exJobPrefix:  "logs/example-ci-run/403/",
    52  			exName:       "example-ci-run",
    53  			exBuildID:    "403",
    54  			exLinkPrefix: "gs://",
    55  			exSource:     "gs://test-bucket/logs/example-ci-run/403",
    56  			expectedErr:  nil,
    57  		},
    58  		{
    59  			name:         "Test GCS link with trailing / (old format)",
    60  			src:          "test-bucket/logs/example-ci-run/403/",
    61  			exBucket:     "test-bucket",
    62  			exJobPrefix:  "logs/example-ci-run/403/",
    63  			exName:       "example-ci-run",
    64  			exBuildID:    "403",
    65  			exLinkPrefix: "gs://",
    66  			exSource:     "gs://test-bucket/logs/example-ci-run/403/",
    67  			expectedErr:  nil,
    68  		},
    69  		{
    70  			name:         "Test GCS link with org name (old format)",
    71  			src:          "test-bucket/logs/sig-flexing/example-ci-run/403",
    72  			exBucket:     "test-bucket",
    73  			exJobPrefix:  "logs/sig-flexing/example-ci-run/403/",
    74  			exName:       "example-ci-run",
    75  			exBuildID:    "403",
    76  			exLinkPrefix: "gs://",
    77  			exSource:     "gs://test-bucket/logs/sig-flexing/example-ci-run/403",
    78  			expectedErr:  nil,
    79  		},
    80  		{
    81  			name:         "Test standard GCS link (new format)",
    82  			src:          "gs://test-bucket/logs/example-ci-run/403",
    83  			exBucket:     "test-bucket",
    84  			exJobPrefix:  "logs/example-ci-run/403/",
    85  			exName:       "example-ci-run",
    86  			exBuildID:    "403",
    87  			exLinkPrefix: "gs://",
    88  			exSource:     "gs://test-bucket/logs/example-ci-run/403",
    89  			expectedErr:  nil,
    90  		},
    91  		{
    92  			name:         "Test standard GCS link (new format) with bucket alias",
    93  			src:          "gs://alias/logs/example-ci-run/403",
    94  			exBucket:     "test-bucket",
    95  			exJobPrefix:  "logs/example-ci-run/403/",
    96  			exName:       "example-ci-run",
    97  			exBuildID:    "403",
    98  			exLinkPrefix: "gs://",
    99  			exSource:     "gs://test-bucket/logs/example-ci-run/403",
   100  			expectedErr:  nil,
   101  		},
   102  		{
   103  			name:         "Test GCS link with trailing / (new format)",
   104  			src:          "gs://test-bucket/logs/example-ci-run/403/",
   105  			exBucket:     "test-bucket",
   106  			exJobPrefix:  "logs/example-ci-run/403/",
   107  			exName:       "example-ci-run",
   108  			exBuildID:    "403",
   109  			exLinkPrefix: "gs://",
   110  			exSource:     "gs://test-bucket/logs/example-ci-run/403/",
   111  			expectedErr:  nil,
   112  		},
   113  		{
   114  			name:         "Test GCS link with org name (new format)",
   115  			src:          "gs://test-bucket/logs/sig-flexing/example-ci-run/403",
   116  			exBucket:     "test-bucket",
   117  			exJobPrefix:  "logs/sig-flexing/example-ci-run/403/",
   118  			exName:       "example-ci-run",
   119  			exBuildID:    "403",
   120  			exLinkPrefix: "gs://",
   121  			exSource:     "gs://test-bucket/logs/sig-flexing/example-ci-run/403",
   122  			expectedErr:  nil,
   123  		},
   124  		{
   125  			name:         "Test standard S3 link",
   126  			src:          "s3://test-bucket/logs/example-ci-run/403",
   127  			exBucket:     "test-bucket",
   128  			exJobPrefix:  "logs/example-ci-run/403/",
   129  			exName:       "example-ci-run",
   130  			exBuildID:    "403",
   131  			exLinkPrefix: "s3://",
   132  			exSource:     "s3://test-bucket/logs/example-ci-run/403",
   133  			expectedErr:  nil,
   134  		},
   135  		{
   136  			name:         "Test S3 link with trailing /",
   137  			src:          "s3://test-bucket/logs/example-ci-run/403/",
   138  			exBucket:     "test-bucket",
   139  			exJobPrefix:  "logs/example-ci-run/403/",
   140  			exName:       "example-ci-run",
   141  			exBuildID:    "403",
   142  			exLinkPrefix: "s3://",
   143  			exSource:     "s3://test-bucket/logs/example-ci-run/403/",
   144  			expectedErr:  nil,
   145  		},
   146  		{
   147  			name:         "Test S3 link with org name",
   148  			src:          "s3://test-bucket/logs/sig-flexing/example-ci-run/403",
   149  			exBucket:     "test-bucket",
   150  			exJobPrefix:  "logs/sig-flexing/example-ci-run/403/",
   151  			exName:       "example-ci-run",
   152  			exBuildID:    "403",
   153  			exLinkPrefix: "s3://",
   154  			exSource:     "s3://test-bucket/logs/sig-flexing/example-ci-run/403",
   155  			expectedErr:  nil,
   156  		},
   157  		{
   158  			name:        "Test S3 link which cannot be parsed",
   159  			src:         "s3;://test-bucket/logs/sig-flexing/example-ci-run/403",
   160  			expectedErr: ErrCannotParseSource,
   161  		},
   162  	}
   163  	for _, tc := range testCases {
   164  		t.Run(tc.name, func(t *testing.T) {
   165  			cfg := createConfigGetter("test-bucket")
   166  			cfg().Deck.Spyglass.BucketAliases = map[string]string{"alias": "test-bucket"}
   167  			af := NewStorageArtifactFetcher(nil, cfg, false)
   168  			jobSource, err := af.newStorageJobSource(tc.src)
   169  			if err != tc.expectedErr {
   170  				t.Errorf("Expected err: %v, got err: %v", tc.expectedErr, err)
   171  			}
   172  			if tc.exBucket != jobSource.bucket {
   173  				t.Errorf("Expected bucket %s, got %s", tc.exBucket, jobSource.bucket)
   174  			}
   175  			if tc.exName != jobSource.jobName {
   176  				t.Errorf("Expected jobName %s, got %s", tc.exName, jobSource.jobName)
   177  			}
   178  			if tc.exJobPrefix != jobSource.jobPrefix {
   179  				t.Errorf("Expected jobPrefix %s, got %s", tc.exJobPrefix, jobSource.jobPrefix)
   180  			}
   181  			if tc.exLinkPrefix != jobSource.linkPrefix {
   182  				t.Errorf("Expected linkPrefix %s, got %s", tc.exLinkPrefix, jobSource.linkPrefix)
   183  			}
   184  			if tc.exSource != jobSource.source {
   185  				t.Errorf("Expected source %s, got %s", tc.exSource, jobSource.source)
   186  			}
   187  		})
   188  	}
   189  }
   190  
   191  // Tests listing objects associated with the current job in GCS
   192  func TestArtifacts_ListGCS(t *testing.T) {
   193  	cfg := createConfigGetter("test-bucket")
   194  	cfg().Deck.Spyglass.BucketAliases = map[string]string{"alias": "test-bucket"}
   195  	fakeGCSClient := fakeGCSServer.Client()
   196  	testAf := NewStorageArtifactFetcher(io.NewGCSOpener(fakeGCSClient), cfg, false)
   197  	testCases := []struct {
   198  		name              string
   199  		handle            artifactHandle
   200  		source            string
   201  		expectedArtifacts []string
   202  	}{
   203  		{
   204  			name:   "Test ArtifactFetcher simple list artifacts (old format)",
   205  			source: "test-bucket/logs/example-ci-run/403",
   206  			expectedArtifacts: []string{
   207  				"build-log.txt",
   208  				prowv1.StartedStatusFile,
   209  				prowv1.FinishedStatusFile,
   210  				"junit_01.xml",
   211  				"long-log.txt",
   212  			},
   213  		},
   214  		{
   215  			name:              "Test ArtifactFetcher list artifacts on source with no artifacts (old format)",
   216  			source:            "test-bucket/logs/example-ci/404",
   217  			expectedArtifacts: []string{},
   218  		},
   219  		{
   220  			name:   "Test ArtifactFetcher simple list artifacts (new format)",
   221  			source: "gs://test-bucket/logs/example-ci-run/403",
   222  			expectedArtifacts: []string{
   223  				"build-log.txt",
   224  				prowv1.StartedStatusFile,
   225  				prowv1.FinishedStatusFile,
   226  				"junit_01.xml",
   227  				"long-log.txt",
   228  			},
   229  		},
   230  		{
   231  			name:              "Test ArtifactFetcher list artifacts on source with no artifacts (new format)",
   232  			source:            "gs://test-bucket/logs/example-ci/404",
   233  			expectedArtifacts: []string{},
   234  		},
   235  		{
   236  			name:   "Test ArtifactFetcher list artifacts with bucket alias configured",
   237  			source: "gs://alias/logs/example-ci-run/403",
   238  			expectedArtifacts: []string{
   239  				"build-log.txt",
   240  				prowv1.StartedStatusFile,
   241  				prowv1.FinishedStatusFile,
   242  				"junit_01.xml",
   243  				"long-log.txt",
   244  			},
   245  		},
   246  	}
   247  
   248  	for _, tc := range testCases {
   249  		t.Run(tc.name, func(nested *testing.T) {
   250  			actualArtifacts, err := testAf.artifacts(context.Background(), tc.source)
   251  			if err != nil {
   252  				nested.Fatalf("Failed to get artifact names: %v", err)
   253  			}
   254  			for _, ea := range tc.expectedArtifacts {
   255  				found := false
   256  				for _, aa := range actualArtifacts {
   257  					if ea == aa {
   258  						found = true
   259  						break
   260  					}
   261  				}
   262  				if !found {
   263  					nested.Fatalf("failed to retrieve the following artifact: %s\nRetrieved: %s.", ea, actualArtifacts)
   264  				}
   265  
   266  			}
   267  			if len(tc.expectedArtifacts) != len(actualArtifacts) {
   268  				nested.Fatalf("produced more artifacts than expected. Expected: %s\nActual: %s.", tc.expectedArtifacts, actualArtifacts)
   269  			}
   270  		})
   271  	}
   272  }
   273  
   274  // Tests getting handles to objects associated with the current job in GCS
   275  func TestFetchArtifacts_GCS(t *testing.T) {
   276  	cfg := createConfigGetter("test-bucket")
   277  	fakeGCSClient := fakeGCSServer.Client()
   278  	testAf := NewStorageArtifactFetcher(io.NewGCSOpener(fakeGCSClient), cfg, false)
   279  	maxSize := int64(500e6)
   280  	testCases := []struct {
   281  		name             string
   282  		artifactName     string
   283  		source           string
   284  		expectedSize     int64
   285  		expectedMetadata map[string]string
   286  		expectErr        bool
   287  	}{
   288  		{
   289  			name:         "Fetch build-log.txt from valid source",
   290  			artifactName: "build-log.txt",
   291  			source:       "test-bucket/logs/example-ci-run/403",
   292  			expectedSize: 25,
   293  			expectedMetadata: map[string]string{
   294  				"foo": "bar",
   295  			},
   296  		},
   297  		{
   298  			name:         "Fetch build-log.txt from invalid source",
   299  			artifactName: "build-log.txt",
   300  			source:       "test-bucket/logs/example-ci-run/404",
   301  			expectErr:    true,
   302  		},
   303  		{
   304  			name:         "Fetch build-log.txt from valid source",
   305  			artifactName: "build-log.txt",
   306  			source:       "gs://test-bucket/logs/example-ci-run/403",
   307  			expectedSize: 25,
   308  			expectedMetadata: map[string]string{
   309  				"foo": "bar",
   310  			},
   311  		},
   312  		{
   313  			name:         "Fetch build-log.txt from invalid source",
   314  			artifactName: "build-log.txt",
   315  			source:       "gs://test-bucket/logs/example-ci-run/404",
   316  			expectErr:    true,
   317  		},
   318  	}
   319  
   320  	for _, tc := range testCases {
   321  		t.Run(tc.name, func(t *testing.T) {
   322  			artifact, err := testAf.Artifact(context.Background(), tc.source, tc.artifactName, maxSize)
   323  			if err != nil {
   324  				t.Errorf("Failed to get artifacts: %v", err)
   325  			}
   326  			size, err := artifact.Size()
   327  			if err != nil && !tc.expectErr {
   328  				t.Fatalf("Failed getting size for artifact %s, err: %v", artifact.JobPath(), err)
   329  			}
   330  			if err == nil && tc.expectErr {
   331  				t.Error("Expected error, got no error")
   332  			}
   333  
   334  			if size != tc.expectedSize {
   335  				t.Errorf("Expected artifact with size %d but got %d", tc.expectedSize, size)
   336  			}
   337  			meta, err := artifact.Metadata()
   338  			if err != nil && !tc.expectErr {
   339  				t.Fatalf("Failed getting metadata for artifact %s, err: %v", artifact.JobPath(), err)
   340  			}
   341  			if err == nil && tc.expectErr {
   342  				t.Errorf("Expected error, got no error")
   343  			}
   344  			if diff := cmp.Diff(tc.expectedMetadata, meta); diff != "" {
   345  				t.Errorf("Metadata got unexpected diff (-want +got):\n%s", diff)
   346  			}
   347  		})
   348  	}
   349  }
   350  
   351  func TestSignURL(t *testing.T) {
   352  	// This fake key is revoked and thus worthless but still make its contents less obvious
   353  	fakeKeyBuf, err := base64.StdEncoding.DecodeString(`
   354  LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tXG5NSUlFdlFJQkFEQU5CZ2txaGtpRzl3MEJBUUVG
   355  QUFTQ0JLY3dnZ1NqQWdFQUFvSUJBUUN4MEF2aW1yMjcwZDdaXG5pamw3b1FRUW1oZTFOb3dpeWMy
   356  UStuQW95aFE1YkQvUW1jb01zcWg2YldneVI0UU90aXVBbHM2VWhJenF4Q25pXG5PazRmbWJqVnhp
   357  STl1Ri9EVTV6ZE5wM0dkQWFiUlVPNW5yWkpMelN0VXhudFBEcjZvK281RHM5YWJJWkNYYUVTXG5o
   358  UWxOdTBrUm5HbHZGUHNkV1JYMmtSN01Yb3pkcXczcHZZRXZyaGlhRStYZnRhUzhKdmZEc0NPT2RQ
   359  OWp5TzNTXG5aR2lkaU5hRmhYK2xnZEcrdHdqOUE3UDFlb1NMbTZCdXVhcjRDOGhlOEVkVGVEbXVk
   360  a1BPeWwvb2tHWU5tSzJkXG5yUkQ0WHBhcy93VGxsTXBLRUZxWllZeVdkRnJvVWQwMFVhQnhHV0cz
   361  UlZ2TWZoRk80QUhrSkNwZlE1U00rSElmXG5VN2lkRjAyYkFnTUJBQUVDZ2dFQURIaVhoTTZ1bFFB
   362  OHZZdzB5T2Q3cGdCd3ZqeHpxckwxc0gvb0l1dzlhK09jXG5QREMxRzV2aU5pZjdRVitEc3haeXlh
   363  T0tISitKVktQcWZodnh3OFNmMHBxQlowdkpwNlR6SVE3R0ZSZXBLUFc4XG5NTVloYWRPZVFiUE00
   364  emN3dWNpS1VuTW45dU1hcllmc2xxUnZDUjBrSEZDWWtucHB2RjYxckNQMGdZZjJJRXZUXG5qNVlV
   365  QWFrNDlVRDQyaUdEZnh2OGUzMGlMTmRRWE1iMHE3V2dyRGdxL0ttUHM2Q2dOaGRzME1uSlRFbUE5
   366  YlFtXG52MHV0K2hUYWpXalcxVWNyUTBnM2JjNng1VWN2V1VjK1ZndUllVmxVcEgvM2dJNXVYZkxn
   367  bTVQNThNa0s4UlhTXG5YYW92Rk05VkNNRFhTK25PWk1uSXoyNVd5QmhkNmdpVWs5UkJhc05Tb1FL
   368  QmdRRGFxUXpyYWJUZEZNY1hwVlNnXG41TUpuNEcvSFVPWUxveVM5cE9UZi9qbFN1ZUYrNkt6RGJV
   369  N1F6TC9wT1JtYjJldVdxdmpmZDVBaU1oUnY2Snk1XG41ZVNpa3dYRDZJeS9sZGh3QUdtMUZrZ1ZX
   370  TXJ3ZHlqYjJpV2I2Um4rNXRBYjgwdzNEN2ZTWWhEWkxUOWJCNjdCXG4ybGxiOGFycEJRcndZUFFB
   371  U2pUVUVYQnVJUUtCZ1FEUUxVemkrd0tHNEhLeko1NE1sQjFoR3cwSFZlWEV4T0pmXG53bS9IVjhl
   372  aThDeHZLMTRoRXpCT3JXQi9aNlo4VFFxWnA0eENnYkNiY0hwY3pLRUxvcDA2K2hqa1N3ZkR2TUJZ
   373  XG5mNnN6U2RSenNYVTI1NndmcG1hRjJ0TlJZZFpVblh2QWc5MFIrb1BFSjhrRHd4cjdiMGZmL3lu
   374  b0UrWUx0ckowXG53dklad3Joc093S0JnQWVPbWlTMHRZeUNnRkwvNHNuZ3ZodEs5WElGQ0w1VU9C
   375  dlp6Qk0xdlJOdjJ5eEFyRi9nXG5zajJqSmVyUWoyTUVpQkRmL2RQelZPYnBwaTByOCthMDNFOEdG
   376  OGZxakpxK2VnbDg2aXBaQjhxOUU5NTFyOUxSXG5Xa1ZtTEFEVVIxTC8rSjFhakxiWHJzOWlzZkxh
   377  ZEI2OUJpT1lXWmpPRk0reitocmNkYkR5blZraEFvR0FJbW42XG50ZU1zN2NNWTh3anZsY0MrZ3Br
   378  SU5GZzgzYVIyajhJQzNIOWtYMGs0N3ovS0ZjbW9TTGxjcEhNc0VJeGozamJXXG5kd0FkZy9TNkpi
   379  RW1SbGdoaWVoaVNRc21RM05ta0xxNlFJWkorcjR4VkZ4RUZnOWFEM0szVUZMT0xickRCSFpJXG5D
   380  M3JRWVpMNkpnY1E1TlBtbTk4QXZIN2RucjRiRGpaVDgzSS9McFVDZ1lFQWttNXlvVUtZY0tXMVQz
   381  R1hadUNIXG40SDNWVGVzZDZyb3pKWUhmTWVkNE9jQ3l1bnBIVmZmSmFCMFIxRjZ2MjFQaitCVWlW
   382  WjBzU010RjEvTE1uQkc4XG5TQVlQUnVxOHVNUUdNQTFpdE1Hc2VhMmg1V2RhbXNGODhXRFd4VEoy
   383  QXVnblJHNERsdmJLUDhPQmVLUFFKeDhEXG5RMzJ2SVpNUVkyV1hVMVhwUkMrNWs5RT1cbi0tLS0t
   384  RU5EIFBSSVZBVEUgS0VZLS0tLS1cbgo=`)
   385  	if err != nil {
   386  		t.Fatalf("Failed to decode fake key: %v", err)
   387  	}
   388  	fakePrivateKey := strings.TrimSpace(string(fakeKeyBuf))
   389  	cases := []struct {
   390  		name      string
   391  		fakeCreds string
   392  		useCookie bool
   393  		expected  string
   394  		contains  []string
   395  		err       string
   396  	}{
   397  		{
   398  			name:     "anon auth works",
   399  			expected: fmt.Sprintf("https://%s/foo/bar/stuff", io.GSAnonHost),
   400  		},
   401  		{
   402  			name:      "cookie auth works",
   403  			useCookie: true,
   404  			expected:  fmt.Sprintf("https://%s/foo/bar/stuff", io.GSCookieHost),
   405  		},
   406  		{
   407  			name:      "invalid json file errors",
   408  			fakeCreds: "yaml: 123",
   409  			err:       "dialing: invalid character 'y' looking for beginning of value",
   410  		},
   411  		{
   412  			name: "bad private key errors",
   413  			fakeCreds: `{
   414  			  "type": "service_account",
   415  			  "private_key": "-----BEGIN PRIVATE KEY-----\nMIIE==\n-----END PRIVATE KEY-----\n",
   416  			  "client_email": "fake-user@k8s.io"
   417  			}`,
   418  			err: "asn1: structure error: tags don't match (16 vs {class:0 tag:13 length:45 isCompound:true}) {optional:false explicit:false application:false private:false defaultValue:<nil> tag:<nil> stringType:0 timeType:0 set:false omitEmpty:false} pkcs1PrivateKey @2",
   419  		},
   420  		{
   421  			name: "bad type errors",
   422  			fakeCreds: `{
   423  			  "type": "user",
   424  			  "private_key": "` + fakePrivateKey + `",
   425  			  "client_email": "fake-user@k8s.io"
   426  			}`,
   427  			err: "dialing: unknown credential type: \"user\"",
   428  		},
   429  		{
   430  			name: "signed URLs work",
   431  			fakeCreds: `{
   432  			  "type": "service_account",
   433  			  "private_key": "` + fakePrivateKey + `",
   434  			  "client_email": "fake-user@k8s.io"
   435  			}`,
   436  			contains: []string{
   437  				"https://storage.googleapis.com/foo/bar/stuff?",
   438  				"GoogleAccessId=fake-user%40k8s.io",
   439  				"Signature=", // Do not particularly care about the Signature contents
   440  			},
   441  		},
   442  	}
   443  
   444  	for _, tc := range cases {
   445  		t.Run(tc.name, func(t *testing.T) {
   446  			var path string
   447  			if tc.fakeCreds != "" {
   448  				fp, err := os.CreateTemp("", "fake-creds")
   449  				if err != nil {
   450  					t.Fatalf("Failed to create fake creds: %v", err)
   451  				}
   452  
   453  				path = fp.Name()
   454  				defer os.Remove(path)
   455  				if _, err := fp.Write([]byte(tc.fakeCreds)); err != nil {
   456  					t.Fatalf("Failed to write fake creds %s: %v", path, err)
   457  				}
   458  
   459  				if err := fp.Close(); err != nil {
   460  					t.Fatalf("Failed to close fake creds %s: %v", path, err)
   461  				}
   462  			}
   463  			// We're testing the combination of NewOpener and signURL here
   464  			// to make sure that the behaviour is more or less the same as before
   465  			// we moved the signURL code to the io package.
   466  			// The errors which were previously tested on the signURL method are now
   467  			// already returned by newOpener.
   468  			// Before, these error should have already lead to errors on gcs client creation, so signURL probably was never able to produce these errors during runtime.
   469  			// (because deck crashed on gcsClient creation)
   470  			var actual string
   471  			cfg := createConfigGetter("test-bucket")
   472  			opener, err := io.NewOpener(context.Background(), path, "")
   473  			if err == nil {
   474  				af := NewStorageArtifactFetcher(opener, cfg, tc.useCookie)
   475  				actual, err = af.signURL(context.Background(), "gs://foo/bar/stuff")
   476  			}
   477  			switch {
   478  			case err != nil:
   479  				if tc.err != err.Error() {
   480  					t.Errorf("expected error: %v, got: %v", tc.err, err)
   481  				}
   482  			case tc.err != "":
   483  				t.Errorf("Failed to receive an expected error, got %q", actual)
   484  			case len(tc.contains) == 0 && actual != tc.expected:
   485  				t.Errorf("signURL(): got %q, want %q", actual, tc.expected)
   486  			default:
   487  				for _, part := range tc.contains {
   488  					if !strings.Contains(actual, part) {
   489  						t.Errorf("signURL(): got %q, does not contain %q", actual, part)
   490  					}
   491  				}
   492  			}
   493  		})
   494  	}
   495  }
   496  
   497  func createConfigGetter(bucketNames ...string) config.Getter {
   498  	ca := config.Agent{}
   499  	ca.Set(&config.Config{
   500  		ProwConfig: config.ProwConfig{
   501  			Deck: config.Deck{
   502  				AllKnownStorageBuckets: sets.New[string](bucketNames...),
   503  			},
   504  		},
   505  	})
   506  	return ca.Config
   507  }