From bfc327a08e1024f28c64cc75a80c9677d4e90e08 Mon Sep 17 00:00:00 2001 From: Ygal Blum Date: Wed, 18 Jun 2025 16:28:48 -0400 Subject: [PATCH] Secret create - add ignore option to allow noop Signed-off-by: Ygal Blum --- cmd/podman/secrets/create.go | 7 +++ .../source/markdown/podman-secret-create.1.md | 7 +++ go.mod | 4 +- go.sum | 8 ++-- pkg/api/handlers/libpod/secrets.go | 2 + pkg/bindings/secrets/types.go | 1 + pkg/bindings/secrets/types_create_options.go | 15 +++++++ pkg/domain/entities/secrets.go | 1 + pkg/domain/infra/abi/secrets.go | 7 +-- pkg/domain/infra/tunnel/secrets.go | 3 +- test/e2e/secret_test.go | 28 ++++++++++++ .../common/pkg/cgroups/memory_linux.go | 43 ++++++++++++------- .../containers/common/pkg/secrets/secrets.go | 15 ++++++- .../opencontainers/cgroups/config_linux.go | 2 +- .../opencontainers/cgroups/utils.go | 27 +++++++++--- vendor/modules.txt | 4 +- 16 files changed, 138 insertions(+), 36 deletions(-) diff --git a/cmd/podman/secrets/create.go b/cmd/podman/secrets/create.go index 9ebe4b4990..70c8cdab02 100644 --- a/cmd/podman/secrets/create.go +++ b/cmd/podman/secrets/create.go @@ -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.Ignore, "ignore", false, "If a secret with the same name exists, ignore and do not create a new secret") + labelFlagName := "label" flags.StringArrayVarP(&labels, labelFlagName, "l", nil, "Specify labels on the secret") _ = createCmd.RegisterFlagCompletionFunc(labelFlagName, completion.AutocompleteNone) @@ -65,6 +67,11 @@ func init() { func create(cmd *cobra.Command, args []string) error { 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 path := args[1] diff --git a/docs/source/markdown/podman-secret-create.1.md b/docs/source/markdown/podman-secret-create.1.md index f499f1d718..2bb22d62d3 100644 --- a/docs/source/markdown/podman-secret-create.1.md +++ b/docs/source/markdown/podman-secret-create.1.md @@ -38,6 +38,12 @@ Read secret data from environment variable. 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* 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. The `--replace` option does not change secrets within existing containers, only newly created containers. +Cannot be used with `--ignore`. The default is **false**. ## SECRET DRIVERS diff --git a/go.mod b/go.mod index 0ee4a280b1..a4f5ef0427 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/checkpoint-restore/go-criu/v7 v7.2.0 github.com/containernetworking/plugins v1.7.1 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/gvisor-tap-vsock v0.8.6 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/onsi/ginkgo/v2 v2.23.4 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/image-spec v1.1.1 github.com/opencontainers/runtime-spec v1.2.1 diff --git a/go.sum b/go.sum index 7aa616fc8d..f19f3c23a8 100644 --- a/go.sum +++ b/go.sum @@ -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/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/common v0.63.2-0.20250604184922-bb2062b6265c h1:j4epZCkQt8Jdpz2GsUzvqY4MfaOfJamrNpZnmbV84Ug= -github.com/containers/common v0.63.2-0.20250604184922-bb2062b6265c/go.mod h1:efNRNweihnq5nXALnAPDXTpC7uJtnFV4pNuETTfvI8s= +github.com/containers/common v0.63.2-0.20250624163146-1bc9d1737003 h1:Nk8VZ9Ht7/HnYveikzd8RqNSPphbh358Chmt/GyPeWI= +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/go.mod h1:hgwZ2mtuDrppv78a/cOBNiCm6O0UMWGx1mu7P00nu5I= 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/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y= 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.2/go.mod h1:s8lktyhlGUqM7OSRL5P7eAW6Wb+kWPNvt4qvVfzA5vs= +github.com/opencontainers/cgroups v0.0.3 h1:Jc9dWh/0YLGjdy6J/9Ln8NM5BfTA4W2BY0GMozy3aDU= +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/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= diff --git a/pkg/api/handlers/libpod/secrets.go b/pkg/api/handlers/libpod/secrets.go index 068ebecb76..ece86bc4f1 100644 --- a/pkg/api/handlers/libpod/secrets.go +++ b/pkg/api/handlers/libpod/secrets.go @@ -27,6 +27,7 @@ func CreateSecret(w http.ResponseWriter, r *http.Request) { DriverOpts map[string]string `schema:"driveropts"` Labels map[string]string `schema:"labels"` Replace bool `schema:"replace"` + Ignore bool `schema:"ignore"` }{ // override any golang type defaults } @@ -40,6 +41,7 @@ func CreateSecret(w http.ResponseWriter, r *http.Request) { opts.DriverOpts = query.DriverOpts opts.Labels = query.Labels opts.Replace = query.Replace + opts.Ignore = query.Ignore ic := abi.ContainerEngine{Libpod: runtime} report, err := ic.SecretCreate(r.Context(), query.Name, r.Body, opts) diff --git a/pkg/bindings/secrets/types.go b/pkg/bindings/secrets/types.go index 4b4244a94d..cefdf6af7a 100644 --- a/pkg/bindings/secrets/types.go +++ b/pkg/bindings/secrets/types.go @@ -29,4 +29,5 @@ type CreateOptions struct { DriverOpts map[string]string Labels map[string]string Replace *bool + Ignore *bool } diff --git a/pkg/bindings/secrets/types_create_options.go b/pkg/bindings/secrets/types_create_options.go index 8403b1f805..35b14e3bc7 100644 --- a/pkg/bindings/secrets/types_create_options.go +++ b/pkg/bindings/secrets/types_create_options.go @@ -91,3 +91,18 @@ func (o *CreateOptions) GetReplace() bool { } 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 +} diff --git a/pkg/domain/entities/secrets.go b/pkg/domain/entities/secrets.go index cd35ab4cc9..f865d546c6 100644 --- a/pkg/domain/entities/secrets.go +++ b/pkg/domain/entities/secrets.go @@ -12,6 +12,7 @@ type SecretCreateOptions struct { DriverOpts map[string]string Labels map[string]string Replace bool + Ignore bool } type SecretInspectOptions struct { diff --git a/pkg/domain/infra/abi/secrets.go b/pkg/domain/infra/abi/secrets.go index b56da6568f..3969ca7990 100644 --- a/pkg/domain/infra/abi/secrets.go +++ b/pkg/domain/infra/abi/secrets.go @@ -47,9 +47,10 @@ func (ic *ContainerEngine) SecretCreate(ctx context.Context, name string, reader } storeOpts := secrets.StoreOptions{ - DriverOpts: options.DriverOpts, - Labels: options.Labels, - Replace: options.Replace, + DriverOpts: options.DriverOpts, + Labels: options.Labels, + Replace: options.Replace, + IgnoreIfExists: options.Ignore, } secretID, err := manager.Store(name, data, options.Driver, storeOpts) diff --git a/pkg/domain/infra/tunnel/secrets.go b/pkg/domain/infra/tunnel/secrets.go index 326a75c8b4..d6dc71221b 100644 --- a/pkg/domain/infra/tunnel/secrets.go +++ b/pkg/domain/infra/tunnel/secrets.go @@ -16,7 +16,8 @@ func (ic *ContainerEngine) SecretCreate(ctx context.Context, name string, reader WithDriverOpts(options.DriverOpts). WithName(name). WithLabels(options.Labels). - WithReplace(options.Replace) + WithReplace(options.Replace). + WithIgnore(options.Ignore) created, err := secrets.Create(ic.ClientCtx, reader, opts) if err != nil { return nil, err diff --git a/test/e2e/secret_test.go b/test/e2e/secret_test.go index d56a9f757f..d8951089fd 100644 --- a/test/e2e/secret_test.go +++ b/test/e2e/secret_test.go @@ -64,6 +64,34 @@ var _ = Describe("Podman secret", func() { 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() { secretFilePath := filepath.Join(podmanTest.TempDir, "secret") err := os.WriteFile(secretFilePath, []byte("mysecret"), 0755) diff --git a/vendor/github.com/containers/common/pkg/cgroups/memory_linux.go b/vendor/github.com/containers/common/pkg/cgroups/memory_linux.go index f81bf992b3..fc45ffe859 100644 --- a/vendor/github.com/containers/common/pkg/cgroups/memory_linux.go +++ b/vendor/github.com/containers/common/pkg/cgroups/memory_linux.go @@ -3,9 +3,7 @@ package cgroups import ( - "fmt" "path/filepath" - "strconv" "github.com/opencontainers/cgroups" "github.com/opencontainers/cgroups/fs" @@ -57,31 +55,44 @@ func (c *linuxMemHandler) Stat(ctr *CgroupControl, m *cgroups.Stats) error { if ctr.cgroup2 { memoryRoot = filepath.Join(cgroupRoot, ctr.config.Path) 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 } + + // 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 { memoryRoot = ctr.getCgroupv1Path(Memory) limitFilename = "memory.limit_in_bytes" - path := filepath.Join(memoryRoot, "memory.stat") - values, err := readCgroupMapPath(path) + // Read memory.usage_in_bytes + usageInBytes, err := readFileAsUint64(filepath.Join(memoryRoot, "memory.usage_in_bytes")) if err != nil { return err } - // cgroup v1 does not have a single "anon" field, but we can calculate it - // from total_active_anon and total_inactive_anon + // Read total_inactive_file from memory.stat + 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 - for _, key := range []string{"total_active_anon", "total_inactive_anon"} { - if _, found := values[key]; !found { - 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 + if totalInactiveFile < usageInBytes { + memUsage.Usage.Usage = usageInBytes - totalInactiveFile } } diff --git a/vendor/github.com/containers/common/pkg/secrets/secrets.go b/vendor/github.com/containers/common/pkg/secrets/secrets.go index 7d8d33a114..958b3971de 100644 --- a/vendor/github.com/containers/common/pkg/secrets/secrets.go +++ b/vendor/github.com/containers/common/pkg/secrets/secrets.go @@ -48,6 +48,9 @@ var errAmbiguous = errors.New("secret is ambiguous") // 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") +// 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 var secretsFile = "secrets.json" @@ -114,6 +117,8 @@ type StoreOptions struct { Labels map[string]string // Replace existing secret Replace bool + // Ignore if already exists + IgnoreIfExists bool } // 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 { return "", errDataSize } + + if options.IgnoreIfExists && options.Replace { + return "", errIgnoreIfExistsAndReplace + } + var secr *Secret s.lockfile.Lock() defer s.lockfile.Unlock() @@ -179,13 +189,16 @@ func (s *SecretsManager) Store(name string, data []byte, driverType string, opti } if exist { - if !options.Replace { + if !options.Replace && !options.IgnoreIfExists { return "", fmt.Errorf("%s: %w", name, errSecretNameInUse) } secr, err = s.lookupSecret(name) if err != nil { return "", err } + if options.IgnoreIfExists { + return secr.ID, nil + } secr.UpdatedAt = time.Now() } else { secr = new(Secret) diff --git a/vendor/github.com/opencontainers/cgroups/config_linux.go b/vendor/github.com/opencontainers/cgroups/config_linux.go index 9f1d44537b..9bc58a3789 100644 --- a/vendor/github.com/opencontainers/cgroups/config_linux.go +++ b/vendor/github.com/opencontainers/cgroups/config_linux.go @@ -29,7 +29,7 @@ type Cgroup struct { ScopePrefix string `json:"scope_prefix,omitempty"` // Resources contains various cgroups settings to apply. - *Resources `json:"Resources,omitempty"` + *Resources // Systemd tells if systemd should be used to manage cgroups. Systemd bool `json:"Systemd,omitempty"` diff --git a/vendor/github.com/opencontainers/cgroups/utils.go b/vendor/github.com/opencontainers/cgroups/utils.go index 98b6a07fab..95b3310ab6 100644 --- a/vendor/github.com/opencontainers/cgroups/utils.go +++ b/vendor/github.com/opencontainers/cgroups/utils.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "io" + "math" "os" "path/filepath" "strconv" @@ -413,16 +414,30 @@ func WriteCgroupProc(dir string, pid int) error { return err } -// Since the OCI spec is designed for cgroup v1, in some cases -// there is need to convert from the cgroup v1 configuration to cgroup v2 -// the formula for cpuShares is y = (1 + ((x - 2) * 9999) / 262142) -// convert from [2-262144] to [1-10000] -// 262144 comes from Linux kernel definition "#define MAX_SHARES (1UL << 18)" +// ConvertCPUSharesToCgroupV2Value converts CPU shares, used by cgroup v1, +// to CPU weight, used by cgroup v2. +// +// Cgroup v1 CPU shares has a range of [2^1...2^18], i.e. [2...262144], +// 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 { + // The value of 0 means "unset". if cpuShares == 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 diff --git a/vendor/modules.txt b/vendor/modules.txt index 60ce13e7ba..629ebc81bf 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -144,7 +144,7 @@ github.com/containers/buildah/pkg/sshagent github.com/containers/buildah/pkg/util github.com/containers/buildah/pkg/volumes 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 github.com/containers/common/internal 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/util github.com/onsi/gomega/types -# github.com/opencontainers/cgroups v0.0.2 +# github.com/opencontainers/cgroups v0.0.3 ## explicit; go 1.23.0 github.com/opencontainers/cgroups github.com/opencontainers/cgroups/devices/config