diff --git a/internal/app/wwctl/overlay/delete/main.go b/internal/app/wwctl/overlay/delete/main.go index ffa5d669..3aaa13b0 100644 --- a/internal/app/wwctl/overlay/delete/main.go +++ b/internal/app/wwctl/overlay/delete/main.go @@ -16,5 +16,10 @@ func CobraRunE(cmd *cobra.Command, args []string) error { if err != nil { return err } - return myOverlay.DeleteFile(fileName, Force, Parents) + + if fileName == "" { + return myOverlay.Delete(Force) + } else { + return myOverlay.DeleteFile(fileName, Force, Parents) + } } diff --git a/internal/pkg/overlay/overlay.go b/internal/pkg/overlay/overlay.go index 00e64ebf..015e744b 100644 --- a/internal/pkg/overlay/overlay.go +++ b/internal/pkg/overlay/overlay.go @@ -159,70 +159,72 @@ func (overlay Overlay) AddFile(filePath string, content []byte, parents bool, fo return os.WriteFile(fullPath, content, 0o644) } +func (overlay Overlay) Delete(force bool) (err error) { + wwlog.Info("Deleting overlay %s, force: %v", overlay.Name(), force) + if overlay.IsDistributionOverlay() { + return fmt.Errorf("cannot delete a distribution overlay: %s", overlay.Name()) + } + if force { + err := os.RemoveAll(overlay.Path()) + if err != nil { + return fmt.Errorf("failed to delete overlay forcely: %w", err) + } + } else { + // remove rootfs at first + if err = os.Remove(overlay.Rootfs()); err != nil { + return fmt.Errorf("failed to delete overlay: %w", err) + } + if overlay.Exists() { + if err = os.Remove(overlay.Path()); err != nil { + return fmt.Errorf("failed to delete overlay: %w", err) + } + } + } + return nil +} + // DeleteFile deletes a file or the entire overlay directory. // before deletion. func (overlay Overlay) DeleteFile(filePath string, force, cleanup bool) (err error) { wwlog.Info("Deleting file %s from overlay %s, force: %v, cleanup: %v", filePath, overlay.Name(), force, cleanup) - if filePath == "" { - if overlay.IsDistributionOverlay() { - return fmt.Errorf("cannot delete a distribution overlay: %s", overlay.Name()) + // first check if file exists + if !util.IsFile(overlay.File(filePath)) { + return fmt.Errorf("file %s does not exist in overlay %s", filePath, overlay.Name()) + } + if overlay.IsDistributionOverlay() { + siteOverlay, err := overlay.CloneSiteOverlay() + if err != nil { + return fmt.Errorf("failed to clone distribution overlay '%s' to site overlay: %w", overlay.Name(), err) } - if force { - err := os.RemoveAll(overlay.Path()) - if err != nil { - return fmt.Errorf("failed to delete overlay forcely: %w", err) - } - } else { - // remove rootfs at first - if err = os.Remove(overlay.Rootfs()); err != nil { - return fmt.Errorf("failed to delete overlay: %w", err) - } - if overlay.Exists() { - if err = os.Remove(overlay.Path()); err != nil { - return fmt.Errorf("failed to delete overlay: %w", err) - } - } + // replace the overlay with newly created siteOverlay + overlay = siteOverlay + } + fullPath := overlay.File(filePath) + if force { + if err := os.RemoveAll(fullPath); err != nil { + return fmt.Errorf("failed to delete file %s from overlay %s: %w", filePath, overlay.Name(), err) } } else { - // first check if file exists - if !util.IsFile(overlay.File(filePath)) { - return fmt.Errorf("file %s does not exist in overlay %s", filePath, overlay.Name()) - } - if overlay.IsDistributionOverlay() { - siteOverlay, err := overlay.CloneSiteOverlay() - if err != nil { - return fmt.Errorf("failed to clone distribution overlay '%s' to site overlay: %w", overlay.Name(), err) - } - // replace the overlay with newly created siteOverlay - overlay = siteOverlay - } - fullPath := overlay.File(filePath) - if force { - if err := os.RemoveAll(fullPath); err != nil { - return fmt.Errorf("failed to delete file %s from overlay %s: %w", filePath, overlay.Name(), err) - } - } else { - if err := os.Remove(fullPath); err != nil { - return fmt.Errorf("failed to delete file %s from overlay %s: %w", filePath, overlay.Name(), err) - } + if err := os.Remove(fullPath); err != nil { + return fmt.Errorf("failed to delete file %s from overlay %s: %w", filePath, overlay.Name(), err) } + } - if cleanup { - // cleanup the empty parents - i := path.Dir(fullPath) - for i != overlay.Rootfs() { - wwlog.Debug("Evaluating directory to remove: %s", i) - err := os.Remove(i) - if err != nil { - // if the directory is not empty, we stop here - if !os.IsNotExist(err) { - wwlog.Debug("Could not remove directory %s: %v", i, err) - } - break + if cleanup { + // cleanup the empty parents + i := path.Dir(fullPath) + for i != overlay.Rootfs() { + wwlog.Debug("Evaluating directory to remove: %s", i) + err := os.Remove(i) + if err != nil { + // if the directory is not empty, we stop here + if !os.IsNotExist(err) { + wwlog.Debug("Could not remove directory %s: %v", i, err) } - wwlog.Debug("Removed empty directory: %s", i) - i = path.Dir(i) + break } + wwlog.Debug("Removed empty directory: %s", i) + i = path.Dir(i) } } return nil diff --git a/internal/pkg/warewulfd/api/overlay.go b/internal/pkg/warewulfd/api/overlay.go index 44d3cbd6..09d3bf85 100644 --- a/internal/pkg/warewulfd/api/overlay.go +++ b/internal/pkg/warewulfd/api/overlay.go @@ -232,7 +232,7 @@ func createOverlay() usecase.Interactor { func deleteOverlay() usecase.Interactor { type deleteOverlayInput struct { Name string `path:"name" required:"true" description:"Name of overlay to delete"` - Force bool `query:"force" default:"false" description:"Whether to delete a non-empty overlay, default:'false'"` + Force bool `query:"force" default:"false" description:"Whether to delete a non-empty or in-use overlay, default:'false'"` } u := usecase.NewInteractor(func(ctx context.Context, input deleteOverlayInput, output *OverlayResponse) error { @@ -241,7 +241,11 @@ func deleteOverlay() usecase.Interactor { if err != nil { return err } - isSiteOverlay := overlay_.IsSiteOverlay() + + if overlay_.IsDistributionOverlay() { + return status.Wrap(fmt.Errorf("distribution overlay: %s", overlay_.Name()), status.InvalidArgument) + } + if !input.Force { if registry, err := node.New(); err != nil { return err @@ -255,11 +259,11 @@ func deleteOverlay() usecase.Interactor { } } } - if err := os.RemoveAll(overlay_.Path()); err != nil { + if err := overlay_.Delete(input.Force); err != nil { return status.Wrap(fmt.Errorf("failed to remove overlay: %v", err), status.Code(409)) } *output = *NewOverlayResponse(input.Name) - output.Site = isSiteOverlay + output.Site = overlay_.IsSiteOverlay() return nil }) u.SetTitle("Delete an overlay") diff --git a/internal/pkg/warewulfd/api/overlay_test.go b/internal/pkg/warewulfd/api/overlay_test.go index 91e1c0da..d9991fd1 100644 --- a/internal/pkg/warewulfd/api/overlay_test.go +++ b/internal/pkg/warewulfd/api/overlay_test.go @@ -108,7 +108,7 @@ var overlayTests = map[string]struct { response: `{"files":null, "site":true}`, }, - "delete overlay": { + "force delete site overlay": { initFiles: map[string]string{ "/var/lib/warewulf/overlays/test/": "", }, @@ -117,6 +117,16 @@ var overlayTests = map[string]struct { }, response: `{"files":[], "site":true}`, }, + + "force delete distribution overlay": { + initFiles: map[string]string{ + "/usr/share/warewulf/overlays/test/email.ww": sampleTemplate, + }, + request: func(serverURL string) (*http.Request, error) { + return http.NewRequest(http.MethodDelete, serverURL+"/api/overlays/test?force=true", nil) + }, + status: 400, + }, } func TestOverlayAPI(t *testing.T) { @@ -160,8 +170,10 @@ func TestOverlayAPI(t *testing.T) { assert.NoError(t, err) assert.NoError(t, resp.Body.Close()) - ja := jsonassert.New(t) - ja.Assertf(string(body), tt.response) //nolint:govet // tt.response is used as a format string with special tokens + if tt.response != "" { + ja := jsonassert.New(t) + ja.Assertf(string(body), tt.response) //nolint:govet // tt.response is used as a format string with special tokens + } for _, fileName := range tt.resultFiles { assert.DirExists(t, env.GetPath(fileName))