diff --git a/docs/website/docs/command-reference/json-output.md b/docs/website/docs/command-reference/json-output.md index dcdde9270ea..89cdcf30869 100644 --- a/docs/website/docs/command-reference/json-output.md +++ b/docs/website/docs/command-reference/json-output.md @@ -314,69 +314,106 @@ odo registry -o json ``` ```json [ - { - "name": "python-django", - "displayName": "Django", - "description": "Python3.7 with Django", - "registry": { - "name": "DefaultDevfileRegistry", - "url": "https://registry.devfile.io", - "secure": false - }, - "language": "python", - "tags": [ - "Python", - "pip", - "Django" - ], - "projectType": "django", - "version": "1.0.0", - "starterProjects": [ - "django-example" - ] - }, [...] + { + "name": "python", + "displayName": "Python", + "description": "Python is an interpreted, object-oriented, high-level programming language with dynamic semantics. Its high-level built in data structures, combined with dynamic typing and dynamic binding, make it very attractive for Rapid Application Development, as well as for use as a scripting or glue language to connect existing components together.", + "registry": { + "name": "DefaultDevfileRegistry", + "url": "https://registry.devfile.io", + "secure": false + }, + "language": "Python", + "tags": [ + "Python", + "Pip", + "Flask" + ], + "projectType": "Python", + "version": "2.1.0", + "versions": [ + { + "version": "2.1.0", + "isDefault": true, + "schemaVersion": "2.1.0", + "starterProjects": [ + "flask-example" + ] + }, + { + "version": "3.0.0", + "isDefault": false, + "schemaVersion": "2.2.0", + "starterProjects": [ + "flask-example" + ] + } + ], + "starterProjects": [ + "flask-example" + ] + }, + [...] ] ``` Using the `--details` flag, you will also get information about the Devfile: ```shell -odo registry --details -o json +odo registry --devfile java-springboot --details -o json ``` ```json [ - { - "name": "python-django", - "displayName": "Django", - "description": "Python3.7 with Django", - "registry": { - "name": "DefaultDevfileRegistry", - "url": "https://registry.devfile.io", - "secure": false - }, - "language": "python", - "tags": [ - "Python", - "pip", - "Django" - ], - "projectType": "django", - "version": "1.0.0", - "starterProjects": [ - "django-example" - ], - "devfileData": { - "devfile": { - "schemaVersion": "2.0.0", - [ devfile.yaml file content ] - }, - "supportedOdoFeatures": { - "dev": true, - "deploy": false, - "debug": true - } - }, - }, [...] + { + "name": "java-springboot", + "displayName": "Spring Boot", + "description": "Spring Boot using Java", + "registry": { + "name": "DefaultDevfileRegistry", + "url": "https://registry.devfile.io", + "secure": false + }, + "language": "Java", + "tags": [ + "Java", + "Spring Boot" + ], + "projectType": "springboot", + "version": "1.2.0", + "versions": [ + { + "version": "1.2.0", + "isDefault": true, + "schemaVersion": "2.1.0", + "starterProjects": [ + "springbootproject" + ] + }, + { + "version": "2.0.0", + "isDefault": false, + "schemaVersion": "2.2.0", + "starterProjects": [ + "springbootproject" + ] + } + ], + "starterProjects": [ + "springbootproject" + ], + "devfileData": { + "devfile": { + "schemaVersion": "2.0.0", + [ devfile.yaml file content ] + }, + "supportedOdoFeatures": { + "dev": true, + "deploy": false, + "debug": true + } + } + }, + [...] ] ``` diff --git a/docs/website/docs/command-reference/registry.md b/docs/website/docs/command-reference/registry.md index 598c7ea7165..6b737ea725d 100644 --- a/docs/website/docs/command-reference/registry.md +++ b/docs/website/docs/command-reference/registry.md @@ -18,10 +18,9 @@ These flags let you filter the listed Devfile stacks: when adding the registry to the preferences with `odo preference add registry `) * `--filter ` to list the Devfile for which the term is found in the devfile name or description -By default, the name, registry and description -of the Devfile stacks are displayed on a table. +By default, the name, registry, description and versions of the Devfile stacks are displayed on a table. -This flag lets you change the content of the output: +The flags below let you change the content of the output: * `--details` to display details about the Devfile stacks * `-o json` to output the information in a JSON format @@ -50,18 +49,18 @@ odo registry ```console $ odo registry - NAME REGISTRY DESCRIPTION - dotnet50 Staging Stack with .NET 5.0 - dotnet50 DefaultDevfileRegistry Stack with .NET 5.0 - dotnet60 Staging Stack with .NET 6.0 - dotnet60 DefaultDevfileRegistry Stack with .NET 6.0 - dotnetcore31 Staging Stack with .NET Core 3.1 - dotnetcore31 DefaultDevfileRegistry Stack with .NET Core 3.1 - go Staging Stack with the latest Go version - go DefaultDevfileRegistry Stack with the latest Go version - java-maven Staging Upstream Maven and OpenJDK 11 - java-maven DefaultDevfileRegistry Upstream Maven and OpenJDK 11 -[...] + NAME REGISTRY DESCRIPTION VERSIONS + dotnet50 Staging Stack with .NET 5.0 1.0.3 + dotnet50 DefaultDevfileRegistry Stack with .NET 5.0 1.0.3 + dotnet60 Staging Stack with .NET 6.0 1.0.2 + dotnet60 DefaultDevfileRegistry Stack with .NET 6.0 1.0.2 + dotnetcore31 Staging Stack with .NET Core 3.1 1.0.3 + dotnetcore31 DefaultDevfileRegistry Stack with .NET Core 3.1 1.0.3 + go Staging Go is an open source programming languag... 1.0.2, 2.0.0 + go DefaultDevfileRegistry Go is an open source programming languag... 1.0.2, 2.0.0 + java-maven Staging Upstream Maven and OpenJDK 11 1.2.0 + java-maven DefaultDevfileRegistry Upstream Maven and OpenJDK 11 1.2.0 + [...] ``` @@ -77,13 +76,13 @@ odo registry --devfile-registry ```console $ odo registry --devfile-registry Staging - NAME REGISTRY DESCRIPTION - dotnet50 Staging Stack with .NET 5.0 - dotnet60 Staging Stack with .NET 6.0 - dotnetcore31 Staging Stack with .NET Core 3.1 - go Staging Stack with the latest Go version - java-maven Staging Upstream Maven and OpenJDK 11 -[...] + NAME REGISTRY DESCRIPTION VERSIONS + dotnet50 Staging Stack with .NET 5.0 1.0.3 + dotnet60 Staging Stack with .NET 6.0 1.0.2 + dotnetcore31 Staging Stack with .NET Core 3.1 1.0.3 + go Staging Go is an open source programming languag... 1.0.2, 2.0.0 + java-maven Staging Upstream Maven and OpenJDK 11 1.2.0 + [...] ``` @@ -98,15 +97,15 @@ odo registry --filter ```console $ odo registry --filter Maven - NAME REGISTRY DESCRIPTION - java-maven Staging Upstream Maven and OpenJDK 11 - java-maven DefaultDevfileRegistry Upstream Maven and OpenJDK 11 - java-openliberty Staging Java application Maven-built stack using... - java-openliberty DefaultDevfileRegistry Java application Maven-built stack using... - java-websphereliberty Staging Java application Maven-built stack using... - java-websphereliberty DefaultDevfileRegistry Java application Maven-built stack using... - java-wildfly-bootable-jar Staging Java stack with WildFly in bootable Jar ... - java-wildfly-bootable-jar DefaultDevfileRegistry Java stack with WildFly in bootable Jar ... + NAME REGISTRY DESCRIPTION VERSIONS + java-maven Staging Upstream Maven and OpenJDK 11 1.2.0 + java-maven DefaultDevfileRegistry Upstream Maven and OpenJDK 11 1.2.0 + java-openliberty Staging Java application Maven-built stack using... 0.9.0 + java-openliberty DefaultDevfileRegistry Java application Maven-built stack using... 0.9.0 + java-websphereliberty Staging Java application Maven-built stack using... 0.9.0 + java-websphereliberty DefaultDevfileRegistry Java application Maven-built stack using... 0.9.0 + java-wildfly-bootable-jar Staging Java stack with WildFly in bootable Jar ... 1.1.0 + java-wildfly-bootable-jar DefaultDevfileRegistry Java stack with WildFly in bootable Jar ... 1.1.0 ``` @@ -125,17 +124,20 @@ Name: java-maven Display Name: Maven Java Registry: Staging Registry URL: https://registry.stage.devfile.io -Version: 1.1.0 +Version: 1.2.0 Description: Upstream Maven and OpenJDK 11 Tags: Java, Maven -Project Type: maven -Language: java +Project Type: Maven +Language: Java Starter Projects: - springbootproject Supported odo Features: - Dev: Y - Deploy: N - Debug: Y +Versions: + - 1.2.0 + ``` diff --git a/docs/website/docs/user-guides/quickstart/quickstart.md b/docs/website/docs/user-guides/quickstart/quickstart.md index 454f2a484b8..0114101d223 100644 --- a/docs/website/docs/user-guides/quickstart/quickstart.md +++ b/docs/website/docs/user-guides/quickstart/quickstart.md @@ -18,30 +18,31 @@ A full list of example applications can be viewed with the `odo registry` comman ```shell $ odo registry - NAME REGISTRY DESCRIPTION - dotnet50 DefaultDevfileRegistry Stack with .NET 5.0 - dotnet60 DefaultDevfileRegistry Stack with .NET 6.0 - dotnetcore31 DefaultDevfileRegistry Stack with .NET Core 3.1 - go DefaultDevfileRegistry Go is an open source programming languag... - java-maven DefaultDevfileRegistry Upstream Maven and OpenJDK 11 - java-openliberty DefaultDevfileRegistry Java application Maven-built stack using... - java-openliberty-gradle DefaultDevfileRegistry Java application Gradle-built stack usin... - java-quarkus DefaultDevfileRegistry Quarkus with Java - java-springboot DefaultDevfileRegistry Spring Boot® using Java - java-vertx DefaultDevfileRegistry Upstream Vert.x using Java - java-websphereliberty DefaultDevfileRegistry Java application Maven-built stack using... - java-websphereliberty-gradle DefaultDevfileRegistry Java application Gradle-built stack usin... - java-wildfly DefaultDevfileRegistry Upstream WildFly - java-wildfly-bootable-jar DefaultDevfileRegistry Java stack with WildFly in bootable Jar ... - nodejs DefaultDevfileRegistry Stack with Node.js 16 - nodejs-angular DefaultDevfileRegistry Angular is a development platform, built... - nodejs-nextjs DefaultDevfileRegistry Next.js gives you the best developer exp... - nodejs-nuxtjs DefaultDevfileRegistry Nuxt is the backbone of your Vue.js proj... - nodejs-react DefaultDevfileRegistry React is a free and open-source front-en... - nodejs-svelte DefaultDevfileRegistry Svelte is a radical new approach to buil... - nodejs-vue DefaultDevfileRegistry Vue is a JavaScript framework for buildi... - php-laravel DefaultDevfileRegistry Laravel is an open-source PHP framework,... - python DefaultDevfileRegistry Python is an interpreted, object-oriente... - python-django DefaultDevfileRegistry Django is a high-level Python web framew... + NAME REGISTRY DESCRIPTION VERSIONS + dotnet50 DefaultDevfileRegistry Stack with .NET 5.0 1.0.3 + dotnet60 DefaultDevfileRegistry Stack with .NET 6.0 1.0.2 + dotnetcore31 DefaultDevfileRegistry Stack with .NET Core 3.1 1.0.3 + go DefaultDevfileRegistry Go is an open source programming languag... 1.0.2, 2.0.0 + java-maven DefaultDevfileRegistry Upstream Maven and OpenJDK 11 1.2.0 + java-openliberty DefaultDevfileRegistry Java application Maven-built stack using... 0.9.0 + java-openliberty-gradle DefaultDevfileRegistry Java application Gradle-built stack usin... 0.4.0 + java-quarkus DefaultDevfileRegistry Quarkus with Java 1.3.0 + java-springboot DefaultDevfileRegistry Spring Boot using Java 1.2.0, 2.0.0 + java-vertx DefaultDevfileRegistry Upstream Vert.x using Java 1.2.0 + java-websphereliberty DefaultDevfileRegistry Java application Maven-built stack using... 0.9.0 + java-websphereliberty-gradle DefaultDevfileRegistry Java application Gradle-built stack usin... 0.4.0 + java-wildfly DefaultDevfileRegistry Upstream WildFly 1.1.0 + java-wildfly-bootable-jar DefaultDevfileRegistry Java stack with WildFly in bootable Jar ... 1.1.0 + nodejs DefaultDevfileRegistry Stack with Node.js 16 2.1.1 + nodejs-angular DefaultDevfileRegistry Angular is a development platform, built... 2.0.2 + nodejs-nextjs DefaultDevfileRegistry Next.js gives you the best developer exp... 1.0.3 + nodejs-nuxtjs DefaultDevfileRegistry Nuxt is the backbone of your Vue.js proj... 1.0.3 + nodejs-react DefaultDevfileRegistry React is a free and open-source front-en... 2.0.2 + nodejs-svelte DefaultDevfileRegistry Svelte is a radical new approach to buil... 1.0.3 + nodejs-vue DefaultDevfileRegistry Vue is a JavaScript framework for buildi... 1.0.2 + php-laravel DefaultDevfileRegistry Laravel is an open-source PHP framework,... 1.0.1 + python DefaultDevfileRegistry Python is an interpreted, object-oriente... 2.1.0, 3.0.0 + python-django DefaultDevfileRegistry Django is a high-level Python web framew... 2.1.0 + ``` diff --git a/pkg/api/registry.go b/pkg/api/registry.go index d050656f695..2171d87f2b6 100644 --- a/pkg/api/registry.go +++ b/pkg/api/registry.go @@ -11,14 +11,29 @@ type Registry struct { // DevfileStack is the main struct for devfile stack type DevfileStack struct { - Name string `json:"name"` - DisplayName string `json:"displayName"` - Description string `json:"description"` - Registry Registry `json:"registry"` - Language string `json:"language"` - Tags []string `json:"tags"` - ProjectType string `json:"projectType"` - Version string `json:"version"` - StarterProjects []string `json:"starterProjects"` - DevfileData *DevfileData `json:"devfileData,omitempty"` + Name string `json:"name"` + DisplayName string `json:"displayName"` + Description string `json:"description"` + Registry Registry `json:"registry"` + Language string `json:"language"` + Tags []string `json:"tags"` + ProjectType string `json:"projectType"` + + // DefaultVersion is the default version. Marshalled as "version" for backward compatibility. + // Deprecated. Use Versions instead. + DefaultVersion string `json:"version"` + Versions []DevfileStackVersion `json:"versions,omitempty"` + + // DefaultStarterProjects is the list of starter projects for the default stack. + // Marshalled as "starterProjects" for backward compatibility. + // Deprecated. Use Versions.StarterProjects instead. + DefaultStarterProjects []string `json:"starterProjects"` + DevfileData *DevfileData `json:"devfileData,omitempty"` +} + +type DevfileStackVersion struct { + Version string `json:"version,omitempty"` + IsDefault bool `json:"isDefault"` + SchemaVersion string `json:"schemaVersion,omitempty"` + StarterProjects []string `json:"starterProjects"` } diff --git a/pkg/odo/cli/registry/registry.go b/pkg/odo/cli/registry/registry.go index 3b326227caf..3fb29af8d9c 100644 --- a/pkg/odo/cli/registry/registry.go +++ b/pkg/odo/cli/registry/registry.go @@ -157,12 +157,35 @@ func (o *ListOptions) printDevfileList(DevfileList []api.DevfileStack) { }) t.SetOutputMirror(log.GetStdout()) - t.AppendHeader(table.Row{"NAME", "REGISTRY", "DESCRIPTION"}) + t.AppendHeader(table.Row{"NAME", "REGISTRY", "DESCRIPTION", "VERSIONS"}) for _, devfileComponent := range DevfileList { // Mark the name as yellow in the index so it's easier to see. name := text.Colors{text.FgHiYellow}.Sprint(devfileComponent.Name) + defaultVersion := devfileComponent.DefaultVersion + if defaultVersion == "" { + for _, v := range devfileComponent.Versions { + if v.IsDefault { + defaultVersion = v.Version + break + } + } + } + + var vList []string + for _, v := range devfileComponent.Versions { + s := v.Version + if v.IsDefault { + s = log.Sbold(s) + } + vList = append(vList, s) + } + if len(vList) == 0 { + //For backward compatibility, add the default version + vList = append(vList, log.Sbold(defaultVersion)) + } + if o.detailsFlag { // Output the details of the component @@ -181,25 +204,35 @@ func (o *ListOptions) printDevfileList(DevfileList []api.DevfileStack) { - Dev: %s - Deploy: %s - Debug: %s +%s: + - %s %s`, log.Sbold("Name"), name, log.Sbold("Display Name"), devfileComponent.DisplayName, log.Sbold("Registry"), devfileComponent.Registry.Name, log.Sbold("Registry URL"), devfileComponent.Registry.URL, - log.Sbold("Version"), devfileComponent.Version, + // Version is kept for backward compatibility + log.Sbold("Version"), defaultVersion, log.Sbold("Description"), devfileComponent.Description, log.Sbold("Tags"), strings.Join(devfileComponent.Tags[:], ", "), log.Sbold("Project Type"), devfileComponent.ProjectType, log.Sbold("Language"), devfileComponent.Language, - log.Sbold("Starter Projects"), strings.Join(devfileComponent.StarterProjects, "\n - "), + log.Sbold("Starter Projects"), strings.Join(devfileComponent.DefaultStarterProjects, "\n - "), log.Sbold("Supported odo Features"), boolToYesNo(devfileComponent.DevfileData.SupportedOdoFeatures.Dev), boolToYesNo(devfileComponent.DevfileData.SupportedOdoFeatures.Deploy), boolToYesNo(devfileComponent.DevfileData.SupportedOdoFeatures.Debug), + log.Sbold("Versions"), + strings.Join(vList, "\n - "), "\n") } else { - // Create a simplified row only showing the name, registry and description. - t.AppendRow(table.Row{name, devfileComponent.Registry.Name, util.TruncateString(devfileComponent.Description, 40, "...")}) + // Create a simplified row only showing the name, registry and description and versions + t.AppendRow(table.Row{ + name, + devfileComponent.Registry.Name, + util.TruncateString(devfileComponent.Description, 40, "..."), + strings.Join(vList, ", "), + }) } } diff --git a/pkg/registry/registry.go b/pkg/registry/registry.go index 3d71c80449b..ae5da848364 100644 --- a/pkg/registry/registry.go +++ b/pkg/registry/registry.go @@ -9,6 +9,7 @@ import ( "strings" "sync" + "github.com/blang/semver" devfilev1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" dfutil "github.com/devfile/library/pkg/util" indexSchema "github.com/devfile/registry-support/index/generator/schema" @@ -197,9 +198,18 @@ func getRegistryStacks(ctx context.Context, preferenceClient preference.Client, return nil, &ErrGithubRegistryNotSupported{} } // OCI-based registry - devfileIndex, err := library.GetRegistryIndex(registry.URL, segment.GetRegistryOptions(ctx), indexSchema.StackDevfileType) + options := segment.GetRegistryOptions(ctx) + options.NewIndexSchema = true + devfileIndex, err := library.GetRegistryIndex(registry.URL, options, indexSchema.StackDevfileType) if err != nil { - return nil, err + // Fallback to the "old" index + klog.V(3).Infof("error while accessing the v2index endpoint for registry %s (%s) => falling back to the old index endpoint: %v", + registry.Name, registry.URL, err) + options.NewIndexSchema = false + devfileIndex, err = library.GetRegistryIndex(registry.URL, options, indexSchema.StackDevfileType) + if err != nil { + return nil, err + } } return createRegistryDevfiles(registry, devfileIndex) } @@ -208,16 +218,41 @@ func createRegistryDevfiles(registry api.Registry, devfileIndex []indexSchema.Sc registryDevfiles := make([]api.DevfileStack, 0, len(devfileIndex)) for _, devfileIndexEntry := range devfileIndex { stackDevfile := api.DevfileStack{ - Name: devfileIndexEntry.Name, - DisplayName: devfileIndexEntry.DisplayName, - Description: devfileIndexEntry.Description, - Registry: registry, - Language: devfileIndexEntry.Language, - Tags: devfileIndexEntry.Tags, - ProjectType: devfileIndexEntry.ProjectType, - StarterProjects: devfileIndexEntry.StarterProjects, - Version: devfileIndexEntry.Version, + Name: devfileIndexEntry.Name, + DisplayName: devfileIndexEntry.DisplayName, + Description: devfileIndexEntry.Description, + Registry: registry, + Language: devfileIndexEntry.Language, + Tags: devfileIndexEntry.Tags, + ProjectType: devfileIndexEntry.ProjectType, + DefaultStarterProjects: devfileIndexEntry.StarterProjects, + DefaultVersion: devfileIndexEntry.Version, } + for _, v := range devfileIndexEntry.Versions { + if v.Default { + // There should be only 1 default version. But if there is more than one, the last one will be used. + stackDevfile.DefaultVersion = v.Version + stackDevfile.DefaultStarterProjects = v.StarterProjects + } + stackDevfile.Versions = append(stackDevfile.Versions, api.DevfileStackVersion{ + IsDefault: v.Default, + Version: v.Version, + SchemaVersion: v.SchemaVersion, + StarterProjects: v.StarterProjects, + }) + } + sort.Slice(stackDevfile.Versions, func(i, j int) bool { + vi, err := semver.Make(stackDevfile.Versions[i].Version) + if err != nil { + return false + } + vj, err := semver.Make(stackDevfile.Versions[j].Version) + if err != nil { + return false + } + return vi.LT(vj) + }) + registryDevfiles = append(registryDevfiles, stackDevfile) } diff --git a/pkg/registry/registry_test.go b/pkg/registry/registry_test.go index c4bfcee2e4c..5558d5ae477 100644 --- a/pkg/registry/registry_test.go +++ b/pkg/registry/registry_test.go @@ -276,59 +276,246 @@ func TestListDevfileStacks(t *testing.T) { } func TestGetRegistryDevfiles(t *testing.T) { - // Start a local HTTP server - server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - // Send response to be tested - _, err := rw.Write([]byte( - ` - [ - { - "name": "nodejs", - "displayName": "NodeJS Angular Web Application", - "description": "Stack for developing NodeJS Angular Web Application", - "tags": [ - "NodeJS", - "Angular", - "Alpine" - ], - "language": "nodejs", - "icon": "/images/angular.svg", - "globalMemoryLimit": "2686Mi", - "links": { - "self": "/devfiles/angular/devfile.yaml" - } - } - ] - `, - )) - if err != nil { - t.Error(err) + const registryName = "some registry" + const ( + v1IndexResponse = ` +[ + { + "name": "nodejs", + "displayName": "NodeJS Angular Web Application", + "description": "Stack for developing NodeJS Angular Web Application", + "version": "1.2.3", + "tags": [ + "NodeJS", + "Angular", + "Alpine" + ], + "language": "nodejs", + "icon": "/images/angular.svg", + "globalMemoryLimit": "2686Mi", + "links": { + "self": "/devfiles/angular/devfile.yaml" } - })) - // Close the server when test finishes - defer server.Close() + } +] +` + v2IndexResponse = ` +[ + { + "name": "go", + "displayName": "Go Runtime", + "description": "Go is an open source programming language that makes it easy to build simple, reliable, and efficient software.", + "type": "stack", + "tags": [ + "Go" + ], + "icon": "https://raw.githubusercontent.com/devfile-samples/devfile-stack-icons/main/golang.svg", + "projectType": "Go", + "language": "Go", + "provider": "Red Hat", + "versions": [ + { + "version": "2.0.0", + "schemaVersion": "2.2.0", + "description": "Go is an open source programming language that makes it easy to build simple, reliable, and efficient software.", + "tags": [ + "Go" + ], + "icon": "https://raw.githubusercontent.com/devfile-samples/devfile-stack-icons/main/golang.svg", + "links": { + "self": "devfile-catalog/go:2.0.0" + }, + "resources": [ + "devfile.yaml" + ], + "starterProjects": [ + "go-starter" + ] + }, + { + "version": "1.0.2", + "schemaVersion": "2.1.0", + "default": true, + "description": "Go is an open source programming language that makes it easy to build simple, reliable, and efficient software.", + "tags": [ + "Go" + ], + "icon": "https://raw.githubusercontent.com/devfile-samples/devfile-stack-icons/main/golang.svg", + "links": { + "self": "devfile-catalog/go:1.0.2" + }, + "resources": [ + "devfile.yaml" + ], + "starterProjects": [ + "go-starter" + ] + } + ] + } +] +` + ) - const registryName = "some registry" - tests := []struct { - name string - registry api.Registry - want []api.DevfileStack - }{ + type test struct { + name string + registryServerProvider func(t *testing.T) (*httptest.Server, string) + wantErr bool + wantProvider func(registryUrl string) []api.DevfileStack + } + tests := []test{ { - name: "Test NodeJS devfile index", - registry: api.Registry{Name: registryName, URL: server.URL}, - want: []api.DevfileStack{ - { - Name: "nodejs", - DisplayName: "NodeJS Angular Web Application", - Description: "Stack for developing NodeJS Angular Web Application", - Registry: api.Registry{ - Name: registryName, - URL: server.URL, + name: "GitHub-based registry: github.com", + registryServerProvider: func(t *testing.T) (*httptest.Server, string) { + return nil, "https://github.com/redhat-developer/odo" + }, + wantErr: true, + }, + { + name: "GitHub-based registry: raw.githubusercontent.com", + registryServerProvider: func(t *testing.T) (*httptest.Server, string) { + return nil, "https://raw.githubusercontent.com/redhat-developer/odo" + }, + wantErr: true, + }, + { + name: "GitHub-based registry: *.github.com", + registryServerProvider: func(t *testing.T) (*httptest.Server, string) { + return nil, "https://redhat-developer.github.com/odo" + }, + wantErr: true, + }, + { + name: "GitHub-based registry: *.raw.githubusercontent.com", + registryServerProvider: func(t *testing.T) (*httptest.Server, string) { + return nil, "https://redhat-developer.raw.githubusercontent.com/odo" + }, + wantErr: true, + }, + { + name: "Devfile registry server: client error (4xx)", + registryServerProvider: func(t *testing.T) (*httptest.Server, string) { + server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.WriteHeader(http.StatusNotFound) + })) + return server, server.URL + }, + wantErr: true, + }, + { + name: "Devfile registry server: server error (5xx)", + registryServerProvider: func(t *testing.T) (*httptest.Server, string) { + server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.WriteHeader(http.StatusInternalServerError) + })) + return server, server.URL + }, + wantErr: true, + }, + { + name: "Devfile registry: only /index", + registryServerProvider: func(t *testing.T) (*httptest.Server, string) { + server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + if req.URL.Path != "/index" { + rw.WriteHeader(http.StatusNotFound) + return + } + _, err := rw.Write([]byte(v1IndexResponse)) + if err != nil { + t.Error(err) + } + })) + return server, server.URL + }, + wantProvider: func(registryUrl string) []api.DevfileStack { + return []api.DevfileStack{ + { + Name: "nodejs", + DisplayName: "NodeJS Angular Web Application", + Description: "Stack for developing NodeJS Angular Web Application", + Registry: api.Registry{Name: registryName, URL: registryUrl}, + Language: "nodejs", + Tags: []string{"NodeJS", "Angular", "Alpine"}, + DefaultVersion: "1.2.3", }, - Language: "nodejs", - Tags: []string{"NodeJS", "Angular", "Alpine"}, - }, + } + }, + }, + { + name: "Devfile registry: only /v2index", + registryServerProvider: func(t *testing.T) (*httptest.Server, string) { + server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + if req.URL.Path != "/v2index" { + rw.WriteHeader(http.StatusNotFound) + return + } + _, err := rw.Write([]byte(v2IndexResponse)) + if err != nil { + t.Error(err) + } + })) + return server, server.URL + }, + wantProvider: func(registryUrl string) []api.DevfileStack { + return []api.DevfileStack{ + { + Name: "go", + DisplayName: "Go Runtime", + Description: "Go is an open source programming language that makes it easy to build simple, reliable, and efficient software.", + Registry: api.Registry{Name: registryName, URL: registryUrl}, + Language: "Go", + ProjectType: "Go", + Tags: []string{"Go"}, + DefaultVersion: "1.0.2", + DefaultStarterProjects: []string{"go-starter"}, + Versions: []api.DevfileStackVersion{ + {Version: "1.0.2", IsDefault: true, SchemaVersion: "2.1.0", StarterProjects: []string{"go-starter"}}, + {Version: "2.0.0", IsDefault: false, SchemaVersion: "2.2.0", StarterProjects: []string{"go-starter"}}, + }, + }, + } + }, + }, + { + name: "Devfile registry: both /index and /v2index => v2index has precedence", + registryServerProvider: func(t *testing.T) (*httptest.Server, string) { + server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + var resp string + switch req.URL.Path { + case "/index": + resp = v1IndexResponse + case "/v2index": + resp = v2IndexResponse + } + if resp == "" { + rw.WriteHeader(http.StatusNotFound) + return + } + _, err := rw.Write([]byte(resp)) + if err != nil { + t.Error(err) + } + })) + return server, server.URL + }, + wantProvider: func(registryUrl string) []api.DevfileStack { + return []api.DevfileStack{ + { + Name: "go", + DisplayName: "Go Runtime", + Description: "Go is an open source programming language that makes it easy to build simple, reliable, and efficient software.", + Registry: api.Registry{Name: registryName, URL: registryUrl}, + Language: "Go", + ProjectType: "Go", + Tags: []string{"Go"}, + DefaultVersion: "1.0.2", + DefaultStarterProjects: []string{"go-starter"}, + Versions: []api.DevfileStackVersion{ + {Version: "1.0.2", IsDefault: true, SchemaVersion: "2.1.0", StarterProjects: []string{"go-starter"}}, + {Version: "2.0.0", IsDefault: false, SchemaVersion: "2.2.0", StarterProjects: []string{"go-starter"}}, + }, + }, + } }, }, } @@ -337,13 +524,24 @@ func TestGetRegistryDevfiles(t *testing.T) { t.Run(tt.name, func(t *testing.T) { ctrl := gomock.NewController(t) prefClient := preference.NewMockClient(ctrl) - ctx := context.Background() - ctx = envcontext.WithEnvConfig(ctx, config.Configuration{}) - got, err := getRegistryStacks(ctx, prefClient, tt.registry) + ctx := envcontext.WithEnvConfig(context.Background(), config.Configuration{}) + server, url := tt.registryServerProvider(t) + if server != nil { + defer server.Close() + } - if diff := cmp.Diff(tt.want, got); diff != "" { - t.Errorf("getRegistryStacks() mismatch (-want +got):\n%s", diff) - t.Logf("Error message is: %v", err) + got, err := getRegistryStacks(ctx, prefClient, api.Registry{Name: registryName, URL: url}) + + if tt.wantErr != (err != nil) { + t.Errorf("error = %v, wantErr %v", err, tt.wantErr) + } + + if tt.wantProvider != nil { + want := tt.wantProvider(url) + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("getRegistryStacks() mismatch (-want +got):\n%s", diff) + t.Logf("Error message is: %v", err) + } } }) } diff --git a/tests/integration/cmd_devfile_registry_test.go b/tests/integration/cmd_devfile_registry_test.go index cdca960885f..6a26873bfda 100644 --- a/tests/integration/cmd_devfile_registry_test.go +++ b/tests/integration/cmd_devfile_registry_test.go @@ -5,6 +5,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "github.com/tidwall/gjson" "github.com/redhat-developer/odo/tests/helper" ) @@ -53,7 +54,27 @@ var _ = Describe("odo devfile registry command tests", func() { By("using human readable output", func() { output := helper.Cmd("odo", args...).ShouldPass().Out() - helper.MatchAllInOutput(output, []string{"nodejs-starter", "JavaScript", "Node.js Runtime", "Dev: Y"}) + By("checking headers", func() { + for _, h := range []string{ + "Name", + "Display Name", + "Registry", + "Registry URL", + "Version", + "Description", + "Tags", + "Project Type", + "Language", + "Starter Projects", + "Supported odo Features", + "Versions", + } { + Expect(output).Should(ContainSubstring(h + ":")) + } + }) + By("checking values", func() { + helper.MatchAllInOutput(output, []string{"nodejs-starter", "JavaScript", "Node.js Runtime", "Dev: Y"}) + }) }) By("using JSON output", func() { args = append(args, "-o", "json") @@ -66,9 +87,21 @@ var _ = Describe("odo devfile registry command tests", func() { helper.JsonPathContentContain(stdout, "0.description", "Node") helper.JsonPathContentContain(stdout, "0.language", "JavaScript") helper.JsonPathContentContain(stdout, "0.projectType", "Node.js") - helper.JsonPathContentContain(stdout, "0.starterProjects.0", "nodejs-starter") helper.JsonPathContentContain(stdout, "0.devfileData.devfile.metadata.name", "nodejs") helper.JsonPathContentContain(stdout, "0.devfileData.supportedOdoFeatures.dev", "true") + + defaultVersion := gjson.Get(stdout, "0.version").String() + By("returning backward-compatible information linked to the default stack version", func() { + helper.JsonPathContentContain(stdout, "0.starterProjects.0", "nodejs-starter") + Expect(defaultVersion).ShouldNot(BeEmpty()) + }) + By("returning a non-empty list of versions", func() { + versions := gjson.Get(stdout, "0.versions").Array() + Expect(versions).ShouldNot(BeEmpty()) + }) + By("listing the default version as such in the versions list", func() { + Expect(gjson.Get(stdout, "0.versions.#(isDefault==true).version").String()).Should(Equal(defaultVersion)) + }) }) }) @@ -76,7 +109,12 @@ var _ = Describe("odo devfile registry command tests", func() { args := []string{"registry", "--devfile", "python", "--devfile-registry", "DefaultDevfileRegistry"} By("using human readable output", func() { output := helper.Cmd("odo", args...).ShouldPass().Out() - helper.MatchAllInOutput(output, []string{"python"}) + By("checking table header", func() { + helper.MatchAllInOutput(output, []string{"NAME", "REGISTRY", "DESCRIPTION", "VERSIONS"}) + }) + By("checking table row", func() { + helper.MatchAllInOutput(output, []string{"python"}) + }) }) By("using JSON output", func() { args = append(args, "-o", "json") @@ -89,9 +127,20 @@ var _ = Describe("odo devfile registry command tests", func() { helper.JsonPathContentContain(stdout, "0.description", "Python is an interpreted") helper.JsonPathContentContain(stdout, "0.language", "Python") helper.JsonPathContentContain(stdout, "0.projectType", "Python") - helper.JsonPathContentContain(stdout, "0.starterProjects.0", "flask-example") helper.JsonPathContentContain(stdout, "0.devfileData", "") + defaultVersion := gjson.Get(stdout, "0.version").String() + By("returning backward-compatible information linked to the default stack version", func() { + helper.JsonPathContentContain(stdout, "0.starterProjects.0", "flask-example") + Expect(defaultVersion).ShouldNot(BeEmpty()) + }) + By("returning a non-empty list of versions", func() { + versions := gjson.Get(stdout, "0.versions").Array() + Expect(versions).ShouldNot(BeEmpty()) + }) + By("listing the default version as such in the versions list", func() { + Expect(gjson.Get(stdout, "0.versions.#(isDefault==true).version").String()).Should(Equal(defaultVersion)) + }) }) })