From 18fd7c4fee76831603ae66d02f1ccfd37643d041 Mon Sep 17 00:00:00 2001 From: saiflayouni Date: Thu, 18 Jun 2026 22:07:45 +0100 Subject: [PATCH 1/2] fix: preserve app attributes when generating a new key The Apigee POST /developers/{dev}/apps/{name} endpoint generates a new key but also updates the app, clearing any field absent from the request body. GenerateKey did not include existing attributes in its payload, so all app attributes were wiped on every genkey call. Fix by fetching the current app before the POST and re-including its attributes in the payload. Fixes #651 --- internal/client/apps/apps.go | 25 +++++++++++++++++++++++++ internal/client/apps/apps_test.go | 12 +++++++++++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/internal/client/apps/apps.go b/internal/client/apps/apps.go index 0f492422b..951b196b7 100644 --- a/internal/client/apps/apps.go +++ b/internal/client/apps/apps.go @@ -261,6 +261,23 @@ func ListApps(productName string) (respBody []byte, err error) { func GenerateKey(name string, developerID string, apiProducts []string, callback string, expires string, scopes []string) (respBody []byte, err error) { u, _ := url.Parse(apiclient.GetApigeeBaseURL()) + // Fetch the existing app to preserve its attributes; the Apigee POST + // endpoint for key generation also updates the app, clearing any fields + // absent from the request body. + apiclient.ClientPrintHttpResponse.Set(false) + appURL, _ := url.Parse(apiclient.GetApigeeBaseURL()) + appURL.Path = path.Join(appURL.Path, apiclient.GetApigeeOrg(), "developers", developerID, "apps", name) + existingAppBody, err := apiclient.HttpClient(appURL.String()) + apiclient.ClientPrintHttpResponse.Set(apiclient.GetCmdPrintHttpResponseSetting()) + if err != nil { + return nil, err + } + + var existingApp application + if err = json.Unmarshal(existingAppBody, &existingApp); err != nil { + return nil, err + } + key := []string{} key = append(key, "\"name\":\""+name+"\"") @@ -278,6 +295,14 @@ func GenerateKey(name string, developerID string, apiProducts []string, callback key = append(key, "\"scopes\":[\""+getArrayStr(scopes)+"\"]") } + if len(existingApp.Attributes) > 0 { + attributes := []string{} + for _, attr := range existingApp.Attributes { + attributes = append(attributes, "{\"name\":\""+attr.Name+"\",\"value\":\""+attr.Value+"\"}") + } + key = append(key, "\"attributes\":["+strings.Join(attributes, ",")+"]") + } + payload := "{" + strings.Join(key, ",") + "}" u.Path = path.Join(u.Path, apiclient.GetApigeeOrg(), "developers", developerID, "apps", name) respBody, err = apiclient.HttpClient(u.String(), payload) diff --git a/internal/client/apps/apps_test.go b/internal/client/apps/apps_test.go index fb4954f0f..b635b6b81 100644 --- a/internal/client/apps/apps_test.go +++ b/internal/client/apps/apps_test.go @@ -131,9 +131,19 @@ func TestGenerateKey(t *testing.T) { expires := "-1" callback := "" scopes := []string{"test"} - if _, err := GenerateKey(name, devID, apiProducts, callback, expires, scopes); err != nil { + respBody, err := GenerateKey(name, devID, apiProducts, callback, expires, scopes) + if err != nil { t.Fatalf("%v", err) } + + var respJSONMap map[string]interface{} + if err = json.Unmarshal(respBody, &respJSONMap); err != nil { + t.Fatalf("%v", err) + } + attrs, ok := respJSONMap["attributes"].([]interface{}) + if !ok || len(attrs) == 0 { + t.Fatalf("expected app attributes to be preserved after genkey, got none") + } } func TestExport(t *testing.T) { From 5b8d568ed5826633cc0b1c633155d68ff05a9202 Mon Sep 17 00:00:00 2001 From: saiflayouni Date: Sun, 28 Jun 2026 21:16:15 +0100 Subject: [PATCH 2/2] fix: use json.Marshal for attribute encoding; fix pre-existing keys_test arg count Replace unsafe string-concatenation for attribute JSON with json.Marshal, which correctly handles any special characters in attribute names/values. Also fix pre-existing test breakage in keys_test.go where ManageKey was called with 4 arguments instead of the required 5. --- internal/client/apps/apps.go | 8 ++++---- internal/client/apps/keys_test.go | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/internal/client/apps/apps.go b/internal/client/apps/apps.go index 951b196b7..92928464a 100644 --- a/internal/client/apps/apps.go +++ b/internal/client/apps/apps.go @@ -296,11 +296,11 @@ func GenerateKey(name string, developerID string, apiProducts []string, callback } if len(existingApp.Attributes) > 0 { - attributes := []string{} - for _, attr := range existingApp.Attributes { - attributes = append(attributes, "{\"name\":\""+attr.Name+"\",\"value\":\""+attr.Value+"\"}") + attrBytes, err := json.Marshal(existingApp.Attributes) + if err != nil { + return nil, err } - key = append(key, "\"attributes\":["+strings.Join(attributes, ",")+"]") + key = append(key, "\"attributes\":"+string(attrBytes)) } payload := "{" + strings.Join(key, ",") + "}" diff --git a/internal/client/apps/keys_test.go b/internal/client/apps/keys_test.go index 527282f26..fbc04bfc1 100644 --- a/internal/client/apps/keys_test.go +++ b/internal/client/apps/keys_test.go @@ -52,7 +52,7 @@ func TestManageKey(t *testing.T) { clienttest.SITEID_NOT_REQD, clienttest.CLIPATH_NOT_REQD); err != nil { t.Fatalf("%v", err) } - if _, err := ManageKey(name, appID, "key1", "approve"); err != nil { + if _, err := ManageKey(name, appID, "key1", "approve", ""); err != nil { t.Fatalf("%v", err) } }