Add wwctl container <exec|shell> --build=false

- Closes #1490
- Closes #1489

Signed-off-by: Jonathon Anderson <janderson@ciq.com>
This commit is contained in:
Jonathon Anderson
2024-12-19 02:10:59 -07:00
committed by Christian Goll
parent ebf53bae22
commit 1a3b9a16ed
8 changed files with 46 additions and 24 deletions

View File

@@ -33,6 +33,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Support defining a symlink with an overlay template. #1303
- New "localtime" overlay to define the system time zone. #1303
- Add support for nested profiles. #1572, #1598
- Adds `wwctl container <exec|shell> --build=false` to prevent automatically (re)building the container. #1490, #1489
### Changed

View File

@@ -21,9 +21,6 @@ import (
)
const exitEval = `$(VALU="$?" ; if [ $VALU == 0 ]; then echo write; else echo discard; fi)`
const msgStr = `Container image is rebuilt depending on the exit status of the last command.
Run "true" or "false" to enforce or abort image rebuild.`
func CobraRunE(cmd *cobra.Command, args []string) (err error) {
if os.Getpid() != 1 {
@@ -82,7 +79,6 @@ func CobraRunE(cmd *cobra.Command, args []string) (err error) {
return fmt.Errorf("failed to mount: %w", err)
}
ps1Str := fmt.Sprintf("[%s|%s] Warewulf> ", containerName, exitEval)
wwlog.Info(msgStr)
if len(lowerObjects) != 0 && nodename == "" {
options := fmt.Sprintf("lowerdir=%s,upperdir=%s,workdir=%s",
path.Join(runDir, "lower"), containerPath, path.Join(runDir, "work"))

View File

@@ -145,14 +145,16 @@ func CobraRunE(cmd *cobra.Command, args []string) error {
if userdbChanged && SyncUser {
err = container.SyncUids(containerName, false)
if err != nil {
wwlog.Error("Error in user sync, fix error and run 'syncuser' manually, but trying to build container: %s", err)
wwlog.Error("Error in user sync, fix error and run 'syncuser' manually: %s", err)
}
}
fmt.Printf("Rebuilding container...\n")
err = container.Build(containerName, false)
if err != nil {
return fmt.Errorf("could not build container %s: %s", containerName, err)
if Build {
wwlog.Info("Building container image: %s", containerName)
err = container.Build(containerName, false)
if err != nil {
return fmt.Errorf("could not build container image: %s: %s", containerName, err)
}
}
return nil
}

View File

@@ -2,15 +2,14 @@ package exec
import (
"bytes"
"os"
"os/exec"
"testing"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
"github.com/warewulf/warewulf/internal/pkg/testenv"
"path"
"strings"
"github.com/warewulf/warewulf/internal/pkg/testenv"
"github.com/warewulf/warewulf/internal/pkg/warewulfd"
)
@@ -25,7 +24,7 @@ func mockChildCmd(cmd *cobra.Command, args []string) error {
func Test_Exec(t *testing.T) {
env := testenv.New(t)
defer env.RemoveAll(t)
env.MkdirAll(t, path.Join(testenv.WWChrootdir, "test/rootfs"))
env.MkdirAll(t, "/var/lib/warewulf/chroots/test/rootfs")
childCommandFunc = mockChildCmd
defer func() {
childCommandFunc = runChildCmd
@@ -36,40 +35,55 @@ func Test_Exec(t *testing.T) {
name string
args []string
stdout string
build bool
}{
{
name: "plain test",
args: []string{"test", "/bin/true"},
stdout: `--loglevel 20 container exec __child test -- /bin/true`,
build: true,
},
{
name: "test with --bind",
args: []string{"test", "--bind", "/tmp", "/bin/true"},
stdout: `--loglevel 20 container exec __child test --bind /tmp -- /bin/true`,
build: true,
},
{
name: "test with --node",
args: []string{"test", "--node", "node1", "/bin/true"},
stdout: `--loglevel 20 container exec __child test --node node1 -- /bin/true`,
build: true,
},
{
name: "test with --build=false",
args: []string{"test", "--build=false", "/bin/true"},
stdout: `--loglevel 20 container exec __child test -- /bin/true`,
build: false,
},
{
name: "test with --node and --bind",
args: []string{"test", "--bind", "/tmp", "--node", "node1", "/bin/true"},
stdout: `--loglevel 20 container exec __child test --bind /tmp --node node1 -- /bin/true`,
build: true,
},
{
name: "test with complex command",
args: []string{"test", "/bin/bash", "echo 'hello'"},
stdout: `--loglevel 20 container exec __child test -- /bin/bash echo 'hello'`,
build: true,
},
}
for _, tt := range tests {
t.Logf("Running test: %s\n", tt.name)
t.Run(tt.name, func(t *testing.T) {
defer func() {
binds = []string{}
nodeName = ""
Build = true
SyncUser = false
os.Remove(env.GetPath("/srv/warewulf/container/test.img"))
os.Remove(env.GetPath("/srv/warewulf/container/test.img.gz"))
}()
cmd := GetCommand()
cmd.SetArgs(tt.args)
@@ -77,14 +91,15 @@ func Test_Exec(t *testing.T) {
err := bytes.NewBufferString("")
cmd.SetOut(out)
cmd.SetErr(err)
if err := cmd.Execute(); err != nil {
t.Errorf("Received error when running command, err: %v", err)
t.FailNow()
}
assert.NotEmpty(t, out.String(), "os.stdout should not be empty")
if !strings.Contains(out.String(), tt.stdout) {
t.Errorf("Got wrong output, got:\n '%s'\n, but want:\n '%s'\n", out.String(), tt.stdout)
t.FailNow()
assert.NoError(t, cmd.Execute())
assert.NotEmpty(t, out.String())
assert.Contains(t, out.String(), tt.stdout)
if tt.build {
assert.FileExists(t, env.GetPath("/srv/warewulf/container/test.img"))
assert.FileExists(t, env.GetPath("/srv/warewulf/container/test.img.gz"))
} else {
assert.NoFileExists(t, env.GetPath("/srv/warewulf/container/test.img"))
assert.NoFileExists(t, env.GetPath("/srv/warewulf/container/test.img.gz"))
}
})
}

View File

@@ -26,6 +26,7 @@ var (
FParseErrWhitelist: cobra.FParseErrWhitelist{UnknownFlags: true},
}
SyncUser bool
Build bool
binds []string
nodeName string
)
@@ -37,6 +38,7 @@ Bind a local path which must exist into the container. If destination is not
set, uses the same path as source. "ro" binds read-only. "copy" temporarily
copies the file into the container.`)
baseCmd.PersistentFlags().BoolVar(&SyncUser, "syncuser", false, "Synchronize UIDs/GIDs from host to container")
baseCmd.PersistentFlags().BoolVar(&Build, "build", true, "(Re)build the container image automatically")
baseCmd.PersistentFlags().StringVarP(&nodeName, "node", "n", "", "Create a read only view of the container for the given node")
}

View File

@@ -44,5 +44,9 @@ func CobraRunE(cmd *cobra.Command, args []string) error {
cntexec.SetBinds(binds)
cntexec.SetNode(nodeName)
cntexec.SyncUser = syncUser
cntexec.Build = build
if cntexec.Build {
wwlog.Info("Container image build will be skipped if the shell ends with a non-zero exit code.")
}
return cntexec.CobraRunE(cmd, allargs)
}

View File

@@ -26,6 +26,7 @@ var (
binds []string
nodeName string
syncUser bool
build bool
)
func init() {
@@ -35,6 +36,7 @@ set, uses the same path as source. "ro" binds read-only. "copy" temporarily
copies the file into the container.`)
baseCmd.PersistentFlags().StringVarP(&nodeName, "node", "n", "", "Create a read only view of the container for the given node")
baseCmd.PersistentFlags().BoolVar(&syncUser, "syncuser", false, "Synchronize UIDs/GIDs from host to container")
baseCmd.PersistentFlags().BoolVar(&build, "build", true, "(Re)build the container image automatically")
}
// GetRootCommand returns the root cobra.Command for the application.

View File

@@ -229,7 +229,7 @@ when using the exec command. This works as follows:
.. code-block:: console
# wwctl container exec --bind /tmp:/mnt rocky-8 /bin/sh
# wwctl container shell --bind /tmp:/mnt rocky-8
[rocky-8] Warewulf>
.. note::
@@ -257,7 +257,7 @@ can be specified in ``warewulf.conf``:
When the command completes, if anything within the container changed,
the container will be rebuilt into a bootable static object
automatically.
automatically. (To skip the automatic container rebuild, specify ``--build=false``.)
If the files ``/etc/passwd`` or ``/etc/group`` were updated, there
will be an additional check to confirm if the users are in sync as