Skip to content

Commit

Permalink
adding support for dockerfile components (#14)
Browse files Browse the repository at this point in the history
Signed-off-by: Michael Hoang <[email protected]>
  • Loading branch information
mike-hoang authored Jul 25, 2023
1 parent 1e2e10b commit 7f521b2
Show file tree
Hide file tree
Showing 43 changed files with 1,032 additions and 66 deletions.
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"
Empty file.
Loading

0 comments on commit 7f521b2

Please sign in to comment.