diff --git a/internal/client/products/products.go b/internal/client/products/products.go index 54e2a8c0..806aa4c4 100644 --- a/internal/client/products/products.go +++ b/internal/client/products/products.go @@ -57,6 +57,7 @@ type APIProduct struct { QuotaTimeUnit string `json:"quotaTimeUnit,omitempty"` Scopes []string `json:"scopes,omitempty"` QuotaCounterScope string `json:"quotaCounterScope,omitempty"` + Space string `json:"space,omitempty"` } type OperationGroup struct { @@ -142,6 +143,17 @@ func Delete(name string) (respBody []byte, err error) { return respBody, err } +// Move between spaces +func Move(name string, space string) (respBody []byte, err error) { + u, _ := url.Parse(apiclient.GetApigeeBaseURL()) + q := u.Query() + q.Set("space", space) + u.RawQuery = q.Encode() + u.Path = path.Join(u.Path, apiclient.GetApigeeOrg(), "apiproducts", name, ":move") + respBody, err = apiclient.HttpClient(u.String(), "") + return respBody, err +} + // upsert - use Action to control if upsert is enabled func upsert(p APIProduct, a Action) (respBody []byte, err error) { u, _ := url.Parse(apiclient.GetApigeeBaseURL()) @@ -213,7 +225,7 @@ func ListAttributes(name string) (respBody []byte, err error) { } // List -func List(count int, startKey string, expand bool) (respBody []byte, err error) { +func List(count int, startKey string, expand bool, space string) (respBody []byte, err error) { u, _ := url.Parse(apiclient.GetApigeeBaseURL()) u.Path = path.Join(u.Path, apiclient.GetApigeeOrg(), "apiproducts") q := u.Query() @@ -228,6 +240,9 @@ func List(count int, startKey string, expand bool) (respBody []byte, err error) if startKey != "" { q.Set("startKey", startKey) } + if space != "" { + q.Set("space", space) + } u.RawQuery = q.Encode() @@ -237,7 +252,7 @@ func List(count int, startKey string, expand bool) (respBody []byte, err error) } // ListFilter -func ListFilter(filter map[string]string) (respBody []byte, err error) { +func ListFilter(filter map[string]string, space string) (respBody []byte, err error) { maxProducts := 1000 nextPage := true startKey := "" @@ -247,7 +262,7 @@ func ListFilter(filter map[string]string) (respBody []byte, err error) { apiclient.ClientPrintHttpResponse.Set(false) for nextPage { - pageResp, err := List(maxProducts, startKey, true) + pageResp, err := List(maxProducts, startKey, true, space) if err != nil { return nil, err } @@ -305,13 +320,13 @@ func ListFilter(filter map[string]string) (respBody []byte, err error) { } // Export -func Export(conn int) (payload [][]byte, err error) { +func Export(conn int, space string) (payload [][]byte, err error) { // parent workgroup var pwg sync.WaitGroup var mu sync.Mutex entityType := "apiproducts" - products, err := listAllProducts() + products, err := listAllProducts(space) if err != nil { return apiclient.GetEntityPayloadList(), err } @@ -461,7 +476,7 @@ func readProductsFile(filePath string) ([]APIProduct, error) { return products, nil } -func listAllProducts() (products apiProducts, err error) { +func listAllProducts(space string) (products apiProducts, err error) { var startKey string products = apiProducts{} @@ -474,14 +489,16 @@ func listAllProducts() (products apiProducts, err error) { for { p := apiProducts{} - + q := u.Query() if startKey != "" { - q := u.Query() + q.Set("startKey", startKey) q.Set("count", "1000") - u.RawQuery = q.Encode() } - + if space != "" { + q.Set("space", space) + } + u.RawQuery = q.Encode() respBody, err := apiclient.HttpClient(u.String()) startKey = "" if err != nil { diff --git a/internal/cmd/org/export.go b/internal/cmd/org/export.go index dc6c45c1..b5086dc0 100644 --- a/internal/cmd/org/export.go +++ b/internal/cmd/org/export.go @@ -87,7 +87,7 @@ var ExportCmd = &cobra.Command{ } clilog.Info.Println("Exporting API Products...") - if productResponse, err = products.Export(conn); proceedOnError(err) != nil { + if productResponse, err = products.Export(conn, space); proceedOnError(err) != nil { return err } if err = apiclient.WriteArrayByteArrayToFile( diff --git a/internal/cmd/org/import.go b/internal/cmd/org/import.go index d6ed9961..8e723d36 100644 --- a/internal/cmd/org/import.go +++ b/internal/cmd/org/import.go @@ -223,7 +223,7 @@ var ImportCmd = &cobra.Command{ var ( importTrace, importDebugmask bool - folder, space string + folder string ) func init() { diff --git a/internal/cmd/org/org.go b/internal/cmd/org/org.go index 2d79e232..a247dec1 100644 --- a/internal/cmd/org/org.go +++ b/internal/cmd/org/org.go @@ -26,7 +26,7 @@ var Cmd = &cobra.Command{ Long: "Manage Apigee Orgs", } -var org, region string +var org, region, space string func init() { Cmd.PersistentFlags().StringVarP(®ion, "region", "r", diff --git a/internal/cmd/products/crtprod.go b/internal/cmd/products/crtprod.go index 46f8f330..36953eb1 100644 --- a/internal/cmd/products/crtprod.go +++ b/internal/cmd/products/crtprod.go @@ -51,6 +51,7 @@ var CreateCmd = &cobra.Command{ p.Environments = environments p.Proxies = proxies p.Scopes = scopes + p.Space = space p.OperationGroup, err = getOperationGroup(operationGroupFile) if err != nil { @@ -117,6 +118,8 @@ func init() { "", "File containing gRPC Operation Group JSON. See samples for how to create the file") CreateCmd.Flags().StringVarP("aCounterScope, "quota-counter-scope", "", "", "Scope of the quota decides how the quota counter gets applied; can be PROXY or OPERATION") + CreateCmd.Flags().StringVarP(&space, "space", "", + "", "Apigee Space to associate to") // TODO: apiresource -r later _ = CreateCmd.MarkFlagRequired("name") diff --git a/internal/cmd/products/expprod.go b/internal/cmd/products/expprod.go index 4e722d3b..0e4975dd 100644 --- a/internal/cmd/products/expprod.go +++ b/internal/cmd/products/expprod.go @@ -35,7 +35,7 @@ var ExpCmd = &cobra.Command{ const exportFileName = "products.json" apiclient.DisableCmdPrintHttpResponse() - payload, err := products.Export(conn) + payload, err := products.Export(conn, space) if err != nil { return err } @@ -46,4 +46,6 @@ var ExpCmd = &cobra.Command{ func init() { ExpCmd.Flags().IntVarP(&conn, "conn", "c", 4, "Number of connections") + ExpCmd.Flags().StringVarP(&space, "space", "", + "", "Apigee Space associated to") } diff --git a/internal/cmd/products/listproducts.go b/internal/cmd/products/listproducts.go index a848b507..a69fece8 100644 --- a/internal/cmd/products/listproducts.go +++ b/internal/cmd/products/listproducts.go @@ -42,9 +42,9 @@ var ListCmd = &cobra.Command{ cmd.SilenceUsage = true if len(filter) > 0 { - _, err = products.ListFilter(filter) + _, err = products.ListFilter(filter, space) } else { - _, err = products.List(count, startKey, expand) + _, err = products.List(count, startKey, expand, space) } return err }, @@ -72,4 +72,7 @@ func init() { ListCmd.Flags().BoolVarP(&expand, "expand", "x", false, "Expand Details") + + ListCmd.Flags().StringVarP(&space, "space", "", + "", "Apigee Space associated to") } diff --git a/internal/cmd/products/moveprod.go b/internal/cmd/products/moveprod.go new file mode 100644 index 00000000..7ab158e2 --- /dev/null +++ b/internal/cmd/products/moveprod.go @@ -0,0 +1,48 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package products + +import ( + "internal/apiclient" + "internal/client/products" + + "github.com/spf13/cobra" +) + +// MoveCmd to move products between spaces +var MoveCmd = &cobra.Command{ + Use: "move", + Short: "Move an API product between Spaces", + Long: "Move an API product between Spaces", + Args: func(cmd *cobra.Command, args []string) (err error) { + apiclient.SetRegion(region) + return apiclient.SetApigeeOrg(org) + }, + RunE: func(cmd *cobra.Command, args []string) (err error) { + cmd.SilenceUsage = true + _, err = products.Move(name, space) + return err + }, +} + +func init() { + MoveCmd.Flags().StringVarP(&name, "name", "n", + "", "Name of the API Product") + MoveCmd.Flags().StringVarP(&space, "space", "", + "", "Name of the Apigee Space moving to") + + _ = MoveCmd.MarkFlagRequired("name") + _ = MoveCmd.MarkFlagRequired("space") +} diff --git a/internal/cmd/products/products.go b/internal/cmd/products/products.go index 0148eb7d..c4668a9f 100644 --- a/internal/cmd/products/products.go +++ b/internal/cmd/products/products.go @@ -37,9 +37,9 @@ var ( ) var ( - description, approval, displayName, quota, quotaInterval, quotaUnit string - environments, proxies, scopes []string - attrs map[string]string + description, approval, displayName, quota, quotaInterval, quotaUnit, space string + environments, proxies, scopes []string + attrs map[string]string ) var examples = []string{ @@ -47,12 +47,15 @@ var examples = []string{ --display-name $product_name \ --opgrp $ops_file \ --envs $env \ +--space $space \ --approval auto \ ---attrs access=public --default-token`, `apigeecli products create --name $product_name \ +--attrs access=public --default-token`, + `apigeecli products create --name $product_name \ --display-name $product_name \ --opgrp $ops_file \ --envs $env \ --approval auto \ +--space $space \ --attrs access=public \ --quota 100 --interval 1 --unit minute --default-token`, `apigeecli products import -f samples/apiproduct-legacy.json --default-token`, @@ -75,6 +78,7 @@ func init() { Cmd.AddCommand(UpdateCmd) Cmd.AddCommand(AttributesCmd) Cmd.AddCommand(RatePlanCmd) + Cmd.AddCommand(MoveCmd) } func getOperationGroup(operationGroupFile string) (*products.OperationGroup, error) { diff --git a/internal/cmd/products/updprod.go b/internal/cmd/products/updprod.go index d3f78001..e75b254c 100644 --- a/internal/cmd/products/updprod.go +++ b/internal/cmd/products/updprod.go @@ -51,6 +51,7 @@ var UpdateCmd = &cobra.Command{ p.Environments = environments p.Proxies = proxies p.Scopes = scopes + p.Space = space p.OperationGroup, err = getOperationGroup(operationGroupFile) if err != nil { @@ -110,6 +111,8 @@ func init() { "", "File containing gRPC Operation Group JSON. See samples for how to create the file") UpdateCmd.Flags().StringVarP("aCounterScope, "quota-counter-scope", "", "", "Scope of the quota decides how the quota counter gets applied; can be PROXY or OPERATION") + UpdateCmd.Flags().StringVarP(&space, "space", "", + "", "Associated Apigee Space. Pass this if the API Product being updated is part of a space") // TODO: apiresource -r later _ = UpdateCmd.MarkFlagRequired("name")