diff --git a/CHANGELOG.md b/CHANGELOG.md index 320c5eb6..cd19991e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Add support to render template using `host` or `$(uname -n)` as the value of `overlay show --render`. #623 - Added command line parameters for credentials of a container registry - Add flag `--build` to `wwctl container copy`. #1378 +- Add `wwctl clean` to remove OCI cache and overlays from deleted nodes ### Changed diff --git a/internal/app/wwctl/clean/main.go b/internal/app/wwctl/clean/main.go new file mode 100644 index 00000000..fd69a699 --- /dev/null +++ b/internal/app/wwctl/clean/main.go @@ -0,0 +1,13 @@ +package clean + +import ( + "github.com/warewulf/warewulf/internal/pkg/api/clean" + + "github.com/spf13/cobra" +) + +func CobraRunE(vars *variables) func(cmd *cobra.Command, args []string) (err error) { + return func(cmd *cobra.Command, args []string) (err error) { + return clean.Clean() + } +} diff --git a/internal/app/wwctl/clean/main_test.go b/internal/app/wwctl/clean/main_test.go new file mode 100644 index 00000000..0d82e301 --- /dev/null +++ b/internal/app/wwctl/clean/main_test.go @@ -0,0 +1,33 @@ +package clean + +import ( + "path" + "testing" + + warewulfconf "github.com/warewulf/warewulf/internal/pkg/config" + + "github.com/stretchr/testify/assert" + "github.com/warewulf/warewulf/internal/pkg/testenv" + "github.com/warewulf/warewulf/internal/pkg/wwlog" +) + +func Test_Clean(t *testing.T) { + wwlog.SetLogLevel(wwlog.DEBUG) + env := testenv.New(t) + env.WriteFile(t, "etc/warewulf/nodes.conf", + `WW_INTERNAL: 45 +nodeprofiles: {} +nodes: + node1: {} +`) + wwconf := warewulfconf.Get() + env.WriteFileAbs(t, path.Join(wwconf.Paths.WWProvisiondir, "overlays/node1/__SYSTEM__.img"), "Fake System") + env.WriteFileAbs(t, path.Join(wwconf.Paths.WWProvisiondir, "overlays/node2/__SYSTEM__.img"), "Fake System") + env.WriteFileAbs(t, path.Join(wwconf.Paths.Cachedir, "warewulf/test"), "Nothing to see here") + baseCmd := GetCommand() + err := baseCmd.Execute() + assert.NoError(t, err) + assert.FileExists(t, path.Join(wwconf.Paths.WWProvisiondir, "overlays/node1/__SYSTEM__.img")) + assert.NoFileExists(t, path.Join(wwconf.Paths.WWProvisiondir, "overlays/node2/__SYSTEM__.img")) + assert.NoDirExists(t, path.Join(wwconf.Paths.Cachedir, "warewulf")) +} diff --git a/internal/app/wwctl/clean/root.go b/internal/app/wwctl/clean/root.go new file mode 100644 index 00000000..d6515dce --- /dev/null +++ b/internal/app/wwctl/clean/root.go @@ -0,0 +1,20 @@ +package clean + +import ( + "github.com/spf13/cobra" +) + +type variables struct { +} + +func GetCommand() *cobra.Command { + vars := variables{} + baseCmd := &cobra.Command{ + DisableFlagsInUseLine: true, + Use: "clean", + Short: "Clean up", + Long: "This command cleans the OCI cache and removes leftovers from deleted nodes", + RunE: CobraRunE(&vars), + } + return baseCmd +} diff --git a/internal/app/wwctl/root.go b/internal/app/wwctl/root.go index 0b68398e..fb730d48 100644 --- a/internal/app/wwctl/root.go +++ b/internal/app/wwctl/root.go @@ -4,6 +4,7 @@ import ( "os" "github.com/spf13/cobra" + "github.com/warewulf/warewulf/internal/app/wwctl/clean" "github.com/warewulf/warewulf/internal/app/wwctl/configure" "github.com/warewulf/warewulf/internal/app/wwctl/container" "github.com/warewulf/warewulf/internal/app/wwctl/genconf" @@ -58,6 +59,7 @@ func init() { rootCmd.AddCommand(version.GetCommand()) rootCmd.AddCommand(ssh.GetCommand()) rootCmd.AddCommand(genconf.GetCommand()) + rootCmd.AddCommand(clean.GetCommand()) } // GetRootCommand returns the root cobra.Command for the application. diff --git a/internal/pkg/api/clean/Clean.go b/internal/pkg/api/clean/Clean.go new file mode 100644 index 00000000..025d567f --- /dev/null +++ b/internal/pkg/api/clean/Clean.go @@ -0,0 +1,47 @@ +package clean + +import ( + "os" + "path" + + _ "golang.org/x/exp/slices" + + warewulfconf "github.com/warewulf/warewulf/internal/pkg/config" + "github.com/warewulf/warewulf/internal/pkg/node" + "github.com/warewulf/warewulf/internal/pkg/util" + "github.com/warewulf/warewulf/internal/pkg/wwlog" +) + +/* +Cleans up the OCI cache and remains of deleted nodes +*/ +func Clean() (err error) { + warewulfconf := warewulfconf.Get() + wwlog.Verbose("removing oci cache dir: %s", path.Join(warewulfconf.Paths.Cachedir+"/warewulf")) + err = os.RemoveAll(path.Join(warewulfconf.Paths.Cachedir + "/warewulf")) + if err != nil { + return err + } + nodeDB, err := node.New() + if err != nil { + return err + } + nodes := nodeDB.ListAllNodes() + dirList, err := os.ReadDir(path.Join(warewulfconf.Paths.WWProvisiondir, "overlays/")) + if err != nil { + return err + } + for _, item := range dirList { + if !item.IsDir() { + continue + } + if !util.InSlice(nodes, item.Name()) { + wwlog.Verbose("removing overlays of delete node: %s", item.Name()) + err = os.RemoveAll(path.Join(warewulfconf.Paths.WWProvisiondir, "overlays/", item.Name())) + if err != nil { + return err + } + } + } + return +} diff --git a/internal/pkg/testenv/testenv.go b/internal/pkg/testenv/testenv.go index 65e3ff94..d5656616 100644 --- a/internal/pkg/testenv/testenv.go +++ b/internal/pkg/testenv/testenv.go @@ -9,6 +9,7 @@ import ( "os" "path" "path/filepath" + "strings" "testing" "time" @@ -41,6 +42,7 @@ const Systemddir = "usr/lib/systemd/system" const WWOverlaydir = "var/lib/warewulf/overlays" const WWChrootdir = "var/lib/warewulf/chroots" const WWProvisiondir = "srv/warewulf" +const Cachedir = "cache" // New creates a test environment in a temporary directory and configures // Warewulf to use it. @@ -76,6 +78,7 @@ func New(t *testing.T) (env *TestEnv) { conf.Paths.WWOverlaydir = env.GetPath(WWOverlaydir) conf.Paths.WWChrootdir = env.GetPath(WWChrootdir) conf.Paths.WWProvisiondir = env.GetPath(WWProvisiondir) + conf.Paths.Cachedir = env.GetPath(Cachedir) conf.Paths.WWClientdir = "/warewulf" for _, confPath := range []string{ @@ -161,3 +164,26 @@ func (env *TestEnv) RemoveAll(t *testing.T) { err := os.RemoveAll(env.BaseDir) assert.NoError(t, err) } + +// Writes to absolute path, but checks if given file name +// is within testenv. +// +// Asserts no errors occur. +func (env *TestEnv) WriteFileAbs(t *testing.T, fileName string, content string) { + ok := strings.HasPrefix(fileName, env.BaseDir) + if !ok { + assert.Fail(t, "given filename is not in testenv") + } + dirName := filepath.Dir(fileName) + err := os.MkdirAll(dirName, 0755) + assert.NoError(t, err) + f, err := os.Create(fileName) + assert.NoError(t, err) + defer f.Close() + _, err = f.WriteString(content) + assert.NoError(t, err) + err = os.Chtimes(fileName, + time.Date(2006, time.February, 1, 3, 4, 5, 0, time.UTC), + time.Date(2006, time.February, 1, 3, 4, 5, 0, time.UTC)) + assert.NoError(t, err) +}