github.com/treeverse/lakefs@v1.24.1-0.20240520134607-95648127bfb0/pkg/testutil/setup.go (about)

     1  package testutil
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"net/http"
     8  	"net/url"
     9  	"os"
    10  	"path/filepath"
    11  	"strings"
    12  	"time"
    13  
    14  	"github.com/aws/aws-sdk-go-v2/aws"
    15  	awsconfig "github.com/aws/aws-sdk-go-v2/config"
    16  	"github.com/aws/aws-sdk-go-v2/credentials"
    17  	"github.com/aws/aws-sdk-go-v2/service/s3"
    18  	"github.com/deepmap/oapi-codegen/pkg/securityprovider"
    19  	"github.com/spf13/viper"
    20  	"github.com/treeverse/lakefs/pkg/api/apigen"
    21  	"github.com/treeverse/lakefs/pkg/api/apiutil"
    22  	"github.com/treeverse/lakefs/pkg/block"
    23  	"github.com/treeverse/lakefs/pkg/config"
    24  	"github.com/treeverse/lakefs/pkg/logging"
    25  )
    26  
    27  const defaultSetupTimeout = 5 * time.Minute
    28  
    29  type SetupTestingEnvParams struct {
    30  	Name      string
    31  	StorageNS string
    32  
    33  	// Only if non-empty
    34  	AdminAccessKeyID     string
    35  	AdminSecretAccessKey string
    36  }
    37  
    38  func SetupTestingEnv(params *SetupTestingEnvParams) (logging.Logger, apigen.ClientWithResponsesInterface, *s3.Client, string) {
    39  	logger := logging.ContextUnavailable()
    40  	viper.AddConfigPath(".")
    41  	viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) // support nested config
    42  	viper.SetEnvPrefix(strings.ToUpper(params.Name))
    43  	viper.SetConfigName(strings.ToLower(params.Name))
    44  	viper.AutomaticEnv()
    45  
    46  	viper.SetDefault("setup_lakefs", true)
    47  	viper.SetDefault("setup_lakefs_timeout", defaultSetupTimeout)
    48  	viper.SetDefault("endpoint_url", "http://localhost:8000")
    49  	viper.SetDefault("s3_endpoint", "s3.local.lakefs.io:8000")
    50  	viper.SetDefault("storage_namespace", fmt.Sprintf("s3://%s", params.StorageNS))
    51  	viper.SetDefault(config.BlockstoreTypeKey, block.BlockstoreTypeS3)
    52  	viper.SetDefault("version", "dev")
    53  	currDir, err := os.Getwd()
    54  	if err != nil {
    55  		logger.WithError(err).Fatal("Failed to get CWD")
    56  	}
    57  	viper.SetDefault("glue_export_hooks_database", "export-hooks-esti")
    58  	viper.SetDefault("glue_export_region", "us-east-1")
    59  	viper.SetDefault("lakectl_dir", filepath.Join(currDir, ".."))
    60  	viper.SetDefault("azure_storage_account", "")
    61  	viper.SetDefault("azure_storage_access_key", "")
    62  	viper.SetDefault("large_object_path", "")
    63  	err = viper.ReadInConfig()
    64  	if err != nil && !errors.As(err, &viper.ConfigFileNotFoundError{}) {
    65  		logger.WithError(err).Fatal("Failed to read configuration")
    66  	}
    67  
    68  	ctx := context.Background()
    69  
    70  	// initialize the env/repo
    71  	logger = logging.ContextUnavailable()
    72  	logger.WithField("settings", viper.AllSettings()).Info(fmt.Sprintf("Starting %s", params.Name))
    73  
    74  	endpointURL := ParseEndpointURL(logger, viper.GetString("endpoint_url"))
    75  
    76  	client, err := apigen.NewClientWithResponses(endpointURL)
    77  	if err != nil {
    78  		logger.WithError(err).Fatal("could not initialize API client")
    79  	}
    80  
    81  	if err := waitUntilLakeFSRunning(ctx, logger, client); err != nil {
    82  		logger.WithError(err).Fatal("Waiting for lakeFS")
    83  	}
    84  
    85  	setupLakeFS := viper.GetBool("setup_lakefs")
    86  	if setupLakeFS {
    87  		// first setup of lakeFS
    88  		mockEmail := "test@acme.co"
    89  		_, err := client.SetupCommPrefsWithResponse(context.Background(), apigen.SetupCommPrefsJSONRequestBody{
    90  			Email:           &mockEmail,
    91  			FeatureUpdates:  false,
    92  			SecurityUpdates: false,
    93  		})
    94  		if err != nil {
    95  			logger.WithError(err).Fatal("Failed to setup lakeFS")
    96  		}
    97  		adminUserName := params.Name
    98  		requestBody := apigen.SetupJSONRequestBody{
    99  			Username: adminUserName,
   100  		}
   101  		if params.AdminAccessKeyID != "" || params.AdminSecretAccessKey != "" {
   102  			requestBody.Key = &apigen.AccessKeyCredentials{
   103  				AccessKeyId:     params.AdminAccessKeyID,
   104  				SecretAccessKey: params.AdminSecretAccessKey,
   105  			}
   106  		}
   107  		res, err := client.SetupWithResponse(ctx, requestBody)
   108  		if err != nil {
   109  			logger.WithError(err).Fatal("Failed to setup lakeFS")
   110  		}
   111  		if res.StatusCode() != http.StatusOK {
   112  			logger.WithField("status", res.HTTPResponse.Status).Fatal("Failed to setup lakeFS")
   113  		}
   114  		logger.Info("Cluster setup successfully")
   115  		credentialsWithSecret := res.JSON200
   116  		viper.Set("access_key_id", credentialsWithSecret.AccessKeyId)
   117  		viper.Set("secret_access_key", credentialsWithSecret.SecretAccessKey)
   118  	} else {
   119  		viper.Set("access_key_id", params.AdminAccessKeyID)
   120  		viper.Set("secret_access_key", params.AdminSecretAccessKey)
   121  	}
   122  
   123  	key := viper.GetString("access_key_id")
   124  	secret := viper.GetString("secret_access_key")
   125  	client, err = NewClientFromCreds(logger, key, secret, endpointURL)
   126  	if err != nil {
   127  		logger.WithError(err).Fatal("could not initialize API client with security provider")
   128  	}
   129  
   130  	s3Endpoint := viper.GetString("s3_endpoint")
   131  	svc, err := SetupTestS3Client(s3Endpoint, key, secret)
   132  	if err != nil {
   133  		logger.WithError(err).Fatal("could not initialize S3 client")
   134  	}
   135  	return logger, client, svc, endpointURL
   136  }
   137  
   138  func SetupTestS3Client(endpoint, key, secret string) (*s3.Client, error) {
   139  	if !strings.HasPrefix(endpoint, "http") {
   140  		endpoint = "http://" + endpoint
   141  	}
   142  	cfg, err := awsconfig.LoadDefaultConfig(context.Background(),
   143  		awsconfig.WithRegion("us-east-1"),
   144  		awsconfig.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(key, secret, "")),
   145  	)
   146  	if err != nil {
   147  		return nil, err
   148  	}
   149  	forcePathStyleS3Client := viper.GetBool("force_path_style")
   150  	svc := s3.NewFromConfig(cfg, func(options *s3.Options) {
   151  		options.BaseEndpoint = aws.String(endpoint)
   152  		options.UsePathStyle = forcePathStyleS3Client
   153  	})
   154  	return svc, nil
   155  }
   156  
   157  // ParseEndpointURL parses the given endpoint string
   158  func ParseEndpointURL(logger logging.Logger, endpointURL string) string {
   159  	u, err := url.Parse(endpointURL)
   160  	if err != nil {
   161  		logger.WithError(err).Fatal("could not initialize API client with security provider")
   162  	}
   163  	if u.Path == "" || u.Path == "/" {
   164  		endpointURL = strings.TrimRight(endpointURL, "/") + apiutil.BaseURL
   165  	}
   166  
   167  	return endpointURL
   168  }
   169  
   170  // NewClientFromCreds creates a client using the credentials of a user
   171  func NewClientFromCreds(logger logging.Logger, accessKeyID string, secretAccessKey string, endpointURL string) (*apigen.ClientWithResponses, error) {
   172  	basicAuthProvider, err := securityprovider.NewSecurityProviderBasicAuth(accessKeyID, secretAccessKey)
   173  	if err != nil {
   174  		logger.WithError(err).Fatal("could not initialize basic auth security provider")
   175  	}
   176  
   177  	return apigen.NewClientWithResponses(endpointURL, apigen.WithRequestEditorFn(basicAuthProvider.Intercept))
   178  }
   179  
   180  const checkIteration = 5 * time.Second
   181  
   182  func waitUntilLakeFSRunning(ctx context.Context, logger logging.Logger, cl apigen.ClientWithResponsesInterface) error {
   183  	setupCtx, cancel := context.WithTimeout(ctx, viper.GetDuration("setup_lakefs_timeout"))
   184  	defer cancel()
   185  	for {
   186  		_, err := cl.HealthCheckWithResponse(setupCtx)
   187  		if err == nil {
   188  			return nil
   189  		}
   190  		logger.WithError(err).Info("Setup failed")
   191  
   192  		select {
   193  		case <-setupCtx.Done():
   194  			return setupCtx.Err()
   195  		case <-time.After(checkIteration):
   196  		}
   197  	}
   198  }