diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c88d875 --- /dev/null +++ b/.gitignore @@ -0,0 +1,28 @@ +# output +/dcip + +# Created by https://www.toptal.com/developers/gitignore/api/go +# Edit at https://www.toptal.com/developers/gitignore?templates=go + +### Go ### +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +### Go Patch ### +/vendor/ +/Godeps/ + +# End of https://www.toptal.com/developers/gitignore/api/go \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..fa13355 --- /dev/null +++ b/README.md @@ -0,0 +1,30 @@ +# 简介 + +通过 ssh 转发容器未暴露的端口到本地进行操作. 比如: 数据库端口转发到本地进行管理之类的. + +# 使用 + +## 获取容器的主机可访问 ip + +获取本机上的容器 ip + +```sh +dcip of main_pg +``` + +获取远程 ssh 主机上的容器 ip + +```sh +dcip of debian@example.host main_pg +``` + +## 端口转发到本地 + +基于命令 `ssh -NT -L 0.0.0.0:5432:172.17.0.5:5432 debian@example.host` + +```sh +# main_pg 可以是容器名或服务名, 但只会选中最新的容器 +dcip export debian@example.host main_pg:5432 0.0.0.0:5432 +# 可省略本地端口以及监听地址, 监听地址默认是 0.0.0.0, 本地端口默认为容器端口 +dcip export debian@example.host main_pg:5432 +``` diff --git a/cmd/dcip/dcip.go b/cmd/dcip/dcip.go new file mode 100644 index 0000000..cd3c8c9 --- /dev/null +++ b/cmd/dcip/dcip.go @@ -0,0 +1,105 @@ +package main + +import ( + "fmt" + "os" + "os/exec" + "strings" + + "github.com/alecthomas/kong" + "github.com/shynome/dcip" +) + +var CLI struct { + Of struct { + Host string `arg name:"[host|name]" passthrough help:"remote host optional or just a container name"` + Container string `arg name:"name" optional help:"container name"` + } `cmd help:"get container ip."` + Export struct { + Host string `arg name:"host" passthrough help:"ssh host. example: debian@example.host"` + ContainerPort string `arg name:"cport" help:"remote container name and port. example: pg:5432"` + LocalAddr string `arg name:"lport" optional help:"local bind address and port. default bind address is 0.0.0.0, default port is remote container port. example: 127.0.0.1:5432 or 5432"` + } `cmd help:"export remote container port to local host."` + Debug bool `name:"debug" short:"D" optional` + Version kong.VersionFlag `short:"V"` +} + +func reportError(err error) { + os.Stderr.WriteString(err.Error()) + os.Stderr.WriteString("\r\n") + os.Exit(1) +} + +func main() { + ctx := kong.Parse(&CLI, + kong.Vars{"version": "0.1.0"}, + ) + switch ctx.Command() { + case "of <[host|name]>": + fallthrough + case "of <[host|name]> ": + params := CLI.Of + if params.Container == "" { + params.Container = params.Host + params.Host = "" + } + cmdStr := dcip.MakeGetContainerIPCmd(params.Container) + var cmd *exec.Cmd + if params.Host == "" { + cmd = RunCommand(cmdStr) + } else { + cmd = RunSSHCommand(params.Host, cmdStr) + } + PrintCmd(cmd) + result, err := cmd.CombinedOutput() + if err != nil { + reportError(err) + return + } + fmt.Print(string(result)) + case "export ": + fallthrough + case "export ": + params := CLI.Export + var container string + var cport string + var lbind string = "0.0.0.0" + var lport string + ContainerPortArr := strings.Split(params.ContainerPort, ":") + container = ContainerPortArr[0] + cport = ContainerPortArr[1] + if container == "" || cport == "" { + reportError(fmt.Errorf("container name and port is required")) + return + } + localAddrArr := strings.Split(params.LocalAddr, ":") + if params.LocalAddr == "" { + lport = cport + } else if len(localAddrArr) == 1 { + lport = localAddrArr[0] + } else { + lbind = localAddrArr[0] + lport = localAddrArr[1] + } + getIPCmd := RunSSHCommand(params.Host, dcip.MakeGetContainerIPCmd(container)) + PrintCmd(getIPCmd) + cipBytes, err := getIPCmd.CombinedOutput() + cip := strings.Replace(string(cipBytes), "\n", "", 1) + if err != nil { + reportError(err) + return + } + cmdStr := dcip.MakeForwardPortCmd(params.Host, string(cip)+":"+cport, lbind+":"+lport) + cmd := RunSSHCommand(cmdStr, "") + cmd.Stdin = os.Stdin + cmd.Stderr = os.Stderr + cmd.Stdout = os.Stdout + PrintCmd(cmd) + if err := cmd.Run(); err != nil { + reportError(err) + return + } + default: + panic(ctx.Command()) + } +} diff --git a/cmd/dcip/util.go b/cmd/dcip/util.go new file mode 100644 index 0000000..e1c7bd2 --- /dev/null +++ b/cmd/dcip/util.go @@ -0,0 +1,32 @@ +package main + +import ( + "fmt" + "os/exec" +) + +func PrintCmd(cmd *exec.Cmd) { + if !CLI.Debug { + return + } + fmt.Print("+ ") + fmt.Println(cmd) +} + +func RunCommand(command string) *exec.Cmd { + cmd := exec.Command("bash", "-c", command) + return cmd +} + +func RunSSHCommand(host interface{}, command string) *exec.Cmd { + var cmd *exec.Cmd + switch host.(type) { + case string: + cmd = exec.Command("ssh", host.(string), command) + default: + params := host.([]string) + params = append(params, command) + cmd = exec.Command("ssh", params...) + } + return cmd +} diff --git a/export.go b/export.go new file mode 100644 index 0000000..a16da05 --- /dev/null +++ b/export.go @@ -0,0 +1,12 @@ +package dcip + +import "fmt" + +// ssh -NT -L 0.0.0.0:5432:172.17.0.5:5432 debian@example.host +func MakeForwardPortCmd(host string, cport string, lport string) []string { + return []string{ + "-NT", + "-L", fmt.Sprintf("%s:%s", lport, cport), + host, + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..69352f9 --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module github.com/shynome/dcip + +go 1.16 + +require github.com/alecthomas/kong v0.2.16 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..f115a65 --- /dev/null +++ b/go.sum @@ -0,0 +1,7 @@ +github.com/alecthomas/kong v0.2.16 h1:F232CiYSn54Tnl1sJGTeHmx4vJDNLVP2b9yCVMOQwHQ= +github.com/alecthomas/kong v0.2.16/go.mod h1:kQOmtJgV+Lb4aj+I2LEn40cbtawdWJ9Y8QLq+lElKxE= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= diff --git a/ip.go b/ip.go new file mode 100644 index 0000000..f08038d --- /dev/null +++ b/ip.go @@ -0,0 +1,13 @@ +package dcip + +import "fmt" + +var getNetworks = "docker network ls --format='{{.Name}}' | grep -E 'bridge|docker_gwbridge'" +var getAllConatinersIP = fmt.Sprintf("for dn in $(%s);do docker network inspect $dn --format '{{range $k,$c:=.Containers}}{{$k}}/{{.IPv4Address}}{{println}}{{end}}';done", getNetworks) +var getContainerID = "docker ps --latest -q --no-trunc --filter='name=%s'" +var getIPOnly = "awk -F '/' '{print $2}'" +var getContainerIPCmdFormat = fmt.Sprintf("%s | grep $(%s) | %s", getAllConatinersIP, getContainerID, getIPOnly) + +func MakeGetContainerIPCmd(name string) string { + return fmt.Sprintf(getContainerIPCmdFormat, name) +}