Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Implement never_wipe_images flag
Implement #9242.

Signed-off-by: Sonny Sasaka <[email protected]>
  • Loading branch information
sonnysasaka committed Jun 18, 2025
commit 7ebec3e706f2f13ceb692c19d9ec511e7c4d5f94
6 changes: 6 additions & 0 deletions docs/crio.conf.5.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,12 @@ Store newly pulled images in the specified path, rather than the path provided b
Whether CRI-O should wipe containers after a reboot and images after an upgrade when the server starts.
If set to false, one must run `crio wipe` to wipe the containers and images in these situations.

**never_wipe_images**=false
Prevents image storage from being wiped under any circumstances, including startup and failed repair attempts.
This is useful for disconnected environments where image storage integrity is crucial to preserve.
When set to true, this setting takes precedence over other wiping mechanisms. Containers may still be wiped
according to normal procedures, but image data will be preserved.

**internal_repair**=true
InternalRepair is whether CRI-O should check if the container and image storage was corrupted after a sudden restart.
If it was, CRI-O also attempts to repair the storage.
Expand Down
84 changes: 7 additions & 77 deletions internal/criocli/wipe.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
package criocli

import (
"errors"
"fmt"
"os"

cstorage "github.com/containers/storage"
json "github.com/json-iterator/go"
"github.com/sirupsen/logrus"
"github.com/urfave/cli/v2"

"github.com/cri-o/cri-o/internal/lib"
"github.com/cri-o/cri-o/internal/storage"
"github.com/cri-o/cri-o/internal/version"
)

Expand Down Expand Up @@ -113,20 +110,11 @@ func crioWipe(c *cli.Context) error {
return nil
}

cstore := ContainerStore{store}
if err := cstore.wipeCrio(shouldWipeImages); err != nil {
return err
}

return nil
return wipeCrio(store, shouldWipeImages && !config.NeverWipeImages)
}

type ContainerStore struct {
store cstorage.Store
}

func (c ContainerStore) wipeCrio(shouldWipeImages bool) error {
crioContainers, crioImages, err := c.getCrioContainersAndImages()
func wipeCrio(store cstorage.Store, shouldWipeImages bool) error {
crioContainers, crioImages, err := lib.GetCrioContainersAndImages(store)
if err != nil {
return err
}
Expand All @@ -136,7 +124,7 @@ func (c ContainerStore) wipeCrio(shouldWipeImages bool) error {
}

for _, id := range crioContainers {
c.deleteContainer(id)
lib.DeleteContainer(store, id)
}

if shouldWipeImages {
Expand All @@ -145,69 +133,11 @@ func (c ContainerStore) wipeCrio(shouldWipeImages bool) error {
}

for _, id := range crioImages {
c.deleteImage(id)
lib.DeleteImage(store, id)
}
} else if !shouldWipeImages && len(crioImages) != 0 {
logrus.Infof("Skipping image wipe due to never_wipe_images setting")
}

return nil
}

func (c ContainerStore) getCrioContainersAndImages() (crioContainers, crioImages []string, _ error) {
containers, err := c.store.Containers()
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return crioContainers, crioImages, err
}

logrus.Errorf("Could not read containers and sandboxes: %v", err)
}

for i := range containers {
id := containers[i].ID

metadataString, err := c.store.Metadata(id)
if err != nil {
continue
}

metadata := storage.RuntimeContainerMetadata{}
if err := json.Unmarshal([]byte(metadataString), &metadata); err != nil {
continue
}

if !storage.IsCrioContainer(&metadata) {
continue
}

crioContainers = append(crioContainers, id)
crioImages = append(crioImages, containers[i].ImageID)
}

return crioContainers, crioImages, nil
}

func (c ContainerStore) deleteContainer(id string) {
if mounted, err := c.store.Unmount(id, true); err != nil || mounted {
logrus.Errorf("Unable to unmount container %s: %v", id, err)

return
}

if err := c.store.DeleteContainer(id); err != nil {
logrus.Errorf("Unable to delete container %s: %v", id, err)

return
}

logrus.Infof("Deleted container %s", id)
}

func (c ContainerStore) deleteImage(id string) {
if _, err := c.store.DeleteImage(id, true); err != nil {
logrus.Errorf("Unable to delete image %s: %v", id, err)

return
}

logrus.Infof("Deleted image %s", id)
}
94 changes: 94 additions & 0 deletions internal/lib/container_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -902,7 +902,101 @@ func ShutdownWasUnclean(config *libconfig.Config) bool {
return true
}

