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
}