diff --git a/pkg/devfile/components.go b/pkg/devfile/components.go index ba235eed7a6..baabcb5ae65 100644 --- a/pkg/devfile/components.go +++ b/pkg/devfile/components.go @@ -6,6 +6,53 @@ import ( parsercommon "github.com/devfile/library/v2/pkg/devfile/parser/data/v2/common" ) +// GetImageComponentsToPush returns the list of Image components that can be automatically created on startup. +// The list returned is governed by the AutoBuild field in each component. +// All components with AutoBuild set to true are included, along with those with no AutoBuild set and not-referenced. +func GetImageComponentsToPush(devfileObj parser.DevfileObj) ([]devfilev1.Component, error) { + imageComponents, err := devfileObj.Data.GetComponents(parsercommon.DevfileOptions{ + ComponentOptions: parsercommon.ComponentOptions{ComponentType: devfilev1.ImageComponentType}, + }) + if err != nil { + return nil, err + } + + allApplyCommands, err := devfileObj.Data.GetCommands(parsercommon.DevfileOptions{ + CommandOptions: parsercommon.CommandOptions{CommandType: devfilev1.ApplyCommandType}, + }) + if err != nil { + return nil, err + } + + m := make(map[string]devfilev1.Component) + for _, comp := range imageComponents { + if comp.Image == nil { + continue + } + var add bool + if comp.Image.AutoBuild == nil { + // auto-created only if not referenced by any apply command + if !isComponentReferenced(allApplyCommands, comp.Name) { + add = true + } + } else if *comp.Image.AutoBuild { + add = true + } + if !add { + continue + } + if _, present := m[comp.Name]; !present { + m[comp.Name] = comp + } + } + + var result []devfilev1.Component + for _, comp := range m { + result = append(result, comp) + } + return result, nil +} + // GetK8sAndOcComponentsToPush returns the list of Kubernetes and OpenShift components to push, // The list returned is governed by the DeployByDefault field in each component. // All components with DeployByDefault set to true are included, along with those with no DeployByDefault set and not-referenced. diff --git a/pkg/devfile/components_test.go b/pkg/devfile/components_test.go index e6852f8a327..63dc7668964 100644 --- a/pkg/devfile/components_test.go +++ b/pkg/devfile/components_test.go @@ -227,10 +227,6 @@ func TestGetK8sAndOcComponentsToPush(t *testing.T) { }, } - lessFunc := func(x, y devfilev1.Component) bool { - return x.Name < y.Name - } - for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { devfileObj, err := tt.args.devfileObj() @@ -248,9 +244,194 @@ func TestGetK8sAndOcComponentsToPush(t *testing.T) { t.Errorf("Got %d components, expected %d\n", len(got), len(tt.want)) } - if diff := cmp.Diff(tt.want, got, cmpopts.EquateEmpty(), cmpopts.SortSlices(lessFunc)); diff != "" { + if diff := cmp.Diff(tt.want, got, cmpopts.EquateEmpty(), cmpopts.SortSlices(less)); diff != "" { t.Errorf("GetK8sAndOcComponentsToPush() mismatch (-want +got):\n%s", diff) } }) } } + +func TestGetImageComponentsToPush(t *testing.T) { + fs := devfileFileSystem.NewFakeFs() + + buildImageComponent := func(name string, autoBuild *bool, referenced bool) (devfilev1.Component, devfilev1.Command) { + comp := devfilev1.Component{ + Name: name, + ComponentUnion: devfilev1.ComponentUnion{ + Image: &devfilev1.ImageComponent{ + Image: devfilev1.Image{ + ImageName: "my-image:" + name, + ImageUnion: devfilev1.ImageUnion{ + AutoBuild: autoBuild, + }, + }, + }, + }, + } + if referenced { + cmd := devfilev1.Command{ + Id: "apply-" + name, + CommandUnion: devfilev1.CommandUnion{ + Apply: &devfilev1.ApplyCommand{ + Component: name, + }, + }, + } + return comp, cmd + } + return comp, devfilev1.Command{} + } + + var ( + autoBuildTrueReferenced, applyAutoBuildTrueReferenced = buildImageComponent( + "autoBuildTrueReferenced", pointer.Bool(true), true) + autoBuildTrueNotReferenced, _ = buildImageComponent( + "autoBuildTrueNotReferenced", pointer.Bool(true), false) + autoBuildFalseReferenced, applyAutoBuildFalseReferenced = buildImageComponent( + "autoBuildFalseReferenced", pointer.Bool(false), true) + autoBuildFalseNotReferenced, _ = buildImageComponent( + "autoBuildFalseNotReferenced", pointer.Bool(false), false) + autoBuildNotSetReferenced, applyAutoBuildNotSetReferenced = buildImageComponent( + "autoBuildNotSetReferenced", nil, true) + autoBuildNotSetNotReferenced, _ = buildImageComponent( + "autoBuildNotSetNotReferenced", nil, false) + ) + + buildFullDevfile := func() (parser.DevfileObj, error) { + devfileData, err := data.NewDevfileData(string(data.APISchemaVersion220)) + if err != nil { + return parser.DevfileObj{}, err + } + devfileData.SetMetadata(devfilepkg.DevfileMetadata{Name: "my-devfile"}) + err = devfileData.AddComponents([]devfilev1.Component{ + autoBuildTrueReferenced, + autoBuildTrueNotReferenced, + autoBuildFalseReferenced, + autoBuildFalseNotReferenced, + autoBuildNotSetReferenced, + autoBuildNotSetNotReferenced, + + //Add other kinds of components + { + Name: "my-k8s-component", + ComponentUnion: devfilev1.ComponentUnion{ + Kubernetes: &devfilev1.KubernetesComponent{ + K8sLikeComponent: devfilev1.K8sLikeComponent{ + K8sLikeComponentLocation: devfilev1.K8sLikeComponentLocation{ + Inlined: "my-k8s-component-inlined", + }, + }, + }, + }, + }, + { + Name: "container-component", + ComponentUnion: devfilev1.ComponentUnion{ + Container: &devfilev1.ContainerComponent{ + Container: devfilev1.Container{ + DedicatedPod: pointer.Bool(true), + Image: "my-container-image", + }, + }, + }, + }, + }) + if err != nil { + return parser.DevfileObj{}, err + } + err = devfileData.AddCommands([]devfilev1.Command{ + applyAutoBuildTrueReferenced, + applyAutoBuildFalseReferenced, + applyAutoBuildNotSetReferenced, + + //Add other kinds of components + { + Id: "apply-k8s-component", + CommandUnion: devfilev1.CommandUnion{ + Apply: &devfilev1.ApplyCommand{ + Component: "my-k8s-component", + }, + }, + }, + { + Id: "exec-command", + CommandUnion: devfilev1.CommandUnion{ + Apply: &devfilev1.ApplyCommand{ + Component: "my-image-component", + }, + Exec: &devfilev1.ExecCommand{ + CommandLine: "/path/to/my/command -success", + Component: "container-component", + }, + }, + }, + }) + if err != nil { + return parser.DevfileObj{}, err + } + return parser.DevfileObj{ + Ctx: devfileCtx.FakeContext(fs, parser.OutputDevfileYamlPath), + Data: devfileData, + }, nil + } + + type args struct { + devfileObj func() (parser.DevfileObj, error) + } + tests := []struct { + name string + args args + want []devfilev1.Component + wantErr bool + }{ + { + name: "empty devfile", + args: args{ + devfileObj: func() (parser.DevfileObj, error) { + return parser.DevfileObj{ + Data: devfiletesting.GetDevfileData(t, nil, nil), + Ctx: devfileCtx.FakeContext(fs, parser.OutputDevfileYamlPath), + }, nil + }, + }, + want: []devfilev1.Component{}, + wantErr: false, + }, + { + name: "return components that need to be created automatically on startup", + args: args{ + devfileObj: buildFullDevfile, + }, + want: []devfilev1.Component{ + autoBuildTrueReferenced, + autoBuildTrueNotReferenced, + autoBuildNotSetNotReferenced, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + devfileObj, err := tt.args.devfileObj() + if err != nil { + t.Errorf("unable to create Devfile object: %v", err) + return + } + + got, err := GetImageComponentsToPush(devfileObj) + if (err != nil) != tt.wantErr { + t.Errorf("GetImageComponentsToPush() error = %v, wantErr %v", err, tt.wantErr) + return + } + if len(got) != len(tt.want) { + t.Errorf("Got %d components, expected %d\n", len(got), len(tt.want)) + } + if diff := cmp.Diff(tt.want, got, cmpopts.EquateEmpty(), cmpopts.SortSlices(less)); diff != "" { + t.Errorf("GetImageComponentsToPush() mismatch (-want +got):\n%s", diff) + } + }) + } +} + +func less(x, y devfilev1.Component) bool { + return x.Name < y.Name +}