func ParseParameters()

in registry/storage/driver/s3-aws/common/parser.go [353:658]


func ParseParameters(driverVersion string, parameters map[string]any) (*DriverParameters, error) {
	var mErr *multierror.Error
	res := new(DriverParameters)

	// Providing no values for these is valid in case the user is authenticating
	// with an IAM on an ec2 instance (in which case the instance credentials will
	// be summoned when GetAuth is called)
	accessKey := parameters[ParamAccessKey]
	if accessKey == nil {
		accessKey = ""
	}
	res.AccessKey = fmt.Sprint(accessKey)

	secretKey := parameters[ParamSecretKey]
	if secretKey == nil {
		// nolint: gosec // G101 -- This is a false positive
		secretKey = ""
	}
	res.SecretKey = fmt.Sprint(secretKey)

	regionEndpoint := parameters[ParamRegionEndpoint]
	if regionEndpoint == nil {
		regionEndpoint = ""
	}
	res.RegionEndpoint = fmt.Sprint(regionEndpoint)

	regionName := parameters[ParamRegion]
	if regionName == nil || fmt.Sprint(regionName) == "" {
		err := fmt.Errorf("no %q parameter provided", ParamRegion)
		mErr = multierror.Append(mErr, err)
	}
	region := fmt.Sprint(regionName)
	res.Region = region
	// Don't check the region value if a custom endpoint is provided.
	if regionEndpoint == "" {
		if _, ok := validRegions[region]; !ok {
			err := fmt.Errorf("validating region provided: %v", region)
			mErr = multierror.Append(mErr, err)
		}
	}

	bucket := parameters[ParamBucket]
	if bucket == nil || fmt.Sprint(bucket) == "" {
		err := errors.New("no bucket parameter provided")
		mErr = multierror.Append(mErr, err)
	}
	res.Bucket = fmt.Sprint(bucket)

	encryptEnable, err := parse.Bool(parameters, ParamEncrypt, false)
	if err != nil {
		mErr = multierror.Append(mErr, err)
	}
	res.Encrypt = encryptEnable

	secureEnable, err := parse.Bool(parameters, ParamSecure, true)
	if err != nil {
		mErr = multierror.Append(mErr, err)
	}
	res.Secure = secureEnable

	skipVerifyEnable, err := parse.Bool(parameters, ParamSkipVerify, false)
	if err != nil {
		mErr = multierror.Append(mErr, err)
	}
	res.SkipVerify = skipVerifyEnable

	v4Enable, err := parse.Bool(parameters, ParamV4Auth, true)
	if err != nil {
		mErr = multierror.Append(mErr, err)
	}
	res.V4Auth = v4Enable

	keyID := parameters[ParamKeyID]
	if keyID == nil {
		keyID = ""
	}
	res.KeyID = fmt.Sprint(keyID)

	chunkSize, err := parse.Int64(
		parameters,
		ParamChunkSize,
		// NOTE(prozlach): We are using two buffers, each one can hold up to
		// chunksize bytes, and in some circumstances their contents may be
		// concatenated and committed as a single part. For example:
		//
		// https://gitlab.com/gitlab-org/container-registry/-/blob/0604f5b44093b9647dcbf5f4f7a0d6ab824ff0a4/registry/storage/driver/s3-aws/v2/s3.go?page=2#L1447-L1450
		//
		// We need to halve the limit in order to not to exceed MaxChunkSize.
		DefaultChunkSize, MinChunkSize, MaxChunkSize/2,
	)
	if err != nil {
		err := fmt.Errorf("converting %q to int64: %w", ParamChunkSize, err)
		mErr = multierror.Append(mErr, err)
	}
	res.ChunkSize = chunkSize

	multipartCopyChunkSize, err := parse.Int64(
		parameters,
		ParamMultipartCopyChunkSize,
		DefaultMultipartCopyChunkSize, MinChunkSize, MaxChunkSize,
	)
	if err != nil {
		err := fmt.Errorf("converting %q to valid int64: %w", ParamMultipartCopyChunkSize, err)
		mErr = multierror.Append(mErr, err)
	}
	res.MultipartCopyChunkSize = multipartCopyChunkSize

	multipartCopyMaxConcurrency, err := parse.Int32(
		parameters,
		ParamMultipartCopyMaxConcurrency,
		DefaultMultipartCopyMaxConcurrency, 1, math.MaxInt32,
	)
	if err != nil {
		err := fmt.Errorf("converting %q to valid int64: %w", ParamMultipartCopyMaxConcurrency, err)
		mErr = multierror.Append(mErr, err)
	}
	res.MultipartCopyMaxConcurrency = int(multipartCopyMaxConcurrency)

	multipartCopyThresholdSize, err := parse.Int64(
		parameters,
		ParamMultipartCopyThresholdSize,
		DefaultMultipartCopyThresholdSize, 0, MaxChunkSize,
	)
	if err != nil {
		err := fmt.Errorf("converting %q to valid int64: %w", ParamMultipartCopyThresholdSize, err)
		mErr = multierror.Append(mErr, err)
	}
	res.MultipartCopyThresholdSize = multipartCopyThresholdSize

	rootDirectory := parameters[ParamRootDirectory]
	if rootDirectory == nil {
		rootDirectory = ""
	}
	res.RootDirectory = fmt.Sprint(rootDirectory)

	var storageClass string
	storageClassParam := parameters[ParamStorageClass]
	if storageClassParam != nil {
		storageClassString, ok := storageClassParam.(string)
		switch {
		case !ok:
			err := fmt.Errorf("the storageclass parameter must be a string: %v", storageClassParam)
			mErr = multierror.Append(mErr, err)
		case (driverVersion == V1DriverName || driverVersion == V1DriverNameAlt):
			// All valid storage class parameters are UPPERCASE, so be a bit more flexible here
			storageClassString = strings.ToUpper(storageClassString)
			if !slices.Contains(validStorageClassesV1, storageClassString) {
				err := fmt.Errorf(
					"the storageclass parameter must be one of %v, %v is invalid",
					strings.Join(validStorageClassesV1, ","), storageClassParam,
				)
				mErr = multierror.Append(mErr, err)
			} else {
				storageClass = storageClassString
			}
		case driverVersion == V2DriverName:
			storageClassString = strings.ToUpper(storageClassString)
			if !slices.Contains(validStorageClassesV2, storageClassString) {
				err := fmt.Errorf(
					"the storageclass parameter must be one of %v, %v is invalid",
					strings.Join(validStorageClassesV2, ","), storageClassParam,
				)
				mErr = multierror.Append(mErr, err)
			} else {
				storageClass = storageClassString
			}
		default:
			storageClass = storageClassString
		}
	} else {
		switch {
		case (driverVersion == V1DriverName || driverVersion == V1DriverNameAlt):
			storageClass = string(s3.StorageClassStandard)
		case driverVersion == V2DriverName:
			storageClass = string(types.StorageClassStandard)
		}
	}
	res.StorageClass = storageClass

	// Parse checksum_disabled parameter
	checksumDisabled, err := parse.Bool(parameters, ParamChecksumDisabled, false)
	if err != nil {
		mErr = multierror.Append(mErr, err)
	}
	res.ChecksumDisabled = checksumDisabled

	// Parse checksum algorithm
	defaultChecksumAlgorithm := types.ChecksumAlgorithmCrc64nvme
	checksumAlgorithmParam := parameters[ParamChecksumAlgorithm]
	if checksumDisabled {
		// If checksum_disabled is true, ignore checksum_algorithm
		if checksumAlgorithmParam != nil {
			logger := parameters[driver.ParamLogger].(dcontext.Logger)
			logger.Warnf("Both %s and %s parameters provided, %s takes precedence",
				ParamChecksumDisabled, ParamChecksumAlgorithm, ParamChecksumDisabled)
		}
		defaultChecksumAlgorithm = ""
	} else if checksumAlgorithmParam != nil {
		checksumAlgorithm, ok := checksumAlgorithmParam.(string)
		if !ok {
			err := fmt.Errorf("the checksum_algorithm parameter must be a string: %v", checksumAlgorithmParam)
			mErr = multierror.Append(mErr, err)
		} else {
			// Convert to uppercase for consistency and check if it's valid
			checksumAlgorithmTyped := (types.ChecksumAlgorithm)(strings.ToUpper(checksumAlgorithm))
			// nolint: revive // max-control-nesting
			if !slices.Contains(checksumAlgorithmTyped.Values(), checksumAlgorithmTyped) {
				err := fmt.Errorf("the checksum_algorithm parameter must be one of %v, %q is invalid", checksumAlgorithmTyped.Values(), checksumAlgorithmParam)
				mErr = multierror.Append(mErr, err)
			} else {
				defaultChecksumAlgorithm = checksumAlgorithmTyped
			}
		}
	}
	res.ChecksumAlgorithm = defaultChecksumAlgorithm

	objectOwnership, err := parse.Bool(parameters, ParamObjectOwnership, false)
	if err != nil {
		mErr = multierror.Append(mErr, err)
	}
	res.ObjectOwnership = objectOwnership

	var objectACL string
	objectACLParam := parameters[ParamObjectACL]
	if objectACLParam != nil {
		if objectOwnership {
			err := fmt.Errorf("object ACL parameter should not be set when object ownership is enabled")
			mErr = multierror.Append(mErr, err)
		} else {
			objectACLString, ok := objectACLParam.(string)
			switch {
			case !ok:
				err := fmt.Errorf("object ACL parameter should be a string: %v", objectACLParam)
				mErr = multierror.Append(mErr, err)
			case (driverVersion == V1DriverName || driverVersion == V1DriverNameAlt) && !slices.Contains(s3.ObjectCannedACL_Values(), objectACLString):
				err := fmt.Errorf("object ACL parameter should be one of %v: %v", strings.Join(s3.ObjectCannedACL_Values(), ","), objectACLParam)
				mErr = multierror.Append(mErr, err)
			case driverVersion == V2DriverName && !slices.Contains(types.ObjectCannedACLPrivate.Values(), (types.ObjectCannedACL)(objectACLString)):
				// typecast:
				strValues := make([]string, len(types.ObjectCannedACLPrivate.Values()))
				for i, v := range types.ObjectCannedACLPrivate.Values() {
					strValues[i] = string(v)
				}
				err := fmt.Errorf("object ACL parameter should be one of %v: %v", strings.Join(strValues, ","), objectACLParam)
				mErr = multierror.Append(mErr, err)
			default:
				objectACL = objectACLString
			}
		}
	} else {
		switch {
		case (driverVersion == V1DriverName || driverVersion == V1DriverNameAlt):
			objectACL = string(s3.ObjectCannedACLPrivate)
		case driverVersion == V2DriverName:
			objectACL = string(types.ObjectCannedACLPrivate)
		}
	}
	res.ObjectACL = objectACL

	// If regionEndpoint is set, default to forcing pathstyle to preserve legacy behavior.
	defaultPathStyle := regionEndpoint != ""
	pathStyleBool, err := parse.Bool(parameters, ParamPathStyle, defaultPathStyle)
	if err != nil {
		mErr = multierror.Append(mErr, err)
	}
	res.PathStyle = pathStyleBool

	parallelWalkBool, err := parse.Bool(parameters, ParamParallelWalk, false)
	if err != nil {
		mErr = multierror.Append(mErr, err)
	}
	res.ParallelWalk = parallelWalkBool

	maxRequestsPerSecond, err := parse.Int64(parameters, ParamMaxRequestsPerSecond, DefaultMaxRequestsPerSecond, 0, math.MaxInt64)
	if err != nil {
		err = fmt.Errorf("converting maxrequestspersecond to valid int64: %w", err)
		mErr = multierror.Append(mErr, err)
	}
	res.MaxRequestsPerSecond = maxRequestsPerSecond

	maxRetries, err := parse.Int64(
		parameters,
		ParamMaxRetries,
		DefaultMaxRetries, 0, math.MaxInt64,
	)
	if err != nil {
		err := fmt.Errorf("converting maxrequestspersecond to valid int64: %w", err)
		mErr = multierror.Append(mErr, err)
	}
	res.MaxRetries = maxRetries

	if err := mErr.ErrorOrNil(); err != nil {
		return nil, err
	}

	logger := parameters[driver.ParamLogger].(dcontext.Logger)
	res.Logger = logger
	switch driverVersion {
	case V1DriverName, V1DriverNameAlt:
		res.LogLevel = uint64(ParseLogLevelParamV1(logger, parameters[ParamLogLevel]))
	case V2DriverName:
		res.LogLevel = uint64(ParseLogLevelParamV2(logger, parameters[ParamLogLevel]))
	}

	return res, nil
}