From 81b5e8802610bcc192618d1fed34522cc50b4236 Mon Sep 17 00:00:00 2001 From: Dennis Ploeger Date: Tue, 6 Feb 2024 13:00:17 +0100 Subject: [PATCH 1/5] feat: Use arm runner --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 206e878..0c27fb1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -37,7 +37,7 @@ jobs: uses: golangci/golangci-lint-action@v3.4.0 testmac: - runs-on: macos-latest # beta arm runner + runs-on: macos-14 # beta arm runner strategy: matrix: goos: From 690bec65a5aca9e21d5ab5adb07366800df1f21d Mon Sep 17 00:00:00 2001 From: Dennis Ploeger Date: Fri, 20 Sep 2024 14:41:34 +0200 Subject: [PATCH 2/5] fix: Added missing error handling for when Docker crashes --- internal/adapters/docker.go | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/internal/adapters/docker.go b/internal/adapters/docker.go index abe9a9e..756743b 100644 --- a/internal/adapters/docker.go +++ b/internal/adapters/docker.go @@ -16,6 +16,7 @@ import ( "github.com/docker/docker/client" resty "github.com/go-resty/resty/v2" "github.com/moby/term" + "github.com/sirupsen/logrus" "io" "net" "os" @@ -140,8 +141,13 @@ func (d *DockerAdapter) RunCloudControl(_ string, name string, consoleWidth uint case <-quitWriter: return default: - if b, err := s.ReadByte(); err == nil { - _, _ = stdout.Write([]byte{b}) + if b, err := s.ReadByte(); err != nil { + logrus.Errorf("error reading from Docker: %s", err) + return + } else { + if _, err := stdout.Write([]byte{b}); err != nil { + logrus.Errorf("error writing to stdout: %s", err) + } } } } @@ -154,8 +160,14 @@ func (d *DockerAdapter) RunCloudControl(_ string, name string, consoleWidth uint case <-quitReader: return default: - if b, err := s.ReadByte(); err == nil { - _, _ = c.Write([]byte{b}) + if b, err := s.ReadByte(); err != nil { + logrus.Errorf("error reading from Docker: %s", err) + return + } else { + if _, err := c.Write([]byte{b}); err != nil { + logrus.Errorf("error writing to Docker: %s", err) + return + } } } } @@ -169,14 +181,20 @@ func (d *DockerAdapter) RunCloudControl(_ string, name string, consoleWidth uint case <-quitTermResize: return default: - if w, err := term.GetWinsize(fd); err == nil { + if w, err := term.GetWinsize(fd); err != nil { + logrus.Errorf("error getting terminal size: %s", err) + return + } else { if w.Width != uint16(width) || w.Height != uint16(height) { width = uint(w.Width) height = uint(w.Height) - _ = dockerCli.ContainerExecResize(context.Background(), executeID, types.ResizeOptions{ + if err := dockerCli.ContainerExecResize(context.Background(), executeID, types.ResizeOptions{ Width: width, Height: height, - }) + }); err != nil { + logrus.Errorf("error resizing container terminal: %s", err) + return + } } } } From 3fceb6df17646ed3755627160854a7e46c5acfb3 Mon Sep 17 00:00:00 2001 From: Dennis Ploeger Date: Fri, 27 Sep 2024 10:13:39 +0200 Subject: [PATCH 3/5] chore: Split mac build into arm64 and amd64 --- .github/workflows/build.yml | 38 +++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6b51166..dc1c8e1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -42,15 +42,49 @@ jobs: asset-name: ccmanager-${{ matrix.goos }}-${{ matrix.goarch }} file: ccmanager + # Separate mac amd64 build to support fsevents dependency + buildmacamd64: + runs-on: macos-13 + strategy: + matrix: + goos: + - darwin + goarch: + - amd64 + + env: + GOOS: ${{ matrix.goos }} + GOARCH: ${{ matrix.goarch }} + + steps: + - uses: actions/checkout@v3 + + - name: Set up Go + uses: actions/setup-go@v3 + with: + go-version: "1.21" + + - name: Build + run: go build -o ccmanager cmd/ccmanager.go + + - name: Release file + uses: djnicholson/release-action@v2.10 + with: + token: ${{ secrets.GITHUB_TOKEN }} + release-name: ${{ github.event.release.name }} + tag-name: ${{ github.event.release.tag_name }} + asset-name: ccmanager-${{ matrix.goos }}-${{ matrix.goarch }} + file: ccmanager + + # Separate mac build to support fsevents dependency - buildmac: + buildmacarm64: runs-on: macos-14 # beta arm runner strategy: matrix: goos: - darwin goarch: - - amd64 - arm64 env: From 3419e3d55cfd8e6ecee4c27b91f5694ea539f30d Mon Sep 17 00:00:00 2001 From: Dennis Ploeger Date: Thu, 15 May 2025 14:05:19 +0200 Subject: [PATCH 4/5] feat: Better support for podman and optimized argument parsing --- cmd/ccmanager.go | 28 ++++------------ go.mod | 2 ++ go.sum | 4 +++ internal/adapters/docker.go | 67 ++++++++++++++++++++----------------- 4 files changed, 49 insertions(+), 52 deletions(-) diff --git a/cmd/ccmanager.go b/cmd/ccmanager.go index 56619b6..4a32644 100644 --- a/cmd/ccmanager.go +++ b/cmd/ccmanager.go @@ -6,40 +6,24 @@ import ( "ccmanager/internal/adapters" "ccmanager/internal/models" "fmt" - "github.com/akamensky/argparse" + "github.com/alexflint/go-arg" "github.com/charmbracelet/bubbles/list" tea "github.com/charmbracelet/bubbletea" - "log" "os" - "strings" ) func main() { - parser := argparse.NewParser("ccmanager", "CloudControl instance manager") - basePath := parser.StringList("b", "basepath", &argparse.Options{ - Help: "Paths where to find CloudControl docker compose folders. Can be set using a comma separated list in CCMANAGER_BASEPATH", - Required: false, - }) - - if err := parser.Parse(os.Args); err != nil { - log.Fatal(parser.Usage(err)) - } - - if len(*basePath) == 0 { - if e, found := os.LookupEnv("CCMANAGER_BASEPATH"); found { - *basePath = strings.Split(e, ",") - } - } - - if len(*basePath) == 0 { - log.Fatal(parser.Usage("No basepath given. Please use CCMANAGER_BASEPATH or the --basepath argument")) + var args struct { + BasePath []string `arg:"required,env:CCMANAGER_BASEPATH,separate" help:"Paths where to find CloudControl docker compose folders"` + ContainerSeparator string `default:"-" arg:"env:CCMANAGER_SEP" help:"Separator used in docker compose container names"` } + arg.MustParse(&args) var items []list.Item d := adapters.DockerAdapter{} - program := tea.NewProgram(models.NewMainModel(&d, *basePath, items)) + program := tea.NewProgram(models.NewMainModel(&d, args.BasePath, items)) if _, err := program.Run(); err != nil { fmt.Println("Error running program:", err) os.Exit(1) diff --git a/go.mod b/go.mod index 950b683..877d476 100644 --- a/go.mod +++ b/go.mod @@ -36,6 +36,8 @@ require ( github.com/Masterminds/semver/v3 v3.2.1 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect github.com/Microsoft/hcsshim v0.11.4 // indirect + github.com/alexflint/go-arg v1.5.1 // indirect + github.com/alexflint/go-scalar v1.2.0 // indirect github.com/atotto/clipboard v0.1.4 // indirect github.com/aws/aws-sdk-go-v2 v1.17.6 // indirect github.com/aws/aws-sdk-go-v2/config v1.18.16 // indirect diff --git a/go.sum b/go.sum index be3dc9c..fa1d045 100644 --- a/go.sum +++ b/go.sum @@ -29,6 +29,10 @@ github.com/akamensky/argparse v1.4.0 h1:YGzvsTqCvbEZhL8zZu2AiA5nq805NZh75JNj4ajn github.com/akamensky/argparse v1.4.0/go.mod h1:S5kwC7IuDcEr5VeXtGPRVZ5o/FdhcMlQz4IZQuw64xA= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alexflint/go-arg v1.5.1 h1:nBuWUCpuRy0snAG+uIJ6N0UvYxpxA0/ghA/AaHxlT8Y= +github.com/alexflint/go-arg v1.5.1/go.mod h1:A7vTJzvjoaSTypg4biM5uYNTkJ27SkNTArtYXnlqVO8= +github.com/alexflint/go-scalar v1.2.0 h1:WR7JPKkeNpnYIOfHRa7ivM21aWAdHD0gEWHCx+WQBRw= +github.com/alexflint/go-scalar v1.2.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o= github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 h1:aM1rlcoLz8y5B2r4tTLMiVTrMtpfY0O8EScKJxaSaEc= github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092/go.mod h1:rYqSE9HbjzpHTI74vwPvae4ZVYZd1lue2ta6xHPdblA= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= diff --git a/internal/adapters/docker.go b/internal/adapters/docker.go index 756743b..4457c4b 100644 --- a/internal/adapters/docker.go +++ b/internal/adapters/docker.go @@ -37,7 +37,7 @@ type DockerAdapter struct { } func (d *DockerAdapter) GetContainerStatus(basePath string, name string) (CloudControlStatus, error) { - containerName := fmt.Sprintf("%s_cli_1", name) + containerName := fmt.Sprintf("%s%scli%s1", name, api.Separator, api.Separator) c := d.getClient() notFound := regexp.MustCompile("No such container") if i, err := c.ContainerInspect(context.Background(), containerName); err != nil { @@ -86,7 +86,7 @@ func (d *DockerAdapter) GetContainerStatus(basePath string, name string) (CloudC } func (d *DockerAdapter) RunCloudControl(_ string, name string, consoleWidth uint, consoleHeight uint) (*ContainerExec, error) { - containerName := fmt.Sprintf("%s_cli_1", name) + containerName := fmt.Sprintf("%s%scli%s1", name, api.Separator, api.Separator) consoleSize := [2]uint{consoleHeight, consoleWidth} return &ContainerExec{ exec: func(stdin io.Reader, stdout io.Writer) error { @@ -135,18 +135,21 @@ func (d *DockerAdapter) RunCloudControl(_ string, name string, consoleWidth uint } go func(c net.Conn) { + active := true s := bufio.NewReader(c) for { select { case <-quitWriter: return default: - if b, err := s.ReadByte(); err != nil { - logrus.Errorf("error reading from Docker: %s", err) - return - } else { - if _, err := stdout.Write([]byte{b}); err != nil { - logrus.Errorf("error writing to stdout: %s", err) + if active { + if b, err := s.ReadByte(); err != nil { + logrus.Errorf("error reading from Docker: %s", err) + active = false + } else { + if _, err := stdout.Write([]byte{b}); err != nil { + logrus.Errorf("error writing to stdout: %s", err) + } } } } @@ -154,19 +157,22 @@ func (d *DockerAdapter) RunCloudControl(_ string, name string, consoleWidth uint }(execResponse.Conn) go func(c net.Conn) { + active := true s := bufio.NewReader(stdin) for { select { case <-quitReader: return default: - if b, err := s.ReadByte(); err != nil { - logrus.Errorf("error reading from Docker: %s", err) - return - } else { - if _, err := c.Write([]byte{b}); err != nil { - logrus.Errorf("error writing to Docker: %s", err) - return + if active { + if b, err := s.ReadByte(); err != nil { + logrus.Errorf("error reading from Docker: %s", err) + active = false + } else { + if _, err := c.Write([]byte{b}); err != nil { + logrus.Errorf("error writing to Docker: %s", err) + active = false + } } } } @@ -174,6 +180,7 @@ func (d *DockerAdapter) RunCloudControl(_ string, name string, consoleWidth uint }(execResponse.Conn) go func() { + active := true width := consoleWidth height := consoleHeight for { @@ -181,19 +188,21 @@ func (d *DockerAdapter) RunCloudControl(_ string, name string, consoleWidth uint case <-quitTermResize: return default: - if w, err := term.GetWinsize(fd); err != nil { - logrus.Errorf("error getting terminal size: %s", err) - return - } else { - if w.Width != uint16(width) || w.Height != uint16(height) { - width = uint(w.Width) - height = uint(w.Height) - if err := dockerCli.ContainerExecResize(context.Background(), executeID, types.ResizeOptions{ - Width: width, - Height: height, - }); err != nil { - logrus.Errorf("error resizing container terminal: %s", err) - return + if active { + if w, err := term.GetWinsize(fd); err != nil { + logrus.Errorf("error getting terminal size: %s", err) + active = false + } else { + if w.Width != uint16(width) || w.Height != uint16(height) { + width = uint(w.Width) + height = uint(w.Height) + if err := dockerCli.ContainerExecResize(context.Background(), executeID, types.ResizeOptions{ + Width: width, + Height: height, + }); err != nil { + logrus.Errorf("error resizing container terminal: %s", err) + active = false + } } } } @@ -270,8 +279,6 @@ func (d *DockerAdapter) getClient() client.Client { func (d *DockerAdapter) getComposeBackend() api.Service { if d.composeBackend == nil { cl := d.getClient() - // ensure old docker-compose compatibility - api.Separator = "_" if c, err := command.NewDockerCli(command.WithAPIClient(&cl), command.WithDefaultContextStoreConfig()); err != nil { panic(fmt.Sprintf("Can not connect to Docker API: %s", err.Error())) } else { From fc23ff4b81165e5099a83eedade11196c458459d Mon Sep 17 00:00:00 2001 From: Dennis Ploeger Date: Thu, 15 May 2025 14:16:41 +0200 Subject: [PATCH 5/5] fix: logrus went missing --- internal/adapters/docker.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/adapters/docker.go b/internal/adapters/docker.go index c746058..4457c4b 100644 --- a/internal/adapters/docker.go +++ b/internal/adapters/docker.go @@ -16,6 +16,7 @@ import ( "github.com/docker/docker/client" resty "github.com/go-resty/resty/v2" "github.com/moby/term" + "github.com/sirupsen/logrus" "io" "net" "os"