From fe6e7d3a9f2778307b7603e3409edf8730cee4c1 Mon Sep 17 00:00:00 2001 From: srinandan <13950006+srinandan@users.noreply.github.com> Date: Wed, 29 Nov 2023 11:07:27 -0800 Subject: [PATCH 1/5] feat: adds support to create and deploy proxy and sf #336 --- cmd/apis/bundlecrtapis.go | 39 +++++++++-- cmd/apis/common.go | 77 ++++++++++++++++++++++ cmd/apis/depapi.go | 34 +--------- cmd/sharedflows/bundlecrtsf.go | 31 ++++++++- cmd/sharedflows/common.go | 76 +++++++++++++++++++++ cmd/sharedflows/depsf.go | 31 +-------- internal/apiclient/bundles.go | 16 ++--- internal/client/apis/apis.go | 2 +- internal/client/sharedflows/sharedflows.go | 2 +- 9 files changed, 227 insertions(+), 81 deletions(-) create mode 100644 cmd/apis/common.go create mode 100644 cmd/sharedflows/common.go diff --git a/cmd/apis/bundlecrtapis.go b/cmd/apis/bundlecrtapis.go index 323b72bf9..f64cddffd 100644 --- a/cmd/apis/bundlecrtapis.go +++ b/cmd/apis/bundlecrtapis.go @@ -45,11 +45,15 @@ var BundleCreateCmd = &cobra.Command{ return err } } + if env != "" { + apiclient.SetApigeeEnv(env) + } return apiclient.SetApigeeOrg(org) }, RunE: func(cmd *cobra.Command, args []string) (err error) { + var respBody []byte if proxyZip != "" { - _, err = apis.CreateProxy(name, proxyZip) + respBody, err = apis.CreateProxy(name, proxyZip) } else if proxyFolder != "" { if stat, err := os.Stat(folder); err == nil && !stat.IsDir() { return fmt.Errorf("supplied path is not a folder") @@ -68,11 +72,24 @@ var BundleCreateCmd = &cobra.Command{ if err = proxybundle.GenerateArchiveBundle(proxyFolder, proxyBundlePath, false); err != nil { return err } - if _, err = apis.CreateProxy(name, proxyBundlePath); err != nil { + if respBody, err = apis.CreateProxy(name, proxyBundlePath); err != nil { + return err + } + if err = os.Remove(proxyBundlePath); err != nil { return err } - - return os.Remove(proxyBundlePath) + } + if env != "" { + if revision, err = GetRevision(respBody); err != nil { + return err + } + if _, err = apis.DeployProxy(name, revision, overrides, + sequencedRollout, safeDeploy, serviceAccountName); err != nil { + return err + } + if wait { + return Wait(name, revision) + } } return err }, @@ -89,5 +106,19 @@ func init() { BundleCreateCmd.Flags().StringVarP(&proxyFolder, "proxy-folder", "f", "", "Path to the Proxy Bundle; ex: ./test/apiproxy") + BundleCreateCmd.Flags().StringVarP(&env, "env", "e", + "", "Name of the environment to deploy the proxy") + BundleCreateCmd.Flags().BoolVarP(&overrides, "ovr", "r", + false, "Forces deployment of the new revision") + BundleCreateCmd.Flags().BoolVarP(&wait, "wait", "", + false, "Waits for the deployment to finish, with success or error") + BundleCreateCmd.Flags().BoolVarP(&sequencedRollout, "sequencedrollout", "", + false, "If set to true, the routing rules will be rolled out in a safe order; default is false") + BundleCreateCmd.Flags().BoolVarP(&safeDeploy, "safedeploy", "", + true, "When set to true, generateDeployChangeReport will be executed and "+ + "deployment will proceed if there are no conflicts; default is true") + BundleCreateCmd.Flags().StringVarP(&serviceAccountName, "sa", "s", + "", "The format must be {ACCOUNT_ID}@{PROJECT}.iam.gserviceaccount.com.") + _ = BundleCreateCmd.MarkFlagRequired("name") } diff --git a/cmd/apis/common.go b/cmd/apis/common.go new file mode 100644 index 000000000..ea16c87b7 --- /dev/null +++ b/cmd/apis/common.go @@ -0,0 +1,77 @@ +// 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 apis + +import ( + "encoding/json" + "fmt" + "strconv" + "time" + + "internal/apiclient" + "internal/client/apis" + "internal/clilog" +) + +func GetRevision(respBody []byte) (revision int, err error) { + var apiProxyRevResp map[string]interface{} + + err = json.Unmarshal(respBody, &apiProxyRevResp) + if err != nil { + return -1, err + } + apiProxyRev, err := strconv.Atoi(fmt.Sprintf("%v", apiProxyRevResp["revision"])) + if err != nil { + return -1, err + } + return apiProxyRev, nil +} + +func Wait(name string, revision int) error { + var err error + + clilog.Info.Printf("Checking deployment status in %d seconds\n", interval) + + apiclient.DisableCmdPrintHttpResponse() + + stop := apiclient.Every(interval*time.Second, func(time.Time) bool { + var respBody []byte + respMap := make(map[string]interface{}) + if respBody, err = apis.ListProxyRevisionDeployments(name, revision); err != nil { + clilog.Error.Printf("Error fetching proxy revision status: %v", err) + return false + } + + if err = json.Unmarshal(respBody, &respMap); err != nil { + return true + } + + switch respMap["state"] { + case "PROGRESSING": + clilog.Info.Printf("Proxy deployment status is: %s. Waiting %d seconds.\n", respMap["state"], interval) + return true + case "READY": + clilog.Info.Println("Proxy deployment completed with status: ", respMap["state"]) + default: + clilog.Info.Println("Proxy deployment failed with status: ", respMap["state"]) + } + + return false + }) + + <-stop + + return err +} diff --git a/cmd/apis/depapi.go b/cmd/apis/depapi.go index d812271f7..b9669ef61 100644 --- a/cmd/apis/depapi.go +++ b/cmd/apis/depapi.go @@ -15,11 +15,7 @@ package apis import ( - "encoding/json" - "time" - "internal/apiclient" - "internal/clilog" "internal/client/apis" @@ -51,37 +47,9 @@ var DepCmd = &cobra.Command{ return err } - apiclient.DisableCmdPrintHttpResponse() - if wait { - clilog.Info.Printf("Checking deployment status in %d seconds\n", interval) - - stop := apiclient.Every(interval*time.Second, func(time.Time) bool { - var respBody []byte - respMap := make(map[string]interface{}) - if respBody, err = apis.ListProxyRevisionDeployments(name, revision); err != nil { - clilog.Error.Printf("Error fetching proxy revision status: %v", err) - return false - } - - if err = json.Unmarshal(respBody, &respMap); err != nil { - return true - } - - if respMap["state"] == "PROGRESSING" { - clilog.Info.Printf("Proxy deployment status is: %s. Waiting %d seconds.\n", respMap["state"], interval) - return true - } else if respMap["state"] == "READY" { - clilog.Info.Println("Proxy deployment completed with status: ", respMap["state"]) - } else { - clilog.Info.Println("Proxy deployment failed with status: ", respMap["state"]) - } - return false - }) - - <-stop + err = Wait(name, revision) } - return err }, } diff --git a/cmd/sharedflows/bundlecrtsf.go b/cmd/sharedflows/bundlecrtsf.go index e606ac313..864338ddd 100644 --- a/cmd/sharedflows/bundlecrtsf.go +++ b/cmd/sharedflows/bundlecrtsf.go @@ -47,11 +47,15 @@ var BundleCreateCmd = &cobra.Command{ return err } } + if env != "" { + apiclient.SetApigeeEnv(env) + } return apiclient.SetApigeeOrg(org) }, RunE: func(cmd *cobra.Command, args []string) (err error) { + var respBody []byte if sfZip != "" { - _, err = sharedflows.Create(name, sfZip) + respBody, err = sharedflows.Create(name, sfZip) } else if sfFolder != "" { if stat, err := os.Stat(folder); err == nil && !stat.IsDir() { return fmt.Errorf("supplied path is not a folder") @@ -70,10 +74,23 @@ var BundleCreateCmd = &cobra.Command{ if err = proxybundle.GenerateArchiveBundle(sfFolder, sfBundlePath, true); err != nil { return err } - if _, err = sharedflows.Create(name, sfBundlePath); err != nil { + if respBody, err = sharedflows.Create(name, sfBundlePath); err != nil { + return err + } + if err = os.Remove(sfBundlePath); err != nil { return err } - return os.Remove(sfBundlePath) + } + if env != "" { + if revision, err = GetRevision(respBody); err != nil { + return err + } + if _, err = sharedflows.Deploy(name, revision, overrides, serviceAccountName); err != nil { + return err + } + if wait { + return Wait(name, revision) + } } return err }, @@ -88,6 +105,14 @@ func init() { "", "Path to the Sharedflow bundle/zip file") BundleCreateCmd.Flags().StringVarP(&sfFolder, "sf-folder", "f", "", "Path to the Sharedflow Bundle; ex: ./test/sharedflowbundle") + BundleCreateCmd.Flags().StringVarP(&env, "env", "e", + "", "Apigee environment name") + BundleCreateCmd.Flags().IntVarP(&revision, "rev", "v", + -1, "Sharedflow revision. If not set, the highest revision is used") + BundleCreateCmd.Flags().BoolVarP(&overrides, "ovr", "r", + false, "Forces deployment of the new revision") + BundleCreateCmd.Flags().BoolVarP(&wait, "wait", "", + false, "Waits for the deployment to finish, with success or error") _ = BundleCreateCmd.MarkFlagRequired("name") } diff --git a/cmd/sharedflows/common.go b/cmd/sharedflows/common.go new file mode 100644 index 000000000..d86653034 --- /dev/null +++ b/cmd/sharedflows/common.go @@ -0,0 +1,76 @@ +// 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 sharedflows + +import ( + "encoding/json" + "fmt" + "strconv" + "time" + + "internal/apiclient" + "internal/client/sharedflows" + "internal/clilog" +) + +func GetRevision(respBody []byte) (revision int, err error) { + var apiProxyRevResp map[string]interface{} + + err = json.Unmarshal(respBody, &apiProxyRevResp) + if err != nil { + return -1, err + } + apiProxyRev, err := strconv.Atoi(fmt.Sprintf("%v", apiProxyRevResp["revision"])) + if err != nil { + return -1, err + } + return apiProxyRev, nil +} + +func Wait(name string, revision int) error { + var err error + + apiclient.DisableCmdPrintHttpResponse() + + clilog.Info.Printf("Checking deployment status in %d seconds\n", interval) + + stop := apiclient.Every(interval*time.Second, func(time.Time) bool { + var respBody []byte + respMap := make(map[string]interface{}) + if respBody, err = sharedflows.ListRevisionDeployments(name, revision); err != nil { + clilog.Error.Printf("Error fetching sharedflow revision status: %v", err) + return false + } + + if err = json.Unmarshal(respBody, &respMap); err != nil { + return true + } + + switch respMap["state"] { + case "PROGRESSING": + clilog.Info.Printf("Sharedflow deployment status is: %s. Waiting %d seconds.\n", respMap["state"], interval) + return true + case "READY": + clilog.Info.Println("Sharedflow deployment completed with status: ", respMap["state"]) + default: + clilog.Info.Println("Sharedflow deployment failed with status: ", respMap["state"]) + } + return false + }) + + <-stop + + return err +} diff --git a/cmd/sharedflows/depsf.go b/cmd/sharedflows/depsf.go index 2295b12e3..289c73b42 100644 --- a/cmd/sharedflows/depsf.go +++ b/cmd/sharedflows/depsf.go @@ -15,11 +15,7 @@ package sharedflows import ( - "encoding/json" - "time" - "internal/apiclient" - "internal/clilog" "internal/client/sharedflows" @@ -45,32 +41,7 @@ var DepCmd = &cobra.Command{ apiclient.DisableCmdPrintHttpResponse() if wait { - clilog.Info.Printf("Checking deployment status in %d seconds\n", interval) - - stop := apiclient.Every(interval*time.Second, func(time.Time) bool { - var respBody []byte - respMap := make(map[string]interface{}) - if respBody, err = sharedflows.ListRevisionDeployments(name, revision); err != nil { - clilog.Error.Printf("Error fetching sharedflow revision status: %v", err) - return false - } - - if err = json.Unmarshal(respBody, &respMap); err != nil { - return true - } - - if respMap["state"] == "PROGRESSING" { - clilog.Info.Printf("Sharedflow deployment status is: %s. Waiting %d seconds.\n", respMap["state"], interval) - return true - } else if respMap["state"] == "READY" { - clilog.Info.Println("Sharedflow deployment completed with status: ", respMap["state"]) - } else { - clilog.Info.Println("Sharedflow deployment failed with status: ", respMap["state"]) - } - return false - }) - - <-stop + err = Wait(name, revision) } return err diff --git a/internal/apiclient/bundles.go b/internal/apiclient/bundles.go index fe16b6892..59d449697 100644 --- a/internal/apiclient/bundles.go +++ b/internal/apiclient/bundles.go @@ -235,15 +235,14 @@ func FetchBundle(entityType string, folder string, name string, revision string, func ImportBundleAsync(entityType string, name string, bundlePath string, wg *sync.WaitGroup) { defer wg.Done() - _ = ImportBundle(entityType, name, bundlePath) + _, _ = ImportBundle(entityType, name, bundlePath) } // ImportBundle imports a sharedflow or api proxy bundle -func ImportBundle(entityType string, name string, bundlePath string) error { - err := ReadBundle(bundlePath) - if err != nil { +func ImportBundle(entityType string, name string, bundlePath string) (respBody []byte, err error) { + if err = ReadBundle(bundlePath); err != nil { clilog.Error.Println(err) - return err + return nil, err } // when importing from a folder, proxy name = file name @@ -265,14 +264,13 @@ func ImportBundle(entityType string, name string, bundlePath string) error { "proxy": bundlePath, } - _, err = PostHttpOctet(false, u.String(), formParams) - if err != nil { + if respBody, err = PostHttpOctet(false, u.String(), formParams); err != nil { clilog.Error.Println(err) - return err + return nil, err } clilog.Debug.Printf("Completed entity: %s", u.String()) - return nil + return respBody, err } func FolderExists(folder string) (err error) { diff --git a/internal/client/apis/apis.go b/internal/client/apis/apis.go index 61e999d2c..7a94ba873 100644 --- a/internal/client/apis/apis.go +++ b/internal/client/apis/apis.go @@ -83,7 +83,7 @@ type conflictingdeployment struct { // CreateProxy func CreateProxy(name string, proxy string) (respBody []byte, err error) { if proxy != "" { - err = apiclient.ImportBundle("apis", name, proxy) + respBody, err = apiclient.ImportBundle("apis", name, proxy) return respBody, err } u, _ := url.Parse(apiclient.BaseURL) diff --git a/internal/client/sharedflows/sharedflows.go b/internal/client/sharedflows/sharedflows.go index 8f5117297..0340ac3b9 100644 --- a/internal/client/sharedflows/sharedflows.go +++ b/internal/client/sharedflows/sharedflows.go @@ -52,7 +52,7 @@ type revision struct { // Create func Create(name string, proxy string) (respBody []byte, err error) { if proxy != "" { - err = apiclient.ImportBundle("sharedflows", name, proxy) + respBody, err = apiclient.ImportBundle("sharedflows", name, proxy) return respBody, err } u, _ := url.Parse(apiclient.BaseURL) From 4a51973c3070a60e880f1e8b242ff37b5ac287db Mon Sep 17 00:00:00 2001 From: srinandan <13950006+srinandan@users.noreply.github.com> Date: Wed, 29 Nov 2023 12:25:58 -0800 Subject: [PATCH 2/5] feat: adds support for service account #336 --- cmd/sharedflows/bundlecrtsf.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmd/sharedflows/bundlecrtsf.go b/cmd/sharedflows/bundlecrtsf.go index 864338ddd..37d3d5edd 100644 --- a/cmd/sharedflows/bundlecrtsf.go +++ b/cmd/sharedflows/bundlecrtsf.go @@ -113,6 +113,8 @@ func init() { false, "Forces deployment of the new revision") BundleCreateCmd.Flags().BoolVarP(&wait, "wait", "", false, "Waits for the deployment to finish, with success or error") + BundleCreateCmd.Flags().StringVarP(&serviceAccountName, "sa", "s", + "", "The format must be {ACCOUNT_ID}@{PROJECT}.iam.gserviceaccount.com.") _ = BundleCreateCmd.MarkFlagRequired("name") } From 869451f4717f1436f95ad7872ebb04a9eeb077a9 Mon Sep 17 00:00:00 2001 From: srinandan <13950006+srinandan@users.noreply.github.com> Date: Wed, 29 Nov 2023 12:28:28 -0800 Subject: [PATCH 3/5] bug: remove revision as a param #336 --- cmd/sharedflows/bundlecrtsf.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/cmd/sharedflows/bundlecrtsf.go b/cmd/sharedflows/bundlecrtsf.go index 37d3d5edd..422dc24a6 100644 --- a/cmd/sharedflows/bundlecrtsf.go +++ b/cmd/sharedflows/bundlecrtsf.go @@ -107,8 +107,6 @@ func init() { "", "Path to the Sharedflow Bundle; ex: ./test/sharedflowbundle") BundleCreateCmd.Flags().StringVarP(&env, "env", "e", "", "Apigee environment name") - BundleCreateCmd.Flags().IntVarP(&revision, "rev", "v", - -1, "Sharedflow revision. If not set, the highest revision is used") BundleCreateCmd.Flags().BoolVarP(&overrides, "ovr", "r", false, "Forces deployment of the new revision") BundleCreateCmd.Flags().BoolVarP(&wait, "wait", "", From 445b960d5ac6174c0d6eb5463f1d672987ec19ea Mon Sep 17 00:00:00 2001 From: srinandan <13950006+srinandan@users.noreply.github.com> Date: Wed, 29 Nov 2023 13:11:48 -0800 Subject: [PATCH 4/5] chore: update descriptions #336 --- cmd/apis/bundlecrtapis.go | 2 +- cmd/sharedflows/bundlecrtsf.go | 2 +- cmd/targetservers/impts.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/apis/bundlecrtapis.go b/cmd/apis/bundlecrtapis.go index f64cddffd..65dac2e48 100644 --- a/cmd/apis/bundlecrtapis.go +++ b/cmd/apis/bundlecrtapis.go @@ -32,7 +32,7 @@ import ( var BundleCreateCmd = &cobra.Command{ Use: "bundle", Short: "Creates an API proxy from an Zip or folder", - Long: "Creates an API proxy from an Zip or folder", + Long: "Creates an API proxy from an Zip or folder; Optionally deploy the API to an env", Args: func(cmd *cobra.Command, args []string) (err error) { if proxyZip != "" && proxyFolder != "" { return fmt.Errorf("proxy bundle (zip) and folder to an API proxy cannot be combined") diff --git a/cmd/sharedflows/bundlecrtsf.go b/cmd/sharedflows/bundlecrtsf.go index 422dc24a6..ebaddbc43 100644 --- a/cmd/sharedflows/bundlecrtsf.go +++ b/cmd/sharedflows/bundlecrtsf.go @@ -33,7 +33,7 @@ import ( var BundleCreateCmd = &cobra.Command{ Use: "bundle", Short: "Creates a sharedflow in an Apigee Org", - Long: "Creates a sharedflow in an Apigee Org", + Long: "Creates a sharedflow in an Apigee Org; Optionally deploy the sharedflow to an env", Args: func(cmd *cobra.Command, args []string) (err error) { apiclient.SetApigeeEnv(env) if sfZip != "" && sfFolder != "" { diff --git a/cmd/targetservers/impts.go b/cmd/targetservers/impts.go index c6caa98d0..8ee5c749c 100644 --- a/cmd/targetservers/impts.go +++ b/cmd/targetservers/impts.go @@ -40,7 +40,7 @@ var filePath string func init() { ImpCmd.Flags().StringVarP(&filePath, "file", "f", - "", "File containing API Products") + "", "Path to a file containing Target Servers") ImpCmd.Flags().IntVarP(&conn, "conn", "c", 4, "Number of connections") From 5aed2cb40fd45c06023694fcbec5b45e3df7c3bb Mon Sep 17 00:00:00 2001 From: srinandan <13950006+srinandan@users.noreply.github.com> Date: Wed, 29 Nov 2023 15:34:08 -0800 Subject: [PATCH 5/5] chore: add print statements for deploy #336 --- cmd/apis/bundlecrtapis.go | 2 ++ cmd/sharedflows/bundlecrtsf.go | 2 ++ 2 files changed, 4 insertions(+) diff --git a/cmd/apis/bundlecrtapis.go b/cmd/apis/bundlecrtapis.go index 65dac2e48..5adaed46c 100644 --- a/cmd/apis/bundlecrtapis.go +++ b/cmd/apis/bundlecrtapis.go @@ -21,6 +21,7 @@ import ( "path/filepath" "internal/apiclient" + "internal/clilog" proxybundle "internal/bundlegen/proxybundle" @@ -80,6 +81,7 @@ var BundleCreateCmd = &cobra.Command{ } } if env != "" { + clilog.Info.Printf("Deploying the API Proxy %s to environment %s\n", name, env) if revision, err = GetRevision(respBody); err != nil { return err } diff --git a/cmd/sharedflows/bundlecrtsf.go b/cmd/sharedflows/bundlecrtsf.go index ebaddbc43..697d5cc74 100644 --- a/cmd/sharedflows/bundlecrtsf.go +++ b/cmd/sharedflows/bundlecrtsf.go @@ -21,6 +21,7 @@ import ( "path/filepath" "internal/apiclient" + "internal/clilog" "internal/bundlegen/proxybundle" @@ -82,6 +83,7 @@ var BundleCreateCmd = &cobra.Command{ } } if env != "" { + clilog.Info.Printf("Deploying the Sharedflow %s to environment %s\n", name, env) if revision, err = GetRevision(respBody); err != nil { return err }