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

     1  /*
     2  Copyright 2020 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 io
    18  
    19  import (
    20  	"context"
    21  	"encoding/base64"
    22  	"errors"
    23  	"fmt"
    24  	"net/http"
    25  	"os"
    26  	"strings"
    27  	"testing"
    28  
    29  	"cloud.google.com/go/storage"
    30  	"google.golang.org/api/googleapi"
    31  )
    32  
    33  func Test_opener_SignedURL(t *testing.T) {
    34  	// This fake key is revoked and thus worthless but still make its contents less obvious
    35  	fakeKeyBuf, err := base64.StdEncoding.DecodeString(`
    36  LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tXG5NSUlFdlFJQkFEQU5CZ2txaGtpRzl3MEJBUUVG
    37  QUFTQ0JLY3dnZ1NqQWdFQUFvSUJBUUN4MEF2aW1yMjcwZDdaXG5pamw3b1FRUW1oZTFOb3dpeWMy
    38  UStuQW95aFE1YkQvUW1jb01zcWg2YldneVI0UU90aXVBbHM2VWhJenF4Q25pXG5PazRmbWJqVnhp
    39  STl1Ri9EVTV6ZE5wM0dkQWFiUlVPNW5yWkpMelN0VXhudFBEcjZvK281RHM5YWJJWkNYYUVTXG5o
    40  UWxOdTBrUm5HbHZGUHNkV1JYMmtSN01Yb3pkcXczcHZZRXZyaGlhRStYZnRhUzhKdmZEc0NPT2RQ
    41  OWp5TzNTXG5aR2lkaU5hRmhYK2xnZEcrdHdqOUE3UDFlb1NMbTZCdXVhcjRDOGhlOEVkVGVEbXVk
    42  a1BPeWwvb2tHWU5tSzJkXG5yUkQ0WHBhcy93VGxsTXBLRUZxWllZeVdkRnJvVWQwMFVhQnhHV0cz
    43  UlZ2TWZoRk80QUhrSkNwZlE1U00rSElmXG5VN2lkRjAyYkFnTUJBQUVDZ2dFQURIaVhoTTZ1bFFB
    44  OHZZdzB5T2Q3cGdCd3ZqeHpxckwxc0gvb0l1dzlhK09jXG5QREMxRzV2aU5pZjdRVitEc3haeXlh
    45  T0tISitKVktQcWZodnh3OFNmMHBxQlowdkpwNlR6SVE3R0ZSZXBLUFc4XG5NTVloYWRPZVFiUE00
    46  emN3dWNpS1VuTW45dU1hcllmc2xxUnZDUjBrSEZDWWtucHB2RjYxckNQMGdZZjJJRXZUXG5qNVlV
    47  QWFrNDlVRDQyaUdEZnh2OGUzMGlMTmRRWE1iMHE3V2dyRGdxL0ttUHM2Q2dOaGRzME1uSlRFbUE5
    48  YlFtXG52MHV0K2hUYWpXalcxVWNyUTBnM2JjNng1VWN2V1VjK1ZndUllVmxVcEgvM2dJNXVYZkxn
    49  bTVQNThNa0s4UlhTXG5YYW92Rk05VkNNRFhTK25PWk1uSXoyNVd5QmhkNmdpVWs5UkJhc05Tb1FL
    50  QmdRRGFxUXpyYWJUZEZNY1hwVlNnXG41TUpuNEcvSFVPWUxveVM5cE9UZi9qbFN1ZUYrNkt6RGJV
    51  N1F6TC9wT1JtYjJldVdxdmpmZDVBaU1oUnY2Snk1XG41ZVNpa3dYRDZJeS9sZGh3QUdtMUZrZ1ZX
    52  TXJ3ZHlqYjJpV2I2Um4rNXRBYjgwdzNEN2ZTWWhEWkxUOWJCNjdCXG4ybGxiOGFycEJRcndZUFFB
    53  U2pUVUVYQnVJUUtCZ1FEUUxVemkrd0tHNEhLeko1NE1sQjFoR3cwSFZlWEV4T0pmXG53bS9IVjhl
    54  aThDeHZLMTRoRXpCT3JXQi9aNlo4VFFxWnA0eENnYkNiY0hwY3pLRUxvcDA2K2hqa1N3ZkR2TUJZ
    55  XG5mNnN6U2RSenNYVTI1NndmcG1hRjJ0TlJZZFpVblh2QWc5MFIrb1BFSjhrRHd4cjdiMGZmL3lu
    56  b0UrWUx0ckowXG53dklad3Joc093S0JnQWVPbWlTMHRZeUNnRkwvNHNuZ3ZodEs5WElGQ0w1VU9C
    57  dlp6Qk0xdlJOdjJ5eEFyRi9nXG5zajJqSmVyUWoyTUVpQkRmL2RQelZPYnBwaTByOCthMDNFOEdG
    58  OGZxakpxK2VnbDg2aXBaQjhxOUU5NTFyOUxSXG5Xa1ZtTEFEVVIxTC8rSjFhakxiWHJzOWlzZkxh
    59  ZEI2OUJpT1lXWmpPRk0reitocmNkYkR5blZraEFvR0FJbW42XG50ZU1zN2NNWTh3anZsY0MrZ3Br
    60  SU5GZzgzYVIyajhJQzNIOWtYMGs0N3ovS0ZjbW9TTGxjcEhNc0VJeGozamJXXG5kd0FkZy9TNkpi
    61  RW1SbGdoaWVoaVNRc21RM05ta0xxNlFJWkorcjR4VkZ4RUZnOWFEM0szVUZMT0xickRCSFpJXG5D
    62  M3JRWVpMNkpnY1E1TlBtbTk4QXZIN2RucjRiRGpaVDgzSS9McFVDZ1lFQWttNXlvVUtZY0tXMVQz
    63  R1hadUNIXG40SDNWVGVzZDZyb3pKWUhmTWVkNE9jQ3l1bnBIVmZmSmFCMFIxRjZ2MjFQaitCVWlW
    64  WjBzU010RjEvTE1uQkc4XG5TQVlQUnVxOHVNUUdNQTFpdE1Hc2VhMmg1V2RhbXNGODhXRFd4VEoy
    65  QXVnblJHNERsdmJLUDhPQmVLUFFKeDhEXG5RMzJ2SVpNUVkyV1hVMVhwUkMrNWs5RT1cbi0tLS0t
    66  RU5EIFBSSVZBVEUgS0VZLS0tLS1cbgo=`)
    67  	if err != nil {
    68  		t.Fatalf("Failed to decode fake key: %v", err)
    69  	}
    70  	fakePrivateKey := strings.TrimSpace(string(fakeKeyBuf))
    71  	type args struct {
    72  		ctx  context.Context
    73  		p    string
    74  		opts SignedURLOptions
    75  	}
    76  	tests := []struct {
    77  		name      string
    78  		args      args
    79  		fakeCreds string
    80  		want      string
    81  		contains  []string
    82  		wantErr   bool
    83  	}{
    84  		{
    85  			name: "anon auth works",
    86  			args: args{
    87  				p: "gs://foo/bar/stuff",
    88  			},
    89  			want: fmt.Sprintf("https://%s/foo/bar/stuff", GSAnonHost),
    90  		},
    91  		{
    92  			name: "cookie auth works",
    93  			args: args{
    94  				p: "gs://foo/bar/stuff",
    95  				opts: SignedURLOptions{
    96  					UseGSCookieAuth: true,
    97  				},
    98  			},
    99  			want: fmt.Sprintf("https://%s/foo/bar/stuff", GSCookieHost),
   100  		},
   101  		{
   102  			name: "signed URLs work",
   103  			args: args{
   104  				p: "gs://foo/bar/stuff",
   105  			},
   106  			fakeCreds: `{
   107  			  "type": "service_account",
   108  			  "private_key": "` + fakePrivateKey + `",
   109  			  "client_email": "fake-user@k8s.io"
   110  			}`,
   111  			contains: []string{
   112  				"https://storage.googleapis.com/foo/bar/stuff?",
   113  				"GoogleAccessId=fake-user%40k8s.io",
   114  				"Signature=", // Do not particularly care about the Signature contents
   115  			},
   116  		},
   117  	}
   118  	for _, tt := range tests {
   119  		t.Run(tt.name, func(t *testing.T) {
   120  			var gcsCredentialsFile string
   121  			if tt.fakeCreds != "" {
   122  				fp, err := os.CreateTemp("", "fake-creds")
   123  				if err != nil {
   124  					t.Fatalf("Failed to create fake creds: %v", err)
   125  				}
   126  
   127  				gcsCredentialsFile = fp.Name()
   128  				defer os.Remove(gcsCredentialsFile)
   129  				if _, err := fp.Write([]byte(tt.fakeCreds)); err != nil {
   130  					t.Fatalf("Failed to write fake creds %s: %v", gcsCredentialsFile, err)
   131  				}
   132  
   133  				if err := fp.Close(); err != nil {
   134  					t.Fatalf("Failed to close fake creds %s: %v", gcsCredentialsFile, err)
   135  				}
   136  			}
   137  			o, _ := NewOpener(context.Background(), gcsCredentialsFile, "")
   138  			got, err := o.SignedURL(tt.args.ctx, tt.args.p, tt.args.opts)
   139  			if (err != nil) != tt.wantErr {
   140  				t.Errorf("SignedURL() error = %v, wantErr %v", err, tt.wantErr)
   141  				return
   142  			}
   143  			if tt.want != "" && got != tt.want {
   144  				t.Errorf("SignedURL() got = %v, want %v", got, tt.want)
   145  			}
   146  			if len(tt.contains) > 0 {
   147  				for _, contains := range tt.contains {
   148  					if !strings.Contains(got, contains) {
   149  						t.Errorf("SignedURL() got = %q, does not contain %q", got, contains)
   150  					}
   151  				}
   152  			}
   153  		})
   154  	}
   155  }
   156  
   157  func TestIsNotExist(t *testing.T) {
   158  	t.Parallel()
   159  	testCases := []struct {
   160  		name        string
   161  		err         error
   162  		expectMatch bool
   163  	}{
   164  		{
   165  			name:        "Direct ErrNotExist",
   166  			err:         os.ErrNotExist,
   167  			expectMatch: true,
   168  		},
   169  		{
   170  			name:        "Direct storage.ErrObjectNotExist",
   171  			err:         storage.ErrObjectNotExist,
   172  			expectMatch: true,
   173  		},
   174  		{
   175  			name:        "Direct other",
   176  			err:         errors.New("not workx"),
   177  			expectMatch: false,
   178  		},
   179  		{
   180  			name:        "Wrapped ErrNotExist",
   181  			err:         fmt.Errorf("around: %w", os.ErrNotExist),
   182  			expectMatch: true,
   183  		},
   184  		{
   185  			name:        "Wrapped storage.ErrObjectNotExist",
   186  			err:         fmt.Errorf("around: %w", storage.ErrObjectNotExist),
   187  			expectMatch: true,
   188  		},
   189  		{
   190  			name:        "Wrapped other",
   191  			err:         fmt.Errorf("I see it %w", errors.New("not workx")),
   192  			expectMatch: false,
   193  		},
   194  		{
   195  			name:        "Don't panic",
   196  			expectMatch: false,
   197  		},
   198  	}
   199  
   200  	for _, tc := range testCases {
   201  		t.Run(tc.name, func(t *testing.T) {
   202  			if result := IsNotExist(tc.err); result != tc.expectMatch {
   203  				t.Errorf("expect match: %t, got match: %t", tc.expectMatch, result)
   204  			}
   205  		})
   206  	}
   207  }
   208  
   209  func TestIsErrUnexpected(t *testing.T) {
   210  	tests := []struct {
   211  		name       string
   212  		err        error
   213  		unexpected bool
   214  	}{
   215  		{
   216  			name:       "standard errors are unexpected",
   217  			err:        errors.New("this is just a normal error"),
   218  			unexpected: true,
   219  		},
   220  		{
   221  			name:       "nil errors are expected",
   222  			err:        nil,
   223  			unexpected: false,
   224  		},
   225  		{
   226  			name:       "googleapi errors other than Precondition Failed are unexpected",
   227  			err:        &googleapi.Error{Code: http.StatusNotFound},
   228  			unexpected: true,
   229  		},
   230  		{
   231  			name:       "Precondition Failed googleapi errors are expected",
   232  			err:        &googleapi.Error{Code: http.StatusPreconditionFailed},
   233  			unexpected: false,
   234  		},
   235  	}
   236  
   237  	for _, tc := range tests {
   238  		t.Run(tc.name, func(t *testing.T) {
   239  			result := isErrUnexpected(tc.err)
   240  			if result != tc.unexpected {
   241  				t.Errorf("Expected isErrUnexpected() to return %v, got %v", tc.unexpected, result)
   242  			}
   243  		})
   244  	}
   245  }