diff --git a/cli/app/restart.go b/cli/app/restart.go index 198c67e9..520ce15e 100644 --- a/cli/app/restart.go +++ b/cli/app/restart.go @@ -8,7 +8,7 @@ import ( "coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/pkg/client" "coopcloud.tech/abra/pkg/config" - "github.com/docker/docker/api/types" + containerPkg "coopcloud.tech/abra/pkg/container" "github.com/docker/docker/api/types/filters" "github.com/sirupsen/logrus" "github.com/urfave/cli/v2" @@ -37,19 +37,16 @@ var appRestartCommand = &cli.Command{ serviceFilter := fmt.Sprintf("%s_%s", app.StackName(), serviceName) filters := filters.NewArgs() filters.Add("name", serviceFilter) - containerOpts := types.ContainerListOptions{Filters: filters} - containers, err := cl.ContainerList(c.Context, containerOpts) + + targetContainer, err := containerPkg.GetContainer(c.Context, cl, filters, true) if err != nil { logrus.Fatal(err) } - if len(containers) != 1 { - logrus.Fatalf("expected 1 service but got %v", len(containers)) - } logrus.Debugf("attempting to restart %s", serviceFilter) timeout := 30 * time.Second - if err := cl.ContainerRestart(c.Context, containers[0].ID, &timeout); err != nil { + if err := cl.ContainerRestart(c.Context, targetContainer.ID, &timeout); err != nil { logrus.Fatal(err) } diff --git a/cli/app/run.go b/cli/app/run.go index 0003ce55..8fa7d24b 100644 --- a/cli/app/run.go +++ b/cli/app/run.go @@ -7,6 +7,7 @@ import ( "coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/pkg/client" "coopcloud.tech/abra/pkg/config" + containerPkg "coopcloud.tech/abra/pkg/container" "coopcloud.tech/abra/pkg/upstream/container" "github.com/docker/cli/cli/command" "github.com/docker/docker/api/types" @@ -59,18 +60,11 @@ var appRunCommand = &cli.Command{ filters := filters.NewArgs() filters.Add("name", stackAndServiceName) - containers, err := cl.ContainerList(c.Context, types.ContainerListOptions{Filters: filters}) + targetContainer, err := containerPkg.GetContainer(c.Context, cl, filters, true) if err != nil { logrus.Fatal(err) } - if len(containers) == 0 { - logrus.Fatalf("no containers matching '%s' found?", stackAndServiceName) - } - if len(containers) > 1 { - logrus.Fatalf("expected 1 container matching '%s' but got %d", stackAndServiceName, len(containers)) - } - cmd := c.Args().Slice()[2:] execCreateOpts := types.ExecConfig{ AttachStderr: true, @@ -98,7 +92,7 @@ var appRunCommand = &cli.Command{ logrus.Fatal(err) } - if err := container.RunExec(dcli, cl, containers[0].ID, &execCreateOpts); err != nil { + if err := container.RunExec(dcli, cl, targetContainer.ID, &execCreateOpts); err != nil { logrus.Fatal(err) } diff --git a/cli/internal/copy.go b/cli/internal/copy.go index c89656ac..95f9ecaf 100644 --- a/cli/internal/copy.go +++ b/cli/internal/copy.go @@ -7,6 +7,7 @@ import ( "coopcloud.tech/abra/cli/formatter" "coopcloud.tech/abra/pkg/client" "coopcloud.tech/abra/pkg/config" + "coopcloud.tech/abra/pkg/container" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" "github.com/docker/docker/pkg/archive" @@ -32,16 +33,12 @@ func ConfigureAndCp(c *cli.Context, app config.App, srcPath string, dstPath stri filters := filters.NewArgs() filters.Add("name", fmt.Sprintf("%s_%s", appEnv.StackName(), service)) - containers, err := cl.ContainerList(c.Context, types.ContainerListOptions{Filters: filters}) + + container, err := container.GetContainer(c.Context, cl, filters, true) if err != nil { logrus.Fatal(err) } - if len(containers) != 1 { - logrus.Fatalf("expected 1 container but got %v", len(containers)) - } - container := containers[0] - logrus.Debugf("retrieved '%s' as target container on '%s'", formatter.ShortenID(container.ID), app.Server) if isToContainer { diff --git a/pkg/container/container.go b/pkg/container/container.go new file mode 100644 index 00000000..bf0d785f --- /dev/null +++ b/pkg/container/container.go @@ -0,0 +1,70 @@ +package container + +import ( + "context" + "fmt" + "strings" + + abraFormatter "coopcloud.tech/abra/cli/formatter" + "github.com/AlecAivazis/survey/v2" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" + "github.com/docker/docker/client" + "github.com/sirupsen/logrus" +) + +// GetContainer retrieves a container. If prompt is true and the retrievd count +// of containers does not match expectedN, then a prompt is presented to let +// the user choose. +func GetContainer(c context.Context, cl *client.Client, filters filters.Args, prompt bool) (types.Container, error) { + containerOpts := types.ContainerListOptions{Filters: filters} + containers, err := cl.ContainerList(c, containerOpts) + if err != nil { + return types.Container{}, err + } + + if len(containers) == 0 { + filter := filters.Get("name")[0] + return types.Container{}, fmt.Errorf("no containers matching the %v filter found?", filter) + } + + if len(containers) != 1 { + var containersRaw []string + for _, container := range containers { + containerName := strings.Join(container.Names, " ") + trimmed := strings.TrimPrefix(containerName, "/") + created := abraFormatter.HumanDuration(container.Created) + containersRaw = append(containersRaw, fmt.Sprintf("%s (created %v)", trimmed, created)) + } + + if !prompt { + err := fmt.Errorf("expected 1 container but found %v: %s", len(containers), strings.Join(containersRaw, " ")) + return types.Container{}, err + } + + logrus.Warnf("ambiguous container list received, prompting for input") + + var response string + prompt := &survey.Select{ + Message: "which container are you looking for?", + Options: containersRaw, + } + + if err := survey.AskOne(prompt, &response); err != nil { + return types.Container{}, err + } + + chosenContainer := strings.TrimSpace(strings.Split(response, " ")[0]) + for _, container := range containers { + containerName := strings.TrimSpace(strings.Join(container.Names, " ")) + trimmed := strings.TrimPrefix(containerName, "/") + if trimmed == chosenContainer { + return container, nil + } + } + + logrus.Panic("failed to match chosen container") + } + + return containers[0], nil +}