Merge pull request #26459 from giuseppe/add-volume-uid-gid

volumes: add new --uid and --gid option
This commit is contained in:
openshift-merge-bot[bot]
2025-06-18 14:49:51 +00:00
committed by GitHub
6 changed files with 125 additions and 1 deletions

View File

@ -23,7 +23,8 @@ var (
ValidArgsFunction: completion.AutocompleteNone,
Example: `podman volume create myvol
podman volume create
podman volume create --label foo=bar myvol`,
podman volume create --label foo=bar myvol
podman volume create --uid 4321 --gid 1234 myvol`,
}
)
@ -33,6 +34,8 @@ var (
Label []string
Opts []string
Ignore bool
UID int
GID int
}{}
)
@ -57,6 +60,14 @@ func init() {
ignoreFlagName := "ignore"
flags.BoolVar(&opts.Ignore, ignoreFlagName, false, "Don't fail if volume already exists")
uidFlagName := "uid"
flags.IntVar(&opts.UID, uidFlagName, 0, "Set the UID of the volume owner")
_ = createCommand.RegisterFlagCompletionFunc(uidFlagName, completion.AutocompleteNone)
gidFlagName := "gid"
flags.IntVar(&opts.GID, gidFlagName, 0, "Set the GID of the volume owner")
_ = createCommand.RegisterFlagCompletionFunc(gidFlagName, completion.AutocompleteNone)
}
func create(cmd *cobra.Command, args []string) error {
@ -77,6 +88,12 @@ func create(cmd *cobra.Command, args []string) error {
if err != nil {
return fmt.Errorf("unable to process options: %w", err)
}
if cmd.Flags().Changed("uid") {
createOpts.UID = &opts.UID
}
if cmd.Flags().Changed("gid") {
createOpts.GID = &opts.GID
}
response, err := registry.ContainerEngine().VolumeCreate(context.Background(), createOpts)
if err != nil {
return err

View File

@ -28,6 +28,10 @@ An overlay filesystem is created, which allows changes to the volume to be commi
Using a value other than **local** or **image**, Podman attempts to create the volume using a volume plugin with the given name.
Such plugins must be defined in the **volume_plugins** section of the **[containers.conf(5)](https://github.com/containers/common/blob/main/docs/containers.conf.5.md)** configuration file.
#### **--gid**=*gid*
Set the GID that the volume will be created as. Differently than `--opt o=gid=*gid*`, the specified value is not passed to the mount operation. The specified GID will own the volume's mount point directory and affects the volume chown operation.
#### **--help**
Print usage statement
@ -69,6 +73,10 @@ This option is mandatory when using the **image** driver.
When not using the **local** and **image** drivers, the given options are passed directly to the volume plugin. In this case, supported options are dictated by the plugin in question, not Podman.
#### **--uid**=*uid*
Set the UID that the volume will be created as. Differently than `--opt o=uid=*uid*`, the specified value is not passed to the mount operation. The specified UID will own the volume's mount point directory and affects the volume chown operation.
## QUOTAS
`podman volume create` uses `XFS project quota controls` for controlling the size and the number of inodes of builtin volumes. The directory used to store the volumes must be an `XFS` file system and be mounted with the `pquota` option.
@ -124,6 +132,11 @@ Create tmpfs named volume testvol with specified options.
# podman volume create --opt device=tmpfs --opt type=tmpfs --opt o=uid=1000,gid=1000 testvol
```
Create volume overriding the owner UID and GID.
```
# podman volume create --uid 1000 --gid 1000 myvol
```
Create image named volume using the specified local image in containers/storage.
```
# podman volume create --driver image --opt image=fedora:latest fedoraVol

View File

@ -77,6 +77,13 @@ func CreateVolume(w http.ResponseWriter, r *http.Request) {
volumeOptions = append(volumeOptions, libpod.WithVolumeIgnoreIfExist())
}
if input.UID != nil {
volumeOptions = append(volumeOptions, libpod.WithVolumeUID(*input.UID), libpod.WithVolumeNoChown())
}
if input.GID != nil {
volumeOptions = append(volumeOptions, libpod.WithVolumeGID(*input.GID), libpod.WithVolumeNoChown())
}
vol, err := runtime.NewVolume(r.Context(), volumeOptions...)
if err != nil {
utils.InternalServerError(w, err)

View File

@ -18,6 +18,10 @@ type VolumeCreateOptions struct {
Options map[string]string `schema:"opts"`
// Ignore existing volumes
IgnoreIfExists bool `schema:"ignoreIfExist"`
// UID that the volume will be created as
UID *int `schema:"uid"`
// GID that the volume will be created as
GID *int `schema:"gid"`
}
type VolumeRmReport struct {

View File

@ -40,6 +40,13 @@ func (ic *ContainerEngine) VolumeCreate(ctx context.Context, opts entities.Volum
volumeOptions = append(volumeOptions, libpod.WithVolumeIgnoreIfExist())
}
if opts.UID != nil {
volumeOptions = append(volumeOptions, libpod.WithVolumeUID(*opts.UID), libpod.WithVolumeNoChown())
}
if opts.GID != nil {
volumeOptions = append(volumeOptions, libpod.WithVolumeGID(*opts.GID), libpod.WithVolumeNoChown())
}
vol, err := ic.Libpod.NewVolume(ctx, volumeOptions...)
if err != nil {
return nil, err

View File

@ -179,6 +179,82 @@ var _ = Describe("Podman volume create", func() {
Expect(inspectOpts.OutputToString()).To(Equal(optionStrFormatExpect))
})
It("podman create volume with --uid and --gid flags", func() {
volName := "testVolFlags"
uid := "3001"
gid := "4001"
session := podmanTest.Podman([]string{"volume", "create", "--uid", uid, "--gid", gid, volName})
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
inspectUID := podmanTest.Podman([]string{"volume", "inspect", "--format", "{{ .UID }}", volName})
inspectUID.WaitWithDefaultTimeout()
Expect(inspectUID).Should(ExitCleanly())
Expect(inspectUID.OutputToString()).To(Equal(uid))
inspectGID := podmanTest.Podman([]string{"volume", "inspect", "--format", "{{ .GID }}", volName})
inspectGID.WaitWithDefaultTimeout()
Expect(inspectGID).Should(ExitCleanly())
Expect(inspectGID.OutputToString()).To(Equal(gid))
// The specified values must not be passed down to the -o option.
fn := func(typ string) string {
return fmt.Sprintf("{{ if .Options.%s }}{{ .Options.%s }}{{ else }}EMPTY{{ end }}", typ, typ)
}
optionFormat := fmt.Sprintf("%s:%s:%s", fn("o"), fn("UID"), fn("GID"))
optionStrFormatExpected := "EMPTY:EMPTY:EMPTY"
inspectOpts := podmanTest.Podman([]string{"volume", "inspect", "--format", optionFormat, volName})
inspectOpts.WaitWithDefaultTimeout()
Expect(inspectOpts).Should(ExitCleanly())
Expect(inspectOpts.OutputToString()).To(Equal(optionStrFormatExpected))
})
It("podman create volume with --uid flag only", func() {
volName := "testVolUidOnly"
uid := "3002"
session := podmanTest.Podman([]string{"volume", "create", "--uid", uid, volName})
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
inspectUID := podmanTest.Podman([]string{"volume", "inspect", "--format", "{{ .UID }}", volName})
inspectUID.WaitWithDefaultTimeout()
Expect(inspectUID).Should(ExitCleanly())
Expect(inspectUID.OutputToString()).To(Equal(uid))
inspectGID := podmanTest.Podman([]string{"volume", "inspect", "--format", "{{ if .GID }}{{ .GID }}{{ else }} EMPTY {{ end }}", volName})
inspectGID.WaitWithDefaultTimeout()
Expect(inspectGID).Should(ExitCleanly())
Expect(inspectGID.OutputToString()).To(Equal("EMPTY"))
})
It("podman create volume with --gid flag only", func() {
volName := "testVolGidOnly"
gid := "4002"
session := podmanTest.Podman([]string{"volume", "create", "--gid", gid, volName})
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
inspectGID := podmanTest.Podman([]string{"volume", "inspect", "--format", "{{ .GID }}", volName})
inspectGID.WaitWithDefaultTimeout()
Expect(inspectGID).Should(ExitCleanly())
Expect(inspectGID.OutputToString()).To(Equal(gid))
inspectUID := podmanTest.Podman([]string{"volume", "inspect", "--format", "{{ if .UID }}{{ .UID }}{{ else }} EMPTY {{ end }}", volName})
inspectUID.WaitWithDefaultTimeout()
Expect(inspectUID).Should(ExitCleanly())
Expect(inspectUID.OutputToString()).To(Equal("EMPTY"))
})
It("podman create volume --uid and --gid flags with invalid values", func() {
session := podmanTest.Podman([]string{"volume", "create", "--uid", "invalid", "testVol"})
session.WaitWithDefaultTimeout()
Expect(session).To(ExitWithError(125, "invalid argument \"invalid\" for \"--uid\" flag"))
session = podmanTest.Podman([]string{"volume", "create", "--gid", "invalid", "testVol"})
session.WaitWithDefaultTimeout()
Expect(session).To(ExitWithError(125, "invalid argument \"invalid\" for \"--gid\" flag"))
})
It("image-backed volume basic functionality", func() {
podmanTest.AddImageToRWStore(fedoraMinimal)
volName := "testvol"