2017-06-25 23:45:22 +02:00
|
|
|
// +-------------------------------------------------------------------------
|
|
|
|
// | Copyright (C) 2016 Yunify, Inc.
|
|
|
|
// +-------------------------------------------------------------------------
|
|
|
|
// | Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// | you may not use this work except in compliance with the License.
|
|
|
|
// | You may obtain a copy of the License in the LICENSE file, or 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 config
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"io/ioutil"
|
|
|
|
"net/http"
|
|
|
|
"os"
|
|
|
|
"strings"
|
2018-01-16 14:20:59 +01:00
|
|
|
"time"
|
2017-06-25 23:45:22 +02:00
|
|
|
|
2019-08-14 15:56:32 +02:00
|
|
|
"gopkg.in/yaml.v2"
|
2017-06-25 23:45:22 +02:00
|
|
|
|
2019-08-14 15:58:30 +02:00
|
|
|
"github.com/yunify/qingstor-sdk-go/v3/logger"
|
|
|
|
"github.com/yunify/qingstor-sdk-go/v3/utils"
|
2017-06-25 23:45:22 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
// A Config stores a configuration of this sdk.
|
|
|
|
type Config struct {
|
|
|
|
AccessKeyID string `yaml:"access_key_id"`
|
|
|
|
SecretAccessKey string `yaml:"secret_access_key"`
|
|
|
|
|
|
|
|
Host string `yaml:"host"`
|
|
|
|
Port int `yaml:"port"`
|
|
|
|
Protocol string `yaml:"protocol"`
|
|
|
|
ConnectionRetries int `yaml:"connection_retries"`
|
|
|
|
|
|
|
|
AdditionalUserAgent string `yaml:"additional_user_agent"`
|
|
|
|
|
|
|
|
LogLevel string `yaml:"log_level"`
|
|
|
|
|
2018-03-19 16:51:38 +01:00
|
|
|
HTTPSettings HTTPClientSettings
|
|
|
|
|
2017-06-25 23:45:22 +02:00
|
|
|
Connection *http.Client
|
|
|
|
}
|
|
|
|
|
2018-03-19 16:51:38 +01:00
|
|
|
// HTTPClientSettings is the http client settings.
|
|
|
|
type HTTPClientSettings struct {
|
|
|
|
|
|
|
|
// ConnectTimeout affects making new socket connection
|
|
|
|
ConnectTimeout time.Duration `yaml:"connect_timeout"`
|
|
|
|
|
|
|
|
// ReadTimeout affect each call to HTTPResponse.Body.Read()
|
|
|
|
ReadTimeout time.Duration `yaml:"read_timeout"`
|
|
|
|
|
|
|
|
// WriteTimeout affect each write in io.Copy while sending HTTPRequest
|
|
|
|
WriteTimeout time.Duration `yaml:"write_timeout" `
|
|
|
|
|
|
|
|
// TLSHandshakeTimeout affects https hand shake timeout
|
|
|
|
TLSHandshakeTimeout time.Duration `yaml:"tls_timeout"`
|
|
|
|
|
|
|
|
// IdleConnTimeout affects the time limit to re-use http connections
|
|
|
|
IdleConnTimeout time.Duration `yaml:"idle_timeout"`
|
|
|
|
|
2018-05-02 18:09:45 +02:00
|
|
|
TCPKeepAlive time.Duration `yaml:"tcp_keepalive_time"`
|
2018-03-19 16:51:38 +01:00
|
|
|
|
2018-05-02 18:09:45 +02:00
|
|
|
DualStack bool `yaml:"dual_stack"`
|
2018-03-19 16:51:38 +01:00
|
|
|
|
|
|
|
// MaxIdleConns affects the idle connections kept for re-use
|
2018-05-02 18:09:45 +02:00
|
|
|
MaxIdleConns int `yaml:"max_idle_conns"`
|
2018-03-19 16:51:38 +01:00
|
|
|
|
2018-05-02 18:09:45 +02:00
|
|
|
MaxIdleConnsPerHost int `yaml:"max_idle_conns_per_host"`
|
2018-03-19 16:51:38 +01:00
|
|
|
|
|
|
|
ExpectContinueTimeout time.Duration `yaml:"expect_continue_timeout"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// DefaultHTTPClientSettings is the default http client settings.
|
|
|
|
var DefaultHTTPClientSettings = HTTPClientSettings{
|
|
|
|
ConnectTimeout: time.Second * 30,
|
|
|
|
ReadTimeout: time.Second * 30,
|
|
|
|
WriteTimeout: time.Second * 30,
|
|
|
|
TLSHandshakeTimeout: time.Second * 10,
|
|
|
|
IdleConnTimeout: time.Second * 20,
|
|
|
|
TCPKeepAlive: 0,
|
|
|
|
DualStack: false,
|
|
|
|
MaxIdleConns: 100,
|
|
|
|
MaxIdleConnsPerHost: 10,
|
|
|
|
ExpectContinueTimeout: time.Second * 2,
|
|
|
|
}
|
|
|
|
|
2017-06-25 23:45:22 +02:00
|
|
|
// New create a Config with given AccessKeyID and SecretAccessKey.
|
2018-01-16 14:20:59 +01:00
|
|
|
func New(accessKeyID, secretAccessKey string) (c *Config, err error) {
|
|
|
|
c, err = NewDefault()
|
2017-06-25 23:45:22 +02:00
|
|
|
if err != nil {
|
2018-01-16 14:20:59 +01:00
|
|
|
c = nil
|
|
|
|
return
|
2017-06-25 23:45:22 +02:00
|
|
|
}
|
2018-01-16 14:20:59 +01:00
|
|
|
c.AccessKeyID = accessKeyID
|
|
|
|
c.SecretAccessKey = secretAccessKey
|
2017-06-25 23:45:22 +02:00
|
|
|
|
2018-01-16 14:20:59 +01:00
|
|
|
return
|
2017-06-25 23:45:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewDefault create a Config with default configuration.
|
2018-01-16 14:20:59 +01:00
|
|
|
func NewDefault() (c *Config, err error) {
|
|
|
|
c = &Config{}
|
|
|
|
err = c.LoadDefaultConfig()
|
2017-06-25 23:45:22 +02:00
|
|
|
if err != nil {
|
2018-01-16 14:20:59 +01:00
|
|
|
c = nil
|
|
|
|
return
|
2017-06-25 23:45:22 +02:00
|
|
|
}
|
2018-01-16 14:20:59 +01:00
|
|
|
return
|
2017-06-25 23:45:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Check checks the configuration.
|
2018-01-16 14:20:59 +01:00
|
|
|
func (c *Config) Check() (err error) {
|
2017-06-25 23:45:22 +02:00
|
|
|
if c.AccessKeyID == "" {
|
2018-01-16 14:20:59 +01:00
|
|
|
err = errors.New("access key ID not specified")
|
|
|
|
return
|
2017-06-25 23:45:22 +02:00
|
|
|
}
|
|
|
|
if c.SecretAccessKey == "" {
|
2018-01-16 14:20:59 +01:00
|
|
|
err = errors.New("secret access key not specified")
|
|
|
|
return
|
2017-06-25 23:45:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if c.Host == "" {
|
2018-01-16 14:20:59 +01:00
|
|
|
err = errors.New("server host not specified")
|
|
|
|
return
|
2017-06-25 23:45:22 +02:00
|
|
|
}
|
|
|
|
if c.Port <= 0 {
|
2018-01-16 14:20:59 +01:00
|
|
|
err = errors.New("server port not specified")
|
|
|
|
return
|
2017-06-25 23:45:22 +02:00
|
|
|
}
|
|
|
|
if c.Protocol == "" {
|
2018-01-16 14:20:59 +01:00
|
|
|
err = errors.New("server protocol not specified")
|
|
|
|
return
|
2017-06-25 23:45:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if c.AdditionalUserAgent != "" {
|
|
|
|
for _, x := range c.AdditionalUserAgent {
|
|
|
|
// Allow space(32) to ~(126) in ASCII Table, exclude "(34).
|
|
|
|
if int(x) < 32 || int(x) > 126 || int(x) == 32 || int(x) == 34 {
|
2018-01-16 14:20:59 +01:00
|
|
|
err = errors.New("additional User-Agent contains characters that not allowed")
|
|
|
|
return
|
2017-06-25 23:45:22 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-16 14:20:59 +01:00
|
|
|
err = logger.CheckLevel(c.LogLevel)
|
2017-06-25 23:45:22 +02:00
|
|
|
if err != nil {
|
2018-01-16 14:20:59 +01:00
|
|
|
return
|
2017-06-25 23:45:22 +02:00
|
|
|
}
|
|
|
|
|
2018-01-16 14:20:59 +01:00
|
|
|
return
|
2017-06-25 23:45:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// LoadDefaultConfig loads the default configuration for Config.
|
|
|
|
// It returns error if yaml decode failed.
|
2018-01-16 14:20:59 +01:00
|
|
|
func (c *Config) LoadDefaultConfig() (err error) {
|
2018-03-19 16:51:38 +01:00
|
|
|
|
|
|
|
c.HTTPSettings = DefaultHTTPClientSettings
|
|
|
|
|
2018-01-16 14:20:59 +01:00
|
|
|
err = yaml.Unmarshal([]byte(DefaultConfigFileContent), c)
|
2017-06-25 23:45:22 +02:00
|
|
|
if err != nil {
|
2018-01-16 14:20:59 +01:00
|
|
|
logger.Errorf(nil, "Config parse error, %v.", err)
|
|
|
|
return
|
2017-06-25 23:45:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
logger.SetLevel(c.LogLevel)
|
2018-03-19 16:51:38 +01:00
|
|
|
|
|
|
|
c.InitHTTPClient()
|
2018-01-16 14:20:59 +01:00
|
|
|
return
|
2017-06-25 23:45:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// LoadUserConfig loads user configuration in ~/.qingstor/config.yaml for Config.
|
|
|
|
// It returns error if file not found.
|
2018-01-16 14:20:59 +01:00
|
|
|
func (c *Config) LoadUserConfig() (err error) {
|
|
|
|
_, err = os.Stat(GetUserConfigFilePath())
|
2017-06-25 23:45:22 +02:00
|
|
|
if err != nil {
|
2018-01-16 14:20:59 +01:00
|
|
|
logger.Warnf(nil, "Installing default config file to %s.", GetUserConfigFilePath())
|
2017-06-25 23:45:22 +02:00
|
|
|
InstallDefaultUserConfig()
|
|
|
|
}
|
|
|
|
|
|
|
|
return c.LoadConfigFromFilePath(GetUserConfigFilePath())
|
|
|
|
}
|
|
|
|
|
|
|
|
// LoadConfigFromFilePath loads configuration from a specified local path.
|
|
|
|
// It returns error if file not found or yaml decode failed.
|
2018-01-16 14:20:59 +01:00
|
|
|
func (c *Config) LoadConfigFromFilePath(filePath string) (err error) {
|
|
|
|
if strings.Index(filePath, "~/") == 0 {
|
|
|
|
filePath = strings.Replace(filePath, "~/", getHome()+"/", 1)
|
2017-06-25 23:45:22 +02:00
|
|
|
}
|
|
|
|
|
2018-01-16 14:20:59 +01:00
|
|
|
yamlString, err := ioutil.ReadFile(filePath)
|
2017-06-25 23:45:22 +02:00
|
|
|
if err != nil {
|
2018-01-16 14:20:59 +01:00
|
|
|
logger.Errorf(nil, "File not found: %s.", filePath)
|
2017-06-25 23:45:22 +02:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return c.LoadConfigFromContent(yamlString)
|
|
|
|
}
|
|
|
|
|
|
|
|
// LoadConfigFromContent loads configuration from a given byte slice.
|
|
|
|
// It returns error if yaml decode failed.
|
2018-01-16 14:20:59 +01:00
|
|
|
func (c *Config) LoadConfigFromContent(content []byte) (err error) {
|
2017-06-25 23:45:22 +02:00
|
|
|
c.LoadDefaultConfig()
|
|
|
|
|
2018-01-16 14:20:59 +01:00
|
|
|
err = yaml.Unmarshal(content, c)
|
2017-06-25 23:45:22 +02:00
|
|
|
if err != nil {
|
2018-01-16 14:20:59 +01:00
|
|
|
logger.Errorf(nil, "Config parse error, %v.", err)
|
|
|
|
return
|
2017-06-25 23:45:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
err = c.Check()
|
|
|
|
if err != nil {
|
2018-01-16 14:20:59 +01:00
|
|
|
return
|
2017-06-25 23:45:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
logger.SetLevel(c.LogLevel)
|
2018-03-19 16:51:38 +01:00
|
|
|
|
|
|
|
c.InitHTTPClient()
|
2018-01-16 14:20:59 +01:00
|
|
|
return
|
2017-06-25 23:45:22 +02:00
|
|
|
}
|
2018-03-19 16:51:38 +01:00
|
|
|
|
|
|
|
// InitHTTPClient : After modifying Config.HTTPSettings, you should always call this to initialize the HTTP Client.
|
|
|
|
func (c *Config) InitHTTPClient() {
|
|
|
|
var emptySettings HTTPClientSettings
|
|
|
|
|
|
|
|
if c.HTTPSettings == emptySettings { // User forgot to initialize the settings
|
|
|
|
c.HTTPSettings = DefaultHTTPClientSettings
|
|
|
|
} else {
|
|
|
|
if c.HTTPSettings.ConnectTimeout == 0 {
|
|
|
|
c.HTTPSettings.ConnectTimeout = DefaultHTTPClientSettings.ConnectTimeout
|
|
|
|
}
|
|
|
|
// If ReadTimeout and WriteTimeout is zero, means no read/write timeout
|
|
|
|
if c.HTTPSettings.TLSHandshakeTimeout == 0 {
|
|
|
|
c.HTTPSettings.TLSHandshakeTimeout = DefaultHTTPClientSettings.TLSHandshakeTimeout
|
|
|
|
}
|
|
|
|
if c.HTTPSettings.ExpectContinueTimeout == 0 {
|
|
|
|
c.HTTPSettings.ExpectContinueTimeout = DefaultHTTPClientSettings.ExpectContinueTimeout
|
|
|
|
}
|
|
|
|
}
|
|
|
|
dialer := utils.NewDialer(
|
|
|
|
c.HTTPSettings.ConnectTimeout,
|
|
|
|
c.HTTPSettings.ReadTimeout,
|
|
|
|
c.HTTPSettings.WriteTimeout,
|
|
|
|
)
|
|
|
|
dialer.KeepAlive = c.HTTPSettings.TCPKeepAlive
|
|
|
|
// XXX: DualStack enables RFC 6555-compliant "Happy Eyeballs" dialing
|
|
|
|
// when the network is "tcp" and the destination is a host name
|
|
|
|
// with both IPv4 and IPv6 addresses. This allows a client to
|
|
|
|
// tolerate networks where one address family is silently broken
|
|
|
|
dialer.DualStack = c.HTTPSettings.DualStack
|
|
|
|
c.Connection = &http.Client{
|
|
|
|
// We do not use the timeout in http client,
|
|
|
|
// because this timeout is for the whole http body read/write,
|
|
|
|
// it's unsuitable for various length of files and network condition.
|
|
|
|
// We provide a wraper in utils/conn.go of net.Dialer to make io timeout to the http connection
|
|
|
|
// for individual buffer I/O operation,
|
|
|
|
Timeout: 0,
|
|
|
|
Transport: &http.Transport{
|
|
|
|
DialContext: dialer.DialContext,
|
|
|
|
MaxIdleConns: c.HTTPSettings.MaxIdleConns,
|
|
|
|
MaxIdleConnsPerHost: c.HTTPSettings.MaxIdleConnsPerHost,
|
|
|
|
IdleConnTimeout: c.HTTPSettings.IdleConnTimeout,
|
|
|
|
TLSHandshakeTimeout: c.HTTPSettings.TLSHandshakeTimeout,
|
|
|
|
ExpectContinueTimeout: c.HTTPSettings.ExpectContinueTimeout,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|