Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft: Ask for Devfile Personalization #5458

Closed
wants to merge 10 commits into from
56 changes: 54 additions & 2 deletions pkg/init/asker/asker.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package asker

import (
"sort"

"fmt"
"github.com/AlecAivazis/survey/v2"
"sort"

"github.com/redhat-developer/odo/pkg/catalog"
)
Expand Down Expand Up @@ -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
}
9 changes: 9 additions & 0 deletions pkg/init/asker/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
5 changes: 5 additions & 0 deletions pkg/init/backend/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
171 changes: 171 additions & 0 deletions pkg/init/backend/interactive.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ package backend

import (
"fmt"
"github.com/gookit/color"
"github.com/redhat-developer/odo/pkg/log"
"strconv"
"strings"

"github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
"github.com/devfile/library/pkg/devfile/parser"
Expand Down Expand Up @@ -103,3 +107,170 @@ 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"
var deleteEnvMessage = "Delete environment variable: %q"
options := []string{
"NOTHING - configuration is correct",
"Add new port",
"Add new environment variable",
}
options2 := [][2]string{{""}, {""}, {""}}
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)))
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 configChangeAnswer string
var configChangeIndex int
for configChangeAnswer != "NOTHING - configuration is correct" {
printConfiguration(portsMap, envs)

configChangeAnswer, configChangeIndex, err = o.asker.AskPersonalizeConfiguration(options)

if strings.HasPrefix(configChangeAnswer, "Delete port") {
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
}

// 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:]...)
options2 = append(options2[:i], options2[i+1:]...)
break
}
}
} 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)
}

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:]...)
options2 = append(options2[:i], options2[i+1:]...)
break
}
}
} else if configChangeAnswer == "Add new port" {
var containers []string
var containerNameAnswer, newPortAnswer string
for containerName, _ := range portsMap {
containers = append(containers, containerName)
}
containerNameAnswer, newPortAnswer, err = o.asker.AskAddPort(containers)
if err != nil {
return err
}
// 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))
options2 = append(options2, [2]string{containerNameAnswer, newPortAnswer})
} else if configChangeAnswer == "Add new environment variable" {
var newEnvNameAnswer, newEnvValueAnswer string
newEnvNameAnswer, newEnvValueAnswer, err = o.asker.AskAddEnvVar()

// 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))
options2 = append(options2, [2]string{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)
}
}
3 changes: 3 additions & 0 deletions pkg/init/backend/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
4 changes: 4 additions & 0 deletions pkg/init/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,3 +184,7 @@ 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 {
return o.interactiveBackend.PersonalizeDevfileConfig(devfileobj)
}
3 changes: 3 additions & 0 deletions pkg/init/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Loading