From 9bdc0cc27d6d5d35f2830c9366a366c9dd09be0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20P=C3=89AU?= Date: Mon, 2 Sep 2024 22:21:54 +0200 Subject: [PATCH 1/9] Fix ssh context not working --- pkg/commands/docker.go | 24 +++++++++++++++++------- pkg/commands/ssh/ssh.go | 7 ++++--- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/pkg/commands/docker.go b/pkg/commands/docker.go index e96c9653..234776cd 100644 --- a/pkg/commands/docker.go +++ b/pkg/commands/docker.go @@ -69,19 +69,29 @@ func (c *DockerCommand) NewCommandObject(obj CommandObject) CommandObject { return defaultObj } -// NewDockerCommand it runs docker commands +// NewDockerCommand creates a DockerCommand struct that will be able to run docker commands. func NewDockerCommand(log *logrus.Entry, osCommand *OSCommand, tr *i18n.TranslationSet, config *config.AppConfig, errorChan chan error) (*DockerCommand, error) { - tunnelCloser, err := ssh.NewSSHHandler(osCommand).HandleSSHDockerHost() - if err != nil { - ogLog.Fatal(err) - } - dockerHost, err := determineDockerHost() if err != nil { ogLog.Printf("> could not determine host %v", err) } - cli, err := client.NewClientWithOpts(client.FromEnv, client.WithVersion(APIVersion), client.WithHost(dockerHost)) + tunnelCloser, err := ssh.NewSSHHandler(osCommand).HandleSSHDockerHost(dockerHost) + if err != nil { + ogLog.Fatal(err) + } + + clientOpts := []client.Opt{ + client.FromEnv, + client.WithVersion(APIVersion), + } + // For an ssh connection the DOCKER_HOST env variable has been overridden. + // Discard the previously determined dockerHost + if !strings.HasPrefix(dockerHost, "ssh://") { + clientOpts = append(clientOpts, client.WithHost(dockerHost)) + } + + cli, err := client.NewClientWithOpts(clientOpts...) if err != nil { ogLog.Fatal(err) } diff --git a/pkg/commands/ssh/ssh.go b/pkg/commands/ssh/ssh.go index ecc84045..14d13f65 100644 --- a/pkg/commands/ssh/ssh.go +++ b/pkg/commands/ssh/ssh.go @@ -44,10 +44,11 @@ func NewSSHHandler(oSCommand CmdKiller) *SSHHandler { // HandleSSHDockerHost overrides the DOCKER_HOST environment variable // to point towards a local unix socket tunneled over SSH to the specified ssh host. -func (self *SSHHandler) HandleSSHDockerHost() (io.Closer, error) { +func (self *SSHHandler) HandleSSHDockerHost(dockerHost string) (io.Closer, error) { const key = "DOCKER_HOST" ctx := context.Background() - u, err := url.Parse(self.getenv(key)) + + u, err := url.Parse(dockerHost) if err != nil { // if no or an invalid docker host is specified, continue nominally return noopCloser{}, nil @@ -55,7 +56,7 @@ func (self *SSHHandler) HandleSSHDockerHost() (io.Closer, error) { // if the docker host scheme is "ssh", forward the docker socket before creating the client if u.Scheme == "ssh" { - tunnel, err := self.createDockerHostTunnel(ctx, u.Host) + tunnel, err := self.createDockerHostTunnel(ctx, u.String()) if err != nil { return noopCloser{}, fmt.Errorf("tunnel ssh docker host: %w", err) } From 21e18fe7a5587931c0cb1728415a673e7b2bcd02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20P=C3=89AU?= Date: Mon, 2 Sep 2024 22:27:48 +0200 Subject: [PATCH 2/9] Update comment --- pkg/commands/docker.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/commands/docker.go b/pkg/commands/docker.go index 234776cd..61d16879 100644 --- a/pkg/commands/docker.go +++ b/pkg/commands/docker.go @@ -69,7 +69,8 @@ func (c *DockerCommand) NewCommandObject(obj CommandObject) CommandObject { return defaultObj } -// NewDockerCommand creates a DockerCommand struct that will be able to run docker commands. +// NewDockerCommand creates a DockerCommand struct that wraps the docker client. +// Able to run docker commands. And handles func NewDockerCommand(log *logrus.Entry, osCommand *OSCommand, tr *i18n.TranslationSet, config *config.AppConfig, errorChan chan error) (*DockerCommand, error) { dockerHost, err := determineDockerHost() if err != nil { From 94494348d032d663db6372cc5143d87d3b8cbe1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20P=C3=89AU?= Date: Sun, 8 Sep 2024 00:18:39 +0200 Subject: [PATCH 3/9] update test to reflect change to the queried ssh host --- pkg/commands/ssh/ssh_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/commands/ssh/ssh_test.go b/pkg/commands/ssh/ssh_test.go index f1ea1b90..3be7bb5e 100644 --- a/pkg/commands/ssh/ssh_test.go +++ b/pkg/commands/ssh/ssh_test.go @@ -64,7 +64,7 @@ func TestSSHHandlerHandleSSHDockerHost(t *testing.T) { startCmdCount := 0 startCmd := func(cmd *exec.Cmd) error { - assert.EqualValues(t, []string{"ssh", "-L", "/tmp/lazydocker-ssh-tunnel-12345/dockerhost.sock:/var/run/docker.sock", "192.168.5.178", "-N"}, cmd.Args) + assert.EqualValues(t, []string{"ssh", "-L", "/tmp/lazydocker-ssh-tunnel-12345/dockerhost.sock:/var/run/docker.sock", s.envVarValue, "-N"}, cmd.Args) startCmdCount++ @@ -91,7 +91,7 @@ func TestSSHHandlerHandleSSHDockerHost(t *testing.T) { setenv: setenv, } - _, err := handler.HandleSSHDockerHost() + _, err := handler.HandleSSHDockerHost(s.envVarValue) assert.NoError(t, err) assert.Equal(t, s.expectedDialContextCount, dialContextCount) From efd7f4be54f1b94425adae26fca02c5040ba24b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20P=C3=89AU?= Date: Sat, 3 May 2025 20:42:45 +0200 Subject: [PATCH 4/9] finish comment --- pkg/commands/docker.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/commands/docker.go b/pkg/commands/docker.go index 0367ab24..43beaaf9 100644 --- a/pkg/commands/docker.go +++ b/pkg/commands/docker.go @@ -72,7 +72,7 @@ func (c *DockerCommand) NewCommandObject(obj CommandObject) CommandObject { } // NewDockerCommand creates a DockerCommand struct that wraps the docker client. -// Able to run docker commands. And handles +// Able to run docker commands. And handles SSH docker hosts func NewDockerCommand(log *logrus.Entry, osCommand *OSCommand, tr *i18n.TranslationSet, config *config.AppConfig, errorChan chan error) (*DockerCommand, error) { dockerHost, err := determineDockerHost() if err != nil { From 6acacc7a3e5f6a15dea497db4f10282f22ef3c82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20P=C3=89AU?= Date: Sat, 3 May 2025 21:01:17 +0200 Subject: [PATCH 5/9] edit lazydocker's tunnel filename to make it more coherent with the rest of the structure --- pkg/commands/ssh/ssh.go | 2 +- pkg/commands/ssh/ssh_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/commands/ssh/ssh.go b/pkg/commands/ssh/ssh.go index 14d13f65..4e78405d 100644 --- a/pkg/commands/ssh/ssh.go +++ b/pkg/commands/ssh/ssh.go @@ -87,7 +87,7 @@ func (t *tunneledDockerHost) Close() error { } func (self *SSHHandler) createDockerHostTunnel(ctx context.Context, remoteHost string) (*tunneledDockerHost, error) { - socketDir, err := self.tempDir("/tmp", "lazydocker-sshtunnel-") + socketDir, err := self.tempDir("/tmp", "lazydocker-ssh-tunnel-") if err != nil { return nil, fmt.Errorf("create ssh tunnel tmp file: %w", err) } diff --git a/pkg/commands/ssh/ssh_test.go b/pkg/commands/ssh/ssh_test.go index 3be7bb5e..72cc4fc2 100644 --- a/pkg/commands/ssh/ssh_test.go +++ b/pkg/commands/ssh/ssh_test.go @@ -51,7 +51,7 @@ func TestSSHHandlerHandleSSHDockerHost(t *testing.T) { tempDir := func(dir string, pattern string) (string, error) { assert.Equal(t, "/tmp", dir) - assert.Equal(t, "lazydocker-sshtunnel-", pattern) + assert.Equal(t, "lazydocker-ssh-tunnel-", pattern) return "/tmp/lazydocker-ssh-tunnel-12345", nil } From 099ae97e687b12d4aea53166504623e106f0ca17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20P=C3=89AU?= Date: Sun, 4 May 2025 13:39:11 +0200 Subject: [PATCH 6/9] rewrote ssh handling to make it clearer --- pkg/commands/docker.go | 18 +++++++++--------- pkg/commands/ssh/ssh.go | 28 ++++++++++++++++------------ 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/pkg/commands/docker.go b/pkg/commands/docker.go index 43beaaf9..a6e702d7 100644 --- a/pkg/commands/docker.go +++ b/pkg/commands/docker.go @@ -72,26 +72,26 @@ func (c *DockerCommand) NewCommandObject(obj CommandObject) CommandObject { } // NewDockerCommand creates a DockerCommand struct that wraps the docker client. -// Able to run docker commands. And handles SSH docker hosts +// Able to run docker commands and handles SSH docker hosts func NewDockerCommand(log *logrus.Entry, osCommand *OSCommand, tr *i18n.TranslationSet, config *config.AppConfig, errorChan chan error) (*DockerCommand, error) { dockerHost, err := determineDockerHost() if err != nil { ogLog.Printf("> could not determine host %v", err) } - tunnelCloser, err := ssh.NewSSHHandler(osCommand).HandleSSHDockerHost(dockerHost) + tunnelResult, err := ssh.NewSSHHandler(osCommand).HandleSSHDockerHost(dockerHost) if err != nil { ogLog.Fatal(err) } + // If we created a tunnel to the remote ssh host, we then override the dockerhost to point to the tunnel + if tunnelResult.Created { + dockerHost = tunnelResult.SocketPath + } clientOpts := []client.Opt{ - client.FromEnv, + client.WithTLSClientConfigFromEnv(), client.WithVersion(APIVersion), - } - // For an ssh connection the DOCKER_HOST env variable has been overridden. - // Discard the previously determined dockerHost - if !strings.HasPrefix(dockerHost, "ssh://") { - clientOpts = append(clientOpts, client.WithHost(dockerHost)) + client.WithHost(dockerHost), } cli, err := client.NewClientWithOpts(clientOpts...) @@ -107,7 +107,7 @@ func NewDockerCommand(log *logrus.Entry, osCommand *OSCommand, tr *i18n.Translat Client: cli, ErrorChan: errorChan, InDockerComposeProject: true, - Closers: []io.Closer{tunnelCloser}, + Closers: []io.Closer{tunnelResult.Closer}, } dockerCommand.setDockerComposeCommand(config) diff --git a/pkg/commands/ssh/ssh.go b/pkg/commands/ssh/ssh.go index 4e78405d..fb85953c 100644 --- a/pkg/commands/ssh/ssh.go +++ b/pkg/commands/ssh/ssh.go @@ -42,32 +42,36 @@ func NewSSHHandler(oSCommand CmdKiller) *SSHHandler { } } +type TunnelResult struct { + Closer io.Closer + SocketPath string + Created bool +} + // HandleSSHDockerHost overrides the DOCKER_HOST environment variable // to point towards a local unix socket tunneled over SSH to the specified ssh host. -func (self *SSHHandler) HandleSSHDockerHost(dockerHost string) (io.Closer, error) { - const key = "DOCKER_HOST" - ctx := context.Background() - +func (self *SSHHandler) HandleSSHDockerHost(dockerHost string) (TunnelResult, error) { u, err := url.Parse(dockerHost) if err != nil { // if no or an invalid docker host is specified, continue nominally - return noopCloser{}, nil + return TunnelResult{Closer: noopCloser{}}, nil } // if the docker host scheme is "ssh", forward the docker socket before creating the client if u.Scheme == "ssh" { + ctx := context.Background() tunnel, err := self.createDockerHostTunnel(ctx, u.String()) if err != nil { - return noopCloser{}, fmt.Errorf("tunnel ssh docker host: %w", err) - } - err = self.setenv(key, tunnel.socketPath) - if err != nil { - return noopCloser{}, fmt.Errorf("override DOCKER_HOST to tunneled socket: %w", err) + return TunnelResult{Closer: noopCloser{}}, fmt.Errorf("tunnel ssh docker host: %w", err) } - return tunnel, nil + return TunnelResult{ + Closer: tunnel, + SocketPath: tunnel.socketPath, + Created: true, + }, nil } - return noopCloser{}, nil + return TunnelResult{Closer: noopCloser{}}, nil } type noopCloser struct{} From f34b6dbbe335ca86a75e5cd7fcf3cae7f6e927d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20P=C3=89AU?= Date: Sun, 4 May 2025 14:16:27 +0200 Subject: [PATCH 7/9] bump dependencies --- .github/workflows/cd.yml | 6 +++--- .github/workflows/ci.yml | 24 ++++++++++++------------ .github/workflows/sponsors.yml | 2 +- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 66e71608..10c99e43 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -10,15 +10,15 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Unshallow repo run: git fetch --prune --unshallow - name: Setup Go - uses: actions/setup-go@v1 + uses: actions/setup-go@v4 with: go-version: 1.21.x - name: Run goreleaser - uses: goreleaser/goreleaser-action@v1 + uses: goreleaser/goreleaser-action@v5 with: distribution: goreleaser version: v1.17.2 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b9461e75..b90b7b91 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,13 +23,13 @@ jobs: GOFLAGS: -mod=vendor steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Setup Go - uses: actions/setup-go@v1 + uses: actions/setup-go@v4 with: go-version: 1.21.x - name: Cache build - uses: actions/cache@v1 + uses: actions/cache@v4 with: path: ~/.cache/go-build key: ${{runner.os}}-go-${{hashFiles('**/go.sum')}}-test @@ -45,13 +45,13 @@ jobs: GOARCH: amd64 steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Setup Go - uses: actions/setup-go@v1 + uses: actions/setup-go@v4 with: go-version: 1.21.x - name: Cache build - uses: actions/cache@v1 + uses: actions/cache@v4 with: path: ~/.cache/go-build key: ${{runner.os}}-go-${{hashFiles('**/go.sum')}}-build @@ -73,13 +73,13 @@ jobs: GOARCH: amd64 steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Setup Go - uses: actions/setup-go@v1 + uses: actions/setup-go@v4 with: go-version: 1.21.x - name: Cache build - uses: actions/cache@v1 + uses: actions/cache@v4 with: path: | ~/.cache/go-build @@ -100,13 +100,13 @@ jobs: GOFLAGS: -mod=vendor steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Setup Go - uses: actions/setup-go@v1 + uses: actions/setup-go@v4 with: go-version: 1.21.x - name: Cache build - uses: actions/cache@v1 + uses: actions/cache@v4 with: path: ~/.cache/go-build key: ${{runner.os}}-go-${{hashFiles('**/go.sum')}}-test diff --git a/.github/workflows/sponsors.yml b/.github/workflows/sponsors.yml index cda2fce4..81a200c9 100644 --- a/.github/workflows/sponsors.yml +++ b/.github/workflows/sponsors.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout 🛎️ - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Generate Sponsors 💖 uses: JamesIves/github-sponsors-readme-action@v1.2.2 From 47d041af79cca96a2c6b9b54b7fc225dd6309c16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20P=C3=89AU?= Date: Sun, 4 May 2025 14:29:44 +0200 Subject: [PATCH 8/9] remove deprecated linters according to output --- .golangci.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index f1fcc4c1..1d325e3a 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,13 +1,11 @@ linters: - disable: - - structcheck # gives false positives enable: - gofumpt - thelper - goimports - tparallel - wastedassign - - exportloopref + - copyloopvar - unparam - prealloc - unconvert From 98a7527ac0836d3641482f1473e0b94e2e5bac87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=89AU=20Cl=C3=A9ment?= Date: Mon, 6 Oct 2025 09:17:14 +0200 Subject: [PATCH 9/9] Update README.md --- README.md | 48 ------------------------------------------------ 1 file changed, 48 deletions(-) diff --git a/README.md b/README.md index 84f15673..2bb85986 100644 --- a/README.md +++ b/README.md @@ -1,41 +1,3 @@ - -

