From cdde1eb17156447dd352596bf91f261f2abbcb6f Mon Sep 17 00:00:00 2001 From: bhupendray-yb Date: Mon, 13 Jan 2025 19:52:57 +0530 Subject: [PATCH 1/3] [CLOUDGA-25136] Support to associate allow list with API keys --- cmd/api_key/api_key.go | 57 +++++- cmd/api_key_test.go | 164 ++++++++++++++++++ cmd/test/fixtures/get-or-create-api-key.json | 51 ++++++ .../list-api-keys-with-allow-list.json | 100 +++++++++++ cmd/test/fixtures/list-api-keys.json | 100 +++++++++++ go.mod | 2 +- go.sum | 2 + internal/client/client.go | 4 + internal/formatter/api_keys.go | 21 ++- 9 files changed, 495 insertions(+), 6 deletions(-) create mode 100644 cmd/api_key_test.go create mode 100644 cmd/test/fixtures/get-or-create-api-key.json create mode 100644 cmd/test/fixtures/list-api-keys-with-allow-list.json create mode 100644 cmd/test/fixtures/list-api-keys.json diff --git a/cmd/api_key/api_key.go b/cmd/api_key/api_key.go index f1808825..d9b8a99b 100644 --- a/cmd/api_key/api_key.go +++ b/cmd/api_key/api_key.go @@ -56,8 +56,19 @@ var listApiKeysCmd = &cobra.Command{ apiKeyListRequest = apiKeyListRequest.ApiKeyName(name) } + isNameSpecified := cmd.Flags().Changed("name") + keyStatus := "ACTIVE" + // If --name arg is specified, don't set default filter for key-status + // because if an API key is revoked/expired and user do $ ybm api-key list --name + // it will lead to empty response if we filter key by ACTIVE status. + if isNameSpecified { + keyStatus = "" + } + // if user filters by key status, add it to the request - keyStatus, _ := cmd.Flags().GetString("status") + if cmd.Flags().Changed("status") { + keyStatus, _ = cmd.Flags().GetString("status") + } if keyStatus != "" { validStatus := false for _, v := range GetKeyStatusFilters() { @@ -88,7 +99,32 @@ var listApiKeysCmd = &cobra.Command{ return } - formatter.ApiKeyWrite(apiKeyCtx, resp.GetData()) + apiKeyOutputList := make([]formatter.ApiKeyDataAllowListInfo, 0) + + // For each API key, fetch the allow list(s) associated with it + for _, apiKey := range resp.GetData() { + apiKeyId := apiKey.GetInfo().Id + allowListIds := apiKey.GetSpec().AllowListInfo + allowListsNames := make([]string, 0) + + if allowListIds != nil && len(*allowListIds) > 0 { + apiKeyAllowLists, resp, err := authApi.ListApiKeyNetworkAllowLists(apiKeyId).Execute() + if err != nil { + logrus.Debugf("Full HTTP response: %v", resp) + logrus.Fatalf(ybmAuthClient.GetApiErrorDetails(err)) + } + for _, allowList := range apiKeyAllowLists.GetData() { + allowListsNames = append(allowListsNames, allowList.GetSpec().Name) + } + } + + apiKeyOutputList = append(apiKeyOutputList, formatter.ApiKeyDataAllowListInfo{ + ApiKey: &apiKey, + AllowLists: allowListsNames, + }) + } + + formatter.ApiKeyWrite(apiKeyCtx, apiKeyOutputList) }, } @@ -164,6 +200,22 @@ var createApiKeyCmd = &cobra.Command{ apiKeySpec.SetRoleId(roleId) } + if cmd.Flags().Changed("network-allow-lists") { + allowLists, _ := cmd.Flags().GetString("network-allow-lists") + + allowListNames := strings.Split(allowLists, ",") + allowListIds := make([]string, 0) + + for _, allowList := range allowListNames { + allowListId, err := authApi.GetNetworkAllowListIdByName(strings.TrimSpace(allowList)) + if err != nil { + logrus.Fatalln(err) + } + allowListIds = append(allowListIds, allowListId) + } + apiKeySpec.SetAllowListInfo(allowListIds) + } + resp, r, err := authApi.CreateApiKey().ApiKeySpec(*apiKeySpec).Execute() if err != nil { logrus.Debugf("Full HTTP response: %v", r) @@ -233,6 +285,7 @@ func init() { createApiKeyCmd.Flags().String("unit", "", "[REQUIRED] The time units for which the API Key will be valid. Available options are Hours, Days, and Months.") createApiKeyCmd.MarkFlagRequired("unit") createApiKeyCmd.Flags().String("description", "", "[OPTIONAL] Description of the API Key to be created.") + createApiKeyCmd.Flags().String("network-allow-lists", "", "[OPTIONAL] The network allow lists(comma separated names) to assign to the API key.") createApiKeyCmd.Flags().String("role-name", "", "[OPTIONAL] The name of the role to be assigned to the API Key. If not provided, an Admin API Key will be generated.") createApiKeyCmd.Flags().BoolP("force", "f", false, "Bypass the prompt for non-interactive usage") diff --git a/cmd/api_key_test.go b/cmd/api_key_test.go new file mode 100644 index 00000000..10fbed74 --- /dev/null +++ b/cmd/api_key_test.go @@ -0,0 +1,164 @@ +package cmd_test + +import ( + "fmt" + "net/http" + "os" + "os/exec" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/onsi/gomega/gbytes" + "github.com/onsi/gomega/gexec" + "github.com/onsi/gomega/ghttp" + openapi "github.com/yugabyte/yugabytedb-managed-go-client-internal" +) + +var _ = Describe("API Key Management", func() { + + var ( + server *ghttp.Server + statusCode int + args []string + responseAccount openapi.AccountListResponse + responseProject openapi.AccountListResponse + apiKeyListResponse openapi.ApiKeyListResponse + apiKeyResponse openapi.CreateApiKeyResponse + responseNetworkAllowList openapi.NetworkAllowListListResponse + ) + + BeforeEach(func() { + args = os.Args + os.Args = []string{} + var err error + server, err = newGhttpServer(responseAccount, responseProject) + Expect(err).ToNot(HaveOccurred()) + os.Setenv("YBM_HOST", fmt.Sprintf("http://%s", server.Addr())) + os.Setenv("YBM_APIKEY", "test-token") + statusCode = 200 + }) + + AfterEach(func() { + os.Args = args + server.Close() + }) + + Describe("When listing API keys", func() { + Context("with valid request", func() { + It("should list all API keys", func() { + err := loadJson("./test/fixtures/list-api-keys.json", &apiKeyListResponse) + Expect(err).ToNot(HaveOccurred()) + + server.AppendHandlers( + ghttp.CombineHandlers( + ghttp.VerifyRequest(http.MethodGet, "/api/public/v1/accounts/340af43a-8a7c-4659-9258-4876fd6a207b/api-keys"), + ghttp.RespondWithJSONEncodedPtr(&statusCode, apiKeyListResponse), + ), + ) + + cmd := exec.Command(compiledCLIPath, "api-key", "list") + session, err := gexec.Start(cmd, GinkgoWriter, GinkgoWriter) + + Expect(err).NotTo(HaveOccurred()) + session.Wait(2) + Expect(session.Out).Should(gbytes.Say(`Name Role Status Created By Date Created Last Used Expiration Allow List +apikey-1 Admin ACTIVE user@yb.com 2025-01-13T13:55:14.024Z 2025-01-13T14:21:40.713599Z 2099-12-31T00:00Z N/A +apikey-2 Admin ACTIVE user@yb.com 2025-01-08T09:06:35.077Z Not yet used 2025-02-07T09:06:35.077071Z N/A`)) + // Expect(server.ReceivedRequests()).Should(HaveLen(2)) + session.Kill() + }) + }) + }) + + Describe("When listing API keys with allow list", func() { + Context("with valid request", func() { + It("should list all API keys", func() { + err := loadJson("./test/fixtures/list-api-keys-with-allow-list.json", &apiKeyListResponse) + Expect(err).ToNot(HaveOccurred()) + err = loadJson("./test/fixtures/allow-list.json", &responseNetworkAllowList) + Expect(err).ToNot(HaveOccurred()) + + server.AppendHandlers( + ghttp.CombineHandlers( + ghttp.VerifyRequest(http.MethodGet, "/api/public/v1/accounts/340af43a-8a7c-4659-9258-4876fd6a207b/api-keys"), + ghttp.RespondWithJSONEncodedPtr(&statusCode, apiKeyListResponse), + ), + ghttp.CombineHandlers( + ghttp.VerifyRequest(http.MethodGet, "/api/public/v1/accounts/340af43a-8a7c-4659-9258-4876fd6a207b/projects/78d4459c-0f45-47a5-899a-45ddf43eba6e/allow-lists"), + ghttp.RespondWithJSONEncodedPtr(&statusCode, responseNetworkAllowList), + ), + func(w http.ResponseWriter, req *http.Request) { + queryParams := req.URL.Query() + Expect(queryParams.Get("status")).To(Equal("ACTIVE")) + }, + ) + + cmd := exec.Command(compiledCLIPath, "api-key", "list") + session, err := gexec.Start(cmd, GinkgoWriter, GinkgoWriter) + + Expect(err).NotTo(HaveOccurred()) + session.Wait(2) + Expect(session.Out).Should(gbytes.Say(`Name Role Status Created By Date Created Last Used Expiration Allow List +apikey-1 Admin ACTIVE user@yb.com 2025-01-13T13:55:14.024Z 2025-01-13T14:21:40.713599Z 2099-12-31T00:00Z device-ip-gween +apikey-2 Admin ACTIVE user@yb.com 2025-01-08T09:06:35.077Z Not yet used 2025-02-07T09:06:35.077071Z N/A`)) + session.Kill() + }) + }) + }) + + Describe("When creating an API key", func() { + Context("with valid request", func() { + It("should create a new API key", func() { + err := loadJson("./test/fixtures/get-or-create-api-key.json", &apiKeyResponse) + Expect(err).ToNot(HaveOccurred()) + + server.AppendHandlers( + ghttp.CombineHandlers( + ghttp.VerifyRequest(http.MethodPost, "/api/public/v1/accounts/340af43a-8a7c-4659-9258-4876fd6a207b/api-keys"), + ghttp.RespondWithJSONEncodedPtr(&statusCode, apiKeyResponse), + ), + ) + + cmd := exec.Command(compiledCLIPath, "api-key", "create", "--name", "apikey-1", "--duration", "30", "--unit", "DAYS") + session, err := gexec.Start(cmd, GinkgoWriter, GinkgoWriter) + + Expect(err).NotTo(HaveOccurred()) + session.Wait(2) + Expect(session.Out).Should(gbytes.Say(`Name Role Status Created By Date Created Last Used Expiration Allow List +apikey-1 Admin ACTIVE admin 2025-01-13T15:55:55.825Z Not yet used 2025-02-12T15:55:55.824833Z N/A + +API Key: test-jwt + +The API key is only shown once after creation. Copy and store it securely.`)) + session.Kill() + }) + }) + }) + + Describe("When revoking an API key", func() { + Context("with valid request", func() { + It("should revoke the API key", func() { + err := loadJson("./test/fixtures/get-or-create-api-key.json", &apiKeyResponse) + Expect(err).ToNot(HaveOccurred()) + + server.AppendHandlers( + ghttp.CombineHandlers( + ghttp.VerifyRequest(http.MethodGet, "/api/public/v1/accounts/340af43a-8a7c-4659-9258-4876fd6a207b/api-keys"), + ghttp.RespondWithJSONEncodedPtr(&statusCode, apiKeyResponse), + ), + ghttp.CombineHandlers( + ghttp.VerifyRequest(http.MethodPost, "/api/public/v1/accounts/340af43a-8a7c-4659-9258-4876fd6a207b/api-keys/440af43a-8a7c-4659-9258-4876fd6a207b/revoke"), + ghttp.RespondWith(http.StatusOK, nil), + ), + ) + + cmd := exec.Command(compiledCLIPath, "api-key", "revoke", "--name", "apikey-1", "-f") + session, err := gexec.Start(cmd, GinkgoWriter, GinkgoWriter) + Expect(err).NotTo(HaveOccurred()) + session.Wait(2) + Expect(server.ReceivedRequests()).Should(HaveLen(3)) + session.Kill() + }) + }) + }) +}) diff --git a/cmd/test/fixtures/get-or-create-api-key.json b/cmd/test/fixtures/get-or-create-api-key.json new file mode 100644 index 00000000..dd2105b1 --- /dev/null +++ b/cmd/test/fixtures/get-or-create-api-key.json @@ -0,0 +1,51 @@ +{ + "data": { + "spec": { + "name": "apikey-1", + "description": "", + "expire_after_hours": 720, + "role_id": "36ec6789-6dbf-4d12-91b4-243587d6882b", + "allow_list_info": [] + }, + "info": { + "id": "4f32f91e-b5fc-462f-8111-134eb813f6d0", + "issuer": "admin", + "metadata": { + "created_on": "2025-01-13T15:55:55.825Z", + "updated_on": "2025-01-13T15:55:55.825Z" + }, + "expiry_time": "2025-02-12T15:55:55.824833Z", + "status": "ACTIVE", + "last_used_time": "Not yet used", + "usage_count": 0, + "account_id": "531af27a-614c-472f-8edb-a0f9322f0838", + "revoked_by_user_id": null, + "revoked_by_user_email": null, + "revoked_by_api_key_id": null, + "revoked_by_api_key_name": null, + "revoked_at_time": null, + "role": { + "spec": { + "name": "account_admin", + "description": "Full access to all operations, including billing and user management", + "permissions": [] + }, + "info": { + "id": "36ec6789-6dbf-4d12-91b4-243587d6882b", + "metadata": { + "created_on": "2021-10-19T00:00Z", + "updated_on": "2025-01-13T13:54:21.660Z" + }, + "is_active": true, + "is_user_defined": false, + "account_id": "531af27a-614c-472f-8edb-a0f9322f0838", + "effective_permissions": [], + "users": null, + "api_keys": null, + "display_name": "Admin" + } + } + } + }, + "jwt": "test-jwt" +} diff --git a/cmd/test/fixtures/list-api-keys-with-allow-list.json b/cmd/test/fixtures/list-api-keys-with-allow-list.json new file mode 100644 index 00000000..ecf74a4c --- /dev/null +++ b/cmd/test/fixtures/list-api-keys-with-allow-list.json @@ -0,0 +1,100 @@ +{ + "data": [ + { + "spec": { + "name": "apikey-1", + "description": "test-key", + "expire_after_hours": 0, + "role_id": "36ec6789-6dbf-4d12-91b4-243587d6882b", + "allow_list_info": ["1eaf5552-e2f3-4a28-b215-106667c05178"] + }, + "info": { + "id": "6db78592-bfe2-4ea7-8642-73c5cc06d027", + "issuer": "user@yb.com", + "metadata": { + "created_on": "2025-01-13T13:55:14.024Z", + "updated_on": "2025-01-13T13:55:14.024Z" + }, + "expiry_time": "2099-12-31T00:00Z", + "status": "ACTIVE", + "last_used_time": "2025-01-13T14:21:40.713599Z", + "usage_count": 42, + "account_id": "531af27a-614c-472f-8edb-a0f9322f0838", + "revoked_by_user_id": null, + "revoked_by_user_email": null, + "revoked_by_api_key_id": null, + "revoked_by_api_key_name": null, + "revoked_at_time": null, + "role": { + "spec": { + "name": "account_admin", + "description": "Full access to all operations, including billing and user management", + "permissions": [] + }, + "info": { + "id": "36ec6789-6dbf-4d12-91b4-243587d6882b", + "metadata": { + "created_on": "2021-10-19T00:00Z", + "updated_on": "2025-01-13T13:54:21.660Z" + }, + "is_active": true, + "is_user_defined": false, + "account_id": "531af27a-614c-472f-8edb-a0f9322f0838", + "effective_permissions": [], + "users": null, + "api_keys": null, + "display_name": "Admin" + } + } + } + }, + { + "spec": { + "name": "apikey-2", + "description": "test-key", + "expire_after_hours": 720, + "role_id": "36ec6789-6dbf-4d12-91b4-243587d6882b", + "allow_list_info": [] + }, + "info": { + "id": "82862788-6fdf-4fc6-954b-178187e10608", + "issuer": "user@yb.com", + "metadata": { + "created_on": "2025-01-08T09:06:35.077Z", + "updated_on": "2025-01-08T09:06:35.077Z" + }, + "expiry_time": "2025-02-07T09:06:35.077071Z", + "status": "ACTIVE", + "last_used_time": "Not yet used", + "usage_count": 0, + "account_id": "531af27a-614c-472f-8edb-a0f9322f0838", + "revoked_by_user_id": null, + "revoked_by_user_email": null, + "revoked_by_api_key_id": null, + "revoked_by_api_key_name": null, + "revoked_at_time": null, + "role": { + "spec": { + "name": "account_admin", + "description": "Full access to all operations, including billing and user management", + "permissions": [] + }, + "info": { + "id": "36ec6789-6dbf-4d12-91b4-243587d6882b", + "metadata": { + "created_on": "2021-10-19T00:00Z", + "updated_on": "2025-01-13T13:54:21.660Z" + }, + "is_active": true, + "is_user_defined": false, + "account_id": "531af27a-614c-472f-8edb-a0f9322f0838", + "effective_permissions": [], + "users": null, + "api_keys": null, + "display_name": "Admin" + } + } + } + } + ] +} diff --git a/cmd/test/fixtures/list-api-keys.json b/cmd/test/fixtures/list-api-keys.json new file mode 100644 index 00000000..46e9fe9b --- /dev/null +++ b/cmd/test/fixtures/list-api-keys.json @@ -0,0 +1,100 @@ +{ + "data": [ + { + "spec": { + "name": "apikey-1", + "description": "test-key", + "expire_after_hours": 0, + "role_id": "36ec6789-6dbf-4d12-91b4-243587d6882b", + "allow_list_info": [] + }, + "info": { + "id": "6db78592-bfe2-4ea7-8642-73c5cc06d027", + "issuer": "user@yb.com", + "metadata": { + "created_on": "2025-01-13T13:55:14.024Z", + "updated_on": "2025-01-13T13:55:14.024Z" + }, + "expiry_time": "2099-12-31T00:00Z", + "status": "ACTIVE", + "last_used_time": "2025-01-13T14:21:40.713599Z", + "usage_count": 42, + "account_id": "531af27a-614c-472f-8edb-a0f9322f0838", + "revoked_by_user_id": null, + "revoked_by_user_email": null, + "revoked_by_api_key_id": null, + "revoked_by_api_key_name": null, + "revoked_at_time": null, + "role": { + "spec": { + "name": "account_admin", + "description": "Full access to all operations, including billing and user management", + "permissions": [] + }, + "info": { + "id": "36ec6789-6dbf-4d12-91b4-243587d6882b", + "metadata": { + "created_on": "2021-10-19T00:00Z", + "updated_on": "2025-01-13T13:54:21.660Z" + }, + "is_active": true, + "is_user_defined": false, + "account_id": "531af27a-614c-472f-8edb-a0f9322f0838", + "effective_permissions": [], + "users": null, + "api_keys": null, + "display_name": "Admin" + } + } + } + }, + { + "spec": { + "name": "apikey-2", + "description": "test-key", + "expire_after_hours": 720, + "role_id": "36ec6789-6dbf-4d12-91b4-243587d6882b", + "allow_list_info": [] + }, + "info": { + "id": "82862788-6fdf-4fc6-954b-178187e10608", + "issuer": "user@yb.com", + "metadata": { + "created_on": "2025-01-08T09:06:35.077Z", + "updated_on": "2025-01-08T09:06:35.077Z" + }, + "expiry_time": "2025-02-07T09:06:35.077071Z", + "status": "ACTIVE", + "last_used_time": "Not yet used", + "usage_count": 0, + "account_id": "531af27a-614c-472f-8edb-a0f9322f0838", + "revoked_by_user_id": null, + "revoked_by_user_email": null, + "revoked_by_api_key_id": null, + "revoked_by_api_key_name": null, + "revoked_at_time": null, + "role": { + "spec": { + "name": "account_admin", + "description": "Full access to all operations, including billing and user management", + "permissions": [] + }, + "info": { + "id": "36ec6789-6dbf-4d12-91b4-243587d6882b", + "metadata": { + "created_on": "2021-10-19T00:00Z", + "updated_on": "2025-01-13T13:54:21.660Z" + }, + "is_active": true, + "is_user_defined": false, + "account_id": "531af27a-614c-472f-8edb-a0f9322f0838", + "effective_permissions": [], + "users": null, + "api_keys": null, + "display_name": "Admin" + } + } + } + } + ] +} diff --git a/go.mod b/go.mod index b57afdf5..f6344aeb 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/spf13/cobra v1.8.0 github.com/spf13/viper v1.17.0 github.com/t-tomalak/logrus-easy-formatter v0.0.0-20190827215021-c074f06c5816 - github.com/yugabyte/yugabytedb-managed-go-client-internal v0.0.0-20241224115148-e3b391db3af9 + github.com/yugabyte/yugabytedb-managed-go-client-internal v0.0.0-20250111091824-e051c979c3dd golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 golang.org/x/mod v0.20.0 golang.org/x/term v0.25.0 diff --git a/go.sum b/go.sum index 7bb6913b..6aa426e7 100644 --- a/go.sum +++ b/go.sum @@ -286,6 +286,8 @@ github.com/yugabyte/yugabytedb-managed-go-client-internal v0.0.0-20241219183048- github.com/yugabyte/yugabytedb-managed-go-client-internal v0.0.0-20241219183048-50fe86c058d8/go.mod h1:5vW0xIzIZw+1djkiWKx0qqNmqbRBSf4mjc4qw8lIMik= github.com/yugabyte/yugabytedb-managed-go-client-internal v0.0.0-20241224115148-e3b391db3af9 h1:omB8P+cKd/t5CEvgXCYoiCFg1SU+k4P0Ig28NqPrizw= github.com/yugabyte/yugabytedb-managed-go-client-internal v0.0.0-20241224115148-e3b391db3af9/go.mod h1:5vW0xIzIZw+1djkiWKx0qqNmqbRBSf4mjc4qw8lIMik= +github.com/yugabyte/yugabytedb-managed-go-client-internal v0.0.0-20250111091824-e051c979c3dd h1:vuMsPasq6Uzoy5fBJ0HJz7/jiCuLhKRj+LOyNVolqCk= +github.com/yugabyte/yugabytedb-managed-go-client-internal v0.0.0-20250111091824-e051c979c3dd/go.mod h1:5vW0xIzIZw+1djkiWKx0qqNmqbRBSf4mjc4qw8lIMik= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= diff --git a/internal/client/client.go b/internal/client/client.go index 2c8ae32d..9e3e2c21 100644 --- a/internal/client/client.go +++ b/internal/client/client.go @@ -764,6 +764,10 @@ func (a *AuthApiClient) ListClusterNetworkAllowLists(clusterId string) ybmclient return a.ApiClient.ClusterApi.ListClusterNetworkAllowLists(a.ctx, a.AccountID, a.ProjectID, clusterId) } +func (a *AuthApiClient) ListApiKeyNetworkAllowLists(apiKeyId string) ybmclient.ApiListNetworkAllowListsRequest { + return a.ApiClient.NetworkApi.ListNetworkAllowLists(a.ctx, a.AccountID, a.ProjectID).ApiKeyIds([]string{apiKeyId}) +} + func (a *AuthApiClient) GetBillingUsage(startTimestamp string, endTimestamp string, clusterIds []string) ybmclient.ApiGetBillingUsageRequest { return a.ApiClient.BillingApi.GetBillingUsage(a.ctx, a.AccountID).StartTimestamp(startTimestamp).EndTimestamp(endTimestamp).Granularity(ybmclient.GRANULARITYENUM_DAILY).ClusterIds(clusterIds) } diff --git a/internal/formatter/api_keys.go b/internal/formatter/api_keys.go index 761b04cf..c7960c92 100644 --- a/internal/formatter/api_keys.go +++ b/internal/formatter/api_keys.go @@ -17,23 +17,30 @@ package formatter import ( "encoding/json" + "strings" "github.com/sirupsen/logrus" ybmclient "github.com/yugabyte/yugabytedb-managed-go-client-internal" ) const ( - defaultApiKeyListing = "table {{.ApiKeyName}}\t{{.ApiKeyRole}}\t{{.ApiKeyStatus}}\t{{.Issuer}}\t{{.CreatedAt}}\t{{.LastUsed}}\t{{.ExpiryTime}}" + defaultApiKeyListing = "table {{.ApiKeyName}}\t{{.ApiKeyRole}}\t{{.ApiKeyStatus}}\t{{.Issuer}}\t{{.CreatedAt}}\t{{.LastUsed}}\t{{.ExpiryTime}}\t{{.AllowList}}" createdAtHeader = "Date Created" expiryTimeHeader = "Expiration" apiKeyRoleHeader = "Role" lastUsedHeader = "Last Used" ) +type ApiKeyDataAllowListInfo struct { + ApiKey *ybmclient.ApiKeyData + AllowLists []string +} + type ApiKeyContext struct { HeaderContext Context a ybmclient.ApiKeyData + allowLists []string } func NewApiKeyFormat(source string) Format { @@ -47,10 +54,10 @@ func NewApiKeyFormat(source string) Format { } // ApiKeyWrite renders the context for a list of API Keys -func ApiKeyWrite(ctx Context, keys []ybmclient.ApiKeyData) error { +func ApiKeyWrite(ctx Context, keys []ApiKeyDataAllowListInfo) error { render := func(format func(subContext SubContext) error) error { for _, key := range keys { - err := format(&ApiKeyContext{a: key}) + err := format(&ApiKeyContext{a: *key.ApiKey, allowLists: key.AllowLists}) if err != nil { logrus.Debugf("Error rendering API key: %v", err) return err @@ -85,6 +92,7 @@ func NewApiKeyContext() *ApiKeyContext { "Issuer": apiKeyIssuerHeader, "LastUsed": lastUsedHeader, "CreatedAt": createdAtHeader, + "AllowList": "Allow List", } return &apiKeyCtx } @@ -121,6 +129,13 @@ func (a *ApiKeyContext) CreatedAt() string { return a.a.Info.Metadata.Get().GetCreatedOn() } +func (a *ApiKeyContext) AllowList() string { + if len(a.allowLists) == 0 { + return "N/A" + } + return strings.Join(a.allowLists, ", ") +} + func (a *ApiKeyContext) MarshalJSON() ([]byte, error) { return json.Marshal(a.a) } From 06f3a679d3674f14f0a6c11999c5e51dbfba45e0 Mon Sep 17 00:00:00 2001 From: bhupendray-yb Date: Wed, 15 Jan 2025 13:03:38 +0530 Subject: [PATCH 2/3] [CLOUDGA-25136] added it under feature flag --- cmd/api_key/api_key.go | 43 +++--- cmd/api_key_test.go | 139 ++++++++++++------- cmd/test/fixtures/get-or-create-api-key.json | 2 +- cmd/util/feature_flags.go | 1 + internal/formatter/api_keys.go | 23 ++- 5 files changed, 129 insertions(+), 79 deletions(-) diff --git a/cmd/api_key/api_key.go b/cmd/api_key/api_key.go index d9b8a99b..28632cf3 100644 --- a/cmd/api_key/api_key.go +++ b/cmd/api_key/api_key.go @@ -26,6 +26,7 @@ import ( "github.com/yugabyte/ybm-cli/cmd/util" ybmAuthClient "github.com/yugabyte/ybm-cli/internal/client" "github.com/yugabyte/ybm-cli/internal/formatter" + ybmclient "github.com/yugabyte/yugabytedb-managed-go-client-internal" ) var ApiKeyCmd = &cobra.Command{ @@ -99,14 +100,21 @@ var listApiKeysCmd = &cobra.Command{ return } - apiKeyOutputList := make([]formatter.ApiKeyDataAllowListInfo, 0) + apiKeyOutputList := *enrichApiKeyDataWithAllowListInfo(&resp.Data, authApi) + formatter.ApiKeyWrite(apiKeyCtx, apiKeyOutputList) + }, +} - // For each API key, fetch the allow list(s) associated with it - for _, apiKey := range resp.GetData() { - apiKeyId := apiKey.GetInfo().Id - allowListIds := apiKey.GetSpec().AllowListInfo - allowListsNames := make([]string, 0) +func enrichApiKeyDataWithAllowListInfo(apiKeys *[]ybmclient.ApiKeyData, authApi *ybmAuthClient.AuthApiClient) *[]formatter.ApiKeyDataAllowListInfo { + apiKeyOutputList := make([]formatter.ApiKeyDataAllowListInfo, 0) + + // For each API key, fetch the allow list(s) associated with it + for _, apiKey := range *apiKeys { + apiKeyId := apiKey.GetInfo().Id + allowListsNames := make([]string, 0) + if util.IsFeatureFlagEnabled(util.API_KEY_ALLOW_LIST) { + allowListIds := apiKey.GetSpec().AllowListInfo if allowListIds != nil && len(*allowListIds) > 0 { apiKeyAllowLists, resp, err := authApi.ListApiKeyNetworkAllowLists(apiKeyId).Execute() if err != nil { @@ -117,15 +125,14 @@ var listApiKeysCmd = &cobra.Command{ allowListsNames = append(allowListsNames, allowList.GetSpec().Name) } } - - apiKeyOutputList = append(apiKeyOutputList, formatter.ApiKeyDataAllowListInfo{ - ApiKey: &apiKey, - AllowLists: allowListsNames, - }) } - formatter.ApiKeyWrite(apiKeyCtx, apiKeyOutputList) - }, + apiKeyOutputList = append(apiKeyOutputList, formatter.ApiKeyDataAllowListInfo{ + ApiKey: &apiKey, + AllowLists: allowListsNames, + }) + } + return &apiKeyOutputList } func GetKeyStatusFilters() []string { @@ -200,7 +207,7 @@ var createApiKeyCmd = &cobra.Command{ apiKeySpec.SetRoleId(roleId) } - if cmd.Flags().Changed("network-allow-lists") { + if util.IsFeatureFlagEnabled(util.API_KEY_ALLOW_LIST) && cmd.Flags().Changed("network-allow-lists") { allowLists, _ := cmd.Flags().GetString("network-allow-lists") allowListNames := strings.Split(allowLists, ",") @@ -227,7 +234,8 @@ var createApiKeyCmd = &cobra.Command{ Format: formatter.NewApiKeyFormat(viper.GetString("output")), } - formatter.SingleApiKeyWrite(apiKeyCtx, resp.GetData()) + apiKeyOutput := *enrichApiKeyDataWithAllowListInfo(&[]ybmclient.ApiKeyData{resp.GetData()}, authApi) + formatter.ApiKeyWrite(apiKeyCtx, apiKeyOutput) fmt.Printf("\nAPI Key: %s \n", formatter.Colorize(resp.GetJwt(), formatter.GREEN_COLOR)) fmt.Printf("\nThe API key is only shown once after creation. Copy and store it securely.\n") @@ -285,7 +293,10 @@ func init() { createApiKeyCmd.Flags().String("unit", "", "[REQUIRED] The time units for which the API Key will be valid. Available options are Hours, Days, and Months.") createApiKeyCmd.MarkFlagRequired("unit") createApiKeyCmd.Flags().String("description", "", "[OPTIONAL] Description of the API Key to be created.") - createApiKeyCmd.Flags().String("network-allow-lists", "", "[OPTIONAL] The network allow lists(comma separated names) to assign to the API key.") + + if util.IsFeatureFlagEnabled(util.API_KEY_ALLOW_LIST) { + createApiKeyCmd.Flags().String("network-allow-lists", "", "[OPTIONAL] The network allow lists(comma separated names) to assign to the API key.") + } createApiKeyCmd.Flags().String("role-name", "", "[OPTIONAL] The name of the role to be assigned to the API Key. If not provided, an Admin API Key will be generated.") createApiKeyCmd.Flags().BoolP("force", "f", false, "Bypass the prompt for non-interactive usage") diff --git a/cmd/api_key_test.go b/cmd/api_key_test.go index 10fbed74..027161ca 100644 --- a/cmd/api_key_test.go +++ b/cmd/api_key_test.go @@ -43,96 +43,141 @@ var _ = Describe("API Key Management", func() { server.Close() }) - Describe("When listing API keys", func() { - Context("with valid request", func() { - It("should list all API keys", func() { - err := loadJson("./test/fixtures/list-api-keys.json", &apiKeyListResponse) - Expect(err).ToNot(HaveOccurred()) + Describe("When listing API keys with allow list FF disabled", func() { + It("should hide allow list column", func() { + err := loadJson("./test/fixtures/list-api-keys.json", &apiKeyListResponse) + Expect(err).ToNot(HaveOccurred()) + + server.AppendHandlers( + ghttp.CombineHandlers( + ghttp.VerifyRequest(http.MethodGet, "/api/public/v1/accounts/340af43a-8a7c-4659-9258-4876fd6a207b/api-keys"), + ghttp.RespondWithJSONEncodedPtr(&statusCode, apiKeyListResponse), + ), + ) + os.Setenv("YBM_FF_API_KEY_ALLOW_LIST", "false") + cmd := exec.Command(compiledCLIPath, "api-key", "list") + session, err := gexec.Start(cmd, GinkgoWriter, GinkgoWriter) + + Expect(err).NotTo(HaveOccurred()) + session.Wait(2) + Expect(session.Out).Should(gbytes.Say(`Name Role Status Created By Date Created Last Used Expiration +apikey-1 Admin ACTIVE user@yb.com 2025-01-13T13:55:14.024Z 2025-01-13T14:21:40.713599Z 2099-12-31T00:00Z +apikey-2 Admin ACTIVE user@yb.com 2025-01-08T09:06:35.077Z Not yet used 2025-02-07T09:06:35.077071Z`)) + session.Kill() + }) + }) - server.AppendHandlers( - ghttp.CombineHandlers( - ghttp.VerifyRequest(http.MethodGet, "/api/public/v1/accounts/340af43a-8a7c-4659-9258-4876fd6a207b/api-keys"), - ghttp.RespondWithJSONEncodedPtr(&statusCode, apiKeyListResponse), - ), - ) + Describe("When listing API keys with allow list FF enabled", func() { + It("should show allow list column", func() { + err := loadJson("./test/fixtures/list-api-keys-with-allow-list.json", &apiKeyListResponse) + Expect(err).ToNot(HaveOccurred()) + err = loadJson("./test/fixtures/allow-list.json", &responseNetworkAllowList) + Expect(err).ToNot(HaveOccurred()) + + server.AppendHandlers( + ghttp.CombineHandlers( + ghttp.VerifyRequest(http.MethodGet, "/api/public/v1/accounts/340af43a-8a7c-4659-9258-4876fd6a207b/api-keys"), + ghttp.RespondWithJSONEncodedPtr(&statusCode, apiKeyListResponse), + ), + ghttp.CombineHandlers( + ghttp.VerifyRequest(http.MethodGet, "/api/public/v1/accounts/340af43a-8a7c-4659-9258-4876fd6a207b/projects/78d4459c-0f45-47a5-899a-45ddf43eba6e/allow-lists"), + ghttp.RespondWithJSONEncodedPtr(&statusCode, responseNetworkAllowList), + ), + func(w http.ResponseWriter, req *http.Request) { + queryParams := req.URL.Query() + Expect(queryParams.Get("status")).To(Equal("ACTIVE")) + }, + ) + + os.Setenv("YBM_FF_API_KEY_ALLOW_LIST", "true") + cmd := exec.Command(compiledCLIPath, "api-key", "list") + session, err := gexec.Start(cmd, GinkgoWriter, GinkgoWriter) + + Expect(err).NotTo(HaveOccurred()) + session.Wait(2) + Expect(session.Out).Should(gbytes.Say(`Name Role Status Created By Date Created Last Used Expiration Allow List +apikey-1 Admin ACTIVE user@yb.com 2025-01-13T13:55:14.024Z 2025-01-13T14:21:40.713599Z 2099-12-31T00:00Z device-ip-gween +apikey-2 Admin ACTIVE user@yb.com 2025-01-08T09:06:35.077Z Not yet used 2025-02-07T09:06:35.077071Z N/A`)) + session.Kill() + }) - cmd := exec.Command(compiledCLIPath, "api-key", "list") + }) + + Describe("When creating an API key", func() { + Context("with allow list feature flag disabled", func() { + os.Setenv("YBM_FF_API_KEY_ALLOW_LIST", "false") + + It("should error when passing allow list flag", func() { + os.Setenv("YBM_FF_API_KEY_ALLOW_LIST", "false") + cmd := exec.Command(compiledCLIPath, "api-key", "create", "--name", "apikey-1", "--duration", "30", "--unit", "DAYS", "--network-allow-lists", "device-ip") session, err := gexec.Start(cmd, GinkgoWriter, GinkgoWriter) Expect(err).NotTo(HaveOccurred()) session.Wait(2) - Expect(session.Out).Should(gbytes.Say(`Name Role Status Created By Date Created Last Used Expiration Allow List -apikey-1 Admin ACTIVE user@yb.com 2025-01-13T13:55:14.024Z 2025-01-13T14:21:40.713599Z 2099-12-31T00:00Z N/A -apikey-2 Admin ACTIVE user@yb.com 2025-01-08T09:06:35.077Z Not yet used 2025-02-07T09:06:35.077071Z N/A`)) - // Expect(server.ReceivedRequests()).Should(HaveLen(2)) + Expect(session.Err).Should(gbytes.Say(`\bError: unknown flag: --network-allow-lists\b`)) session.Kill() }) - }) - }) - Describe("When listing API keys with allow list", func() { - Context("with valid request", func() { - It("should list all API keys", func() { - err := loadJson("./test/fixtures/list-api-keys-with-allow-list.json", &apiKeyListResponse) - Expect(err).ToNot(HaveOccurred()) - err = loadJson("./test/fixtures/allow-list.json", &responseNetworkAllowList) + It("should create API key", func() { + err := loadJson("./test/fixtures/get-or-create-api-key.json", &apiKeyResponse) Expect(err).ToNot(HaveOccurred()) server.AppendHandlers( ghttp.CombineHandlers( - ghttp.VerifyRequest(http.MethodGet, "/api/public/v1/accounts/340af43a-8a7c-4659-9258-4876fd6a207b/api-keys"), - ghttp.RespondWithJSONEncodedPtr(&statusCode, apiKeyListResponse), - ), - ghttp.CombineHandlers( - ghttp.VerifyRequest(http.MethodGet, "/api/public/v1/accounts/340af43a-8a7c-4659-9258-4876fd6a207b/projects/78d4459c-0f45-47a5-899a-45ddf43eba6e/allow-lists"), - ghttp.RespondWithJSONEncodedPtr(&statusCode, responseNetworkAllowList), + ghttp.VerifyRequest(http.MethodPost, "/api/public/v1/accounts/340af43a-8a7c-4659-9258-4876fd6a207b/api-keys"), + ghttp.RespondWithJSONEncodedPtr(&statusCode, apiKeyResponse), ), - func(w http.ResponseWriter, req *http.Request) { - queryParams := req.URL.Query() - Expect(queryParams.Get("status")).To(Equal("ACTIVE")) - }, ) - cmd := exec.Command(compiledCLIPath, "api-key", "list") + cmd := exec.Command(compiledCLIPath, "api-key", "create", "--name", "apikey-1", "--duration", "30", "--unit", "DAYS") session, err := gexec.Start(cmd, GinkgoWriter, GinkgoWriter) Expect(err).NotTo(HaveOccurred()) session.Wait(2) - Expect(session.Out).Should(gbytes.Say(`Name Role Status Created By Date Created Last Used Expiration Allow List -apikey-1 Admin ACTIVE user@yb.com 2025-01-13T13:55:14.024Z 2025-01-13T14:21:40.713599Z 2099-12-31T00:00Z device-ip-gween -apikey-2 Admin ACTIVE user@yb.com 2025-01-08T09:06:35.077Z Not yet used 2025-02-07T09:06:35.077071Z N/A`)) + Expect(session.Out).Should(gbytes.Say(`Name Role Status Created By Date Created Last Used Expiration +apikey-1 Admin ACTIVE admin 2025-01-13T15:55:55.825Z Not yet used 2025-02-12T15:55:55.824833Z + +API Key: test-jwt`)) session.Kill() }) }) - }) - Describe("When creating an API key", func() { - Context("with valid request", func() { - It("should create a new API key", func() { + Context("with allow list feature flag enabled", func() { + It("should create API key", func() { err := loadJson("./test/fixtures/get-or-create-api-key.json", &apiKeyResponse) Expect(err).ToNot(HaveOccurred()) + err = loadJson("./test/fixtures/allow-list.json", &responseNetworkAllowList) + Expect(err).ToNot(HaveOccurred()) server.AppendHandlers( + ghttp.CombineHandlers( + ghttp.VerifyRequest(http.MethodGet, "/api/public/v1/accounts/340af43a-8a7c-4659-9258-4876fd6a207b/projects/78d4459c-0f45-47a5-899a-45ddf43eba6e/allow-lists"), + ghttp.RespondWithJSONEncodedPtr(&statusCode, responseNetworkAllowList), + ), ghttp.CombineHandlers( ghttp.VerifyRequest(http.MethodPost, "/api/public/v1/accounts/340af43a-8a7c-4659-9258-4876fd6a207b/api-keys"), ghttp.RespondWithJSONEncodedPtr(&statusCode, apiKeyResponse), ), + ghttp.CombineHandlers( + ghttp.VerifyRequest(http.MethodGet, "/api/public/v1/accounts/340af43a-8a7c-4659-9258-4876fd6a207b/projects/78d4459c-0f45-47a5-899a-45ddf43eba6e/allow-lists"), + ghttp.RespondWithJSONEncodedPtr(&statusCode, responseNetworkAllowList), + ), ) - cmd := exec.Command(compiledCLIPath, "api-key", "create", "--name", "apikey-1", "--duration", "30", "--unit", "DAYS") + os.Setenv("YBM_FF_API_KEY_ALLOW_LIST", "true") + cmd := exec.Command(compiledCLIPath, "api-key", "create", "--name", "apikey-1", "--duration", "30", "--unit", "DAYS", "--network-allow-lists", "device-ip-gween") session, err := gexec.Start(cmd, GinkgoWriter, GinkgoWriter) Expect(err).NotTo(HaveOccurred()) session.Wait(2) Expect(session.Out).Should(gbytes.Say(`Name Role Status Created By Date Created Last Used Expiration Allow List -apikey-1 Admin ACTIVE admin 2025-01-13T15:55:55.825Z Not yet used 2025-02-12T15:55:55.824833Z N/A - -API Key: test-jwt +apikey-1 Admin ACTIVE admin 2025-01-13T15:55:55.825Z Not yet used 2025-02-12T15:55:55.824833Z device-ip-gween -The API key is only shown once after creation. Copy and store it securely.`)) +API Key: test-jwt`)) session.Kill() }) }) + }) Describe("When revoking an API key", func() { diff --git a/cmd/test/fixtures/get-or-create-api-key.json b/cmd/test/fixtures/get-or-create-api-key.json index dd2105b1..c2a51243 100644 --- a/cmd/test/fixtures/get-or-create-api-key.json +++ b/cmd/test/fixtures/get-or-create-api-key.json @@ -5,7 +5,7 @@ "description": "", "expire_after_hours": 720, "role_id": "36ec6789-6dbf-4d12-91b4-243587d6882b", - "allow_list_info": [] + "allow_list_info": ["1eaf5552-e2f3-4a28-b215-106667c05178"] }, "info": { "id": "4f32f91e-b5fc-462f-8111-134eb813f6d0", diff --git a/cmd/util/feature_flags.go b/cmd/util/feature_flags.go index f01675d5..75deae2e 100644 --- a/cmd/util/feature_flags.go +++ b/cmd/util/feature_flags.go @@ -35,6 +35,7 @@ const ( PITR_RESTORE FeatureFlag = "PITR_RESTORE" CONNECTION_POOLING FeatureFlag = "CONNECTION_POOLING" DR FeatureFlag = "DR" + API_KEY_ALLOW_LIST FeatureFlag = "API_KEY_ALLOW_LIST" ) func (f FeatureFlag) String() string { diff --git a/internal/formatter/api_keys.go b/internal/formatter/api_keys.go index c7960c92..2fd64641 100644 --- a/internal/formatter/api_keys.go +++ b/internal/formatter/api_keys.go @@ -20,11 +20,13 @@ import ( "strings" "github.com/sirupsen/logrus" + "github.com/yugabyte/ybm-cli/cmd/util" ybmclient "github.com/yugabyte/yugabytedb-managed-go-client-internal" ) const ( - defaultApiKeyListing = "table {{.ApiKeyName}}\t{{.ApiKeyRole}}\t{{.ApiKeyStatus}}\t{{.Issuer}}\t{{.CreatedAt}}\t{{.LastUsed}}\t{{.ExpiryTime}}\t{{.AllowList}}" + defaultApiKeyListing = "table {{.ApiKeyName}}\t{{.ApiKeyRole}}\t{{.ApiKeyStatus}}\t{{.Issuer}}\t{{.CreatedAt}}\t{{.LastUsed}}\t{{.ExpiryTime}}" + apiKeyListingV2 = "table {{.ApiKeyName}}\t{{.ApiKeyRole}}\t{{.ApiKeyStatus}}\t{{.Issuer}}\t{{.CreatedAt}}\t{{.LastUsed}}\t{{.ExpiryTime}}\t{{.AllowList}}" createdAtHeader = "Date Created" expiryTimeHeader = "Expiration" apiKeyRoleHeader = "Role" @@ -32,14 +34,14 @@ const ( ) type ApiKeyDataAllowListInfo struct { - ApiKey *ybmclient.ApiKeyData + ApiKey *ybmclient.ApiKeyData AllowLists []string } type ApiKeyContext struct { HeaderContext Context - a ybmclient.ApiKeyData + a ybmclient.ApiKeyData allowLists []string } @@ -47,6 +49,9 @@ func NewApiKeyFormat(source string) Format { switch source { case "table", "": format := defaultApiKeyListing + if util.IsFeatureFlagEnabled(util.API_KEY_ALLOW_LIST) { + format = apiKeyListingV2 + } return Format(format) default: // custom format or json or pretty return Format(source) @@ -68,18 +73,6 @@ func ApiKeyWrite(ctx Context, keys []ApiKeyDataAllowListInfo) error { return ctx.Write(NewApiKeyContext(), render) } -func SingleApiKeyWrite(ctx Context, key ybmclient.ApiKeyData) error { - render := func(format func(subContext SubContext) error) error { - err := format(&ApiKeyContext{a: key}) - if err != nil { - logrus.Debugf("Error rendering API key: %v", err) - return err - } - return nil - } - return ctx.Write(NewApiKeyContext(), render) -} - // NewApiKeyContext creates a new context for rendering API Keys func NewApiKeyContext() *ApiKeyContext { apiKeyCtx := ApiKeyContext{} From 80e653266ffadee3350e1c15132170c37a836cd2 Mon Sep 17 00:00:00 2001 From: bhupendray-yb Date: Thu, 16 Jan 2025 13:00:28 +0530 Subject: [PATCH 3/3] [CLOUDGA-25136] remove default active status --- cmd/api_key/api_key.go | 22 ++++++---------------- cmd/api_key_test.go | 4 ---- 2 files changed, 6 insertions(+), 20 deletions(-) diff --git a/cmd/api_key/api_key.go b/cmd/api_key/api_key.go index 28632cf3..ce098dc0 100644 --- a/cmd/api_key/api_key.go +++ b/cmd/api_key/api_key.go @@ -57,19 +57,8 @@ var listApiKeysCmd = &cobra.Command{ apiKeyListRequest = apiKeyListRequest.ApiKeyName(name) } - isNameSpecified := cmd.Flags().Changed("name") - keyStatus := "ACTIVE" - // If --name arg is specified, don't set default filter for key-status - // because if an API key is revoked/expired and user do $ ybm api-key list --name - // it will lead to empty response if we filter key by ACTIVE status. - if isNameSpecified { - keyStatus = "" - } - // if user filters by key status, add it to the request - if cmd.Flags().Changed("status") { - keyStatus, _ = cmd.Flags().GetString("status") - } + keyStatus, _ := cmd.Flags().GetString("status") if keyStatus != "" { validStatus := false for _, v := range GetKeyStatusFilters() { @@ -100,20 +89,21 @@ var listApiKeysCmd = &cobra.Command{ return } - apiKeyOutputList := *enrichApiKeyDataWithAllowListInfo(&resp.Data, authApi) + apiKeyOutputList := *addAllowListNameToApiKeyData(&resp.Data, authApi) formatter.ApiKeyWrite(apiKeyCtx, apiKeyOutputList) }, } -func enrichApiKeyDataWithAllowListInfo(apiKeys *[]ybmclient.ApiKeyData, authApi *ybmAuthClient.AuthApiClient) *[]formatter.ApiKeyDataAllowListInfo { +func addAllowListNameToApiKeyData(apiKeys *[]ybmclient.ApiKeyData, authApi *ybmAuthClient.AuthApiClient) *[]formatter.ApiKeyDataAllowListInfo { apiKeyOutputList := make([]formatter.ApiKeyDataAllowListInfo, 0) // For each API key, fetch the allow list(s) associated with it + isApiKeyAllowListEnabled := util.IsFeatureFlagEnabled(util.API_KEY_ALLOW_LIST) for _, apiKey := range *apiKeys { apiKeyId := apiKey.GetInfo().Id allowListsNames := make([]string, 0) - if util.IsFeatureFlagEnabled(util.API_KEY_ALLOW_LIST) { + if isApiKeyAllowListEnabled { allowListIds := apiKey.GetSpec().AllowListInfo if allowListIds != nil && len(*allowListIds) > 0 { apiKeyAllowLists, resp, err := authApi.ListApiKeyNetworkAllowLists(apiKeyId).Execute() @@ -234,7 +224,7 @@ var createApiKeyCmd = &cobra.Command{ Format: formatter.NewApiKeyFormat(viper.GetString("output")), } - apiKeyOutput := *enrichApiKeyDataWithAllowListInfo(&[]ybmclient.ApiKeyData{resp.GetData()}, authApi) + apiKeyOutput := *addAllowListNameToApiKeyData(&[]ybmclient.ApiKeyData{resp.GetData()}, authApi) formatter.ApiKeyWrite(apiKeyCtx, apiKeyOutput) fmt.Printf("\nAPI Key: %s \n", formatter.Colorize(resp.GetJwt(), formatter.GREEN_COLOR)) diff --git a/cmd/api_key_test.go b/cmd/api_key_test.go index 027161ca..168229a1 100644 --- a/cmd/api_key_test.go +++ b/cmd/api_key_test.go @@ -83,10 +83,6 @@ apikey-2 Admin ACTIVE user@yb.com 2025-01-08T09:06:35.077Z Not yet ghttp.VerifyRequest(http.MethodGet, "/api/public/v1/accounts/340af43a-8a7c-4659-9258-4876fd6a207b/projects/78d4459c-0f45-47a5-899a-45ddf43eba6e/allow-lists"), ghttp.RespondWithJSONEncodedPtr(&statusCode, responseNetworkAllowList), ), - func(w http.ResponseWriter, req *http.Request) { - queryParams := req.URL.Query() - Expect(queryParams.Get("status")).To(Equal("ACTIVE")) - }, ) os.Setenv("YBM_FF_API_KEY_ALLOW_LIST", "true")