odo init filters devfile stacks by supported architectures (#7004)

* Add --architecture flag

* Ask architecture during interactive mode

* Display architectures of detected Devfile

* Fix integration tests

* Fix automated doc

* Fix e2e tests

* Ignore empty lines on doc automation tests

* Update pkg/odo/cli/registry/registry.go

Co-authored-by: Armel Soro <armel@rm3l.org>

* Fix Architectures field in API

* Change "select architectures" prompt

---------

Co-authored-by: Armel Soro <armel@rm3l.org>
This commit is contained in:
Philippe Martin
2023-08-01 19:36:48 +02:00
committed by GitHub
parent 3cb1f5c66c
commit d41364e68e
34 changed files with 394 additions and 113 deletions

View File

@@ -52,14 +52,15 @@ func TestOdoAlizer(t *testing.T) {
alizerClient := alizer.NewMockClient(ctrl) alizerClient := alizer.NewMockClient(ctrl)
path := "/" path := "/"
alizerClient.EXPECT().DetectFramework(gomock.Any(), path). alizerClient.EXPECT().DetectFramework(gomock.Any(), path).
Return( Return(alizer.DetectedFramework{
model.DevFileType{ Type: model.DevFileType{
Name: "framework-name", Name: "framework-name",
}, },
"1.1.1", DefaultVersion: "1.1.1",
api.Registry{ Registry: api.Registry{
Name: "TheRegistryName", Name: "TheRegistryName",
}, },
},
nil, nil,
) )
alizerClient.EXPECT().DetectPorts(path).Return([]int{8080, 3000}, nil) alizerClient.EXPECT().DetectPorts(path).Return([]int{8080, 3000}, nil)
@@ -92,14 +93,15 @@ func TestOdoAlizer(t *testing.T) {
alizerClient := alizer.NewMockClient(ctrl) alizerClient := alizer.NewMockClient(ctrl)
path := "/" path := "/"
alizerClient.EXPECT().DetectFramework(gomock.Any(), path). alizerClient.EXPECT().DetectFramework(gomock.Any(), path).
Return( Return(alizer.DetectedFramework{
model.DevFileType{ Type: model.DevFileType{
Name: "framework-name", Name: "framework-name",
}, },
"1.1.1", DefaultVersion: "1.1.1",
api.Registry{ Registry: api.Registry{
Name: "TheRegistryName", Name: "TheRegistryName",
}, },
},
nil, nil,
) )
alizerClient.EXPECT().DetectPorts(path).Return([]int{8080, 3000}, nil) alizerClient.EXPECT().DetectPorts(path).Return([]int{8080, 3000}, nil)

View File

@@ -9,6 +9,7 @@ $ odo init
Interactive mode enabled, please answer the following questions: Interactive mode enabled, please answer the following questions:
✓ Determining a Devfile for the current directory [1s] ✓ Determining a Devfile for the current directory [1s]
Based on the files in the current directory odo detected Based on the files in the current directory odo detected
Supported architectures: all
Language: JavaScript Language: JavaScript
Project type: Node.js Project type: Node.js
Application ports: 8080 Application ports: 8080

View File

@@ -7,6 +7,12 @@ $ odo init
\__/ \__/
Interactive mode enabled, please answer the following questions: Interactive mode enabled, please answer the following questions:
? Select architectures to filter by: [Use arrows to move, space to select, <right> to all, <left> to none, type to filter]
> [x] amd64
[ ] arm64
[ ] ppc64le
[ ] s390x
? Select architectures to filter by: amd64
? Select language: Java ? Select language: Java
? Select project type: Maven Java ? Select project type: Maven Java
✓ Downloading devfile "java-maven" from registry "DefaultDevfileRegistry" [4s] ✓ Downloading devfile "java-maven" from registry "DefaultDevfileRegistry" [4s]

View File

@@ -9,10 +9,17 @@ $ odo init
Interactive mode enabled, please answer the following questions: Interactive mode enabled, please answer the following questions:
✓ Determining a Devfile for the current directory [1s] ✓ Determining a Devfile for the current directory [1s]
Based on the files in the current directory odo detected Based on the files in the current directory odo detected
Supported architectures: all
Language: .NET Language: .NET
Project type: dotnet Project type: dotnet
The devfile "dotnet50:1.0.3" from the registry "DefaultDevfileRegistry" will be downloaded. The devfile "dotnet50:1.0.3" from the registry "DefaultDevfileRegistry" will be downloaded.
? Is this correct? No ? Is this correct? No
? Select architectures to filter by: [Use arrows to move, space to select, <right> to all, <left> to none, type to filter]
> [x] amd64
[ ] arm64
[ ] ppc64le
[ ] s390x
? Select architectures to filter by: amd64
? Select language: .NET ? Select language: .NET
? Select project type: .NET 6.0 ? Select project type: .NET 6.0
✓ Downloading devfile "dotnet60" from registry "DefaultDevfileRegistry" [3s] ✓ Downloading devfile "dotnet60" from registry "DefaultDevfileRegistry" [3s]

View File

@@ -9,6 +9,7 @@ $ odo init
Interactive mode enabled, please answer the following questions: Interactive mode enabled, please answer the following questions:
✓ Determining a Devfile for the current directory [1s] ✓ Determining a Devfile for the current directory [1s]
Based on the files in the current directory odo detected Based on the files in the current directory odo detected
Supported architectures: all
Language: Go Language: Go
Project type: Go Project type: Go
Application ports: 8080 Application ports: 8080

View File

@@ -9,6 +9,7 @@ $ odo init
Interactive mode enabled, please answer the following questions: Interactive mode enabled, please answer the following questions:
✓ Determining a Devfile for the current directory [1s] ✓ Determining a Devfile for the current directory [1s]
Based on the files in the current directory odo detected Based on the files in the current directory odo detected
Supported architectures: all
Language: Java Language: Java
Project type: springboot Project type: springboot
The devfile "java-springboot:1.2.0" from the registry "DefaultDevfileRegistry" will be downloaded. The devfile "java-springboot:1.2.0" from the registry "DefaultDevfileRegistry" will be downloaded.

View File

@@ -9,6 +9,7 @@ $ odo init
Interactive mode enabled, please answer the following questions: Interactive mode enabled, please answer the following questions:
✓ Determining a Devfile for the current directory [1s] ✓ Determining a Devfile for the current directory [1s]
Based on the files in the current directory odo detected Based on the files in the current directory odo detected
Supported architectures: all
Language: JavaScript Language: JavaScript
Project type: Node.js Project type: Node.js
Application ports: 3000 Application ports: 3000

View File

@@ -29,11 +29,11 @@ func NewAlizerClient(registryClient registry.Client) *Alizer {
// DetectFramework uses the alizer library in order to detect the devfile // DetectFramework uses the alizer library in order to detect the devfile
// to use depending on the files in the path // to use depending on the files in the path
func (o *Alizer) DetectFramework(ctx context.Context, path string) (_ model.DevFileType, defaultVersion string, _ api.Registry, _ error) { func (o *Alizer) DetectFramework(ctx context.Context, path string) (DetectedFramework, error) {
types := []model.DevFileType{} types := []model.DevFileType{}
components, err := o.registryClient.ListDevfileStacks(ctx, "", "", "", false, false) components, err := o.registryClient.ListDevfileStacks(ctx, "", "", "", false, false)
if err != nil { if err != nil {
return model.DevFileType{}, defaultVersion, api.Registry{}, err return DetectedFramework{}, err
} }
for _, component := range components.Items { for _, component := range components.Items {
types = append(types, model.DevFileType{ types = append(types, model.DevFileType{
@@ -45,15 +45,21 @@ func (o *Alizer) DetectFramework(ctx context.Context, path string) (_ model.DevF
} }
typ, err := recognizer.SelectDevFileFromTypes(path, types) typ, err := recognizer.SelectDevFileFromTypes(path, types)
if err != nil { if err != nil {
return model.DevFileType{}, defaultVersion, api.Registry{}, err return DetectedFramework{}, err
} }
// Get the default stack version that will be downloaded // Get the default stack version that will be downloaded
var defaultVersion string
for _, version := range components.Items[typ].Versions { for _, version := range components.Items[typ].Versions {
if version.IsDefault { if version.IsDefault {
defaultVersion = version.Version defaultVersion = version.Version
} }
} }
return types[typ], defaultVersion, components.Items[typ].Registry, nil return DetectedFramework{
Type: types[typ],
DefaultVersion: defaultVersion,
Registry: components.Items[typ].Registry,
Architectures: components.Items[typ].Architectures,
}, nil
} }
// DetectName retrieves the name of the project (if available). // DetectName retrieves the name of the project (if available).

View File

@@ -119,18 +119,18 @@ func TestDetectFramework(t *testing.T) {
registryClient.EXPECT().ListDevfileStacks(ctx, "", "", "", false, false).Return(list, nil) registryClient.EXPECT().ListDevfileStacks(ctx, "", "", "", false, false).Return(list, nil)
alizerClient := NewAlizerClient(registryClient) alizerClient := NewAlizerClient(registryClient)
// Run function DetectFramework // Run function DetectFramework
detected, _, registry, err := alizerClient.DetectFramework(ctx, tt.args.path)
detected, err := alizerClient.DetectFramework(ctx, tt.args.path)
if !tt.wantErr == (err != nil) { if !tt.wantErr == (err != nil) {
t.Errorf("unexpected error %v, wantErr %v", err, tt.wantErr) t.Errorf("unexpected error %v, wantErr %v", err, tt.wantErr)
return return
} }
if detected.Name != tt.wantedDevfile { if detected.Type.Name != tt.wantedDevfile {
t.Errorf("unexpected devfile %v, wantedDevfile %v", detected, tt.wantedDevfile) t.Errorf("unexpected devfile %v, wantedDevfile %v", detected, tt.wantedDevfile)
} }
if registry.Name != tt.wantedRegistry { if detected.Registry.Name != tt.wantedRegistry {
t.Errorf("unexpected registry %v, wantedRegistry %v", registry, tt.wantedRegistry) t.Errorf("unexpected registry %v, wantedRegistry %v", detected.Registry, tt.wantedRegistry)
} }
}) })
} }

View File

@@ -4,12 +4,18 @@ import (
"context" "context"
"github.com/devfile/alizer/pkg/apis/model" "github.com/devfile/alizer/pkg/apis/model"
"github.com/redhat-developer/odo/pkg/api" "github.com/redhat-developer/odo/pkg/api"
) )
type DetectedFramework struct {
Type model.DevFileType
DefaultVersion string
Registry api.Registry
Architectures []string
}
type Client interface { type Client interface {
DetectFramework(ctx context.Context, path string) (_ model.DevFileType, defaultVersion string, _ api.Registry, _ error) DetectFramework(ctx context.Context, path string) (DetectedFramework, error)
DetectName(path string) (string, error) DetectName(path string) (string, error)
DetectPorts(path string) ([]int, error) DetectPorts(path string) ([]int, error)
} }

12
pkg/alizer/mock.go generated
View File

@@ -8,9 +8,7 @@ import (
context "context" context "context"
reflect "reflect" reflect "reflect"
model "github.com/devfile/alizer/pkg/apis/model"
gomock "github.com/golang/mock/gomock" gomock "github.com/golang/mock/gomock"
api "github.com/redhat-developer/odo/pkg/api"
) )
// MockClient is a mock of Client interface. // MockClient is a mock of Client interface.
@@ -37,14 +35,12 @@ func (m *MockClient) EXPECT() *MockClientMockRecorder {
} }
// DetectFramework mocks base method. // DetectFramework mocks base method.
func (m *MockClient) DetectFramework(ctx context.Context, path string) (model.DevFileType, string, api.Registry, error) { func (m *MockClient) DetectFramework(ctx context.Context, path string) (DetectedFramework, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DetectFramework", ctx, path) ret := m.ctrl.Call(m, "DetectFramework", ctx, path)
ret0, _ := ret[0].(model.DevFileType) ret0, _ := ret[0].(DetectedFramework)
ret1, _ := ret[1].(string) ret1, _ := ret[1].(error)
ret2, _ := ret[2].(api.Registry) return ret0, ret1
ret3, _ := ret[3].(error)
return ret0, ret1, ret2, ret3
} }
// DetectFramework indicates an expected call of DetectFramework. // DetectFramework indicates an expected call of DetectFramework.

View File

@@ -19,4 +19,6 @@ type DetectionResult struct {
DevfileVersion string `json:"devfileVersion,omitempty"` DevfileVersion string `json:"devfileVersion,omitempty"`
// Name represents the project/application name as detected by alizer // Name represents the project/application name as detected by alizer
Name string `json:"name,omitempty"` Name string `json:"name,omitempty"`
// Architectures represent the architectures with which the Devfile must be compatible with.
Architectures []string `json:"architectures,omitempty"`
} }

