Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
rm3l committed Nov 30, 2022
1 parent d0d4963 commit 9bf3883
Show file tree
Hide file tree
Showing 22 changed files with 445 additions and 50 deletions.
30 changes: 23 additions & 7 deletions pkg/alizer/alizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ func (o *Alizer) DetectFramework(ctx context.Context, path string) (model.DevFil
return types[typ], components.Items[typ].Registry, nil
}

// DetectName retrieves the name of the project (if available)
// DetectName retrieves the name of the project (if available).
// If source code is detected:
// 1. Detect the name (pom.xml for java, package.json for nodejs, etc.)
// 2. If unable to detect the name, use the directory name
Expand All @@ -59,11 +59,11 @@ func (o *Alizer) DetectFramework(ctx context.Context, path string) (model.DevFil
// 1. Use the directory name
//
// Last step. Sanitize the name so it's valid for a component name

//
// Use:
// import "github.com/redhat-developer/alizer/pkg/apis/recognizer"
// components, err := recognizer.DetectComponents("./")

//
// In order to detect the name, the name will first try to find out the name based on the program (pom.xml, etc.) but then if not, it will use the dir name.
func (o *Alizer) DetectName(path string) (string, error) {
if path == "" {
Expand Down Expand Up @@ -117,9 +117,25 @@ func (o *Alizer) DetectName(path string) (string, error) {
return name, nil
}

func GetDevfileLocationFromDetection(typ model.DevFileType, registry api.Registry) *api.DevfileLocation {
return &api.DevfileLocation{
Devfile: typ.Name,
DevfileRegistry: registry.Name,
func (o *Alizer) DetectPorts(path string) ([]int, error) {
//TODO(rm3l): Find a better way not to call recognizer.DetectComponents multiple times (in DetectFramework, DetectName and DetectPorts)
components, err := recognizer.DetectComponents(path)
if err != nil {
return nil, err
}

if len(components) == 0 {
klog.V(4).Infof("no components found at path %q", path)
return nil, nil
}

return components[0].Ports, nil
}

func NewDetectionResult(typ model.DevFileType, registry api.Registry, appPorts []int) *api.DetectionResult {
return &api.DetectionResult{
Devfile: typ.Name,
DevfileRegistry: registry.Name,
ApplicationPorts: appPorts,
}
}
1 change: 1 addition & 0 deletions pkg/alizer/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ import (
type Client interface {
DetectFramework(ctx context.Context, path string) (model.DevFileType, api.Registry, error)
DetectName(path string) (string, error)
DetectPorts(path string) ([]int, error)
}
15 changes: 15 additions & 0 deletions pkg/alizer/mock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 5 additions & 2 deletions pkg/api/devfile-location.go → pkg/api/analyze.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package api

// DevfileLocation indicates the location of a devfile, either in a devfile registry or using a path or an URI
type DevfileLocation struct {
// DetectionResult indicates the location of a devfile, either in a devfile registry or using a path or an URI
type DetectionResult struct {
// name of the Devfile in Devfile registry (required if DevfilePath is not defined)
Devfile string `json:"devfile,omitempty"`

Expand All @@ -10,4 +10,7 @@ type DevfileLocation struct {

// path to a devfile. This is alternative to using devfile from Devfile registry. It can be local filesystem path or http(s) URL (required if Devfile is not defined)
DevfilePath string `json:"devfilePath,omitempty"`

// list of ports detected f
ApplicationPorts []int `json:"ports,omitempty"`
}
27 changes: 24 additions & 3 deletions pkg/init/backend/alizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@ package backend
import (
"context"
"fmt"
"strconv"
"strings"

"github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
"github.com/devfile/library/pkg/devfile/parser"

"github.com/redhat-developer/odo/pkg/alizer"
"github.com/redhat-developer/odo/pkg/api"
"github.com/redhat-developer/odo/pkg/init/asker"
Expand All @@ -31,13 +34,27 @@ func (o *AlizerBackend) Validate(flags map[string]string, fs filesystem.Filesyst
}

// SelectDevfile calls thz 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) (location *api.DevfileLocation, err error) {
func (o *AlizerBackend) SelectDevfile(ctx context.Context, flags map[string]string, fs filesystem.Filesystem, dir string) (location *api.DetectionResult, err error) {
selected, registry, err := o.alizerClient.DetectFramework(ctx, dir)
if err != nil {
return nil, err
}

fmt.Printf("Based on the files in the current directory odo detected\nLanguage: %s\nProject type: %s\n", selected.Language, selected.ProjectType)
msg := fmt.Sprintf("Based on the files in the current directory odo detected\nLanguage: %s\nProject type: %s", selected.Language, selected.ProjectType)

appPorts, err := o.alizerClient.DetectPorts(dir)
if err != nil {
return nil, err
}
appPortsAsString := make([]string, 0, len(appPorts))
for _, p := range appPorts {
appPortsAsString = append(appPortsAsString, strconv.Itoa(p))
}
if len(appPorts) > 0 {
msg += fmt.Sprintf("\nApplication ports: %s", strings.Join(appPortsAsString, ", "))
}

fmt.Println(msg)
fmt.Printf("The devfile %q from the registry %q will be downloaded.\n", selected.Name, registry.Name)
confirm, err := o.askerClient.AskCorrect()
if err != nil {
Expand All @@ -46,7 +63,7 @@ func (o *AlizerBackend) SelectDevfile(ctx context.Context, flags map[string]stri
if !confirm {
return nil, nil
}
return alizer.GetDevfileLocationFromDetection(selected, registry), nil
return alizer.NewDetectionResult(selected, registry, appPorts), nil
}

func (o *AlizerBackend) SelectStarterProject(devfile parser.DevfileObj, flags map[string]string) (starter *v1alpha2.StarterProject, err error) {
Expand All @@ -65,3 +82,7 @@ func (o *AlizerBackend) PersonalizeName(devfile parser.DevfileObj, flags map[str
func (o *AlizerBackend) PersonalizeDevfileConfig(devfile parser.DevfileObj) (parser.DevfileObj, error) {
return devfile, nil
}

func (o *AlizerBackend) HandleApplicationPorts(devfileobj parser.DevfileObj, ports []int, flags map[string]string) (parser.DevfileObj, error) {
return devfileobj, nil
}
4 changes: 2 additions & 2 deletions pkg/init/backend/alizer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func TestAlizerBackend_SelectDevfile(t *testing.T) {
name string
fields fields
args args
wantLocation *api.DevfileLocation
wantLocation *api.DetectionResult
wantErr bool
}{
{
Expand All @@ -63,7 +63,7 @@ func TestAlizerBackend_SelectDevfile(t *testing.T) {
fs: filesystem.DefaultFs{},
dir: GetTestProjectPath("nodejs"),
},
wantLocation: &api.DevfileLocation{
wantLocation: &api.DetectionResult{
Devfile: "a-devfile-name",
DevfileRegistry: "a-registry",
},
Expand Down
65 changes: 65 additions & 0 deletions pkg/init/backend/applicationports.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package backend

import (
"fmt"
"io"
"strconv"
"strings"

"github.com/devfile/library/pkg/devfile/parser"
parsercommon "github.com/devfile/library/pkg/devfile/parser/data/v2/common"
"k8s.io/klog"
)

// handleApplicationPorts updates the ports in the Devfile as needed.
// If there are multiple container components in the Devfile, nothing is done. This will be handled in https://github.com/redhat-developer/odo/issues/6264.
// Otherwise, all the container component endpoints/ports (other than Debug) are updated with the specified ports.
func handleApplicationPorts(w io.Writer, devfileobj parser.DevfileObj, ports []int) (parser.DevfileObj, error) {
if len(ports) == 0 {
return devfileobj, nil
}

components, err := devfileobj.Data.GetDevfileContainerComponents(parsercommon.DevfileOptions{})
if err != nil {
return parser.DevfileObj{}, err
}
nbContainerComponents := len(components)
klog.V(3).Infof("Found %d container components in Devfile at path %q", nbContainerComponents, devfileobj.Ctx.GetAbsPath())
if nbContainerComponents == 0 {
// no container components => nothing to do
return devfileobj, nil
}
if nbContainerComponents > 1 {
klog.V(3).Infof("found more than 1 container components in Devfile at path %q => cannot find out which component needs to be updated."+
"This case will be handled in https://github.com/redhat-developer/odo/issues/6264", devfileobj.Ctx.GetAbsPath())
fmt.Fprintln(w, "\nApplication ports detected but the current Devfile contains multiple container components. Could not determine which component to update. "+
"Please feel free to customize the Devfile configuration below.")
return devfileobj, nil
}

component := components[0]

//Remove all but Debug endpoints
var portsToRemove []string
for _, ep := range component.Container.Endpoints {
if ep.Name == "debug" || strings.HasPrefix(ep.Name, "debug-") {
continue
}
portsToRemove = append(portsToRemove, strconv.Itoa(ep.TargetPort))
}
err = devfileobj.Data.RemovePorts(map[string][]string{component.Name: portsToRemove})
if err != nil {
return parser.DevfileObj{}, err
}

portsToSet := make([]string, 0, len(ports))
for _, p := range ports {
portsToSet = append(portsToSet, strconv.Itoa(p))
}
err = devfileobj.Data.SetPorts(map[string][]string{component.Name: portsToSet})
if err != nil {
return parser.DevfileObj{}, err
}

return devfileobj, err
}
138 changes: 138 additions & 0 deletions pkg/init/backend/applicationports_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package backend

import (
"bytes"
"testing"

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"
devfileCtx "github.com/devfile/library/pkg/devfile/parser/context"
"github.com/devfile/library/pkg/devfile/parser/data"
devfilefs "github.com/devfile/library/pkg/testingutil/filesystem"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"

"github.com/redhat-developer/odo/pkg/testingutil"
)

var fs = devfilefs.NewFakeFs()

func buildDevfileObjWithComponents(components ...v1.Component) parser.DevfileObj {
devfileData, _ := data.NewDevfileData(string(data.APISchemaVersion220))
devfileData.SetMetadata(devfilepkg.DevfileMetadata{Name: "my-nodejs-app"})
_ = devfileData.AddComponents(components)
return parser.DevfileObj{
Ctx: devfileCtx.FakeContext(fs, parser.OutputDevfileYamlPath),
Data: devfileData,
}
}

func Test_handleApplicationPorts(t *testing.T) {
type devfileProvider func() parser.DevfileObj
type args struct {
devfileObjProvider devfileProvider
ports []int
}

tests := []struct {
name string
args args
wantErr bool
wantProvider devfileProvider
}{
{
name: "no component, no ports to set",
args: args{
devfileObjProvider: func() parser.DevfileObj { return buildDevfileObjWithComponents() },
},
wantProvider: func() parser.DevfileObj { return buildDevfileObjWithComponents() },
},
{
name: "multiple container components, no ports to set",
args: args{
devfileObjProvider: func() parser.DevfileObj {
return buildDevfileObjWithComponents(
testingutil.GetFakeContainerComponent("cont1", 8080, 8081, 8082),
testingutil.GetFakeContainerComponent("cont2", 9080, 9081, 9082))
},
},
wantProvider: func() parser.DevfileObj {
return buildDevfileObjWithComponents(
testingutil.GetFakeContainerComponent("cont1", 8080, 8081, 8082),
testingutil.GetFakeContainerComponent("cont2", 9080, 9081, 9082))
},
},
{
name: "no container components",
args: args{
devfileObjProvider: func() parser.DevfileObj {
return buildDevfileObjWithComponents(testingutil.GetFakeVolumeComponent("vol1", "1Gi"))
},
ports: []int{8888, 8889, 8890},
},
wantProvider: func() parser.DevfileObj {
return buildDevfileObjWithComponents(testingutil.GetFakeVolumeComponent("vol1", "1Gi"))
},
},
{
name: "more than one container components",
args: args{
devfileObjProvider: func() parser.DevfileObj {
return buildDevfileObjWithComponents(
testingutil.GetFakeContainerComponent("cont1", 8080, 8081, 8082),
testingutil.GetFakeContainerComponent("cont2", 9080, 9081, 9082),
testingutil.GetFakeVolumeComponent("vol1", "1Gi"))
},
ports: []int{8888, 8889, 8890},
},
wantProvider: func() parser.DevfileObj {
return buildDevfileObjWithComponents(
testingutil.GetFakeContainerComponent("cont1", 8080, 8081, 8082),
testingutil.GetFakeContainerComponent("cont2", 9080, 9081, 9082),
testingutil.GetFakeVolumeComponent("vol1", "1Gi"))
},
},
{
name: "single container component with both application and debug ports",
args: args{
devfileObjProvider: func() parser.DevfileObj {
contWithDebug := testingutil.GetFakeContainerComponent("cont1", 18080, 18081, 18082)
contWithDebug.ComponentUnion.Container.Endpoints = append(contWithDebug.ComponentUnion.Container.Endpoints,
v1.Endpoint{Name: "debug", TargetPort: 5005},
v1.Endpoint{Name: "debug-another", TargetPort: 5858})
return buildDevfileObjWithComponents(
contWithDebug,
testingutil.GetFakeVolumeComponent("vol1", "1Gi"))
},
ports: []int{3000, 9000},
},
wantProvider: func() parser.DevfileObj {
newCont := testingutil.GetFakeContainerComponent("cont1")
newCont.ComponentUnion.Container.Endpoints = append(newCont.ComponentUnion.Container.Endpoints,
v1.Endpoint{Name: "debug", TargetPort: 5005},
v1.Endpoint{Name: "debug-another", TargetPort: 5858},
v1.Endpoint{Name: "port-3000-tcp", TargetPort: 3000, Protocol: v1.TCPEndpointProtocol},
v1.Endpoint{Name: "port-9000-tcp", TargetPort: 9000, Protocol: v1.TCPEndpointProtocol})
return buildDevfileObjWithComponents(
newCont,
testingutil.GetFakeVolumeComponent("vol1", "1Gi"))
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var output bytes.Buffer
got, err := handleApplicationPorts(&output, tt.args.devfileObjProvider(), tt.args.ports)
if (err != nil) != tt.wantErr {
t.Errorf("handleApplicationPorts() error = %v, wantErr %v", err, tt.wantErr)
return
}
if diff := cmp.Diff(tt.wantProvider(), got,
cmp.AllowUnexported(devfileCtx.DevfileCtx{}),
cmpopts.IgnoreInterfaces(struct{ devfilefs.Filesystem }{})); diff != "" {
t.Errorf("handleApplicationPorts() mismatch (-want +got):\n%s", diff)
}
})
}
}
9 changes: 7 additions & 2 deletions pkg/init/backend/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,8 @@ func (o *FlagsBackend) Validate(flags map[string]string, fs filesystem.Filesyste
return nil
}

func (o *FlagsBackend) SelectDevfile(ctx context.Context, flags map[string]string, _ filesystem.Filesystem, _ string) (*api.DevfileLocation, error) {
return &api.DevfileLocation{
func (o *FlagsBackend) SelectDevfile(ctx context.Context, flags map[string]string, _ filesystem.Filesystem, _ string) (*api.DetectionResult, error) {
return &api.DetectionResult{
Devfile: flags[FLAG_DEVFILE],
DevfileRegistry: flags[FLAG_DEVFILE_REGISTRY],
DevfilePath: flags[FLAG_DEVFILE_PATH],
Expand Down Expand Up @@ -123,3 +123,8 @@ func (o *FlagsBackend) PersonalizeName(_ parser.DevfileObj, flags map[string]str
func (o FlagsBackend) PersonalizeDevfileConfig(devfileobj parser.DevfileObj) (parser.DevfileObj, error) {
return devfileobj, nil
}

func (o FlagsBackend) HandleApplicationPorts(devfileobj parser.DevfileObj, ports []int, flags map[string]string) (parser.DevfileObj, error) {
// Currently not supported, but this will be done in a separate issue: https://github.com/redhat-developer/odo/issues/6211
return devfileobj, nil
}
Loading

0 comments on commit 9bf3883

Please sign in to comment.