From 39c8e40ddc3c161e4852ad1d6f184e6acd85e917 Mon Sep 17 00:00:00 2001 From: Parthvi Vala Date: Thu, 24 Feb 2022 13:49:28 +0530 Subject: [PATCH] Personalize devfile (#5467) * Fix: Ask for devfile personalization * Update devfile library Signed-off-by: Parthvi Vala * Add todo * Print live configuration, make it work * Run mockgen script Signed-off-by: Parthvi Vala * TODO * Add missing mock methods * Add review suggestions * Add mock methods * Add unit tests * Add mock methods post rebase * Fixes post rebase Signed-off-by: Parthvi Vala * Review, only run devfile personalization if the dir is not empty and is interactive mode * Mock methods * Fix unit tests Signed-off-by: Parthvi Vala * Review Signed-off-by: Parthvi Vala --- go.mod | 2 +- go.sum | 4 +- pkg/init/asker/asker.go | 118 +++++++- pkg/init/asker/interface.go | 29 +- pkg/init/asker/mock.go | 138 ++++++--- pkg/init/backend/alizer.go | 4 + pkg/init/backend/flags.go | 4 + pkg/init/backend/interactive.go | 157 +++++++++- pkg/init/backend/interactive_test.go | 250 ++++++++++++++++ pkg/init/backend/interface.go | 3 + pkg/init/backend/mock.go | 57 ++-- pkg/init/init.go | 17 ++ pkg/init/interface.go | 3 + pkg/init/mock.go | 109 ++++--- pkg/init/registry/mock.go | 47 ++- pkg/odo/cli/init/init.go | 5 + .../pkg/devfile/parser/configurables.go | 216 ++------------ .../pkg/devfile/parser/data/interface.go | 6 + .../pkg/devfile/parser/data/mock_interface.go | 56 ++++ .../pkg/devfile/parser/data/v2/containers.go | 279 ++++++++++++++++++ .../pkg/testingutil/filesystem/default_fs.go | 18 ++ .../pkg/testingutil/filesystem/fake_fs.go | 13 + .../pkg/testingutil/filesystem/filesystem.go | 3 + .../pkg/testingutil/filesystem/singleton.go | 10 + .../pkg/testingutil/filesystem/watcher.go | 88 ++++++ vendor/modules.txt | 2 +- 26 files changed, 1300 insertions(+), 338 deletions(-) create mode 100644 vendor/github.com/devfile/library/pkg/devfile/parser/data/v2/containers.go create mode 100644 vendor/github.com/devfile/library/pkg/testingutil/filesystem/singleton.go create mode 100644 vendor/github.com/devfile/library/pkg/testingutil/filesystem/watcher.go diff --git a/go.mod b/go.mod index 2028cb69385..eb1dd5876bf 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/Xuanwo/go-locale v1.0.0 github.com/blang/semver v3.5.1+incompatible github.com/devfile/api/v2 v2.0.0-20220117162434-6e6e6a8bc14c - github.com/devfile/library v1.2.1-0.20220201144851-0749c7fa35a1 + github.com/devfile/library v1.2.1-0.20220217161036-0f5995513e92 github.com/devfile/registry-support/index/generator v0.0.0-20211012185733-0a73f866043f github.com/devfile/registry-support/registry-library v0.0.0-20211125162259-d7edf148d3e2 github.com/fatih/color v1.10.0 diff --git a/go.sum b/go.sum index b715097d1aa..d95c84f5ab4 100644 --- a/go.sum +++ b/go.sum @@ -265,8 +265,8 @@ github.com/devfile/api/v2 v2.0.0-20210910153124-da620cd1a7a1/go.mod h1:kLX/nW93g github.com/devfile/api/v2 v2.0.0-20220117162434-6e6e6a8bc14c h1:sjghKUov/WT71dBreHYQcTgQctxHT/p4uAhUFdGQfSU= github.com/devfile/api/v2 v2.0.0-20220117162434-6e6e6a8bc14c/go.mod h1:d99eTN6QxgzihOOFyOZA+VpUyD4Q1pYRYHZ/ci9J96Q= github.com/devfile/library v1.1.1-0.20210910214722-7c5ff63711ec/go.mod h1:svPWwWb+BP15SXCHl0dyOeE4Sohrjl5a2BaOzc/riLc= -github.com/devfile/library v1.2.1-0.20220201144851-0749c7fa35a1 h1:cenRsPWEfoCvKit8AtYnPtVlxA5z9EyVDqK3JW62er4= -github.com/devfile/library v1.2.1-0.20220201144851-0749c7fa35a1/go.mod h1:nZ9P4D8QxHdWe/czYx9sn+BxzsTOXMj/LAUfcqkvvuU= +github.com/devfile/library v1.2.1-0.20220217161036-0f5995513e92 h1:RIrd0pBAET9vLHEZGyaG1PSjp/lJXa+CZfuWiih2p6g= +github.com/devfile/library v1.2.1-0.20220217161036-0f5995513e92/go.mod h1:GSPfJaBg0+bBjBHbwBE5aerJLH6tWGQu2q2rHYd9czM= github.com/devfile/registry-support/index/generator v0.0.0-20211012185733-0a73f866043f h1:fKNUmoOPh7yAs69uMRZWHvev+m3e7T4jBL/hOXZB9ys= github.com/devfile/registry-support/index/generator v0.0.0-20211012185733-0a73f866043f/go.mod h1:bLGagbW2SFn7jo5+kUPlCMehIGqWkRtLKc5O0OyJMJM= github.com/devfile/registry-support/registry-library v0.0.0-20211125162259-d7edf148d3e2 h1:Rwuc0bdx8xSZWdIwXjAxaKYnZIWHneJmDAAZ6a5jXzY= diff --git a/pkg/init/asker/asker.go b/pkg/init/asker/asker.go index ef0d0d16c54..e5c2f38a46d 100644 --- a/pkg/init/asker/asker.go +++ b/pkg/init/asker/asker.go @@ -1,9 +1,10 @@ package asker import ( - "sort" - + "fmt" "github.com/AlecAivazis/survey/v2" + "github.com/redhat-developer/odo/pkg/log" + "sort" "github.com/redhat-developer/odo/pkg/catalog" ) @@ -90,3 +91,116 @@ func (o *Survey) AskCorrect() (bool, error) { } return answer, nil } + +// AskPersonalizeConfiguration asks the configuration user wants to change +func (o *Survey) AskPersonalizeConfiguration(configuration ContainerConfiguration) (OperationOnContainer, error) { + options, tracker := buildPersonalizedConfigurationOptions(configuration) + configChangeQuestion := &survey.Select{ + Message: "What configuration do you want change?", + Default: options[0], + Options: options, + } + var configChangeIndex int + err := survey.AskOne(configChangeQuestion, &configChangeIndex) + if err != nil { + return OperationOnContainer{}, err + } + return tracker[configChangeIndex], nil +} + +// AskAddEnvVar asks the key and value for env var +func (o *Survey) AskAddEnvVar() (string, string, error) { + newEnvNameQuesion := &survey.Input{ + Message: "Enter new environment variable name:", + } + var newEnvNameAnswer string + err := survey.AskOne(newEnvNameQuesion, &newEnvNameAnswer) + if err != nil { + return "", "", err + } + newEnvValueQuestion := &survey.Input{ + Message: fmt.Sprintf("Enter value for %q environment variable:", newEnvNameAnswer), + } + var newEnvValueAnswer string + err = survey.AskOne(newEnvValueQuestion, &newEnvValueAnswer) + if err != nil { + return "", "", err + } + return newEnvNameAnswer, newEnvValueAnswer, nil +} + +// AskAddPort asks the container name and port that user wants to add +func (o *Survey) AskAddPort() (string, error) { + newPortQuestion := &survey.Input{ + Message: "Enter port number:", + } + var newPortAnswer string + log.Warning("Please ensure that you do not add a duplicate port number") + err := survey.AskOne(newPortQuestion, &newPortAnswer) + if err != nil { + return "", err + } + return newPortAnswer, nil +} + +func (o *Survey) AskContainerName(containers []string) (string, error) { + selectContainerQuestion := &survey.Select{ + Message: "Select container for which you want to change configuration?", + Default: containers[len(containers)-1], + Options: containers, + } + var selectContainerAnswer string + err := survey.AskOne(selectContainerQuestion, &selectContainerAnswer) + if err != nil { + return selectContainerAnswer, err + } + return selectContainerAnswer, nil +} + +func (dc *DevfileConfiguration) GetContainers() []string { + keys := []string{} + for k := range *dc { + keys = append(keys, k) + } + return keys +} + +func buildPersonalizedConfigurationOptions(configuration ContainerConfiguration) (options []string, tracker []OperationOnContainer) { + options = []string{ + "NOTHING - configuration is correct", + } + // track the option selected by the user without relying on the UI message + tracker = []OperationOnContainer{{Ops: "Nothing"}} + + // Add available ports + for _, port := range configuration.Ports { + options = append(options, fmt.Sprintf("Delete port %q", port)) + tracker = append(tracker, OperationOnContainer{ + Ops: "Delete", + Kind: "Port", + Key: port, + }) + } + options = append(options, "Add new port") + tracker = append(tracker, OperationOnContainer{ + Ops: "Add", + Kind: "Port", + }) + + // Add available env vars + for key := range configuration.Envs { + options = append(options, fmt.Sprintf("Delete environment variable %q", key)) + tracker = append(tracker, OperationOnContainer{ + Ops: "Delete", + Kind: "EnvVar", + Key: key, + }) + } + options = append(options, "Add new environment variable") + tracker = append(tracker, OperationOnContainer{ + Ops: "Add", + Kind: "EnvVar", + }) + + return +} diff --git a/pkg/init/asker/interface.go b/pkg/init/asker/interface.go index efdc10fb29d..2fce6efdd65 100644 --- a/pkg/init/asker/interface.go +++ b/pkg/init/asker/interface.go @@ -2,7 +2,9 @@ // needed to initiate a project. package asker -import "github.com/redhat-developer/odo/pkg/catalog" +import ( + "github.com/redhat-developer/odo/pkg/catalog" +) // Asker interactively asks for information to the user type Asker interface { @@ -22,4 +24,29 @@ type Asker interface { // AskCorrect asks for confirmation AskCorrect() (bool, error) + + AskContainerName(containers []string) (string, error) + + // AskPersonalizeConfiguration asks the configuration user wants to change + AskPersonalizeConfiguration(configuration ContainerConfiguration) (OperationOnContainer, error) + + // AskAddEnvVar asks the key and value for env var + AskAddEnvVar() (string, string, error) + + // AskAddPort asks the container name and port that user wants to add + AskAddPort() (string, error) } + +type ContainerConfiguration struct { + Ports []string + Envs map[string]string +} + +type OperationOnContainer struct { + Ops string + Kind string + Key string +} + +// key is container name +type DevfileConfiguration map[string]ContainerConfiguration diff --git a/pkg/init/asker/mock.go b/pkg/init/asker/mock.go index 212ba819705..2f38a4dbd80 100644 --- a/pkg/init/asker/mock.go +++ b/pkg/init/asker/mock.go @@ -5,36 +5,97 @@ package asker import ( - reflect "reflect" - gomock "github.com/golang/mock/gomock" catalog "github.com/redhat-developer/odo/pkg/catalog" + reflect "reflect" ) -// MockAsker is a mock of Asker interface. +// MockAsker is a mock of Asker interface type MockAsker struct { ctrl *gomock.Controller recorder *MockAskerMockRecorder } -// MockAskerMockRecorder is the mock recorder for MockAsker. +// MockAskerMockRecorder is the mock recorder for MockAsker type MockAskerMockRecorder struct { mock *MockAsker } -// NewMockAsker creates a new mock instance. +// NewMockAsker creates a new mock instance func NewMockAsker(ctrl *gomock.Controller) *MockAsker { mock := &MockAsker{ctrl: ctrl} mock.recorder = &MockAskerMockRecorder{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 *MockAsker) EXPECT() *MockAskerMockRecorder { return m.recorder } -// AskCorrect mocks base method. +// AskLanguage mocks base method +func (m *MockAsker) AskLanguage(langs []string) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AskLanguage", langs) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// AskLanguage indicates an expected call of AskLanguage +func (mr *MockAskerMockRecorder) AskLanguage(langs interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AskLanguage", reflect.TypeOf((*MockAsker)(nil).AskLanguage), langs) +} + +// AskType mocks base method +func (m *MockAsker) AskType(types catalog.TypesWithDetails) (bool, catalog.DevfileComponentType, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AskType", types) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(catalog.DevfileComponentType) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// AskType indicates an expected call of AskType +func (mr *MockAskerMockRecorder) AskType(types interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AskType", reflect.TypeOf((*MockAsker)(nil).AskType), types) +} + +// AskStarterProject mocks base method +func (m *MockAsker) AskStarterProject(projects []string) (bool, int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AskStarterProject", projects) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(int) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// AskStarterProject indicates an expected call of AskStarterProject +func (mr *MockAskerMockRecorder) AskStarterProject(projects interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AskStarterProject", reflect.TypeOf((*MockAsker)(nil).AskStarterProject), projects) +} + +// AskName mocks base method +func (m *MockAsker) AskName(defaultName string) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AskName", defaultName) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// AskName indicates an expected call of AskName +func (mr *MockAskerMockRecorder) AskName(defaultName interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AskName", reflect.TypeOf((*MockAsker)(nil).AskName), defaultName) +} + +// AskCorrect mocks base method func (m *MockAsker) AskCorrect() (bool, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "AskCorrect") @@ -43,70 +104,69 @@ func (m *MockAsker) AskCorrect() (bool, error) { return ret0, ret1 } -// AskCorrect indicates an expected call of AskCorrect. +// AskCorrect indicates an expected call of AskCorrect func (mr *MockAskerMockRecorder) AskCorrect() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AskCorrect", reflect.TypeOf((*MockAsker)(nil).AskCorrect)) } -// AskLanguage mocks base method. -func (m *MockAsker) AskLanguage(langs []string) (string, error) { +// AskContainerName mocks base method +func (m *MockAsker) AskContainerName(containers []string) (string, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "AskLanguage", langs) + ret := m.ctrl.Call(m, "AskContainerName", containers) ret0, _ := ret[0].(string) ret1, _ := ret[1].(error) return ret0, ret1 } -// AskLanguage indicates an expected call of AskLanguage. -func (mr *MockAskerMockRecorder) AskLanguage(langs interface{}) *gomock.Call { +// AskContainerName indicates an expected call of AskContainerName +func (mr *MockAskerMockRecorder) AskContainerName(containers interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AskLanguage", reflect.TypeOf((*MockAsker)(nil).AskLanguage), langs) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AskContainerName", reflect.TypeOf((*MockAsker)(nil).AskContainerName), containers) } -// AskName mocks base method. -func (m *MockAsker) AskName(defaultName string) (string, error) { +// AskPersonalizeConfiguration mocks base method +func (m *MockAsker) AskPersonalizeConfiguration(configuration ContainerConfiguration) (OperationOnContainer, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "AskName", defaultName) - ret0, _ := ret[0].(string) + ret := m.ctrl.Call(m, "AskPersonalizeConfiguration", configuration) + ret0, _ := ret[0].(OperationOnContainer) ret1, _ := ret[1].(error) return ret0, ret1 } -// AskName indicates an expected call of AskName. -func (mr *MockAskerMockRecorder) AskName(defaultName interface{}) *gomock.Call { +// AskPersonalizeConfiguration indicates an expected call of AskPersonalizeConfiguration +func (mr *MockAskerMockRecorder) AskPersonalizeConfiguration(configuration interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AskName", reflect.TypeOf((*MockAsker)(nil).AskName), defaultName) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AskPersonalizeConfiguration", reflect.TypeOf((*MockAsker)(nil).AskPersonalizeConfiguration), configuration) } -// AskStarterProject mocks base method. -func (m *MockAsker) AskStarterProject(projects []string) (bool, int, error) { +// AskAddEnvVar mocks base method +func (m *MockAsker) AskAddEnvVar() (string, string, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "AskStarterProject", projects) - ret0, _ := ret[0].(bool) - ret1, _ := ret[1].(int) + ret := m.ctrl.Call(m, "AskAddEnvVar") + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(string) ret2, _ := ret[2].(error) return ret0, ret1, ret2 } -// AskStarterProject indicates an expected call of AskStarterProject. -func (mr *MockAskerMockRecorder) AskStarterProject(projects interface{}) *gomock.Call { +// AskAddEnvVar indicates an expected call of AskAddEnvVar +func (mr *MockAskerMockRecorder) AskAddEnvVar() *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AskStarterProject", reflect.TypeOf((*MockAsker)(nil).AskStarterProject), projects) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AskAddEnvVar", reflect.TypeOf((*MockAsker)(nil).AskAddEnvVar)) } -// AskType mocks base method. -func (m *MockAsker) AskType(types catalog.TypesWithDetails) (bool, catalog.DevfileComponentType, error) { +// AskAddPort mocks base method +func (m *MockAsker) AskAddPort() (string, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "AskType", types) - ret0, _ := ret[0].(bool) - ret1, _ := ret[1].(catalog.DevfileComponentType) - ret2, _ := ret[2].(error) - return ret0, ret1, ret2 + ret := m.ctrl.Call(m, "AskAddPort") + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 } -// AskType indicates an expected call of AskType. -func (mr *MockAskerMockRecorder) AskType(types interface{}) *gomock.Call { +// AskAddPort indicates an expected call of AskAddPort +func (mr *MockAskerMockRecorder) AskAddPort() *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AskType", reflect.TypeOf((*MockAsker)(nil).AskType), types) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AskAddPort", reflect.TypeOf((*MockAsker)(nil).AskAddPort)) } diff --git a/pkg/init/backend/alizer.go b/pkg/init/backend/alizer.go index 0b744091077..aa512d014c9 100644 --- a/pkg/init/backend/alizer.go +++ b/pkg/init/backend/alizer.go @@ -90,3 +90,7 @@ func (o *AlizerBackend) SelectStarterProject(devfile parser.DevfileObj, flags ma func (o *AlizerBackend) PersonalizeName(devfile parser.DevfileObj, flags map[string]string) error { return nil } + +func (o *AlizerBackend) PersonalizeDevfileconfig(devfile parser.DevfileObj) error { + return nil +} diff --git a/pkg/init/backend/flags.go b/pkg/init/backend/flags.go index 6829e556bed..a33699595cb 100644 --- a/pkg/init/backend/flags.go +++ b/pkg/init/backend/flags.go @@ -97,3 +97,7 @@ 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]) } + +func (o FlagsBackend) PersonalizeDevfileconfig(devfileobj parser.DevfileObj) error { + return nil +} diff --git a/pkg/init/backend/interactive.go b/pkg/init/backend/interactive.go index d95390c472b..8b5cb08e987 100644 --- a/pkg/init/backend/interactive.go +++ b/pkg/init/backend/interactive.go @@ -2,10 +2,13 @@ package backend import ( "fmt" - "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" + "github.com/fatih/color" + "github.com/redhat-developer/odo/pkg/log" + "sort" + "strconv" "github.com/redhat-developer/odo/pkg/catalog" "github.com/redhat-developer/odo/pkg/init/asker" @@ -104,3 +107,155 @@ func (o *InteractiveBackend) PersonalizeName(devfile parser.DevfileObj, flags ma } return devfile.SetMetadataName(name) } + +func (o *InteractiveBackend) PersonalizeDevfileconfig(devfileobj parser.DevfileObj) error { + // TODO: Add tests + config, err := getPortsAndEnvVar(devfileobj) + if err != nil { + return err + } + + var selectContainerAnswer string + containerOptions := config.GetContainers() + containerOptions = append(containerOptions, "NONE - configuration is correct") + + for selectContainerAnswer != "NONE - configuration is correct" { + PrintConfiguration(config) + selectContainerAnswer, err = o.askerClient.AskContainerName(containerOptions) + if err != nil { + return err + } + + selectedContainer := config[selectContainerAnswer] + if selectContainerAnswer == "NONE - configuration is correct" { + break + } + + var configOps asker.OperationOnContainer + for configOps.Ops != "Nothing" { + configOps, err = o.askerClient.AskPersonalizeConfiguration(selectedContainer) + if err != nil { + return err + } + switch configOps.Ops { + case "Add": + switch configOps.Kind { + case "Port": + var newPort string + newPort, err = o.askerClient.AskAddPort() + if err != nil { + return err + } + + err = devfileobj.Data.SetPorts(map[string][]string{selectContainerAnswer: {newPort}}) + if err != nil { + return err + } + selectedContainer.Ports = append(selectedContainer.Ports, newPort) + + case "EnvVar": + var newEnvNameAnswer, newEnvValueAnswer string + newEnvNameAnswer, newEnvValueAnswer, err = o.askerClient.AskAddEnvVar() + if err != nil { + return err + } + err = devfileobj.Data.AddEnvVars(map[string][]v1alpha2.EnvVar{selectContainerAnswer: {{ + Name: newEnvNameAnswer, + Value: newEnvValueAnswer, + }}}) + if err != nil { + return err + } + selectedContainer.Envs[newEnvNameAnswer] = newEnvValueAnswer + } + case "Delete": + switch configOps.Kind { + case "Port": + portToDelete := configOps.Key + indexToDelete := -1 + for i, port := range selectedContainer.Ports { + if port == portToDelete { + indexToDelete = i + } + } + if indexToDelete == -1 { + log.Warningf(fmt.Sprintf("unable to delete port %q, not found", portToDelete)) + } + err = devfileobj.Data.RemovePorts(map[string][]string{selectContainerAnswer: {portToDelete}}) + if err != nil { + return err + } + selectedContainer.Ports = append(selectedContainer.Ports[:indexToDelete], selectedContainer.Ports[indexToDelete+1:]...) + + case "EnvVar": + envToDelete := configOps.Key + if _, ok := selectedContainer.Envs[envToDelete]; !ok { + log.Warningf(fmt.Sprintf("unable to delete env %q, not found", envToDelete)) + } + err = devfileobj.Data.RemoveEnvVars(map[string][]string{selectContainerAnswer: {envToDelete}}) + if err != nil { + return err + } + delete(selectedContainer.Envs, envToDelete) + } + case "Nothing": + default: + return fmt.Errorf("Unknown configuration selected %q", fmt.Sprintf("%v %v %v", configOps.Ops, configOps.Kind, configOps.Key)) + } + // Update the current configuration + config[selectContainerAnswer] = selectedContainer + } + } + return devfileobj.WriteYamlDevfile() +} + +func PrintConfiguration(config asker.DevfileConfiguration) { + color.New(color.Bold, color.FgGreen).Println("Current component configuration:") + + var keys []string + for key := range config { + keys = append(keys, key) + } + sort.Strings(keys) + + for _, key := range keys { + container := config[key] + color.Green("Container %q:", key) + color.Green(" Opened ports:") + + for _, port := range container.Ports { + color.New(color.Bold, color.FgWhite).Printf(" - %s\n", port) + } + + color.Green(" Environment variables:") + for key, value := range container.Envs { + color.New(color.Bold, color.FgWhite).Printf(" - %s = %s\n", key, value) + } + } +} + +func getPortsAndEnvVar(obj parser.DevfileObj) (asker.DevfileConfiguration, error) { + var config = asker.DevfileConfiguration{} + components, err := obj.Data.GetComponents(parsercommon.DevfileOptions{}) + if err != nil { + return config, err + } + for _, component := range components { + var ports = []string{} + var envMap = map[string]string{} + if component.Container != nil { + // TODO: Fix this for component that are not a container + for _, ep := range component.Container.Endpoints { + ports = append(ports, strconv.Itoa(ep.TargetPort)) + } + for _, env := range component.Container.Env { + envMap[env.Name] = env.Value + } + } + config[component.Name] = asker.ContainerConfiguration{ + Ports: ports, + Envs: envMap, + } + } + return config, nil +} diff --git a/pkg/init/backend/interactive_test.go b/pkg/init/backend/interactive_test.go index 78f9d26ca41..d088a0a14ec 100644 --- a/pkg/init/backend/interactive_test.go +++ b/pkg/init/backend/interactive_test.go @@ -1,6 +1,7 @@ package backend import ( + "github.com/redhat-developer/odo/pkg/testingutil" "reflect" "testing" @@ -262,3 +263,252 @@ func TestInteractiveBackend_PersonalizeName(t *testing.T) { }) } } + +func TestInteractiveBackend_PersonalizeDevfileconfig(t *testing.T) { + container1 := "runtime" + + type fields struct { + asker func(ctrl *gomock.Controller, configuration asker.DevfileConfiguration) asker.Asker + catalogClient catalog.Client + } + type args struct { + devfileobj func(fs filesystem.Filesystem) parser.DevfileObj + key string + value string + } + tests := []struct { + name string + fields fields + args args + wantErr bool + checkResult func(config asker.ContainerConfiguration, key string, value string) bool + }{ + // TODO: Add test cases. + { + name: "Add new port", + fields: fields{ + asker: func(ctrl *gomock.Controller, configuration asker.DevfileConfiguration) asker.Asker { + client := asker.NewMockAsker(ctrl) + client.EXPECT().AskContainerName(append(configuration.GetContainers(), "NONE - configuration is correct")).Return(container1, nil) + containerConfig := configuration[container1] + selectContainer := client.EXPECT().AskPersonalizeConfiguration(containerConfig).Return(asker.OperationOnContainer{ + Ops: "Add", + Kind: "Port", + }, nil).MaxTimes(1) + addPort := client.EXPECT().AskAddPort().Return("5000", nil).After(selectContainer) + containerConfig.Ports = append(containerConfig.Ports, "5000") + containerConfigDone := client.EXPECT().AskPersonalizeConfiguration(containerConfig).Return(asker.OperationOnContainer{Ops: "Nothing"}, nil).After(addPort) + client.EXPECT().AskContainerName(append(configuration.GetContainers(), "NONE - configuration is correct")).Return("NONE - configuration is correct", nil).After(containerConfigDone) + return client + }, + catalogClient: nil, + }, + args: args{ + key: "5000", + devfileobj: func(fs filesystem.Filesystem) parser.DevfileObj { + ports := []string{"7000", "8000"} + envVars := []v1alpha2.EnvVar{{Name: "env1", Value: "val1"}, {Name: "env2", Value: "val2"}} + return getDevfileObj(fs, container1, ports, envVars) + }, + }, + wantErr: false, + checkResult: func(config asker.ContainerConfiguration, key string, value string) bool { + for _, port := range config.Ports { + if port == key { + return true + } + } + return false + }, + }, + { + name: "Add new environment variable", + fields: fields{ + asker: func(ctrl *gomock.Controller, configuration asker.DevfileConfiguration) asker.Asker { + client := asker.NewMockAsker(ctrl) + askContainerName := client.EXPECT().AskContainerName(append(configuration.GetContainers(), "NONE - configuration is correct")).Return(container1, nil) + containerConfig := configuration[container1] + selectContainer := client.EXPECT().AskPersonalizeConfiguration(containerConfig).Return(asker.OperationOnContainer{ + Ops: "Add", + Kind: "EnvVar", + }, nil).After(askContainerName) + key, val := "env3", "val3" + addEnvVar := client.EXPECT().AskAddEnvVar().Return(key, val, nil).After(selectContainer) + // containerConfig.Envs[key] = val + containerConfigDone := client.EXPECT().AskPersonalizeConfiguration(gomock.Any()).Return(asker.OperationOnContainer{Ops: "Nothing"}, nil).After(addEnvVar) + client.EXPECT().AskContainerName(append(configuration.GetContainers(), "NONE - configuration is correct")).Return("NONE - configuration is correct", nil).After(containerConfigDone) + return client + }, + catalogClient: nil, + }, + args: args{ + devfileobj: func(fs filesystem.Filesystem) parser.DevfileObj { + ports := []string{"7000", "8000"} + envVars := []v1alpha2.EnvVar{{Name: "env1", Value: "val1"}, {Name: "env2", Value: "val2"}} + return getDevfileObj(fs, container1, ports, envVars) + }, + key: "env3", + value: "val3", + }, + wantErr: false, + checkResult: func(config asker.ContainerConfiguration, key string, value string) bool { + if val, ok := config.Envs[key]; ok && val == value { + return true + } + return false + }, + }, + { + name: "Delete port", + fields: fields{ + asker: func(ctrl *gomock.Controller, configuration asker.DevfileConfiguration) asker.Asker { + client := asker.NewMockAsker(ctrl) + client.EXPECT().AskContainerName(append(configuration.GetContainers(), "NONE - configuration is correct")).Return(container1, nil) + containerConfig := configuration[container1] + selectContainer := client.EXPECT().AskPersonalizeConfiguration(containerConfig).Return(asker.OperationOnContainer{ + Ops: "Delete", + Kind: "Port", + Key: "7000", + }, nil).MaxTimes(1) + containerConfig.Ports = []string{"8000"} + containerConfigDone := client.EXPECT().AskPersonalizeConfiguration(containerConfig).Return(asker.OperationOnContainer{Ops: "Nothing"}, nil).After(selectContainer) + client.EXPECT().AskContainerName(append(configuration.GetContainers(), "NONE - configuration is correct")).Return("NONE - configuration is correct", nil).After(containerConfigDone) + return client + }, + catalogClient: nil, + }, + args: args{ + devfileobj: func(fs filesystem.Filesystem) parser.DevfileObj { + ports := []string{"7000", "8000"} + envVars := []v1alpha2.EnvVar{{Name: "env1", Value: "val1"}, {Name: "env2", Value: "val2"}} + return getDevfileObj(fs, container1, ports, envVars) + }, + key: "7000", + value: "", + }, + checkResult: func(config asker.ContainerConfiguration, key string, value string) bool { + for _, port := range config.Ports { + if port == key { + return false + } + } + return true + }, + wantErr: false, + }, + { + name: "Delete environment variable", + fields: fields{ + asker: func(ctrl *gomock.Controller, configuration asker.DevfileConfiguration) asker.Asker { + client := asker.NewMockAsker(ctrl) + client.EXPECT().AskContainerName(append(configuration.GetContainers(), "NONE - configuration is correct")).Return(container1, nil) + containerConfig := configuration[container1] + selectContainer := client.EXPECT().AskPersonalizeConfiguration(containerConfig).Return(asker.OperationOnContainer{ + Ops: "Delete", + Kind: "EnvVar", + Key: "env2", + }, nil).MaxTimes(1) + + // delete(containerConfig.Envs, "env2") + containerConfigDone := client.EXPECT().AskPersonalizeConfiguration(gomock.Any()).Return(asker.OperationOnContainer{Ops: "Nothing"}, nil).After(selectContainer) + client.EXPECT().AskContainerName(append(configuration.GetContainers(), "NONE - configuration is correct")).Return("NONE - configuration is correct", nil).After(containerConfigDone) + return client + }, + catalogClient: nil, + }, + args: args{ + devfileobj: func(fs filesystem.Filesystem) parser.DevfileObj { + ports := []string{"7000", "8000"} + envVars := []v1alpha2.EnvVar{{Name: "env1", Value: "val1"}, {Name: "env2", Value: "val2"}} + return getDevfileObj(fs, container1, ports, envVars) + }, + key: "env2", + value: "", + }, + wantErr: false, + checkResult: func(config asker.ContainerConfiguration, key string, value string) bool { + if _, ok := config.Envs[key]; ok { + return false + } + return true + }, + }, + { + name: "None - Configuration is correct", + fields: fields{ + asker: func(ctrl *gomock.Controller, configuration asker.DevfileConfiguration) asker.Asker { + client := asker.NewMockAsker(ctrl) + client.EXPECT().AskContainerName(append(configuration.GetContainers(), "NONE - configuration is correct")).Return("NONE - configuration is correct", nil) + containerConfig := configuration[container1] + client.EXPECT().AskPersonalizeConfiguration(containerConfig).Return(asker.OperationOnContainer{ + Ops: "Nothing", + }, nil).MaxTimes(1) + return client + }, + catalogClient: nil, + }, + args: args{ + devfileobj: func(fs filesystem.Filesystem) parser.DevfileObj { + ports := []string{"7000", "8000"} + envVars := []v1alpha2.EnvVar{{Name: "env1", Value: "val1"}, {Name: "env2", Value: "val2"}} + return getDevfileObj(fs, container1, ports, envVars) + }, + key: "", + value: "", + }, + wantErr: false, + checkResult: func(config asker.ContainerConfiguration, key string, value string) bool { + checkConfig := asker.ContainerConfiguration{ + Ports: []string{"7000", "8000"}, + Envs: map[string]string{"env1": "val1", "env2": "val2"}, + } + return reflect.DeepEqual(config, checkConfig) + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fs := filesystem.NewFakeFs() + devfile := tt.args.devfileobj(fs) + config, err := getPortsAndEnvVar(devfile) + if err != nil { + t.Errorf("getPortsAndEnvVar() error = %v", err) + } + + ctrl := gomock.NewController(t) + var askerClient asker.Asker + if tt.fields.asker != nil { + askerClient = tt.fields.asker(ctrl, config) + } + + o := &InteractiveBackend{ + askerClient: askerClient, + catalogClient: tt.fields.catalogClient, + } + if err = o.PersonalizeDevfileconfig(devfile); (err != nil) != tt.wantErr { + t.Errorf("PersonalizeDevfileconfig() error = %v, wantErr %v", err, tt.wantErr) + } + config, err = getPortsAndEnvVar(devfile) + if err != nil { + t.Errorf("getPortsAndEnvVar() error = %v", err) + } + if tt.checkResult != nil && !tt.checkResult(config[container1], tt.args.key, tt.args.value) { + t.Errorf("InteractiveBackend.PersonalizeName(), checking result failed") + } + }) + } +} + +func getDevfileObj(fs filesystem.Filesystem, containerName string, ports []string, envVars []v1alpha2.EnvVar) parser.DevfileObj { + devfileData, _ := data.NewDevfileData(string(data.APISchemaVersion200)) + _ = devfileData.AddComponents([]v1alpha2.Component{ + testingutil.GetFakeContainerComponent(containerName), + }) + obj := parser.DevfileObj{ + Ctx: parsercontext.FakeContext(fs, "/tmp/devfile.yaml"), + Data: devfileData, + } + _ = obj.SetPorts(map[string][]string{containerName: ports}) + _ = obj.AddEnvVars(map[string][]v1alpha2.EnvVar{containerName: envVars}) + return obj +} diff --git a/pkg/init/backend/interface.go b/pkg/init/backend/interface.go index 1e9b1c6165c..4cb563da242 100644 --- a/pkg/init/backend/interface.go +++ b/pkg/init/backend/interface.go @@ -23,4 +23,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 devfile config for ports and environment variables + PersonalizeDevfileconfig(devfileobj parser.DevfileObj) error } diff --git a/pkg/init/backend/mock.go b/pkg/init/backend/mock.go index a41901535f3..4e1b625a9e0 100644 --- a/pkg/init/backend/mock.go +++ b/pkg/init/backend/mock.go @@ -5,52 +5,51 @@ package backend 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" filesystem "github.com/redhat-developer/odo/pkg/testingutil/filesystem" + reflect "reflect" ) -// MockInitBackend is a mock of InitBackend interface. +// MockInitBackend is a mock of InitBackend interface type MockInitBackend struct { ctrl *gomock.Controller recorder *MockInitBackendMockRecorder } -// MockInitBackendMockRecorder is the mock recorder for MockInitBackend. +// MockInitBackendMockRecorder is the mock recorder for MockInitBackend type MockInitBackendMockRecorder struct { mock *MockInitBackend } -// NewMockInitBackend creates a new mock instance. +// NewMockInitBackend creates a new mock instance func NewMockInitBackend(ctrl *gomock.Controller) *MockInitBackend { mock := &MockInitBackend{ctrl: ctrl} mock.recorder = &MockInitBackendMockRecorder{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 *MockInitBackend) EXPECT() *MockInitBackendMockRecorder { return m.recorder } -// PersonalizeName mocks base method. -func (m *MockInitBackend) PersonalizeName(devfile parser.DevfileObj, flags map[string]string) error { +// Validate mocks base method +func (m *MockInitBackend) Validate(flags map[string]string, fs filesystem.Filesystem, dir string) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "PersonalizeName", devfile, flags) + ret := m.ctrl.Call(m, "Validate", flags, fs, dir) ret0, _ := ret[0].(error) return ret0 } -// PersonalizeName indicates an expected call of PersonalizeName. -func (mr *MockInitBackendMockRecorder) PersonalizeName(devfile, flags interface{}) *gomock.Call { +// Validate indicates an expected call of Validate +func (mr *MockInitBackendMockRecorder) Validate(flags, fs, dir interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PersonalizeName", reflect.TypeOf((*MockInitBackend)(nil).PersonalizeName), devfile, flags) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Validate", reflect.TypeOf((*MockInitBackend)(nil).Validate), flags, fs, dir) } -// SelectDevfile mocks base method. +// SelectDevfile mocks base method func (m *MockInitBackend) SelectDevfile(flags map[string]string, fs filesystem.Filesystem, dir string) (*DevfileLocation, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SelectDevfile", flags, fs, dir) @@ -59,13 +58,13 @@ func (m *MockInitBackend) SelectDevfile(flags map[string]string, fs filesystem.F return ret0, ret1 } -// SelectDevfile indicates an expected call of SelectDevfile. +// SelectDevfile indicates an expected call of SelectDevfile func (mr *MockInitBackendMockRecorder) SelectDevfile(flags, fs, dir interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SelectDevfile", reflect.TypeOf((*MockInitBackend)(nil).SelectDevfile), flags, fs, dir) } -// SelectStarterProject mocks base method. +// SelectStarterProject mocks base method func (m *MockInitBackend) SelectStarterProject(devfile parser.DevfileObj, flags map[string]string) (*v1alpha2.StarterProject, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SelectStarterProject", devfile, flags) @@ -74,22 +73,36 @@ func (m *MockInitBackend) SelectStarterProject(devfile parser.DevfileObj, flags return ret0, ret1 } -// SelectStarterProject indicates an expected call of SelectStarterProject. +// SelectStarterProject indicates an expected call of SelectStarterProject func (mr *MockInitBackendMockRecorder) SelectStarterProject(devfile, flags interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SelectStarterProject", reflect.TypeOf((*MockInitBackend)(nil).SelectStarterProject), devfile, flags) } -// Validate mocks base method. -func (m *MockInitBackend) Validate(flags map[string]string, fs filesystem.Filesystem, dir string) error { +// PersonalizeName mocks base method +func (m *MockInitBackend) PersonalizeName(devfile parser.DevfileObj, flags map[string]string) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Validate", flags, fs, dir) + ret := m.ctrl.Call(m, "PersonalizeName", devfile, flags) ret0, _ := ret[0].(error) return ret0 } -// Validate indicates an expected call of Validate. -func (mr *MockInitBackendMockRecorder) Validate(flags, fs, dir interface{}) *gomock.Call { +// PersonalizeName indicates an expected call of PersonalizeName +func (mr *MockInitBackendMockRecorder) PersonalizeName(devfile, flags interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Validate", reflect.TypeOf((*MockInitBackend)(nil).Validate), flags, fs, dir) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PersonalizeName", reflect.TypeOf((*MockInitBackend)(nil).PersonalizeName), devfile, flags) +} + +// PersonalizeDevfileconfig mocks base method +func (m *MockInitBackend) PersonalizeDevfileconfig(devfileobj parser.DevfileObj) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PersonalizeDevfileconfig", devfileobj) + ret0, _ := ret[0].(error) + return ret0 +} + +// PersonalizeDevfileconfig indicates an expected call of PersonalizeDevfileconfig +func (mr *MockInitBackendMockRecorder) PersonalizeDevfileconfig(devfileobj interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PersonalizeDevfileconfig", reflect.TypeOf((*MockInitBackend)(nil).PersonalizeDevfileconfig), devfileobj) } diff --git a/pkg/init/init.go b/pkg/init/init.go index 61ab53c142a..38d0ee99e16 100644 --- a/pkg/init/init.go +++ b/pkg/init/init.go @@ -221,3 +221,20 @@ func (o *InitClient) PersonalizeName(devfile parser.DevfileObj, flags map[string err := backend.PersonalizeName(devfile, flags) return err } + +func (o InitClient) PersonalizeDevfileConfig(devfileobj parser.DevfileObj, flags map[string]string, fs filesystem.Filesystem, dir string) error { + var backend backend.InitBackend + onlyDevfile, err := location.DirContainsOnlyDevfile(fs, dir) + if err != nil { + return err + } + + // Interactive mode since no flags are provided + if len(flags) == 0 && !onlyDevfile { + // Other files present in the directory; hence alizer is run + backend = o.interactiveBackend + } else { + backend = o.flagsBackend + } + return backend.PersonalizeDevfileconfig(devfileobj) +} diff --git a/pkg/init/interface.go b/pkg/init/interface.go index 07086bb4bad..c12ecc16cad 100644 --- a/pkg/init/interface.go +++ b/pkg/init/interface.go @@ -39,4 +39,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(devfileobj parser.DevfileObj, flags map[string]string, fs filesystem.Filesystem, dir string) error } diff --git a/pkg/init/mock.go b/pkg/init/mock.go index 99cedb8d277..44e3f5fb700 100644 --- a/pkg/init/mock.go +++ b/pkg/init/mock.go @@ -5,39 +5,67 @@ 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" filesystem "github.com/redhat-developer/odo/pkg/testingutil/filesystem" + 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, fs filesystem.Filesystem, dir string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Validate", flags, fs, dir) + ret0, _ := ret[0].(error) + return ret0 +} + +// Validate indicates an expected call of Validate +func (mr *MockClientMockRecorder) Validate(flags, fs, dir interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Validate", reflect.TypeOf((*MockClient)(nil).Validate), flags, fs, dir) +} + +// SelectDevfile mocks base method +func (m *MockClient) SelectDevfile(flags map[string]string, fs filesystem.Filesystem, dir string) (*backend.DevfileLocation, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SelectDevfile", flags, fs, dir) + ret0, _ := ret[0].(*backend.DevfileLocation) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SelectDevfile indicates an expected call of SelectDevfile +func (mr *MockClientMockRecorder) SelectDevfile(flags, fs, dir interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SelectDevfile", reflect.TypeOf((*MockClient)(nil).SelectDevfile), flags, fs, dir) +} + +// 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) @@ -46,13 +74,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, fs filesystem.Filesystem, dir string) (*v1alpha2.StarterProject, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SelectStarterProject", devfile, flags, fs, dir) + 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, fs, dir interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SelectStarterProject", reflect.TypeOf((*MockClient)(nil).SelectStarterProject), devfile, flags, fs, dir) +} + +// 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) @@ -60,13 +103,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) @@ -74,52 +117,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, fs filesystem.Filesystem, dir string) (*backend.DevfileLocation, error) { +// PersonalizeDevfileConfig mocks base method +func (m *MockClient) PersonalizeDevfileConfig(devfileobj parser.DevfileObj, flags map[string]string, fs filesystem.Filesystem, dir string) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SelectDevfile", flags, fs, dir) - ret0, _ := ret[0].(*backend.DevfileLocation) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// SelectDevfile indicates an expected call of SelectDevfile. -func (mr *MockClientMockRecorder) SelectDevfile(flags, fs, dir interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SelectDevfile", reflect.TypeOf((*MockClient)(nil).SelectDevfile), flags, fs, dir) -} - -// SelectStarterProject mocks base method. -func (m *MockClient) SelectStarterProject(devfile parser.DevfileObj, flags map[string]string, fs filesystem.Filesystem, dir string) (*v1alpha2.StarterProject, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SelectStarterProject", devfile, flags, fs, dir) - 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, fs, dir interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SelectStarterProject", reflect.TypeOf((*MockClient)(nil).SelectStarterProject), devfile, flags, fs, dir) -} - -// Validate mocks base method. -func (m *MockClient) Validate(flags map[string]string, fs filesystem.Filesystem, dir string) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Validate", flags, fs, dir) + ret := m.ctrl.Call(m, "PersonalizeDevfileConfig", devfileobj, flags, fs, dir) ret0, _ := ret[0].(error) return ret0 } -// Validate indicates an expected call of Validate. -func (mr *MockClientMockRecorder) Validate(flags, fs, dir interface{}) *gomock.Call { +// PersonalizeDevfileConfig indicates an expected call of PersonalizeDevfileConfig +func (mr *MockClientMockRecorder) PersonalizeDevfileConfig(devfileobj, flags, fs, dir interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Validate", reflect.TypeOf((*MockClient)(nil).Validate), flags, fs, dir) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PersonalizeDevfileConfig", reflect.TypeOf((*MockClient)(nil).PersonalizeDevfileConfig), devfileobj, flags, fs, dir) } diff --git a/pkg/init/registry/mock.go b/pkg/init/registry/mock.go index 5c281afba1e..c1b162c69ce 100644 --- a/pkg/init/registry/mock.go +++ b/pkg/init/registry/mock.go @@ -5,38 +5,51 @@ package registry import ( - reflect "reflect" - v1alpha2 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" util "github.com/devfile/library/pkg/util" library "github.com/devfile/registry-support/registry-library/library" gomock "github.com/golang/mock/gomock" + 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 } -// DownloadFileInMemory mocks base method. +// PullStackFromRegistry mocks base method +func (m *MockClient) PullStackFromRegistry(registry, stack, destDir string, options library.RegistryOptions) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PullStackFromRegistry", registry, stack, destDir, options) + ret0, _ := ret[0].(error) + return ret0 +} + +// PullStackFromRegistry indicates an expected call of PullStackFromRegistry +func (mr *MockClientMockRecorder) PullStackFromRegistry(registry, stack, destDir, options interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PullStackFromRegistry", reflect.TypeOf((*MockClient)(nil).PullStackFromRegistry), registry, stack, destDir, options) +} + +// DownloadFileInMemory mocks base method func (m *MockClient) DownloadFileInMemory(params util.HTTPRequestParams) ([]byte, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "DownloadFileInMemory", params) @@ -45,13 +58,13 @@ func (m *MockClient) DownloadFileInMemory(params util.HTTPRequestParams) ([]byte return ret0, ret1 } -// DownloadFileInMemory indicates an expected call of DownloadFileInMemory. +// DownloadFileInMemory indicates an expected call of DownloadFileInMemory func (mr *MockClientMockRecorder) DownloadFileInMemory(params interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DownloadFileInMemory", reflect.TypeOf((*MockClient)(nil).DownloadFileInMemory), params) } -// DownloadStarterProject mocks base method. +// DownloadStarterProject mocks base method func (m *MockClient) DownloadStarterProject(starterProject *v1alpha2.StarterProject, decryptedToken, contextDir string, verbose bool) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "DownloadStarterProject", starterProject, decryptedToken, contextDir, verbose) @@ -59,22 +72,8 @@ func (m *MockClient) DownloadStarterProject(starterProject *v1alpha2.StarterProj return ret0 } -// DownloadStarterProject indicates an expected call of DownloadStarterProject. +// DownloadStarterProject indicates an expected call of DownloadStarterProject func (mr *MockClientMockRecorder) DownloadStarterProject(starterProject, decryptedToken, contextDir, verbose interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DownloadStarterProject", reflect.TypeOf((*MockClient)(nil).DownloadStarterProject), starterProject, decryptedToken, contextDir, verbose) } - -// PullStackFromRegistry mocks base method. -func (m *MockClient) PullStackFromRegistry(registry, stack, destDir string, options library.RegistryOptions) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "PullStackFromRegistry", registry, stack, destDir, options) - ret0, _ := ret[0].(error) - return ret0 -} - -// PullStackFromRegistry indicates an expected call of PullStackFromRegistry. -func (mr *MockClientMockRecorder) PullStackFromRegistry(registry, stack, destDir, options interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PullStackFromRegistry", reflect.TypeOf((*MockClient)(nil).PullStackFromRegistry), registry, stack, destDir, options) -} diff --git a/pkg/odo/cli/init/init.go b/pkg/odo/cli/init/init.go index af9ea070969..5fe69abd686 100644 --- a/pkg/odo/cli/init/init.go +++ b/pkg/odo/cli/init/init.go @@ -142,6 +142,11 @@ func (o *InitOptions) Run() (err error) { scontext.SetComponentType(o.ctx, component.GetComponentTypeFromDevfileMetadata(devfileObj.Data.GetMetadata())) + err = o.clientset.InitClient.PersonalizeDevfileConfig(devfileObj, o.flags, o.clientset.FS, o.contextDir) + if err != nil { + return fmt.Errorf("Failed to configure devfile: %w", err) + } + starterInfo, err := o.clientset.InitClient.SelectStarterProject(devfileObj, o.flags, o.clientset.FS, o.contextDir) if err != nil { return err 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..e05b46463b0 100644 --- a/vendor/github.com/devfile/library/pkg/devfile/parser/configurables.go +++ b/vendor/github.com/devfile/library/pkg/devfile/parser/configurables.go @@ -1,13 +1,8 @@ package parser import ( - "fmt" - "strconv" - "strings" - v1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" "github.com/devfile/library/pkg/devfile/parser/data/v2/common" - corev1 "k8s.io/api/core/v1" ) const ( @@ -27,70 +22,46 @@ func (d DevfileObj) SetMetadataName(name string) error { return d.WriteYamlDevfile() } -// AddEnvVars adds environment variables to all the components in a devfile -func (d DevfileObj) AddEnvVars(otherList []v1.EnvVar) error { - components, err := d.Data.GetComponents(common.DevfileOptions{}) +// AddEnvVars accepts a map of container name mapped to an array of the env vars to be set; +// it adds the envirnoment variables to a given container name, and writes to the devfile +// Example of containerEnvMap : {"runtime": {{Name: "Foo", Value: "Bar"}}} +func (d DevfileObj) AddEnvVars(containerEnvMap map[string][]v1.EnvVar) error { + err := d.Data.AddEnvVars(containerEnvMap) if err != nil { return err } - for _, component := range components { - if component.Container != nil { - component.Container.Env = Merge(component.Container.Env, otherList) - d.Data.UpdateComponent(component) - } - } return d.WriteYamlDevfile() } -// RemoveEnvVars removes the environment variables which have the keys from all the components in a devfile -func (d DevfileObj) RemoveEnvVars(keys []string) (err error) { - components, err := d.Data.GetComponents(common.DevfileOptions{}) +// RemoveEnvVars accepts a map of container name mapped to an array of environment variables to be removed; +// it removes the env vars from the specified container name and writes it to the devfile +func (d DevfileObj) RemoveEnvVars(containerEnvMap map[string][]string) (err error) { + err = d.Data.RemoveEnvVars(containerEnvMap) if err != nil { return err } - for _, component := range components { - if component.Container != nil { - component.Container.Env, err = RemoveEnvVarsFromList(component.Container.Env, keys) - if err != nil { - return err - } - d.Data.UpdateComponent(component) - } - } return d.WriteYamlDevfile() } -// SetPorts converts ports to endpoints, adds to a devfile -func (d DevfileObj) SetPorts(ports ...string) error { - components, err := d.Data.GetComponents(common.DevfileOptions{}) +// SetPorts accepts a map of container name mapped to an array of port numbers to be set; +// it converts ports to endpoints, sets the endpoint to a given container name, and writes to the devfile +// Example of containerPortsMap: {"runtime": {"8080", "9000"}, "wildfly": {"12956"}} +func (d DevfileObj) SetPorts(containerPortsMap map[string][]string) error { + err := d.Data.SetPorts(containerPortsMap) if err != nil { return err } - endpoints, err := portsToEndpoints(ports...) - if err != nil { - return err - } - for _, component := range components { - if component.Container != nil { - component.Container.Endpoints = addEndpoints(component.Container.Endpoints, endpoints) - d.Data.UpdateComponent(component) - } - } return d.WriteYamlDevfile() } -// RemovePorts removes all container endpoints from a devfile -func (d DevfileObj) RemovePorts() error { - components, err := d.Data.GetComponents(common.DevfileOptions{}) +// RemovePorts accepts a map of container name mapped to an array of port numbers to be removed; +// it removes the container endpoints with the specified port numbers of the specified container, and writes to the devfile +// Example of containerPortsMap: {"runtime": {"8080", "9000"}, "wildfly": {"12956"}} +func (d DevfileObj) RemovePorts(containerPortsMap map[string][]string) error { + err := d.Data.RemovePorts(containerPortsMap) if err != nil { return err } - for _, component := range components { - if component.Container != nil { - component.Container.Endpoints = []v1.Endpoint{} - d.Data.UpdateComponent(component) - } - } return d.WriteYamlDevfile() } @@ -146,152 +117,3 @@ func (d DevfileObj) GetMemory() string { func (d DevfileObj) GetMetadataName() string { return d.Data.GetMetadata().Name } - -func portsToEndpoints(ports ...string) ([]v1.Endpoint, error) { - var endpoints []v1.Endpoint - conPorts, err := GetContainerPortsFromStrings(ports) - if err != nil { - return nil, err - } - for _, port := range conPorts { - - endpoint := v1.Endpoint{ - Name: fmt.Sprintf("port-%d-%s", port.ContainerPort, strings.ToLower(string(port.Protocol))), - TargetPort: int(port.ContainerPort), - Protocol: v1.EndpointProtocol(strings.ToLower(string(port.Protocol))), - } - endpoints = append(endpoints, endpoint) - } - return endpoints, nil - -} - -func addEndpoints(current []v1.Endpoint, other []v1.Endpoint) []v1.Endpoint { - newList := make([]v1.Endpoint, len(current)) - copy(newList, current) - for _, ep := range other { - present := false - - for _, presentep := range newList { - - protocol := presentep.Protocol - if protocol == "" { - // endpoint protocol default value is http - protocol = "http" - } - // if the target port and protocol match, we add a case where the protocol is not provided and hence we assume that to be "tcp" - if presentep.TargetPort == ep.TargetPort && (ep.Protocol == protocol) { - present = true - break - } - } - if !present { - newList = append(newList, ep) - } - } - - return newList -} - -// GetContainerPortsFromStrings generates ContainerPort values from the array of string port values -// ports is the array containing the string port values -func GetContainerPortsFromStrings(ports []string) ([]corev1.ContainerPort, error) { - var containerPorts []corev1.ContainerPort - for _, port := range ports { - splits := strings.Split(port, "/") - if len(splits) < 1 || len(splits) > 2 { - return nil, fmt.Errorf("unable to parse the port string %s", port) - } - - portNumberI64, err := strconv.ParseInt(splits[0], 10, 32) - if err != nil { - return nil, fmt.Errorf("invalid port number %s", splits[0]) - } - portNumber := int32(portNumberI64) - - var portProto corev1.Protocol - if len(splits) == 2 { - switch strings.ToUpper(splits[1]) { - case "TCP": - portProto = corev1.ProtocolTCP - case "UDP": - portProto = corev1.ProtocolUDP - default: - return nil, fmt.Errorf("invalid port protocol %s", splits[1]) - } - } else { - portProto = corev1.ProtocolTCP - } - - port := corev1.ContainerPort{ - Name: fmt.Sprintf("%d-%s", portNumber, strings.ToLower(string(portProto))), - ContainerPort: portNumber, - Protocol: portProto, - } - containerPorts = append(containerPorts, port) - } - return containerPorts, 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) { - // convert the envVarList map to an array to easily search for env var(s) - // to remove from the component - envVarListArray := []string{} - for _, env := range envVarList { - envVarListArray = append(envVarListArray, env.Name) - } - - // now check if the environment variable(s) requested for removal exists in - // the env vars set for the component by odo - for _, key := range keys { - if !InArray(envVarListArray, key) { - return nil, fmt.Errorf("unable to find environment variable %s in the component", key) - } - } - - // finally, let's remove the environment variables(s) requested by the user - newEnvVarList := []v1.EnvVar{} - for _, envVar := range envVarList { - // if the env is in the keys we skip it - if InArray(keys, envVar.Name) { - continue - } - newEnvVarList = append(newEnvVarList, envVar) - } - return newEnvVarList, nil -} - -// Merge merges the other EnvVarlist with keeping last value for duplicate EnvVars -// and returns a new EnvVarList -func Merge(original []v1.EnvVar, other []v1.EnvVar) []v1.EnvVar { - - var dedupNewEvl []v1.EnvVar - newEvl := append(original, other...) - uniqueMap := make(map[string]string) - // last value will be kept in case of duplicate env vars - for _, envVar := range newEvl { - uniqueMap[envVar.Name] = envVar.Value - } - - for key, value := range uniqueMap { - dedupNewEvl = append(dedupNewEvl, v1.EnvVar{ - Name: key, - Value: value, - }) - } - - return dedupNewEvl - -} - -// In checks if the value is in the array -func InArray(arr []string, value string) bool { - for _, item := range arr { - if item == value { - return true - } - } - return false -} diff --git a/vendor/github.com/devfile/library/pkg/devfile/parser/data/interface.go b/vendor/github.com/devfile/library/pkg/devfile/parser/data/interface.go index 5721ca1da83..743077e8b43 100644 --- a/vendor/github.com/devfile/library/pkg/devfile/parser/data/interface.go +++ b/vendor/github.com/devfile/library/pkg/devfile/parser/data/interface.go @@ -82,4 +82,10 @@ type DevfileData interface { GetDevfileContainerComponents(common.DevfileOptions) ([]v1.Component, error) GetDevfileVolumeComponents(common.DevfileOptions) ([]v1.Component, error) + + // containers + RemoveEnvVars(containerEnvMap map[string][]string) error + SetPorts(containerPortsMap map[string][]string) error + AddEnvVars(containerEnvMap map[string][]v1.EnvVar) error + RemovePorts(containerPortsMap map[string][]string) error } diff --git a/vendor/github.com/devfile/library/pkg/devfile/parser/data/mock_interface.go b/vendor/github.com/devfile/library/pkg/devfile/parser/data/mock_interface.go index 89951d41fdf..fb21d6a13f7 100644 --- a/vendor/github.com/devfile/library/pkg/devfile/parser/data/mock_interface.go +++ b/vendor/github.com/devfile/library/pkg/devfile/parser/data/mock_interface.go @@ -550,3 +550,59 @@ func (mr *MockDevfileDataMockRecorder) UpdateStarterProject(project interface{}) mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateStarterProject", reflect.TypeOf((*MockDevfileData)(nil).UpdateStarterProject), project) } + +// RemoveEnvVars mocks base method +func (m *MockDevfileData) RemoveEnvVars(containerEnvMap map[string][]string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RemoveEnvVars", containerEnvMap) + ret0, _ := ret[0].(error) + return ret0 +} + +// RemoveEnvVars indicates an expected call of RemoveEnvVars +func (mr *MockDevfileDataMockRecorder) RemoveEnvVars(containerEnvMap interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveEnvVars", reflect.TypeOf((*MockDevfileData)(nil).RemoveEnvVars), containerEnvMap) +} + +// SetPorts mocks base method +func (m *MockDevfileData) SetPorts(containerPortsMap map[string][]string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetPorts", containerPortsMap) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetPorts indicates an expected call of SetPorts +func (mr *MockDevfileDataMockRecorder) SetPorts(containerPortsMap interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetPorts", reflect.TypeOf((*MockDevfileData)(nil).SetPorts), containerPortsMap) +} + +// AddEnvVars mocks base method +func (m *MockDevfileData) AddEnvVars(containerEnvMap map[string][]v1alpha2.EnvVar) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AddEnvVars", containerEnvMap) + ret0, _ := ret[0].(error) + return ret0 +} + +// AddEnvVars indicates an expected call of AddEnvVars +func (mr *MockDevfileDataMockRecorder) AddEnvVars(containerEnvMap interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddEnvVars", reflect.TypeOf((*MockDevfileData)(nil).AddEnvVars), containerEnvMap) +} + +// RemovePorts mocks base method +func (m *MockDevfileData) RemovePorts(containerPortsMap map[string][]string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RemovePorts", containerPortsMap) + ret0, _ := ret[0].(error) + return ret0 +} + +// RemovePorts indicates an expected call of RemovePorts +func (mr *MockDevfileDataMockRecorder) RemovePorts(containerPortsMap interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemovePorts", reflect.TypeOf((*MockDevfileData)(nil).RemovePorts), containerPortsMap) +} diff --git a/vendor/github.com/devfile/library/pkg/devfile/parser/data/v2/containers.go b/vendor/github.com/devfile/library/pkg/devfile/parser/data/v2/containers.go new file mode 100644 index 00000000000..7671f180bcc --- /dev/null +++ b/vendor/github.com/devfile/library/pkg/devfile/parser/data/v2/containers.go @@ -0,0 +1,279 @@ +package v2 + +import ( + "fmt" + "strconv" + "strings" + + v1alpha2 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" + "github.com/devfile/library/pkg/devfile/parser/data/v2/common" + corev1 "k8s.io/api/core/v1" +) + +// AddEnvVars accepts a map of container name mapped to an array of the env vars to be set; +// it adds the envirnoment variables to a given container name of the DevfileV2 object +// Example of containerEnvMap : {"runtime": {{Name: "Foo", Value: "Bar"}}} +func (d *DevfileV2) AddEnvVars(containerEnvMap map[string][]v1alpha2.EnvVar) error { + components, err := d.GetComponents(common.DevfileOptions{}) + if err != nil { + return err + } + for _, component := range components { + if component.Container != nil { + component.Container.Env = merge(component.Container.Env, containerEnvMap[component.Name]) + d.UpdateComponent(component) + } + } + return nil +} + +// RemoveEnvVars accepts a map of container name mapped to an array of environment variables to be removed; +// it removes the env vars from the specified container name of the DevfileV2 object +func (d *DevfileV2) RemoveEnvVars(containerEnvMap map[string][]string) error { + components, err := d.GetComponents(common.DevfileOptions{}) + if err != nil { + return err + } + for _, component := range components { + if component.Container != nil { + component.Container.Env, err = removeEnvVarsFromList(component.Container.Env, containerEnvMap[component.Name]) + if err != nil { + return err + } + d.UpdateComponent(component) + } + } + return nil +} + +// SetPorts accepts a map of container name mapped to an array of port numbers to be set; +// it converts ports to endpoints, sets the endpoint to a given container name of the DevfileV2 object +// Example of containerPortsMap: {"runtime": {"8080", "9000"}, "wildfly": {"12956"}} +func (d *DevfileV2) SetPorts(containerPortsMap map[string][]string) error { + components, err := d.GetComponents(common.DevfileOptions{}) + if err != nil { + return err + } + for _, component := range components { + endpoints, err := portsToEndpoints(containerPortsMap[component.Name]...) + if err != nil { + return err + } + if component.Container != nil { + component.Container.Endpoints = addEndpoints(component.Container.Endpoints, endpoints) + d.UpdateComponent(component) + } + } + + return nil +} + +// RemovePorts accepts a map of container name mapped to an array of port numbers to be removed; +// it removes the container endpoints with the specified port numbers of the specified container of the DevfileV2 object +// Example of containerPortsMap: {"runtime": {"8080", "9000"}, "wildfly": {"12956"}} +func (d *DevfileV2) RemovePorts(containerPortsMap map[string][]string) error { + components, err := d.GetComponents(common.DevfileOptions{}) + if err != nil { + return err + } + for _, component := range components { + if component.Container != nil { + component.Container.Endpoints, err = removePortsFromList(component.Container.Endpoints, containerPortsMap[component.Name]) + if err != nil { + return err + } + d.UpdateComponent(component) + } + } + + return nil +} + +// removeEnvVarsFromList removes the env variables based on the keys provided +// and returns a new EnvVarList +func removeEnvVarsFromList(envVarList []v1alpha2.EnvVar, keys []string) ([]v1alpha2.EnvVar, error) { + // convert the array of envVarList to a map such that it can easily search for env var(s) + // to remove from the component + envVarListMap := map[string]bool{} + for _, env := range envVarList { + if !envVarListMap[env.Name] { + envVarListMap[env.Name] = true + } + } + + // convert the array of keys to a map so that it can do a fast search for environment variable(s) + // to remove from the component + envVarToBeRemoved := map[string]bool{} + // now check if the environment variable(s) requested for removal exists in + // the env vars currently set in the component + // if an env var requested for removal is not currently set, then raise an error + // else add the env var to the envVarToBeRemoved map + for _, key := range keys { + if !envVarListMap[key] { + return envVarList, fmt.Errorf("unable to find environment variable %s in the component", key) + } + envVarToBeRemoved[key] = true + } + + // finally, let's remove the environment variables(s) requested by the user + newEnvVarList := []v1alpha2.EnvVar{} + for _, envVar := range envVarList { + // if the env is in the keys(env var(s) to be removed), we skip it + if envVarToBeRemoved[envVar.Name] { + continue + } + newEnvVarList = append(newEnvVarList, envVar) + } + return newEnvVarList, nil +} + +// removePortsFromList removes the ports from a given Endpoint list based on the provided port numbers +// and returns a new list of Endpoint +func removePortsFromList(endpoints []v1alpha2.Endpoint, ports []string) ([]v1alpha2.Endpoint, error) { + // convert the array of Endpoint to a map such that it can easily search for port(s) + // to remove from the component + portInEndpoint := map[string]bool{} + for _, ep := range endpoints { + port := strconv.Itoa(ep.TargetPort) + if !portInEndpoint[port] { + portInEndpoint[port] = true + } + } + + // convert the array of ports to a map so that it can do a fast search for port(s) + // to remove from the component + portsToBeRemoved := map[string]bool{} + + // now check if the port(s) requested for removal exists in + // the ports currently present in the component; + // if a port requested for removal is not currently present, then raise an error + // else add the port to the portsToBeRemoved map + for _, port := range ports { + if !portInEndpoint[port] { + return endpoints, fmt.Errorf("unable to find port %q in the component", port) + } + portsToBeRemoved[port] = true + } + + // finally, let's remove the port(s) requested by the user + newEndpointsList := []v1alpha2.Endpoint{} + for _, ep := range endpoints { + // if the port is in the port(s)(to be removed), we skip it + if portsToBeRemoved[strconv.Itoa(ep.TargetPort)] { + continue + } + newEndpointsList = append(newEndpointsList, ep) + } + return newEndpointsList, nil +} + +// merge merges the other EnvVarlist with keeping last value for duplicate EnvVars +// and returns a new EnvVarList +func merge(original []v1alpha2.EnvVar, other []v1alpha2.EnvVar) []v1alpha2.EnvVar { + + var dedupNewEvl []v1alpha2.EnvVar + newEvl := append(original, other...) + uniqueMap := make(map[string]string) + // last value will be kept in case of duplicate env vars + for _, envVar := range newEvl { + uniqueMap[envVar.Name] = envVar.Value + } + + for key, value := range uniqueMap { + dedupNewEvl = append(dedupNewEvl, v1alpha2.EnvVar{ + Name: key, + Value: value, + }) + } + + return dedupNewEvl + +} + +// portsToEndpoints converts an array of ports to an array of v1alpha2.Endpoint +func portsToEndpoints(ports ...string) ([]v1alpha2.Endpoint, error) { + var endpoints []v1alpha2.Endpoint + conPorts, err := getContainerPortsFromStrings(ports) + if err != nil { + return nil, err + } + for _, port := range conPorts { + + endpoint := v1alpha2.Endpoint{ + Name: fmt.Sprintf("port-%d-%s", port.ContainerPort, strings.ToLower(string(port.Protocol))), + TargetPort: int(port.ContainerPort), + Protocol: v1alpha2.EndpointProtocol(strings.ToLower(string(port.Protocol))), + } + endpoints = append(endpoints, endpoint) + } + return endpoints, nil + +} + +// addEndpoints appends two arrays of v1alpha2.Endpoint objects +func addEndpoints(current []v1alpha2.Endpoint, other []v1alpha2.Endpoint) []v1alpha2.Endpoint { + newList := make([]v1alpha2.Endpoint, len(current)) + copy(newList, current) + for _, ep := range other { + present := false + + for _, presentep := range newList { + + protocol := presentep.Protocol + if protocol == "" { + // endpoint protocol default value is http + protocol = "http" + } + // if the target port and protocol match, we add a case where the protocol is not provided and hence we assume that to be "tcp" + if presentep.TargetPort == ep.TargetPort && (ep.Protocol == protocol) { + present = true + break + } + } + if !present { + newList = append(newList, ep) + } + } + + return newList +} + +// getContainerPortsFromStrings generates ContainerPort values from the array of string port values +// ports is the array containing the string port values +func getContainerPortsFromStrings(ports []string) ([]corev1.ContainerPort, error) { + var containerPorts []corev1.ContainerPort + for _, port := range ports { + splits := strings.Split(port, "/") + if len(splits) < 1 || len(splits) > 2 { + return nil, fmt.Errorf("unable to parse the port string %s", port) + } + + portNumberI64, err := strconv.ParseInt(splits[0], 10, 32) + if err != nil { + return nil, fmt.Errorf("invalid port number %s", splits[0]) + } + portNumber := int32(portNumberI64) + + var portProto corev1.Protocol + if len(splits) == 2 { + switch strings.ToUpper(splits[1]) { + case "TCP": + portProto = corev1.ProtocolTCP + case "UDP": + portProto = corev1.ProtocolUDP + default: + return nil, fmt.Errorf("invalid port protocol %s", splits[1]) + } + } else { + portProto = corev1.ProtocolTCP + } + + port := corev1.ContainerPort{ + Name: fmt.Sprintf("%d-%s", portNumber, strings.ToLower(string(portProto))), + ContainerPort: portNumber, + Protocol: portProto, + } + containerPorts = append(containerPorts, port) + } + return containerPorts, nil +} diff --git a/vendor/github.com/devfile/library/pkg/testingutil/filesystem/default_fs.go b/vendor/github.com/devfile/library/pkg/testingutil/filesystem/default_fs.go index 5e5b7101b05..9eea63fc320 100644 --- a/vendor/github.com/devfile/library/pkg/testingutil/filesystem/default_fs.go +++ b/vendor/github.com/devfile/library/pkg/testingutil/filesystem/default_fs.go @@ -92,6 +92,11 @@ func (DefaultFs) Remove(name string) error { return os.Remove(name) } +// Getwd via os.Getwd +func (DefaultFs) Getwd() (dir string, err error) { + return os.Getwd() +} + // ReadFile via ioutil.ReadFile func (DefaultFs) ReadFile(filename string) ([]byte, error) { return ioutil.ReadFile(filename) @@ -126,6 +131,11 @@ func (DefaultFs) Walk(root string, walkFn filepath.WalkFunc) error { return filepath.Walk(root, walkFn) } +// Chmod via os.Chmod +func (f DefaultFs) Chmod(name string, mode os.FileMode) error { + return os.Chmod(name, mode) +} + // defaultFile implements File using same-named functions from "os" type defaultFile struct { file *os.File @@ -159,3 +169,11 @@ func (file *defaultFile) Close() error { func (file *defaultFile) Readdir(n int) ([]os.FileInfo, error) { return file.file.Readdir(n) } + +func (file *defaultFile) Read(b []byte) (n int, err error) { + return file.file.Read(b) +} + +func (file *defaultFile) Chmod(name string, mode os.FileMode) error { + return file.file.Chmod(mode) +} diff --git a/vendor/github.com/devfile/library/pkg/testingutil/filesystem/fake_fs.go b/vendor/github.com/devfile/library/pkg/testingutil/filesystem/fake_fs.go index 85aafa86fc8..fbf016dcdb3 100644 --- a/vendor/github.com/devfile/library/pkg/testingutil/filesystem/fake_fs.go +++ b/vendor/github.com/devfile/library/pkg/testingutil/filesystem/fake_fs.go @@ -125,11 +125,20 @@ func (fs *fakeFs) RemoveAll(path string) error { return fs.a.RemoveAll(path) } +func (fs *fakeFs) Getwd() (dir string, err error) { + return ".", nil +} + // Remove via afero.RemoveAll func (fs *fakeFs) Remove(name string) error { return fs.a.Remove(name) } +// Chmod via afero.Chmod +func (fs *fakeFs) Chmod(name string, mode os.FileMode) error { + return fs.a.Chmod(name, mode) +} + // fakeFile implements File; for use with fakeFs type fakeFile struct { file afero.File @@ -163,3 +172,7 @@ func (file *fakeFile) Close() error { func (file *fakeFile) Readdir(n int) ([]os.FileInfo, error) { return file.file.Readdir(n) } + +func (file *fakeFile) Read(b []byte) (n int, err error) { + return file.file.Read(b) +} diff --git a/vendor/github.com/devfile/library/pkg/testingutil/filesystem/filesystem.go b/vendor/github.com/devfile/library/pkg/testingutil/filesystem/filesystem.go index aee6b16dbc0..e71b6ffc8d2 100644 --- a/vendor/github.com/devfile/library/pkg/testingutil/filesystem/filesystem.go +++ b/vendor/github.com/devfile/library/pkg/testingutil/filesystem/filesystem.go @@ -39,6 +39,8 @@ type Filesystem interface { Chtimes(name string, atime time.Time, mtime time.Time) error RemoveAll(path string) error Remove(name string) error + Chmod(name string, mode os.FileMode) error + Getwd() (dir string, err error) // from "io/ioutil" ReadFile(filename string) ([]byte, error) @@ -58,5 +60,6 @@ type File interface { WriteString(s string) (n int, err error) Sync() error Close() error + Read(b []byte) (n int, err error) Readdir(n int) ([]os.FileInfo, error) } diff --git a/vendor/github.com/devfile/library/pkg/testingutil/filesystem/singleton.go b/vendor/github.com/devfile/library/pkg/testingutil/filesystem/singleton.go new file mode 100644 index 00000000000..a8dcea8f21d --- /dev/null +++ b/vendor/github.com/devfile/library/pkg/testingutil/filesystem/singleton.go @@ -0,0 +1,10 @@ +package filesystem + +var singleFs Filesystem + +func Get() Filesystem { + if singleFs == nil { + singleFs = &DefaultFs{} + } + return singleFs +} diff --git a/vendor/github.com/devfile/library/pkg/testingutil/filesystem/watcher.go b/vendor/github.com/devfile/library/pkg/testingutil/filesystem/watcher.go new file mode 100644 index 00000000000..af93ffbbf31 --- /dev/null +++ b/vendor/github.com/devfile/library/pkg/testingutil/filesystem/watcher.go @@ -0,0 +1,88 @@ +/* +Copyright 2017 The Kubernetes Authors. + +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. +*/ + +/* + This package is a FORK of https://github.com/kubernetes/kubernetes/blob/master/pkg/util/filesystem/watcher.go + See above license +*/ + +package filesystem + +import ( + "github.com/fsnotify/fsnotify" +) + +// FSWatcher is a callback-based filesystem watcher abstraction for fsnotify. +type FSWatcher interface { + // Initializes the watcher with the given watch handlers. + // Called before all other methods. + Init(FSEventHandler, FSErrorHandler) error + + // Starts listening for events and errors. + // When an event or error occurs, the corresponding handler is called. + Run() + + // Add a filesystem path to watch + AddWatch(path string) error +} + +// FSEventHandler is called when a fsnotify event occurs. +type FSEventHandler func(event fsnotify.Event) + +// FSErrorHandler is called when a fsnotify error occurs. +type FSErrorHandler func(err error) + +type fsnotifyWatcher struct { + watcher *fsnotify.Watcher + eventHandler FSEventHandler + errorHandler FSErrorHandler +} + +var _ FSWatcher = &fsnotifyWatcher{} + +func (w *fsnotifyWatcher) AddWatch(path string) error { + return w.watcher.Add(path) +} + +func (w *fsnotifyWatcher) Init(eventHandler FSEventHandler, errorHandler FSErrorHandler) error { + var err error + w.watcher, err = fsnotify.NewWatcher() + if err != nil { + return err + } + + w.eventHandler = eventHandler + w.errorHandler = errorHandler + return nil +} + +func (w *fsnotifyWatcher) Run() { + go func() { + defer w.watcher.Close() + for { + select { + case event := <-w.watcher.Events: + if w.eventHandler != nil { + w.eventHandler(event) + } + case err := <-w.watcher.Errors: + if w.errorHandler != nil { + w.errorHandler(err) + } + } + } + }() +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 5c121e66f96..0d3923e6ed2 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -105,7 +105,7 @@ github.com/devfile/api/v2/pkg/utils/overriding github.com/devfile/api/v2/pkg/utils/unions github.com/devfile/api/v2/pkg/validation github.com/devfile/api/v2/pkg/validation/variables -# github.com/devfile/library v1.2.1-0.20220201144851-0749c7fa35a1 +# github.com/devfile/library v1.2.1-0.20220217161036-0f5995513e92 ## explicit github.com/devfile/library/pkg/devfile github.com/devfile/library/pkg/devfile/generator