Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/public/alizer-spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ Component detection is only enabled for a subset of programming languages
- Python
- Rust
- PHP
- Dockerfile

To perform component detection Alizer splits the languages in two sets: `languages with a configuration file` (like Java
which can have a pom.xml or a build.gradle) and `languages without a configuration file` (such as Python which does not have a
Expand Down
52 changes: 52 additions & 0 deletions pkg/apis/enricher/docker_enricher.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
//
// Copyright 2023 Red Hat, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package enricher

import (
"context"
"github.com/devfile/alizer/pkg/apis/model"
)

type DockerEnricher struct{}

type DockerFrameworkDetector interface {
DoPortsDetection(component *model.Component, ctx *context.Context)
}

func (d DockerEnricher) GetSupportedLanguages() []string {
return []string{"dockerfile"}
}

func (d DockerEnricher) DoEnrichLanguage(language *model.Language, _ *[]string) {
// The Dockerfile language does not contain frameworks
return
}

func (d DockerEnricher) DoEnrichComponent(component *model.Component, _ model.DetectionSettings, _ *context.Context) {
projectName := GetDefaultProjectName(component.Path)
component.Name = projectName

var ports []int
ports = GetPortsFromDockerFile(component.Path)
if len(ports) > 0 {
component.Ports = ports
}
return
}

func (d DockerEnricher) IsConfigValidForComponentDetection(language string, config string) bool {
return IsConfigurationValidForLanguage(language, config)
}
1 change: 1 addition & 0 deletions pkg/apis/enricher/enricher.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ func getEnrichers() []Enricher {
&DotNetEnricher{},
&GoEnricher{},
&PHPEnricher{},
&DockerEnricher{},
}
}

Expand Down
13 changes: 7 additions & 6 deletions pkg/apis/model/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,13 @@ type DetectionSettings struct {
}

type Language struct {
Name string
Aliases []string
Weight float64
Frameworks []string
Tools []string
CanBeComponent bool
Name string
Aliases []string
Weight float64
Frameworks []string
Tools []string
CanBeComponent bool
CanBeContainerComponent bool
}

