Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/usage/distrobox-list.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ from the rest of normal containers.

# SYNOPSIS

**distrobox list**
**distrobox list** [container-name]

--help/-h: show this message
--no-color: disable color formatting
Expand All @@ -24,6 +24,7 @@ from the rest of normal containers.
# EXAMPLES

distrobox-list
distrobox-list my-distrobox

You can also use environment variables to specify container manager

Expand Down
14 changes: 10 additions & 4 deletions internal/cli/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ import (

func newListCommand(cfg *config.Values) *cli.Command {
return &cli.Command{
Name: "list",
Aliases: []string{"ls"},
Usage: "List distroboxes",
Name: "list",
Aliases: []string{"ls"},
Usage: "List distroboxes",
ArgsUsage: "[container-name]",
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "no-color",
Expand All @@ -32,13 +33,18 @@ func newListCommand(cfg *config.Values) *cli.Command {
}

func listAction(ctx context.Context, cmd *cli.Command, cfg *config.Values) error {
args := cmd.Args().Slice()
if len(args) > 1 {
return fmt.Errorf("expected at most 1 container name, got %d", len(args))
}

containerManager, ok := ctx.Value(containerManagerKey).(containermanager.ContainerManager)
if !ok {
return errors.New("container manager not found in context")
}

listCmd := commands.NewListCommand(cfg, containerManager)
result, err := listCmd.Execute(ctx)
result, err := listCmd.Execute(ctx, &commands.ListOptions{ContainerName: cmd.Args().First()})
if err != nil {
return fmt.Errorf("failed to execute list command: %w", err)
}
Expand Down
26 changes: 20 additions & 6 deletions internal/inside-distrobox/assets/distrobox-export
Original file line number Diff line number Diff line change
Expand Up @@ -564,33 +564,47 @@ export_application()
# Add closing quote
# If a TryExec is present, we have to fake it as it will not work
# through the container separation
host_desktop_file="${host_home}/.local/share/applications/${desktop_home_file}"
sed "s|^Exec=\(.*\)|Exec=${container_command_prefix}\1|g" "${desktop_file}" |
sed "s|\(%.*\)|${extra_flags:+${extra_flags} }\1|g" |
sed "/^TryExec=.*/d" |
sed "/^DBusActivatable=true/d" |
sed "s|Name.*|&${exported_app_label}|g" \
> "/run/host${host_home}/.local/share/applications/${desktop_home_file}"
> "/run/host${host_desktop_file}"
# in the end we add the final quote we've opened in the "container_command_prefix"

if ! grep -q "StartupWMClass" "/run/host${host_home}/.local/share/applications/${desktop_home_file}"; then
if ! grep -q "StartupWMClass" "/run/host${host_desktop_file}"; then
printf "StartupWMClass=%s\n" "${exported_app}" >> "\
/run/host${host_home}/.local/share/applications/${desktop_home_file}"
/run/host${host_desktop_file}"
fi
# Add a desktop action to remove this exported launcher from host menus.
if grep -q "^Actions=" "/run/host${host_desktop_file}"; then
sed -i "s|^Actions=.*|&Remove;|g" \
"/run/host${host_desktop_file}"
Comment thread
iTrooz marked this conversation as resolved.
else
printf "Actions=Remove;\n" >> "/run/host${host_desktop_file}"
fi
cat << EOF >> "/run/host${host_desktop_file}"
[Desktop Action Remove]
Name=Remove exported app shortcut
Exec=sh -c 'if ${DISTROBOX_PATH:-"distrobox"} list "${container_name}"; then ${DISTROBOX_PATH:-"distrobox"} enter -n "${container_name}" -- distrobox-export --app "${exported_app}" --delete; else rm -f "${host_desktop_file}"; fi'
Comment thread
iTrooz marked this conversation as resolved.
Comment thread
iTrooz marked this conversation as resolved.
Comment thread
iTrooz marked this conversation as resolved.
EOF

# In case of an icon in a non canonical path, we need to replace the path
# in the desktop file.
if [ -n "${icon_file_absolute_path}" ]; then
sed -i "s|Icon=.*|Icon=${icon_file_absolute_path}|g" \
"/run/host${host_home}/.local/share/applications/${desktop_home_file}"
"/run/host${host_desktop_file}"
# we're done, go to next
continue
fi

# In case of an icon in a canonical path, but specified as an absolute
# we need to replace the path in the desktop file.
sed -i "s|Icon=/usr/share/|Icon=/run/host${host_home}/.local/share/|g" \
"/run/host${host_home}/.local/share/applications/${desktop_home_file}"
"/run/host${host_desktop_file}"
sed -i "s|pixmaps|icons|g" \
"/run/host${host_home}/.local/share/applications/${desktop_home_file}"
"/run/host${host_desktop_file}"
done

# Update the desktop files database to ensure exported applications will
Expand Down
3 changes: 2 additions & 1 deletion man/man1/distrobox-list.1
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ distrobox\-list lists available distroboxes.
It detects them and lists them separately from the rest of normal
containers.
.SH SYNOPSIS
\f[B]distrobox list\f[R]
\f[B]distrobox list\f[R] [container-name]
.IP
.EX
\-\-help/\-h: show this message
Expand All @@ -27,6 +27,7 @@ containers.
.IP
.EX
distrobox\-list
distrobox list my\-distrobox
.EE
.PP
You can also use environment variables to specify container manager
Expand Down
2 changes: 1 addition & 1 deletion pkg/commands/generate_entry.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func (c *GenerateEntryCommand) Execute(
switch {
case opts.All:
// Generate entries for all containers
listResult, err := c.listCommand.Execute(ctx)
listResult, err := c.listCommand.Execute(ctx, nil)
if err != nil {
return fmt.Errorf("failed to list containers: %w", err)
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/commands/generate_entry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ func TestGenerateAllEntriesCommand_Execute(t *testing.T) {
require.NoError(t, err, "GenerateAllEntriesCommand.Execute()")

// retrieve the list of containers to verify entries were created
listResult, err := listCmd.Execute(ctx)
listResult, err := listCmd.Execute(ctx, nil)
require.NoError(t, err, "ListCommand.Execute()")

// verify that each container has a corresponding desktop entry
Expand Down
22 changes: 19 additions & 3 deletions pkg/commands/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package commands

import (
"context"
"errors"
"fmt"
"slices"
"strings"
Expand All @@ -14,6 +15,12 @@ type ListResult struct {
Containers []containermanager.Container
}

var ErrListContainerNotFound = errors.New("cannot find distrobox")

type ListOptions struct {
ContainerName string
}

type ListCommand struct {
cfg *config.Values
containerManager containermanager.ContainerManager
Expand All @@ -26,19 +33,28 @@ func NewListCommand(cfg *config.Values, cm containermanager.ContainerManager) *L
}
}

func (c *ListCommand) Execute(ctx context.Context) (*ListResult, error) {
func (c *ListCommand) Execute(ctx context.Context, opts *ListOptions) (*ListResult, error) {
containers, err := c.containerManager.ListContainers(ctx)
if err != nil {
return nil, fmt.Errorf("failed while listing contaiers: %w", err)
return nil, fmt.Errorf("failed while listing containers: %w", err)
}

containerName := ""
if opts != nil {
containerName = opts.ContainerName
}

var distroboxes []containermanager.Container
for _, container := range containers {
if container.IsDistrobox() {
if container.IsDistrobox() && (containerName == "" || container.Name == containerName) {
distroboxes = append(distroboxes, container)
}
}

if containerName != "" && len(distroboxes) == 0 {
return nil, fmt.Errorf("%w %q", ErrListContainerNotFound, containerName)
}
Comment thread
iTrooz marked this conversation as resolved.

// Sort by container name to keep `distrobox list` output stable for downstream UIs; see https://github.com/89luca89/distrobox/issues/2071.
slices.SortFunc(distroboxes, func(a, b containermanager.Container) int {
return strings.Compare(a.Name, b.Name)
Expand Down
87 changes: 87 additions & 0 deletions pkg/commands/list_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package commands_test

import (
"context"
"errors"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/89luca89/distrobox/pkg/commands"
"github.com/89luca89/distrobox/pkg/config"
"github.com/89luca89/distrobox/pkg/containermanager"
"github.com/89luca89/distrobox/pkg/internal/testutil"
)

// mockListContainerManager wraps MockContainerManager and returns a fixed container list.
type mockListContainerManager struct {
*testutil.MockContainerManager
containers []containermanager.Container
}

func (m *mockListContainerManager) ListContainers(_ context.Context) ([]containermanager.Container, error) {
m.MockContainerManager.Spy.ListContainers = append(m.MockContainerManager.Spy.ListContainers, []any{})
return m.containers, nil
}

func newTestListCommand(containers []containermanager.Container) *commands.ListCommand {
mock := &mockListContainerManager{
MockContainerManager: &testutil.MockContainerManager{},
containers: containers,
}
return commands.NewListCommand(&config.Values{}, mock)
}

func distroboxContainer(name string) containermanager.Container {
return containermanager.Container{
ID: name + "-id",
Name: name,
Image: "ubuntu:latest",
Status: "Up",
Labels: map[string]string{"manager": "distrobox"},
}
}

func TestListCommand_NoOpts_ReturnsAllDistroboxes(t *testing.T) {
containers := []containermanager.Container{
distroboxContainer("box-a"),
distroboxContainer("box-b"),
{Name: "not-a-distrobox", Labels: map[string]string{}},
}

result, err := newTestListCommand(containers).Execute(context.Background(), nil)

require.NoError(t, err)
require.Len(t, result.Containers, 2)
assert.Equal(t, "box-a", result.Containers[0].Name)
assert.Equal(t, "box-b", result.Containers[1].Name)
}

func TestListCommand_MatchingName_ReturnsExactlyOne(t *testing.T) {
containers := []containermanager.Container{
distroboxContainer("box-a"),
distroboxContainer("box-b"),
}

result, err := newTestListCommand(containers).Execute(context.Background(), &commands.ListOptions{
ContainerName: "box-a",
})

require.NoError(t, err)
require.Len(t, result.Containers, 1)
assert.Equal(t, "box-a", result.Containers[0].Name)
}

func TestListCommand_NonMatchingName_ReturnsErrListContainerNotFound(t *testing.T) {
containers := []containermanager.Container{
distroboxContainer("box-a"),
}

_, err := newTestListCommand(containers).Execute(context.Background(), &commands.ListOptions{
ContainerName: "does-not-exist",
})

require.Error(t, err)
assert.True(t, errors.Is(err, commands.ErrListContainerNotFound))
}
2 changes: 1 addition & 1 deletion pkg/commands/rm.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func (c *RmCommand) Execute(ctx context.Context, options RmOptions) (*RmResult,
return nil, errors.New("prompter is required for interactive mode")
}

listResult, err := c.listCmd.Execute(ctx)
listResult, err := c.listCmd.Execute(ctx, nil)
if err != nil {
return nil, fmt.Errorf("failed while listing contaiers: %w", err)
Comment thread
iTrooz marked this conversation as resolved.
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/commands/stop.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func (c *StopCommand) Execute(ctx context.Context, opts *StopOptions) error {
var containerNames []string
switch {
case opts.All:
containers, err := c.listCmd.Execute(ctx)
containers, err := c.listCmd.Execute(ctx, nil)
if err != nil {
return fmt.Errorf("failed to list containers: %w", err)
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/commands/upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ func (c *UpgradeCommand) Execute(ctx context.Context, opts *UpgradeOptions) erro

switch {
case opts.All, opts.Running:
containers, err := c.listCmd.Execute(ctx)
containers, err := c.listCmd.Execute(ctx, nil)
if err != nil {
return fmt.Errorf("failed to list containers: %w", err)
}
Expand Down