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 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 diff --git a/README.md b/README.md index 84f15673..2bb85986 100644 --- a/README.md +++ b/README.md @@ -1,41 +1,3 @@ -
-Special thanks to: -
-
- -
- Warp -
- Warp, the intelligent terminal -
- Available for MacOS and Linux -
-
- Visit warp.dev to learn more. -
-
-
-
- -
- Tuple -
- Tuple, the premier screen sharing app for developers on macOS and Windows. -
-
-
-
- -
- Subble -
- I (Jesse) co-founded Subble to save your company time and money by finding unused and over-provisioned SaaS licences. Check it out! -
-
- -
-
-

@@ -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. diff --git a/pkg/commands/docker.go b/pkg/commands/docker.go index 28c12c23..a6e702d7 100644 --- a/pkg/commands/docker.go +++ b/pkg/commands/docker.go @@ -71,33 +71,30 @@ func (c *DockerCommand) NewCommandObject(obj CommandObject) CommandObject { return defaultObj } -// NewDockerCommand it runs docker commands +// NewDockerCommand creates a DockerCommand struct that wraps the docker client. +// 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) } - // NOTE: Inject the determined docker host to the environment. This allows the - // `SSHHandler.HandleSSHDockerHost()` to create a local unix socket tunneled - // over SSH to the specified ssh host. - if strings.HasPrefix(dockerHost, "ssh://") { - os.Setenv(dockerHostEnvKey, dockerHost) - } - - tunnelCloser, err := ssh.NewSSHHandler(osCommand).HandleSSHDockerHost() + tunnelResult, err := ssh.NewSSHHandler(osCommand).HandleSSHDockerHost(dockerHost) if err != nil { ogLog.Fatal(err) } - - // Retrieve the docker host from the environment which could have been set by - // the `SSHHandler.HandleSSHDockerHost()` and override `dockerHost`. - dockerHostFromEnv := os.Getenv(dockerHostEnvKey) - if dockerHostFromEnv != "" { - dockerHost = dockerHostFromEnv + // 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 } - cli, err := client.NewClientWithOpts(client.FromEnv, client.WithVersion(APIVersion), client.WithHost(dockerHost)) + clientOpts := []client.Opt{ + client.WithTLSClientConfigFromEnv(), + client.WithVersion(APIVersion), + client.WithHost(dockerHost), + } + + cli, err := client.NewClientWithOpts(clientOpts...) if err != nil { ogLog.Fatal(err) } @@ -110,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 ecc84045..fb85953c 100644 --- a/pkg/commands/ssh/ssh.go +++ b/pkg/commands/ssh/ssh.go @@ -42,31 +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() (io.Closer, error) { - const key = "DOCKER_HOST" - ctx := context.Background() - u, err := url.Parse(self.getenv(key)) +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" { - tunnel, err := self.createDockerHostTunnel(ctx, u.Host) + 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{} @@ -86,7 +91,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 f1ea1b90..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 } @@ -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)