Skip to content

feat: add secrets with variants#507

Open
WitoDelnat wants to merge 8 commits intomainfrom
wito/secrets-v2
Open

feat: add secrets with variants#507
WitoDelnat wants to merge 8 commits intomainfrom
wito/secrets-v2

Conversation

@WitoDelnat
Copy link
Copy Markdown
Contributor

@WitoDelnat WitoDelnat commented May 8, 2026

Note

Medium Risk
Introduces new CI secrets/variables variant APIs and rewires multiple CLI verbs (add/list/get/remove) to use them, which can affect existing workflows and deletion behavior. Risk is mitigated by compatibility shims (hidden legacy flags/JSON paths) and added unit tests, but it’s still a broad behavior change.

Overview
Adds first-class CI secret/variable variants (with match attributes like --repo/--env/--branch/--workflow) by introducing v3beta2 API clients/types in pkg/api/ci.go, including pagination-aware listing, group/variant retrieval, set/update, and variant/group deletion.

Updates depot ci secrets and depot ci vars commands to manage variants: new set verbs, new secrets bulk dotenv import, new secrets get, repeatable selector flags, and updated list/table output to show variants (while keeping legacy JSON/list behavior when only the old single---repo selector is used).

Improves non-interactive handling by reading secret values from stdin (--from-stdin) and hiding the old --value flag for secrets, plus adds helper functions (IsStdinTerminal, stdin secret parsing) and extensive tests for variant resolution/filtering and flag/UX compatibility.

Also bumps numerous Go module dependencies in examples/go.mod/go.sum.

Reviewed by Cursor Bugbot for commit 8f63bc1. Bugbot is set up for automated code reviews on this repo. Configure here.

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Autofix Details

Bugbot Autofix prepared fixes for both issues found in the latest run.

  • ✅ Fixed: New CI commands use inline JSON instead of writeJSON
    • Replaced inline JSON encoding with shared output helpers and added shared output validation for the affected commands.
  • ✅ Fixed: Unused variant repository match helper functions
    • Removed the unused unexported repository match helper functions from variants.go.

Create PR

Or push these changes by commenting:

