Secret create - add ignore option to allow noop

Signed-off-by: Ygal Blum <ygal.blum@gmail.com>
This commit is contained in:
Ygal Blum 2025-06-18 16:28:48 -04:00
parent 1f1618fcb0
commit bfc327a08e
16 changed files with 138 additions and 36 deletions

View File

@ -57,6 +57,8 @@ func init() {
flags.BoolVar(&createOpts.Replace, "replace", false, "If a secret with the same name exists, replace it") flags.BoolVar(&createOpts.Replace, "replace", false, "If a secret with the same name exists, replace it")
flags.BoolVar(&createOpts.Ignore, "ignore", false, "If a secret with the same name exists, ignore and do not create a new secret")
labelFlagName := "label" labelFlagName := "label"
flags.StringArrayVarP(&labels, labelFlagName, "l", nil, "Specify labels on the secret") flags.StringArrayVarP(&labels, labelFlagName, "l", nil, "Specify labels on the secret")
_ = createCmd.RegisterFlagCompletionFunc(labelFlagName, completion.AutocompleteNone) _ = createCmd.RegisterFlagCompletionFunc(labelFlagName, completion.AutocompleteNone)
@ -65,6 +67,11 @@ func init() {
func create(cmd *cobra.Command, args []string) error { func create(cmd *cobra.Command, args []string) error {
name := args[0] name := args[0]
// Validate that --ignore and --replace are not used together
if createOpts.Ignore && createOpts.Replace {
return errors.New("cannot use --ignore and --replace flags together")
}
var err error var err error
path := args[1] path := args[1]

View File

@ -38,6 +38,12 @@ Read secret data from environment variable.
Print usage statement. Print usage statement.
#### **--ignore**=*false*
If a secret with the same name already exists, do not return an error and return the existing secret's ID instead of creating a new one.
Cannot be used with `--replace`.
The default is **false**.
#### **--label**, **-l**=*key=val1,key2=val2* #### **--label**, **-l**=*key=val1,key2=val2*
Add label to secret. These labels can be viewed in podman secrete inspect or ls. Add label to secret. These labels can be viewed in podman secrete inspect or ls.
@ -46,6 +52,7 @@ Add label to secret. These labels can be viewed in podman secrete inspect or ls.
If existing secret with the same name already exists, update the secret. If existing secret with the same name already exists, update the secret.
The `--replace` option does not change secrets within existing containers, only newly created containers. The `--replace` option does not change secrets within existing containers, only newly created containers.
Cannot be used with `--ignore`.
The default is **false**. The default is **false**.
## SECRET DRIVERS ## SECRET DRIVERS

4
go.mod
View File

@ -13,7 +13,7 @@ require (
github.com/checkpoint-restore/go-criu/v7 v7.2.0 github.com/checkpoint-restore/go-criu/v7 v7.2.0
github.com/containernetworking/plugins v1.7.1 github.com/containernetworking/plugins v1.7.1
github.com/containers/buildah v1.40.1-0.20250604193037-b8d8cc375f30 github.com/containers/buildah v1.40.1-0.20250604193037-b8d8cc375f30
github.com/containers/common v0.63.2-0.20250604184922-bb2062b6265c github.com/containers/common v0.63.2-0.20250624163146-1bc9d1737003
github.com/containers/conmon v2.0.20+incompatible github.com/containers/conmon v2.0.20+incompatible
github.com/containers/gvisor-tap-vsock v0.8.6 github.com/containers/gvisor-tap-vsock v0.8.6
github.com/containers/image/v5 v5.35.1-0.20250603145948-347a6e7283ef github.com/containers/image/v5 v5.35.1-0.20250603145948-347a6e7283ef
@ -53,7 +53,7 @@ require (
github.com/nxadm/tail v1.4.11 github.com/nxadm/tail v1.4.11
github.com/onsi/ginkgo/v2 v2.23.4 github.com/onsi/ginkgo/v2 v2.23.4
github.com/onsi/gomega v1.37.0 github.com/onsi/gomega v1.37.0
github.com/opencontainers/cgroups v0.0.2 github.com/opencontainers/cgroups v0.0.3
github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/go-digest v1.0.0
github.com/opencontainers/image-spec v1.1.1 github.com/opencontainers/image-spec v1.1.1
github.com/opencontainers/runtime-spec v1.2.1 github.com/opencontainers/runtime-spec v1.2.1

8
go.sum
View File

@ -66,8 +66,8 @@ github.com/containernetworking/plugins v1.7.1 h1:CNAR0jviDj6FS5Vg85NTgKWLDzZPfi/
github.com/containernetworking/plugins v1.7.1/go.mod h1:xuMdjuio+a1oVQsHKjr/mgzuZ24leAsqUYRnzGoXHy0= github.com/containernetworking/plugins v1.7.1/go.mod h1:xuMdjuio+a1oVQsHKjr/mgzuZ24leAsqUYRnzGoXHy0=
github.com/containers/buildah v1.40.1-0.20250604193037-b8d8cc375f30 h1:kCt0fnVBvXY9J98pUDeUc0gHKrhRwaBTWWD3otLutCE= github.com/containers/buildah v1.40.1-0.20250604193037-b8d8cc375f30 h1:kCt0fnVBvXY9J98pUDeUc0gHKrhRwaBTWWD3otLutCE=
github.com/containers/buildah v1.40.1-0.20250604193037-b8d8cc375f30/go.mod h1:QDecwvjrr+e0VD5GYv2dw7tsiqrz673r8B4rIYFP11Y= github.com/containers/buildah v1.40.1-0.20250604193037-b8d8cc375f30/go.mod h1:QDecwvjrr+e0VD5GYv2dw7tsiqrz673r8B4rIYFP11Y=
github.com/containers/common v0.63.2-0.20250604184922-bb2062b6265c h1:j4epZCkQt8Jdpz2GsUzvqY4MfaOfJamrNpZnmbV84Ug= github.com/containers/common v0.63.2-0.20250624163146-1bc9d1737003 h1:Nk8VZ9Ht7/HnYveikzd8RqNSPphbh358Chmt/GyPeWI=
github.com/containers/common v0.63.2-0.20250604184922-bb2062b6265c/go.mod h1:efNRNweihnq5nXALnAPDXTpC7uJtnFV4pNuETTfvI8s= github.com/containers/common v0.63.2-0.20250624163146-1bc9d1737003/go.mod h1:mQkSk7VxbvgOo3vLE7yy6spgWNg8Ni0Zytt8HpmRKRw=
github.com/containers/conmon v2.0.20+incompatible h1:YbCVSFSCqFjjVwHTPINGdMX1F6JXHGTUje2ZYobNrkg= github.com/containers/conmon v2.0.20+incompatible h1:YbCVSFSCqFjjVwHTPINGdMX1F6JXHGTUje2ZYobNrkg=
github.com/containers/conmon v2.0.20+incompatible/go.mod h1:hgwZ2mtuDrppv78a/cOBNiCm6O0UMWGx1mu7P00nu5I= github.com/containers/conmon v2.0.20+incompatible/go.mod h1:hgwZ2mtuDrppv78a/cOBNiCm6O0UMWGx1mu7P00nu5I=
github.com/containers/gvisor-tap-vsock v0.8.6 h1:9SeAXK+K2o36CtrgYk6zRXbU3zrayjvkrI8b7/O6u5A= github.com/containers/gvisor-tap-vsock v0.8.6 h1:9SeAXK+K2o36CtrgYk6zRXbU3zrayjvkrI8b7/O6u5A=
@ -354,8 +354,8 @@ github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus
github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8= github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8=
github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y= github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y=
github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0= github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0=
github.com/opencontainers/cgroups v0.0.2 h1:A+mAPPMfgKNCEZUUtibESFx06uvhAmvo8sSz3Abwk7o= github.com/opencontainers/cgroups v0.0.3 h1:Jc9dWh/0YLGjdy6J/9Ln8NM5BfTA4W2BY0GMozy3aDU=
github.com/opencontainers/cgroups v0.0.2/go.mod h1:s8lktyhlGUqM7OSRL5P7eAW6Wb+kWPNvt4qvVfzA5vs= github.com/opencontainers/cgroups v0.0.3/go.mod h1:s8lktyhlGUqM7OSRL5P7eAW6Wb+kWPNvt4qvVfzA5vs=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=

View File

@ -27,6 +27,7 @@ func CreateSecret(w http.ResponseWriter, r *http.Request) {
DriverOpts map[string]string `schema:"driveropts"` DriverOpts map[string]string `schema:"driveropts"`
Labels map[string]string `schema:"labels"` Labels map[string]string `schema:"labels"`
Replace bool `schema:"replace"` Replace bool `schema:"replace"`
Ignore bool `schema:"ignore"`
}{ }{
// override any golang type defaults // override any golang type defaults
} }
@ -40,6 +41,7 @@ func CreateSecret(w http.ResponseWriter, r *http.Request) {
opts.DriverOpts = query.DriverOpts opts.DriverOpts = query.DriverOpts
opts.Labels = query.Labels opts.Labels = query.Labels
opts.Replace = query.Replace opts.Replace = query.Replace
opts.Ignore = query.Ignore
ic := abi.ContainerEngine{Libpod: runtime} ic := abi.ContainerEngine{Libpod: runtime}
report, err := ic.SecretCreate(r.Context(), query.Name, r.Body, opts) report, err := ic.SecretCreate(r.Context(), query.Name, r.Body, opts)

View File

@ -29,4 +29,5 @@ type CreateOptions struct {
DriverOpts map[string]string DriverOpts map[string]string
Labels map[string]string Labels map[string]string
Replace *bool Replace *bool
Ignore *bool
} }

View File

@ -91,3 +91,18 @@ func (o *CreateOptions) GetReplace() bool {
} }
return *o.Replace return *o.Replace
} }
// WithIgnore set field Ignore to given value
func (o *CreateOptions) WithIgnore(value bool) *CreateOptions {
o.Ignore = &value
return o
}
// GetIgnore returns value of field Ignore
func (o *CreateOptions) GetIgnore() bool {
if o.Ignore == nil {
var z bool
return z
}
return *o.Ignore
}

View File

@ -12,6 +12,7 @@ type SecretCreateOptions struct {
DriverOpts map[string]string DriverOpts map[string]string
Labels map[string]string Labels map[string]string
Replace bool Replace bool
Ignore bool
} }
type SecretInspectOptions struct { type SecretInspectOptions struct {

View File

@ -47,9 +47,10 @@ func (ic *ContainerEngine) SecretCreate(ctx context.Context, name string, reader
} }
storeOpts := secrets.StoreOptions{ storeOpts := secrets.StoreOptions{
DriverOpts: options.DriverOpts, DriverOpts: options.DriverOpts,
Labels: options.Labels, Labels: options.Labels,
Replace: options.Replace, Replace: options.Replace,
IgnoreIfExists: options.Ignore,
} }
secretID, err := manager.Store(name, data, options.Driver, storeOpts) secretID, err := manager.Store(name, data, options.Driver, storeOpts)

View File

@ -16,7 +16,8 @@ func (ic *ContainerEngine) SecretCreate(ctx context.Context, name string, reader
WithDriverOpts(options.DriverOpts). WithDriverOpts(options.DriverOpts).
WithName(name). WithName(name).
WithLabels(options.Labels). WithLabels(options.Labels).
WithReplace(options.Replace) WithReplace(options.Replace).
WithIgnore(options.Ignore)
created, err := secrets.Create(ic.ClientCtx, reader, opts) created, err := secrets.Create(ic.ClientCtx, reader, opts)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -64,6 +64,34 @@ var _ = Describe("Podman secret", func() {
Expect(inspect.OutputToString()).To(ContainSubstring("opt1:val1")) Expect(inspect.OutputToString()).To(ContainSubstring("opt1:val1"))
}) })
It("podman secret create with --ignore", func() {
secretFilePath := filepath.Join(podmanTest.TempDir, "secret")
err := os.WriteFile(secretFilePath, []byte("mysecret"), 0755)
Expect(err).ToNot(HaveOccurred())
// First create a secret
session := podmanTest.Podman([]string{"secret", "create", "ignore-test", secretFilePath})
session.WaitWithDefaultTimeout()
secrID := session.OutputToString()
Expect(session).Should(ExitCleanly())
// Try to create the same secret again without --ignore, should fail
session = podmanTest.Podman([]string{"secret", "create", "ignore-test", secretFilePath})
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitWithError(125, "Error: ignore-test: secret name in use"))
// Try to create the same secret again with --ignore, should succeed and return existing ID
session = podmanTest.Podman([]string{"secret", "create", "--ignore", "ignore-test", secretFilePath})
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
Expect(session.OutputToString()).To(Equal(secrID))
// Try to use both --ignore and --replace, should fail
session = podmanTest.Podman([]string{"secret", "create", "--ignore", "--replace", "ignore-test", secretFilePath})
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitWithError(125, "Error: cannot use --ignore and --replace flags together"))
})
It("podman secret create bad name should fail", func() { It("podman secret create bad name should fail", func() {
secretFilePath := filepath.Join(podmanTest.TempDir, "secret") secretFilePath := filepath.Join(podmanTest.TempDir, "secret")
err := os.WriteFile(secretFilePath, []byte("mysecret"), 0755) err := os.WriteFile(secretFilePath, []byte("mysecret"), 0755)

View File

@ -3,9 +3,7 @@
package cgroups package cgroups
import ( import (
"fmt"
"path/filepath" "path/filepath"
"strconv"
"github.com/opencontainers/cgroups" "github.com/opencontainers/cgroups"
"github.com/opencontainers/cgroups/fs" "github.com/opencontainers/cgroups/fs"
@ -57,31 +55,44 @@ func (c *linuxMemHandler) Stat(ctr *CgroupControl, m *cgroups.Stats) error {
if ctr.cgroup2 { if ctr.cgroup2 {
memoryRoot = filepath.Join(cgroupRoot, ctr.config.Path) memoryRoot = filepath.Join(cgroupRoot, ctr.config.Path)
limitFilename = "memory.max" limitFilename = "memory.max"
if memUsage.Usage.Usage, err = readFileByKeyAsUint64(filepath.Join(memoryRoot, "memory.stat"), "anon"); err != nil {
// Read memory.current
current, err := readFileAsUint64(filepath.Join(memoryRoot, "memory.current"))
if err != nil {
return err return err
} }
// Read inactive_file from memory.stat
inactiveFile, err := readFileByKeyAsUint64(filepath.Join(memoryRoot, "memory.stat"), "inactive_file")
if err != nil {
return err
}
// Docker calculation: memory.current - memory.stat['inactive_file']
memUsage.Usage.Usage = 0
if inactiveFile < current {
memUsage.Usage.Usage = current - inactiveFile
}
} else { } else {
memoryRoot = ctr.getCgroupv1Path(Memory) memoryRoot = ctr.getCgroupv1Path(Memory)
limitFilename = "memory.limit_in_bytes" limitFilename = "memory.limit_in_bytes"
path := filepath.Join(memoryRoot, "memory.stat") // Read memory.usage_in_bytes
values, err := readCgroupMapPath(path) usageInBytes, err := readFileAsUint64(filepath.Join(memoryRoot, "memory.usage_in_bytes"))
if err != nil { if err != nil {
return err return err
} }
// cgroup v1 does not have a single "anon" field, but we can calculate it // Read total_inactive_file from memory.stat
// from total_active_anon and total_inactive_anon totalInactiveFile, err := readFileByKeyAsUint64(filepath.Join(memoryRoot, "memory.stat"), "total_inactive_file")
if err != nil {
return err
}
// Docker calculation: memory.usage_in_bytes - memory.stat['total_inactive_file']
memUsage.Usage.Usage = 0 memUsage.Usage.Usage = 0
for _, key := range []string{"total_active_anon", "total_inactive_anon"} { if totalInactiveFile < usageInBytes {
if _, found := values[key]; !found { memUsage.Usage.Usage = usageInBytes - totalInactiveFile
continue
}
res, err := strconv.ParseUint(values[key][0], 10, 64)
if err != nil {
return fmt.Errorf("parse %s from %s: %w", key, path, err)
}
memUsage.Usage.Usage += res
} }
} }

View File

@ -48,6 +48,9 @@ var errAmbiguous = errors.New("secret is ambiguous")
// errDataSize indicates that the secret data is too large or too small // errDataSize indicates that the secret data is too large or too small
var errDataSize = errors.New("secret data must be larger than 0 and less than 512000 bytes") var errDataSize = errors.New("secret data must be larger than 0 and less than 512000 bytes")
// errIgnoreIfExistsAndReplace indicates that ignoreIfExists and replace cannot be used together.
var errIgnoreIfExistsAndReplace = errors.New("ignoreIfExists and replace cannot be used together")
// secretsFile is the name of the file that the secrets database will be stored in // secretsFile is the name of the file that the secrets database will be stored in
var secretsFile = "secrets.json" var secretsFile = "secrets.json"
@ -114,6 +117,8 @@ type StoreOptions struct {
Labels map[string]string Labels map[string]string
// Replace existing secret // Replace existing secret
Replace bool Replace bool
// Ignore if already exists
IgnoreIfExists bool
} }
// NewManager creates a new secrets manager // NewManager creates a new secrets manager
@ -169,6 +174,11 @@ func (s *SecretsManager) Store(name string, data []byte, driverType string, opti
if len(data) == 0 || len(data) >= maxSecretSize { if len(data) == 0 || len(data) >= maxSecretSize {
return "", errDataSize return "", errDataSize
} }
if options.IgnoreIfExists && options.Replace {
return "", errIgnoreIfExistsAndReplace
}
var secr *Secret var secr *Secret
s.lockfile.Lock() s.lockfile.Lock()
defer s.lockfile.Unlock() defer s.lockfile.Unlock()
@ -179,13 +189,16 @@ func (s *SecretsManager) Store(name string, data []byte, driverType string, opti
} }
if exist { if exist {
if !options.Replace { if !options.Replace && !options.IgnoreIfExists {
return "", fmt.Errorf("%s: %w", name, errSecretNameInUse) return "", fmt.Errorf("%s: %w", name, errSecretNameInUse)
} }
secr, err = s.lookupSecret(name) secr, err = s.lookupSecret(name)
if err != nil { if err != nil {
return "", err return "", err
} }
if options.IgnoreIfExists {
return secr.ID, nil
}
secr.UpdatedAt = time.Now() secr.UpdatedAt = time.Now()
} else { } else {
secr = new(Secret) secr = new(Secret)

View File

@ -29,7 +29,7 @@ type Cgroup struct {
ScopePrefix string `json:"scope_prefix,omitempty"` ScopePrefix string `json:"scope_prefix,omitempty"`
// Resources contains various cgroups settings to apply. // Resources contains various cgroups settings to apply.
*Resources `json:"Resources,omitempty"` *Resources
// Systemd tells if systemd should be used to manage cgroups. // Systemd tells if systemd should be used to manage cgroups.
Systemd bool `json:"Systemd,omitempty"` Systemd bool `json:"Systemd,omitempty"`

View File

@ -5,6 +5,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"math"
"os" "os"
"path/filepath" "path/filepath"
"strconv" "strconv"
@ -413,16 +414,30 @@ func WriteCgroupProc(dir string, pid int) error {
return err return err
} }
// Since the OCI spec is designed for cgroup v1, in some cases // ConvertCPUSharesToCgroupV2Value converts CPU shares, used by cgroup v1,
// there is need to convert from the cgroup v1 configuration to cgroup v2 // to CPU weight, used by cgroup v2.
// the formula for cpuShares is y = (1 + ((x - 2) * 9999) / 262142) //
// convert from [2-262144] to [1-10000] // Cgroup v1 CPU shares has a range of [2^1...2^18], i.e. [2...262144],
// 262144 comes from Linux kernel definition "#define MAX_SHARES (1UL << 18)" // and the default value is 1024.
//
// Cgroup v2 CPU weight has a range of [10^0...10^4], i.e. [1...10000],
// and the default value is 100.
func ConvertCPUSharesToCgroupV2Value(cpuShares uint64) uint64 { func ConvertCPUSharesToCgroupV2Value(cpuShares uint64) uint64 {
// The value of 0 means "unset".
if cpuShares == 0 { if cpuShares == 0 {
return 0 return 0
} }
return (1 + ((cpuShares-2)*9999)/262142) if cpuShares <= 2 {
return 1
}
if cpuShares >= 262144 {
return 10000
}
l := math.Log2(float64(cpuShares))
// Quadratic function which fits min, max, and default.
exponent := (l*l+125*l)/612.0 - 7.0/34.0
return uint64(math.Ceil(math.Pow(10, exponent)))
} }
// ConvertMemorySwapToCgroupV2Value converts MemorySwap value from OCI spec // ConvertMemorySwapToCgroupV2Value converts MemorySwap value from OCI spec

4
vendor/modules.txt vendored
View File

@ -144,7 +144,7 @@ github.com/containers/buildah/pkg/sshagent
github.com/containers/buildah/pkg/util github.com/containers/buildah/pkg/util
github.com/containers/buildah/pkg/volumes github.com/containers/buildah/pkg/volumes
github.com/containers/buildah/util github.com/containers/buildah/util
# github.com/containers/common v0.63.2-0.20250604184922-bb2062b6265c # github.com/containers/common v0.63.2-0.20250624163146-1bc9d1737003
## explicit; go 1.23.3 ## explicit; go 1.23.3
github.com/containers/common/internal github.com/containers/common/internal
github.com/containers/common/internal/attributedstring github.com/containers/common/internal/attributedstring
@ -802,7 +802,7 @@ github.com/onsi/gomega/matchers/support/goraph/edge
github.com/onsi/gomega/matchers/support/goraph/node github.com/onsi/gomega/matchers/support/goraph/node
github.com/onsi/gomega/matchers/support/goraph/util github.com/onsi/gomega/matchers/support/goraph/util
github.com/onsi/gomega/types github.com/onsi/gomega/types
# github.com/opencontainers/cgroups v0.0.2 # github.com/opencontainers/cgroups v0.0.3
## explicit; go 1.23.0 ## explicit; go 1.23.0
github.com/opencontainers/cgroups github.com/opencontainers/cgroups
github.com/opencontainers/cgroups/devices/config github.com/opencontainers/cgroups/devices/config