summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Containerfile-testvol10
-rw-r--r--Makefile6
-rw-r--r--cmd/podman/volumes/reload.go52
-rw-r--r--docs/source/markdown/podman-volume-reload.1.md29
-rw-r--r--docs/source/markdown/podman-volume.1.md1
-rw-r--r--libpod/container_exec.go21
-rw-r--r--libpod/container_internal_linux.go2
-rw-r--r--libpod/define/volume_inspect.go6
-rw-r--r--libpod/events.go10
-rw-r--r--libpod/events/config.go4
-rw-r--r--libpod/events/events.go4
-rw-r--r--libpod/events/journal_linux.go2
-rw-r--r--libpod/healthcheck.go15
-rw-r--r--libpod/plugin/volume_api.go18
-rw-r--r--libpod/runtime_ctr.go6
-rw-r--r--libpod/runtime_pod_linux.go2
-rw-r--r--libpod/runtime_volume.go2
-rw-r--r--libpod/runtime_volume_linux.go98
-rw-r--r--pkg/criu/criu.go43
-rw-r--r--pkg/criu/criu_linux.go44
-rw-r--r--pkg/criu/criu_unsupported.go8
-rw-r--r--pkg/domain/entities/engine_container.go1
-rw-r--r--pkg/domain/entities/events.go10
-rw-r--r--pkg/domain/entities/volumes.go5
-rw-r--r--pkg/domain/filters/volumes.go8
-rw-r--r--pkg/domain/infra/abi/volumes.go5
-rw-r--r--pkg/domain/infra/tunnel/volumes.go4
-rw-r--r--pkg/machine/qemu/machine.go8
-rw-r--r--test/apiv2/27-containersEvents.at4
-rw-r--r--test/e2e/config.go2
-rw-r--r--test/e2e/events_test.go21
-rw-r--r--test/e2e/volume_ls_test.go31
-rw-r--r--test/e2e/volume_plugin_test.go68
-rw-r--r--test/system/160-volumes.bats23
-rw-r--r--test/testvol/Containerfile9
-rw-r--r--test/testvol/create.go26
-rw-r--r--test/testvol/list.go32
-rw-r--r--test/testvol/main.go23
-rw-r--r--test/testvol/remove.go26
-rw-r--r--test/testvol/util.go29
40 files changed, 613 insertions, 105 deletions
diff --git a/Containerfile-testvol b/Containerfile-testvol
deleted file mode 100644
index 6ff45064b..000000000
--- a/Containerfile-testvol
+++ /dev/null
@@ -1,10 +0,0 @@
-FROM golang:1.15-alpine AS build-img
-COPY ./test/testvol/ /go/src/github.com/containers/podman/cmd/testvol/
-COPY ./vendor /go/src/github.com/containers/podman/vendor/
-WORKDIR /go/src/github.com/containers/podman
-RUN go build -o /testvol ./cmd/testvol
-
-FROM alpine
-COPY --from=build-img /testvol /usr/local/bin
-WORKDIR /
-ENTRYPOINT ["/usr/local/bin/testvol"]
diff --git a/Makefile b/Makefile
index 38ea7a256..3ed522b32 100644
--- a/Makefile
+++ b/Makefile
@@ -225,11 +225,11 @@ test/checkseccomp/checkseccomp: $(wildcard test/checkseccomp/*.go)
.PHONY: test/testvol/testvol
test/testvol/testvol: $(wildcard test/testvol/*.go)
- $(GOCMD) build $(BUILDFLAGS) $(GO_LDFLAGS) '$(LDFLAGS_PODMAN)' -o $@ ./test/testvol
+ $(GOCMD) build -o $@ ./test/testvol
-.PHONY: volume-plugin-test-image
+.PHONY: volume-plugin-test-img
volume-plugin-test-img:
- podman build -t quay.io/libpod/volume-plugin-test-img -f Containerfile-testvol .
+ ./bin/podman build --network none -t quay.io/libpod/volume-plugin-test-img:$$(date +%Y%m%d) -f ./test/testvol/Containerfile .
.PHONY: test/goecho/goecho
test/goecho/goecho: $(wildcard test/goecho/*.go)
diff --git a/cmd/podman/volumes/reload.go b/cmd/podman/volumes/reload.go
new file mode 100644
index 000000000..d0d76fb88
--- /dev/null
+++ b/cmd/podman/volumes/reload.go
@@ -0,0 +1,52 @@
+package volumes
+
+import (
+ "fmt"
+
+ "github.com/containers/common/pkg/completion"
+ "github.com/containers/podman/v4/cmd/podman/registry"
+ "github.com/containers/podman/v4/cmd/podman/utils"
+ "github.com/containers/podman/v4/cmd/podman/validate"
+ "github.com/spf13/cobra"
+)
+
+var (
+ reloadDescription = `Check all configured volume plugins and update the libpod database with all available volumes.
+
+ Existing volumes are also removed from the database when they are no longer present in the plugin.`
+ reloadCommand = &cobra.Command{
+ Use: "reload",
+ Args: validate.NoArgs,
+ Short: "reload all volumes from volume plugins",
+ Long: reloadDescription,
+ RunE: reload,
+ ValidArgsFunction: completion.AutocompleteNone,
+ }
+)
+
+func init() {
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Command: reloadCommand,
+ Parent: volumeCmd,
+ })
+}
+
+func reload(cmd *cobra.Command, args []string) error {
+ report, err := registry.ContainerEngine().VolumeReload(registry.Context())
+ if err != nil {
+ return err
+ }
+ printReload("Added", report.Added)
+ printReload("Removed", report.Removed)
+ errs := (utils.OutputErrors)(report.Errors)
+ return errs.PrintErrors()
+}
+
+func printReload(typ string, values []string) {
+ if len(values) > 0 {
+ fmt.Println(typ + ":")
+ for _, name := range values {
+ fmt.Println(name)
+ }
+ }
+}
diff --git a/docs/source/markdown/podman-volume-reload.1.md b/docs/source/markdown/podman-volume-reload.1.md
new file mode 100644
index 000000000..5b9e9b9ac
--- /dev/null
+++ b/docs/source/markdown/podman-volume-reload.1.md
@@ -0,0 +1,29 @@
+% podman-volume-reload(1)
+
+## NAME
+podman\-volume\-reload - Reload all volumes from volumes plugins
+
+## SYNOPSIS
+**podman volume reload**
+
+## DESCRIPTION
+
+**podman volume reload** checks all configured volume plugins and updates the libpod database with all available volumes.
+Existing volumes are also removed from the database when they are no longer present in the plugin.
+
+This command it is best effort and cannot guarantee a perfect state because plugins can be modified from the outside at any time.
+
+Note: This command is not supported with podman-remote.
+
+## EXAMPLES
+
+```
+$ podman volume reload
+Added:
+vol6
+Removed:
+t3
+```
+
+## SEE ALSO
+**[podman(1)](podman.1.md)**, **[podman-volume(1)](podman-volume.1.md)**
diff --git a/docs/source/markdown/podman-volume.1.md b/docs/source/markdown/podman-volume.1.md
index 476d58591..a437590b3 100644
--- a/docs/source/markdown/podman-volume.1.md
+++ b/docs/source/markdown/podman-volume.1.md
@@ -21,6 +21,7 @@ podman volume is a set of subcommands that manage volumes.
| ls | [podman-volume-ls(1)](podman-volume-ls.1.md) | List all the available volumes. |
| mount | [podman-volume-mount(1)](podman-volume-mount.1.md) | Mount a volume filesystem. |
| prune | [podman-volume-prune(1)](podman-volume-prune.1.md) | Remove all unused volumes. |
+| reload | [podman-volume-reload(1)](podman-volume-reload.1.md) | Reload all volumes from volumes plugins. |
| rm | [podman-volume-rm(1)](podman-volume-rm.1.md) | Remove one or more volumes. |
| unmount | [podman-volume-unmount(1)](podman-volume-unmount.1.md) | Unmount a volume. |
diff --git a/libpod/container_exec.go b/libpod/container_exec.go
index be00c6fbe..b112273d0 100644
--- a/libpod/container_exec.go
+++ b/libpod/container_exec.go
@@ -277,9 +277,13 @@ func (c *Container) ExecStart(sessionID string) error {
return c.save()
}
+func (c *Container) ExecStartAndAttach(sessionID string, streams *define.AttachStreams, newSize *define.TerminalSize) error {
+ return c.execStartAndAttach(sessionID, streams, newSize, false)
+}
+
// ExecStartAndAttach starts and attaches to an exec session in a container.
// newSize resizes the tty to this size before the process is started, must be nil if the exec session has no tty
-func (c *Container) ExecStartAndAttach(sessionID string, streams *define.AttachStreams, newSize *define.TerminalSize) error {
+func (c *Container) execStartAndAttach(sessionID string, streams *define.AttachStreams, newSize *define.TerminalSize, isHealthcheck bool) error {
if !c.batched {
c.lock.Lock()
defer c.lock.Unlock()
@@ -315,7 +319,12 @@ func (c *Container) ExecStartAndAttach(sessionID string, streams *define.AttachS
return err
}
- c.newContainerEvent(events.Exec)
+ if isHealthcheck {
+ c.newContainerEvent(events.HealthStatus)
+ } else {
+ c.newContainerEvent(events.Exec)
+ }
+
logrus.Debugf("Successfully started exec session %s in container %s", session.ID(), c.ID())
var lastErr error
@@ -743,10 +752,14 @@ func (c *Container) ExecResize(sessionID string, newSize define.TerminalSize) er
return c.ociRuntime.ExecAttachResize(c, sessionID, newSize)
}
+func (c *Container) Exec(config *ExecConfig, streams *define.AttachStreams, resize <-chan define.TerminalSize) (int, error) {
+ return c.exec(config, streams, resize, false)
+}
+
// Exec emulates the old Libpod exec API, providing a single call to create,
// run, and remove an exec session. Returns exit code and error. Exit code is
// not guaranteed to be set sanely if error is not nil.
-func (c *Container) Exec(config *ExecConfig, streams *define.AttachStreams, resize <-chan define.TerminalSize) (int, error) {
+func (c *Container) exec(config *ExecConfig, streams *define.AttachStreams, resize <-chan define.TerminalSize, isHealthcheck bool) (int, error) {
sessionID, err := c.ExecCreate(config)
if err != nil {
return -1, err
@@ -780,7 +793,7 @@ func (c *Container) Exec(config *ExecConfig, streams *define.AttachStreams, resi
}()
}
- if err := c.ExecStartAndAttach(sessionID, streams, size); err != nil {
+ if err := c.execStartAndAttach(sessionID, streams, size, isHealthcheck); err != nil {
return -1, err
}
diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go
index 10f4eeec1..0f4bf0f55 100644
--- a/libpod/container_internal_linux.go
+++ b/libpod/container_internal_linux.go
@@ -1142,7 +1142,7 @@ func (c *Container) addCheckpointImageMetadata(importBuilder *buildah.Builder) e
return fmt.Errorf("getting host info: %v", err)
}
- criuVersion, err := criu.GetCriuVestion()
+ criuVersion, err := criu.GetCriuVersion()
if err != nil {
return fmt.Errorf("getting criu version: %v", err)
}
diff --git a/libpod/define/volume_inspect.go b/libpod/define/volume_inspect.go
index fac179176..aaa23b4fc 100644
--- a/libpod/define/volume_inspect.go
+++ b/libpod/define/volume_inspect.go
@@ -57,3 +57,9 @@ type InspectVolumeData struct {
// UID/GID.
NeedsChown bool `json:"NeedsChown,omitempty"`
}
+
+type VolumeReload struct {
+ Added []string
+ Removed []string
+ Errors []error
+}
diff --git a/libpod/events.go b/libpod/events.go
index 021b3b53c..bb50df92d 100644
--- a/libpod/events.go
+++ b/libpod/events.go
@@ -33,6 +33,16 @@ func (c *Container) newContainerEvent(status events.Status) {
Attributes: c.Labels(),
}
+ // if the current event is a HealthStatus event, we need to get the current
+ // status of the container to pass to the event
+ if status == events.HealthStatus {
+ containerHealthStatus, err := c.healthCheckStatus()
+ if err != nil {
+ e.HealthStatus = fmt.Sprintf("%v", err)
+ }
+ e.HealthStatus = containerHealthStatus
+ }
+
if err := c.runtime.eventer.Write(e); err != nil {
logrus.Errorf("Unable to write pod event: %q", err)
}
diff --git a/libpod/events/config.go b/libpod/events/config.go
index 2e7016136..a678baa2d 100644
--- a/libpod/events/config.go
+++ b/libpod/events/config.go
@@ -40,6 +40,8 @@ type Event struct {
Time time.Time
// Type of event that occurred
Type Type
+ // Health status of the current container
+ HealthStatus string `json:"health_status,omitempty"`
Details
}
@@ -141,6 +143,8 @@ const (
Exited Status = "died"
// Export ...
Export Status = "export"
+ // HealthStatus ...
+ HealthStatus Status = "health_status"
// History ...
History Status = "history"
// Import ...
diff --git a/libpod/events/events.go b/libpod/events/events.go
index a30e0f1ca..a8001ab95 100644
--- a/libpod/events/events.go
+++ b/libpod/events/events.go
@@ -76,7 +76,7 @@ func (e *Event) ToHumanReadable(truncate bool) string {
}
switch e.Type {
case Container, Pod:
- humanFormat = fmt.Sprintf("%s %s %s %s (image=%s, name=%s", e.Time, e.Type, e.Status, id, e.Image, e.Name)
+ humanFormat = fmt.Sprintf("%s %s %s %s (image=%s, name=%s, health_status=%s", e.Time, e.Type, e.Status, id, e.Image, e.Name, e.HealthStatus)
// check if the container has labels and add it to the output
if len(e.Attributes) > 0 {
for k, v := range e.Attributes {
@@ -168,6 +168,8 @@ func StringToStatus(name string) (Status, error) {
return Exited, nil
case Export.String():
return Export, nil
+ case HealthStatus.String():
+ return HealthStatus, nil
case History.String():
return History, nil
case Import.String():
diff --git a/libpod/events/journal_linux.go b/libpod/events/journal_linux.go
index d21b60c68..036638d34 100644
--- a/libpod/events/journal_linux.go
+++ b/libpod/events/journal_linux.go
@@ -58,6 +58,7 @@ func (e EventJournalD) Write(ee Event) error {
}
m["PODMAN_LABELS"] = string(b)
}
+ m["PODMAN_HEALTH_STATUS"] = ee.HealthStatus
case Network:
m["PODMAN_ID"] = ee.ID
m["PODMAN_NETWORK_NAME"] = ee.Network
@@ -213,6 +214,7 @@ func newEventFromJournalEntry(entry *sdjournal.JournalEntry) (*Event, error) {
newEvent.Details = Details{Attributes: labels}
}
}
+ newEvent.HealthStatus = entry.Fields["PODMAN_HEALTH_STATUS"]
case Network:
newEvent.ID = entry.Fields["PODMAN_ID"]
newEvent.Network = entry.Fields["PODMAN_NETWORK_NAME"]
diff --git a/libpod/healthcheck.go b/libpod/healthcheck.go
index bd77e98c6..95c70b60e 100644
--- a/libpod/healthcheck.go
+++ b/libpod/healthcheck.go
@@ -90,7 +90,7 @@ func (c *Container) runHealthCheck() (define.HealthCheckStatus, error) {
hcResult := define.HealthCheckSuccess
config := new(ExecConfig)
config.Command = newCommand
- exitCode, hcErr := c.Exec(config, streams, nil)
+ exitCode, hcErr := c.exec(config, streams, nil, true)
if hcErr != nil {
errCause := errors.Cause(hcErr)
hcResult = define.HealthCheckFailure
@@ -232,18 +232,27 @@ func (c *Container) getHealthCheckLog() (define.HealthCheckResults, error) {
// HealthCheckStatus returns the current state of a container with a healthcheck
func (c *Container) HealthCheckStatus() (string, error) {
+ c.lock.Lock()
+ defer c.lock.Unlock()
+ return c.healthCheckStatus()
+}
+
+// Internal function to return the current state of a container with a healthcheck.
+// This function does not lock the container.
+func (c *Container) healthCheckStatus() (string, error) {
if !c.HasHealthCheck() {
return "", errors.Errorf("container %s has no defined healthcheck", c.ID())
}
- c.lock.Lock()
- defer c.lock.Unlock()
+
if err := c.syncContainer(); err != nil {
return "", err
}
+
results, err := c.getHealthCheckLog()
if err != nil {
return "", errors.Wrapf(err, "unable to get healthcheck log for %s", c.ID())
}
+
return results.Status, nil
}
diff --git a/libpod/plugin/volume_api.go b/libpod/plugin/volume_api.go
index f997ccf22..2de7db32c 100644
--- a/libpod/plugin/volume_api.go
+++ b/libpod/plugin/volume_api.go
@@ -197,13 +197,13 @@ func (p *VolumePlugin) verifyReachable() error {
// Send a request to the volume plugin for handling.
// Callers *MUST* close the response when they are done.
-func (p *VolumePlugin) sendRequest(toJSON interface{}, hasBody bool, endpoint string) (*http.Response, error) {
+func (p *VolumePlugin) sendRequest(toJSON interface{}, endpoint string) (*http.Response, error) {
var (
reqJSON []byte
err error
)
- if hasBody {
+ if toJSON != nil {
reqJSON, err = json.Marshal(toJSON)
if err != nil {
return nil, errors.Wrapf(err, "error marshalling request JSON for volume plugin %s endpoint %s", p.Name, endpoint)
@@ -274,7 +274,7 @@ func (p *VolumePlugin) CreateVolume(req *volume.CreateRequest) error {
logrus.Infof("Creating volume %s using plugin %s", req.Name, p.Name)
- resp, err := p.sendRequest(req, true, createPath)
+ resp, err := p.sendRequest(req, createPath)
if err != nil {
return err
}
@@ -291,7 +291,7 @@ func (p *VolumePlugin) ListVolumes() ([]*volume.Volume, error) {
logrus.Infof("Listing volumes using plugin %s", p.Name)
- resp, err := p.sendRequest(nil, false, listPath)
+ resp, err := p.sendRequest(nil, listPath)
if err != nil {
return nil, err
}
@@ -326,7 +326,7 @@ func (p *VolumePlugin) GetVolume(req *volume.GetRequest) (*volume.Volume, error)
logrus.Infof("Getting volume %s using plugin %s", req.Name, p.Name)
- resp, err := p.sendRequest(req, true, getPath)
+ resp, err := p.sendRequest(req, getPath)
if err != nil {
return nil, err
}
@@ -361,7 +361,7 @@ func (p *VolumePlugin) RemoveVolume(req *volume.RemoveRequest) error {
logrus.Infof("Removing volume %s using plugin %s", req.Name, p.Name)
- resp, err := p.sendRequest(req, true, removePath)
+ resp, err := p.sendRequest(req, removePath)
if err != nil {
return err
}
@@ -382,7 +382,7 @@ func (p *VolumePlugin) GetVolumePath(req *volume.PathRequest) (string, error) {
logrus.Infof("Getting volume %s path using plugin %s", req.Name, p.Name)
- resp, err := p.sendRequest(req, true, hostVirtualPath)
+ resp, err := p.sendRequest(req, hostVirtualPath)
if err != nil {
return "", err
}
@@ -419,7 +419,7 @@ func (p *VolumePlugin) MountVolume(req *volume.MountRequest) (string, error) {
logrus.Infof("Mounting volume %s using plugin %s for container %s", req.Name, p.Name, req.ID)
- resp, err := p.sendRequest(req, true, mountPath)
+ resp, err := p.sendRequest(req, mountPath)
if err != nil {
return "", err
}
@@ -455,7 +455,7 @@ func (p *VolumePlugin) UnmountVolume(req *volume.UnmountRequest) error {
logrus.Infof("Unmounting volume %s using plugin %s for container %s", req.Name, p.Name, req.ID)
- resp, err := p.sendRequest(req, true, unmountPath)
+ resp, err := p.sendRequest(req, unmountPath)
if err != nil {
return err
}
diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go
index a9ae9d1db..459514d47 100644
--- a/libpod/runtime_ctr.go
+++ b/libpod/runtime_ctr.go
@@ -502,7 +502,7 @@ func (r *Runtime) setupContainer(ctx context.Context, ctr *Container) (_ *Contai
volOptions = append(volOptions, parsedOptions...)
}
}
- newVol, err := r.newVolume(volOptions...)
+ newVol, err := r.newVolume(false, volOptions...)
if err != nil {
return nil, errors.Wrapf(err, "error creating named volume %q", vol.Name)
}
@@ -805,7 +805,7 @@ func (r *Runtime) removeContainer(ctx context.Context, c *Container, force, remo
if !volume.Anonymous() {
continue
}
- if err := runtime.removeVolume(ctx, volume, false, timeout); err != nil && errors.Cause(err) != define.ErrNoSuchVolume {
+ if err := runtime.removeVolume(ctx, volume, false, timeout, false); err != nil && errors.Cause(err) != define.ErrNoSuchVolume {
if errors.Cause(err) == define.ErrVolumeBeingUsed {
// Ignore error, since podman will report original error
volumesFrom, _ := c.volumesFrom()
@@ -963,7 +963,7 @@ func (r *Runtime) evictContainer(ctx context.Context, idOrName string, removeVol
if !volume.Anonymous() {
continue
}
- if err := r.removeVolume(ctx, volume, false, timeout); err != nil && err != define.ErrNoSuchVolume && err != define.ErrVolumeBeingUsed {
+ if err := r.removeVolume(ctx, volume, false, timeout, false); err != nil && err != define.ErrNoSuchVolume && err != define.ErrVolumeBeingUsed {
logrus.Errorf("Cleaning up volume (%s): %v", v, err)
}
}
diff --git a/libpod/runtime_pod_linux.go b/libpod/runtime_pod_linux.go
index d75ac2971..00017ca21 100644
--- a/libpod/runtime_pod_linux.go
+++ b/libpod/runtime_pod_linux.go
@@ -315,7 +315,7 @@ func (r *Runtime) removePod(ctx context.Context, p *Pod, removeCtrs, force bool,
if !volume.Anonymous() {
continue
}
- if err := r.removeVolume(ctx, volume, false, timeout); err != nil {
+ if err := r.removeVolume(ctx, volume, false, timeout, false); err != nil {
if errors.Cause(err) == define.ErrNoSuchVolume || errors.Cause(err) == define.ErrVolumeRemoved {
continue
}
diff --git a/libpod/runtime_volume.go b/libpod/runtime_volume.go
index 21bf8aefc..6872db21d 100644
--- a/libpod/runtime_volume.go
+++ b/libpod/runtime_volume.go
@@ -33,7 +33,7 @@ func (r *Runtime) RemoveVolume(ctx context.Context, v *Volume, force bool, timeo
return nil
}
}
- return r.removeVolume(ctx, v, force, timeout)
+ return r.removeVolume(ctx, v, force, timeout, false)
}
// GetVolume retrieves a volume given its full name.
diff --git a/libpod/runtime_volume_linux.go b/libpod/runtime_volume_linux.go
index f8788e183..877f3a1fd 100644
--- a/libpod/runtime_volume_linux.go
+++ b/libpod/runtime_volume_linux.go
@@ -5,6 +5,7 @@ package libpod
import (
"context"
+ "fmt"
"os"
"path/filepath"
"strings"
@@ -25,11 +26,13 @@ func (r *Runtime) NewVolume(ctx context.Context, options ...VolumeCreateOption)
if !r.valid {
return nil, define.ErrRuntimeStopped
}
- return r.newVolume(options...)
+ return r.newVolume(false, options...)
}
-// newVolume creates a new empty volume
-func (r *Runtime) newVolume(options ...VolumeCreateOption) (_ *Volume, deferredErr error) {
+// newVolume creates a new empty volume with the given options.
+// The createPluginVolume can be set to true to make it not create the volume in the volume plugin,
+// this is required for the UpdateVolumePlugins() function. If you are not sure set this to false.
+func (r *Runtime) newVolume(noCreatePluginVolume bool, options ...VolumeCreateOption) (_ *Volume, deferredErr error) {
volume := newVolume(r)
for _, option := range options {
if err := option(volume); err != nil {
@@ -83,7 +86,7 @@ func (r *Runtime) newVolume(options ...VolumeCreateOption) (_ *Volume, deferredE
// Now we get conditional: we either need to make the volume in the
// volume plugin, or on disk if not using a plugin.
- if volume.plugin != nil {
+ if volume.plugin != nil && !noCreatePluginVolume {
// We can't chown, or relabel, or similar the path the volume is
// using, because it's not managed by us.
// TODO: reevaluate this once we actually have volume plugins in
@@ -164,6 +167,85 @@ func (r *Runtime) newVolume(options ...VolumeCreateOption) (_ *Volume, deferredE
return volume, nil
}
+// UpdateVolumePlugins reads all volumes from all configured volume plugins and
+// imports them into the libpod db. It also checks if existing libpod volumes
+// are removed in the plugin, in this case we try to remove it from libpod.
+// On errors we continue and try to do as much as possible. all errors are
+// returned as array in the returned struct.
+// This function has many race conditions, it is best effort but cannot guarantee
+// a perfect state since plugins can be modified from the outside at any time.
+func (r *Runtime) UpdateVolumePlugins(ctx context.Context) *define.VolumeReload {
+ var (
+ added []string
+ removed []string
+ errs []error
+ allPluginVolumes = map[string]struct{}{}
+ )
+
+ for driverName, socket := range r.config.Engine.VolumePlugins {
+ driver, err := volplugin.GetVolumePlugin(driverName, socket)
+ if err != nil {
+ errs = append(errs, err)
+ continue
+ }
+ vols, err := driver.ListVolumes()
+ if err != nil {
+ errs = append(errs, fmt.Errorf("failed to read volumes from plugin %q: %w", driverName, err))
+ continue
+ }
+ for _, vol := range vols {
+ allPluginVolumes[vol.Name] = struct{}{}
+ if _, err := r.newVolume(true, WithVolumeName(vol.Name), WithVolumeDriver(driverName)); err != nil {
+ // If the volume exists this is not an error, just ignore it and log. It is very likely
+ // that the volume from the plugin was already in our db.
+ if !errors.Is(err, define.ErrVolumeExists) {
+ errs = append(errs, err)
+ continue
+ }
+ logrus.Infof("Volume %q already exists: %v", vol.Name, err)
+ continue
+ }
+ added = append(added, vol.Name)
+ }
+ }
+
+ libpodVolumes, err := r.state.AllVolumes()
+ if err != nil {
+ errs = append(errs, fmt.Errorf("cannot delete dangling plugin volumes: failed to read libpod volumes: %w", err))
+ }
+ for _, vol := range libpodVolumes {
+ if vol.UsesVolumeDriver() {
+ if _, ok := allPluginVolumes[vol.Name()]; !ok {
+ // The volume is no longer in the plugin, lets remove it from the libpod db.
+ if err := r.removeVolume(ctx, vol, false, nil, true); err != nil {
+ if errors.Is(err, define.ErrVolumeBeingUsed) {
+ // Volume is still used by at least one container. This is very bad,
+ // the plugin no longer has this but we still need it.
+ errs = append(errs, fmt.Errorf("volume was removed from the plugin %q but containers still require it: %w", vol.config.Driver, err))
+ continue
+ }
+ if errors.Is(err, define.ErrNoSuchVolume) || errors.Is(err, define.ErrVolumeRemoved) || errors.Is(err, define.ErrMissingPlugin) {
+ // Volume was already removed, no problem just ignore it and continue.
+ continue
+ }
+
+ // some other error
+ errs = append(errs, err)
+ continue
+ }
+ // Volume was successfully removed
+ removed = append(removed, vol.Name())
+ }
+ }
+ }
+
+ return &define.VolumeReload{
+ Added: added,
+ Removed: removed,
+ Errors: errs,
+ }
+}
+
// makeVolumeInPluginIfNotExist makes a volume in the given volume plugin if it
// does not already exist.
func makeVolumeInPluginIfNotExist(name string, options map[string]string, plugin *volplugin.VolumePlugin) error {
@@ -197,8 +279,10 @@ func makeVolumeInPluginIfNotExist(name string, options map[string]string, plugin
return nil
}
-// removeVolume removes the specified volume from state as well tears down its mountpoint and storage
-func (r *Runtime) removeVolume(ctx context.Context, v *Volume, force bool, timeout *uint) error {
+// removeVolume removes the specified volume from state as well tears down its mountpoint and storage.
+// ignoreVolumePlugin is used to only remove the volume from the db and not the plugin,
+// this is required when the volume was already removed from the plugin, i.e. in UpdateVolumePlugins().
+func (r *Runtime) removeVolume(ctx context.Context, v *Volume, force bool, timeout *uint, ignoreVolumePlugin bool) error {
if !v.valid {
if ok, _ := r.state.HasVolume(v.Name()); !ok {
return nil
@@ -263,7 +347,7 @@ func (r *Runtime) removeVolume(ctx context.Context, v *Volume, force bool, timeo
var removalErr error
// If we use a volume plugin, we need to remove from the plugin.
- if v.UsesVolumeDriver() {
+ if v.UsesVolumeDriver() && !ignoreVolumePlugin {
canRemove := true
// Do we have a volume driver?
diff --git a/pkg/criu/criu.go b/pkg/criu/criu.go
index 6570159d7..0b0bbff5d 100644
--- a/pkg/criu/criu.go
+++ b/pkg/criu/criu.go
@@ -1,51 +1,8 @@
-//go:build linux
-// +build linux
-
package criu
-import (
- "github.com/checkpoint-restore/go-criu/v5"
- "github.com/checkpoint-restore/go-criu/v5/rpc"
-
- "google.golang.org/protobuf/proto"
-)
-
// MinCriuVersion for Podman at least CRIU 3.11 is required
const MinCriuVersion = 31100
// PodCriuVersion is the version of CRIU needed for
// checkpointing and restoring containers out of and into Pods.
const PodCriuVersion = 31600
-
-// CheckForCriu uses CRIU's go bindings to check if the CRIU
-// binary exists and if it at least the version Podman needs.
-func CheckForCriu(version int) bool {
- c := criu.MakeCriu()
- result, err := c.IsCriuAtLeast(version)
- if err != nil {
- return false
- }
- return result
-}
-
-func GetCriuVestion() (int, error) {
- c := criu.MakeCriu()
- return c.GetCriuVersion()
-}
-
-func MemTrack() bool {
- features, err := criu.MakeCriu().FeatureCheck(
- &rpc.CriuFeatures{
- MemTrack: proto.Bool(true),
- },
- )
- if err != nil {
- return false
- }
-
- if features == nil || features.MemTrack == nil {
- return false
- }
-
- return *features.MemTrack
-}
diff --git a/pkg/criu/criu_linux.go b/pkg/criu/criu_linux.go
new file mode 100644
index 000000000..c28e23fd7
--- /dev/null
+++ b/pkg/criu/criu_linux.go
@@ -0,0 +1,44 @@
+//go:build linux
+// +build linux
+
+package criu
+
+import (
+ "github.com/checkpoint-restore/go-criu/v5"
+ "github.com/checkpoint-restore/go-criu/v5/rpc"
+
+ "google.golang.org/protobuf/proto"
+)
+
+// CheckForCriu uses CRIU's go bindings to check if the CRIU
+// binary exists and if it at least the version Podman needs.
+func CheckForCriu(version int) bool {
+ c := criu.MakeCriu()
+ result, err := c.IsCriuAtLeast(version)
+ if err != nil {
+ return false
+ }
+ return result
+}
+
+func MemTrack() bool {
+ features, err := criu.MakeCriu().FeatureCheck(
+ &rpc.CriuFeatures{
+ MemTrack: proto.Bool(true),
+ },
+ )
+ if err != nil {
+ return false
+ }
+
+ if features == nil || features.MemTrack == nil {
+ return false
+ }
+
+ return *features.MemTrack
+}
+
+func GetCriuVersion() (int, error) {
+ c := criu.MakeCriu()
+ return c.GetCriuVersion()
+}
diff --git a/pkg/criu/criu_unsupported.go b/pkg/criu/criu_unsupported.go
index 3e3ed9c6c..437482a0e 100644
--- a/pkg/criu/criu_unsupported.go
+++ b/pkg/criu/criu_unsupported.go
@@ -3,6 +3,14 @@
package criu
+func CheckForCriu(version int) bool {
+ return false
+}
+
func MemTrack() bool {
return false
}
+
+func GetCriuVersion() (int, error) {
+ return MinCriuVersion, nil
+}
diff --git a/pkg/domain/entities/engine_container.go b/pkg/domain/entities/engine_container.go
index df42876f6..e4eb808b4 100644
--- a/pkg/domain/entities/engine_container.go
+++ b/pkg/domain/entities/engine_container.go
@@ -104,4 +104,5 @@ type ContainerEngine interface {
VolumePrune(ctx context.Context, options VolumePruneOptions) ([]*reports.PruneReport, error)
VolumeRm(ctx context.Context, namesOrIds []string, opts VolumeRmOptions) ([]*VolumeRmReport, error)
VolumeUnmount(ctx context.Context, namesOrIds []string) ([]*VolumeUnmountReport, error)
+ VolumeReload(ctx context.Context) (*VolumeReloadReport, error)
}
diff --git a/pkg/domain/entities/events.go b/pkg/domain/entities/events.go
index d8ba0f1d3..de218b285 100644
--- a/pkg/domain/entities/events.go
+++ b/pkg/domain/entities/events.go
@@ -14,6 +14,7 @@ type Event struct {
// TODO: it would be nice to have full control over the types at some
// point and fork such Docker types.
dockerEvents.Message
+ HealthStatus string
}
// ConvertToLibpodEvent converts an entities event to a libpod one.
@@ -44,6 +45,7 @@ func ConvertToLibpodEvent(e Event) *libpodEvents.Event {
Status: status,
Time: time.Unix(0, e.TimeNano),
Type: t,
+ HealthStatus: e.HealthStatus,
Details: libpodEvents.Details{
Attributes: details,
},
@@ -59,7 +61,7 @@ func ConvertToEntitiesEvent(e libpodEvents.Event) *Event {
attributes["image"] = e.Image
attributes["name"] = e.Name
attributes["containerExitCode"] = strconv.Itoa(e.ContainerExitCode)
- return &Event{dockerEvents.Message{
+ message := dockerEvents.Message{
// Compatibility with clients that still look for deprecated API elements
Status: e.Status.String(),
ID: e.ID,
@@ -73,5 +75,9 @@ func ConvertToEntitiesEvent(e libpodEvents.Event) *Event {
Scope: "local",
Time: e.Time.Unix(),
TimeNano: e.Time.UnixNano(),
- }}
+ }
+ return &Event{
+ message,
+ e.HealthStatus,
+ }
}
diff --git a/pkg/domain/entities/volumes.go b/pkg/domain/entities/volumes.go
index 556df16c1..9a06b2238 100644
--- a/pkg/domain/entities/volumes.go
+++ b/pkg/domain/entities/volumes.go
@@ -54,6 +54,11 @@ type VolumeListReport struct {
VolumeConfigResponse
}
+// VolumeReloadReport describes the response from reload volume plugins
+type VolumeReloadReport struct {
+ define.VolumeReload
+}
+
/*
* Docker API compatibility types
*/
diff --git a/pkg/domain/filters/volumes.go b/pkg/domain/filters/volumes.go
index e88bd4228..a18e6332c 100644
--- a/pkg/domain/filters/volumes.go
+++ b/pkg/domain/filters/volumes.go
@@ -2,6 +2,7 @@ package filters
import (
"net/url"
+ "regexp"
"strings"
"github.com/containers/podman/v4/libpod"
@@ -15,9 +16,12 @@ func GenerateVolumeFilters(filters url.Values) ([]libpod.VolumeFilter, error) {
for _, val := range v {
switch filter {
case "name":
- nameVal := val
+ nameRegexp, err := regexp.Compile(val)
+ if err != nil {
+ return nil, err
+ }
vf = append(vf, func(v *libpod.Volume) bool {
- return nameVal == v.Name()
+ return nameRegexp.MatchString(v.Name())
})
case "driver":
driverVal := val
diff --git a/pkg/domain/infra/abi/volumes.go b/pkg/domain/infra/abi/volumes.go
index a9c53c140..1186d8e81 100644
--- a/pkg/domain/infra/abi/volumes.go
+++ b/pkg/domain/infra/abi/volumes.go
@@ -211,3 +211,8 @@ func (ic *ContainerEngine) VolumeUnmount(ctx context.Context, nameOrIDs []string
return reports, nil
}
+
+func (ic *ContainerEngine) VolumeReload(ctx context.Context) (*entities.VolumeReloadReport, error) {
+ report := ic.Libpod.UpdateVolumePlugins(ctx)
+ return &entities.VolumeReloadReport{VolumeReload: *report}, nil
+}
diff --git a/pkg/domain/infra/tunnel/volumes.go b/pkg/domain/infra/tunnel/volumes.go
index 33e090148..6ec35e836 100644
--- a/pkg/domain/infra/tunnel/volumes.go
+++ b/pkg/domain/infra/tunnel/volumes.go
@@ -108,3 +108,7 @@ func (ic *ContainerEngine) VolumeMount(ctx context.Context, nameOrIDs []string)
func (ic *ContainerEngine) VolumeUnmount(ctx context.Context, nameOrIDs []string) ([]*entities.VolumeUnmountReport, error) {
return nil, errors.New("unmounting volumes is not supported for remote clients")
}
+
+func (ic *ContainerEngine) VolumeReload(ctx context.Context) (*entities.VolumeReloadReport, error) {
+ return nil, errors.New("volume reload is not supported for remote clients")
+}
diff --git a/pkg/machine/qemu/machine.go b/pkg/machine/qemu/machine.go
index 5094345ea..3a1495021 100644
--- a/pkg/machine/qemu/machine.go
+++ b/pkg/machine/qemu/machine.go
@@ -1074,6 +1074,7 @@ func getVMInfos() ([]*machine.ListResponse, error) {
listEntry.RemoteUsername = vm.RemoteUsername
listEntry.IdentityPath = vm.IdentityPath
listEntry.CreatedAt = vm.Created
+ listEntry.Starting = vm.Starting
if listEntry.CreatedAt.IsZero() {
listEntry.CreatedAt = time.Now()
@@ -1087,6 +1088,7 @@ func getVMInfos() ([]*machine.ListResponse, error) {
if err != nil {
return err
}
+ listEntry.Running = state == machine.Running
if !vm.LastUp.IsZero() { // this means we have already written a time to the config
listEntry.LastUp = vm.LastUp
@@ -1097,12 +1099,6 @@ func getVMInfos() ([]*machine.ListResponse, error) {
return err
}
}
- switch state {
- case machine.Running:
- listEntry.Running = true
- case machine.Starting:
- listEntry.Starting = true
- }
listed = append(listed, listEntry)
}
diff --git a/test/apiv2/27-containersEvents.at b/test/apiv2/27-containersEvents.at
index a86f2e353..e0a66e0ac 100644
--- a/test/apiv2/27-containersEvents.at
+++ b/test/apiv2/27-containersEvents.at
@@ -18,6 +18,10 @@ t GET "libpod/events?stream=false&since=$START" 200 \
'select(.status | contains("died")).Action=died' \
'select(.status | contains("died")).Actor.Attributes.containerExitCode=1'
+t GET "libpod/events?stream=false&since=$START" 200 \
+ 'select(.status | contains("start")).Action=start' \
+ 'select(.status | contains("start")).HealthStatus='\
+
# compat api, uses status=die (#12643)
t GET "events?stream=false&since=$START" 200 \
'select(.status | contains("start")).Action=start' \
diff --git a/test/e2e/config.go b/test/e2e/config.go
index 2ca8e2a15..fbcc9dfff 100644
--- a/test/e2e/config.go
+++ b/test/e2e/config.go
@@ -15,7 +15,7 @@ var (
healthcheck = "quay.io/libpod/alpine_healthcheck:latest"
ImageCacheDir = "/tmp/podman/imagecachedir"
fedoraToolbox = "registry.fedoraproject.org/fedora-toolbox:36"
- volumeTest = "quay.io/libpod/volume-plugin-test-img:latest"
+ volumeTest = "quay.io/libpod/volume-plugin-test-img:20220623"
// This image has seccomp profiles that blocks all syscalls.
// The intention behind blocking all syscalls is to prevent
diff --git a/test/e2e/events_test.go b/test/e2e/events_test.go
index 725118ab0..528fa143d 100644
--- a/test/e2e/events_test.go
+++ b/test/e2e/events_test.go
@@ -216,4 +216,25 @@ var _ = Describe("Podman events", func() {
Expect(result.OutputToString()).To(ContainSubstring("create"))
})
+ It("podman events health_status generated", func() {
+ session := podmanTest.Podman([]string{"run", "--name", "test-hc", "-dt", "--health-cmd", "echo working", "busybox"})
+ session.WaitWithDefaultTimeout()
+ Expect(session).Should(Exit(0))
+
+ for i := 0; i < 5; i++ {
+ hc := podmanTest.Podman([]string{"healthcheck", "run", "test-hc"})
+ hc.WaitWithDefaultTimeout()
+ exitCode := hc.ExitCode()
+ if exitCode == 0 || i == 4 {
+ break
+ }
+ time.Sleep(1 * time.Second)
+ }
+
+ result := podmanTest.Podman([]string{"events", "--stream=false", "--filter", "event=health_status"})
+ result.WaitWithDefaultTimeout()
+ Expect(result).Should(Exit(0))
+ Expect(len(result.OutputToStringArray())).To(BeNumerically(">=", 1), "Number of health_status events")
+ })
+
})
diff --git a/test/e2e/volume_ls_test.go b/test/e2e/volume_ls_test.go
index 19f87fb8a..dcfb13f4e 100644
--- a/test/e2e/volume_ls_test.go
+++ b/test/e2e/volume_ls_test.go
@@ -152,6 +152,37 @@ var _ = Describe("Podman volume ls", func() {
Expect(lsDangling).Should(Exit(0))
Expect(lsDangling.OutputToString()).To(ContainSubstring(volName1))
})
+
+ It("podman ls volume with --filter name", func() {
+ volName1 := "volume1"
+ session := podmanTest.Podman([]string{"volume", "create", volName1})
+ session.WaitWithDefaultTimeout()
+ Expect(session).Should(Exit(0))
+
+ volName2 := "volume2"
+ session2 := podmanTest.Podman([]string{"volume", "create", volName2})
+ session2.WaitWithDefaultTimeout()
+ Expect(session2).Should(Exit(0))
+
+ session = podmanTest.Podman([]string{"volume", "ls", "--filter", "name=volume1*"})
+ session.WaitWithDefaultTimeout()
+ Expect(session).Should(Exit(0))
+ Expect(session.OutputToStringArray()).To(HaveLen(3))
+ Expect(session.OutputToStringArray()[1]).To(ContainSubstring(volName1))
+ Expect(session.OutputToStringArray()[2]).To(ContainSubstring(volName2))
+
+ session = podmanTest.Podman([]string{"volume", "ls", "--filter", "name=volumex"})
+ session.WaitWithDefaultTimeout()
+ Expect(session).Should(Exit(0))
+ Expect(session.OutputToStringArray()).To(BeEmpty())
+
+ session = podmanTest.Podman([]string{"volume", "ls", "--filter", "name=volume1"})
+ session.WaitWithDefaultTimeout()
+ Expect(session).Should(Exit(0))
+ Expect(session.OutputToStringArray()).To(HaveLen(2))
+ Expect(session.OutputToStringArray()[1]).To(ContainSubstring(volName1))
+ })
+
It("podman ls volume with multiple --filter flag", func() {
session := podmanTest.Podman([]string{"volume", "create", "--label", "foo=bar", "myvol"})
volName := session.OutputToString()
diff --git a/test/e2e/volume_plugin_test.go b/test/e2e/volume_plugin_test.go
index 4700afdb5..b585f8dd8 100644
--- a/test/e2e/volume_plugin_test.go
+++ b/test/e2e/volume_plugin_test.go
@@ -6,6 +6,7 @@ import (
"path/filepath"
. "github.com/containers/podman/v4/test/utils"
+ "github.com/containers/storage/pkg/stringid"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
. "github.com/onsi/gomega/gexec"
@@ -188,4 +189,71 @@ var _ = Describe("Podman volume plugins", func() {
rmAll.WaitWithDefaultTimeout()
Expect(rmAll).Should(Exit(0))
})
+
+ It("podman volume reload", func() {
+ podmanTest.AddImageToRWStore(volumeTest)
+
+ confFile := filepath.Join(podmanTest.TempDir, "containers.conf")
+ err := os.WriteFile(confFile, []byte(`[engine]
+[engine.volume_plugins]
+testvol5 = "/run/docker/plugins/testvol5.sock"`), 0o644)
+ Expect(err).ToNot(HaveOccurred())
+ os.Setenv("CONTAINERS_CONF", confFile)
+
+ pluginStatePath := filepath.Join(podmanTest.TempDir, "volumes")
+ err = os.Mkdir(pluginStatePath, 0755)
+ Expect(err).ToNot(HaveOccurred())
+
+ // Keep this distinct within tests to avoid multiple tests using the same plugin.
+ pluginName := "testvol5"
+ ctrName := "pluginCtr"
+ plugin := podmanTest.Podman([]string{"run", "--name", ctrName, "--security-opt", "label=disable", "-v", "/run/docker/plugins:/run/docker/plugins",
+ "-v", fmt.Sprintf("%v:%v", pluginStatePath, pluginStatePath), "-d", volumeTest, "--sock-name", pluginName, "--path", pluginStatePath})
+ plugin.WaitWithDefaultTimeout()
+ Expect(plugin).Should(Exit(0))
+
+ localvol := "local-" + stringid.GenerateNonCryptoID()
+ // create local volume
+ session := podmanTest.Podman([]string{"volume", "create", localvol})
+ session.WaitWithDefaultTimeout()
+ Expect(session).To(Exit(0))
+
+ vol1 := "vol1-" + stringid.GenerateNonCryptoID()
+ session = podmanTest.Podman([]string{"volume", "create", "--driver", pluginName, vol1})
+ session.WaitWithDefaultTimeout()
+ Expect(session).To(Exit(0))
+
+ // now create volume in plugin without podman
+ vol2 := "vol2-" + stringid.GenerateNonCryptoID()
+ plugin = podmanTest.Podman([]string{"exec", ctrName, "/usr/local/bin/testvol", "--sock-name", pluginName, "create", vol2})
+ plugin.WaitWithDefaultTimeout()
+ Expect(plugin).Should(Exit(0))
+
+ session = podmanTest.Podman([]string{"volume", "ls", "-q"})
+ session.WaitWithDefaultTimeout()
+ Expect(session).To(Exit(0))
+ Expect(session.OutputToStringArray()).To(ContainElements(localvol, vol1))
+ Expect(session.ErrorToString()).To(Equal("")) // make sure no errors are shown
+
+ plugin = podmanTest.Podman([]string{"exec", ctrName, "/usr/local/bin/testvol", "--sock-name", pluginName, "remove", vol1})
+ plugin.WaitWithDefaultTimeout()
+ Expect(plugin).Should(Exit(0))
+
+ // now reload volumes from plugins
+ session = podmanTest.Podman([]string{"volume", "reload"})
+ session.WaitWithDefaultTimeout()
+ Expect(session).To(Exit(0))
+ Expect(string(session.Out.Contents())).To(Equal(fmt.Sprintf(`Added:
+%s
+Removed:
+%s
+`, vol2, vol1)))
+ Expect(session.ErrorToString()).To(Equal("")) // make sure no errors are shown
+
+ session = podmanTest.Podman([]string{"volume", "ls", "-q"})
+ session.WaitWithDefaultTimeout()
+ Expect(session).To(Exit(0))
+ Expect(session.OutputToStringArray()).To(ContainElements(localvol, vol2))
+ Expect(session.ErrorToString()).To(Equal("")) // make no errors are shown
+ })
})
diff --git a/test/system/160-volumes.bats b/test/system/160-volumes.bats
index 797883ec6..da60112a0 100644
--- a/test/system/160-volumes.bats
+++ b/test/system/160-volumes.bats
@@ -64,6 +64,29 @@ function teardown() {
}
+# Filter volumes by name
+@test "podman volume filter --name" {
+ suffix=$(random_string)
+ prefix="volume"
+
+ for i in 1 2; do
+ myvolume=${prefix}_${i}_${suffix}
+ run_podman volume create $myvolume
+ is "$output" "$myvolume" "output from volume create $i"
+ done
+
+ run_podman volume ls --filter name=${prefix}_1.+ --format "{{.Name}}"
+ is "$output" "${prefix}_1_${suffix}" "--filter name=${prefix}_1.+ shows only one volume"
+
+ # The _1* is intentional as asterisk has different meaning in glob and regexp. Make sure this is regexp
+ run_podman volume ls --filter name=${prefix}_1* --format "{{.Name}}"
+ is "$output" "${prefix}_1_${suffix}.*${prefix}_2_${suffix}.*" "--filter name=${prefix}_1* shows ${prefix}_1_${suffix} and ${prefix}_2_${suffix}"
+
+ for i in 1 2; do
+ run_podman volume rm ${prefix}_${i}_${suffix}
+ done
+}
+
# Named volumes
@test "podman volume create / run" {
myvolume=myvol$(random_string)
diff --git a/test/testvol/Containerfile b/test/testvol/Containerfile
new file mode 100644
index 000000000..32448f5a9
--- /dev/null
+++ b/test/testvol/Containerfile
@@ -0,0 +1,9 @@
+FROM docker.io/library/golang:1.18-alpine AS build-img
+COPY ./ /go/src/github.com/containers/podman/
+WORKDIR /go/src/github.com/containers/podman
+RUN GO111MODULE=off go build -o /testvol ./test/testvol
+
+FROM alpine
+COPY --from=build-img /testvol /usr/local/bin
+WORKDIR /
+ENTRYPOINT ["/usr/local/bin/testvol", "serve"]
diff --git a/test/testvol/create.go b/test/testvol/create.go
new file mode 100644
index 000000000..d29300f0b
--- /dev/null
+++ b/test/testvol/create.go
@@ -0,0 +1,26 @@
+package main
+
+import (
+ pluginapi "github.com/docker/go-plugins-helpers/volume"
+ "github.com/spf13/cobra"
+)
+
+var createCmd = &cobra.Command{
+ Use: "create NAME",
+ Short: "create a volume",
+ Long: `Create a volume in the volume plugin listening on --sock-name`,
+ Args: cobra.ExactArgs(1),
+ RunE: func(cmd *cobra.Command, args []string) error {
+ return createVol(config.sockName, args[0])
+ },
+}
+
+func createVol(sockName, volName string) error {
+ plugin, err := getPlugin(sockName)
+ if err != nil {
+ return err
+ }
+ createReq := new(pluginapi.CreateRequest)
+ createReq.Name = volName
+ return plugin.CreateVolume(createReq)
+}
diff --git a/test/testvol/list.go b/test/testvol/list.go
new file mode 100644
index 000000000..fea615a70
--- /dev/null
+++ b/test/testvol/list.go
@@ -0,0 +1,32 @@
+package main
+
+import (
+ "fmt"
+
+ "github.com/spf13/cobra"
+)
+
+var listCmd = &cobra.Command{
+ Use: "list",
+ Short: "list all volumes",
+ Long: `List all volumes from the volume plugin listening on --sock-name`,
+ Args: cobra.NoArgs,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ return listVol(config.sockName)
+ },
+}
+
+func listVol(sockName string) error {
+ plugin, err := getPlugin(sockName)
+ if err != nil {
+ return err
+ }
+ vols, err := plugin.ListVolumes()
+ if err != nil {
+ return err
+ }
+ for _, vol := range vols {
+ fmt.Println(vol.Name)
+ }
+ return nil
+}
diff --git a/test/testvol/main.go b/test/testvol/main.go
index 30ab365b3..99c6fb694 100644
--- a/test/testvol/main.go
+++ b/test/testvol/main.go
@@ -14,13 +14,20 @@ import (
)
var rootCmd = &cobra.Command{
- Use: "testvol",
- Short: "testvol - volume plugin for Podman",
+ Use: "testvol",
+ Short: "testvol - volume plugin for Podman testing",
+ PersistentPreRunE: before,
+ SilenceUsage: true,
+}
+
+var serveCmd = &cobra.Command{
+ Use: "serve",
+ Short: "serve the volume plugin on the unix socket",
Long: `Creates simple directory volumes using the Volume Plugin API for testing volume plugin functionality`,
+ Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
return startServer(config.sockName)
},
- PersistentPreRunE: before,
}
// Configuration for the volume plugin
@@ -37,9 +44,12 @@ var config = cliConfig{
}
func init() {
- rootCmd.Flags().StringVar(&config.sockName, "sock-name", config.sockName, "Name of unix socket for plugin")
- rootCmd.Flags().StringVar(&config.path, "path", "", "Path to initialize state and mount points")
+ rootCmd.PersistentFlags().StringVar(&config.sockName, "sock-name", config.sockName, "Name of unix socket for plugin")
rootCmd.PersistentFlags().StringVar(&config.logLevel, "log-level", config.logLevel, "Log messages including and over the specified level: debug, info, warn, error, fatal, panic")
+
+ serveCmd.Flags().StringVar(&config.path, "path", "", "Path to initialize state and mount points")
+
+ rootCmd.AddCommand(serveCmd, createCmd, removeCmd, listCmd)
}
func before(cmd *cobra.Command, args []string) error {
@@ -59,11 +69,8 @@ func before(cmd *cobra.Command, args []string) error {
func main() {
if err := rootCmd.Execute(); err != nil {
- logrus.Errorf("Running volume plugin: %v", err)
os.Exit(1)
}
-
- os.Exit(0)
}
// startServer runs the HTTP server and responds to requests
diff --git a/test/testvol/remove.go b/test/testvol/remove.go
new file mode 100644
index 000000000..2839b0b50
--- /dev/null
+++ b/test/testvol/remove.go
@@ -0,0 +1,26 @@
+package main
+
+import (
+ pluginapi "github.com/docker/go-plugins-helpers/volume"
+ "github.com/spf13/cobra"
+)
+
+var removeCmd = &cobra.Command{
+ Use: "remove NAME",
+ Short: "remove a volume",
+ Long: `Remove a volume in the volume plugin listening on --sock-name`,
+ Args: cobra.ExactArgs(1),
+ RunE: func(cmd *cobra.Command, args []string) error {
+ return removeVol(config.sockName, args[0])
+ },
+}
+
+func removeVol(sockName, volName string) error {
+ plugin, err := getPlugin(sockName)
+ if err != nil {
+ return err
+ }
+ removeReq := new(pluginapi.RemoveRequest)
+ removeReq.Name = volName
+ return plugin.RemoveVolume(removeReq)
+}
diff --git a/test/testvol/util.go b/test/testvol/util.go
new file mode 100644
index 000000000..7a0aeba86
--- /dev/null
+++ b/test/testvol/util.go
@@ -0,0 +1,29 @@
+package main
+
+import (
+ "path/filepath"
+ "strings"
+
+ "github.com/containers/podman/v4/libpod/plugin"
+)
+
+const pluginSockDir = "/run/docker/plugins"
+
+func getSocketPath(pathOrName string) string {
+ if filepath.IsAbs(pathOrName) {
+ return pathOrName
+ }
+
+ // only a name join it with the default path
+ return filepath.Join(pluginSockDir, pathOrName+".sock")
+}
+
+func getPluginName(pathOrName string) string {
+ return strings.TrimSuffix(filepath.Base(pathOrName), ".sock")
+}
+
+func getPlugin(sockNameOrPath string) (*plugin.VolumePlugin, error) {
+ path := getSocketPath(sockNameOrPath)
+ name := getPluginName(sockNameOrPath)
+ return plugin.GetVolumePlugin(name, path)
+}