diff --git a/go.mod b/go.mod index b30c77064d3..347bf0ccc98 100644 --- a/go.mod +++ b/go.mod @@ -8,8 +8,8 @@ require ( github.com/Netflix/go-expect v0.0.0-20200312175327-da48e75238e2 github.com/blang/semver v3.5.1+incompatible github.com/containerd/containerd v1.3.3 // indirect - github.com/devfile/api/v2 v2.0.0-20210211160219-33a78aec06af - github.com/devfile/library v0.0.0-20210216162950-3066a892876c + github.com/devfile/api/v2 v2.0.0-20210304212617-bfc3f501616b + github.com/devfile/library v1.0.0-alpha.2.0.20210323153322-3d708859f0b5 github.com/docker/docker v17.12.0-ce-rc1.0.20200309214505-aa6a9891b09c+incompatible github.com/docker/go-connections v0.4.1-0.20200120150455-7dc0a2d6ddce github.com/fatih/color v1.7.0 diff --git a/go.sum b/go.sum index 52b1dcd10bf..b710d98c688 100644 --- a/go.sum +++ b/go.sum @@ -218,10 +218,10 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd/go.mod h1:dv4zxwHi5C/8AeI+4gX4dCWOIvNi7I6JCSX0HvlKPgE= github.com/deislabs/oras v0.7.0/go.mod h1:sqMKPG3tMyIX9xwXUBRLhZ24o+uT4y6jgBD2RzUTKDM= github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM= -github.com/devfile/api/v2 v2.0.0-20210211160219-33a78aec06af h1:egbFAAS/CWJMAqa4zIm8Cik3+iCqTAfLPfCj6PLEG5Y= -github.com/devfile/api/v2 v2.0.0-20210211160219-33a78aec06af/go.mod h1:Cot4snybn3qhIh48oIFi9McocnIx7zY5fFbjfrIpPvg= -github.com/devfile/library v0.0.0-20210216162950-3066a892876c h1:NL9EpuPuFVxCqE00Bh1Uw3YsEqoMdwq4vx0OXSZRVnU= -github.com/devfile/library v0.0.0-20210216162950-3066a892876c/go.mod h1:aGJSpcGrRiYwsQQJMQH1ChHuOptUf49n+j0RDBYyTIQ= +github.com/devfile/api/v2 v2.0.0-20210304212617-bfc3f501616b h1:r6Z2rXRA60eQQTdh1cJBBrj3vp9/c1rNbX42kyw2WpA= +github.com/devfile/api/v2 v2.0.0-20210304212617-bfc3f501616b/go.mod h1:Cot4snybn3qhIh48oIFi9McocnIx7zY5fFbjfrIpPvg= +github.com/devfile/library v1.0.0-alpha.2.0.20210323153322-3d708859f0b5 h1:cxv4Cs86FcWhpKFXITuxwH0qI1Ik9U34HVM+F8x+ncU= +github.com/devfile/library v1.0.0-alpha.2.0.20210323153322-3d708859f0b5/go.mod h1:6WF7h4BVqUyAULCa3bCjHgFueeOPc2qIipVmtDbWkNY= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index c8f359f0c5b..f0a84cc895c 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -8,10 +8,11 @@ import ( "reflect" "testing" + "github.com/devfile/library/pkg/devfile/parser/data" + devfilev1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" "github.com/devfile/library/pkg/devfile/parser" devfileCtx "github.com/devfile/library/pkg/devfile/parser/context" - "github.com/devfile/library/pkg/testingutil" devfilefs "github.com/devfile/library/pkg/testingutil/filesystem" "github.com/kylelemons/godebug/pretty" odoTestingUtil "github.com/openshift/odo/pkg/testingutil" @@ -525,18 +526,12 @@ func TestSetDevfileConfiguration(t *testing.T) { currentDevfile: odoTestingUtil.GetTestDevfileObj(fs), wantDevFile: parser.DevfileObj{ Ctx: devfileCtx.FakeContext(fs, parser.OutputDevfileYamlPath), - Data: &testingutil.TestDevfileData{ - Commands: []devfilev1.Command{ - { - Id: "devbuild", - CommandUnion: devfilev1.CommandUnion{ - Exec: &devfilev1.ExecCommand{ - WorkingDir: "/projects/nodejs-starter", - }, - }, - }, - }, - Components: []devfilev1.Component{ + Data: func() data.DevfileData { + devfileData, err := data.NewDevfileData(string(data.APIVersion200)) + if err != nil { + t.Error(err) + } + err = devfileData.AddComponents([]devfilev1.Component{ { Name: "runtime", ComponentUnion: devfilev1.ComponentUnion{ @@ -565,8 +560,25 @@ func TestSetDevfileConfiguration(t *testing.T) { }, }, }, - }, - }, + }) + if err != nil { + t.Error(err) + } + err = devfileData.AddCommands([]devfilev1.Command{ + { + Id: "devbuild", + CommandUnion: devfilev1.CommandUnion{ + Exec: &devfilev1.ExecCommand{ + WorkingDir: "/projects/nodejs-starter", + }, + }, + }, + }) + if err != nil { + t.Error(err) + } + return devfileData + }(), }, }, { @@ -577,8 +589,12 @@ func TestSetDevfileConfiguration(t *testing.T) { currentDevfile: odoTestingUtil.GetTestDevfileObj(fs), wantDevFile: parser.DevfileObj{ Ctx: devfileCtx.FakeContext(fs, parser.OutputDevfileYamlPath), - Data: &testingutil.TestDevfileData{ - Commands: []devfilev1.Command{ + Data: func() data.DevfileData { + devfileData, err := data.NewDevfileData(string(data.APIVersion200)) + if err != nil { + t.Error(err) + } + err = devfileData.AddCommands([]devfilev1.Command{ { Id: "devbuild", CommandUnion: devfilev1.CommandUnion{ @@ -587,8 +603,11 @@ func TestSetDevfileConfiguration(t *testing.T) { }, }, }, - }, - Components: []devfilev1.Component{ + }) + if err != nil { + t.Error(err) + } + err = devfileData.AddComponents([]devfilev1.Component{ { Name: "runtime", ComponentUnion: devfilev1.ComponentUnion{ @@ -635,8 +654,12 @@ func TestSetDevfileConfiguration(t *testing.T) { }, }, }, - }, - }, + }) + if err != nil { + t.Error(err) + } + return devfileData + }(), }, }, { diff --git a/pkg/devfile/adapters/common/command_test.go b/pkg/devfile/adapters/common/command_test.go index 7a029be3c54..643683d458f 100644 --- a/pkg/devfile/adapters/common/command_test.go +++ b/pkg/devfile/adapters/common/command_test.go @@ -5,6 +5,8 @@ import ( "reflect" "testing" + "github.com/devfile/library/pkg/devfile/parser/data" + devfilev1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" devfileParser "github.com/devfile/library/pkg/devfile/parser" "github.com/devfile/library/pkg/testingutil" @@ -123,7 +125,7 @@ func TestGetCommand(t *testing.T) { }, }, }, - retCommandName: "defaultruncommand", + retCommandName: "defaultRunCommand", requestedType: []devfilev1.CommandGroupKind{runGroup}, wantErr: false, }, @@ -174,7 +176,7 @@ func TestGetCommand(t *testing.T) { }, }, }, - retCommandName: "mycomposite", + retCommandName: "myComposite", requestedType: []devfilev1.CommandGroupKind{buildGroup}, wantErr: false, }, @@ -183,10 +185,25 @@ func TestGetCommand(t *testing.T) { t.Run(tt.name, func(t *testing.T) { components := []devfilev1.Component{testingutil.GetFakeContainerComponent(tt.execCommands[0].Exec.Component)} devObj := devfileParser.DevfileObj{ - Data: &testingutil.TestDevfileData{ - Commands: append(tt.execCommands, tt.compCommands...), - Components: components, - }, + Data: func() data.DevfileData { + devfileData, err := data.NewDevfileData(string(data.APIVersion200)) + if err != nil { + t.Error(err) + } + err = devfileData.AddCommands(tt.execCommands) + if err != nil { + t.Error(err) + } + err = devfileData.AddCommands(tt.compCommands) + if err != nil { + t.Error(err) + } + err = devfileData.AddComponents(components) + if err != nil { + t.Error(err) + } + return devfileData + }(), } for _, gtype := range tt.requestedType { @@ -448,10 +465,25 @@ func TestGetCommandFromDevfile(t *testing.T) { t.Run(tt.name, func(t *testing.T) { components := []devfilev1.Component{testingutil.GetFakeContainerComponent(tt.execCommands[0].Exec.Component)} devObj := devfileParser.DevfileObj{ - Data: &testingutil.TestDevfileData{ - Commands: append(tt.execCommands, tt.compCommands...), - Components: components, - }, + Data: func() data.DevfileData { + devfileData, err := data.NewDevfileData(string(data.APIVersion200)) + if err != nil { + t.Error(err) + } + err = devfileData.AddCommands(tt.execCommands) + if err != nil { + t.Error(err) + } + err = devfileData.AddCommands(tt.compCommands) + if err != nil { + t.Error(err) + } + err = devfileData.AddComponents(components) + if err != nil { + t.Error(err) + } + return devfileData + }(), } for _, gtype := range tt.requestedType { @@ -722,58 +754,6 @@ func TestGetCommandFromFlag(t *testing.T) { requestedType: buildGroup, wantErr: false, }, - { - name: "Case 9: Valid devfile with invalid composite commands", - execCommands: []devfilev1.Command{ - { - Id: "build", - CommandUnion: devfilev1.CommandUnion{ - Exec: &devfilev1.ExecCommand{ - LabeledCommand: devfilev1.LabeledCommand{ - BaseCommand: devfilev1.BaseCommand{ - Group: &devfilev1.CommandGroup{Kind: buildGroup, IsDefault: false}, - }, - }, - CommandLine: commands[0], - Component: components[0], - }, - }, - }, - { - Id: "run", - CommandUnion: devfilev1.CommandUnion{ - Exec: &devfilev1.ExecCommand{ - LabeledCommand: devfilev1.LabeledCommand{ - BaseCommand: devfilev1.BaseCommand{ - Group: &devfilev1.CommandGroup{Kind: runGroup}, - }, - }, - CommandLine: commands[0], - Component: components[0], - }, - }, - }, - }, - compCommands: []devfilev1.Command{ - { - Id: "myComp", - CommandUnion: devfilev1.CommandUnion{ - Composite: &devfilev1.CompositeCommand{ - LabeledCommand: devfilev1.LabeledCommand{ - BaseCommand: devfilev1.BaseCommand{ - Group: &devfilev1.CommandGroup{Kind: buildGroup, IsDefault: true}, - }, - }, - Commands: []string{"fake"}, - }, - }, - }, - }, - reqCommandName: "myComp", - retCommandName: "myComp", - requestedType: buildGroup, - wantErr: true, - }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -782,10 +762,25 @@ func TestGetCommandFromFlag(t *testing.T) { components = []devfilev1.Component{testingutil.GetFakeContainerComponent("randomComponent")} } devObj := devfileParser.DevfileObj{ - Data: &testingutil.TestDevfileData{ - Commands: append(tt.compCommands, tt.execCommands...), - Components: components, - }, + Data: func() data.DevfileData { + devfileData, err := data.NewDevfileData(string(data.APIVersion200)) + if err != nil { + t.Error(err) + } + err = devfileData.AddCommands(tt.execCommands) + if err != nil { + t.Error(err) + } + err = devfileData.AddCommands(tt.compCommands) + if err != nil { + t.Error(err) + } + err = devfileData.AddComponents(components) + if err != nil { + t.Error(err) + } + return devfileData + }(), } cmd, err := getCommandFromFlag(devObj.Data, tt.requestedType, tt.reqCommandName) @@ -934,10 +929,21 @@ func TestGetBuildCommand(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { devObj := devfileParser.DevfileObj{ - Data: &testingutil.TestDevfileData{ - Commands: tt.execCommands, - Components: []devfilev1.Component{testingutil.GetFakeContainerComponent(component)}, - }, + Data: func() data.DevfileData { + devfileData, err := data.NewDevfileData(string(data.APIVersion200)) + if err != nil { + t.Error(err) + } + err = devfileData.AddCommands(tt.execCommands) + if err != nil { + t.Error(err) + } + err = devfileData.AddComponents([]devfilev1.Component{testingutil.GetFakeContainerComponent(component)}) + if err != nil { + t.Error(err) + } + return devfileData + }(), } command, err := GetBuildCommand(devObj.Data, tt.commandName) @@ -1046,10 +1052,21 @@ func TestGetDebugCommand(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { devObj := devfileParser.DevfileObj{ - Data: &testingutil.TestDevfileData{ - Components: []devfilev1.Component{testingutil.GetFakeContainerComponent(component)}, - Commands: tt.execCommands, - }, + Data: func() data.DevfileData { + devfileData, err := data.NewDevfileData(string(data.APIVersion200)) + if err != nil { + t.Error(err) + } + err = devfileData.AddComponents([]devfilev1.Component{testingutil.GetFakeContainerComponent(component)}) + if err != nil { + t.Error(err) + } + err = devfileData.AddCommands(tt.execCommands) + if err != nil { + t.Error(err) + } + return devfileData + }(), } command, err := GetDebugCommand(devObj.Data, tt.commandName) @@ -1160,10 +1177,21 @@ func TestGetTestCommand(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { devObj := devfileParser.DevfileObj{ - Data: &testingutil.TestDevfileData{ - Components: []devfilev1.Component{testingutil.GetFakeContainerComponent(component)}, - Commands: tt.execCommands, - }, + Data: func() data.DevfileData { + devfileData, err := data.NewDevfileData(string(data.APIVersion200)) + if err != nil { + t.Error(err) + } + err = devfileData.AddComponents([]devfilev1.Component{testingutil.GetFakeContainerComponent(component)}) + if err != nil { + t.Error(err) + } + err = devfileData.AddCommands(tt.execCommands) + if err != nil { + t.Error(err) + } + return devfileData + }(), } command, err := GetTestCommand(devObj.Data, tt.commandName) @@ -1280,10 +1308,21 @@ func TestGetRunCommand(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { devObj := devfileParser.DevfileObj{ - Data: &testingutil.TestDevfileData{ - Commands: tt.execCommands, - Components: []devfilev1.Component{testingutil.GetFakeContainerComponent(component)}, - }, + Data: func() data.DevfileData { + devfileData, err := data.NewDevfileData(string(data.APIVersion200)) + if err != nil { + t.Error(err) + } + err = devfileData.AddComponents([]devfilev1.Component{testingutil.GetFakeContainerComponent(component)}) + if err != nil { + t.Error(err) + } + err = devfileData.AddCommands(tt.execCommands) + if err != nil { + t.Error(err) + } + return devfileData + }(), } command, err := GetRunCommand(devObj.Data, tt.commandName) @@ -1372,10 +1411,21 @@ func TestValidateAndGetDebugDevfileCommands(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { devObj := devfileParser.DevfileObj{ - Data: &testingutil.TestDevfileData{ - Components: []devfilev1.Component{testingutil.GetFakeContainerComponent(component)}, - Commands: execCommands, - }, + Data: func() data.DevfileData { + devfileData, err := data.NewDevfileData(string(data.APIVersion200)) + if err != nil { + t.Error(err) + } + err = devfileData.AddComponents([]devfilev1.Component{testingutil.GetFakeContainerComponent(component)}) + if err != nil { + t.Error(err) + } + err = devfileData.AddCommands(execCommands) + if err != nil { + t.Error(err) + } + return devfileData + }(), } debugCommand, err := ValidateAndGetDebugDevfileCommands(devObj.Data, tt.debugCommand) @@ -1545,10 +1595,21 @@ func TestValidateAndGetPushDevfileCommands(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { devObj := devfileParser.DevfileObj{ - Data: &testingutil.TestDevfileData{ - Commands: tt.execCommands, - Components: []devfilev1.Component{testingutil.GetFakeContainerComponent(component)}, - }, + Data: func() data.DevfileData { + devfileData, err := data.NewDevfileData(string(data.APIVersion200)) + if err != nil { + t.Error(err) + } + err = devfileData.AddCommands(tt.execCommands) + if err != nil { + t.Error(err) + } + err = devfileData.AddComponents(([]devfilev1.Component{testingutil.GetFakeContainerComponent(component)})) + if err != nil { + t.Error(err) + } + return devfileData + }(), } pushCommands, err := ValidateAndGetPushDevfileCommands(devObj.Data, tt.buildCommand, tt.runCommand) @@ -1640,10 +1701,21 @@ func TestValidateAndGetTestDevfileCommands(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { devObj := devfileParser.DevfileObj{ - Data: &testingutil.TestDevfileData{ - Components: []devfilev1.Component{testingutil.GetFakeContainerComponent(component)}, - Commands: execCommands, - }, + Data: func() data.DevfileData { + devfileData, err := data.NewDevfileData(string(data.APIVersion200)) + if err != nil { + t.Error(err) + } + err = devfileData.AddComponents([]devfilev1.Component{testingutil.GetFakeContainerComponent(component)}) + if err != nil { + t.Error(err) + } + err = devfileData.AddCommands(execCommands) + if err != nil { + t.Error(err) + } + return devfileData + }(), } testCommand, err := ValidateAndGetTestDevfileCommands(devObj.Data, tt.testCommand) diff --git a/pkg/devfile/adapters/common/generic_test.go b/pkg/devfile/adapters/common/generic_test.go index e25819f41cb..655ed02bd93 100644 --- a/pkg/devfile/adapters/common/generic_test.go +++ b/pkg/devfile/adapters/common/generic_test.go @@ -5,9 +5,10 @@ import ( "io" "testing" + "github.com/devfile/library/pkg/devfile/parser/data" + devfilev1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" devfileParser "github.com/devfile/library/pkg/devfile/parser" - "github.com/devfile/library/pkg/testingutil" ) // Create a simple mock client for the ExecClient interface for the devfile exec unit tests. @@ -300,8 +301,11 @@ func TestExecuteDevfileCommand(t *testing.T) { } func adapter(fakeExecClient ExecClient, commands []devfilev1.Command, cif func(command devfilev1.Command) (ComponentInfo, error)) *GenericAdapter { - data := &testingutil.TestDevfileData{} - _ = data.AddCommands(commands...) + data := func() data.DevfileData { + devfileData, _ := data.NewDevfileData(string(data.APIVersion200)) + return devfileData + }() + _ = data.AddCommands(commands) devObj := devfileParser.DevfileObj{ Data: data, } diff --git a/pkg/devfile/adapters/common/utils_test.go b/pkg/devfile/adapters/common/utils_test.go index e638137b80c..94a31281763 100644 --- a/pkg/devfile/adapters/common/utils_test.go +++ b/pkg/devfile/adapters/common/utils_test.go @@ -5,6 +5,8 @@ import ( "reflect" "testing" + "github.com/devfile/library/pkg/devfile/parser/data" + devfilev1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" devfileParser "github.com/devfile/library/pkg/devfile/parser" parsercommon "github.com/devfile/library/pkg/devfile/parser/data/v2/common" @@ -140,9 +142,17 @@ func TestGetVolumes(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { devObj := devfileParser.DevfileObj{ - Data: &testingutil.TestDevfileData{ - Components: tt.component, - }, + Data: func() data.DevfileData { + devfileData, err := data.NewDevfileData(string(data.APIVersion200)) + if err != nil { + t.Error(err) + } + err = devfileData.AddComponents(tt.component) + if err != nil { + t.Error(err) + } + return devfileData + }(), } containerNameToVolumes, err := GetVolumes(devObj) @@ -361,10 +371,21 @@ func TestGetCommandsForGroup(t *testing.T) { } devObj := devfileParser.DevfileObj{ - Data: &testingutil.TestDevfileData{ - Components: component, - Commands: execCommands, - }, + Data: func() data.DevfileData { + devfileData, err := data.NewDevfileData(string(data.APIVersion200)) + if err != nil { + t.Error(err) + } + err = devfileData.AddComponents(component) + if err != nil { + t.Error(err) + } + err = devfileData.AddCommands(execCommands) + if err != nil { + t.Error(err) + } + return devfileData + }(), } tests := []struct { @@ -511,10 +532,25 @@ func TestGetCommands(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { devObj := devfileParser.DevfileObj{ - Data: &testingutil.TestDevfileData{ - Components: component, - Commands: append(tt.execCommands, tt.compCommands...), - }, + Data: func() data.DevfileData { + devfileData, err := data.NewDevfileData(string(data.APIVersion200)) + if err != nil { + t.Error(err) + } + err = devfileData.AddComponents(component) + if err != nil { + t.Error(err) + } + err = devfileData.AddCommands(tt.execCommands) + if err != nil { + t.Error(err) + } + err = devfileData.AddCommands(tt.compCommands) + if err != nil { + t.Error(err) + } + return devfileData + }(), } commands, err := devObj.Data.GetCommands(parsercommon.DevfileOptions{}) @@ -664,9 +700,21 @@ func TestGetCommandsFromEvent(t *testing.T) { t.Run(tt.name, func(t *testing.T) { devObj := devfileParser.DevfileObj{ - Data: &testingutil.TestDevfileData{ - Commands: append(compCommands, execCommands...), - }, + Data: func() data.DevfileData { + devfileData, err := data.NewDevfileData(string(data.APIVersion200)) + if err != nil { + t.Error(err) + } + err = devfileData.AddCommands(compCommands) + if err != nil { + t.Error(err) + } + err = devfileData.AddCommands(execCommands) + if err != nil { + t.Error(err) + } + return devfileData + }(), } devfileCommands, err := devObj.Data.GetCommands(parsercommon.DevfileOptions{}) diff --git a/pkg/devfile/adapters/docker/component/adapter_test.go b/pkg/devfile/adapters/docker/component/adapter_test.go index 5d5e02a545e..218c661aa3f 100644 --- a/pkg/devfile/adapters/docker/component/adapter_test.go +++ b/pkg/devfile/adapters/docker/component/adapter_test.go @@ -5,6 +5,8 @@ import ( "os" "testing" + "github.com/devfile/library/pkg/devfile/parser/data" + devfilev1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" devfileParser "github.com/devfile/library/pkg/devfile/parser" "github.com/devfile/library/pkg/testingutil" @@ -104,10 +106,21 @@ func TestPush(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { devObj := devfileParser.DevfileObj{ - Data: &testingutil.TestDevfileData{ - Components: tt.components, - Commands: execCommands, - }, + Data: func() data.DevfileData { + devfileData, err := data.NewDevfileData(string(data.APIVersion200)) + if err != nil { + t.Error(err) + } + err = devfileData.AddComponents(tt.components) + if err != nil { + t.Error(err) + } + err = devfileData.AddCommands(execCommands) + if err != nil { + t.Error(err) + } + return devfileData + }(), } adapterCtx := adaptersCommon.AdapterContext{ @@ -289,10 +302,21 @@ func TestDockerTest(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { devObj := devfileParser.DevfileObj{ - Data: &testingutil.TestDevfileData{ - Components: tt.components, - Commands: tt.execCommands, - }, + Data: func() data.DevfileData { + devfileData, err := data.NewDevfileData(string(data.APIVersion200)) + if err != nil { + t.Error(err) + } + err = devfileData.AddComponents(tt.components) + if err != nil { + t.Error(err) + } + err = devfileData.AddCommands(tt.execCommands) + if err != nil { + t.Error(err) + } + return devfileData + }(), } adapterCtx := adaptersCommon.AdapterContext{ @@ -378,9 +402,18 @@ func TestDoesComponentExist(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { devObj := devfileParser.DevfileObj{ - Data: &testingutil.TestDevfileData{ - Components: tt.components, - }, + Data: func() data.DevfileData { + devfileData, err := data.NewDevfileData(string(data.APIVersion200)) + if err != nil { + t.Error(err) + } + err = devfileData.AddComponents(tt.components) + if err != nil { + t.Error(err) + } + + return devfileData + }(), } adapterCtx := adaptersCommon.AdapterContext{ @@ -454,9 +487,17 @@ func TestAdapterDelete(t *testing.T) { defer ctrl.Finish() devObj := devfileParser.DevfileObj{ - Data: &testingutil.TestDevfileData{ - Components: []devfilev1.Component{}, - }, + Data: func() data.DevfileData { + devfileData, err := data.NewDevfileData(string(data.APIVersion200)) + if err != nil { + t.Error(err) + } + err = devfileData.AddComponents([]devfilev1.Component{}) + if err != nil { + t.Error(err) + } + return devfileData + }(), } adapterCtx := adaptersCommon.AdapterContext{ @@ -828,9 +869,18 @@ func TestAdapterDeleteVolumes(t *testing.T) { defer ctrl.Finish() devObj := devfileParser.DevfileObj{ - Data: &testingutil.TestDevfileData{ - Components: []devfilev1.Component{}, - }, + Data: func() data.DevfileData { + devfileData, err := data.NewDevfileData(string(data.APIVersion200)) + if err != nil { + t.Error(err) + } + err = devfileData.AddComponents([]devfilev1.Component{}) + if err != nil { + t.Error(err) + } + + return devfileData + }(), } adapterCtx := adaptersCommon.AdapterContext{ diff --git a/pkg/devfile/adapters/docker/component/utils_test.go b/pkg/devfile/adapters/docker/component/utils_test.go index 41949436fc8..41695af4304 100644 --- a/pkg/devfile/adapters/docker/component/utils_test.go +++ b/pkg/devfile/adapters/docker/component/utils_test.go @@ -4,9 +4,10 @@ import ( "strings" "testing" + "github.com/devfile/library/pkg/devfile/parser/data" + devfilev1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" devfileParser "github.com/devfile/library/pkg/devfile/parser" - "github.com/devfile/library/pkg/testingutil" adaptersCommon "github.com/openshift/odo/pkg/devfile/adapters/common" "github.com/openshift/odo/pkg/lclient" ) @@ -120,9 +121,17 @@ func TestExecDevfile(t *testing.T) { t.Run(tt.name, func(t *testing.T) { devObj := devfileParser.DevfileObj{ - Data: &testingutil.TestDevfileData{ - Components: []devfilev1.Component{}, - }, + Data: func() data.DevfileData { + devfileData, err := data.NewDevfileData(string(data.APIVersion200)) + if err != nil { + t.Error() + } + err = devfileData.AddComponents([]devfilev1.Component{}) + if err != nil { + t.Error() + } + return devfileData + }(), } adapterCtx := adaptersCommon.AdapterContext{ @@ -230,9 +239,17 @@ func TestExecTestCmd(t *testing.T) { t.Run(tt.name, func(t *testing.T) { devObj := devfileParser.DevfileObj{ - Data: &testingutil.TestDevfileData{ - Components: validComponents, - }, + Data: func() data.DevfileData { + devfileData, err := data.NewDevfileData(string(data.APIVersion200)) + if err != nil { + t.Error() + } + err = devfileData.AddComponents(validComponents) + if err != nil { + t.Error() + } + return devfileData + }(), } adapterCtx := adaptersCommon.AdapterContext{ @@ -293,9 +310,17 @@ func TestCreateProjectVolumeIfReqd(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { devObj := devfileParser.DevfileObj{ - Data: &testingutil.TestDevfileData{ - Components: []devfilev1.Component{}, - }, + Data: func() data.DevfileData { + devfileData, err := data.NewDevfileData(string(data.APIVersion200)) + if err != nil { + t.Error() + } + err = devfileData.AddComponents([]devfilev1.Component{}) + if err != nil { + t.Error() + } + return devfileData + }(), } adapterCtx := adaptersCommon.AdapterContext{ @@ -341,9 +366,17 @@ func TestStartBootstrapSupervisordInitContainer(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { devObj := devfileParser.DevfileObj{ - Data: &testingutil.TestDevfileData{ - Components: []devfilev1.Component{}, - }, + Data: func() data.DevfileData { + devfileData, err := data.NewDevfileData(string(data.APIVersion200)) + if err != nil { + t.Error() + } + err = devfileData.AddComponents([]devfilev1.Component{}) + if err != nil { + t.Error() + } + return devfileData + }(), } adapterCtx := adaptersCommon.AdapterContext{ @@ -387,9 +420,17 @@ func TestCreateAndInitSupervisordVolumeIfReqd(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { devObj := devfileParser.DevfileObj{ - Data: &testingutil.TestDevfileData{ - Components: []devfilev1.Component{}, - }, + Data: func() data.DevfileData { + devfileData, err := data.NewDevfileData(string(data.APIVersion200)) + if err != nil { + t.Error() + } + err = devfileData.AddComponents([]devfilev1.Component{}) + if err != nil { + t.Error() + } + return devfileData + }(), } adapterCtx := adaptersCommon.AdapterContext{ diff --git a/pkg/devfile/adapters/docker/utils/utils_test.go b/pkg/devfile/adapters/docker/utils/utils_test.go index 685e078c52b..28db9e72902 100644 --- a/pkg/devfile/adapters/docker/utils/utils_test.go +++ b/pkg/devfile/adapters/docker/utils/utils_test.go @@ -5,6 +5,8 @@ import ( "reflect" "testing" + "github.com/devfile/library/pkg/devfile/parser/data" + "github.com/docker/go-connections/nat" devfilev1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" @@ -88,9 +90,17 @@ func TestComponentExists(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { devObj := devfileParser.DevfileObj{ - Data: &testingutil.TestDevfileData{ - Components: tt.components, - }, + Data: func() data.DevfileData { + devfileData, err := data.NewDevfileData(string(data.APIVersion200)) + if err != nil { + t.Error() + } + err = devfileData.AddComponents(tt.components) + if err != nil { + t.Error() + } + return devfileData + }(), } cmpExists, err := ComponentExists(*tt.client, devObj.Data, tt.componentName) if !tt.wantErr && err != nil { diff --git a/pkg/devfile/adapters/helper_test.go b/pkg/devfile/adapters/helper_test.go index 8985eb40569..107edbd9016 100644 --- a/pkg/devfile/adapters/helper_test.go +++ b/pkg/devfile/adapters/helper_test.go @@ -4,9 +4,10 @@ import ( "reflect" "testing" + "github.com/devfile/library/pkg/devfile/parser/data" + devfilev1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" devfileParser "github.com/devfile/library/pkg/devfile/parser" - "github.com/devfile/library/pkg/testingutil" adaptersCommon "github.com/openshift/odo/pkg/devfile/adapters/common" "github.com/openshift/odo/pkg/occlient" ) @@ -30,9 +31,17 @@ func TestNewPlatformAdapter(t *testing.T) { for _, tt := range tests { t.Run("get platform adapter", func(t *testing.T) { devObj := devfileParser.DevfileObj{ - Data: &testingutil.TestDevfileData{ - Components: []devfilev1.Component{}, - }, + Data: func() data.DevfileData { + devfileData, err := data.NewDevfileData(string(data.APIVersion200)) + if err != nil { + t.Error(err) + } + err = devfileData.AddComponents([]devfilev1.Component{}) + if err != nil { + t.Error(err) + } + return devfileData + }(), } adapterContext := adaptersCommon.AdapterContext{ diff --git a/pkg/devfile/adapters/kubernetes/component/adapter_test.go b/pkg/devfile/adapters/kubernetes/component/adapter_test.go index 823ecc5c73f..4f00d37e42b 100644 --- a/pkg/devfile/adapters/kubernetes/component/adapter_test.go +++ b/pkg/devfile/adapters/kubernetes/component/adapter_test.go @@ -3,6 +3,8 @@ package component import ( "testing" + "github.com/devfile/library/pkg/devfile/parser/data" + "github.com/devfile/library/pkg/devfile/generator" "github.com/openshift/odo/pkg/envinfo" "github.com/openshift/odo/pkg/util" @@ -82,10 +84,21 @@ func TestCreateOrUpdateComponent(t *testing.T) { comp = testingutil.GetFakeContainerComponent("component") } devObj := devfileParser.DevfileObj{ - Data: &testingutil.TestDevfileData{ - Components: []devfilev1.Component{comp}, - Commands: []devfilev1.Command{getExecCommand("run", devfilev1.RunCommandGroupKind)}, - }, + Data: func() data.DevfileData { + devfileData, err := data.NewDevfileData(string(data.APIVersion200)) + if err != nil { + t.Error(err) + } + err = devfileData.AddComponents([]devfilev1.Component{comp}) + if err != nil { + t.Error(err) + } + err = devfileData.AddCommands([]devfilev1.Command{getExecCommand("run", devfilev1.RunCommandGroupKind)}) + if err != nil { + t.Error(err) + } + return devfileData + }(), } adapterCtx := adaptersCommon.AdapterContext{ @@ -260,10 +273,21 @@ func TestDoesComponentExist(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { devObj := devfileParser.DevfileObj{ - Data: &testingutil.TestDevfileData{ - Components: []devfilev1.Component{testingutil.GetFakeContainerComponent("component")}, - Commands: []devfilev1.Command{getExecCommand("run", devfilev1.RunCommandGroupKind)}, - }, + Data: func() data.DevfileData { + devfileData, err := data.NewDevfileData(string(data.APIVersion200)) + if err != nil { + t.Error(err) + } + err = devfileData.AddComponents([]devfilev1.Component{testingutil.GetFakeContainerComponent("component")}) + if err != nil { + t.Error(err) + } + err = devfileData.AddCommands([]devfilev1.Command{getExecCommand("run", devfilev1.RunCommandGroupKind)}) + if err != nil { + t.Error(err) + } + return devfileData + }(), } adapterCtx := adaptersCommon.AdapterContext{ @@ -342,9 +366,17 @@ func TestWaitAndGetComponentPod(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { devObj := devfileParser.DevfileObj{ - Data: &testingutil.TestDevfileData{ - Components: []devfilev1.Component{testingutil.GetFakeContainerComponent("component")}, - }, + Data: func() data.DevfileData { + devfileData, err := data.NewDevfileData(string(data.APIVersion200)) + if err != nil { + t.Error(err) + } + err = devfileData.AddComponents([]devfilev1.Component{testingutil.GetFakeContainerComponent("component")}) + if err != nil { + t.Error(err) + } + return devfileData + }(), } adapterCtx := adaptersCommon.AdapterContext{ @@ -465,9 +497,13 @@ func TestAdapterDelete(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { devObj := devfileParser.DevfileObj{ - Data: &testingutil.TestDevfileData{ - // ComponentType: "nodejs", - }, + Data: func() data.DevfileData { + devfileData, err := data.NewDevfileData(string(data.APIVersion200)) + if err != nil { + t.Error(err) + } + return devfileData + }(), } adapterCtx := adaptersCommon.AdapterContext{ diff --git a/pkg/devfile/adapters/kubernetes/component/podwatcher_test.go b/pkg/devfile/adapters/kubernetes/component/podwatcher_test.go index 00626079ee6..8675e5099d4 100644 --- a/pkg/devfile/adapters/kubernetes/component/podwatcher_test.go +++ b/pkg/devfile/adapters/kubernetes/component/podwatcher_test.go @@ -3,12 +3,12 @@ package component import ( "encoding/json" "fmt" + "github.com/devfile/library/pkg/devfile/parser/data" "sync" "testing" "time" devfileParser "github.com/devfile/library/pkg/devfile/parser" - "github.com/devfile/library/pkg/testingutil" adaptersCommon "github.com/openshift/odo/pkg/devfile/adapters/common" "github.com/openshift/odo/pkg/machineoutput" "github.com/openshift/odo/pkg/occlient" @@ -277,7 +277,13 @@ func TestStatusReconciler(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { devObj := devfileParser.DevfileObj{ - Data: &testingutil.TestDevfileData{}, + Data: func() data.DevfileData { + devfileData, err := data.NewDevfileData(string(data.APIVersion200)) + if err != nil { + t.Error(err) + } + return devfileData + }(), } adapterCtx := adaptersCommon.AdapterContext{ diff --git a/pkg/devfile/adapters/kubernetes/component/status_test.go b/pkg/devfile/adapters/kubernetes/component/status_test.go index def10f2d962..98350de31f0 100644 --- a/pkg/devfile/adapters/kubernetes/component/status_test.go +++ b/pkg/devfile/adapters/kubernetes/component/status_test.go @@ -3,6 +3,8 @@ package component import ( "testing" + "github.com/devfile/library/pkg/devfile/parser/data" + "github.com/openshift/odo/pkg/envinfo" devfilev1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" @@ -163,12 +165,21 @@ func TestGetDeploymentStatus(t *testing.T) { comp := testingutil.GetFakeContainerComponent(testComponentName) devObj := devfileParser.DevfileObj{ - Data: &testingutil.TestDevfileData{ - Components: []devfilev1.Component{comp}, - Commands: []devfilev1.Command{ - getExecCommand("run", devfilev1.RunCommandGroupKind), - }, - }, + Data: func() data.DevfileData { + devfileData, err := data.NewDevfileData(string(data.APIVersion200)) + if err != nil { + t.Error(err) + } + err = devfileData.AddComponents([]devfilev1.Component{comp}) + if err != nil { + t.Error(err) + } + err = devfileData.AddCommands([]devfilev1.Command{getExecCommand("run", devfilev1.RunCommandGroupKind)}) + if err != nil { + t.Error(err) + } + return devfileData + }(), } adapterCtx := adaptersCommon.AdapterContext{ diff --git a/pkg/devfile/adapters/kubernetes/utils/utils_test.go b/pkg/devfile/adapters/kubernetes/utils/utils_test.go index 27d5f5a0e59..65e558daf88 100644 --- a/pkg/devfile/adapters/kubernetes/utils/utils_test.go +++ b/pkg/devfile/adapters/kubernetes/utils/utils_test.go @@ -1,12 +1,14 @@ package utils import ( - "github.com/openshift/odo/pkg/storage" "reflect" "strconv" "strings" "testing" + "github.com/devfile/library/pkg/devfile/parser/data" + "github.com/openshift/odo/pkg/storage" + devfilev1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" devfileParser "github.com/devfile/library/pkg/devfile/parser" "github.com/devfile/library/pkg/testingutil" @@ -726,8 +728,12 @@ func TestUpdateContainersWithSupervisord(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { devObj := devfileParser.DevfileObj{ - Data: &testingutil.TestDevfileData{ - Components: []devfilev1.Component{ + Data: func() data.DevfileData { + devfileData, err := data.NewDevfileData(string(data.APIVersion200)) + if err != nil { + t.Error(err) + } + err = devfileData.AddComponents([]devfilev1.Component{ { Name: component, ComponentUnion: devfilev1.ComponentUnion{ @@ -748,9 +754,16 @@ func TestUpdateContainersWithSupervisord(t *testing.T) { }, }, }, - }, - Commands: tt.execCommands, - }, + }) + if err != nil { + t.Error(err) + } + err = devfileData.AddCommands(tt.execCommands) + if err != nil { + t.Error(err) + } + return devfileData + }(), } containers, err := UpdateContainersWithSupervisord(devObj, tt.containers, tt.runCommand, tt.debugCommand, tt.debugPort) @@ -947,14 +960,29 @@ func TestGetPreStartInitContainers(t *testing.T) { } devObj := devfileParser.DevfileObj{ - Data: &testingutil.TestDevfileData{ - Commands: append(execCommands, compCommands...), - Events: devfilev1.Events{ + Data: func() data.DevfileData { + devfileData, err := data.NewDevfileData(string(data.APIVersion200)) + if err != nil { + t.Error(err) + } + err = devfileData.AddCommands(execCommands) + if err != nil { + t.Error(err) + } + err = devfileData.AddCommands(compCommands) + if err != nil { + t.Error(err) + } + err = devfileData.AddEvents(devfilev1.Events{ WorkspaceEvents: devfilev1.WorkspaceEvents{ PreStart: tt.eventCommands, }, - }, - }, + }) + if err != nil { + t.Error(err) + } + return devfileData + }(), } initContainers, err := GetPreStartInitContainers(devObj, containers) diff --git a/pkg/devfile/convert/convert.go b/pkg/devfile/convert/convert.go index e1d71aef5df..288bb1a47cd 100644 --- a/pkg/devfile/convert/convert.go +++ b/pkg/devfile/convert/convert.go @@ -14,6 +14,7 @@ import ( imagev1 "github.com/openshift/api/image/v1" devfilev1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" + devfilepkg "github.com/devfile/api/v2/pkg/devfile" devfileCtx "github.com/devfile/library/pkg/devfile/parser/context" ) @@ -65,7 +66,10 @@ func GenerateDevfileYaml(client *occlient.Client, co *config.LocalConfigInfo, co s2iDevfile.SetSchemaVersion(devfileVersion) // set metadata - s2iDevfile.SetMetadata(co.GetName(), "1.0.0") + s2iDevfile.SetMetadata(devfilepkg.DevfileMetadata{ + Name: co.GetName(), + Version: "1.0.0", + }) // set commponents err = setDevfileComponentsForS2I(s2iDevfile, imageforDevfile, co, s2iEnv) if err != nil { @@ -191,7 +195,7 @@ func setDevfileCommandsForS2I(d data.DevfileData) { }, } // Ignoring error as we are writing new file - _ = d.AddCommands(buildCommand, runCommand) + _ = d.AddCommands([]devfilev1.Command{buildCommand, runCommand}) } diff --git a/pkg/envinfo/envinfo.go b/pkg/envinfo/envinfo.go index 28380f37985..6015c0156c8 100644 --- a/pkg/envinfo/envinfo.go +++ b/pkg/envinfo/envinfo.go @@ -419,6 +419,11 @@ func (ei *EnvInfo) SetDevfileObj(devfileObj parser.DevfileObj) { ei.devfileObj = devfileObj } +// GetDevfileObj returns devfileObj of the envinfo +func (ei *EnvInfo) GetDevfileObj() parser.DevfileObj { + return ei.devfileObj +} + // GetLink returns the EnvInfoLink, returns default if nil func (ei *EnvInfo) GetLink() []EnvInfoLink { if ei.componentSettings.Link == nil { diff --git a/pkg/envinfo/envinfo_test.go b/pkg/envinfo/envinfo_test.go index d947335759e..74ae47c135e 100644 --- a/pkg/envinfo/envinfo_test.go +++ b/pkg/envinfo/envinfo_test.go @@ -8,11 +8,12 @@ import ( "reflect" "testing" + "github.com/devfile/library/pkg/devfile/parser/data" + devfilev1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" "github.com/devfile/library/pkg/devfile/parser" devfileCtx "github.com/devfile/library/pkg/devfile/parser/context" "github.com/devfile/library/pkg/devfile/parser/data/v2/common" - "github.com/devfile/library/pkg/testingutil" "github.com/openshift/odo/pkg/localConfigProvider" devfileFileSystem "github.com/devfile/library/pkg/testingutil/filesystem" @@ -178,8 +179,12 @@ func TestDeleteURLFromMultipleURLs(t *testing.T) { }, existingDevfile: parser.DevfileObj{ Ctx: devfileCtx.FakeContext(fs, parser.OutputDevfileYamlPath), - Data: &testingutil.TestDevfileData{ - Components: []devfilev1.Component{ + Data: func() data.DevfileData { + devfileData, err := data.NewDevfileData(string(data.APIVersion200)) + if err != nil { + t.Error(err) + } + err = devfileData.AddComponents([]devfilev1.Component{ { Name: "runtime", ComponentUnion: devfilev1.ComponentUnion{ @@ -197,8 +202,12 @@ func TestDeleteURLFromMultipleURLs(t *testing.T) { }, }, }, - }, - }, + }) + if err != nil { + t.Error(err) + } + return devfileData + }(), }, deleteParam: testURL1.Name, remainingParam: testURL2.Name, @@ -213,8 +222,12 @@ func TestDeleteURLFromMultipleURLs(t *testing.T) { }, existingDevfile: parser.DevfileObj{ Ctx: devfileCtx.FakeContext(fs, parser.OutputDevfileYamlPath), - Data: &testingutil.TestDevfileData{ - Components: []devfilev1.Component{ + Data: func() data.DevfileData { + devfileData, err := data.NewDevfileData(string(data.APIVersion200)) + if err != nil { + t.Error(err) + } + err = devfileData.AddComponents([]devfilev1.Component{ { Name: "runtime", ComponentUnion: devfilev1.ComponentUnion{ @@ -228,8 +241,12 @@ func TestDeleteURLFromMultipleURLs(t *testing.T) { }, }, }, - }, - }, + }) + if err != nil { + t.Error(err) + } + return devfileData + }(), }, deleteParam: testURL1.Name, singleURL: true, @@ -382,8 +399,12 @@ func TestAddEndpointInDevfile(t *testing.T) { container: "testcontainer1", devObj: parser.DevfileObj{ Ctx: devfileCtx.FakeContext(fs, parser.OutputDevfileYamlPath), - Data: &testingutil.TestDevfileData{ - Components: []devfilev1.Component{ + Data: func() data.DevfileData { + devfileData, err := data.NewDevfileData(string(data.APIVersion200)) + if err != nil { + t.Error(err) + } + err = devfileData.AddComponents([]devfilev1.Component{ { Name: "testcontainer1", ComponentUnion: devfilev1.ComponentUnion{ @@ -400,8 +421,12 @@ func TestAddEndpointInDevfile(t *testing.T) { }, }, }, - }, - }, + }) + if err != nil { + t.Error(err) + } + return devfileData + }(), }, wantComponents: []devfilev1.Component{ { @@ -437,8 +462,12 @@ func TestAddEndpointInDevfile(t *testing.T) { container: "testcontainer1", devObj: parser.DevfileObj{ Ctx: devfileCtx.FakeContext(fs, parser.OutputDevfileYamlPath), - Data: &testingutil.TestDevfileData{ - Components: []devfilev1.Component{ + Data: func() data.DevfileData { + devfileData, err := data.NewDevfileData(string(data.APIVersion200)) + if err != nil { + t.Error(err) + } + err = devfileData.AddComponents([]devfilev1.Component{ { Name: "testcontainer1", ComponentUnion: devfilev1.ComponentUnion{ @@ -449,8 +478,12 @@ func TestAddEndpointInDevfile(t *testing.T) { }, }, }, - }, - }, + }) + if err != nil { + t.Error(err) + } + return devfileData + }(), }, wantComponents: []devfilev1.Component{ { @@ -482,8 +515,12 @@ func TestAddEndpointInDevfile(t *testing.T) { container: "testcontainer1", devObj: parser.DevfileObj{ Ctx: devfileCtx.FakeContext(fs, parser.OutputDevfileYamlPath), - Data: &testingutil.TestDevfileData{ - Components: []devfilev1.Component{ + Data: func() data.DevfileData { + devfileData, err := data.NewDevfileData(string(data.APIVersion200)) + if err != nil { + t.Error(err) + } + err = devfileData.AddComponents([]devfilev1.Component{ { Name: "testcontainer1", ComponentUnion: devfilev1.ComponentUnion{ @@ -511,8 +548,12 @@ func TestAddEndpointInDevfile(t *testing.T) { }, }, }, - }, - }, + }) + if err != nil { + t.Error(err) + } + return devfileData + }(), }, wantComponents: []devfilev1.Component{ { @@ -587,8 +628,12 @@ func TestRemoveEndpointInDevfile(t *testing.T) { urlName: urlName, devObj: parser.DevfileObj{ Ctx: devfileCtx.FakeContext(fs, parser.OutputDevfileYamlPath), - Data: &testingutil.TestDevfileData{ - Components: []devfilev1.Component{ + Data: func() data.DevfileData { + devfileData, err := data.NewDevfileData(string(data.APIVersion200)) + if err != nil { + t.Error(err) + } + err = devfileData.AddComponents([]devfilev1.Component{ { Name: "testcontainer1", ComponentUnion: devfilev1.ComponentUnion{ @@ -610,8 +655,12 @@ func TestRemoveEndpointInDevfile(t *testing.T) { }, }, }, - }, - }, + }) + if err != nil { + t.Error(err) + } + return devfileData + }(), }, wantComponents: []devfilev1.Component{ { @@ -638,8 +687,12 @@ func TestRemoveEndpointInDevfile(t *testing.T) { urlName: urlName, devObj: parser.DevfileObj{ Ctx: devfileCtx.FakeContext(fs, parser.OutputDevfileYamlPath), - Data: &testingutil.TestDevfileData{ - Components: []devfilev1.Component{ + Data: func() data.DevfileData { + devfileData, err := data.NewDevfileData(string(data.APIVersion200)) + if err != nil { + t.Error(err) + } + err = devfileData.AddComponents([]devfilev1.Component{ { Name: "testcontainer1", ComponentUnion: devfilev1.ComponentUnion{ @@ -657,8 +710,12 @@ func TestRemoveEndpointInDevfile(t *testing.T) { }, }, }, - }, - }, + }) + if err != nil { + t.Error(err) + } + return devfileData + }(), }, wantComponents: []devfilev1.Component{ { @@ -680,8 +737,12 @@ func TestRemoveEndpointInDevfile(t *testing.T) { urlName: urlName, devObj: parser.DevfileObj{ Ctx: devfileCtx.FakeContext(fs, parser.OutputDevfileYamlPath), - Data: &testingutil.TestDevfileData{ - Components: []devfilev1.Component{ + Data: func() data.DevfileData { + devfileData, err := data.NewDevfileData(string(data.APIVersion200)) + if err != nil { + t.Error(err) + } + err = devfileData.AddComponents([]devfilev1.Component{ { Name: "testcontainer1", ComponentUnion: devfilev1.ComponentUnion{ @@ -716,8 +777,12 @@ func TestRemoveEndpointInDevfile(t *testing.T) { }, }, }, - }, - }, + }) + if err != nil { + t.Error(err) + } + return devfileData + }(), }, wantComponents: []devfilev1.Component{ { @@ -756,8 +821,12 @@ func TestRemoveEndpointInDevfile(t *testing.T) { urlName: "invalidurl", devObj: parser.DevfileObj{ Ctx: devfileCtx.FakeContext(fs, parser.OutputDevfileYamlPath), - Data: &testingutil.TestDevfileData{ - Components: []devfilev1.Component{ + Data: func() data.DevfileData { + devfileData, err := data.NewDevfileData(string(data.APIVersion200)) + if err != nil { + t.Error(err) + } + err = devfileData.AddComponents([]devfilev1.Component{ { Name: "testcontainer1", ComponentUnion: devfilev1.ComponentUnion{ @@ -775,8 +844,12 @@ func TestRemoveEndpointInDevfile(t *testing.T) { }, }, }, - }, - }, + }) + if err != nil { + t.Error(err) + } + return devfileData + }(), }, wantComponents: []devfilev1.Component{ { diff --git a/pkg/envinfo/storage.go b/pkg/envinfo/storage.go index 7ee43ba9874..c511b0e1531 100644 --- a/pkg/envinfo/storage.go +++ b/pkg/envinfo/storage.go @@ -2,6 +2,7 @@ package envinfo import ( "fmt" + "github.com/devfile/library/pkg/devfile/parser/data/v2/common" devfilev1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" @@ -55,7 +56,26 @@ func (ei *EnvInfo) GetStorage(name string) (*localConfigProvider.LocalStorage, e // CreateStorage sets the storage related information in the local configuration func (ei *EnvInfo) CreateStorage(storage localConfigProvider.LocalStorage) error { - err := ei.devfileObj.Data.AddVolume(devfilev1.Component{ + // Get all the containers in the devfile + containers, err := ei.GetContainers() + if err != nil { + return err + } + + // Add volumeMount to all containers in the devfile + for _, c := range containers { + if err := ei.devfileObj.Data.AddVolumeMounts(c.Name, []devfilev1.VolumeMount{ + { + Name: storage.Name, + Path: storage.Path, + }, + }); err != nil { + return err + } + } + + // Add volume component to devfile. Think along the lines of a k8s pod spec's volumeMount and volume. + err = ei.devfileObj.Data.AddComponents([]devfilev1.Component{{ Name: storage.Name, ComponentUnion: devfilev1.ComponentUnion{ Volume: &devfilev1.VolumeComponent{ @@ -64,11 +84,11 @@ func (ei *EnvInfo) CreateStorage(storage localConfigProvider.LocalStorage) error }, }, }, - }, storage.Path) - + }}) if err != nil { return err } + err = ei.devfileObj.WriteYamlDevfile() if err != nil { return err @@ -119,10 +139,15 @@ func (ei *EnvInfo) ListStorage() ([]localConfigProvider.LocalStorage, error) { // DeleteStorage deletes the storage with the given name func (ei *EnvInfo) DeleteStorage(name string) error { - err := ei.devfileObj.Data.DeleteVolume(name) + err := ei.devfileObj.Data.DeleteVolumeMount(name) if err != nil { return err } + err = ei.devfileObj.Data.DeleteComponent(name) + if err != nil { + return err + } + err = ei.devfileObj.WriteYamlDevfile() if err != nil { return err @@ -133,7 +158,29 @@ func (ei *EnvInfo) DeleteStorage(name string) error { // GetStorageMountPath gets the mount path of the storage with the given storage name func (ei *EnvInfo) GetStorageMountPath(storageName string) (string, error) { - return ei.devfileObj.Data.GetVolumeMountPath(storageName) + containers, err := ei.GetContainers() + if err != nil { + return "", err + } + if len(containers) == 0 { + return "", fmt.Errorf("invalid devfile: components.container: required value") + } + + // since all container components have same volume mounts, we simply refer to the first container in the list + // refer https://github.com/openshift/odo/issues/4105 for addressing "all containers have same volume mounts" + paths, err := ei.devfileObj.Data.GetVolumeMountPaths(storageName, containers[0].Name) + if err != nil { + return "", err + } + + // TODO: Below "if" condition needs to go away when https://github.com/openshift/odo/issues/4105 is addressed. + if len(paths) > 0 { + return paths[0], nil + } + // Sending empty string will lead to bad UX as user will be shown an empty value for the mount path + // that's supposed to be deleted through "odo storage delete" command. + // This and the above "if" condition need to go away when we address https://github.com/openshift/odo/issues/4105 + return "", nil } // GetVolumeMountPath gets the volume mount's path diff --git a/pkg/envinfo/storage_test.go b/pkg/envinfo/storage_test.go index 85506197db8..9a2ee937b4e 100644 --- a/pkg/envinfo/storage_test.go +++ b/pkg/envinfo/storage_test.go @@ -4,6 +4,8 @@ import ( "reflect" "testing" + "github.com/devfile/library/pkg/devfile/parser/data" + devfilev1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" "github.com/devfile/library/pkg/devfile/parser" "github.com/devfile/library/pkg/testingutil" @@ -124,8 +126,12 @@ func TestEnvInfo_ListStorage(t *testing.T) { name: "case 1: list all the volumes in the devfile along with their respective size and containers", fields: fields{ devfileObj: parser.DevfileObj{ - Data: &testingutil.TestDevfileData{ - Components: []devfilev1.Component{ + Data: func() data.DevfileData { + devfileData, err := data.NewDevfileData(string(data.APIVersion200)) + if err != nil { + t.Error(err) + } + err = devfileData.AddComponents([]devfilev1.Component{ { Name: "container-0", ComponentUnion: devfilev1.ComponentUnion{ @@ -162,8 +168,12 @@ func TestEnvInfo_ListStorage(t *testing.T) { }, testingutil.GetFakeVolumeComponent("volume-0", "5Gi"), testingutil.GetFakeVolumeComponent("volume-1", "10Gi"), - }, - }, + }) + if err != nil { + t.Error(err) + } + return devfileData + }(), }, }, want: []localConfigProvider.LocalStorage{ @@ -191,8 +201,12 @@ func TestEnvInfo_ListStorage(t *testing.T) { name: "case 2: list all the volumes in the devfile with the default size when no size is mentioned", fields: fields{ devfileObj: parser.DevfileObj{ - Data: &testingutil.TestDevfileData{ - Components: []devfilev1.Component{ + Data: func() data.DevfileData { + devfileData, err := data.NewDevfileData(string(data.APIVersion200)) + if err != nil { + t.Error(err) + } + err = devfileData.AddComponents([]devfilev1.Component{ { Name: "container-0", ComponentUnion: devfilev1.ComponentUnion{ @@ -214,8 +228,12 @@ func TestEnvInfo_ListStorage(t *testing.T) { }, testingutil.GetFakeVolumeComponent("volume-0", ""), testingutil.GetFakeVolumeComponent("volume-1", "10Gi"), - }, - }, + }) + if err != nil { + t.Error(err) + } + return devfileData + }(), }, }, want: []localConfigProvider.LocalStorage{ @@ -237,8 +255,12 @@ func TestEnvInfo_ListStorage(t *testing.T) { name: "case 3: list all the volumes in the devfile with the default mount path when no path is mentioned", fields: fields{ devfileObj: parser.DevfileObj{ - Data: &testingutil.TestDevfileData{ - Components: []devfilev1.Component{ + Data: func() data.DevfileData { + devfileData, err := data.NewDevfileData(string(data.APIVersion200)) + if err != nil { + t.Error(err) + } + err = devfileData.AddComponents([]devfilev1.Component{ { Name: "container-0", ComponentUnion: devfilev1.ComponentUnion{ @@ -254,8 +276,12 @@ func TestEnvInfo_ListStorage(t *testing.T) { }, }, testingutil.GetFakeVolumeComponent("volume-0", ""), - }, - }, + }) + if err != nil { + t.Error(err) + } + return devfileData + }(), }, }, want: []localConfigProvider.LocalStorage{ @@ -271,8 +297,12 @@ func TestEnvInfo_ListStorage(t *testing.T) { name: "case 4: return empty when no volumes is mounted", fields: fields{ devfileObj: parser.DevfileObj{ - Data: &testingutil.TestDevfileData{ - Components: []devfilev1.Component{ + Data: func() data.DevfileData { + devfileData, err := data.NewDevfileData(string(data.APIVersion200)) + if err != nil { + t.Error(err) + } + err = devfileData.AddComponents([]devfilev1.Component{ { Name: "container-0", ComponentUnion: devfilev1.ComponentUnion{ @@ -283,8 +313,12 @@ func TestEnvInfo_ListStorage(t *testing.T) { }, testingutil.GetFakeVolumeComponent("volume-0", ""), testingutil.GetFakeVolumeComponent("volume-1", "10Gi"), - }, - }, + }) + if err != nil { + t.Error(err) + } + return devfileData + }(), }, }, want: nil, @@ -323,8 +357,12 @@ func TestEnvInfo_ValidateStorage(t *testing.T) { name: "case 1: storage with the same name doesn't exist", fields: fields{ devfileObj: parser.DevfileObj{ - Data: &testingutil.TestDevfileData{ - Components: []devfilev1.Component{ + Data: func() data.DevfileData { + devfileData, err := data.NewDevfileData(string(data.APIVersion200)) + if err != nil { + t.Error(err) + } + err = devfileData.AddComponents([]devfilev1.Component{ { Name: "container-0", ComponentUnion: devfilev1.ComponentUnion{ @@ -333,8 +371,12 @@ func TestEnvInfo_ValidateStorage(t *testing.T) { }, }, }, - }, - }, + }) + if err != nil { + t.Error(err) + } + return devfileData + }(), }, }, args: args{ @@ -349,8 +391,12 @@ func TestEnvInfo_ValidateStorage(t *testing.T) { name: "case 2: storage with same name exists", fields: fields{ devfileObj: parser.DevfileObj{ - Data: &testingutil.TestDevfileData{ - Components: []devfilev1.Component{ + Data: func() data.DevfileData { + devfileData, err := data.NewDevfileData(string(data.APIVersion200)) + if err != nil { + t.Error(err) + } + err = devfileData.AddComponents([]devfilev1.Component{ { Name: "container-0", ComponentUnion: devfilev1.ComponentUnion{ @@ -386,8 +432,12 @@ func TestEnvInfo_ValidateStorage(t *testing.T) { }, }, testingutil.GetFakeVolumeComponent("volume-0", "5Gi"), - }, - }, + }) + if err != nil { + t.Error(err) + } + return devfileData + }(), }, }, args: args{ @@ -428,8 +478,12 @@ func TestEnvInfo_GetStorage(t *testing.T) { name: "case 1: storage with the given name doesn't exist", fields: fields{ devfileObj: parser.DevfileObj{ - Data: &testingutil.TestDevfileData{ - Components: []devfilev1.Component{ + Data: func() data.DevfileData { + devfileData, err := data.NewDevfileData(string(data.APIVersion200)) + if err != nil { + t.Error(err) + } + err = devfileData.AddComponents([]devfilev1.Component{ { Name: "container-0", ComponentUnion: devfilev1.ComponentUnion{ @@ -438,8 +492,12 @@ func TestEnvInfo_GetStorage(t *testing.T) { }, }, }, - }, - }, + }) + if err != nil { + t.Error(err) + } + return devfileData + }(), }, }, args: args{ @@ -451,8 +509,12 @@ func TestEnvInfo_GetStorage(t *testing.T) { name: "case 2: storage with the given name exists", fields: fields{ devfileObj: parser.DevfileObj{ - Data: &testingutil.TestDevfileData{ - Components: []devfilev1.Component{ + Data: func() data.DevfileData { + devfileData, err := data.NewDevfileData(string(data.APIVersion200)) + if err != nil { + t.Error(err) + } + err = devfileData.AddComponents([]devfilev1.Component{ { Name: "container-0", ComponentUnion: devfilev1.ComponentUnion{ @@ -489,8 +551,12 @@ func TestEnvInfo_GetStorage(t *testing.T) { }, testingutil.GetFakeVolumeComponent("volume-0", "5Gi"), testingutil.GetFakeVolumeComponent("volume-1", "10Gi"), - }, - }, + }) + if err != nil { + t.Error(err) + } + return devfileData + }(), }, }, args: args{ diff --git a/pkg/envinfo/url_test.go b/pkg/envinfo/url_test.go index 88b13e47d64..1903de65994 100644 --- a/pkg/envinfo/url_test.go +++ b/pkg/envinfo/url_test.go @@ -1,11 +1,11 @@ package envinfo import ( + "github.com/devfile/library/pkg/devfile/parser/data" "reflect" "testing" "github.com/devfile/library/pkg/devfile/parser" - "github.com/devfile/library/pkg/testingutil" "github.com/devfile/library/pkg/testingutil/filesystem" "github.com/kylelemons/godebug/pretty" "github.com/openshift/odo/pkg/localConfigProvider" @@ -168,7 +168,14 @@ func TestEnvInfo_CompleteURL(t *testing.T) { name: "case 8: no container is present in the devfile and none is provided in the url", fields: fields{ devfileObj: parser.DevfileObj{ - Data: &testingutil.TestDevfileData{}}, + Data: func() data.DevfileData { + devfileData, err := data.NewDevfileData(string(data.APIVersion200)) + if err != nil { + t.Error(err) + } + return devfileData + }(), + }, }, args: args{ url: localConfigProvider.LocalURL{ @@ -383,7 +390,13 @@ func TestEnvInfo_ValidateURL(t *testing.T) { name: "case 10: no container found in devfile", fields: fields{ devfileObj: parser.DevfileObj{ - Data: &testingutil.TestDevfileData{}, + Data: func() data.DevfileData { + devfileData, err := data.NewDevfileData(string(data.APIVersion200)) + if err != nil { + t.Error(err) + } + return devfileData + }(), }, }, args: args{ diff --git a/pkg/kclient/deployments_test.go b/pkg/kclient/deployments_test.go index 981e9981385..3f1deff16fa 100644 --- a/pkg/kclient/deployments_test.go +++ b/pkg/kclient/deployments_test.go @@ -3,6 +3,8 @@ package kclient import ( "testing" + "github.com/devfile/library/pkg/devfile/parser/data" + devfilev1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" "github.com/devfile/library/pkg/devfile/generator" devfileParser "github.com/devfile/library/pkg/devfile/parser" @@ -24,15 +26,20 @@ import ( // createFakeDeployment creates a fake deployment with the given pod name and labels func createFakeDeployment(fkclient *Client, fkclientset *FakeClientset, podName string, labels map[string]string) (*appsv1.Deployment, error) { fakeUID := types.UID("12345") + devfileData, err := data.NewDevfileData(string(data.APIVersion200)) + if err != nil { + return nil, err + } - devObj := devfileParser.DevfileObj{ - Data: &testingutil.TestDevfileData{ - Components: []devfilev1.Component{ - testingutil.GetFakeContainerComponent("container1"), - }, - }, + err = devfileData.AddComponents([]devfilev1.Component{ + testingutil.GetFakeContainerComponent("container1"), + }) + if err != nil { + return nil, err } + devObj := devfileParser.DevfileObj{Data: devfileData} + containers, err := generator.GetContainers(devObj, parsercommon.DevfileOptions{}) if err != nil { return nil, err @@ -185,11 +192,13 @@ func TestUpdateDeployment(t *testing.T) { } devObj := devfileParser.DevfileObj{ - Data: &testingutil.TestDevfileData{ - Components: []devfilev1.Component{ + Data: func() data.DevfileData { + devfileData, _ := data.NewDevfileData(string(data.APIVersion200)) + _ = devfileData.AddComponents([]devfilev1.Component{ testingutil.GetFakeContainerComponent("container1"), - }, - }, + }) + return devfileData + }(), } containers, err := generator.GetContainers(devObj, parsercommon.DevfileOptions{}) diff --git a/pkg/kclient/serviceCatalog.go b/pkg/kclient/serviceCatalog.go index 3cee02cb13d..2998311e7e7 100644 --- a/pkg/kclient/serviceCatalog.go +++ b/pkg/kclient/serviceCatalog.go @@ -3,6 +3,7 @@ package kclient import ( "encoding/json" "fmt" + "github.com/ghodss/yaml" scv1beta1 "github.com/kubernetes-sigs/service-catalog/pkg/apis/servicecatalog/v1beta1" "github.com/openshift/odo/pkg/util" "github.com/pkg/errors" @@ -24,43 +25,49 @@ func serviceInstanceParameters(params map[string]string) (*runtime.RawExtension, } // CreateServiceInstance creates service instance from service catalog -func (c *Client) CreateServiceInstance(serviceName string, serviceType string, servicePlan string, parameters map[string]string, labels map[string]string) error { +func (c *Client) CreateServiceInstance(serviceName string, serviceType string, servicePlan string, parameters map[string]string, labels map[string]string) (string, error) { serviceInstanceParameters, err := serviceInstanceParameters(parameters) if err != nil { - return errors.Wrap(err, "unable to create the service instance parameters") + return "", errors.Wrap(err, "unable to create the service instance parameters") } - _, err = c.serviceCatalogClient.ServiceInstances(c.Namespace).Create( - &scv1beta1.ServiceInstance{ - TypeMeta: metav1.TypeMeta{ - Kind: "ServiceInstance", - APIVersion: "servicecatalog.k8s.io/v1beta1", + si := &scv1beta1.ServiceInstance{ + TypeMeta: metav1.TypeMeta{ + Kind: "ServiceInstance", + APIVersion: "servicecatalog.k8s.io/v1beta1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: serviceName, + Namespace: c.Namespace, + Labels: labels, + }, + Spec: scv1beta1.ServiceInstanceSpec{ + PlanReference: scv1beta1.PlanReference{ + ClusterServiceClassExternalName: serviceType, + ClusterServicePlanExternalName: servicePlan, }, - ObjectMeta: metav1.ObjectMeta{ - Name: serviceName, - Namespace: c.Namespace, - Labels: labels, - }, - Spec: scv1beta1.ServiceInstanceSpec{ - PlanReference: scv1beta1.PlanReference{ - ClusterServiceClassExternalName: serviceType, - ClusterServicePlanExternalName: servicePlan, - }, - Parameters: serviceInstanceParameters, - }, - }) + Parameters: serviceInstanceParameters, + }, + } + + _, err = c.serviceCatalogClient.ServiceInstances(c.Namespace).Create(si) if err != nil { - return errors.Wrapf(err, "unable to create the service instance %s for the service type %s and plan %s", serviceName, serviceType, servicePlan) + return "", errors.Wrapf(err, "unable to create the service instance %s for the service type %s and plan %s", serviceName, serviceType, servicePlan) } // Create the secret containing the parameters of the plan selected. err = c.CreateServiceBinding(serviceName, c.Namespace, labels) if err != nil { - return errors.Wrapf(err, "unable to create the secret %s for the service instance", serviceName) + return "", errors.Wrapf(err, "unable to create the secret %s for the service instance", serviceName) } - return nil + siString, err := yaml.Marshal(si) + if err != nil { + return "", errors.Wrapf(err, "unable to marshal service instance to string") + } + + return string(siString), nil } // ListServiceInstances returns list service instances diff --git a/pkg/kclient/serviceCatalog_test.go b/pkg/kclient/serviceCatalog_test.go index e8adf2e9757..834601dd2c3 100644 --- a/pkg/kclient/serviceCatalog_test.go +++ b/pkg/kclient/serviceCatalog_test.go @@ -620,7 +620,7 @@ func TestCreateServiceInstance(t *testing.T) { t.Run(tt.name, func(t *testing.T) { fkclient, fkclientset := FakeNew() - err := fkclient.CreateServiceInstance(tt.args.serviceName, tt.args.serviceType, tt.args.plan, tt.args.parameters, tt.args.labels) + _, err := fkclient.CreateServiceInstance(tt.args.serviceName, tt.args.serviceType, tt.args.plan, tt.args.parameters, tt.args.labels) // Checks for error in positive cases if tt.wantErr == false && (err != nil) { t.Errorf(" client.CreateServiceInstance(serviceName,serviceType, labels) unexpected error %v, wantErr %v", err, tt.wantErr) diff --git a/pkg/kclient/services_test.go b/pkg/kclient/services_test.go index 811dd1b7c05..294bef41e3c 100644 --- a/pkg/kclient/services_test.go +++ b/pkg/kclient/services_test.go @@ -4,6 +4,8 @@ import ( "reflect" "testing" + "github.com/devfile/library/pkg/devfile/parser/data" + devfilev1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" "github.com/devfile/library/pkg/devfile/generator" devfileParser "github.com/devfile/library/pkg/devfile/parser" @@ -21,11 +23,17 @@ import ( func TestCreateService(t *testing.T) { devObj := devfileParser.DevfileObj{ - Data: &testingutil.TestDevfileData{ - Components: []devfilev1.Component{ - testingutil.GetFakeContainerComponent("container1"), - }, - }, + Data: func() data.DevfileData { + devfileData, err := data.NewDevfileData(string(data.APIVersion200)) + if err != nil { + t.Error(err) + } + err = devfileData.AddComponents([]devfilev1.Component{testingutil.GetFakeContainerComponent("container1")}) + if err != nil { + t.Error(err) + } + return devfileData + }(), } tests := []struct { @@ -102,11 +110,17 @@ func TestCreateService(t *testing.T) { func TestUpdateService(t *testing.T) { devObj := devfileParser.DevfileObj{ - Data: &testingutil.TestDevfileData{ - Components: []devfilev1.Component{ - testingutil.GetFakeContainerComponent("container1"), - }, - }, + Data: func() data.DevfileData { + devfileData, err := data.NewDevfileData(string(data.APIVersion200)) + if err != nil { + t.Error(err) + } + err = devfileData.AddComponents([]devfilev1.Component{testingutil.GetFakeContainerComponent("container1")}) + if err != nil { + t.Error(err) + } + return devfileData + }(), } tests := []struct { diff --git a/pkg/odo/cli/service/create.go b/pkg/odo/cli/service/create.go index caf37eecb0e..4fc0af38ce4 100644 --- a/pkg/odo/cli/service/create.go +++ b/pkg/odo/cli/service/create.go @@ -2,28 +2,17 @@ package service import ( "bytes" - "encoding/json" "fmt" - "io/ioutil" - "os" "strings" "text/template" "github.com/openshift/odo/pkg/log" + "github.com/openshift/odo/pkg/odo/cli/component" "github.com/openshift/odo/pkg/odo/cli/service/ui" - commonui "github.com/openshift/odo/pkg/odo/cli/ui" "github.com/openshift/odo/pkg/odo/genericclioptions" "github.com/openshift/odo/pkg/odo/util/completion" - "github.com/openshift/odo/pkg/odo/util/validation" - svc "github.com/openshift/odo/pkg/service" - - "github.com/ghodss/yaml" - "github.com/pkg/errors" "github.com/spf13/cobra" - scv1beta1 "github.com/kubernetes-sigs/service-catalog/pkg/apis/servicecatalog/v1beta1" - olm "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1" - "k8s.io/klog" ktemplates "k8s.io/kubectl/pkg/util/templates" ) @@ -51,6 +40,10 @@ var ( createLongDesc = ktemplates.LongDesc(` Create a new service from Operator Hub or Service Catalog and deploy it on OpenShift. +Service creation can be performed from a valid component directory (one containing a devfile.yaml) only. + +To create the service from outside a component directory, specify path to a valid component directory using "--context" flag. + When creating a service using Operator Hub, provide a service name along with Operator name. When creating a service using Service Catalog, a --plan must be passed along with the service type. Parameters to configure the service are passed as key=value pairs. @@ -58,8 +51,8 @@ When creating a service using Service Catalog, a --plan must be passed along wit For a full list of service types, use: 'odo catalog list services'`) ) -// ServiceCreateOptions encapsulates the options for the odo service create command -type ServiceCreateOptions struct { +// CreateOptions encapsulates the options for the odo service create command +type CreateOptions struct { // parameters hold the user-provided values for service class parameters via flags (populated by cobra) parameters []string // Plan is the selected service plan @@ -82,179 +75,59 @@ type ServiceCreateOptions struct { *genericclioptions.Context // Context to use when creating service. This will use app and project values from the context componentContext string - // Custom Resrouce to create service from - CustomResource string - // Custom Resrouce's Definition fetched from alm-examples - CustomResourceDefinition map[string]interface{} - // Group of the GVR - group string - // Version of the GVR - version string - // Resource of the GVR - resource string // If set to true, DryRun prints the yaml that will create the service DryRun bool // Location of the file in which yaml specification of CR is stored. fromFile string - // choose between Operator Hub and Service Catalog. If true, Operator Hub - csvSupport bool -} - -// NewServiceCreateOptions creates a new ServiceCreateOptions instance -func NewServiceCreateOptions() *ServiceCreateOptions { - return &ServiceCreateOptions{} -} - -// DynamicCRD holds the original CR obtained from the Operator (a CSV), or user -// (when they use --from-file flag), and few other attributes that are likely -// to be used to validate a CRD before creating a service from it -type DynamicCRD struct { - // contains the CR as obtained from CSV or user - OriginalCRD map[string]interface{} + // Backend is the service provider backend (Operator Hub or Service Catalog) providing the service requested by the user + Backend ServiceProviderBackend } -func NewDynamicCRD() *DynamicCRD { - return &DynamicCRD{} +// NewCreateOptions creates a new CreateOptions instance +func NewCreateOptions() *CreateOptions { + return &CreateOptions{} } -// Complete completes ServiceCreateOptions after they've been created -func (o *ServiceCreateOptions) Complete(name string, cmd *cobra.Command, args []string) (err error) { - - if len(args) == 0 || !cmd.HasFlags() { - o.interactive = true - } - - if o.csvSupport, err = svc.IsCSVSupported(); err != nil { +// Complete completes CreateOptions after they've been created +func (o *CreateOptions) Complete(name string, cmd *cobra.Command, args []string) (err error) { + o.Context, err = genericclioptions.New(genericclioptions.CreateParameters{ + Cmd: cmd, + DevfilePath: component.DevfilePath, + ComponentContext: o.componentContext, + }) + if err != nil { return err - } else if o.csvSupport { - o.Context, err = genericclioptions.NewDevfileContext(cmd) - } else if o.componentContext != "" { - o.Context, err = genericclioptions.NewContext(cmd) - } else { - o.Context, err = genericclioptions.NewContextCreatingAppIfNeeded(cmd) } + + err = validDevfileDirectory(o.componentContext) if err != nil { - return nil + return err } - client := o.Client - - var class scv1beta1.ClusterServiceClass - - if o.csvSupport { - // we don't support interactive mode for Operator Hub yet + // decide which service backend to use + if o.fromFile != "" { + // fromFile is supported only for Operator backend + o.Backend = NewOperatorBackend() + // since interactive mode is not supported for Operators yet, set it to false o.interactive = false - // if user has just used "odo service create", simply return - if o.fromFile == "" && len(args) == 0 { - return - } - - // if user wants to create service from file and use a name given on CLI - if o.fromFile != "" { - if len(args) == 1 { - o.ServiceName = args[0] - } - return - } + return o.Backend.CompleteServiceCreate(o, cmd, args) } - if o.interactive { - classesByCategory, err := client.GetKubeClient().ListServiceClassesByCategory() - if err != nil { - return fmt.Errorf("unable to retrieve service classes: %v", err) - } - - if len(classesByCategory) == 0 { - return fmt.Errorf("no available service classes") - } - - class, o.ServiceType = ui.SelectClassInteractively(classesByCategory) - - plans, err := client.GetKubeClient().ListMatchingPlans(class) - if err != nil { - return fmt.Errorf("couldn't retrieve plans for class %s: %v", class.GetExternalName(), err) - } - - var svcPlan scv1beta1.ClusterServicePlan - // if there is only one available plan, we select it - if len(plans) == 1 { - for k, v := range plans { - o.Plan = k - svcPlan = v - } - klog.V(4).Infof("Plan %s was automatically selected since it's the only one available for service %s", o.Plan, o.ServiceType) - } else { - // otherwise select the plan interactively - o.Plan = ui.SelectPlanNameInteractively(plans, "Which service plan should we use ") - svcPlan = plans[o.Plan] - } - - o.ParametersMap = ui.EnterServicePropertiesInteractively(svcPlan) - o.ServiceName = ui.EnterServiceNameInteractively(o.ServiceType, "How should we name your service ", o.validateServiceName) - o.outputCLI = commonui.Proceed("Output the non-interactive version of the selected options") - o.wait = commonui.Proceed("Wait for the service to be ready") + // check if interactive mode is requested + if len(args) == 0 { + o.interactive = true + // only Service Catalog backend supports interactive mode for service creation + o.Backend = NewServiceCatalogBackend() } else { - if o.csvSupport { - // split the name provided on CLI and populate servicetype & customresource - o.ServiceType, o.CustomResource, err = svc.SplitServiceKindName(args[0]) - if err != nil { - return fmt.Errorf("invalid service name, use the format /") - } - - } else { - o.ServiceType = args[0] - } - // if only one arg is given, then it is considered as service name and service type both - // ONLY if working on a cluster with Service Catalog and not Operator Hub - if !o.csvSupport { - // This is because an operator with name - // "etcdoperator.v0.9.4-clusterwide" would lead to creation of a - // serice with name like - // "etcdoperator.v0.9.4-clusterwide-c47rf28l56" and that would fail - // because it's an invalid name for k8s/OCP - o.ServiceName = o.ServiceType - } - // if two args are given, first is service type and second one is service name - if len(args) == 2 { - o.ServiceName = args[1] - } - - // we convert the param list provided in the format of key=value list - // to a map - o.ParametersMap = make(map[string]string) - for _, kv := range o.parameters { - kvSlice := strings.Split(kv, "=") - // key value not provided in format of key=value - if len(kvSlice) != 2 { - return errors.New("parameters not provided in key=value format") - } - o.ParametersMap[kvSlice[0]] = kvSlice[1] - } + o.Backend = decideBackend(args[0]) } - return -} - -// validateServiceName adopts the Validator interface and checks that the name of the service being created is valid -func (o *ServiceCreateOptions) validateServiceName(i interface{}) (err error) { - s := i.(string) - err = validation.ValidateName(s) - if err != nil { - return err - } - exists, err := svc.SvcExists(o.Client, s, o.Application) - if err != nil { - return err - } - if exists { - return fmt.Errorf("%s service already exists in the current application", o.ServiceName) - } - return + return o.Backend.CompleteServiceCreate(o, cmd, args) } // outputNonInteractiveEquivalent outputs the populated options as the equivalent command that would be used in non-interactive mode -func (o *ServiceCreateOptions) outputNonInteractiveEquivalent() string { +func (o *CreateOptions) outputNonInteractiveEquivalent() string { if o.outputCLI { var tpl bytes.Buffer t := template.Must(template.New("service-create-cli").Parse(equivalentTemplate)) @@ -267,236 +140,28 @@ func (o *ServiceCreateOptions) outputNonInteractiveEquivalent() string { return "" } -// Validate validates the ServiceCreateOptions based on completed values -func (o *ServiceCreateOptions) Validate() (err error) { +// Validate validates the CreateOptions based on completed values +func (o *CreateOptions) Validate() (err error) { // if we are in interactive mode, all values are already valid if o.interactive { return nil } - // we want to find an Operator only if something's passed to the crd flag on CLI - if o.csvSupport { - d := NewDynamicCRD() - // if the user wants to create service from a file, we check for - // existence of file and validate if the requested operator and CR - // exist on the cluster - if o.fromFile != "" { - if _, err := os.Stat(o.fromFile); err != nil { - return errors.Wrap(err, "unable to find specified file") - } - - // Parse the file to find Operator and CR info - fileContents, err := ioutil.ReadFile(o.fromFile) - if err != nil { - return err - } - - err = yaml.Unmarshal(fileContents, &d.OriginalCRD) - if err != nil { - return err - } - - // Check if the operator and the CR exist on cluster - var csv olm.ClusterServiceVersion - o.CustomResource, csv, err = svc.GetCSV(o.KClient, d.OriginalCRD) - if err != nil { - return err - } - - // all is well, let's populate the fields required for creating operator backed service - o.group, o.version, o.resource, err = svc.GetGVRFromOperator(csv, o.CustomResource) - if err != nil { - return err - } - - err = d.validateMetadataInCRD() - if err != nil { - return err - } - - if o.ServiceName != "" && !o.DryRun { - // First check if service with provided name already exists - svcFullName := strings.Join([]string{o.CustomResource, o.ServiceName}, "/") - exists, err := svc.OperatorSvcExists(o.KClient, svcFullName) - if err != nil { - return err - } - if exists { - return fmt.Errorf("service %q already exists; please provide a different name or delete the existing service first.", svcFullName) - } - - d.setServiceName(o.ServiceName) - } else { - o.ServiceName, err = d.getServiceNameFromCRD() - if err != nil { - return err - } - } - - // CRD is valid. We can use it further to create a service from it. - o.CustomResourceDefinition = d.OriginalCRD - - return nil - } else if o.CustomResource != "" { - // make sure that CSV of the specified ServiceType exists - csv, err := o.KClient.GetClusterServiceVersion(o.ServiceType) - if err != nil { - // error only occurs when OperatorHub is not installed. - // k8s does't have it installed by default but OCP does - return err - } - - almExample, err := svc.GetAlmExample(csv, o.CustomResource, o.ServiceType) - if err != nil { - return err - } - - d.OriginalCRD = almExample - - o.group, o.version, o.resource, err = svc.GetGVRFromOperator(csv, o.CustomResource) - if err != nil { - return err - } - - if o.ServiceName != "" && !o.DryRun { - // First check if service with provided name already exists - svcFullName := strings.Join([]string{o.CustomResource, o.ServiceName}, "/") - exists, err := svc.OperatorSvcExists(o.KClient, svcFullName) - if err != nil { - return err - } - if exists { - return fmt.Errorf("service %q already exists; please provide a different name or delete the existing service first.", svcFullName) - } - - d.setServiceName(o.ServiceName) - } - - err = d.validateMetadataInCRD() - if err != nil { - return err - } - - // CRD is valid. We can use it further to create a service from it. - o.CustomResourceDefinition = d.OriginalCRD - - if o.ServiceName == "" { - o.ServiceName, err = d.getServiceNameFromCRD() - if err != nil { - return err - } - } - - return nil - } else { - // This block is executed only when user has neither provided a - // file nor a valid `odo service create ` to start - // the service from an Operator. So we raise and error because the - // correct way is to execute: - // `odo service create /` - - return fmt.Errorf("please use a valid command to start an Operator backed service; desired format: %q", "odo service create /") - } - } - // make sure the service type exists - classPtr, err := o.Client.GetKubeClient().GetClusterServiceClass(o.ServiceType) - if err != nil { - return errors.Wrap(err, "unable to create service because Service Catalog is not enabled in your cluster") - } - if classPtr == nil { - return fmt.Errorf("service %v doesn't exist\nRun 'odo catalog list services' to see a list of supported services.\n", o.ServiceType) - } - - // check plan - plans, err := o.Client.GetKubeClient().ListMatchingPlans(*classPtr) - if err != nil { - return err - } - if len(o.Plan) == 0 { - // when the plan has not been supplied, if there is only one available plan, we select it - if len(plans) == 1 { - for k := range plans { - o.Plan = k - } - klog.V(4).Infof("Plan %s was automatically selected since it's the only one available for service %s", o.Plan, o.ServiceType) - } else { - return fmt.Errorf("no plan was supplied for service %v.\nPlease select one of: %v\n", o.ServiceType, strings.Join(ui.GetServicePlanNames(plans), ",")) - } - } else { - // when the plan has been supplied, we need to make sure it exists - if _, ok := plans[o.Plan]; !ok { - return fmt.Errorf("plan %s is invalid for service %v.\nPlease select one of: %v\n", o.Plan, o.ServiceType, strings.Join(ui.GetServicePlanNames(plans), ",")) - } - } - //validate service name - return o.validateServiceName(o.ServiceName) + return o.Backend.ValidateServiceCreate(o) } // Run contains the logic for the odo service create command -func (o *ServiceCreateOptions) Run() (err error) { - s := &log.Status{} - if o.csvSupport { - // in case of an opertor backed service, name of the service is - // provided by the yaml specification in alm-examples. It might also - // happen that a user wants to spin up Service Catalog based service in - // spite of having 4.x cluster mode but we're not supporting - // interacting with both Operator Hub and Service Catalog on 4.x. So - // the user won't get to see service name in the log message - if !o.DryRun { - log.Infof("Deploying service of type: %s", o.CustomResource) - s = log.Spinner("Deploying service") - defer s.End(false) - } - } else { - log.Infof("Deploying service %s of type: %s", o.ServiceName, o.ServiceType) - } - - if o.csvSupport && o.CustomResource != "" { - // if cluster has resources of type CSV and o.CustomResource is not - // empty, we're expected to create an Operator backed service - if o.DryRun { - // if it's dry run, only print the alm-example (o.CustomResourceDefinition) and exit - jsonCR, err := json.MarshalIndent(o.CustomResourceDefinition, "", " ") - if err != nil { - return err - } - - // convert json to yaml - yamlCR, err := yaml.JSONToYAML(jsonCR) - if err != nil { - return err - } - - log.Info(string(yamlCR)) - - return nil - } else { - err = svc.CreateOperatorService(o.KClient, o.group, o.version, o.resource, o.CustomResourceDefinition) - } - } else { - // otherwise just create a ServiceInstance - err = svc.CreateService(o.Client, o.ServiceName, o.ServiceType, o.Plan, o.ParametersMap, o.Application) - } +func (o *CreateOptions) Run() (err error) { + err = o.Backend.RunServiceCreate(o) if err != nil { return err } - s.End(true) - if o.wait { - s = log.Spinner("Waiting for service to come up") - _, err = o.Client.GetKubeClient().WaitAndGetSecret(o.ServiceName, o.Project) - if err == nil { - s.End(true) - log.Successf(`Service '%s' is ready for use`, o.ServiceName) - } - } else { - log.Successf(`Service '%s' was created`, o.ServiceName) - log.Italic("\nProgress of the provisioning will not be reported and might take a long time\nYou can see the current status by executing 'odo service list'") + // Information on what to do next; don't do this if "--dry-run" was requested as it gets appended to the file + if !o.DryRun { + log.Infof("You can now link the service to a component using 'odo link'; check 'odo link -h'") } - // Information on what to do next - log.Infof("Optionally, link %s to your component by running: 'odo link '", o.ServiceType) - equivalent := o.outputNonInteractiveEquivalent() if len(equivalent) > 0 { log.Info("Equivalent command:\n" + ui.StyledOutput(equivalent, "cyan")) @@ -506,7 +171,7 @@ func (o *ServiceCreateOptions) Run() (err error) { // NewCmdServiceCreate implements the odo service create command. func NewCmdServiceCreate(name, fullName string) *cobra.Command { - o := NewServiceCreateOptions() + o := NewCreateOptions() o.CmdFullName = fullName serviceCreateCmd := &cobra.Command{ Use: name + " --plan [service_name]", @@ -534,48 +199,3 @@ func NewCmdServiceCreate(name, fullName string) *cobra.Command { completion.RegisterCommandFlagHandler(serviceCreateCmd, "parameters", completion.ServiceParameterCompletionHandler) return serviceCreateCmd } - -// validateMetadataInCRD validates if the CRD has metadata.name field and returns an error -func (d *DynamicCRD) validateMetadataInCRD() error { - metadata, ok := d.OriginalCRD["metadata"].(map[string]interface{}) - if !ok { - // this condition is satisfied if there's no metadata at all in the provided CRD - return fmt.Errorf("couldn't find \"metadata\" in the yaml; need metadata start the service") - } - - if _, ok := metadata["name"].(string); ok { - // found the metadata.name; no error - return nil - } - return fmt.Errorf("couldn't find metadata.name in the yaml; provide a name for the service") -} - -// setServiceName modifies the CRD to contain user provided name on the CLI -// instead of using the default one in almExample -func (d *DynamicCRD) setServiceName(name string) { - metaMap := d.OriginalCRD["metadata"].(map[string]interface{}) - - for k := range metaMap { - if k == "name" { - metaMap[k] = name - return - } - // if metadata doesn't have 'name' field, we set it up - metaMap["name"] = name - } -} - -// getServiceNameFromCRD fetches the service name from metadata.name field of the CRD -func (d *DynamicCRD) getServiceNameFromCRD() (string, error) { - metadata, ok := d.OriginalCRD["metadata"].(map[string]interface{}) - if !ok { - // this condition is satisfied if there's no metadata at all in the provided CRD - return "", fmt.Errorf("couldn't find \"metadata\" in the yaml; need metadata.name to start the service") - } - - if name, ok := metadata["name"].(string); ok { - // found the metadata.name; no error - return name, nil - } - return "", fmt.Errorf("couldn't find metadata.name in the yaml; provide a name for the service") -} diff --git a/pkg/odo/cli/service/create_test.go b/pkg/odo/cli/service/create_test.go index 7d7eeb569af..440bc0abe55 100644 --- a/pkg/odo/cli/service/create_test.go +++ b/pkg/odo/cli/service/create_test.go @@ -14,12 +14,12 @@ func TestOutputNonInteractiveEquivalent(t *testing.T) { tests := []struct { name string - options ServiceCreateOptions + options CreateOptions expected string }{ { name: "when output is not requested, should return empty string", - options: ServiceCreateOptions{ + options: CreateOptions{ Context: genericclioptions.NewFakeContext("testproject", "app", "", client, nil), CmdFullName: RecommendedCommandName, @@ -30,7 +30,7 @@ func TestOutputNonInteractiveEquivalent(t *testing.T) { }, { name: "just service class", - options: ServiceCreateOptions{ + options: CreateOptions{ Context: genericclioptions.NewFakeContext("testproject", "app", "", client, nil), CmdFullName: RecommendedCommandName, @@ -41,7 +41,7 @@ func TestOutputNonInteractiveEquivalent(t *testing.T) { }, { name: "just service class and name", - options: ServiceCreateOptions{ + options: CreateOptions{ Context: genericclioptions.NewFakeContext("testproject", "app", "", client, nil), CmdFullName: RecommendedCommandName, @@ -53,7 +53,7 @@ func TestOutputNonInteractiveEquivalent(t *testing.T) { }, { name: "service class, name and plan", - options: ServiceCreateOptions{ + options: CreateOptions{ Context: genericclioptions.NewFakeContext("testproject", "app", "", client, nil), CmdFullName: RecommendedCommandName, @@ -66,7 +66,7 @@ func TestOutputNonInteractiveEquivalent(t *testing.T) { }, { name: "service class and plan", - options: ServiceCreateOptions{ + options: CreateOptions{ Context: genericclioptions.NewFakeContext("testproject", "app", "", client, nil), CmdFullName: RecommendedCommandName, outputCLI: true, @@ -77,7 +77,7 @@ func TestOutputNonInteractiveEquivalent(t *testing.T) { }, { name: "service class and empty params", - options: ServiceCreateOptions{ + options: CreateOptions{ Context: genericclioptions.NewFakeContext("testproject", "app", "", client, nil), CmdFullName: RecommendedCommandName, outputCLI: true, @@ -88,7 +88,7 @@ func TestOutputNonInteractiveEquivalent(t *testing.T) { }, { name: "service class and params", - options: ServiceCreateOptions{ + options: CreateOptions{ Context: genericclioptions.NewFakeContext("testproject", "app", "", client, nil), CmdFullName: RecommendedCommandName, outputCLI: true, @@ -99,7 +99,7 @@ func TestOutputNonInteractiveEquivalent(t *testing.T) { }, { name: "all", - options: ServiceCreateOptions{ + options: CreateOptions{ Context: genericclioptions.NewFakeContext("testproject", "app", "", client, nil), CmdFullName: RecommendedCommandName, outputCLI: true, diff --git a/pkg/odo/cli/service/delete.go b/pkg/odo/cli/service/delete.go index 7f2cfdc02e4..26e05687e7f 100644 --- a/pkg/odo/cli/service/delete.go +++ b/pkg/odo/cli/service/delete.go @@ -4,12 +4,11 @@ import ( "fmt" "strings" - "github.com/openshift/odo/pkg/odo/cli/ui" - "github.com/openshift/odo/pkg/log" + "github.com/openshift/odo/pkg/odo/cli/component" + "github.com/openshift/odo/pkg/odo/cli/ui" "github.com/openshift/odo/pkg/odo/genericclioptions" "github.com/openshift/odo/pkg/odo/util/completion" - svc "github.com/openshift/odo/pkg/service" "github.com/spf13/cobra" "k8s.io/klog" ktemplates "k8s.io/kubectl/pkg/util/templates" @@ -26,89 +25,72 @@ var ( Delete an existing service`) ) -// ServiceDeleteOptions encapsulates the options for the odo service delete command -type ServiceDeleteOptions struct { +// DeleteOptions encapsulates the options for the odo service delete command +type DeleteOptions struct { serviceForceDeleteFlag bool serviceName string *genericclioptions.Context // Context to use when listing service. This will use app and project values from the context componentContext string - // choose between Operator Hub and Service Catalog. If true, Operator Hub - csvSupport bool + // Backend is the service provider backend (Operator Hub or Service Catalog) that was used to create the service + Backend ServiceProviderBackend } -// NewServiceDeleteOptions creates a new ServiceDeleteOptions instance -func NewServiceDeleteOptions() *ServiceDeleteOptions { - return &ServiceDeleteOptions{} +// NewDeleteOptions creates a new DeleteOptions instance +func NewDeleteOptions() *DeleteOptions { + return &DeleteOptions{} } -// Complete completes ServiceDeleteOptions after they've been created -func (o *ServiceDeleteOptions) Complete(name string, cmd *cobra.Command, args []string) (err error) { - if o.csvSupport, err = svc.IsCSVSupported(); err != nil { +// Complete completes DeleteOptions after they've been created +func (o *DeleteOptions) Complete(name string, cmd *cobra.Command, args []string) (err error) { + o.Context, err = genericclioptions.New(genericclioptions.CreateParameters{ + Cmd: cmd, + DevfilePath: component.DevfilePath, + ComponentContext: o.componentContext, + }) + if err != nil { return err - } else if o.csvSupport { - o.Context, err = genericclioptions.NewDevfileContext(cmd) - } else { - o.Context, err = genericclioptions.NewContext(cmd) } + + err = validDevfileDirectory(o.componentContext) if err != nil { return err } + + // decide which service backend to use + o.Backend = decideBackend(args[0]) o.serviceName = args[0] return } -// Validate validates the ServiceDeleteOptions based on completed values -func (o *ServiceDeleteOptions) Validate() (err error) { - if o.csvSupport { - svcExists, err := svc.OperatorSvcExists(o.KClient, o.serviceName) - if err != nil { - return err - } - - if !svcExists { - return fmt.Errorf("Couldn't find service named %q. Refer %q to see list of running services", o.serviceName, "odo service list") - } - return nil - } - - exists, err := svc.SvcExists(o.Client, o.serviceName, o.Application) +// Validate validates the DeleteOptions based on completed values +func (o *DeleteOptions) Validate() (err error) { + svcExists, err := o.Backend.ServiceExists(o) if err != nil { - return fmt.Errorf("unable to delete service because Service Catalog is not enabled in your cluster:\n%v", err) + return err } - if !exists { - return fmt.Errorf("Service with the name %s does not exist in the current application\n", o.serviceName) + + if !svcExists { + return fmt.Errorf("couldn't find service named %q. Refer %q to see list of running services", o.serviceName, "odo service list") } return } // Run contains the logic for the odo service delete command -func (o *ServiceDeleteOptions) Run() (err error) { - if o.csvSupport { - if o.serviceForceDeleteFlag || ui.Proceed(fmt.Sprintf("Are you sure you want to delete %v", o.serviceName)) { - - s := log.Spinner("Waiting for service to be deleted") - defer s.End(false) - - err = svc.DeleteOperatorService(o.KClient, o.serviceName) - if err != nil { - return err - } - - s.End(true) +func (o *DeleteOptions) Run() (err error) { + if o.serviceForceDeleteFlag || ui.Proceed(fmt.Sprintf("Are you sure you want to delete %v", o.serviceName)) { + s := log.Spinner("Waiting for service to be deleted") + defer s.End(false) - log.Infof("Service %q has been successfully deleted", o.serviceName) - } - return nil - } - - if o.serviceForceDeleteFlag || ui.Proceed(fmt.Sprintf("Are you sure you want to delete %v from %v", o.serviceName, o.Application)) { - err = svc.DeleteServiceAndUnlinkComponents(o.Client, o.serviceName, o.Application) + err = o.Backend.DeleteService(o, o.serviceName, o.Application) if err != nil { - return fmt.Errorf("unable to delete service %s:\n%v", o.serviceName, err) + return err } - log.Infof("Service %s from application %s has been deleted", o.serviceName, o.Application) + + s.End(true) + + log.Infof("Service %q has been successfully deleted", o.serviceName) } else { log.Errorf("Aborting deletion of service: %v", o.serviceName) } @@ -117,7 +99,7 @@ func (o *ServiceDeleteOptions) Run() (err error) { // NewCmdServiceDelete implements the odo service delete command. func NewCmdServiceDelete(name, fullName string) *cobra.Command { - o := NewServiceDeleteOptions() + o := NewDeleteOptions() serviceDeleteCmd := &cobra.Command{ Use: name + " ", Short: "Delete an existing service", diff --git a/pkg/odo/cli/service/interface.go b/pkg/odo/cli/service/interface.go new file mode 100644 index 00000000000..19949bbcc1f --- /dev/null +++ b/pkg/odo/cli/service/interface.go @@ -0,0 +1,14 @@ +package service + +import "github.com/spf13/cobra" + +// ServiceProviderBackend is implemented by the backends supported by odo +// It is used in "odo service create" and "odo service delete" +type ServiceProviderBackend interface { + CompleteServiceCreate(options *CreateOptions, cmd *cobra.Command, args []string) error + ValidateServiceCreate(options *CreateOptions) error + RunServiceCreate(options *CreateOptions) error + + ServiceExists(options *DeleteOptions) (bool, error) + DeleteService(options *DeleteOptions, serviceName, app string) error +} diff --git a/pkg/odo/cli/service/operator_backend.go b/pkg/odo/cli/service/operator_backend.go new file mode 100644 index 00000000000..d9b4a60118e --- /dev/null +++ b/pkg/odo/cli/service/operator_backend.go @@ -0,0 +1,313 @@ +/* + This file contains code for various service backends supported by odo. Different backends have different logics for + Complete, Validate and Run functions. These are covered in this file. +*/ +package service + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "os" + "strings" + + "github.com/ghodss/yaml" + "github.com/openshift/odo/pkg/log" + svc "github.com/openshift/odo/pkg/service" + olm "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +// DynamicCRD holds the original CR obtained from the Operator (a CSV), or user +// (when they use --from-file flag), and few other attributes that are likely +// to be used to validate a CRD before creating a service from it +type DynamicCRD struct { + // contains the CR as obtained from CSV or user + OriginalCRD map[string]interface{} +} + +func NewDynamicCRD() *DynamicCRD { + return &DynamicCRD{} +} + +// validateMetadataInCRD validates if the CRD has metadata.name field and returns an error +func (d *DynamicCRD) validateMetadataInCRD() error { + metadata, ok := d.OriginalCRD["metadata"].(map[string]interface{}) + if !ok { + // this condition is satisfied if there's no metadata at all in the provided CRD + return fmt.Errorf("couldn't find \"metadata\" in the yaml; need metadata start the service") + } + + if _, ok := metadata["name"].(string); ok { + // found the metadata.name; no error + return nil + } + return fmt.Errorf("couldn't find metadata.name in the yaml; provide a name for the service") +} + +// setServiceName modifies the CRD to contain user provided name on the CLI +// instead of using the default one in almExample +func (d *DynamicCRD) setServiceName(name string) { + metaMap := d.OriginalCRD["metadata"].(map[string]interface{}) + + for k := range metaMap { + if k == "name" { + metaMap[k] = name + return + } + // if metadata doesn't have 'name' field, we set it up + metaMap["name"] = name + } +} + +// getServiceNameFromCRD fetches the service name from metadata.name field of the CRD +func (d *DynamicCRD) getServiceNameFromCRD() (string, error) { + metadata, ok := d.OriginalCRD["metadata"].(map[string]interface{}) + if !ok { + // this condition is satisfied if there's no metadata at all in the provided CRD + return "", fmt.Errorf("couldn't find \"metadata\" in the yaml; need metadata.name to start the service") + } + + if name, ok := metadata["name"].(string); ok { + // found the metadata.name; no error + return name, nil + } + return "", fmt.Errorf("couldn't find metadata.name in the yaml; provide a name for the service") +} + +// This CompleteServiceCreate contains logic to complete the "odo service create" call for the case of Operator backend +func (b *OperatorBackend) CompleteServiceCreate(o *CreateOptions, cmd *cobra.Command, args []string) (err error) { + // since interactive mode is not supported for Operators yet, set it to false + o.interactive = false + + // if user has just used "odo service create", simply return + if o.fromFile == "" && len(args) == 0 { + return + } + + // if user wants to create service from file and use a name given on CLI + if o.fromFile != "" { + if len(args) == 1 { + o.ServiceName = args[0] + } + return + } + + // split the name provided on CLI and populate servicetype & customresource + o.ServiceType, b.CustomResource, err = svc.SplitServiceKindName(args[0]) + if err != nil { + return fmt.Errorf("invalid service name, use the format /") + } + + // if two args are given, first is service type and second one is service name + if len(args) == 2 { + o.ServiceName = args[1] + } + + return nil +} + +func (b *OperatorBackend) ValidateServiceCreate(o *CreateOptions) (err error) { + d := NewDynamicCRD() + // if the user wants to create service from a file, we check for + // existence of file and validate if the requested operator and CR + // exist on the cluster + if o.fromFile != "" { + if _, err := os.Stat(o.fromFile); err != nil { + return errors.Wrap(err, "unable to find specified file") + } + + // Parse the file to find Operator and CR info + fileContents, err := ioutil.ReadFile(o.fromFile) + if err != nil { + return err + } + + err = yaml.Unmarshal(fileContents, &d.OriginalCRD) + if err != nil { + return err + } + + // Check if the operator and the CR exist on cluster + var csv olm.ClusterServiceVersion + b.CustomResource, csv, err = svc.GetCSV(o.KClient, d.OriginalCRD) + if err != nil { + return err + } + + // all is well, let's populate the fields required for creating operator backed service + b.group, b.version, b.resource, err = svc.GetGVRFromOperator(csv, b.CustomResource) + if err != nil { + return err + } + + err = d.validateMetadataInCRD() + if err != nil { + return err + } + + if o.ServiceName != "" && !o.DryRun { + // First check if service with provided name already exists + svcFullName := strings.Join([]string{b.CustomResource, o.ServiceName}, "/") + exists, err := svc.OperatorSvcExists(o.KClient, svcFullName) + if err != nil { + return err + } + if exists { + return fmt.Errorf("service %q already exists; please provide a different name or delete the existing service first", svcFullName) + } + + d.setServiceName(o.ServiceName) + } else { + o.ServiceName, err = d.getServiceNameFromCRD() + if err != nil { + return err + } + } + + // CRD is valid. We can use it further to create a service from it. + b.CustomResourceDefinition = d.OriginalCRD + + return nil + } else if b.CustomResource != "" { + // make sure that CSV of the specified ServiceType exists + csv, err := o.KClient.GetClusterServiceVersion(o.ServiceType) + if err != nil { + // error only occurs when OperatorHub is not installed. + // k8s does't have it installed by default but OCP does + return err + } + + almExample, err := svc.GetAlmExample(csv, b.CustomResource, o.ServiceType) + if err != nil { + return err + } + + d.OriginalCRD = almExample + + b.group, b.version, b.resource, err = svc.GetGVRFromOperator(csv, b.CustomResource) + if err != nil { + return err + } + + if o.ServiceName != "" && !o.DryRun { + // First check if service with provided name already exists + svcFullName := strings.Join([]string{b.CustomResource, o.ServiceName}, "/") + exists, err := svc.OperatorSvcExists(o.KClient, svcFullName) + if err != nil { + return err + } + if exists { + return fmt.Errorf("service %q already exists; please provide a different name or delete the existing service first", svcFullName) + } + + d.setServiceName(o.ServiceName) + } + + err = d.validateMetadataInCRD() + if err != nil { + return err + } + + // CRD is valid. We can use it further to create a service from it. + b.CustomResourceDefinition = d.OriginalCRD + + if o.ServiceName == "" { + o.ServiceName, err = d.getServiceNameFromCRD() + if err != nil { + return err + } + } + + return nil + } else { + // This block is executed only when user has neither provided a + // file nor a valid `odo service create ` to start + // the service from an Operator. So we raise an error because the + // correct way is to execute: + // `odo service create /` + + return fmt.Errorf("please use a valid command to start an Operator backed service; desired format: %q", "odo service create /") + } +} + +func (b *OperatorBackend) RunServiceCreate(o *CreateOptions) (err error) { + s := &log.Status{} + + // in case of an Operator backed service, name of the service is + // provided by the yaml specification in alm-examples. It might also + // happen that a user wants to spin up Service Catalog based service in + // spite of having 4.x cluster mode but we're not supporting + // interacting with both Operator Hub and Service Catalog on 4.x. So + // the user won't get to see service name in the log message + if !o.DryRun { + log.Infof("Deploying service %q of type: %q", o.ServiceName, b.CustomResource) + s = log.Spinner("Deploying service") + defer s.End(false) + } + + // if cluster has resources of type CSV and o.CustomResource is not + // empty, we're expected to create an Operator backed service + if o.DryRun { + // if it's dry run, only print the alm-example (o.CustomResourceDefinition) and exit + jsonCR, err := json.MarshalIndent(b.CustomResourceDefinition, "", " ") + if err != nil { + return err + } + + // convert json to yaml + yamlCR, err := yaml.JSONToYAML(jsonCR) + if err != nil { + return err + } + + log.Info(string(yamlCR)) + + return nil + } else { + err = svc.CreateOperatorService(o.KClient, b.group, b.version, b.resource, b.CustomResourceDefinition) + if err != nil { + // TODO: logic to remove CRD info from devfile because service creation failed. + return err + } else { + s.End(true) + log.Successf(`Service %q was created`, o.ServiceName) + } + + crdYaml, err := yaml.Marshal(b.CustomResourceDefinition) + if err != nil { + return err + } + + err = svc.AddKubernetesComponentToDevfile(string(crdYaml), o.ServiceName, o.EnvSpecificInfo.GetDevfileObj()) + if err != nil { + return err + } + } + s.End(true) + + return +} + +func (b *OperatorBackend) ServiceExists(o *DeleteOptions) (bool, error) { + return svc.OperatorSvcExists(o.KClient, o.serviceName) +} + +func (b *OperatorBackend) DeleteService(o *DeleteOptions, name string, application string) error { + err := svc.DeleteOperatorService(o.KClient, o.serviceName) + if err != nil { + return err + } + + // "name" is of the form CR-Name/Instance-Name so we split it + // we ignore the error because the function used below is called in the call to "DeleteOperatorService" above. + _, instanceName, _ := svc.SplitServiceKindName(name) + + err = svc.DeleteKubernetesComponentFromDevfile(instanceName, o.EnvSpecificInfo.GetDevfileObj()) + if err != nil { + return errors.Wrap(err, "failed to delete service from the devfile") + } + + return nil +} diff --git a/pkg/odo/cli/service/service_catalog_backend.go b/pkg/odo/cli/service/service_catalog_backend.go new file mode 100644 index 00000000000..897374dfb7d --- /dev/null +++ b/pkg/odo/cli/service/service_catalog_backend.go @@ -0,0 +1,188 @@ +package service + +import ( + "errors" + "fmt" + "strings" + + "github.com/openshift/odo/pkg/odo/util/validation" + + svc "github.com/openshift/odo/pkg/service" + + scv1beta1 "github.com/kubernetes-sigs/service-catalog/pkg/apis/servicecatalog/v1beta1" + "github.com/openshift/odo/pkg/log" + "github.com/openshift/odo/pkg/odo/cli/service/ui" + commonui "github.com/openshift/odo/pkg/odo/cli/ui" + "github.com/spf13/cobra" + "k8s.io/klog" +) + +// This CompleteServiceCreate contains logic to complete the "odo service create" call for the case of Service Catalog backend +func (b *ServiceCatalogBackend) CompleteServiceCreate(o *CreateOptions, cmd *cobra.Command, args []string) (err error) { + var class scv1beta1.ClusterServiceClass + + if o.interactive { + classesByCategory, err := o.Client.GetKubeClient().ListServiceClassesByCategory() + if err != nil { + // this error indicates that Service Catalog is not properly setup + // we inform the user that if they're trying interactive mode for Operators, it's not yet supported. + // TODO: remove the warning when interactive mode for Operators is supported + log.Warning("odo doesn't support interactive mode for creating Operator backed service yet; refer \"odo service create -h\"") + return fmt.Errorf("unable to retrieve service classes: %v", err) + } + + if len(classesByCategory) == 0 { + return fmt.Errorf("no available service classes") + } + + class, o.ServiceType = ui.SelectClassInteractively(classesByCategory) + + plans, err := o.Client.GetKubeClient().ListMatchingPlans(class) + if err != nil { + return fmt.Errorf("couldn't retrieve plans for class %s: %v", class.GetExternalName(), err) + } + + var svcPlan scv1beta1.ClusterServicePlan + // if there is only one available plan, we select it + if len(plans) == 1 { + for k, v := range plans { + o.Plan = k + svcPlan = v + } + klog.V(4).Infof("Plan %s was automatically selected since it's the only one available for service %s", o.Plan, o.ServiceType) + } else { + // otherwise select the plan interactively + o.Plan = ui.SelectPlanNameInteractively(plans, "Which service plan should we use ") + svcPlan = plans[o.Plan] + } + + o.ParametersMap = ui.EnterServicePropertiesInteractively(svcPlan) + o.ServiceName = ui.EnterServiceNameInteractively(o.ServiceType, "How should we name your service ", o.validateServiceName) + o.outputCLI = commonui.Proceed("Output the non-interactive version of the selected options") + o.wait = commonui.Proceed("Wait for the service to be ready") + } else { + + o.ServiceType = args[0] + + // if two args are given, first is service type and second one is service name + if len(args) == 2 { + o.ServiceName = args[1] + } else { + o.ServiceName = o.ServiceType + } + + // we convert the param list provided in the format of key=value list + // to a map + o.ParametersMap = make(map[string]string) + for _, kv := range o.parameters { + kvSlice := strings.Split(kv, "=") + // key value not provided in format of key=value + if len(kvSlice) != 2 { + return errors.New("parameters not provided in key=value format") + } + o.ParametersMap[kvSlice[0]] = kvSlice[1] + } + } + return nil +} + +func (b *ServiceCatalogBackend) ValidateServiceCreate(o *CreateOptions) (err error) { + // make sure the service type exists + classPtr, err := o.Client.GetKubeClient().GetClusterServiceClass(o.ServiceType) + if err != nil { + return fmt.Errorf("unable to create service because Service Catalog is not enabled in your cluster") + } + if classPtr == nil { + return fmt.Errorf("service %v doesn't exist\nRun 'odo catalog list services' to see a list of supported services.\n", o.ServiceType) + } + + // check plan + plans, err := o.Client.GetKubeClient().ListMatchingPlans(*classPtr) + if err != nil { + return err + } + if len(o.Plan) == 0 { + // when the plan has not been supplied, if there is only one available plan, we select it + if len(plans) == 1 { + for k := range plans { + o.Plan = k + } + klog.V(4).Infof("Plan %s was automatically selected since it's the only one available for service %s", o.Plan, o.ServiceType) + } else { + return fmt.Errorf("no plan was supplied for service %v.\nPlease select one of: %v\n", o.ServiceType, strings.Join(ui.GetServicePlanNames(plans), ",")) + } + } else { + // when the plan has been supplied, we need to make sure it exists + if _, ok := plans[o.Plan]; !ok { + return fmt.Errorf("plan %s is invalid for service %v.\nPlease select one of: %v\n", o.Plan, o.ServiceType, strings.Join(ui.GetServicePlanNames(plans), ",")) + } + } + //validate service name + return o.validateServiceName(o.ServiceName) +} + +// validateServiceName adopts the Validator interface and checks that the name of the service being created is valid +func (o *CreateOptions) validateServiceName(i interface{}) (err error) { + s := i.(string) + err = validation.ValidateName(s) + if err != nil { + return err + } + exists, err := svc.SvcExists(o.Client, s, o.Application) + if err != nil { + return err + } + if exists { + return fmt.Errorf("%s service already exists in the current application", o.ServiceName) + } + return +} + +func (b *ServiceCatalogBackend) RunServiceCreate(o *CreateOptions) (err error) { + s := &log.Status{} + + log.Infof("Deploying service %q of type: %q", o.ServiceName, o.ServiceType) + // create a ServiceInstance + serviceInstance, err := svc.CreateService(o.Client, o.ServiceName, o.ServiceType, o.Plan, o.ParametersMap, o.Application) + if err != nil { + return err + } + + err = svc.AddKubernetesComponentToDevfile(serviceInstance, o.ServiceName, o.EnvSpecificInfo.GetDevfileObj()) + if err != nil { + return err + } + + s.End(true) + + if o.wait { + s = log.Spinner("Waiting for service to come up") + _, err = o.Client.GetKubeClient().WaitAndGetSecret(o.ServiceName, o.Project) + if err == nil { + s.End(true) + log.Successf(`Service %q is ready for use`, o.ServiceName) + } + } else { + log.Successf(`Service %q was created`, o.ServiceName) + log.Italic("\nProgress of the provisioning will not be reported and might take a long time\nYou can see the current status by executing 'odo service list'") + } + return +} + +func (b *ServiceCatalogBackend) ServiceExists(o *DeleteOptions) (bool, error) { + return svc.SvcExists(o.Client, o.serviceName, o.Application) +} + +func (b *ServiceCatalogBackend) DeleteService(o *DeleteOptions, name string, application string) error { + err := svc.DeleteServiceAndUnlinkComponents(o.Client, o.serviceName, o.Application) + if err != nil { + return err + } + + err = svc.DeleteKubernetesComponentFromDevfile(o.serviceName, o.EnvSpecificInfo.GetDevfileObj()) + if err != nil { + return err + } + + return nil +} diff --git a/pkg/odo/cli/service/types.go b/pkg/odo/cli/service/types.go new file mode 100644 index 00000000000..b44ef32b2a2 --- /dev/null +++ b/pkg/odo/cli/service/types.go @@ -0,0 +1,27 @@ +package service + +//OperatorBackend implements the interface ServiceProviderBackend and contains methods that help create a service from Operators +type OperatorBackend struct { + // Custom Resrouce to create service from + CustomResource string + // Custom Resrouce's Definition fetched from alm-examples + CustomResourceDefinition map[string]interface{} + // Group of the GVR + group string + // Version of the GVR + version string + // Resource of the GVR + resource string +} + +func NewOperatorBackend() *OperatorBackend { + return &OperatorBackend{} +} + +// ServiceCatalogBackend implements the interface ServiceProviderBackend and contains methods that help create a service from Service Catalog +type ServiceCatalogBackend struct { +} + +func NewServiceCatalogBackend() *ServiceCatalogBackend { + return &ServiceCatalogBackend{} +} diff --git a/pkg/odo/cli/service/utils.go b/pkg/odo/cli/service/utils.go new file mode 100644 index 00000000000..3aa6f573d81 --- /dev/null +++ b/pkg/odo/cli/service/utils.go @@ -0,0 +1,36 @@ +package service + +import ( + "fmt" + "path/filepath" + + svc "github.com/openshift/odo/pkg/service" + + "github.com/openshift/odo/pkg/odo/cli/component" + "github.com/openshift/odo/pkg/util" +) + +// validDevfileDirectory returns an error if the "odo service" command is executed from a directory not containing devfile.yaml +func validDevfileDirectory(componentContext string) error { + if componentContext == "" { + componentContext = component.LocalDirectoryDefaultLocation + } + devfilePath := filepath.Join(componentContext, component.DevfilePath) + if !util.CheckPathExists(devfilePath) { + return fmt.Errorf("service can be created/deleted from a valid component directory only\n"+ + "refer %q for more information", "odo servce create -h") + } + return nil +} + +// decideBackend returns the type of service provider backend to be used +func decideBackend(arg string) ServiceProviderBackend { + _, _, err := svc.SplitServiceKindName(arg) + if err != nil { + // failure to split provided name into two; hence ServiceCatalogBackend + return NewServiceCatalogBackend() + } else { + // provided name adheres to the format /; hence OperatorBackend + return NewOperatorBackend() + } +} diff --git a/pkg/service/service.go b/pkg/service/service.go index 7fed50099c8..3d17ed6a29c 100644 --- a/pkg/service/service.go +++ b/pkg/service/service.go @@ -5,6 +5,8 @@ import ( "fmt" "strings" + devfile "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" + "github.com/devfile/library/pkg/devfile/parser/data/v2/common" "github.com/openshift/odo/pkg/kclient" "github.com/openshift/odo/pkg/odo/util/validation" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -20,6 +22,8 @@ import ( "github.com/openshift/odo/pkg/occlient" "github.com/openshift/odo/pkg/util" "github.com/pkg/errors" + + "github.com/devfile/library/pkg/devfile/parser" ) const provisionedAndBoundStatus = "ProvisionedAndBound" @@ -53,16 +57,16 @@ func (params servicePlanParameters) Swap(i, j int) { } // CreateService creates new service from serviceCatalog -func CreateService(client *occlient.Client, serviceName string, serviceType string, servicePlan string, parameters map[string]string, applicationName string) error { +// It returns string representation of service instance created on the cluster and error (if any). +func CreateService(client *occlient.Client, serviceName, serviceType, servicePlan string, parameters map[string]string, applicationName string) (string, error) { labels := componentlabels.GetLabels(serviceName, applicationName, true) // save service type as label labels[componentlabels.ComponentTypeLabel] = serviceType - err := client.GetKubeClient().CreateServiceInstance(serviceName, serviceType, servicePlan, parameters, labels) + serviceInstance, err := client.GetKubeClient().CreateServiceInstance(serviceName, serviceType, servicePlan, parameters, labels) if err != nil { - return errors.Wrap(err, "unable to create service instance") - + return "", errors.Wrap(err, "unable to create service instance") } - return nil + return serviceInstance, nil } // GetCSV checks if the CR provided by the user in the YAML file exists in the namesapce @@ -162,7 +166,7 @@ func DeleteOperatorService(client *kclient.Client, serviceName string) error { } if csv == nil { - return fmt.Errorf("Unable to find any Operator providing the service %q", kind) + return fmt.Errorf("unable to find any Operator providing the service %q", kind) } crs := client.GetCustomResourcesFromCSV(csv) @@ -353,7 +357,7 @@ func getGVKRFromCR(cr olm.CRDDescription) (group, version, kind, resource string gr := strings.SplitN(cr.Name, ".", 2) if len(gr) != 2 { - err = fmt.Errorf("Couldn't split Custom Resource's name into two: %s", cr.Name) + err = fmt.Errorf("couldn't split Custom Resource's name into two: %s", cr.Name) return } resource = gr[0] @@ -400,7 +404,7 @@ func getGVKFromCR(cr *olm.CRDDescription) (group, version, kind string, err erro gr := strings.SplitN(cr.Name, ".", 2) if len(gr) != 2 { - err = fmt.Errorf("Couldn't split Custom Resource's name into two: %s", cr.Name) + err = fmt.Errorf("couldn't split Custom Resource's name into two: %s", cr.Name) return } group = gr[1] @@ -423,7 +427,7 @@ func GetAlmExample(csv olm.ClusterServiceVersion, cr, serviceType string) (almEx } else { // There's no alm examples in the CSV's definition return nil, - fmt.Errorf("could not find alm-examples in %q Operator's definition.", cr) + fmt.Errorf("could not find alm-examples in %q Operator's definition", cr) } almExample, err = getAlmExample(almExamples, cr, serviceType) @@ -487,7 +491,7 @@ func IsOperatorServiceNameValid(name string) (string, string, error) { checkName := strings.SplitN(name, "/", 2) if len(checkName) != 2 || checkName[0] == "" || checkName[1] == "" { - return "", "", fmt.Errorf("Invalid service name. Must adhere to / formatting. For example: %q. Execute %q for list of services.", "EtcdCluster/example", "odo service list") + return "", "", fmt.Errorf("invalid service name. Must adhere to / formatting. For example: %q. Execute %q for list of services", "EtcdCluster/example", "odo service list") } return checkName[0], checkName[1], nil } @@ -679,10 +683,58 @@ func isRequired(required []string, name string) bool { // IsCSVSupported checks if the cluster supports resources of type ClusterServiceVersion func IsCSVSupported() (bool, error) { - occlient, err := occlient.New() + client, err := occlient.New() if err != nil { return false, err } - return occlient.GetKubeClient().IsCSVSupported() + return client.GetKubeClient().IsCSVSupported() +} + +// AddKubernetesComponentToDevfile adds service definition to devfile as an inlined Kubernetes component +func AddKubernetesComponentToDevfile(crd, name string, devfileObj parser.DevfileObj) error { + err := devfileObj.Data.AddComponents([]devfile.Component{{ + Name: name, + ComponentUnion: devfile.ComponentUnion{ + Kubernetes: &devfile.KubernetesComponent{ + K8sLikeComponent: devfile.K8sLikeComponent{ + BaseComponent: devfile.BaseComponent{}, + K8sLikeComponentLocation: devfile.K8sLikeComponentLocation{ + Inlined: crd, + }, + }, + }, + }, + }}) + if err != nil { + return err + } + + return devfileObj.WriteYamlDevfile() +} + +// DeleteKubernetesComponentFromDevfile deletes an inlined Kubernetes component from devfile, if one exists +func DeleteKubernetesComponentFromDevfile(name string, devfileObj parser.DevfileObj) error { + components, err := devfileObj.Data.GetComponents(common.DevfileOptions{}) + if err != nil { + return err + } + + found := false + for _, c := range components { + if c.Name == name { + err = devfileObj.Data.DeleteComponent(c.Name) + if err != nil { + return err + } + found = true + break + } + } + + if !found { + return fmt.Errorf("could not find the service %q in devfile", name) + } + + return devfileObj.WriteYamlDevfile() } diff --git a/pkg/service/service_test.go b/pkg/service/service_test.go index c0a91f191b1..3aaebcbce68 100644 --- a/pkg/service/service_test.go +++ b/pkg/service/service_test.go @@ -3,6 +3,15 @@ package service import ( "encoding/json" "fmt" + + "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" + devfile "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" + "github.com/devfile/library/pkg/devfile/parser/data/v2/common" + + "github.com/devfile/library/pkg/devfile/parser" + devfileCtx "github.com/devfile/library/pkg/devfile/parser/context" + "github.com/devfile/library/pkg/devfile/parser/data" + devfileFileSystem "github.com/devfile/library/pkg/testingutil/filesystem" "github.com/kylelemons/godebug/pretty" "github.com/onsi/gomega/matchers" "github.com/openshift/odo/pkg/testingutil" @@ -763,3 +772,131 @@ func TestServicePlanParameterMarshalling(t *testing.T) { }) } } + +func TestAddKubernetesComponentToDevfile(t *testing.T) { + fs := devfileFileSystem.NewFakeFs() + + type args struct { + crd string + name string + devfileObj parser.DevfileObj + } + tests := []struct { + name string + args args + wantErr bool + want []v1alpha2.Component + }{ + { + name: "Case 1: Add service CRD to devfile.yaml", + args: args{ + crd: "test CRD", + name: "testName", + devfileObj: parser.DevfileObj{ + Data: func() data.DevfileData { + devfileData, err := data.NewDevfileData(string(data.APIVersion200)) + if err != nil { + t.Error(err) + } + return devfileData + }(), + Ctx: devfileCtx.FakeContext(fs, parser.OutputDevfileYamlPath), + }, + }, + wantErr: false, + want: []v1alpha2.Component{{ + Name: "testName", + ComponentUnion: devfile.ComponentUnion{ + Kubernetes: &devfile.KubernetesComponent{ + K8sLikeComponent: devfile.K8sLikeComponent{ + BaseComponent: devfile.BaseComponent{}, + K8sLikeComponentLocation: devfile.K8sLikeComponentLocation{ + Inlined: "test CRD", + }, + }, + }, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := AddKubernetesComponentToDevfile(tt.args.crd, tt.args.name, tt.args.devfileObj); (err != nil) != tt.wantErr { + t.Errorf("AddKubernetesComponentToDevfile() error = %v, wantErr %v", err, tt.wantErr) + } + got, err := tt.args.devfileObj.Data.GetComponents(common.DevfileOptions{}) + if err != nil { + t.Error(err) + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("GetComponents() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestDeleteKubernetesComponentFromDevfile(t *testing.T) { + fs := devfileFileSystem.NewFakeFs() + + type args struct { + name string + devfileObj parser.DevfileObj + } + tests := []struct { + name string + args args + wantErr bool + want []v1alpha2.Component + }{ + { + name: "Case 1: Remove a CRD from devfile.yaml", + args: args{ + name: "testName", + devfileObj: parser.DevfileObj{ + Data: func() data.DevfileData { + devfileData, err := data.NewDevfileData(string(data.APIVersion200)) + if err != nil { + t.Error(err) + } + err = devfileData.AddComponents([]v1alpha2.Component{{ + Name: "testName", + ComponentUnion: devfile.ComponentUnion{ + Kubernetes: &devfile.KubernetesComponent{ + K8sLikeComponent: devfile.K8sLikeComponent{ + BaseComponent: devfile.BaseComponent{}, + K8sLikeComponentLocation: devfile.K8sLikeComponentLocation{ + Inlined: "test CRD", + }, + }, + }, + }, + }, + }) + if err != nil { + t.Error(err) + } + return devfileData + }(), + Ctx: devfileCtx.FakeContext(fs, parser.OutputDevfileYamlPath), + }, + }, + wantErr: false, + want: []v1alpha2.Component{}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := DeleteKubernetesComponentFromDevfile(tt.args.name, tt.args.devfileObj); (err != nil) != tt.wantErr { + t.Errorf("DeleteKubernetesComponentFromDevfile() error = %v, wantErr %v", err, tt.wantErr) + } + got, err := tt.args.devfileObj.Data.GetComponents(common.DevfileOptions{}) + if err != nil { + t.Error(err) + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("GetComponents() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/sync/adapter_test.go b/pkg/sync/adapter_test.go index 1bcb5d0922d..560454fb633 100644 --- a/pkg/sync/adapter_test.go +++ b/pkg/sync/adapter_test.go @@ -1,6 +1,7 @@ package sync import ( + "github.com/devfile/library/pkg/devfile/parser/data" "io/ioutil" "os" "path" @@ -11,7 +12,6 @@ import ( devfilev1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" "github.com/devfile/library/pkg/devfile/generator" "github.com/devfile/library/pkg/devfile/parser" - "github.com/devfile/library/pkg/testingutil" "github.com/golang/mock/gomock" "github.com/openshift/odo/pkg/devfile/adapters/common" "github.com/openshift/odo/pkg/sync/mock" @@ -190,9 +190,17 @@ func TestSyncFiles(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { devObj := parser.DevfileObj{ - Data: &testingutil.TestDevfileData{ - Components: []devfilev1.Component{}, - }, + Data: func() data.DevfileData { + devfileData, err := data.NewDevfileData(string(data.APIVersion200)) + if err != nil { + t.Error(err) + } + err = devfileData.AddComponents([]devfilev1.Component{}) + if err != nil { + t.Error(err) + } + return devfileData + }(), } adapterCtx := common.AdapterContext{ @@ -332,9 +340,17 @@ func TestPushLocal(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { devObj := parser.DevfileObj{ - Data: &testingutil.TestDevfileData{ - Components: []devfilev1.Component{}, - }, + Data: func() data.DevfileData { + devfileData, err := data.NewDevfileData(string(data.APIVersion200)) + if err != nil { + t.Error(err) + } + err = devfileData.AddComponents([]devfilev1.Component{}) + if err != nil { + t.Error(err) + } + return devfileData + }(), } adapterCtx := common.AdapterContext{ diff --git a/pkg/testingutil/devfile.go b/pkg/testingutil/devfile.go index 32ba028f950..d726ab97e09 100644 --- a/pkg/testingutil/devfile.go +++ b/pkg/testingutil/devfile.go @@ -4,7 +4,7 @@ import ( v1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" "github.com/devfile/library/pkg/devfile/parser" devfileCtx "github.com/devfile/library/pkg/devfile/parser/context" - devfileTestingUtil "github.com/devfile/library/pkg/testingutil" + "github.com/devfile/library/pkg/devfile/parser/data" devfilefs "github.com/devfile/library/pkg/testingutil/filesystem" ) @@ -87,223 +87,228 @@ func GetFakeVolumeMount(name, path string) v1.VolumeMount { // GetTestDevfileObj returns a devfile object for testing func GetTestDevfileObj(fs devfilefs.Filesystem) parser.DevfileObj { - return parser.DevfileObj{ - Ctx: devfileCtx.FakeContext(fs, parser.OutputDevfileYamlPath), - Data: &devfileTestingUtil.TestDevfileData{ - Commands: []v1.Command{ - { - Id: "devbuild", - CommandUnion: v1.CommandUnion{ - Exec: &v1.ExecCommand{ - WorkingDir: "/projects/nodejs-starter", - }, - }, + devfileData, _ := data.NewDevfileData(string(data.APIVersion200)) + _ = devfileData.AddCommands([]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", - }, - Endpoints: []v1.Endpoint{ - { - Name: "port-3030", - TargetPort: 3000, - }, - }, + }, + }) + _ = devfileData.AddComponents([]v1.Component{ + { + Name: "runtime", + ComponentUnion: v1.ComponentUnion{ + Container: &v1.ContainerComponent{ + Container: v1.Container{ + Image: "quay.io/nodejs-12", + }, + Endpoints: []v1.Endpoint{ + { + Name: "port-3030", + TargetPort: 3000, }, }, }, - { - Name: "loadbalancer", - ComponentUnion: v1.ComponentUnion{ - Container: &v1.ContainerComponent{ - Container: v1.Container{ - Image: "quay.io/nginx", - }, - }, + }, + }, + { + Name: "loadbalancer", + ComponentUnion: v1.ComponentUnion{ + Container: &v1.ContainerComponent{ + Container: v1.Container{ + Image: "quay.io/nginx", }, }, }, }, + }) + + return parser.DevfileObj{ + Ctx: devfileCtx.FakeContext(fs, parser.OutputDevfileYamlPath), + Data: devfileData, } } // GetTestDevfileObjWithMultipleEndpoints returns a devfile object with multiple endpoints for testing func GetTestDevfileObjWithMultipleEndpoints(fs devfilefs.Filesystem) parser.DevfileObj { - return parser.DevfileObj{ - Ctx: devfileCtx.FakeContext(fs, parser.OutputDevfileYamlPath), - Data: &devfileTestingUtil.TestDevfileData{ - Components: []v1.Component{ - { - Name: "runtime", - ComponentUnion: v1.ComponentUnion{ - Container: &v1.ContainerComponent{ - Endpoints: []v1.Endpoint{ - { - Name: "port-3030", - TargetPort: 3030, - }, - { - Name: "port-3000", - TargetPort: 3000, - }, - }, + devfileData, _ := data.NewDevfileData(string(data.APIVersion200)) + _ = devfileData.AddComponents([]v1.Component{ + { + Name: "runtime", + ComponentUnion: v1.ComponentUnion{ + Container: &v1.ContainerComponent{ + Endpoints: []v1.Endpoint{ + { + Name: "port-3030", + TargetPort: 3030, + }, + { + Name: "port-3000", + TargetPort: 3000, }, }, }, - { - Name: "runtime-debug", - ComponentUnion: v1.ComponentUnion{ - Container: &v1.ContainerComponent{ - Endpoints: []v1.Endpoint{ - { - Name: "port-8080", - TargetPort: 8080, - }, - }, + }, + }, + { + Name: "runtime-debug", + ComponentUnion: v1.ComponentUnion{ + Container: &v1.ContainerComponent{ + Endpoints: []v1.Endpoint{ + { + Name: "port-8080", + TargetPort: 8080, }, }, }, }, }, + }) + return parser.DevfileObj{ + Ctx: devfileCtx.FakeContext(fs, parser.OutputDevfileYamlPath), + Data: devfileData, } } // DevfileObjWithInternalNoneEndpoints returns a devfile object with internal endpoints for testing func DevfileObjWithInternalNoneEndpoints(fs devfilefs.Filesystem) parser.DevfileObj { - return parser.DevfileObj{ - Ctx: devfileCtx.FakeContext(fs, parser.OutputDevfileYamlPath), - Data: &devfileTestingUtil.TestDevfileData{ - Components: []v1.Component{ - { - Name: "runtime", - ComponentUnion: v1.ComponentUnion{ - Container: &v1.ContainerComponent{ - Endpoints: []v1.Endpoint{ - { - Name: "port-3030", - TargetPort: 3030, - Exposure: v1.NoneEndpointExposure, - }, - { - Name: "port-3000", - TargetPort: 3000, - }, - }, + devfileData, _ := data.NewDevfileData(string(data.APIVersion200)) + + _ = devfileData.AddComponents([]v1.Component{ + { + Name: "runtime", + ComponentUnion: v1.ComponentUnion{ + Container: &v1.ContainerComponent{ + Endpoints: []v1.Endpoint{ + { + Name: "port-3030", + TargetPort: 3030, + Exposure: v1.NoneEndpointExposure, + }, + { + Name: "port-3000", + TargetPort: 3000, }, }, }, - { - Name: "runtime-debug", - ComponentUnion: v1.ComponentUnion{ - Container: &v1.ContainerComponent{ - Endpoints: []v1.Endpoint{ - { - Name: "port-8080", - TargetPort: 8080, - Exposure: v1.InternalEndpointExposure, - }, - }, + }, + }, + { + Name: "runtime-debug", + ComponentUnion: v1.ComponentUnion{ + Container: &v1.ContainerComponent{ + Endpoints: []v1.Endpoint{ + { + Name: "port-8080", + TargetPort: 8080, + Exposure: v1.InternalEndpointExposure, }, }, }, }, }, + }) + + return parser.DevfileObj{ + Ctx: devfileCtx.FakeContext(fs, parser.OutputDevfileYamlPath), + Data: devfileData, } } // DevfileObjWithSecureEndpoints returns a devfile object with internal endpoints for testing func DevfileObjWithSecureEndpoints(fs devfilefs.Filesystem) parser.DevfileObj { - return parser.DevfileObj{ - Ctx: devfileCtx.FakeContext(fs, parser.OutputDevfileYamlPath), - Data: &devfileTestingUtil.TestDevfileData{ - Components: []v1.Component{ - { - Name: "runtime", - ComponentUnion: v1.ComponentUnion{ - Container: &v1.ContainerComponent{ - Endpoints: []v1.Endpoint{ - { - Name: "port-3030", - TargetPort: 3030, - Protocol: v1.WSSEndpointProtocol, - }, - { - Name: "port-3000", - TargetPort: 3000, - Protocol: v1.HTTPSEndpointProtocol, - }, - }, + devfileData, _ := data.NewDevfileData(string(data.APIVersion200)) + + _ = devfileData.AddComponents([]v1.Component{ + { + Name: "runtime", + ComponentUnion: v1.ComponentUnion{ + Container: &v1.ContainerComponent{ + Endpoints: []v1.Endpoint{ + { + Name: "port-3030", + TargetPort: 3030, + Protocol: v1.WSSEndpointProtocol, + }, + { + Name: "port-3000", + TargetPort: 3000, + Protocol: v1.HTTPSEndpointProtocol, }, }, }, - { - Name: "runtime-debug", - ComponentUnion: v1.ComponentUnion{ - Container: &v1.ContainerComponent{ - Endpoints: []v1.Endpoint{ - { - Name: "port-8080", - TargetPort: 8080, - Secure: true, - }, - }, + }, + }, + { + Name: "runtime-debug", + ComponentUnion: v1.ComponentUnion{ + Container: &v1.ContainerComponent{ + Endpoints: []v1.Endpoint{ + { + Name: "port-8080", + TargetPort: 8080, + Secure: true, }, }, }, }, }, + }) + return parser.DevfileObj{ + Ctx: devfileCtx.FakeContext(fs, parser.OutputDevfileYamlPath), + Data: devfileData, } } // GetTestDevfileObjWithPath returns a devfile object for testing func GetTestDevfileObjWithPath(fs devfilefs.Filesystem) parser.DevfileObj { - return parser.DevfileObj{ - Ctx: devfileCtx.FakeContext(fs, parser.OutputDevfileYamlPath), - Data: &devfileTestingUtil.TestDevfileData{ - Commands: []v1.Command{ - { - Id: "devbuild", - CommandUnion: v1.CommandUnion{ - Exec: &v1.ExecCommand{ - WorkingDir: "/projects/nodejs-starter", - }, - }, + devfileData, _ := data.NewDevfileData(string(data.APIVersion200)) + + _ = devfileData.AddCommands([]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", - }, - Endpoints: []v1.Endpoint{ - { - Name: "port-3030", - TargetPort: 3000, - Path: "/test", - }, - }, + }, + }) + _ = devfileData.AddComponents([]v1.Component{ + { + Name: "runtime", + ComponentUnion: v1.ComponentUnion{ + Container: &v1.ContainerComponent{ + Container: v1.Container{ + Image: "quay.io/nodejs-12", + }, + Endpoints: []v1.Endpoint{ + { + Name: "port-3030", + TargetPort: 3000, + Path: "/test", }, }, }, - { - Name: "loadbalancer", - ComponentUnion: v1.ComponentUnion{ - Container: &v1.ContainerComponent{ - Container: v1.Container{ - Image: "quay.io/nginx", - }, - }, + }, + }, + { + Name: "loadbalancer", + ComponentUnion: v1.ComponentUnion{ + Container: &v1.ContainerComponent{ + Container: v1.Container{ + Image: "quay.io/nginx", }, }, }, }, + }) + return parser.DevfileObj{ + Ctx: devfileCtx.FakeContext(fs, parser.OutputDevfileYamlPath), + Data: devfileData, } } diff --git a/tests/integration/devfile/cmd_devfile_push_test.go b/tests/integration/devfile/cmd_devfile_push_test.go index 9d227ff0f98..9fba407a899 100644 --- a/tests/integration/devfile/cmd_devfile_push_test.go +++ b/tests/integration/devfile/cmd_devfile_push_test.go @@ -787,7 +787,7 @@ var _ = Describe("odo devfile push command tests", func() { // Verify odo push failed output := helper.CmdShouldFail("odo", "push") - Expect(output).To(ContainSubstring("unable to find schema for version \"1.0.0\"")) + Expect(output).To(ContainSubstring("schemaVersion not present in devfile")) }) }) diff --git a/tests/integration/operatorhub/cmd_service_test.go b/tests/integration/operatorhub/cmd_service_test.go index ccf73345019..e44a4b7bcd5 100644 --- a/tests/integration/operatorhub/cmd_service_test.go +++ b/tests/integration/operatorhub/cmd_service_test.go @@ -16,7 +16,6 @@ import ( var _ = Describe("odo service command tests for OperatorHub", func() { var commonVar helper.CommonVar - var project string var oc helper.OcRunner BeforeEach(func() { @@ -30,9 +29,6 @@ var _ = Describe("odo service command tests for OperatorHub", func() { }) preSetup := func() { - project = helper.CreateRandProject() - helper.CmdShouldPass("odo", "project", "set", project) - // wait till oc can see the all operators installed by setup script in the namespace odoArgs := []string{"catalog", "list", "services"} operators := []string{"etcdoperator", "service-binding-operator"} @@ -44,7 +40,7 @@ var _ = Describe("odo service command tests for OperatorHub", func() { } cleanPreSetup := func() { - helper.DeleteProject(project) + helper.DeleteProject(commonVar.Project) } Context("When Operators are installed in the cluster", func() { @@ -62,9 +58,13 @@ var _ = Describe("odo service command tests for OperatorHub", func() { helper.MatchAllInOutput(stdOut, []string{"Services available through Operators", "etcdoperator"}) }) - It("should not allow interactive mode command to be executed", func() { + It("should not allow creating service without valid context, and fail for interactive mode", func() { stdOut := helper.CmdShouldFail("odo", "service", "create") - Expect(stdOut).To(ContainSubstring("please use a valid command to start an Operator backed service")) + Expect(stdOut).To(ContainSubstring("service can be created/deleted from a valid component directory only")) + + helper.CmdShouldPass("odo", "create", "nodejs") + stdOut = helper.CmdShouldFail("odo", "service", "create") + Expect(stdOut).To(ContainSubstring("odo doesn't support interactive mode for creating Operator backed service")) }) }) @@ -79,17 +79,25 @@ var _ = Describe("odo service command tests for OperatorHub", func() { }) It("should be able to create, list and then delete EtcdCluster from its alm example", func() { + helper.CmdShouldPass("odo", "create", "nodejs") operators := helper.CmdShouldPass("odo", "catalog", "list", "services") etcdOperator := regexp.MustCompile(`etcdoperator\.*[a-z][0-9]\.[0-9]\.[0-9]-clusterwide`).FindString(operators) - stdOut := helper.CmdShouldPass("odo", "service", "create", fmt.Sprintf("%s/EtcdCluster", etcdOperator), "--project", project) - Expect(stdOut).To(ContainSubstring("Service 'example' was created")) + stdOut := helper.CmdShouldPass("odo", "service", "create", fmt.Sprintf("%s/EtcdCluster", etcdOperator), "--project", commonVar.Project) + Expect(stdOut).To(ContainSubstring("Service \"example\" was created")) + + // read the devfile.yaml to check if service definition was rightly inserted + devfilePath := filepath.Join(commonVar.Context, "devfile.yaml") + content, err := ioutil.ReadFile(devfilePath) + Expect(err).To(BeNil()) + matchInOutput := []string{"kubernetes", "inlined", "EtcdCluster", "example"} + helper.MatchAllInOutput(string(content), matchInOutput) // now verify if the pods for the operator have started - pods := oc.GetAllPodsInNs(project) + pods := oc.GetAllPodsInNs(commonVar.Project) // Look for pod with example name because that's the name etcd will give to the pods. etcdPod := regexp.MustCompile(`example-.[a-z0-9]*`).FindString(pods) - ocArgs := []string{"get", "pods", etcdPod, "-o", "template=\"{{.status.phase}}\"", "-n", project} + ocArgs := []string{"get", "pods", etcdPod, "-o", "template=\"{{.status.phase}}\"", "-n", commonVar.Project} helper.WaitForCmdOut("oc", ocArgs, 1, true, func(output string) bool { return strings.Contains(output, "Running") }) @@ -101,57 +109,43 @@ var _ = Describe("odo service command tests for OperatorHub", func() { // now test the deletion of the service using odo helper.CmdShouldPass("odo", "service", "delete", "EtcdCluster/example", "-f") + // read the devfile.yaml to check if service definition was deleted + content, err = ioutil.ReadFile(devfilePath) + Expect(err).To(BeNil()) + helper.DontMatchAllInOutput(string(content), matchInOutput) + // now try deleting the same service again. It should fail with error message stdOut = helper.CmdShouldFail("odo", "service", "delete", "EtcdCluster/example", "-f") - Expect(stdOut).To(ContainSubstring("Couldn't find service named")) + Expect(stdOut).To(ContainSubstring("couldn't find service named")) }) It("should be able to create service with name passed on CLI", func() { + helper.CmdShouldPass("odo", "create", "nodejs") name := helper.RandString(6) svcFullName := strings.Join([]string{"EtcdCluster", name}, "/") operators := helper.CmdShouldPass("odo", "catalog", "list", "services") etcdOperator := regexp.MustCompile(`etcdoperator\.*[a-z][0-9]\.[0-9]\.[0-9]-clusterwide`).FindString(operators) - helper.CmdShouldPass("odo", "service", "create", fmt.Sprintf("%s/EtcdCluster", etcdOperator), name, "--project", project) + helper.CmdShouldPass("odo", "service", "create", fmt.Sprintf("%s/EtcdCluster", etcdOperator), name, "--project", commonVar.Project) // now verify if the pods for the operator have started - pods := oc.GetAllPodsInNs(project) + pods := oc.GetAllPodsInNs(commonVar.Project) // Look for pod with custom name because that's the name etcd will give to the pods. compileString := name + `-.[a-z0-9]*` etcdPod := regexp.MustCompile(compileString).FindString(pods) - ocArgs := []string{"get", "pods", etcdPod, "-o", "template=\"{{.status.phase}}\"", "-n", project} + ocArgs := []string{"get", "pods", etcdPod, "-o", "template=\"{{.status.phase}}\"", "-n", commonVar.Project} helper.WaitForCmdOut("oc", ocArgs, 1, true, func(output string) bool { return strings.Contains(output, "Running") }) // now try creating service with same name again. it should fail - stdOut := helper.CmdShouldFail("odo", "service", "create", fmt.Sprintf("%s/EtcdCluster", etcdOperator), name, "--project", project) + stdOut := helper.CmdShouldFail("odo", "service", "create", fmt.Sprintf("%s/EtcdCluster", etcdOperator), name, "--project", commonVar.Project) Expect(stdOut).To(ContainSubstring(fmt.Sprintf("service %q already exists", svcFullName))) helper.CmdShouldPass("odo", "service", "delete", svcFullName, "-f") }) }) - Context("When deleting an invalid operator backed service", func() { - - JustBeforeEach(func() { - preSetup() - }) - - JustAfterEach(func() { - cleanPreSetup() - }) - - It("should correctly detect invalid service names", func() { - names := []string{"EtcdCluster", "EtcdCluster/", "/example"} - - for _, name := range names { - stdOut := helper.CmdShouldFail("odo", "service", "delete", name, "-f") - Expect(stdOut).To(ContainSubstring("couldn't split %q into exactly two", name)) - } - }) - }) - Context("When using dry-run option to create operator backed service", func() { JustBeforeEach(func() { @@ -163,11 +157,12 @@ var _ = Describe("odo service command tests for OperatorHub", func() { }) It("should only output the definition of the CR that will be used to start service", func() { + helper.CmdShouldPass("odo", "create", "nodejs") // First let's grab the etcd operator's name from "odo catalog list services" output operators := helper.CmdShouldPass("odo", "catalog", "list", "services") etcdOperator := regexp.MustCompile(`etcdoperator\.*[a-z][0-9]\.[0-9]\.[0-9]-clusterwide`).FindString(operators) - stdOut := helper.CmdShouldPass("odo", "service", "create", fmt.Sprintf("%s/EtcdCluster", etcdOperator), "--dry-run", "--project", project) + stdOut := helper.CmdShouldPass("odo", "service", "create", fmt.Sprintf("%s/EtcdCluster", etcdOperator), "--dry-run", "--project", commonVar.Project) helper.MatchAllInOutput(stdOut, []string{"apiVersion", "kind"}) }) }) @@ -205,11 +200,13 @@ var _ = Describe("odo service command tests for OperatorHub", func() { }) It("should be able to create a service", func() { + helper.CmdShouldPass("odo", "create", "nodejs") + // First let's grab the etcd operator's name from "odo catalog list services" output operators := helper.CmdShouldPass("odo", "catalog", "list", "services") etcdOperator := regexp.MustCompile(`etcdoperator\.*[a-z][0-9]\.[0-9]\.[0-9]-clusterwide`).FindString(operators) - stdOut := helper.CmdShouldPass("odo", "service", "create", fmt.Sprintf("%s/EtcdCluster", etcdOperator), "--dry-run", "--project", project) + stdOut := helper.CmdShouldPass("odo", "service", "create", fmt.Sprintf("%s/EtcdCluster", etcdOperator), "--dry-run", "--project", commonVar.Project) // stdOut contains the yaml specification. Store it to a file randomFileName := helper.RandString(6) + ".yaml" @@ -219,14 +216,14 @@ var _ = Describe("odo service command tests for OperatorHub", func() { } // now create operator backed service - helper.CmdShouldPass("odo", "service", "create", "--from-file", fileName, "--project", project) + helper.CmdShouldPass("odo", "service", "create", "--from-file", fileName, "--project", commonVar.Project) // now verify if the pods for the operator have started - pods := oc.GetAllPodsInNs(project) + pods := oc.GetAllPodsInNs(commonVar.Project) // Look for pod with example name because that's the name etcd will give to the pods. etcdPod := regexp.MustCompile(`example-.[a-z0-9]*`).FindString(pods) - ocArgs := []string{"get", "pods", etcdPod, "-o", "template=\"{{.status.phase}}\"", "-n", project} + ocArgs := []string{"get", "pods", etcdPod, "-o", "template=\"{{.status.phase}}\"", "-n", commonVar.Project} helper.WaitForCmdOut("oc", ocArgs, 1, true, func(output string) bool { return strings.Contains(output, "Running") }) @@ -235,12 +232,14 @@ var _ = Describe("odo service command tests for OperatorHub", func() { }) It("should be able to create a service with name passed on CLI", func() { + helper.CmdShouldPass("odo", "create", "nodejs") + name := helper.RandString(6) // First let's grab the etcd operator's name from "odo catalog list services" output operators := helper.CmdShouldPass("odo", "catalog", "list", "services") etcdOperator := regexp.MustCompile(`etcdoperator\.*[a-z][0-9]\.[0-9]\.[0-9]-clusterwide`).FindString(operators) - stdOut := helper.CmdShouldPass("odo", "service", "create", fmt.Sprintf("%s/EtcdCluster", etcdOperator), "--dry-run", "--project", project) + stdOut := helper.CmdShouldPass("odo", "service", "create", fmt.Sprintf("%s/EtcdCluster", etcdOperator), "--dry-run", "--project", commonVar.Project) // stdOut contains the yaml specification. Store it to a file randomFileName := helper.RandString(6) + ".yaml" @@ -250,10 +249,10 @@ var _ = Describe("odo service command tests for OperatorHub", func() { } // now create operator backed service - helper.CmdShouldPass("odo", "service", "create", "--from-file", fileName, name, "--project", project) + helper.CmdShouldPass("odo", "service", "create", "--from-file", fileName, name, "--project", commonVar.Project) // Attempting to create service with same name should fail - stdOut = helper.CmdShouldFail("odo", "service", "create", "--from-file", fileName, name, "--project", project) + stdOut = helper.CmdShouldFail("odo", "service", "create", "--from-file", fileName, name, "--project", commonVar.Project) Expect(stdOut).To(ContainSubstring("please provide a different name or delete the existing service first")) }) }) @@ -269,6 +268,7 @@ var _ = Describe("odo service command tests for OperatorHub", func() { }) It("should fail to create service if metadata doesn't exist or is invalid", func() { + helper.CmdShouldPass("odo", "create", "nodejs") noMetadata := ` apiVersion: etcd.database.coreos.com/v1beta2 kind: EtcdCluster @@ -294,7 +294,7 @@ spec: } // now create operator backed service - stdOut := helper.CmdShouldFail("odo", "service", "create", "--from-file", fileName, "--project", project) + stdOut := helper.CmdShouldFail("odo", "service", "create", "--from-file", fileName, "--project", commonVar.Project) Expect(stdOut).To(ContainSubstring("couldn't find \"metadata\" in the yaml")) invalidMetaFile := helper.RandString(6) + ".yaml" @@ -304,7 +304,7 @@ spec: } // now create operator backed service - stdOut = helper.CmdShouldFail("odo", "service", "create", "--from-file", fileName, "--project", project) + stdOut = helper.CmdShouldFail("odo", "service", "create", "--from-file", fileName, "--project", commonVar.Project) Expect(stdOut).To(ContainSubstring("couldn't find metadata.name in the yaml")) }) @@ -337,16 +337,18 @@ spec: }) It("should list the services if they exist", func() { + helper.CmdShouldPass("odo", "create", "nodejs") + operators := helper.CmdShouldPass("odo", "catalog", "list", "services") etcdOperator := regexp.MustCompile(`etcdoperator\.*[a-z][0-9]\.[0-9]\.[0-9]-clusterwide`).FindString(operators) - helper.CmdShouldPass("odo", "service", "create", fmt.Sprintf("%s/EtcdCluster", etcdOperator), "--project", project) + helper.CmdShouldPass("odo", "service", "create", fmt.Sprintf("%s/EtcdCluster", etcdOperator), "--project", commonVar.Project) // now verify if the pods for the operator have started - pods := oc.GetAllPodsInNs(project) + pods := oc.GetAllPodsInNs(commonVar.Project) // Look for pod with example name because that's the name etcd will give to the pods. etcdPod := regexp.MustCompile(`example-.[a-z0-9]*`).FindString(pods) - ocArgs := []string{"get", "pods", etcdPod, "-o", "template=\"{{.status.phase}}\"", "-n", project} + ocArgs := []string{"get", "pods", etcdPod, "-o", "template=\"{{.status.phase}}\"", "-n", commonVar.Project} helper.WaitForCmdOut("oc", ocArgs, 1, true, func(output string) bool { return strings.Contains(output, "Running") }) @@ -364,30 +366,20 @@ spec: stdOut = helper.CmdShouldFail("odo", "service", "list") jsonOut = helper.CmdShouldFail("odo", "service", "list", "-o", "json") - msg := fmt.Sprintf("no operator backed services found in namespace: %s", project) - msgWithQuote := fmt.Sprintf("\"message\": \"no operator backed services found in namespace: %s\"", project) + msg := fmt.Sprintf("no operator backed services found in namespace: %s", commonVar.Project) + msgWithQuote := fmt.Sprintf("\"message\": \"no operator backed services found in namespace: %s\"", commonVar.Project) Expect(stdOut).To(ContainSubstring(msg)) helper.MatchAllInOutput(jsonOut, []string{msg, msgWithQuote}) }) }) Context("When linking devfile component with Operator backed service", func() { - var context, currentWorkingDirectory, devfilePath string - const devfile = "devfile.yaml" - JustBeforeEach(func() { preSetup() - context = helper.CreateNewContext() - devfilePath = filepath.Join(context, devfile) - currentWorkingDirectory = helper.Getwd() - helper.Chdir(context) - helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", devfile), devfilePath) }) JustAfterEach(func() { cleanPreSetup() - helper.Chdir(currentWorkingDirectory) - helper.DeleteDir(context) }) It("should fail if service name doesn't adhere to / format", func() { @@ -396,16 +388,16 @@ spec: } componentName := helper.RandString(6) - helper.CmdShouldPass("odo", "create", componentName) + helper.CmdShouldPass("odo", "create", "nodejs", componentName) stdOut := helper.CmdShouldFail("odo", "link", "EtcdCluster") - Expect(stdOut).To(ContainSubstring("Invalid service name")) + Expect(stdOut).To(ContainSubstring("invalid service name")) stdOut = helper.CmdShouldFail("odo", "link", "EtcdCluster/") - Expect(stdOut).To(ContainSubstring("Invalid service name")) + Expect(stdOut).To(ContainSubstring("invalid service name")) stdOut = helper.CmdShouldFail("odo", "link", "/example") - Expect(stdOut).To(ContainSubstring("Invalid service name")) + Expect(stdOut).To(ContainSubstring("invalid service name")) }) It("should fail if the provided service doesn't exist in the namespace", func() { @@ -414,7 +406,7 @@ spec: } componentName := helper.RandString(6) - helper.CmdShouldPass("odo", "create", componentName) + helper.CmdShouldPass("odo", "create", "nodejs", componentName) helper.CmdShouldPass("odo", "push") stdOut := helper.CmdShouldFail("odo", "link", "EtcdCluster/example") @@ -427,20 +419,20 @@ spec: } componentName := helper.RandString(6) - helper.CmdShouldPass("odo", "create", componentName, "--devfile", "devfile.yaml", "--starter") + helper.CmdShouldPass("odo", "create", "nodejs", componentName) helper.CmdShouldPass("odo", "push") // start the Operator backed service first operators := helper.CmdShouldPass("odo", "catalog", "list", "services") etcdOperator := regexp.MustCompile(`etcdoperator\.*[a-z][0-9]\.[0-9]\.[0-9]-clusterwide`).FindString(operators) - helper.CmdShouldPass("odo", "service", "create", fmt.Sprintf("%s/EtcdCluster", etcdOperator), "--project", project) + helper.CmdShouldPass("odo", "service", "create", fmt.Sprintf("%s/EtcdCluster", etcdOperator), "--project", commonVar.Project) // now verify if the pods for the operator have started - pods := oc.GetAllPodsInNs(project) + pods := oc.GetAllPodsInNs(commonVar.Project) // Look for pod with example name because that's the name etcd will give to the pods. etcdPod := regexp.MustCompile(`example-.[a-z0-9]*`).FindString(pods) - ocArgs := []string{"get", "pods", etcdPod, "-o", "template=\"{{.status.phase}}\"", "-n", project} + ocArgs := []string{"get", "pods", etcdPod, "-o", "template=\"{{.status.phase}}\"", "-n", commonVar.Project} helper.WaitForCmdOut("oc", ocArgs, 1, true, func(output string) bool { return strings.Contains(output, "Running") }) @@ -452,9 +444,9 @@ spec: Expect(stdOut).To(ContainSubstring("already linked with the service")) // Before running "odo unlink" checks, wait for the pod to come up from "odo push" done after "odo link" - pods = oc.GetAllPodsInNs(project) + pods = oc.GetAllPodsInNs(commonVar.Project) componentPod := regexp.MustCompile(fmt.Sprintf(`%s-.[a-z0-9]*-.[a-z0-9\-]*`, componentName)).FindString(pods) - ocArgs = []string{"get", "pods", componentPod, "-o", "template=\"{{.status.phase}}\"", "-n", project} + ocArgs = []string{"get", "pods", componentPod, "-o", "template=\"{{.status.phase}}\"", "-n", commonVar.Project} helper.WaitForCmdOut("oc", ocArgs, 1, true, func(output string) bool { return strings.Contains(output, "Running") }) @@ -474,20 +466,20 @@ spec: } componentName := helper.RandString(6) - helper.CmdShouldPass("odo", "create", componentName) + helper.CmdShouldPass("odo", "create", "nodejs", componentName) helper.CmdShouldPass("odo", "push") // start the Operator backed service first operators := helper.CmdShouldPass("odo", "catalog", "list", "services") etcdOperator := regexp.MustCompile(`etcdoperator\.*[a-z][0-9]\.[0-9]\.[0-9]-clusterwide`).FindString(operators) - helper.CmdShouldPass("odo", "service", "create", fmt.Sprintf("%s/EtcdCluster", etcdOperator), "--project", project) + helper.CmdShouldPass("odo", "service", "create", fmt.Sprintf("%s/EtcdCluster", etcdOperator), "--project", commonVar.Project) // now verify if the pods for the operator have started - pods := helper.CmdShouldPass("oc", "get", "pods", "-n", project) + pods := helper.CmdShouldPass("oc", "get", "pods", "-n", commonVar.Project) // Look for pod with example name because that's the name etcd will give to the pods. etcdPod := regexp.MustCompile(`example-.[a-z0-9]*`).FindString(pods) - ocArgs := []string{"get", "pods", etcdPod, "-o", "template=\"{{.status.phase}}\"", "-n", project} + ocArgs := []string{"get", "pods", etcdPod, "-o", "template=\"{{.status.phase}}\"", "-n", commonVar.Project} helper.WaitForCmdOut("oc", ocArgs, 1, true, func(output string) bool { return strings.Contains(output, "Running") }) diff --git a/vendor/github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2/commands.go b/vendor/github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2/commands.go index 84d70e40e27..7c2872c3c58 100644 --- a/vendor/github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2/commands.go +++ b/vendor/github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2/commands.go @@ -7,16 +7,14 @@ import ( // CommandType describes the type of command. // Only one of the following command type may be specified. -// +kubebuilder:validation:Enum=Exec;Apply;VscodeTask;VscodeLaunch;Composite;Custom +// +kubebuilder:validation:Enum=Exec;Apply;Composite;Custom type CommandType string const ( - ExecCommandType CommandType = "Exec" - ApplyCommandType CommandType = "Apply" - VscodeTaskCommandType CommandType = "VscodeTask" - VscodeLaunchCommandType CommandType = "VscodeLaunch" - CompositeCommandType CommandType = "Composite" - CustomCommandType CommandType = "Custom" + ExecCommandType CommandType = "Exec" + ApplyCommandType CommandType = "Apply" + CompositeCommandType CommandType = "Composite" + CustomCommandType CommandType = "Custom" ) // CommandGroupKind describes the kind of command group. @@ -92,14 +90,6 @@ type CommandUnion struct { // +optional Apply *ApplyCommand `json:"apply,omitempty"` - // Command providing the definition of a VsCode Task - // +optional - VscodeTask *VscodeConfigurationCommand `json:"vscodeTask,omitempty"` - - // Command providing the definition of a VsCode launch action - // +optional - VscodeLaunch *VscodeConfigurationCommand `json:"vscodeLaunch,omitempty"` - // Composite command that allows executing several sub-commands // either sequentially or concurrently // +optional @@ -173,40 +163,6 @@ type CompositeCommand struct { Parallel bool `json:"parallel,omitempty"` } -// VscodeConfigurationCommandLocationType describes the type of -// the location the configuration is fetched from. -// Only one of the following component type may be specified. -// +kubebuilder:validation:Enum=Uri;Inlined -type VscodeConfigurationCommandLocationType string - -const ( - UriVscodeConfigurationCommandLocationType VscodeConfigurationCommandLocationType = "Uri" - InlinedVscodeConfigurationCommandLocationType VscodeConfigurationCommandLocationType = "Inlined" -) - -// +union -type VscodeConfigurationCommandLocation struct { - // Type of Vscode configuration command location - // + - // +unionDiscriminator - // +optional - LocationType VscodeConfigurationCommandLocationType `json:"locationType,omitempty"` - - // Location as an absolute of relative URI - // the VsCode configuration will be fetched from - // +optional - Uri string `json:"uri,omitempty"` - - // Inlined content of the VsCode configuration - // +optional - Inlined string `json:"inlined,omitempty"` -} - -type VscodeConfigurationCommand struct { - BaseCommand `json:",inline"` - VscodeConfigurationCommandLocation `json:",inline"` -} - type CustomCommand struct { LabeledCommand `json:",inline"` diff --git a/vendor/github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2/projects.go b/vendor/github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2/projects.go index a111ac65016..b4a4e6934c7 100644 --- a/vendor/github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2/projects.go +++ b/vendor/github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2/projects.go @@ -73,7 +73,7 @@ type ProjectSource struct { // +optional Git *GitProjectSource `json:"git,omitempty"` - // Project's GitHub source + // Project's GitHub source. Deprecated, use `Git` instead // +optional Github *GithubProjectSource `json:"github,omitempty"` diff --git a/vendor/github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2/volumeComponent.go b/vendor/github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2/volumeComponent.go index 06923819812..6323180a201 100644 --- a/vendor/github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2/volumeComponent.go +++ b/vendor/github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2/volumeComponent.go @@ -11,4 +11,9 @@ type Volume struct { // +optional // Size of the volume Size string `json:"size,omitempty"` + + // +optional + // Ephemeral volumes are not stored persistently across restarts. Defaults + // to false + Ephemeral bool `json:"ephemeral,omitempty"` } diff --git a/vendor/github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2/zz_generated.deepcopy.go b/vendor/github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2/zz_generated.deepcopy.go index 3ee9a3c32fe..6647e17eb84 100644 --- a/vendor/github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2/zz_generated.deepcopy.go +++ b/vendor/github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2/zz_generated.deepcopy.go @@ -409,16 +409,6 @@ func (in *CommandUnion) DeepCopyInto(out *CommandUnion) { *out = new(ApplyCommand) (*in).DeepCopyInto(*out) } - if in.VscodeTask != nil { - in, out := &in.VscodeTask, &out.VscodeTask - *out = new(VscodeConfigurationCommand) - (*in).DeepCopyInto(*out) - } - if in.VscodeLaunch != nil { - in, out := &in.VscodeLaunch, &out.VscodeLaunch - *out = new(VscodeConfigurationCommand) - (*in).DeepCopyInto(*out) - } if in.Composite != nil { in, out := &in.Composite, &out.Composite *out = new(CompositeCommand) @@ -454,16 +444,6 @@ func (in *CommandUnionParentOverride) DeepCopyInto(out *CommandUnionParentOverri *out = new(ApplyCommandParentOverride) (*in).DeepCopyInto(*out) } - if in.VscodeTask != nil { - in, out := &in.VscodeTask, &out.VscodeTask - *out = new(VscodeConfigurationCommandParentOverride) - (*in).DeepCopyInto(*out) - } - if in.VscodeLaunch != nil { - in, out := &in.VscodeLaunch, &out.VscodeLaunch - *out = new(VscodeConfigurationCommandParentOverride) - (*in).DeepCopyInto(*out) - } if in.Composite != nil { in, out := &in.Composite, &out.Composite *out = new(CompositeCommandParentOverride) @@ -494,16 +474,6 @@ func (in *CommandUnionPluginOverride) DeepCopyInto(out *CommandUnionPluginOverri *out = new(ApplyCommandPluginOverride) (*in).DeepCopyInto(*out) } - if in.VscodeTask != nil { - in, out := &in.VscodeTask, &out.VscodeTask - *out = new(VscodeConfigurationCommandPluginOverride) - (*in).DeepCopyInto(*out) - } - if in.VscodeLaunch != nil { - in, out := &in.VscodeLaunch, &out.VscodeLaunch - *out = new(VscodeConfigurationCommandPluginOverride) - (*in).DeepCopyInto(*out) - } if in.Composite != nil { in, out := &in.Composite, &out.Composite *out = new(CompositeCommandPluginOverride) @@ -534,16 +504,6 @@ func (in *CommandUnionPluginOverrideParentOverride) DeepCopyInto(out *CommandUni *out = new(ApplyCommandPluginOverrideParentOverride) (*in).DeepCopyInto(*out) } - if in.VscodeTask != nil { - in, out := &in.VscodeTask, &out.VscodeTask - *out = new(VscodeConfigurationCommandPluginOverrideParentOverride) - (*in).DeepCopyInto(*out) - } - if in.VscodeLaunch != nil { - in, out := &in.VscodeLaunch, &out.VscodeLaunch - *out = new(VscodeConfigurationCommandPluginOverrideParentOverride) - (*in).DeepCopyInto(*out) - } if in.Composite != nil { in, out := &in.Composite, &out.Composite *out = new(CompositeCommandPluginOverrideParentOverride) @@ -2836,134 +2796,6 @@ func (in *VolumePluginOverrideParentOverride) DeepCopy() *VolumePluginOverridePa return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *VscodeConfigurationCommand) DeepCopyInto(out *VscodeConfigurationCommand) { - *out = *in - in.BaseCommand.DeepCopyInto(&out.BaseCommand) - out.VscodeConfigurationCommandLocation = in.VscodeConfigurationCommandLocation -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VscodeConfigurationCommand. -func (in *VscodeConfigurationCommand) DeepCopy() *VscodeConfigurationCommand { - if in == nil { - return nil - } - out := new(VscodeConfigurationCommand) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *VscodeConfigurationCommandLocation) DeepCopyInto(out *VscodeConfigurationCommandLocation) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VscodeConfigurationCommandLocation. -func (in *VscodeConfigurationCommandLocation) DeepCopy() *VscodeConfigurationCommandLocation { - if in == nil { - return nil - } - out := new(VscodeConfigurationCommandLocation) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *VscodeConfigurationCommandLocationParentOverride) DeepCopyInto(out *VscodeConfigurationCommandLocationParentOverride) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VscodeConfigurationCommandLocationParentOverride. -func (in *VscodeConfigurationCommandLocationParentOverride) DeepCopy() *VscodeConfigurationCommandLocationParentOverride { - if in == nil { - return nil - } - out := new(VscodeConfigurationCommandLocationParentOverride) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *VscodeConfigurationCommandLocationPluginOverride) DeepCopyInto(out *VscodeConfigurationCommandLocationPluginOverride) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VscodeConfigurationCommandLocationPluginOverride. -func (in *VscodeConfigurationCommandLocationPluginOverride) DeepCopy() *VscodeConfigurationCommandLocationPluginOverride { - if in == nil { - return nil - } - out := new(VscodeConfigurationCommandLocationPluginOverride) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *VscodeConfigurationCommandLocationPluginOverrideParentOverride) DeepCopyInto(out *VscodeConfigurationCommandLocationPluginOverrideParentOverride) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VscodeConfigurationCommandLocationPluginOverrideParentOverride. -func (in *VscodeConfigurationCommandLocationPluginOverrideParentOverride) DeepCopy() *VscodeConfigurationCommandLocationPluginOverrideParentOverride { - if in == nil { - return nil - } - out := new(VscodeConfigurationCommandLocationPluginOverrideParentOverride) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *VscodeConfigurationCommandParentOverride) DeepCopyInto(out *VscodeConfigurationCommandParentOverride) { - *out = *in - in.BaseCommandParentOverride.DeepCopyInto(&out.BaseCommandParentOverride) - out.VscodeConfigurationCommandLocationParentOverride = in.VscodeConfigurationCommandLocationParentOverride -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VscodeConfigurationCommandParentOverride. -func (in *VscodeConfigurationCommandParentOverride) DeepCopy() *VscodeConfigurationCommandParentOverride { - if in == nil { - return nil - } - out := new(VscodeConfigurationCommandParentOverride) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *VscodeConfigurationCommandPluginOverride) DeepCopyInto(out *VscodeConfigurationCommandPluginOverride) { - *out = *in - in.BaseCommandPluginOverride.DeepCopyInto(&out.BaseCommandPluginOverride) - out.VscodeConfigurationCommandLocationPluginOverride = in.VscodeConfigurationCommandLocationPluginOverride -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VscodeConfigurationCommandPluginOverride. -func (in *VscodeConfigurationCommandPluginOverride) DeepCopy() *VscodeConfigurationCommandPluginOverride { - if in == nil { - return nil - } - out := new(VscodeConfigurationCommandPluginOverride) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *VscodeConfigurationCommandPluginOverrideParentOverride) DeepCopyInto(out *VscodeConfigurationCommandPluginOverrideParentOverride) { - *out = *in - in.BaseCommandPluginOverrideParentOverride.DeepCopyInto(&out.BaseCommandPluginOverrideParentOverride) - out.VscodeConfigurationCommandLocationPluginOverrideParentOverride = in.VscodeConfigurationCommandLocationPluginOverrideParentOverride -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VscodeConfigurationCommandPluginOverrideParentOverride. -func (in *VscodeConfigurationCommandPluginOverrideParentOverride) DeepCopy() *VscodeConfigurationCommandPluginOverrideParentOverride { - if in == nil { - return nil - } - out := new(VscodeConfigurationCommandPluginOverrideParentOverride) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *WorkspaceCondition) DeepCopyInto(out *WorkspaceCondition) { *out = *in diff --git a/vendor/github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2/zz_generated.parent_overrides.go b/vendor/github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2/zz_generated.parent_overrides.go index 933e3b4e5ff..292203982b5 100644 --- a/vendor/github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2/zz_generated.parent_overrides.go +++ b/vendor/github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2/zz_generated.parent_overrides.go @@ -174,7 +174,7 @@ type ProjectSourceParentOverride struct { // +optional Git *GitProjectSourceParentOverride `json:"git,omitempty"` - // Project's GitHub source + // Project's GitHub source. Deprecated, use `Git` instead // +optional Github *GithubProjectSourceParentOverride `json:"github,omitempty"` @@ -186,7 +186,7 @@ type ProjectSourceParentOverride struct { // +union type CommandUnionParentOverride struct { - // +kubebuilder:validation:Enum=Exec;Apply;VscodeTask;VscodeLaunch;Composite + // +kubebuilder:validation:Enum=Exec;Apply;Composite // Type of workspace command // +unionDiscriminator // +optional @@ -210,14 +210,6 @@ type CommandUnionParentOverride struct { // +optional Apply *ApplyCommandParentOverride `json:"apply,omitempty"` - // Command providing the definition of a VsCode Task - // +optional - VscodeTask *VscodeConfigurationCommandParentOverride `json:"vscodeTask,omitempty"` - - // Command providing the definition of a VsCode launch action - // +optional - VscodeLaunch *VscodeConfigurationCommandParentOverride `json:"vscodeLaunch,omitempty"` - // Composite command that allows executing several sub-commands // either sequentially or concurrently // +optional @@ -335,11 +327,6 @@ type ApplyCommandParentOverride struct { Component string `json:"component,omitempty"` } -type VscodeConfigurationCommandParentOverride struct { - BaseCommandParentOverride `json:",inline"` - VscodeConfigurationCommandLocationParentOverride `json:",inline"` -} - type CompositeCommandParentOverride struct { LabeledCommandParentOverride `json:",inline"` @@ -498,6 +485,11 @@ type VolumeParentOverride struct { // +optional // Size of the volume Size string `json:"size,omitempty"` + + // +optional + // Ephemeral volumes are not stored persistently across restarts. Defaults + // to false + Ephemeral bool `json:"ephemeral,omitempty"` } type ImportReferenceParentOverride struct { @@ -557,33 +549,6 @@ type EnvVarParentOverride struct { Value string `json:"value,omitempty" yaml:"value"` } -type BaseCommandParentOverride struct { - - // +optional - // Defines the group this command is part of - Group *CommandGroupParentOverride `json:"group,omitempty"` -} - -// +union -type VscodeConfigurationCommandLocationParentOverride struct { - - // +kubebuilder:validation:Enum=Uri;Inlined - // Type of Vscode configuration command location - // + - // +unionDiscriminator - // +optional - LocationType VscodeConfigurationCommandLocationTypeParentOverride `json:"locationType,omitempty"` - - // Location as an absolute of relative URI - // the VsCode configuration will be fetched from - // +optional - Uri string `json:"uri,omitempty"` - - // Inlined content of the VsCode configuration - // +optional - Inlined string `json:"inlined,omitempty"` -} - // Volume that should be mounted to a component container type VolumeMountParentOverride struct { @@ -700,22 +665,13 @@ type CheckoutFromParentOverride struct { Remote string `json:"remote,omitempty"` } -type CommandGroupParentOverride struct { - - // +optional - // Kind of group the command is part of - Kind CommandGroupKindParentOverride `json:"kind,omitempty"` +type BaseCommandParentOverride struct { // +optional - // Identifies the default command for a given group kind - IsDefault bool `json:"isDefault,omitempty"` + // Defines the group this command is part of + Group *CommandGroupParentOverride `json:"group,omitempty"` } -// VscodeConfigurationCommandLocationType describes the type of -// the location the configuration is fetched from. -// Only one of the following component type may be specified. -type VscodeConfigurationCommandLocationTypeParentOverride string - // K8sLikeComponentLocationType describes the type of // the location the configuration is fetched from. // Only one of the following component type may be specified. @@ -771,7 +727,7 @@ type ComponentUnionPluginOverrideParentOverride struct { // +union type CommandUnionPluginOverrideParentOverride struct { - // +kubebuilder:validation:Enum=Exec;Apply;VscodeTask;VscodeLaunch;Composite + // +kubebuilder:validation:Enum=Exec;Apply;Composite // Type of workspace command // +unionDiscriminator // +optional @@ -795,23 +751,22 @@ type CommandUnionPluginOverrideParentOverride struct { // +optional Apply *ApplyCommandPluginOverrideParentOverride `json:"apply,omitempty"` - // Command providing the definition of a VsCode Task - // +optional - VscodeTask *VscodeConfigurationCommandPluginOverrideParentOverride `json:"vscodeTask,omitempty"` - - // Command providing the definition of a VsCode launch action - // +optional - VscodeLaunch *VscodeConfigurationCommandPluginOverrideParentOverride `json:"vscodeLaunch,omitempty"` - // Composite command that allows executing several sub-commands // either sequentially or concurrently // +optional Composite *CompositeCommandPluginOverrideParentOverride `json:"composite,omitempty"` } -// CommandGroupKind describes the kind of command group. -// +kubebuilder:validation:Enum=build;run;test;debug -type CommandGroupKindParentOverride string +type CommandGroupParentOverride struct { + + // +optional + // Kind of group the command is part of + Kind CommandGroupKindParentOverride `json:"kind,omitempty"` + + // +optional + // Identifies the default command for a given group kind + IsDefault bool `json:"isDefault,omitempty"` +} // ComponentType describes the type of component. // Only one of the following component type may be specified. @@ -896,11 +851,6 @@ type ApplyCommandPluginOverrideParentOverride struct { Component string `json:"component,omitempty"` } -type VscodeConfigurationCommandPluginOverrideParentOverride struct { - BaseCommandPluginOverrideParentOverride `json:",inline"` - VscodeConfigurationCommandLocationPluginOverrideParentOverride `json:",inline"` -} - type CompositeCommandPluginOverrideParentOverride struct { LabeledCommandPluginOverrideParentOverride `json:",inline"` @@ -912,6 +862,10 @@ type CompositeCommandPluginOverrideParentOverride struct { Parallel bool `json:"parallel,omitempty"` } +// CommandGroupKind describes the kind of command group. +// +kubebuilder:validation:Enum=build;run;test;debug +type CommandGroupKindParentOverride string + // Workspace component: Anything that will bring additional features / tooling / behaviour / context // to the workspace, in order to make working in it easier. type BaseComponentPluginOverrideParentOverride struct { @@ -1060,6 +1014,11 @@ type VolumePluginOverrideParentOverride struct { // +optional // Size of the volume Size string `json:"size,omitempty"` + + // +optional + // Ephemeral volumes are not stored persistently across restarts. Defaults + // to false + Ephemeral bool `json:"ephemeral,omitempty"` } type LabeledCommandPluginOverrideParentOverride struct { @@ -1078,33 +1037,6 @@ type EnvVarPluginOverrideParentOverride struct { Value string `json:"value,omitempty" yaml:"value"` } -type BaseCommandPluginOverrideParentOverride struct { - - // +optional - // Defines the group this command is part of - Group *CommandGroupPluginOverrideParentOverride `json:"group,omitempty"` -} - -// +union -type VscodeConfigurationCommandLocationPluginOverrideParentOverride struct { - - // +kubebuilder:validation:Enum=Uri;Inlined - // Type of Vscode configuration command location - // + - // +unionDiscriminator - // +optional - LocationType VscodeConfigurationCommandLocationTypePluginOverrideParentOverride `json:"locationType,omitempty"` - - // Location as an absolute of relative URI - // the VsCode configuration will be fetched from - // +optional - Uri string `json:"uri,omitempty"` - - // Inlined content of the VsCode configuration - // +optional - Inlined string `json:"inlined,omitempty"` -} - // Volume that should be mounted to a component container type VolumeMountPluginOverrideParentOverride struct { @@ -1150,6 +1082,18 @@ type K8sLikeComponentLocationPluginOverrideParentOverride struct { Inlined string `json:"inlined,omitempty"` } +type BaseCommandPluginOverrideParentOverride struct { + + // +optional + // Defines the group this command is part of + Group *CommandGroupPluginOverrideParentOverride `json:"group,omitempty"` +} + +// K8sLikeComponentLocationType describes the type of +// the location the configuration is fetched from. +// Only one of the following component type may be specified. +type K8sLikeComponentLocationTypePluginOverrideParentOverride string + type CommandGroupPluginOverrideParentOverride struct { // +optional @@ -1161,16 +1105,6 @@ type CommandGroupPluginOverrideParentOverride struct { IsDefault bool `json:"isDefault,omitempty"` } -// VscodeConfigurationCommandLocationType describes the type of -// the location the configuration is fetched from. -// Only one of the following component type may be specified. -type VscodeConfigurationCommandLocationTypePluginOverrideParentOverride string - -// K8sLikeComponentLocationType describes the type of -// the location the configuration is fetched from. -// Only one of the following component type may be specified. -type K8sLikeComponentLocationTypePluginOverrideParentOverride string - // CommandGroupKind describes the kind of command group. // +kubebuilder:validation:Enum=build;run;test;debug type CommandGroupKindPluginOverrideParentOverride string diff --git a/vendor/github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2/zz_generated.plugin_overrides.go b/vendor/github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2/zz_generated.plugin_overrides.go index 42655fe8860..de0a7dc46bf 100644 --- a/vendor/github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2/zz_generated.plugin_overrides.go +++ b/vendor/github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2/zz_generated.plugin_overrides.go @@ -93,7 +93,7 @@ type ComponentUnionPluginOverride struct { // +union type CommandUnionPluginOverride struct { - // +kubebuilder:validation:Enum=Exec;Apply;VscodeTask;VscodeLaunch;Composite + // +kubebuilder:validation:Enum=Exec;Apply;Composite // Type of workspace command // +unionDiscriminator // +optional @@ -117,14 +117,6 @@ type CommandUnionPluginOverride struct { // +optional Apply *ApplyCommandPluginOverride `json:"apply,omitempty"` - // Command providing the definition of a VsCode Task - // +optional - VscodeTask *VscodeConfigurationCommandPluginOverride `json:"vscodeTask,omitempty"` - - // Command providing the definition of a VsCode launch action - // +optional - VscodeLaunch *VscodeConfigurationCommandPluginOverride `json:"vscodeLaunch,omitempty"` - // Composite command that allows executing several sub-commands // either sequentially or concurrently // +optional @@ -214,11 +206,6 @@ type ApplyCommandPluginOverride struct { Component string `json:"component,omitempty"` } -type VscodeConfigurationCommandPluginOverride struct { - BaseCommandPluginOverride `json:",inline"` - VscodeConfigurationCommandLocationPluginOverride `json:",inline"` -} - type CompositeCommandPluginOverride struct { LabeledCommandPluginOverride `json:",inline"` @@ -377,6 +364,11 @@ type VolumePluginOverride struct { // +optional // Size of the volume Size string `json:"size,omitempty"` + + // +optional + // Ephemeral volumes are not stored persistently across restarts. Defaults + // to false + Ephemeral bool `json:"ephemeral,omitempty"` } type LabeledCommandPluginOverride struct { @@ -394,33 +386,6 @@ type EnvVarPluginOverride struct { Value string `json:"value,omitempty" yaml:"value"` } -type BaseCommandPluginOverride struct { - - // +optional - // Defines the group this command is part of - Group *CommandGroupPluginOverride `json:"group,omitempty"` -} - -// +union -type VscodeConfigurationCommandLocationPluginOverride struct { - - // +kubebuilder:validation:Enum=Uri;Inlined - // Type of Vscode configuration command location - // + - // +unionDiscriminator - // +optional - LocationType VscodeConfigurationCommandLocationTypePluginOverride `json:"locationType,omitempty"` - - // Location as an absolute of relative URI - // the VsCode configuration will be fetched from - // +optional - Uri string `json:"uri,omitempty"` - - // Inlined content of the VsCode configuration - // +optional - Inlined string `json:"inlined,omitempty"` -} - // Volume that should be mounted to a component container type VolumeMountPluginOverride struct { @@ -466,6 +431,18 @@ type K8sLikeComponentLocationPluginOverride struct { Inlined string `json:"inlined,omitempty"` } +type BaseCommandPluginOverride struct { + + // +optional + // Defines the group this command is part of + Group *CommandGroupPluginOverride `json:"group,omitempty"` +} + +// K8sLikeComponentLocationType describes the type of +// the location the configuration is fetched from. +// Only one of the following component type may be specified. +type K8sLikeComponentLocationTypePluginOverride string + type CommandGroupPluginOverride struct { // +optional @@ -477,16 +454,6 @@ type CommandGroupPluginOverride struct { IsDefault bool `json:"isDefault,omitempty"` } -// VscodeConfigurationCommandLocationType describes the type of -// the location the configuration is fetched from. -// Only one of the following component type may be specified. -type VscodeConfigurationCommandLocationTypePluginOverride string - -// K8sLikeComponentLocationType describes the type of -// the location the configuration is fetched from. -// Only one of the following component type may be specified. -type K8sLikeComponentLocationTypePluginOverride string - // CommandGroupKind describes the kind of command group. // +kubebuilder:validation:Enum=build;run;test;debug type CommandGroupKindPluginOverride string diff --git a/vendor/github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2/zz_generated.union_definitions.go b/vendor/github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2/zz_generated.union_definitions.go index 406ca523f67..03a6e64d21d 100644 --- a/vendor/github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2/zz_generated.union_definitions.go +++ b/vendor/github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2/zz_generated.union_definitions.go @@ -21,33 +21,10 @@ func (union *CommandUnion) Simplify() { // +k8s:deepcopy-gen=false type CommandUnionVisitor struct { - Exec func(*ExecCommand) error - Apply func(*ApplyCommand) error - VscodeTask func(*VscodeConfigurationCommand) error - VscodeLaunch func(*VscodeConfigurationCommand) error - Composite func(*CompositeCommand) error - Custom func(*CustomCommand) error -} - -var vscodeConfigurationCommandLocation reflect.Type = reflect.TypeOf(VscodeConfigurationCommandLocationVisitor{}) - -func (union VscodeConfigurationCommandLocation) Visit(visitor VscodeConfigurationCommandLocationVisitor) error { - return visitUnion(union, visitor) -} -func (union *VscodeConfigurationCommandLocation) discriminator() *string { - return (*string)(&union.LocationType) -} -func (union *VscodeConfigurationCommandLocation) Normalize() error { - return normalizeUnion(union, vscodeConfigurationCommandLocation) -} -func (union *VscodeConfigurationCommandLocation) Simplify() { - simplifyUnion(union, vscodeConfigurationCommandLocation) -} - -// +k8s:deepcopy-gen=false -type VscodeConfigurationCommandLocationVisitor struct { - Uri func(string) error - Inlined func(string) error + Exec func(*ExecCommand) error + Apply func(*ApplyCommand) error + Composite func(*CompositeCommand) error + Custom func(*CustomCommand) error } var componentUnion reflect.Type = reflect.TypeOf(ComponentUnionVisitor{}) @@ -204,32 +181,9 @@ func (union *CommandUnionParentOverride) Simplify() { // +k8s:deepcopy-gen=false type CommandUnionParentOverrideVisitor struct { - Exec func(*ExecCommandParentOverride) error - Apply func(*ApplyCommandParentOverride) error - VscodeTask func(*VscodeConfigurationCommandParentOverride) error - VscodeLaunch func(*VscodeConfigurationCommandParentOverride) error - Composite func(*CompositeCommandParentOverride) error -} - -var vscodeConfigurationCommandLocationParentOverride reflect.Type = reflect.TypeOf(VscodeConfigurationCommandLocationParentOverrideVisitor{}) - -func (union VscodeConfigurationCommandLocationParentOverride) Visit(visitor VscodeConfigurationCommandLocationParentOverrideVisitor) error { - return visitUnion(union, visitor) -} -func (union *VscodeConfigurationCommandLocationParentOverride) discriminator() *string { - return (*string)(&union.LocationType) -} -func (union *VscodeConfigurationCommandLocationParentOverride) Normalize() error { - return normalizeUnion(union, vscodeConfigurationCommandLocationParentOverride) -} -func (union *VscodeConfigurationCommandLocationParentOverride) Simplify() { - simplifyUnion(union, vscodeConfigurationCommandLocationParentOverride) -} - -// +k8s:deepcopy-gen=false -type VscodeConfigurationCommandLocationParentOverrideVisitor struct { - Uri func(string) error - Inlined func(string) error + Exec func(*ExecCommandParentOverride) error + Apply func(*ApplyCommandParentOverride) error + Composite func(*CompositeCommandParentOverride) error } var k8sLikeComponentLocationParentOverride reflect.Type = reflect.TypeOf(K8sLikeComponentLocationParentOverrideVisitor{}) @@ -315,32 +269,9 @@ func (union *CommandUnionPluginOverrideParentOverride) Simplify() { // +k8s:deepcopy-gen=false type CommandUnionPluginOverrideParentOverrideVisitor struct { - Exec func(*ExecCommandPluginOverrideParentOverride) error - Apply func(*ApplyCommandPluginOverrideParentOverride) error - VscodeTask func(*VscodeConfigurationCommandPluginOverrideParentOverride) error - VscodeLaunch func(*VscodeConfigurationCommandPluginOverrideParentOverride) error - Composite func(*CompositeCommandPluginOverrideParentOverride) error -} - -var vscodeConfigurationCommandLocationPluginOverrideParentOverride reflect.Type = reflect.TypeOf(VscodeConfigurationCommandLocationPluginOverrideParentOverrideVisitor{}) - -func (union VscodeConfigurationCommandLocationPluginOverrideParentOverride) Visit(visitor VscodeConfigurationCommandLocationPluginOverrideParentOverrideVisitor) error { - return visitUnion(union, visitor) -} -func (union *VscodeConfigurationCommandLocationPluginOverrideParentOverride) discriminator() *string { - return (*string)(&union.LocationType) -} -func (union *VscodeConfigurationCommandLocationPluginOverrideParentOverride) Normalize() error { - return normalizeUnion(union, vscodeConfigurationCommandLocationPluginOverrideParentOverride) -} -func (union *VscodeConfigurationCommandLocationPluginOverrideParentOverride) Simplify() { - simplifyUnion(union, vscodeConfigurationCommandLocationPluginOverrideParentOverride) -} - -// +k8s:deepcopy-gen=false -type VscodeConfigurationCommandLocationPluginOverrideParentOverrideVisitor struct { - Uri func(string) error - Inlined func(string) error + Exec func(*ExecCommandPluginOverrideParentOverride) error + Apply func(*ApplyCommandPluginOverrideParentOverride) error + Composite func(*CompositeCommandPluginOverrideParentOverride) error } var k8sLikeComponentLocationPluginOverrideParentOverride reflect.Type = reflect.TypeOf(K8sLikeComponentLocationPluginOverrideParentOverrideVisitor{}) @@ -404,32 +335,9 @@ func (union *CommandUnionPluginOverride) Simplify() { // +k8s:deepcopy-gen=false type CommandUnionPluginOverrideVisitor struct { - Exec func(*ExecCommandPluginOverride) error - Apply func(*ApplyCommandPluginOverride) error - VscodeTask func(*VscodeConfigurationCommandPluginOverride) error - VscodeLaunch func(*VscodeConfigurationCommandPluginOverride) error - Composite func(*CompositeCommandPluginOverride) error -} - -var vscodeConfigurationCommandLocationPluginOverride reflect.Type = reflect.TypeOf(VscodeConfigurationCommandLocationPluginOverrideVisitor{}) - -func (union VscodeConfigurationCommandLocationPluginOverride) Visit(visitor VscodeConfigurationCommandLocationPluginOverrideVisitor) error { - return visitUnion(union, visitor) -} -func (union *VscodeConfigurationCommandLocationPluginOverride) discriminator() *string { - return (*string)(&union.LocationType) -} -func (union *VscodeConfigurationCommandLocationPluginOverride) Normalize() error { - return normalizeUnion(union, vscodeConfigurationCommandLocationPluginOverride) -} -func (union *VscodeConfigurationCommandLocationPluginOverride) Simplify() { - simplifyUnion(union, vscodeConfigurationCommandLocationPluginOverride) -} - -// +k8s:deepcopy-gen=false -type VscodeConfigurationCommandLocationPluginOverrideVisitor struct { - Uri func(string) error - Inlined func(string) error + Exec func(*ExecCommandPluginOverride) error + Apply func(*ApplyCommandPluginOverride) error + Composite func(*CompositeCommandPluginOverride) error } var k8sLikeComponentLocationPluginOverride reflect.Type = reflect.TypeOf(K8sLikeComponentLocationPluginOverrideVisitor{}) diff --git a/vendor/github.com/devfile/api/v2/pkg/devfile/header.go b/vendor/github.com/devfile/api/v2/pkg/devfile/header.go index 23cccd04b9b..6cd81d2bb2a 100644 --- a/vendor/github.com/devfile/api/v2/pkg/devfile/header.go +++ b/vendor/github.com/devfile/api/v2/pkg/devfile/header.go @@ -50,4 +50,16 @@ type DevfileMetadata struct { // Optional devfile global memory limit // +optional GlobalMemoryLimit string `json:"globalMemoryLimit,omitempty"` + + // Optional devfile project type + // +optional + ProjectType string `json:"projectType,omitempty"` + + // Optional devfile language + // +optional + Language string `json:"language,omitempty"` + + // Optional devfile website + // +optional + Website string `json:"website,omitempty"` } diff --git a/vendor/github.com/devfile/api/v2/pkg/validation/commands.go b/vendor/github.com/devfile/api/v2/pkg/validation/commands.go index 9fc4e2270e3..8447737c8bb 100644 --- a/vendor/github.com/devfile/api/v2/pkg/validation/commands.go +++ b/vendor/github.com/devfile/api/v2/pkg/validation/commands.go @@ -57,14 +57,6 @@ func validateCommand(command v1alpha2.Command, parentCommands map[string]string, return validateCompositeCommand(&command, parentCommands, devfileCommands, components) case command.Exec != nil || command.Apply != nil: return validateCommandComponent(command, components) - case command.VscodeLaunch != nil: - if command.VscodeLaunch.Uri != "" { - return ValidateURI(command.VscodeLaunch.Uri) - } - case command.VscodeTask != nil: - if command.VscodeTask.Uri != "" { - return ValidateURI(command.VscodeTask.Uri) - } default: err = fmt.Errorf("command %s type is invalid", command.Id) } @@ -106,10 +98,6 @@ func getGroup(command v1alpha2.Command) *v1alpha2.CommandGroup { return command.Exec.Group case command.Apply != nil: return command.Apply.Group - case command.VscodeLaunch != nil: - return command.VscodeLaunch.Group - case command.VscodeTask != nil: - return command.VscodeTask.Group case command.Custom != nil: return command.Custom.Group diff --git a/vendor/github.com/devfile/api/v2/pkg/validation/validation-rule.md b/vendor/github.com/devfile/api/v2/pkg/validation/validation-rule.md index d1d850dbf65..454ffa4142d 100644 --- a/vendor/github.com/devfile/api/v2/pkg/validation/validation-rule.md +++ b/vendor/github.com/devfile/api/v2/pkg/validation/validation-rule.md @@ -24,8 +24,7 @@ Since network is shared in the same pod, endpoint ports should be unique across - Should not indirectly reference itself via a subcommand which is a composite command - Should reference a valid devfile command 3. exec and apply command should: map to a valid container component -4. vscodeLaunch & vscodeTask: URI needs to be in valid URI format -5. `{build, run, test, debug}`, each kind of group can only have one default command associated with it. If there are multiple commands of the same kind without a default, a warning will be displayed. +4. `{build, run, test, debug}`, each kind of group can only have one default command associated with it. If there are multiple commands of the same kind without a default, a warning will be displayed. ### Components: Common rules for all components types: diff --git a/vendor/github.com/devfile/library/pkg/devfile/parse.go b/vendor/github.com/devfile/library/pkg/devfile/parse.go index cebc9fab7a2..92a858ed62d 100644 --- a/vendor/github.com/devfile/library/pkg/devfile/parse.go +++ b/vendor/github.com/devfile/library/pkg/devfile/parse.go @@ -9,6 +9,7 @@ import ( // and validates the devfile integrity with the schema // and validates the devfile data. // Creates devfile context and runtime objects. +// Deprecated, use ParseDevfileAndValidate() instead func ParseFromURLAndValidate(url string) (d parser.DevfileObj, err error) { // read and parse devfile from the given URL @@ -30,6 +31,7 @@ func ParseFromURLAndValidate(url string) (d parser.DevfileObj, err error) { // and validates the devfile integrity with the schema // and validates the devfile data. // Creates devfile context and runtime objects. +// Deprecated, use ParseDevfileAndValidate() instead func ParseFromDataAndValidate(data []byte) (d parser.DevfileObj, err error) { // read and parse devfile from the given bytes d, err = parser.ParseFromData(data) @@ -49,6 +51,7 @@ func ParseFromDataAndValidate(data []byte) (d parser.DevfileObj, err error) { // and validates the devfile integrity with the schema // and validates the devfile data. // Creates devfile context and runtime objects. +// Deprecated, use ParseDevfileAndValidate() instead func ParseAndValidate(path string) (d parser.DevfileObj, err error) { // read and parse devfile from given path @@ -65,3 +68,22 @@ func ParseAndValidate(path string) (d parser.DevfileObj, err error) { return d, err } + +// ParseDevfileAndValidate func parses the devfile data +// and validates the devfile integrity with the schema +// and validates the devfile data. +// Creates devfile context and runtime objects. +func ParseDevfileAndValidate(args parser.ParserArgs) (d parser.DevfileObj, err error) { + d, err = parser.ParseDevfile(args) + if err != nil { + return d, err + } + + // generic validation on devfile content + err = validate.ValidateDevfileData(d.Data) + if err != nil { + return d, err + } + + return d, err +} diff --git a/vendor/github.com/devfile/library/pkg/devfile/parser/configurables.go b/vendor/github.com/devfile/library/pkg/devfile/parser/configurables.go index 8bef2197e27..f25437ddf1e 100644 --- a/vendor/github.com/devfile/library/pkg/devfile/parser/configurables.go +++ b/vendor/github.com/devfile/library/pkg/devfile/parser/configurables.go @@ -22,7 +22,8 @@ const ( // SetMetadataName set metadata name in a devfile func (d DevfileObj) SetMetadataName(name string) error { metadata := d.Data.GetMetadata() - d.Data.SetMetadata(name, metadata.Version) + metadata.Name = name + d.Data.SetMetadata(metadata) return d.WriteYamlDevfile() } diff --git a/vendor/github.com/devfile/library/pkg/devfile/parser/context/apiVersion.go b/vendor/github.com/devfile/library/pkg/devfile/parser/context/apiVersion.go index 2f05a209f8b..a6928a6e270 100644 --- a/vendor/github.com/devfile/library/pkg/devfile/parser/context/apiVersion.go +++ b/vendor/github.com/devfile/library/pkg/devfile/parser/context/apiVersion.go @@ -19,35 +19,27 @@ func (d *DevfileCtx) SetDevfileAPIVersion() error { return errors.Wrapf(err, "failed to decode devfile json") } - var apiVer string - - // Get "apiVersion" value from map for devfile V1 - apiVersion, okApi := r["apiVersion"] - // Get "schemaVersion" value from map for devfile V2 schemaVersion, okSchema := r["schemaVersion"] + var devfilePath string + if d.GetAbsPath() != "" { + devfilePath = d.GetAbsPath() + } else if d.GetURL() != "" { + devfilePath = d.GetURL() + } - if okApi { - apiVer = apiVersion.(string) - // apiVersion cannot be empty - if apiVer == "" { - return fmt.Errorf("apiVersion in devfile cannot be empty") - } - - } else if okSchema { - apiVer = schemaVersion.(string) + if okSchema { // SchemaVersion cannot be empty if schemaVersion.(string) == "" { - return fmt.Errorf("schemaVersion in devfile cannot be empty") + return fmt.Errorf("schemaVersion in devfile: %s cannot be empty", devfilePath) } } else { - return fmt.Errorf("apiVersion or schemaVersion not present in devfile") - + return fmt.Errorf("schemaVersion not present in devfile: %s", devfilePath) } // Successful - d.apiVersion = apiVer - klog.V(4).Infof("devfile apiVersion: '%s'", d.apiVersion) + d.apiVersion = schemaVersion.(string) + klog.V(4).Infof("devfile schemaVersion: '%s'", d.apiVersion) return nil } diff --git a/vendor/github.com/devfile/library/pkg/devfile/parser/context/content.go b/vendor/github.com/devfile/library/pkg/devfile/parser/context/content.go index e9d817399d5..7f98dcaa54d 100644 --- a/vendor/github.com/devfile/library/pkg/devfile/parser/context/content.go +++ b/vendor/github.com/devfile/library/pkg/devfile/parser/context/content.go @@ -54,7 +54,7 @@ func (d *DevfileCtx) SetDevfileContent() error { if d.url != "" { data, err = util.DownloadFileInMemory(d.url) if err != nil { - return errors.Wrap(err, "error getting parent info from url") + return errors.Wrap(err, "error getting devfile info from url") } } else if d.absPath != "" { // Read devfile diff --git a/vendor/github.com/devfile/library/pkg/devfile/parser/context/context.go b/vendor/github.com/devfile/library/pkg/devfile/parser/context/context.go index 252a920cf39..327fc43312a 100644 --- a/vendor/github.com/devfile/library/pkg/devfile/parser/context/context.go +++ b/vendor/github.com/devfile/library/pkg/devfile/parser/context/context.go @@ -3,14 +3,16 @@ package parser import ( "fmt" "net/url" + "os" + "path" + "path/filepath" + "strings" "github.com/devfile/library/pkg/testingutil/filesystem" "github.com/devfile/library/pkg/util" "k8s.io/klog" ) -var URIMap = make(map[string]bool) - // DevfileCtx stores context info regarding devfile type DevfileCtx struct { @@ -34,6 +36,12 @@ type DevfileCtx struct { // filesystem for devfile fs filesystem.Filesystem + + // trace of all url referenced + uriMap map[string]bool + + // registry URLs list + registryURLs []string } // NewDevfileCtx returns a new DevfileCtx type object @@ -65,15 +73,28 @@ func (d *DevfileCtx) populateDevfile() (err error) { // Populate fills the DevfileCtx struct with relevant context info func (d *DevfileCtx) Populate() (err error) { - + if !strings.HasSuffix(d.relPath, ".yaml") { + if _, err := os.Stat(filepath.Join(d.relPath, "devfile.yaml")); os.IsNotExist(err) { + if _, err := os.Stat(filepath.Join(d.relPath, ".devfile.yaml")); os.IsNotExist(err) { + return fmt.Errorf("the provided path is not a valid yaml filepath, and devfile.yaml or .devfile.yaml not found in the provided path : %s", d.relPath) + } else { + d.relPath = filepath.Join(d.relPath, ".devfile.yaml") + } + } else { + d.relPath = filepath.Join(d.relPath, "devfile.yaml") + } + } if err := d.SetAbsPath(); err != nil { return err } klog.V(4).Infof("absolute devfile path: '%s'", d.absPath) - if URIMap[d.absPath] { + if d.uriMap == nil { + d.uriMap = make(map[string]bool) + } + if d.uriMap[d.absPath] { return fmt.Errorf("URI %v is recursively referenced", d.absPath) } - URIMap[d.absPath] = true + d.uriMap[d.absPath] = true // Read and save devfile content if err := d.SetDevfileContent(); err != nil { return err @@ -83,15 +104,27 @@ func (d *DevfileCtx) Populate() (err error) { // PopulateFromURL fills the DevfileCtx struct with relevant context info func (d *DevfileCtx) PopulateFromURL() (err error) { - - _, err = url.ParseRequestURI(d.url) + u, err := url.ParseRequestURI(d.url) if err != nil { return err } - if URIMap[d.url] { + if !strings.HasSuffix(d.url, ".yaml") { + u.Path = path.Join(u.Path, "devfile.yaml") + if _, err = util.DownloadFileInMemory(u.String()); err != nil { + u.Path = path.Join(path.Dir(u.Path), ".devfile.yaml") + if _, err = util.DownloadFileInMemory(u.String()); err != nil { + return fmt.Errorf("the provided url is not a valid yaml filepath, and devfile.yaml or .devfile.yaml not found in the provided path : %s", d.url) + } + } + d.url = u.String() + } + if d.uriMap == nil { + d.uriMap = make(map[string]bool) + } + if d.uriMap[d.url] { return fmt.Errorf("URI %v is recursively referenced", d.url) } - URIMap[d.url] = true + d.uriMap[d.url] = true // Read and save devfile content if err := d.SetDevfileContent(); err != nil { return err @@ -132,3 +165,23 @@ func (d *DevfileCtx) SetAbsPath() (err error) { return nil } + +// GetURIMap func returns current devfile uri map +func (d *DevfileCtx) GetURIMap() map[string]bool { + return d.uriMap +} + +// SetURIMap set uri map in the devfile ctx +func (d *DevfileCtx) SetURIMap(uriMap map[string]bool) { + d.uriMap = uriMap +} + +// GetRegistryURLs func returns current devfile registry URLs +func (d *DevfileCtx) GetRegistryURLs() []string { + return d.registryURLs +} + +// SetRegistryURLs set registry URLs in the devfile ctx +func (d *DevfileCtx) SetRegistryURLs(registryURLs []string) { + d.registryURLs = registryURLs +} diff --git a/vendor/github.com/devfile/library/pkg/devfile/parser/data/interface.go b/vendor/github.com/devfile/library/pkg/devfile/parser/data/interface.go index 732a15cfa5f..ecbeb90a02d 100644 --- a/vendor/github.com/devfile/library/pkg/devfile/parser/data/interface.go +++ b/vendor/github.com/devfile/library/pkg/devfile/parser/data/interface.go @@ -11,7 +11,7 @@ type DevfileData interface { GetSchemaVersion() string SetSchemaVersion(version string) GetMetadata() devfilepkg.DevfileMetadata - SetMetadata(name, version string) + SetMetadata(metadata devfilepkg.DevfileMetadata) // parent related methods GetParent() *v1.Parent @@ -26,32 +26,36 @@ type DevfileData interface { GetComponents(common.DevfileOptions) ([]v1.Component, error) AddComponents(components []v1.Component) error UpdateComponent(component v1.Component) + DeleteComponent(name string) error // project related methods GetProjects(common.DevfileOptions) ([]v1.Project, error) AddProjects(projects []v1.Project) error UpdateProject(project v1.Project) + DeleteProject(name string) error // starter projects related commands GetStarterProjects(common.DevfileOptions) ([]v1.StarterProject, error) AddStarterProjects(projects []v1.StarterProject) error UpdateStarterProject(project v1.StarterProject) + DeleteStarterProject(name string) error // command related methods GetCommands(common.DevfileOptions) ([]v1.Command, error) - AddCommands(commands ...v1.Command) error + AddCommands(commands []v1.Command) error UpdateCommand(command v1.Command) + DeleteCommand(id string) error - // volume related methods - AddVolume(volume v1.Component, path string) error - DeleteVolume(name string) error - GetVolumeMountPath(name string) (string, error) + // volume mount related methods + AddVolumeMounts(containerName string, volumeMounts []v1.VolumeMount) error + DeleteVolumeMount(name string) error + GetVolumeMountPaths(mountName, containerName string) ([]string, error) // workspace related methods GetDevfileWorkspace() *v1.DevWorkspaceTemplateSpecContent SetDevfileWorkspace(content v1.DevWorkspaceTemplateSpecContent) - //utils + // utils GetDevfileContainerComponents(common.DevfileOptions) ([]v1.Component, error) GetDevfileVolumeComponents(common.DevfileOptions) ([]v1.Component, error) } diff --git a/vendor/github.com/devfile/library/pkg/devfile/parser/data/v2/2.1.0/devfileJsonSchema210.go b/vendor/github.com/devfile/library/pkg/devfile/parser/data/v2/2.1.0/devfileJsonSchema210.go index 5bf3fac4829..2ae44959e4f 100644 --- a/vendor/github.com/devfile/library/pkg/devfile/parser/data/v2/2.1.0/devfileJsonSchema210.go +++ b/vendor/github.com/devfile/library/pkg/devfile/parser/data/v2/2.1.0/devfileJsonSchema210.go @@ -28,16 +28,6 @@ const JsonSchema210 = `{ "apply" ] }, - { - "required": [ - "vscodeTask" - ] - }, - { - "required": [ - "vscodeLaunch" - ] - }, { "required": [ "composite" @@ -218,108 +208,6 @@ const JsonSchema210 = `{ "type": "string", "maxLength": 63, "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" - }, - "vscodeLaunch": { - "description": "Command providing the definition of a VsCode launch action", - "type": "object", - "oneOf": [ - { - "required": [ - "uri" - ] - }, - { - "required": [ - "inlined" - ] - } - ], - "properties": { - "group": { - "description": "Defines the group this command is part of", - "type": "object", - "required": [ - "kind" - ], - "properties": { - "isDefault": { - "description": "Identifies the default command for a given group kind", - "type": "boolean" - }, - "kind": { - "description": "Kind of group the command is part of", - "type": "string", - "enum": [ - "build", - "run", - "test", - "debug" - ] - } - }, - "additionalProperties": false - }, - "inlined": { - "description": "Inlined content of the VsCode configuration", - "type": "string" - }, - "uri": { - "description": "Location as an absolute of relative URI the VsCode configuration will be fetched from", - "type": "string" - } - }, - "additionalProperties": false - }, - "vscodeTask": { - "description": "Command providing the definition of a VsCode Task", - "type": "object", - "oneOf": [ - { - "required": [ - "uri" - ] - }, - { - "required": [ - "inlined" - ] - } - ], - "properties": { - "group": { - "description": "Defines the group this command is part of", - "type": "object", - "required": [ - "kind" - ], - "properties": { - "isDefault": { - "description": "Identifies the default command for a given group kind", - "type": "boolean" - }, - "kind": { - "description": "Kind of group the command is part of", - "type": "string", - "enum": [ - "build", - "run", - "test", - "debug" - ] - } - }, - "additionalProperties": false - }, - "inlined": { - "description": "Inlined content of the VsCode configuration", - "type": "string" - }, - "uri": { - "description": "Location as an absolute of relative URI the VsCode configuration will be fetched from", - "type": "string" - } - }, - "additionalProperties": false } }, "additionalProperties": false @@ -733,16 +621,6 @@ const JsonSchema210 = `{ "apply" ] }, - { - "required": [ - "vscodeTask" - ] - }, - { - "required": [ - "vscodeLaunch" - ] - }, { "required": [ "composite" @@ -906,102 +784,6 @@ const JsonSchema210 = `{ "type": "string", "maxLength": 63, "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" - }, - "vscodeLaunch": { - "description": "Command providing the definition of a VsCode launch action", - "type": "object", - "oneOf": [ - { - "required": [ - "uri" - ] - }, - { - "required": [ - "inlined" - ] - } - ], - "properties": { - "group": { - "description": "Defines the group this command is part of", - "type": "object", - "properties": { - "isDefault": { - "description": "Identifies the default command for a given group kind", - "type": "boolean" - }, - "kind": { - "description": "Kind of group the command is part of", - "type": "string", - "enum": [ - "build", - "run", - "test", - "debug" - ] - } - }, - "additionalProperties": false - }, - "inlined": { - "description": "Inlined content of the VsCode configuration", - "type": "string" - }, - "uri": { - "description": "Location as an absolute of relative URI the VsCode configuration will be fetched from", - "type": "string" - } - }, - "additionalProperties": false - }, - "vscodeTask": { - "description": "Command providing the definition of a VsCode Task", - "type": "object", - "oneOf": [ - { - "required": [ - "uri" - ] - }, - { - "required": [ - "inlined" - ] - } - ], - "properties": { - "group": { - "description": "Defines the group this command is part of", - "type": "object", - "properties": { - "isDefault": { - "description": "Identifies the default command for a given group kind", - "type": "boolean" - }, - "kind": { - "description": "Kind of group the command is part of", - "type": "string", - "enum": [ - "build", - "run", - "test", - "debug" - ] - } - }, - "additionalProperties": false - }, - "inlined": { - "description": "Inlined content of the VsCode configuration", - "type": "string" - }, - "uri": { - "description": "Location as an absolute of relative URI the VsCode configuration will be fetched from", - "type": "string" - } - }, - "additionalProperties": false } }, "additionalProperties": false @@ -1360,6 +1142,10 @@ const JsonSchema210 = `{ "description": "Allows specifying the definition of a volume shared by several other components", "type": "object", "properties": { + "ephemeral": { + "description": "Ephemeral volumes are not stored persistently across restarts. Defaults to false", + "type": "boolean" + }, "size": { "description": "Size of the volume", "type": "string" @@ -1405,6 +1191,10 @@ const JsonSchema210 = `{ "description": "Allows specifying the definition of a volume shared by several other components", "type": "object", "properties": { + "ephemeral": { + "description": "Ephemeral volumes are not stored persistently across restarts. Defaults to false", + "type": "boolean" + }, "size": { "description": "Size of the volume", "type": "string" @@ -1476,10 +1266,18 @@ const JsonSchema210 = `{ "description": "Optional devfile icon", "type": "string" }, + "language": { + "description": "Optional devfile language", + "type": "string" + }, "name": { "description": "Optional devfile name", "type": "string" }, + "projectType": { + "description": "Optional devfile project type", + "type": "string" + }, "tags": { "description": "Optional devfile tags", "type": "array", @@ -1491,6 +1289,10 @@ const JsonSchema210 = `{ "description": "Optional semver-compatible version", "type": "string", "pattern": "^([0-9]+)\\.([0-9]+)\\.([0-9]+)(\\-[0-9a-z-]+(\\.[0-9a-z-]+)*)?(\\+[0-9A-Za-z-]+(\\.[0-9A-Za-z-]+)*)?$" + }, + "website": { + "description": "Optional devfile website", + "type": "string" } }, "additionalProperties": true @@ -1535,16 +1337,6 @@ const JsonSchema210 = `{ "apply" ] }, - { - "required": [ - "vscodeTask" - ] - }, - { - "required": [ - "vscodeLaunch" - ] - }, { "required": [ "composite" @@ -1708,102 +1500,6 @@ const JsonSchema210 = `{ "type": "string", "maxLength": 63, "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" - }, - "vscodeLaunch": { - "description": "Command providing the definition of a VsCode launch action", - "type": "object", - "oneOf": [ - { - "required": [ - "uri" - ] - }, - { - "required": [ - "inlined" - ] - } - ], - "properties": { - "group": { - "description": "Defines the group this command is part of", - "type": "object", - "properties": { - "isDefault": { - "description": "Identifies the default command for a given group kind", - "type": "boolean" - }, - "kind": { - "description": "Kind of group the command is part of", - "type": "string", - "enum": [ - "build", - "run", - "test", - "debug" - ] - } - }, - "additionalProperties": false - }, - "inlined": { - "description": "Inlined content of the VsCode configuration", - "type": "string" - }, - "uri": { - "description": "Location as an absolute of relative URI the VsCode configuration will be fetched from", - "type": "string" - } - }, - "additionalProperties": false - }, - "vscodeTask": { - "description": "Command providing the definition of a VsCode Task", - "type": "object", - "oneOf": [ - { - "required": [ - "uri" - ] - }, - { - "required": [ - "inlined" - ] - } - ], - "properties": { - "group": { - "description": "Defines the group this command is part of", - "type": "object", - "properties": { - "isDefault": { - "description": "Identifies the default command for a given group kind", - "type": "boolean" - }, - "kind": { - "description": "Kind of group the command is part of", - "type": "string", - "enum": [ - "build", - "run", - "test", - "debug" - ] - } - }, - "additionalProperties": false - }, - "inlined": { - "description": "Inlined content of the VsCode configuration", - "type": "string" - }, - "uri": { - "description": "Location as an absolute of relative URI the VsCode configuration will be fetched from", - "type": "string" - } - }, - "additionalProperties": false } }, "additionalProperties": false @@ -2203,16 +1899,6 @@ const JsonSchema210 = `{ "apply" ] }, - { - "required": [ - "vscodeTask" - ] - }, - { - "required": [ - "vscodeLaunch" - ] - }, { "required": [ "composite" @@ -2376,102 +2062,6 @@ const JsonSchema210 = `{ "type": "string", "maxLength": 63, "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" - }, - "vscodeLaunch": { - "description": "Command providing the definition of a VsCode launch action", - "type": "object", - "oneOf": [ - { - "required": [ - "uri" - ] - }, - { - "required": [ - "inlined" - ] - } - ], - "properties": { - "group": { - "description": "Defines the group this command is part of", - "type": "object", - "properties": { - "isDefault": { - "description": "Identifies the default command for a given group kind", - "type": "boolean" - }, - "kind": { - "description": "Kind of group the command is part of", - "type": "string", - "enum": [ - "build", - "run", - "test", - "debug" - ] - } - }, - "additionalProperties": false - }, - "inlined": { - "description": "Inlined content of the VsCode configuration", - "type": "string" - }, - "uri": { - "description": "Location as an absolute of relative URI the VsCode configuration will be fetched from", - "type": "string" - } - }, - "additionalProperties": false - }, - "vscodeTask": { - "description": "Command providing the definition of a VsCode Task", - "type": "object", - "oneOf": [ - { - "required": [ - "uri" - ] - }, - { - "required": [ - "inlined" - ] - } - ], - "properties": { - "group": { - "description": "Defines the group this command is part of", - "type": "object", - "properties": { - "isDefault": { - "description": "Identifies the default command for a given group kind", - "type": "boolean" - }, - "kind": { - "description": "Kind of group the command is part of", - "type": "string", - "enum": [ - "build", - "run", - "test", - "debug" - ] - } - }, - "additionalProperties": false - }, - "inlined": { - "description": "Inlined content of the VsCode configuration", - "type": "string" - }, - "uri": { - "description": "Location as an absolute of relative URI the VsCode configuration will be fetched from", - "type": "string" - } - }, - "additionalProperties": false } }, "additionalProperties": false @@ -2830,6 +2420,10 @@ const JsonSchema210 = `{ "description": "Allows specifying the definition of a volume shared by several other components", "type": "object", "properties": { + "ephemeral": { + "description": "Ephemeral volumes are not stored persistently across restarts. Defaults to false", + "type": "boolean" + }, "size": { "description": "Size of the volume", "type": "string" @@ -2872,6 +2466,10 @@ const JsonSchema210 = `{ "description": "Allows specifying the definition of a volume shared by several other components", "type": "object", "properties": { + "ephemeral": { + "description": "Ephemeral volumes are not stored persistently across restarts. Defaults to false", + "type": "boolean" + }, "size": { "description": "Size of the volume", "type": "string" @@ -2968,7 +2566,7 @@ const JsonSchema210 = `{ "additionalProperties": false }, "github": { - "description": "Project's GitHub source", + "description": "Project's GitHub source. Deprecated, use 'Git' instead", "type": "object", "properties": { "checkoutFrom": { @@ -3092,7 +2690,7 @@ const JsonSchema210 = `{ "additionalProperties": false }, "github": { - "description": "Project's GitHub source", + "description": "Project's GitHub source. Deprecated, use 'Git' instead", "type": "object", "properties": { "checkoutFrom": { @@ -3220,7 +2818,7 @@ const JsonSchema210 = `{ "additionalProperties": false }, "github": { - "description": "Project's GitHub source", + "description": "Project's GitHub source. Deprecated, use 'Git' instead", "type": "object", "required": [ "remotes" @@ -3352,7 +2950,7 @@ const JsonSchema210 = `{ "additionalProperties": false }, "github": { - "description": "Project's GitHub source", + "description": "Project's GitHub source. Deprecated, use 'Git' instead", "type": "object", "required": [ "remotes" diff --git a/vendor/github.com/devfile/library/pkg/devfile/parser/data/v2/commands.go b/vendor/github.com/devfile/library/pkg/devfile/parser/data/v2/commands.go index d308713c19e..9ebb735f5a5 100644 --- a/vendor/github.com/devfile/library/pkg/devfile/parser/data/v2/commands.go +++ b/vendor/github.com/devfile/library/pkg/devfile/parser/data/v2/commands.go @@ -1,6 +1,7 @@ package v2 import ( + "reflect" "strings" v1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" @@ -9,21 +10,40 @@ import ( // GetCommands returns the slice of Command objects parsed from the Devfile func (d *DevfileV2) GetCommands(options common.DevfileOptions) ([]v1.Command, error) { - if len(options.Filter) == 0 { + + if reflect.DeepEqual(options, common.DevfileOptions{}) { return d.Commands, nil } var commands []v1.Command for _, command := range d.Commands { + // Filter Command Attributes filterIn, err := common.FilterDevfileObject(command.Attributes, options) if err != nil { return nil, err + } else if !filterIn { + continue + } + + // Filter Command Type - Exec, Composite, etc. + commandType, err := common.GetCommandType(command) + if err != nil { + return nil, err + } + if options.CommandOptions.CommandType != "" && commandType != options.CommandOptions.CommandType { + continue } - if filterIn { - command.Id = strings.ToLower(command.Id) - commands = append(commands, command) + // Filter Command Group Kind - Run, Build, etc. + commandGroup := common.GetGroup(command) + // exclude conditions: + // 1. options group is present and command group is present but does not match + // 2. options group is present and command group is not present + if options.CommandOptions.CommandGroupKind != "" && ((commandGroup != nil && options.CommandOptions.CommandGroupKind != commandGroup.Kind) || commandGroup == nil) { + continue } + + commands = append(commands, command) } return commands, nil @@ -31,14 +51,10 @@ func (d *DevfileV2) GetCommands(options common.DevfileOptions) ([]v1.Command, er // AddCommands adds the slice of Command objects to the Devfile's commands // if a command is already defined, error out -func (d *DevfileV2) AddCommands(commands ...v1.Command) error { - devfileCommands, err := d.GetCommands(common.DevfileOptions{}) - if err != nil { - return err - } +func (d *DevfileV2) AddCommands(commands []v1.Command) error { for _, command := range commands { - for _, devfileCommand := range devfileCommands { + for _, devfileCommand := range d.Commands { if command.Id == devfileCommand.Id { return &common.FieldAlreadyExistError{Name: command.Id, Field: "command"} } @@ -57,3 +73,19 @@ func (d *DevfileV2) UpdateCommand(command v1.Command) { } } } + +// DeleteCommand removes the specified command +func (d *DevfileV2) DeleteCommand(id string) error { + + for i := range d.Commands { + if d.Commands[i].Id == id { + d.Commands = append(d.Commands[:i], d.Commands[i+1:]...) + return nil + } + } + + return &common.FieldNotFoundError{ + Field: "command", + Name: id, + } +} diff --git a/vendor/github.com/devfile/library/pkg/devfile/parser/data/v2/common/command_helper.go b/vendor/github.com/devfile/library/pkg/devfile/parser/data/v2/common/command_helper.go index 728ce29dad1..6a93ac93fed 100644 --- a/vendor/github.com/devfile/library/pkg/devfile/parser/data/v2/common/command_helper.go +++ b/vendor/github.com/devfile/library/pkg/devfile/parser/data/v2/common/command_helper.go @@ -1,6 +1,8 @@ package common import ( + "fmt" + v1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" ) @@ -13,10 +15,6 @@ func GetGroup(dc v1.Command) *v1.CommandGroup { return dc.Exec.Group case dc.Apply != nil: return dc.Apply.Group - case dc.VscodeLaunch != nil: - return dc.VscodeLaunch.Group - case dc.VscodeTask != nil: - return dc.VscodeTask.Group case dc.Custom != nil: return dc.Custom.Group @@ -51,3 +49,20 @@ func GetExecWorkingDir(dc v1.Command) string { return "" } + +// GetCommandType returns the command type of a given command +func GetCommandType(command v1.Command) (v1.CommandType, error) { + switch { + case command.Apply != nil: + return v1.ApplyCommandType, nil + case command.Composite != nil: + return v1.CompositeCommandType, nil + case command.Exec != nil: + return v1.ExecCommandType, nil + case command.Custom != nil: + return v1.CustomCommandType, nil + + default: + return "", fmt.Errorf("unknown command type") + } +} diff --git a/vendor/github.com/devfile/library/pkg/devfile/parser/data/v2/common/options.go b/vendor/github.com/devfile/library/pkg/devfile/parser/data/v2/common/options.go index 14d595d0153..b144315147d 100644 --- a/vendor/github.com/devfile/library/pkg/devfile/parser/data/v2/common/options.go +++ b/vendor/github.com/devfile/library/pkg/devfile/parser/data/v2/common/options.go @@ -3,13 +3,46 @@ package common import ( "reflect" + v1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" apiAttributes "github.com/devfile/api/v2/pkg/attributes" ) // DevfileOptions provides options for Devfile operations type DevfileOptions struct { - // Filter is a map that lets you filter devfile object against their attributes. Interface can be string, float, boolean or a map + // Filter is a map that lets filter devfile object against their attributes. Interface can be string, float, boolean or a map Filter map[string]interface{} + + // CommandOptions specifies the various options available to filter commands + CommandOptions CommandOptions + + // ComponentOptions specifies the various options available to filter components + ComponentOptions ComponentOptions + + // ProjectOptions specifies the various options available to filter projects/starterProjects + ProjectOptions ProjectOptions +} + +// CommandOptions specifies the various options available to filter commands +type CommandOptions struct { + // CommandGroupKind is an option that allows to filter command based on their kind + CommandGroupKind v1.CommandGroupKind + + // CommandType is an option that allows to filter command based on their type + CommandType v1.CommandType +} + +// ComponentOptions specifies the various options available to filter components +type ComponentOptions struct { + + // ComponentType is an option that allows to filter component based on their type + ComponentType v1.ComponentType +} + +// ProjectOptions specifies the various options available to filter projects/starterProjects +type ProjectOptions struct { + + // ProjectSourceType is an option that allows to filter project based on their source type + ProjectSourceType v1.ProjectSourceType } // FilterDevfileObject filters devfile attributes with the given options diff --git a/vendor/github.com/devfile/library/pkg/devfile/parser/data/v2/common/project_helper.go b/vendor/github.com/devfile/library/pkg/devfile/parser/data/v2/common/project_helper.go index 4028779b5c6..fe2d0859314 100644 --- a/vendor/github.com/devfile/library/pkg/devfile/parser/data/v2/common/project_helper.go +++ b/vendor/github.com/devfile/library/pkg/devfile/parser/data/v2/common/project_helper.go @@ -41,3 +41,20 @@ func GetDefaultSource(ps v1.GitLikeProjectSource) (remoteName string, remoteURL return remoteName, remoteURL, revision, err } + +// GetProjectSourceType returns the source type of a given project source +func GetProjectSourceType(projectSrc v1.ProjectSource) (v1.ProjectSourceType, error) { + switch { + case projectSrc.Git != nil: + return v1.GitProjectSourceType, nil + case projectSrc.Github != nil: + return v1.GitHubProjectSourceType, nil + case projectSrc.Zip != nil: + return v1.ZipProjectSourceType, nil + case projectSrc.Custom != nil: + return v1.CustomProjectSourceType, nil + + default: + return "", fmt.Errorf("unknown project source type") + } +} diff --git a/vendor/github.com/devfile/library/pkg/devfile/parser/data/v2/components.go b/vendor/github.com/devfile/library/pkg/devfile/parser/data/v2/components.go index 2da15b73606..372ba80f8ba 100644 --- a/vendor/github.com/devfile/library/pkg/devfile/parser/data/v2/components.go +++ b/vendor/github.com/devfile/library/pkg/devfile/parser/data/v2/components.go @@ -1,32 +1,46 @@ package v2 import ( + "reflect" + v1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" "github.com/devfile/library/pkg/devfile/parser/data/v2/common" ) // GetComponents returns the slice of Component objects parsed from the Devfile func (d *DevfileV2) GetComponents(options common.DevfileOptions) ([]v1.Component, error) { - if len(options.Filter) == 0 { + + if reflect.DeepEqual(options, common.DevfileOptions{}) { return d.Components, nil } var components []v1.Component - for _, comp := range d.Components { - filterIn, err := common.FilterDevfileObject(comp.Attributes, options) + for _, component := range d.Components { + // Filter Component Attributes + filterIn, err := common.FilterDevfileObject(component.Attributes, options) if err != nil { return nil, err + } else if !filterIn { + continue } - if filterIn { - components = append(components, comp) + // Filter Component Type - Container, Volume, etc. + componentType, err := common.GetComponentType(component) + if err != nil { + return nil, err + } + if options.ComponentOptions.ComponentType != "" && componentType != options.ComponentOptions.ComponentType { + continue } + + components = append(components, component) } return components, nil } -// GetDevfileContainerComponents iterates through the components in the devfile and returns a list of devfile container components +// GetDevfileContainerComponents iterates through the components in the devfile and returns a list of devfile container components. +// Deprecated, use GetComponents() with the DevfileOptions. func (d *DevfileV2) GetDevfileContainerComponents(options common.DevfileOptions) ([]v1.Component, error) { var components []v1.Component devfileComponents, err := d.GetComponents(options) @@ -41,7 +55,8 @@ func (d *DevfileV2) GetDevfileContainerComponents(options common.DevfileOptions) return components, nil } -// GetDevfileVolumeComponents iterates through the components in the devfile and returns a list of devfile volume components +// GetDevfileVolumeComponents iterates through the components in the devfile and returns a list of devfile volume components. +// Deprecated, use GetComponents() with the DevfileOptions. func (d *DevfileV2) GetDevfileVolumeComponents(options common.DevfileOptions) ([]v1.Component, error) { var components []v1.Component devfileComponents, err := d.GetComponents(options) @@ -60,17 +75,13 @@ func (d *DevfileV2) GetDevfileVolumeComponents(options common.DevfileOptions) ([ // if a component is already defined, error out func (d *DevfileV2) AddComponents(components []v1.Component) error { - componentMap := make(map[string]bool) - - for _, component := range d.Components { - componentMap[component.Name] = true - } for _, component := range components { - if _, ok := componentMap[component.Name]; !ok { - d.Components = append(d.Components, component) - } else { - return &common.FieldAlreadyExistError{Name: component.Name, Field: "component"} + for _, devfileComponent := range d.Components { + if component.Name == devfileComponent.Name { + return &common.FieldAlreadyExistError{Name: component.Name, Field: "component"} + } } + d.Components = append(d.Components, component) } return nil } @@ -88,3 +99,19 @@ func (d *DevfileV2) UpdateComponent(component v1.Component) { d.Components[index] = component } } + +// DeleteComponent removes the specified component +func (d *DevfileV2) DeleteComponent(name string) error { + + for i := range d.Components { + if d.Components[i].Name == name { + d.Components = append(d.Components[:i], d.Components[i+1:]...) + return nil + } + } + + return &common.FieldNotFoundError{ + Field: "component", + Name: name, + } +} diff --git a/vendor/github.com/devfile/library/pkg/devfile/parser/data/v2/events.go b/vendor/github.com/devfile/library/pkg/devfile/parser/data/v2/events.go index 621a8d05c94..eb3e386465f 100644 --- a/vendor/github.com/devfile/library/pkg/devfile/parser/data/v2/events.go +++ b/vendor/github.com/devfile/library/pkg/devfile/parser/data/v2/events.go @@ -16,6 +16,11 @@ func (d *DevfileV2) GetEvents() v1.Events { // AddEvents adds the Events Object to the devfile's events // if the event is already defined in the devfile, error out func (d *DevfileV2) AddEvents(events v1.Events) error { + + if d.Events == nil { + d.Events = &v1.Events{} + } + if len(events.PreStop) > 0 { if len(d.Events.PreStop) > 0 { return &common.FieldAlreadyExistError{Field: "pre stop"} @@ -50,16 +55,21 @@ func (d *DevfileV2) AddEvents(events v1.Events) error { // UpdateEvents updates the devfile's events // it only updates the events passed to it func (d *DevfileV2) UpdateEvents(postStart, postStop, preStart, preStop []string) { - if len(postStart) != 0 { + + if d.Events == nil { + d.Events = &v1.Events{} + } + + if postStart != nil { d.Events.PostStart = postStart } - if len(postStop) != 0 { + if postStop != nil { d.Events.PostStop = postStop } - if len(preStart) != 0 { + if preStart != nil { d.Events.PreStart = preStart } - if len(preStop) != 0 { + if preStop != nil { d.Events.PreStop = preStop } } diff --git a/vendor/github.com/devfile/library/pkg/devfile/parser/data/v2/header.go b/vendor/github.com/devfile/library/pkg/devfile/parser/data/v2/header.go index 6798ec95d40..b005fd447bd 100644 --- a/vendor/github.com/devfile/library/pkg/devfile/parser/data/v2/header.go +++ b/vendor/github.com/devfile/library/pkg/devfile/parser/data/v2/header.go @@ -20,9 +20,6 @@ func (d *DevfileV2) GetMetadata() devfilepkg.DevfileMetadata { } // SetMetadata sets the metadata for devfile -func (d *DevfileV2) SetMetadata(name, version string) { - d.Metadata = devfilepkg.DevfileMetadata{ - Name: name, - Version: version, - } +func (d *DevfileV2) SetMetadata(metadata devfilepkg.DevfileMetadata) { + d.Metadata = metadata } diff --git a/vendor/github.com/devfile/library/pkg/devfile/parser/data/v2/projects.go b/vendor/github.com/devfile/library/pkg/devfile/parser/data/v2/projects.go index 01bee4debc3..20871c9837c 100644 --- a/vendor/github.com/devfile/library/pkg/devfile/parser/data/v2/projects.go +++ b/vendor/github.com/devfile/library/pkg/devfile/parser/data/v2/projects.go @@ -1,6 +1,7 @@ package v2 import ( + "reflect" "strings" v1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" @@ -9,20 +10,31 @@ import ( // GetProjects returns the Project Object parsed from devfile func (d *DevfileV2) GetProjects(options common.DevfileOptions) ([]v1.Project, error) { - if len(options.Filter) == 0 { + + if reflect.DeepEqual(options, common.DevfileOptions{}) { return d.Projects, nil } var projects []v1.Project - for _, proj := range d.Projects { - filterIn, err := common.FilterDevfileObject(proj.Attributes, options) + for _, project := range d.Projects { + // Filter Project Attributes + filterIn, err := common.FilterDevfileObject(project.Attributes, options) if err != nil { return nil, err + } else if !filterIn { + continue } - if filterIn { - projects = append(projects, proj) + // Filter Project Source Type - Git, Zip, etc. + projectSourceType, err := common.GetProjectSourceType(project.ProjectSource) + if err != nil { + return nil, err } + if options.ProjectOptions.ProjectSourceType != "" && projectSourceType != options.ProjectOptions.ProjectSourceType { + continue + } + + projects = append(projects, project) } return projects, nil @@ -55,22 +67,49 @@ func (d *DevfileV2) UpdateProject(project v1.Project) { } } +// DeleteProject removes the specified project +func (d *DevfileV2) DeleteProject(name string) error { + + for i := range d.Projects { + if d.Projects[i].Name == name { + d.Projects = append(d.Projects[:i], d.Projects[i+1:]...) + return nil + } + } + + return &common.FieldNotFoundError{ + Field: "project", + Name: name, + } +} + //GetStarterProjects returns the DevfileStarterProject parsed from devfile func (d *DevfileV2) GetStarterProjects(options common.DevfileOptions) ([]v1.StarterProject, error) { - if len(options.Filter) == 0 { + + if reflect.DeepEqual(options, common.DevfileOptions{}) { return d.StarterProjects, nil } var starterProjects []v1.StarterProject - for _, starterProj := range d.StarterProjects { - filterIn, err := common.FilterDevfileObject(starterProj.Attributes, options) + for _, starterProject := range d.StarterProjects { + // Filter Starter Project Attributes + filterIn, err := common.FilterDevfileObject(starterProject.Attributes, options) if err != nil { return nil, err + } else if !filterIn { + continue } - if filterIn { - starterProjects = append(starterProjects, starterProj) + // Filter Starter Project Source Type - Git, Zip, etc. + starterProjectSourceType, err := common.GetProjectSourceType(starterProject.ProjectSource) + if err != nil { + return nil, err + } + if options.ProjectOptions.ProjectSourceType != "" && starterProjectSourceType != options.ProjectOptions.ProjectSourceType { + continue } + + starterProjects = append(starterProjects, starterProject) } return starterProjects, nil @@ -102,3 +141,19 @@ func (d *DevfileV2) UpdateStarterProject(project v1.StarterProject) { } } } + +// DeleteStarterProject removes the specified starter project +func (d *DevfileV2) DeleteStarterProject(name string) error { + + for i := range d.StarterProjects { + if d.StarterProjects[i].Name == name { + d.StarterProjects = append(d.StarterProjects[:i], d.StarterProjects[i+1:]...) + return nil + } + } + + return &common.FieldNotFoundError{ + Field: "starter project", + Name: name, + } +} diff --git a/vendor/github.com/devfile/library/pkg/devfile/parser/data/v2/volumes.go b/vendor/github.com/devfile/library/pkg/devfile/parser/data/v2/volumes.go index 2a3380069ee..37a29fc70d2 100644 --- a/vendor/github.com/devfile/library/pkg/devfile/parser/data/v2/volumes.go +++ b/vendor/github.com/devfile/library/pkg/devfile/parser/data/v2/volumes.go @@ -8,67 +8,61 @@ import ( "github.com/devfile/library/pkg/devfile/parser/data/v2/common" ) -// AddVolume adds the volume to the devFile and mounts it to all the container components -func (d *DevfileV2) AddVolume(volumeComponent v1.Component, path string) error { - volumeExists := false +// AddVolumeMounts adds the volume mounts to the specified container component +func (d *DevfileV2) AddVolumeMounts(containerName string, volumeMounts []v1.VolumeMount) error { var pathErrorContainers []string + found := false for _, component := range d.Components { - if component.Container != nil { - for _, volumeMount := range component.Container.VolumeMounts { - if volumeMount.Path == path { - var err = fmt.Errorf("another volume, %s, is mounted to the same path: %s, on the container: %s", volumeMount.Name, path, component.Name) - pathErrorContainers = append(pathErrorContainers, err.Error()) + if component.Container != nil && component.Name == containerName { + found = true + for _, devfileVolumeMount := range component.Container.VolumeMounts { + for _, volumeMount := range volumeMounts { + if devfileVolumeMount.Path == volumeMount.Path { + pathErrorContainers = append(pathErrorContainers, fmt.Sprintf("unable to mount volume %s, as another volume %s is mounted to the same path %s in the container %s", volumeMount.Name, devfileVolumeMount.Name, volumeMount.Path, component.Name)) + } } } - component.Container.VolumeMounts = append(component.Container.VolumeMounts, v1.VolumeMount{ - Name: volumeComponent.Name, - Path: path, - }) - } else if component.Volume != nil && component.Name == volumeComponent.Name { - volumeExists = true - break + if len(pathErrorContainers) == 0 { + component.Container.VolumeMounts = append(component.Container.VolumeMounts, volumeMounts...) + } } } - if volumeExists { - return &common.FieldAlreadyExistError{ - Field: "volume", - Name: volumeComponent.Name, + if !found { + return &common.FieldNotFoundError{ + Field: "container component", + Name: containerName, } } if len(pathErrorContainers) > 0 { - return fmt.Errorf("errors while creating volume:\n%s", strings.Join(pathErrorContainers, "\n")) + return fmt.Errorf("errors while adding volume mounts:\n%s", strings.Join(pathErrorContainers, "\n")) } - d.Components = append(d.Components, volumeComponent) - return nil } -// DeleteVolume removes the volume from the devFile and removes all the related volume mounts -func (d *DevfileV2) DeleteVolume(name string) error { +// DeleteVolumeMount deletes the volume mount from container components +func (d *DevfileV2) DeleteVolumeMount(name string) error { found := false - for i := len(d.Components) - 1; i >= 0; i-- { - if d.Components[i].Container != nil { - var tmp []v1.VolumeMount - for _, volumeMount := range d.Components[i].Container.VolumeMounts { - if volumeMount.Name != name { - tmp = append(tmp, volumeMount) + for i := range d.Components { + if d.Components[i].Container != nil && d.Components[i].Name != name { + // Volume Mounts can have multiple instances of a volume mounted at different paths + // As arrays are rearraged/shifted for deletion, we lose one element every time there is a match + // Looping backward is efficient, otherwise we would have to manually decrement counter + // if we looped forward + for j := len(d.Components[i].Container.VolumeMounts) - 1; j >= 0; j-- { + if d.Components[i].Container.VolumeMounts[j].Name == name { + found = true + d.Components[i].Container.VolumeMounts = append(d.Components[i].Container.VolumeMounts[:j], d.Components[i].Container.VolumeMounts[j+1:]...) } } - d.Components[i].Container.VolumeMounts = tmp - } else if d.Components[i].Volume != nil { - if d.Components[i].Name == name { - found = true - d.Components = append(d.Components[:i], d.Components[i+1:]...) - } } } if !found { return &common.FieldNotFoundError{ - Field: "volume", + Field: "volume mount", Name: name, } } @@ -76,31 +70,33 @@ func (d *DevfileV2) DeleteVolume(name string) error { return nil } -// GetVolumeMountPath gets the mount path of the required volume -func (d *DevfileV2) GetVolumeMountPath(name string) (string, error) { - volumeFound := false - mountFound := false - path := "" +// GetVolumeMountPaths gets all the mount paths of the specified volume mount from the specified container component. +// A container can mount at different paths for a given volume. +func (d *DevfileV2) GetVolumeMountPaths(mountName, containerName string) ([]string, error) { + componentFound := false + var mountPaths []string for _, component := range d.Components { - if component.Container != nil { + if component.Container != nil && component.Name == containerName { + componentFound = true for _, volumeMount := range component.Container.VolumeMounts { - if volumeMount.Name == name { - mountFound = true - path = volumeMount.Path + if volumeMount.Name == mountName { + mountPaths = append(mountPaths, volumeMount.Path) } } - } else if component.Volume != nil { - volumeFound = true } } - if volumeFound && mountFound { - return path, nil - } else if !mountFound && volumeFound { - return "", fmt.Errorf("volume not mounted to any component") + + if !componentFound { + return mountPaths, &common.FieldNotFoundError{ + Field: "container component", + Name: containerName, + } } - return "", &common.FieldNotFoundError{ - Field: "volume", - Name: "name", + + if len(mountPaths) == 0 { + return mountPaths, fmt.Errorf("volume %s not mounted to component %s", mountName, containerName) } + + return mountPaths, nil } diff --git a/vendor/github.com/devfile/library/pkg/devfile/parser/parse.go b/vendor/github.com/devfile/library/pkg/devfile/parser/parse.go index a86459b4d36..38398f687d3 100644 --- a/vendor/github.com/devfile/library/pkg/devfile/parser/parse.go +++ b/vendor/github.com/devfile/library/pkg/devfile/parser/parse.go @@ -3,6 +3,7 @@ package parser import ( "encoding/json" "fmt" + "github.com/devfile/library/pkg/util" "net/url" "path" "strings" @@ -48,83 +49,134 @@ func parseDevfile(d DevfileObj, flattenedDevfile bool) (DevfileObj, error) { return DevfileObj{}, err } } - for uri := range devfileCtx.URIMap { - delete(devfileCtx.URIMap, uri) - } + // Successful return d, nil } -// Parse func populates the flattened devfile data, parses and validates the devfile integrity. +// ParserArgs is the struct to pass into parser functions which contains required info for parsing devfile. +// It accepts devfile path, devfile URL or devfile content in []byte format. +type ParserArgs struct { + // Path is a relative or absolute devfile path. + Path string + // URL is the URL address of the specific devfile. + URL string + // Data is the devfile content in []byte format. + Data []byte + // FlattenedDevfile defines if the returned devfileObj is flattened content (true) or raw content (false). + // The value is default to be true. + FlattenedDevfile *bool + // RegistryURLs is a list of registry hosts which parser should pull parent devfile from. + // If registryUrl is defined in devfile, this list will be ignored. + RegistryURLs []string +} + +// ParseDevfile func populates the devfile data, parses and validates the devfile integrity. // Creates devfile context and runtime objects -func Parse(path string) (d DevfileObj, err error) { +func ParseDevfile(args ParserArgs) (d DevfileObj, err error) { + if args.Data != nil { + d.Ctx = devfileCtx.DevfileCtx{} + err = d.Ctx.SetDevfileContentFromBytes(args.Data) + if err != nil { + return d, errors.Wrap(err, "failed to set devfile content from bytes") + } + } else if args.Path != "" { + d.Ctx = devfileCtx.NewDevfileCtx(args.Path) + } else if args.URL != "" { + d.Ctx = devfileCtx.NewURLDevfileCtx(args.URL) + } else { + return d, errors.Wrap(err, "the devfile source is not provided") + } - // NewDevfileCtx - d.Ctx = devfileCtx.NewDevfileCtx(path) + if args.RegistryURLs != nil { + d.Ctx.SetRegistryURLs(args.RegistryURLs) + } + + flattenedDevfile := true + if args.FlattenedDevfile != nil { + flattenedDevfile = *args.FlattenedDevfile + } + + return populateAndParseDevfile(d, flattenedDevfile) +} + +func populateAndParseDevfile(d DevfileObj, flattenedDevfile bool) (DevfileObj, error) { + var err error // Fill the fields of DevfileCtx struct - err = d.Ctx.Populate() + if d.Ctx.GetURL() != "" { + err = d.Ctx.PopulateFromURL() + } else if d.Ctx.GetDevfileContent() != nil { + err = d.Ctx.PopulateFromRaw() + } else { + err = d.Ctx.Populate() + } if err != nil { return d, err } - return parseDevfile(d, true) + + return parseDevfile(d, flattenedDevfile) +} + +// Parse func populates the flattened devfile data, parses and validates the devfile integrity. +// Creates devfile context and runtime objects +// Deprecated, use ParseDevfile() instead +func Parse(path string) (d DevfileObj, err error) { + + // NewDevfileCtx + d.Ctx = devfileCtx.NewDevfileCtx(path) + + return populateAndParseDevfile(d, true) } // ParseRawDevfile populates the raw devfile data without overriding and merging +// Deprecated, use ParseDevfile() instead 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) + return populateAndParseDevfile(d, false) } // ParseFromURL func parses and validates the devfile integrity. // Creates devfile context and runtime objects +// Deprecated, use ParseDevfile() instead func ParseFromURL(url string) (d DevfileObj, err error) { d.Ctx = devfileCtx.NewURLDevfileCtx(url) - // Fill the fields of DevfileCtx struct - err = d.Ctx.PopulateFromURL() - if err != nil { - return d, err - } - return parseDevfile(d, true) + return populateAndParseDevfile(d, true) } // ParseFromData func parses and validates the devfile integrity. // Creates devfile context and runtime objects +// Deprecated, use ParseDevfile() instead func ParseFromData(data []byte) (d DevfileObj, err error) { d.Ctx = devfileCtx.DevfileCtx{} err = d.Ctx.SetDevfileContentFromBytes(data) if err != nil { return d, errors.Wrap(err, "failed to set devfile content from bytes") } - err = d.Ctx.PopulateFromRaw() - if err != nil { - return d, err - } - - return parseDevfile(d, true) + return populateAndParseDevfile(d, true) } 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() + if parent != nil { + if !reflect.DeepEqual(parent, &v1.Parent{}) { - parent := d.Data.GetParent() var parentDevfileObj DevfileObj - if d.Data.GetParent().Uri != "" { + if parent.Uri != "" { parentDevfileObj, err = parseFromURI(parent.Uri, d.Ctx) if err != nil { return err } + } else if parent.Id != "" { + parentDevfileObj, err = parseFromRegistry(parent.Id, parent.RegistryUrl, d.Ctx) + if err != nil { + return err + } } else { - return fmt.Errorf("parent URI undefined, currently only URI is suppported") + return fmt.Errorf("parent URI or parent Id undefined, currently only URI and Id are suppported") } parentWorkspaceContent := parentDevfileObj.Data.GetDevfileWorkspace() @@ -140,6 +192,7 @@ func parseParentAndPlugin(d DevfileObj) (err error) { klog.V(4).Infof("adding data of devfile with URI: %v", parent.Uri) } } + flattenedPlugins := []*v1.DevWorkspaceTemplateSpecContent{} components, err := d.Data.GetComponents(common.DevfileOptions{}) if err != nil { @@ -168,6 +221,7 @@ func parseParentAndPlugin(d DevfileObj) (err error) { flattenedPlugins = append(flattenedPlugins, flattenedPlugin) } } + mergedContent, err := apiOverride.MergeDevWorkspaceTemplateSpec(d.Data.GetDevfileWorkspace(), flattenedParent, flattenedPlugins...) if err != nil { return err @@ -185,27 +239,56 @@ func parseFromURI(uri string, curDevfileCtx devfileCtx.DevfileCtx) (DevfileObj, if err != nil { return DevfileObj{}, err } - - // absolute URL address - if strings.HasPrefix(uri, "http://") || strings.HasPrefix(uri, "https://") { - return ParseFromURL(uri) - } + // NewDevfileCtx + var d DevfileObj + absoluteURL := strings.HasPrefix(uri, "http://") || strings.HasPrefix(uri, "https://") // relative path on disk - if curDevfileCtx.GetAbsPath() != "" { - return Parse(path.Join(path.Dir(curDevfileCtx.GetAbsPath()), uri)) - } - - if curDevfileCtx.GetURL() != "" { + if !absoluteURL && curDevfileCtx.GetAbsPath() != "" { + d.Ctx = devfileCtx.NewDevfileCtx(path.Join(path.Dir(curDevfileCtx.GetAbsPath()), uri)) + } else if absoluteURL { + // absolute URL address + d.Ctx = devfileCtx.NewURLDevfileCtx(uri) + } else if curDevfileCtx.GetURL() != "" { + // relative path to a URL u, err := url.Parse(curDevfileCtx.GetURL()) if err != nil { return DevfileObj{}, err } - u.Path = path.Join(path.Dir(u.Path), uri) - // u.String() is the joint absolute URL path - return ParseFromURL(u.String()) + d.Ctx = devfileCtx.NewURLDevfileCtx(u.String()) } + d.Ctx.SetURIMap(curDevfileCtx.GetURIMap()) + return populateAndParseDevfile(d, true) +} - return DevfileObj{}, fmt.Errorf("fail to parse from uri: %s", uri) +func parseFromRegistry(parentId, registryURL string, curDevfileCtx devfileCtx.DevfileCtx) (DevfileObj, error) { + if registryURL != "" { + devfileContent, err := getDevfileFromRegistry(parentId, registryURL) + if err != nil { + return DevfileObj{}, err + } + return ParseDevfile(ParserArgs{Data: devfileContent, RegistryURLs: curDevfileCtx.GetRegistryURLs()}) + } else if curDevfileCtx.GetRegistryURLs() != nil { + for _, registry := range curDevfileCtx.GetRegistryURLs() { + devfileContent, err := getDevfileFromRegistry(parentId, registry) + if devfileContent != nil && err == nil { + return ParseDevfile(ParserArgs{Data: devfileContent, RegistryURLs: curDevfileCtx.GetRegistryURLs()}) + } + } + } else { + return DevfileObj{}, fmt.Errorf("failed to fetch from registry, registry URL is not provided") + } + + return DevfileObj{}, fmt.Errorf("failed to get parent Id: %s from registry URLs provided", parentId) +} + +func getDevfileFromRegistry(parentId, registryURL string) ([]byte, error) { + if !strings.HasPrefix(registryURL, "http://") && !strings.HasPrefix(registryURL, "https://") { + registryURL = fmt.Sprintf("http://%s", registryURL) + } + param := util.HTTPRequestParams{ + URL: fmt.Sprintf("%s/devfiles/%s", registryURL, parentId), + } + return util.HTTPGetRequest(param, 0) } diff --git a/vendor/github.com/devfile/library/pkg/testingutil/devfile.go b/vendor/github.com/devfile/library/pkg/testingutil/devfile.go index 3386d9df710..87ce3c8092c 100644 --- a/vendor/github.com/devfile/library/pkg/testingutil/devfile.go +++ b/vendor/github.com/devfile/library/pkg/testingutil/devfile.go @@ -1,233 +1,9 @@ package testingutil import ( - "fmt" - "strings" - v1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" - devfilepkg "github.com/devfile/api/v2/pkg/devfile" - "github.com/devfile/library/pkg/devfile/parser/data/v2/common" ) -// TestDevfileData is a convenience data type used to mock up a devfile configuration -type TestDevfileData struct { - Components []v1.Component - ExecCommands []v1.ExecCommand - CompositeCommands []v1.CompositeCommand - Commands []v1.Command - Events v1.Events -} - -// GetMetadata is a mock function to get metadata from devfile -func (d TestDevfileData) GetMetadata() devfilepkg.DevfileMetadata { - return devfilepkg.DevfileMetadata{} -} - -// SetMetadata sets metadata for the test devfile -func (d TestDevfileData) SetMetadata(name, version string) {} - -// GetSchemaVersion gets the schema version for the test devfile -func (d TestDevfileData) GetSchemaVersion() string { return "testSchema" } - -// SetSchemaVersion sets the schema version for the test devfile -func (d TestDevfileData) SetSchemaVersion(version string) {} - -// GetParent is a mock function to get parent from devfile -func (d TestDevfileData) GetParent() *v1.Parent { - return &v1.Parent{} -} - -// SetParent is a mock function to set parent of the test devfile -func (d TestDevfileData) SetParent(parent *v1.Parent) {} - -// GetEvents is a mock function to get events from devfile -func (d TestDevfileData) GetEvents() v1.Events { - return d.Events -} - -// AddEvents is a mock function to add events to the test devfile -func (d TestDevfileData) AddEvents(events v1.Events) error { return nil } - -// UpdateEvents is a mock function to update the events of the test devfile -func (d TestDevfileData) UpdateEvents(postStart, postStop, preStart, preStop []string) {} - -// GetComponents is a mock function to get the components from a devfile -func (d TestDevfileData) GetComponents(options common.DevfileOptions) ([]v1.Component, error) { - if len(options.Filter) == 0 { - return d.Components, nil - } - - var components []v1.Component - for _, comp := range d.Components { - filterIn, err := common.FilterDevfileObject(comp.Attributes, options) - if err != nil { - return nil, err - } - - if filterIn { - components = append(components, comp) - } - } - - return components, nil -} - -// AddComponents is a mock function to add components to the test devfile -func (d TestDevfileData) AddComponents(components []v1.Component) error { return nil } - -// UpdateComponent is a mock function to update the component of the test devfile -func (d TestDevfileData) UpdateComponent(component v1.Component) {} - -// GetProjects is a mock function to get the projects from a test devfile -func (d TestDevfileData) GetProjects(options common.DevfileOptions) ([]v1.Project, error) { - projectName := [...]string{"test-project", "anotherproject"} - clonePath := [...]string{"test-project/", "anotherproject/"} - sourceLocation := [...]string{"https://github.com/someproject/test-project.git", "https://github.com/another/project.git"} - - project1 := v1.Project{ - ClonePath: clonePath[0], - Name: projectName[0], - ProjectSource: v1.ProjectSource{ - Git: &v1.GitProjectSource{ - GitLikeProjectSource: v1.GitLikeProjectSource{ - Remotes: map[string]string{ - "origin": sourceLocation[0], - }, - }, - }, - }, - } - - project2 := v1.Project{ - ClonePath: clonePath[1], - Name: projectName[1], - ProjectSource: v1.ProjectSource{ - Git: &v1.GitProjectSource{ - GitLikeProjectSource: v1.GitLikeProjectSource{ - Remotes: map[string]string{ - "origin": sourceLocation[1], - }, - }, - }, - }, - } - return []v1.Project{project1, project2}, nil - -} - -// AddProjects is a mock function to add projects to the test devfile -func (d TestDevfileData) AddProjects(projects []v1.Project) error { return nil } - -// UpdateProject is a mock function to update a project for the test devfile -func (d TestDevfileData) UpdateProject(project v1.Project) {} - -// GetStarterProjects is a mock function to get the starter projects from a test devfile -func (d TestDevfileData) GetStarterProjects(options common.DevfileOptions) ([]v1.StarterProject, error) { - return []v1.StarterProject{}, nil -} - -// AddStarterProjects is a mock func to add the starter projects to the test devfile -func (d TestDevfileData) AddStarterProjects(projects []v1.StarterProject) error { - return nil -} - -// UpdateStarterProject is a mock func to update the starter project for a test devfile -func (d TestDevfileData) UpdateStarterProject(project v1.StarterProject) {} - -// GetCommands is a mock function to get the commands from a devfile -func (d TestDevfileData) GetCommands(options common.DevfileOptions) ([]v1.Command, error) { - - var commands []v1.Command - - for _, command := range d.Commands { - // we convert devfile command id to lowercase so that we can handle - // cases efficiently without being error prone - command.Id = strings.ToLower(command.Id) - commands = append(commands, command) - } - - return commands, nil -} - -// AddCommands is a mock func that adds commands to the test devfile -func (d *TestDevfileData) AddCommands(commands ...v1.Command) error { - devfileCommands, err := d.GetCommands(common.DevfileOptions{}) - if err != nil { - return err - } - - for _, command := range commands { - id := command.Id - for _, devfileCommand := range devfileCommands { - if id == devfileCommand.Id { - return fmt.Errorf("command %s already exist in the devfile", id) - } - } - - d.Commands = append(d.Commands, command) - } - return nil -} - -// UpdateCommand is a mock func to update the command in a test devfile -func (d TestDevfileData) UpdateCommand(command v1.Command) {} - -// AddVolume is a mock func that adds volume to the test devfile -func (d TestDevfileData) AddVolume(volumeComponent v1.Component, path string) error { - return nil -} - -// DeleteVolume is a mock func that deletes volume from the test devfile -func (d TestDevfileData) DeleteVolume(name string) error { return nil } - -// GetVolumeMountPath is a mock func that gets the volume mount path of a container -func (d TestDevfileData) GetVolumeMountPath(name string) (string, error) { - return "", nil -} - -// GetDevfileContainerComponents gets the container components from the test devfile -func (d TestDevfileData) GetDevfileContainerComponents(options common.DevfileOptions) ([]v1.Component, error) { - var components []v1.Component - devfileComponents, err := d.GetComponents(options) - if err != nil { - return nil, err - } - for _, comp := range devfileComponents { - if comp.Container != nil { - components = append(components, comp) - } - } - return components, nil -} - -// GetDevfileVolumeComponents gets the volume components from the test devfile -func (d TestDevfileData) GetDevfileVolumeComponents(options common.DevfileOptions) ([]v1.Component, error) { - var components []v1.Component - devfileComponents, err := d.GetComponents(options) - if err != nil { - return nil, err - } - for _, comp := range devfileComponents { - if comp.Volume != nil { - components = append(components, comp) - } - } - 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 -} - // GetFakeContainerComponent returns a fake container component for testing. func GetFakeContainerComponent(name string) v1.Component { image := "docker.io/maven:latest" diff --git a/vendor/github.com/devfile/library/pkg/util/util.go b/vendor/github.com/devfile/library/pkg/util/util.go index e8f4b2cdda1..82425894fd6 100644 --- a/vendor/github.com/devfile/library/pkg/util/util.go +++ b/vendor/github.com/devfile/library/pkg/util/util.go @@ -768,7 +768,7 @@ func HTTPGetRequest(request HTTPRequestParams, cacheFor int) ([]byte, error) { // We have a non 1xx / 2xx status, return an error if (resp.StatusCode - 300) > 0 { - return nil, errors.Errorf("fail to retrive %s: %s", request.URL, http.StatusText(resp.StatusCode)) + return nil, errors.Errorf("fail to retrive %s, %v: %s", request.URL, resp.StatusCode, http.StatusText(resp.StatusCode)) } // Process http response @@ -1030,6 +1030,10 @@ func DownloadFileInMemory(url string) ([]byte, error) { if err != nil { return nil, err } + // We have a non 1xx / 2xx status, return an error + if (resp.StatusCode - 300) > 0 { + return nil, errors.Errorf("fail to retrive %s, %v: %s", url, resp.StatusCode, http.StatusText(resp.StatusCode)) + } defer resp.Body.Close() return ioutil.ReadAll(resp.Body) diff --git a/vendor/modules.txt b/vendor/modules.txt index 903aad07773..30cad0f6356 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -46,14 +46,14 @@ github.com/containerd/containerd/errdefs github.com/danieljoos/wincred # github.com/davecgh/go-spew v1.1.1 github.com/davecgh/go-spew/spew -# github.com/devfile/api/v2 v2.0.0-20210211160219-33a78aec06af +# github.com/devfile/api/v2 v2.0.0-20210304212617-bfc3f501616b github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2 github.com/devfile/api/v2/pkg/attributes github.com/devfile/api/v2/pkg/devfile github.com/devfile/api/v2/pkg/utils/overriding github.com/devfile/api/v2/pkg/utils/unions github.com/devfile/api/v2/pkg/validation -# github.com/devfile/library v0.0.0-20210216162950-3066a892876c +# github.com/devfile/library v1.0.0-alpha.2.0.20210323153322-3d708859f0b5 github.com/devfile/library/pkg/devfile github.com/devfile/library/pkg/devfile/generator github.com/devfile/library/pkg/devfile/parser