@@ -55,16 +17,6 @@ A simple terminal UI for both docker and docker-compose, written in Go with the [Demo](https://youtu.be/NICqQPxwJWw) -## Sponsors - -

- Maintenance of this project is made possible by all the contributors and sponsors. If you'd like to sponsor this project and have your avatar or company logo appear below click here. 💙 -

- -

-Mark LussierDean HerbertPeter BjorklundReilly WoodOliver GüntherPawan DhananjayBartłomiej DachDavid KarlssonCarsten GehlingCEUKAkos PutzXeteraHolden LucasChau TranmatejciktheAverageDev (Luca Tumedei)Ivan ZaitsevNicholas CloudLightQuantumGabriel SaillardAliaksandr StelmachonakBurgy BenjaminJoe KlemmerTobias LütkeBen BeaumontHollyJames SantucciJeff ForcierMaciej T. NowakFarzad MajidfayyazYuryAndreas KurthBraden SteffaniakJordan GillardSebastianGeorge SpanosFrantisek StankoAndy SlezakMartin KockIllarion KoperskiJesse AlamaCodacyBrettJan HeijmansKevin Nowaldsem pruijsOmar Luq Ethan LiBrian MacAskillMaxinbrJan ZenknerVictor AremuIgor RamazanovElliott Maguiren8n - Workflow Automationkaleb allmonJosh ThomasJJFrederick MorlockDarren CraineMaximilian LangenfeldNurzhanDavis BulsGrec MarcsainuMarc Güell SegarraChris OlsenVladimir PopovNeil LambertShaun GarwoodDavid Heinemeier HanssonWayan jimmy -

- ## Elevator Pitch Minor rant incoming: Something's not working? Maybe a service is down. `docker-compose ps`. Yep, it's that microservice that's still buggy. No issue, I'll just restart it: `docker-compose restart`. Okay now let's try again. Oh wait the issue is still there. Hmm. `docker-compose ps`. Right so the service must have just stopped immediately after starting. I probably would have known that if I was reading the log stream, but there is a lot of clutter in there from other services. I could get the logs for just that one service with `docker compose logs --follow myservice` but that dies everytime the service dies so I'd need to run that command every time I restart the service. I could alternatively run `docker-compose up myservice` and in that terminal window if the service is down I could just `up` it again, but now I've got one service hogging a terminal window even after I no longer care about its logs. I guess when I want to reclaim the terminal realestate I can do `ctrl+P,Q`, but... wait, that's not working for some reason. Should I use ctrl+C instead? I can't remember if that closes the foreground process or kills the actual service.