From 3fec1a2b504148312b276e736a03ad8d737d5274 Mon Sep 17 00:00:00 2001 From: Jordan Dubrick Date: Fri, 28 Jun 2024 09:20:42 -0400 Subject: [PATCH] Feature: Last Modified Filter Parameter (#249) * add and update test data Signed-off-by: Jordan Dubrick * add new lastModified field to generated index Signed-off-by: Jordan Dubrick * address gosec flags Signed-off-by: Jordan Dubrick * add nosec exemp Signed-off-by: Jordan Dubrick * update openapi spec and generated code Signed-off-by: Jordan Dubrick * add filtering logic for last modified parameter Signed-off-by: Jordan Dubrick * update vendor for server Signed-off-by: Jordan Dubrick * add last modified file to test resources Signed-off-by: Jordan Dubrick * make generator more readable Signed-off-by: Jordan Dubrick * run codegen Signed-off-by: Jordan Dubrick * address review changes Signed-off-by: Jordan Dubrick * add new test case Signed-off-by: Jordan Dubrick --------- Signed-off-by: Jordan Dubrick --- index/generator/library/library.go | 62 ++++ index/generator/library/library_test.go | 152 +++++++++ index/generator/schema/schema.go | 24 +- .../generator/tests/registry/index_main.json | 58 +++- .../tests/registry/last_modified.json | 81 +++++ index/server/openapi.yaml | 27 ++ index/server/pkg/server/endpoint.gen.go | 150 +++++---- index/server/pkg/server/endpoint.go | 25 ++ index/server/pkg/server/types.gen.go | 27 ++ index/server/pkg/server/types.go | 4 + index/server/pkg/util/filter.go | 58 ++++ index/server/pkg/util/filter_test.go | 292 ++++++++++++++++++ index/server/pkg/util/util.go | 56 ++++ index/server/pkg/util/util_test.go | 200 ++++++++++++ .../index/generator/library/library.go | 62 ++++ .../index/generator/schema/schema.go | 24 +- tests/registry/last_modified.json | 57 ++++ 17 files changed, 1282 insertions(+), 77 deletions(-) create mode 100644 index/generator/tests/registry/last_modified.json create mode 100644 tests/registry/last_modified.json diff --git a/index/generator/library/library.go b/index/generator/library/library.go index b13927ff..d735619a 100644 --- a/index/generator/library/library.go +++ b/index/generator/library/library.go @@ -24,6 +24,7 @@ import ( "path" "path/filepath" "strings" + "time" devfileParser "github.com/devfile/library/v2/pkg/devfile" "github.com/devfile/library/v2/pkg/devfile/parser" @@ -122,6 +123,10 @@ func GenerateIndexStruct(registryDirPath string, force bool) ([]schema.Schema, e index = append(index, indexFromExtraDevfileEntries...) } + index, err = SetLastModifiedValue(index, registryDirPath) + if err != nil { + return index, err + } return index, nil } @@ -705,6 +710,63 @@ func validateStackInfo(stackInfo schema.Schema, stackfolderDir string) []error { return errors } +// SetLastModifiedValue adds the last modified value to a pre-created index +// The last modified dates are contained in a file named last_modified.json that is apart of the registry dir +/* #nosec G304 -- lastModFile is produced from filepath.Join which cleans the input path */ +func SetLastModifiedValue(index []schema.Schema, registryDirPath string) ([]schema.Schema, error) { + lastModFile := filepath.Join(registryDirPath, "last_modified.json") + bytes, err := os.ReadFile(lastModFile) + if err != nil { + return index, err + } + + var lastModifiedEntries schema.LastModifiedInfo + err = json.Unmarshal(bytes, &lastModifiedEntries) + if err != nil { + return index, err + } + + lastModifiedEntriesMap := make(map[string]map[string]time.Time) + + for idx := range lastModifiedEntries.Stacks { + updateLastModifiedMap(lastModifiedEntriesMap, &lastModifiedEntries.Stacks[idx]) + } + + for idx := range lastModifiedEntries.Samples { + updateLastModifiedMap(lastModifiedEntriesMap, &lastModifiedEntries.Samples[idx]) + } + + for i := range index { + var mostCurrentLastModifiedDate time.Time + for j := range index[i].Versions { + schemaItem := index[i] // a stack or sample + version := schemaItem.Versions[j] + versionNum := version.Version + lastModifiedDate := lastModifiedEntriesMap[schemaItem.Name][versionNum] + updateSchemaLastModified(&schemaItem, j, lastModifiedDate) + if lastModifiedDate.After(mostCurrentLastModifiedDate) { + mostCurrentLastModifiedDate = lastModifiedDate + } + } + // lastModified of a stack or sample will be the date any version of it was last changed + index[i].LastModified = mostCurrentLastModifiedDate.Format(time.RFC3339) + } + + return index, nil +} + +func updateLastModifiedMap(m map[string]map[string]time.Time, entry *schema.LastModifiedEntry) { + _, ok := m[entry.Name] + if !ok { + m[entry.Name] = make(map[string]time.Time) + } + m[entry.Name][entry.Version] = entry.LastModified +} + +func updateSchemaLastModified(s *schema.Schema, versionIndx int, lastModifiedDate time.Time) { + s.Versions[versionIndx].LastModified = lastModifiedDate.Format(time.RFC3339) +} + // In checks if the value is in the array func inArray(arr []string, value string) bool { for _, item := range arr { diff --git a/index/generator/library/library_test.go b/index/generator/library/library_test.go index 72fdc225..8db6d17c 100644 --- a/index/generator/library/library_test.go +++ b/index/generator/library/library_test.go @@ -850,3 +850,155 @@ func TestCheckForRequiredMetadata(t *testing.T) { } } } + +func TestSetLastModifiedValue(t *testing.T) { + testRegistryDirPath := "../tests/registry" + index := []schema.Schema{ + schema.Schema{ + Name: "go", + DisplayName: "Go Runtime", + Description: "Stack with the latest Go version", + Type: "stack", + Tags: []string{"Go"}, + Icon: "https://raw.githubusercontent.com/devfile-samples/devfile-stack-icons/main/golang.svg", + ProjectType: "go", + Language: "go", + Provider: "Red Hat", + Versions: []schema.Version{ + schema.Version{ + Version: "1.2.0", + SchemaVersion: "2.1.0", + Description: "Stack with the latest Go version with devfile v2.1.0 schema version", + Icon: "https://raw.githubusercontent.com/devfile-samples/devfile-stack-icons/main/golang.svg", + Tags: []string{"testtag"}, + Links: map[string]string{"self": "devfile-catalog/go:1.2.0"}, + Resources: []string{"devfile.yaml"}, + StarterProjects: []string{"go-starter"}, + CommandGroups: map[schema.CommandGroupKind]bool{"build": true, "debug": false, "deploy": false, "run": true, "test": false}, + }, + schema.Version{ + Version: "1.1.0", + SchemaVersion: "2.0.0", + Description: "Stack with the latest Go version with devfile v2.0.0 schema version", + Icon: "https://raw.githubusercontent.com/devfile-samples/devfile-stack-icons/main/golang.svg", + Tags: []string{"Go"}, + Links: map[string]string{"self": "devfile-catalog/go:1.1.0"}, + Resources: []string{"devfile.yaml"}, + StarterProjects: []string{"go-starter"}, + CommandGroups: map[schema.CommandGroupKind]bool{"build": true, "debug": false, "deploy": false, "run": true, "test": false}, + }, + }, + }, + schema.Schema{ + Name: "code-with-quarkus", + DisplayName: "Basic Quarkus", + Description: "A simple Hello World Java application using Quarkus", + Type: "sample", + Tags: []string{"Java", "Quarkus"}, + Icon: "https://raw.githubusercontent.com/elsony/devfile-sample-code-with-quarkus/main/.devfile/icon/quarkus.png", + ProjectType: "quarkus", + Language: "java", + Provider: "Red Hat", + Versions: []schema.Version{ + schema.Version{ + Version: "1.1.0", + SchemaVersion: "2.0.0", + Description: "java quarkus with devfile v2.0.0", + Default: true, + Git: &schema.Git{ + Remotes: map[string]string{"origin": "https://github.com/elsony/devfile-sample-code-with-quarkus.git"}, + }, + }, + }, + }, + } + + wantIndex := []schema.Schema{ + schema.Schema{ + Name: "go", + DisplayName: "Go Runtime", + Description: "Stack with the latest Go version", + Type: "stack", + Tags: []string{"Go"}, + Icon: "https://raw.githubusercontent.com/devfile-samples/devfile-stack-icons/main/golang.svg", + ProjectType: "go", + Language: "go", + Provider: "Red Hat", + LastModified: "2023-11-08T12:54:08Z", + Versions: []schema.Version{ + schema.Version{ + Version: "1.2.0", + SchemaVersion: "2.1.0", + Description: "Stack with the latest Go version with devfile v2.1.0 schema version", + Icon: "https://raw.githubusercontent.com/devfile-samples/devfile-stack-icons/main/golang.svg", + Tags: []string{"testtag"}, + Links: map[string]string{"self": "devfile-catalog/go:1.2.0"}, + Resources: []string{"devfile.yaml"}, + StarterProjects: []string{"go-starter"}, + CommandGroups: map[schema.CommandGroupKind]bool{"build": true, "debug": false, "deploy": false, "run": true, "test": false}, + LastModified: "2023-04-08T11:51:08Z", + }, + schema.Version{ + Version: "1.1.0", + SchemaVersion: "2.0.0", + Description: "Stack with the latest Go version with devfile v2.0.0 schema version", + Icon: "https://raw.githubusercontent.com/devfile-samples/devfile-stack-icons/main/golang.svg", + Tags: []string{"Go"}, + Links: map[string]string{"self": "devfile-catalog/go:1.1.0"}, + Resources: []string{"devfile.yaml"}, + StarterProjects: []string{"go-starter"}, + CommandGroups: map[schema.CommandGroupKind]bool{"build": true, "debug": false, "deploy": false, "run": true, "test": false}, + LastModified: "2023-11-08T12:54:08Z", + }, + }, + }, + schema.Schema{ + Name: "code-with-quarkus", + DisplayName: "Basic Quarkus", + Description: "A simple Hello World Java application using Quarkus", + Type: "sample", + Tags: []string{"Java", "Quarkus"}, + Icon: "https://raw.githubusercontent.com/elsony/devfile-sample-code-with-quarkus/main/.devfile/icon/quarkus.png", + ProjectType: "quarkus", + Language: "java", + Provider: "Red Hat", + LastModified: "2024-04-19T11:45:48+01:00", + Versions: []schema.Version{ + schema.Version{ + Version: "1.1.0", + SchemaVersion: "2.0.0", + Description: "java quarkus with devfile v2.0.0", + Default: true, + Git: &schema.Git{ + Remotes: map[string]string{"origin": "https://github.com/elsony/devfile-sample-code-with-quarkus.git"}, + }, + LastModified: "2024-04-19T11:45:48+01:00", + }, + }, + }, + } + + tests := []struct { + name string + index []schema.Schema + wantIndex []schema.Schema + }{ + { + name: "Test SetLastModifiedValue", + index: index, + wantIndex: wantIndex, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotIndex, err := SetLastModifiedValue(index, testRegistryDirPath) + if err != nil { + t.Errorf("Failed to set last modified value: %v", err) + } + if !reflect.DeepEqual(tt.wantIndex, gotIndex) { + t.Errorf("Generated index does not match") + } + }) + } +} diff --git a/index/generator/schema/schema.go b/index/generator/schema/schema.go index 126c9070..82c69d60 100644 --- a/index/generator/schema/schema.go +++ b/index/generator/schema/schema.go @@ -16,6 +16,8 @@ package schema import ( + "time" + apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" ) @@ -38,6 +40,7 @@ Sample index file: ], "projectType": "maven", "language": "java", + "lastModified": "2024-05-13T12:32:02+02:00", "versions": [ { "version": "1.1.0", @@ -61,7 +64,8 @@ Sample index file: ], "starterProjects": [ "springbootproject" - ] + ], + "lastModified": "2024-05-13T12:32:02+02:00" } ] }, @@ -79,6 +83,7 @@ Sample index file: ], "projectType": "quarkus", "language": "java", + "lastModified": "2024-04-29T17:08:43+03:00", "versions": [ { "version": "1.1.0", @@ -108,7 +113,8 @@ Sample index file: "starterProjects": [ "community", "redhat-product" - ] + ], + "lastModified": "2024-04-29T17:08:43+03:00" } ] } @@ -136,6 +142,7 @@ starterProjects: string[] - The project templates that can be used in the devfil git: *git - The information of remote repositories provider: string - The devfile provider information versions: []Version - The list of stack versions information +lastModified: string - The date that a version of this stack/sample was last changed */ // Schema is the index file schema @@ -161,6 +168,7 @@ type Schema struct { Provider string `yaml:"provider,omitempty" json:"provider,omitempty"` SupportUrl string `yaml:"supportUrl,omitempty" json:"supportUrl,omitempty"` Versions []Version `yaml:"versions,omitempty" json:"versions,omitempty"` + LastModified string `yaml:"lastModified,omitempty" json:"lastModified,omitempty"` } // DevfileType describes the type of devfile @@ -264,4 +272,16 @@ type Version struct { DeploymentScopes map[DeploymentScopeKind]bool `yaml:"deploymentScopes,omitempty" json:"deploymentScopes,omitempty"` Resources []string `yaml:"resources,omitempty" json:"resources,omitempty"` StarterProjects []string `yaml:"starterProjects,omitempty" json:"starterProjects,omitempty"` + LastModified string `yaml:"lastModified,omitempty" json:"lastModified,omitempty"` +} + +type LastModifiedEntry struct { + Name string `yaml:"name,omitempty" json:"name,omitempty"` + Version string `yaml:"version,omitempty" json:"version,omitempty"` + LastModified time.Time `yaml:"lastModified,omitempty" json:"lastModified,omitempty"` +} + +type LastModifiedInfo struct { + Stacks []LastModifiedEntry `yaml:"stacks,omitempty" json:"stacks,omitempty"` + Samples []LastModifiedEntry `yaml:"samples,omitempty" json:"samples,omitempty"` } diff --git a/index/generator/tests/registry/index_main.json b/index/generator/tests/registry/index_main.json index bee022b3..939abcee 100644 --- a/index/generator/tests/registry/index_main.json +++ b/index/generator/tests/registry/index_main.json @@ -8,6 +8,7 @@ "icon": "https://raw.githubusercontent.com/devfile-samples/devfile-stack-icons/main/golang.svg", "projectType": "go", "language": "go", + "lastModified": "2023-11-08T12:54:08Z", "provider": "Red Hat", "versions": [ { @@ -27,7 +28,8 @@ "deploy": false, "run": true, "test": false - } + }, + "lastModified": "2023-04-08T11:51:08Z" }, { "version": "1.1.0", @@ -47,7 +49,8 @@ "deploy": false, "run": true, "test": false - } + }, + "lastModified": "2023-11-08T12:54:08Z" } ] }, @@ -60,6 +63,7 @@ "tags": ["Java", "Maven"], "projectType": "maven", "language": "java", + "lastModified": "2024-05-13T12:32:02+02:00", "provider": "Red Hat", "versions": [ { @@ -80,7 +84,8 @@ "deploy": false, "run": true, "test": false - } + }, + "lastModified": "2024-05-13T12:32:02+02:00" } ] }, @@ -92,6 +97,7 @@ "type": "stack", "projectType": "docker", "language": "java", + "lastModified": "2024-04-23T06:20:34-04:00", "provider": "Red Hat", "versions": [ { @@ -111,7 +117,8 @@ "deploy": false, "run": true, "test": true - } + }, + "lastModified": "2024-04-23T06:20:34-04:00" } ] }, @@ -124,6 +131,7 @@ "tags": ["Java", "Quarkus"], "projectType": "quarkus", "language": "java", + "lastModified": "2024-04-29T17:08:43+03:00", "provider": "Red Hat", "versions": [ { @@ -144,7 +152,8 @@ "deploy": false, "run": true, "test": false - } + }, + "lastModified": "2024-04-29T17:08:43+03:00" } ] }, @@ -157,6 +166,7 @@ "icon": "https://raw.githubusercontent.com/devfile-samples/devfile-stack-icons/main/spring.svg", "projectType": "spring", "language": "java", + "lastModified": "2024-05-13T12:32:02+02:00", "versions": [ { "version": "1.1.0", @@ -176,7 +186,8 @@ "deploy": false, "run": true, "test": false - } + }, + "lastModified": "2024-05-13T12:32:02+02:00" } ] }, @@ -189,6 +200,7 @@ "tags": ["Java", "Vert.x"], "projectType": "vertx", "language": "java", + "lastModified": "2024-05-13T12:32:02+02:00", "versions": [ { "version": "1.1.0", @@ -228,7 +240,8 @@ "deploy": false, "run": true, "test": false - } + }, + "lastModified": "2024-05-13T12:32:02+02:00" } ] }, @@ -241,6 +254,7 @@ "tags": ["Java", "WildFly"], "projectType": "wildfly", "language": "java", + "lastModified": "2024-04-22T23:00:14+02:00", "versions": [ { "version": "1.0.0", @@ -269,7 +283,8 @@ "deploy": false, "run": true, "test": false - } + }, + "lastModified": "2024-04-22T23:00:14+02:00" } ] }, @@ -282,6 +297,7 @@ "tags": ["RHEL8", "Java", "OpenJDK", "Maven", "WildFly", "Microprofile", "WildFly Bootable"], "projectType": "WildFly", "language": "java", + "lastModified": "2024-05-27T11:00:03+02:00", "versions": [ { "version": "1.0.0", @@ -318,7 +334,8 @@ "deploy": false, "run": true, "test": false - } + }, + "lastModified": "2024-05-27T11:00:03+02:00" } ] }, @@ -331,6 +348,7 @@ "tags": ["NodeJS", "Express", "ubi8"], "projectType": "nodejs", "language": "nodejs", + "lastModified": "2024-03-25T12:16:30-04:00", "versions": [ { "version": "1.0.0", @@ -350,7 +368,8 @@ "deploy": false, "run": true, "test": true - } + }, + "lastModified": "2024-03-25T12:16:30-04:00" } ] }, @@ -363,6 +382,7 @@ "tags": ["Python", "pip"], "projectType": "python", "language": "python", + "lastModified": "2024-05-28T17:20:11+02:00", "versions": [ { "version": "1.0.0", @@ -382,7 +402,8 @@ "deploy": false, "run": true, "test": false - } + }, + "lastModified": "2024-05-28T17:20:11+02:00" } ] }, @@ -395,6 +416,7 @@ "tags": ["Python", "pip", "Django"], "projectType": "django", "language": "python", + "lastModified": "2024-05-28T17:20:11+02:00", "versions": [ { "version": "1.0.0", @@ -414,7 +436,8 @@ "deploy": false, "run": true, "test": false - } + }, + "lastModified": "2024-05-28T17:20:11+02:00" } ] }, @@ -427,6 +450,7 @@ "icon": "https://raw.githubusercontent.com/devfile-samples/devfile-stack-icons/main/node-js.svg", "projectType": "nodejs", "language": "nodejs", + "lastModified": "2024-06-05T15:26:11-04:00", "versions": [ { "version": "1.0.0", @@ -436,7 +460,8 @@ "origin": "https://github.com/redhat-developer/devfile-sample" } }, - "description": "nodejs with devfile v2.0.0" + "description": "nodejs with devfile v2.0.0", + "lastModified": "2024-06-05T15:26:11-04:00" }, { "version": "1.0.1", @@ -447,7 +472,8 @@ "origin": "https://github.com/nodeshift-starters/devfile-sample" } }, - "description": "nodejs with devfile v2.2.0" + "description": "nodejs with devfile v2.2.0", + "lastModified": "2024-06-05T15:26:11-04:00" } ] }, @@ -460,6 +486,7 @@ "icon": "https://raw.githubusercontent.com/elsony/devfile-sample-code-with-quarkus/main/.devfile/icon/quarkus.png", "projectType": "quarkus", "language": "java", + "lastModified": "2024-04-19T11:45:48+01:00", "provider": "Red Hat", "versions": [ { @@ -471,7 +498,8 @@ "origin": "https://github.com/elsony/devfile-sample-code-with-quarkus.git" } }, - "description": "java quarkus with devfile v2.0.0" + "description": "java quarkus with devfile v2.0.0", + "lastModified": "2024-04-19T11:45:48+01:00" } ] } diff --git a/index/generator/tests/registry/last_modified.json b/index/generator/tests/registry/last_modified.json new file mode 100644 index 00000000..0c26bba5 --- /dev/null +++ b/index/generator/tests/registry/last_modified.json @@ -0,0 +1,81 @@ +{ + "stacks": [ + { + "name": "go", + "version": "1.1.0", + "lastModified": "2023-11-08T12:54:08+00:00" + }, + { + "name": "go", + "version": "1.2.0", + "lastModified": "2023-04-08T11:51:08+00:00" + }, + { + "name": "java-maven", + "version": "1.1.0", + "lastModified": "2024-05-13T12:32:02+02:00" + }, + { + "name": "java-openliberty", + "version": "0.5.0", + "lastModified": "2024-04-23T06:20:34-04:00" + }, + { + "name": "java-quarkus", + "version": "1.1.0", + "lastModified": "2024-04-29T17:08:43+03:00" + }, + { + "name": "java-springboot", + "version": "1.1.0", + "lastModified": "2024-05-13T12:32:02+02:00" + }, + { + "name": "java-vertx", + "version": "1.1.0", + "lastModified": "2024-05-13T12:32:02+02:00" + }, + { + "name": "java-wildfly-bootable-jar", + "version": "1.0.0", + "lastModified": "2024-05-27T11:00:03+02:00" + }, + { + "name": "java-wildfly", + "version": "1.0.0", + "lastModified": "2024-04-22T23:00:14+02:00" + }, + { + "name": "nodejs", + "version": "1.0.0", + "lastModified": "2024-03-25T12:16:30-04:00" + }, + { + "name": "python-django", + "version": "1.0.0", + "lastModified": "2024-05-28T17:20:11+02:00" + }, + { + "name": "python", + "version": "1.0.0", + "lastModified": "2024-05-28T17:20:11+02:00" + } + ], + "samples": [ + { + "name": "nodejs-basic", + "version": "1.0.0", + "lastModified": "2024-06-05T15:26:11-04:00" + }, + { + "name": "nodejs-basic", + "version": "1.0.1", + "lastModified": "2024-06-05T15:26:11-04:00" + }, + { + "name": "code-with-quarkus", + "version": "1.1.0", + "lastModified": "2024-04-19T11:45:48+01:00" + } + ] +} diff --git a/index/server/openapi.yaml b/index/server/openapi.yaml index 317a4b16..65f8fc68 100644 --- a/index/server/openapi.yaml +++ b/index/server/openapi.yaml @@ -277,6 +277,8 @@ paths: - $ref: '#/components/parameters/gitRevisionParam' - $ref: '#/components/parameters/providerParam' - $ref: '#/components/parameters/supportUrlParam' + - $ref: '#/components/parameters/minLastModifiedParam' + - $ref: '#/components/parameters/maxLastModifiedParam' responses: 200: $ref: '#/components/responses/v2IndexResponse' @@ -349,6 +351,8 @@ paths: - $ref: '#/components/parameters/gitRevisionParam' - $ref: '#/components/parameters/providerParam' - $ref: '#/components/parameters/supportUrlParam' + - $ref: '#/components/parameters/minLastModifiedParam' + - $ref: '#/components/parameters/maxLastModifiedParam' responses: 200: $ref: '#/components/responses/v2IndexResponse' @@ -880,6 +884,10 @@ components: $ref: '#/components/schemas/Provider' supportUrl: $ref: '#/components/schemas/Url' + minLastModified: + $ref: '#/components/schemas/LastModified' + maxLastModified: + $ref: '#/components/schemas/LastModified' Name: description: Name of devfile registry entry type: string @@ -1016,6 +1024,11 @@ components: Provider: description: Name of provider of the devfile registry entry type: string + LastModified: + description: Last modified date of a stack or sample + type: string + pattern: '^\d{4}\-(0[1-9]|1[012])\-(0[1-9]|[12][0-9]|3[01])$' + example: '2024-01-01' parameters: nameParam: name: name @@ -1252,6 +1265,20 @@ components: description: Search string to filter stacks by their given support url schema: $ref: '#/components/schemas/Url' + minLastModifiedParam: + name: minLastModified + in: query + required: false + description: The minimum (earliest) last modified date of a stack or sample + schema: + $ref: '#/components/schemas/LastModified' + maxLastModifiedParam: + name: maxLastModified + in: query + required: false + description: The maximum (latest) last modified date of a stack or sample + schema: + $ref: '#/components/schemas/LastModified' responses: devfileErrorResponse: description: Failed to get the devfile. diff --git a/index/server/pkg/server/endpoint.gen.go b/index/server/pkg/server/endpoint.gen.go index 746dd1d8..895f2474 100644 --- a/index/server/pkg/server/endpoint.gen.go +++ b/index/server/pkg/server/endpoint.gen.go @@ -1555,6 +1555,22 @@ func (siw *ServerInterfaceWrapper) ServeDevfileIndexV2(c *gin.Context) { return } + // ------------- Optional query parameter "minLastModified" ------------- + + err = runtime.BindQueryParameter("form", true, false, "minLastModified", c.Request.URL.Query(), ¶ms.MinLastModified) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter minLastModified: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "maxLastModified" ------------- + + err = runtime.BindQueryParameter("form", true, false, "maxLastModified", c.Request.URL.Query(), ¶ms.MaxLastModified) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter maxLastModified: %s", err), http.StatusBadRequest) + return + } + for _, middleware := range siw.HandlerMiddlewares { middleware(c) } @@ -1860,6 +1876,22 @@ func (siw *ServerInterfaceWrapper) ServeDevfileIndexV2WithType(c *gin.Context) { return } + // ------------- Optional query parameter "minLastModified" ------------- + + err = runtime.BindQueryParameter("form", true, false, "minLastModified", c.Request.URL.Query(), ¶ms.MinLastModified) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter minLastModified: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "maxLastModified" ------------- + + err = runtime.BindQueryParameter("form", true, false, "maxLastModified", c.Request.URL.Query(), ¶ms.MaxLastModified) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter maxLastModified: %s", err), http.StatusBadRequest) + return + } + for _, middleware := range siw.HandlerMiddlewares { middleware(c) } @@ -2024,64 +2056,66 @@ func RegisterHandlersWithOptions(router *gin.Engine, si ServerInterface, options // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "AgAA//9NQMEQeoAAAA==", + "H4sIAAAAAAAC/+xdbXPbNhL+KxheZy6eoyVbcTtTf+m0TZN6Js1l7Dj3IfLNQORKQkMCDADKVn367zd4", + "o0iRlKC3xE34JZElYPHsYoF9sEtBj0HE0oxRoFIEl49BhjlOQQLXf2EeTd+qd9QfMYiIk0wSRoPL4FeW", + "JBCpPxAbIwGqKRKSEzoRSDI0JokEjoTE0UeBRnMkp0A4Us2IhEjmHEQQBkTJ+pQDnwdhQHEKwaUeNQgD", + "EU0hxWrk7ziMg8vgH/0l1r75VPR/rghcLMIAS8nJKJfwBqcgDggfKXxCtR/SGMaEQozGHOB0zHiKimFb", + "1argqihIJKTa4HKeqaYGSLAI3RuYczzX2kUsTTGNX3GWZ+Kwc5NxEEAlskOgIZ3oUVr0qSDxnq9fK72U", + "RjGMcZ7IFl1+YSwBTOuwyVjBniPMAVkRiHFEmWzBaxt5I31h2xuMWcLmKVB5E7EMjmT45ShDKvQ4rapU", + "4Wyh00pHqxyHCEuI95sDJ2XTNLh226B2XQzeAlwL4Juy5VuXc6kPkvDQDngp2h/xso+GTESW4Lla+ftA", + "JhxZSWYvakO8HM0fcamPQjwh8hpSZnarPTFPiERcC9OwW1BXRvTG/arSq4b8WPu/+nOplvBRSeymk6gq", + "dVCFbq+vdtNnB11KesyI2HPtFk5lRK2DW7TYAq/tYwHf5KMXhO8JV2I+AYlEPooJh0gyPkdDyvQGanXJ", + "mCDq/XZtDJJtdLE9rCa3PDmA1XOerHGQW554A1RtFTQStbrDOzaZJIAYRUAjFit0EaNShcsMC6GjSBMS", + "JdIbx1VkZ1v1uuVkTyMpKSjnZA20W/2pPzrVXgFMMJ3keLLvjpxxNuE4TVUrJ7IFbeljP7ivXQeNl9CP", + "R9qHi9WjxkCC5Txq3bgKGP5aFD2cGgcmfRr1kG7GvR1mgzfFD6+xkH+wmIxJK697NwWU4geS5il6lmAJ", + "Qp6gBAuJUtsRxViCUgsb/IraCZxmSZu3rAy8hdOUOlkNbvRn74GviRZlFWKYjUkCyMhEM9OxHWhFvjfS", + "ai8L1R+kMeNGbNuiquAh1HfyCTWTD5gn5BDTXx16j+kn1Hv6rRLbTf+K/H2mn1B/kF7TXwjcafrp/oR9", + "DUun25DzgpNnnP0JkXw3zw4QuZQkpBMjzRBLg3kjfVvqYwHPSAx7sT472U5UO1r3sTdU00Hh5GADyGGD", + "05A6wapFa3gqRvcGf130UOiFxFwCt8Y/JkewIzn3aVNoBZD/rrDSTyuXZxnjB+HcM6DIilPsuw18MeDW", + "BFziyYE9SElswWk/2iH9aRw+Y1SAMEj1lv8b54xf2w/U+/aEoFPYWZaQCCv8/T+F0uixNHLGWQZcEiMO", + "lJw6jjB4OJ2wU4teDxYY55W52NT8xrRaLJVhI+UjJlVdAjfHafKEwFXzbMFl8BKTBGI14+oca/Jn2vq9", + "QLfVr98w+ZLlND7AZHxm8x7TYGNC4zaL7WSp9alHLdfDAH5Sanrd5FEEQozzBCkDaum9IR3SGx3uHA2z", + "ymhdp4ATOT2AU6QghDpabpimP2wzs2F8ygmHOLj8UHS/29dbjofD39y/a6Mi47jazITG8HBwh7pSUg3t", + "3dOpqpL8NdX9Kg6Vgpyy+A2TPycJu4e4c61dXOsPbUWUC4gREYgy6VgGxL2gxs88jPwXyaq6jRlPsQwu", + "gxGhWJOAlRi/hRu8VNvKaC5B846Y3dOEYQN0Nrja2fe1VR0q/W7P+mi4/OyUpMoupkQupybVOc1HvYil", + "fbvl9TlMiJB8fmqt2NcLsj8BqtRg3C4Eo/R6n/gioPyn4v0ArS7KhWN02ourZfkaqfy3foETlBAhFa/M", + "OFMjsZUnBJCc4grZcA4qQgRpJudGgMgnExCyoXmEKRqBcXFGEabzygCKoTryWUVYVsBWCkcaD1QEuHMo", + "0DxVyw+n8Q8XQRhgnur/syz64UKnS8TzH88eSsuyjeSGQbVEXkP22prMlelNkR65JxIIdcqXlXP4RjlJ", + "4iAMeE7VWgQhAzXpo3wSuCr3ZoxhkFPyKYcrI13yHBZh4OrlNcAvEzxBY8aLMr2bHOeaCKj6d5kVsaON", + "TN050MJXCtetZlnWxpGpoRunMCVqNWvGRiUnabIToRR4wlgWhAHLpX29s2WK+vU64xT182b7tNilJGxV", + "dulDY5n1Ypc6OfbXItEtBCF5blaBThFGCcvjU4olmWnb3jP+UWQ4AqTcNIYZJCzTEwN0Rjijqd45wspm", + "NzvHSTbFg96LYnK22+9wRvqzQT/7OFEvRb9AIfpOtt6sygXvmp63AjjigGM8SkwuYTsDVqvSNfGvKvW+", + "ldr4emFrPH/SKlWUXXyLarqXd5cqvFtCc4VEH2Q6U1HfK8vl2vpzKxzTaBoiiSchYlzvmBrIGDjQqM3Y", + "tmZaz9eUa7d1pSRTYQZhsX4AXWtsD4jOx3QNsSh52ijTKOyWE195OSehC4kY3V6/VlbBiENiFq1aVW5z", + "tJmyxlFLPL4x260pBipl41cW+ediNGFQFCNrON82lECRzRY6oxV7R5MVKjWLuud711Hgwby6DAZng4vT", + "s/PTs3PFHLCUwJWo/w6H8ePFYjg8fXb24fz0x7v/nX84Ox/cnZTe+XA+uPtwpl49/3B2fnfyXSPioqZZ", + "g/vGPdriVHeVR8/cXPPWYAqSa0J1y1g7bATNG+2brXfuciGg7tvzDCxbNUWIBmLaItSk7FsBuirAqgdu", + "BrzMqLfa2Uqy7TTbaKE/G7lptfzVwA+aanCI5ulIVzhKvt4b9M5CpP57fqrDctXntTP/azjsmRfPyq9M", + "+5OfTn5qdPPVZHyrXVaKAvVQ2TxXq93KVZBdVony6DoD4QkaE0ji1r1/4yQ00+umyTjvXfTOPO3faHR1", + "+IMo50TOtYcYbxxhQaLiXKuZq36n6D6VMjOnYULHrEETFuWKJOJWCnv928079PPbK30qfde0cFwLRIQ5", + "Bir3J1QCx5FUm/89kdNatx66UmGQCBSXMYR64UyZkEqcAD7T0VmFy3yUkKgmJ0RzluuQG00xnQAiUhGF", + "Ocs5YvfUihrrVveYSsciMk5mKmbUcCnjEaln7UWrMYIwcIcpNblnvXPlMCwDijMSXAbP9Vt6vqd6pvrG", + "9glIvesVh/2rWI+j3r9mTP5G44wRqkhBpQ5zcfZ927ZdtOu35uy0A0xANlXIpEB5ZoyOaZwAL3YvzphE", + "z/onCCwodb7XpxLgM+BDejVGU5kmaqI4fMpBqKPVM9KDHhpzliKM7mGERpzdC+AnZmZnBO6Bqy72GVqI", + "Q8TkFPg9EVDZmQ3HsV4AsbJ51Ww36v11VouXB+YnnagNAwkPsq+MGVw+1rNESkfkNLOZoDxNMZ+7D5dT", + "NF56q5knnb7LmJB1v3vLhDyy12V507j5cYddhIHjsqL/qAnhYvP6W56Hy9/R+dBEvw3HLD/Eocl0ucod", + "fQzKeWMVicoV2Q01sOijmrWGN+8+08ZwDTLndrlnEJExiazWK+WnpqDRslT/FgYOm425RNxvfnLKp2Pj", + "E3dmSvX2+QuL59XNqrYVKOPY1mjE4jlKc/UKTLJWr/WKfwzOzjb7x2qldBEGF2cX3v1qNelFGHy/xbjV", + "pwuqu9srWGYzR/OSX+g4jSfKgVylN7hbu9N9q+u7bQv+Nu3RFBv69sBx6h5e0h+Ujjj+0aN6NHryW10j", + "nNqRzWWsKgGgFW1V/51hl8Us1n/6uYLiS5DRFETNRjZCmrrHkgoviVglcOrj0JDa88M/RRFJMY1dmkIg", + "TE0ZbAbo2V8kOzE5BleTRdgUCH5/9+5tiRmuC7udZ34Bz/xCbGJbAtDyGMKSB6xkdvEE9MMMYxX2e4eM", + "921LrIj9dpEUHhD4xPzO97+OXXkDlemm+WuY5kaG9mjDpT8T+w+R0+W3PP5unuByycun6/XoDcCWD3Xs", + "Bq34ZkvL219BxqFzhcO4Qpcb+cpzI91CebJ75gbq083cE5259WzmaJmnzh8OFvI6mv6N5si6NdStoS6b", + "d9RsnjJ0WL2zITxEiq9but3SfVLJyM4hO4c8ftrUfA1885HBfLP51ylYJ/q86b3aE6TT6hetGxnaGshe", + "QXHlG/LewbCWcamBdZkW87jdhkTLcS3ftiUdc1Tld/pRTe+Tqn748f158JlPJPqZVudypS/QlNLK1UOI", + "WuxoSPWzrJVDxNozxFK7lV1+AwNc3irlQRdr19369Fm91dejT9NV3x7dlrfd+IxR3IHu0Xh5kaVn4+Ky", + "SY/2tUu0PPpU74v0mobqZdAeXVbuoPLo0Xjvk4821dskPXv4t266Onibblt1KS6G2hbYNr3Kd9f6jlO+", + "ntfPLUtXpfnM/srFWE+gjFK9OWXn6Cvsxm2fo68QUrdvi53LH8cLTBvOCscauIjN/Uf9n9rZFtvGaXV0", + "sVf9bTy3VEKnubismXMXcHam21eFhEXrB3dPkGK42vZBWMbfeG7CjhF1jKhjRB0j6hjRERiR40KVkKO2", + "7H3JUccGtud1nc2qlHQ22CVhNPiiCSOXAh+08LohbUge7UTqBl3qqCNKbdXlHerKW3c5cA37KASw8pt2", + "3xRhbPh9Qj8TN/y+XsdPnyo/9Vqm9R8h8VulDf2+PB9evWF2D0b8fvAZ0oSDL5UmHARH5GR7JAoHHc09", + "ArXUP7DRnDTcjV12KcOOCXdMuGPCHRPumHDHhL9RJnys9HDHAXfg853NSkcRfdEpnzkL5Dyx15iKy35x", + "MXVPSDyBnvu1J8L6er23NK40u1v8PwAA//+Y36ezkoMAAA==", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/index/server/pkg/server/endpoint.go b/index/server/pkg/server/endpoint.go index a495dc25..6ce0a15d 100644 --- a/index/server/pkg/server/endpoint.go +++ b/index/server/pkg/server/endpoint.go @@ -551,6 +551,8 @@ func buildIndexAPIResponse(c *gin.Context, indexType string, wantV1Index bool, p maxSchemaVersion := params.MaxSchemaVersion minVersion := params.MinVersion maxVersion := params.MaxVersion + minLastModified := params.MinLastModified + maxLastModified := params.MaxLastModified if util.StrPtrIsSet(maxSchemaVersion) || util.StrPtrIsSet(minSchemaVersion) { // check if schema version filters are in valid format. @@ -613,6 +615,29 @@ func buildIndexAPIResponse(c *gin.Context, indexType string, wantV1Index bool, p return } } + + if util.StrPtrIsSet(minLastModified) || util.StrPtrIsSet(maxLastModified) { + if util.StrPtrIsSet(minLastModified) && util.IsInvalidLastModifiedDate(minLastModified) { + c.JSON(http.StatusBadRequest, gin.H{ + "status": fmt.Sprintf("minLastModified %s is not valid, format should be 'YYYY-MM-DD' and be a valid date. %v", *minLastModified, err), + }) + return + } + if util.StrPtrIsSet(maxLastModified) && util.IsInvalidLastModifiedDate(maxLastModified) { + c.JSON(http.StatusBadRequest, gin.H{ + "status": fmt.Sprintf("maxLastModified %s is not valid, format should be 'YYYY-MM-DD' and be a valid date. %v", *maxLastModified, err), + }) + return + } + index, err = util.FilterLastModifiedDate(index, minLastModified, maxLastModified) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{ + "status": fmt.Sprintf("failed to apply last modified filter: %v", err), + }) + return + } + } + } // Filter the fields of the index diff --git a/index/server/pkg/server/types.gen.go b/index/server/pkg/server/types.gen.go index 1d9a654c..cc34eeab 100644 --- a/index/server/pkg/server/types.gen.go +++ b/index/server/pkg/server/types.gen.go @@ -127,12 +127,18 @@ type IndexParams struct { // Links List of devfile links Links *Links `json:"links,omitempty"` + // MaxLastModified Last modified date of a stack or sample + MaxLastModified *LastModified `json:"maxLastModified,omitempty"` + // MaxSchemaVersion Devfile schema version number MaxSchemaVersion *SchemaVersion `json:"maxSchemaVersion,omitempty"` // MaxVersion Devfile registry entry version number MaxVersion *Version `json:"maxVersion,omitempty"` + // MinLastModified Last modified date of a stack or sample + MinLastModified *LastModified `json:"minLastModified,omitempty"` + // MinSchemaVersion Devfile schema version number MinSchemaVersion *SchemaVersion `json:"minSchemaVersion,omitempty"` @@ -167,6 +173,9 @@ type IndexSchema = schema.Schema // Language Programming language of the devfile workspace type Language = string +// LastModified Last modified date of a stack or sample +type LastModified = string + // LinkNames Names of devfile links type LinkNames = []string @@ -257,12 +266,18 @@ type LinkNamesParam = LinkNames // LinksParam List of devfile links type LinksParam = Links +// MaxLastModifiedParam Last modified date of a stack or sample +type MaxLastModifiedParam = LastModified + // MaxSchemaVersionParam Devfile schema version number type MaxSchemaVersionParam = SchemaVersion // MaxVersionParam Devfile registry entry version number type MaxVersionParam = Version +// MinLastModifiedParam Last modified date of a stack or sample +type MinLastModifiedParam = LastModified + // MinSchemaVersionParam Devfile schema version number type MinSchemaVersionParam = SchemaVersion @@ -617,6 +632,12 @@ type ServeDevfileIndexV2Params struct { // SupportUrl Search string to filter stacks by their given support url SupportUrl *SupportUrlParam `form:"supportUrl,omitempty" json:"supportUrl,omitempty"` + + // MinLastModified The minimum (earliest) last modified date of a stack or sample + MinLastModified *MinLastModifiedParam `form:"minLastModified,omitempty" json:"minLastModified,omitempty"` + + // MaxLastModified The maximum (latest) last modified date of a stack or sample + MaxLastModified *MaxLastModifiedParam `form:"maxLastModified,omitempty" json:"maxLastModified,omitempty"` } // ServeDevfileIndexV2WithTypeParams defines parameters for ServeDevfileIndexV2WithType. @@ -720,4 +741,10 @@ type ServeDevfileIndexV2WithTypeParams struct { // SupportUrl Search string to filter stacks by their given support url SupportUrl *SupportUrlParam `form:"supportUrl,omitempty" json:"supportUrl,omitempty"` + + // MinLastModified The minimum (earliest) last modified date of a stack or sample + MinLastModified *MinLastModifiedParam `form:"minLastModified,omitempty" json:"minLastModified,omitempty"` + + // MaxLastModified The maximum (latest) last modified date of a stack or sample + MaxLastModified *MaxLastModifiedParam `form:"maxLastModified,omitempty" json:"maxLastModified,omitempty"` } diff --git a/index/server/pkg/server/types.go b/index/server/pkg/server/types.go index 08cec166..632293e9 100644 --- a/index/server/pkg/server/types.go +++ b/index/server/pkg/server/types.go @@ -45,6 +45,8 @@ func (params *ServeDevfileIndexV2Params) toIndexParams() IndexParams { GitRevision: params.GitRevision, Provider: params.Provider, SupportUrl: params.SupportUrl, + MinLastModified: params.MinLastModified, + MaxLastModified: params.MaxLastModified, } } @@ -80,6 +82,8 @@ func (params *ServeDevfileIndexV2WithTypeParams) toIndexParams() IndexParams { GitRevision: params.GitRevision, Provider: params.Provider, SupportUrl: params.SupportUrl, + MinLastModified: params.MinLastModified, + MaxLastModified: params.MaxLastModified, } } diff --git a/index/server/pkg/util/filter.go b/index/server/pkg/util/filter.go index 76f57418..d4776288 100644 --- a/index/server/pkg/util/filter.go +++ b/index/server/pkg/util/filter.go @@ -66,6 +66,8 @@ const ( ParamProvider = "provider" // Parameter 'supportUrl' ParamSupportUrl = "supportUrl" + // Parameter 'lastModified' + ParamLastModified = "lastModified" /* Array Parameter Names */ @@ -794,3 +796,59 @@ func FilterDevfileStrArrayField(index []indexSchema.Schema, paramName string, re Index: filterDevfileArrayFuzzy(index, requestedValues, options), } } + +// FilterLastModifiedDate filters based on the last modified date of a stack or sample +func FilterLastModifiedDate(index []indexSchema.Schema, minLastModified *string, maxLastModified *string) ([]indexSchema.Schema, error) { + filteredIndex := deepcopy.Copy(index).([]indexSchema.Schema) + for i := 0; i < len(filteredIndex); i++ { + for versionIndex := 0; versionIndex < len(filteredIndex[i].Versions); versionIndex++ { + currentLastModifiedDate := filteredIndex[i].Versions[versionIndex].LastModified + matchedLastModified := false + if StrPtrIsSet(minLastModified) && StrPtrIsSet(maxLastModified) { + minModified, err := ConvertNonRFC3339Date(*minLastModified) + if err != nil { + return filteredIndex, err + } + maxModified, err := ConvertNonRFC3339Date(*maxLastModified) + if err != nil { + return filteredIndex, err + } + curModified, err := ConvertRFC3339Date(¤tLastModifiedDate) + if err != nil { + return filteredIndex, err + } + matchedLastModified = IsDateGreaterOrEqual(minModified, curModified) && IsDateLowerOrEqual(maxModified, curModified) + } else if StrPtrIsSet(minLastModified) { + minModified, err := ConvertNonRFC3339Date(*minLastModified) + if err != nil { + return filteredIndex, err + } + curModified, err := ConvertRFC3339Date(¤tLastModifiedDate) + if err != nil { + return filteredIndex, err + } + matchedLastModified = IsDateGreaterOrEqual(minModified, curModified) + } else if StrPtrIsSet(maxLastModified) { + maxModified, err := ConvertNonRFC3339Date(*maxLastModified) + if err != nil { + return filteredIndex, err + } + curModified, err := ConvertRFC3339Date(¤tLastModifiedDate) + if err != nil { + return filteredIndex, err + } + matchedLastModified = IsDateLowerOrEqual(maxModified, curModified) + } + + if !matchedLastModified { + filterOut(&filteredIndex[i].Versions, &versionIndex) + } + } + if len(filteredIndex[i].Versions) == 0 { + // if versions list is empty after filter, remove this index + filterOut(&filteredIndex, &i) + } + } + + return filteredIndex, nil +} diff --git a/index/server/pkg/util/filter_test.go b/index/server/pkg/util/filter_test.go index 2fdef340..17cb9390 100644 --- a/index/server/pkg/util/filter_test.go +++ b/index/server/pkg/util/filter_test.go @@ -3666,3 +3666,295 @@ func TestAndFilter(t *testing.T) { }) } } + +func TestFilterLastModifiedDate(t *testing.T) { + validMin := "2023-11-08" + validMax := "2024-04-08" + validMid := "2024-02-25" + invalidMin := "2025-04-04" + invalidMax := "2020-01-01" + var nilPtr *string + var validMinPtr *string = &validMin + var validMaxPtr *string = &validMax + var validMidPtr *string = &validMid + var invalidMinPtr *string = &invalidMin + var invalidMaxPtr *string = &invalidMax + + tests := []struct { + name string + index []indexSchema.Schema + minLastModified *string + maxLastModified *string + wantIndex []indexSchema.Schema + }{ + { + name: "Match with minLastModified", + index: []indexSchema.Schema{ + { + Name: "devfileA", + Versions: []indexSchema.Version{ + { + Version: "1.0.0", + LastModified: "2023-11-08T12:54:08+00:00", + }, + { + Version: "1.1.0", + LastModified: "2024-04-08T11:51:08+00:00", + }, + { + Version: "1.2.0", + LastModified: "2024-04-08T11:51:08+00:00", + }, + }, + }, + { + Name: "devfileB", + Versions: []indexSchema.Version{ + { + Version: "1.0.0", + LastModified: "2024-04-08T11:51:08+00:00", + }, + }, + }, + }, + minLastModified: validMinPtr, + maxLastModified: nilPtr, + wantIndex: []indexSchema.Schema{ + { + Name: "devfileA", + Versions: []indexSchema.Version{ + { + Version: "1.0.0", + LastModified: "2023-11-08T12:54:08+00:00", + }, + { + Version: "1.1.0", + LastModified: "2024-04-08T11:51:08+00:00", + }, + { + Version: "1.2.0", + LastModified: "2024-04-08T11:51:08+00:00", + }, + }, + }, + { + Name: "devfileB", + Versions: []indexSchema.Version{ + { + Version: "1.0.0", + LastModified: "2024-04-08T11:51:08+00:00", + }, + }, + }, + }, + }, + { + name: "Match with maxLastModified", + index: []indexSchema.Schema{ + { + Name: "devfileA", + Versions: []indexSchema.Version{ + { + Version: "1.0.0", + LastModified: "2023-11-08T12:54:08+00:00", + }, + { + Version: "1.1.0", + LastModified: "2024-04-08T11:51:08+00:00", + }, + { + Version: "1.2.0", + LastModified: "2024-04-08T11:51:08+00:00", + }, + }, + }, + { + Name: "devfileB", + Versions: []indexSchema.Version{ + { + Version: "1.0.0", + LastModified: "2024-04-08T11:51:08+00:00", + }, + }, + }, + }, + minLastModified: nilPtr, + maxLastModified: validMaxPtr, + wantIndex: []indexSchema.Schema{ + { + Name: "devfileA", + Versions: []indexSchema.Version{ + { + Version: "1.0.0", + LastModified: "2023-11-08T12:54:08+00:00", + }, + { + Version: "1.1.0", + LastModified: "2024-04-08T11:51:08+00:00", + }, + { + Version: "1.2.0", + LastModified: "2024-04-08T11:51:08+00:00", + }, + }, + }, + { + Name: "devfileB", + Versions: []indexSchema.Version{ + { + Version: "1.0.0", + LastModified: "2024-04-08T11:51:08+00:00", + }, + }, + }, + }, + }, + { + name: "Match with minLastModified and maxLastModified", + index: []indexSchema.Schema{ + { + Name: "devfileA", + Versions: []indexSchema.Version{ + { + Version: "1.0.0", + LastModified: "2023-11-08T12:54:08+00:00", + }, + { + Version: "1.1.0", + LastModified: "2024-04-08T11:51:08+00:00", + }, + { + Version: "1.2.0", + LastModified: "2024-04-08T11:51:08+00:00", + }, + { + Version: "1.3.0", + LastModified: "2024-01-23T11:51:08+00:00", + }, + }, + }, + { + Name: "devfileB", + Versions: []indexSchema.Version{ + { + Version: "1.0.0", + LastModified: "2024-04-08T11:51:08+00:00", + }, + { + Version: "1.0.0", + LastModified: "2023-12-18T11:51:08+00:00", + }, + }, + }, + }, + minLastModified: validMinPtr, + maxLastModified: validMidPtr, + wantIndex: []indexSchema.Schema{ + { + Name: "devfileA", + Versions: []indexSchema.Version{ + { + Version: "1.0.0", + LastModified: "2023-11-08T12:54:08+00:00", + }, + { + Version: "1.3.0", + LastModified: "2024-01-23T11:51:08+00:00", + }, + }, + }, + { + Name: "devfileB", + Versions: []indexSchema.Version{ + { + Version: "1.0.0", + LastModified: "2023-12-18T11:51:08+00:00", + }, + }, + }, + }, + }, + { + name: "No Match", + index: []indexSchema.Schema{ + { + Name: "devfileA", + Versions: []indexSchema.Version{ + { + Version: "1.0.0", + LastModified: "2023-11-08T12:54:08+00:00", + }, + { + Version: "1.1.0", + LastModified: "2024-04-08T11:51:08+00:00", + }, + { + Version: "1.2.0", + LastModified: "2024-04-08T11:51:08+00:00", + }, + }, + }, + { + Name: "devfileB", + Versions: []indexSchema.Version{ + { + Version: "1.0.0", + LastModified: "2024-04-08T11:51:08+00:00", + }, + }, + }, + }, + minLastModified: invalidMinPtr, + maxLastModified: invalidMaxPtr, + wantIndex: []indexSchema.Schema{}, + }, + { + name: "Unset Pointers", + index: []indexSchema.Schema{ + { + Name: "devfileA", + Versions: []indexSchema.Version{ + { + Version: "1.0.0", + LastModified: "2023-11-08T12:54:08+00:00", + }, + { + Version: "1.1.0", + LastModified: "2024-04-08T11:51:08+00:00", + }, + { + Version: "1.2.0", + LastModified: "2024-04-08T11:51:08+00:00", + }, + }, + }, + { + Name: "devfileB", + Versions: []indexSchema.Version{ + { + Version: "1.0.0", + LastModified: "2024-04-08T11:51:08+00:00", + }, + }, + }, + }, + minLastModified: nilPtr, + maxLastModified: nilPtr, + wantIndex: []indexSchema.Schema{}, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + gotIndex, gotErr := FilterLastModifiedDate(test.index, test.minLastModified, test.maxLastModified) + if gotErr != nil { + if gotIndex != nil { + t.Errorf("Unexpected non-nil index on error: %v", gotIndex) + } + t.Errorf("Unexpected error: %v", gotErr) + } else if !reflect.DeepEqual(gotIndex, test.wantIndex) { + t.Errorf("Got: %v, Expected: %v", gotIndex, test.wantIndex) + } + }) + } +} diff --git a/index/server/pkg/util/util.go b/index/server/pkg/util/util.go index 61402474..6d3132be 100644 --- a/index/server/pkg/util/util.go +++ b/index/server/pkg/util/util.go @@ -24,8 +24,10 @@ import ( "net/url" "os" "reflect" + "regexp" "strconv" "strings" + "time" versionpkg "github.com/hashicorp/go-version" @@ -33,6 +35,10 @@ import ( indexSchema "github.com/devfile/registry-support/index/generator/schema" ) +const ( + truncateOptions = 24 * time.Hour +) + // IsHtmlRequested checks the accept header if html has been requested func IsHtmlRequested(acceptHeader []string) bool { for _, header := range acceptHeader { @@ -293,3 +299,53 @@ func StructToMap[T any](s T) map[string]any { func StrPtrIsSet(ptr *string) bool { return ptr != nil && *ptr != "" } + +func IsInvalidLastModifiedDate(lastModifiedDate *string) bool { + if lastModifiedDate == nil { + return true + } + validFormat, err := regexp.MatchString(`^\d{4}\-(0[1-9]|1[012])\-(0[1-9]|[12][0-9]|3[01])$`, *lastModifiedDate) + if !validFormat || err != nil || !isValidDate(*lastModifiedDate) { + return true + } + return false +} + +func isValidDate(d string) bool { + _, err := time.Parse(time.DateOnly, d) + return err == nil +} + +func ConvertRFC3339Date(dt *string) (time.Time, error) { + convertedDate, err := time.Parse(time.RFC3339, *dt) + if err != nil { + return time.Now(), err + } + return convertedDate, nil +} + +func ConvertNonRFC3339Date(dt string) (time.Time, error) { + convertedCurrent, err := time.Parse(time.DateOnly, dt) + if err != nil { + return time.Now(), err + } + tmp := convertedCurrent.Format(time.RFC3339) + convertedCurrent, err = time.Parse(time.RFC3339, tmp) + if err != nil { + return time.Now(), err + } + + return convertedCurrent, nil +} + +func IsDateGreaterOrEqual(bound time.Time, current time.Time) bool { + equal := bound.Truncate(truncateOptions).Equal(current.Truncate(truncateOptions)) + greater := bound.Truncate(truncateOptions).Before(current.Truncate(truncateOptions)) + return equal || greater +} + +func IsDateLowerOrEqual(bound time.Time, current time.Time) bool { + equal := bound.Truncate(truncateOptions).Equal(current.Truncate(truncateOptions)) + lower := bound.Truncate(truncateOptions).After(current.Truncate(truncateOptions)) + return equal || lower +} diff --git a/index/server/pkg/util/util_test.go b/index/server/pkg/util/util_test.go index 07db7318..2dc7d245 100644 --- a/index/server/pkg/util/util_test.go +++ b/index/server/pkg/util/util_test.go @@ -21,6 +21,7 @@ import ( "os" "reflect" "testing" + "time" indexSchema "github.com/devfile/registry-support/index/generator/schema" "k8s.io/utils/pointer" @@ -498,3 +499,202 @@ func TestStrPtrIsSet(t *testing.T) { }) } } + +func TestIsInvalidLastModifiedDate(t *testing.T) { + validFormatValidDate := "2020-12-20" + validFormatInvalidDate := "2020-02-31" + invalidFormatInvalidDate := "2020-6-45" + invalidFormatValidDate := "2020-6-12" + + tests := []struct { + name string + lastModified *string + want bool + }{ + { + name: "Case 1: Valid Input Format and Valid Date", + lastModified: &validFormatValidDate, + want: false, + }, + { + name: "Case 2: Valid Input Format and Invalid Date Range", + lastModified: &validFormatInvalidDate, + want: true, + }, + { + name: "Case 3: Invalid Input Format and Invalid Date", + lastModified: &invalidFormatInvalidDate, + want: true, + }, + + { + name: "Case 4: Invalid Input Format and Valid Date Range", + lastModified: &invalidFormatValidDate, + want: true, + }, + { + name: "Case 5: No Input", + lastModified: nil, + want: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + valid := IsInvalidLastModifiedDate(test.lastModified) + if valid != test.want { + t.Errorf("Got: %v, Expected: %v", valid, test.want) + } + }) + } +} + +func TestConvertRFC3339Date(t *testing.T) { + validRFCDate := "2024-04-08T11:51:08+00:00" + invalidRFCDate := "2024-04-08" + var validPtr *string = &validRFCDate + var invalidPtr *string = &invalidRFCDate + + tests := []struct { + name string + dt *string + shouldFail bool + }{ + { + name: "Case 1: Valid Date", + dt: validPtr, + shouldFail: false, + }, + { + name: "Case 2: Invalid Date", + dt: invalidPtr, + shouldFail: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + _, err := ConvertRFC3339Date(test.dt) + if err != nil && !test.shouldFail { + t.Errorf("Got: %v, Expected: %v", err, test.shouldFail) + } else if err == nil && test.shouldFail { + t.Errorf("Got: %v, Expected: %v", err, test.shouldFail) + } + }) + } +} + +func TestConvertNonRFC3339Date(t *testing.T) { + validDate := "2024-04-08" + + tests := []struct { + name string + dt string + shouldFail bool + }{ + { + name: "Case 1: Valid Date", + dt: validDate, + shouldFail: false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + _, err := ConvertNonRFC3339Date(test.dt) + if err != nil && !test.shouldFail { + t.Errorf("Got: %v, Expected: %v", err, test.shouldFail) + } else if err == nil && test.shouldFail { + t.Errorf("Got: %v, Expected: %v", err, test.shouldFail) + } + }) + } +} + +func TestIsDateGreaterOrEqual(t *testing.T) { + bound := "2024-04-08T11:51:08+00:00" + boundDt, _ := ConvertRFC3339Date(&bound) + greaterDt, _ := ConvertNonRFC3339Date("2024-07-15") + lesserDt, _ := ConvertNonRFC3339Date("2020-07-15") + equalDt, _ := ConvertNonRFC3339Date("2024-04-08") + tests := []struct { + name string + bound time.Time + testDt time.Time + dt string + want bool + }{ + { + name: "Case 1: Date is Greater", + bound: boundDt, + testDt: greaterDt, + want: true, + }, + { + name: "Case 2: Date is Lesser", + bound: boundDt, + testDt: lesserDt, + want: false, + }, + { + name: "Case 3: Date is Equal", + bound: boundDt, + testDt: equalDt, + want: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + + res := IsDateGreaterOrEqual(test.bound, test.testDt) + if res != test.want { + t.Errorf("Got: %v, Expected: %v", res, test.want) + } + }) + } +} + +func TestIsDateLowerOrEqual(t *testing.T) { + bound := "2024-04-08T11:51:08+00:00" + boundDt, _ := ConvertRFC3339Date(&bound) + greaterDt, _ := ConvertNonRFC3339Date("2024-07-15") + lesserDt, _ := ConvertNonRFC3339Date("2020-07-15") + equalDt, _ := ConvertNonRFC3339Date("2024-04-08") + tests := []struct { + name string + bound time.Time + testDt time.Time + dt string + want bool + }{ + { + name: "Case 1: Date is Greater", + bound: boundDt, + testDt: greaterDt, + want: false, + }, + { + name: "Case 2: Date is Lesser", + bound: boundDt, + testDt: lesserDt, + want: true, + }, + { + name: "Case 3: Date is Equal", + bound: boundDt, + testDt: equalDt, + want: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + + res := IsDateLowerOrEqual(test.bound, test.testDt) + if res != test.want { + t.Errorf("Got: %v, Expected: %v", res, test.want) + } + }) + } +} diff --git a/index/server/vendor/github.com/devfile/registry-support/index/generator/library/library.go b/index/server/vendor/github.com/devfile/registry-support/index/generator/library/library.go index b13927ff..d735619a 100644 --- a/index/server/vendor/github.com/devfile/registry-support/index/generator/library/library.go +++ b/index/server/vendor/github.com/devfile/registry-support/index/generator/library/library.go @@ -24,6 +24,7 @@ import ( "path" "path/filepath" "strings" + "time" devfileParser "github.com/devfile/library/v2/pkg/devfile" "github.com/devfile/library/v2/pkg/devfile/parser" @@ -122,6 +123,10 @@ func GenerateIndexStruct(registryDirPath string, force bool) ([]schema.Schema, e index = append(index, indexFromExtraDevfileEntries...) } + index, err = SetLastModifiedValue(index, registryDirPath) + if err != nil { + return index, err + } return index, nil } @@ -705,6 +710,63 @@ func validateStackInfo(stackInfo schema.Schema, stackfolderDir string) []error { return errors } +// SetLastModifiedValue adds the last modified value to a pre-created index +// The last modified dates are contained in a file named last_modified.json that is apart of the registry dir +/* #nosec G304 -- lastModFile is produced from filepath.Join which cleans the input path */ +func SetLastModifiedValue(index []schema.Schema, registryDirPath string) ([]schema.Schema, error) { + lastModFile := filepath.Join(registryDirPath, "last_modified.json") + bytes, err := os.ReadFile(lastModFile) + if err != nil { + return index, err + } + + var lastModifiedEntries schema.LastModifiedInfo + err = json.Unmarshal(bytes, &lastModifiedEntries) + if err != nil { + return index, err + } + + lastModifiedEntriesMap := make(map[string]map[string]time.Time) + + for idx := range lastModifiedEntries.Stacks { + updateLastModifiedMap(lastModifiedEntriesMap, &lastModifiedEntries.Stacks[idx]) + } + + for idx := range lastModifiedEntries.Samples { + updateLastModifiedMap(lastModifiedEntriesMap, &lastModifiedEntries.Samples[idx]) + } + + for i := range index { + var mostCurrentLastModifiedDate time.Time + for j := range index[i].Versions { + schemaItem := index[i] // a stack or sample + version := schemaItem.Versions[j] + versionNum := version.Version + lastModifiedDate := lastModifiedEntriesMap[schemaItem.Name][versionNum] + updateSchemaLastModified(&schemaItem, j, lastModifiedDate) + if lastModifiedDate.After(mostCurrentLastModifiedDate) { + mostCurrentLastModifiedDate = lastModifiedDate + } + } + // lastModified of a stack or sample will be the date any version of it was last changed + index[i].LastModified = mostCurrentLastModifiedDate.Format(time.RFC3339) + } + + return index, nil +} + +func updateLastModifiedMap(m map[string]map[string]time.Time, entry *schema.LastModifiedEntry) { + _, ok := m[entry.Name] + if !ok { + m[entry.Name] = make(map[string]time.Time) + } + m[entry.Name][entry.Version] = entry.LastModified +} + +func updateSchemaLastModified(s *schema.Schema, versionIndx int, lastModifiedDate time.Time) { + s.Versions[versionIndx].LastModified = lastModifiedDate.Format(time.RFC3339) +} + // In checks if the value is in the array func inArray(arr []string, value string) bool { for _, item := range arr { diff --git a/index/server/vendor/github.com/devfile/registry-support/index/generator/schema/schema.go b/index/server/vendor/github.com/devfile/registry-support/index/generator/schema/schema.go index 126c9070..82c69d60 100644 --- a/index/server/vendor/github.com/devfile/registry-support/index/generator/schema/schema.go +++ b/index/server/vendor/github.com/devfile/registry-support/index/generator/schema/schema.go @@ -16,6 +16,8 @@ package schema import ( + "time" + apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" ) @@ -38,6 +40,7 @@ Sample index file: ], "projectType": "maven", "language": "java", + "lastModified": "2024-05-13T12:32:02+02:00", "versions": [ { "version": "1.1.0", @@ -61,7 +64,8 @@ Sample index file: ], "starterProjects": [ "springbootproject" - ] + ], + "lastModified": "2024-05-13T12:32:02+02:00" } ] }, @@ -79,6 +83,7 @@ Sample index file: ], "projectType": "quarkus", "language": "java", + "lastModified": "2024-04-29T17:08:43+03:00", "versions": [ { "version": "1.1.0", @@ -108,7 +113,8 @@ Sample index file: "starterProjects": [ "community", "redhat-product" - ] + ], + "lastModified": "2024-04-29T17:08:43+03:00" } ] } @@ -136,6 +142,7 @@ starterProjects: string[] - The project templates that can be used in the devfil git: *git - The information of remote repositories provider: string - The devfile provider information versions: []Version - The list of stack versions information +lastModified: string - The date that a version of this stack/sample was last changed */ // Schema is the index file schema @@ -161,6 +168,7 @@ type Schema struct { Provider string `yaml:"provider,omitempty" json:"provider,omitempty"` SupportUrl string `yaml:"supportUrl,omitempty" json:"supportUrl,omitempty"` Versions []Version `yaml:"versions,omitempty" json:"versions,omitempty"` + LastModified string `yaml:"lastModified,omitempty" json:"lastModified,omitempty"` } // DevfileType describes the type of devfile @@ -264,4 +272,16 @@ type Version struct { DeploymentScopes map[DeploymentScopeKind]bool `yaml:"deploymentScopes,omitempty" json:"deploymentScopes,omitempty"` Resources []string `yaml:"resources,omitempty" json:"resources,omitempty"` StarterProjects []string `yaml:"starterProjects,omitempty" json:"starterProjects,omitempty"` + LastModified string `yaml:"lastModified,omitempty" json:"lastModified,omitempty"` +} + +type LastModifiedEntry struct { + Name string `yaml:"name,omitempty" json:"name,omitempty"` + Version string `yaml:"version,omitempty" json:"version,omitempty"` + LastModified time.Time `yaml:"lastModified,omitempty" json:"lastModified,omitempty"` +} + +type LastModifiedInfo struct { + Stacks []LastModifiedEntry `yaml:"stacks,omitempty" json:"stacks,omitempty"` + Samples []LastModifiedEntry `yaml:"samples,omitempty" json:"samples,omitempty"` } diff --git a/tests/registry/last_modified.json b/tests/registry/last_modified.json new file mode 100644 index 00000000..6d158bde --- /dev/null +++ b/tests/registry/last_modified.json @@ -0,0 +1,57 @@ +{ + "stacks": [ + { + "name": "go", + "version": "1.0.2", + "lastModified": "2023-11-08T12:54:08+00:00" + }, + { + "name": "go", + "version": "1.2.0", + "lastModified": "2023-04-08T11:51:08+00:00" + }, + { + "name": "go", + "version": "2.0.0", + "lastModified": "2023-11-08T12:54:08+00:00" + }, + { + "name": "go", + "version": "2.1.0", + "lastModified": "2023-04-08T11:51:08+00:00" + }, + { + "name": "java-maven", + "version": "1.1.0", + "lastModified": "2024-05-13T12:32:02+02:00" + }, + { + "name": "java-quarkus", + "version": "1.1.0", + "lastModified": "2024-04-29T17:08:43+03:00" + }, + { + "name": "nodejs", + "version": "1.0.0", + "lastModified": "2024-04-29T12:16:30-04:00" + } + ], + "samples": [ + { + "name": "nodejs-basic", + "version": "1.1.0", + "lastModified": "2024-06-05T15:26:11-04:00" + }, + { + "name": "code-with-quarkus", + "version": "1.1.0", + "lastModified": "2024-04-29T11:45:48+01:00" + }, + { + "name": "code-with-quarkus", + "version": "1.0.0", + "lastModified": "2024-02-15T11:45:48+01:00" + } + ] + } + \ No newline at end of file