mirror of
https://github.com/89luca89/distrobox.git
synced 2026-08-17 16:34:42 -05:00
288 lines
8.9 KiB
Go
288 lines
8.9 KiB
Go
// SPDX-License-Identifier: GPL-3.0-only
|
|
//
|
|
// This file is part of the distrobox project:
|
|
// https://github.com/89luca89/distrobox
|
|
//
|
|
// Copyright (C) 2021 distrobox contributors
|
|
//
|
|
// distrobox is free software; you can redistribute it and/or modify it
|
|
// under the terms of the GNU General Public License version 3
|
|
// as published by the Free Software Foundation.
|
|
//
|
|
// distrobox is distributed in the hope that it will be useful, but
|
|
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
// General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with distrobox; if not, see <http://www.gnu.org/licenses/>.
|
|
|
|
package cli
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"github.com/urfave/cli/v3"
|
|
|
|
"github.com/89luca89/distrobox/pkg/config"
|
|
"github.com/89luca89/distrobox/pkg/containermanager"
|
|
"github.com/89luca89/distrobox/pkg/ui"
|
|
)
|
|
|
|
// migrateSpyContainerManager is a minimal spy that records calls to the
|
|
// methods used by the migrate command. All other methods are no-ops.
|
|
type migrateSpyContainerManager struct {
|
|
stops [][]string
|
|
creates []containermanager.CreateOptions
|
|
removes []string
|
|
commits []string
|
|
|
|
inspectResult *containermanager.InspectResult
|
|
|
|
// needsMigrationResult, when non-nil, overrides the default return
|
|
// of NeedsMigration. The default is true (migrate), matching a v1
|
|
// container with no version label.
|
|
needsMigrationResult *bool
|
|
}
|
|
|
|
func (s *migrateSpyContainerManager) Name() string { return "spy" }
|
|
func (s *migrateSpyContainerManager) CloneAsRoot() containermanager.ContainerManager { return s }
|
|
func (s *migrateSpyContainerManager) Enter(_ context.Context, _ containermanager.EnterOptions, _ *ui.Progress, _ *ui.Printer) error {
|
|
return nil
|
|
}
|
|
func (s *migrateSpyContainerManager) ListContainers(_ context.Context) ([]containermanager.Container, error) {
|
|
return nil, nil
|
|
}
|
|
func (s *migrateSpyContainerManager) Create(_ context.Context, opts containermanager.CreateOptions) error {
|
|
s.creates = append(s.creates, opts)
|
|
return nil
|
|
}
|
|
func (s *migrateSpyContainerManager) Remove(_ context.Context, name string, _ containermanager.RmOptions) error {
|
|
s.removes = append(s.removes, name)
|
|
return nil
|
|
}
|
|
func (s *migrateSpyContainerManager) Exists(_ context.Context, _ string) bool { return true }
|
|
func (s *migrateSpyContainerManager) Stop(_ context.Context, names []string) error {
|
|
s.stops = append(s.stops, names)
|
|
return nil
|
|
}
|
|
func (s *migrateSpyContainerManager) InspectContainer(_ context.Context, _ string) (*containermanager.InspectResult, error) {
|
|
if s.inspectResult != nil {
|
|
return s.inspectResult, nil
|
|
}
|
|
return &containermanager.InspectResult{}, nil
|
|
}
|
|
func (s *migrateSpyContainerManager) Commit(_ context.Context, containerID string, _ string) error {
|
|
s.commits = append(s.commits, containerID)
|
|
return nil
|
|
}
|
|
func (s *migrateSpyContainerManager) ImageExists(_ context.Context, _ string) bool { return true }
|
|
func (s *migrateSpyContainerManager) PullImage(_ context.Context, _ string, _ string, _ bool) error {
|
|
return nil
|
|
}
|
|
func (s *migrateSpyContainerManager) NeedsMigration(_ context.Context, _ string) (bool, error) {
|
|
if s.needsMigrationResult != nil {
|
|
return *s.needsMigrationResult, nil
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
// runMigrate runs the migrate subcommand with the given argv (starting from
|
|
// "migrate") against a spy container manager. It returns the spy so the
|
|
// caller can assert on recorded calls.
|
|
func runMigrate(t *testing.T, spy *migrateSpyContainerManager, argv ...string) {
|
|
t.Helper()
|
|
|
|
cfg := config.DefaultValues()
|
|
cfg.NonInteractive = true
|
|
|
|
cmd := newMigrateCommand(cfg)
|
|
// Override the Before hook to inject our spy instead of detecting a
|
|
// real container manager, matching the pattern in enter_internal_test.go.
|
|
cmd.Before = func(ctx context.Context, _ *cli.Command) (context.Context, error) {
|
|
return context.WithValue(ctx, containerManagerKey, spy), nil
|
|
}
|
|
|
|
root := &cli.Command{Commands: []*cli.Command{cmd}}
|
|
full := append([]string{"distrobox"}, argv...)
|
|
|
|
if err := root.Run(context.Background(), full); err != nil {
|
|
t.Fatalf("unexpected error running migrate: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestNewMigrateCommand_HasFlags(t *testing.T) {
|
|
cfg := config.DefaultValues()
|
|
cmd := newMigrateCommand(cfg)
|
|
|
|
if cmd.Name != "migrate" {
|
|
t.Errorf("expected name 'migrate', got %q", cmd.Name)
|
|
}
|
|
|
|
flagNames := map[string]bool{
|
|
"all": false,
|
|
"force": false,
|
|
"dry-run": false,
|
|
"yes": false,
|
|
}
|
|
for _, flag := range cmd.Flags {
|
|
for name := range flagNames {
|
|
if flag.Names()[0] == name {
|
|
flagNames[name] = true
|
|
}
|
|
}
|
|
}
|
|
for name, found := range flagNames {
|
|
if !found {
|
|
t.Errorf("expected flag --%s to be defined", name)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestMigrateAction_NoContainerSpecified_ReturnsError(t *testing.T) {
|
|
t.Setenv("DBX_SCRIPTS_DIR", t.TempDir())
|
|
|
|
cfg := config.DefaultValues()
|
|
cfg.ContainerName = ""
|
|
cfg.NonInteractive = true
|
|
|
|
spy := &migrateSpyContainerManager{}
|
|
cmd := newMigrateCommand(cfg)
|
|
cmd.Before = func(ctx context.Context, _ *cli.Command) (context.Context, error) {
|
|
return context.WithValue(ctx, containerManagerKey, spy), nil
|
|
}
|
|
|
|
root := &cli.Command{Commands: []*cli.Command{cmd}}
|
|
args := []string{"distrobox", "migrate"}
|
|
|
|
err := root.Run(context.Background(), args)
|
|
if err == nil {
|
|
t.Fatal("expected error when no container specified and no --all")
|
|
}
|
|
}
|
|
|
|
func TestMigrateAction_DryRun(t *testing.T) {
|
|
t.Setenv("DBX_SCRIPTS_DIR", t.TempDir())
|
|
|
|
spy := &migrateSpyContainerManager{
|
|
inspectResult: &containermanager.InspectResult{
|
|
ContainerID: "abc123",
|
|
ContainerStatus: "exited",
|
|
ContainerImage: "alpine:latest",
|
|
NetworkMode: "host",
|
|
IpcMode: "host",
|
|
PidMode: "host",
|
|
Env: []string{"HOME=/home/testuser"},
|
|
Cmd: []string{
|
|
"--verbose", "--name", "testuser", "--user", "1000",
|
|
"--group", "1000", "--home", "/home/testuser",
|
|
"--init", "0", "--nvidia", "0",
|
|
"--pre-init-hooks", "", "--additional-packages", "",
|
|
"--", "",
|
|
},
|
|
Mounts: []containermanager.MountInfo{
|
|
{Source: "/usr/lib/distrobox/distrobox-init", Destination: "/usr/bin/entrypoint"},
|
|
},
|
|
},
|
|
}
|
|
|
|
runMigrate(t, spy, "migrate", "--dry-run", "my-box")
|
|
|
|
// Dry run: no side effects
|
|
if len(spy.stops) != 0 {
|
|
t.Errorf("expected 0 Stop calls, got %d", len(spy.stops))
|
|
}
|
|
if len(spy.commits) != 0 {
|
|
t.Errorf("expected 0 Commit calls, got %d", len(spy.commits))
|
|
}
|
|
if len(spy.removes) != 0 {
|
|
t.Errorf("expected 0 Remove calls, got %d", len(spy.removes))
|
|
}
|
|
if len(spy.creates) != 0 {
|
|
t.Errorf("expected 0 Create calls, got %d", len(spy.creates))
|
|
}
|
|
}
|
|
|
|
func TestMigrateAction_V2Container_Skipped(t *testing.T) {
|
|
v2ScriptDir := t.TempDir()
|
|
t.Setenv("DBX_SCRIPTS_DIR", v2ScriptDir)
|
|
|
|
notNeeded := false
|
|
spy := &migrateSpyContainerManager{
|
|
inspectResult: &containermanager.InspectResult{
|
|
ContainerID: "abc123",
|
|
ContainerStatus: "exited",
|
|
ContainerImage: "alpine:latest",
|
|
NetworkMode: "host",
|
|
IpcMode: "host",
|
|
PidMode: "host",
|
|
Env: []string{"HOME=/home/testuser"},
|
|
Labels: map[string]string{
|
|
containermanager.VersionLabelKey: "2",
|
|
},
|
|
},
|
|
needsMigrationResult: ¬Needed,
|
|
}
|
|
|
|
runMigrate(t, spy, "migrate", "my-box")
|
|
|
|
// Already migrated: no side effects
|
|
if len(spy.stops) != 0 {
|
|
t.Errorf("expected 0 Stop calls, got %d", len(spy.stops))
|
|
}
|
|
if len(spy.commits) != 0 {
|
|
t.Errorf("expected 0 Commit calls, got %d", len(spy.commits))
|
|
}
|
|
if len(spy.removes) != 0 {
|
|
t.Errorf("expected 0 Remove calls, got %d", len(spy.removes))
|
|
}
|
|
if len(spy.creates) != 0 {
|
|
t.Errorf("expected 0 Create calls, got %d", len(spy.creates))
|
|
}
|
|
}
|
|
|
|
func TestMigrateAction_ForceRecreates(t *testing.T) {
|
|
t.Setenv("USER", "testuser")
|
|
t.Setenv("HOME", "/home/testuser")
|
|
t.Setenv("SHELL", "/bin/sh")
|
|
|
|
v2ScriptDir := t.TempDir()
|
|
t.Setenv("DBX_SCRIPTS_DIR", v2ScriptDir)
|
|
|
|
spy := &migrateSpyContainerManager{
|
|
inspectResult: &containermanager.InspectResult{
|
|
ContainerID: "abc123",
|
|
ContainerStatus: "exited",
|
|
ContainerImage: "alpine:latest",
|
|
NetworkMode: "host",
|
|
IpcMode: "host",
|
|
PidMode: "host",
|
|
Env: []string{"HOME=/home/testuser"},
|
|
Cmd: []string{
|
|
"--verbose", "--name", "testuser", "--user", "1000",
|
|
"--group", "1000", "--home", "/home/testuser",
|
|
"--init", "0", "--nvidia", "0",
|
|
"--pre-init-hooks", "", "--additional-packages", "",
|
|
"--", "",
|
|
},
|
|
Mounts: []containermanager.MountInfo{
|
|
{Source: v2ScriptDir + "/distrobox-init", Destination: "/usr/bin/entrypoint"},
|
|
},
|
|
},
|
|
}
|
|
|
|
runMigrate(t, spy, "migrate", "--force", "my-box")
|
|
|
|
// Force: should recreate even if already v2
|
|
if len(spy.commits) != 1 {
|
|
t.Errorf("expected 1 Commit call, got %d", len(spy.commits))
|
|
}
|
|
if len(spy.removes) != 1 {
|
|
t.Errorf("expected 1 Remove call, got %d", len(spy.removes))
|
|
}
|
|
if len(spy.creates) != 1 {
|
|
t.Errorf("expected 1 Create call, got %d", len(spy.creates))
|
|
}
|
|
}
|