Merge pull request #13821 from ahrtr/configspec_config

Move the newClientCfg into clientv3 package so as to be reused by both etcdctl and v3discovery
This commit is contained in:
Marek Siarkowicz 2022-03-24 10:12:55 +01:00 committed by GitHub
commit 0d55a1ca2a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 153 additions and 176 deletions

View File

@ -19,6 +19,7 @@ import (
"crypto/tls"
"time"
"go.etcd.io/etcd/client/pkg/v3/transport"
"go.uber.org/zap"
"google.golang.org/grpc"
)
@ -118,3 +119,65 @@ type AuthConfig struct {
Username string `json:"username"`
Password string `json:"password"`
}
// NewClientConfig creates a Config based on the provided ConfigSpec.
func NewClientConfig(confSpec *ConfigSpec, lg *zap.Logger) (*Config, error) {
tlsCfg, err := newTLSConfig(confSpec.Secure, lg)
if err != nil {
return nil, err
}
cfg := &Config{
Endpoints: confSpec.Endpoints,
DialTimeout: confSpec.DialTimeout,
DialKeepAliveTime: confSpec.KeepAliveTime,
DialKeepAliveTimeout: confSpec.KeepAliveTimeout,
TLS: tlsCfg,
}
if confSpec.Auth != nil {
cfg.Username = confSpec.Auth.Username
cfg.Password = confSpec.Auth.Password
}
return cfg, nil
}
func newTLSConfig(scfg *SecureConfig, lg *zap.Logger) (*tls.Config, error) {
var (
tlsCfg *tls.Config
err error
)
if scfg == nil {
return nil, nil
}
if scfg.Cert != "" || scfg.Key != "" || scfg.Cacert != "" || scfg.ServerName != "" {
cfgtls := &transport.TLSInfo{
CertFile: scfg.Cert,
KeyFile: scfg.Key,
TrustedCAFile: scfg.Cacert,
ServerName: scfg.ServerName,
Logger: lg,
}
if tlsCfg, err = cfgtls.ClientConfig(); err != nil {
return nil, err
}
}
// If key/cert is not given but user wants secure connection, we
// should still setup an empty tls configuration for gRPC to setup
// secure connection.
if tlsCfg == nil && !scfg.InsecureTransport {
tlsCfg = &tls.Config{}
}
// If the user wants to skip TLS verification then we should set
// the InsecureSkipVerify flag in tls configuration.
if tlsCfg != nil && scfg.InsecureSkipVerify {
tlsCfg.InsecureSkipVerify = true
}
return tlsCfg, nil
}

View File

