Fix warewulfd api behavior when deleting distribution overlay

Signed-off-by: Jonathon Anderson <janderson@ciq.com>
This commit is contained in:
Jonathon Anderson
2025-08-27 22:02:22 -06:00
parent e609c599a1
commit 6fd858899d
4 changed files with 84 additions and 61 deletions

View File

@@ -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)
}
}

View File

@@ -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

View File

@@ -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")

View File

@@ -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))