-
Notifications
You must be signed in to change notification settings - Fork 0
fix(stack): add missing stack metadata update flag #83
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -10,7 +10,8 @@ import ( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
nameFlag = "name" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
nameFlag = "name" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
metadataFlag = "metadata" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
type StackUpdateStore struct { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -46,6 +47,7 @@ func NewUpdateCommand() *cobra.Command { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
fctl.WithBoolFlag(unprotectFlag, false, "Unprotect stacks (no confirmation on write commands)"), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
fctl.WithStringFlag(nameFlag, "", "Name of the stack"), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
fctl.WithStringSliceFlag(metadataFlag, []string{""}, "Metadata to use"), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
fctl.WithController(NewStackUpdateController()), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -57,10 +59,23 @@ func (c *StackUpdateController) Run(cmd *cobra.Command, args []string) (fctl.Ren | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
store := fctl.GetOrganizationStore(cmd) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
c.profile = store.Config.GetProfile(fctl.GetCurrentProfileName(cmd, store.Config)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Parse user-provided metadata | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
userMetadata, err := fctl.ParseMetadata(fctl.GetStringSlice(cmd, metadataFlag)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return nil, errors.Wrap(err, "parsing metadata") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Merge user metadata with protected metadata | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
protected := !fctl.GetBool(cmd, unprotectFlag) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
metadata := map[string]string{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
fctl.ProtectedStackMetadata: fctl.BoolPointerToString(&protected), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
metadata := make(map[string]string) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Add user metadata first | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
for k, v := range userMetadata { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
metadata[k] = v | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Add/override with protected metadata | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
metadata[fctl.ProtectedStackMetadata] = fctl.BoolPointerToString(&protected) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+68
to
79
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❓ Verification inconclusiveRisk: updating name with no --metadata will wipe existing metadata keys. You always send a fresh map containing only user pairs plus the protected key. If the stack already has other metadata, those keys are lost on update. Preserve existing metadata and still enforce the protected key. Apply this minimal merge and gate the request payload: - // Merge user metadata with protected metadata
- protected := !fctl.GetBool(cmd, unprotectFlag)
- metadata := make(map[string]string)
-
- // Add user metadata first
- for k, v := range userMetadata {
- metadata[k] = v
- }
-
- // Add/override with protected metadata
- metadata[fctl.ProtectedStackMetadata] = fctl.BoolPointerToString(&protected)
+ // Prepare metadata (user-supplied now, merge existing later)
+ protected := !fctl.GetBool(cmd, unprotectFlag)
+ metadata := make(map[string]string, len(userMetadata)+1)
+ for k, v := range userMetadata {
+ metadata[k] = v
+ }
+ // Enforce protected flag; prefer direct bool->string if available.
+ metadata[fctl.ProtectedStackMetadata] = fctl.BoolToString(protected)
@@
- name := fctl.GetString(cmd, nameFlag)
+ // Preserve existing metadata keys not provided by the user
+ if stack.Data != nil && stack.Data.Metadata != nil {
+ for k, v := range stack.Data.Metadata {
+ if _, exists := metadata[k]; !exists {
+ metadata[k] = v
+ }
+ }
+ }
+ name := fctl.GetString(cmd, nameFlag)
@@
- req := membershipclient.UpdateStackRequest{
- Name: name,
- Metadata: pointer.For(metadata),
- }
+ // If neither --metadata nor --unprotect was provided, avoid sending Metadata to prevent unintended changes.
+ var metaPtr *map[string]string
+ if len(userMetadata) > 0 || cmd.Flags().Changed(unprotectFlag) {
+ metaPtr = pointer.For(metadata)
+ }
+ req := membershipclient.UpdateStackRequest{
+ Name: name,
+ Metadata: metaPtr,
+ } If Don't overwrite existing stack metadata — merge current metadata and only send Metadata when the user changed it. File: cmd/stack/update.go Lines: 68-79 The code builds a fresh metadata map (userMetadata + protected key) and will erase any existing metadata on update; merge stack.Data.Metadata (preserving keys not provided by the user) and only include Metadata in UpdateStackRequest when userMetadata is non-empty or the unprotect flag was changed. - // Merge user metadata with protected metadata
- protected := !fctl.GetBool(cmd, unprotectFlag)
- metadata := make(map[string]string)
-
- // Add user metadata first
- for k, v := range userMetadata {
- metadata[k] = v
- }
-
- // Add/override with protected metadata
- metadata[fctl.ProtectedStackMetadata] = fctl.BoolPointerToString(&protected)
+ // Prepare metadata (user-supplied now, merge existing later)
+ protected := !fctl.GetBool(cmd, unprotectFlag)
+ metadata := make(map[string]string, len(userMetadata)+1)
+ for k, v := range userMetadata {
+ metadata[k] = v
+ }
+ // Enforce protected flag; prefer direct bool->string if available.
+ metadata[fctl.ProtectedStackMetadata] = fctl.BoolToString(protected)
@@
- name := fctl.GetString(cmd, nameFlag)
+ // Preserve existing metadata keys not provided by the user
+ if stack.Data != nil && stack.Data.Metadata != nil {
+ for k, v := range stack.Data.Metadata {
+ if _, exists := metadata[k]; !exists {
+ metadata[k] = v
+ }
+ }
+ }
+ name := fctl.GetString(cmd, nameFlag)
@@
- req := membershipclient.UpdateStackRequest{
- Name: name,
- Metadata: pointer.For(metadata),
- }
+ // If neither --metadata nor --unprotect was provided, avoid sending Metadata to prevent unintended changes.
+ var metaPtr *map[string]string
+ if len(userMetadata) > 0 || cmd.Flags().Changed(unprotectFlag) {
+ metaPtr = pointer.For(metadata)
+ }
+ req := membershipclient.UpdateStackRequest{
+ Name: name,
+ Metadata: metaPtr,
+ } 📝 Committable suggestion
Suggested change
🧰 Tools🪛 GitHub Actions: Default[error] 68-79: CI cleanliness check failed due to uncommitted changes. The script ran 'git status --porcelain' and detected changes in cmd/stack/update.go, causing the step to exit with code 1. 🤖 Prompt for AI Agents
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
stack, res, err := store.Client().GetStack(cmd.Context(), store.OrganizationId(), args[0]).Execute() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: default []string{""} makes ParseMetadata fail when flag is omitted.
With the current default, GetStringSlice returns a single empty string, and ParseMetadata errors with “malformed metadata: ”. Use an empty slice as default.
Apply this diff:
📝 Committable suggestion
🤖 Prompt for AI Agents