summaryrefslogtreecommitdiffstats
path: root/src/tools/emcoctl/cmd
diff options
context:
space:
mode:
authorRitu Sood <ritu.sood@intel.com>2020-08-25 22:01:22 -0700
committerRitu Sood <ritu.sood@intel.com>2020-08-25 22:14:34 -0700
commit16ca82713faf6678d4b7055130768541f86ea20c (patch)
treebc8a457b5a545c79aff23e95e515f09c91dca3b6 /src/tools/emcoctl/cmd
parent706d0990fc2210041f467934f3ec72c9b5a06ff4 (diff)
CLI code for EMCO
Add cli emcoctl as a client utility for EMCO Issue-ID: MULTICLOUD-1065 Signed-off-by: Ritu Sood <ritu.sood@intel.com> Change-Id: Ie1951910628469b5a7e75550b9daa34ba377d1a4
Diffstat (limited to 'src/tools/emcoctl/cmd')
-rw-r--r--src/tools/emcoctl/cmd/apply.go62
-rw-r--r--src/tools/emcoctl/cmd/config.go80
-rw-r--r--src/tools/emcoctl/cmd/delete.go57
-rw-r--r--src/tools/emcoctl/cmd/get.go40
-rw-r--r--src/tools/emcoctl/cmd/getall.go39
-rw-r--r--src/tools/emcoctl/cmd/root.go85
-rw-r--r--src/tools/emcoctl/cmd/utils.go305
7 files changed, 668 insertions, 0 deletions
diff --git a/src/tools/emcoctl/cmd/apply.go b/src/tools/emcoctl/cmd/apply.go
new file mode 100644
index 00000000..f451a614
--- /dev/null
+++ b/src/tools/emcoctl/cmd/apply.go
@@ -0,0 +1,62 @@
+/*
+Copyright © 2020 Intel Corp
+
+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 cmd
+
+import (
+ "fmt"
+
+ "github.com/spf13/cobra"
+)
+
+// applyCmd represents the apply command
+var applyCmd = &cobra.Command{
+ Use: "apply",
+ Short: "apply(Post) the resources from input file or url(with body) from command line",
+ Run: func(cmd *cobra.Command, args []string) {
+ fmt.Println("apply called")
+ c := NewRestClient()
+ if len(inputFiles) > 0 {
+ resources := readResources()
+ for _, res := range resources {
+ if res.file != "" {
+ err := c.RestClientMultipartPost(res.anchor, res.body, res.file)
+ if err != nil {
+ fmt.Println("Apply: ", res.anchor, "Error: ",err)
+ return
+ }
+ } else {
+ err := c.RestClientPost(res.anchor, res.body)
+ if err != nil {
+ fmt.Println("Apply: ", res.anchor, "Error: ",err)
+ return
+ }
+ }
+ }
+ } else if len(args) >= 1 {
+ fmt.Println(args[0])
+ c.RestClientPost(args[0], []byte{})
+ } else {
+ fmt.Println("Error: No args ")
+ }
+ },
+}
+
+func init() {
+ fmt.Println("INIT ")
+ rootCmd.AddCommand(applyCmd)
+ applyCmd.Flags().StringSliceVarP(&inputFiles, "filename", "f", []string{}, "Filename of the input file")
+ applyCmd.Flags().StringSliceVarP(&valuesFiles, "values", "v", []string{}, "Values to go with the file")
+}
diff --git a/src/tools/emcoctl/cmd/config.go b/src/tools/emcoctl/cmd/config.go
new file mode 100644
index 00000000..c5e44660
--- /dev/null
+++ b/src/tools/emcoctl/cmd/config.go
@@ -0,0 +1,80 @@
+/*
+Copyright © 2020 Intel Corp
+
+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 cmd
+
+import (
+ "strconv"
+)
+
+// Configurations exported
+type EmcoConfigurations struct {
+ Orchestrator ControllerConfigurations
+ Clm ControllerConfigurations
+ Ncm ControllerConfigurations
+ Dcm ControllerConfigurations
+ Rsync ControllerConfigurations
+ OvnAction ControllerConfigurations
+}
+
+// ControllerConfigurations exported
+type ControllerConfigurations struct {
+ Port int
+ Host string
+}
+
+const urlVersion string = "v2"
+const urlPrefix string = "http://"
+var Configurations EmcoConfigurations
+
+// GetOrchestratorURL Url for Orchestrator
+func GetOrchestratorURL() string {
+ if Configurations.Orchestrator.Host == "" || Configurations.Orchestrator.Port == 0 {
+ panic("No Orchestrator Information in Config File")
+ }
+ return urlPrefix + Configurations.Orchestrator.Host + ":" + strconv.Itoa(Configurations.Orchestrator.Port) + "/" + urlVersion
+}
+
+// GetClmURL Url for Clm
+func GetClmURL() string {
+ if Configurations.Clm.Host == "" || Configurations.Clm.Port == 0 {
+ panic("No Clm Information in Config File")
+ }
+ return urlPrefix + Configurations.Clm.Host + ":" + strconv.Itoa(Configurations.Clm.Port) + "/" + urlVersion
+}
+
+// GetNcmURL Url for Ncm
+func GetNcmURL() string {
+ if Configurations.Ncm.Host == "" || Configurations.Ncm.Port == 0 {
+ panic("No Ncm Information in Config File")
+ }
+ return urlPrefix + Configurations.Ncm.Host + ":" + strconv.Itoa(Configurations.Ncm.Port) + "/" + urlVersion
+}
+
+// GetDcmURL Url for Dcm
+func GetDcmURL() string {
+ if Configurations.Dcm.Host == "" || Configurations.Dcm.Port == 0 {
+ panic("No Dcm Information in Config File")
+ }
+ return urlPrefix + Configurations.Dcm.Host + ":" + strconv.Itoa(Configurations.Dcm.Port) + "/" + urlVersion
+}
+
+// GetOvnactionURL Url for Ovnaction
+func GetOvnactionURL() string {
+ if Configurations.OvnAction.Host == "" || Configurations.OvnAction.Port == 0 {
+ panic("No Ovn Action Information in Config File")
+ }
+ return urlPrefix + Configurations.OvnAction.Host + ":" + strconv.Itoa(Configurations.OvnAction.Port) + "/" + urlVersion
+}
diff --git a/src/tools/emcoctl/cmd/delete.go b/src/tools/emcoctl/cmd/delete.go
new file mode 100644
index 00000000..d6dbfe34
--- /dev/null
+++ b/src/tools/emcoctl/cmd/delete.go
@@ -0,0 +1,57 @@
+/*
+Copyright © 2020 Intel Corp
+
+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 cmd
+
+import (
+ "fmt"
+
+ "github.com/spf13/cobra"
+)
+
+// deleteCmd represents the delete command
+var deleteCmd = &cobra.Command{
+ Use: "delete",
+ Short: "Delete resources in input file or commandline",
+ Run: func(cmd *cobra.Command, args []string) {
+ fmt.Println("delete called")
+ c := NewRestClient()
+ if len(inputFiles) > 0 {
+ resources := readResources()
+ for i := len(resources) - 1; i >= 0; i-- {
+ res := resources[i]
+ c.RestClientDelete(res.anchor, res.body)
+ }
+ } else if len(args) >= 1 {
+ fmt.Println(args[0])
+ c.RestClientDelete(args[0], nil)
+ }
+ },
+}
+
+func init() {
+ rootCmd.AddCommand(deleteCmd)
+
+ // Here you will define your flags and configuration settings.
+
+ // Cobra supports Persistent Flags which will work for this command
+ // and all subcommands, e.g.:
+ // deleteCmd.PersistentFlags().String("foo", "", "A help for foo")
+
+ // Cobra supports local flags which will only run when this command
+ // is called directly, e.g.:
+ // deleteCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
+ deleteCmd.Flags().StringSliceVarP(&inputFiles, "filename", "f", []string{}, "Filename of the input file")
+}
diff --git a/src/tools/emcoctl/cmd/get.go b/src/tools/emcoctl/cmd/get.go
new file mode 100644
index 00000000..2cc96dc4
--- /dev/null
+++ b/src/tools/emcoctl/cmd/get.go
@@ -0,0 +1,40 @@
+/*
+Copyright © 2020 Intel Corp
+
+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 cmd
+
+import (
+ "fmt"
+
+ "github.com/spf13/cobra"
+)
+
+// getCmd represents the get command
+var getCmd = &cobra.Command{
+ Use: "get",
+ Short: "Get the resource(s) based on the URL",
+ Run: func(cmd *cobra.Command, args []string) {
+ fmt.Println("get called")
+ c := NewRestClient()
+ if len(args) >= 1 {
+ fmt.Println(args[0])
+ c.RestClientGet(args[0])
+ }
+ },
+}
+
+func init() {
+ rootCmd.AddCommand(getCmd)
+}
diff --git a/src/tools/emcoctl/cmd/getall.go b/src/tools/emcoctl/cmd/getall.go
new file mode 100644
index 00000000..329b2582
--- /dev/null
+++ b/src/tools/emcoctl/cmd/getall.go
@@ -0,0 +1,39 @@
+/*
+Copyright © 2020 Intel Corp
+
+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 cmd
+
+import (
+ "github.com/spf13/cobra"
+)
+
+// getallCmd represents the getall command
+var getallCmd = &cobra.Command{
+ Use: "getall",
+ Short: "Get all resources in the file provided",
+ Run: func(cmd *cobra.Command, args []string) {
+ resources := readResources()
+ c := NewRestClient()
+ for _, res := range resources {
+ c.RestClientGetAll(res.anchor)
+ }
+ },
+}
+
+func init() {
+ rootCmd.AddCommand(getallCmd)
+ // Here you will define your flags and configuration settings.
+ getallCmd.Flags().StringSliceVarP(&inputFiles, "filename", "f", []string{}, "Filename of the input file")
+}
diff --git a/src/tools/emcoctl/cmd/root.go b/src/tools/emcoctl/cmd/root.go
new file mode 100644
index 00000000..4c1ac19f
--- /dev/null
+++ b/src/tools/emcoctl/cmd/root.go
@@ -0,0 +1,85 @@
+/*
+Copyright © 2020 Intel Corp
+
+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 cmd
+import (
+ "fmt"
+ "github.com/spf13/cobra"
+ "os"
+
+ homedir "github.com/mitchellh/go-homedir"
+ "github.com/spf13/viper"
+)
+
+var cfgFile string
+
+// rootCmd represents the base command when called without any subcommands
+var rootCmd = &cobra.Command{
+ Use: "emco",
+ Short: "Emcoctl - CLI for EMCO",
+ // Uncomment the following line if your bare application
+ // has an action associated with it:
+ Run: func(cmd *cobra.Command, args []string) {fmt.Println("emcoctl <command> -f file") },
+}
+
+// Execute adds all child commands to the root command and sets flags appropriately.
+// This is called by main.main(). It only needs to happen once to the rootCmd.
+func Execute() {
+ if err := rootCmd.Execute(); err != nil {
+ fmt.Println("Test")
+ fmt.Println(err)
+ os.Exit(1)
+ }
+}
+
+func init() {
+ cobra.OnInitialize(initConfig)
+ rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.emco.yaml)")
+
+ // Cobra also supports local flags, which will only run
+ // when this action is called directly.
+ rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
+}
+
+// initConfig reads in config file and ENV variables if set.
+func initConfig() {
+ cfgFile = "emco-cfg.yaml"
+ if cfgFile != "" {
+ // Use config file from the flag.
+ viper.SetConfigFile(cfgFile)
+ } else {
+ // Find home directory.
+ home, err := homedir.Dir()
+ if err != nil {
+ fmt.Println(err)
+ os.Exit(1)
+ }
+ fmt.Println(home)
+ // Search config in home directory with name ".emco" (without extension).
+ viper.AddConfigPath(home)
+ viper.SetConfigName(".emco")
+ }
+
+ viper.AutomaticEnv() // read in environment variables that match
+
+ // If a config file is found, read it in.
+ if err := viper.ReadInConfig(); err == nil {
+ fmt.Println("Using config file:", viper.ConfigFileUsed())
+ err := viper.Unmarshal(&Configurations)
+ if err != nil {
+ fmt.Printf("Unable to decode into struct, %v", err)
+ }
+ }
+} \ No newline at end of file
diff --git a/src/tools/emcoctl/cmd/utils.go b/src/tools/emcoctl/cmd/utils.go
new file mode 100644
index 00000000..34063eee
--- /dev/null
+++ b/src/tools/emcoctl/cmd/utils.go
@@ -0,0 +1,305 @@
+/*Copyright © 2020 Intel Corp
+
+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 cmd
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ neturl "net/url"
+ "os"
+ "strings"
+
+ "github.com/go-resty/resty/v2"
+ "github.com/mitchellh/mapstructure"
+ "gopkg.in/yaml.v3"
+ pkgerrors "github.com/pkg/errors"
+)
+
+var inputFiles []string
+var valuesFiles []string
+
+type ResourceContext struct {
+ Anchor string `json:"anchor" yaml:"anchor"`
+}
+
+type Metadata struct {
+ Name string `yaml:"name" json:"name"`
+ Description string `yaml:"description,omitempty" json:"description,omitempty"`
+ UserData1 string `yaml:"userData1,omitempty" json:"userData1,omitempty"`
+ UserData2 string `yaml:"userData2,omitempty" json:"userData2,omitempty"`
+}
+
+type emcoRes struct {
+ Version string `yaml:"version" json:"version"`
+ Context ResourceContext `yaml:"resourceContext" json:"resourceContext"`
+ Meta Metadata `yaml:"metadata" json:"metadata"`
+ Spec map[string]interface{} `yaml:"spec,omitempty" json:"spec,omitempty"`
+ File string `yaml:"file,omitempty" json:"file,omitempty"`
+ Label string `yaml:"label-name,omitempty" json:"label-name,omitempty"`
+}
+
+type emcoBody struct {
+ Meta Metadata `json:"metadata,omitempty"`
+ Label string `json:"label-name,omitempty"`
+ Spec map[string]interface{} `json:"spec,omitempty"`
+}
+
+type emcoCompositeAppSpec struct {
+ Version string `json: "version"`
+}
+
+type Resources struct {
+ anchor string
+ body []byte
+ file string
+}
+// RestyClient to use with CLI
+type RestyClient struct {
+ client *resty.Client
+}
+
+var Client RestyClient
+
+// NewRestClient returns a rest client
+func NewRestClient() RestyClient {
+ // Create a Resty Client
+ Client.client = resty.New()
+ // Bearer Auth Token for all request
+ // Client.client.SetAuthToken()
+ // Registering global Error object structure for JSON/XML request
+ //Client.client.SetError(&Error{})
+ return Client
+}
+
+// readResources reads all the resources in the file provided
+func readResources() []Resources {
+ // TODO: Remove Assumption only one file
+ // Open file and Parse to get all resources
+ var resources []Resources
+ f, err := os.Open(inputFiles[0])
+ defer f.Close()
+ if err != nil {
+ fmt.Printf("Error %s reading file %s\n", err, inputFiles[0])
+ return []Resources{}
+ }
+ if len(valuesFiles) > 0 {
+ //Apply template
+ }
+
+ dec := yaml.NewDecoder(f)
+ // Iterate through all resources in the file
+ for {
+ var doc emcoRes
+ if dec.Decode(&doc) != nil {
+ break
+ }
+ body := &emcoBody{Meta: doc.Meta, Spec: doc.Spec, Label: doc.Label}
+ jsonBody, err := json.Marshal(body)
+ if err != nil {
+ return []Resources{}
+ }
+ var res Resources
+ if doc.File != "" {
+ res = Resources{anchor: doc.Context.Anchor, body: jsonBody, file: doc.File}
+ } else {
+ res = Resources{anchor: doc.Context.Anchor, body: jsonBody}
+ }
+ resources = append(resources, res)
+ }
+ return resources
+}
+
+//RestClientPost to post to server no multipart
+func (r RestyClient) RestClientPost(anchor string, body []byte) error {
+
+ url, err := GetURL(anchor)
+ if err != nil {
+ return err
+ }
+
+ // POST JSON string
+ resp, err := r.client.R().
+ SetHeader("Content-Type", "application/json").
+ SetBody(body).
+ Post(url)
+ if err != nil {
+ fmt.Println(err)
+ return err
+ }
+ fmt.Println("URL:", anchor, "Response Code:", resp.StatusCode(), "Response:", resp)
+ if (resp.StatusCode() >= 201 && resp.StatusCode() <= 299) {
+ return nil
+ }
+ return pkgerrors.Errorf("Server Post Error")
+}
+
+//RestClientMultipartPost to post to server with multipart
+func (r RestyClient) RestClientMultipartPost(anchor string, body []byte, file string) error {
+ url, err := GetURL(anchor)
+ if err != nil {
+ return err
+ }
+
+ // Read file for multipart
+ f, err := ioutil.ReadFile(file)
+ if err != nil {
+ fmt.Printf("Error %s reading file %s\n", err, file)
+ return err
+ }
+
+ // Multipart Post
+ formParams := neturl.Values{}
+ formParams.Add("metadata", string(body))
+ resp, err := r.client.R().
+ SetFileReader("file", "filename", bytes.NewReader(f)).
+ SetFormDataFromValues(formParams).
+ Post(url)
+
+ if err != nil {
+ fmt.Println(err)
+ return err
+ }
+ fmt.Println("URL:", anchor, "Response Code:", resp.StatusCode(), "Response:", resp)
+ if (resp.StatusCode() >= 201 && resp.StatusCode() <= 299) {
+ return nil
+ }
+ return pkgerrors.Errorf("Server Multipart Post Error")
+}
+// RestClientGetAll returns all resource in the input file
+func (r RestyClient) RestClientGetAll(anchor string) error {
+ url, err := GetURL(anchor)
+ if err != nil {
+ return err
+ }
+ resp, err := r.client.R().
+ Get(url)
+ if err != nil {
+ fmt.Println(err)
+ return err
+ }
+ fmt.Println("URL:", anchor, "Response Code:", resp.StatusCode(), "Response:", resp)
+ return nil
+}
+// RestClientGet gets resource
+func (r RestyClient) RestClientGet(anchor string) error {
+ s := strings.Split(anchor, "/")
+ a := s[len(s)-2]
+ // Determine if multipart
+ if a == "apps" || a == "profiles" || a == "clusters" {
+ url, err := GetURL(anchor)
+ if err != nil {
+ return err
+ }
+ // Supports only getting metadata
+ resp, err := r.client.R().
+ SetHeader("Accept", "application/json").
+ Get(url)
+ if err != nil {
+ fmt.Println(err)
+ return err
+ }
+ fmt.Println("URL:", anchor, "Response Code:", resp.StatusCode(), "Response:", resp)
+ } else {
+ r.RestClientGetAll(anchor)
+ }
+
+ return nil
+}
+// RestClientDelete calls rest delete command
+func (r RestyClient) RestClientDelete(anchor string, body []byte) error {
+ var url string
+
+ s := strings.Split(anchor, "/")
+ a := s[len(s)-1]
+ if a == "instantiate" {
+ // Change instantiate to destroy
+ s[len(s)-1] = "terminate"
+ anchor = strings.Join(s[:], "/")
+ fmt.Println("URL:", anchor)
+ return r.RestClientPost(anchor, []byte{})
+ } else if a == "apply" {
+ // Change apply to terminate
+ s[len(s)-1] = "terminate"
+ anchor = strings.Join(s[:], "/")
+ fmt.Println("URL:", anchor)
+ return r.RestClientPost(anchor, []byte{})
+ } else if a == "approve" || a == "status" {
+ // Approve and status doesn't have delete
+ return nil
+ }
+ var e emcoBody
+ err := json.Unmarshal(body, &e)
+ if err != nil {
+ fmt.Println(err)
+ return err
+ }
+ if e.Meta.Name != "" {
+ s := strings.Split(anchor, "/")
+ a := s[len(s)-1]
+ name := e.Meta.Name
+ anchor = anchor + "/" + name
+ if a == "composite-apps" {
+ var cav emcoCompositeAppSpec
+ err := mapstructure.Decode(e.Spec, &cav)
+ if err != nil {
+ fmt.Println("mapstruct error")
+ return err
+ }
+ anchor = anchor + "/" + cav.Version
+ }
+ }
+ url, err = GetURL(anchor)
+ if err != nil {
+ return err
+ }
+ resp, err := r.client.R().
+ Delete(url)
+ if err != nil {
+ fmt.Println(err)
+ return err
+ }
+ fmt.Println("URL:", anchor, "Response Code:", resp.StatusCode())
+ return nil
+}
+// GetURL reads the configuration file to get URL
+func GetURL(anchor string) (string, error) {
+ var baseUrl string
+ s := strings.Split(anchor, "/")
+ if len(s) < 1 {
+ return "", fmt.Errorf("Invalid Anchor")
+ }
+
+ switch s[0] {
+ case "cluster-providers":
+ if len(s) >= 5 && (s[4] == "networks" || s[4] == "provider-networks" || s[4] == "apply" || s[4] == "terminate") {
+ baseUrl = GetNcmURL()
+ } else {
+ baseUrl = GetClmURL()
+ }
+ case "controllers":
+ baseUrl = GetOrchestratorURL()
+ case "projects":
+ if len(s) >= 6 && s[5] == "network-controller-intent" {
+ baseUrl = GetOvnactionURL()
+ } else {
+ baseUrl = GetOrchestratorURL()
+ }
+ default:
+ return "", fmt.Errorf("Invalid Anchor")
+ }
+ return (baseUrl + "/" + anchor), nil
+}