// GetCrioContainersAndImages returns lists of CRI-O containers and their associated images
func GetCrioContainersAndImages(store cstorage.Store) ([]string, []string, error) {
var crioContainers, crioImages []string

containers, err := store.Containers()
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return crioContainers, crioImages, err
}

logrus.Errorf("Could not read containers and sandboxes: %v", err)
return nil, nil, err
}

for i := range containers {
id := containers[i].ID

metadataString, err := store.Metadata(id)
if err != nil {
continue
}

metadata := storage.RuntimeContainerMetadata{}
if err := json.Unmarshal([]byte(metadataString), &metadata); err != nil {
continue
}

if !storage.IsCrioContainer(&metadata) {
continue
}

crioContainers = append(crioContainers, id)
crioImages = append(crioImages, containers[i].ImageID)
}

return crioContainers, crioImages, nil
}

// DeleteContainer deletes a container from the given store
func DeleteContainer(store cstorage.Store, id string) error {
// Unmount the container first
if mounted, err := store.Unmount(id, true); err != nil || mounted {
logrus.Warnf("Unable to unmount container %s: %v", id, err)
return err
}

// Delete the container
if err := store.DeleteContainer(id); err != nil {
logrus.Warnf("Unable to delete container %s: %v", id, err)
return err
}

logrus.Infof("Deleted container %s", id)
return nil
}

// DeleteImage deletes an image from the given store
func DeleteImage(store cstorage.Store, id string) error {
if _, err := store.DeleteImage(id, true); err != nil {
logrus.Warnf("Unable to delete image %s: %v", id, err)
return err
}

logrus.Infof("Deleted image %s", id)
return nil
}

func RemoveStorageDirectory(config *libconfig.Config, store cstorage.Store, force bool) error {
// If never_wipe_images is set, we should only clean up containers and leave the image storage intact
if config.NeverWipeImages {
logrus.Info("NeverWipeImages is set - only containers will be wiped, images will be preserved")

// Get CRI-O containers
containers, _, err := GetCrioContainersAndImages(store)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
// No containers exist, nothing to do
return nil
}
return fmt.Errorf("could not get containers: %w", err)
}

// Wipe all containers but keep images
if len(containers) > 0 {
logrus.Infof("Wiping %d containers while preserving images", len(containers))
for _, id := range containers {
DeleteContainer(store, id)
}
} else {
logrus.Info("No containers to wipe")
}

return nil
}

// If we do not do this, we may leak other resources that are not directly
// in the graphroot. Erroring here should not be fatal though, it's a best
// effort cleanup.
Expand Down
6 changes: 6 additions & 0 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,11 @@ type RootConfig struct {
// The option InternalWipe is deprecated, and will be removed in a future release.
InternalWipe bool `toml:"internal_wipe"`

// NeverWipeImages prevents image storage from being wiped under any circumstances,
// including startup and failed repair attempts. This is useful for disconnected
// environments where image storage integrity is crucial to preserve.
NeverWipeImages bool `toml:"never_wipe_images"`

// InternalRepair is used to repair the affected images.
InternalRepair bool `toml:"internal_repair"`
}
Expand Down Expand Up @@ -925,6 +930,7 @@ func DefaultConfig() (*Config, error) {
VersionFile: CrioVersionPathTmp,
CleanShutdownFile: CrioCleanShutdownFile,
InternalWipe: true,
NeverWipeImages: false,
InternalRepair: true,
},
APIConfig: APIConfig{
Expand Down
12 changes: 12 additions & 0 deletions pkg/config/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,11 @@ func initCrioTemplateConfig(c *Config) ([]*templateConfigValue, error) {
group: crioRootConfig,
isDefaultValue: simpleEqual(dc.InternalWipe, c.InternalWipe),
},
{
templateString: templateStringCrioNeverWipeImages,
group: crioRootConfig,
isDefaultValue: simpleEqual(dc.NeverWipeImages, c.NeverWipeImages),
},
{
templateString: templateStringCrioInternalRepair,
group: crioRootConfig,
Expand Down Expand Up @@ -803,6 +808,13 @@ const templateStringCrioInternalWipe = `# InternalWipe is whether CRI-O should w

`

const templateStringCrioNeverWipeImages = `# NeverWipeImages prevents image storage from being wiped under any circumstances,
# including startup and failed repair attempts. This is useful for disconnected
# environments where image storage integrity is crucial to preserve.
{{ $.Comment }}never_wipe_images = {{ .NeverWipeImages }}

`

const templateStringCrioInternalRepair = `# InternalRepair is whether CRI-O should check if the container and image storage was corrupted after a sudden restart.
# If it was, CRI-O also attempts to repair the storage.
{{ $.Comment }}internal_repair = {{ .InternalRepair }}
Expand Down