pkg/api/platformapi/snaprepoapi/s3_config.go (126 lines of code) (raw):

// Licensed to Elasticsearch B.V. under one or more contributor // license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright // ownership. Elasticsearch B.V. licenses this file to you under // the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. package snaprepoapi import ( "bytes" "encoding/json" "fmt" "io" "time" "github.com/asaskevich/govalidator" "github.com/ghodss/yaml" "github.com/pkg/errors" "github.com/elastic/cloud-sdk-go/pkg/multierror" ) var ( validStorageClasses = []string{"standard", "reduced_redundancy", "standard_ia"} validCannedACLs = []string{ "private", "public-read", "public-read-write", "authenticated-read", "log-delivery-write", "bucket-owner-read", "bucket-owner-full-control", } validProtocols = []string{"http", "https"} ) var ( // required setting errors errRegionCannotBeEmpty = errors.New("region cannot be empty") errBucketCannotBeEmpty = errors.New("bucket cannot be empty") errAccessKeyCannotBeEmpty = errors.New("access key cannot be empty") errSecretKeyCannotBeEmpty = errors.New("secret key cannot be empty") // Optional setting errors errInvalidStorageClass = fmt.Errorf("storage class must be one of %s", validStorageClasses) errInvalidCannedACL = fmt.Errorf("canned acl must be one of %s", validCannedACLs) errInvalidProtocol = fmt.Errorf("protocol must be one of %s", validProtocols) errInvalidEndpoint = errors.New("endpoint is not valid") // Parser errors errFailedParsingConfig = errors.New("failed to parse config format") errReaderCannotBeNil = errors.New("reader cannot be nil") ) // ParseS3Config reads the contents of an io.Reader and tries to parse its // contents as YAML or JSON, returns an error if parsing fails in both formats. func ParseS3Config(input io.Reader) (S3Config, error) { var config S3Config if input == nil { return config, errReaderCannotBeNil } var buf = new(bytes.Buffer) if _, err := buf.ReadFrom(input); err != nil { return config, nil } if err := yaml.Unmarshal(buf.Bytes(), &config); err == nil { return config, nil } if err := json.Unmarshal(buf.Bytes(), &config); err != nil { return config, errors.Wrap(err, errFailedParsingConfig.Error()) } return config, nil } // S3Config is used to configure an S3 snapshot repository // Full list of settings in the Elasticsearch official documentation: // https://www.elastic.co/guide/en/elasticsearch/reference/current/repository-s3.html#repository-s3-repository // https://www.elastic.co/guide/en/elasticsearch/reference/current/repository-s3.html#repository-s3-client // nolint type S3Config struct { // Required settings Region string `json:"region,omitempty"` Bucket string `json:"bucket,omitempty"` AccessKey string `json:"access_key,omitempty"` SecretKey string `json:"secret_key,omitempty"` // Optional settings BasePath string `json:"base_path,omitempty"` Compress bool `json:"compress,omitempty"` ServerSideEncryption bool `json:"server_side_encryption,omitempty"` // Advanced Settings ChunkSize string `json:"chunk_size,omitempty"` CannedACL string `json:"canned_acl,omitempty"` StorageClass string `json:"storage_class,omitempty"` // Client settings // See http://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region. Endpoint string `json:"endpoint,omitempty"` Protocol string `json:"protocol,omitempty"` Timeout time.Duration `json:"timeout,omitempty"` MaxRetries int `json:"max_retries,omitempty"` ThrottleRetries bool `json:"throttle_retries,omitempty"` PathStyleAccess bool `json:"path_style_access,omitempty"` } // S3TypeConfig is used by the text formatter to wrwap the S3 config with the // type field. type S3TypeConfig struct { Type string `json:"type"` Settings S3Config `json:"settings"` } // Validate ensures that S3Config is func (c S3Config) Validate() error { var merr = multierror.NewPrefixed("s3 configuration") if err := validateRequiredSettings(c); err != nil { merr = merr.Append(err) } if e := validateOptionalSettings(c); e != nil { merr = merr.Append(e) } return merr.ErrorOrNil() } func validateRequiredSettings(c S3Config) error { var merr = multierror.NewPrefixed("required setting") if c.Region == "" { merr = merr.Append(errRegionCannotBeEmpty) } if c.Bucket == "" { merr = merr.Append(errBucketCannotBeEmpty) } if c.AccessKey == "" { merr = merr.Append(errAccessKeyCannotBeEmpty) } if c.SecretKey == "" { merr = merr.Append(errSecretKeyCannotBeEmpty) } return merr.ErrorOrNil() } func validateOptionalSettings(c S3Config) error { var merr = multierror.NewPrefixed("optional setting") if c.StorageClass != "" && !stringInSlice(c.StorageClass, validStorageClasses) { merr = merr.Append(errInvalidStorageClass) } if c.CannedACL != "" && !stringInSlice(c.CannedACL, validCannedACLs) { merr = merr.Append(errInvalidCannedACL) } if c.Endpoint != "" && !govalidator.IsURL(c.Endpoint) { merr = merr.Append(errInvalidEndpoint) } if c.Protocol != "" && !stringInSlice(c.Protocol, validProtocols) { merr = merr.Append(errInvalidProtocol) } return merr.ErrorOrNil() } func stringInSlice(a string, list []string) bool { for _, b := range list { if b == a { return true } } return false }