Skip to content

Commit 9a1a788

Browse files
committed
adding support for dockerfile components
Signed-off-by: Michael Hoang <mhoang@redhat.com>
1 parent e55ee31 commit 9a1a788

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+1033
-67
lines changed

docs/public/alizer-spec.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ Component detection is only enabled for a subset of programming languages
156156
- Python
157157
- Rust
158158
- PHP
159+
- Dockerfile
159160

160161
To perform component detection Alizer splits the languages in two sets: `languages with a configuration file` (like Java
161162
which can have a pom.xml or a build.gradle) and `languages without a configuration file` (such as Python which does not have a
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
//
2+
// Copyright 2023 Red Hat, Inc.
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
16+
package enricher
17+
18+
import (
19+
"context"
20+
"github.com/devfile/alizer/pkg/apis/model"
21+
)
22+
23+
type DockerEnricher struct{}
24+
25+
type DockerFrameworkDetector interface {
26+
DoPortsDetection(component *model.Component, ctx *context.Context)
27+
}
28+
29+
func (d DockerEnricher) GetSupportedLanguages() []string {
30+
return []string{"dockerfile"}
31+
}
32+
33+
func (d DockerEnricher) DoEnrichLanguage(language *model.Language, _ *[]string) {
34+
// The Dockerfile language does not contain frameworks
35+
return
36+
}
37+
38+
func (d DockerEnricher) DoEnrichComponent(component *model.Component, _ model.DetectionSettings, _ *context.Context) {
39+
projectName := GetDefaultProjectName(component.Path)
40+
component.Name = projectName
41+
42+
var ports []int
43+
ports = GetPortsFromDockerFile(component.Path)
44+
if len(ports) > 0 {
45+
component.Ports = ports
46+
}
47+
return
48+
}
49+
50+
func (d DockerEnricher) IsConfigValidForComponentDetection(language string, config string) bool {
51+
return IsConfigurationValidForLanguage(language, config)
52+
}

pkg/apis/enricher/enricher.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ func getEnrichers() []Enricher {
9393
&DotNetEnricher{},
9494
&GoEnricher{},
9595
&PHPEnricher{},
96+
&DockerEnricher{},
9697
}
9798
}
9899

pkg/apis/model/model.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,13 @@ type DetectionSettings struct {
2727
}
2828

2929
type Language struct {
30-
Name string
31-
Aliases []string
32-
Weight float64
33-
Frameworks []string
34-
Tools []string
35-
CanBeComponent bool
30+
Name string
31+
Aliases []string
32+
Weight float64
33+
Frameworks []string
34+
Tools []string
35+
CanBeComponent bool
36+
CanBeContainerComponent bool
3637
}
3738