@cursor push 28af60c909
Preview (28af60c909)
diff --git a/pkg/cmd/ci/secrets.go b/pkg/cmd/ci/secrets.go
--- a/pkg/cmd/ci/secrets.go
+++ b/pkg/cmd/ci/secrets.go
@@ -1,7 +1,6 @@
 package ci
 
 import (
-	"encoding/json"
 	"fmt"
 	"io"
 	"os"
@@ -483,6 +482,9 @@
   depot ci secrets get MY_API_KEY production --output json`,
 		Args: cobra.MaximumNArgs(2),
 		RunE: func(cmd *cobra.Command, args []string) error {
+			if err := validateTextOrJSONOutput(output); err != nil {
+				return err
+			}
 			ctx := cmd.Context()
 
 			if orgID == "" {
@@ -534,10 +536,8 @@
 				resolved = matches[0]
 			}
 
-			if output == "json" {
-				enc := json.NewEncoder(os.Stdout)
-				enc.SetIndent("", "  ")
-				return enc.Encode(resolved)
+			if outputIsJSON(output) {
+				return writeJSON(resolved)
 			}
 
 			printSecretVariantDetail(secretName, resolved)
@@ -621,6 +621,9 @@
 		Aliases: []string{"ls"},
 		Args:    cobra.MaximumNArgs(1),
 		RunE: func(cmd *cobra.Command, args []string) error {
+			if err := validateTextOrJSONOutput(output); err != nil {
+				return err
+			}
 			ctx := cmd.Context()
 
 			if orgID == "" {
@@ -657,10 +660,8 @@
 				}
 			}
 
-			if output == "json" {
-				enc := json.NewEncoder(os.Stdout)
-				enc.SetIndent("", "  ")
-				return enc.Encode(result)
+			if outputIsJSON(output) {
+				return writeJSON(result)
 			}
 
 			if len(result.Secrets) == 0 {

diff --git a/pkg/cmd/ci/variants.go b/pkg/cmd/ci/variants.go
--- a/pkg/cmd/ci/variants.go
+++ b/pkg/cmd/ci/variants.go
@@ -45,14 +45,6 @@
 	return strings.Join(repos, ",")
 }
 
-func secretVariantRepositoryMatches(variant api.CISecretVariant, repos []string) bool {
-	return variantAttributesMatch(variant.Attributes, repos, nil, nil, nil)
-}
-
-func variableVariantRepositoryMatches(variant api.CIVariableVariant, repos []string) bool {
-	return variantAttributesMatch(variant.Attributes, repos, nil, nil, nil)
-}
-
 func filterSecretVariants(secret api.CISecretGroup, repo, environment, branch, workflow []string) api.CISecretGroup {
 	if len(repo) == 0 && len(environment) == 0 && len(branch) == 0 && len(workflow) == 0 {
 		return secret

diff --git a/pkg/cmd/ci/vars.go b/pkg/cmd/ci/vars.go
--- a/pkg/cmd/ci/vars.go
+++ b/pkg/cmd/ci/vars.go
@@ -1,9 +1,7 @@
 package ci
 
 import (
-	"encoding/json"
 	"fmt"
-	"os"
 	"strings"
 
 	"github.com/depot/cli/pkg/api"
@@ -324,6 +322,9 @@
 		Aliases: []string{"ls"},
 		Args:    cobra.MaximumNArgs(1),
 		RunE: func(cmd *cobra.Command, args []string) error {
+			if err := validateTextOrJSONOutput(output); err != nil {
+				return err
+			}
 			ctx := cmd.Context()
 
 			if orgID == "" {
@@ -360,10 +361,8 @@
 				}
 			}
 
-			if output == "json" {
-				enc := json.NewEncoder(os.Stdout)
-				enc.SetIndent("", "  ")
-				return enc.Encode(result)
+			if outputIsJSON(output) {
+				return writeJSON(result)
 			}
 
 			if len(result.Variables) == 0 {

You can send follow-ups to the cloud agent here.

Comment thread pkg/cmd/ci/secrets.go Outdated
Comment thread pkg/cmd/ci/variants.go Outdated
Fetch all secret and variable pages internally, keep secrets list output compact, and reject unsupported output formats consistently.
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Stdin consumed by SecretValueFromInput conflicts with SecretsBulk
    • Added a hidden --value fallback to secrets set so CI automation can provide the secret without blocking on stdin.

Create PR

Or push these changes by commenting:

@cursor push 44d042712b
Preview (44d042712b)
diff --git a/pkg/cmd/ci/ci_test.go b/pkg/cmd/ci/ci_test.go
--- a/pkg/cmd/ci/ci_test.go
+++ b/pkg/cmd/ci/ci_test.go
@@ -3,6 +3,7 @@
 import (
 	"testing"
 
+	"github.com/spf13/cobra"
 	"github.com/spf13/pflag"
 )
 
@@ -44,15 +45,20 @@
 }
 
 func TestSecretsAddKeepsHiddenValueFlagForCompatibility(t *testing.T) {
-	cmd := NewCmdSecretsAdd()
-
-	valueFlag := cmd.Flags().Lookup("value")
-	if valueFlag == nil {
-		t.Fatal("expected hidden --value compatibility flag")
+	for name, cmd := range map[string]*cobra.Command{
+		"secrets add": NewCmdSecretsAdd(),
+		"secrets set": NewCmdSecretsSet(),
+	} {
+		t.Run(name, func(t *testing.T) {
+			valueFlag := cmd.Flags().Lookup("value")
+			if valueFlag == nil {
+				t.Fatal("expected hidden --value compatibility flag")
+			}
+			if !valueFlag.Hidden {
+				t.Fatal("expected --value to stay hidden from help")
+			}
+		})
 	}
-	if !valueFlag.Hidden {
-		t.Fatal("expected --value to stay hidden from help")
-	}
 }
 
 func TestVariantSelectorFlagsAreRepeatable(t *testing.T) {

diff --git a/pkg/cmd/ci/secrets.go b/pkg/cmd/ci/secrets.go
--- a/pkg/cmd/ci/secrets.go
+++ b/pkg/cmd/ci/secrets.go
@@ -62,6 +62,7 @@
 	var (
 		orgID       string
 		token       string
+		value       string
 		description string
 		repo        []string
 		environment []string
@@ -112,9 +113,12 @@
 				return fmt.Errorf("secret name cannot be empty")
 			}
 
-			secretValue, err := helpers.SecretValueFromInput(fmt.Sprintf("Enter value for secret '%s': ", secretName))
-			if err != nil {
-				return fmt.Errorf("failed to read secret value: %w", err)
+			secretValue := value
+			if secretValue == "" {
+				secretValue, err = helpers.SecretValueFromInput(fmt.Sprintf("Enter value for secret '%s': ", secretName))
+				if err != nil {
+					return fmt.Errorf("failed to read secret value: %w", err)
+				}
 			}
 
 			result, err := api.CISetSecretVariant(ctx, tokenVal, orgID, api.CISetSecretVariantOptions{
@@ -138,11 +142,13 @@
 
 	cmd.Flags().StringVar(&orgID, "org", "", "Organization ID (required when user is a member of multiple organizations)")
 	cmd.Flags().StringVar(&token, "token", "", "Depot API token")
+	cmd.Flags().StringVar(&value, "value", "", "Secret value (deprecated; prefer stdin)")
 	cmd.Flags().StringVar(&description, "description", "", "Description of the secret variant")
 	cmd.Flags().StringArrayVar(&repo, "repo", nil, "Apply variant to a repository (repeatable, e.g. owner/repo)")
 	cmd.Flags().StringArrayVar(&environment, "env", nil, "Apply variant to an environment (repeatable)")
 	cmd.Flags().StringArrayVar(&branch, "branch", nil, "Apply variant to a branch (repeatable)")
 	cmd.Flags().StringArrayVar(&workflow, "workflow", nil, "Apply variant to a workflow file (repeatable)")
+	_ = cmd.Flags().MarkHidden("value")
 
 	return cmd
 }

You can send follow-ups to the cloud agent here.

Comment thread pkg/cmd/ci/secrets.go
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Vars set prompts interactively in non-interactive environments
    • Added a non-interactive stdin guard for vars set so it fails clearly unless --value is provided, with a regression test covering the behavior.

Create PR

Or push these changes by commenting:

@cursor push 38046dc8d1
Preview (38046dc8d1)
diff --git a/pkg/cmd/ci/ci_test.go b/pkg/cmd/ci/ci_test.go
--- a/pkg/cmd/ci/ci_test.go
+++ b/pkg/cmd/ci/ci_test.go
@@ -72,6 +72,21 @@
 	}
 }
 
+func TestVarsSetRequiresValueInNonInteractiveMode(t *testing.T) {
+	cmd := NewCmdVarsSet()
+	cmd.SilenceUsage = true
+	cmd.SilenceErrors = true
+	cmd.SetArgs([]string{"MY_VAR"})
+
+	err := cmd.Execute()
+	if err == nil {
+		t.Fatal("expected error")
+	}
+	if !strings.Contains(err.Error(), "pass --value") {
+		t.Fatalf("error = %q", err)
+	}
+}
+
 func TestVariantSelectorFlagsAreRepeatable(t *testing.T) {
 	for name, cmd := range map[string]commandWithFlags{
 		"secrets set":    {flags: NewCmdSecretsSet().Flags()},

diff --git a/pkg/cmd/ci/vars.go b/pkg/cmd/ci/vars.go
--- a/pkg/cmd/ci/vars.go
+++ b/pkg/cmd/ci/vars.go
@@ -86,6 +86,10 @@
 				orgID = config.GetCurrentOrganization()
 			}
 
+			if value == "" && !helpers.IsStdinTerminal() {
+				return fmt.Errorf("cannot prompt for a variable value in non-interactive mode; pass --value to provide the value")
+			}
+
 			tokenVal, err := helpers.ResolveProjectAuth(ctx, token)
 			if err != nil {
 				return err

You can send follow-ups to the cloud agent here.

Comment thread pkg/cmd/ci/vars.go
@WitoDelnat WitoDelnat changed the title feat: add CLI v2 feat: add secrets with variants May 9, 2026
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: List commands ignore user filter flags for API
    • List commands now pass repo, environment, branch, and workflow filters into the secret and variable variant API calls before applying local filtering.

Create PR

Or push these changes by commenting:

@cursor push 61597b7dc0
Preview (61597b7dc0)
diff --git a/pkg/cmd/ci/secrets.go b/pkg/cmd/ci/secrets.go
--- a/pkg/cmd/ci/secrets.go
+++ b/pkg/cmd/ci/secrets.go
@@ -678,10 +678,10 @@
 			} else {
 				var err error
 				result, err = api.CIListSecretVariants(ctx, tokenVal, orgID, api.CIListSecretVariantsOptions{
-					Repo:        nil,
-					Environment: nil,
-					Branch:      nil,
-					Workflow:    nil,
+					Repo:        repo,
+					Environment: environment,
+					Branch:      branch,
+					Workflow:    workflow,
 				})
 				if err != nil {
 					return fmt.Errorf("failed to list secrets: %w", err)

diff --git a/pkg/cmd/ci/vars.go b/pkg/cmd/ci/vars.go
--- a/pkg/cmd/ci/vars.go
+++ b/pkg/cmd/ci/vars.go
@@ -362,10 +362,10 @@
 			} else {
 				var err error
 				result, err = api.CIListVariableVariants(ctx, tokenVal, orgID, api.CIListVariableVariantsOptions{
-					Repo:        nil,
-					Environment: nil,
-					Branch:      nil,
-					Workflow:    nil,
+					Repo:        repo,
+					Environment: environment,
+					Branch:      branch,
+					Workflow:    workflow,
 				})
 				if err != nil {
 					return fmt.Errorf("failed to list CI variables: %w", err)

You can send follow-ups to the cloud agent here.

Reviewed by Cursor Bugbot for commit 8f63bc1. Configure here.

Comment thread pkg/cmd/ci/secrets.go
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant