diff --git a/cmd/appgroups/appgroups.go b/cmd/appgroups/appgroups.go new file mode 100644 index 000000000..858a71799 --- /dev/null +++ b/cmd/appgroups/appgroups.go @@ -0,0 +1,43 @@ +// Copyright 2023 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 appgroups + +import ( + "github.com/spf13/cobra" +) + +// Cmd to manage appgroups +var Cmd = &cobra.Command{ + Use: "appgroups", + Short: "Manage Apigee Application Groups", + Long: "Manage Apigee Application Groups", +} + +var org string + +func init() { + Cmd.PersistentFlags().StringVarP(&org, "org", "o", + "", "Apigee organization name") + + Cmd.AddCommand(ListCmd) + Cmd.AddCommand(GetCmd) + Cmd.AddCommand(DelCmd) + Cmd.AddCommand(CreateCmd) + Cmd.AddCommand(UpdateCmd) + Cmd.AddCommand(ManageCmd) + Cmd.AddCommand(AppCmd) + Cmd.AddCommand(ExpCmd) + Cmd.AddCommand(ImpCmd) +} diff --git a/cmd/appgroups/apps.go b/cmd/appgroups/apps.go new file mode 100644 index 000000000..c732b8bd9 --- /dev/null +++ b/cmd/appgroups/apps.go @@ -0,0 +1,38 @@ +// Copyright 2023 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 appgroups + +import ( + "github.com/spf13/cobra" +) + +// AppCmd to manage apps +var AppCmd = &cobra.Command{ + Use: "apps", + Short: "Manage apps in an Apigee Application Group", + Long: "Manage apps in an Apigee Application Group", +} + +func init() { + AppCmd.AddCommand(CreateAppCmd) + AppCmd.AddCommand(ListAppCmd) + AppCmd.AddCommand(GetAppCmd) + AppCmd.AddCommand(DelAppCmd) + AppCmd.AddCommand(ManageAppCmd) + AppCmd.AddCommand(UpdateAppCmd) + AppCmd.AddCommand(KeyCmd) + AppCmd.AddCommand(ExpAppCmd) + AppCmd.AddCommand(ImpAppCmd) +} diff --git a/cmd/appgroups/createkey.go b/cmd/appgroups/createkey.go new file mode 100644 index 000000000..ebd66ab7e --- /dev/null +++ b/cmd/appgroups/createkey.go @@ -0,0 +1,69 @@ +// Copyright 2023 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 appgroups + +import ( + "fmt" + "strconv" + + "internal/apiclient" + "internal/client/appgroups" + + "github.com/spf13/cobra" +) + +// CreateKeyCmd to create developer keys +var CreateKeyCmd = &cobra.Command{ + Use: "create", + Short: "Create an app key", + Long: "Create an app key", + Args: func(cmd *cobra.Command, args []string) (err error) { + if (key != "" && secret == "") || (secret != "" && key == "") { + return fmt.Errorf("key and secret must both be passed or neither must be sent") + } + return apiclient.SetApigeeOrg(org) + }, + RunE: func(cmd *cobra.Command, args []string) (err error) { + _, err = appgroups.CreateKey(name, appName, key, secret, strconv.Itoa(expiry), apiProducts, scopes, attrs) + return + }, +} + +var ( + secret string + expiry int +) + +func init() { + CreateKeyCmd.Flags().StringVarP(&name, "name", "n", + "", "Name of the AppGroup") + CreateKeyCmd.Flags().StringVarP(&appName, "app-name", "", + "", "Name of the app") + CreateKeyCmd.Flags().StringVarP(&key, "key", "k", + "", "Import an existing AppGroup app consumer key") + CreateKeyCmd.Flags().StringVarP(&secret, "secret", "r", + "", "Import an existing AppGroup app consumer secret") + CreateKeyCmd.Flags().IntVarP(&expiry, "expiry", "x", + -1, "Expiration time, in seconds, for the consumer key") + CreateKeyCmd.Flags().StringArrayVarP(&apiProducts, "prods", "p", + []string{}, "A list of api products") + CreateKeyCmd.Flags().StringArrayVarP(&scopes, "scopes", "s", + []string{}, "OAuth scopes") + CreateKeyCmd.Flags().StringToStringVar(&attrs, "attrs", + nil, "Custom attributes") + + _ = CreateKeyCmd.MarkFlagRequired("name") + _ = CreateKeyCmd.MarkFlagRequired("app-name") +} diff --git a/cmd/appgroups/crtapp.go b/cmd/appgroups/crtapp.go new file mode 100644 index 000000000..724617d14 --- /dev/null +++ b/cmd/appgroups/crtapp.go @@ -0,0 +1,62 @@ +// Copyright 2020 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 appgroups + +import ( + "internal/apiclient" + + "internal/client/appgroups" + + "github.com/spf13/cobra" +) + +// CreateAppCmd to create app +var CreateAppCmd = &cobra.Command{ + Use: "create", + Short: "Create an App", + Long: "Create an App in an AppGroup", + Args: func(cmd *cobra.Command, args []string) (err error) { + return apiclient.SetApigeeOrg(org) + }, + RunE: func(cmd *cobra.Command, args []string) (err error) { + _, err = appgroups.CreateApp(name, appName, expires, callback, apiProducts, scopes, attrs) + return + }, +} + +var ( + expires, callback string + apiProducts, scopes []string +) + +func init() { + CreateAppCmd.Flags().StringVarP(&name, "name", "n", + "", "Name of the AppGroup") + CreateAppCmd.Flags().StringVarP(&appName, "app-name", "", + "", "Name of the app") + CreateAppCmd.Flags().StringVarP(&expires, "expires", "x", + "", "A setting, in milliseconds, for the lifetime of the consumer key") + CreateAppCmd.Flags().StringVarP(&callback, "callback", "c", + "", "The callbackUrl is used by OAuth") + CreateAppCmd.Flags().StringArrayVarP(&apiProducts, "prods", "p", + []string{}, "A list of api products") + CreateAppCmd.Flags().StringArrayVarP(&scopes, "scopes", "s", + []string{}, "OAuth scopes") + CreateAppCmd.Flags().StringToStringVar(&attrs, "attrs", + nil, "Custom attributes") + + _ = CreateAppCmd.MarkFlagRequired("name") + _ = CreateAppCmd.MarkFlagRequired("app-name") +} diff --git a/cmd/appgroups/crtappgroup.go b/cmd/appgroups/crtappgroup.go new file mode 100644 index 000000000..391b6bb40 --- /dev/null +++ b/cmd/appgroups/crtappgroup.go @@ -0,0 +1,56 @@ +// Copyright 2023 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 appgroups + +import ( + "internal/apiclient" + "internal/client/appgroups" + + "github.com/spf13/cobra" +) + +// CreateCmd to create appgroup +var CreateCmd = &cobra.Command{ + Use: "create", + Short: "Create an AppGroup", + Long: "Create an AppGroup", + Args: func(cmd *cobra.Command, args []string) (err error) { + return apiclient.SetApigeeOrg(org) + }, + RunE: func(cmd *cobra.Command, args []string) (err error) { + _, err = appgroups.Create(name, channelURI, channelID, displayName, attrs) + return + }, +} + +var ( + channelID, channelURI, displayName string + attrs map[string]string +) + +func init() { + CreateCmd.Flags().StringVarP(&name, "name", "n", + "", "Name of the AppGroup") + CreateCmd.Flags().StringVarP(&channelID, "channelid", "i", + "", "channel identifier identifies the owner maintaining this grouping") + CreateCmd.Flags().StringVarP(&channelURI, "channelurl", "u", + "", "A reference to the associated storefront/marketplace") + CreateCmd.Flags().StringVarP(&displayName, "display-name", "d", + "", "app group name displayed in the UI") + CreateCmd.Flags().StringToStringVar(&attrs, "attrs", + nil, "Custom attributes") + + _ = CreateCmd.MarkFlagRequired("name") +} diff --git a/cmd/appgroups/delapp.go b/cmd/appgroups/delapp.go new file mode 100644 index 000000000..c66e2c982 --- /dev/null +++ b/cmd/appgroups/delapp.go @@ -0,0 +1,46 @@ +// Copyright 2020 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 appgroups + +import ( + "internal/apiclient" + "internal/client/appgroups" + + "github.com/spf13/cobra" +) + +// DelAppCmd to get app +var DelAppCmd = &cobra.Command{ + Use: "delete", + Short: "Delete an App in an AppGroup", + Long: "Delete an in an AppGroup", + Args: func(cmd *cobra.Command, args []string) error { + return apiclient.SetApigeeOrg(org) + }, + RunE: func(cmd *cobra.Command, args []string) (err error) { + _, err = appgroups.DeleteApp(name, appName) + return err + }, +} + +func init() { + DelAppCmd.Flags().StringVarP(&name, "name", "n", + "", "Name of the AppGroup") + DelAppCmd.Flags().StringVarP(&appName, "app-name", "", + "", "Name of the app") + + _ = DelAppCmd.MarkFlagRequired("name") + _ = DelAppCmd.MarkFlagRequired("app-name") +} diff --git a/cmd/appgroups/delappgroup.go b/cmd/appgroups/delappgroup.go new file mode 100644 index 000000000..e2bf817fd --- /dev/null +++ b/cmd/appgroups/delappgroup.go @@ -0,0 +1,41 @@ +// Copyright 2023 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 appgroups + +import ( + "internal/apiclient" + "internal/client/appgroups" + + "github.com/spf13/cobra" +) + +// DelCmd to get appgroup +var DelCmd = &cobra.Command{ + Use: "delete", + Short: "Delete AppGroup in an Organization by AppGroup Name", + Long: "Delete AppGroup in an Organization by AppGroup Name", + Args: func(cmd *cobra.Command, args []string) error { + return apiclient.SetApigeeOrg(org) + }, + RunE: func(cmd *cobra.Command, args []string) (err error) { + _, err = appgroups.Delete(name) + return err + }, +} + +func init() { + DelCmd.Flags().StringVarP(&name, "name", "n", + "", "Name of the AppGroup") +} diff --git a/cmd/appgroups/delkey.go b/cmd/appgroups/delkey.go new file mode 100644 index 000000000..e46bfeb8e --- /dev/null +++ b/cmd/appgroups/delkey.go @@ -0,0 +1,50 @@ +// Copyright 2023 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 appgroups + +import ( + "internal/apiclient" + + "internal/client/appgroups" + + "github.com/spf13/cobra" +) + +// DelKeyCmd to delete credential +var DelKeyCmd = &cobra.Command{ + Use: "delete", + Short: "Deletes a consumer key for a AppGroup app", + Long: "Deletes a consumer key for a AppGroup app", + Args: func(cmd *cobra.Command, args []string) (err error) { + return apiclient.SetApigeeOrg(org) + }, + RunE: func(cmd *cobra.Command, args []string) (err error) { + _, err = appgroups.DeleteKey(name, appName, key) + return + }, +} + +func init() { + DelKeyCmd.Flags().StringVarP(&name, "name", "n", + "", "Name of the AppGroup") + DelKeyCmd.Flags().StringVarP(&appName, "app-name", "", + "", "Name of the app") + DelKeyCmd.Flags().StringVarP(&key, "key", "k", + "", "App consumer key") + + _ = DelKeyCmd.MarkFlagRequired("name") + _ = DelKeyCmd.MarkFlagRequired("app-name") + _ = DelKeyCmd.MarkFlagRequired("key") +} diff --git a/cmd/appgroups/delkeyprod.go b/cmd/appgroups/delkeyprod.go new file mode 100644 index 000000000..28b4251cd --- /dev/null +++ b/cmd/appgroups/delkeyprod.go @@ -0,0 +1,55 @@ +// Copyright 2023 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 appgroups + +import ( + "internal/apiclient" + + "internal/client/appgroups" + + "github.com/spf13/cobra" +) + +// DelProdKeyCmd to delete credential +var DelProdKeyCmd = &cobra.Command{ + Use: "delete-product", + Short: "Deletes a product from an App in an AppGroup app", + Long: "Deletes a product from an App in an AppGroup app", + Args: func(cmd *cobra.Command, args []string) (err error) { + return apiclient.SetApigeeOrg(org) + }, + RunE: func(cmd *cobra.Command, args []string) (err error) { + _, err = appgroups.DeleteKeyProduct(name, appName, key, productName) + return + }, +} + +var productName string + +func init() { + DelProdKeyCmd.Flags().StringVarP(&name, "name", "n", + "", "Name of the AppGroup") + DelProdKeyCmd.Flags().StringVarP(&appName, "app-name", "", + "", "Name of the app") + DelProdKeyCmd.Flags().StringVarP(&key, "key", "k", + "", "App consumer key") + DelProdKeyCmd.Flags().StringVarP(&productName, "prod-name", "p", + "", "API Product Name") + + _ = DelProdKeyCmd.MarkFlagRequired("name") + _ = DelProdKeyCmd.MarkFlagRequired("app-name") + _ = DelProdKeyCmd.MarkFlagRequired("key") + _ = DelProdKeyCmd.MarkFlagRequired("prod-name") +} diff --git a/cmd/appgroups/expappgroups.go b/cmd/appgroups/expappgroups.go new file mode 100644 index 000000000..99dadba87 --- /dev/null +++ b/cmd/appgroups/expappgroups.go @@ -0,0 +1,46 @@ +// Copyright 2023 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 appgroups + +import ( + "internal/apiclient" + + "internal/client/appgroups" + + "github.com/spf13/cobra" +) + +// ExpCmd to export apps +var ExpCmd = &cobra.Command{ + Use: "export", + Short: "Export AppGroups to a file", + Long: "Export AppGroups to a file", + Args: func(cmd *cobra.Command, args []string) (err error) { + return apiclient.SetApigeeOrg(org) + }, + RunE: func(cmd *cobra.Command, args []string) (err error) { + const exportFileName = "appgroups.json" + payload, err := appgroups.Export() + if err != nil { + return err + } + + prettyPayload, err := apiclient.PrettifyJSON(payload) + if err != nil { + return err + } + return apiclient.WriteByteArrayToFile(exportFileName, false, prettyPayload) + }, +} diff --git a/cmd/appgroups/expapps.go b/cmd/appgroups/expapps.go new file mode 100644 index 000000000..8b776c4b3 --- /dev/null +++ b/cmd/appgroups/expapps.go @@ -0,0 +1,75 @@ +// Copyright 2023 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 appgroups + +import ( + "fmt" + + "internal/apiclient" + + "internal/client/appgroups" + + "github.com/spf13/cobra" +) + +// ExpAppCmd to export apps +var ExpAppCmd = &cobra.Command{ + Use: "export", + Short: "Export Apps in an AppGroup to a file", + Long: "Export Apps in an AppGroup to a file", + Args: func(cmd *cobra.Command, args []string) (err error) { + if name == "" && !all { + return fmt.Errorf("either all must be set to true or a name must be passed") + } + return apiclient.SetApigeeOrg(org) + }, + RunE: func(cmd *cobra.Command, args []string) (err error) { + exportFileName := name + "_" + "apps.json" + + apiclient.DisableCmdPrintHttpResponse() + + if name != "" { + payload, err := appgroups.ExportApps(name) + if err != nil { + return err + } + prettyPayload, err := apiclient.PrettifyJSON(payload) + if err != nil { + return err + } + return apiclient.WriteByteArrayToFile(exportFileName, false, prettyPayload) + } + + appGroupsList, err := appgroups.Export() + if err != nil { + return err + } + + payload, err := appgroups.ExportAllApps(appGroupsList, 4) + if err != nil { + return err + } + return apiclient.WriteArrayByteArrayToFile(exportFileName, false, payload) + }, +} + +var all bool + +func init() { + ExpAppCmd.Flags().StringVarP(&name, "name", "n", + "", "Name of the AppGroup") + ExpAppCmd.Flags().BoolVarP(&all, "all", "", + false, "Export apps for all appgroups in the org") +} diff --git a/cmd/appgroups/getapp.go b/cmd/appgroups/getapp.go new file mode 100644 index 000000000..afa63d51b --- /dev/null +++ b/cmd/appgroups/getapp.go @@ -0,0 +1,48 @@ +// Copyright 2020 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 appgroups + +import ( + "internal/apiclient" + "internal/client/appgroups" + + "github.com/spf13/cobra" +) + +// GetAppCmd to get app +var GetAppCmd = &cobra.Command{ + Use: "get", + Short: "Get App in an AppGroup", + Long: "Get App in an AppGroup", + Args: func(cmd *cobra.Command, args []string) error { + return apiclient.SetApigeeOrg(org) + }, + RunE: func(cmd *cobra.Command, args []string) (err error) { + _, err = appgroups.GetApp(name, appName) + return err + }, +} + +var appName string + +func init() { + GetAppCmd.Flags().StringVarP(&name, "name", "n", + "", "Name of the AppGroup") + GetAppCmd.Flags().StringVarP(&appName, "app-name", "", + "", "Name of the app") + + _ = GetAppCmd.MarkFlagRequired("name") + _ = GetAppCmd.MarkFlagRequired("app-name") +} diff --git a/cmd/appgroups/getappgroup.go b/cmd/appgroups/getappgroup.go new file mode 100644 index 000000000..84ff1bcd1 --- /dev/null +++ b/cmd/appgroups/getappgroup.go @@ -0,0 +1,43 @@ +// Copyright 2023 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 appgroups + +import ( + "internal/apiclient" + "internal/client/appgroups" + + "github.com/spf13/cobra" +) + +// GetCmd to get appgroup +var GetCmd = &cobra.Command{ + Use: "get", + Short: "Get AppGroup in an Organization by AppGroup Name", + Long: "Returns the app group details for the specified app group name", + Args: func(cmd *cobra.Command, args []string) error { + return apiclient.SetApigeeOrg(org) + }, + RunE: func(cmd *cobra.Command, args []string) (err error) { + _, err = appgroups.Get(name) + return err + }, +} + +var name string + +func init() { + GetCmd.Flags().StringVarP(&name, "name", "n", + "", "Name of the AppGroup") +} diff --git a/cmd/appgroups/getkey.go b/cmd/appgroups/getkey.go new file mode 100644 index 000000000..852df14c2 --- /dev/null +++ b/cmd/appgroups/getkey.go @@ -0,0 +1,52 @@ +// Copyright 2023 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 appgroups + +import ( + "internal/apiclient" + + "internal/client/appgroups" + + "github.com/spf13/cobra" +) + +// GetKeyCmd to get credential +var GetKeyCmd = &cobra.Command{ + Use: "get", + Short: "Gets details for a consumer key for a AppGroup app", + Long: "Gets details for a consumer key for a AppGroup app", + Args: func(cmd *cobra.Command, args []string) (err error) { + return apiclient.SetApigeeOrg(org) + }, + RunE: func(cmd *cobra.Command, args []string) (err error) { + _, err = appgroups.GetKey(name, appName, key) + return + }, +} + +var key string + +func init() { + GetKeyCmd.Flags().StringVarP(&name, "name", "n", + "", "Name of the AppGroup") + GetKeyCmd.Flags().StringVarP(&appName, "app-name", "", + "", "Name of the app") + GetKeyCmd.Flags().StringVarP(&key, "key", "k", + "", "App consumer key") + + _ = GetKeyCmd.MarkFlagRequired("name") + _ = GetKeyCmd.MarkFlagRequired("app-name") + _ = GetKeyCmd.MarkFlagRequired("key") +} diff --git a/cmd/appgroups/impappgroup.go b/cmd/appgroups/impappgroup.go new file mode 100644 index 000000000..cdc1e4c91 --- /dev/null +++ b/cmd/appgroups/impappgroup.go @@ -0,0 +1,50 @@ +// Copyright 2020 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 appgroups + +import ( + "internal/apiclient" + + "internal/client/appgroups" + + "github.com/spf13/cobra" +) + +// ImpCmd to import apps +var ImpCmd = &cobra.Command{ + Use: "import", + Short: "Import a file containing AppGroups", + Long: "Import a file containing AppGroups", + Args: func(cmd *cobra.Command, args []string) (err error) { + return apiclient.SetApigeeOrg(org) + }, + RunE: func(cmd *cobra.Command, args []string) error { + return appgroups.Import(conn, filePath) + }, +} + +var ( + filePath string + conn int +) + +func init() { + ImpCmd.Flags().StringVarP(&filePath, "file", "f", + "", "File containing AppGroups") + ImpCmd.Flags().IntVarP(&conn, "conn", "c", + 4, "Number of connections") + + _ = ImpCmd.MarkFlagRequired("file") +} diff --git a/cmd/appgroups/impapps.go b/cmd/appgroups/impapps.go new file mode 100644 index 000000000..fda3c3435 --- /dev/null +++ b/cmd/appgroups/impapps.go @@ -0,0 +1,58 @@ +// Copyright 2020 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 appgroups + +import ( + "fmt" + + "internal/apiclient" + + "internal/client/appgroups" + + "github.com/spf13/cobra" +) + +// ImpAppCmd to import apps +var ImpAppCmd = &cobra.Command{ + Use: "import", + Short: "Import a file containing Apps to an AppGroup", + Long: "Import a file containing Apps to an AppGroup", + Args: func(cmd *cobra.Command, args []string) (err error) { + if name == "" && !all { + return fmt.Errorf("either all must be set to true or a name must be passed") + } + return apiclient.SetApigeeOrg(org) + }, + RunE: func(cmd *cobra.Command, args []string) error { + apiclient.DisableCmdPrintHttpResponse() + if name != "" { + return appgroups.ImportApps(conn, filePath, name) + } + return appgroups.ImportAllApps(conn, filePath) + }, +} + +func init() { + ImpAppCmd.Flags().StringVarP(&name, "name", "n", + "", "Name of the AppGroup") + ImpAppCmd.Flags().StringVarP(&filePath, "file", "f", + "", "File containing Apps") + ImpAppCmd.Flags().IntVarP(&conn, "conn", "c", + 4, "Number of connections") + ImpAppCmd.Flags().BoolVarP(&all, "all", "", + false, "Import Apps for all AppGroups in the org") + + _ = ImpAppCmd.MarkFlagRequired("file") +} diff --git a/cmd/appgroups/keys.go b/cmd/appgroups/keys.go new file mode 100644 index 000000000..6848c1800 --- /dev/null +++ b/cmd/appgroups/keys.go @@ -0,0 +1,35 @@ +// Copyright 2023 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 appgroups + +import ( + "github.com/spf13/cobra" +) + +// KeyCmd to manage keys in an app +var KeyCmd = &cobra.Command{ + Use: "keys", + Short: "Manage keys in an App within an AppGroup", + Long: "Manage keys in an App within an AppGroup", +} + +func init() { + KeyCmd.AddCommand(GetKeyCmd) + KeyCmd.AddCommand(DelKeyCmd) + KeyCmd.AddCommand(CreateKeyCmd) + KeyCmd.AddCommand(ManageKeyCmd) + KeyCmd.AddCommand(DelProdKeyCmd) + KeyCmd.AddCommand(UpdateKeyProdCmd) +} diff --git a/cmd/appgroups/listappgroups.go b/cmd/appgroups/listappgroups.go new file mode 100644 index 000000000..86f239203 --- /dev/null +++ b/cmd/appgroups/listappgroups.go @@ -0,0 +1,52 @@ +// Copyright 2023 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 appgroups + +import ( + "internal/apiclient" + + "internal/client/appgroups" + + "github.com/spf13/cobra" +) + +// ListCmd to list apps +var ListCmd = &cobra.Command{ + Use: "list", + Short: "Returns a list of AppGroups", + Long: "Returns a list of AppGroups", + Args: func(cmd *cobra.Command, args []string) (err error) { + return apiclient.SetApigeeOrg(org) + }, + RunE: func(cmd *cobra.Command, args []string) (err error) { + _, err = appgroups.List(pageSize, pageToken, filter) + return + }, +} + +var ( + filter string + pageToken string + pageSize int +) + +func init() { + ListCmd.Flags().IntVarP(&pageSize, "page-size", "s", + -1, "Number of appgroups; limit is 1000") + ListCmd.Flags().StringVarP(&pageToken, "page-token", "p", + "", "Page token") + ListCmd.Flags().StringVarP(&filter, "filter", "f", + "", "List filter") +} diff --git a/cmd/appgroups/listapps.go b/cmd/appgroups/listapps.go new file mode 100644 index 000000000..1cb3b81c5 --- /dev/null +++ b/cmd/appgroups/listapps.go @@ -0,0 +1,48 @@ +// Copyright 2023 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 appgroups + +import ( + "internal/apiclient" + + "internal/client/appgroups" + + "github.com/spf13/cobra" +) + +// ListAppCmd to list apps +var ListAppCmd = &cobra.Command{ + Use: "list", + Short: "Returns a list of Developer Applications", + Long: "Returns a list of app IDs within an organization based on app status", + Args: func(cmd *cobra.Command, args []string) (err error) { + return apiclient.SetApigeeOrg(org) + }, + RunE: func(cmd *cobra.Command, args []string) (err error) { + _, err = appgroups.ListApps(name, pageSize, pageToken) + return + }, +} + +func init() { + ListAppCmd.Flags().StringVarP(&name, "name", "n", + "", "Name of the AppGroup") + ListAppCmd.Flags().IntVarP(&pageSize, "page-size", "s", + -1, "Number of appgroups; limit is 1000") + ListAppCmd.Flags().StringVarP(&pageToken, "page-token", "p", + "", "Page token") + + _ = ListAppCmd.MarkFlagRequired("name") +} diff --git a/cmd/appgroups/manage.go b/cmd/appgroups/manage.go new file mode 100644 index 000000000..4779881a7 --- /dev/null +++ b/cmd/appgroups/manage.go @@ -0,0 +1,46 @@ +// Copyright 2020 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 appgroups + +import ( + "internal/apiclient" + "internal/client/appgroups" + + "github.com/spf13/cobra" +) + +// ManageCmd to appgroups +var ManageCmd = &cobra.Command{ + Use: "manage", + Short: "Approve or revoke an AppGroup", + Long: "Approve or revoke an AppGroup", + Args: func(cmd *cobra.Command, args []string) (err error) { + return apiclient.SetApigeeOrg(org) + }, + RunE: func(cmd *cobra.Command, args []string) (err error) { + _, err = appgroups.Manage(name, action) + return + }, +} + +func init() { + ManageCmd.Flags().StringVarP(&name, "name", "n", + "", "Name of the AppGroup") + ManageCmd.Flags().StringVarP(&action, "action", "x", + "active", "Action to perform - active or inactive") + + _ = ManageCmd.MarkFlagRequired("name") + _ = ManageCmd.MarkFlagRequired("action") +} diff --git a/cmd/appgroups/manageapp.go b/cmd/appgroups/manageapp.go new file mode 100644 index 000000000..5c015cced --- /dev/null +++ b/cmd/appgroups/manageapp.go @@ -0,0 +1,49 @@ +// Copyright 2020 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 appgroups + +import ( + "internal/apiclient" + "internal/client/appgroups" + + "github.com/spf13/cobra" +) + +// ManageAppCmd to create developer keys +var ManageAppCmd = &cobra.Command{ + Use: "manage", + Short: "Approve or revoke an app", + Long: "Approve or revoke an app", + Args: func(cmd *cobra.Command, args []string) (err error) { + return apiclient.SetApigeeOrg(org) + }, + RunE: func(cmd *cobra.Command, args []string) (err error) { + _, err = appgroups.ManageApp(name, appName, action) + return + }, +} + +func init() { + ManageAppCmd.Flags().StringVarP(&name, "name", "n", + "", "Name of the AppGroup") + ManageAppCmd.Flags().StringVarP(&appName, "app-name", "", + "", "Name of the app") + ManageAppCmd.Flags().StringVarP(&action, "action", "x", + "revoke", "Action to perform - revoke or approve") + + _ = ManageAppCmd.MarkFlagRequired("name") + _ = ManageAppCmd.MarkFlagRequired("app-name") + _ = ManageAppCmd.MarkFlagRequired("action") +} diff --git a/cmd/appgroups/managekey.go b/cmd/appgroups/managekey.go new file mode 100644 index 000000000..20ddc9407 --- /dev/null +++ b/cmd/appgroups/managekey.go @@ -0,0 +1,54 @@ +// Copyright 2020 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 appgroups + +import ( + "internal/apiclient" + "internal/client/appgroups" + + "github.com/spf13/cobra" +) + +// ManageKeyCmd to create developer keys +var ManageKeyCmd = &cobra.Command{ + Use: "manage", + Short: "Approve or revoke an app key", + Long: "Approve or revoke an app key", + Args: func(cmd *cobra.Command, args []string) (err error) { + return apiclient.SetApigeeOrg(org) + }, + RunE: func(cmd *cobra.Command, args []string) (err error) { + _, err = appgroups.ManageKey(name, appName, key, action) + return + }, +} + +var action string + +func init() { + ManageKeyCmd.Flags().StringVarP(&name, "name", "n", + "", "Name of the AppGroup") + ManageKeyCmd.Flags().StringVarP(&appName, "app-name", "", + "", "Name of the app") + ManageKeyCmd.Flags().StringVarP(&key, "key", "k", + "", "AppGroup app consumer key") + ManageKeyCmd.Flags().StringVarP(&action, "action", "x", + "revoke", "Action to perform - revoke or approve") + + _ = ManageKeyCmd.MarkFlagRequired("name") + _ = ManageKeyCmd.MarkFlagRequired("app-name") + _ = ManageKeyCmd.MarkFlagRequired("key") + _ = ManageKeyCmd.MarkFlagRequired("action") +} diff --git a/cmd/appgroups/updateapp.go b/cmd/appgroups/updateapp.go new file mode 100644 index 000000000..f393bf2e4 --- /dev/null +++ b/cmd/appgroups/updateapp.go @@ -0,0 +1,57 @@ +// Copyright 2020 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 appgroups + +import ( + "internal/apiclient" + + "internal/client/appgroups" + + "github.com/spf13/cobra" +) + +// UpdateAppCmd to create app +var UpdateAppCmd = &cobra.Command{ + Use: "update", + Short: "Update an App in an AppGroup", + Long: "Update an App in an AppGroup", + Args: func(cmd *cobra.Command, args []string) (err error) { + return apiclient.SetApigeeOrg(org) + }, + RunE: func(cmd *cobra.Command, args []string) (err error) { + _, err = appgroups.UpdateApp(name, appName, expires, callback, apiProducts, scopes, attrs) + return + }, +} + +func init() { + UpdateAppCmd.Flags().StringVarP(&name, "name", "n", + "", "Name of the AppGroup") + UpdateAppCmd.Flags().StringVarP(&appName, "app-name", "", + "", "Name of the app") + UpdateAppCmd.Flags().StringVarP(&expires, "expires", "x", + "", "A setting, in milliseconds, for the lifetime of the consumer key") + UpdateAppCmd.Flags().StringVarP(&callback, "callback", "c", + "", "The callbackUrl is used by OAuth") + UpdateAppCmd.Flags().StringArrayVarP(&apiProducts, "prods", "p", + []string{}, "A list of api products") + UpdateAppCmd.Flags().StringArrayVarP(&scopes, "scopes", "s", + []string{}, "OAuth scopes") + UpdateAppCmd.Flags().StringToStringVar(&attrs, "attrs", + nil, "Custom attributes") + + _ = UpdateAppCmd.MarkFlagRequired("name") + _ = UpdateAppCmd.MarkFlagRequired("app-name") +} diff --git a/cmd/appgroups/updateappgroup.go b/cmd/appgroups/updateappgroup.go new file mode 100644 index 000000000..fc98407a8 --- /dev/null +++ b/cmd/appgroups/updateappgroup.go @@ -0,0 +1,51 @@ +// Copyright 2023 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 appgroups + +import ( + "internal/apiclient" + "internal/client/appgroups" + + "github.com/spf13/cobra" +) + +// UpdateCmd to create appgroup +var UpdateCmd = &cobra.Command{ + Use: "update", + Short: "Update an AppGroup", + Long: "Update an AppGroup", + Args: func(cmd *cobra.Command, args []string) (err error) { + return apiclient.SetApigeeOrg(org) + }, + RunE: func(cmd *cobra.Command, args []string) (err error) { + _, err = appgroups.Update(name, channelURI, channelID, displayName, attrs) + return + }, +} + +func init() { + UpdateCmd.Flags().StringVarP(&name, "name", "n", + "", "Name of the AppGroup") + UpdateCmd.Flags().StringVarP(&channelID, "channelid", "i", + "", "channel identifier identifies the owner maintaining this grouping") + UpdateCmd.Flags().StringVarP(&channelURI, "channelurl", "u", + "", "A reference to the associated storefront/marketplace") + UpdateCmd.Flags().StringVarP(&displayName, "display-name", "d", + "", "AppGroup name displayed in the UI") + UpdateCmd.Flags().StringToStringVar(&attrs, "attrs", + nil, "Custom attributes") + + _ = UpdateCmd.MarkFlagRequired("name") +} diff --git a/cmd/appgroups/updateappkeyprods.go b/cmd/appgroups/updateappkeyprods.go new file mode 100644 index 000000000..484594f21 --- /dev/null +++ b/cmd/appgroups/updateappkeyprods.go @@ -0,0 +1,52 @@ +// Copyright 2020 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 appgroups + +import ( + "internal/apiclient" + "internal/client/appgroups" + + "github.com/spf13/cobra" +) + +// UpdateKeyProdCmd to create developer keys +var UpdateKeyProdCmd = &cobra.Command{ + Use: "update-prod", + Short: "Update products in an app key contained in an AppGroup", + Long: "Update products in an app key contained in an AppGroup", + Args: func(cmd *cobra.Command, args []string) (err error) { + return apiclient.SetApigeeOrg(org) + }, + RunE: func(cmd *cobra.Command, args []string) (err error) { + _, err = appgroups.UpdateKeyProducts(name, appName, key, apiProducts) + return + }, +} + +func init() { + UpdateKeyProdCmd.Flags().StringVarP(&name, "name", "n", + "", "Name of the AppGroup") + UpdateKeyProdCmd.Flags().StringVarP(&appName, "app-name", "", + "", "Name of the app") + UpdateKeyProdCmd.Flags().StringVarP(&key, "key", "k", + "", "AppGroup app consumer key") + UpdateKeyProdCmd.Flags().StringArrayVarP(&apiProducts, "prods", "p", + []string{}, "A list of api products") + + _ = UpdateKeyProdCmd.MarkFlagRequired("name") + _ = UpdateKeyProdCmd.MarkFlagRequired("app-name") + _ = UpdateKeyProdCmd.MarkFlagRequired("key") + _ = UpdateKeyProdCmd.MarkFlagRequired("prods") +} diff --git a/cmd/apps/expapp.go b/cmd/apps/expapp.go index aafada22a..0f3df8d8a 100644 --- a/cmd/apps/expapp.go +++ b/cmd/apps/expapp.go @@ -25,8 +25,8 @@ import ( // ExpCmd to export apps var ExpCmd = &cobra.Command{ Use: "export", - Short: "Export API products to a file", - Long: "Export API products to a file", + Short: "Export Developer Apps to a file", + Long: "Export Developer Apps to a file", Args: func(cmd *cobra.Command, args []string) (err error) { return apiclient.SetApigeeOrg(org) }, diff --git a/cmd/org/export.go b/cmd/org/export.go index 7c6ef3f4e..c9806d4da 100644 --- a/cmd/org/export.go +++ b/cmd/org/export.go @@ -27,6 +27,7 @@ import ( "internal/clilog" "internal/client/apis" + "internal/client/appgroups" "internal/client/apps" "internal/client/datacollectors" "internal/client/developers" @@ -53,8 +54,8 @@ var ExportCmd = &cobra.Command{ return apiclient.SetApigeeOrg(org) }, RunE: func(cmd *cobra.Command, args []string) (err error) { - var productResponse, appsResponse, targetServerResponse, referencesResponse [][]byte - var respBody, listKVMBytes []byte + var productResponse, appsResponse, targetServerResponse, referencesResponse, appGroupAppsResponse [][]byte + var respBody, listKVMBytes, appGroupsRespBody []byte apiclient.DisableCmdPrintHttpResponse() @@ -116,6 +117,16 @@ var ExportCmd = &cobra.Command{ return err } + clilog.Info.Println("Exporting AppGroups...") + if appGroupsRespBody, err = appgroups.Export(); proceedOnError(err) != nil { + return err + } + if err = apiclient.WriteByteArrayToFile( + appGroupsFileName, + false, appGroupsRespBody); proceedOnError(err) != nil { + return err + } + clilog.Info.Println("Exporting Developer Apps...") if appsResponse, err = apps.Export(conn); proceedOnError(err) != nil { return err @@ -126,6 +137,16 @@ var ExportCmd = &cobra.Command{ return err } + clilog.Info.Println("Exporting AppGroups Apps...") + if appGroupAppsResponse, err = appgroups.ExportAllApps(appGroupsRespBody, conn); proceedOnError(err) != nil { + return err + } + if err = apiclient.WriteArrayByteArrayToFile( + appsFileName, + false, appGroupAppsResponse); proceedOnError(err) != nil { + return err + } + clilog.Info.Println("Exporting Environment Group Configuration...") if respBody, err = envgroups.List(); proceedOnError(err) != nil { return err diff --git a/cmd/org/variables.go b/cmd/org/variables.go index c96ebae1e..dd377ec6c 100644 --- a/cmd/org/variables.go +++ b/cmd/org/variables.go @@ -15,18 +15,20 @@ package org const ( - productsFileName = "products.json" - developersFileName = "developers.json" - appsFileName = "apps.json" - targetServerFileName = "targetservers.json" - envGroupsFileName = "envgroups.json" - dataCollFileName = "datacollectors.json" - kvmFileName = "kvms.json" - keyStoresFileName = "keystores.json" - syncAuthFileName = "syncauth.json" - debugmaskFileName = "_debugmask.json" - tracecfgFileName = "_tracecfg.json" - referencesFileName = "references.json" + productsFileName = "products.json" + developersFileName = "developers.json" + appsFileName = "apps.json" + appGroupsFileName = "appgroups.json" + appGroupsAppsFileName = "appgroupsapp.json" + targetServerFileName = "targetservers.json" + envGroupsFileName = "envgroups.json" + dataCollFileName = "datacollectors.json" + kvmFileName = "kvms.json" + keyStoresFileName = "keystores.json" + syncAuthFileName = "syncauth.json" + debugmaskFileName = "_debugmask.json" + tracecfgFileName = "_tracecfg.json" + referencesFileName = "references.json" proxiesFolderName = "proxies" sharedFlowsFolderName = "sharedflows" diff --git a/cmd/root.go b/cmd/root.go index b55876366..384f8cabd 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -28,6 +28,7 @@ import ( "internal/clilog" "github.com/apigee/apigeecli/cmd/apis" + "github.com/apigee/apigeecli/cmd/appgroups" "github.com/apigee/apigeecli/cmd/apps" cache "github.com/apigee/apigeecli/cmd/cache" "github.com/apigee/apigeecli/cmd/datacollectors" @@ -142,6 +143,7 @@ func init() { RootCmd.AddCommand(preferences.Cmd) RootCmd.AddCommand(overrides.Cmd) RootCmd.AddCommand(eptattachment.Cmd) + RootCmd.AddCommand(appgroups.Cmd) } func initConfig() { diff --git a/internal/apiclient/httpclient.go b/internal/apiclient/httpclient.go index 4cb4ddfa4..05b48676c 100644 --- a/internal/apiclient/httpclient.go +++ b/internal/apiclient/httpclient.go @@ -326,7 +326,7 @@ func HttpClient(params ...string) (respBody []byte, err error) { func PrettyPrint(contentType string, body []byte) error { if GetCmdPrintHttpResponseSetting() && ClientPrintHttpResponse.Get() { var prettyJSON bytes.Buffer - //pretty print only json responses with body + // pretty print only json responses with body if strings.Contains(contentType, "json") && len(body) > 0 { err := json.Indent(&prettyJSON, body, "", "\t") if err != nil { @@ -340,6 +340,17 @@ func PrettyPrint(contentType string, body []byte) error { return nil } +// PrettifyJSON +func PrettifyJSON(body []byte) ([]byte, error) { + var prettyJSON bytes.Buffer + err := json.Indent(&prettyJSON, body, "", "\t") + if err != nil { + clilog.Error.Println("error parsing response: ", err) + return nil, err + } + return prettyJSON.Bytes(), nil +} + // Do the HTTP request func (c *RateLimitedHTTPClient) Do(req *http.Request) (*http.Response, error) { ctx := context.Background() diff --git a/internal/client/apis/apis.go b/internal/client/apis/apis.go index 2e3c7adce..9c83f3a00 100644 --- a/internal/client/apis/apis.go +++ b/internal/client/apis/apis.go @@ -65,8 +65,7 @@ type violation struct { Description string `json:"description,omitempty"` } -type routingchanges struct { -} +type routingchanges struct{} type routingconflicts struct { EnvironmentGroup string `json:"environmentGroup,omitempty"` @@ -112,7 +111,6 @@ func DeleteProxyRevision(name string, revision int) (respBody []byte, err error) // DeployProxy func DeployProxy(name string, revision int, overrides bool, sequencedRollout bool, safeDeploy bool, serviceAccountName string) (respBody []byte, err error) { - if safeDeploy { var safeResp []byte d := deploychangereport{} diff --git a/internal/client/appgroups/app.go b/internal/client/appgroups/app.go new file mode 100644 index 000000000..2b6ca8024 --- /dev/null +++ b/internal/client/appgroups/app.go @@ -0,0 +1,541 @@ +// Copyright 2023 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 appgroups + +import ( + "encoding/json" + "errors" + "fmt" + "io" + "net/url" + "os" + "path" + "strconv" + "strings" + "sync" + + "internal/apiclient" + "internal/clilog" +) + +type appgroupapps struct { + AppGroupApps []appgroupapp `json:"appGroupApps,omitempty"` + NextPageToken string `json:"nextPageToken,omitempty"` +} + +type appgroupapp struct { + AppId string `json:"appId,omitempty"` + Name string `json:"name,omitempty"` + Status string `json:"status,omitempty"` + AppGroup string `json:"appGroup,omitempty"` + CreatedAt string `json:"createdAt,omitempty"` + LastModifiedAt string `json:"lastModifiedAt,omitempty"` + Credentials []credential `json:"credentials,omitempty"` +} + +type credential struct { + APIProducts []apiProduct `json:"apiProducts,omitempty"` + ConsumerKey string `json:"consumerKey,omitempty"` + ConsumerSecret string `json:"consumerSecret,omitempty"` + ExpiresAt string `json:"expiresAt,omitempty"` + Status string `json:"status,omitempty"` + Scopes []string `json:"scopes,omitempty"` +} + +type apiProduct struct { + ApiProduct string `json:"apiproduct,omitempty"` + Status string `json:"status,omitempty"` +} + +type Action uint8 + +const ( + CREATE Action = iota + UPDATE +) + +// CreateApp +func CreateApp(name string, appName string, expires string, callback string, apiProducts []string, scopes []string, attrs map[string]string) (respBody []byte, err error) { + return createOrUpdate(name, appName, expires, callback, apiProducts, scopes, attrs, CREATE) +} + +// createAppNoKey +func createAppNoKey(name string, appName string, expires string, callback string, apiProducts []string, scopes []string, attrs map[string]string) (err error) { + respBody, err := CreateApp(name, appName, expires, callback, apiProducts, scopes, attrs) + if err != nil { + return err + } + + a := appgroupapp{} + err = json.Unmarshal(respBody, &a) + if err != nil { + return err + } + + _, err = DeleteKey(name, appName, a.Credentials[0].ConsumerKey) + return err +} + +// DeleteApp +func DeleteApp(name string, appName string) (respBody []byte, err error) { + u, _ := url.Parse(apiclient.BaseURL) + u.Path = path.Join(u.Path, apiclient.GetApigeeOrg(), "appgroups", name, "apps", appName) + respBody, err = apiclient.HttpClient(u.String(), "", "DELETE") + return respBody, err +} + +// GetApp +func GetApp(name string, appName string) (respBody []byte, err error) { + u, _ := url.Parse(apiclient.BaseURL) + u.Path = path.Join(u.Path, apiclient.GetApigeeOrg(), "appgroups", name, "apps", appName) + respBody, err = apiclient.HttpClient(u.String()) + return respBody, err +} + +// ListApps +func ListApps(name string, pageSize int, pageToken string) (respBody []byte, err error) { + u, _ := url.Parse(apiclient.BaseURL) + u.Path = path.Join(u.Path, apiclient.GetApigeeOrg(), "appgroups", name, "apps") + q := u.Query() + if pageSize != -1 { + q.Set("pageSize", strconv.Itoa(pageSize)) + } + if pageToken != "" { + q.Set("pageToken", pageToken) + } + + u.RawQuery = q.Encode() + respBody, err = apiclient.HttpClient(u.String()) + return respBody, err +} + +// UpdateApp +func UpdateApp(name string, appName string, expires string, callback string, apiProducts []string, scopes []string, attrs map[string]string) (respBody []byte, err error) { + return createOrUpdate(name, appName, expires, callback, apiProducts, scopes, attrs, UPDATE) +} + +func createOrUpdate(name string, appName string, expires string, callback string, apiProducts []string, scopes []string, attrs map[string]string, action Action) (respBody []byte, err error) { + u, _ := url.Parse(apiclient.BaseURL) + app := []string{} + + app = append(app, "\"name\":\""+appName+"\"") + + if len(apiProducts) > 0 { + app = append(app, "\"apiProducts\":[\""+getArrayStr(apiProducts)+"\"]") + } + + if callback != "" { + app = append(app, "\"callbackUrl\":\""+callback+"\"") + } + + if expires != "" { + app = append(app, "\"keyExpiresIn\":\""+expires+"\"") + } + + if len(scopes) > 0 { + app = append(app, "\"scopes\":[\""+getArrayStr(scopes)+"\"]") + } + + if len(attrs) != 0 { + attributes := []string{} + for key, value := range attrs { + attributes = append(attributes, "{\"name\":\""+key+"\",\"value\":\""+value+"\"}") + } + attributesStr := "\"attributes\":[" + strings.Join(attributes, ",") + "]" + app = append(app, attributesStr) + } + + payload := "{" + strings.Join(app, ",") + "}" + + if action == CREATE { + u.Path = path.Join(u.Path, apiclient.GetApigeeOrg(), "appgroups", name, "apps") + respBody, err = apiclient.HttpClient(u.String(), payload) + return respBody, err + } + u.Path = path.Join(u.Path, apiclient.GetApigeeOrg(), "appgroups", name, "apps", appName) + respBody, err = apiclient.HttpClient(u.String(), payload, "PUT") + return respBody, err +} + +// ManageApp +func ManageApp(name string, appName string, action string) (respBody []byte, err error) { + if action != "revoke" && action != "approve" { + return nil, fmt.Errorf("invalid action. action must be revoke or approve") + } + + u, _ := url.Parse(apiclient.BaseURL) + u.Path = path.Join(u.Path, apiclient.GetApigeeOrg(), "appgroups", name, "apps", appName) + q := u.Query() + q.Set("action", action) + u.RawQuery = q.Encode() + + respBody, err = apiclient.HttpClient(u.String(), "", "PUT", "application/octet-stream") + return respBody, err +} + +// ExportApps +func ExportApps(name string) (respBody []byte, err error) { + // don't print to sysout + apiclient.ClientPrintHttpResponse.Set(false) + defer apiclient.ClientPrintHttpResponse.Set(apiclient.GetCmdPrintHttpResponseSetting()) + + pageToken := "" + applist := appgroupapps{} + + for { + a := appgroupapps{} + listRespBytes, err := ListApps(name, maxPageSize, pageToken) + if err != nil { + return nil, err + } + err = json.Unmarshal(listRespBytes, &a) + if err != nil { + return nil, fmt.Errorf("failed to unmarshall: %w", err) + } + applist.AppGroupApps = append(applist.AppGroupApps, a.AppGroupApps...) + pageToken = a.NextPageToken + if a.NextPageToken == "" { + break + } + } + + respBody, err = json.Marshal(applist.AppGroupApps) + + return respBody, err +} + +// ImportApps +func ImportApps(conn int, filePath string, name string) (err error) { + appgrpapps, err := readAppsFile(filePath) + if err != nil { + clilog.Error.Println("Error reading file: ", err) + return err + } + + clilog.Debug.Printf("Found %d apps in the file\n", len(appgrpapps)) + clilog.Debug.Printf("Create apps with %d connections\n", conn) + + knownAppGroupAppsBytes, err := ExportApps(name) + if err != nil { + return err + } + + var knownAppGroupApps []appgroupapp + if err = json.Unmarshal(knownAppGroupAppsBytes, &knownAppGroupApps); err != nil { + return err + } + + knownAppGroupAppsList := map[string]bool{} + for _, name := range knownAppGroupApps { + knownAppGroupAppsList[name.Name] = true + } + + jobChan := make(chan appgroupapp) + errChan := make(chan error) + + fanOutWg := sync.WaitGroup{} + fanInWg := sync.WaitGroup{} + + errs := []string{} + fanInWg.Add(1) + go func() { + defer fanInWg.Done() + for { + newErr, ok := <-errChan + if !ok { + return + } + errs = append(errs, newErr.Error()) + } + }() + + for i := 0; i < conn; i++ { + fanOutWg.Add(1) + go importAppGroupApp(knownAppGroupAppsList, &fanOutWg, jobChan, name, errChan) + } + + for _, aga := range appgrpapps { + jobChan <- aga + } + + close(jobChan) + fanOutWg.Wait() + close(errChan) + fanInWg.Wait() + + if len(errs) > 0 { + return errors.New(strings.Join(errs, "\n")) + } + + return nil +} + +func importAppGroupApp(knownAppGroupsList map[string]bool, wg *sync.WaitGroup, jobs <-chan appgroupapp, name string, errs chan<- error) { + defer wg.Done() + var err error + + for { + job, ok := <-jobs + if !ok { + return + } + + if knownAppGroupsList[job.Name] { + // the appgroup already exists, perform an update + clilog.Warning.Printf("App %s in AppGroup %s already exists. Updates to app is not currently supported", job.Name, name) + } else { + // 1. Create the app + err = createAppNoKey(name, job.Name, "-1", "", nil, nil, nil) + if err != nil { + errs <- err + continue + } + // 2. Create app keys + for _, c := range job.Credentials { + _, err = CreateKey(name, job.Name, c.ConsumerKey, c.ConsumerSecret, c.ExpiresAt, getAPIProductsAsArray(c.APIProducts), c.Scopes, nil) + if err != nil { + errs <- err + } + } + } + + clilog.Debug.Printf("Completed app %s import to appgroup %s", job.Name, name) + } +} + +// ExportAllApps +func ExportAllApps(appGroupListBytes []byte, conn int) (results [][]byte, err error) { + appGroupsList := []appgroup{} + + err = json.Unmarshal(appGroupListBytes, &appGroupsList) + if err != nil { + return nil, fmt.Errorf("failed to unmarshall: %w", err) + } + + clilog.Debug.Printf("Found %d appgroups in the org\n", len(appGroupsList)) + clilog.Debug.Printf("Exporting appgroups with %d parallel connections\n", conn) + + jobChan := make(chan appgroup) + resultChan := make(chan []byte) + errChan := make(chan error) + + fanOutWg := sync.WaitGroup{} + fanInWg := sync.WaitGroup{} + + errs := []string{} + fanInWg.Add(1) + + go func() { + defer fanInWg.Done() + for { + newErr, ok := <-errChan + if !ok { + return + } + errs = append(errs, newErr.Error()) + } + }() + + fanInWg.Add(1) + go func() { + defer fanInWg.Done() + for { + newResult, ok := <-resultChan + if !ok { + return + } + results = append(results, newResult) + } + }() + + for i := 0; i < conn; i++ { + fanOutWg.Add(1) + go exportApp(&fanOutWg, jobChan, resultChan, errChan) + } + + for _, a := range appGroupsList { + jobChan <- a + } + close(jobChan) + fanOutWg.Wait() + close(errChan) + close(resultChan) + fanInWg.Wait() + + if len(errs) > 0 { + return nil, errors.New(strings.Join(errs, "\n")) + } + return results, nil +} + +// ImportAllApps +func ImportAllApps(conn int, filePath string) (err error) { + appgrpapps, err := readAppsArrayFile(filePath) + if err != nil { + clilog.Error.Println("Error reading file: ", err) + return err + } + + clilog.Info.Printf("Found %d apps in the file\n", len(appgrpapps)) + clilog.Info.Printf("Create apps with %d connections\n", conn) + + // flatten the structure + a := []appgroupapp{} + for _, aga := range appgrpapps { + a = append(a, aga[0]) + } + + jobChan := make(chan appgroupapp) + errChan := make(chan error) + + fanOutWg := sync.WaitGroup{} + fanInWg := sync.WaitGroup{} + + errs := []string{} + fanInWg.Add(1) + go func() { + defer fanInWg.Done() + for { + newErr, ok := <-errChan + if !ok { + return + } + errs = append(errs, newErr.Error()) + } + }() + + for i := 0; i < conn; i++ { + fanOutWg.Add(1) + go importAllAppGroupApps(&fanOutWg, jobChan, errChan) + } + + for _, aga := range a { + jobChan <- aga + } + + close(jobChan) + fanOutWg.Wait() + close(errChan) + fanInWg.Wait() + + if len(errs) > 0 { + return errors.New(strings.Join(errs, "\n")) + } + + return nil +} + +func importAllAppGroupApps(wg *sync.WaitGroup, jobs <-chan appgroupapp, errs chan<- error) { + defer wg.Done() + var err error + + for { + job, ok := <-jobs + if !ok { + return + } + // 1. Create the app + err = createAppNoKey(job.AppGroup, job.Name, "-1", "", nil, nil, nil) + if err != nil { + errs <- err + continue + } + // 2. Create app keys + for _, c := range job.Credentials { + _, err = CreateKey(job.AppGroup, job.Name, c.ConsumerKey, c.ConsumerSecret, c.ExpiresAt, getAPIProductsAsArray(c.APIProducts), c.Scopes, nil) + if err != nil { + errs <- err + } + } + clilog.Debug.Printf("Completed app %s import to appgroup %s", job.Name, job.AppGroup) + } +} + +func exportApp(wg *sync.WaitGroup, jobs <-chan appgroup, results chan<- []byte, errs chan<- error) { + defer wg.Done() + for { + job, ok := <-jobs + if !ok { + return + } + respBody, err := ExportApps(job.Name) + if err != nil { + errs <- err + } else { + results <- respBody + clilog.Debug.Printf("Completed Exporting app %s in appgroup %s\n", job.Name, job.AppGroupId) + } + } +} + +func getArrayStr(str []string) string { + tmp := strings.Join(str, ",") + tmp = strings.ReplaceAll(tmp, ",", "\",\"") + return tmp +} + +func readAppsFile(filePath string) ([]appgroupapp, error) { + a := []appgroupapp{} + + jsonFile, err := os.Open(filePath) + if err != nil { + return a, err + } + + defer jsonFile.Close() + + byteValue, err := io.ReadAll(jsonFile) + if err != nil { + return a, err + } + + err = json.Unmarshal(byteValue, &a) + if err != nil { + return a, err + } + + return a, nil +} + +func getAPIProductsAsArray(prods []apiProduct) []string { + var products []string + for _, p := range prods { + products = append(products, p.ApiProduct) + } + return products +} + +func readAppsArrayFile(filePath string) ([][]appgroupapp, error) { + a := [][]appgroupapp{} + + jsonFile, err := os.Open(filePath) + if err != nil { + return a, err + } + + defer jsonFile.Close() + + byteValue, err := io.ReadAll(jsonFile) + if err != nil { + return a, err + } + + err = json.Unmarshal(byteValue, &a) + if err != nil { + return a, err + } + + return a, nil +} diff --git a/internal/client/appgroups/appgroups.go b/internal/client/appgroups/appgroups.go new file mode 100644 index 000000000..3b99de703 --- /dev/null +++ b/internal/client/appgroups/appgroups.go @@ -0,0 +1,327 @@ +// Copyright 2023 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 appgroups + +import ( + "encoding/json" + "errors" + "fmt" + "io" + "net/url" + "os" + "path" + "strconv" + "strings" + "sync" + + "internal/apiclient" + "internal/clilog" +) + +type appgroups struct { + AppGroups []appgroup `json:"appGroups,omitempty"` + NextPageToken string `json:"nextPageToken,omitempty"` +} + +type appgroup struct { + Name string `json:"name,omitempty"` + Status *string `json:"status,omitempty"` + AppGroupId string `json:"appGroupId,omitempty"` + ChannelURI string `json:"channelUri,omitempty"` + ChannelID string `json:"channelId,omitempty"` + Attributes []attribute `json:"attributes,omitempty"` + DisplayName string `json:"displayName,omitempty"` +} + +// attribute to used to hold custom attributes for entities +type attribute struct { + Name string `json:"name,omitempty"` + Value string `json:"value,omitempty"` +} + +var maxPageSize = 1000 + +// Create +func Create(name string, channelURI string, channelID string, displayName string, attrs map[string]string) (respBody []byte, err error) { + u, _ := url.Parse(apiclient.BaseURL) + + app := []string{} + + app = append(app, "\"name\":\""+name+"\"") + if channelURI != "" { + app = append(app, "\"channelUri\":\""+channelURI+"\"") + } + + if channelID != "" { + app = append(app, "\"channelId\":\""+channelID+"\"") + } + + if displayName != "" { + app = append(app, "\"displayName\":\""+displayName+"\"") + } + + if len(attrs) != 0 { + attributes := []string{} + for key, value := range attrs { + attributes = append(attributes, "{\"name\":\""+key+"\",\"value\":\""+value+"\"}") + } + attributesStr := "\"attributes\":[" + strings.Join(attributes, ",") + "]" + app = append(app, attributesStr) + } + + payload := "{" + strings.Join(app, ",") + "}" + + u.Path = path.Join(u.Path, apiclient.GetApigeeOrg(), "appgroups") + respBody, err = apiclient.HttpClient(u.String(), payload) + return respBody, err +} + +// Update +func Update(name string, channelURI string, channelID string, displayName string, attrs map[string]string) (respBody []byte, err error) { + apiclient.ClientPrintHttpResponse.Set(false) + appGroupRespBody, err := Get(name) + if err != nil { + return nil, err + } + apiclient.ClientPrintHttpResponse.Set(apiclient.GetCmdPrintHttpResponseSetting()) + + a := appgroup{} + if err = json.Unmarshal(appGroupRespBody, &a); err != nil { + return nil, err + } + + if channelURI != "" { + a.ChannelURI = channelURI + } + + if channelID != "" { + a.ChannelID = channelID + } + + if displayName != "" { + a.DisplayName = displayName + } + + if len(attrs) != 0 { + // TODO: attributes + } + + reqBody, err := json.Marshal(a) + if err != nil { + return nil, err + } + + u, _ := url.Parse(apiclient.BaseURL) + u.Path = path.Join(u.Path, apiclient.GetApigeeOrg(), "appgroups", name) + respBody, err = apiclient.HttpClient(u.String(), string(reqBody), "PUT") + return respBody, err +} + +// Get +func Get(name string) (respBody []byte, err error) { + u, _ := url.Parse(apiclient.BaseURL) + u.Path = path.Join(u.Path, apiclient.GetApigeeOrg(), "appgroups", name) + respBody, err = apiclient.HttpClient(u.String()) + return respBody, err +} + +// Delete +func Delete(name string) (respBody []byte, err error) { + u, _ := url.Parse(apiclient.BaseURL) + u.Path = path.Join(u.Path, apiclient.GetApigeeOrg(), "appgroups", name) + respBody, err = apiclient.HttpClient(u.String(), "", "DELETE") + return respBody, err +} + +// Manage +func Manage(name string, action string) (respBody []byte, err error) { + if action != "active" && action != "inactive" { + return nil, fmt.Errorf("invalid action. action must be active or inactive") + } + + u, _ := url.Parse(apiclient.BaseURL) + u.Path = path.Join(u.Path, apiclient.GetApigeeOrg(), "appgroups", name) + q := u.Query() + q.Set("action", action) + u.RawQuery = q.Encode() + + respBody, err = apiclient.HttpClient(u.String(), "", "PUT", "application/octet-stream") + return respBody, err +} + +// List +func List(pageSize int, pageToken string, filter string) (respBody []byte, err error) { + u, _ := url.Parse(apiclient.BaseURL) + u.Path = path.Join(u.Path, apiclient.GetApigeeOrg(), "appgroups") + q := u.Query() + if pageSize != -1 { + q.Set("pageSize", strconv.Itoa(pageSize)) + } + if pageToken != "" { + q.Set("pageToken", pageToken) + } + if filter != "" { + q.Set("filter", filter) + } + u.RawQuery = q.Encode() + respBody, err = apiclient.HttpClient(u.String()) + return respBody, err +} + +// Export +func Export() (respBody []byte, err error) { + // don't print to sysout + apiclient.ClientPrintHttpResponse.Set(false) + defer apiclient.ClientPrintHttpResponse.Set(apiclient.GetCmdPrintHttpResponseSetting()) + + pageToken := "" + applist := appgroups{} + + for { + a := appgroups{} + listRespBytes, err := List(maxPageSize, pageToken, "") + if err != nil { + return nil, err + } + err = json.Unmarshal(listRespBytes, &a) + if err != nil { + return nil, fmt.Errorf("failed to unmarshall: %w", err) + } + applist.AppGroups = append(applist.AppGroups, a.AppGroups...) + pageToken = a.NextPageToken + if a.NextPageToken == "" { + break + } + } + + respBody, err = json.Marshal(applist.AppGroups) + + return respBody, err +} + +// Import +func Import(conn int, filePath string) error { + appgrps, err := readAppGroupsFile(filePath) + if err != nil { + clilog.Error.Println("Error reading file: ", err) + return err + } + + clilog.Debug.Printf("Found %d AppGroups in the file\n", len(appgrps)) + clilog.Debug.Printf("Create AppGroups with %d connections\n", conn) + + knownAppGroupsBytes, err := Export() + if err != nil { + return err + } + + var knownAppGroups []appgroup + if err = json.Unmarshal(knownAppGroupsBytes, &knownAppGroups); err != nil { + return err + } + + knownAppGroupsList := map[string]bool{} + for _, name := range knownAppGroups { + knownAppGroupsList[name.Name] = true + } + + jobChan := make(chan appgroup) + errChan := make(chan error) + + fanOutWg := sync.WaitGroup{} + fanInWg := sync.WaitGroup{} + + errs := []string{} + fanInWg.Add(1) + go func() { + defer fanInWg.Done() + for { + newErr, ok := <-errChan + if !ok { + return + } + errs = append(errs, newErr.Error()) + } + }() + + for i := 0; i < conn; i++ { + fanOutWg.Add(1) + go importAppGroup(knownAppGroupsList, &fanOutWg, jobChan, errChan) + } + + for _, ag := range appgrps { + jobChan <- ag + } + + close(jobChan) + fanOutWg.Wait() + close(errChan) + fanInWg.Wait() + + if len(errs) > 0 { + return errors.New(strings.Join(errs, "\n")) + } + + return nil +} + +func importAppGroup(knownAppGroupsList map[string]bool, wg *sync.WaitGroup, jobs <-chan appgroup, errs chan<- error) { + defer wg.Done() + var err error + + for { + job, ok := <-jobs + if !ok { + return + } + + if knownAppGroupsList[job.Name] { + // the appgroup already exists, perform an update + _, err = Update(job.Name, job.ChannelURI, job.ChannelID, job.DisplayName, nil) + } else { + _, err = Create(job.Name, job.ChannelURI, job.ChannelID, job.DisplayName, nil) + } + + if err != nil { + errs <- err + continue + } + + clilog.Debug.Printf("Completed appgroup: %s", job.Name) + } +} + +func readAppGroupsFile(filePath string) ([]appgroup, error) { + a := []appgroup{} + + jsonFile, err := os.Open(filePath) + if err != nil { + return a, err + } + + defer jsonFile.Close() + + byteValue, err := io.ReadAll(jsonFile) + if err != nil { + return a, err + } + + err = json.Unmarshal(byteValue, &a) + if err != nil { + return a, err + } + + return a, nil +} diff --git a/internal/client/appgroups/keys.go b/internal/client/appgroups/keys.go new file mode 100644 index 000000000..cfd2004ed --- /dev/null +++ b/internal/client/appgroups/keys.go @@ -0,0 +1,124 @@ +// Copyright 2023 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 appgroups + +import ( + "fmt" + "net/url" + "path" + "strings" + + "internal/apiclient" +) + +// CreateKey +func CreateKey(name string, appName string, consumerKey string, consumerSecret string, expiresInSeconds string, apiProducts []string, scopes []string, attrs map[string]string) (respBody []byte, err error) { + u, _ := url.Parse(apiclient.BaseURL) + + key := []string{} + + key = append(key, "\"scopes\":[\""+getArrayStr(scopes)+"\"]") + + if len(attrs) != 0 { + attributes := []string{} + for keyattr, value := range attrs { + attributes = append(attributes, "{\"name\":\""+keyattr+"\",\"value\":\""+value+"\"}") + } + attributesStr := "\"attributes\":[" + strings.Join(attributes, ",") + "]" + key = append(key, attributesStr) + } + + if consumerKey != "" { + key = append(key, "\"consumerKey\":\""+consumerKey+"\"") + key = append(key, "\"consumerSecret\":\""+consumerSecret+"\"") + } + + payload := "{" + strings.Join(key, ",") + "}" + + u.Path = path.Join(u.Path, apiclient.GetApigeeOrg(), "appgroups", name, "apps", appName, "keys") + + if len(apiProducts) > 0 { + apiclient.ClientPrintHttpResponse.Set(false) + } + respBody, err = apiclient.HttpClient(u.String(), payload) + + if err != nil { + return respBody, err + } + + // since the API does not support adding products when creating a key, use a second API call to add products + if len(apiProducts) > 0 { + apiclient.ClientPrintHttpResponse.Set(false) + respBody, err = UpdateKeyProducts(name, appName, consumerKey, apiProducts) + apiclient.ClientPrintHttpResponse.Set(apiclient.GetCmdPrintHttpResponseSetting()) + } + + return respBody, err +} + +// GetKey +func GetKey(name string, appName string, key string) (respBody []byte, err error) { + u, _ := url.Parse(apiclient.BaseURL) + u.Path = path.Join(u.Path, apiclient.GetApigeeOrg(), "appgroups", name, "apps", appName, "keys", key) + respBody, err = apiclient.HttpClient(u.String()) + return respBody, err +} + +// DeleteKey +func DeleteKey(name string, appName string, key string) (respBody []byte, err error) { + u, _ := url.Parse(apiclient.BaseURL) + u.Path = path.Join(u.Path, apiclient.GetApigeeOrg(), "appgroups", name, "apps", appName, "keys", key) + respBody, err = apiclient.HttpClient(u.String(), "", "DELETE") + return respBody, err +} + +// ManageKey +func ManageKey(name string, appName string, consumerKey string, action string) (respBody []byte, err error) { + if action != "revoke" && action != "approve" { + return nil, fmt.Errorf("invalid action. action must be revoke or approve") + } + + u, _ := url.Parse(apiclient.BaseURL) + u.Path = path.Join(u.Path, apiclient.GetApigeeOrg(), "appgroups", name, "apps", appName, "keys", consumerKey) + q := u.Query() + q.Set("action", action) + u.RawQuery = q.Encode() + respBody, err = apiclient.HttpClient(u.String(), "", "POST", "application/octet-stream") + + return respBody, err +} + +// UpdateKeyProducts +func UpdateKeyProducts(name string, appName string, consumerKey string, apiProducts []string) (respBody []byte, err error) { + u, _ := url.Parse(apiclient.BaseURL) + + key := []string{} + key = append(key, "\"apiProducts\":[\""+getArrayStr(apiProducts)+"\"]") + + payload := "{" + strings.Join(key, ",") + "}" + + u.Path = path.Join(u.Path, apiclient.GetApigeeOrg(), "appgroups", name, "apps", appName, "keys", consumerKey) + respBody, err = apiclient.HttpClient(u.String(), payload) + + return respBody, err +} + +// DeleteKeyProduct +func DeleteKeyProduct(name string, appName string, consumerKey string, productName string) (respBody []byte, err error) { + u, _ := url.Parse(apiclient.BaseURL) + u.Path = path.Join(u.Path, apiclient.GetApigeeOrg(), "appgroups", name, "apps", appName, "keys", consumerKey, "apiproducts", productName) + respBody, err = apiclient.HttpClient(u.String(), "", "DELETE") + return respBody, err +} diff --git a/internal/client/developers/developers.go b/internal/client/developers/developers.go index add2369b0..ffba71d99 100644 --- a/internal/client/developers/developers.go +++ b/internal/client/developers/developers.go @@ -168,7 +168,6 @@ func Export() (respBody []byte, err error) { // Import func Import(conn int, filePath string) error { - entities, err := ReadDevelopersFile(filePath) if err != nil { clilog.Error.Println("Error reading file: ", err) diff --git a/internal/client/sharedflows/sharedflows.go b/internal/client/sharedflows/sharedflows.go index fe097f1a1..ce77b1528 100644 --- a/internal/client/sharedflows/sharedflows.go +++ b/internal/client/sharedflows/sharedflows.go @@ -345,7 +345,6 @@ func Export(conn int, folder string, allRevisions bool) (err error) { lastRevision := maxRevision(proxy.Revision) jobChan <- revision{name: proxy.Name, rev: lastRevision} } - } close(jobChan) fanOutWg.Wait()