Skip to content

Commit 517070e

Browse files
authored
Merge pull request #28673 from kyounghunJang/feat/add-volume-prune-dry-run
volume prune: add dry-run support
2 parents 113a491 + 3d899a8 commit 517070e

13 files changed

Lines changed: 175 additions & 16 deletions

File tree

cmd/podman/volumes/prune.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ func init() {
4545
_ = pruneCommand.RegisterFlagCompletionFunc(filterFlagName, common.AutocompleteVolumePruneFilters)
4646
flags.BoolP("force", "f", false, "Do not prompt for confirmation")
4747
flags.BoolP("all", "a", false, "Remove all unused volumes, both anonymous and named")
48+
flags.Bool("dry-run", false, "Show what would be pruned without actually pruning")
4849
}
4950

5051
func prune(cmd *cobra.Command, _ []string) error {
@@ -74,7 +75,13 @@ func prune(cmd *cobra.Command, _ []string) error {
7475
pruneOptions.Filters.Set("all", "true")
7576
}
7677

77-
if !force {
78+
dryRun, _ := cmd.Flags().GetBool("dry-run")
79+
if force && dryRun {
80+
return errors.New("--force and --dry-run cannot be used together")
81+
}
82+
pruneOptions.DryRun = dryRun
83+
84+
if !force && !dryRun {
7885
reader := bufio.NewReader(os.Stdin)
7986
if allFlag {
8087
fmt.Println("WARNING! This will remove all volumes not used by at least one container. The following volumes will be removed:")
@@ -135,6 +142,9 @@ func prune(cmd *cobra.Command, _ []string) error {
135142
if err != nil {
136143
return err
137144
}
145+
if dryRun {
146+
fmt.Println("Volumes that would be pruned:")
147+
}
138148
return utils.PrintVolumePruneResults(responses, false)
139149
}
140150

docs/source/markdown/podman-volume-prune.1.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ removal unless **--force** is used.
2121

2222
Remove all unused volumes (anonymous and named). Without this option, only anonymous unused volumes are removed.
2323

24+
#### **--dry-run**
25+
26+
Show which volumes would be pruned without removing them.
27+
2428
#### **--filter**
2529

2630
Provide filter values.
@@ -98,6 +102,12 @@ Prune unused volumes that do NOT have a specific label key:
98102
$ podman volume prune --filter label!=environment
99103
```
100104

105+
Preview all unused volumes without removing them.
106+
107+
```
108+
$ podman volume prune --all --dry-run
109+
```
110+
101111
## SEE ALSO
102112
**[podman(1)](podman.1.md)**, **[podman-volume(1)](podman-volume.1.md)**
103113

libpod/runtime_volume.go

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -111,31 +111,44 @@ func (r *Runtime) GetAllVolumes() ([]*Volume, error) {
111111
}
112112

113113
// PruneVolumes removes unused volumes from the system
114-
func (r *Runtime) PruneVolumes(ctx context.Context, filterFuncs []VolumeFilter) ([]*reports.PruneReport, error) {
114+
func (r *Runtime) PruneVolumes(ctx context.Context, filterFuncs []VolumeFilter, dryRun bool) ([]*reports.PruneReport, error) {
115115
preports := make([]*reports.PruneReport, 0)
116116
vols, err := r.Volumes(filterFuncs...)
117117
if err != nil {
118118
return nil, err
119119
}
120120

121121
for _, vol := range vols {
122+
dangling, err := vol.IsDangling()
123+
if err != nil {
124+
preports = append(preports, &reports.PruneReport{
125+
Id: vol.Name(),
126+
Err: err,
127+
})
128+
}
129+
if !dangling {
130+
continue
131+
}
132+
122133
report := new(reports.PruneReport)
123134
volSize, err := vol.Size()
124135
if err != nil {
125136
volSize = 0
126137
}
127138
report.Size = volSize
128139
report.Id = vol.Name()
129-
var timeout *uint
130-
if err := r.RemoveVolume(ctx, vol, false, timeout); err != nil {
131-
if !errors.Is(err, define.ErrVolumeBeingUsed) && !errors.Is(err, define.ErrVolumeRemoved) {
132-
report.Err = err
140+
if !dryRun {
141+
var timeout *uint
142+
if err := r.RemoveVolume(ctx, vol, false, timeout); err != nil {
143+
if !errors.Is(err, define.ErrVolumeBeingUsed) && !errors.Is(err, define.ErrVolumeRemoved) {
144+
report.Err = err
145+
} else {
146+
// We didn't remove the volume for some reason
147+
continue
148+
}
133149
} else {
134-
// We didn't remove the volume for some reason
135-
continue
150+
vol.newVolumeEvent(events.Prune)
136151
}
137-
} else {
138-
vol.newVolumeEvent(events.Prune)
139152
}
140153
preports = append(preports, report)
141154
}

pkg/api/handlers/compat/volumes.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@ func PruneVolumes(w http.ResponseWriter, r *http.Request) {
297297
filterFuncs = append(filterFuncs, filterFunc)
298298
}
299299

300-
pruned, err := runtime.PruneVolumes(r.Context(), filterFuncs)
300+
pruned, err := runtime.PruneVolumes(r.Context(), filterFuncs, false)
301301
if err != nil {
302302
utils.InternalServerError(w, err)
303303
return

pkg/api/handlers/libpod/volumes.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,16 @@ func PruneVolumes(w http.ResponseWriter, r *http.Request) {
144144
}
145145

146146
func pruneVolumesHelper(r *http.Request) ([]*reports.PruneReport, error) {
147+
decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
147148
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
149+
150+
query := struct {
151+
DryRun bool `schema:"dryrun"`
152+
}{}
153+
154+
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
155+
return nil, err
156+
}
148157
filterMap, err := util.PrepareFilters(r)
149158
if err != nil {
150159
return nil, err
@@ -160,7 +169,7 @@ func pruneVolumesHelper(r *http.Request) ([]*reports.PruneReport, error) {
160169
filterFuncs = append(filterFuncs, filterFunc)
161170
}
162171

163-
reports, err := runtime.PruneVolumes(r.Context(), filterFuncs)
172+
reports, err := runtime.PruneVolumes(r.Context(), filterFuncs, query.DryRun)
164173
if err != nil {
165174
return nil, err
166175
}

pkg/api/server/register_volumes.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,12 @@ func (s *APIServer) registerVolumeHandlers(r *mux.Router) error {
9595
// - `anonymous` When true/false, restrict to anonymous or named volumes only.
9696
// - `until=<timestamp>` Prune volumes created before this timestamp. The `<timestamp>` can be Unix timestamps, date formatted timestamps, or Go duration strings (e.g. `10m`, `1h30m`) computed relative to the daemon machine’s time.
9797
// - `label` (`label=<key>`, `label=<key>=<value>`, `label!=<key>`, or `label!=<key>=<value>`) Prune volumes with (or without, in case `label!=...` is used) the specified labels.
98+
// - in: query
99+
// name: dryrun
100+
// type: boolean
101+
// required: false
102+
// default: false
103+
// description: Show which volumes would be pruned without removing them.
98104
// responses:
99105
// '200':
100106
// "$ref": "#/responses/volumePruneLibpod"

pkg/bindings/test/volumes_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,4 +226,20 @@ var _ = Describe("Podman volumes", func() {
226226
Expect(err).ToNot(HaveOccurred())
227227
Expect(pruned).To(HaveLen(1))
228228
})
229+
230+
It("prune volume with dry-run", func() {
231+
vol, err := volumes.Create(connText, entities.VolumeCreateOptions{Name: "vol"}, nil)
232+
Expect(err).ToNot(HaveOccurred())
233+
234+
options := new(volumes.PruneOptions).
235+
WithFilters(map[string][]string{"all": {"true"}}).
236+
WithDryRun(true)
237+
238+
vols, err := volumes.Prune(connText, options)
239+
Expect(err).ToNot(HaveOccurred())
240+
Expect(reports.PruneReportsIds(vols)).To(ContainElement(vol.Name))
241+
242+
_, err = volumes.Inspect(connText, vol.Name, nil)
243+
Expect(err).ToNot(HaveOccurred())
244+
})
229245
})

pkg/bindings/volumes/types.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ type ListOptions struct {
2424
type PruneOptions struct {
2525
// Filters applied to the pruning of volumes
2626
Filters map[string][]string
27+
// DryRun lists volumes that would be pruned without removing them.
28+
DryRun *bool
2729
}
2830

2931
// RemoveOptions are optional options for removing volumes

pkg/bindings/volumes/types_prune_options.go

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/domain/entities/volumes.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ type VolumeInspectReport = types.VolumeInspectReport
2929
// - when filter "all" is true, all unused volumes are pruned.
3030
type VolumePruneOptions struct {
3131
Filters url.Values `json:"filters" schema:"filters"`
32+
DryRun bool `json:"dry_run" schema:"dryrun"`
3233
}
3334

3435
type VolumeListOptions struct {

0 commit comments

Comments
 (0)