type Component struct {
Expand Down
31 changes: 28 additions & 3 deletions pkg/apis/recognizer/component_recognizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,17 @@ func isAnyComponentInPath(path string, components []model.Component) bool {
return false
}

// isAnyComponentInDirectPath checks if a component is present in the exact path.
// Search starts from path and will return true if a component is found.
func isAnyComponentInDirectPath(path string, components []model.Component) bool {
for _, component := range components {
if strings.Contains(path, component.Path) {
return true
}
}
return false
}

// isFirstPathParentOfSecond check if first path is parent (direct or not) of second path.
func isFirstPathParentOfSecond(firstPath string, secondPath string) bool {
return strings.Contains(secondPath, firstPath)
Expand All @@ -194,6 +205,7 @@ func DetectComponentsFromFilesList(files []string, settings model.DetectionSetti
alizerLogger.V(0).Info(fmt.Sprintf("Detecting components for %d fetched file paths", len(files)))
configurationPerLanguage := langfiles.Get().GetConfigurationPerLanguageMapping()
var components []model.Component
var containerComponents []model.Component
for _, file := range files {
alizerLogger.V(1).Info(fmt.Sprintf("Accessing %s", file))
languages, err := getLanguagesByConfigurationFile(configurationPerLanguage, file)
Expand All @@ -210,8 +222,20 @@ func DetectComponentsFromFilesList(files []string, settings model.DetectionSetti
alizerLogger.V(1).Info(err.Error())
continue
}
alizerLogger.V(0).Info(fmt.Sprintf("Component %s found", component.Name))
components = appendIfMissing(components, component)
if component.Languages[0].CanBeComponent {
alizerLogger.V(0).Info(fmt.Sprintf("Component %s found", component.Name))
components = appendIfMissing(components, component)
}
if component.Languages[0].CanBeContainerComponent {
alizerLogger.V(0).Info(fmt.Sprintf("Container component %s found", component.Name))
containerComponents = appendIfMissing(containerComponents, component)
}
}

for _, component := range containerComponents {
if !isAnyComponentInDirectPath(component.Path, components) {
components = appendIfMissing(components, component)
}
}
return components
}
Expand All @@ -226,8 +250,9 @@ func appendIfMissing(components []model.Component, component model.Component) []
}

func getLanguagesByConfigurationFile(configurationPerLanguage map[string][]string, file string) ([]string, error) {
filename := filepath.Base(file)
for regex, languages := range configurationPerLanguage {
if match, _ := regexp.MatchString(regex, file); match {
if match, _ := regexp.MatchString(regex, filename); match {
return languages, nil
}
}
Expand Down
59 changes: 59 additions & 0 deletions pkg/apis/recognizer/component_recognizer_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package recognizer

import (
"github.com/devfile/alizer/pkg/apis/model"
"reflect"
"testing"
)

func Test_isAnyComponentInDirectPath(t *testing.T) {
tests := []struct {
name string
path string
components []model.Component
want bool
}{
{
name: "Case 1: path should match",
path: "/alizer/resources/projects/ocparcade/arkanoid/",
components: []model.Component{{
Name: "",
Path: "/alizer/resources/projects/ocparcade/arkanoid/",
Languages: nil,
Ports: nil,
}},
want: true,
},
{
name: "Case 2: path should match",
path: "/alizer/resources/projects/ocparcade/arkanoid/arkanoid/",
components: []model.Component{{
Name: "",
Path: "/alizer/resources/projects/ocparcade/arkanoid/arkanoid/",
Languages: nil,
Ports: nil,
}},
want: true,
},
{
name: "Case 3: path should not match",
path: "/alizer/resources/projects/ocparcade/arkanoid/",
components: []model.Component{{
Name: "",
Path: "/alizer/resources/projects/ocparcade/arkanoid/arkanoid/",
Languages: nil,
Ports: nil,
}},
want: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := isAnyComponentInDirectPath(tt.path, tt.components)
if !reflect.DeepEqual(result, tt.want) {
t.Errorf("Got: %t, want: %t", result, tt.want)
}
})
}
}
14 changes: 8 additions & 6 deletions pkg/apis/recognizer/language_recognizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,12 +127,14 @@ func AnalyzeFile(configFile string, targetLanguage string) (model.Language, erro
return model.Language{}, err
}
tmpLanguage := model.Language{
Name: lang.Name,
Aliases: lang.Aliases,
Frameworks: []string{},
Tools: []string{},
Weight: 100,
CanBeComponent: true}
Name: lang.Name,
Aliases: lang.Aliases,
Frameworks: []string{},
Tools: []string{},
Weight: 100,
CanBeComponent: lang.Component,
CanBeContainerComponent: lang.ContainerComponent,
}
langEnricher := enricher.GetEnricherByLanguage(targetLanguage)
if langEnricher != nil {
langEnricher.DoEnrichLanguage(&tmpLanguage, &[]string{configFile})
Expand Down
1 change: 1 addition & 0 deletions pkg/schema/languages.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ type LanguagesProperties map[string]LanguageProperties
type LanguageCustomization struct {
ConfigurationFiles []string `yaml:"configuration_files"`
Component bool `yaml:"component"`
ContainerComponent bool `yaml:"container_component"`
ExcludeFolders []string `yaml:"exclude_folders,omitempty"`
Aliases []string `yaml:"aliases"`
Disabled bool `default:"false" yaml:"disable_detection"`
Expand Down
2 changes: 2 additions & 0 deletions pkg/utils/langfiles/languages_file_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ type LanguageItem struct {
ConfigurationFiles []string
ExcludeFolders []string
Component bool
ContainerComponent bool
disabled bool
}

Expand Down Expand Up @@ -87,6 +88,7 @@ func customizeLanguage(languageItem *LanguageItem) {
(*languageItem).ConfigurationFiles = customization.ConfigurationFiles
(*languageItem).ExcludeFolders = customization.ExcludeFolders
(*languageItem).Component = customization.Component
(*languageItem).ContainerComponent = customization.ContainerComponent
(*languageItem).Aliases = appendSlice((*languageItem).Aliases, customization.Aliases)
(*languageItem).disabled = customization.Disabled
}
Expand Down
19 changes: 15 additions & 4 deletions pkg/utils/langfiles/languages_file_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ func TestGetLanguageByName(t *testing.T) {
},
{
name: "JavaScript",
expectedItem: LanguageItem{Name: "JavaScript", Aliases: []string{"js", "node", "nodejs", "TypeScript"}, Kind: "programming", Group: "", ConfigurationFiles: []string{"[^-]package.json"}, ExcludeFolders: []string{"node_modules"}, Component: true, disabled: false},
expectedItem: LanguageItem{Name: "JavaScript", Aliases: []string{"js", "node", "nodejs", "TypeScript"}, Kind: "programming", Group: "", ConfigurationFiles: []string{"package.json"}, ExcludeFolders: []string{"node_modules"}, Component: true, disabled: false},
expectedErr: nil,
},
{
Expand All @@ -133,7 +133,12 @@ func TestGetLanguageByName(t *testing.T) {
},
{
name: "PHP",
expectedItem: LanguageItem{Name: "PHP", Aliases: []string{"inc"}, Kind: "programming", Group: "", ConfigurationFiles: []string{"composer.json", "[^-]package.json"}, ExcludeFolders: []string(nil), Component: true, disabled: false},
expectedItem: LanguageItem{Name: "PHP", Aliases: []string{"inc"}, Kind: "programming", Group: "", ConfigurationFiles: []string{"composer.json", "package.json"}, ExcludeFolders: []string(nil), Component: true, disabled: false},
expectedErr: nil,
},
{
name: "Dockerfile",
expectedItem: LanguageItem{Name: "Dockerfile", Aliases: []string{"Containerfile"}, Kind: "programming", Group: "", ConfigurationFiles: []string{"[Dd]ockerfile(\\.\\w+)?$", "[Cc]ontainerfile(\\.\\w+)?$"}, ExcludeFolders: []string(nil), Component: false, ContainerComponent: true, disabled: false},
expectedErr: nil,
},
}
Expand Down Expand Up @@ -194,7 +199,7 @@ func TestGetLanguageByAlias(t *testing.T) {
{
name: "JavaScript",
alias: "TypeScript",
expectedItem: LanguageItem{Name: "JavaScript", Aliases: []string{"js", "node", "nodejs", "TypeScript"}, Kind: "programming", Group: "", ConfigurationFiles: []string{"[^-]package.json"}, ExcludeFolders: []string{"node_modules"}, Component: true, disabled: false},
expectedItem: LanguageItem{Name: "JavaScript", Aliases: []string{"js", "node", "nodejs", "TypeScript"}, Kind: "programming", Group: "", ConfigurationFiles: []string{"package.json"}, ExcludeFolders: []string{"node_modules"}, Component: true, disabled: false},
expectedErr: nil,
},
{
Expand All @@ -206,7 +211,13 @@ func TestGetLanguageByAlias(t *testing.T) {
{
name: "PHP",
alias: "inc",
expectedItem: LanguageItem{Name: "PHP", Aliases: []string{"inc"}, Kind: "programming", Group: "", ConfigurationFiles: []string{"composer.json", "[^-]package.json"}, ExcludeFolders: []string(nil), Component: true, disabled: false},
expectedItem: LanguageItem{Name: "PHP", Aliases: []string{"inc"}, Kind: "programming", Group: "", ConfigurationFiles: []string{"composer.json", "package.json"}, ExcludeFolders: []string(nil), Component: true, disabled: false},
expectedErr: nil,
},
{
name: "Dockerfile",
alias: "Containerfile",
expectedItem: LanguageItem{Name: "Dockerfile", Aliases: []string{"Containerfile"}, Kind: "programming", Group: "", ConfigurationFiles: []string{"[Dd]ockerfile(\\.\\w+)?$", "[Cc]ontainerfile(\\.\\w+)?$"}, ExcludeFolders: []string(nil), Component: false, ContainerComponent: true, disabled: false},
expectedErr: nil,
},
}
Expand Down
13 changes: 10 additions & 3 deletions pkg/utils/langfiles/resources/languages-customization.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ C#:
- ".*\\.\\w+proj"
- "appsettings.json"
component: true
Dockerfile:
aliases:
- "Containerfile"
configuration_files:
- "[Dd]ockerfile(\\.\\w+)?$"
- "[Cc]ontainerfile(\\.\\w+)?$"
container_component: true
F#:
aliases:
- "dotnet"
Expand Down Expand Up @@ -33,12 +40,12 @@ JavaScript:
exclude_folders:
- "node_modules"
configuration_files:
- "[^-]package.json"
- "package.json"
component: true
PHP:
configuration_files:
- "composer.json"
- "[^-]package.json"
- "package.json"
component: true
Python:
configuration_files:
Expand All @@ -55,7 +62,7 @@ TypeScript:
exclude_folders:
- "node_modules"
configuration_files:
- "[^-]package.json"
- "package.json"
component: true
Visual Basic .NET:
aliases:
Expand Down
16 changes: 16 additions & 0 deletions resources/projects/containerfile-orphan/Containerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
FROM node:16

# Create app directory
WORKDIR /usr/src/app

# Install app dependencies
# A wildcard is used to ensure both package.json AND package-lock.json are copied
COPY package*.json ./

RUN npm install

# Bundle app source
COPY . .

EXPOSE 8090
CMD [ "node", "server.js" ]
16 changes: 16 additions & 0 deletions resources/projects/dockerfile-double-components/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
FROM node:16

# Create app directory
WORKDIR /usr/src/app

# Install app dependencies
# A wildcard is used to ensure both package.json AND package-lock.json are copied
COPY package*.json ./

RUN npm install

# Bundle app source
COPY .. .

EXPOSE 8085
CMD [ "node", "server.js" ]
12 changes: 12 additions & 0 deletions resources/projects/dockerfile-double-components/python/manage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/usr/bin/env python
import os
import sys

if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project.settings")

from django.core.management import execute_from_command_line

execute_from_command_line(sys.argv)
from django.core.management.commands.runserver import Command as runserver
runserver.default_port = "3543"
Loading