@ -12,38 +12,72 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package command
package clientv3
import (
"crypto/tls"
"go.uber.org/zap"
"testing"
"time"
"github.com/stretchr/testify/assert"
"go.etcd.io/etcd/client/pkg/v3/transport"
clientv3 "go.etcd.io/etcd/client/v3"
"go.uber.org/zap"
)
func TestNewClientConfig(t *testing.T) {
cases := []struct {
name string
spec clientv3.ConfigSpec
expectedConf clientv3.Config
spec ConfigSpec
expectedConf Config
}{
{
name: "default secure transport",
spec: clientv3.ConfigSpec{
name: "only has basic info",
spec: ConfigSpec{
Endpoints: []string{"http://192.168.0.10:2379"},
DialTimeout: 2 * time.Second,
KeepAliveTime: 3 * time.Second,
KeepAliveTimeout: 5 * time.Second,
Secure: &clientv3.SecureConfig{
},
expectedConf: Config{
Endpoints: []string{"http://192.168.0.10:2379"},
DialTimeout: 2 * time.Second,
DialKeepAliveTime: 3 * time.Second,
DialKeepAliveTimeout: 5 * time.Second,
},
},
{
name: "auth enabled",
spec: ConfigSpec{
Endpoints: []string{"http://192.168.0.12:2379"},
DialTimeout: 1 * time.Second,
KeepAliveTime: 4 * time.Second,
KeepAliveTimeout: 6 * time.Second,
Auth: &AuthConfig{
Username: "test",
Password: "changeme",
},
},
expectedConf: Config{
Endpoints: []string{"http://192.168.0.12:2379"},
DialTimeout: 1 * time.Second,
DialKeepAliveTime: 4 * time.Second,
DialKeepAliveTimeout: 6 * time.Second,
Username: "test",
Password: "changeme",
},
},
{
name: "default secure transport",
spec: ConfigSpec{
Endpoints: []string{"http://192.168.0.10:2379"},
DialTimeout: 2 * time.Second,
KeepAliveTime: 3 * time.Second,
KeepAliveTimeout: 5 * time.Second,
Secure: &SecureConfig{
InsecureTransport: false,
},
},
expectedConf: clientv3.Config{
expectedConf: Config{
Endpoints: []string{"http://192.168.0.10:2379"},
DialTimeout: 2 * time.Second,
DialKeepAliveTime: 3 * time.Second,
@ -51,44 +85,19 @@ func TestNewClientConfig(t *testing.T) {
TLS: &tls.Config{},
},
},
{
name: "default secure transport and auth enabled",
spec: clientv3.ConfigSpec{
Endpoints: []string{"http://192.168.0.12:2379"},
DialTimeout: 1 * time.Second,
KeepAliveTime: 4 * time.Second,
KeepAliveTimeout: 6 * time.Second,
Secure: &clientv3.SecureConfig{
InsecureTransport: false,
},
Auth: &clientv3.AuthConfig{
Username: "test",
Password: "changeme",
},
},
expectedConf: clientv3.Config{
Endpoints: []string{"http://192.168.0.12:2379"},
DialTimeout: 1 * time.Second,
DialKeepAliveTime: 4 * time.Second,
DialKeepAliveTimeout: 6 * time.Second,
TLS: &tls.Config{},
Username: "test",
Password: "changeme",
},
},
{
name: "default secure transport and skip TLS verification",
spec: clientv3.ConfigSpec{
spec: ConfigSpec{
Endpoints: []string{"http://192.168.0.13:2379"},
DialTimeout: 1 * time.Second,
KeepAliveTime: 3 * time.Second,
KeepAliveTimeout: 5 * time.Second,
Secure: &clientv3.SecureConfig{
Secure: &SecureConfig{
InsecureTransport: false,
InsecureSkipVerify: true,
},
},
expectedConf: clientv3.Config{
expectedConf: Config{
Endpoints: []string{"http://192.168.0.13:2379"},
DialTimeout: 1 * time.Second,
DialKeepAliveTime: 3 * time.Second,
@ -102,7 +111,9 @@ func TestNewClientConfig(t *testing.T) {
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
cfg, err := newClientCfg(tc.spec.Endpoints, tc.spec.DialTimeout, tc.spec.KeepAliveTime, tc.spec.KeepAliveTimeout, tc.spec.Secure, tc.spec.Auth)
lg, _ := zap.NewProduction()
cfg, err := NewClientConfig(&tc.spec, lg)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
@ -118,14 +129,20 @@ func TestNewClientConfigWithSecureCfg(t *testing.T) {
t.Fatalf("Unexpected error: %v", err)
}
scfg := &clientv3.SecureConfig{
scfg := &SecureConfig{
Cert: tls.CertFile,
Key: tls.KeyFile,
Cacert: tls.TrustedCAFile,
}
cfg, err := newClientCfg([]string{"http://192.168.0.13:2379"}, 2*time.Second, 3*time.Second, 5*time.Second, scfg, nil)
if cfg == nil || err != nil {
cfg, err := NewClientConfig(&ConfigSpec{
Endpoints: []string{"http://192.168.0.13:2379"},
DialTimeout: 2 * time.Second,
KeepAliveTime: 3 * time.Second,
KeepAliveTimeout: 5 * time.Second,
Secure: scfg,
}, nil)
if err != nil || cfg == nil || cfg.TLS == nil {
t.Fatalf("Unexpected result client config: %v", err)
}
}

View File

@ -6,6 +6,7 @@ require (
github.com/dustin/go-humanize v1.0.0
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
github.com/prometheus/client_golang v1.11.0
github.com/stretchr/testify v1.7.0
go.etcd.io/etcd/api/v3 v3.6.0-alpha.0
go.etcd.io/etcd/client/pkg/v3 v3.6.0-alpha.0
go.uber.org/zap v1.17.0
@ -26,7 +27,6 @@ require (
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.26.0 // indirect
github.com/prometheus/procfs v0.6.0 // indirect
github.com/stretchr/testify v1.7.0 // indirect
go.uber.org/atomic v1.7.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 // indirect

View File

@ -22,7 +22,7 @@ import (
"go.etcd.io/etcd/api/v3/etcdserverpb"
"go.etcd.io/etcd/api/v3/v3rpc/rpctypes"
v3 "go.etcd.io/etcd/client/v3"
"go.etcd.io/etcd/client/v3"
"go.etcd.io/etcd/pkg/v3/cobrautl"
"go.etcd.io/etcd/pkg/v3/flags"
@ -100,9 +100,16 @@ func epHealthCommandFunc(cmd *cobra.Command, args []string) {
ka := keepAliveTimeFromCmd(cmd)
kat := keepAliveTimeoutFromCmd(cmd)
auth := authCfgFromCmd(cmd)
cfgs := []*v3.Config{}
cfgs := []*clientv3.Config{}
for _, ep := range endpointsFromCluster(cmd) {
cfg, err := newClientCfg([]string{ep}, dt, ka, kat, sec, auth)
cfg, err := clientv3.NewClientConfig(&clientv3.ConfigSpec{
Endpoints: []string{ep},
DialTimeout: dt,
KeepAliveTime: ka,
KeepAliveTimeout: kat,
Secure: sec,
Auth: auth,
}, lg)
if err != nil {
cobrautl.ExitWithError(cobrautl.ExitBadArgs, err)
}
@ -113,11 +120,11 @@ func epHealthCommandFunc(cmd *cobra.Command, args []string) {
hch := make(chan epHealth, len(cfgs))
for _, cfg := range cfgs {
wg.Add(1)
go func(cfg *v3.Config) {
go func(cfg *clientv3.Config) {
defer wg.Done()
ep := cfg.Endpoints[0]
cfg.Logger = lg.Named("client")
cli, err := v3.New(*cfg)
cli, err := clientv3.New(*cfg)
if err != nil {
hch <- epHealth{Ep: ep, Health: false, Error: err.Error()}
return
@ -178,8 +185,8 @@ func epHealthCommandFunc(cmd *cobra.Command, args []string) {
}
type epStatus struct {
Ep string `json:"Endpoint"`
Resp *v3.StatusResponse `json:"Status"`
Ep string `json:"Endpoint"`
Resp *clientv3.StatusResponse `json:"Status"`
}
func epStatusCommandFunc(cmd *cobra.Command, args []string) {
@ -207,8 +214,8 @@ func epStatusCommandFunc(cmd *cobra.Command, args []string) {
}
type epHashKV struct {
Ep string `json:"Endpoint"`
Resp *v3.HashKVResponse `json:"HashKV"`
Ep string `json:"Endpoint"`
Resp *clientv3.HashKVResponse `json:"HashKV"`
}
func epHashKVCommandFunc(cmd *cobra.Command, args []string) {
@ -253,12 +260,18 @@ func endpointsFromCluster(cmd *cobra.Command) []string {
cobrautl.ExitWithError(cobrautl.ExitError, err)
}
// exclude auth for not asking needless password (MemberList() doesn't need authentication)
cfg, err := newClientCfg(eps, dt, ka, kat, sec, nil)
lg, _ := zap.NewProduction()
cfg, err := clientv3.NewClientConfig(&clientv3.ConfigSpec{
Endpoints: eps,
DialTimeout: dt,
KeepAliveTime: ka,
KeepAliveTimeout: kat,
Secure: sec,
}, lg)
if err != nil {
cobrautl.ExitWithError(cobrautl.ExitError, err)
}
c, err := v3.New(*cfg)
c, err := clientv3.New(*cfg)
if err != nil {
cobrautl.ExitWithError(cobrautl.ExitError, err)
}

View File

@ -15,7 +15,6 @@
package command
import (
"crypto/tls"
"errors"
"fmt"
"io"
@ -138,7 +137,8 @@ func clientConfigFromCmd(cmd *cobra.Command) *clientv3.ConfigSpec {
func mustClientCfgFromCmd(cmd *cobra.Command) *clientv3.Config {
cc := clientConfigFromCmd(cmd)
cfg, err := newClientCfg(cc.Endpoints, cc.DialTimeout, cc.KeepAliveTime, cc.KeepAliveTimeout, cc.Secure, cc.Auth)
lg, _ := zap.NewProduction()
cfg, err := clientv3.NewClientConfig(cc, lg)
if err != nil {
cobrautl.ExitWithError(cobrautl.ExitBadArgs, err)
}
@ -151,7 +151,8 @@ func mustClientFromCmd(cmd *cobra.Command) *clientv3.Client {
}
func mustClient(cc *clientv3.ConfigSpec) *clientv3.Client {
cfg, err := newClientCfg(cc.Endpoints, cc.DialTimeout, cc.KeepAliveTime, cc.KeepAliveTimeout, cc.Secure, cc.Auth)
lg, _ := zap.NewProduction()
cfg, err := clientv3.NewClientConfig(cc, lg)
if err != nil {
cobrautl.ExitWithError(cobrautl.ExitBadArgs, err)
}
@ -164,67 +165,6 @@ func mustClient(cc *clientv3.ConfigSpec) *clientv3.Client {
return client
}
func newClientCfg(endpoints []string, dialTimeout, keepAliveTime, keepAliveTimeout time.Duration, scfg *clientv3.SecureConfig, acfg *clientv3.AuthConfig) (*clientv3.Config, error) {
// set tls if any one tls option set
var cfgtls *transport.TLSInfo
tlsinfo := transport.TLSInfo{}
tlsinfo.Logger, _ = zap.NewProduction()
if scfg.Cert != "" {
tlsinfo.CertFile = scfg.Cert
cfgtls = &tlsinfo
}
if scfg.Key != "" {
tlsinfo.KeyFile = scfg.Key
cfgtls = &tlsinfo
}
if scfg.Cacert != "" {
tlsinfo.TrustedCAFile = scfg.Cacert
cfgtls = &tlsinfo
}
if scfg.ServerName != "" {
tlsinfo.ServerName = scfg.ServerName
cfgtls = &tlsinfo
}
cfg := &clientv3.Config{
Endpoints: endpoints,
DialTimeout: dialTimeout,
DialKeepAliveTime: keepAliveTime,
DialKeepAliveTimeout: keepAliveTimeout,
}
if cfgtls != nil {
clientTLS, err := cfgtls.ClientConfig()
if err != nil {
return nil, err
}
cfg.TLS = clientTLS
}
// if key/cert is not given but user wants secure connection, we
// should still setup an empty tls configuration for gRPC to setup
// secure connection.
if cfg.TLS == nil && !scfg.InsecureTransport {
cfg.TLS = &tls.Config{}
}
// If the user wants to skip TLS verification then we should set
// the InsecureSkipVerify flag in tls configuration.
if scfg.InsecureSkipVerify && cfg.TLS != nil {
cfg.TLS.InsecureSkipVerify = true
}
if acfg != nil {
cfg.Username = acfg.Username
cfg.Password = acfg.Password
}
return cfg, nil
}
func argOrStdin(args []string, stdin io.Reader, i int) (string, error) {
if i < len(args) {
return args[i], nil

View File

@ -8,7 +8,6 @@ require (
github.com/olekukonko/tablewriter v0.0.5
github.com/spf13/cobra v1.2.1
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.7.0
github.com/urfave/cli v1.22.4
go.etcd.io/etcd/api/v3 v3.6.0-alpha.0
go.etcd.io/etcd/client/pkg/v3 v3.6.0-alpha.0
@ -28,7 +27,6 @@ require (
github.com/coreos/go-semver v0.3.0 // indirect
github.com/coreos/go-systemd/v22 v22.3.2 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/golang/protobuf v1.5.2 // indirect
@ -37,7 +35,6 @@ require (
github.com/jonboulle/clockwork v0.2.2 // indirect
github.com/mattn/go-runewidth v0.0.9 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.11.0 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.26.0 // indirect
@ -59,7 +56,6 @@ require (
golang.org/x/text v0.3.7 // indirect
google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1 // indirect
google.golang.org/protobuf v1.27.1 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 // indirect
)

View File

@ -242,11 +242,9 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxv
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
@ -731,7 +729,6 @@ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/cheggaaa/pb.v1 v1.0.28 h1:n1tBJnnK2r7g9OW2btFH91V92STTUevLXYFb8gy9EMk=
gopkg.in/cheggaaa/pb.v1 v1.0.28/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=

View File

@ -18,7 +18,6 @@ package v3discovery
import (
"context"
"crypto/tls"
"errors"
"math"
@ -28,7 +27,6 @@ import (
"strings"
"time"
"go.etcd.io/etcd/client/pkg/v3/transport"
"go.etcd.io/etcd/client/pkg/v3/types"
"go.etcd.io/etcd/client/v3"
@ -173,7 +171,7 @@ func newDiscovery(lg *zap.Logger, dcfg *DiscoveryConfig, id types.ID) (*discover
}
lg = lg.With(zap.String("discovery-token", dcfg.Token), zap.String("discovery-endpoints", strings.Join(dcfg.Endpoints, ",")))
cfg, err := newClientCfg(dcfg, lg)
cfg, err := clientv3.NewClientConfig(&dcfg.ConfigSpec, lg)
if err != nil {
return nil, err
}
@ -192,53 +190,6 @@ func newDiscovery(lg *zap.Logger, dcfg *DiscoveryConfig, id types.ID) (*discover
}, nil
}
// The following function follows the same logic as etcdctl, refer to
// https://github.com/etcd-io/etcd/blob/f9a8c49c695b098d66a07948666664ea10d01a82/etcdctl/ctlv3/command/global.go#L191-L250
func newClientCfg(dcfg *DiscoveryConfig, lg *zap.Logger) (*clientv3.Config, error) {
var cfgtls *transport.TLSInfo
if dcfg.Secure.Cert != "" || dcfg.Secure.Key != "" || dcfg.Secure.Cacert != "" {
cfgtls = &transport.TLSInfo{
CertFile: dcfg.Secure.Cert,
KeyFile: dcfg.Secure.Key,
TrustedCAFile: dcfg.Secure.Cacert,
Logger: lg,
}
}
cfg := &clientv3.Config{
Endpoints: dcfg.Endpoints,
DialTimeout: dcfg.DialTimeout,
DialKeepAliveTime: dcfg.KeepAliveTime,
DialKeepAliveTimeout: dcfg.KeepAliveTimeout,
Username: dcfg.Auth.Username,
Password: dcfg.Auth.Password,
}
if cfgtls != nil {
if clientTLS, err := cfgtls.ClientConfig(); err == nil {
cfg.TLS = clientTLS
} else {
return nil, err
}
}
// If key/cert is not given but user wants secure connection, we
// should still setup an empty tls configuration for gRPC to setup
// secure connection.
if cfg.TLS == nil && !dcfg.Secure.InsecureTransport {
cfg.TLS = &tls.Config{}
}
// If the user wants to skip TLS verification then we should set
// the InsecureSkipVerify flag in tls configuration.
if cfg.TLS != nil && dcfg.Secure.InsecureSkipVerify {
cfg.TLS.InsecureSkipVerify = true
}
return cfg, nil
}
func (d *discovery) getCluster() (string, error) {
cls, clusterSize, rev, err := d.checkCluster()
if err != nil {