From 68dbddd9791f9cf5367cd4ceacc41adb33a5f507 Mon Sep 17 00:00:00 2001 From: Ygal Blum Date: Mon, 3 Apr 2023 08:01:38 +0300 Subject: [PATCH] Add support for secret exists Add the command along with the abi and tunnel support Add e2e tests Add man page Add apiv2 test to ensure return codes Signed-off-by: Ygal Blum --- cmd/podman/secrets/exists.go | 38 ++++++++++++++++ .../source/markdown/podman-secret-exists.1.md | 43 +++++++++++++++++++ docs/source/markdown/podman-secret.1.md | 1 + pkg/api/handlers/libpod/secrets.go | 18 ++++++++ pkg/api/server/register_secrets.go | 21 +++++++++ pkg/bindings/secrets/secrets.go | 14 ++++++ pkg/domain/entities/engine_container.go | 1 + pkg/domain/infra/abi/secrets.go | 15 +++++++ pkg/domain/infra/tunnel/secrets.go | 8 ++++ test/apiv2/50-secrets.at | 6 +++ test/e2e/secret_test.go | 29 +++++++++++++ 11 files changed, 194 insertions(+) create mode 100644 cmd/podman/secrets/exists.go create mode 100644 docs/source/markdown/podman-secret-exists.1.md diff --git a/cmd/podman/secrets/exists.go b/cmd/podman/secrets/exists.go new file mode 100644 index 0000000000..3ec68a2130 --- /dev/null +++ b/cmd/podman/secrets/exists.go @@ -0,0 +1,38 @@ +package secrets + +import ( + "github.com/containers/podman/v4/cmd/podman/common" + "github.com/containers/podman/v4/cmd/podman/registry" + "github.com/spf13/cobra" +) + +var ( + existsCmd = &cobra.Command{ + Use: "exists SECRET", + Short: "Check if a secret exists in local storage", + Long: `If the named secret exists in local storage, podman secret exists exits with 0, otherwise the exit code will be 1.`, + Args: cobra.ExactArgs(1), + RunE: exists, + ValidArgsFunction: common.AutocompleteSecrets, + Example: `podman secret exists ID + podman secret exists SECRET || podman secret create SECRET `, + } +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Command: existsCmd, + Parent: secretCmd, + }) +} + +func exists(cmd *cobra.Command, args []string) error { + found, err := registry.ContainerEngine().SecretExists(registry.GetContext(), args[0]) + if err != nil { + return err + } + if !found.Value { + registry.SetExitCode(1) + } + return nil +} diff --git a/docs/source/markdown/podman-secret-exists.1.md b/docs/source/markdown/podman-secret-exists.1.md new file mode 100644 index 0000000000..e240646319 --- /dev/null +++ b/docs/source/markdown/podman-secret-exists.1.md @@ -0,0 +1,43 @@ +% podman-secret-exists 1 + +## NAME +podman\-secret\-exists - Check if the given secret exists + +## SYNOPSIS +**podman secret exists** *secret* + +## DESCRIPTION +**podman secret exists** checks if a secret exists. Podman will return an exit code +of `0` when the secret is found. A `1` will be returned otherwise. An exit code of +`125` indicates there was another issue. + + +## OPTIONS + +#### **--help**, **-h** + +Print usage statement + +## EXAMPLE + +Check if a secret called `mysecret` exists (the secret does actually exist). +``` +$ podman secret exists mysecret +$ echo $? +0 +$ +``` + +Check if a secret called `mypassword` exists (the secret does not actually exist). +``` +$ podman secret exists mypassword +$ echo $? +1 +$ +``` + +## SEE ALSO +**[podman(1)](podman.1.md)**, **[podman-secret(1)](podman-secret.1.md)** + +## HISTORY +April 2023, Originally compiled by Ygal Blum `` diff --git a/docs/source/markdown/podman-secret.1.md b/docs/source/markdown/podman-secret.1.md index f6c0a945f9..c4181ccf32 100644 --- a/docs/source/markdown/podman-secret.1.md +++ b/docs/source/markdown/podman-secret.1.md @@ -14,6 +14,7 @@ podman secret is a set of subcommands that manage secrets. | Command | Man Page | Description | | ------- | ------------------------------------------------------ | ------------------------------------------------------ | | create | [podman-secret-create(1)](podman-secret-create.1.md) | Create a new secret | +| exists | [podman-secret-exists(1)](podman-secret-exists.1.md) | Check if the given secret exists | | inspect | [podman-secret-inspect(1)](podman-secret-inspect.1.md) | Display detailed information on one or more secrets | | ls | [podman-secret-ls(1)](podman-secret-ls.1.md) | List all available secrets | | rm | [podman-secret-rm(1)](podman-secret-rm.1.md) | Remove one or more secrets | diff --git a/pkg/api/handlers/libpod/secrets.go b/pkg/api/handlers/libpod/secrets.go index c24ac85638..ec6b60d953 100644 --- a/pkg/api/handlers/libpod/secrets.go +++ b/pkg/api/handlers/libpod/secrets.go @@ -4,6 +4,7 @@ import ( "fmt" "net/http" + "github.com/containers/common/pkg/secrets" "github.com/containers/podman/v4/libpod" "github.com/containers/podman/v4/pkg/api/handlers/utils" api "github.com/containers/podman/v4/pkg/api/types" @@ -44,3 +45,20 @@ func CreateSecret(w http.ResponseWriter, r *http.Request) { } utils.WriteResponse(w, http.StatusOK, report) } + +func SecretExists(w http.ResponseWriter, r *http.Request) { + runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) + name := utils.GetName(r) + ic := abi.ContainerEngine{Libpod: runtime} + + report, err := ic.SecretExists(r.Context(), name) + if err != nil { + utils.InternalServerError(w, err) + return + } + if !report.Value { + utils.SecretNotFound(w, name, secrets.ErrNoSuchSecret) + return + } + utils.WriteResponse(w, http.StatusNoContent, "") +} diff --git a/pkg/api/server/register_secrets.go b/pkg/api/server/register_secrets.go index a60145958d..f5bda79d37 100644 --- a/pkg/api/server/register_secrets.go +++ b/pkg/api/server/register_secrets.go @@ -89,6 +89,27 @@ func (s *APIServer) registerSecretHandlers(r *mux.Router) error { // '500': // "$ref": "#/responses/internalError" r.Handle(VersionedPath("/libpod/secrets/{name}/json"), s.APIHandler(compat.InspectSecret)).Methods(http.MethodGet) + // swagger:operation GET /libpod/secrets/{name}/exists libpod SecretExistsLibpod + // --- + // tags: + // - secrets + // summary: Secret exists + // parameters: + // - in: path + // name: name + // type: string + // required: true + // description: the name or ID of the secret + // produces: + // - application/json + // responses: + // 204: + // description: secret exists + // 404: + // $ref: '#/responses/NoSuchSecret' + // '500': + // "$ref": "#/responses/internalError" + r.Handle(VersionedPath("/libpod/secrets/{name}/exists"), s.APIHandler(libpod.SecretExists)).Methods(http.MethodGet) // swagger:operation DELETE /libpod/secrets/{name} libpod SecretDeleteLibpod // --- // tags: diff --git a/pkg/bindings/secrets/secrets.go b/pkg/bindings/secrets/secrets.go index 7ad827dd33..d78e3902f8 100644 --- a/pkg/bindings/secrets/secrets.go +++ b/pkg/bindings/secrets/secrets.go @@ -88,3 +88,17 @@ func Create(ctx context.Context, reader io.Reader, options *CreateOptions) (*ent return create, response.Process(&create) } + +func Exists(ctx context.Context, nameOrID string) (bool, error) { + conn, err := bindings.GetClient(ctx) + if err != nil { + return false, err + } + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/secrets/%s/exists", nil, nil, nameOrID) + if err != nil { + return false, err + } + defer response.Body.Close() + + return response.IsSuccess(), nil +} diff --git a/pkg/domain/entities/engine_container.go b/pkg/domain/entities/engine_container.go index 06a6372fbf..b5da16c491 100644 --- a/pkg/domain/entities/engine_container.go +++ b/pkg/domain/entities/engine_container.go @@ -95,6 +95,7 @@ type ContainerEngine interface { //nolint:interfacebloat SecretInspect(ctx context.Context, nameOrIDs []string) ([]*SecretInfoReport, []error, error) SecretList(ctx context.Context, opts SecretListRequest) ([]*SecretInfoReport, error) SecretRm(ctx context.Context, nameOrID []string, opts SecretRmOptions) ([]*SecretRmReport, error) + SecretExists(ctx context.Context, nameOrID string) (*BoolReport, error) Shutdown(ctx context.Context) SystemDf(ctx context.Context, options SystemDfOptions) (*SystemDfReport, error) Unshare(ctx context.Context, args []string, options SystemUnshareOptions) error diff --git a/pkg/domain/infra/abi/secrets.go b/pkg/domain/infra/abi/secrets.go index 929858c5c6..45d7246418 100644 --- a/pkg/domain/infra/abi/secrets.go +++ b/pkg/domain/infra/abi/secrets.go @@ -2,6 +2,7 @@ package abi import ( "context" + "errors" "fmt" "io" "path/filepath" @@ -165,3 +166,17 @@ func (ic *ContainerEngine) SecretRm(ctx context.Context, nameOrIDs []string, opt return reports, nil } + +func (ic *ContainerEngine) SecretExists(ctx context.Context, nameOrID string) (*entities.BoolReport, error) { + manager, err := ic.Libpod.SecretsManager() + if err != nil { + return nil, err + } + + secret, err := manager.Lookup(nameOrID) + if err != nil && !errors.Is(err, secrets.ErrNoSuchSecret) { + return nil, err + } + + return &entities.BoolReport{Value: secret != nil}, nil +} diff --git a/pkg/domain/infra/tunnel/secrets.go b/pkg/domain/infra/tunnel/secrets.go index aa48cb7646..47ce328356 100644 --- a/pkg/domain/infra/tunnel/secrets.go +++ b/pkg/domain/infra/tunnel/secrets.go @@ -87,3 +87,11 @@ func (ic *ContainerEngine) SecretRm(ctx context.Context, nameOrIDs []string, opt } return allRm, nil } + +func (ic *ContainerEngine) SecretExists(ctx context.Context, nameOrID string) (*entities.BoolReport, error) { + exists, err := secrets.Exists(ic.ClientCtx, nameOrID) + if err != nil { + return nil, err + } + return &entities.BoolReport{Value: exists}, nil +} diff --git a/test/apiv2/50-secrets.at b/test/apiv2/50-secrets.at index acd8f3de94..f0449e600e 100644 --- a/test/apiv2/50-secrets.at +++ b/test/apiv2/50-secrets.at @@ -18,6 +18,12 @@ t GET secrets/mysecret 200 \ # secret inspect non-existent secret t GET secrets/bogus 404 +# Check exists with existing secret +t GET libpod/secrets/mysecret/exists 204 + +# Check exists for non-existing secret +t GET libpod/secrets/bogus/exists 404 + # secret list t GET secrets 200 \ length=1 \ diff --git a/test/e2e/secret_test.go b/test/e2e/secret_test.go index a7835fb731..72e6058639 100644 --- a/test/e2e/secret_test.go +++ b/test/e2e/secret_test.go @@ -372,4 +372,33 @@ var _ = Describe("Podman secret", func() { Expect(inspect.OutputToString()).To(Equal("map[]")) }) + + It("podman secret exists should return true if secret exists", func() { + secretFilePath := filepath.Join(podmanTest.TempDir, "secret") + err := os.WriteFile(secretFilePath, []byte("mysecret"), 0755) + Expect(err).ToNot(HaveOccurred()) + + secretName := "does_exist" + + session := podmanTest.Podman([]string{"secret", "create", secretName, secretFilePath}) + session.WaitWithDefaultTimeout() + secretID := session.OutputToString() + Expect(session).Should(Exit(0)) + + exists := podmanTest.Podman([]string{"secret", "exists", secretName}) + exists.WaitWithDefaultTimeout() + Expect(exists).Should(Exit(0)) + + exists = podmanTest.Podman([]string{"secret", "exists", secretID}) + exists.WaitWithDefaultTimeout() + Expect(exists).Should(Exit(0)) + }) + + It("podman secret exists should return false if secret does not exist", func() { + secretName := "does_not_exist" + + exists := podmanTest.Podman([]string{"secret", "exists", secretName}) + exists.WaitWithDefaultTimeout() + Expect(exists).Should(Exit(1)) + }) })