Skip to content

Commit

Permalink
Allow user to multi-select context names for which to remove containe…
Browse files Browse the repository at this point in the history
…rs, showing only context names with actual containers
  • Loading branch information
silphid committed Jun 28, 2021
1 parent 1b4fb3d commit 7bcfe7d
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 12 deletions.
57 changes: 49 additions & 8 deletions src/cmd/prompts.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,26 +12,67 @@ func GetOrPromptContextNames(contexts yey.Contexts, names []string) ([]string, e
availableNames := contexts.GetNamesInAllLayers()

// Prompt unspecified names
for i := len(names); i < len(contexts.Layers); i++ {
for layer := len(names); layer < len(contexts.Layers); layer++ {
// Don't prompt when single name in layer
if len(availableNames[i]) == 1 {
names = append(names, availableNames[i][0])
if len(availableNames[layer]) == 1 {
names = append(names, availableNames[layer][0])
continue
}
prompt := &survey.Select{
Message: fmt.Sprintf("Select %s", contexts.Layers[i].Name),
Options: availableNames[i],
Message: fmt.Sprintf("Select %s", contexts.Layers[layer].Name),
Options: availableNames[layer],
}
selectedIndex := 0
if err := survey.AskOne(prompt, &selectedIndex); err != nil {
var selectedName string
if err := survey.AskOne(prompt, &selectedName); err != nil {
return nil, err
}
names = append(names, availableNames[i][selectedIndex])
names = append(names, selectedName)
}

return names, nil
}

// Parses given value into context name and variant and, as needed, prompt user for those values
func GetOrPromptMultipleContextNames(contexts yey.Contexts, names []string, predicate func(name string, layer int) bool) ([][]string, error) {
availableNames := contexts.GetNamesInAllLayers()

outputNames := make([][]string, 0, len(contexts.Layers))
for layer := 0; layer < len(contexts.Layers); layer++ {
// Context name for this layer already specified by user?
if layer < len(names) {
// Just take name specified by user
outputNames = append(outputNames, []string{names[layer]})
} else {
// Filter context names through predicate
filteredNames := make([]string, 0, len(availableNames[layer]))
for _, name := range availableNames[layer] {
if predicate(name, layer) {
filteredNames = append(filteredNames, name)
}
}

// Don't prompt when single option
if len(filteredNames) == 1 {
outputNames = append(outputNames, filteredNames)
continue
}

// Prompt to multiselect context names for unspecified layer
prompt := &survey.MultiSelect{
Message: fmt.Sprintf("Select %s(s)", contexts.Layers[layer].Name),
Options: filteredNames,
}
var selectedNames []string
if err := survey.AskOne(prompt, &selectedNames); err != nil {
return nil, err
}
outputNames = append(outputNames, selectedNames)
}
}

return outputNames, nil
}

// Prompts user to multi-select among given images
func PromptImageNames(allImages []string) ([]string, error) {

Expand Down
58 changes: 57 additions & 1 deletion src/cmd/remove/remove.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ package remove
import (
"context"
"fmt"
"os"
"strings"

"github.com/TwinProduction/go-color"
"github.com/spf13/cobra"

"github.com/silphid/yey/src/cmd"
Expand Down Expand Up @@ -36,17 +39,70 @@ func run(ctx context.Context, names []string, options docker.RemoveOptions) erro
return err
}

names, err = cmd.GetOrPromptContextNames(contexts, names)
containers, err := docker.ListContainers(ctx)
if err != nil {
return fmt.Errorf("failed to list containers to prompt for removal: %w", err)
}

// Abort if no containers to remove
if len(containers) == 0 {
fmt.Fprintln(os.Stderr, color.Ize(color.Green, "no containers to remove"))
return nil
}

// Parse container names to slices
containerNames := make([][]string, len(containers))
for i, container := range containers {
// TODO: improve this logic to support context names with dashes in them
// We need a more deterministic way to trace back a container name to its context names
containerNames[i] = strings.Split(container, "-")
}

// Predicate to determine whether context name in given layer has a corresponding container
predicate := func(name string, layer int) bool {
for _, containerName := range containerNames {
skipContainerPrefixes := 3
if containerName[skipContainerPrefixes+layer] == name {
return true
}
}
return false
}

// Prompt
selectedNames, err := cmd.GetOrPromptMultipleContextNames(contexts, names, predicate)
if err != nil {
return fmt.Errorf("failed to prompt for context: %w", err)
}

return removeRecursively(ctx, contexts, selectedNames, []string{}, 0, options)
}

func removeRecursively(ctx context.Context, contexts yey.Contexts, selectedNames [][]string, names []string, layer int, options docker.RemoveOptions) error {
for _, name := range selectedNames[layer] {
currentNames := append(names, name)
var err error
if layer == len(selectedNames)-1 {
err = remove(ctx, contexts, currentNames, options)
} else {
// Recurse
err = removeRecursively(ctx, contexts, selectedNames, currentNames, layer+1, options)
}
if err != nil {
return err
}
}
return nil
}

func remove(ctx context.Context, contexts yey.Contexts, names []string, options docker.RemoveOptions) error {
context, err := contexts.GetContext(names)
if err != nil {
return fmt.Errorf("failed to get context: %w", err)
}

container := yey.ContainerName(contexts.Path, context)

yey.Log("Removing %s", container)
return docker.Remove(ctx, container, options)
}
16 changes: 13 additions & 3 deletions src/internal/docker/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"os/exec"
"path/filepath"
"regexp"
"sort"
"strings"

yey "github.com/silphid/yey/src/internal"
Expand Down Expand Up @@ -100,12 +101,21 @@ var newlines = regexp.MustCompile(`\r?\n`)

func ListContainers(ctx context.Context) ([]string, error) {
cmd := exec.Command("docker", "ps", "--all", "--filter", "name=yey-*", "--format", "{{.Names}}")
output, err := cmd.Output()

// Parse output
outputBuf, err := cmd.Output()
if err != nil {
return nil, err
}
output = bytes.TrimSpace(output)
return newlines.Split(string(output), -1), nil
output := string(bytes.TrimSpace(outputBuf))
if output == "" {
return []string{}, nil
}
containers := newlines.Split(string(output), -1)

// Sort
sort.Strings(containers)
return containers, nil
}

func imageExists(ctx context.Context, tag string) (bool, error) {
Expand Down

0 comments on commit 7bcfe7d

Please sign in to comment.