github.com/grafana/pyroscope@v1.18.0/pkg/objstore/client/factory_test.go (about) 1 // SPDX-License-Identifier: AGPL-3.0-only 2 // Provenance-includes-location: https://github.com/cortexproject/cortex/blob/master/pkg/storage/bucket/client_test.go 3 // Provenance-includes-license: Apache-2.0 4 // Provenance-includes-copyright: The Cortex Authors. 5 6 package client 7 8 import ( 9 "bytes" 10 "context" 11 "os" 12 "path" 13 "testing" 14 15 "github.com/go-kit/log" 16 "github.com/grafana/dskit/flagext" 17 "github.com/stretchr/testify/assert" 18 "github.com/stretchr/testify/require" 19 "gopkg.in/yaml.v3" 20 21 "github.com/grafana/pyroscope/pkg/objstore/providers/filesystem" 22 ) 23 24 const ( 25 configWithS3Backend = ` 26 backend: s3 27 s3: 28 endpoint: localhost 29 bucket_name: test 30 access_key_id: xxx 31 secret_access_key: yyy 32 insecure: true 33 ` 34 35 configWithGCSBackend = ` 36 backend: gcs 37 gcs: 38 bucket_name: test 39 service_account: |- 40 { 41 "type": "service_account", 42 "project_id": "id", 43 "private_key_id": "id", 44 "private_key": "-----BEGIN PRIVATE KEY-----\nSOMETHING\n-----END PRIVATE KEY-----\n", 45 "client_email": "test@test.com", 46 "client_id": "12345", 47 "auth_uri": "https://accounts.google.com/o/oauth2/auth", 48 "token_uri": "https://oauth2.googleapis.com/token", 49 "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", 50 "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/test%40test.com" 51 } 52 ` 53 54 configWithUnknownBackend = ` 55 backend: unknown 56 ` 57 ) 58 59 func TestNewClient(t *testing.T) { 60 t.Parallel() 61 62 tests := map[string]struct { 63 config string 64 expectedErr error 65 }{ 66 "should create an S3 bucket": { 67 config: configWithS3Backend, 68 expectedErr: nil, 69 }, 70 "should create a GCS bucket": { 71 config: configWithGCSBackend, 72 expectedErr: nil, 73 }, 74 "should return error on unknown backend": { 75 config: configWithUnknownBackend, 76 expectedErr: ErrUnsupportedStorageBackend, 77 }, 78 } 79 80 for testName, testData := range tests { 81 testData := testData 82 83 t.Run(testName, func(t *testing.T) { 84 // Load config 85 cfg := Config{} 86 flagext.DefaultValues(&cfg) 87 88 err := yaml.Unmarshal([]byte(testData.config), &cfg) 89 require.NoError(t, err) 90 91 // Instance a new bucket client from the config 92 bucketClient, err := NewBucket(context.Background(), cfg, "test") 93 require.Equal(t, testData.expectedErr, err) 94 95 if testData.expectedErr == nil { 96 require.NotNil(t, bucketClient) 97 bucketClient.Close() 98 } else { 99 assert.Equal(t, nil, bucketClient) 100 } 101 }) 102 } 103 } 104 105 func TestClient_ConfigValidation(t *testing.T) { 106 testCases := []struct { 107 name string 108 cfg Config 109 expectedError error 110 expectedLog string 111 }{ 112 { 113 name: "prefix/valid", 114 cfg: Config{StorageBackendConfig: StorageBackendConfig{Backend: Filesystem}, Prefix: "helloWORLD123"}, 115 }, 116 { 117 name: "prefix/valid-subdir", 118 cfg: Config{StorageBackendConfig: StorageBackendConfig{Backend: Filesystem}, Prefix: "hello/world/env"}, 119 }, 120 { 121 name: "prefix/valid-subdir-trailing-slash", 122 cfg: Config{StorageBackendConfig: StorageBackendConfig{Backend: Filesystem}, Prefix: "hello/world/env/"}, 123 }, 124 { 125 name: "prefix/invalid-directory-up", 126 cfg: Config{StorageBackendConfig: StorageBackendConfig{Backend: Filesystem}, Prefix: ".."}, 127 expectedError: ErrStoragePrefixInvalidCharacters, 128 }, 129 { 130 name: "prefix/invalid-directory", 131 cfg: Config{StorageBackendConfig: StorageBackendConfig{Backend: Filesystem}, Prefix: "."}, 132 expectedError: ErrStoragePrefixInvalidCharacters, 133 }, 134 { 135 name: "prefix/invalid-absolute-path", 136 cfg: Config{StorageBackendConfig: StorageBackendConfig{Backend: Filesystem}, Prefix: "/hello/world"}, 137 expectedError: ErrStoragePrefixStartsWithSlash, 138 }, 139 { 140 name: "prefix/invalid-..-in-a-path-segement", 141 cfg: Config{StorageBackendConfig: StorageBackendConfig{Backend: Filesystem}, Prefix: "hello/../test"}, 142 expectedError: ErrStoragePrefixInvalidCharacters, 143 }, 144 { 145 name: "prefix/invalid-empty-path-segement", 146 cfg: Config{StorageBackendConfig: StorageBackendConfig{Backend: Filesystem}, Prefix: "hello//test"}, 147 expectedError: ErrStoragePrefixEmptyPathSegment, 148 }, 149 { 150 name: "prefix/invalid-emoji", 151 cfg: Config{StorageBackendConfig: StorageBackendConfig{Backend: Filesystem}, Prefix: "👋"}, 152 expectedError: ErrStoragePrefixInvalidCharacters, 153 }, 154 { 155 name: "prefix/invalid-exclamation-mark", 156 cfg: Config{StorageBackendConfig: StorageBackendConfig{Backend: Filesystem}, Prefix: "hello!world"}, 157 expectedError: ErrStoragePrefixInvalidCharacters, 158 }, 159 { 160 name: "unsupported backend", 161 cfg: Config{StorageBackendConfig: StorageBackendConfig{Backend: "flash drive"}}, 162 expectedError: ErrUnsupportedStorageBackend, 163 }, 164 { 165 name: "prefix/valid-legacy-subdir-trailing-slash", 166 cfg: Config{StorageBackendConfig: StorageBackendConfig{Backend: Filesystem}, DeprecatedStoragePrefix: "hello/world/env/"}, 167 expectedLog: "config has a deprecated storage.storage-prefix flag set", 168 }, 169 { 170 name: "prefix/deprecated-invalid-exclamation-mark", 171 cfg: Config{StorageBackendConfig: StorageBackendConfig{Backend: Filesystem}, DeprecatedStoragePrefix: "hello!world"}, 172 expectedError: ErrStoragePrefixInvalidCharacters, 173 expectedLog: "config has a deprecated storage.storage-prefix flag set", 174 }, 175 { 176 name: "prefix/invalid-both-configs", 177 cfg: Config{StorageBackendConfig: StorageBackendConfig{Backend: Filesystem}, DeprecatedStoragePrefix: "hello-world1", Prefix: "hello-world2"}, 178 expectedError: ErrStoragePrefixBothFlagsSet, 179 }, 180 } 181 182 logBuf := new(bytes.Buffer) 183 logger := log.NewLogfmtLogger(logBuf) 184 185 for _, tc := range testCases { 186 tc := tc 187 t.Run(tc.name, func(t *testing.T) { 188 // Reset log buffer 189 logBuf.Reset() 190 191 actualErr := tc.cfg.Validate(logger) 192 if tc.expectedError != nil { 193 assert.Equal(t, actualErr, tc.expectedError) 194 } else { 195 assert.NoError(t, actualErr) 196 } 197 if tc.expectedLog != "" { 198 assert.Contains(t, logBuf.String(), tc.expectedLog) 199 } else { 200 assert.Empty(t, logBuf.String()) 201 } 202 }) 203 } 204 } 205 206 func TestNewPrefixedBucketClient(t *testing.T) { 207 t.Run("with prefix", func(t *testing.T) { 208 ctx := context.Background() 209 tempDir := t.TempDir() 210 cfg := Config{ 211 StorageBackendConfig: StorageBackendConfig{ 212 Backend: Filesystem, 213 Filesystem: filesystem.Config{ 214 Directory: tempDir, 215 }, 216 }, 217 Prefix: "prefix", 218 } 219 220 client, err := NewBucket(ctx, cfg, "test") 221 require.NoError(t, err) 222 223 err = client.Upload(ctx, "file", bytes.NewBufferString("content")) 224 assert.NoError(t, err) 225 226 _, err = client.Get(ctx, "file") 227 assert.NoError(t, err) 228 229 filePath := path.Join(tempDir, "prefix", "file") 230 assert.FileExists(t, filePath) 231 232 b, err := os.ReadFile(filePath) 233 assert.NoError(t, err) 234 assert.Equal(t, "content", string(b)) 235 }) 236 237 t.Run("without prefix", func(t *testing.T) { 238 ctx := context.Background() 239 tempDir := t.TempDir() 240 cfg := Config{ 241 StorageBackendConfig: StorageBackendConfig{ 242 Backend: Filesystem, 243 Filesystem: filesystem.Config{ 244 Directory: tempDir, 245 }, 246 }, 247 } 248 249 client, err := NewBucket(ctx, cfg, "test") 250 require.NoError(t, err) 251 err = client.Upload(ctx, "file", bytes.NewBufferString("content")) 252 require.NoError(t, err) 253 254 _, err = client.Get(ctx, "file") 255 assert.NoError(t, err) 256 257 filePath := path.Join(tempDir, "file") 258 assert.FileExists(t, filePath) 259 260 b, err := os.ReadFile(filePath) 261 assert.NoError(t, err) 262 assert.Equal(t, "content", string(b)) 263 }) 264 }