diff --git a/devfile.yaml b/devfile.yaml index b4490506..d9913736 100644 --- a/devfile.yaml +++ b/devfile.yaml @@ -4,13 +4,24 @@ metadata: version: 1.0.0 attributes: alpha.build-dockerfile: /relative/path/to/Dockerfile +parent: + uri: https://raw.githubusercontent.com/odo-devfiles/registry/master/devfiles/nodejs/devfile.yaml + commands: + - id: install + exec: + component: runtime + commandLine: npm install + workingDir: /project-starter + group: + kind: build + isDefault: true starterProjects: -- name: nodejs-starter +- name: nodejs-starter2 git: remotes: origin: https://github.com/odo-devfiles/nodejs-ex.git components: -- name: runtime +- name: runtime2 attributes: tool: console-import import: @@ -23,7 +34,7 @@ components: memoryLimit: 1024Mi mountSources: true sourceMapping: /project -- name: runtime2 +- name: runtime3 attributes: tool: odo cli: @@ -36,7 +47,7 @@ components: memoryLimit: 1024Mi mountSources: true sourceMapping: /project -- name: runtime3 +- name: runtime4 attributes: tool: workspace-operator container: @@ -50,39 +61,39 @@ components: commands: - exec: commandLine: npm install - component: runtime + component: runtime2 group: isDefault: true kind: build workingDir: /project - id: install + id: install2 attributes: tool: odo mandatory: false - exec: commandLine: npm start - component: runtime + component: runtime2 group: isDefault: true kind: run workingDir: /project - id: run + id: run2 attributes: tool: odo mandatory: true - exec: commandLine: npm run debug - component: runtime + component: runtime2 group: isDefault: true kind: debug workingDir: /project - id: debug + id: debug2 - exec: commandLine: npm test - component: runtime + component: runtime2 group: isDefault: true kind: test workingDir: /project - id: test + id: test2 diff --git a/go.mod b/go.mod index 4761fdb9..7537dcf1 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/devfile/library go 1.13 require ( - github.com/devfile/api v0.0.0-20201126204309-ec222215253e + github.com/devfile/api v0.0.0-20201211221100-a68230324c7e github.com/fatih/color v1.7.0 github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 github.com/gobwas/glob v0.2.3 diff --git a/go.sum b/go.sum index 66dbcc3e..68bd42ff 100644 --- a/go.sum +++ b/go.sum @@ -44,8 +44,8 @@ github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7Do github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/devfile/api v0.0.0-20201126204309-ec222215253e h1:NO6mS8ktVFjkVIKZKF2JjfL3ZxXw4VAtKIZWyp9e2kQ= -github.com/devfile/api v0.0.0-20201126204309-ec222215253e/go.mod h1:/aDiwWjDEW/fY1/Ig8umVtmneAXKZImnLvWqzMlcfrY= +github.com/devfile/api v0.0.0-20201211221100-a68230324c7e h1:Jy3C3ul05YvL4bJpAVhFwPZD8neOJUBZy7GuCcjc8nc= +github.com/devfile/api v0.0.0-20201211221100-a68230324c7e/go.mod h1:/aDiwWjDEW/fY1/Ig8umVtmneAXKZImnLvWqzMlcfrY= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= @@ -182,7 +182,9 @@ github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:Fecb github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI= github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -235,6 +237,7 @@ github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzp github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/reflectwalk v1.0.1 h1:FVzMWA5RllMAKIdUSC8mdWo3XtwoecrH79BY70sEEpE= github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= diff --git a/main.go b/main.go index e849288c..17a09b68 100644 --- a/main.go +++ b/main.go @@ -11,7 +11,7 @@ import ( ) func main() { - devfile, err := ParseDevfile("devfile.yaml") + devfile, err := ParseDevfile("./devfile.yaml") if err != nil { fmt.Println(err) } else { @@ -21,6 +21,29 @@ func main() { fmt.Printf("schema version: %s\n", d.SchemaVersion) } + components, e := devfile.Data.GetComponents(common.DevfileOptions{}) + if e != nil { + fmt.Printf("err: %v\n", err) + } + fmt.Printf("All component: \n") + for _, component := range components { + fmt.Printf("%s\n", component.Name) + } + + fmt.Printf("All Exec commands: \n") + commands, e := devfile.Data.GetCommands(common.DevfileOptions{}) + if e != nil { + fmt.Printf("err: %v\n", err) + } + for _, command := range commands { + if command.Exec != nil { + fmt.Printf("command %s is with kind: %s", command.Id, command.Exec.Group.Kind) + fmt.Printf("workingDir is: %s\n", command.Exec.WorkingDir) + } + } + + fmt.Println("=========================================================") + compOptions := common.DevfileOptions{ Filter: map[string]interface{}{ "tool": "console-import", @@ -30,14 +53,14 @@ func main() { }, } - components, e := devfile.Data.GetComponents(compOptions) + components, e = devfile.Data.GetComponents(compOptions) if e != nil { fmt.Printf("err: %v\n", err) } - + fmt.Printf("Container components applied filter: \n") for _, component := range components { if component.Container != nil { - fmt.Printf("component container: %s\n", component.Name) + fmt.Printf("%s\n", component.Name) } } @@ -47,13 +70,15 @@ func main() { }, } - commands, e := devfile.Data.GetCommands(cmdOptions) + fmt.Printf("Exec commands applied filter: \n") + commands, e = devfile.Data.GetCommands(cmdOptions) if e != nil { fmt.Printf("err: %v\n", err) } for _, command := range commands { if command.Exec != nil { - fmt.Printf("exec command kind: %s\n", command.Exec.Group.Kind) + fmt.Printf("command %s is with kind: %s", command.Id, command.Exec.Group.Kind) + fmt.Printf("workingDir is: %s\n", command.Exec.WorkingDir) } } diff --git a/pkg/devfile/parser/context/context.go b/pkg/devfile/parser/context/context.go index 49448eb0..c00763c6 100644 --- a/pkg/devfile/parser/context/context.go +++ b/pkg/devfile/parser/context/context.go @@ -67,7 +67,6 @@ func (d *DevfileCtx) Populate() (err error) { return err } klog.V(4).Infof("absolute devfile path: '%s'", d.absPath) - // Read and save devfile content if err := d.SetDevfileContent(); err != nil { return err diff --git a/pkg/devfile/parser/data/interface.go b/pkg/devfile/parser/data/interface.go index 714cd81b..56dd6e68 100644 --- a/pkg/devfile/parser/data/interface.go +++ b/pkg/devfile/parser/data/interface.go @@ -47,6 +47,10 @@ type DevfileData interface { DeleteVolume(name string) error GetVolumeMountPath(name string) (string, error) + // workspace related methods + GetDevfileWorkspace() *v1.DevWorkspaceTemplateSpecContent + SetDevfileWorkspace(content v1.DevWorkspaceTemplateSpecContent) + //utils GetDevfileContainerComponents(common.DevfileOptions) ([]v1.Component, error) GetDevfileVolumeComponents(common.DevfileOptions) ([]v1.Component, error) diff --git a/pkg/devfile/parser/data/v2/common/options.go b/pkg/devfile/parser/data/v2/common/options.go index ffdfb710..f4de76ee 100644 --- a/pkg/devfile/parser/data/v2/common/options.go +++ b/pkg/devfile/parser/data/v2/common/options.go @@ -3,7 +3,7 @@ package common import ( "reflect" - "github.com/devfile/api/pkg/attributes" + apiAttributes "github.com/devfile/api/pkg/attributes" ) // DevfileOptions provides options for Devfile operations @@ -13,13 +13,14 @@ type DevfileOptions struct { } // FilterDevfileObject filters devfile attributes with the given options -func FilterDevfileObject(attributes attributes.Attributes, options DevfileOptions) (bool, error) { +func FilterDevfileObject(attributes apiAttributes.Attributes, options DevfileOptions) (bool, error) { var err error filterIn := true for key, value := range options.Filter { currentFilterIn := false attrValue := attributes.Get(key, &err) - if err != nil { + var keyNotFoundErr = &apiAttributes.KeyNotFoundError{Key: key} + if err != nil && err.Error() != keyNotFoundErr.Error() { return false, err } else if reflect.DeepEqual(attrValue, value) { currentFilterIn = true diff --git a/pkg/devfile/parser/data/v2/components.go b/pkg/devfile/parser/data/v2/components.go index 8b0c5d6b..e6a4b708 100644 --- a/pkg/devfile/parser/data/v2/components.go +++ b/pkg/devfile/parser/data/v2/components.go @@ -60,36 +60,16 @@ func (d *DevfileV2) GetDevfileVolumeComponents(options common.DevfileOptions) ([ // if a component is already defined, error out func (d *DevfileV2) AddComponents(components []v1.Component) error { - // different map for volume and container component as a volume and a container with same name - // can exist in devfile - containerMap := make(map[string]bool) - volumeMap := make(map[string]bool) + componentMap := make(map[string]bool) for _, component := range d.Components { - if component.Volume != nil { - volumeMap[component.Name] = true - } - if component.Container != nil { - containerMap[component.Name] = true - } + componentMap[component.Name] = true } - for _, component := range components { - - if component.Volume != nil { - if _, ok := volumeMap[component.Name]; !ok { - d.Components = append(d.Components, component) - } else { - return &common.FieldAlreadyExistError{Name: component.Name, Field: "component"} - } - } - - if component.Container != nil { - if _, ok := containerMap[component.Name]; !ok { - d.Components = append(d.Components, component) - } else { - return &common.FieldAlreadyExistError{Name: component.Name, Field: "component"} - } + if _, ok := componentMap[component.Name]; !ok { + d.Components = append(d.Components, component) + } else { + return &common.FieldAlreadyExistError{Name: component.Name, Field: "component"} } } return nil diff --git a/pkg/devfile/parser/data/v2/components_test.go b/pkg/devfile/parser/data/v2/components_test.go index 0369607f..930612da 100644 --- a/pkg/devfile/parser/data/v2/components_test.go +++ b/pkg/devfile/parser/data/v2/components_test.go @@ -35,12 +35,6 @@ func TestDevfile200_AddComponent(t *testing.T) { }, }, newComponents: []v1.Component{ - { - Name: "component2", - ComponentUnion: v1.ComponentUnion{ - Container: &v1.ContainerComponent{}, - }, - }, { Name: "component3", ComponentUnion: v1.ComponentUnion{ diff --git a/pkg/devfile/parser/data/v2/workspace.go b/pkg/devfile/parser/data/v2/workspace.go new file mode 100644 index 00000000..54e047e2 --- /dev/null +++ b/pkg/devfile/parser/data/v2/workspace.go @@ -0,0 +1,16 @@ +package v2 + +import ( + v1 "github.com/devfile/api/pkg/apis/workspaces/v1alpha2" +) + +// GetDevfileWorkspace returns the workspace content for the devfile +func (d *DevfileV2) GetDevfileWorkspace() *v1.DevWorkspaceTemplateSpecContent { + + return &d.DevWorkspaceTemplateSpecContent +} + +// SetDevfileWorkspace sets the workspace content +func (d *DevfileV2) SetDevfileWorkspace(content v1.DevWorkspaceTemplateSpecContent) { + d.DevWorkspaceTemplateSpecContent = content +} diff --git a/pkg/devfile/parser/data/v2/workspace_test.go b/pkg/devfile/parser/data/v2/workspace_test.go new file mode 100644 index 00000000..75f95237 --- /dev/null +++ b/pkg/devfile/parser/data/v2/workspace_test.go @@ -0,0 +1,74 @@ +package v2 + +import ( + "reflect" + "testing" + + v1 "github.com/devfile/api/pkg/apis/workspaces/v1alpha2" +) + +func TestDevfile200_SetDevfileWorkspace(t *testing.T) { + + type args struct { + name string + } + tests := []struct { + name string + workspace v1.DevWorkspaceTemplateSpecContent + devfilev2 *DevfileV2 + expectedDevfilev2 *DevfileV2 + }{ + { + name: "set workspace", + devfilev2: &DevfileV2{ + v1.Devfile{}, + }, + workspace: v1.DevWorkspaceTemplateSpecContent{ + Components: []v1.Component{ + { + Name: "component1", + ComponentUnion: v1.ComponentUnion{ + Container: &v1.ContainerComponent{}, + }, + }, + { + Name: "component2", + ComponentUnion: v1.ComponentUnion{ + Volume: &v1.VolumeComponent{}, + }, + }, + }, + }, + expectedDevfilev2: &DevfileV2{ + v1.Devfile{ + DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ + DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ + Components: []v1.Component{ + { + Name: "component1", + ComponentUnion: v1.ComponentUnion{ + Container: &v1.ContainerComponent{}, + }, + }, + { + Name: "component2", + ComponentUnion: v1.ComponentUnion{ + Volume: &v1.VolumeComponent{}, + }, + }, + }, + }, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.devfilev2.SetDevfileWorkspace(tt.workspace) + if !reflect.DeepEqual(tt.devfilev2, tt.expectedDevfilev2) { + t.Errorf("TestDevfile200_SetDevfileWorkspace() expected %v, got %v", tt.expectedDevfilev2, tt.devfilev2) + } + }) + } +} diff --git a/pkg/devfile/parser/devfileobj.go b/pkg/devfile/parser/devfileobj.go index eea65a9d..f7ee33e9 100644 --- a/pkg/devfile/parser/devfileobj.go +++ b/pkg/devfile/parser/devfileobj.go @@ -1,16 +1,8 @@ package parser import ( - "encoding/json" - "fmt" - "strings" - - v1 "github.com/devfile/api/pkg/apis/workspaces/v1alpha2" devfileCtx "github.com/devfile/library/pkg/devfile/parser/context" "github.com/devfile/library/pkg/devfile/parser/data" - "github.com/devfile/library/pkg/devfile/parser/data/v2/common" - "github.com/pkg/errors" - "k8s.io/apimachinery/pkg/util/strategicpatch" ) // Default filenames for create devfile @@ -28,221 +20,3 @@ type DevfileObj struct { // Data has the devfile data Data data.DevfileData } - -// OverrideComponents overrides the components of the parent devfile -// overridePatch contains the patches to be applied to the parent's components -func (d DevfileObj) OverrideComponents(overridePatch []v1.ComponentParentOverride) error { - // func (d DevfileObj) OverrideComponents(overridePatch interface{}) error { - for _, patchComponent := range overridePatch { - found := false - originalComponents, err := d.Data.GetComponents(common.DevfileOptions{}) - if err != nil { - return err - } - for _, originalComponent := range originalComponents { - if strings.ToLower(patchComponent.Name) == originalComponent.Name { - found = true - - var updatedComponent v1.ContainerComponent - - merged, err := handleMerge(originalComponent.Container, patchComponent.Container, v1.ContainerComponent{}) - if err != nil { - return err - } - err = json.Unmarshal(merged, &updatedComponent) - if err != nil { - return errors.Wrap(err, "failed to unmarshal override components") - } - - d.Data.UpdateComponent(v1.Component{ - Name: patchComponent.Name, - ComponentUnion: v1.ComponentUnion{ - Container: &updatedComponent, - }, - }) - } - } - if !found { - return fmt.Errorf("the component to override is not found in the parent") - } - } - return nil -} - -// OverrideCommands overrides the commands of the parent devfile -// overridePatch contains the patches to be applied to the parent's commands -func (d DevfileObj) OverrideCommands(overridePatch []v1.CommandParentOverride) (err error) { - for _, patchCommand := range overridePatch { - found := false - originalCommands, err := d.Data.GetCommands(common.DevfileOptions{}) - if err != nil { - return err - } - for _, originalCommand := range originalCommands { - - if strings.ToLower(patchCommand.Id) == originalCommand.Id { - found = true - - var devfileCommand v1.Command - - if patchCommand.Exec != nil && originalCommand.Exec != nil { - devfileCommand, err = overrideExecCommand(patchCommand, originalCommand) - if err != nil { - return err - } - - } else if patchCommand.Composite != nil && originalCommand.Composite != nil { - devfileCommand, err = overrideCompositeCommand(patchCommand, originalCommand) - if err != nil { - return err - } - // TODO: add other command types - } else { - // If the original command and patch command are different types, then we can't patch, so throw an error - return fmt.Errorf("cannot overide command %q with a different type of command", originalCommand.Id) - } - - d.Data.UpdateCommand(devfileCommand) - } - } - if !found { - return fmt.Errorf("the command to override is not found in the parent") - } - } - return nil -} - -// overrideCompositeCommand overrides the given parent composite commmand -// patchCommand contains the patches to be applied to the parent's command -func overrideCompositeCommand(patchCommand v1.CommandParentOverride, originalCommand v1.Command) (v1.Command, error) { - var updatedComposite v1.CompositeCommand - - merged, err := handleMerge(originalCommand.Composite, patchCommand.Composite, v1.CompositeCommand{}) - if err != nil { - return v1.Command{}, err - } - - err = json.Unmarshal(merged, &updatedComposite) - if err != nil { - return v1.Command{}, errors.Wrap(err, "failed to unmarshal override commands") - } - return v1.Command{ - Id: patchCommand.Id, - CommandUnion: v1.CommandUnion{ - Composite: &updatedComposite, - }, - }, nil -} - -// overrideExecCommand overrides the given parent Exec commmand -// patchCommand contains the patches to be applied to the parent's command -func overrideExecCommand(patchCommand v1.CommandParentOverride, originalCommand v1.Command) (v1.Command, error) { - var updatedExec v1.ExecCommand - merged, err := handleMerge(originalCommand.Exec, patchCommand.Exec, v1.ExecCommand{}) - if err != nil { - return v1.Command{}, err - } - - err = json.Unmarshal(merged, &updatedExec) - if err != nil { - return v1.Command{}, errors.Wrap(err, "failed to unmarshal override commands") - } - return v1.Command{ - Id: patchCommand.Id, - CommandUnion: v1.CommandUnion{ - Exec: &updatedExec, - }, - }, nil -} - -// OverrideProjects overrides the projects of the parent devfile -// overridePatch contains the patches to be applied to the parent's projects -func (d DevfileObj) OverrideProjects(overridePatch []v1.ProjectParentOverride) error { - for _, patchProject := range overridePatch { - found := false - originalProjects, err := d.Data.GetProjects(common.DevfileOptions{}) - if err != nil { - return err - } - for _, originalProject := range originalProjects { - if strings.ToLower(patchProject.Name) == originalProject.Name { - found = true - var updatedProject v1.Project - - merged, err := handleMerge(originalProject, patchProject, v1.Project{}) - if err != nil { - return err - } - - err = json.Unmarshal(merged, &updatedProject) - if err != nil { - return errors.Wrap(err, "failed to unmarshal override projects") - } - - d.Data.UpdateProject(updatedProject) - } - } - if !found { - return fmt.Errorf("the command to override is not found in the parent") - } - } - return nil -} - -// OverrideStarterProjects overrides the starter projects of the parent devfile -// overridePatch contains the patches to be applied to the parent's starter projects -func (d DevfileObj) OverrideStarterProjects(overridePatch []v1.StarterProjectParentOverride) error { - for _, patchProject := range overridePatch { - found := false - - originalProjects, err := d.Data.GetStarterProjects(common.DevfileOptions{}) - if err != nil { - return err - } - for _, originalProject := range originalProjects { - if strings.ToLower(patchProject.Name) == originalProject.Name { - found = true - var updatedProject v1.StarterProject - - merged, err := handleMerge(originalProject, patchProject, v1.StarterProject{}) - if err != nil { - return err - } - - err = json.Unmarshal(merged, &updatedProject) - if err != nil { - return errors.Wrap(err, "failed to unmarshal override starter projects") - } - d.Data.UpdateStarterProject(updatedProject) - } - } - if !found { - return fmt.Errorf("the starterProject to override is not found in the parent") - } - } - return nil -} - -// handleMerge merges the patch to the original data -// dataStruct is the type of the original and the patch data -func handleMerge(original, patch, dataStruct interface{}) ([]byte, error) { - // if reflect.TypeOf(original) != reflect.TypeOf(patch) { - // return nil, fmt.Errorf("type of original and patch doesn't match") - // } - - originalJson, err := json.Marshal(original) - if err != nil { - return nil, err - } - - patchJson, err := json.Marshal(patch) - if err != nil { - return nil, err - } - - merged, err := strategicpatch.StrategicMergePatch(originalJson, patchJson, dataStruct) - if err != nil { - return nil, err - } - return merged, nil -} diff --git a/pkg/devfile/parser/devfileobj_test.go b/pkg/devfile/parser/devfileobj_test.go deleted file mode 100644 index 28efb946..00000000 --- a/pkg/devfile/parser/devfileobj_test.go +++ /dev/null @@ -1,1399 +0,0 @@ -package parser - -import ( - "reflect" - "testing" - - devfileCtx "github.com/devfile/library/pkg/devfile/parser/context" - v2 "github.com/devfile/library/pkg/devfile/parser/data/v2" - "github.com/devfile/library/pkg/testingutil" - "github.com/kylelemons/godebug/pretty" - - v1 "github.com/devfile/api/pkg/apis/workspaces/v1alpha2" -) - -const devfileTempPath = "devfile.yaml" - -func TestDevfileObj_OverrideCommands(t *testing.T) { - componentName0 := "component-0" - overrideComponent0 := "override-component-0" - - commandLineBuild := "npm build" - overrideBuild := "npm custom build" - commandLineRun := "npm run" - - workingDir := "/project" - overrideWorkingDir := "/data" - - type args struct { - overridePatch []v1.CommandParentOverride - } - tests := []struct { - name string - devFileObj DevfileObj - args args - wantDevFileObj DevfileObj - wantErr bool - }{ - { - name: "case 1: override a command's non list/map fields", - devFileObj: DevfileObj{ - Ctx: devfileCtx.NewDevfileCtx(devfileTempPath), - Data: &v2.DevfileV2{ - Devfile: v1.Devfile{ - DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ - DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ - Commands: []v1.Command{ - { - Id: "devbuild", - CommandUnion: v1.CommandUnion{ - Exec: &v1.ExecCommand{ - CommandLine: commandLineBuild, - Component: componentName0, - Env: nil, - LabeledCommand: v1.LabeledCommand{ - BaseCommand: v1.BaseCommand{ - Group: &v1.CommandGroup{ - IsDefault: false, - Kind: v1.BuildCommandGroupKind, - }, - }, - }, - WorkingDir: workingDir, - }, - }, - }, - }, - }, - }, - }, - }, - }, - args: args{ - overridePatch: []v1.CommandParentOverride{ - { - Id: "devbuild", - CommandUnionParentOverride: v1.CommandUnionParentOverride{ - Exec: &v1.ExecCommandParentOverride{ - CommandLine: overrideBuild, - Component: overrideComponent0, - LabeledCommandParentOverride: v1.LabeledCommandParentOverride{ - BaseCommandParentOverride: v1.BaseCommandParentOverride{ - Group: &v1.CommandGroupParentOverride{ - IsDefault: true, - Kind: v1.CommandGroupKindParentOverride(v1.BuildCommandGroupKind), - }, - }, - }, - WorkingDir: overrideWorkingDir, - }, - }, - }, - }, - }, - wantDevFileObj: DevfileObj{ - Ctx: devfileCtx.NewDevfileCtx(devfileTempPath), - Data: &v2.DevfileV2{ - Devfile: v1.Devfile{ - DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ - DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ - Commands: []v1.Command{ - { - Id: "devbuild", - CommandUnion: v1.CommandUnion{ - Exec: &v1.ExecCommand{ - CommandLine: overrideBuild, - Component: overrideComponent0, - LabeledCommand: v1.LabeledCommand{ - BaseCommand: v1.BaseCommand{ - Group: &v1.CommandGroup{ - IsDefault: true, - Kind: v1.BuildCommandGroupKind, - }, - }, - }, - WorkingDir: overrideWorkingDir, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - // { - // name: "case 2: append/override a command's list fields based on the key", - // devFileObj: DevfileObj{ - // Ctx: devfileCtx.NewDevfileCtx(devfileTempPath), - // Data: &v2.DevfileV2{ - // Devfile: v1.Devfile{ - // DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ - // DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ - // Commands: []v1.Command{ - // { - // Id: "devbuild", - // CommandUnion: v1.CommandUnion{ - // Exec: &v1.ExecCommand{ - // LabeledCommand: v1.LabeledCommand{ - // BaseCommand: v1.BaseCommand{ - // Attributes: map[string]string{ - // "key-0": "value-0", - // }, - // }, - // }, - // Env: []v1.EnvVar{ - // testingutil.GetFakeEnv("env-0", "value-0"), - // }, - // }, - // }, - // }, - // }, - // }, - // }, - // }, - // }, - // }, - // args: args{ - // overridePatch: []v1.CommandParentOverride{ - // { - // Id: "devbuild", - // CommandUnionParentOverride: v1.CommandUnionParentOverride{ - // Exec: &v1.ExecCommandParentOverride{ - // LabeledCommandParentOverride: v1.LabeledCommandParentOverride{ - // BaseCommandParentOverride: v1.BaseCommandParentOverride{ - // Attributes: map[string]string{ - // "key-1": "value-1", - // }, - // }, - // }, - // Env: []v1.EnvVarParentOverride{ - // testingutil.GetFakeEnvParentOverride("env-0", "value-0-0"), - // testingutil.GetFakeEnvParentOverride("env-1", "value-1"), - // }, - // }, - // }, - // }, - // }, - // }, - // wantDevFileObj: DevfileObj{ - // Ctx: devfileCtx.NewDevfileCtx(devfileTempPath), - // Data: &v2.DevfileV2{ - // Devfile: v1.Devfile{ - // DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ - // DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ - // Commands: []v1.Command{ - // { - // Id: "devbuild", - // CommandUnion: v1.CommandUnion{ - // Exec: &v1.ExecCommand{ - // LabeledCommand: v1.LabeledCommand{ - // BaseCommand: v1.BaseCommand{ - // Attributes: map[string]string{ - // "key-0": "value-0", - // "key-1": "value-1", - // }, - // }, - // }, - // Env: []v1.EnvVar{ - // testingutil.GetFakeEnv("env-0", "value-0-0"), - // testingutil.GetFakeEnv("env-1", "value-1"), - // }, - // }, - // }, - // }, - // }, - // }, - // }, - // }, - // }, - // }, - // }, - { - name: "case 3: if multiple, override the correct command", - devFileObj: DevfileObj{ - Ctx: devfileCtx.NewDevfileCtx(devfileTempPath), - Data: &v2.DevfileV2{ - Devfile: v1.Devfile{ - DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ - DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ - Commands: []v1.Command{ - { - Id: "devbuild", - CommandUnion: v1.CommandUnion{ - Exec: &v1.ExecCommand{ - CommandLine: commandLineBuild, - }, - }, - }, - { - Id: "devrun", - CommandUnion: v1.CommandUnion{ - Exec: &v1.ExecCommand{ - CommandLine: commandLineRun, - }, - }, - }, - }, - }, - }, - }, - }, - }, - args: args{ - overridePatch: []v1.CommandParentOverride{ - { - Id: "devbuild", - CommandUnionParentOverride: v1.CommandUnionParentOverride{ - Exec: &v1.ExecCommandParentOverride{ - CommandLine: overrideBuild, - }, - }, - }, - }, - }, - wantDevFileObj: DevfileObj{ - Ctx: devfileCtx.NewDevfileCtx(devfileTempPath), - Data: &v2.DevfileV2{ - Devfile: v1.Devfile{ - DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ - DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ - Commands: []v1.Command{ - { - Id: "devbuild", - CommandUnion: v1.CommandUnion{ - Exec: &v1.ExecCommand{ - CommandLine: overrideBuild, - }, - }, - }, - { - Id: "devrun", - CommandUnion: v1.CommandUnion{ - Exec: &v1.ExecCommand{ - CommandLine: commandLineRun, - }, - }, - }, - }, - }, - }, - }, - }, - }, - wantErr: false, - }, - { - name: "case 4: throw error if command to override is not found", - devFileObj: DevfileObj{ - Ctx: devfileCtx.NewDevfileCtx(devfileTempPath), - Data: &v2.DevfileV2{ - Devfile: v1.Devfile{ - DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ - DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ - Commands: []v1.Command{ - { - Id: "devbuild", - CommandUnion: v1.CommandUnion{ - Exec: &v1.ExecCommand{ - Env: []v1.EnvVar{ - testingutil.GetFakeEnv("env-0", "value-0"), - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - args: args{ - overridePatch: []v1.CommandParentOverride{ - { - Id: "devbuild-custom", - CommandUnionParentOverride: v1.CommandUnionParentOverride{ - Exec: &v1.ExecCommandParentOverride{ - Env: []v1.EnvVarParentOverride{ - testingutil.GetFakeEnvParentOverride("env-0", "value-0-0"), - testingutil.GetFakeEnvParentOverride("env-1", "value-1"), - }, - }, - }, - }, - }, - }, - wantDevFileObj: DevfileObj{ - Ctx: devfileCtx.NewDevfileCtx(devfileTempPath), - Data: &v2.DevfileV2{ - Devfile: v1.Devfile{ - DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ - DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ - Commands: []v1.Command{}, - }, - }, - }, - }, - }, - wantErr: true, - }, - { - name: "case 5: override a composite command", - devFileObj: DevfileObj{ - Ctx: devfileCtx.NewDevfileCtx(devfileTempPath), - Data: &v2.DevfileV2{ - Devfile: v1.Devfile{ - DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ - DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ - Commands: []v1.Command{ - { - Id: "exec1", - CommandUnion: v1.CommandUnion{ - Exec: &v1.ExecCommand{ - CommandLine: commandLineBuild, - }, - }, - }, - { - Id: "exec2", - CommandUnion: v1.CommandUnion{ - Exec: &v1.ExecCommand{ - CommandLine: commandLineBuild, - }, - }, - }, - { - Id: "exec3", - CommandUnion: v1.CommandUnion{ - Exec: &v1.ExecCommand{ - CommandLine: commandLineBuild, - }, - }, - }, - { - Id: "mycomposite", - CommandUnion: v1.CommandUnion{ - Composite: &v1.CompositeCommand{ - Commands: []string{"exec1", "exec2"}, - }, - }, - }, - }, - }, - }, - }, - }, - }, - args: args{ - overridePatch: []v1.CommandParentOverride{ - { - Id: "mycomposite", - CommandUnionParentOverride: v1.CommandUnionParentOverride{ - Composite: &v1.CompositeCommandParentOverride{ - Commands: []string{"exec1", "exec3"}, - }, - }, - }, - }, - }, - wantDevFileObj: DevfileObj{ - Ctx: devfileCtx.NewDevfileCtx(devfileTempPath), - Data: &v2.DevfileV2{ - Devfile: v1.Devfile{ - DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ - DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ - Commands: []v1.Command{ - { - Id: "exec1", - CommandUnion: v1.CommandUnion{ - Exec: &v1.ExecCommand{ - CommandLine: commandLineBuild, - }, - }, - }, - { - Id: "exec2", - CommandUnion: v1.CommandUnion{ - Exec: &v1.ExecCommand{ - CommandLine: commandLineBuild, - }, - }, - }, - { - Id: "exec3", - CommandUnion: v1.CommandUnion{ - Exec: &v1.ExecCommand{ - CommandLine: commandLineBuild, - }, - }, - }, - { - Id: "mycomposite", - CommandUnion: v1.CommandUnion{ - Composite: &v1.CompositeCommand{ - Commands: []string{"exec1", "exec3"}, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - { - name: "case 6: throw error if trying to overide command with different type", - devFileObj: DevfileObj{ - Ctx: devfileCtx.NewDevfileCtx(devfileTempPath), - Data: &v2.DevfileV2{ - Devfile: v1.Devfile{ - DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ - DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ - Commands: []v1.Command{ - { - Id: "devbuild", - CommandUnion: v1.CommandUnion{ - Exec: &v1.ExecCommand{ - Env: []v1.EnvVar{ - testingutil.GetFakeEnv("env-0", "value-0-0"), - }, - CommandLine: commandLineBuild, - }, - }, - }, - }, - }, - }, - }, - }, - }, - args: args{ - overridePatch: []v1.CommandParentOverride{ - { - Id: "devbuild", - CommandUnionParentOverride: v1.CommandUnionParentOverride{ - Composite: &v1.CompositeCommandParentOverride{}, - }, - }, - }, - }, - wantDevFileObj: DevfileObj{ - Ctx: devfileCtx.NewDevfileCtx(devfileTempPath), - Data: &v2.DevfileV2{ - Devfile: v1.Devfile{ - DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ - DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ - Commands: []v1.Command{}, - }, - }, - }, - }, - }, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := tt.devFileObj.OverrideCommands(tt.args.overridePatch) - - if (err != nil) != tt.wantErr { - t.Errorf("OverrideCommands() error = %v, wantErr %v", err, tt.wantErr) - return - } - - if tt.wantErr && err != nil { - return - } - - if !reflect.DeepEqual(tt.wantDevFileObj, tt.devFileObj) { - t.Errorf("expected devfile and got devfile are different: %v", pretty.Compare(tt.wantDevFileObj, tt.devFileObj)) - } - }) - } -} - -func TestDevfileObj_OverrideComponents(t *testing.T) { - - containerImage0 := "image-0" - containerImage1 := "image-1" - - overrideContainerImage := "image-0-override" - MountSources := false - overrideMountSources := true - - type args struct { - overridePatch []v1.ComponentParentOverride - } - tests := []struct { - name string - devFileObj DevfileObj - args args - wantDevFileObj DevfileObj - wantErr bool - }{ - { - name: "case 1: override a container's non list/map fields", - devFileObj: DevfileObj{ - Ctx: devfileCtx.NewDevfileCtx(devfileTempPath), - Data: &v2.DevfileV2{ - Devfile: v1.Devfile{ - DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ - DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ - Components: []v1.Component{ - { - Name: "nodejs", - ComponentUnion: v1.ComponentUnion{ - Container: &v1.ContainerComponent{ - Container: v1.Container{ - Args: []string{"arg-0", "arg-1"}, - Command: []string{"cmd-0", "cmd-1"}, - Image: containerImage0, - MemoryLimit: "512Mi", - MountSources: &MountSources, - SourceMapping: "/source", - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - args: args{ - overridePatch: []v1.ComponentParentOverride{ - { - Name: "nodejs", - ComponentUnionParentOverride: v1.ComponentUnionParentOverride{ - Container: &v1.ContainerComponentParentOverride{ - ContainerParentOverride: v1.ContainerParentOverride{ - Args: []string{"arg-0-0", "arg-1-1"}, - Command: []string{"cmd-0-0", "cmd-1-1"}, - MemoryLimit: "1Gi", - Image: overrideContainerImage, - MountSources: &overrideMountSources, - SourceMapping: "/data", - }, - }, - }, - }, - }, - }, - wantDevFileObj: DevfileObj{ - Ctx: devfileCtx.NewDevfileCtx(devfileTempPath), - Data: &v2.DevfileV2{ - Devfile: v1.Devfile{ - DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ - DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ - Components: []v1.Component{ - { - Name: "nodejs", - ComponentUnion: v1.ComponentUnion{ - Container: &v1.ContainerComponent{ - Container: v1.Container{ - Args: []string{"arg-0-0", "arg-1-1"}, - Command: []string{"cmd-0-0", "cmd-1-1"}, - Image: overrideContainerImage, - MemoryLimit: "1Gi", - MountSources: &overrideMountSources, - SourceMapping: "/data", - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - wantErr: false, - }, - // { - // name: "case 2: append/override a command's list fields based on the key", - // devFileObj: DevfileObj{ - // Ctx: devfileCtx.NewDevfileCtx(devfileTempPath), - // Data: &v2.DevfileV2{ - // Devfile: v1.Devfile{ - // DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ - // DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ - // Components: []v1.Component{ - // { - // Name: "nodejs", - // ComponentUnion: v1.ComponentUnion{ - // Container: &v1.ContainerComponent{ - // Endpoints: []v1.Endpoint{ - // { - // Attributes: map[string]string{ - // "key-0": "value-0", - // "key-1": "value-1", - // }, - // Name: "endpoint-0", - // TargetPort: 8080, - // }, - // }, - // Container: v1.Container{ - // Env: []v1.EnvVar{ - // testingutil.GetFakeEnv("env-0", "value-0"), - // }, - // VolumeMounts: []v1.VolumeMount{ - // testingutil.GetFakeVolumeMount("volume-0", "path-0"), - // }, - // }, - // }, - // }, - // }, - // }, - // }, - // }, - // }, - // }, - // }, - // args: args{ - // overridePatch: []v1.ComponentParentOverride{ - // { - // Name: "nodejs", - // ComponentUnionParentOverride: v1.ComponentUnionParentOverride{ - // Container: &v1.ContainerComponentParentOverride{ - // Endpoints: []v1.EndpointParentOverride{ - // { - // Attributes: map[string]string{ - // "key-1": "value-1-1", - // "key-append": "value-append", - // }, - // Name: "endpoint-0", - // TargetPort: 9090, - // }, - // { - // Attributes: map[string]string{ - // "key-0": "value-0", - // }, - // Name: "endpoint-1", - // TargetPort: 3000, - // }, - // }, - // ContainerParentOverride: v1.ContainerParentOverride{ - // Env: []v1.EnvVarParentOverride{ - // testingutil.GetFakeEnvParentOverride("env-0", "value-0-0"), - // testingutil.GetFakeEnvParentOverride("env-1", "value-1"), - // }, - // VolumeMounts: []v1.VolumeMountParentOverride{ - // testingutil.GetFakeVolumeMountParentOverride("volume-0", "path-0-0"), - // testingutil.GetFakeVolumeMountParentOverride("volume-1", "path-1"), - // }, - // }, - // }, - // }, - // }, - // }, - // }, - // wantDevFileObj: DevfileObj{ - // Ctx: devfileCtx.NewDevfileCtx(devfileTempPath), - // Data: &v2.DevfileV2{ - // Devfile: v1.Devfile{ - // DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ - // DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ - // Components: []v1.Component{ - // { - // Name: "nodejs", - // ComponentUnion: v1.ComponentUnion{ - // Container: &v1.ContainerComponent{ - // Container: v1.Container{ - // Env: []v1.EnvVar{ - // testingutil.GetFakeEnv("env-0", "value-0-0"), - // testingutil.GetFakeEnv("env-1", "value-1"), - // }, - // VolumeMounts: []v1.VolumeMount{ - // testingutil.GetFakeVolumeMount("volume-0", "path-0-0"), - // testingutil.GetFakeVolumeMount("volume-1", "path-1"), - // }, - // }, - // Endpoints: []v1.Endpoint{ - // { - // Attributes: map[string]string{ - // "key-0": "value-0", - // "key-1": "value-1-1", - // "key-append": "value-append", - // }, - // Name: "endpoint-0", - // TargetPort: 9090, - // }, - // { - // Attributes: map[string]string{ - // "key-0": "value-0", - // }, - // Name: "endpoint-1", - // TargetPort: 3000, - // }, - // }, - // }, - // }, - // }, - // }, - // }, - // }, - // }, - // }, - // }, - // wantErr: false, - // }, - { - name: "case 3: if multiple, override the correct command", - devFileObj: DevfileObj{ - Ctx: devfileCtx.NewDevfileCtx(devfileTempPath), - Data: &v2.DevfileV2{ - Devfile: v1.Devfile{ - DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ - DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ - Components: []v1.Component{ - { - Name: "nodejs", - ComponentUnion: v1.ComponentUnion{ - Container: &v1.ContainerComponent{ - Container: v1.Container{ - Image: containerImage0, - }, - }, - }, - }, - { - Name: "runtime", - ComponentUnion: v1.ComponentUnion{ - Container: &v1.ContainerComponent{ - Container: v1.Container{ - Image: containerImage1, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - args: args{ - overridePatch: []v1.ComponentParentOverride{ - { - Name: "nodejs", - ComponentUnionParentOverride: v1.ComponentUnionParentOverride{ - Container: &v1.ContainerComponentParentOverride{ - ContainerParentOverride: v1.ContainerParentOverride{ - Image: overrideContainerImage, - }, - }, - }, - }, - { - Name: "runtime", - ComponentUnionParentOverride: v1.ComponentUnionParentOverride{ - Container: &v1.ContainerComponentParentOverride{ - ContainerParentOverride: v1.ContainerParentOverride{ - Image: containerImage1, - }, - }, - }, - }, - }, - }, - wantDevFileObj: DevfileObj{ - Ctx: devfileCtx.NewDevfileCtx(devfileTempPath), - Data: &v2.DevfileV2{ - Devfile: v1.Devfile{ - DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ - DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ - Components: []v1.Component{ - { - Name: "nodejs", - ComponentUnion: v1.ComponentUnion{ - Container: &v1.ContainerComponent{ - Container: v1.Container{ - Image: overrideContainerImage, - }, - }, - }, - }, - { - Name: "runtime", - ComponentUnion: v1.ComponentUnion{ - Container: &v1.ContainerComponent{ - Container: v1.Container{ - Image: containerImage1, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - wantErr: false, - }, - { - name: "case 4: throw error if component to override is not found", - devFileObj: DevfileObj{ - Ctx: devfileCtx.NewDevfileCtx(devfileTempPath), - Data: &v2.DevfileV2{ - Devfile: v1.Devfile{ - DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ - DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ - Components: []v1.Component{ - { - Name: "nodejs", - ComponentUnion: v1.ComponentUnion{ - Container: &v1.ContainerComponent{ - Container: v1.Container{ - Image: containerImage0, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - args: args{ - overridePatch: []v1.ComponentParentOverride{ - { - Name: "nodejs-custom", - ComponentUnionParentOverride: v1.ComponentUnionParentOverride{ - Container: &v1.ContainerComponentParentOverride{ - ContainerParentOverride: v1.ContainerParentOverride{ - Image: containerImage0, - }, - }, - }, - }, - }, - }, - wantDevFileObj: DevfileObj{}, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := tt.devFileObj.OverrideComponents(tt.args.overridePatch) - if (err != nil) != tt.wantErr { - t.Errorf("OverrideComponents() error = %v, wantErr %v", err, tt.wantErr) - } - - if tt.wantErr && err != nil { - return - } - - if !reflect.DeepEqual(tt.wantDevFileObj, tt.devFileObj) { - t.Errorf("expected devfile and got devfile are different: %v", pretty.Compare(tt.wantDevFileObj, tt.devFileObj)) - } - }) - } -} - -func TestDevfileObj_OverrideProjects(t *testing.T) { - projectName0 := "project-0" - projectName1 := "project-1" - - type args struct { - overridePatch []v1.ProjectParentOverride - } - tests := []struct { - name string - devFileObj DevfileObj - wantDevFileObj DevfileObj - args args - wantErr bool - }{ - { - name: "case 1: override a project's fields", - devFileObj: DevfileObj{ - Ctx: devfileCtx.NewDevfileCtx(devfileTempPath), - Data: &v2.DevfileV2{ - Devfile: v1.Devfile{ - DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ - DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ - Projects: []v1.Project{ - { - ClonePath: "/data", - ProjectSource: v1.ProjectSource{ - Github: &v1.GithubProjectSource{ - GitLikeProjectSource: v1.GitLikeProjectSource{ - CheckoutFrom: &v1.CheckoutFrom{ - Revision: "master", - }, - }, - }, - Zip: nil, - }, - Name: projectName0, - }, - }, - }, - }, - }, - }, - }, - args: args{ - overridePatch: []v1.ProjectParentOverride{ - { - ClonePath: "/source", - ProjectSourceParentOverride: v1.ProjectSourceParentOverride{ - Github: &v1.GithubProjectSourceParentOverride{ - GitLikeProjectSourceParentOverride: v1.GitLikeProjectSourceParentOverride{ - CheckoutFrom: &v1.CheckoutFromParentOverride{ - Revision: "release-1.0.0", - }, - }, - }, - Zip: nil, - }, - Name: projectName0, - }, - }, - }, - wantDevFileObj: DevfileObj{ - Ctx: devfileCtx.NewDevfileCtx(devfileTempPath), - Data: &v2.DevfileV2{ - Devfile: v1.Devfile{ - DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ - DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ - Projects: []v1.Project{ - { - ClonePath: "/source", - ProjectSource: v1.ProjectSource{ - Github: &v1.GithubProjectSource{ - GitLikeProjectSource: v1.GitLikeProjectSource{ - CheckoutFrom: &v1.CheckoutFrom{ - Revision: "release-1.0.0", - }, - }, - }, - Zip: nil, - }, - Name: projectName0, - }, - }, - }, - }, - }, - }, - }, - }, - { - name: "case 2: if multiple, override the correct project", - devFileObj: DevfileObj{ - Ctx: devfileCtx.NewDevfileCtx(devfileTempPath), - Data: &v2.DevfileV2{ - Devfile: v1.Devfile{ - DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ - DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ - Projects: []v1.Project{ - { - ClonePath: "/data", - ProjectSource: v1.ProjectSource{ - Github: &v1.GithubProjectSource{ - GitLikeProjectSource: v1.GitLikeProjectSource{ - CheckoutFrom: &v1.CheckoutFrom{ - Revision: "master", - }, - }, - }, - Zip: nil, - }, - Name: projectName0, - }, - { - ProjectSource: v1.ProjectSource{ - Github: &v1.GithubProjectSource{ - GitLikeProjectSource: v1.GitLikeProjectSource{ - CheckoutFrom: &v1.CheckoutFrom{ - Revision: "master", - }, - }, - }, - }, - Name: projectName1, - }, - }, - }, - }, - }, - }, - }, - args: args{ - overridePatch: []v1.ProjectParentOverride{ - { - ClonePath: "/source", - ProjectSourceParentOverride: v1.ProjectSourceParentOverride{ - Github: &v1.GithubProjectSourceParentOverride{ - GitLikeProjectSourceParentOverride: v1.GitLikeProjectSourceParentOverride{ - CheckoutFrom: &v1.CheckoutFromParentOverride{ - Revision: "release-1.0.0", - }, - }, - }, - Zip: nil, - }, - Name: projectName0, - }, - }, - }, - wantDevFileObj: DevfileObj{ - Ctx: devfileCtx.NewDevfileCtx(devfileTempPath), - Data: &v2.DevfileV2{ - Devfile: v1.Devfile{ - DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ - DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ - Projects: []v1.Project{ - { - ClonePath: "/source", - ProjectSource: v1.ProjectSource{ - Github: &v1.GithubProjectSource{ - GitLikeProjectSource: v1.GitLikeProjectSource{ - CheckoutFrom: &v1.CheckoutFrom{ - Revision: "release-1.0.0", - }, - }, - }, - Zip: nil, - }, - Name: projectName0, - }, - { - ProjectSource: v1.ProjectSource{ - Github: &v1.GithubProjectSource{ - GitLikeProjectSource: v1.GitLikeProjectSource{ - CheckoutFrom: &v1.CheckoutFrom{ - Revision: "master", - }, - }, - }, - }, - Name: projectName1, - }, - }, - }, - }, - }, - }, - }, - }, - { - name: "case 3: throw error if project to override is not found", - devFileObj: DevfileObj{ - Ctx: devfileCtx.NewDevfileCtx(devfileTempPath), - Data: &v2.DevfileV2{ - Devfile: v1.Devfile{ - DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ - DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ - Projects: []v1.Project{ - { - ClonePath: "/data", - ProjectSource: v1.ProjectSource{ - Github: &v1.GithubProjectSource{ - GitLikeProjectSource: v1.GitLikeProjectSource{ - CheckoutFrom: &v1.CheckoutFrom{ - Revision: "master", - }, - }, - }, - Zip: nil, - }, - Name: projectName0, - }, - }, - }, - }, - }, - }, - }, - args: args{ - overridePatch: []v1.ProjectParentOverride{ - { - ClonePath: "/source", - ProjectSourceParentOverride: v1.ProjectSourceParentOverride{ - Github: &v1.GithubProjectSourceParentOverride{ - GitLikeProjectSourceParentOverride: v1.GitLikeProjectSourceParentOverride{ - CheckoutFrom: &v1.CheckoutFromParentOverride{ - Revision: "release-1.0.0", - }, - }, - }, - Zip: nil, - }, - Name: "custom-project", - }, - }, - }, - wantDevFileObj: DevfileObj{}, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := tt.devFileObj.OverrideProjects(tt.args.overridePatch) - - if (err != nil) != tt.wantErr { - t.Errorf("OverrideProjects() error = %v, wantErr %v", err, tt.wantErr) - return - } - - if tt.wantErr && err != nil { - return - } - - if !reflect.DeepEqual(tt.wantDevFileObj, tt.devFileObj) { - t.Errorf("expected devfile and got devfile are different: %v", pretty.Compare(tt.wantDevFileObj, tt.devFileObj)) - } - }) - } -} - -func TestDevfileObj_OverrideStarterProjects(t *testing.T) { - projectName1 := "starter-1" - projectName2 := "starter-2" - - type args struct { - overridePatch []v1.StarterProjectParentOverride - } - tests := []struct { - name string - devFileObj DevfileObj - wantDevFileObj DevfileObj - args args - wantErr bool - }{ - { - name: "Case 1: override a starter projects fields", - devFileObj: DevfileObj{ - Ctx: devfileCtx.NewDevfileCtx(devfileTempPath), - Data: &v2.DevfileV2{ - Devfile: v1.Devfile{ - DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ - DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ - StarterProjects: []v1.StarterProject{ - { - Name: projectName1, - SubDir: "/data", - ProjectSource: v1.ProjectSource{ - Github: &v1.GithubProjectSource{ - GitLikeProjectSource: v1.GitLikeProjectSource{ - Remotes: map[string]string{"origin": "url"}, - CheckoutFrom: &v1.CheckoutFrom{Revision: "master"}, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - args: args{ - overridePatch: []v1.StarterProjectParentOverride{ - { - Name: projectName1, - SubDir: "/source", - ProjectSourceParentOverride: v1.ProjectSourceParentOverride{ - Github: &v1.GithubProjectSourceParentOverride{ - GitLikeProjectSourceParentOverride: v1.GitLikeProjectSourceParentOverride{ - Remotes: map[string]string{"origin": "url"}, - CheckoutFrom: &v1.CheckoutFromParentOverride{Revision: "release-1.0.0"}, - }, - }, - Zip: nil, - }, - }, - }, - }, - wantDevFileObj: DevfileObj{ - Ctx: devfileCtx.NewDevfileCtx(devfileTempPath), - Data: &v2.DevfileV2{ - Devfile: v1.Devfile{ - DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ - DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ - StarterProjects: []v1.StarterProject{ - { - Name: projectName1, - SubDir: "/source", - ProjectSource: v1.ProjectSource{ - Github: &v1.GithubProjectSource{ - GitLikeProjectSource: v1.GitLikeProjectSource{ - Remotes: map[string]string{"origin": "url"}, - CheckoutFrom: &v1.CheckoutFrom{Revision: "release-1.0.0"}, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - { - name: "Case 2: if multiple, override the correct starter project", - devFileObj: DevfileObj{ - Ctx: devfileCtx.NewDevfileCtx(devfileTempPath), - Data: &v2.DevfileV2{ - Devfile: v1.Devfile{ - DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ - DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ - StarterProjects: []v1.StarterProject{ - { - Name: projectName1, - SubDir: "/data", - ProjectSource: v1.ProjectSource{ - Github: &v1.GithubProjectSource{ - GitLikeProjectSource: v1.GitLikeProjectSource{ - Remotes: map[string]string{"origin": "url"}, - CheckoutFrom: &v1.CheckoutFrom{Revision: "master"}, - }, - }, - }, - }, - { - Name: projectName2, - ProjectSource: v1.ProjectSource{ - Github: &v1.GithubProjectSource{ - GitLikeProjectSource: v1.GitLikeProjectSource{ - Remotes: map[string]string{"origin": "url"}, - CheckoutFrom: &v1.CheckoutFrom{Revision: "master"}, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - args: args{ - overridePatch: []v1.StarterProjectParentOverride{ - { - Name: projectName1, - SubDir: "/source", - ProjectSourceParentOverride: v1.ProjectSourceParentOverride{ - Github: &v1.GithubProjectSourceParentOverride{ - GitLikeProjectSourceParentOverride: v1.GitLikeProjectSourceParentOverride{ - Remotes: map[string]string{"origin": "url"}, - CheckoutFrom: &v1.CheckoutFromParentOverride{Revision: "release-1.0.0"}, - }, - }, - }, - }, - }, - }, - wantDevFileObj: DevfileObj{ - Ctx: devfileCtx.NewDevfileCtx(devfileTempPath), - Data: &v2.DevfileV2{ - Devfile: v1.Devfile{ - DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ - DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ - StarterProjects: []v1.StarterProject{ - { - Name: projectName1, - SubDir: "/source", - ProjectSource: v1.ProjectSource{ - Github: &v1.GithubProjectSource{ - GitLikeProjectSource: v1.GitLikeProjectSource{ - Remotes: map[string]string{"origin": "url"}, - CheckoutFrom: &v1.CheckoutFrom{Revision: "release-1.0.0"}, - }, - }, - }, - }, - { - Name: projectName2, - ProjectSource: v1.ProjectSource{ - Github: &v1.GithubProjectSource{ - GitLikeProjectSource: v1.GitLikeProjectSource{ - Remotes: map[string]string{"origin": "url"}, - CheckoutFrom: &v1.CheckoutFrom{Revision: "master"}, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - { - name: "Case 3: throw error if starter project to override is not found", - devFileObj: DevfileObj{ - Ctx: devfileCtx.NewDevfileCtx(devfileTempPath), - Data: &v2.DevfileV2{ - Devfile: v1.Devfile{ - DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ - DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ - StarterProjects: []v1.StarterProject{ - { - Name: projectName1, - SubDir: "/data", - ProjectSource: v1.ProjectSource{ - Github: &v1.GithubProjectSource{ - GitLikeProjectSource: v1.GitLikeProjectSource{ - Remotes: map[string]string{"origin": "url"}, - CheckoutFrom: &v1.CheckoutFrom{Revision: "master"}, - }, - }, - Zip: nil, - }, - }, - }, - }, - }, - }, - }, - }, - args: args{ - overridePatch: []v1.StarterProjectParentOverride{ - { - Name: "custom-starter-project", - SubDir: "/source", - ProjectSourceParentOverride: v1.ProjectSourceParentOverride{ - Github: &v1.GithubProjectSourceParentOverride{ - GitLikeProjectSourceParentOverride: v1.GitLikeProjectSourceParentOverride{ - Remotes: map[string]string{"origin": "url"}, - CheckoutFrom: &v1.CheckoutFromParentOverride{Revision: "release-1.0.0"}, - }, - }, - Zip: nil, - }, - }, - }, - }, - wantDevFileObj: DevfileObj{}, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := tt.devFileObj.OverrideStarterProjects(tt.args.overridePatch) - - if (err != nil) != tt.wantErr { - t.Errorf("OverrideStarterProjects() error = %v, wantErr %v", err, tt.wantErr) - return - } - - if tt.wantErr && err != nil { - return - } - - if !reflect.DeepEqual(tt.wantDevFileObj, tt.devFileObj) { - t.Errorf("expected devfile and got devfile are different: %v", pretty.Compare(tt.wantDevFileObj, tt.devFileObj)) - } - }) - } -} diff --git a/pkg/devfile/parser/parse.go b/pkg/devfile/parser/parse.go index 491bd5f0..43c72895 100644 --- a/pkg/devfile/parser/parse.go +++ b/pkg/devfile/parser/parse.go @@ -2,21 +2,25 @@ package parser import ( "encoding/json" + "fmt" devfileCtx "github.com/devfile/library/pkg/devfile/parser/context" "github.com/devfile/library/pkg/devfile/parser/data" "github.com/devfile/library/pkg/devfile/parser/data/v2/common" + "k8s.io/klog" "reflect" v1 "github.com/devfile/api/pkg/apis/workspaces/v1alpha2" + apiOverride "github.com/devfile/api/pkg/utils/overriding" "github.com/pkg/errors" - "k8s.io/klog" ) +var URLMap = make(map[string]bool) + // ParseDevfile func validates the devfile integrity. // Creates devfile context and runtime objects -func parseDevfile(d DevfileObj) (DevfileObj, error) { +func parseDevfile(d DevfileObj, flattenedDevfile bool) (DevfileObj, error) { // Validate devfile err := d.Ctx.Validate() @@ -36,20 +40,20 @@ func parseDevfile(d DevfileObj) (DevfileObj, error) { return d, errors.Wrapf(err, "failed to decode devfile content") } - if d.Data.GetParent() != nil { - if !reflect.DeepEqual(d.Data.GetParent(), &v1.Parent{}) && d.Data.GetParent().Uri != "" { - err = parseParent(d) - if err != nil { - return DevfileObj{}, err - } + if flattenedDevfile { + err = parseParentAndPlugin(d) + if err != nil { + return DevfileObj{}, err } } - + for url := range URLMap { + delete(URLMap, url) + } // Successful return d, nil } -// Parse func populates the devfile data, parses and validates the devfile integrity. +// Parse func populates the flattened devfile data, parses and validates the devfile integrity. // Creates devfile context and runtime objects func Parse(path string) (d DevfileObj, err error) { @@ -61,20 +65,37 @@ func Parse(path string) (d DevfileObj, err error) { if err != nil { return d, err } - return parseDevfile(d) + return parseDevfile(d, true) +} + +// ParseRawDevfile populates the raw devfile data without overriding and merging +func ParseRawDevfile(path string) (d DevfileObj, err error) { + // NewDevfileCtx + d.Ctx = devfileCtx.NewDevfileCtx(path) + + // Fill the fields of DevfileCtx struct + err = d.Ctx.Populate() + if err != nil { + return d, err + } + return parseDevfile(d, false) } // ParseFromURL func parses and validates the devfile integrity. // Creates devfile context and runtime objects func ParseFromURL(url string) (d DevfileObj, err error) { + if _, exist := URLMap[url]; !exist { + URLMap[url] = true + } else { + return d, fmt.Errorf("URI %v is recursively referenced", url) + } d.Ctx = devfileCtx.NewURLDevfileCtx(url) - // Fill the fields of DevfileCtx struct err = d.Ctx.PopulateFromURL() if err != nil { return d, err } - return parseDevfile(d) + return parseDevfile(d, true) } // ParseFromData func parses and validates the devfile integrity. @@ -90,83 +111,73 @@ func ParseFromData(data []byte) (d DevfileObj, err error) { return d, err } - return parseDevfile(d) + return parseDevfile(d, true) } -func parseParent(d DevfileObj) error { - parent := d.Data.GetParent() +func parseParentAndPlugin(d DevfileObj) (err error) { + flattenedParent := &v1.DevWorkspaceTemplateSpecContent{} + if d.Data.GetParent() != nil { + if !reflect.DeepEqual(d.Data.GetParent(), &v1.Parent{}) { + + parent := d.Data.GetParent() + var parentDevfileObj DevfileObj + if d.Data.GetParent().Uri != "" { + parentDevfileObj, err = ParseFromURL(parent.Uri) + if err != nil { + return err + } + } else { + return fmt.Errorf("parent URI undefined, currently only URI is suppported") + } - parentData, err := ParseFromURL(parent.Uri) - if err != nil { - return err - } - klog.V(4).Infof("overriding data of devfile with URI: %v", parent.Uri) + parentWorkspaceContent := parentDevfileObj.Data.GetDevfileWorkspace() + if !reflect.DeepEqual(parent.ParentOverrides, v1.ParentOverrides{}) { + flattenedParent, err = apiOverride.OverrideDevWorkspaceTemplateSpec(parentWorkspaceContent, parent.ParentOverrides) + if err != nil { + return err + } + } else { + flattenedParent = parentWorkspaceContent + } - // override the parent's components, commands, projects and events - err = parentData.OverrideComponents(d.Data.GetParent().Components) - if err != nil { - return err + klog.V(4).Infof("adding data of devfile with URI: %v", parent.Uri) + } } - - err = parentData.OverrideCommands(d.Data.GetParent().Commands) + flattenedPlugins := []*v1.DevWorkspaceTemplateSpecContent{} + components, err := d.Data.GetComponents(common.DevfileOptions{}) if err != nil { return err } - - err = parentData.OverrideProjects(d.Data.GetParent().Projects) - if err != nil { - return err + for _, component := range components { + if component.Plugin != nil && !reflect.DeepEqual(component.Plugin, &v1.PluginComponent{}) { + plugin := component.Plugin + var pluginDevfileObj DevfileObj + if plugin.Uri != "" { + pluginDevfileObj, err = ParseFromURL(plugin.Uri) + if err != nil { + return err + } + } else { + return fmt.Errorf("plugin URI undefined, currently only URI is suppported") + } + pluginWorkspaceContent := pluginDevfileObj.Data.GetDevfileWorkspace() + flattenedPlugin := pluginWorkspaceContent + if !reflect.DeepEqual(plugin.PluginOverrides, v1.PluginOverrides{}) { + flattenedPlugin, err = apiOverride.OverrideDevWorkspaceTemplateSpec(pluginWorkspaceContent, plugin.PluginOverrides) + if err != nil { + return err + } + } + flattenedPlugins = append(flattenedPlugins, flattenedPlugin) + } } - - err = parentData.OverrideStarterProjects(d.Data.GetParent().StarterProjects) + mergedContent, err := apiOverride.MergeDevWorkspaceTemplateSpec(d.Data.GetDevfileWorkspace(), flattenedParent, flattenedPlugins...) if err != nil { return err } + d.Data.SetDevfileWorkspace(*mergedContent) + // remove parent from flatterned devfile + d.Data.SetParent(nil) - klog.V(4).Infof("adding data of devfile with URI: %v", parent.Uri) - - // since the parent's data has been overriden - // add the items back to the current devfile - // error indicates that the item has been defined again in the current devfile - parentCommands, err := parentData.Data.GetCommands(common.DevfileOptions{}) - if err != nil { - return errors.Wrapf(err, "error while getting commands from the parent devfiles") - } - err = d.Data.AddCommands(parentCommands...) - if err != nil { - return errors.Wrapf(err, "error while adding commands from the parent devfiles") - } - - parentComponents, err := parentData.Data.GetComponents(common.DevfileOptions{}) - if err != nil { - return errors.Wrapf(err, "error getting components from the parent devfiles") - } - err = d.Data.AddComponents(parentComponents) - if err != nil { - return errors.Wrapf(err, "error while adding components from the parent devfiles") - } - - parentProjects, err := parentData.Data.GetProjects(common.DevfileOptions{}) - if err != nil { - return errors.Wrapf(err, "error while getting projects from the parent devfiles") - } - err = d.Data.AddProjects(parentProjects) - if err != nil { - return errors.Wrapf(err, "error while adding projects from the parent devfiles") - } - - parentStarterProjects, err := parentData.Data.GetStarterProjects(common.DevfileOptions{}) - if err != nil { - return errors.Wrapf(err, "error while getting starter projects from the parent devfiles") - } - err = d.Data.AddStarterProjects(parentStarterProjects) - if err != nil { - return errors.Wrapf(err, "error while adding starter projects from the parent devfiles") - } - - err = d.Data.AddEvents(parentData.Data.GetEvents()) - if err != nil { - return errors.Wrapf(err, "error while adding events from the parent devfiles") - } return nil } diff --git a/pkg/devfile/parser/parse_test.go b/pkg/devfile/parser/parse_test.go index 40432e05..5c15290d 100644 --- a/pkg/devfile/parser/parse_test.go +++ b/pkg/devfile/parser/parse_test.go @@ -1,6 +1,8 @@ package parser import ( + "fmt" + "net" "net/http" "net/http/httptest" "reflect" @@ -15,17 +17,22 @@ import ( ) const schemaV200 = "2.0.0" +const devfileTempPath = "devfile.yaml" + +func Test_parseParentAndPlugin(t *testing.T) { -func Test_parseParent(t *testing.T) { type args struct { devFileObj DevfileObj } tests := []struct { - name string - args args - parentDevFile DevfileObj - wantDevFile DevfileObj - wantErr bool + name string + args args + parentDevfile DevfileObj + pluginDevfile DevfileObj + pluginOverride v1.PluginOverrides + wantDevFile DevfileObj + wantErr bool + testRecursiveReference bool }{ { name: "case 1: it should override the requested parent's data and add the local devfile's data", @@ -107,7 +114,7 @@ func Test_parseParent(t *testing.T) { }, }, }, - parentDevFile: DevfileObj{ + parentDevfile: DevfileObj{ Data: &v2.DevfileV2{ Devfile: v1.Devfile{ DevfileHeader: devfilepkg.DevfileHeader{ @@ -170,26 +177,26 @@ func Test_parseParent(t *testing.T) { DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ Commands: []v1.Command{ { - Id: "devbuild", + Id: "devrun", CommandUnion: v1.CommandUnion{ Exec: &v1.ExecCommand{ - WorkingDir: "/projects/nodejs-starter", + CommandLine: "npm run", + WorkingDir: "/projects/nodejs-starter", }, }, }, { - Id: "devrun", + Id: "devbuild", CommandUnion: v1.CommandUnion{ Exec: &v1.ExecCommand{ - CommandLine: "npm run", - WorkingDir: "/projects/nodejs-starter", + WorkingDir: "/projects/nodejs-starter", }, }, }, }, Components: []v1.Component{ { - Name: "runtime", + Name: "nodejs", ComponentUnion: v1.ComponentUnion{ Container: &v1.ContainerComponent{ Container: v1.Container{ @@ -199,7 +206,7 @@ func Test_parseParent(t *testing.T) { }, }, { - Name: "nodejs", + Name: "runtime", ComponentUnion: v1.ComponentUnion{ Container: &v1.ContainerComponent{ Container: v1.Container{ @@ -213,13 +220,11 @@ func Test_parseParent(t *testing.T) { WorkspaceEvents: v1.WorkspaceEvents{ PostStart: []string{"post-start-0"}, PostStop: []string{"post-stop"}, + PreStop: []string{}, + PreStart: []string{}, }, }, Projects: []v1.Project{ - { - ClonePath: "/projects", - Name: "nodejs-starter-build", - }, { ClonePath: "/projects", ProjectSource: v1.ProjectSource{ @@ -233,6 +238,10 @@ func Test_parseParent(t *testing.T) { }, Name: "nodejs-starter", }, + { + ClonePath: "/projects", + Name: "nodejs-starter-build", + }, }, }, }, @@ -288,7 +297,7 @@ func Test_parseParent(t *testing.T) { }, }, }, - parentDevFile: DevfileObj{ + parentDevfile: DevfileObj{ Data: &v2.DevfileV2{ Devfile: v1.Devfile{ DevfileHeader: devfilepkg.DevfileHeader{ @@ -351,40 +360,40 @@ func Test_parseParent(t *testing.T) { DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ Commands: []v1.Command{ { - Id: "devbuild", + Id: "devrun", CommandUnion: v1.CommandUnion{ Exec: &v1.ExecCommand{ - WorkingDir: "/projects/nodejs-starter", + CommandLine: "npm run", + WorkingDir: "/projects", }, }, }, { - Id: "devrun", + Id: "devbuild", CommandUnion: v1.CommandUnion{ Exec: &v1.ExecCommand{ - CommandLine: "npm run", - WorkingDir: "/projects", + WorkingDir: "/projects/nodejs-starter", }, }, }, }, Components: []v1.Component{ { - Name: "runtime", + Name: "nodejs", ComponentUnion: v1.ComponentUnion{ Container: &v1.ContainerComponent{ Container: v1.Container{ - Image: "quay.io/nodejs-12", + Image: "quay.io/nodejs-10", }, }, }, }, { - Name: "nodejs", + Name: "runtime", ComponentUnion: v1.ComponentUnion{ Container: &v1.ContainerComponent{ Container: v1.Container{ - Image: "quay.io/nodejs-10", + Image: "quay.io/nodejs-12", }, }, }, @@ -394,13 +403,11 @@ func Test_parseParent(t *testing.T) { WorkspaceEvents: v1.WorkspaceEvents{ PostStart: []string{"post-start-0"}, PostStop: []string{"post-stop"}, + PreStop: []string{}, + PreStart: []string{}, }, }, Projects: []v1.Project{ - { - ClonePath: "/projects", - Name: "nodejs-starter-build", - }, { ClonePath: "/data", ProjectSource: v1.ProjectSource{ @@ -414,6 +421,10 @@ func Test_parseParent(t *testing.T) { }, Name: "nodejs-starter", }, + { + ClonePath: "/projects", + Name: "nodejs-starter-build", + }, }, }, }, @@ -466,7 +477,7 @@ func Test_parseParent(t *testing.T) { }, }, }, - parentDevFile: DevfileObj{ + parentDevfile: DevfileObj{ Data: &v2.DevfileV2{ Devfile: v1.Devfile{ DevfileHeader: devfilepkg.DevfileHeader{ @@ -512,7 +523,7 @@ func Test_parseParent(t *testing.T) { }, }, }, - parentDevFile: DevfileObj{ + parentDevfile: DevfileObj{ Data: &v2.DevfileV2{ Devfile: v1.Devfile{ DevfileHeader: devfilepkg.DevfileHeader{ @@ -567,7 +578,7 @@ func Test_parseParent(t *testing.T) { }, }, }, - parentDevFile: DevfileObj{ + parentDevfile: DevfileObj{ Data: &v2.DevfileV2{ Devfile: v1.Devfile{ DevfileHeader: devfilepkg.DevfileHeader{ @@ -598,7 +609,7 @@ func Test_parseParent(t *testing.T) { wantErr: true, }, { - name: "case 6: error out if the same event is defined again in the local devfile", + name: "case 6: should not have error if the same event is defined again in the local devfile", args: args{ devFileObj: DevfileObj{ Ctx: devfileCtx.NewDevfileCtx(devfileTempPath), @@ -617,7 +628,7 @@ func Test_parseParent(t *testing.T) { }, }, }, - parentDevFile: DevfileObj{ + parentDevfile: DevfileObj{ Data: &v2.DevfileV2{ Devfile: v1.Devfile{ DevfileHeader: devfilepkg.DevfileHeader{ @@ -636,12 +647,26 @@ func Test_parseParent(t *testing.T) { }, }, wantDevFile: DevfileObj{ - Data: &v2.DevfileV2{}, + Data: &v2.DevfileV2{ + Devfile: v1.Devfile{ + DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ + DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ + Events: &v1.Events{ + WorkspaceEvents: v1.WorkspaceEvents{ + PostStop: []string{"post-stop"}, + PreStart: []string{}, + PreStop: []string{}, + PostStart: []string{}, + }, + }, + }, + }, + }, + }, }, - wantErr: true, }, { - name: "case 7: error out if the same project is defined again in the local devfile", + name: "case 7: error out if the parent project is defined again in the local devfile", args: args{ devFileObj: DevfileObj{ Ctx: devfileCtx.NewDevfileCtx(devfileTempPath), @@ -661,7 +686,7 @@ func Test_parseParent(t *testing.T) { }, }, }, - parentDevFile: DevfileObj{ + parentDevfile: DevfileObj{ Data: &v2.DevfileV2{ Devfile: v1.Devfile{ DevfileHeader: devfilepkg.DevfileHeader{ @@ -685,45 +710,1688 @@ func Test_parseParent(t *testing.T) { }, wantErr: true, }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - data, err := yaml.Marshal(tt.parentDevFile.Data) - if err != nil { - t.Errorf("unexpected error: %v", err) - } - _, err = w.Write(data) - if err != nil { - t.Errorf("unexpected error: %v", err) - } - })) - defer testServer.Close() - - parent := tt.args.devFileObj.Data.GetParent() - if parent == nil { - parent = &v1.Parent{} - } - parent.Uri = testServer.URL - - tt.args.devFileObj.Data.SetParent(parent) - tt.wantDevFile.Data.SetParent(parent) - err := parseParent(tt.args.devFileObj) - - // Unexpected error - if (err != nil) != tt.wantErr { - t.Errorf("parseParent() error = %v, wantErr %v", err, tt.wantErr) - return - } - - // Expected error and got an err - if tt.wantErr && err != nil { - return - } - - if !reflect.DeepEqual(tt.args.devFileObj.Data, tt.wantDevFile.Data) { - t.Errorf("wanted: %v, got: %v, difference at %v", tt.wantDevFile, tt.args.devFileObj, pretty.Compare(tt.args.devFileObj.Data, tt.wantDevFile.Data)) - } - }) - } + { + name: "case 8: it should merge the plugin's uri data and add the local devfile's data", + args: args{ + devFileObj: DevfileObj{ + Ctx: devfileCtx.NewDevfileCtx(devfileTempPath), + Data: &v2.DevfileV2{ + Devfile: v1.Devfile{ + DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ + DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ + Commands: []v1.Command{ + { + Id: "devbuild", + CommandUnion: v1.CommandUnion{ + Exec: &v1.ExecCommand{ + WorkingDir: "/projects/nodejs-starter", + }, + }, + }, + }, + Components: []v1.Component{ + { + Name: "runtime", + ComponentUnion: v1.ComponentUnion{ + Container: &v1.ContainerComponent{ + Container: v1.Container{ + Image: "quay.io/nodejs-12", + }, + }, + }, + }, + }, + Events: &v1.Events{ + WorkspaceEvents: v1.WorkspaceEvents{ + PostStop: []string{"post-stop"}, + }, + }, + Projects: []v1.Project{ + { + ClonePath: "/projects", + Name: "nodejs-starter-build", + }, + }, + }, + }, + }, + }, + }, + }, + pluginDevfile: DevfileObj{ + Data: &v2.DevfileV2{ + Devfile: v1.Devfile{ + DevfileHeader: devfilepkg.DevfileHeader{ + SchemaVersion: schemaV200, + }, + DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ + DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ + Commands: []v1.Command{ + { + Id: "devrun", + CommandUnion: v1.CommandUnion{ + Exec: &v1.ExecCommand{ + WorkingDir: "/projects", + CommandLine: "npm run", + }, + }, + }, + }, + Components: []v1.Component{ + { + Name: "nodejs", + ComponentUnion: v1.ComponentUnion{ + Container: &v1.ContainerComponent{ + Container: v1.Container{ + Image: "quay.io/nodejs-10", + }, + }, + }, + }, + }, + Events: &v1.Events{ + WorkspaceEvents: v1.WorkspaceEvents{ + PostStart: []string{"post-start-0"}, + }, + }, + Projects: []v1.Project{ + { + ClonePath: "/data", + ProjectSource: v1.ProjectSource{ + Github: &v1.GithubProjectSource{ + GitLikeProjectSource: v1.GitLikeProjectSource{ + Remotes: map[string]string{ + "master": "https://githube.com/somerepo/someproject.git", + }, + }, + }, + }, + Name: "nodejs-starter", + }, + }, + }, + }, + }, + }, + }, + wantDevFile: DevfileObj{ + Data: &v2.DevfileV2{ + Devfile: v1.Devfile{ + DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ + DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ + Commands: []v1.Command{ + { + Id: "devrun", + CommandUnion: v1.CommandUnion{ + Exec: &v1.ExecCommand{ + CommandLine: "npm run", + WorkingDir: "/projects", + }, + }, + }, + { + Id: "devbuild", + CommandUnion: v1.CommandUnion{ + Exec: &v1.ExecCommand{ + WorkingDir: "/projects/nodejs-starter", + }, + }, + }, + }, + Components: []v1.Component{ + { + Name: "nodejs", + ComponentUnion: v1.ComponentUnion{ + Container: &v1.ContainerComponent{ + Container: v1.Container{ + Image: "quay.io/nodejs-10", + }, + }, + }, + }, + { + Name: "runtime", + ComponentUnion: v1.ComponentUnion{ + Container: &v1.ContainerComponent{ + Container: v1.Container{ + Image: "quay.io/nodejs-12", + }, + }, + }, + }, + }, + Events: &v1.Events{ + WorkspaceEvents: v1.WorkspaceEvents{ + PostStart: []string{"post-start-0"}, + PostStop: []string{"post-stop"}, + PreStop: []string{}, + PreStart: []string{}, + }, + }, + Projects: []v1.Project{ + { + ClonePath: "/data", + ProjectSource: v1.ProjectSource{ + Github: &v1.GithubProjectSource{ + GitLikeProjectSource: v1.GitLikeProjectSource{ + Remotes: map[string]string{ + "master": "https://githube.com/somerepo/someproject.git", + }, + }, + }, + }, + Name: "nodejs-starter", + }, + { + ClonePath: "/projects", + Name: "nodejs-starter-build", + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "case 9: it should override the plugin's data with local overrides and add the local devfile's data", + args: args{ + devFileObj: DevfileObj{ + Ctx: devfileCtx.NewDevfileCtx(devfileTempPath), + Data: &v2.DevfileV2{ + Devfile: v1.Devfile{ + DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ + DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ + Commands: []v1.Command{ + { + Id: "devbuild", + CommandUnion: v1.CommandUnion{ + Exec: &v1.ExecCommand{ + WorkingDir: "/projects/nodejs-starter", + }, + }, + }, + }, + Components: []v1.Component{ + { + Name: "runtime", + ComponentUnion: v1.ComponentUnion{ + Container: &v1.ContainerComponent{ + Container: v1.Container{ + Image: "quay.io/nodejs-12", + }, + }, + }, + }, + }, + Events: &v1.Events{ + WorkspaceEvents: v1.WorkspaceEvents{ + PostStop: []string{"post-stop-1"}, + }, + }, + Projects: []v1.Project{ + { + ClonePath: "/projects", + Name: "nodejs-starter-build", + }, + }, + }, + }, + }, + }, + }, + }, + pluginDevfile: DevfileObj{ + Data: &v2.DevfileV2{ + Devfile: v1.Devfile{ + DevfileHeader: devfilepkg.DevfileHeader{ + SchemaVersion: schemaV200, + }, + DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ + DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ + Commands: []v1.Command{ + { + Id: "devrun", + CommandUnion: v1.CommandUnion{ + Exec: &v1.ExecCommand{ + WorkingDir: "/projects", + CommandLine: "npm run", + }, + }, + }, + }, + Components: []v1.Component{ + { + Name: "nodejs", + ComponentUnion: v1.ComponentUnion{ + Container: &v1.ContainerComponent{ + Container: v1.Container{ + Image: "quay.io/nodejs-10", + }, + }, + }, + }, + }, + Events: &v1.Events{ + WorkspaceEvents: v1.WorkspaceEvents{ + PostStart: []string{"post-start-0"}, + PostStop: []string{"post-stop-2"}, + }, + }, + Projects: []v1.Project{ + { + ClonePath: "/data", + ProjectSource: v1.ProjectSource{ + Github: &v1.GithubProjectSource{ + GitLikeProjectSource: v1.GitLikeProjectSource{ + Remotes: map[string]string{ + "master": "https://githube.com/somerepo/someproject.git", + }, + }, + }, + }, + Name: "nodejs-starter", + }, + }, + }, + }, + }, + }, + }, + pluginOverride: v1.PluginOverrides{ + OverridesBase: v1.OverridesBase{}, + Components: []v1.ComponentPluginOverride{ + { + Name: "nodejs", + ComponentUnionPluginOverride: v1.ComponentUnionPluginOverride{ + Container: &v1.ContainerComponentPluginOverride{ + ContainerPluginOverride: v1.ContainerPluginOverride{ + Image: "quay.io/nodejs-12", + }, + }, + }, + }, + }, + Commands: []v1.CommandPluginOverride{ + { + Id: "devrun", + CommandUnionPluginOverride: v1.CommandUnionPluginOverride{ + Exec: &v1.ExecCommandPluginOverride{ + WorkingDir: "/projects-new", + CommandLine: "npm build", + }, + }, + }, + }, + }, + wantDevFile: DevfileObj{ + Data: &v2.DevfileV2{ + Devfile: v1.Devfile{ + DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ + DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ + Commands: []v1.Command{ + { + Id: "devrun", + CommandUnion: v1.CommandUnion{ + Exec: &v1.ExecCommand{ + CommandLine: "npm build", + WorkingDir: "/projects-new", + }, + }, + }, + { + Id: "devbuild", + CommandUnion: v1.CommandUnion{ + Exec: &v1.ExecCommand{ + WorkingDir: "/projects/nodejs-starter", + }, + }, + }, + }, + Components: []v1.Component{ + { + Name: "nodejs", + ComponentUnion: v1.ComponentUnion{ + Container: &v1.ContainerComponent{ + Container: v1.Container{ + Image: "quay.io/nodejs-12", + }, + }, + }, + }, + { + Name: "runtime", + ComponentUnion: v1.ComponentUnion{ + Container: &v1.ContainerComponent{ + Container: v1.Container{ + Image: "quay.io/nodejs-12", + }, + }, + }, + }, + }, + Events: &v1.Events{ + WorkspaceEvents: v1.WorkspaceEvents{ + PostStart: []string{"post-start-0"}, + PostStop: []string{"post-stop-1", "post-stop-2"}, + PreStop: []string{}, + PreStart: []string{}, + }, + }, + Projects: []v1.Project{ + { + ClonePath: "/data", + ProjectSource: v1.ProjectSource{ + Github: &v1.GithubProjectSource{ + GitLikeProjectSource: v1.GitLikeProjectSource{ + Remotes: map[string]string{ + "master": "https://githube.com/somerepo/someproject.git", + }, + }, + }, + }, + Name: "nodejs-starter", + }, + { + ClonePath: "/projects", + Name: "nodejs-starter-build", + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "case 10: it should error out when the plugin devfile is invalid", + args: args{ + devFileObj: DevfileObj{ + Ctx: devfileCtx.NewDevfileCtx(devfileTempPath), + Data: &v2.DevfileV2{ + Devfile: v1.Devfile{ + DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{}, + }, + }, + }, + }, + pluginDevfile: DevfileObj{ + Data: &v2.DevfileV2{ + Devfile: v1.Devfile{ + DevfileHeader: devfilepkg.DevfileHeader{ + SchemaVersion: schemaV200, + }, + DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ + DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ + Commands: []v1.Command{}, + Components: []v1.Component{}, + Projects: []v1.Project{}, + }, + }, + }, + }, + }, + pluginOverride: v1.PluginOverrides{ + Commands: []v1.CommandPluginOverride{ + { + Id: "devrun", + CommandUnionPluginOverride: v1.CommandUnionPluginOverride{ + Exec: &v1.ExecCommandPluginOverride{ + WorkingDir: "/projects/nodejs-starter", + }, + }, + }, + }, + }, + wantDevFile: DevfileObj{ + Data: &v2.DevfileV2{}, + }, + wantErr: true, + }, + { + name: "case 11: error out if the same plugin command is defined again in the local devfile", + args: args{ + devFileObj: DevfileObj{ + Ctx: devfileCtx.NewDevfileCtx(devfileTempPath), + Data: &v2.DevfileV2{ + Devfile: v1.Devfile{ + DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ + DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ + Commands: []v1.Command{ + { + Id: "devbuild", + CommandUnion: v1.CommandUnion{ + Exec: &v1.ExecCommand{ + WorkingDir: "/projects/nodejs-starter", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + pluginDevfile: DevfileObj{ + Data: &v2.DevfileV2{ + Devfile: v1.Devfile{ + DevfileHeader: devfilepkg.DevfileHeader{ + SchemaVersion: schemaV200, + }, + DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ + DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ + Commands: []v1.Command{ + { + Id: "devbuild", + CommandUnion: v1.CommandUnion{ + Exec: &v1.ExecCommand{ + WorkingDir: "/projects/nodejs-starter", + }, + }, + }, + }, + }, + }, + }, + }, + }, + wantDevFile: DevfileObj{ + Data: &v2.DevfileV2{}, + }, + wantErr: true, + }, + { + name: "case 12: error out if the same plugin component is defined again in the local devfile", + args: args{ + devFileObj: DevfileObj{ + Ctx: devfileCtx.NewDevfileCtx(devfileTempPath), + Data: &v2.DevfileV2{ + Devfile: v1.Devfile{ + DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ + DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ + Components: []v1.Component{ + { + Name: "runtime", + ComponentUnion: v1.ComponentUnion{ + Container: &v1.ContainerComponent{ + Container: v1.Container{ + Image: "quay.io/nodejs-12", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + pluginDevfile: DevfileObj{ + Data: &v2.DevfileV2{ + Devfile: v1.Devfile{ + DevfileHeader: devfilepkg.DevfileHeader{ + SchemaVersion: schemaV200, + }, + DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ + DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ + Components: []v1.Component{ + { + Name: "runtime", + ComponentUnion: v1.ComponentUnion{ + Container: &v1.ContainerComponent{ + Container: v1.Container{ + Image: "quay.io/nodejs-12", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + wantDevFile: DevfileObj{ + Data: &v2.DevfileV2{}, + }, + wantErr: true, + }, + { + name: "case 13: error out if the plugin project is defined again in the local devfile", + args: args{ + devFileObj: DevfileObj{ + Ctx: devfileCtx.NewDevfileCtx(devfileTempPath), + Data: &v2.DevfileV2{ + Devfile: v1.Devfile{ + DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ + DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ + Projects: []v1.Project{ + { + ClonePath: "/projects", + Name: "nodejs-starter-build", + }, + }, + }, + }, + }, + }, + }, + }, + pluginDevfile: DevfileObj{ + Data: &v2.DevfileV2{ + Devfile: v1.Devfile{ + DevfileHeader: devfilepkg.DevfileHeader{ + SchemaVersion: schemaV200, + }, + DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ + DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ + Projects: []v1.Project{ + { + ClonePath: "/projects", + Name: "nodejs-starter-build", + }, + }, + }, + }, + }, + }, + }, + wantDevFile: DevfileObj{ + Data: &v2.DevfileV2{}, + }, + wantErr: true, + }, + { + name: "case 14: error out if the same project is defined in the both plugin devfile and parent", + args: args{ + devFileObj: DevfileObj{ + Ctx: devfileCtx.NewDevfileCtx(devfileTempPath), + Data: &v2.DevfileV2{ + Devfile: v1.Devfile{ + DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ + DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ + Projects: []v1.Project{ + { + ClonePath: "/projects", + Name: "nodejs-starter-build", + }, + }, + }, + }, + }, + }, + }, + }, + pluginDevfile: DevfileObj{ + Data: &v2.DevfileV2{ + Devfile: v1.Devfile{ + DevfileHeader: devfilepkg.DevfileHeader{ + SchemaVersion: schemaV200, + }, + DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ + DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ + Projects: []v1.Project{ + { + ClonePath: "/projects", + Name: "nodejs-starter", + }, + }, + }, + }, + }, + }, + }, + parentDevfile: DevfileObj{ + Data: &v2.DevfileV2{ + Devfile: v1.Devfile{ + DevfileHeader: devfilepkg.DevfileHeader{ + SchemaVersion: schemaV200, + }, + DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ + DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ + Projects: []v1.Project{ + { + ClonePath: "/projects", + Name: "nodejs-starter", + }, + }, + }, + }, + }, + }, + }, + wantDevFile: DevfileObj{ + Data: &v2.DevfileV2{}, + }, + wantErr: true, + }, + { + name: "case 15: error out if the same command is defined in both plugin devfile and parent devfile", + args: args{ + devFileObj: DevfileObj{ + Ctx: devfileCtx.NewDevfileCtx(devfileTempPath), + Data: &v2.DevfileV2{ + Devfile: v1.Devfile{ + DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ + DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ + Commands: []v1.Command{ + { + Id: "devbuild", + CommandUnion: v1.CommandUnion{ + Exec: &v1.ExecCommand{ + WorkingDir: "/projects/nodejs-starter", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + pluginDevfile: DevfileObj{ + Data: &v2.DevfileV2{ + Devfile: v1.Devfile{ + DevfileHeader: devfilepkg.DevfileHeader{ + SchemaVersion: schemaV200, + }, + DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ + DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ + Commands: []v1.Command{ + { + Id: "devrun", + CommandUnion: v1.CommandUnion{ + Exec: &v1.ExecCommand{ + WorkingDir: "/projects/nodejs-starter", + }, + }, + }, + }, + }, + }, + }, + }, + }, + parentDevfile: DevfileObj{ + Data: &v2.DevfileV2{ + Devfile: v1.Devfile{ + DevfileHeader: devfilepkg.DevfileHeader{ + SchemaVersion: schemaV200, + }, + DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ + DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ + Commands: []v1.Command{ + { + Id: "devrun", + CommandUnion: v1.CommandUnion{ + Exec: &v1.ExecCommand{ + WorkingDir: "/projects/nodejs-starter", + }, + }, + }, + }, + }, + }, + }, + }, + }, + wantDevFile: DevfileObj{ + Data: &v2.DevfileV2{}, + }, + wantErr: true, + }, + { + name: "case 16: error out if the same component is defined in both plugin devfile and parent devfile", + args: args{ + devFileObj: DevfileObj{ + Ctx: devfileCtx.NewDevfileCtx(devfileTempPath), + Data: &v2.DevfileV2{ + Devfile: v1.Devfile{ + DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ + DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ + Components: []v1.Component{ + { + Name: "runtime", + ComponentUnion: v1.ComponentUnion{ + Container: &v1.ContainerComponent{ + Container: v1.Container{ + Image: "quay.io/nodejs-12", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + pluginDevfile: DevfileObj{ + Data: &v2.DevfileV2{ + Devfile: v1.Devfile{ + DevfileHeader: devfilepkg.DevfileHeader{ + SchemaVersion: schemaV200, + }, + DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ + DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ + Components: []v1.Component{ + { + Name: "build", + ComponentUnion: v1.ComponentUnion{ + Container: &v1.ContainerComponent{ + Container: v1.Container{ + Image: "quay.io/nodejs-12", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + parentDevfile: DevfileObj{ + Data: &v2.DevfileV2{ + Devfile: v1.Devfile{ + DevfileHeader: devfilepkg.DevfileHeader{ + SchemaVersion: schemaV200, + }, + DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ + DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ + Components: []v1.Component{ + { + Name: "build", + ComponentUnion: v1.ComponentUnion{ + Container: &v1.ContainerComponent{ + Container: v1.Container{ + Image: "quay.io/nodejs-10", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + wantDevFile: DevfileObj{ + Data: &v2.DevfileV2{}, + }, + wantErr: true, + }, + { + name: "case 17: it should override the requested parent's data and plugin's data, and add the local devfile's data", + args: args{ + devFileObj: DevfileObj{ + Ctx: devfileCtx.NewDevfileCtx(devfileTempPath), + Data: &v2.DevfileV2{ + Devfile: v1.Devfile{ + DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ + Parent: &v1.Parent{ + ParentOverrides: v1.ParentOverrides{ + Commands: []v1.CommandParentOverride{ + { + Id: "devrun", + CommandUnionParentOverride: v1.CommandUnionParentOverride{ + Exec: &v1.ExecCommandParentOverride{ + WorkingDir: "/projects/nodejs-starter", + }, + }, + }, + }, + Projects: []v1.ProjectParentOverride{ + { + ClonePath: "/projects", + Name: "nodejs-starter", + }, + }, + }, + }, + DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ + Commands: []v1.Command{ + { + Id: "devbuild", + CommandUnion: v1.CommandUnion{ + Exec: &v1.ExecCommand{ + WorkingDir: "/projects/nodejs-starter", + }, + }, + }, + }, + Components: []v1.Component{ + { + Name: "runtime", + ComponentUnion: v1.ComponentUnion{ + Container: &v1.ContainerComponent{ + Container: v1.Container{ + Image: "quay.io/nodejs-12", + }, + }, + }, + }, + }, + Events: &v1.Events{ + WorkspaceEvents: v1.WorkspaceEvents{ + PostStop: []string{"post-stop"}, + }, + }, + Projects: []v1.Project{ + { + ClonePath: "/projects", + Name: "nodejs-starter-build", + }, + }, + }, + }, + }, + }, + }, + }, + parentDevfile: DevfileObj{ + Data: &v2.DevfileV2{ + Devfile: v1.Devfile{ + DevfileHeader: devfilepkg.DevfileHeader{ + SchemaVersion: schemaV200, + }, + DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ + DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ + Commands: []v1.Command{ + { + Id: "devrun", + CommandUnion: v1.CommandUnion{ + Exec: &v1.ExecCommand{ + WorkingDir: "/projects", + CommandLine: "npm run", + }, + }, + }, + }, + Events: &v1.Events{ + WorkspaceEvents: v1.WorkspaceEvents{ + PostStart: []string{"post-start-0"}, + }, + }, + Projects: []v1.Project{ + { + ClonePath: "/data", + ProjectSource: v1.ProjectSource{ + Github: &v1.GithubProjectSource{ + GitLikeProjectSource: v1.GitLikeProjectSource{ + Remotes: map[string]string{ + "master": "https://githube.com/somerepo/someproject.git", + }, + }, + }, + }, + Name: "nodejs-starter", + }, + }, + }, + }, + }, + }, + }, + pluginDevfile: DevfileObj{ + Data: &v2.DevfileV2{ + Devfile: v1.Devfile{ + DevfileHeader: devfilepkg.DevfileHeader{ + SchemaVersion: schemaV200, + }, + DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ + DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ + Commands: []v1.Command{ + { + Id: "devdebug", + CommandUnion: v1.CommandUnion{ + Exec: &v1.ExecCommand{ + WorkingDir: "/projects", + CommandLine: "npm debug", + }, + }, + }, + }, + Components: []v1.Component{ + { + Name: "nodejs", + ComponentUnion: v1.ComponentUnion{ + Container: &v1.ContainerComponent{ + Container: v1.Container{ + Image: "quay.io/nodejs-10", + }, + }, + }, + }, + }, + Events: &v1.Events{ + WorkspaceEvents: v1.WorkspaceEvents{ + PreStart: []string{"pre-start-0"}, + }, + }, + }, + }, + }, + }, + }, + pluginOverride: v1.PluginOverrides{ + Components: []v1.ComponentPluginOverride{ + { + Name: "nodejs", + ComponentUnionPluginOverride: v1.ComponentUnionPluginOverride{ + Container: &v1.ContainerComponentPluginOverride{ + ContainerPluginOverride: v1.ContainerPluginOverride{ + Image: "quay.io/nodejs-12", + }, + }, + }, + }, + }, + }, + wantDevFile: DevfileObj{ + Data: &v2.DevfileV2{ + Devfile: v1.Devfile{ + DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ + DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ + Commands: []v1.Command{ + { + Id: "devrun", + CommandUnion: v1.CommandUnion{ + Exec: &v1.ExecCommand{ + CommandLine: "npm run", + WorkingDir: "/projects/nodejs-starter", + }, + }, + }, + { + Id: "devdebug", + CommandUnion: v1.CommandUnion{ + Exec: &v1.ExecCommand{ + WorkingDir: "/projects", + CommandLine: "npm debug", + }, + }, + }, + { + Id: "devbuild", + CommandUnion: v1.CommandUnion{ + Exec: &v1.ExecCommand{ + WorkingDir: "/projects/nodejs-starter", + }, + }, + }, + }, + Components: []v1.Component{ + { + Name: "nodejs", + ComponentUnion: v1.ComponentUnion{ + Container: &v1.ContainerComponent{ + Container: v1.Container{ + Image: "quay.io/nodejs-12", + }, + }, + }, + }, + { + Name: "runtime", + ComponentUnion: v1.ComponentUnion{ + Container: &v1.ContainerComponent{ + Container: v1.Container{ + Image: "quay.io/nodejs-12", + }, + }, + }, + }, + }, + Events: &v1.Events{ + WorkspaceEvents: v1.WorkspaceEvents{ + PostStart: []string{"post-start-0"}, + PostStop: []string{"post-stop"}, + PreStop: []string{}, + PreStart: []string{"pre-start-0"}, + }, + }, + Projects: []v1.Project{ + { + ClonePath: "/projects", + ProjectSource: v1.ProjectSource{ + Github: &v1.GithubProjectSource{ + GitLikeProjectSource: v1.GitLikeProjectSource{ + Remotes: map[string]string{ + "master": "https://githube.com/somerepo/someproject.git", + }, + }, + }, + }, + Name: "nodejs-starter", + }, + { + ClonePath: "/projects", + Name: "nodejs-starter-build", + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "case 18: error out if the plugin component is defined with a different component type in the local devfile", + args: args{ + devFileObj: DevfileObj{ + Ctx: devfileCtx.NewDevfileCtx(devfileTempPath), + Data: &v2.DevfileV2{ + Devfile: v1.Devfile{ + DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ + DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ + Components: []v1.Component{ + { + Name: "runtime", + ComponentUnion: v1.ComponentUnion{ + Container: &v1.ContainerComponent{ + Container: v1.Container{ + Image: "quay.io/nodejs-12", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + pluginDevfile: DevfileObj{ + Data: &v2.DevfileV2{ + Devfile: v1.Devfile{ + DevfileHeader: devfilepkg.DevfileHeader{ + SchemaVersion: schemaV200, + }, + DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ + DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ + Components: []v1.Component{ + { + Name: "runtime", + ComponentUnion: v1.ComponentUnion{ + Volume: &v1.VolumeComponent{ + Volume: v1.Volume{ + Size: "500Mi", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + wantDevFile: DevfileObj{ + Data: &v2.DevfileV2{}, + }, + wantErr: true, + }, + { + name: "case 19: it should override with no errors if the plugin component is defined with a different component type in the plugin override", + args: args{ + devFileObj: DevfileObj{ + Ctx: devfileCtx.NewDevfileCtx(devfileTempPath), + Data: &v2.DevfileV2{ + Devfile: v1.Devfile{}, + }, + }, + }, + pluginOverride: v1.PluginOverrides{ + Components: []v1.ComponentPluginOverride{ + { + Name: "runtime", + ComponentUnionPluginOverride: v1.ComponentUnionPluginOverride{ + Container: &v1.ContainerComponentPluginOverride{ + ContainerPluginOverride: v1.ContainerPluginOverride{ + Image: "quay.io/nodejs-12", + }, + }, + }, + }, + }, + }, + pluginDevfile: DevfileObj{ + Data: &v2.DevfileV2{ + Devfile: v1.Devfile{ + DevfileHeader: devfilepkg.DevfileHeader{ + SchemaVersion: schemaV200, + }, + DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ + DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ + Components: []v1.Component{ + { + Name: "runtime", + ComponentUnion: v1.ComponentUnion{ + Volume: &v1.VolumeComponent{ + Volume: v1.Volume{ + Size: "500Mi", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + wantDevFile: DevfileObj{ + Data: &v2.DevfileV2{ + Devfile: v1.Devfile{ + DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ + DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ + Components: []v1.Component{ + { + Name: "runtime", + ComponentUnion: v1.ComponentUnion{ + Container: &v1.ContainerComponent{ + Container: v1.Container{ + Image: "quay.io/nodejs-12", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + wantErr: false, + }, + { + name: "case 20: error out if the parent component is defined with a different component type in the local devfile", + args: args{ + devFileObj: DevfileObj{ + Ctx: devfileCtx.NewDevfileCtx(devfileTempPath), + Data: &v2.DevfileV2{ + Devfile: v1.Devfile{ + DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ + DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ + Components: []v1.Component{ + { + Name: "runtime", + ComponentUnion: v1.ComponentUnion{ + Container: &v1.ContainerComponent{ + Container: v1.Container{ + Image: "quay.io/nodejs-12", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + parentDevfile: DevfileObj{ + Data: &v2.DevfileV2{ + Devfile: v1.Devfile{ + DevfileHeader: devfilepkg.DevfileHeader{ + SchemaVersion: schemaV200, + }, + DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ + DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ + Components: []v1.Component{ + { + Name: "runtime", + ComponentUnion: v1.ComponentUnion{ + Volume: &v1.VolumeComponent{ + Volume: v1.Volume{ + Size: "500Mi", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + wantDevFile: DevfileObj{ + Data: &v2.DevfileV2{}, + }, + wantErr: true, + }, + { + name: "case 21: it should override with no errors if the parent component is defined with a different component type in the parent override", + args: args{ + devFileObj: DevfileObj{ + Ctx: devfileCtx.NewDevfileCtx(devfileTempPath), + Data: &v2.DevfileV2{ + Devfile: v1.Devfile{ + DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ + Parent: &v1.Parent{ + ParentOverrides: v1.ParentOverrides{ + Components: []v1.ComponentParentOverride{ + { + Name: "runtime", + ComponentUnionParentOverride: v1.ComponentUnionParentOverride{ + Container: &v1.ContainerComponentParentOverride{ + ContainerParentOverride: v1.ContainerParentOverride{ + Image: "quay.io/nodejs-12", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + parentDevfile: DevfileObj{ + Data: &v2.DevfileV2{ + Devfile: v1.Devfile{ + DevfileHeader: devfilepkg.DevfileHeader{ + SchemaVersion: schemaV200, + }, + DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ + DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ + Components: []v1.Component{ + { + Name: "runtime", + ComponentUnion: v1.ComponentUnion{ + Volume: &v1.VolumeComponent{ + Volume: v1.Volume{ + Size: "500Mi", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + wantDevFile: DevfileObj{ + Data: &v2.DevfileV2{ + Devfile: v1.Devfile{ + DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ + DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ + Components: []v1.Component{ + { + Name: "runtime", + ComponentUnion: v1.ComponentUnion{ + Container: &v1.ContainerComponent{ + Container: v1.Container{ + Image: "quay.io/nodejs-12", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + wantErr: false, + }, + { + name: "case 22: error out if the URI is recursively referenced", + args: args{ + devFileObj: DevfileObj{ + Ctx: devfileCtx.NewDevfileCtx(devfileTempPath), + Data: &v2.DevfileV2{ + Devfile: v1.Devfile{}, + }, + }, + }, + pluginDevfile: DevfileObj{ + Data: &v2.DevfileV2{ + Devfile: v1.Devfile{ + DevfileHeader: devfilepkg.DevfileHeader{ + SchemaVersion: schemaV200, + }, + DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ + Parent: &v1.Parent{ + ImportReference: v1.ImportReference{ + ImportReferenceUnion: v1.ImportReferenceUnion{ + Uri: "http://127.0.0.1:8080", + }, + }, + }, + DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ + Components: []v1.Component{ + { + Name: "runtime", + ComponentUnion: v1.ComponentUnion{ + Volume: &v1.VolumeComponent{ + Volume: v1.Volume{ + Size: "500Mi", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + wantDevFile: DevfileObj{ + Data: &v2.DevfileV2{}, + }, + wantErr: true, + testRecursiveReference: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if !reflect.DeepEqual(tt.parentDevfile, DevfileObj{}) { + testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + data, err := yaml.Marshal(tt.parentDevfile.Data) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + _, err = w.Write(data) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + })) + defer testServer.Close() + parent := tt.args.devFileObj.Data.GetParent() + if parent == nil { + parent = &v1.Parent{} + } + parent.Uri = testServer.URL + + tt.args.devFileObj.Data.SetParent(parent) + } + if !reflect.DeepEqual(tt.pluginDevfile, DevfileObj{}) { + + testServer := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + data, err := yaml.Marshal(tt.pluginDevfile.Data) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + _, err = w.Write(data) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + })) + if tt.testRecursiveReference { + // create a listener with the desired port. + l, err := net.Listen("tcp", "127.0.0.1:8080") + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + // NewUnstartedServer creates a listener. Close that listener and replace + // with the one we created. + testServer.Listener.Close() + testServer.Listener = l + } + testServer.Start() + defer testServer.Close() + + plugincomp := []v1.Component{ + { + Name: "plugincomp", + ComponentUnion: v1.ComponentUnion{ + Plugin: &v1.PluginComponent{ + ImportReference: v1.ImportReference{ + ImportReferenceUnion: v1.ImportReferenceUnion{ + Uri: testServer.URL, + }, + }, + PluginOverrides: tt.pluginOverride, + }, + }, + }, + } + tt.args.devFileObj.Data.AddComponents(plugincomp) + + } + err := parseParentAndPlugin(tt.args.devFileObj) + + // Unexpected error + if (err != nil) != tt.wantErr { + t.Errorf("parseParentAndPlugin() error = %v, wantErr %v", err, tt.wantErr) + return + } + + // Expected error and got an err + if tt.wantErr && err != nil { + return + } + + if !reflect.DeepEqual(tt.args.devFileObj.Data, tt.wantDevFile.Data) { + t.Errorf("wanted: %v, got: %v, difference at %v", tt.wantDevFile.Data, tt.args.devFileObj.Data, pretty.Compare(tt.args.devFileObj.Data, tt.wantDevFile.Data)) + } + + }) + } +} + +func Test_parseParentAndPlugin_RecursivelyReference_withMultipleURI(t *testing.T) { + const uri1 = "127.0.0.1:8080" + const uri2 = "127.0.0.1:9090" + const uri3 = "127.0.0.1:8090" + const httpPrefix = "http://" + + devFileObj := DevfileObj{ + Ctx: devfileCtx.NewDevfileCtx(devfileTempPath), + Data: &v2.DevfileV2{ + Devfile: v1.Devfile{ + DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ + Parent: &v1.Parent{ + ImportReference: v1.ImportReference{ + ImportReferenceUnion: v1.ImportReferenceUnion{ + Uri: httpPrefix + uri1, + }, + }, + }, + DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ + Components: []v1.Component{ + { + Name: "runtime2", + ComponentUnion: v1.ComponentUnion{ + Volume: &v1.VolumeComponent{ + Volume: v1.Volume{ + Size: "500Mi", + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + parentDevfile1 := DevfileObj{ + Ctx: devfileCtx.NewDevfileCtx(devfileTempPath), + Data: &v2.DevfileV2{ + Devfile: v1.Devfile{ + DevfileHeader: devfilepkg.DevfileHeader{ + SchemaVersion: schemaV200, + }, + DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ + Parent: &v1.Parent{ + ImportReference: v1.ImportReference{ + ImportReferenceUnion: v1.ImportReferenceUnion{ + Uri: httpPrefix + uri2, + }, + }, + }, + DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ + Components: []v1.Component{ + { + Name: "runtime", + ComponentUnion: v1.ComponentUnion{ + Volume: &v1.VolumeComponent{ + Volume: v1.Volume{ + Size: "500Mi", + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + parentDevfile2 := DevfileObj{ + Ctx: devfileCtx.NewDevfileCtx(devfileTempPath), + Data: &v2.DevfileV2{ + Devfile: v1.Devfile{ + DevfileHeader: devfilepkg.DevfileHeader{ + SchemaVersion: schemaV200, + }, + DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ + DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ + Components: []v1.Component{ + { + Name: "plugin", + ComponentUnion: v1.ComponentUnion{ + Plugin: &v1.PluginComponent{ + ImportReference: v1.ImportReference{ + ImportReferenceUnion: v1.ImportReferenceUnion{ + Uri: httpPrefix + uri3, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + parentDevfile3 := DevfileObj{ + Ctx: devfileCtx.NewDevfileCtx(devfileTempPath), + Data: &v2.DevfileV2{ + Devfile: v1.Devfile{ + DevfileHeader: devfilepkg.DevfileHeader{ + SchemaVersion: schemaV200, + }, + DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ + Parent: &v1.Parent{ + ImportReference: v1.ImportReference{ + ImportReferenceUnion: v1.ImportReferenceUnion{ + Uri: httpPrefix + uri1, + }, + }, + }, + DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ + Components: []v1.Component{ + { + Name: "test", + ComponentUnion: v1.ComponentUnion{ + Volume: &v1.VolumeComponent{ + Volume: v1.Volume{ + Size: "500Mi", + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + + testServer1 := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + data, err := yaml.Marshal(parentDevfile1.Data) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + _, err = w.Write(data) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + })) + // create a listener with the desired port. + l1, err := net.Listen("tcp", uri1) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + // NewUnstartedServer creates a listener. Close that listener and replace + // with the one we created. + testServer1.Listener.Close() + testServer1.Listener = l1 + + testServer1.Start() + defer testServer1.Close() + + testServer2 := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + data, err := yaml.Marshal(parentDevfile2.Data) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + _, err = w.Write(data) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + })) + // create a listener with the desired port. + l2, err := net.Listen("tcp", uri2) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + // NewUnstartedServer creates a listener. Close that listener and replace + // with the one we created. + testServer2.Listener.Close() + testServer2.Listener = l2 + + testServer2.Start() + defer testServer2.Close() + + testServer3 := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + data, err := yaml.Marshal(parentDevfile3.Data) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + _, err = w.Write(data) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + })) + // create a listener with the desired port. + l3, err := net.Listen("tcp", uri3) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + // NewUnstartedServer creates a listener. Close that listener and replace + // with the one we created. + testServer3.Listener.Close() + testServer3.Listener = l3 + + testServer3.Start() + defer testServer3.Close() + t.Run("it should error out if URI is recursively referenced with multiple references", func(t *testing.T) { + err := parseParentAndPlugin(devFileObj) + expectedErr := fmt.Sprintf("URI %v%v is recursively referenced", httpPrefix, uri1) + // Unexpected error + if err == nil || !reflect.DeepEqual(expectedErr, err.Error()) { + t.Errorf("Test_parseParentAndPlugin_RecursivelyReference_withMultipleURI() unexpected error = %v", err) + return + } + + }) } diff --git a/pkg/devfile/parser/writer.go b/pkg/devfile/parser/writer.go index e583b774..a98ceca7 100644 --- a/pkg/devfile/parser/writer.go +++ b/pkg/devfile/parser/writer.go @@ -5,6 +5,7 @@ import ( "sigs.k8s.io/yaml" + "github.com/devfile/library/pkg/testingutil/filesystem" "github.com/pkg/errors" "k8s.io/klog" ) @@ -38,9 +39,11 @@ func (d *DevfileObj) WriteYamlDevfile() error { if err != nil { return errors.Wrapf(err, "failed to marshal devfile object into yaml") } - // Write to devfile.yaml fs := d.Ctx.GetFs() + if fs == nil { + fs = filesystem.DefaultFs{} + } err = fs.WriteFile(d.Ctx.GetAbsPath(), yamlData, 0644) if err != nil { return errors.Wrapf(err, "failed to create devfile yaml file") diff --git a/pkg/testingutil/devfile.go b/pkg/testingutil/devfile.go index 2024c4b0..08cca6a3 100644 --- a/pkg/testingutil/devfile.go +++ b/pkg/testingutil/devfile.go @@ -215,6 +215,14 @@ func (d TestDevfileData) GetDevfileVolumeComponents(options common.DevfileOption return components, nil } +// GetDevfileWorkspace is a mock func to get the DevfileWorkspace in a test devfile +func (d TestDevfileData) GetDevfileWorkspace() *v1.DevWorkspaceTemplateSpecContent { + return &v1.DevWorkspaceTemplateSpecContent{} +} + +// SetDevfileWorkspace is a mock func to set the DevfileWorkspace in a test devfile +func (d TestDevfileData) SetDevfileWorkspace(content v1.DevWorkspaceTemplateSpecContent) {} + // Validate is a mock validation that always validates without error func (d TestDevfileData) Validate() error { return nil