Add wwctl container <exec|shell> --build=false
- Closes #1490 - Closes #1489 Signed-off-by: Jonathon Anderson <janderson@ciq.com>
This commit is contained in:
committed by
Christian Goll
parent
ebf53bae22
commit
1a3b9a16ed
@@ -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
|
||||
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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"))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user