From d21debdfba49a813926488bd1a5e516f8911531e Mon Sep 17 00:00:00 2001 From: ivyxjc Date: Thu, 14 Sep 2023 10:25:35 +0800 Subject: [PATCH] feat(): support sigv4auth for prometheus metrics reader --- go.mod | 12 ++ go.sum | 23 +++ pkg/prometheus/config/config.go | 3 + pkg/sigv4authextension/config.go | 46 ++++++ pkg/sigv4authextension/extension.go | 97 +++++++++++ pkg/sigv4authextension/signingroundtripper.go | 151 ++++++++++++++++++ .../metrics/prometheus/metricsstore/reader.go | 12 +- plugin/metrics/prometheus/options.go | 7 + plugin/storage/cassandra/schema/create.sh | 2 + 9 files changed, 352 insertions(+), 1 deletion(-) create mode 100644 pkg/sigv4authextension/config.go create mode 100644 pkg/sigv4authextension/extension.go create mode 100644 pkg/sigv4authextension/signingroundtripper.go diff --git a/go.mod b/go.mod index 32882a4366a..100d5294791 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,10 @@ require ( github.com/HdrHistogram/hdrhistogram-go v1.1.2 github.com/Shopify/sarama v1.37.2 github.com/apache/thrift v0.19.0 + github.com/aws/aws-sdk-go-v2 v1.21.0 + github.com/aws/aws-sdk-go-v2/config v1.8.3 + github.com/aws/aws-sdk-go-v2/credentials v1.13.35 + github.com/aws/aws-sdk-go-v2/service/sts v1.21.5 github.com/bsm/sarama-cluster v2.1.13+incompatible github.com/crossdock/crossdock-go v0.0.0-20160816171116-049aabb0122b github.com/dgraph-io/badger/v3 v3.2103.5 @@ -76,6 +80,14 @@ require ( require ( github.com/VividCortex/gohistogram v1.0.0 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.11 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.41 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.35 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.2.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.35 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.13.5 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.15.5 // indirect + github.com/aws/smithy-go v1.14.2 // indirect github.com/benbjohnson/clock v1.3.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect diff --git a/go.sum b/go.sum index ee77b738dfe..0142ad0d61a 100644 --- a/go.sum +++ b/go.sum @@ -72,15 +72,37 @@ github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:W github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/aws/aws-sdk-go-v2 v1.9.2/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4= +github.com/aws/aws-sdk-go-v2 v1.21.0 h1:gMT0IW+03wtYJhRqTVYn0wLzwdnK9sRMcxmtfGzRdJc= +github.com/aws/aws-sdk-go-v2 v1.21.0/go.mod h1:/RfNgGmRxI+iFOB1OeJUyxiU+9s88k3pfHvDagGEp0M= +github.com/aws/aws-sdk-go-v2/config v1.8.3 h1:o5583X4qUfuRrOGOgmOcDgvr5gJVSu57NK08cWAhIDk= github.com/aws/aws-sdk-go-v2/config v1.8.3/go.mod h1:4AEiLtAb8kLs7vgw2ZV3p2VZ1+hBavOc84hqxVNpCyw= github.com/aws/aws-sdk-go-v2/credentials v1.4.3/go.mod h1:FNNC6nQZQUuyhq5aE5c7ata8o9e4ECGmS4lAXC7o1mQ= +github.com/aws/aws-sdk-go-v2/credentials v1.13.35 h1:QpsNitYJu0GgvMBLUIYu9H4yryA5kMksjeIVQfgXrt8= +github.com/aws/aws-sdk-go-v2/credentials v1.13.35/go.mod h1:o7rCaLtvK0hUggAGclf76mNGGkaG5a9KWlp+d9IpcV8= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.6.0/go.mod h1:gqlclDEZp4aqJOancXK6TN24aKhT0W0Ae9MHk3wzTMM= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.11 h1:uDZJF1hu0EVT/4bogChk8DyjSF6fof6uL/0Y26Ma7Fg= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.11/go.mod h1:TEPP4tENqBGO99KwVpV9MlOX4NSrSLP8u3KRy2CDwA8= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.41 h1:22dGT7PneFMx4+b3pz7lMTRyN8ZKH7M2cW4GP9yUS2g= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.41/go.mod h1:CrObHAuPneJBlfEJ5T3szXOUkLEThaGfvnhTf33buas= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.35 h1:SijA0mgjV8E+8G45ltVHs0fvKpTj8xmZJ3VwhGKtUSI= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.35/go.mod h1:SJC1nEVVva1g3pHAIdCp7QsRIkMmLAgoDquQ9Rr8kYw= +github.com/aws/aws-sdk-go-v2/internal/ini v1.2.4 h1:leSJ6vCqtPpTmBIgE7044B1wql1E4n//McF+mEgNrYg= github.com/aws/aws-sdk-go-v2/internal/ini v1.2.4/go.mod h1:ZcBrrI3zBKlhGFNYWvju0I3TR93I7YIgAfy82Fh4lcQ= github.com/aws/aws-sdk-go-v2/service/appconfig v1.4.2/go.mod h1:FZ3HkCe+b10uFZZkFdvf98LHW21k49W8o8J366lqVKY= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.3.2/go.mod h1:72HRZDLMtmVQiLG2tLfQcaWLCssELvGl+Zf2WVxMmR8= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.35 h1:CdzPW9kKitgIiLV1+MHobfR5Xg25iYnyzWZhyQuSlDI= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.35/go.mod h1:QGF2Rs33W5MaN9gYdEQOBBFPLwTZkEhRwI33f7KIG0o= github.com/aws/aws-sdk-go-v2/service/sso v1.4.2/go.mod h1:NBvT9R1MEF+Ud6ApJKM0G+IkPchKS7p7c2YPKwHmBOk= +github.com/aws/aws-sdk-go-v2/service/sso v1.13.5 h1:oCvTFSDi67AX0pOX3PuPdGFewvLRU2zzFSrTsgURNo0= +github.com/aws/aws-sdk-go-v2/service/sso v1.13.5/go.mod h1:fIAwKQKBFu90pBxx07BFOMJLpRUGu8VOzLJakeY+0K4= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.15.5 h1:dnInJb4S0oy8aQuri1mV6ipLlnZPfnsDNB9BGO9PDNY= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.15.5/go.mod h1:yygr8ACQRY2PrEcy3xsUI357stq2AxnFM6DIsR9lij4= github.com/aws/aws-sdk-go-v2/service/sts v1.7.2/go.mod h1:8EzeIqfWt2wWT4rJVu3f21TfrhJ8AEMzVybRNSb/b4g= +github.com/aws/aws-sdk-go-v2/service/sts v1.21.5 h1:CQBFElb0LS8RojMJlxRSo/HXipvTZW2S44Lt9Mk2aYQ= +github.com/aws/aws-sdk-go-v2/service/sts v1.21.5/go.mod h1:VC7JDqsqiwXukYEDjoHh9U0fOJtNWh04FPQz4ct4GGU= github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= +github.com/aws/smithy-go v1.14.2 h1:MJU9hqBGbvWZdApzpvoF2WAIJDbtjK2NDJSiJP7HblQ= +github.com/aws/smithy-go v1.14.2/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= @@ -315,6 +337,7 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= diff --git a/pkg/prometheus/config/config.go b/pkg/prometheus/config/config.go index e316df30c90..30be124ae5d 100644 --- a/pkg/prometheus/config/config.go +++ b/pkg/prometheus/config/config.go @@ -22,6 +22,9 @@ import ( // Configuration describes the options to customize the storage behavior. type Configuration struct { + Authenticator string + Region string + ServerURL string ConnectTimeout time.Duration TLS tlscfg.Options diff --git a/pkg/sigv4authextension/config.go b/pkg/sigv4authextension/config.go new file mode 100644 index 00000000000..b49c5d6adc9 --- /dev/null +++ b/pkg/sigv4authextension/config.go @@ -0,0 +1,46 @@ +package sigv4authextension + +import ( + "fmt" + + "github.com/aws/aws-sdk-go-v2/aws" + "go.opentelemetry.io/collector/component" +) + +// Config stores the configuration for the Sigv4 Authenticator +type Config struct { + Region string `mapstructure:"region,omitempty"` + Service string `mapstructure:"service,omitempty"` + AssumeRole AssumeRole `mapstructure:"assume_role"` + credsProvider *aws.CredentialsProvider +} + +// AssumeRole holds the configuration needed to assume a role +type AssumeRole struct { + ARN string `mapstructure:"arn,omitempty"` + SessionName string `mapstructure:"session_name,omitempty"` + STSRegion string `mapstructure:"sts_region,omitempty"` +} + +// compile time check that the Config struct satisfies the component.Config interface +var _ component.Config = (*Config)(nil) + +// Validate checks that the configuration is valid. +// We aim to catch most errors here to ensure that we +// fail early and to avoid revalidating static data. +func (cfg *Config) Validate() error { + if cfg.AssumeRole.STSRegion == "" && cfg.Region != "" { + cfg.AssumeRole.STSRegion = cfg.Region + } + + credsProvider, err := getCredsProviderFromConfig(cfg) + if err != nil { + return fmt.Errorf("could not retrieve credential provider: %w", err) + } + if credsProvider == nil { + return fmt.Errorf("credsProvider cannot be nil") + } + cfg.credsProvider = credsProvider + + return nil +} diff --git a/pkg/sigv4authextension/extension.go b/pkg/sigv4authextension/extension.go new file mode 100644 index 00000000000..38577dfdf9e --- /dev/null +++ b/pkg/sigv4authextension/extension.go @@ -0,0 +1,97 @@ +package sigv4authextension + +import ( + "context" + "errors" + "fmt" + "github.com/aws/aws-sdk-go-v2/credentials" + "net/http" + "os" + + "github.com/aws/aws-sdk-go-v2/aws" + sigv4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4" + awsconfig "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/credentials/stscreds" + "github.com/aws/aws-sdk-go-v2/service/sts" + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/extension/auth" + "go.uber.org/zap" + grpcCredentials "google.golang.org/grpc/credentials" +) + +// Sigv4Auth is a struct that implements the auth.Client interface. +// It provides the implementation for providing Sigv4 authentication for HTTP requests only. +type Sigv4Auth struct { + cfg *Config + logger *zap.Logger + awsSDKInfo string + component.StartFunc // embedded default behavior to do nothing with Start() + component.ShutdownFunc // embedded default behavior to do nothing with Shutdown() +} + +// compile time check that the Sigv4Auth struct satisfies the auth.Client interface +var _ auth.Client = (*Sigv4Auth)(nil) + +// RoundTripper() returns a custom SigningRoundTripper. +func (sa *Sigv4Auth) RoundTripper(base http.RoundTripper) (http.RoundTripper, error) { + cfg := sa.cfg + + signer := sigv4.NewSigner() + + // Create the SigningRoundTripper struct + rt := SigningRoundTripper{ + transport: base, + signer: signer, + region: cfg.Region, + service: cfg.Service, + credsProvider: cfg.credsProvider, + awsSDKInfo: sa.awsSDKInfo, + logger: sa.logger, + } + + return &rt, nil +} + +// PerRPCCredentials is implemented to satisfy the auth.Client interface but will not be implemented. +func (sa *Sigv4Auth) PerRPCCredentials() (grpcCredentials.PerRPCCredentials, error) { + return nil, errors.New("Not Implemented") +} + +// newSigv4Extension() is called by createExtension() in factory.go and +// returns a new Sigv4Auth struct. +func NewSigv4Extension(cfg *Config, logger *zap.Logger) *Sigv4Auth { + awsSDKInfo := fmt.Sprintf("%s/%s", aws.SDKName, aws.SDKVersion) + return &Sigv4Auth{ + cfg: cfg, + logger: logger, + awsSDKInfo: awsSDKInfo, + } +} + +// getCredsProviderFromConfig() is a helper function that gets AWS credentials +// from the Config. +func getCredsProviderFromConfig(cfg *Config) (*aws.CredentialsProvider, error) { + awscfg, err := awsconfig.LoadDefaultConfig(context.Background(), + awsconfig.WithRegion(cfg.AssumeRole.STSRegion), + ) + if err != nil { + return nil, err + } + if cfg.AssumeRole.ARN != "" { + stsSvc := sts.NewFromConfig(awscfg) + + provider := stscreds.NewAssumeRoleProvider(stsSvc, cfg.AssumeRole.ARN) + awscfg.Credentials = aws.NewCredentialsCache(provider) + } + customAccessKey := os.Getenv("CUSTOM_AWS_ACCESS_KEY") + customSecretKey := os.Getenv("CUSTOM_AWS_SECRET_ACCESS_KEY") + if customAccessKey != "" && customSecretKey != "" { + awscfg.Credentials = credentials.NewStaticCredentialsProvider(customAccessKey, customSecretKey, "") + } + _, err = awscfg.Credentials.Retrieve(context.Background()) + if err != nil { + return nil, err + } + + return &awscfg.Credentials, nil +} diff --git a/pkg/sigv4authextension/signingroundtripper.go b/pkg/sigv4authextension/signingroundtripper.go new file mode 100644 index 00000000000..ec760776a42 --- /dev/null +++ b/pkg/sigv4authextension/signingroundtripper.go @@ -0,0 +1,151 @@ +package sigv4authextension + +import ( + "crypto/sha256" + "encoding/hex" + "errors" + "fmt" + "io" + "net/http" + "strings" + "time" + + "github.com/aws/aws-sdk-go-v2/aws" + sigv4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4" + "go.uber.org/zap" +) + +var errNilRequest = errors.New("sigv4: unable to sign nil *http.Request") + +// SigningRoundTripper is a custom RoundTripper that performs AWS Sigv4. +type SigningRoundTripper struct { + transport http.RoundTripper + signer *sigv4.Signer + region string + service string + credsProvider *aws.CredentialsProvider + awsSDKInfo string + logger *zap.Logger +} + +// RoundTrip() executes a single HTTP transaction and returns an HTTP response, signing +// the request with Sigv4. +func (si *SigningRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { + if req == nil { + si.logger.Warn("nil *http.Request encountered") + return nil, errNilRequest + } + + req2, err := si.signRequest(req) + if err != nil { + si.logger.Debug("error signing request", zap.Error(err)) + return nil, err + } + + // Send the request + return si.transport.RoundTrip(req2) +} + +func (si *SigningRoundTripper) signRequest(req *http.Request) (*http.Request, error) { + payloadHash, err := hashPayload(req) + if err != nil { + return nil, fmt.Errorf("unable to hash request body: %w", err) + } + + // Clone request to ensure thread safety. + req2 := cloneRequest(req) + + // Add the runtime information to the User-Agent header of the request + ua := req2.Header.Get("User-Agent") + if len(ua) > 0 { + ua = ua + " " + si.awsSDKInfo + } else { + ua = si.awsSDKInfo + } + req2.Header.Set("User-Agent", ua) + + // Use user provided service/region if specified, use inferred service/region if not, then sign the request + service, region := si.inferServiceAndRegion(req2) + if si.credsProvider == nil { + return nil, fmt.Errorf("a credentials provider is not set") + } + creds, err := (*si.credsProvider).Retrieve(req2.Context()) + if err != nil { + return nil, fmt.Errorf("error retrieving credentials: %w", err) + } + + err = si.signer.SignHTTP(req.Context(), creds, req2, payloadHash, service, region, time.Now()) + if err != nil { + return nil, fmt.Errorf("error signing the request: %w", err) + } + + return req2, nil +} + +// hashPayload creates a SHA256 hash of the request body +func hashPayload(req *http.Request) (string, error) { + if req.GetBody == nil { + // hash of an empty payload to use if there is no request body + return "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", nil + } + + reqBody, err := req.GetBody() + if err != nil { + return "", err + } + + // Hash the request body + h := sha256.New() + _, err = io.Copy(h, reqBody) + if err != nil { + return "", err + } + + return hex.EncodeToString(h.Sum(nil)), reqBody.Close() +} + +// inferServiceAndRegion attempts to infer a service +// and a region from an http.request, and returns either an empty +// string for both or a valid value for both. +func (si *SigningRoundTripper) inferServiceAndRegion(r *http.Request) (service string, region string) { + service = si.service + region = si.region + + h := r.Host + if strings.HasPrefix(h, "aps-workspaces") { + if service == "" { + service = "aps" + } + rest := h[strings.Index(h, ".")+1:] + if region == "" { + region = rest[0:strings.Index(rest, ".")] + } + } else if strings.HasPrefix(h, "search-") { + if service == "" { + service = "es" + } + rest := h[strings.Index(h, ".")+1:] + if region == "" { + region = rest[0:strings.Index(rest, ".")] + } + } + + if service == "" || region == "" { + si.logger.Warn("Unable to infer region and/or service from the URL. Please provide values for region and/or service in the collector configuration.") + } + return service, region +} + +// cloneRequest() is a helper function that makes a shallow copy of the request and a +// deep copy of the header, for thread safety purposes. +func cloneRequest(r *http.Request) *http.Request { + // shallow copy of the struct + r2 := new(http.Request) + *r2 = *r + // deep copy of the Header + r2.Header = make(http.Header, len(r.Header)) + for k, s := range r.Header { + r2.Header[k] = append([]string(nil), s...) + } + return r2 +} diff --git a/plugin/metrics/prometheus/metricsstore/reader.go b/plugin/metrics/prometheus/metricsstore/reader.go index 975aee2e77a..840e442ca4c 100644 --- a/plugin/metrics/prometheus/metricsstore/reader.go +++ b/plugin/metrics/prometheus/metricsstore/reader.go @@ -18,6 +18,7 @@ import ( "context" "crypto/tls" "fmt" + "github.com/jaegertracing/jaeger/pkg/sigv4authextension" "net" "net/http" "os" @@ -324,7 +325,16 @@ func getHTTPRoundTripper(c *config.Configuration, logger *zap.Logger) (rt http.R TLSHandshakeTimeout: 10 * time.Second, TLSClientConfig: ctlsConfig, } - + if c.Authenticator == "sigv4auth" { + sigv4Config := &sigv4authextension.Config{ + Region: c.Region, + } + if err = sigv4Config.Validate(); err != nil { + return nil, err + } + sigv4Auth := sigv4authextension.NewSigv4Extension(sigv4Config, logger) + return sigv4Auth.RoundTripper(httpTransport) + } token := "" if c.TokenFilePath != "" { tokenFromFile, err := loadToken(c.TokenFilePath) diff --git a/plugin/metrics/prometheus/options.go b/plugin/metrics/prometheus/options.go index db516bc24f3..5f2d3b9379c 100644 --- a/plugin/metrics/prometheus/options.go +++ b/plugin/metrics/prometheus/options.go @@ -29,6 +29,8 @@ import ( const ( suffixServerURL = ".server-url" + suffixAuthenticator = ".authenticator" + suffixRegion = ".region" suffixConnectTimeout = ".connect-timeout" suffixTokenFilePath = ".token-file" suffixOverrideFromContext = ".token-override-from-context" @@ -65,6 +67,7 @@ type Options struct { // NewOptions creates a new Options struct. func NewOptions(primaryNamespace string) *Options { defaultConfig := config.Configuration{ + Authenticator: "", ServerURL: defaultServerURL, ConnectTimeout: defaultConnectTimeout, @@ -88,6 +91,8 @@ func (opt *Options) AddFlags(flagSet *flag.FlagSet) { nsConfig := &opt.Primary flagSet.String(nsConfig.namespace+suffixServerURL, defaultServerURL, "The Prometheus server's URL, must include the protocol scheme e.g. http://localhost:9090") + flagSet.String(nsConfig.namespace+suffixAuthenticator, "", "") + flagSet.String(nsConfig.namespace+suffixRegion, "", "") flagSet.Duration(nsConfig.namespace+suffixConnectTimeout, defaultConnectTimeout, "The period to wait for a connection to Prometheus when executing queries.") flagSet.String(nsConfig.namespace+suffixTokenFilePath, defaultTokenFilePath, @@ -123,6 +128,8 @@ func (opt *Options) AddFlags(flagSet *flag.FlagSet) { // InitFromViper initializes the options struct with values from Viper. func (opt *Options) InitFromViper(v *viper.Viper) error { cfg := &opt.Primary + cfg.Authenticator = stripWhiteSpace(v.GetString(cfg.namespace + suffixAuthenticator)) + cfg.Region = stripWhiteSpace(v.GetString(cfg.namespace + suffixRegion)) cfg.ServerURL = stripWhiteSpace(v.GetString(cfg.namespace + suffixServerURL)) cfg.ConnectTimeout = v.GetDuration(cfg.namespace + suffixConnectTimeout) cfg.TokenFilePath = v.GetString(cfg.namespace + suffixTokenFilePath) diff --git a/plugin/storage/cassandra/schema/create.sh b/plugin/storage/cassandra/schema/create.sh index 1c0d1377b5d..8bd09cf61b7 100755 --- a/plugin/storage/cassandra/schema/create.sh +++ b/plugin/storage/cassandra/schema/create.sh @@ -1,5 +1,7 @@ #!/usr/bin/env bash + + function usage { >&2 echo "Error: $1" >&2 echo ""