diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..b444581e --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "gomod" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "daily" diff --git a/README.md b/README.md index 90cc97b5..6e6ceb2f 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,7 @@ $ go build alizer.go ``` ```sh + --log {debug|info|warning} sets the logging level of the CLI. The arg accepts only 3 values [`debug`, `info`, `warning`]. The default value is `warning` and the logging level is `ErrorLevel`. --port-detection {docker|compose|source} port detection strategy to use when detecting a port. Currently supported strategies are 'docker', 'compose' and 'source'. You can pass more strategies at the same time. They will be executed in order. By default Alizer will execute docker, compose and source. ``` @@ -53,7 +54,10 @@ $ go build alizer.go ``` ```sh + --log {debug|info|warning} sets the logging level of the CLI. The arg accepts only 3 values [`debug`, `info`, `warning`]. The default value is `warning` and the logging level is `ErrorLevel`. --registry strings registry where to download the devfiles. Default value: https://registry.devfile.io + --min-version strings the minimum SchemaVersion of the matched devfile(s). The minimum accepted value is `2.0.0`, otherwise an error is returned. + --max-version strings the maximum SchemaVersion of the matched devfile(s). The minimum accepted value is `2.0.0`, otherwise an error is returned. ``` ### Library Package diff --git a/docs/proposals/support_22x_devfiles.md b/docs/proposals/support_22x_devfiles.md index c24d6378..8f5ee53e 100644 --- a/docs/proposals/support_22x_devfiles.md +++ b/docs/proposals/support_22x_devfiles.md @@ -185,7 +185,7 @@ filter := map[string]interface{} { "max-version": "2.1.0", "min-version": "2.0.0", } -devfiles, err := recognizer.SelectDevFilesFromTypesWithArgs("myproject", devfiles, filter) +devfiles, err := recognizer.MatchDevfiles("myproject", devfiles, filter) ``` ### model.DevfileType diff --git a/go.mod b/go.mod index 3acafbd2..f03f5b26 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.19 require ( github.com/go-git/go-git/v5 v5.7.0 github.com/go-logr/logr v1.2.4 + github.com/hashicorp/go-version v1.6.0 github.com/moby/buildkit v0.11.6 github.com/pkg/errors v0.9.1 github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 diff --git a/go.sum b/go.sum index 5498da22..5d85a11f 100644 --- a/go.sum +++ b/go.sum @@ -55,6 +55,8 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOl github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= +github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= diff --git a/pkg/apis/model/model.go b/pkg/apis/model/model.go index 0294edb1..94740290 100644 --- a/pkg/apis/model/model.go +++ b/pkg/apis/model/model.go @@ -42,6 +42,12 @@ type Component struct { Ports []int } +type Version struct { + SchemaVersion string + Default bool + Version string +} + type DevFileType struct { Name string Language string @@ -49,6 +55,11 @@ type DevFileType struct { Tags []string } +type DevfileFilter struct { + MinVersion string + MaxVersion string +} + type ApplicationFileInfo struct { Dir string File string diff --git a/pkg/apis/recognizer/devfile_recognizer.go b/pkg/apis/recognizer/devfile_recognizer.go index f14f3119..407494b7 100644 --- a/pkg/apis/recognizer/devfile_recognizer.go +++ b/pkg/apis/recognizer/devfile_recognizer.go @@ -23,8 +23,11 @@ import ( "github.com/devfile/alizer/pkg/apis/model" "github.com/devfile/alizer/pkg/utils" + "github.com/hashicorp/go-version" ) +const MinimumAllowedVersion = "2.0.0" + func SelectDevFilesFromTypes(path string, devFileTypes []model.DevFileType) ([]int, error) { alizerLogger := utils.GetOrCreateLogger() ctx := context.Background() @@ -102,15 +105,31 @@ func SelectDevFileUsingLanguagesFromTypes(languages []model.Language, devFileTyp return devFilesIndexes[0], nil } +func MatchDevfiles(path string, url string, filter model.DevfileFilter) ([]model.DevFileType, error) { + alizerLogger := utils.GetOrCreateLogger() + alizerLogger.V(0).Info("Starting devfile matching") + alizerLogger.V(1).Info(fmt.Sprintf("Downloading devfiles from registry %s", url)) + devFileTypesFromRegistry, err := downloadDevFileTypesFromRegistry(url, filter) + if err != nil { + return []model.DevFileType{}, err + } + + return selectDevfiles(path, devFileTypesFromRegistry) +} + func SelectDevFilesFromRegistry(path string, url string) ([]model.DevFileType, error) { alizerLogger := utils.GetOrCreateLogger() alizerLogger.V(0).Info("Starting devfile matching") alizerLogger.V(1).Info(fmt.Sprintf("Downloading devfiles from registry %s", url)) - devFileTypesFromRegistry, err := downloadDevFileTypesFromRegistry(url) + devFileTypesFromRegistry, err := downloadDevFileTypesFromRegistry(url, model.DevfileFilter{MinVersion: "", MaxVersion: ""}) if err != nil { return []model.DevFileType{}, err } - alizerLogger.V(1).Info(fmt.Sprintf("Fetched %d devfiles", len(devFileTypesFromRegistry))) + + return selectDevfiles(path, devFileTypesFromRegistry) +} + +func selectDevfiles(path string, devFileTypesFromRegistry []model.DevFileType) ([]model.DevFileType, error) { indexes, err := SelectDevFilesFromTypes(path, devFileTypesFromRegistry) if err != nil { return []model.DevFileType{}, err @@ -122,10 +141,11 @@ func SelectDevFilesFromRegistry(path string, url string) ([]model.DevFileType, e } return devFileTypes, nil + } func SelectDevFileFromRegistry(path string, url string) (model.DevFileType, error) { - devFileTypes, err := downloadDevFileTypesFromRegistry(url) + devFileTypes, err := downloadDevFileTypesFromRegistry(url, model.DevfileFilter{MinVersion: "", MaxVersion: ""}) if err != nil { return model.DevFileType{}, err } @@ -137,17 +157,63 @@ func SelectDevFileFromRegistry(path string, url string) (model.DevFileType, erro return devFileTypes[index], nil } -func downloadDevFileTypesFromRegistry(url string) ([]model.DevFileType, error) { - url = adaptUrl(url) - // Get the data - resp, err := http.Get(url) +func GetUrlWithVersions(url, minVersion, maxVersion string) (string, error) { + minAllowedVersion, err := version.NewVersion(MinimumAllowedVersion) if err != nil { - // retry by appending index to url - url = appendIndexPath(url) - resp, err = http.Get(url) + return "", nil + } + + if minVersion != "" && maxVersion != "" { + minV, err := version.NewVersion(minVersion) + if err != nil { + return url, nil + } + maxV, err := version.NewVersion(maxVersion) + if err != nil { + return url, nil + } + if maxV.LessThan(minV) { + return "", fmt.Errorf("max-version cannot be lower than min-version") + } + if maxV.LessThan(minAllowedVersion) || minV.LessThan(minAllowedVersion) { + return "", fmt.Errorf("min and/or max version are lower than the minimum allowed version (2.0.0)") + } + + return fmt.Sprintf("%s?minSchemaVersion=%s&maxSchemaVersion=%s", url, minVersion, maxVersion), nil + } else if minVersion != "" { + minV, err := version.NewVersion(minVersion) + if err != nil { + return "", nil + } + if minV.LessThan(minAllowedVersion) { + return "", fmt.Errorf("min version is lower than the minimum allowed version (2.0.0)") + } + return fmt.Sprintf("%s?minSchemaVersion=%s", url, minVersion), nil + } else if maxVersion != "" { + maxV, err := version.NewVersion(maxVersion) if err != nil { - return []model.DevFileType{}, err + return "", nil } + if maxV.LessThan(minAllowedVersion) { + return "", fmt.Errorf("max version is lower than the minimum allowed version (2.0.0)") + } + return fmt.Sprintf("%s?maxSchemaVersion=%s", url, maxVersion), nil + } else { + return url, nil + } +} + +func downloadDevFileTypesFromRegistry(url string, filter model.DevfileFilter) ([]model.DevFileType, error) { + url = adaptUrl(url) + tmpUrl := appendIndexPath(url) + url, err := GetUrlWithVersions(tmpUrl, filter.MinVersion, filter.MaxVersion) + if err != nil { + return nil, err + } + // This value is set by the user in order to configure the registry + resp, err := http.Get(url) // #nosec G107 + if err != nil { + return []model.DevFileType{}, err } defer func() error { if err := resp.Body.Close(); err != nil { @@ -177,9 +243,9 @@ func downloadDevFileTypesFromRegistry(url string) ([]model.DevFileType, error) { func appendIndexPath(url string) string { if strings.HasSuffix(url, "/") { - return url + "index" + return url + "v2index" } - return url + "/index" + return url + "/v2index" } func adaptUrl(url string) string { diff --git a/pkg/cli/devfile/devfile.go b/pkg/cli/devfile/devfile.go index 6f31a15e..235fb204 100644 --- a/pkg/cli/devfile/devfile.go +++ b/pkg/cli/devfile/devfile.go @@ -1,12 +1,13 @@ package devfile import ( + "github.com/devfile/alizer/pkg/apis/model" "github.com/devfile/alizer/pkg/apis/recognizer" "github.com/devfile/alizer/pkg/utils" "github.com/spf13/cobra" ) -var logLevel, registry string +var logLevel, registry, minVersion, maxVersion string func NewCmdDevfile() *cobra.Command { devfileCmd := &cobra.Command{ @@ -17,6 +18,8 @@ func NewCmdDevfile() *cobra.Command { Run: doSelectDevfile, } devfileCmd.Flags().StringVar(&logLevel, "log", "", "log level for alizer. Default value: error. Accepted values: [debug, info, warning]") + devfileCmd.Flags().StringVar(&minVersion, "min-version", "", "minimum version of devfile schemaVersion. Minimum allowed version: 2.0.0") + devfileCmd.Flags().StringVar(&maxVersion, "max-version", "", "maximum version of devfile schemaVersion. Minimum allowed version: 2.0.0") devfileCmd.Flags().StringVarP(®istry, "registry", "r", "", "registry where to download the devfiles. Default value: https://registry.devfile.io") return devfileCmd } @@ -27,12 +30,16 @@ func doSelectDevfile(cmd *cobra.Command, args []string) { return } if registry == "" { - registry = "https://registry.devfile.io/index" + registry = "https://registry.devfile.io/" } err := utils.GenLogger(logLevel) if err != nil { utils.PrintWrongLoggingLevelMessage(cmd.Name()) return } - utils.PrintPrettifyOutput(recognizer.SelectDevFilesFromRegistry(args[0], registry)) + filter := model.DevfileFilter{ + MinVersion: minVersion, + MaxVersion: maxVersion, + } + utils.PrintPrettifyOutput(recognizer.MatchDevfiles(args[0], registry, filter)) } diff --git a/test/apis/devfile_recognizer_test.go b/test/apis/devfile_recognizer_test.go index d7c4215a..65b65a80 100644 --- a/test/apis/devfile_recognizer_test.go +++ b/test/apis/devfile_recognizer_test.go @@ -11,10 +11,12 @@ package recognizer * Red Hat, Inc. ******************************************************************************/ import ( + "fmt" "testing" "github.com/devfile/alizer/pkg/apis/model" "github.com/devfile/alizer/pkg/apis/recognizer" + "github.com/stretchr/testify/assert" ) func TestDetectQuarkusDevfile(t *testing.T) { @@ -133,6 +135,100 @@ func TestDetectLaravelDevfile(t *testing.T) { detectDevFiles(t, "laravel", []string{"php-laravel"}) } +func TestGetUrlWithVersions(t *testing.T) { + tests := []struct { + name string + minVersion string + maxVersion string + testUrl string + expectedError error + expectedUrl string + }{ + { + + name: "Case 1: Url with valid min and max versions", + minVersion: "2.0.0", + maxVersion: "2.2.0", + testUrl: "http://localhost:5000/", + expectedError: nil, + }, + { + name: "Case 2: Url with valid min version", + minVersion: "2.0.0", + maxVersion: "", + testUrl: "http://localhost:5000", + expectedError: nil, + }, + { + name: "Case 3: Url with valid max version", + minVersion: "", + maxVersion: "2.2.0", + testUrl: "http://localhost:5000/", + expectedError: nil, + }, + { + name: "Case 4: Url with max version lower than min version", + minVersion: "2.2.0", + maxVersion: "2.1.0", + testUrl: "http://localhost:5000/v2index", + expectedError: fmt.Errorf("max-version cannot be lower than min-version"), + }, + { + name: "Case 5: Url with max version lower than minimum allowed version", + minVersion: "0.1.0", + maxVersion: "1.1.0", + testUrl: "http://localhost:5000/v2index", + expectedError: fmt.Errorf("min and/or max version are lower than the minimum allowed version (2.0.0)"), + }, + { + name: "Case 6: Url with max version lower than minimum allowed version & minVersion", + minVersion: "2.1.0", + maxVersion: "1.1.0", + testUrl: "http://localhost:5000/v2index", + expectedError: fmt.Errorf("max-version cannot be lower than min-version"), + }, + { + name: "Case 7: Url with min version lower than minimum allowed version", + minVersion: "1.1.0", + maxVersion: "", + testUrl: "http://localhost:5000/v2index", + expectedError: fmt.Errorf("min version is lower than the minimum allowed version (2.0.0)"), + }, + { + name: "Case 8: Url with max version lower than minimum allowed version", + minVersion: "", + maxVersion: "1.1.0", + testUrl: "http://localhost:5000/v2index", + expectedError: fmt.Errorf("max version is lower than the minimum allowed version (2.0.0)"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := recognizer.GetUrlWithVersions(tt.testUrl, tt.minVersion, tt.maxVersion) + if err != nil { + assert.EqualValues(t, tt.expectedError, err) + } else { + assert.EqualValues(t, getExceptedVersionsUrl(tt.testUrl, tt.minVersion, tt.maxVersion, tt.expectedError), result) + } + }) + } +} + +func getExceptedVersionsUrl(url, minVersion, maxVersion string, err error) string { + if err != nil { + return "" + } else if minVersion != "" && maxVersion != "" { + return fmt.Sprintf("%s?minSchemaVersion=%s&maxSchemaVersion=%s", url, minVersion, maxVersion) + } else if minVersion != "" { + return fmt.Sprintf("%s?minSchemaVersion=%s", url, minVersion) + } else if maxVersion != "" { + return fmt.Sprintf("%s?maxSchemaVersion=%s", url, maxVersion) + } else { + return url + } +} + func detectDevFiles(t *testing.T, projectName string, devFilesName []string) { detectDevFilesFunc := func(devFileTypes []model.DevFileType) ([]int, error) { testingProjectPath := GetTestProjectPath(projectName)