diff --git a/client/channel/channel.go b/client/channel/channel.go index 24967ca28..3aed377a0 100644 --- a/client/channel/channel.go +++ b/client/channel/channel.go @@ -277,6 +277,11 @@ func (c *Channel) runReceiver() { ID: msg.Id, Payload: p.PbmSwitchPitr, } + case *agentpb.ServerMessage_ParseDefaultsFile: + c.requests <- &ServerRequest{ + ID: msg.Id, + Payload: p.ParseDefaultsFile, + } // responses case *agentpb.ServerMessage_Pong: diff --git a/client/client.go b/client/client.go index d95508587..6f7c66358 100644 --- a/client/client.go +++ b/client/client.go @@ -55,10 +55,11 @@ const ( // Client represents pmm-agent's connection to nginx/pmm-managed. type Client struct { - cfg *config.Config - supervisor supervisor - connectionChecker connectionChecker - softwareVersioner softwareVersioner + cfg *config.Config + supervisor supervisor + connectionChecker connectionChecker + softwareVersioner softwareVersioner + defaultsFileParser defaultsFileParser l *logrus.Entry backoff *backoff.Backoff @@ -78,16 +79,17 @@ type Client struct { // New creates new client. // // Caller should call Run. -func New(cfg *config.Config, supervisor supervisor, connectionChecker connectionChecker, sv softwareVersioner) *Client { +func New(cfg *config.Config, supervisor supervisor, connectionChecker connectionChecker, sv softwareVersioner, dfp defaultsFileParser) *Client { return &Client{ - cfg: cfg, - supervisor: supervisor, - connectionChecker: connectionChecker, - softwareVersioner: sv, - l: logrus.WithField("component", "client"), - backoff: backoff.New(backoffMinDelay, backoffMaxDelay), - done: make(chan struct{}), - dialTimeout: dialTimeout, + cfg: cfg, + supervisor: supervisor, + connectionChecker: connectionChecker, + softwareVersioner: sv, + l: logrus.WithField("component", "client"), + backoff: backoff.New(backoffMinDelay, backoffMaxDelay), + done: make(chan struct{}), + dialTimeout: dialTimeout, + defaultsFileParser: dfp, } } @@ -436,6 +438,8 @@ func (c *Client) processChannelRequests(ctx context.Context) { resp.Error = err.Error() } responsePayload = &resp + case *agentpb.ParseDefaultsFileRequest: + responsePayload = c.defaultsFileParser.ParseDefaultsFile(p) default: c.l.Errorf("Unhandled server request: %v.", req) } diff --git a/client/client_test.go b/client/client_test.go index 05c943478..34c60df1f 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -78,7 +78,7 @@ func TestClient(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) cfg := &config.Config{} - client := New(cfg, nil, nil, nil) + client := New(cfg, nil, nil, nil, nil) cancel() err := client.Run(ctx) assert.EqualError(t, err, "missing PMM Server address: context canceled") @@ -93,7 +93,7 @@ func TestClient(t *testing.T) { Address: "127.0.0.1:1", }, } - client := New(cfg, nil, nil, nil) + client := New(cfg, nil, nil, nil, nil) cancel() err := client.Run(ctx) assert.EqualError(t, err, "missing Agent ID: context canceled") @@ -110,7 +110,7 @@ func TestClient(t *testing.T) { Address: "127.0.0.1:1", }, } - client := New(cfg, nil, nil, nil) + client := New(cfg, nil, nil, nil, nil) err := client.Run(ctx) assert.EqualError(t, err, "failed to dial: context deadline exceeded") }) @@ -156,7 +156,7 @@ func TestClient(t *testing.T) { s.On("Changes").Return(make(<-chan *agentpb.StateChangedRequest)) s.On("QANRequests").Return(make(<-chan *agentpb.QANCollectRequest)) - client := New(cfg, &s, nil, nil) + client := New(cfg, &s, nil, nil, nil) err := client.Run(context.Background()) assert.NoError(t, err) assert.Equal(t, serverMD, client.GetServerConnectMetadata()) @@ -184,7 +184,7 @@ func TestClient(t *testing.T) { }, } - client := New(cfg, nil, nil, nil) + client := New(cfg, nil, nil, nil, nil) client.dialTimeout = 100 * time.Millisecond err := client.Run(ctx) assert.EqualError(t, err, "failed to get server metadata: rpc error: code = Canceled desc = context canceled", "%+v", err) @@ -212,7 +212,7 @@ func TestGetActionTimeout(t *testing.T) { for _, tc := range testCases { tc := tc t.Run(prototext.Format(tc.req), func(t *testing.T) { - client := New(nil, nil, nil, nil) + client := New(nil, nil, nil, nil, nil) actual := client.getActionTimeout(tc.req) assert.Equal(t, tc.expected, actual) }) @@ -271,7 +271,7 @@ func TestUnexpectedActionType(t *testing.T) { s.On("Changes").Return(make(<-chan *agentpb.StateChangedRequest)) s.On("QANRequests").Return(make(<-chan *agentpb.QANCollectRequest)) - client := New(cfg, s, nil, nil) + client := New(cfg, s, nil, nil, nil) err := client.Run(context.Background()) assert.NoError(t, err) assert.Equal(t, serverMD, client.GetServerConnectMetadata()) diff --git a/client/deps.go b/client/deps.go index 1f7fcd2b3..391d93824 100644 --- a/client/deps.go +++ b/client/deps.go @@ -24,6 +24,7 @@ import ( //go:generate mockery -name=connectionChecker -case=snake -inpkg -testonly //go:generate mockery -name=supervisor -case=snake -inpkg -testonly +//go:generate mockery -name=defaultsFileParser -case=snake -inpkg -testonly // connectionChecker is a subset of methods of connectionchecker.ConnectionChecker used by this package. // We use it instead of real type for testing and to avoid dependency cycle. @@ -48,3 +49,7 @@ type supervisor interface { // Collector added to use client as Prometheus collector prometheus.Collector } + +type defaultsFileParser interface { + ParseDefaultsFile(req *agentpb.ParseDefaultsFileRequest) *agentpb.ParseDefaultsFileResponse +} diff --git a/client/mock_defaults_file_parser_test.go b/client/mock_defaults_file_parser_test.go new file mode 100644 index 000000000..ac57142e4 --- /dev/null +++ b/client/mock_defaults_file_parser_test.go @@ -0,0 +1,29 @@ +// Code generated by mockery v1.0.0. DO NOT EDIT. + +package client + +import ( + agentpb "github.com/percona/pmm/api/agentpb" + mock "github.com/stretchr/testify/mock" +) + +// mockDefaultsFileParser is an autogenerated mock type for the defaultsFileParser type +type mockDefaultsFileParser struct { + mock.Mock +} + +// ParseDefaultsFile provides a mock function with given fields: req +func (_m *mockDefaultsFileParser) ParseDefaultsFile(req *agentpb.ParseDefaultsFileRequest) *agentpb.ParseDefaultsFileResponse { + ret := _m.Called(req) + + var r0 *agentpb.ParseDefaultsFileResponse + if rf, ok := ret.Get(0).(func(*agentpb.ParseDefaultsFileRequest) *agentpb.ParseDefaultsFileResponse); ok { + r0 = rf(req) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*agentpb.ParseDefaultsFileResponse) + } + } + + return r0 +} diff --git a/commands/run.go b/commands/run.go index 005abd799..f1631d86f 100644 --- a/commands/run.go +++ b/commands/run.go @@ -28,6 +28,7 @@ import ( "github.com/percona/pmm-agent/client" "github.com/percona/pmm-agent/config" "github.com/percona/pmm-agent/connectionchecker" + "github.com/percona/pmm-agent/defaultsfile" "github.com/percona/pmm-agent/versioner" ) @@ -75,8 +76,9 @@ func run(ctx context.Context, cfg *config.Config, configFilepath string) { supervisor := supervisor.NewSupervisor(ctx, &cfg.Paths, &cfg.Ports, &cfg.Server) connectionChecker := connectionchecker.New(&cfg.Paths) + defaultsFileParser := defaultsfile.New() v := versioner.New(&versioner.RealExecFunctions{}) - client := client.New(cfg, supervisor, connectionChecker, v) + client := client.New(cfg, supervisor, connectionChecker, v, defaultsFileParser) localServer := agentlocal.NewServer(cfg, supervisor, client, configFilepath) go func() { diff --git a/defaultsfile/defaults_file.go b/defaultsfile/defaults_file.go new file mode 100644 index 000000000..01c43e582 --- /dev/null +++ b/defaultsfile/defaults_file.go @@ -0,0 +1,131 @@ +// pmm-agent +// Copyright 2019 Percona LLC +// +// Licensed 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 defaultsfile provides managing of defaults file. +package defaultsfile + +import ( + "fmt" + "os/user" + "path/filepath" + "strings" + + "github.com/percona/pmm/api/agentpb" + "github.com/percona/pmm/api/inventorypb" + "github.com/pkg/errors" + "gopkg.in/ini.v1" +) + +// Parser is a struct which is responsible for parsing defaults file. +type Parser struct{} + +// New creates new DefaultsFileParser. +func New() *Parser { + return &Parser{} +} + +type defaultsFile struct { + username string + password string + host string + port uint32 + socket string +} + +// ParseDefaultsFile parses given defaultsFile in request. It returns the database specs. +func (d *Parser) ParseDefaultsFile(req *agentpb.ParseDefaultsFileRequest) *agentpb.ParseDefaultsFileResponse { + var res agentpb.ParseDefaultsFileResponse + defaultsFile, err := parseDefaultsFile(req.ConfigPath, req.ServiceType) + if err != nil { + res.Error = err.Error() + return &res + } + + res.Username = defaultsFile.username + res.Password = defaultsFile.password + res.Host = defaultsFile.host + res.Port = defaultsFile.port + res.Socket = defaultsFile.socket + + return &res +} + +func parseDefaultsFile(configPath string, serviceType inventorypb.ServiceType) (*defaultsFile, error) { + if len(configPath) == 0 { + return nil, errors.New("configPath for DefaultsFile is empty") + } + + switch serviceType { + case inventorypb.ServiceType_MYSQL_SERVICE: + return parseMySQLDefaultsFile(configPath) + case inventorypb.ServiceType_EXTERNAL_SERVICE: + case inventorypb.ServiceType_HAPROXY_SERVICE: + case inventorypb.ServiceType_MONGODB_SERVICE: + case inventorypb.ServiceType_POSTGRESQL_SERVICE: + case inventorypb.ServiceType_PROXYSQL_SERVICE: + case inventorypb.ServiceType_SERVICE_TYPE_INVALID: + return nil, errors.Errorf("unimplemented service type %s", serviceType) + } + + return nil, errors.Errorf("unimplemented service type %s", serviceType) +} + +func parseMySQLDefaultsFile(configPath string) (*defaultsFile, error) { + configPath, err := expandPath(configPath) + if err != nil { + return nil, fmt.Errorf("fail to normalize path: %w", err) + } + + cfg, err := ini.Load(configPath) + if err != nil { + return nil, fmt.Errorf("fail to read config file: %w", err) + } + + cfgSection := cfg.Section("client") + port, _ := cfgSection.Key("port").Uint() + + parsedData := &defaultsFile{ + username: cfgSection.Key("user").String(), + password: cfgSection.Key("password").String(), + host: cfgSection.Key("host").String(), + port: uint32(port), + socket: cfgSection.Key("socket").String(), + } + + err = validateDefaultsFileResults(parsedData) + if err != nil { + return nil, err + } + + return parsedData, nil +} + +func validateDefaultsFileResults(data *defaultsFile) error { + if data.username == "" && data.password == "" && data.host == "" && data.port == 0 && data.socket == "" { + return errors.New("no data found in defaults file") + } + return nil +} + +func expandPath(path string) (string, error) { + if strings.HasPrefix(path, "~/") { + usr, err := user.Current() + if err != nil { + return "", fmt.Errorf("failed to expand path: %w", err) + } + return filepath.Join(usr.HomeDir, path[2:]), nil + } + return path, nil +} diff --git a/defaultsfile/defaults_file_test.go b/defaultsfile/defaults_file_test.go new file mode 100644 index 000000000..5fedd3289 --- /dev/null +++ b/defaultsfile/defaults_file_test.go @@ -0,0 +1,120 @@ +// pmm-agent +// Copyright 2019 Percona LLC +// +// Licensed 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 defaultsfile + +import ( + "path/filepath" + "testing" + + "github.com/percona/pmm/api/agentpb" + "github.com/percona/pmm/api/inventorypb" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestDefaultsFileParser(t *testing.T) { + t.Parallel() + cnfFilePath, err := filepath.Abs("../utils/tests/testdata/defaultsfile/.my.cnf") + assert.NoError(t, err) + + testCases := []struct { + name string + req *agentpb.ParseDefaultsFileRequest + expectedErr string + }{ + { + name: "Valid MySQL file", + req: &agentpb.ParseDefaultsFileRequest{ + ServiceType: inventorypb.ServiceType_MYSQL_SERVICE, + ConfigPath: cnfFilePath, + }, + }, + { + name: "File not found", + req: &agentpb.ParseDefaultsFileRequest{ + ServiceType: inventorypb.ServiceType_MYSQL_SERVICE, + ConfigPath: "path/to/invalid/file.cnf", + }, + expectedErr: `no such file or directory`, + }, + { + name: "Service type not supported", + req: &agentpb.ParseDefaultsFileRequest{ + ServiceType: inventorypb.ServiceType_HAPROXY_SERVICE, + ConfigPath: cnfFilePath, + }, + expectedErr: `unimplemented service type HAPROXY_SERVICE`, + }, + } + + for _, testCase := range testCases { + testCase := testCase + t.Run(testCase.name, func(t *testing.T) { + t.Parallel() + c := New() + resp := c.ParseDefaultsFile(testCase.req) + require.NotNil(t, resp) + if testCase.expectedErr == "" { + assert.Empty(t, resp.Error) + } else { + require.NotEmpty(t, resp.Error) + assert.Regexp(t, `.*`+testCase.expectedErr+`.*`, resp.Error) + } + }) + } +} + +func TestValidateResults(t *testing.T) { + t.Parallel() + t.Run("validation error", func(t *testing.T) { + t.Parallel() + err := validateDefaultsFileResults(&defaultsFile{ + "", + "", + "", + 0, + "", + }) + + require.Error(t, err) + }) + + t.Run("validation ok - user and password", func(t *testing.T) { + t.Parallel() + err := validateDefaultsFileResults(&defaultsFile{ + "root", + "root123", + "", + 0, + "", + }) + + require.NoError(t, err) + }) + + t.Run("validation ok - only port", func(t *testing.T) { + t.Parallel() + err := validateDefaultsFileResults(&defaultsFile{ + "", + "", + "", + 3133, + "", + }) + + require.NoError(t, err) + }) +} diff --git a/go.mod b/go.mod index f0da987dc..6429bf47e 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,7 @@ require ( github.com/percona/exporter_shared v0.7.3 github.com/percona/go-mysql v0.0.0-20200630114833-b77f37c0bfa2 github.com/percona/percona-toolkit v3.2.1+incompatible - github.com/percona/pmm v0.0.0-20220516171205-6f9c9d3e0c6b + github.com/percona/pmm v0.0.0-20220526203331-cb61205581dc github.com/pganalyze/pg_query_go v1.0.3 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.12.2 @@ -39,6 +39,7 @@ require ( google.golang.org/grpc v1.46.0 google.golang.org/protobuf v1.28.0 gopkg.in/alecthomas/kingpin.v2 v2.2.6 + gopkg.in/ini.v1 v1.66.4 gopkg.in/reform.v1 v1.5.1 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b ) diff --git a/go.sum b/go.sum index 5b4904b45..778d12c11 100644 --- a/go.sum +++ b/go.sum @@ -122,7 +122,6 @@ github.com/go-openapi/loads v0.21.1 h1:Wb3nVZpdEzDTcly8S4HMkey6fjARRzb7iEaySimlD github.com/go-openapi/loads v0.21.1/go.mod h1:/DtAMXXneXFjbQMGEtbamCZb+4x7eGwkvZCvBmwUG+g= github.com/go-openapi/runtime v0.24.0 h1:vTgDijpGLCgJOJTdAp5kG+O+nRsVCbH417YQ3O0iZo0= github.com/go-openapi/runtime v0.24.0/go.mod h1:AKurw9fNre+h3ELZfk6ILsfvPN+bvvlaU/M9q/r9hpk= -github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M= github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I= github.com/go-openapi/spec v0.20.5 h1:skHa8av4VnAtJU5zyAUXrrdK/NDiVX8lchbG+BfcdrE= github.com/go-openapi/spec v0.20.5/go.mod h1:QbfOSIVt3/sac+a1wzmKbbcLXm5NdZnyBZYtCijp43o= @@ -294,7 +293,6 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0j github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs= github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= @@ -323,10 +321,8 @@ github.com/percona/go-mysql v0.0.0-20200630114833-b77f37c0bfa2 h1:0tQBti5FIrKfH3 github.com/percona/go-mysql v0.0.0-20200630114833-b77f37c0bfa2/go.mod h1:/SGLf9OMxlnK6jq4mkFiImBcJXXk5jwD+lDrwDaGXcw= github.com/percona/percona-toolkit v3.2.1+incompatible h1:5jLvtZKcu9fDmaLRB8qA4bLR727t5iYyguHJJQTk9w0= github.com/percona/percona-toolkit v3.2.1+incompatible/go.mod h1:netQWdWMaF1cnmwiIS+i5uyaqNXz46yNeM6HKkR6yeI= -github.com/percona/pmm v0.0.0-20220505164356-d8b4097358e1 h1:Iil3UzE49DPn4keMZ4apU396bzRJRQZvNGJc8jWRp08= -github.com/percona/pmm v0.0.0-20220505164356-d8b4097358e1/go.mod h1:k7HS59HPX33tmrSZGiNzUTYuLr0+a49F3BEZ48MAbuo= -github.com/percona/pmm v0.0.0-20220516171205-6f9c9d3e0c6b h1:i7MbHYxAT7AkX5PWBrC5W+0YA9rr/lgEq5OX3u0rJ2k= -github.com/percona/pmm v0.0.0-20220516171205-6f9c9d3e0c6b/go.mod h1:gr+WLd8clEAe2xMFgsGhpw9ziZc2UCWcfy6d3M6Aq00= +github.com/percona/pmm v0.0.0-20220526203331-cb61205581dc h1:J8ZbKkqhlbx4oGeQmUnoZzVJVh/XPcENudq/ovx0dlI= +github.com/percona/pmm v0.0.0-20220526203331-cb61205581dc/go.mod h1:gr+WLd8clEAe2xMFgsGhpw9ziZc2UCWcfy6d3M6Aq00= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -338,7 +334,6 @@ github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5Fsn github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk= github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/prometheus/client_golang v1.12.2 h1:51L9cDoUHVrXx4zWYlcLQIZ+d+VXHgqnYKkIuq4g/34= github.com/prometheus/client_golang v1.12.2/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= @@ -489,8 +484,6 @@ golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1 golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220412020605-290c469a71a5 h1:bRb386wvrE+oBNdF1d/Xh9mQrfQ4ecYhW5qJ5GvTGT4= -golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA= golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -722,6 +715,8 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/ini.v1 v1.66.4 h1:SsAcf+mM7mRZo2nJNGt8mZCjG8ZRaNGMURJw7BsIST4= +gopkg.in/ini.v1 v1.66.4/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/reform.v1 v1.5.1 h1:7vhDFW1n1xAPC6oDSvIvVvpRkaRpXlxgJ4QB4s3aDdo= gopkg.in/reform.v1 v1.5.1/go.mod h1:AIv0CbDRJ0ljQwptGeaIXfpDRo02uJwTq92aMFELEeU= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/utils/tests/testdata/defaultsfile/.my.cnf b/utils/tests/testdata/defaultsfile/.my.cnf new file mode 100644 index 000000000..50bc24c18 --- /dev/null +++ b/utils/tests/testdata/defaultsfile/.my.cnf @@ -0,0 +1,5 @@ +[client] +host=127.0.0.1 +port=12345 +user=root123 +password=root-password123