From 30164efb4fe42e5d0d14277a8509b271f1342261 Mon Sep 17 00:00:00 2001 From: Jindrich Skupa Date: Thu, 26 Aug 2021 08:16:16 +0200 Subject: [PATCH 01/18] Small fixes - close connection on broken tunnel, private key is not mandatory (encrypted keys) --- tunnel/tunnel.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index e3ebbd5..eaf46ba 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -452,10 +452,9 @@ func sshClientConfig(server Server) (*ssh.ClientConfig, error) { var signers []ssh.Signer signer, err := server.Key.Parse() - if err != nil { - return nil, err + if err == nil { + signers = append(signers, signer) } - signers = append(signers, signer) if server.SSHAgent != "" { if _, err := os.Stat(server.SSHAgent); err == nil { @@ -486,6 +485,8 @@ func sshClientConfig(server Server) (*ssh.ClientConfig, error) { func copyConn(writer, reader net.Conn) { _, err := io.Copy(writer, reader) + defer writer.Close() + defer reader.Close() if err != nil { log.Errorf("%v", err) } From 3cf2b415bf72dd8e7f3b1e199d9268ea100c0f9d Mon Sep 17 00:00:00 2001 From: Jindrich Skupa Date: Thu, 26 Aug 2021 08:47:31 +0200 Subject: [PATCH 02/18] Don't fail on empty config --- tunnel/tunnel.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index eaf46ba..327beaf 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -54,13 +54,13 @@ func NewServer(user, address, key, sshAgent, cfgPath string) (*Server, error) { c, err := NewSSHConfigFile(cfgPath) if err != nil { - if !errors.Is(err, os.ErrNotExist) { + if !(errors.Is(err, os.ErrNotExist) || errors.Is(err, os.ErrInvalid)){ return nil, fmt.Errorf("error accessing %s: %v", host, err) } } // If ssh config file doesnt exists, create an empty ssh config struct to avoid nil pointer deference - if errors.Is(err, os.ErrNotExist) { + if errors.Is(err, os.ErrNotExist) || errors.Is(err, os.ErrInvalid){ c = NewEmptySSHConfigStruct() } From eb9f646cba9ec2b82fd25b9e6aa6de98986725aa Mon Sep 17 00:00:00 2001 From: Jindrich Skupa Date: Thu, 26 Aug 2021 09:12:06 +0200 Subject: [PATCH 03/18] Don't fail on empty config --- tunnel/config.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tunnel/config.go b/tunnel/config.go index 963e124..cee4251 100644 --- a/tunnel/config.go +++ b/tunnel/config.go @@ -21,6 +21,9 @@ type SSHConfigFile struct { // NewSSHConfigFile creates a new instance of SSHConfigFile based on the // ssh config file from configPath func NewSSHConfigFile(configPath string) (*SSHConfigFile, error) { + if configPath == "" { + return nil, os.ErrInvalid + } if strings.Contains(configPath, homeVar) { home, err := os.UserHomeDir() if err != nil { From 672bb76e403f7839f509ae88bf215fd62229fd61 Mon Sep 17 00:00:00 2001 From: Jindrich Skupa Date: Tue, 14 Sep 2021 10:51:25 +0200 Subject: [PATCH 04/18] fix missing auth method, check cfgPath was provided --- tunnel/tunnel.go | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 327beaf..d1d9738 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -44,6 +44,7 @@ func NewServer(user, address, key, sshAgent, cfgPath string) (*Server, error) { var host string var hostname string var port string + var c *SSHConfigFile host = address if strings.Contains(host, ":") { @@ -52,16 +53,17 @@ func NewServer(user, address, key, sshAgent, cfgPath string) (*Server, error) { port = args[1] } - c, err := NewSSHConfigFile(cfgPath) - if err != nil { - if !(errors.Is(err, os.ErrNotExist) || errors.Is(err, os.ErrInvalid)){ - return nil, fmt.Errorf("error accessing %s: %v", host, err) - } - } - - // If ssh config file doesnt exists, create an empty ssh config struct to avoid nil pointer deference - if errors.Is(err, os.ErrNotExist) || errors.Is(err, os.ErrInvalid){ + if cfgPath == "" { c = NewEmptySSHConfigStruct() + } else { + c, err := NewSSHConfigFile(cfgPath) + if err != nil { + if !errors.Is(err, os.ErrNotExist) { + return nil, fmt.Errorf("error accessing %s: %v", host, err) + } else { + c = NewEmptySSHConfigStruct() + } + } } h := c.Get(host) @@ -451,9 +453,14 @@ func (t *Tunnel) keepAlive() { func sshClientConfig(server Server) (*ssh.ClientConfig, error) { var signers []ssh.Signer - signer, err := server.Key.Parse() - if err == nil { - signers = append(signers, signer) + if server.Key == nil || server.SSHAgent == "" { + return nil, fmt.Errorf("at least one authentication method (key or ssh agent) must be present.") + } + if server.Key != nil { + signer, err := server.Key.Parse() + if err == nil { + signers = append(signers, signer) + } } if server.SSHAgent != "" { From 379e7cd8120e68837516b3a7b17c7edc504f1367 Mon Sep 17 00:00:00 2001 From: Jindrich Skupa Date: Tue, 14 Sep 2021 10:56:06 +0200 Subject: [PATCH 05/18] fix missing auth method, check cfgPath was provided --- tunnel/config.go | 3 --- tunnel/tunnel.go | 5 +++++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/tunnel/config.go b/tunnel/config.go index cee4251..963e124 100644 --- a/tunnel/config.go +++ b/tunnel/config.go @@ -21,9 +21,6 @@ type SSHConfigFile struct { // NewSSHConfigFile creates a new instance of SSHConfigFile based on the // ssh config file from configPath func NewSSHConfigFile(configPath string) (*SSHConfigFile, error) { - if configPath == "" { - return nil, os.ErrInvalid - } if strings.Contains(configPath, homeVar) { home, err := os.UserHomeDir() if err != nil { diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index d1d9738..0b371db 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -456,6 +456,7 @@ func sshClientConfig(server Server) (*ssh.ClientConfig, error) { if server.Key == nil || server.SSHAgent == "" { return nil, fmt.Errorf("at least one authentication method (key or ssh agent) must be present.") } + if server.Key != nil { signer, err := server.Key.Parse() if err == nil { @@ -475,6 +476,10 @@ func sshClientConfig(server Server) (*ssh.ClientConfig, error) { } } + if len(signers) == 0 { + return nil, fmt.Errorf("at least one authentication method (key or ssh agent) must be present.") + } + clb, err := knownHostsCallback(server.Insecure) if err != nil { return nil, err From 6ed614412eac4149829d88790bf4a9c8edb3b794 Mon Sep 17 00:00:00 2001 From: Jindrich Skupa Date: Wed, 15 Sep 2021 13:31:50 +0200 Subject: [PATCH 06/18] fixes --- tunnel/tunnel.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 0b371db..d95401c 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -45,6 +45,7 @@ func NewServer(user, address, key, sshAgent, cfgPath string) (*Server, error) { var hostname string var port string var c *SSHConfigFile + var err error host = address if strings.Contains(host, ":") { @@ -56,7 +57,7 @@ func NewServer(user, address, key, sshAgent, cfgPath string) (*Server, error) { if cfgPath == "" { c = NewEmptySSHConfigStruct() } else { - c, err := NewSSHConfigFile(cfgPath) + c, err = NewSSHConfigFile(cfgPath) if err != nil { if !errors.Is(err, os.ErrNotExist) { return nil, fmt.Errorf("error accessing %s: %v", host, err) @@ -453,7 +454,7 @@ func (t *Tunnel) keepAlive() { func sshClientConfig(server Server) (*ssh.ClientConfig, error) { var signers []ssh.Signer - if server.Key == nil || server.SSHAgent == "" { + if server.Key == nil && server.SSHAgent == "" { return nil, fmt.Errorf("at least one authentication method (key or ssh agent) must be present.") } @@ -477,7 +478,7 @@ func sshClientConfig(server Server) (*ssh.ClientConfig, error) { } if len(signers) == 0 { - return nil, fmt.Errorf("at least one authentication method (key or ssh agent) must be present.") + return nil, fmt.Errorf("at least one working authentication method (key or ssh agent) must be present.") } clb, err := knownHostsCallback(server.Insecure) From 5c78ff38f3f69ee5ea5434b9462e0244414280b5 Mon Sep 17 00:00:00 2001 From: David Pinheiro Date: Tue, 22 Dec 2020 21:49:42 -0800 Subject: [PATCH 07/18] Add new command to show running configuration This changes adds a new command to show the runtime configuration of one or all running instances of mole running on the system. It leverages the internal rpc server to call a remote procedure that returns the runtime configuration. --- alias/address_input.go | 6 +- cmd/misc_rpc.go | 18 +++++- cmd/show_instances.go | 60 +++++++++++++++++ cmd/start_local.go | 1 + fsutils/fsutils.go | 62 +++++++++++++++++- fsutils/fsutils_test.go | 139 ++++++++++++++++++++++++++++++++++++++-- go.mod | 6 +- go.sum | 102 +++++------------------------ mole/app.go | 32 --------- mole/mole.go | 90 ++++++++++++++++++++------ mole/rpc.go | 59 +++++++++++++++++ mole/runtime.go | 64 ++++++++++++++++++ mole/runtime_test.go | 99 ++++++++++++++++++++++++++++ rpc/client.go | 115 +++++++++++++++++++++++++++++++++ rpc/rpc.go | 21 ------ 15 files changed, 701 insertions(+), 173 deletions(-) create mode 100644 cmd/show_instances.go create mode 100644 mole/rpc.go create mode 100644 mole/runtime.go create mode 100644 mole/runtime_test.go create mode 100644 rpc/client.go diff --git a/alias/address_input.go b/alias/address_input.go index aec0e82..1cb60b0 100644 --- a/alias/address_input.go +++ b/alias/address_input.go @@ -14,9 +14,9 @@ var re = regexp.MustCompile(`(?P.+@)?(?P[[:alpha:][:digit:]\_\-\.]+) // AddressInput holds information about a host type AddressInput struct { - User string - Host string - Port string + User string `mapstructure:"user" toml:"user"` + Host string `mapstructure:"host" toml:"host"` + Port string `mapstructure:"port" toml:"port"` } // String returns a string representation of a AddressInput diff --git a/cmd/misc_rpc.go b/cmd/misc_rpc.go index 9357502..398ae3e 100644 --- a/cmd/misc_rpc.go +++ b/cmd/misc_rpc.go @@ -1,10 +1,12 @@ package cmd import ( + "context" + "encoding/json" "fmt" "os" - "github.com/davrodpin/mole/mole" + "github.com/davrodpin/mole/rpc" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -32,15 +34,25 @@ var ( return nil }, Run: func(cmd *cobra.Command, arg []string) { - resp, err := mole.Rpc(id, method, params) + resp, err := rpc.CallById(context.Background(), id, method, params) if err != nil { log.WithError(err).WithFields(log.Fields{ "id": id, }).Error("error executing remote procedure.") + + os.Exit(1) + } + + json, err := json.MarshalIndent(resp, "", " ") + if err != nil { + log.WithError(err).WithFields(log.Fields{ + "id": id, + }).Error("error executing remote procedure.") + os.Exit(1) } - fmt.Printf(resp) + fmt.Printf("%s\n", string(json)) }, } ) diff --git a/cmd/show_instances.go b/cmd/show_instances.go new file mode 100644 index 0000000..6c3a42b --- /dev/null +++ b/cmd/show_instances.go @@ -0,0 +1,60 @@ +package cmd + +import ( + "fmt" + "os" + + "github.com/davrodpin/mole/mole" + + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" +) + +var ( + showInstancesCmd = &cobra.Command{ + Use: "instances [name]", + Short: "Shows runtime information about application instances", + Long: `Shows runtime information about application instances. + +Only instances with rpc enabled will be shown by this command.`, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) > 0 { + id = args[0] + } + + return nil + }, + Run: func(cmd *cobra.Command, arg []string) { + var err error + var formatter mole.Formatter + + if id == "" { + formatter, err = mole.ShowInstances() + } else { + formatter, err = mole.ShowInstance(id) + } + + if err != nil { + log.WithError(err).WithFields(log.Fields{ + "id": id, + }).Error("could not retrieve information about application instance(s)") + os.Exit(1) + } + + out, err := formatter.Format("toml") + if err != nil { + log.WithError(err).WithFields(log.Fields{ + "id": id, + }).Error("error converting output") + os.Exit(1) + } + + fmt.Printf("%s\n", out) + + }, + } +) + +func init() { + showCmd.AddCommand(showInstancesCmd) +} diff --git a/cmd/start_local.go b/cmd/start_local.go index 9d2804c..c55b4fe 100644 --- a/cmd/start_local.go +++ b/cmd/start_local.go @@ -30,6 +30,7 @@ Destination endpoints are adrresess that can be reached from the jump server. err := client.Start() if err != nil { + log.WithError(err).Error("error starting mole") os.Exit(1) } }, diff --git a/fsutils/fsutils.go b/fsutils/fsutils.go index 3087bd4..47e696c 100644 --- a/fsutils/fsutils.go +++ b/fsutils/fsutils.go @@ -56,7 +56,7 @@ func CreateHomeDir() (string, error) { // Along with the directory this function also created a pid file where the // instance process id is stored. func CreateInstanceDir(appId string) (*InstanceDirInfo, error) { - home, err := Dir() + home, err := CreateHomeDir() if err != nil { return nil, err } @@ -162,3 +162,63 @@ func createPidFile(id string) (string, error) { return pfl, nil } + +// RpcAddress returns the network address of the rpc server for a given +// application instance id or alias. +func RpcAddress(id string) (string, error) { + d, err := InstanceDir(id) + if err != nil { + return "", err + } + + rf := filepath.Join(d.Dir, "rpc") + + if _, err := os.Stat(rf); os.IsNotExist(err) { + return "", fmt.Errorf("can't find rpc address for instance %s: instance is not running or rpc is disabled", id) + } + + data, err := ioutil.ReadFile(rf) + if err != nil { + return "", err + } + + return string(data), nil +} + +// PidFileLocation returns the location of the pid file associated with a mole +// instance. +// +// Only detached instances keep a pid file so, if an alias is given to +// this function, a path to a non-existent file will be returned. +func PidFileLocation(id string) (string, error) { + d, err := InstanceDir(id) + if err != nil { + return "", err + } + + return filepath.Join(d.Dir, InstancePidFile), nil +} + +// Pid returns the process id associated with the given alias or id. +func Pid(id string) (int, error) { + if pid, err := strconv.Atoi(id); err == nil { + return pid, nil + } + + pfl, err := PidFileLocation(id) + if err != nil { + return -1, err + } + + ps, err := ioutil.ReadFile(pfl) + if err != nil { + return -1, err + } + + pid, err := strconv.Atoi(string(ps)) + if err != nil { + return -1, err + } + + return pid, nil +} diff --git a/fsutils/fsutils_test.go b/fsutils/fsutils_test.go index 8bebdca..a41c52c 100644 --- a/fsutils/fsutils_test.go +++ b/fsutils/fsutils_test.go @@ -1,9 +1,11 @@ package fsutils_test import ( + "fmt" "io/ioutil" "os" "path/filepath" + "strconv" "testing" "github.com/davrodpin/mole/fsutils" @@ -21,8 +23,8 @@ func TestCreateInstanceDir(t *testing.T) { id string preCreate bool }{ - {id: "d34dbeef", preCreate: true}, {id: "f00d", preCreate: false}, + {id: "d34dbeef", preCreate: true}, } for idx, test := range tests { @@ -64,20 +66,143 @@ func TestCreateInstanceDir(t *testing.T) { } -func TestMain(m *testing.M) { - var err error +func TestPid(t *testing.T) { + expectedPid := 1234 + id := strconv.Itoa(expectedPid) - home, err = ioutil.TempDir("", "mole") + pid, err := fsutils.Pid(id) if err != nil { - os.Exit(1) + t.Errorf("unexpected error: %v", err) + } + + if expectedPid != pid { + t.Errorf("pid does not match: want %d, got %d", expectedPid, pid) + } +} + +func TestPidAlias(t *testing.T) { + expectedPid := 1234 + id := "test-env" + + err := createPidFile(id, expectedPid) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + pid, err := fsutils.Pid(id) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + if expectedPid != pid { + t.Errorf("pid does not match: want %d, got %d", expectedPid, pid) + } +} + +func TestLogFileLocation(t *testing.T) { + instanceId := "id" + instanceDir, err := fsutils.InstanceDir(instanceId) + if err != nil { + t.Errorf("%w", err) + } + + expected := filepath.Join(instanceDir.Dir, fsutils.InstanceLogFile) + lfp, err := fsutils.GetLogFileLocation(instanceId) + if err != nil { + t.Errorf("%w", err) + } + + if lfp != expected { + t.Errorf("expected: %s; got: %s", expected, lfp) + } +} + +func TestRpcAddress(t *testing.T) { + instanceId := "id" + expectedRpcAddress := "127.0.0.1:8181" + instanceDir := filepath.Join(home, ".mole", instanceId) + rpcFileLocation := filepath.Join(instanceDir, "rpc") + + // create RPC address file + os.MkdirAll(instanceDir, 0755) + ioutil.WriteFile(rpcFileLocation, []byte(expectedRpcAddress), 0644) + + rpcAddress, err := fsutils.RpcAddress(instanceId) + if err != nil { + t.Errorf("%s", err) + } + + if expectedRpcAddress != rpcAddress { + t.Errorf("expected: %s; got: %s", expectedRpcAddress, rpcAddress) } +} - os.Setenv("HOME", home) - os.Setenv("USERPROFILE", home) +func TestMain(m *testing.M) { + home, err := setup() + if err != nil { + fmt.Printf("error while loading data for TestShow: %v", err) + os.RemoveAll(home) + os.Exit(1) + } code := m.Run() os.RemoveAll(home) os.Exit(code) + +} + +//setup prepares the system environment to run the tests by: +// 1. Create temp dir and /.mole +// 2. Copy fixtures to /.mole +// 3. Set temp dir as the user testDir dir +func setup() (string, error) { + testDir, err := ioutil.TempDir("", "mole-fsutils") + if err != nil { + return "", fmt.Errorf("error while setting up tests: %v", err) + } + + moleAliasDir := filepath.Join(testDir, ".mole") + /* + err = os.Mkdir(moleAliasDir, 0755) + if err != nil { + return "", fmt.Errorf("error while setting up tests: %v", err) + } + */ + + err = os.Setenv("HOME", testDir) + if err != nil { + return "", fmt.Errorf("error while setting up tests: %v", err) + } + + err = os.Setenv("USERPROFILE", testDir) + if err != nil { + return "", fmt.Errorf("error while setting up tests: %v", err) + } + + home = testDir + + return moleAliasDir, nil +} + +func createPidFile(id string, pid int) error { + dir := filepath.Join(home, ".mole", id) + + err := os.Mkdir(dir, 0755) + if err != nil { + return err + } + + d := []byte(strconv.Itoa(pid)) + err = ioutil.WriteFile(filepath.Join(dir, fsutils.InstancePidFile), d, 0644) + if err != nil { + return err + } + + if err != nil { + return err + } + + return nil } diff --git a/go.mod b/go.mod index d2b0e0c..80ceda1 100644 --- a/go.mod +++ b/go.mod @@ -4,21 +4,21 @@ go 1.14 require ( github.com/BurntSushi/toml v0.3.1 + github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 github.com/awnumar/memguard v0.17.1 github.com/gofrs/uuid v3.2.0+incompatible github.com/hpcloud/tail v1.0.0 github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect github.com/kevinburke/ssh_config v0.0.0-20190630040420-2e50c441276c + github.com/mitchellh/mapstructure v1.4.1 github.com/pelletier/go-buffruneio v0.2.0 // indirect github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 - github.com/prometheus/common v0.10.0 + github.com/sergi/go-diff v1.2.0 // indirect github.com/sevlyar/go-daemon v0.1.5 github.com/sirupsen/logrus v1.6.0 github.com/sourcegraph/jsonrpc2 v0.0.0-20200429184054-15c2290dcb37 github.com/spf13/cobra v0.0.5 github.com/spf13/pflag v1.0.5 - github.com/spf13/viper v1.3.2 - github.com/stretchr/testify v1.3.0 // indirect golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de gopkg.in/fsnotify.v1 v1.4.7 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect diff --git a/go.sum b/go.sum index c3f0318..c69400d 100644 --- a/go.sum +++ b/go.sum @@ -1,145 +1,81 @@ github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4 h1:Hs82Z41s6SdL1CELW+XaDYmOH4hkBN4/N9og/AsOv7E= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/awnumar/memguard v0.17.1 h1:A+LXGWDm55TFXwm8k3S8fy0XqC+2GptdLPGpTBSjjlo= github.com/awnumar/memguard v0.17.1/go.mod h1:s8LpRI3oAAgcbfLEN4lRsDqJoDVVW2J52y8ED5sl8ug= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/cosiner/argv v0.1.0 h1:BVDiEL32lwHukgJKP87btEPenzrrHUjajs/8yzaqcXg= -github.com/cosiner/argv v0.1.0/go.mod h1:EusR6TucWKX+zFgtdUsKT2Cvg45K5rtpCcWz4hK06d8= -github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/go-delve/delve v1.5.0 h1:gQsRvFdR0BGk19NROQZsAv6iG4w5QIZoJlxJeEUBb0c= -github.com/go-delve/delve v1.5.0/go.mod h1:c6b3a1Gry6x8a4LGCe/CWzrocrfaHvkUxCj3k4bvSUQ= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/google/go-dap v0.2.0 h1:whjIGQRumwbR40qRU7CEKuFLmePUUc2s4Nt9DoXXxWk= -github.com/google/go-dap v0.2.0/go.mod h1:5q8aYQFnHOAZEMP+6vmq25HKYAEwE+LF5yh7JKrrhSQ= +github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= -github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= -github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA= github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= github.com/kevinburke/ssh_config v0.0.0-20190630040420-2e50c441276c h1:VAx3LRNjVNvjtgO7KFRuT/3aye/0zJvwn01rHSfoolo= github.com/kevinburke/ssh_config v0.0.0-20190630040420-2e50c441276c/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -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 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/mattn/go-colorable v0.0.0-20170327083344-ded68f7a9561 h1:isR/L+BIZ+rqODWYR/f526ygrBMGKZYFhaaFRDGvuZ8= -github.com/mattn/go-colorable v0.0.0-20170327083344-ded68f7a9561/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI= -github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/pelletier/go-buffruneio v0.2.0 h1:U4t4R6YkofJ5xHm3dJzuRpPZ0mr5MMCoAWooScCR7aA= github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/peterh/liner v0.0.0-20170317030525-88609521dc4b h1:8uaXtUkxiy+T/zdLWuxa/PG4so0TPZDZfafFNNSaptE= -github.com/peterh/liner v0.0.0-20170317030525-88609521dc4b/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc= github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= -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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.10.0 h1:RyRA7RzGXQZiW+tGMr7sxa85G1z0yOpM1qq5c8lNawc= -github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= +github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sevlyar/go-daemon v0.1.5 h1:Zy/6jLbM8CfqJ4x4RPr7MJlSKt90f00kNM1D401C+Qk= github.com/sevlyar/go-daemon v0.1.5/go.mod h1:6dJpPatBT9eUwM5VCw9Bt6CdX9Tk6UWvhW3MebLDRKE= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sourcegraph/jsonrpc2 v0.0.0-20200429184054-15c2290dcb37 h1:marA1XQDC7N870zmSFIoHZpIUduK80USeY0Rkuflgp4= github.com/sourcegraph/jsonrpc2 v0.0.0-20200429184054-15c2290dcb37/go.mod h1:ZafdZgk/axhT1cvZAPOhw+95nz2I/Ra5qMlU4gTRwIo= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v0.0.0-20170417170307-b6cb39589372/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/pflag v0.0.0-20170417173400-9e4c21054fa1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= -go.starlark.net v0.0.0-20190702223751-32f345186213 h1:lkYv5AKwvvduv5XWP6szk/bvvgO6aDeUujhZQXIFTes= -go.starlark.net v0.0.0-20190702223751-32f345186213/go.mod h1:c1/X6cHgvdXj6pUlmWKMkuqRnW4K8x2vwt6JAaaircg= -golang.org/x/arch v0.0.0-20190927153633-4e8777c89be4 h1:QlVATYS7JBoZMVaf+cNjb90WD/beKVHnIxFKT4QaHVI= -golang.org/x/arch v0.0.0-20190927153633-4e8777c89be4/go.mod h1:flIaEI6LNU6xOCD5PaJvn9wGP0agmIOqjrtsKGRguv4= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc= -golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de h1:ikNHVSjEfnvz6sxdSPCaPt572qowuyMDMJLLm3Db3ig= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -147,17 +83,13 @@ golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb h1:fgwFCsaw9buMuxNd6+DQfAuSFqbNiQZpcgJQAgJsK6k= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/tools v0.0.0-20191127201027-ecd32218bd7f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= -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-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/mole/app.go b/mole/app.go index 184142f..b16d418 100644 --- a/mole/app.go +++ b/mole/app.go @@ -1,15 +1,10 @@ package mole import ( - "context" - "encoding/json" "fmt" - "io/ioutil" "os" - "path/filepath" "github.com/davrodpin/mole/fsutils" - "github.com/davrodpin/mole/rpc" "github.com/hpcloud/tail" ) @@ -75,30 +70,3 @@ func ShowLogs(id string, follow bool) error { return nil } - -// Rpc calls a remote procedure on another mole instance given its id or alias. -func Rpc(id, method string, params interface{}) (string, error) { - d, err := fsutils.InstanceDir(id) - if err != nil { - return "", err - } - - rf := filepath.Join(d.Dir, "rpc") - - addr, err := ioutil.ReadFile(rf) - if err != nil { - return "", err - } - - resp, err := rpc.Call(context.Background(), string(addr), method, params) - if err != nil { - return "", err - } - - r, err := json.MarshalIndent(resp, "", " ") - if err != nil { - return "", err - } - - return string(r), nil -} diff --git a/mole/mole.go b/mole/mole.go index 12496b2..35bb8d1 100644 --- a/mole/mole.go +++ b/mole/mole.go @@ -1,6 +1,7 @@ package mole import ( + "context" "fmt" "io/ioutil" "os" @@ -16,29 +17,35 @@ import ( "github.com/awnumar/memguard" "github.com/gofrs/uuid" + "github.com/mitchellh/mapstructure" daemon "github.com/sevlyar/go-daemon" log "github.com/sirupsen/logrus" "golang.org/x/crypto/ssh/terminal" ) +// cli keeps a reference to the latest Client object created. +// This is mostly needed to introspect client states during runtime (e.g. a +// remote procedure call that needs to check certain runtime information) +var cli *Client + type Configuration struct { - Id string - TunnelType string - Verbose bool - Insecure bool - Detach bool - Source alias.AddressInputList - Destination alias.AddressInputList - Server alias.AddressInput - Key string - KeepAliveInterval time.Duration - ConnectionRetries int - WaitAndRetry time.Duration - SshAgent string - Timeout time.Duration - SshConfig string - Rpc bool - RpcAddress string + Id string `json:"id" mapstructure:"id" toml:"id"` + TunnelType string `json:"tunnel-type" mapstructure:"tunnel-type" toml:"tunnel-type"` + Verbose bool `json:"verbose" mapstructure:"verbose" toml:"verbose"` + Insecure bool `json:"insecure" mapstructure:"insecure" toml:"insecure"` + Detach bool `json:"detach" mapstructure:"detach" toml:"detach"` + Source alias.AddressInputList `json:"source" mapstructure:"source" toml:"source"` + Destination alias.AddressInputList `json:"destination" mapstructure:"destination" toml:"destination"` + Server alias.AddressInput `json:"server" mapstructure:"server" toml:"server"` + Key string `json:"key" mapstructure:"key" toml:"key"` + KeepAliveInterval time.Duration `json:"keep-alive-interval" mapstructure:"keep-alive-interva" toml:"keep-alive-interval"` + ConnectionRetries int `json:"connection-retries" mapstructure:"connection-retries" toml:"connection-retries"` + WaitAndRetry time.Duration `json:"wait-and-retry" mapstructure:"wait-and-retry" toml:"wait-and-retry"` + SshAgent string `json:"ssh-agent" mapstructure:"ssh-agent" toml:"ssh-agent"` + Timeout time.Duration `json:"timeout" mapstructure:"timeout" toml:"timeout"` + SshConfig string `json:"ssh-config" mapstructure:"ssh-config" toml:"ssh-config"` + Rpc bool `json:"rpc" mapstructure:"rpc" toml:"rpc"` + RpcAddress string `json:"rpc-address" mapstructure:"rpc-address" toml:"rpc-address"` } // ParseAlias translates a Configuration object to an Alias object. @@ -72,10 +79,12 @@ type Client struct { // New initializes a new mole's client. func New(conf *Configuration) *Client { - return &Client{ + cli = &Client{ Conf: conf, sigs: make(chan os.Signal, 1), } + + return cli } // Start kicks off mole's client, establishing the tunnel and its channels @@ -146,6 +155,8 @@ func (c *Client) Start() error { return err } + c.Conf.RpcAddress = addr.String() + log.Infof("rpc server address saved on %s", rd) } @@ -349,6 +360,49 @@ func (c *Configuration) Merge(al *alias.Alias, givenFlags []string) error { return nil } +// ShowInstances returns the runtime information about all instances of mole +// running on the system with rpc enabled. +func ShowInstances() (*InstancesRuntime, error) { + ctx := context.Background() + data, err := rpc.ShowAll(ctx) + if err != nil { + return nil, err + } + + var instances []Runtime + + err = mapstructure.Decode(data, &instances) + if err != nil { + return nil, err + } + + runtime := InstancesRuntime(instances) + + if len(runtime) == 0 { + return nil, fmt.Errorf("no instances were found.") + } + + return &runtime, nil +} + +// ShowInstance returns the runtime information about an application instance +// from the given id or alias. +func ShowInstance(id string) (*Runtime, error) { + ctx := context.Background() + info, err := rpc.Show(ctx, id) + if err != nil { + return nil, err + } + + var r Runtime + err = mapstructure.Decode(info, &r) + if err != nil { + return nil, err + } + + return &r, nil +} + func startDaemonProcess(instanceConf *DetachedInstance) error { cntxt := &daemon.Context{ PidFileName: fsutils.InstancePidFile, diff --git a/mole/rpc.go b/mole/rpc.go new file mode 100644 index 0000000..65bf20d --- /dev/null +++ b/mole/rpc.go @@ -0,0 +1,59 @@ +package mole + +import ( + "context" + "encoding/json" + "fmt" + "io/ioutil" + "path/filepath" + + "github.com/davrodpin/mole/fsutils" + "github.com/davrodpin/mole/rpc" +) + +func init() { + rpc.Register("show-instance", ShowRpc) +} + +// ShowRpc is a rpc callback that returns runtime information about the mole client. +func ShowRpc(params interface{}) (json.RawMessage, error) { + if cli == nil { + return nil, fmt.Errorf("client configuration could not be found.") + } + + conf := cli.Conf + + cj, err := json.Marshal(conf) + if err != nil { + return nil, err + } + + return json.RawMessage(cj), nil +} + +// Rpc calls a remote procedure on another mole instance given its id or alias. +func Rpc(id, method string, params interface{}) (string, error) { + d, err := fsutils.InstanceDir(id) + if err != nil { + return "", err + } + + rf := filepath.Join(d.Dir, "rpc") + + addr, err := ioutil.ReadFile(rf) + if err != nil { + return "", err + } + + resp, err := rpc.Call(context.Background(), string(addr), method, params) + if err != nil { + return "", err + } + + r, err := json.MarshalIndent(resp, "", " ") + if err != nil { + return "", err + } + + return string(r), nil +} diff --git a/mole/runtime.go b/mole/runtime.go new file mode 100644 index 0000000..814ef43 --- /dev/null +++ b/mole/runtime.go @@ -0,0 +1,64 @@ +package mole + +import ( + "bytes" + "fmt" + + "github.com/BurntSushi/toml" +) + +type Formatter interface { + Format(format string) (string, error) +} + +// Runtime holds runtime data about an application instances. +type Runtime Configuration + +// Format parses a Runtime object into a string representation based on the given +// format (i.e. toml). +func (rt Runtime) Format(format string) (string, error) { + if format == "toml" { + return rt.ToToml() + } else { + return "", fmt.Errorf("unknown %s format", format) + } +} + +func (rt Runtime) ToToml() (string, error) { + var buf bytes.Buffer + e := toml.NewEncoder(&buf) + + if err := e.Encode(rt); err != nil { + return "", err + } + + return buf.String(), nil +} + +type InstancesRuntime []Runtime + +func (ir InstancesRuntime) Format(format string) (string, error) { + if format == "toml" { + return ir.ToToml() + } else { + return "", fmt.Errorf("unknown %s format", format) + } +} + +func (ir InstancesRuntime) ToToml() (string, error) { + rt := make(map[string]map[string]Runtime) + rt["instances"] = make(map[string]Runtime) + + for _, instance := range ir { + rt["instances"][instance.Id] = instance + } + + var buf bytes.Buffer + e := toml.NewEncoder(&buf) + + if err := e.Encode(rt); err != nil { + return "", err + } + + return buf.String(), nil +} diff --git a/mole/runtime_test.go b/mole/runtime_test.go new file mode 100644 index 0000000..d9389dc --- /dev/null +++ b/mole/runtime_test.go @@ -0,0 +1,99 @@ +package mole_test + +import ( + "strings" + "testing" + + "github.com/davrodpin/mole/mole" + + "github.com/andreyvit/diff" +) + +const expectedInstance string = `id = "id1" +tunnel-type = "" +verbose = false +insecure = false +detach = false +key = "" +keep-alive-interval = 0 +connection-retries = 0 +wait-and-retry = 0 +ssh-agent = "" +timeout = 0 +ssh-config = "" +rpc = false +rpc-address = "" + +[server] + user = "" + host = "" + port = ""` + +const expectedMultipleInstances string = `[instances] + [instances.id1] + id = "id1" + tunnel-type = "" + verbose = false + insecure = false + detach = false + key = "" + keep-alive-interval = 0 + connection-retries = 0 + wait-and-retry = 0 + ssh-agent = "" + timeout = 0 + ssh-config = "" + rpc = false + rpc-address = "" + [instances.id1.server] + user = "" + host = "" + port = "" + [instances.id2] + id = "id2" + tunnel-type = "" + verbose = false + insecure = false + detach = false + key = "" + keep-alive-interval = 0 + connection-retries = 0 + wait-and-retry = 0 + ssh-agent = "" + timeout = 0 + ssh-config = "" + rpc = false + rpc-address = "" + [instances.id2.server] + user = "" + host = "" + port = ""` + +func TestFormatRuntimeToML(t *testing.T) { + instances := []mole.Runtime{ + mole.Runtime{Id: "id1"}, + mole.Runtime{Id: "id2"}, + } + + runtimes := mole.InstancesRuntime(instances) + + tests := []struct { + formatter mole.Formatter + expected string + }{ + {formatter: mole.Runtime{Id: "id1"}, expected: expectedInstance}, + {formatter: runtimes, expected: expectedMultipleInstances}, + } + + for _, test := range tests { + out, err := test.formatter.Format("toml") + + if err != nil { + t.Errorf(err.Error()) + } + + if a, e := strings.TrimSpace(out), strings.TrimSpace(test.expected); a != e { + t.Errorf("Result not as expected:\n%v", diff.LineDiff(e, a)) + } + } +} diff --git a/rpc/client.go b/rpc/client.go new file mode 100644 index 0000000..7cc3063 --- /dev/null +++ b/rpc/client.go @@ -0,0 +1,115 @@ +package rpc + +import ( + "context" + "net" + "os" + "path/filepath" + + "github.com/davrodpin/mole/fsutils" + + log "github.com/sirupsen/logrus" + "github.com/sourcegraph/jsonrpc2" +) + +var ( + InstancePidFile = "pid" +) + +// Show returns runtime information about a mole instance, given its +// alias or id. +func Show(context context.Context, id string) (map[string]interface{}, error) { + resp, err := CallById(context, id, "show-instance", nil) + if err != nil { + return nil, err + } + + return resp, nil +} + +// CallById returns the response of a remote procedure call made against +// another mole instance, given its id or alias. +func CallById(context context.Context, id, method string, params interface{}) (map[string]interface{}, error) { + addr, err := fsutils.RpcAddress(id) + if err != nil { + return nil, err + } + + resp, err := Call(context, addr, method, params) + if err != nil { + return nil, err + } + + return resp, nil +} + +// Call initiates a JSON-RPC call to a given rpc server address, using the +// specified method and waits for the response. +func Call(ctx context.Context, addr, method string, params interface{}) (map[string]interface{}, error) { + tc, err := net.Dial("tcp", addr) + if err != nil { + return nil, err + } + + stream := jsonrpc2.NewBufferedStream(tc, jsonrpc2.VarintObjectCodec{}) + h := &Handler{} + conn := jsonrpc2.NewConn(ctx, stream, h) + + var r map[string]interface{} + err = conn.Call(ctx, method, params, &r) + if err != nil { + return nil, err + } + + return r, nil +} + +// ShowAll returns runtime information about all instaces of mole running on +// the system. +func ShowAll(context context.Context) ([]map[string]interface{}, error) { + var instances []map[string]interface{} + + home, err := fsutils.Dir() + if err != nil { + return nil, err + } + + err = filepath.Walk(home, func(path string, info os.FileInfo, err error) error { + if info.IsDir() { + id := filepath.Base(path) + + if id == "." { + return nil + } + + addr, err := fsutils.RpcAddress(id) + if err != nil { + log.WithFields(log.Fields{ + "rpc": "enabled", + "id": id, + }).WithError(err).Debugf("rpc failed") + + return nil + } + + resp, err := Call(context, addr, "show-instance", nil) + if err != nil { + log.WithFields(log.Fields{ + "rpc": "enabled", + "id": id, + }).WithError(err).Debugf("rpc failed") + + return nil + } + + instances = append(instances, resp) + } + + return nil + }) + if err != nil { + return nil, err + } + + return instances, nil +} diff --git a/rpc/rpc.go b/rpc/rpc.go index 0b29cbc..43a0b1c 100644 --- a/rpc/rpc.go +++ b/rpc/rpc.go @@ -135,27 +135,6 @@ func Register(name string, method Method) { // Method represents a procedure that can be called remotely. type Method func(params interface{}) (json.RawMessage, error) -// Call initiates a JSON-RPC call to a given rpc server address, using the -// specified method and waits for the response. -func Call(ctx context.Context, addr, method string, params interface{}) (map[string]interface{}, error) { - tc, err := net.Dial("tcp", addr) - if err != nil { - return nil, err - } - - stream := jsonrpc2.NewBufferedStream(tc, jsonrpc2.VarintObjectCodec{}) - h := &Handler{} - conn := jsonrpc2.NewConn(ctx, stream, h) - - var r map[string]interface{} - err = conn.Call(ctx, method, params, &r) - if err != nil { - return nil, err - } - - return r, nil -} - func sendResponse(ctx context.Context, conn *jsonrpc2.Conn, req *jsonrpc2.Request, resp *jsonrpc2.Response) error { if err := conn.SendResponse(ctx, resp); err != nil { log.WithFields(log.Fields{ From d6c2076a5f014362b12021c2fa14f3d7d99bf7db Mon Sep 17 00:00:00 2001 From: David Pinheiro Date: Wed, 1 Sep 2021 11:11:02 -0700 Subject: [PATCH 08/18] Move AddressInput to mole package --- {alias => mole}/address_input.go | 2 +- {alias => mole}/address_input_test.go | 10 +++---- mole/mole.go | 40 +++++++++++++-------------- mole/runtime.go | 2 +- 4 files changed, 27 insertions(+), 27 deletions(-) rename {alias => mole}/address_input.go (99%) rename {alias => mole}/address_input_test.go (88%) diff --git a/alias/address_input.go b/mole/address_input.go similarity index 99% rename from alias/address_input.go rename to mole/address_input.go index 1cb60b0..b45e151 100644 --- a/alias/address_input.go +++ b/mole/address_input.go @@ -1,4 +1,4 @@ -package alias +package mole import ( "fmt" diff --git a/alias/address_input_test.go b/mole/address_input_test.go similarity index 88% rename from alias/address_input_test.go rename to mole/address_input_test.go index d7d5ec2..b21998e 100644 --- a/alias/address_input_test.go +++ b/mole/address_input_test.go @@ -1,10 +1,10 @@ -package alias_test +package mole_test import ( "fmt" "testing" - "github.com/davrodpin/mole/alias" + "github.com/davrodpin/mole/mole" ) func TestAddressInputSet(t *testing.T) { @@ -20,7 +20,7 @@ func TestAddressInputSet(t *testing.T) { }, } - var ai alias.AddressInput + var ai mole.AddressInput for id, test := range tests { ai.Set(fmt.Sprintf("%s@%s:%s", test.user, test.host, test.port)) @@ -50,7 +50,7 @@ func TestAddressInputListSet(t *testing.T) { } for id, test := range tests { - aiList := alias.AddressInputList{} + aiList := mole.AddressInputList{} aiList.Set(test.ai) if test.ai != aiList.String() { @@ -79,7 +79,7 @@ func TestAddressInputAddress(t *testing.T) { } for id, test := range tests { - ai := alias.AddressInput{Host: test.host, Port: test.port} + ai := mole.AddressInput{Host: test.host, Port: test.port} addr := ai.Address() if test.expected != addr { diff --git a/mole/mole.go b/mole/mole.go index 35bb8d1..2e5b7b5 100644 --- a/mole/mole.go +++ b/mole/mole.go @@ -29,23 +29,23 @@ import ( var cli *Client type Configuration struct { - Id string `json:"id" mapstructure:"id" toml:"id"` - TunnelType string `json:"tunnel-type" mapstructure:"tunnel-type" toml:"tunnel-type"` - Verbose bool `json:"verbose" mapstructure:"verbose" toml:"verbose"` - Insecure bool `json:"insecure" mapstructure:"insecure" toml:"insecure"` - Detach bool `json:"detach" mapstructure:"detach" toml:"detach"` - Source alias.AddressInputList `json:"source" mapstructure:"source" toml:"source"` - Destination alias.AddressInputList `json:"destination" mapstructure:"destination" toml:"destination"` - Server alias.AddressInput `json:"server" mapstructure:"server" toml:"server"` - Key string `json:"key" mapstructure:"key" toml:"key"` - KeepAliveInterval time.Duration `json:"keep-alive-interval" mapstructure:"keep-alive-interva" toml:"keep-alive-interval"` - ConnectionRetries int `json:"connection-retries" mapstructure:"connection-retries" toml:"connection-retries"` - WaitAndRetry time.Duration `json:"wait-and-retry" mapstructure:"wait-and-retry" toml:"wait-and-retry"` - SshAgent string `json:"ssh-agent" mapstructure:"ssh-agent" toml:"ssh-agent"` - Timeout time.Duration `json:"timeout" mapstructure:"timeout" toml:"timeout"` - SshConfig string `json:"ssh-config" mapstructure:"ssh-config" toml:"ssh-config"` - Rpc bool `json:"rpc" mapstructure:"rpc" toml:"rpc"` - RpcAddress string `json:"rpc-address" mapstructure:"rpc-address" toml:"rpc-address"` + Id string `json:"id" mapstructure:"id" toml:"id"` + TunnelType string `json:"tunnel-type" mapstructure:"tunnel-type" toml:"tunnel-type"` + Verbose bool `json:"verbose" mapstructure:"verbose" toml:"verbose"` + Insecure bool `json:"insecure" mapstructure:"insecure" toml:"insecure"` + Detach bool `json:"detach" mapstructure:"detach" toml:"detach"` + Source AddressInputList `json:"source" mapstructure:"source" toml:"source"` + Destination AddressInputList `json:"destination" mapstructure:"destination" toml:"destination"` + Server AddressInput `json:"server" mapstructure:"server" toml:"server"` + Key string `json:"key" mapstructure:"key" toml:"key"` + KeepAliveInterval time.Duration `json:"keep-alive-interval" mapstructure:"keep-alive-interva" toml:"keep-alive-interval"` + ConnectionRetries int `json:"connection-retries" mapstructure:"connection-retries" toml:"connection-retries"` + WaitAndRetry time.Duration `json:"wait-and-retry" mapstructure:"wait-and-retry" toml:"wait-and-retry"` + SshAgent string `json:"ssh-agent" mapstructure:"ssh-agent" toml:"ssh-agent"` + Timeout time.Duration `json:"timeout" mapstructure:"timeout" toml:"timeout"` + SshConfig string `json:"ssh-config" mapstructure:"ssh-config" toml:"ssh-config"` + Rpc bool `json:"rpc" mapstructure:"rpc" toml:"rpc"` + RpcAddress string `json:"rpc-address" mapstructure:"rpc-address" toml:"rpc-address"` } // ParseAlias translates a Configuration object to an Alias object. @@ -302,7 +302,7 @@ func (c *Configuration) Merge(al *alias.Alias, givenFlags []string) error { c.Id = al.Name c.TunnelType = al.TunnelType - srcl := alias.AddressInputList{} + srcl := AddressInputList{} for _, src := range al.Source { err := srcl.Set(src) if err != nil { @@ -311,7 +311,7 @@ func (c *Configuration) Merge(al *alias.Alias, givenFlags []string) error { } c.Source = srcl - dstl := alias.AddressInputList{} + dstl := AddressInputList{} for _, dst := range al.Destination { err := dstl.Set(dst) if err != nil { @@ -320,7 +320,7 @@ func (c *Configuration) Merge(al *alias.Alias, givenFlags []string) error { } c.Destination = dstl - srv := alias.AddressInput{} + srv := AddressInput{} err := srv.Set(al.Server) if err != nil { return err diff --git a/mole/runtime.go b/mole/runtime.go index 814ef43..9b26315 100644 --- a/mole/runtime.go +++ b/mole/runtime.go @@ -11,7 +11,7 @@ type Formatter interface { Format(format string) (string, error) } -// Runtime holds runtime data about an application instances. +// Runtime holds runtime data about an application instance. type Runtime Configuration // Format parses a Runtime object into a string representation based on the given From b28b44804ebf29bff011a8eed9441a72f7e99f38 Mon Sep 17 00:00:00 2001 From: David Pinheiro Date: Wed, 1 Sep 2021 11:06:18 -0700 Subject: [PATCH 09/18] Return resolved addresses This change makes the runtime information to return the addresses used by the ssh channels instead of the input. --- mole/mole.go | 120 +++++++++++++++++++++++++++-------------------- mole/rpc.go | 4 +- mole/runtime.go | 19 ++++++++ tunnel/tunnel.go | 12 +++++ 4 files changed, 101 insertions(+), 54 deletions(-) diff --git a/mole/mole.go b/mole/mole.go index 2e5b7b5..9ff1656 100644 --- a/mole/mole.go +++ b/mole/mole.go @@ -73,8 +73,9 @@ func (c Configuration) ParseAlias(name string) *alias.Alias { // Client manages the overall state of the application based on its configuration. type Client struct { - Conf *Configuration - sigs chan os.Signal + Conf *Configuration + Tunnel *tunnel.Tunnel + sigs chan os.Signal } // New initializes a new mole's client. @@ -160,63 +161,20 @@ func (c *Client) Start() error { log.Infof("rpc server address saved on %s", rd) } - s, err := tunnel.NewServer(c.Conf.Server.User, c.Conf.Server.Address(), c.Conf.Key, c.Conf.SshAgent, c.Conf.SshConfig) + t, err := createTunnel(c.Conf) if err != nil { - log.Errorf("error processing server options: %v\n", err) - return err - } - - s.Insecure = c.Conf.Insecure - s.Timeout = c.Conf.Timeout - - err = s.Key.HandlePassphrase(func() ([]byte, error) { - fmt.Printf("The key provided is secured by a password. Please provide it below:\n") - fmt.Printf("Password: ") - p, err := terminal.ReadPassword(int(syscall.Stdin)) - fmt.Printf("\n") - return p, err - }) - - if err != nil { - log.WithError(err).Error("error setting up password handling function") - return err - } - - log.Debugf("server: %s", s) - - source := make([]string, len(c.Conf.Source)) - for i, r := range c.Conf.Source { - source[i] = r.String() - } - - destination := make([]string, len(c.Conf.Destination)) - for i, r := range c.Conf.Destination { - if r.Port == "" { - log.WithError(err).Errorf("missing port in destination address: %s", r.String()) - return err - } - - destination[i] = r.String() - } + log.WithFields(log.Fields{ + "id": c.Conf.Id, + }).WithError(err).Error("error creating tunnel") - t, err := tunnel.New(c.Conf.TunnelType, s, source, destination, c.Conf.SshConfig) - if err != nil { - log.Error(err) return err } - //TODO need to find a way to require the attributes below to be always set - // since they are not optional (functionality will break if they are not - // set and CLI parsing is the one setting the default values). - // That could be done by make them required in the constructor's signature or - // by creating a configuration struct for a tunnel object. - t.ConnectionRetries = c.Conf.ConnectionRetries - t.WaitAndRetry = c.Conf.WaitAndRetry - t.KeepAliveInterval = c.Conf.KeepAliveInterval + c.Tunnel = t - if err = t.Start(); err != nil { + if err = c.Tunnel.Start(); err != nil { log.WithFields(log.Fields{ - "tunnel": t.String(), + "tunnel": c.Tunnel.String(), }).WithError(err).Error("error while starting tunnel") return err @@ -450,3 +408,61 @@ func (fs flags) lookup(flag string) bool { return false } + +func createTunnel(conf *Configuration) (*tunnel.Tunnel, error) { + s, err := tunnel.NewServer(conf.Server.User, conf.Server.Address(), conf.Key, conf.SshAgent, conf.SshConfig) + if err != nil { + log.Errorf("error processing server options: %v\n", err) + return nil, err + } + + s.Insecure = conf.Insecure + s.Timeout = conf.Timeout + + err = s.Key.HandlePassphrase(func() ([]byte, error) { + fmt.Printf("The key provided is secured by a password. Please provide it below:\n") + fmt.Printf("Password: ") + p, err := terminal.ReadPassword(int(syscall.Stdin)) + fmt.Printf("\n") + return p, err + }) + + if err != nil { + log.WithError(err).Error("error setting up password handling function") + return nil, err + } + + log.Debugf("server: %s", s) + + source := make([]string, len(conf.Source)) + for i, r := range conf.Source { + source[i] = r.String() + } + + destination := make([]string, len(conf.Destination)) + for i, r := range conf.Destination { + if r.Port == "" { + log.WithError(err).Errorf("missing port in destination address: %s", r.String()) + return nil, err + } + + destination[i] = r.String() + } + + t, err := tunnel.New(conf.TunnelType, s, source, destination, conf.SshConfig) + if err != nil { + log.Error(err) + return nil, err + } + + //TODO need to find a way to require the attributes below to be always set + // since they are not optional (functionality will break if they are not + // set and CLI parsing is the one setting the default values). + // That could be done by make them required in the constructor's signature or + // by creating a configuration struct for a tunnel object. + t.ConnectionRetries = conf.ConnectionRetries + t.WaitAndRetry = conf.WaitAndRetry + t.KeepAliveInterval = conf.KeepAliveInterval + + return t, nil +} diff --git a/mole/rpc.go b/mole/rpc.go index 65bf20d..5e5b1c6 100644 --- a/mole/rpc.go +++ b/mole/rpc.go @@ -21,9 +21,9 @@ func ShowRpc(params interface{}) (json.RawMessage, error) { return nil, fmt.Errorf("client configuration could not be found.") } - conf := cli.Conf + runtime := cli.Runtime() - cj, err := json.Marshal(conf) + cj, err := json.Marshal(runtime) if err != nil { return nil, err } diff --git a/mole/runtime.go b/mole/runtime.go index 9b26315..384d6af 100644 --- a/mole/runtime.go +++ b/mole/runtime.go @@ -62,3 +62,22 @@ func (ir InstancesRuntime) ToToml() (string, error) { return buf.String(), nil } + +func (c *Client) Runtime() *Runtime { + runtime := Runtime(*c.Conf) + + if c.Tunnel != nil { + source := &AddressInputList{} + destination := &AddressInputList{} + + for _, channel := range c.Tunnel.Channels() { + source.Set(channel.Source) + destination.Set(channel.Destination) + } + + runtime.Source = *source + runtime.Destination = *destination + } + + return &runtime +} diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index d95401c..544c30b 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -451,6 +451,18 @@ func (t *Tunnel) keepAlive() { } } +// Channels returns a copy of all channels configured for the tunnel. +func (t *Tunnel) Channels() []*SSHChannel { + channels := make([]*SSHChannel, len(t.channels)) + + for i, c := range t.channels { + cc := *c + channels[i] = &cc + } + + return channels +} + func sshClientConfig(server Server) (*ssh.ClientConfig, error) { var signers []ssh.Signer From 9b9782cbdb3be24b901f575336ab010c4e210d95 Mon Sep 17 00:00:00 2001 From: David Pinheiro Date: Tue, 14 Sep 2021 10:38:48 -0700 Subject: [PATCH 10/18] New CI workflow --- .github/workflows/main.yml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..e842459 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,28 @@ +name: CI + +on: + push: + branches: [ $default-branch ] + pull_request: + branches: [ $default-branch ] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: 1.17 + + - name: Build + run: make build + + - name: Test + run: make test From e3fa24795742b76cba1f43d5123b733a82f45448 Mon Sep 17 00:00:00 2001 From: David Pinheiro Date: Tue, 14 Sep 2021 10:43:17 -0700 Subject: [PATCH 11/18] Removing old CI workflow --- .github/actions/check/Dockerfile | 18 ----- .github/actions/check/README.md | 27 ------- .github/actions/check/cov-diff.html.tpl | 54 -------------- .github/actions/check/cov-diff.txt.tpl | 11 --- .github/actions/check/entrypoint.sh | 95 ------------------------- .github/workflows/main.yml | 5 +- .github/workflows/push.yml | 12 ---- 7 files changed, 1 insertion(+), 221 deletions(-) delete mode 100644 .github/actions/check/Dockerfile delete mode 100644 .github/actions/check/README.md delete mode 100644 .github/actions/check/cov-diff.html.tpl delete mode 100644 .github/actions/check/cov-diff.txt.tpl delete mode 100755 .github/actions/check/entrypoint.sh delete mode 100644 .github/workflows/push.yml diff --git a/.github/actions/check/Dockerfile b/.github/actions/check/Dockerfile deleted file mode 100644 index eec7568..0000000 --- a/.github/actions/check/Dockerfile +++ /dev/null @@ -1,18 +0,0 @@ -FROM golang:1.14.7 - -LABEL "com.github.actions.name"="action-check" -LABEL "com.github.actions.description"="Code quality check for Mole" -LABEL "com.github.actions.icon"="award" -LABEL "com.github.actions.color"="orange" - -LABEL "repository"="http://github.com/davrodpin/mole" -LABEL "homepage"="https://davrodpin.github.io/mole/" -LABEL "maintainer"="David Pinheiro " - -RUN apt-get update && apt install -y curl jq bc - -ADD cov-diff.html.tpl /cov-diff.html.tpl -ADD cov-diff.txt.tpl /cov-diff.txt.tpl -ADD entrypoint.sh /entrypoint.sh - -ENTRYPOINT ["/entrypoint.sh"] diff --git a/.github/actions/check/README.md b/.github/actions/check/README.md deleted file mode 100644 index 4e553ba..0000000 --- a/.github/actions/check/README.md +++ /dev/null @@ -1,27 +0,0 @@ -# Mole Quality Checker - -Github Action to report if a given change meets the project quality standards. - -This action generates the following reports: - - * Files that have formatting issues - * Code Coverage for the target commit - * Code Coverage difference between the target commit and its predecessor to - inform if the coverage has increased or decreased - -## Environment Variables - -N/A - -## Secrets - - * `DROPBOX_TOKEN`: Authentication token allow reports to be uploaded to - Dropbox. - -## Required Arguments - -N/A - -## Optional Arguments - -N/A diff --git a/.github/actions/check/cov-diff.html.tpl b/.github/actions/check/cov-diff.html.tpl deleted file mode 100644 index cd6d333..0000000 --- a/.github/actions/check/cov-diff.html.tpl +++ /dev/null @@ -1,54 +0,0 @@ - - - {{.mole.Title}} - - - - - - - -
- -
- -
-
-
-

Code coverage comparison between {{.mole.Previous_Commit}} and {{.mole.Current_Commit}}

-
-
-
-
- - - - - - - - - - - {{- range $file := .mole.Files}} - - - - - {{- if (eq $file.Diff 0.0)}} - - {{- else if (gt $file.Diff 0.0)}} - - {{- else}} - - {{- end}} - - {{- end}} - -
FilePrevious Coverage ({{.mole.Previous_Commit}})Current Coverage ({{.mole.Current_Commit}})Delta
{{$file.File}}{{printf "%.2f" $file.Previous_Coverage}}%{{printf "%.2f" $file.Current_Coverage}}%{{$file.Diff}}%+{{$file.Diff}}%{{$file.Diff}}%
-
-
-
- - - diff --git a/.github/actions/check/cov-diff.txt.tpl b/.github/actions/check/cov-diff.txt.tpl deleted file mode 100644 index c093abc..0000000 --- a/.github/actions/check/cov-diff.txt.tpl +++ /dev/null @@ -1,11 +0,0 @@ -{{.mole.Title}} - -Current Commit : {{.mole.Current_Commit}} -Previous Commit: {{.mole.Previous_Commit}} -{{ range $file := .mole.Files}} -File: {{$file.File}} - Previous Coverage: {{$file.Previous_Coverage}}% - Current Coverage : {{$file.Current_Coverage}}% - Diff : {{$file.Diff}}% -{{ end}} - diff --git a/.github/actions/check/entrypoint.sh b/.github/actions/check/entrypoint.sh deleted file mode 100755 index 0f82a71..0000000 --- a/.github/actions/check/entrypoint.sh +++ /dev/null @@ -1,95 +0,0 @@ -#!/bin/sh -l - -export GOPATH=/go - -GO="/usr/local/go/bin/go" -GOMPLATE="/usr/local/bin/gomplate" -MOLE_SRC_PATH=${GOPATH}/src/github.com/${GITHUB_REPOSITORY} -COV_PROFILE=${GITHUB_WORKSPACE}/coverage.txt -COV_REPORT=${GITHUB_WORKSPACE}/mole-coverage.html -COV_DIFF_DATA=${GITHUB_WORKSPACE}/cov-diff.json -COV_DIFF_HTML_REPORT_TPL=/cov-diff.html.tpl -COV_DIFF_TXT_REPORT_TPL=/cov-diff.txt.tpl -COV_DIFF_REPORT=${GITHUB_WORKSPACE}/mole-diff-coverage.html -COV_DIFF_DATA_TPL='{ "Title": "{{TITLE}}", "Previous_Commit": "{{PREV_COMMIT}}", "Current_Commit": "{{CURR_COMMIT}}", "Created_At": "{{CREATED_AT}}", "Files": [{{ROWS}}] }' -COV_DIFF_DATA_ROW_TPL='{ "File": "{{FILE}}", "Previous_Coverage": {{PREV}}, "Current_Coverage": {{CUR}}, "Diff": {{DIFF}} }' - -log() { - level="$1" - message="$2" - - [ -z "$level" ] || [ -z "$message" ] && return 1 - - printf "`date +%Y-%m-%dT%H:%M:%S%z`\t%s\t%s\n" "${level}" "${message}" - - return 0 -} - -mole_wksp() { - log "info" "Creating Go workspace at ${GOPATH}" - - mkdir -p ${MOLE_SRC_PATH} && \ - cp -a $GITHUB_WORKSPACE/* ${MOLE_SRC_PATH} || return 1 - - return 0 -} - -download_report() { - commit="$1" - - [ -z "$commit" ] && return 1 - - resp=`curl --silent --show-error -X POST https://content.dropboxapi.com/2/files/download \ - --header "Authorization: Bearer ${DROPBOX_TOKEN}" \ - --header "Dropbox-API-Arg: {\"path\": \"/reports/${commit}/mole-coverage.html\"}"` - - error=`printf "%s\n" "$resp" | jq 'select(.error != null) | .error' 2> /dev/null` - [ -n "$error" ] && { - log "debug" "${resp}" - return 1 - } - - printf "%s\n" "${resp}" - - return 0 -} - -mole_test() { - prev_commit_id=`jq '.before' ${GITHUB_EVENT_PATH} | sed 's/"//g' | cut -c-7` - commit_id=`jq '.after' ${GITHUB_EVENT_PATH} | sed 's/"//g' | cut -c-7` - - mole_wksp || return 1 - - ## TEST - - log "info" "running mole's tests and generating coverage profile for ${commit_id}" - $GO test github.com/${GITHUB_REPOSITORY}/... -v -race -coverprofile=${COV_PROFILE} -covermode=atomic || return 1 - - $GO tool cover -html=${COV_PROFILE} -o ${COV_REPORT} || { - log "error" "error generating coverage report" - return 1 - } - - log "info" "looking for code formatting issues on mole@${commit_id}" || return 1 - fmt=`$GO fmt github.com/${GITHUB_REPOSITORY}/... | sed 's/\n/ /p'` - retcode=$? - - if [ -n "$fmt" ] - then - log "error" "the following files do not follow the Go formatting convention: ${fmt}" - return ${retcode} - else - log "info" "all source code files are following the formatting Go convention" - fi - - #TODO publish list of files failing on go-fmt - - #TODO Use $GITHUB_REF to post comment back to PR - #TODO warning if code coverage decreases - - return 0 -} - -mole_test -#TODO return 1 if error and 78 if check fails - diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e842459..26a4486 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -6,16 +6,13 @@ on: pull_request: branches: [ $default-branch ] - # Allows you to run this workflow manually from the Actions tab - workflow_dispatch: - jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - + - name: Set up Go uses: actions/setup-go@v2 with: diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml deleted file mode 100644 index 982bdae..0000000 --- a/.github/workflows/push.yml +++ /dev/null @@ -1,12 +0,0 @@ -on: [push,pull_request] -name: Mole Code Quality Checks -jobs: - checkCodeQuality: - name: Check Code Quality - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@master - - name: Check Code Quality - uses: ./.github/actions/check - env: - DROPBOX_TOKEN: ${{ secrets.DROPBOX_TOKEN }} From 095c1ff2492456ae3634c55fa8fb9bf7a307dde6 Mon Sep 17 00:00:00 2001 From: David Pinheiro Date: Tue, 14 Sep 2021 11:15:45 -0700 Subject: [PATCH 12/18] Add properties file to main CI workflow --- .github/workflows/properties/main.properties.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .github/workflows/properties/main.properties.json diff --git a/.github/workflows/properties/main.properties.json b/.github/workflows/properties/main.properties.json new file mode 100644 index 0000000..7306651 --- /dev/null +++ b/.github/workflows/properties/main.properties.json @@ -0,0 +1,6 @@ +{ + "name": "Main", + "description": "Build and test Mole project", + "iconName": "go", + "categories": ["Go"] +} From 2c1f7913990b329353c527c6879e4928458bc921 Mon Sep 17 00:00:00 2001 From: David Pinheiro Date: Tue, 14 Sep 2021 11:28:03 -0700 Subject: [PATCH 13/18] Update CI --- .github/workflows/main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 26a4486..ce5b24d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -2,9 +2,9 @@ name: CI on: push: - branches: [ $default-branch ] + branches: [ master ] pull_request: - branches: [ $default-branch ] + branches: [ master ] jobs: build: From 33dfe0bfaef188d3b7bb6a31a6a2a44260ade6f9 Mon Sep 17 00:00:00 2001 From: David Pinheiro Date: Tue, 14 Sep 2021 11:31:32 -0700 Subject: [PATCH 14/18] Update CI --- .github/workflows/main.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ce5b24d..18f38e0 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,8 +1,6 @@ name: CI on: - push: - branches: [ master ] pull_request: branches: [ master ] From bb7755f1b1f2b131007eba5657c176cb78cca984 Mon Sep 17 00:00:00 2001 From: David Pinheiro Date: Tue, 14 Sep 2021 11:32:10 -0700 Subject: [PATCH 15/18] Update CI --- .github/workflows/codeql-analysis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index dcd9461..e0d80d9 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -1,8 +1,6 @@ name: "CodeQL" on: - push: - branches: [master] pull_request: # The branches below must be a subset of the branches above branches: [master] From 3a8f223f5b659fecb90ba3dc182cb7e9fcb3b2bd Mon Sep 17 00:00:00 2001 From: David Pinheiro Date: Tue, 14 Sep 2021 11:33:38 -0700 Subject: [PATCH 16/18] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 2e847ce..f9cfcdb 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +[![CI](https://github.com/davrodpin/mole/actions/workflows/main.yml/badge.svg)](https://github.com/davrodpin/mole/actions/workflows/main.yml) [![Documentation](https://godoc.org/github.com/davrodpin/mole?status.svg)](http://godoc.org/github.com/davrodpin/mole) # [Mole](https://davrodpin.github.io/mole/) From 515e8c1ec1a5ac37c4266b1a8b1f83bb43bb2586 Mon Sep 17 00:00:00 2001 From: David Pinheiro Date: Tue, 14 Sep 2021 12:08:23 -0700 Subject: [PATCH 17/18] Add CI when code is pushed to master --- .github/workflows/ci-master.yml | 23 +++++++++++++++++++ .github/workflows/{main.yml => ci-pr.yml} | 4 ++-- .github/workflows/codeql-analysis.yml | 2 ++ .../workflows/properties/main.properties.json | 6 ----- README.md | 2 +- 5 files changed, 28 insertions(+), 9 deletions(-) create mode 100644 .github/workflows/ci-master.yml rename .github/workflows/{main.yml => ci-pr.yml} (93%) delete mode 100644 .github/workflows/properties/main.properties.json diff --git a/.github/workflows/ci-master.yml b/.github/workflows/ci-master.yml new file mode 100644 index 0000000..ca082ef --- /dev/null +++ b/.github/workflows/ci-master.yml @@ -0,0 +1,23 @@ +name: CI Master + +on: + push: + branches: [ master ] + +jobs: + check: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: 1.17 + + - name: Build + run: make build + + - name: Test + run: make test diff --git a/.github/workflows/main.yml b/.github/workflows/ci-pr.yml similarity index 93% rename from .github/workflows/main.yml rename to .github/workflows/ci-pr.yml index 18f38e0..90252ad 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/ci-pr.yml @@ -1,11 +1,11 @@ -name: CI +name: CI PR on: pull_request: branches: [ master ] jobs: - build: + check: runs-on: ubuntu-latest steps: diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index e0d80d9..dcd9461 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -1,6 +1,8 @@ name: "CodeQL" on: + push: + branches: [master] pull_request: # The branches below must be a subset of the branches above branches: [master] diff --git a/.github/workflows/properties/main.properties.json b/.github/workflows/properties/main.properties.json deleted file mode 100644 index 7306651..0000000 --- a/.github/workflows/properties/main.properties.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "Main", - "description": "Build and test Mole project", - "iconName": "go", - "categories": ["Go"] -} diff --git a/README.md b/README.md index f9cfcdb..e2c2bab 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![CI](https://github.com/davrodpin/mole/actions/workflows/main.yml/badge.svg)](https://github.com/davrodpin/mole/actions/workflows/main.yml) +[![CI](https://github.com/davrodpin/mole/actions/workflows/ci-master.yml/badge.svg)](https://github.com/davrodpin/mole/actions/workflows/ci-master.yml) [![Documentation](https://godoc.org/github.com/davrodpin/mole?status.svg)](http://godoc.org/github.com/davrodpin/mole) # [Mole](https://davrodpin.github.io/mole/) From f6864f2431e9719621c95d30bbaacc8d6ac7fcbb Mon Sep 17 00:00:00 2001 From: David Pinheiro Date: Thu, 16 Sep 2021 14:41:07 -0700 Subject: [PATCH 18/18] Execute reconnection on a goroutine This change makes the connection to the ssh server and channels setup to happen in a goroutine when mole is trying to reconnect. The reason for this change is to allow the tunnel to be closed while the attemp to reconnect is still in progress. --- tunnel/tunnel.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 544c30b..2367814 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -248,7 +248,15 @@ func (t *Tunnel) Start() error { log.Debugf("restablishing the tunnel after disconnection: %s", t) - t.connect() + // The reconnecion must happens on a goroutine to support the scenario + // where tunnel.Stop() is called while the tunnel.connect() is getting + // executed. + // + // In an event where tunnel.reconnect receives data from any point of the + // code rather than tunnel.dial(), which is evoked by tunnel.connect() + // this code needs to be updated to make sure tunnel.connect() is not + // schedule in two goroutines at the same time. + go t.connect() } case err := <-t.done: if t.client != nil {