View File

@@ -24,18 +24,36 @@ func NewSurveyAsker() *Survey {
return &Survey{} return &Survey{}
} }
func (o *Survey) AskLanguage(langs []string) (string, error) { func (o *Survey) AskArchitectures(archs []string, selectedDefault []string) ([]string, error) {
question := &survey.MultiSelect{
Message: "Select architectures to filter by:",
Options: archs,
Default: selectedDefault,
}
var answer []string
err := survey.AskOne(question, &answer)
if err != nil {
return nil, err
}
return answer, nil
}
func (o *Survey) AskLanguage(langs []string) (bool, string, error) {
sort.Strings(langs) sort.Strings(langs)
langs = append(langs, GOBACK)
question := &survey.Select{ question := &survey.Select{
Message: "Select language:", Message: "Select language:",
Options: langs, Options: langs,
} }
var answer string var answerPos int
err := survey.AskOne(question, &answer) err := survey.AskOne(question, &answerPos)
if err != nil { if err != nil {
return "", err return false, "", err
} }
return answer, nil if answerPos == len(langs)-1 {
return true, "", nil
}
return false, langs[answerPos], nil
} }
func (o *Survey) AskType(types registry.TypesWithDetails) (back bool, _ api.DevfileStack, _ error) { func (o *Survey) AskType(types registry.TypesWithDetails) (back bool, _ api.DevfileStack, _ error) {

View File

@@ -9,8 +9,12 @@ import (
// Asker interactively asks for information to the user // Asker interactively asks for information to the user
type Asker interface { type Asker interface {
// AskLanguage asks for a language, from a list of language names. The language name is returned // AskArchitectures asks for a selection of architectures from a list of architecture names
AskLanguage(langs []string) (string, error) AskArchitectures(archs []string, selectedDefault []string) ([]string, error)
// AskLanguage asks for a language, from a list of language names.
// back is returned as true if the user selected to go back, or the language name is returned
AskLanguage(langs []string) (back bool, result string, err error)
// AskType asks for a Devfile type, or to go back. back is returned as true if the user selected to go back, // AskType asks for a Devfile type, or to go back. back is returned as true if the user selected to go back,
// or the selected type is returned // or the selected type is returned

24
pkg/init/asker/mock.go generated
View File

@@ -66,6 +66,21 @@ func (mr *MockAskerMockRecorder) AskAddPort() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AskAddPort", reflect.TypeOf((*MockAsker)(nil).AskAddPort)) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AskAddPort", reflect.TypeOf((*MockAsker)(nil).AskAddPort))
} }
// AskArchitectures mocks base method.
func (m *MockAsker) AskArchitectures(archs, selectedDefault []string) ([]string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "AskArchitectures", archs, selectedDefault)
ret0, _ := ret[0].([]string)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// AskArchitectures indicates an expected call of AskArchitectures.
func (mr *MockAskerMockRecorder) AskArchitectures(archs, selectedDefault interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AskArchitectures", reflect.TypeOf((*MockAsker)(nil).AskArchitectures), archs, selectedDefault)
}
// AskContainerName mocks base method. // AskContainerName mocks base method.
func (m *MockAsker) AskContainerName(containers []string) (string, error) { func (m *MockAsker) AskContainerName(containers []string) (string, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()
@@ -97,12 +112,13 @@ func (mr *MockAskerMockRecorder) AskCorrect() *gomock.Call {
} }
// AskLanguage mocks base method. // AskLanguage mocks base method.
func (m *MockAsker) AskLanguage(langs []string) (string, error) { func (m *MockAsker) AskLanguage(langs []string) (bool, string, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "AskLanguage", langs) ret := m.ctrl.Call(m, "AskLanguage", langs)
ret0, _ := ret[0].(string) ret0, _ := ret[0].(bool)
ret1, _ := ret[1].(error) ret1, _ := ret[1].(string)
return ret0, ret1 ret2, _ := ret[2].(error)
return ret0, ret1, ret2
} }
// AskLanguage indicates an expected call of AskLanguage. // AskLanguage indicates an expected call of AskLanguage.

View File

@@ -3,10 +3,11 @@ package backend
import ( import (
"context" "context"
"fmt" "fmt"
"github.com/redhat-developer/odo/pkg/log"
"strconv" "strconv"
"strings" "strings"
"github.com/redhat-developer/odo/pkg/log"
"github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
"github.com/devfile/library/v2/pkg/devfile/parser" "github.com/devfile/library/v2/pkg/devfile/parser"
@@ -34,6 +35,13 @@ func (o *AlizerBackend) Validate(flags map[string]string, fs filesystem.Filesyst
return nil return nil
} }
func archList(archs []string) string {
if len(archs) == 0 {
return "all"
}
return strings.Join(archs, ", ")
}
// SelectDevfile calls the Alizer to detect the devfile and asks for confirmation to the user // SelectDevfile calls the Alizer to detect the devfile and asks for confirmation to the user
func (o *AlizerBackend) SelectDevfile(ctx context.Context, flags map[string]string, fs filesystem.Filesystem, dir string) (*api.DetectionResult, error) { func (o *AlizerBackend) SelectDevfile(ctx context.Context, flags map[string]string, fs filesystem.Filesystem, dir string) (*api.DetectionResult, error) {
type result struct { type result struct {
@@ -47,12 +55,13 @@ func (o *AlizerBackend) SelectDevfile(ctx context.Context, flags map[string]stri
location, err := func() (location *api.DetectionResult, err error) { location, err := func() (location *api.DetectionResult, err error) {
spinner := log.Spinnerf("Determining a Devfile for the current directory") spinner := log.Spinnerf("Determining a Devfile for the current directory")
defer spinner.End(err == nil) defer spinner.End(err == nil)
selected, defaultVersion, registry, err := o.alizerClient.DetectFramework(ctx, dir) detected, err := o.alizerClient.DetectFramework(ctx, dir)
if err != nil { if err != nil {
return nil, err return nil, err
} }
msg := fmt.Sprintf("Based on the files in the current directory odo detected\nLanguage: %s\nProject type: %s", selected.Language, selected.ProjectType) msg := fmt.Sprintf("Based on the files in the current directory odo detected\nSupported architectures: %s\nLanguage: %s\nProject type: %s",
archList(detected.Architectures), detected.Type.Language, detected.Type.ProjectType)
appPorts, err := o.alizerClient.DetectPorts(dir) appPorts, err := o.alizerClient.DetectPorts(dir)
if err != nil { if err != nil {
@@ -68,7 +77,7 @@ func (o *AlizerBackend) SelectDevfile(ctx context.Context, flags map[string]stri
} }
fmt.Println(msg) fmt.Println(msg)
fmt.Printf("The devfile \"%s:%s\" from the registry %q will be downloaded.\n", selected.Name, defaultVersion, registry.Name) fmt.Printf("The devfile \"%s:%s\" from the registry %q will be downloaded.\n", detected.Type.Name, detected.DefaultVersion, detected.Registry.Name)
confirm, err := o.askerClient.AskCorrect() confirm, err := o.askerClient.AskCorrect()
if err != nil { if err != nil {
return nil, err return nil, err
@@ -76,7 +85,7 @@ func (o *AlizerBackend) SelectDevfile(ctx context.Context, flags map[string]stri
if !confirm { if !confirm {
return nil, nil return nil, nil
} }
return alizer.NewDetectionResult(selected, registry, appPorts, defaultVersion, ""), nil return alizer.NewDetectionResult(detected.Type, detected.Registry, appPorts, detected.DefaultVersion, ""), nil
}() }()
resultChan <- result{ resultChan <- result{
location: location, location: location,

View File

@@ -53,11 +53,7 @@ func TestAlizerBackend_SelectDevfile(t *testing.T) {
}, },
alizerClient: func(ctrl *gomock.Controller) alizer.Client { alizerClient: func(ctrl *gomock.Controller) alizer.Client {
alizerClient := alizer.NewMockClient(ctrl) alizerClient := alizer.NewMockClient(ctrl)
alizerClient.EXPECT().DetectFramework(gomock.Any(), gomock.Any()).Return(model.DevFileType{ alizerClient.EXPECT().DetectFramework(gomock.Any(), gomock.Any()).Return(alizer.DetectedFramework{}, errors.New("unable to detect framework"))
Name: "a-devfile-name",
}, "1.0.0", api.Registry{
Name: "a-registry",
}, errors.New("unable to detect framework"))
return alizerClient return alizerClient
}, },
}, },
@@ -77,11 +73,7 @@ func TestAlizerBackend_SelectDevfile(t *testing.T) {
}, },
alizerClient: func(ctrl *gomock.Controller) alizer.Client { alizerClient: func(ctrl *gomock.Controller) alizer.Client {
alizerClient := alizer.NewMockClient(ctrl) alizerClient := alizer.NewMockClient(ctrl)
alizerClient.EXPECT().DetectFramework(gomock.Any(), gomock.Any()).Return(model.DevFileType{ alizerClient.EXPECT().DetectFramework(gomock.Any(), gomock.Any()).Return(alizer.DetectedFramework{}, nil)
Name: "a-devfile-name",
}, "1.0.0", api.Registry{
Name: "a-registry",
}, nil)
alizerClient.EXPECT().DetectPorts(gomock.Any()).Return(nil, errors.New("unable to detect ports")) alizerClient.EXPECT().DetectPorts(gomock.Any()).Return(nil, errors.New("unable to detect ports"))
return alizerClient return alizerClient
}, },
@@ -102,10 +94,14 @@ func TestAlizerBackend_SelectDevfile(t *testing.T) {
}, },
alizerClient: func(ctrl *gomock.Controller) alizer.Client { alizerClient: func(ctrl *gomock.Controller) alizer.Client {
alizerClient := alizer.NewMockClient(ctrl) alizerClient := alizer.NewMockClient(ctrl)
alizerClient.EXPECT().DetectFramework(gomock.Any(), gomock.Any()).Return(model.DevFileType{ alizerClient.EXPECT().DetectFramework(gomock.Any(), gomock.Any()).Return(alizer.DetectedFramework{
Name: "a-devfile-name", Type: model.DevFileType{
}, "1.0.0", api.Registry{ Name: "a-devfile-name",
Name: "a-registry", },
DefaultVersion: "1.0.0",
Registry: api.Registry{
Name: "a-registry",
},
}, nil) }, nil)
alizerClient.EXPECT().DetectPorts(gomock.Any()).Return(nil, nil) alizerClient.EXPECT().DetectPorts(gomock.Any()).Return(nil, nil)
return alizerClient return alizerClient
@@ -127,10 +123,14 @@ func TestAlizerBackend_SelectDevfile(t *testing.T) {
}, },
alizerClient: func(ctrl *gomock.Controller) alizer.Client { alizerClient: func(ctrl *gomock.Controller) alizer.Client {
alizerClient := alizer.NewMockClient(ctrl) alizerClient := alizer.NewMockClient(ctrl)
alizerClient.EXPECT().DetectFramework(gomock.Any(), gomock.Any()).Return(model.DevFileType{ alizerClient.EXPECT().DetectFramework(gomock.Any(), gomock.Any()).Return(alizer.DetectedFramework{
Name: "a-devfile-name", Type: model.DevFileType{
}, "1.0.0", api.Registry{ Name: "a-devfile-name",
Name: "a-registry", },
DefaultVersion: "1.0.0",
Registry: api.Registry{
Name: "a-registry",
},
}, nil) }, nil)
alizerClient.EXPECT().DetectPorts(gomock.Any()).Return(nil, nil) alizerClient.EXPECT().DetectPorts(gomock.Any()).Return(nil, nil)
return alizerClient return alizerClient
@@ -156,7 +156,7 @@ func TestAlizerBackend_SelectDevfile(t *testing.T) {
}, },
alizerClient: func(ctrl *gomock.Controller) alizer.Client { alizerClient: func(ctrl *gomock.Controller) alizer.Client {
alizerClient := alizer.NewMockClient(ctrl) alizerClient := alizer.NewMockClient(ctrl)
alizerClient.EXPECT().DetectFramework(gomock.Any(), gomock.Any()).Return(model.DevFileType{}, "", api.Registry{}, nil) alizerClient.EXPECT().DetectFramework(gomock.Any(), gomock.Any()).Return(alizer.DetectedFramework{}, nil)
alizerClient.EXPECT().DetectPorts(gomock.Any()).Return(nil, nil) alizerClient.EXPECT().DetectPorts(gomock.Any()).Return(nil, nil)
return alizerClient return alizerClient
}, },
@@ -177,10 +177,14 @@ func TestAlizerBackend_SelectDevfile(t *testing.T) {
}, },
alizerClient: func(ctrl *gomock.Controller) alizer.Client { alizerClient: func(ctrl *gomock.Controller) alizer.Client {
alizerClient := alizer.NewMockClient(ctrl) alizerClient := alizer.NewMockClient(ctrl)
alizerClient.EXPECT().DetectFramework(gomock.Any(), gomock.Any()).Return(model.DevFileType{ alizerClient.EXPECT().DetectFramework(gomock.Any(), gomock.Any()).Return(alizer.DetectedFramework{
Name: "a-devfile-name", Type: model.DevFileType{
}, "1.0.0", api.Registry{ Name: "a-devfile-name",
Name: "a-registry", },
DefaultVersion: "1.0.0",
Registry: api.Registry{
Name: "a-registry",
},
}, nil) }, nil)
alizerClient.EXPECT().DetectPorts(gomock.Any()).Return([]int{1234, 5678}, nil) alizerClient.EXPECT().DetectPorts(gomock.Any()).Return([]int{1234, 5678}, nil)
return alizerClient return alizerClient

View File

@@ -13,6 +13,7 @@ import (
"github.com/redhat-developer/odo/pkg/registry" "github.com/redhat-developer/odo/pkg/registry"
"github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
"github.com/devfile/api/v2/pkg/devfile"
"github.com/devfile/library/v2/pkg/devfile/parser" "github.com/devfile/library/v2/pkg/devfile/parser"
"github.com/devfile/library/v2/pkg/devfile/parser/data/v2/common" "github.com/devfile/library/v2/pkg/devfile/parser/data/v2/common"
dfutil "github.com/devfile/library/v2/pkg/util" dfutil "github.com/devfile/library/v2/pkg/util"
@@ -30,6 +31,7 @@ const (
FLAG_DEVFILE_PATH = "devfile-path" FLAG_DEVFILE_PATH = "devfile-path"
FLAG_DEVFILE_VERSION = "devfile-version" FLAG_DEVFILE_VERSION = "devfile-version"
FLAG_RUN_PORT = "run-port" FLAG_RUN_PORT = "run-port"
FLAG_ARCHITECTURE = "architecture"
) )
// FlagsBackend is a backend that will extract all needed information from flags passed to the command // FlagsBackend is a backend that will extract all needed information from flags passed to the command
@@ -39,6 +41,13 @@ type FlagsBackend struct {
var _ InitBackend = (*FlagsBackend)(nil) var _ InitBackend = (*FlagsBackend)(nil)
var knownArchitectures []string = []string{
string(devfile.AMD64),
string(devfile.ARM64),
string(devfile.PPC64LE),
string(devfile.S390X),
}
func NewFlagsBackend(registryClient registry.Client) *FlagsBackend { func NewFlagsBackend(registryClient registry.Client) *FlagsBackend {
return &FlagsBackend{ return &FlagsBackend{
registryClient: registryClient, registryClient: registryClient,
@@ -97,15 +106,37 @@ Please use 'odo preference <add/remove> registry'' command to configure devfile
return errors.New("--starter parameter cannot be used when the directory is not empty") return errors.New("--starter parameter cannot be used when the directory is not empty")
} }
archs, err := parseStringArrayFlagValue(flags[FLAG_ARCHITECTURE])
if err != nil {
return err
}
for _, arch := range archs {
if !isKnownArch(arch) {
return fmt.Errorf("value %q is not valid for flag --architecture. Possible values are: %s", arch, strings.Join(knownArchitectures, ", "))
}
}
return nil return nil
} }
func isKnownArch(arch string) bool {
for _, known := range knownArchitectures {
if known == arch {
return true
}
}
return false
}
func (o *FlagsBackend) SelectDevfile(ctx context.Context, flags map[string]string, _ filesystem.Filesystem, _ string) (*api.DetectionResult, error) { func (o *FlagsBackend) SelectDevfile(ctx context.Context, flags map[string]string, _ filesystem.Filesystem, _ string) (*api.DetectionResult, error) {
// This has been validated before
archs, _ := parseStringArrayFlagValue(flags[FLAG_ARCHITECTURE])
return &api.DetectionResult{ return &api.DetectionResult{
Devfile: flags[FLAG_DEVFILE], Devfile: flags[FLAG_DEVFILE],
DevfileRegistry: flags[FLAG_DEVFILE_REGISTRY], DevfileRegistry: flags[FLAG_DEVFILE_REGISTRY],
DevfilePath: flags[FLAG_DEVFILE_PATH], DevfilePath: flags[FLAG_DEVFILE_PATH],
DevfileVersion: flags[FLAG_DEVFILE_VERSION], DevfileVersion: flags[FLAG_DEVFILE_VERSION],
Architectures: archs,
}, nil }, nil
} }
@@ -150,16 +181,16 @@ func (o FlagsBackend) HandleApplicationPorts(devfileobj parser.DevfileObj, _ []i
func setPortsForFlag(devfileobj parser.DevfileObj, flags map[string]string, flagName string) (parser.DevfileObj, error) { func setPortsForFlag(devfileobj parser.DevfileObj, flags map[string]string, flagName string) (parser.DevfileObj, error) {
flagVal := flags[flagName] flagVal := flags[flagName]
// Repeatable flags are formatted as "[val1,val2]"
if !(strings.HasPrefix(flagVal, "[") && strings.HasSuffix(flagVal, "]")) { split, err := parseStringArrayFlagValue(flagVal)
if err != nil || len(split) == 0 {
return devfileobj, nil return devfileobj, nil
} }
portsStr := flagVal[1 : len(flagVal)-1]
var ports []int var ports []int
split := strings.Split(portsStr, ",")
for _, s := range split { for _, s := range split {
p, err := strconv.Atoi(s) var p int
p, err = strconv.Atoi(s)
if err != nil { if err != nil {
return parser.DevfileObj{}, fmt.Errorf("invalid value for %s (%q): %w", flagName, s, err) return parser.DevfileObj{}, fmt.Errorf("invalid value for %s (%q): %w", flagName, s, err)
} }
@@ -216,3 +247,15 @@ func setPortsForFlag(devfileobj parser.DevfileObj, flags map[string]string, flag
} }
return devfileobj, nil return devfileobj, nil
} }
func parseStringArrayFlagValue(flagVal string) ([]string, error) {
if flagVal == "" {
return []string{}, nil
}
// Repeatable flags are formatted as "[val1,val2]"
if !(strings.HasPrefix(flagVal, "[") && strings.HasSuffix(flagVal, "]")) {
return nil, fmt.Errorf("malformed value %q", flagVal)
}
portsStr := flagVal[1 : len(flagVal)-1]
return strings.Split(portsStr, ","), nil
}

View File

@@ -45,6 +45,7 @@ func TestFlagsBackend_SelectDevfile(t *testing.T) {
Devfile: "adevfile", Devfile: "adevfile",
DevfilePath: "apath", DevfilePath: "apath",
DevfileRegistry: "aregistry", DevfileRegistry: "aregistry",
Architectures: []string{},
}, },
}, },
} }

View File

@@ -6,6 +6,7 @@ import (
"path/filepath" "path/filepath"
"sort" "sort"
"strconv" "strconv"
"strings"
"github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
"github.com/devfile/library/v2/pkg/devfile/parser" "github.com/devfile/library/v2/pkg/devfile/parser"
@@ -22,7 +23,8 @@ import (
) )
const ( const (
STATE_ASK_LANG = iota STATE_ASK_ARCHITECTURES = iota
STATE_ASK_LANG
STATE_ASK_TYPE STATE_ASK_TYPE
STATE_ASK_VERSION STATE_ASK_VERSION
STATE_END STATE_END
@@ -51,22 +53,36 @@ func (o *InteractiveBackend) Validate(flags map[string]string, fs filesystem.Fil
func (o *InteractiveBackend) SelectDevfile(ctx context.Context, flags map[string]string, _ filesystem.Filesystem, _ string) (*api.DetectionResult, error) { func (o *InteractiveBackend) SelectDevfile(ctx context.Context, flags map[string]string, _ filesystem.Filesystem, _ string) (*api.DetectionResult, error) {
result := &api.DetectionResult{} result := &api.DetectionResult{}
devfileEntries, _ := o.registryClient.ListDevfileStacks(ctx, "", "", "", false, false) var devfileEntries registry.DevfileStackList
state := STATE_ASK_ARCHITECTURES
langs := devfileEntries.GetLanguages()
state := STATE_ASK_LANG
var lang string var lang string
archs := []string{"amd64"}
var err error var err error
var details api.DevfileStack var details api.DevfileStack
loop: loop:
for { for {
switch state { switch state {
case STATE_ASK_LANG: case STATE_ASK_ARCHITECTURES:
lang, err = o.askerClient.AskLanguage(langs) archs, err = o.askerClient.AskArchitectures(knownArchitectures, archs)
if err != nil { if err != nil {
return nil, err return nil, err
} }
state = STATE_ASK_LANG
case STATE_ASK_LANG:
filter := strings.Join(archs, ",")
devfileEntries, _ = o.registryClient.ListDevfileStacks(ctx, "", "", filter, false, false)
langs := devfileEntries.GetLanguages()
var back bool
back, lang, err = o.askerClient.AskLanguage(langs)
if err != nil {
return nil, err
}
if back {
state = STATE_ASK_ARCHITECTURES
continue loop
}
state = STATE_ASK_TYPE state = STATE_ASK_TYPE
case STATE_ASK_TYPE: case STATE_ASK_TYPE:

View File

@@ -36,7 +36,8 @@ func TestInteractiveBackend_SelectDevfile(t *testing.T) {
fields: fields{ fields: fields{
buildAsker: func(ctrl *gomock.Controller) asker.Asker { buildAsker: func(ctrl *gomock.Controller) asker.Asker {
client := asker.NewMockAsker(ctrl) client := asker.NewMockAsker(ctrl)
client.EXPECT().AskLanguage(gomock.Any()).Return("java", nil) client.EXPECT().AskArchitectures(knownArchitectures, []string{"amd64"}).Return([]string{"amd64"}, nil)
client.EXPECT().AskLanguage(gomock.Any()).Return(false, "java", nil)
client.EXPECT().AskType(gomock.Any()).Return(false, api.DevfileStack{ client.EXPECT().AskType(gomock.Any()).Return(false, api.DevfileStack{
Name: "a-devfile-name", Name: "a-devfile-name",
Registry: api.Registry{ Registry: api.Registry{
@@ -57,13 +58,14 @@ func TestInteractiveBackend_SelectDevfile(t *testing.T) {
}, },
}, },
{ {
name: "selection with back", name: "selection with back on language selection",
fields: fields{ fields: fields{
buildAsker: func(ctrl *gomock.Controller) asker.Asker { buildAsker: func(ctrl *gomock.Controller) asker.Asker {
client := asker.NewMockAsker(ctrl) client := asker.NewMockAsker(ctrl)
client.EXPECT().AskLanguage(gomock.Any()).Return("java", nil) client.EXPECT().AskArchitectures(knownArchitectures, []string{"amd64"}).Return([]string{"amd64", "arm64"}, nil)
client.EXPECT().AskType(gomock.Any()).Return(true, api.DevfileStack{}, nil) client.EXPECT().AskLanguage(gomock.Any()).Return(true, "", nil)
client.EXPECT().AskLanguage(gomock.Any()).Return("go", nil) client.EXPECT().AskArchitectures(knownArchitectures, []string{"amd64", "arm64"}).Return([]string{"arm64"}, nil)
client.EXPECT().AskLanguage(gomock.Any()).Return(false, "go", nil)
client.EXPECT().AskType(gomock.Any()).Return(false, api.DevfileStack{ client.EXPECT().AskType(gomock.Any()).Return(false, api.DevfileStack{
Name: "a-devfile-name", Name: "a-devfile-name",
Registry: api.Registry{ Registry: api.Registry{
@@ -74,7 +76,35 @@ func TestInteractiveBackend_SelectDevfile(t *testing.T) {
}, },
buildCatalogClient: func(ctrl *gomock.Controller) registry.Client { buildCatalogClient: func(ctrl *gomock.Controller) registry.Client {
client := registry.NewMockClient(ctrl) client := registry.NewMockClient(ctrl)
client.EXPECT().ListDevfileStacks(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()) client.EXPECT().ListDevfileStacks(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Times(2)
return client
},
},
want: &api.DetectionResult{
Devfile: "a-devfile-name",
DevfileRegistry: "MyRegistry1",
},
},
{
name: "selection with back on type selection",
fields: fields{
buildAsker: func(ctrl *gomock.Controller) asker.Asker {
client := asker.NewMockAsker(ctrl)
client.EXPECT().AskArchitectures(knownArchitectures, []string{"amd64"}).Return([]string{"amd64"}, nil)
client.EXPECT().AskLanguage(gomock.Any()).Return(false, "java", nil)
client.EXPECT().AskType(gomock.Any()).Return(true, api.DevfileStack{}, nil)
client.EXPECT().AskLanguage(gomock.Any()).Return(false, "go", nil)
client.EXPECT().AskType(gomock.Any()).Return(false, api.DevfileStack{
Name: "a-devfile-name",
Registry: api.Registry{
Name: "MyRegistry1",
},
}, nil)
return client
},
buildCatalogClient: func(ctrl *gomock.Controller) registry.Client {
client := registry.NewMockClient(ctrl)
client.EXPECT().ListDevfileStacks(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Times(2)
return client return client
}, },
}, },

View File

@@ -50,6 +50,7 @@ var _initFlags = []string{
backend.FLAG_DEVFILE_PATH, backend.FLAG_DEVFILE_PATH,
backend.FLAG_DEVFILE_VERSION, backend.FLAG_DEVFILE_VERSION,
backend.FLAG_RUN_PORT, backend.FLAG_RUN_PORT,
backend.FLAG_ARCHITECTURE,
} }
func NewInitClient(fsys filesystem.Filesystem, preferenceClient preference.Client, registryClient registry.Client, alizerClient alizer.Client) *InitClient { func NewInitClient(fsys filesystem.Filesystem, preferenceClient preference.Client, registryClient registry.Client, alizerClient alizer.Client) *InitClient {
@@ -137,7 +138,7 @@ func (o *InitClient) DownloadDevfile(ctx context.Context, devfileLocation *api.D
if devfileLocation.DevfileVersion != "" { if devfileLocation.DevfileVersion != "" {
devfile = fmt.Sprintf("%s:%s", devfileLocation.Devfile, devfileLocation.DevfileVersion) devfile = fmt.Sprintf("%s:%s", devfileLocation.Devfile, devfileLocation.DevfileVersion)
} }
return destDevfile, o.downloadFromRegistry(ctx, devfileLocation.DevfileRegistry, devfile, destDir) return destDevfile, o.downloadFromRegistry(ctx, devfileLocation.DevfileRegistry, devfile, destDir, devfileLocation.Architectures)
} }
} }
@@ -185,10 +186,14 @@ func (o *InitClient) downloadDirect(URL string, dest string) error {
// downloadFromRegistry downloads a devfile from the provided registry and saves it in dest // downloadFromRegistry downloads a devfile from the provided registry and saves it in dest
// If registryName is empty, will try to download the devfile from the list of registries in preferences // If registryName is empty, will try to download the devfile from the list of registries in preferences
func (o *InitClient) downloadFromRegistry(ctx context.Context, registryName string, devfile string, dest string) error { // The architectures value indicates to download a Devfile compatible with all of these architectures
func (o *InitClient) downloadFromRegistry(ctx context.Context, registryName string, devfile string, dest string, architectures []string) error {
// setting NewIndexSchema ensures that the Devfile library pulls registry based on the stack version // setting NewIndexSchema ensures that the Devfile library pulls registry based on the stack version
registryOptions := segment.GetRegistryOptions(ctx) registryOptions := segment.GetRegistryOptions(ctx)
registryOptions.NewIndexSchema = true registryOptions.NewIndexSchema = true
if len(architectures) > 0 {
registryOptions.Filter.Architectures = architectures
}
var downloadSpinner *log.Status var downloadSpinner *log.Status
var forceRegistry bool var forceRegistry bool

View File

@@ -6,6 +6,7 @@ import (
"testing" "testing"
"github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
"github.com/devfile/registry-support/registry-library/library"
"github.com/golang/mock/gomock" "github.com/golang/mock/gomock"
"github.com/redhat-developer/odo/pkg/api" "github.com/redhat-developer/odo/pkg/api"
@@ -25,6 +26,7 @@ func TestInitClient_downloadFromRegistry(t *testing.T) {
registryName string registryName string
devfile string devfile string
dest string dest string
archs []string
} }
tests := []struct { tests := []struct {
name string name string
@@ -52,7 +54,12 @@ func TestInitClient_downloadFromRegistry(t *testing.T) {
}, },
} }
client.EXPECT().GetDevfileRegistries(gomock.Eq("Registry1")).Return(registryList, nil).Times(1) client.EXPECT().GetDevfileRegistries(gomock.Eq("Registry1")).Return(registryList, nil).Times(1)
client.EXPECT().PullStackFromRegistry("http://registry1", "java", gomock.Any(), gomock.Any()).Return(nil).Times(1) client.EXPECT().PullStackFromRegistry("http://registry1", "java", gomock.Any(), library.RegistryOptions{
Telemetry: library.TelemetryData{
Client: "odo",
},
NewIndexSchema: true,
}).Return(nil).Times(1)
return client return client
}, },
}, },
@@ -63,6 +70,46 @@ func TestInitClient_downloadFromRegistry(t *testing.T) {
}, },
wantErr: false, wantErr: false,
}, },
{
name: "Download devfile from one specific Registry where devfile is present and arch is passed",
fields: fields{
preferenceClient: func(ctrl *gomock.Controller) preference.Client {
client := preference.NewMockClient(ctrl)
return client
},
registryClient: func(ctrl *gomock.Controller) registry.Client {
client := registry.NewMockClient(ctrl)
registryList := []api.Registry{
{
Name: "Registry0",
URL: "http://registry0",
},
{
Name: "Registry1",
URL: "http://registry1",
},
}
client.EXPECT().GetDevfileRegistries(gomock.Eq("Registry1")).Return(registryList, nil).Times(1)
client.EXPECT().PullStackFromRegistry("http://registry1", "java", gomock.Any(), library.RegistryOptions{
Telemetry: library.TelemetryData{
Client: "odo",
},
Filter: library.RegistryFilter{
Architectures: []string{"arm64"},
},
NewIndexSchema: true,
}).Return(nil).Times(1)
return client
},
},
args: args{
registryName: "Registry1",
devfile: "java",
dest: ".",
archs: []string{"arm64"},
},
wantErr: false,
},
{ {
name: "Fail to download devfile from one specific Registry where devfile is absent", name: "Fail to download devfile from one specific Registry where devfile is absent",
fields: fields{ fields: fields{
@@ -167,7 +214,7 @@ func TestInitClient_downloadFromRegistry(t *testing.T) {
} }
ctx := context.Background() ctx := context.Background()
ctx = envcontext.WithEnvConfig(ctx, config.Configuration{}) ctx = envcontext.WithEnvConfig(ctx, config.Configuration{})
if err := o.downloadFromRegistry(ctx, tt.args.registryName, tt.args.devfile, tt.args.dest); (err != nil) != tt.wantErr { if err := o.downloadFromRegistry(ctx, tt.args.registryName, tt.args.devfile, tt.args.dest, tt.args.archs); (err != nil) != tt.wantErr {
t.Errorf("InitClient.downloadFromRegistry() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("InitClient.downloadFromRegistry() error = %v, wantErr %v", err, tt.wantErr)
} }
}) })

View File

@@ -50,7 +50,7 @@ func (o *AlizerOptions) Run(ctx context.Context) (err error) {
// RunForJsonOutput contains the logic for the odo command // RunForJsonOutput contains the logic for the odo command
func (o *AlizerOptions) RunForJsonOutput(ctx context.Context) (out interface{}, err error) { func (o *AlizerOptions) RunForJsonOutput(ctx context.Context) (out interface{}, err error) {
workingDir := odocontext.GetWorkingDirectory(ctx) workingDir := odocontext.GetWorkingDirectory(ctx)
df, defaultVersion, reg, err := o.clientset.AlizerClient.DetectFramework(ctx, workingDir) detected, err := o.clientset.AlizerClient.DetectFramework(ctx, workingDir)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -62,7 +62,7 @@ func (o *AlizerOptions) RunForJsonOutput(ctx context.Context) (out interface{},
if err != nil { if err != nil {
return nil, err return nil, err
} }
result := alizer.NewDetectionResult(df, reg, appPorts, defaultVersion, name) result := alizer.NewDetectionResult(detected.Type, detected.Registry, appPorts, detected.DefaultVersion, name)
return []api.DetectionResult{*result}, nil return []api.DetectionResult{*result}, nil
} }

View File

@@ -63,6 +63,9 @@ var initExample = templates.Examples(`
# Bootstrap a new component and download a starter project # Bootstrap a new component and download a starter project
%[1]s --name my-app --devfile nodejs --starter nodejs-starter %[1]s --name my-app --devfile nodejs --starter nodejs-starter
# Bootstrap a new component with a specific devfile from registry for a specific architecture
%[1]s --name my-app --devfile nodejs --architecture s390x
`) `)
type InitOptions struct { type InitOptions struct {
@@ -285,6 +288,7 @@ func NewCmdInit(name, fullName string, testClientset clientset.Clientset) *cobra
initCmd.Flags().String(backend.FLAG_STARTER, "", "name of the starter project") initCmd.Flags().String(backend.FLAG_STARTER, "", "name of the starter project")
initCmd.Flags().String(backend.FLAG_DEVFILE_PATH, "", "path to a devfile. This is an alternative to using devfile from Devfile registry. It can be local filesystem path or http(s) URL") initCmd.Flags().String(backend.FLAG_DEVFILE_PATH, "", "path to a devfile. This is an alternative to using devfile from Devfile registry. It can be local filesystem path or http(s) URL")
initCmd.Flags().String(backend.FLAG_DEVFILE_VERSION, "", "version of the devfile stack; use \"latest\" to dowload the latest stack") initCmd.Flags().String(backend.FLAG_DEVFILE_VERSION, "", "version of the devfile stack; use \"latest\" to dowload the latest stack")
initCmd.Flags().StringArray(backend.FLAG_ARCHITECTURE, []string{}, "Architecture supported. Can be one or multiple values from amd64, arm64, ppc64le, s390x. Default is amd64.")
initCmd.Flags().StringArray(backend.FLAG_RUN_PORT, []string{}, "ports used by the application (via the 'run' command)") initCmd.Flags().StringArray(backend.FLAG_RUN_PORT, []string{}, "ports used by the application (via the 'run' command)")
commonflags.UseOutputFlag(initCmd) commonflags.UseOutputFlag(initCmd)

View File

@@ -32,8 +32,8 @@ var Example = ` # Get all devfile components
# Filter by name and devfile registry # Filter by name and devfile registry
%[1]s --filter nodejs --devfile-registry DefaultDevfileRegistry %[1]s --filter nodejs --devfile-registry DefaultDevfileRegistry
# Filter by architecture # Show the Devfiles supporting both architectures
%[1]s --filter amd64 %[1]s --filter amd64,arm64
# Show more details from a specific devfile # Show more details from a specific devfile
%[1]s --details --devfile nodejs %[1]s --details --devfile nodejs
@@ -120,7 +120,7 @@ func NewCmdRegistry(name, fullName string, testClientset clientset.Clientset) *c
clientset.Add(listCmd, clientset.REGISTRY) clientset.Add(listCmd, clientset.REGISTRY)
// Flags // Flags
listCmd.Flags().StringVar(&o.filterFlag, "filter", "", "Filter based on the name or description or supported architecture of the component") listCmd.Flags().StringVar(&o.filterFlag, "filter", "", "Comma-separated list of terms for filtering. Search is done using a logical AND against the name or description or supported architectures of the component.")
listCmd.Flags().StringVar(&o.devfileFlag, "devfile", "", "Only the specific Devfile component") listCmd.Flags().StringVar(&o.devfileFlag, "devfile", "", "Only the specific Devfile component")
listCmd.Flags().StringVar(&o.registryFlag, "devfile-registry", "", "Only show components from the specific Devfile registry") listCmd.Flags().StringVar(&o.registryFlag, "devfile-registry", "", "Only show components from the specific Devfile registry")
listCmd.Flags().BoolVar(&o.detailsFlag, "details", false, "Show details of a Devfile, to be used only with --devfile") listCmd.Flags().BoolVar(&o.detailsFlag, "details", false, "Show details of a Devfile, to be used only with --devfile")

View File

@@ -283,32 +283,37 @@ func (o RegistryClient) ListDevfileStacks(ctx context.Context, registryName, dev
devfiles := []api.DevfileStack{} devfiles := []api.DevfileStack{}
devfileLoop:
for _, devfile := range registryDevfiles { for _, devfile := range registryDevfiles {
// Add the "priority" of the registry to the devfile // Add the "priority" of the registry to the devfile
devfile.Registry.Priority = priorityNumber devfile.Registry.Priority = priorityNumber
if filterFlag != "" { if filterFlag != "" {
archs := append(make([]string, 0, len(devfile.Architectures)), devfile.Architectures...) filters := strings.Split(filterFlag, ",")
if len(archs) == 0 { for _, filter := range filters {
// Devfiles with no architectures are compatible with all architectures. filter = strings.TrimSpace(filter)
archs = append(archs, archs := append(make([]string, 0, len(devfile.Architectures)), devfile.Architectures...)
string(apidevfile.AMD64), if len(archs) == 0 {
string(apidevfile.ARM64), // Devfiles with no architectures are compatible with all architectures.
string(apidevfile.PPC64LE), archs = append(archs,
string(apidevfile.S390X), string(apidevfile.AMD64),
) string(apidevfile.ARM64),
} string(apidevfile.PPC64LE),
containsArch := func(s string) bool { string(apidevfile.S390X),
for _, arch := range archs { )
if strings.Contains(arch, s) { }
return true containsArch := func(s string) bool {
} for _, arch := range archs {
if strings.Contains(arch, s) {
return true
}
}
return false
}
if !strings.Contains(devfile.Name, filter) && !strings.Contains(devfile.Description, filter) && !containsArch(filter) {
continue devfileLoop
} }
return false
}
if !strings.Contains(devfile.Name, filterFlag) && !strings.Contains(devfile.Description, filterFlag) && !containsArch(filterFlag) {
continue
} }
} }

View File

@@ -32,6 +32,9 @@ var _ = Describe("doc command reference odo init", Label(helper.LabelNoCluster),
It("Empty directory", func() { It("Empty directory", func() {
args := []string{"odo", "init"} args := []string{"odo", "init"}
out, err := helper.RunInteractive(args, []string{"ODO_LOG_LEVEL=0"}, func(ctx helper.InteractiveContext) { out, err := helper.RunInteractive(args, []string{"ODO_LOG_LEVEL=0"}, func(ctx helper.InteractiveContext) {
helper.ExpectString(ctx, "Select architectures")
helper.SendLine(ctx, "")
helper.ExpectString(ctx, "Select language") helper.ExpectString(ctx, "Select language")
helper.SendLine(ctx, "Java") helper.SendLine(ctx, "Java")

View File

@@ -166,6 +166,9 @@ var _ = Describe("User guides: Quickstart test", func() {
helper.ExpectString(ctx, "Is this correct?") helper.ExpectString(ctx, "Is this correct?")
helper.SendLine(ctx, "No") helper.SendLine(ctx, "No")
helper.ExpectString(ctx, "Select architectures")
helper.SendLine(ctx, "")
helper.ExpectString(ctx, "Select language") helper.ExpectString(ctx, "Select language")
helper.SendLine(ctx, ".") helper.SendLine(ctx, ".")

View File

@@ -69,6 +69,9 @@ var _ = Describe("E2E Test", func() {
command := []string{"odo", "init"} command := []string{"odo", "init"}
_, err := helper.RunInteractive(command, nil, func(ctx helper.InteractiveContext) { _, err := helper.RunInteractive(command, nil, func(ctx helper.InteractiveContext) {
helper.ExpectString(ctx, "Select architectures")
helper.SendLine(ctx, "")
helper.ExpectString(ctx, "Select language") helper.ExpectString(ctx, "Select language")
helper.SendLine(ctx, "JavaScript") helper.SendLine(ctx, "JavaScript")

View File

@@ -35,6 +35,11 @@ func StripSpinner(docString string) (returnString string) {
line := sc.Text() line := sc.Text()
// trim any special character present in the line // trim any special character present in the line
line = strings.TrimFunc(line, unicode.IsSpace) line = strings.TrimFunc(line, unicode.IsSpace)
if len(line) == 0 {
continue
}
// This check is to avoid spinner statements in the cmd output // This check is to avoid spinner statements in the cmd output
// currently it does so for init and dev // currently it does so for init and dev
// e.g. " • Syncing file changes ..." // e.g. " • Syncing file changes ..."
@@ -92,6 +97,9 @@ func GetMDXContent(filePath string) (mdxContent string) {
for fileScanner.Scan() { for fileScanner.Scan() {
line := fileScanner.Text() line := fileScanner.Text()
line = strings.TrimFunc(line, unicode.IsSpace) line = strings.TrimFunc(line, unicode.IsSpace)
if len(line) == 0 {
continue
}
mdxContent += line + "\n" mdxContent += line + "\n"
} }

View File

@@ -159,7 +159,7 @@ var _ = Describe("odo deploy interactive command tests", func() {
It("should not fail but fallback to the interactive mode", func() { It("should not fail but fallback to the interactive mode", func() {
_, err := helper.RunInteractive([]string{"odo", "deploy"}, nil, func(ctx helper.InteractiveContext) { _, err := helper.RunInteractive([]string{"odo", "deploy"}, nil, func(ctx helper.InteractiveContext) {
helper.ExpectString(ctx, "Could not determine a Devfile based on the files in the current directory") helper.ExpectString(ctx, "Could not determine a Devfile based on the files in the current directory")
helper.ExpectString(ctx, "Select language") helper.ExpectString(ctx, "Select architectures")
ctx.StopCommand() ctx.StopCommand()
}) })
Expect(err).Should(HaveOccurred()) Expect(err).Should(HaveOccurred())

View File

@@ -165,6 +165,9 @@ var _ = Describe("odo dev interactive command tests", func() {
output, _ := helper.RunInteractive([]string{"odo", "dev", "--random-ports"}, nil, func(ctx helper.InteractiveContext) { output, _ := helper.RunInteractive([]string{"odo", "dev", "--random-ports"}, nil, func(ctx helper.InteractiveContext) {
helper.ExpectString(ctx, "Could not determine a Devfile based on the files in the current directory") helper.ExpectString(ctx, "Could not determine a Devfile based on the files in the current directory")
helper.ExpectString(ctx, "Select architectures")
helper.SendLine(ctx, "")
helper.ExpectString(ctx, "Select language") helper.ExpectString(ctx, "Select language")
helper.SendLine(ctx, "Python") helper.SendLine(ctx, "Python")

View File

@@ -56,6 +56,9 @@ var _ = Describe("odo init interactive command tests", func() {
helper.ExpectString(ctx, messages.InteractiveModeEnabled) helper.ExpectString(ctx, messages.InteractiveModeEnabled)
}) })
helper.ExpectString(ctx, "Select architectures")
helper.SendLine(ctx, "")
helper.ExpectString(ctx, "Select language") helper.ExpectString(ctx, "Select language")
helper.SendLine(ctx, "Go") helper.SendLine(ctx, "Go")
@@ -97,6 +100,9 @@ var _ = Describe("odo init interactive command tests", func() {
command := []string{"odo", "init"} command := []string{"odo", "init"}
output, err := helper.RunInteractive(command, nil, func(ctx helper.InteractiveContext) { output, err := helper.RunInteractive(command, nil, func(ctx helper.InteractiveContext) {
helper.ExpectString(ctx, "Select architectures")
helper.SendLine(ctx, "")
helper.ExpectString(ctx, "Select language") helper.ExpectString(ctx, "Select language")
helper.SendLine(ctx, "Javascript") helper.SendLine(ctx, "Javascript")
@@ -148,6 +154,9 @@ var _ = Describe("odo init interactive command tests", func() {
command := []string{"odo", "init"} command := []string{"odo", "init"}
_, err := helper.RunInteractive(command, nil, func(ctx helper.InteractiveContext) { _, err := helper.RunInteractive(command, nil, func(ctx helper.InteractiveContext) {
helper.ExpectString(ctx, "Select architectures")
helper.SendLine(ctx, "")
helper.ExpectString(ctx, "Select language") helper.ExpectString(ctx, "Select language")
helper.SendLine(ctx, "Go") helper.SendLine(ctx, "Go")
@@ -192,6 +201,9 @@ var _ = Describe("odo init interactive command tests", func() {
output, err := helper.RunInteractive(command, nil, func(ctx helper.InteractiveContext) { output, err := helper.RunInteractive(command, nil, func(ctx helper.InteractiveContext) {
helper.ExpectString(ctx, "Select architectures")
helper.SendLine(ctx, "")
helper.ExpectString(ctx, "Select language") helper.ExpectString(ctx, "Select language")
helper.SendLine(ctx, "Go") helper.SendLine(ctx, "Go")
@@ -230,6 +242,9 @@ var _ = Describe("odo init interactive command tests", func() {
helper.ExpectString(ctx, messages.InteractiveModeEnabled) helper.ExpectString(ctx, messages.InteractiveModeEnabled)
}) })
helper.ExpectString(ctx, "Select architectures")
helper.SendLine(ctx, "")
helper.ExpectString(ctx, "Select language") helper.ExpectString(ctx, "Select language")
helper.SendLine(ctx, "Go") helper.SendLine(ctx, "Go")
@@ -265,6 +280,9 @@ var _ = Describe("odo init interactive command tests", func() {
output, err := helper.RunInteractive(command, nil, func(ctx helper.InteractiveContext) { output, err := helper.RunInteractive(command, nil, func(ctx helper.InteractiveContext) {
helper.ExpectString(ctx, "Select architectures")
helper.SendLine(ctx, "")
helper.ExpectString(ctx, "Select language") helper.ExpectString(ctx, "Select language")
helper.SendLine(ctx, "Go") helper.SendLine(ctx, "Go")
@@ -301,6 +319,9 @@ var _ = Describe("odo init interactive command tests", func() {
helper.ExpectString(ctx, messages.InteractiveModeEnabled) helper.ExpectString(ctx, messages.InteractiveModeEnabled)
}) })
helper.ExpectString(ctx, "Select architectures")
helper.SendLine(ctx, "")
helper.ExpectString(ctx, "Select language") helper.ExpectString(ctx, "Select language")
helper.SendLine(ctx, "java") helper.SendLine(ctx, "java")
@@ -395,6 +416,9 @@ var _ = Describe("odo init interactive command tests", func() {
welcomingMsgs := strings.Split(odolog.Stitle(messages.InitializingNewComponent, messages.NoSourceCodeDetected, "odo version: "+version.VERSION), "\n") welcomingMsgs := strings.Split(odolog.Stitle(messages.InitializingNewComponent, messages.NoSourceCodeDetected, "odo version: "+version.VERSION), "\n")
output, err := testRunner(language, welcomingMsgs, func(ctx helper.InteractiveContext) { output, err := testRunner(language, welcomingMsgs, func(ctx helper.InteractiveContext) {
helper.ExpectString(ctx, "Select architectures")
helper.SendLine(ctx, "")
helper.ExpectString(ctx, "Select language") helper.ExpectString(ctx, "Select language")
helper.SendLine(ctx, language) helper.SendLine(ctx, language)
@@ -547,6 +571,9 @@ var _ = Describe("odo init interactive command tests", func() {
output, err := helper.RunInteractive([]string{"odo", "init"}, nil, func(ctx helper.InteractiveContext) { output, err := helper.RunInteractive([]string{"odo", "init"}, nil, func(ctx helper.InteractiveContext) {
helper.ExpectString(ctx, "Select architectures")
helper.SendLine(ctx, "")
helper.ExpectString(ctx, "Select language") helper.ExpectString(ctx, "Select language")
helper.SendLine(ctx, ".NET") helper.SendLine(ctx, ".NET")
@@ -643,7 +670,7 @@ var _ = Describe("odo init interactive command tests", func() {
_, err := helper.RunInteractive([]string{"odo", "init"}, nil, func(ctx helper.InteractiveContext) { _, err := helper.RunInteractive([]string{"odo", "init"}, nil, func(ctx helper.InteractiveContext) {
helper.ExpectString(ctx, "Could not determine a Devfile based on the files in the current directory") helper.ExpectString(ctx, "Could not determine a Devfile based on the files in the current directory")
helper.ExpectString(ctx, "Select language") helper.ExpectString(ctx, "Select architectures")
ctx.StopCommand() ctx.StopCommand()
}) })
Expect(err).Should(HaveOccurred()) Expect(err).Should(HaveOccurred())
@@ -689,6 +716,9 @@ spec:
} }
output, err := helper.RunInteractive([]string{"odo", "init"}, nil, func(ctx helper.InteractiveContext) { output, err := helper.RunInteractive([]string{"odo", "init"}, nil, func(ctx helper.InteractiveContext) {
helper.ExpectString(ctx, "Select architectures")
helper.SendLine(ctx, "")
helper.ExpectString(ctx, "Select language") helper.ExpectString(ctx, "Select language")
helper.SendLine(ctx, "Java") helper.SendLine(ctx, "Java")