From b07bd98bbfbccbb904a91d1c287064428ae17135 Mon Sep 17 00:00:00 2001 From: Parthvi Vala Date: Fri, 11 Feb 2022 18:23:16 +0530 Subject: [PATCH 01/10] First commit --- pkg/init/init.go | 42 ++++++++++++++++++++++++++++++++++++++++++ pkg/init/interface.go | 3 +++ 2 files changed, 45 insertions(+) diff --git a/pkg/init/init.go b/pkg/init/init.go index 5a0d4bc124f..b9c1d27b4df 100644 --- a/pkg/init/init.go +++ b/pkg/init/init.go @@ -2,8 +2,11 @@ package init import ( "fmt" + "github.com/devfile/library/pkg/devfile/parser/data/v2/common" + "gopkg.in/AlecAivazis/survey.v1" "net/url" "path/filepath" + "regexp" "strings" "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" @@ -184,3 +187,42 @@ func (o *InitClient) PersonalizeName(devfile parser.DevfileObj, flags map[string err := backend.PersonalizeName(devfile, flags) return err } + +func (o *InitClient) PersonalizeDevfileConfig(devfileobj parser.DevfileObj) error { + options := []string{ + "NOTHING - configuration is correct", + "Add new port", + "Add new environment variable", + } + components, err := devfileobj.Data.GetComponents(common.DevfileOptions{}) + if err != nil { + return err + } + var configChangeAnswer string + for _, component := range components { + if component.Container != nil { + for _, ep := range component.Container.Endpoints { + options = append(options, fmt.Sprintf("Delete port: %q", ep.TargetPort)) + } + for _, env := range component.Container.Env { + options = append(options, fmt.Sprintf("Delete environment variable %q", env.Name)) + } + } + } + + configChangeQuestion := &survey.Select{ + Message: "What configuration do you want change?", + Default: options[0], + Options: options, + } + survey.AskOne(configChangeQuestion, &configChangeAnswer) + + if strings.HasPrefix(configChangeAnswer, "Delete environment variable") { + re := regexp.MustCompile("\"(.*?)\"") + match := re.FindStringSubmatch(configChangeAnswer) + envToDelete := match[1] + devfileobj.RemoveEnvVars([]string{envToDelete}) + } + + return nil +} diff --git a/pkg/init/interface.go b/pkg/init/interface.go index 1ea190de164..a68023e1f63 100644 --- a/pkg/init/interface.go +++ b/pkg/init/interface.go @@ -37,4 +37,7 @@ type Client interface { // PersonalizeName updates a devfile name, depending on the flags. // The method will modify the devfile content and save the devfile on disk PersonalizeName(devfile parser.DevfileObj, flags map[string]string) error + + // PersonalizeDevfileConfig updates the env vars, and URL endpoints + PersonalizeDevfileConfig(devfile parser.DevfileObj) error } From 7364a3a699378287eaa2b5ad0f45cc9953e03e3b Mon Sep 17 00:00:00 2001 From: Parthvi Vala Date: Sat, 12 Feb 2022 00:03:53 +0530 Subject: [PATCH 02/10] Working attempt --- pkg/init/init.go | 116 +++++++++++++++++++++++++++++++++++---- pkg/odo/cli/init/init.go | 5 +- 2 files changed, 108 insertions(+), 13 deletions(-) diff --git a/pkg/init/init.go b/pkg/init/init.go index b9c1d27b4df..4911e1c16ea 100644 --- a/pkg/init/init.go +++ b/pkg/init/init.go @@ -3,10 +3,12 @@ package init import ( "fmt" "github.com/devfile/library/pkg/devfile/parser/data/v2/common" + color "github.com/gookit/color" "gopkg.in/AlecAivazis/survey.v1" "net/url" "path/filepath" "regexp" + "strconv" "strings" "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" @@ -189,6 +191,8 @@ func (o *InitClient) PersonalizeName(devfile parser.DevfileObj, flags map[string } func (o *InitClient) PersonalizeDevfileConfig(devfileobj parser.DevfileObj) error { + var ports = []string{} + var envs = map[string]string{} options := []string{ "NOTHING - configuration is correct", "Add new port", @@ -202,27 +206,115 @@ func (o *InitClient) PersonalizeDevfileConfig(devfileobj parser.DevfileObj) erro for _, component := range components { if component.Container != nil { for _, ep := range component.Container.Endpoints { - options = append(options, fmt.Sprintf("Delete port: %q", ep.TargetPort)) + ports = append(ports, strconv.Itoa(ep.TargetPort)) + options = append(options, fmt.Sprintf("Delete port: %q", strconv.Itoa(ep.TargetPort))) } for _, env := range component.Container.Env { + envs[env.Name] = env.Value options = append(options, fmt.Sprintf("Delete environment variable %q", env.Name)) } } } - configChangeQuestion := &survey.Select{ - Message: "What configuration do you want change?", - Default: options[0], - Options: options, + for configChangeAnswer != "NOTHING - configuration is correct" { + printConfiguration(ports, envs) + + configChangeQuestion := &survey.Select{ + Message: "What configuration do you want change?", + Default: options[0], + Options: options, + } + + err = survey.AskOne(configChangeQuestion, &configChangeAnswer, nil) + if err != nil { + return err + } + + if strings.HasPrefix(configChangeAnswer, "Delete port") { + re := regexp.MustCompile("\"(.*?)\"") + match := re.FindStringSubmatch(configChangeAnswer) + portToDelete := match[1] + + indexToDelete := -1 + for i, port := range ports { + if port == portToDelete { + indexToDelete = i + } + } + if indexToDelete == -1 { + panic(fmt.Sprintf("unable to delete port %q, not found", portToDelete)) + } + ports = append(ports[:indexToDelete], ports[indexToDelete+1:]...) + // Delete port from the devfile + err = devfileobj.SetPorts(ports...) + if err != nil { + return err + } + options = append(options, fmt.Sprintf("Delete port: %q", portToDelete)) + } else if strings.HasPrefix(configChangeAnswer, "Delete environment variable") { + re := regexp.MustCompile("\"(.*?)\"") + match := re.FindStringSubmatch(configChangeAnswer) + envToDelete := match[1] + if _, ok := envs[envToDelete]; !ok { + panic(fmt.Sprintf("unable to delete env %q, not found", envToDelete)) + } + delete(envs, envToDelete) + err = devfileobj.RemoveEnvVars([]string{envToDelete}) + if err != nil { + return err + } + } else if configChangeAnswer == "NOTHING - configuration is correct" { + // nothing to do + } else if configChangeAnswer == "Add new port" { + newPortQuestion := &survey.Input{ + Message: "Enter port number:", + } + var newPortAnswer string + survey.AskOne(newPortQuestion, &newPortAnswer, survey.Required) + // Ensure the newPortAnswer is not already present; otherwise it will cause a duplicate endpoint error + ports = append(ports, newPortAnswer) + err = devfileobj.SetPorts(newPortAnswer) + if err != nil { + return err + } + } else if configChangeAnswer == "Add new environment variable" { + newEnvNameQuesion := &survey.Input{ + Message: "Enter new environment variable name:", + } + var newEnvNameAnswer string + survey.AskOne(newEnvNameQuesion, &newEnvNameAnswer, survey.Required) + newEnvValueQuestion := &survey.Input{ + Message: fmt.Sprintf("Enter value for %q environment variable:", newEnvNameAnswer), + } + var newEnvValueAnswer string + survey.AskOne(newEnvValueQuestion, &newEnvValueAnswer, survey.Required) + envs[newEnvNameAnswer] = newEnvValueAnswer + err = devfileobj.AddEnvVars([]v1alpha2.EnvVar{ + { + Name: newEnvNameAnswer, + Value: newEnvValueAnswer, + }, + }) + if err != nil { + return err + } + options = append(options, fmt.Sprintf("Delete environment variable %q", newEnvNameAnswer)) + } else { + return fmt.Errorf("Unknown configuration selected %q", configChangeAnswer) + } } - survey.AskOne(configChangeQuestion, &configChangeAnswer) + return nil +} - if strings.HasPrefix(configChangeAnswer, "Delete environment variable") { - re := regexp.MustCompile("\"(.*?)\"") - match := re.FindStringSubmatch(configChangeAnswer) - envToDelete := match[1] - devfileobj.RemoveEnvVars([]string{envToDelete}) +func printConfiguration(ports []string, envs map[string]string) { + color.New(color.Bold, color.FgGreen).Println("Current component configuration:") + color.Greenln("Opened ports:") + for _, port := range ports { + color.New(color.Bold, color.FgWhite).Printf(" - %s\n", port) } - return nil + color.Greenln("Environment variables:") + for key, value := range envs { + color.New(color.Bold, color.FgWhite).Printf(" - %s = %s\n", key, value) + } } diff --git a/pkg/odo/cli/init/init.go b/pkg/odo/cli/init/init.go index e4923c9da81..dde0917487a 100644 --- a/pkg/odo/cli/init/init.go +++ b/pkg/odo/cli/init/init.go @@ -148,7 +148,10 @@ func (o *InitOptions) Run() (err error) { } scontext.SetComponentType(o.ctx, component.GetComponentTypeFromDevfileMetadata(devfileObj.Data.GetMetadata())) - + err = o.clientset.InitClient.PersonalizeDevfileConfig(devfileObj) + if err != nil { + return fmt.Errorf("Failed to configure devfile: %w", err) + } starterInfo, err := o.clientset.InitClient.SelectStarterProject(devfileObj, o.flags) if err != nil { return err From c6b9bf6a7df367c477d712a10a8f601e2714792f Mon Sep 17 00:00:00 2001 From: Parthvi Vala Date: Mon, 14 Feb 2022 11:05:31 +0530 Subject: [PATCH 03/10] Fix bugs --- pkg/init/init.go | 62 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 58 insertions(+), 4 deletions(-) diff --git a/pkg/init/init.go b/pkg/init/init.go index 4911e1c16ea..dedbdbbbfd3 100644 --- a/pkg/init/init.go +++ b/pkg/init/init.go @@ -246,11 +246,17 @@ func (o *InitClient) PersonalizeDevfileConfig(devfileobj parser.DevfileObj) erro } ports = append(ports[:indexToDelete], ports[indexToDelete+1:]...) // Delete port from the devfile - err = devfileobj.SetPorts(ports...) + err = RemovePort(devfileobj, portToDelete) if err != nil { return err } - options = append(options, fmt.Sprintf("Delete port: %q", portToDelete)) + // TODO: Delete port from the options + for i, opt := range options { + if opt == fmt.Sprintf("Delete port: %q", portToDelete) { + options = append(options[:i], options[i+1:]...) + break + } + } } else if strings.HasPrefix(configChangeAnswer, "Delete environment variable") { re := regexp.MustCompile("\"(.*?)\"") match := re.FindStringSubmatch(configChangeAnswer) @@ -263,8 +269,13 @@ func (o *InitClient) PersonalizeDevfileConfig(devfileobj parser.DevfileObj) erro if err != nil { return err } - } else if configChangeAnswer == "NOTHING - configuration is correct" { - // nothing to do + // TODO: Delete env from the options + for i, opt := range options { + if opt == fmt.Sprintf("Delete environment variable %q", envToDelete) { + options = append(options[:i], options[i+1:]...) + break + } + } } else if configChangeAnswer == "Add new port" { newPortQuestion := &survey.Input{ Message: "Enter port number:", @@ -272,23 +283,33 @@ func (o *InitClient) PersonalizeDevfileConfig(devfileobj parser.DevfileObj) erro var newPortAnswer string survey.AskOne(newPortQuestion, &newPortAnswer, survey.Required) // Ensure the newPortAnswer is not already present; otherwise it will cause a duplicate endpoint error + if duplicatePort(ports, newPortAnswer) { + color.Yellowln("Port already present. Moving on.") + continue + } ports = append(ports, newPortAnswer) err = devfileobj.SetPorts(newPortAnswer) if err != nil { return err } + options = append(options, fmt.Sprintf("Delete port: %q", newPortAnswer)) } else if configChangeAnswer == "Add new environment variable" { newEnvNameQuesion := &survey.Input{ Message: "Enter new environment variable name:", } + // Ask for env name var newEnvNameAnswer string survey.AskOne(newEnvNameQuesion, &newEnvNameAnswer, survey.Required) newEnvValueQuestion := &survey.Input{ Message: fmt.Sprintf("Enter value for %q environment variable:", newEnvNameAnswer), } + + // Ask for env value var newEnvValueAnswer string survey.AskOne(newEnvValueQuestion, &newEnvValueAnswer, survey.Required) envs[newEnvNameAnswer] = newEnvValueAnswer + + // Write the env to devfile err = devfileobj.AddEnvVars([]v1alpha2.EnvVar{ { Name: newEnvNameAnswer, @@ -298,7 +319,10 @@ func (o *InitClient) PersonalizeDevfileConfig(devfileobj parser.DevfileObj) erro if err != nil { return err } + // Append the env to list of options options = append(options, fmt.Sprintf("Delete environment variable %q", newEnvNameAnswer)) + } else if configChangeAnswer == "NOTHING - configuration is correct" { + // nothing to do } else { return fmt.Errorf("Unknown configuration selected %q", configChangeAnswer) } @@ -318,3 +342,33 @@ func printConfiguration(ports []string, envs map[string]string) { color.New(color.Bold, color.FgWhite).Printf(" - %s = %s\n", key, value) } } + +func duplicatePort(ports []string, port string) bool { + for _, p := range ports { + if p == port { + return true + // return fmt.Errorf("Port is already present; cannot use a duplicate port") + } + } + return false +} + +func RemovePort(devfileObj parser.DevfileObj, portToRemove string) error { + components, err := devfileObj.Data.GetComponents(common.DevfileOptions{}) + if err != nil { + return err + } + + for _, component := range components { + if component.Container != nil { + for i, ep := range component.Container.Endpoints { + if strconv.Itoa(ep.TargetPort) == portToRemove { + component.Container.Endpoints = append(component.Container.Endpoints[:i], component.Container.Endpoints[i+1:]...) + } + } + devfileObj.Data.UpdateComponent(component) + } + } + return devfileObj.WriteYamlDevfile() + +} From 65c3d769b2cad0791b498db3b319c0b9c0f24779 Mon Sep 17 00:00:00 2001 From: Parthvi Vala Date: Mon, 14 Feb 2022 18:03:29 +0530 Subject: [PATCH 04/10] Add option to select container while adding/removing port --- pkg/init/init.go | 100 ++++++++++-------- .../pkg/devfile/parser/configurables.go | 50 +++++++-- 2 files changed, 99 insertions(+), 51 deletions(-) diff --git a/pkg/init/init.go b/pkg/init/init.go index dedbdbbbfd3..d814d3f4ced 100644 --- a/pkg/init/init.go +++ b/pkg/init/init.go @@ -191,8 +191,11 @@ func (o *InitClient) PersonalizeName(devfile parser.DevfileObj, flags map[string } func (o *InitClient) PersonalizeDevfileConfig(devfileobj parser.DevfileObj) error { - var ports = []string{} + // var ports = []string{} var envs = map[string]string{} + var portsMap = map[string][]string{} + var deletePortMessage = "Delete port (container: %q): %q" + var deleteEnvMessage = "Delete environment variable: %q" options := []string{ "NOTHING - configuration is correct", "Add new port", @@ -202,22 +205,27 @@ func (o *InitClient) PersonalizeDevfileConfig(devfileobj parser.DevfileObj) erro if err != nil { return err } - var configChangeAnswer string for _, component := range components { if component.Container != nil { for _, ep := range component.Container.Endpoints { - ports = append(ports, strconv.Itoa(ep.TargetPort)) - options = append(options, fmt.Sprintf("Delete port: %q", strconv.Itoa(ep.TargetPort))) + if _, ok := portsMap[component.Name]; !ok { + portsMap[component.Name] = []string{strconv.Itoa(ep.TargetPort)} + } else { + portsMap[component.Name] = append(portsMap[component.Name], strconv.Itoa(ep.TargetPort)) + } + // ports = append(ports, strconv.Itoa(ep.TargetPort)) + options = append(options, fmt.Sprintf(deletePortMessage, component.Name, strconv.Itoa(ep.TargetPort))) } for _, env := range component.Container.Env { envs[env.Name] = env.Value - options = append(options, fmt.Sprintf("Delete environment variable %q", env.Name)) + options = append(options, fmt.Sprintf(deleteEnvMessage, env.Name)) } } } + var configChangeAnswer string for configChangeAnswer != "NOTHING - configuration is correct" { - printConfiguration(ports, envs) + printConfiguration(portsMap, envs) configChangeQuestion := &survey.Select{ Message: "What configuration do you want change?", @@ -232,27 +240,28 @@ func (o *InitClient) PersonalizeDevfileConfig(devfileobj parser.DevfileObj) erro if strings.HasPrefix(configChangeAnswer, "Delete port") { re := regexp.MustCompile("\"(.*?)\"") - match := re.FindStringSubmatch(configChangeAnswer) - portToDelete := match[1] + match := re.FindAllStringSubmatch(configChangeAnswer, -1) + containerName, portToDelete := match[0][1], match[1][1] - indexToDelete := -1 - for i, port := range ports { - if port == portToDelete { - indexToDelete = i - } - } - if indexToDelete == -1 { - panic(fmt.Sprintf("unable to delete port %q, not found", portToDelete)) + if _, ok := portsMap[containerName]; !ok && !parser.InArray(portsMap[containerName], portToDelete) { + log.Warningf("unable to delete port %q, not found", portToDelete) + continue } - ports = append(ports[:indexToDelete], ports[indexToDelete+1:]...) // Delete port from the devfile - err = RemovePort(devfileobj, portToDelete) + // err = RemovePort(devfileobj, portToDelete) + err = devfileobj.RemovePorts(map[string][]string{containerName: []string{portToDelete}}) if err != nil { return err } - // TODO: Delete port from the options + for i, port := range portsMap[containerName] { + if port == portToDelete { + portsMap[containerName] = append(portsMap[containerName][:i], portsMap[containerName][i+1:]...) + break + } + } + // Delete port from the options for i, opt := range options { - if opt == fmt.Sprintf("Delete port: %q", portToDelete) { + if opt == fmt.Sprintf(deletePortMessage, containerName, portToDelete) { options = append(options[:i], options[i+1:]...) break } @@ -262,37 +271,49 @@ func (o *InitClient) PersonalizeDevfileConfig(devfileobj parser.DevfileObj) erro match := re.FindStringSubmatch(configChangeAnswer) envToDelete := match[1] if _, ok := envs[envToDelete]; !ok { - panic(fmt.Sprintf("unable to delete env %q, not found", envToDelete)) + log.Warningf("unable to delete env %q, not found", envToDelete) } delete(envs, envToDelete) err = devfileobj.RemoveEnvVars([]string{envToDelete}) if err != nil { return err } - // TODO: Delete env from the options + // Delete env from the options for i, opt := range options { - if opt == fmt.Sprintf("Delete environment variable %q", envToDelete) { + if opt == fmt.Sprintf(deleteEnvMessage, envToDelete) { options = append(options[:i], options[i+1:]...) break } } } else if configChangeAnswer == "Add new port" { + var containers []string + for containerName, _ := range portsMap { + containers = append(containers, containerName) + } + containerNameQuestion := &survey.Select{ + Message: "Enter container name: ", + Options: containers, + } + var containerNameAnswer string + survey.AskOne(containerNameQuestion, &containerNameAnswer, survey.Required) + newPortQuestion := &survey.Input{ Message: "Enter port number:", } var newPortAnswer string survey.AskOne(newPortQuestion, &newPortAnswer, survey.Required) - // Ensure the newPortAnswer is not already present; otherwise it will cause a duplicate endpoint error - if duplicatePort(ports, newPortAnswer) { - color.Yellowln("Port already present. Moving on.") + + // Ensure the newPortAnswer is not already present; otherwise it will cause a duplicate endpoint error while parsing the devfile + if parser.InArray(portsMap[containerNameAnswer], newPortAnswer) { + log.Warningf("Port is %q already present in container %q.", newPortAnswer, containerNameAnswer) continue } - ports = append(ports, newPortAnswer) - err = devfileobj.SetPorts(newPortAnswer) + portsMap[containerNameAnswer] = append(portsMap[containerNameAnswer], newPortAnswer) + err = devfileobj.SetPorts(map[string][]string{containerNameAnswer: []string{newPortAnswer}}) if err != nil { return err } - options = append(options, fmt.Sprintf("Delete port: %q", newPortAnswer)) + options = append(options, fmt.Sprintf(deletePortMessage, containerNameAnswer, newPortAnswer)) } else if configChangeAnswer == "Add new environment variable" { newEnvNameQuesion := &survey.Input{ Message: "Enter new environment variable name:", @@ -320,7 +341,7 @@ func (o *InitClient) PersonalizeDevfileConfig(devfileobj parser.DevfileObj) erro return err } // Append the env to list of options - options = append(options, fmt.Sprintf("Delete environment variable %q", newEnvNameAnswer)) + options = append(options, fmt.Sprintf(deleteEnvMessage, newEnvNameAnswer)) } else if configChangeAnswer == "NOTHING - configuration is correct" { // nothing to do } else { @@ -330,11 +351,14 @@ func (o *InitClient) PersonalizeDevfileConfig(devfileobj parser.DevfileObj) erro return nil } -func printConfiguration(ports []string, envs map[string]string) { +func printConfiguration(portsMap map[string][]string, envs map[string]string) { color.New(color.Bold, color.FgGreen).Println("Current component configuration:") color.Greenln("Opened ports:") - for _, port := range ports { - color.New(color.Bold, color.FgWhite).Printf(" - %s\n", port) + for containerName, ports := range portsMap { + color.New(color.Bold, color.FgWhite).Printf(" - Container %q:\n", containerName) + for _, port := range ports { + color.New(color.FgWhite).Printf(" · %s\n", port) + } } color.Greenln("Environment variables:") @@ -343,16 +367,6 @@ func printConfiguration(ports []string, envs map[string]string) { } } -func duplicatePort(ports []string, port string) bool { - for _, p := range ports { - if p == port { - return true - // return fmt.Errorf("Port is already present; cannot use a duplicate port") - } - } - return false -} - func RemovePort(devfileObj parser.DevfileObj, portToRemove string) error { components, err := devfileObj.Data.GetComponents(common.DevfileOptions{}) if err != nil { diff --git a/vendor/github.com/devfile/library/pkg/devfile/parser/configurables.go b/vendor/github.com/devfile/library/pkg/devfile/parser/configurables.go index f25437ddf1e..b24f63e27be 100644 --- a/vendor/github.com/devfile/library/pkg/devfile/parser/configurables.go +++ b/vendor/github.com/devfile/library/pkg/devfile/parser/configurables.go @@ -61,16 +61,16 @@ func (d DevfileObj) RemoveEnvVars(keys []string) (err error) { } // SetPorts converts ports to endpoints, adds to a devfile -func (d DevfileObj) SetPorts(ports ...string) error { +func (d DevfileObj) SetPorts(portsMap map[string][]string) error { components, err := d.Data.GetComponents(common.DevfileOptions{}) if err != nil { return err } - endpoints, err := portsToEndpoints(ports...) - if err != nil { - return err - } for _, component := range components { + endpoints, err := portsToEndpoints(portsMap[component.Name]...) + if err != nil { + return err + } if component.Container != nil { component.Container.Endpoints = addEndpoints(component.Container.Endpoints, endpoints) d.Data.UpdateComponent(component) @@ -80,14 +80,18 @@ func (d DevfileObj) SetPorts(ports ...string) error { } // RemovePorts removes all container endpoints from a devfile -func (d DevfileObj) RemovePorts() error { +// {"runtime": [8080, 9000], "wildfly": [1295]} +func (d DevfileObj) RemovePorts(portsMap map[string][]string) error { components, err := d.Data.GetComponents(common.DevfileOptions{}) if err != nil { return err } for _, component := range components { if component.Container != nil { - component.Container.Endpoints = []v1.Endpoint{} + component.Container.Endpoints, err = RemovePortsFromList(component.Container.Endpoints,portsMap[component.Name]) + if err != nil { + return err + } d.Data.UpdateComponent(component) } } @@ -233,6 +237,36 @@ func GetContainerPortsFromStrings(ports []string) ([]corev1.ContainerPort, error return containerPorts, nil } +// RemovePortsFromList removes the ports based on the ports provided +// and returns a new list of Endpoint +func RemovePortsFromList(endpoints []v1.Endpoint, ports []string) ([]v1.Endpoint, error) { + // create an array of ports of the endpoints to easily search for port(s) + // to remove from the component + portList := []string{} + for _, ep := range endpoints{ + portList = append(portList, strconv.Itoa(ep.TargetPort)) + } + + // now check if the port(s) requested for removal exists in + // the ports set for the component + for _, port := range ports{ + if !InArray(portList, port){ + return nil, fmt.Errorf("unable to find port %q in the component",port) + } + } + + // finally, let's remove the port(s) requested by the user + newEndpointsList := []v1.Endpoint{} + for _, ep := range endpoints{ + // if the port is in the ports we skip it + if InArray(ports, strconv.Itoa(ep.TargetPort)){ + continue + } + newEndpointsList = append(newEndpointsList, ep) + } + return newEndpointsList, nil +} + // RemoveEnvVarsFromList removes the env variables based on the keys provided // and returns a new EnvVarList func RemoveEnvVarsFromList(envVarList []v1.EnvVar, keys []string) ([]v1.EnvVar, error) { @@ -244,7 +278,7 @@ func RemoveEnvVarsFromList(envVarList []v1.EnvVar, keys []string) ([]v1.EnvVar, } // now check if the environment variable(s) requested for removal exists in - // the env vars set for the component by odo + // the env vars set for the component for _, key := range keys { if !InArray(envVarListArray, key) { return nil, fmt.Errorf("unable to find environment variable %s in the component", key) From 419f5080144932faa7482edec120d12c123be894 Mon Sep 17 00:00:00 2001 From: Parthvi Vala Date: Mon, 14 Feb 2022 19:23:05 +0530 Subject: [PATCH 05/10] Update mock package --- pkg/init/mock.go | 109 ++++++++++++++++++++++++++--------------------- 1 file changed, 61 insertions(+), 48 deletions(-) diff --git a/pkg/init/mock.go b/pkg/init/mock.go index 0dba6051fa0..aa086a73b1a 100644 --- a/pkg/init/mock.go +++ b/pkg/init/mock.go @@ -5,38 +5,66 @@ package init import ( - reflect "reflect" - v1alpha2 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" parser "github.com/devfile/library/pkg/devfile/parser" gomock "github.com/golang/mock/gomock" backend "github.com/redhat-developer/odo/pkg/init/backend" + reflect "reflect" ) -// MockClient is a mock of Client interface. +// MockClient is a mock of Client interface type MockClient struct { ctrl *gomock.Controller recorder *MockClientMockRecorder } -// MockClientMockRecorder is the mock recorder for MockClient. +// MockClientMockRecorder is the mock recorder for MockClient type MockClientMockRecorder struct { mock *MockClient } -// NewMockClient creates a new mock instance. +// NewMockClient creates a new mock instance func NewMockClient(ctrl *gomock.Controller) *MockClient { mock := &MockClient{ctrl: ctrl} mock.recorder = &MockClientMockRecorder{mock} return mock } -// EXPECT returns an object that allows the caller to indicate expected use. +// EXPECT returns an object that allows the caller to indicate expected use func (m *MockClient) EXPECT() *MockClientMockRecorder { return m.recorder } -// DownloadDevfile mocks base method. +// Validate mocks base method +func (m *MockClient) Validate(flags map[string]string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Validate", flags) + ret0, _ := ret[0].(error) + return ret0 +} + +// Validate indicates an expected call of Validate +func (mr *MockClientMockRecorder) Validate(flags interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Validate", reflect.TypeOf((*MockClient)(nil).Validate), flags) +} + +// SelectDevfile mocks base method +func (m *MockClient) SelectDevfile(flags map[string]string) (*backend.DevfileLocation, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SelectDevfile", flags) + ret0, _ := ret[0].(*backend.DevfileLocation) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SelectDevfile indicates an expected call of SelectDevfile +func (mr *MockClientMockRecorder) SelectDevfile(flags interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SelectDevfile", reflect.TypeOf((*MockClient)(nil).SelectDevfile), flags) +} + +// DownloadDevfile mocks base method func (m *MockClient) DownloadDevfile(devfileLocation *backend.DevfileLocation, destDir string) (string, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "DownloadDevfile", devfileLocation, destDir) @@ -45,13 +73,28 @@ func (m *MockClient) DownloadDevfile(devfileLocation *backend.DevfileLocation, d return ret0, ret1 } -// DownloadDevfile indicates an expected call of DownloadDevfile. +// DownloadDevfile indicates an expected call of DownloadDevfile func (mr *MockClientMockRecorder) DownloadDevfile(devfileLocation, destDir interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DownloadDevfile", reflect.TypeOf((*MockClient)(nil).DownloadDevfile), devfileLocation, destDir) } -// DownloadStarterProject mocks base method. +// SelectStarterProject mocks base method +func (m *MockClient) SelectStarterProject(devfile parser.DevfileObj, flags map[string]string) (*v1alpha2.StarterProject, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SelectStarterProject", devfile, flags) + ret0, _ := ret[0].(*v1alpha2.StarterProject) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SelectStarterProject indicates an expected call of SelectStarterProject +func (mr *MockClientMockRecorder) SelectStarterProject(devfile, flags interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SelectStarterProject", reflect.TypeOf((*MockClient)(nil).SelectStarterProject), devfile, flags) +} + +// DownloadStarterProject mocks base method func (m *MockClient) DownloadStarterProject(project *v1alpha2.StarterProject, dest string) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "DownloadStarterProject", project, dest) @@ -59,13 +102,13 @@ func (m *MockClient) DownloadStarterProject(project *v1alpha2.StarterProject, de return ret0 } -// DownloadStarterProject indicates an expected call of DownloadStarterProject. +// DownloadStarterProject indicates an expected call of DownloadStarterProject func (mr *MockClientMockRecorder) DownloadStarterProject(project, dest interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DownloadStarterProject", reflect.TypeOf((*MockClient)(nil).DownloadStarterProject), project, dest) } -// PersonalizeName mocks base method. +// PersonalizeName mocks base method func (m *MockClient) PersonalizeName(devfile parser.DevfileObj, flags map[string]string) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "PersonalizeName", devfile, flags) @@ -73,52 +116,22 @@ func (m *MockClient) PersonalizeName(devfile parser.DevfileObj, flags map[string return ret0 } -// PersonalizeName indicates an expected call of PersonalizeName. +// PersonalizeName indicates an expected call of PersonalizeName func (mr *MockClientMockRecorder) PersonalizeName(devfile, flags interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PersonalizeName", reflect.TypeOf((*MockClient)(nil).PersonalizeName), devfile, flags) } -// SelectDevfile mocks base method. -func (m *MockClient) SelectDevfile(flags map[string]string) (*backend.DevfileLocation, error) { +// PersonalizeDevfileConfig mocks base method +func (m *MockClient) PersonalizeDevfileConfig(devfile parser.DevfileObj) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SelectDevfile", flags) - ret0, _ := ret[0].(*backend.DevfileLocation) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// SelectDevfile indicates an expected call of SelectDevfile. -func (mr *MockClientMockRecorder) SelectDevfile(flags interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SelectDevfile", reflect.TypeOf((*MockClient)(nil).SelectDevfile), flags) -} - -// SelectStarterProject mocks base method. -func (m *MockClient) SelectStarterProject(devfile parser.DevfileObj, flags map[string]string) (*v1alpha2.StarterProject, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SelectStarterProject", devfile, flags) - ret0, _ := ret[0].(*v1alpha2.StarterProject) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// SelectStarterProject indicates an expected call of SelectStarterProject. -func (mr *MockClientMockRecorder) SelectStarterProject(devfile, flags interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SelectStarterProject", reflect.TypeOf((*MockClient)(nil).SelectStarterProject), devfile, flags) -} - -// Validate mocks base method. -func (m *MockClient) Validate(flags map[string]string) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Validate", flags) + ret := m.ctrl.Call(m, "PersonalizeDevfileConfig", devfile) ret0, _ := ret[0].(error) return ret0 } -// Validate indicates an expected call of Validate. -func (mr *MockClientMockRecorder) Validate(flags interface{}) *gomock.Call { +// PersonalizeDevfileConfig indicates an expected call of PersonalizeDevfileConfig +func (mr *MockClientMockRecorder) PersonalizeDevfileConfig(devfile interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Validate", reflect.TypeOf((*MockClient)(nil).Validate), flags) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PersonalizeDevfileConfig", reflect.TypeOf((*MockClient)(nil).PersonalizeDevfileConfig), devfile) } From 1a60b6bf3518e4fb543b534e52d0bd8be0469d7b Mon Sep 17 00:00:00 2001 From: Parthvi Vala Date: Mon, 14 Feb 2022 19:24:24 +0530 Subject: [PATCH 06/10] Cleanup --- pkg/init/init.go | 46 +++++++++++----------------------------------- 1 file changed, 11 insertions(+), 35 deletions(-) diff --git a/pkg/init/init.go b/pkg/init/init.go index d814d3f4ced..86fa89ba28d 100644 --- a/pkg/init/init.go +++ b/pkg/init/init.go @@ -191,7 +191,6 @@ func (o *InitClient) PersonalizeName(devfile parser.DevfileObj, flags map[string } func (o *InitClient) PersonalizeDevfileConfig(devfileobj parser.DevfileObj) error { - // var ports = []string{} var envs = map[string]string{} var portsMap = map[string][]string{} var deletePortMessage = "Delete port (container: %q): %q" @@ -208,12 +207,7 @@ func (o *InitClient) PersonalizeDevfileConfig(devfileobj parser.DevfileObj) erro for _, component := range components { if component.Container != nil { for _, ep := range component.Container.Endpoints { - if _, ok := portsMap[component.Name]; !ok { - portsMap[component.Name] = []string{strconv.Itoa(ep.TargetPort)} - } else { - portsMap[component.Name] = append(portsMap[component.Name], strconv.Itoa(ep.TargetPort)) - } - // ports = append(ports, strconv.Itoa(ep.TargetPort)) + portsMap[component.Name] = append(portsMap[component.Name], strconv.Itoa(ep.TargetPort)) options = append(options, fmt.Sprintf(deletePortMessage, component.Name, strconv.Itoa(ep.TargetPort))) } for _, env := range component.Container.Env { @@ -243,16 +237,16 @@ func (o *InitClient) PersonalizeDevfileConfig(devfileobj parser.DevfileObj) erro match := re.FindAllStringSubmatch(configChangeAnswer, -1) containerName, portToDelete := match[0][1], match[1][1] - if _, ok := portsMap[containerName]; !ok && !parser.InArray(portsMap[containerName], portToDelete) { + if !parser.InArray(portsMap[containerName], portToDelete) { log.Warningf("unable to delete port %q, not found", portToDelete) continue } // Delete port from the devfile - // err = RemovePort(devfileobj, portToDelete) - err = devfileobj.RemovePorts(map[string][]string{containerName: []string{portToDelete}}) + err = devfileobj.RemovePorts(map[string][]string{containerName: {portToDelete}}) if err != nil { return err } + // Delete port from portsMap for i, port := range portsMap[containerName] { if port == portToDelete { portsMap[containerName] = append(portsMap[containerName][:i], portsMap[containerName][i+1:]...) @@ -273,11 +267,12 @@ func (o *InitClient) PersonalizeDevfileConfig(devfileobj parser.DevfileObj) erro if _, ok := envs[envToDelete]; !ok { log.Warningf("unable to delete env %q, not found", envToDelete) } - delete(envs, envToDelete) + err = devfileobj.RemoveEnvVars([]string{envToDelete}) if err != nil { return err } + delete(envs, envToDelete) // Delete env from the options for i, opt := range options { if opt == fmt.Sprintf(deleteEnvMessage, envToDelete) { @@ -308,11 +303,13 @@ func (o *InitClient) PersonalizeDevfileConfig(devfileobj parser.DevfileObj) erro log.Warningf("Port is %q already present in container %q.", newPortAnswer, containerNameAnswer) continue } - portsMap[containerNameAnswer] = append(portsMap[containerNameAnswer], newPortAnswer) + + // Add port err = devfileobj.SetPorts(map[string][]string{containerNameAnswer: []string{newPortAnswer}}) if err != nil { return err } + portsMap[containerNameAnswer] = append(portsMap[containerNameAnswer], newPortAnswer) options = append(options, fmt.Sprintf(deletePortMessage, containerNameAnswer, newPortAnswer)) } else if configChangeAnswer == "Add new environment variable" { newEnvNameQuesion := &survey.Input{ @@ -328,9 +325,8 @@ func (o *InitClient) PersonalizeDevfileConfig(devfileobj parser.DevfileObj) erro // Ask for env value var newEnvValueAnswer string survey.AskOne(newEnvValueQuestion, &newEnvValueAnswer, survey.Required) - envs[newEnvNameAnswer] = newEnvValueAnswer - // Write the env to devfile + // Add env var err = devfileobj.AddEnvVars([]v1alpha2.EnvVar{ { Name: newEnvNameAnswer, @@ -340,7 +336,7 @@ func (o *InitClient) PersonalizeDevfileConfig(devfileobj parser.DevfileObj) erro if err != nil { return err } - // Append the env to list of options + envs[newEnvNameAnswer] = newEnvValueAnswer options = append(options, fmt.Sprintf(deleteEnvMessage, newEnvNameAnswer)) } else if configChangeAnswer == "NOTHING - configuration is correct" { // nothing to do @@ -366,23 +362,3 @@ func printConfiguration(portsMap map[string][]string, envs map[string]string) { color.New(color.Bold, color.FgWhite).Printf(" - %s = %s\n", key, value) } } - -func RemovePort(devfileObj parser.DevfileObj, portToRemove string) error { - components, err := devfileObj.Data.GetComponents(common.DevfileOptions{}) - if err != nil { - return err - } - - for _, component := range components { - if component.Container != nil { - for i, ep := range component.Container.Endpoints { - if strconv.Itoa(ep.TargetPort) == portToRemove { - component.Container.Endpoints = append(component.Container.Endpoints[:i], component.Container.Endpoints[i+1:]...) - } - } - devfileObj.Data.UpdateComponent(component) - } - } - return devfileObj.WriteYamlDevfile() - -} From 45adb20fbfa75f11a6bb9396ef58e5acf4a3f4bd Mon Sep 17 00:00:00 2001 From: Parthvi Vala Date: Tue, 15 Feb 2022 11:05:18 +0530 Subject: [PATCH 07/10] Move PersonalizeDevfileConfig to Interactive backend Signed-off-by: Parthvi Vala --- pkg/init/backend/flags.go | 5 + pkg/init/backend/interactive.go | 179 ++++++++++++++++++++++++++++++++ pkg/init/backend/interface.go | 3 + pkg/init/init.go | 176 +------------------------------ 4 files changed, 188 insertions(+), 175 deletions(-) diff --git a/pkg/init/backend/flags.go b/pkg/init/backend/flags.go index 552541ae9c2..e1f6b078629 100644 --- a/pkg/init/backend/flags.go +++ b/pkg/init/backend/flags.go @@ -86,3 +86,8 @@ func (o *FlagsBackend) SelectStarterProject(devfile parser.DevfileObj, flags map func (o *FlagsBackend) PersonalizeName(devfile parser.DevfileObj, flags map[string]string) error { return devfile.SetMetadataName(flags[FLAG_NAME]) } + +// PersonalizeDevfileConfig updates the env vars, and URL endpoints +func (o *FlagsBackend) PersonalizeDevfileConfig(devfile parser.DevfileObj) error { + return nil +} diff --git a/pkg/init/backend/interactive.go b/pkg/init/backend/interactive.go index ea21ae67e75..88536b853de 100644 --- a/pkg/init/backend/interactive.go +++ b/pkg/init/backend/interactive.go @@ -2,6 +2,12 @@ package backend import ( "fmt" + "github.com/gookit/color" + "github.com/redhat-developer/odo/pkg/log" + "gopkg.in/AlecAivazis/survey.v1" + "regexp" + "strconv" + "strings" "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" "github.com/devfile/library/pkg/devfile/parser" @@ -103,3 +109,176 @@ func (o *InteractiveBackend) PersonalizeName(devfile parser.DevfileObj, flags ma } return devfile.SetMetadataName(name) } + +func (o *InteractiveBackend) PersonalizeDevfileConfig(devfileobj parser.DevfileObj) error { + var envs = map[string]string{} + var portsMap = map[string][]string{} + var deletePortMessage = "Delete port (container: %q): %q" + var deleteEnvMessage = "Delete environment variable: %q" + options := []string{ + "NOTHING - configuration is correct", + "Add new port", + "Add new environment variable", + } + components, err := devfileobj.Data.GetComponents(parsercommon.DevfileOptions{}) + if err != nil { + return err + } + for _, component := range components { + if component.Container != nil { + for _, ep := range component.Container.Endpoints { + portsMap[component.Name] = append(portsMap[component.Name], strconv.Itoa(ep.TargetPort)) + options = append(options, fmt.Sprintf(deletePortMessage, component.Name, strconv.Itoa(ep.TargetPort))) + } + for _, env := range component.Container.Env { + envs[env.Name] = env.Value + options = append(options, fmt.Sprintf(deleteEnvMessage, env.Name)) + } + } + } + + var configChangeAnswer string + for configChangeAnswer != "NOTHING - configuration is correct" { + printConfiguration(portsMap, envs) + + configChangeQuestion := &survey.Select{ + Message: "What configuration do you want change?", + Default: options[0], + Options: options, + } + + err = survey.AskOne(configChangeQuestion, &configChangeAnswer, nil) + if err != nil { + return err + } + + if strings.HasPrefix(configChangeAnswer, "Delete port") { + re := regexp.MustCompile("\"(.*?)\"") + match := re.FindAllStringSubmatch(configChangeAnswer, -1) + containerName, portToDelete := match[0][1], match[1][1] + + if !parser.InArray(portsMap[containerName], portToDelete) { + log.Warningf("unable to delete port %q, not found", portToDelete) + continue + } + // Delete port from the devfile + err = devfileobj.RemovePorts(map[string][]string{containerName: {portToDelete}}) + if err != nil { + return err + } + // Delete port from portsMap + for i, port := range portsMap[containerName] { + if port == portToDelete { + portsMap[containerName] = append(portsMap[containerName][:i], portsMap[containerName][i+1:]...) + break + } + } + // Delete port from the options + for i, opt := range options { + if opt == fmt.Sprintf(deletePortMessage, containerName, portToDelete) { + options = append(options[:i], options[i+1:]...) + break + } + } + } else if strings.HasPrefix(configChangeAnswer, "Delete environment variable") { + re := regexp.MustCompile("\"(.*?)\"") + match := re.FindStringSubmatch(configChangeAnswer) + envToDelete := match[1] + if _, ok := envs[envToDelete]; !ok { + log.Warningf("unable to delete env %q, not found", envToDelete) + } + + err = devfileobj.RemoveEnvVars([]string{envToDelete}) + if err != nil { + return err + } + delete(envs, envToDelete) + // Delete env from the options + for i, opt := range options { + if opt == fmt.Sprintf(deleteEnvMessage, envToDelete) { + options = append(options[:i], options[i+1:]...) + break + } + } + } else if configChangeAnswer == "Add new port" { + var containers []string + for containerName, _ := range portsMap { + containers = append(containers, containerName) + } + containerNameQuestion := &survey.Select{ + Message: "Enter container name: ", + Options: containers, + } + var containerNameAnswer string + survey.AskOne(containerNameQuestion, &containerNameAnswer, survey.Required) + + newPortQuestion := &survey.Input{ + Message: "Enter port number:", + } + var newPortAnswer string + survey.AskOne(newPortQuestion, &newPortAnswer, survey.Required) + + // Ensure the newPortAnswer is not already present; otherwise it will cause a duplicate endpoint error while parsing the devfile + if parser.InArray(portsMap[containerNameAnswer], newPortAnswer) { + log.Warningf("Port is %q already present in container %q.", newPortAnswer, containerNameAnswer) + continue + } + + // Add port + err = devfileobj.SetPorts(map[string][]string{containerNameAnswer: []string{newPortAnswer}}) + if err != nil { + return err + } + portsMap[containerNameAnswer] = append(portsMap[containerNameAnswer], newPortAnswer) + options = append(options, fmt.Sprintf(deletePortMessage, containerNameAnswer, newPortAnswer)) + } else if configChangeAnswer == "Add new environment variable" { + newEnvNameQuesion := &survey.Input{ + Message: "Enter new environment variable name:", + } + // Ask for env name + var newEnvNameAnswer string + survey.AskOne(newEnvNameQuesion, &newEnvNameAnswer, survey.Required) + newEnvValueQuestion := &survey.Input{ + Message: fmt.Sprintf("Enter value for %q environment variable:", newEnvNameAnswer), + } + + // Ask for env value + var newEnvValueAnswer string + survey.AskOne(newEnvValueQuestion, &newEnvValueAnswer, survey.Required) + + // Add env var + err = devfileobj.AddEnvVars([]v1alpha2.EnvVar{ + { + Name: newEnvNameAnswer, + Value: newEnvValueAnswer, + }, + }) + if err != nil { + return err + } + envs[newEnvNameAnswer] = newEnvValueAnswer + options = append(options, fmt.Sprintf(deleteEnvMessage, newEnvNameAnswer)) + } else if configChangeAnswer == "NOTHING - configuration is correct" { + // nothing to do + } else { + return fmt.Errorf("Unknown configuration selected %q", configChangeAnswer) + } + } + return nil +} + +func printConfiguration(portsMap map[string][]string, envs map[string]string) { + color.New(color.Bold, color.FgGreen).Println("Current component configuration:") + color.Greenln("Opened ports:") + for containerName, ports := range portsMap { + color.New(color.Bold, color.FgWhite).Printf(" - Container %q:\n", containerName) + for _, port := range ports { + color.New(color.FgWhite).Printf(" · %s\n", port) + } + } + + color.Greenln("Environment variables:") + for key, value := range envs { + color.New(color.Bold, color.FgWhite).Printf(" - %s = %s\n", key, value) + } +} diff --git a/pkg/init/backend/interface.go b/pkg/init/backend/interface.go index 11260d7f4fa..dfa461f97e1 100644 --- a/pkg/init/backend/interface.go +++ b/pkg/init/backend/interface.go @@ -22,4 +22,7 @@ type InitBackend interface { // PersonalizeName updates a devfile name, depending on the flags PersonalizeName(devfile parser.DevfileObj, flags map[string]string) error + + // PersonalizeDevfileConfig updates the env vars, and URL endpoints + PersonalizeDevfileConfig(devfile parser.DevfileObj) error } diff --git a/pkg/init/init.go b/pkg/init/init.go index 86fa89ba28d..f27528ae11f 100644 --- a/pkg/init/init.go +++ b/pkg/init/init.go @@ -2,13 +2,8 @@ package init import ( "fmt" - "github.com/devfile/library/pkg/devfile/parser/data/v2/common" - color "github.com/gookit/color" - "gopkg.in/AlecAivazis/survey.v1" "net/url" "path/filepath" - "regexp" - "strconv" "strings" "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" @@ -191,174 +186,5 @@ func (o *InitClient) PersonalizeName(devfile parser.DevfileObj, flags map[string } func (o *InitClient) PersonalizeDevfileConfig(devfileobj parser.DevfileObj) error { - var envs = map[string]string{} - var portsMap = map[string][]string{} - var deletePortMessage = "Delete port (container: %q): %q" - var deleteEnvMessage = "Delete environment variable: %q" - options := []string{ - "NOTHING - configuration is correct", - "Add new port", - "Add new environment variable", - } - components, err := devfileobj.Data.GetComponents(common.DevfileOptions{}) - if err != nil { - return err - } - for _, component := range components { - if component.Container != nil { - for _, ep := range component.Container.Endpoints { - portsMap[component.Name] = append(portsMap[component.Name], strconv.Itoa(ep.TargetPort)) - options = append(options, fmt.Sprintf(deletePortMessage, component.Name, strconv.Itoa(ep.TargetPort))) - } - for _, env := range component.Container.Env { - envs[env.Name] = env.Value - options = append(options, fmt.Sprintf(deleteEnvMessage, env.Name)) - } - } - } - - var configChangeAnswer string - for configChangeAnswer != "NOTHING - configuration is correct" { - printConfiguration(portsMap, envs) - - configChangeQuestion := &survey.Select{ - Message: "What configuration do you want change?", - Default: options[0], - Options: options, - } - - err = survey.AskOne(configChangeQuestion, &configChangeAnswer, nil) - if err != nil { - return err - } - - if strings.HasPrefix(configChangeAnswer, "Delete port") { - re := regexp.MustCompile("\"(.*?)\"") - match := re.FindAllStringSubmatch(configChangeAnswer, -1) - containerName, portToDelete := match[0][1], match[1][1] - - if !parser.InArray(portsMap[containerName], portToDelete) { - log.Warningf("unable to delete port %q, not found", portToDelete) - continue - } - // Delete port from the devfile - err = devfileobj.RemovePorts(map[string][]string{containerName: {portToDelete}}) - if err != nil { - return err - } - // Delete port from portsMap - for i, port := range portsMap[containerName] { - if port == portToDelete { - portsMap[containerName] = append(portsMap[containerName][:i], portsMap[containerName][i+1:]...) - break - } - } - // Delete port from the options - for i, opt := range options { - if opt == fmt.Sprintf(deletePortMessage, containerName, portToDelete) { - options = append(options[:i], options[i+1:]...) - break - } - } - } else if strings.HasPrefix(configChangeAnswer, "Delete environment variable") { - re := regexp.MustCompile("\"(.*?)\"") - match := re.FindStringSubmatch(configChangeAnswer) - envToDelete := match[1] - if _, ok := envs[envToDelete]; !ok { - log.Warningf("unable to delete env %q, not found", envToDelete) - } - - err = devfileobj.RemoveEnvVars([]string{envToDelete}) - if err != nil { - return err - } - delete(envs, envToDelete) - // Delete env from the options - for i, opt := range options { - if opt == fmt.Sprintf(deleteEnvMessage, envToDelete) { - options = append(options[:i], options[i+1:]...) - break - } - } - } else if configChangeAnswer == "Add new port" { - var containers []string - for containerName, _ := range portsMap { - containers = append(containers, containerName) - } - containerNameQuestion := &survey.Select{ - Message: "Enter container name: ", - Options: containers, - } - var containerNameAnswer string - survey.AskOne(containerNameQuestion, &containerNameAnswer, survey.Required) - - newPortQuestion := &survey.Input{ - Message: "Enter port number:", - } - var newPortAnswer string - survey.AskOne(newPortQuestion, &newPortAnswer, survey.Required) - - // Ensure the newPortAnswer is not already present; otherwise it will cause a duplicate endpoint error while parsing the devfile - if parser.InArray(portsMap[containerNameAnswer], newPortAnswer) { - log.Warningf("Port is %q already present in container %q.", newPortAnswer, containerNameAnswer) - continue - } - - // Add port - err = devfileobj.SetPorts(map[string][]string{containerNameAnswer: []string{newPortAnswer}}) - if err != nil { - return err - } - portsMap[containerNameAnswer] = append(portsMap[containerNameAnswer], newPortAnswer) - options = append(options, fmt.Sprintf(deletePortMessage, containerNameAnswer, newPortAnswer)) - } else if configChangeAnswer == "Add new environment variable" { - newEnvNameQuesion := &survey.Input{ - Message: "Enter new environment variable name:", - } - // Ask for env name - var newEnvNameAnswer string - survey.AskOne(newEnvNameQuesion, &newEnvNameAnswer, survey.Required) - newEnvValueQuestion := &survey.Input{ - Message: fmt.Sprintf("Enter value for %q environment variable:", newEnvNameAnswer), - } - - // Ask for env value - var newEnvValueAnswer string - survey.AskOne(newEnvValueQuestion, &newEnvValueAnswer, survey.Required) - - // Add env var - err = devfileobj.AddEnvVars([]v1alpha2.EnvVar{ - { - Name: newEnvNameAnswer, - Value: newEnvValueAnswer, - }, - }) - if err != nil { - return err - } - envs[newEnvNameAnswer] = newEnvValueAnswer - options = append(options, fmt.Sprintf(deleteEnvMessage, newEnvNameAnswer)) - } else if configChangeAnswer == "NOTHING - configuration is correct" { - // nothing to do - } else { - return fmt.Errorf("Unknown configuration selected %q", configChangeAnswer) - } - } - return nil -} - -func printConfiguration(portsMap map[string][]string, envs map[string]string) { - color.New(color.Bold, color.FgGreen).Println("Current component configuration:") - color.Greenln("Opened ports:") - for containerName, ports := range portsMap { - color.New(color.Bold, color.FgWhite).Printf(" - Container %q:\n", containerName) - for _, port := range ports { - color.New(color.FgWhite).Printf(" · %s\n", port) - } - } - - color.Greenln("Environment variables:") - for key, value := range envs { - color.New(color.Bold, color.FgWhite).Printf(" - %s = %s\n", key, value) - } + return o.interactiveBackend.PersonalizeDevfileConfig(devfileobj) } From 2000ad6865f6e83fcd7ae684bc5b1adf1c80ba32 Mon Sep 17 00:00:00 2001 From: Parthvi Vala Date: Tue, 15 Feb 2022 11:29:26 +0530 Subject: [PATCH 08/10] Use map instead of regex --- pkg/init/backend/interactive.go | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/pkg/init/backend/interactive.go b/pkg/init/backend/interactive.go index 88536b853de..4d82b312217 100644 --- a/pkg/init/backend/interactive.go +++ b/pkg/init/backend/interactive.go @@ -5,7 +5,6 @@ import ( "github.com/gookit/color" "github.com/redhat-developer/odo/pkg/log" "gopkg.in/AlecAivazis/survey.v1" - "regexp" "strconv" "strings" @@ -137,6 +136,11 @@ func (o *InteractiveBackend) PersonalizeDevfileConfig(devfileobj parser.DevfileO } } + var deleteMap = map[string][2]string{ + "Delete port (container: \"runtime\": 8000": {"runtime", "8000"}, + "Delete environment variable: \"A\"": {"A"}, + } + var configChangeAnswer string for configChangeAnswer != "NOTHING - configuration is correct" { printConfiguration(portsMap, envs) @@ -153,9 +157,7 @@ func (o *InteractiveBackend) PersonalizeDevfileConfig(devfileobj parser.DevfileO } if strings.HasPrefix(configChangeAnswer, "Delete port") { - re := regexp.MustCompile("\"(.*?)\"") - match := re.FindAllStringSubmatch(configChangeAnswer, -1) - containerName, portToDelete := match[0][1], match[1][1] + containerName, portToDelete := deleteMap[configChangeAnswer][0], deleteMap[configChangeAnswer][1] if !parser.InArray(portsMap[containerName], portToDelete) { log.Warningf("unable to delete port %q, not found", portToDelete) @@ -180,10 +182,9 @@ func (o *InteractiveBackend) PersonalizeDevfileConfig(devfileobj parser.DevfileO break } } + delete(deleteMap, fmt.Sprintf(deletePortMessage, containerName, portToDelete)) } else if strings.HasPrefix(configChangeAnswer, "Delete environment variable") { - re := regexp.MustCompile("\"(.*?)\"") - match := re.FindStringSubmatch(configChangeAnswer) - envToDelete := match[1] + envToDelete := deleteMap[configChangeAnswer][0] if _, ok := envs[envToDelete]; !ok { log.Warningf("unable to delete env %q, not found", envToDelete) } @@ -200,6 +201,7 @@ func (o *InteractiveBackend) PersonalizeDevfileConfig(devfileobj parser.DevfileO break } } + delete(deleteMap, fmt.Sprintf(deleteEnvMessage, envToDelete)) } else if configChangeAnswer == "Add new port" { var containers []string for containerName, _ := range portsMap { @@ -231,6 +233,7 @@ func (o *InteractiveBackend) PersonalizeDevfileConfig(devfileobj parser.DevfileO } portsMap[containerNameAnswer] = append(portsMap[containerNameAnswer], newPortAnswer) options = append(options, fmt.Sprintf(deletePortMessage, containerNameAnswer, newPortAnswer)) + deleteMap[fmt.Sprintf(deletePortMessage, containerNameAnswer, newPortAnswer)] = [2]string{containerNameAnswer, newPortAnswer} } else if configChangeAnswer == "Add new environment variable" { newEnvNameQuesion := &survey.Input{ Message: "Enter new environment variable name:", @@ -258,6 +261,7 @@ func (o *InteractiveBackend) PersonalizeDevfileConfig(devfileobj parser.DevfileO } envs[newEnvNameAnswer] = newEnvValueAnswer options = append(options, fmt.Sprintf(deleteEnvMessage, newEnvNameAnswer)) + deleteMap[fmt.Sprintf(deleteEnvMessage, newEnvNameAnswer)] = [2]string{newEnvNameAnswer} } else if configChangeAnswer == "NOTHING - configuration is correct" { // nothing to do } else { From fc0fd675e06f858f08bf914f35734bf77ec633bb Mon Sep 17 00:00:00 2001 From: Parthvi Vala Date: Tue, 15 Feb 2022 12:50:01 +0530 Subject: [PATCH 09/10] Use a second array to track options instead of maps --- pkg/init/backend/interactive.go | 35 ++++++++++++++++----------------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/pkg/init/backend/interactive.go b/pkg/init/backend/interactive.go index 4d82b312217..46387a1da19 100644 --- a/pkg/init/backend/interactive.go +++ b/pkg/init/backend/interactive.go @@ -4,10 +4,10 @@ import ( "fmt" "github.com/gookit/color" "github.com/redhat-developer/odo/pkg/log" - "gopkg.in/AlecAivazis/survey.v1" "strconv" "strings" + "github.com/AlecAivazis/survey/v2" "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" "github.com/devfile/library/pkg/devfile/parser" parsercommon "github.com/devfile/library/pkg/devfile/parser/data/v2/common" @@ -119,6 +119,7 @@ func (o *InteractiveBackend) PersonalizeDevfileConfig(devfileobj parser.DevfileO "Add new port", "Add new environment variable", } + options2 := [][2]string{{""}, {""}, {""}} components, err := devfileobj.Data.GetComponents(parsercommon.DevfileOptions{}) if err != nil { return err @@ -128,20 +129,18 @@ func (o *InteractiveBackend) PersonalizeDevfileConfig(devfileobj parser.DevfileO for _, ep := range component.Container.Endpoints { portsMap[component.Name] = append(portsMap[component.Name], strconv.Itoa(ep.TargetPort)) options = append(options, fmt.Sprintf(deletePortMessage, component.Name, strconv.Itoa(ep.TargetPort))) + options2 = append(options2, [2]string{component.Name, strconv.Itoa(ep.TargetPort)}) } for _, env := range component.Container.Env { envs[env.Name] = env.Value options = append(options, fmt.Sprintf(deleteEnvMessage, env.Name)) + options2 = append(options2, [2]string{env.Name}) } } } - var deleteMap = map[string][2]string{ - "Delete port (container: \"runtime\": 8000": {"runtime", "8000"}, - "Delete environment variable: \"A\"": {"A"}, - } - var configChangeAnswer string + var configChangeIndex int for configChangeAnswer != "NOTHING - configuration is correct" { printConfiguration(portsMap, envs) @@ -151,14 +150,14 @@ func (o *InteractiveBackend) PersonalizeDevfileConfig(devfileobj parser.DevfileO Options: options, } - err = survey.AskOne(configChangeQuestion, &configChangeAnswer, nil) + err = survey.AskOne(configChangeQuestion, &configChangeIndex) if err != nil { return err } + configChangeAnswer = options[configChangeIndex] if strings.HasPrefix(configChangeAnswer, "Delete port") { - containerName, portToDelete := deleteMap[configChangeAnswer][0], deleteMap[configChangeAnswer][1] - + containerName, portToDelete := options2[configChangeIndex][0], options2[configChangeIndex][1] if !parser.InArray(portsMap[containerName], portToDelete) { log.Warningf("unable to delete port %q, not found", portToDelete) continue @@ -179,12 +178,12 @@ func (o *InteractiveBackend) PersonalizeDevfileConfig(devfileobj parser.DevfileO for i, opt := range options { if opt == fmt.Sprintf(deletePortMessage, containerName, portToDelete) { options = append(options[:i], options[i+1:]...) + options2 = append(options2[:i], options2[i+1:]...) break } } - delete(deleteMap, fmt.Sprintf(deletePortMessage, containerName, portToDelete)) } else if strings.HasPrefix(configChangeAnswer, "Delete environment variable") { - envToDelete := deleteMap[configChangeAnswer][0] + envToDelete := options2[configChangeIndex][0] if _, ok := envs[envToDelete]; !ok { log.Warningf("unable to delete env %q, not found", envToDelete) } @@ -198,10 +197,10 @@ func (o *InteractiveBackend) PersonalizeDevfileConfig(devfileobj parser.DevfileO for i, opt := range options { if opt == fmt.Sprintf(deleteEnvMessage, envToDelete) { options = append(options[:i], options[i+1:]...) + options2 = append(options2[:i], options2[i+1:]...) break } } - delete(deleteMap, fmt.Sprintf(deleteEnvMessage, envToDelete)) } else if configChangeAnswer == "Add new port" { var containers []string for containerName, _ := range portsMap { @@ -212,13 +211,13 @@ func (o *InteractiveBackend) PersonalizeDevfileConfig(devfileobj parser.DevfileO Options: containers, } var containerNameAnswer string - survey.AskOne(containerNameQuestion, &containerNameAnswer, survey.Required) + survey.AskOne(containerNameQuestion, &containerNameAnswer) newPortQuestion := &survey.Input{ Message: "Enter port number:", } var newPortAnswer string - survey.AskOne(newPortQuestion, &newPortAnswer, survey.Required) + survey.AskOne(newPortQuestion, &newPortAnswer) // Ensure the newPortAnswer is not already present; otherwise it will cause a duplicate endpoint error while parsing the devfile if parser.InArray(portsMap[containerNameAnswer], newPortAnswer) { @@ -233,21 +232,21 @@ func (o *InteractiveBackend) PersonalizeDevfileConfig(devfileobj parser.DevfileO } portsMap[containerNameAnswer] = append(portsMap[containerNameAnswer], newPortAnswer) options = append(options, fmt.Sprintf(deletePortMessage, containerNameAnswer, newPortAnswer)) - deleteMap[fmt.Sprintf(deletePortMessage, containerNameAnswer, newPortAnswer)] = [2]string{containerNameAnswer, newPortAnswer} + options2 = append(options2, [2]string{containerNameAnswer, newPortAnswer}) } else if configChangeAnswer == "Add new environment variable" { newEnvNameQuesion := &survey.Input{ Message: "Enter new environment variable name:", } // Ask for env name var newEnvNameAnswer string - survey.AskOne(newEnvNameQuesion, &newEnvNameAnswer, survey.Required) + survey.AskOne(newEnvNameQuesion, &newEnvNameAnswer) newEnvValueQuestion := &survey.Input{ Message: fmt.Sprintf("Enter value for %q environment variable:", newEnvNameAnswer), } // Ask for env value var newEnvValueAnswer string - survey.AskOne(newEnvValueQuestion, &newEnvValueAnswer, survey.Required) + survey.AskOne(newEnvValueQuestion, &newEnvValueAnswer) // Add env var err = devfileobj.AddEnvVars([]v1alpha2.EnvVar{ @@ -261,7 +260,7 @@ func (o *InteractiveBackend) PersonalizeDevfileConfig(devfileobj parser.DevfileO } envs[newEnvNameAnswer] = newEnvValueAnswer options = append(options, fmt.Sprintf(deleteEnvMessage, newEnvNameAnswer)) - deleteMap[fmt.Sprintf(deleteEnvMessage, newEnvNameAnswer)] = [2]string{newEnvNameAnswer} + options2 = append(options2, [2]string{newEnvNameAnswer}) } else if configChangeAnswer == "NOTHING - configuration is correct" { // nothing to do } else { From 331dc198f098bbd4f97d681ea4fb7160fc2b2cd4 Mon Sep 17 00:00:00 2001 From: Parthvi Vala Date: Tue, 15 Feb 2022 17:35:54 +0530 Subject: [PATCH 10/10] Move survey questions from backend to asker pkg --- pkg/init/asker/asker.go | 56 +++++++++++++++++++++++++++-- pkg/init/asker/interface.go | 9 +++++ pkg/init/backend/interactive.go | 63 ++++++++++++++------------------- 3 files changed, 89 insertions(+), 39 deletions(-) diff --git a/pkg/init/asker/asker.go b/pkg/init/asker/asker.go index d28eba412ae..d5e8278d706 100644 --- a/pkg/init/asker/asker.go +++ b/pkg/init/asker/asker.go @@ -1,9 +1,9 @@ package asker import ( - "sort" - + "fmt" "github.com/AlecAivazis/survey/v2" + "sort" "github.com/redhat-developer/odo/pkg/catalog" ) @@ -77,3 +77,55 @@ func (o *Survey) AskName(defaultName string) (string, error) { } return answer, nil } + +func (o *Survey) AskAddPort(containers []string) (containerNameAnswer, newPortAnswer string, err error) { + containerNameQuestion := &survey.Select{ + Message: "Enter container name: ", + Options: containers, + } + err = survey.AskOne(containerNameQuestion, &containerNameAnswer) + if err != nil { + return + } + newPortQuestion := &survey.Input{ + Message: "Enter port number:", + } + err = survey.AskOne(newPortQuestion, &newPortAnswer) + if err != nil { + return + } + return +} + +func (o *Survey) AskAddEnvVar() (newEnvNameAnswer, newEnvValueAnswer string, err error) { + newEnvNameQuesion := &survey.Input{ + Message: "Enter new environment variable name:", + } + // Ask for env name + survey.AskOne(newEnvNameQuesion, &newEnvNameAnswer) + newEnvValueQuestion := &survey.Input{ + Message: fmt.Sprintf("Enter value for %q environment variable:", newEnvNameAnswer), + } + + // Ask for env value + err = survey.AskOne(newEnvValueQuestion, &newEnvValueAnswer) + if err != nil { + return + } + return +} + +func (o *Survey) AskPersonalizeConfiguration(options []string) (configChangeAnswer string, configChangeIndex int, err error) { + configChangeQuestion := &survey.Select{ + Message: "What configuration do you want change?", + Default: options[0], + Options: options, + } + + err = survey.AskOne(configChangeQuestion, &configChangeIndex) + if err != nil { + return + } + configChangeAnswer = options[configChangeIndex] + return +} diff --git a/pkg/init/asker/interface.go b/pkg/init/asker/interface.go index 143d7278489..76581d44513 100644 --- a/pkg/init/asker/interface.go +++ b/pkg/init/asker/interface.go @@ -19,4 +19,13 @@ type Asker interface { // AskName asks for a devfile component name AskName(defaultName string) (string, error) + + // AskPersonalizeConfiguration asks the configuration user wants to change + AskPersonalizeConfiguration(options []string) (configChangeAnswer string, configChangeIndex int, err error) + + // AskAddPort asks the container name and port that user wants to add + AskAddPort(containers []string) (containerNameAnswer, newPortAnswer string, err error) + + // AskAddEnvVar asks the key and value for env var + AskAddEnvVar() (newEnvNameAnswer, newEnvValueAnswer string, err error) } diff --git a/pkg/init/backend/interactive.go b/pkg/init/backend/interactive.go index 46387a1da19..327fb8e3b50 100644 --- a/pkg/init/backend/interactive.go +++ b/pkg/init/backend/interactive.go @@ -7,7 +7,6 @@ import ( "strconv" "strings" - "github.com/AlecAivazis/survey/v2" "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" "github.com/devfile/library/pkg/devfile/parser" parsercommon "github.com/devfile/library/pkg/devfile/parser/data/v2/common" @@ -109,7 +108,19 @@ func (o *InteractiveBackend) PersonalizeName(devfile parser.DevfileObj, flags ma return devfile.SetMetadataName(name) } +// type devfileConfig struct { +// // ops will be add or remove +// Ops string +// // kind will be port or env var +// Kind string +// // key will be container name in case of port ops, and env var key in case of env var +// Key string +// // value will be an array of ports in case of port ops, and env var value in case of env var +// Value string +// } + func (o *InteractiveBackend) PersonalizeDevfileConfig(devfileobj parser.DevfileObj) error { + // var d devfileConfig var envs = map[string]string{} var portsMap = map[string][]string{} var deletePortMessage = "Delete port (container: %q): %q" @@ -144,17 +155,7 @@ func (o *InteractiveBackend) PersonalizeDevfileConfig(devfileobj parser.DevfileO for configChangeAnswer != "NOTHING - configuration is correct" { printConfiguration(portsMap, envs) - configChangeQuestion := &survey.Select{ - Message: "What configuration do you want change?", - Default: options[0], - Options: options, - } - - err = survey.AskOne(configChangeQuestion, &configChangeIndex) - if err != nil { - return err - } - configChangeAnswer = options[configChangeIndex] + configChangeAnswer, configChangeIndex, err = o.asker.AskPersonalizeConfiguration(options) if strings.HasPrefix(configChangeAnswer, "Delete port") { containerName, portToDelete := options2[configChangeIndex][0], options2[configChangeIndex][1] @@ -162,6 +163,7 @@ func (o *InteractiveBackend) PersonalizeDevfileConfig(devfileobj parser.DevfileO log.Warningf("unable to delete port %q, not found", portToDelete) continue } + // Delete port from the devfile err = devfileobj.RemovePorts(map[string][]string{containerName: {portToDelete}}) if err != nil { @@ -183,6 +185,12 @@ func (o *InteractiveBackend) PersonalizeDevfileConfig(devfileobj parser.DevfileO } } } else if strings.HasPrefix(configChangeAnswer, "Delete environment variable") { + // d = devfileConfig{ + // Ops: "Delete", + // Kind: "EnvVar", + // Key: "", + // Value: "", + // } envToDelete := options2[configChangeIndex][0] if _, ok := envs[envToDelete]; !ok { log.Warningf("unable to delete env %q, not found", envToDelete) @@ -203,22 +211,14 @@ func (o *InteractiveBackend) PersonalizeDevfileConfig(devfileobj parser.DevfileO } } else if configChangeAnswer == "Add new port" { var containers []string + var containerNameAnswer, newPortAnswer string for containerName, _ := range portsMap { containers = append(containers, containerName) } - containerNameQuestion := &survey.Select{ - Message: "Enter container name: ", - Options: containers, - } - var containerNameAnswer string - survey.AskOne(containerNameQuestion, &containerNameAnswer) - - newPortQuestion := &survey.Input{ - Message: "Enter port number:", + containerNameAnswer, newPortAnswer, err = o.asker.AskAddPort(containers) + if err != nil { + return err } - var newPortAnswer string - survey.AskOne(newPortQuestion, &newPortAnswer) - // Ensure the newPortAnswer is not already present; otherwise it will cause a duplicate endpoint error while parsing the devfile if parser.InArray(portsMap[containerNameAnswer], newPortAnswer) { log.Warningf("Port is %q already present in container %q.", newPortAnswer, containerNameAnswer) @@ -234,19 +234,8 @@ func (o *InteractiveBackend) PersonalizeDevfileConfig(devfileobj parser.DevfileO options = append(options, fmt.Sprintf(deletePortMessage, containerNameAnswer, newPortAnswer)) options2 = append(options2, [2]string{containerNameAnswer, newPortAnswer}) } else if configChangeAnswer == "Add new environment variable" { - newEnvNameQuesion := &survey.Input{ - Message: "Enter new environment variable name:", - } - // Ask for env name - var newEnvNameAnswer string - survey.AskOne(newEnvNameQuesion, &newEnvNameAnswer) - newEnvValueQuestion := &survey.Input{ - Message: fmt.Sprintf("Enter value for %q environment variable:", newEnvNameAnswer), - } - - // Ask for env value - var newEnvValueAnswer string - survey.AskOne(newEnvValueQuestion, &newEnvValueAnswer) + var newEnvNameAnswer, newEnvValueAnswer string + newEnvNameAnswer, newEnvValueAnswer, err = o.asker.AskAddEnvVar() // Add env var err = devfileobj.AddEnvVars([]v1alpha2.EnvVar{