···4444 }
4545}
46464747-// CreateSecret creates a new secret in the HSM
4747+// CreateSecret creates a new secret in the HSM, merging with existing data if present
4848func (c *Client) CreateSecret(ctx context.Context, name string, data map[string]any) error {
4949+ return c.CreateSecretWithOptions(ctx, name, data, false)
5050+}
5151+5252+// CreateSecretWithOptions creates a new secret in the HSM with replace option
5353+func (c *Client) CreateSecretWithOptions(ctx context.Context, name string, data map[string]any, replace bool) error {
5454+ // Only merge if replace is false
5555+ if !replace {
5656+ // Try to read existing secret first for merge behavior
5757+ existing, err := c.GetSecret(ctx, name)
5858+ if err == nil && existing != nil {
5959+ // Merge existing data with new data (new data takes precedence)
6060+ mergedData := make(map[string]any)
6161+6262+ // Start with existing data
6363+ for k, v := range existing.Data {
6464+ mergedData[k] = v
6565+ }
6666+6767+ // Override/add with new data
6868+ for k, v := range data {
6969+ mergedData[k] = v
7070+ }
7171+7272+ data = mergedData
7373+ }
7474+ // If error reading existing secret, continue with original data (new secret)
7575+ }
7676+4977 req := CreateSecretRequest{
5078 Data: data,
5179 }
+27-7
kubectl-hsm/pkg/commands/create.go
···3131 FromFile []string
3232 FromJsonFile string
3333 Interactive bool
3434+ Replace bool
3435}
35363637// NewCreateCmd creates the create command
···39404041 cmd := &cobra.Command{
4142 Use: "create SECRET_NAME [flags]",
4242- Short: "Create a new HSM secret",
4343- Long: `Create a new secret in the HSM.
4343+ Short: "Create or update an HSM secret",
4444+ Long: `Create a new secret in the HSM, or add keys to an existing secret.
4545+4646+By default, if the secret already exists, new keys will be added and existing keys with the same name will be updated.
4747+Existing keys not specified in the command will be preserved. Use --replace to completely replace the secret instead.
44484549The secret data can be provided in several ways:
4650- --from-literal key=value: Specify key-value pairs directly
···5155 # Create secret with literal values
5256 kubectl hsm create database-creds --from-literal username=admin --from-literal password=secret123
53575858+ # Add a new key to existing secret (preserves username and password)
5959+ kubectl hsm create database-creds --from-literal connection_string="postgres://..."
6060+6161+ # Replace entire secret (removes username and password, only keeps api_key)
6262+ kubectl hsm create database-creds --from-literal api_key=xyz123 --replace
6363+5464 # Load values from files
5565 kubectl hsm create tls-cert --from-file cert=server.crt --from-file key=server.key
5666···6676 cmd.Flags().StringArrayVar(&opts.FromFile, "from-file", nil, "Load secret data from files. Use 'key=file' or just 'file' (uses filename without extension as key)")
6777 cmd.Flags().StringVar(&opts.FromJsonFile, "from-json-file", "", "Load secret data from a JSON file with structure {\"name\":\"secret-name\",\"secrets\":[{\"key\":\"k\",\"value\":\"v\"}]}")
6878 cmd.Flags().BoolVar(&opts.Interactive, "interactive", false, "Prompt for secret values interactively")
7979+ cmd.Flags().BoolVar(&opts.Replace, "replace", false, "Replace the entire secret instead of merging with existing data")
6980 cmd.Flags().StringVarP(&opts.Namespace, "namespace", "n", "", "Override the default namespace")
7081 cmd.Flags().StringVarP(&opts.Output, "output", "o", "text", "Output format (text, json, yaml)")
7182 cmd.Flags().BoolVarP(&opts.Verbose, "verbose", "v", false, "Show verbose output including port forward details")
···168179 return err
169180 }
170181171171- // Create the secret
172172- fmt.Printf("Creating secret '%s' in namespace '%s'...\n", secretName, cm.GetCurrentNamespace())
173173- if err := hsmClient.CreateSecret(ctx, secretName, secretData); err != nil {
174174- return fmt.Errorf("failed to create secret: %w", err)
182182+ // Create the secret (with merge or replace behavior)
183183+ if opts.Replace {
184184+ fmt.Printf("Replacing secret '%s' in namespace '%s'...\n", secretName, cm.GetCurrentNamespace())
185185+ } else {
186186+ fmt.Printf("Creating/updating secret '%s' in namespace '%s'...\n", secretName, cm.GetCurrentNamespace())
187187+ }
188188+189189+ if err := hsmClient.CreateSecretWithOptions(ctx, secretName, secretData, opts.Replace); err != nil {
190190+ return fmt.Errorf("failed to create/update secret: %w", err)
175191 }
176192177177- fmt.Printf("Secret '%s' created successfully.\n", secretName)
193193+ if opts.Replace {
194194+ fmt.Printf("Secret '%s' replaced successfully.\n", secretName)
195195+ } else {
196196+ fmt.Printf("Secret '%s' created/updated successfully.\n", secretName)
197197+ }
178198179199 // Show how to retrieve the secret
180200 fmt.Printf("\nTo view the secret:\n")