3839
type Component struct {

pkg/apis/recognizer/component_recognizer.go

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,17 @@ func isAnyComponentInPath(path string, components []model.Component) bool {
182182
return false
183183
}
184184

185+
// isAnyComponentInDirectPath checks if a component is present in the exact path.
186+
// Search starts from path and will return true if a component is found.
187+
func isAnyComponentInDirectPath(path string, components []model.Component) bool {
188+
for _, component := range components {
189+
if strings.Contains(path, component.Path) {
190+
return true
191+
}
192+
}
193+
return false
194+
}
195+
185196
// isFirstPathParentOfSecond check if first path is parent (direct or not) of second path.
186197
func isFirstPathParentOfSecond(firstPath string, secondPath string) bool {
187198
return strings.Contains(secondPath, firstPath)
@@ -194,6 +205,7 @@ func DetectComponentsFromFilesList(files []string, settings model.DetectionSetti
194205
alizerLogger.V(0).Info(fmt.Sprintf("Detecting components for %d fetched file paths", len(files)))
195206
configurationPerLanguage := langfiles.Get().GetConfigurationPerLanguageMapping()
196207
var components []model.Component
208+
var containerComponents []model.Component
197209
for _, file := range files {
198210
alizerLogger.V(1).Info(fmt.Sprintf("Accessing %s", file))
199211
languages, err := getLanguagesByConfigurationFile(configurationPerLanguage, file)
@@ -210,8 +222,20 @@ func DetectComponentsFromFilesList(files []string, settings model.DetectionSetti
210222
alizerLogger.V(1).Info(err.Error())
211223
continue
212224
}
213-
alizerLogger.V(0).Info(fmt.Sprintf("Component %s found", component.Name))
214-
components = appendIfMissing(components, component)
225+
if component.Languages[0].CanBeComponent {
226+
alizerLogger.V(0).Info(fmt.Sprintf("Component %s found", component.Name))
227+
components = appendIfMissing(components, component)
228+
}
229+
if component.Languages[0].CanBeContainerComponent {
230+
alizerLogger.V(0).Info(fmt.Sprintf("Container component %s found", component.Name))
231+
containerComponents = appendIfMissing(containerComponents, component)
232+
}
233+
}
234+
235+
for _, component := range containerComponents {
236+
if !isAnyComponentInDirectPath(component.Path, components) {
237+
components = appendIfMissing(components, component)
238+
}
215239
}
216240
return components
217241
}
@@ -226,8 +250,9 @@ func appendIfMissing(components []model.Component, component model.Component) []
226250
}
227251

228252
func getLanguagesByConfigurationFile(configurationPerLanguage map[string][]string, file string) ([]string, error) {
253+
filename := filepath.Base(file)
229254
for regex, languages := range configurationPerLanguage {
230-
if match, _ := regexp.MatchString(regex, file); match {
255+
if match, _ := regexp.MatchString(regex, filename); match {
231256
return languages, nil
232257
}
233258
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package recognizer
2+
3+
import (
4+
"github.com/devfile/alizer/pkg/apis/model"
5+
"reflect"
6+
"testing"
7+
)
8+
9+
func Test_isAnyComponentInDirectPath(t *testing.T) {
10+
tests := []struct {
11+
name string
12+
path string
13+
components []model.Component
14+
want bool
15+
}{
16+
{
17+
name: "Case 1: path should match",
18+
path: "/alizer/resources/projects/ocparcade/arkanoid/",
19+
components: []model.Component{{
20+
Name: "",
21+
Path: "/alizer/resources/projects/ocparcade/arkanoid/",
22+
Languages: nil,
23+
Ports: nil,
24+
}},
25+
want: true,
26+
},
27+
{
28+
name: "Case 2: path should match",
29+
path: "/alizer/resources/projects/ocparcade/arkanoid/arkanoid/",
30+
components: []model.Component{{
31+
Name: "",
32+
Path: "/alizer/resources/projects/ocparcade/arkanoid/arkanoid/",
33+
Languages: nil,
34+
Ports: nil,
35+
}},
36+
want: true,
37+
},
38+
{
39+
name: "Case 3: path should not match",
40+
path: "/alizer/resources/projects/ocparcade/arkanoid/",
41+
components: []model.Component{{
42+
Name: "",
43+
Path: "/alizer/resources/projects/ocparcade/arkanoid/arkanoid/",
44+
Languages: nil,
45+
Ports: nil,
46+
}},
47+
want: false,
48+
},
49+
}
50+
51+
for _, tt := range tests {
52+
t.Run(tt.name, func(t *testing.T) {
53+
result := isAnyComponentInDirectPath(tt.path, tt.components)
54+
if !reflect.DeepEqual(result, tt.want) {
55+
t.Errorf("Got: %t, want: %t", result, tt.want)
56+
}
57+
})
58+
}
59+
}

pkg/apis/recognizer/language_recognizer.go

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -127,12 +127,14 @@ func AnalyzeFile(configFile string, targetLanguage string) (model.Language, erro
127127
return model.Language{}, err
128128
}
129129
tmpLanguage := model.Language{
130-
Name: lang.Name,
131-
Aliases: lang.Aliases,
132-
Frameworks: []string{},
133-
Tools: []string{},
134-
Weight: 100,
135-
CanBeComponent: true}
130+
Name: lang.Name,
131+
Aliases: lang.Aliases,
132+
Frameworks: []string{},
133+
Tools: []string{},
134+
Weight: 100,
135+
CanBeComponent: lang.Component,
136+
CanBeContainerComponent: lang.ContainerComponent,
137+
}
136138
langEnricher := enricher.GetEnricherByLanguage(targetLanguage)
137139
if langEnricher != nil {
138140
langEnricher.DoEnrichLanguage(&tmpLanguage, &[]string{configFile})

pkg/schema/languages.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ type LanguagesProperties map[string]LanguageProperties
2929
type LanguageCustomization struct {
3030
ConfigurationFiles []string `yaml:"configuration_files"`
3131
Component bool `yaml:"component"`
32+
ContainerComponent bool `yaml:"container_component"`
3233
ExcludeFolders []string `yaml:"exclude_folders,omitempty"`
3334
Aliases []string `yaml:"aliases"`
3435
Disabled bool `default:"false" yaml:"disable_detection"`

pkg/utils/langfiles/languages_file_handler.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ type LanguageItem struct {
2828
ConfigurationFiles []string
2929
ExcludeFolders []string
3030
Component bool
31+
ContainerComponent bool
3132
disabled bool
3233
}
3334

@@ -87,6 +88,7 @@ func customizeLanguage(languageItem *LanguageItem) {
8788
(*languageItem).ConfigurationFiles = customization.ConfigurationFiles
8889
(*languageItem).ExcludeFolders = customization.ExcludeFolders
8990
(*languageItem).Component = customization.Component
91+
(*languageItem).ContainerComponent = customization.ContainerComponent
9092
(*languageItem).Aliases = appendSlice((*languageItem).Aliases, customization.Aliases)
9193
(*languageItem).disabled = customization.Disabled
9294
}

pkg/utils/langfiles/languages_file_handler_test.go

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ func TestGetLanguageByName(t *testing.T) {
118118
},
119119
{
120120
name: "JavaScript",
121-
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},
121+
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},
122122
expectedErr: nil,
123123
},
124124
{
@@ -133,7 +133,12 @@ func TestGetLanguageByName(t *testing.T) {
133133
},
134134
{
135135
name: "PHP",
136-
expectedItem: LanguageItem{Name: "PHP", Aliases: []string{"inc"}, Kind: "programming", Group: "", ConfigurationFiles: []string{"composer.json", "[^-]package.json"}, ExcludeFolders: []string(nil), Component: true, disabled: false},
136+
expectedItem: LanguageItem{Name: "PHP", Aliases: []string{"inc"}, Kind: "programming", Group: "", ConfigurationFiles: []string{"composer.json", "package.json"}, ExcludeFolders: []string(nil), Component: true, disabled: false},
137+
expectedErr: nil,
138+
},
139+
{
140+
name: "Dockerfile",
141+
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},
137142
expectedErr: nil,
138143
},
139144
}
@@ -194,7 +199,7 @@ func TestGetLanguageByAlias(t *testing.T) {
194199
{
195200
name: "JavaScript",
196201
alias: "TypeScript",
197-
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},
202+
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},
198203
expectedErr: nil,
199204
},
200205
{
@@ -206,7 +211,13 @@ func TestGetLanguageByAlias(t *testing.T) {
206211
{
207212
name: "PHP",
208213
alias: "inc",
209-
expectedItem: LanguageItem{Name: "PHP", Aliases: []string{"inc"}, Kind: "programming", Group: "", ConfigurationFiles: []string{"composer.json", "[^-]package.json"}, ExcludeFolders: []string(nil), Component: true, disabled: false},
214+
expectedItem: LanguageItem{Name: "PHP", Aliases: []string{"inc"}, Kind: "programming", Group: "", ConfigurationFiles: []string{"composer.json", "package.json"}, ExcludeFolders: []string(nil), Component: true, disabled: false},
215+
expectedErr: nil,
216+
},
217+
{
218+
name: "Dockerfile",
219+
alias: "Containerfile",
220+
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},
210221
expectedErr: nil,
211222
},
212223
}

0 commit comments

Comments
 (0)