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  }