diff --git a/internal/generator/factory.go b/internal/generator/factory.go index 3420ef045..06c7eeca6 100644 --- a/internal/generator/factory.go +++ b/internal/generator/factory.go @@ -8,14 +8,10 @@ import ( "regexp" "runtime" "slices" - "sort" - "strconv" - "strings" "github.com/CustomResourceDefinition/catalog/internal/configuration" "github.com/CustomResourceDefinition/catalog/internal/crd" "github.com/CustomResourceDefinition/catalog/internal/registry" - "github.com/CustomResourceDefinition/catalog/internal/semver" ) type Builder struct { @@ -25,34 +21,24 @@ type Builder struct { logger io.Writer config configuration.Configuration generator Generator - versionFilter *regexp.Regexp registry *registry.SourceRegistry } -func NewBuilder(config configuration.Configuration, reader crd.CrdReader, generatedRepository, schemaRepository, definitionRepository string, logger io.Writer, reg *registry.SourceRegistry) (*Builder, error) { +func NewBuilder( + config configuration.Configuration, + reader crd.CrdReader, + generatedRepository, schemaRepository, definitionRepository string, + logger io.Writer, + reg *registry.SourceRegistry, +) (*Builder, error) { generator, err := resolveGenerator(config, reader, logger) if err != nil { return nil, err } - if len(config.Namespace) == 0 { - config.Namespace = "namespace" - } - - pattern := defaultVersionPattern(config.Kind) - if len(config.VersionPattern) > 0 { - pattern = config.VersionPattern - } - - re, err := regexp.Compile(pattern) - if err != nil { - return nil, err - } - return &Builder{ config: config, generator: generator, - versionFilter: re, logger: logger, schemaRepository: schemaRepository, generatedRepository: generatedRepository, @@ -78,13 +64,20 @@ func (builder Builder) Build() error { fmt.Fprintf(logger, "Producing for %s@%s:\n", builder.config.Name, builder.config.Kind) defer fmt.Fprintf(logger, "End.\n") - latestVersion, isUpdated := builder.registryStatus() + if _, ok := builder.generator.(*PreparedGitGenerator); ok { + fmt.Fprintf(logger, " - using prepared git generator\n") + } + + latestVersion, isUpdated, err := builder.registryStatus() + if err != nil { + return err + } if isUpdated { fmt.Fprintf(logger, " - skipping %s@%s (version %s unchanged)\n", builder.config.Name, builder.config.Kind, latestVersion) return nil } - versions, err := builder.versions() + versions, err := builder.generator.Versions() if err != nil { return err } @@ -157,51 +150,23 @@ func (builder Builder) Build() error { return nil } -func (builder Builder) versions() ([]string, error) { - versions, err := builder.generator.Versions() +// registryStatus reports on the state in registry based on the latest version +// available and only uses the decided interface method for latest version information +func (builder Builder) registryStatus() (string, bool, error) { + version, err := builder.generator.LatestVersion() if err != nil { - return nil, err + return "", false, fmt.Errorf("unable to check registry: %w", err) } - filtered := make([]string, 0) - for _, v := range versions { - if builder.versionFilter.MatchString(v) { - filtered = append(filtered, v) - } - } - - sort.Slice(filtered, func(i, j int) bool { - keyA, errA := builder.generator.VersionSortKey(filtered[i]) - keyB, errB := builder.generator.VersionSortKey(filtered[j]) - - if errA == nil && errB == nil && keyA != 0 && keyB != 0 && keyA != keyB { - return keyA > keyB - } - - a := normalizeVersion(builder.versionFilter.FindAllStringSubmatch(filtered[i], -1)) - b := normalizeVersion(builder.versionFilter.FindAllStringSubmatch(filtered[j], -1)) - return semver.Compare(a, b) > 0 - }) - return filtered, nil -} - -func (builder Builder) registryStatus() (string, bool) { - versions, err := builder.versions() - if err != nil || len(versions) == 0 { - return "", false - } - - version := versions[0] - if builder.registry == nil { - return version, false + return version, false, nil } if entry, ok := builder.registry.Get(builder.config.Name); ok { - return version, entry.Kind == string(builder.config.Kind) && entry.Version == version + return version, entry.Kind == string(builder.config.Kind) && entry.Version == version, nil } - return version, false + return version, false, nil } func (builder Builder) updateRegistry(version string) { @@ -212,37 +177,31 @@ func (builder Builder) updateRegistry(version string) { builder.registry.Set(builder.config.Name, string(builder.config.Kind), version) } -func normalizeVersion(matches [][]string) string { - if len(matches) == 0 || len(matches[0]) < 2 { - return "v0.0.0" +func resolveGenerator(config configuration.Configuration, reader crd.CrdReader, logger io.Writer) (Generator, error) { + if len(config.Namespace) == 0 { + config.Namespace = "namespace" } - version := matches[0][1] - parts := strings.Split(version, ".") - if len(parts) < 3 { - return "v0.0.0" + pattern := defaultVersionPattern(config.Kind) + if len(config.VersionPattern) > 0 { + pattern = config.VersionPattern } - ints := make([]int, 3) - for i := range 3 { - n, _ := strconv.Atoi(parts[i]) - ints[i] = n + filter, err := regexp.Compile(pattern) + if err != nil { + return nil, err } - return fmt.Sprintf("v%d.%d.%d", ints[0], ints[1], ints[2]) -} - -func resolveGenerator(config configuration.Configuration, reader crd.CrdReader, logger io.Writer) (Generator, error) { switch config.Kind { case configuration.Git: - return NewGitGeneratorFactory(config, reader, logger).Build() + return NewGitGeneratorFactory(config, reader, filter, logger).Build() case configuration.Http: - return NewHttpGenerator(config, reader), nil + return NewHttpGenerator(config, reader, filter), nil case configuration.Helm: target := config.Entries[len(config.Entries)-1] - return NewHelmGenerator(target, config, reader), nil + return NewHelmGenerator(target, config, reader, filter), nil case configuration.HelmOci: - return NewOciGenerator(config, reader), nil + return NewOciGenerator(config, reader, filter), nil default: return nil, fmt.Errorf("no generators matched for kind '%s'", config.Kind) } diff --git a/internal/generator/factory_test.go b/internal/generator/factory_test.go index 16c17a5ac..6f03faccf 100644 --- a/internal/generator/factory_test.go +++ b/internal/generator/factory_test.go @@ -1,7 +1,6 @@ package generator import ( - "fmt" "net/http" "net/http/httptest" "os" @@ -14,107 +13,37 @@ import ( "github.com/stretchr/testify/assert" ) -func TestBuilderVersionSorting(t *testing.T) { - seedVersions := []string{ - "2.10.0", "1.0.0", "2.2.0", "2.1.0", "1.01.01", - } - bundles := make([]gitBundle, 0) - for _, v := range seedVersions { - bundles = append(bundles, gitBundle{tag: v, paths: []gitPath{}}) +func TestBuildWithVersionPatternFiltering(t *testing.T) { + config := configuration.Configuration{ + Kind: configuration.Http, + Name: "test", + ApiGroups: []string{"chart.uri"}, + VersionPattern: `^v([0-9]+\.[0-9]+\.[0-9]+)$`, + Downloads: []configuration.ConfigurationDownload{ + { + Version: "v1.0.0", + }, + { + Version: "v2.0.0", + }, + { + Version: "3.0.0", + }, + }, } - expectedVersions := []string{seedVersions[0], seedVersions[2], seedVersions[3], seedVersions[4], seedVersions[1]} - p, err := setupGit(t, bundles) + reader, err := crd.NewCrdReader(setupLogger()) assert.Nil(t, err) - assert.NotNil(t, p) - config := configuration.Configuration{ - Kind: configuration.Git, - Repository: fmt.Sprintf("file://%s", *p), - VersionPattern: `^([0-9]+\.[0-9]+\.[0-9]+)$`, - } + tmpDir := t.TempDir() - b, err := NewBuilder(config, nil, "-", "-", "-", nil, nil) + builder, err := NewBuilder(config, reader, tmpDir, tmpDir, tmpDir, setupLogger(), nil) assert.Nil(t, err) - versions, err := b.versions() + version, result, err := builder.registryStatus() assert.Nil(t, err) - assert.NotNil(t, versions) - assert.Equal(t, expectedVersions, versions) -} - -type testScenario struct { - versions []string - expectedVersions []string - pattern string -} - -func TestBuilderVersionFiltering(t *testing.T) { - tests := []testScenario{ - { - versions: []string{"2.0.0", "1.3.0", "1.0.0"}, - expectedVersions: []string{"2.0.0", "1.3.0", "1.0.0"}, - }, - { - versions: []string{"v2.0.0", "v1.3.0", "v1.0.0"}, - expectedVersions: []string{}, - }, - { - versions: []string{"v2.0.0", "v1.3.0", "v1.0.0"}, - expectedVersions: []string{"v2.0.0", "v1.3.0", "v1.0.0"}, - pattern: `^v([0-9]+\.[0-9]+\.[0-9]+)$`, - }, - { - versions: []string{"2.0.0", "v1.3.0", "v1.0.0"}, - expectedVersions: []string{"2.0.0", "v1.3.0", "v1.0.0"}, - pattern: `^v?([0-9]+\.[0-9]+\.[0-9]+)$`, - }, - { - versions: []string{"2.0.0", "v1.3.0v", "v1.0.0"}, - expectedVersions: []string{"2.0.0", "v1.0.0"}, - pattern: `^v?([0-9]+\.[0-9]+\.[0-9]+)$`, - }, - { - versions: []string{"2.0.0-2", "1.3.0-1892", "1.0.0-01"}, - expectedVersions: []string{"2.0.0-2", "1.3.0-1892", "1.0.0-01"}, - pattern: `^([0-9]+\.[0-9]+\.[0-9]+-\d+)$`, - }, - { - versions: []string{"v1.33.2+k0s.0"}, - expectedVersions: []string{"v1.33.2+k0s.0"}, - pattern: `^v([0-9]+\.[0-9]+\.[0-9]+\+k0s\.0)$`, - }, - { - versions: []string{"main", "v1.0", "master"}, - expectedVersions: []string{"main", "master"}, - pattern: `^(main|master)$`, - }, - { - versions: []string{"main", "v1.0.0", "2.0.0", "master"}, - expectedVersions: []string{"2.0.0", "main", "master"}, - pattern: `^([0-9]+\.[0-9]+\.[0-9]+)|(main|master)$`, - }, - } - - for i, test := range tests { - downloads := make([]configuration.ConfigurationDownload, 0) - for _, v := range test.versions { - downloads = append(downloads, configuration.ConfigurationDownload{Version: v}) - } - config := configuration.Configuration{ - Kind: configuration.Http, - Downloads: downloads, - VersionPattern: test.pattern, - } - - b, err := NewBuilder(config, nil, "-", "-", "-", nil, nil) - assert.Nil(t, err) - - versions, err := b.versions() - assert.Nil(t, err, "index %d failed", i) - assert.NotNil(t, versions, "index %d failed", i) - assert.Equal(t, test.expectedVersions, versions, "index %d failed", i) - } + assert.False(t, result) + assert.Equal(t, "v2.0.0", version) } func TestResolveGenerator(t *testing.T) { @@ -241,11 +170,12 @@ func TestRegistryStatusNoRegistry(t *testing.T) { Downloads: []configuration.ConfigurationDownload{{Version: "1.0.0"}}, } - b, err := NewBuilder(config, nil, "-", "-", "-", nil, nil) + builder, err := NewBuilder(config, nil, "-", "-", "-", nil, nil) assert.Nil(t, err) - assert.NotNil(t, b) + assert.NotNil(t, builder) - version, result := b.registryStatus() + version, result, err := builder.registryStatus() + assert.Nil(t, err) assert.False(t, result) assert.Equal(t, "1.0.0", version) } @@ -283,7 +213,8 @@ func TestRegistryStatusSameVersion(t *testing.T) { builder, err := NewBuilder(config, reader, tmpDir, tmpDir, tmpDir, setupLogger(), reg) assert.Nil(t, err) - version, result := builder.registryStatus() + version, result, err := builder.registryStatus() + assert.Nil(t, err) assert.True(t, result) assert.Equal(t, "1.0.0", version) } @@ -321,7 +252,8 @@ func TestRegistryStatusDifferentVersion(t *testing.T) { builder, err := NewBuilder(config, reader, tmpDir, tmpDir, tmpDir, setupLogger(), reg) assert.Nil(t, err) - version, result := builder.registryStatus() + version, result, err := builder.registryStatus() + assert.Nil(t, err) assert.False(t, result) assert.Equal(t, "2.0.0", version) } @@ -359,7 +291,8 @@ func TestRegistryStatusDifferentKind(t *testing.T) { builder, err := NewBuilder(config, reader, tmpDir, tmpDir, tmpDir, setupLogger(), reg) assert.Nil(t, err) - version, result := builder.registryStatus() + version, result, err := builder.registryStatus() + assert.Nil(t, err) assert.False(t, result) assert.Equal(t, "1.0.0", version) } diff --git a/internal/generator/generator.go b/internal/generator/generator.go index 87148cb29..6cf73e374 100644 --- a/internal/generator/generator.go +++ b/internal/generator/generator.go @@ -8,8 +8,8 @@ import ( type Generator interface { Versions() ([]string, error) + LatestVersion() (string, error) MetaData(version string) ([]crd.CrdMetaSchema, error) Crds(version string) ([]crd.Crd, error) - VersionSortKey(version string) (int64, error) io.Closer } diff --git a/internal/generator/generatorVersions.go b/internal/generator/generatorVersions.go new file mode 100644 index 000000000..fd5f1d219 --- /dev/null +++ b/internal/generator/generatorVersions.go @@ -0,0 +1,69 @@ +package generator + +import ( + "fmt" + "regexp" + "sort" + "strconv" + + "github.com/CustomResourceDefinition/catalog/internal/semver" +) + +type GeneratorVersions struct{} + +func (v *GeneratorVersions) latest(versions []string) (string, error) { + if len(versions) == 0 { + return "", fmt.Errorf("no versions are available") + } + return versions[0], nil +} + +func (v *GeneratorVersions) semverSort(versions []string, filter *regexp.Regexp) ([]string, error) { + if filter == nil { + return nil, fmt.Errorf("filter is required") + } + + filtered := make([]string, 0) + for _, v := range versions { + if filter.MatchString(v) { + filtered = append(filtered, v) + } + } + + sort.Slice(filtered, func(i, j int) bool { + a := normalizeVersion(filter.FindAllStringSubmatch(filtered[i], -1)) + b := normalizeVersion(filter.FindAllStringSubmatch(filtered[j], -1)) + return semver.Compare(a, b) > 0 + }) + return filtered, nil +} + +var versionFormat = regexp.MustCompile(`^([0-9]+)\.([0-9]+)\.([0-9]+)$`) + +func normalizeVersion(matches [][]string) string { + for _, match := range matches { + if len(match) == 0 { + continue + } + + var version string + if len(match) == 1 { + version = match[0] // no capture groups - use entire match + } else { + version = match[1] // use matched capture group + } + + var parts = versionFormat.FindAllStringSubmatch(version, -1) + if len(parts) == 0 { + continue + } + ints := make([]int, 3) + for i := range 3 { + n, _ := strconv.Atoi(parts[0][i+1]) + ints[i] = n + } + return fmt.Sprintf("v%d.%d.%d", ints[0], ints[1], ints[2]) + } + + return "v0.0.0" +} diff --git a/internal/generator/generatorVersions_test.go b/internal/generator/generatorVersions_test.go new file mode 100644 index 000000000..8304bcf0d --- /dev/null +++ b/internal/generator/generatorVersions_test.go @@ -0,0 +1,126 @@ +package generator + +import ( + "regexp" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGeneratorVersionsSemverSortNoFilter(t *testing.T) { + gv := GeneratorVersions{} + + _, err := gv.semverSort([]string{"1.0.0", "2.0.0", "1.5.0"}, nil) + assert.NotNil(t, err) +} + +func TestGeneratorVersionsSemverSortWithFilter(t *testing.T) { + gv := GeneratorVersions{} + + filter := regexp.MustCompile(`^v([0-9]+\.[0-9]+\.[0-9]+)$`) + versions, err := gv.semverSort([]string{"v1.0.0", "v2.0.0", "main"}, filter) + assert.Nil(t, err) + assert.Equal(t, []string{"v2.0.0", "v1.0.0"}, versions) +} + +func TestGeneratorVersionsSemverSortFiltersCorrectly(t *testing.T) { + gv := GeneratorVersions{} + + filter := regexp.MustCompile(`^([0-9]+\.[0-9]+\.[0-9]+)$`) + versions, err := gv.semverSort([]string{"1.0.0", "main", "2.0.0", "master"}, filter) + assert.Nil(t, err) + assert.Equal(t, []string{"2.0.0", "1.0.0"}, versions) +} + +func TestGeneratorVersionsSemverSortSortsDescending(t *testing.T) { + gv := GeneratorVersions{} + + filter := regexp.MustCompile(`^([0-9]+\.[0-9]+\.[0-9]+)$`) + versions, err := gv.semverSort([]string{"1.0.0", "2.10.0", "2.2.0", "1.5.0", "1.01.01"}, filter) + assert.Nil(t, err) + assert.Equal(t, []string{"2.10.0", "2.2.0", "1.5.0", "1.01.01", "1.0.0"}, versions) +} + +func TestGeneratorVersionsFiltering(t *testing.T) { + gv := GeneratorVersions{} + + tests := []struct { + name string + versions []string + expectedVersions []string + pattern string + }{ + { + name: "filter matches all", + versions: []string{"2.0.0", "1.3.0", "1.0.0"}, + expectedVersions: []string{"2.0.0", "1.3.0", "1.0.0"}, + pattern: `.*`, + }, + { + name: "v prefix with pattern", + versions: []string{"v2.0.0", "v1.3.0", "v1.0.0"}, + expectedVersions: []string{"v2.0.0", "v1.3.0", "v1.0.0"}, + pattern: `^v([0-9]+\.[0-9]+\.[0-9]+)$`, + }, + { + name: "optional v prefix", + versions: []string{"2.0.0", "v1.3.0", "v1.0.0"}, + expectedVersions: []string{"2.0.0", "v1.3.0", "v1.0.0"}, + pattern: `^v?([0-9]+\.[0-9]+\.[0-9]+)$`, + }, + { + name: "optional v prefix filters non-matching", + versions: []string{"2.0.0", "v1.3v", "v1.0.0"}, + expectedVersions: []string{"2.0.0", "v1.0.0"}, + pattern: `^v?([0-9]+\.[0-9]+\.[0-9]+)$`, + }, + { + name: "prerelease versions", + versions: []string{"2.0.0-2", "1.3.0-1892", "1.0.0-01"}, + expectedVersions: []string{"2.0.0-2", "1.3.0-1892", "1.0.0-01"}, + pattern: `^([0-9]+\.[0-9]+\.[0-9]+-\d+)$`, + }, + { + name: "version with build metadata", + versions: []string{"v1.33.2+k0s.0"}, + expectedVersions: []string{"v1.33.2+k0s.0"}, + pattern: `^v([0-9]+\.[0-9]+\.[0-9]+)\+k0s\.0$`, + }, + { + name: "branch names only", + versions: []string{"main", "v1.0", "master"}, + expectedVersions: []string{"main", "master"}, + pattern: `^(main|master)$`, + }, + { + name: "mixed versions and branches", + versions: []string{"main", "v1.0.0", "2.0.0", "master"}, + expectedVersions: []string{"2.0.0", "main", "master"}, + pattern: `^([0-9]+\.[0-9]+\.[0-9]+)|(main|master)$`, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + versions, err := gv.semverSort(test.versions, regexp.MustCompile(test.pattern)) + assert.Nil(t, err) + assert.Equal(t, test.expectedVersions, versions) + }) + } +} + +func TestGeneratorVersionsLatestEmpty(t *testing.T) { + gv := GeneratorVersions{} + + version, err := gv.latest([]string{}) + assert.NotNil(t, err) + assert.Equal(t, "", version) +} + +func TestGeneratorVersionsLatestReturnsFirst(t *testing.T) { + gv := GeneratorVersions{} + + version, err := gv.latest([]string{"1.0.0", "2.0.0", "1.5.0"}) + assert.Nil(t, err) + assert.Equal(t, "1.0.0", version) +} diff --git a/internal/generator/git.go b/internal/generator/git.go index 7d1efea8d..421bc0bf6 100644 --- a/internal/generator/git.go +++ b/internal/generator/git.go @@ -8,6 +8,8 @@ import ( "os/exec" "path" "path/filepath" + "regexp" + "sort" "strconv" "strings" @@ -15,20 +17,24 @@ import ( "github.com/CustomResourceDefinition/catalog/internal/crd" "github.com/CustomResourceDefinition/catalog/internal/genall" "github.com/CustomResourceDefinition/catalog/internal/kustomize" + "github.com/CustomResourceDefinition/catalog/internal/semver" ) type GitGenerator struct { + *GeneratorVersions config configuration.Configuration reader crd.CrdReader + filter *regexp.Regexp tmpDir, gitDir string tags []string branches []string } -func NewGitGenerator(config configuration.Configuration, reader crd.CrdReader) Generator { +func NewGitGenerator(config configuration.Configuration, reader crd.CrdReader, filter *regexp.Regexp) Generator { return &GitGenerator{ config: config, reader: reader, + filter: filter, } } @@ -36,27 +42,6 @@ func (generator *GitGenerator) Close() error { return os.RemoveAll(generator.tmpDir) } -func (generator *GitGenerator) VersionSortKey(version string) (int64, error) { - if err := generator.ensureLoaded(); err != nil { - return 0, err - } - - out, err := exec.Command("git", "--git-dir", generator.gitDir, "log", "-1", "--format=%ct", version).Output() // tag - if err != nil { - out, err = exec.Command("git", "--git-dir", generator.gitDir, "log", "-1", "--format=%ct", fmt.Sprintf("origin/%s", version)).Output() // branch - if err != nil { - return 0, fmt.Errorf("unable to get commit date for '%s': %w", version, err) - } - } - - ts, err := strconv.ParseInt(strings.TrimSpace(string(out)), 10, 64) - if err != nil { - return 0, fmt.Errorf("unable to parse commit date for '%s': %w", version, err) - } - - return ts, nil -} - func (generator *GitGenerator) MetaData(version string) ([]crd.CrdMetaSchema, error) { if err := generator.ensureLoaded(); err != nil { return nil, err @@ -157,6 +142,14 @@ func (generator *GitGenerator) Crds(version string) ([]crd.Crd, error) { return crds, nil } +func (generator *GitGenerator) LatestVersion() (string, error) { + versions, err := generator.Versions() + if err != nil { + return "", err + } + return generator.latest(versions) +} + func (generator *GitGenerator) Versions() ([]string, error) { if err := generator.ensureLoaded(); err != nil { return nil, err @@ -165,7 +158,52 @@ func (generator *GitGenerator) Versions() ([]string, error) { versions := make([]string, 0, len(generator.tags)+len(generator.branches)) versions = append(versions, generator.tags...) versions = append(versions, generator.branches...) - return versions, nil + + filtered := make([]string, 0) + for _, v := range versions { + if generator.filter.MatchString(v) { + filtered = append(filtered, v) + } + } + + sort.Slice(filtered, func(i, j int) bool { + a, err := generator.sortKey(filtered[i]) + if err != nil { + return false + } + b, err := generator.sortKey(filtered[j]) + if err != nil { + return true + } + if a != b { + return a < b + } + aa := normalizeVersion(generator.filter.FindAllStringSubmatch(filtered[i], -1)) + bb := normalizeVersion(generator.filter.FindAllStringSubmatch(filtered[j], -1)) + return semver.Compare(aa, bb) > 0 + }) + return filtered, nil +} + +func (generator *GitGenerator) sortKey(version string) (int64, error) { + if err := generator.ensureLoaded(); err != nil { + return 0, err + } + + out, err := exec.Command("git", "--git-dir", generator.gitDir, "log", "-1", "--format=%ct", version).Output() // tag + if err != nil { + out, err = exec.Command("git", "--git-dir", generator.gitDir, "log", "-1", "--format=%ct", fmt.Sprintf("origin/%s", version)).Output() // branch + if err != nil { + return 0, fmt.Errorf("unable to get commit date for '%s': %w", version, err) + } + } + + ts, err := strconv.ParseInt(strings.TrimSpace(string(out)), 10, 64) + if err != nil { + return 0, fmt.Errorf("unable to parse commit date for '%s': %w", version, err) + } + + return ts, nil } func (generator *GitGenerator) ensureLoaded() error { diff --git a/internal/generator/gitGeneratorFactory.go b/internal/generator/gitGeneratorFactory.go index d95e55108..ae97dfc11 100644 --- a/internal/generator/gitGeneratorFactory.go +++ b/internal/generator/gitGeneratorFactory.go @@ -19,26 +19,28 @@ import ( type gitGeneratorFactory struct { config configuration.Configuration reader crd.CrdReader + filter *regexp.Regexp logger io.Writer } -func NewGitGeneratorFactory(config configuration.Configuration, reader crd.CrdReader, logger io.Writer) *gitGeneratorFactory { +func NewGitGeneratorFactory(config configuration.Configuration, reader crd.CrdReader, filter *regexp.Regexp, logger io.Writer) *gitGeneratorFactory { return &gitGeneratorFactory{ config: config, reader: reader, + filter: filter, logger: logger, } } func (f *gitGeneratorFactory) Build() (Generator, error) { - generator := NewGitGenerator(f.config, f.reader).(*GitGenerator) + generator := NewGitGenerator(f.config, f.reader, f.filter).(*GitGenerator) if isGitHub, owner, repo := isGitHubRepo(f.config.Repository); isGitHub { token := os.Getenv("GITHUB_TOKEN") if token != "" { versions, err := f.fetchGitHubVersions(owner, repo, token) if err == nil { - return NewPreparedGitGenerator(generator, versions), nil + return NewPreparedGitGenerator(generator, versions, f.filter), nil } fmt.Fprintf(f.logger, "Use git fallback instead of GitHub APIs for %s: %s\n", f.config.Name, err.Error()) } diff --git a/internal/generator/gitGeneratorFactory_test.go b/internal/generator/gitGeneratorFactory_test.go index afc3321a9..9e6e43641 100644 --- a/internal/generator/gitGeneratorFactory_test.go +++ b/internal/generator/gitGeneratorFactory_test.go @@ -5,6 +5,7 @@ import ( "net/http" "net/http/httptest" "os" + "regexp" "testing" "github.com/CustomResourceDefinition/catalog/internal/configuration" @@ -106,96 +107,14 @@ func TestGitGeneratorFactoryBuildGitHubSuccessWithVersions(t *testing.T) { Repository: "https://github.com/owner/repo", } - generator, err := NewGitGeneratorFactory(config, nil, nil).Build() + generator, err := NewGitGeneratorFactory(config, nil, regexp.MustCompile(".*"), nil).Build() assert.Nil(t, err) defer generator.Close() assert.IsType(t, &PreparedGitGenerator{}, generator) - versions, err := generator.Versions() + version, err := generator.LatestVersion() assert.Nil(t, err) - assert.Contains(t, versions, "v1.0.0") - assert.Contains(t, versions, "v0.9.0") - assert.Contains(t, versions, "main") - assert.Contains(t, versions, "develop") -} - -func TestGitGeneratorFactoryBuildGitHubSuccessWithSortKey(t *testing.T) { - t.Setenv("GITHUB_TOKEN", "test-token") - - cleanup := setupGitHubServer(t, []gitHubResponse{ - { - prefix: "refs/tags/", - tags: []githubRef{ - {name: "v1.0.0", committedDate: "2000-01-15T10:00:00Z"}, - }, - }, - { - prefix: "refs/heads/", - branches: []githubRef{ - {name: "main", committedDate: "2000-01-20T10:00:00Z"}, - }, - }, - }) - defer cleanup() - - config := configuration.Configuration{ - Kind: configuration.Git, - Repository: "https://github.com/owner/repo", - } - - generator, err := NewGitGeneratorFactory(config, nil, nil).Build() - assert.Nil(t, err) - defer generator.Close() - assert.IsType(t, &PreparedGitGenerator{}, generator) - - key1, err := generator.VersionSortKey("v1.0.0") - assert.Nil(t, err) - assert.Greater(t, key1, int64(0)) - - key2, err := generator.VersionSortKey("main") - assert.Nil(t, err) - assert.Greater(t, key2, int64(0)) - - assert.Greater(t, key2, key1, "main (2000-01-20) should have later timestamp than v1.0.0 (2000-01-15)") - - _, err = generator.VersionSortKey("nonexistent") - assert.NotNil(t, err) -} - -func TestPreparedGitGeneratorVersionsSortedByDate(t *testing.T) { - t.Setenv("GITHUB_TOKEN", "test-token") - - cleanup := setupGitHubServer(t, []gitHubResponse{ - { - prefix: "refs/tags/", - tags: []githubRef{ - {name: "v0.9.0", committedDate: "2000-01-10T10:00:00Z"}, - {name: "v1.0.0", committedDate: "2000-01-15T10:00:00Z"}, - }, - }, - { - prefix: "refs/heads/", - branches: []githubRef{ - {name: "develop", committedDate: "2000-01-18T10:00:00Z"}, - {name: "main", committedDate: "2000-01-20T10:00:00Z"}, - }, - }, - }) - defer cleanup() - - config := configuration.Configuration{ - Kind: configuration.Git, - Repository: "https://github.com/owner/repo", - } - - generator, err := NewGitGeneratorFactory(config, nil, nil).Build() - assert.Nil(t, err) - defer generator.Close() - assert.IsType(t, &PreparedGitGenerator{}, generator) - - versions, err := generator.Versions() - assert.Nil(t, err) - assert.Equal(t, []string{"v0.9.0", "v1.0.0", "develop", "main"}, versions) + assert.Equal(t, version, "main") } var logger = bytes.NewBuffer([]byte{}) @@ -219,7 +138,7 @@ func TestGitGeneratorFactoryBuildGitHubFailure(t *testing.T) { Repository: "https://github.com/owner/repo", } - generator, err := NewGitGeneratorFactory(config, nil, logger).Build() + generator, err := NewGitGeneratorFactory(config, nil, regexp.MustCompile(".*"), logger).Build() assert.Nil(t, err) defer generator.Close() assert.IsType(t, &GitGenerator{}, generator) @@ -249,7 +168,7 @@ func TestGitGeneratorFactoryBuildGitHubSuccess(t *testing.T) { Repository: "https://github.com/owner/repo", } - generator, err := NewGitGeneratorFactory(config, nil, nil).Build() + generator, err := NewGitGeneratorFactory(config, nil, regexp.MustCompile(".*"), nil).Build() assert.Nil(t, err) defer generator.Close() assert.IsType(t, &PreparedGitGenerator{}, generator) @@ -309,26 +228,22 @@ func TestGitGeneratorFactoryAnnotatedTags(t *testing.T) { Repository: "https://github.com/owner/repo", } - generator, err := NewGitGeneratorFactory(config, nil, nil).Build() + generator, err := NewGitGeneratorFactory(config, nil, regexp.MustCompile(".*"), nil).Build() assert.Nil(t, err) defer generator.Close() assert.IsType(t, &PreparedGitGenerator{}, generator) - versions, err := generator.Versions() - assert.Nil(t, err) + info := generator.(*PreparedGitGenerator).versions + versions := make([]string, 0) + for _, i := range info { + versions = append(versions, i.name) + } + + assert.Equal(t, len(versions), 4) assert.Contains(t, versions, "v1.0.0") assert.Contains(t, versions, "v0.9.0") assert.Contains(t, versions, "v0.8.0") assert.Contains(t, versions, "main") - - key1, _ := generator.VersionSortKey("v1.0.0") - key2, _ := generator.VersionSortKey("v0.9.0") - key3, _ := generator.VersionSortKey("v0.8.0") - keyMain, _ := generator.VersionSortKey("main") - - assert.Greater(t, keyMain, key1, "main > v1.0.0") - assert.Greater(t, key1, key2, "v1.0.0 > v0.9.0 (via target.tagger.date)") - assert.Greater(t, key2, key3, "v0.9.0 > v0.8.0 (via target.target.committedDate)") } func TestGitGeneratorFactoryNestedTags(t *testing.T) { @@ -361,26 +276,22 @@ func TestGitGeneratorFactoryNestedTags(t *testing.T) { Repository: "https://github.com/owner/repo", } - generator, err := NewGitGeneratorFactory(config, nil, nil).Build() + generator, err := NewGitGeneratorFactory(config, nil, regexp.MustCompile(".*"), nil).Build() assert.Nil(t, err) defer generator.Close() assert.IsType(t, &PreparedGitGenerator{}, generator) - versions, err := generator.Versions() - assert.Nil(t, err) + info := generator.(*PreparedGitGenerator).versions + versions := make([]string, 0) + for _, i := range info { + versions = append(versions, i.name) + } + + assert.Equal(t, len(versions), 4) assert.Contains(t, versions, "v1.0.0") assert.Contains(t, versions, "v0.9.0") assert.Contains(t, versions, "nested-tag") assert.Contains(t, versions, "main") - - key1, _ := generator.VersionSortKey("v1.0.0") - key2, _ := generator.VersionSortKey("v0.9.0") - keyNested, _ := generator.VersionSortKey("nested-tag") - keyMain, _ := generator.VersionSortKey("main") - - assert.Greater(t, keyMain, key1, "main > v1.0.0") - assert.Greater(t, key1, key2, "v1.0.0 > v0.9.0") - assert.Greater(t, key2, keyNested, "v0.9.0 > nested-tag (via nested target.target.committedDate)") } func TestGitGeneratorFactoryPagination(t *testing.T) { @@ -411,14 +322,18 @@ func TestGitGeneratorFactoryPagination(t *testing.T) { Repository: "https://github.com/owner/repo", } - generator, err := NewGitGeneratorFactory(config, nil, nil).Build() + generator, err := NewGitGeneratorFactory(config, nil, regexp.MustCompile(".*"), nil).Build() assert.Nil(t, err) defer generator.Close() assert.IsType(t, &PreparedGitGenerator{}, generator) - versions, err := generator.Versions() - assert.Nil(t, err) - assert.Equal(t, 5, len(versions), "should have all 4 tags + 1 branch") + info := generator.(*PreparedGitGenerator).versions + versions := make([]string, 0) + for _, i := range info { + versions = append(versions, i.name) + } + + assert.Equal(t, len(versions), 5) assert.Contains(t, versions, "v1.0.0") assert.Contains(t, versions, "v0.9.0") assert.Contains(t, versions, "v0.8.0") @@ -468,13 +383,12 @@ func TestGitGeneratorFactoryEmptyResponses(t *testing.T) { Repository: "https://github.com/owner/repo", } - generator, err := NewGitGeneratorFactory(config, nil, nil).Build() + generator, err := NewGitGeneratorFactory(config, nil, regexp.MustCompile(".*"), nil).Build() assert.Nil(t, err) defer generator.Close() assert.IsType(t, &PreparedGitGenerator{}, generator) - versions, err := generator.Versions() - assert.Nil(t, err) + versions := generator.(*PreparedGitGenerator).versions assert.Equal(t, tt.expectedVersions, len(versions)) }) } diff --git a/internal/generator/git_test.go b/internal/generator/git_test.go index d2663a0c1..afa962e5c 100644 --- a/internal/generator/git_test.go +++ b/internal/generator/git_test.go @@ -3,6 +3,7 @@ package generator import ( "fmt" "os" + "regexp" "testing" "github.com/CustomResourceDefinition/catalog/internal/configuration" @@ -40,7 +41,7 @@ func TestGitGeneratorVersionsCombinesTagsAndBranches(t *testing.T) { Repository: fmt.Sprintf("file://%s", *p), } - generator := NewGitGenerator(config, nil) + generator := NewGitGenerator(config, nil, regexp.MustCompile(".*")) defer generator.Close() versions, err := generator.Versions() @@ -68,7 +69,7 @@ func TestGitGeneratorUnknownVersion(t *testing.T) { Repository: fmt.Sprintf("file://%s", *p), } - generator := NewGitGenerator(config, nil) + generator := NewGitGenerator(config, nil, regexp.MustCompile(".*")) defer generator.Close() metadata, err := generator.MetaData("4.5.6") @@ -102,7 +103,7 @@ func TestGitGeneratorMetadataForRegularFile(t *testing.T) { reader, err := crd.NewCrdReader(setupLogger()) assert.Nil(t, err) - generator := NewGitGenerator(config, reader) + generator := NewGitGenerator(config, reader, regexp.MustCompile(".*")) defer generator.Close() metadata, err := generator.MetaData("") @@ -147,7 +148,7 @@ func TestGitGeneratorMetadataForKustomizeFile(t *testing.T) { reader, err := crd.NewCrdReader(setupLogger()) assert.Nil(t, err) - generator := NewGitGenerator(config, reader) + generator := NewGitGenerator(config, reader, regexp.MustCompile(".*")) defer generator.Close() metadata, err := generator.MetaData("") @@ -192,7 +193,7 @@ func TestGitGeneratorMetadataForSourceFiles(t *testing.T) { reader, err := crd.NewCrdReader(setupLogger()) assert.Nil(t, err) - generator := NewGitGenerator(config, reader) + generator := NewGitGenerator(config, reader, regexp.MustCompile(".*")) defer generator.Close() metadata, err := generator.MetaData("") @@ -203,48 +204,13 @@ func TestGitGeneratorMetadataForSourceFiles(t *testing.T) { assert.Equal(t, "v1", metadata[0].Version) } -func TestGitGeneratorVersionSortKeyForBranch(t *testing.T) { - bundles := []gitBundle{ - { - tag: "1.0.0", - branch: "develop", - paths: []gitPath{ - { - path: "regular/crd.yaml", - file: "testdata/test-crd.yaml", - }, - }, - }, - } - - p, err := setupGit(t, bundles) - assert.Nil(t, err) - assert.NotNil(t, p) - - config := configuration.Configuration{ - Kind: configuration.Git, - Repository: fmt.Sprintf("file://%s", *p), - } - - generator := NewGitGenerator(config, nil) - defer generator.Close() - - key, err := generator.VersionSortKey("main") - assert.Nil(t, err) - assert.Greater(t, key, int64(0)) - - key, err = generator.VersionSortKey("develop") - assert.Nil(t, err) - assert.Greater(t, key, int64(0)) -} - func TestGitGeneratorCloneFailure(t *testing.T) { config := configuration.Configuration{ Kind: configuration.Git, Repository: "file:///nonexistent/path/repo.git", } - generator := NewGitGenerator(config, nil) + generator := NewGitGenerator(config, nil, regexp.MustCompile(".*")) defer generator.Close() _, err := generator.Versions() @@ -274,7 +240,7 @@ func TestGitGeneratorCheckoutFailure(t *testing.T) { Repository: fmt.Sprintf("file://%s", *p), } - generator := NewGitGenerator(config, nil) + generator := NewGitGenerator(config, nil, regexp.MustCompile(".*")) defer generator.Close() _, err = generator.Crds("nonexistent-branch-tag") @@ -304,7 +270,7 @@ func TestGitGeneratorClose(t *testing.T) { Repository: fmt.Sprintf("file://%s", *p), } - generator := NewGitGenerator(config, nil) + generator := NewGitGenerator(config, nil, regexp.MustCompile(".*")) defer generator.Close() versions, err := generator.Versions() @@ -319,3 +285,30 @@ func TestGitGeneratorClose(t *testing.T) { _, err = os.Stat(tmpDir) assert.True(t, os.IsNotExist(err), "temp dir should be removed after Close()") } + +func TestGitGeneratorVersionsCustomSort(t *testing.T) { + seedVersions := []string{ + "2.10.0", "1.0.0", "2.2.0", "2.1.0", "1.01.01", + } + bundles := make([]gitBundle, 0) + for _, v := range seedVersions { + bundles = append(bundles, gitBundle{tag: v, paths: []gitPath{}}) + } + + p, err := setupGit(t, bundles) + assert.Nil(t, err) + assert.NotNil(t, p) + + config := configuration.Configuration{ + Kind: configuration.Git, + Repository: fmt.Sprintf("file://%s", *p), + } + + filter := regexp.MustCompile(`^([0-9]+\.[0-9]+\.[0-9]+)$`) + generator := NewGitGenerator(config, nil, filter) + defer generator.Close() + + versions, err := generator.Versions() + assert.Nil(t, err) + assert.Equal(t, []string{"2.10.0", "2.2.0", "2.1.0", "1.01.01", "1.0.0"}, versions) +} diff --git a/internal/generator/helm.go b/internal/generator/helm.go index c29b03212..6c7adaba1 100644 --- a/internal/generator/helm.go +++ b/internal/generator/helm.go @@ -5,6 +5,7 @@ import ( "fmt" "os" "path" + "regexp" "strings" "github.com/CustomResourceDefinition/catalog/internal/configuration" @@ -20,19 +21,22 @@ import ( ) type HelmGenerator struct { + *GeneratorVersions target string config configuration.Configuration reader crd.CrdReader + filter *regexp.Regexp tmpDir string versions repo.ChartVersions downloader downloader.ChartDownloader } -func NewHelmGenerator(target string, config configuration.Configuration, reader crd.CrdReader) Generator { +func NewHelmGenerator(target string, config configuration.Configuration, reader crd.CrdReader, filter *regexp.Regexp) Generator { return &HelmGenerator{ target: target, config: config, reader: reader, + filter: filter, } } @@ -95,6 +99,14 @@ func (generator *HelmGenerator) Crds(version string) ([]crd.Crd, error) { return crds, nil } +func (generator *HelmGenerator) LatestVersion() (string, error) { + versions, err := generator.Versions() + if err != nil { + return "", err + } + return generator.latest(versions) +} + func (generator *HelmGenerator) Versions() ([]string, error) { if err := generator.ensureLoaded(); err != nil { return nil, err @@ -108,17 +120,13 @@ func (generator *HelmGenerator) Versions() ([]string, error) { } } - return versions, nil + return generator.semverSort(versions, generator.filter) } func (generator *HelmGenerator) Close() error { return os.RemoveAll(generator.tmpDir) } -func (generator *HelmGenerator) VersionSortKey(version string) (int64, error) { - return 0, nil -} - func (generator *HelmGenerator) ensureLoaded() error { if len(generator.tmpDir) != 0 { return nil diff --git a/internal/generator/helm_test.go b/internal/generator/helm_test.go index 66b193e99..e66478fd6 100644 --- a/internal/generator/helm_test.go +++ b/internal/generator/helm_test.go @@ -2,6 +2,7 @@ package generator import ( "os" + "regexp" "strings" "testing" @@ -21,7 +22,7 @@ func TestHelmGeneratorVersions(t *testing.T) { Repository: server.URL, } - generator := NewHelmGenerator("connect", config, nil) + generator := NewHelmGenerator("connect", config, nil, regexp.MustCompile(".*")) defer generator.Close() versions, err := generator.Versions() @@ -38,7 +39,7 @@ func TestHelmGeneratorUnknownTarget(t *testing.T) { Repository: server.URL, } - generator := NewHelmGenerator("unknown", config, nil) + generator := NewHelmGenerator("unknown", config, nil, regexp.MustCompile(".*")) defer generator.Close() versions, err := generator.Versions() @@ -56,7 +57,7 @@ func TestHelmGeneratorUnknownVersion(t *testing.T) { Repository: server.URL, } - generator := NewHelmGenerator("connect", config, nil) + generator := NewHelmGenerator("connect", config, nil, regexp.MustCompile(".*")) defer generator.Close() metadata, err := generator.MetaData("4.5.6") @@ -82,7 +83,7 @@ func TestHelmGeneratorMetadata(t *testing.T) { reader, err := crd.NewCrdReader(setupLogger()) assert.Nil(t, err) - generator := NewHelmGenerator("connect", config, reader) + generator := NewHelmGenerator("connect", config, reader, regexp.MustCompile(".*")) defer generator.Close() metadata, err := generator.MetaData("") @@ -92,21 +93,3 @@ func TestHelmGeneratorMetadata(t *testing.T) { assert.Equal(t, "onepassworditem", metadata[0].Kind) assert.Equal(t, "v1", metadata[0].Version) } - -func TestHelmGeneratorHasInertSortingKeys(t *testing.T) { - config := configuration.Configuration{ - Kind: configuration.Helm, - Repository: "http:localhost", - } - - generator := NewHelmGenerator("connect", config, nil) - defer generator.Close() - - versions := []string{"0.0.0", "1.0.0", "3.2.1", "999.999.999"} - - for _, version := range versions { - key, err := generator.VersionSortKey(version) - assert.Nil(t, err) - assert.Equal(t, key, int64(0)) - } -} diff --git a/internal/generator/http.go b/internal/generator/http.go index 580d201d2..a4af51054 100644 --- a/internal/generator/http.go +++ b/internal/generator/http.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "net/http" + "regexp" "time" "github.com/CustomResourceDefinition/catalog/internal/configuration" @@ -12,18 +13,21 @@ import ( ) type HttpGenerator struct { + *GeneratorVersions client http.Client config configuration.Configuration reader crd.CrdReader + filter *regexp.Regexp } -func NewHttpGenerator(config configuration.Configuration, reader crd.CrdReader) Generator { +func NewHttpGenerator(config configuration.Configuration, reader crd.CrdReader, filter *regexp.Regexp) Generator { return &HttpGenerator{ client: http.Client{ Timeout: 15 * time.Second, }, config: config, reader: reader, + filter: filter, } } @@ -80,6 +84,14 @@ func (generator *HttpGenerator) Crds(version string) ([]crd.Crd, error) { return crds, nil } +func (generator *HttpGenerator) LatestVersion() (string, error) { + versions, err := generator.Versions() + if err != nil { + return "", err + } + return generator.latest(versions) +} + func (generator *HttpGenerator) Versions() ([]string, error) { versions := make([]string, len(generator.config.Downloads)) @@ -87,17 +99,13 @@ func (generator *HttpGenerator) Versions() ([]string, error) { versions[i] = download.Version } - return versions, nil + return generator.semverSort(versions, generator.filter) } func (generator *HttpGenerator) Close() error { return nil } -func (generator *HttpGenerator) VersionSortKey(version string) (int64, error) { - return 0, nil -} - func (generator *HttpGenerator) read(resp *http.Response) ([]byte, error) { defer resp.Body.Close() diff --git a/internal/generator/http_test.go b/internal/generator/http_test.go index aad9944e1..3485f9260 100644 --- a/internal/generator/http_test.go +++ b/internal/generator/http_test.go @@ -1,6 +1,7 @@ package generator import ( + "regexp" "testing" "github.com/CustomResourceDefinition/catalog/internal/configuration" @@ -9,18 +10,18 @@ import ( ) func TestHttpGeneratorVersions(t *testing.T) { - expectedVersions := []string{"1.0.0", "1.3.0", "2.0.0"} + expectedVersions := []string{"2.0.0", "1.3.0", "1.0.0"} config := configuration.Configuration{ Kind: configuration.Http, Downloads: []configuration.ConfigurationDownload{ - {Version: expectedVersions[0]}, - {Version: expectedVersions[1]}, {Version: expectedVersions[2]}, + {Version: expectedVersions[1]}, + {Version: expectedVersions[0]}, }, } - generator := NewHttpGenerator(config, nil) + generator := NewHttpGenerator(config, nil, regexp.MustCompile(".*")) defer generator.Close() versions, err := generator.Versions() @@ -38,7 +39,7 @@ func TestHttpGeneratorUnknownVersion(t *testing.T) { }, } - generator := NewHttpGenerator(config, nil) + generator := NewHttpGenerator(config, nil, regexp.MustCompile(".*")) metadata, err := generator.MetaData("4.5.6") assert.Nil(t, metadata) @@ -67,7 +68,7 @@ func TestHttpGeneratorSchemas(t *testing.T) { reader, err := crd.NewCrdReader(setupLogger()) assert.Nil(t, err) - generator := NewHttpGenerator(config, reader) + generator := NewHttpGenerator(config, reader, regexp.MustCompile(".*")) defer generator.Close() crds, err := generator.Crds(version) @@ -111,7 +112,7 @@ func TestHttpGeneratorMetadata(t *testing.T) { reader, err := crd.NewCrdReader(setupLogger()) assert.Nil(t, err) - generator := NewHttpGenerator(config, reader) + generator := NewHttpGenerator(config, reader, regexp.MustCompile(".*")) defer generator.Close() metadata, err := generator.MetaData(version) @@ -147,7 +148,7 @@ func TestHttpGeneratorPartialSchemas(t *testing.T) { reader, err := crd.NewCrdReader(setupLogger()) assert.Nil(t, err) - generator := NewHttpGenerator(config, reader) + generator := NewHttpGenerator(config, reader, regexp.MustCompile(".*")) defer generator.Close() crds, err := generator.Crds(version) @@ -193,7 +194,7 @@ func TestHttpGeneratorNoSchemas(t *testing.T) { reader, err := crd.NewCrdReader(setupLogger()) assert.Nil(t, err) - generator := NewHttpGenerator(config, reader) + generator := NewHttpGenerator(config, reader, regexp.MustCompile(".*")) defer generator.Close() crds, err := generator.Crds(version) @@ -211,22 +212,69 @@ func TestHttpGeneratorNoSchemas(t *testing.T) { assert.Equal(t, 0, len(schemas)) } -func TestHttpGeneratorHasInertSortingKeys(t *testing.T) { - config := configuration.Configuration{ - Kind: configuration.Http, - Downloads: []configuration.ConfigurationDownload{ - {Version: "1.0.0"}, +func TestHttpGeneratorVersionsFiltering(t *testing.T) { + tests := []struct { + versions []string + expectedVersions []string + pattern string + }{ + { + versions: []string{"2.0.0", "1.3.0", "1.0.0"}, + expectedVersions: []string{"2.0.0", "1.3.0", "1.0.0"}, + pattern: `.*`, + }, + { + versions: []string{"v2.0.0", "v1.3.0", "v1.0.0"}, + expectedVersions: []string{"v2.0.0", "v1.3.0", "v1.0.0"}, + pattern: `^v([0-9]+\.[0-9]+\.[0-9]+)$`, + }, + { + versions: []string{"2.0.0", "v1.3.0", "v1.0.0"}, + expectedVersions: []string{"2.0.0", "v1.3.0", "v1.0.0"}, + pattern: `^v?([0-9]+\.[0-9]+\.[0-9]+)$`, + }, + { + versions: []string{"2.0.0", "v1.3v", "v1.0.0"}, + expectedVersions: []string{"2.0.0", "v1.0.0"}, + pattern: `^v?([0-9]+\.[0-9]+\.[0-9]+)$`, + }, + { + versions: []string{"2.0.0-2", "1.3.0-1892", "1.0.0-01"}, + expectedVersions: []string{"2.0.0-2", "1.3.0-1892", "1.0.0-01"}, + pattern: `^([0-9]+\.[0-9]+\.[0-9]+-\d+)$`, + }, + { + versions: []string{"v1.33.2+k0s.0"}, + expectedVersions: []string{"v1.33.2+k0s.0"}, + pattern: `^v([0-9]+\.[0-9]+\.[0-9]+\+k0s\.0)$`, + }, + { + versions: []string{"main", "v1.0", "master"}, + expectedVersions: []string{"main", "master"}, + pattern: `^(main|master)$`, + }, + { + versions: []string{"main", "v1.0.0", "2.0.0", "master"}, + expectedVersions: []string{"2.0.0", "main", "master"}, + pattern: `^([0-9]+\.[0-9]+\.[0-9]+)|(main|master)$`, }, } - generator := NewHttpGenerator(config, nil) - defer generator.Close() + for i, test := range tests { + downloads := make([]configuration.ConfigurationDownload, 0) + for _, v := range test.versions { + downloads = append(downloads, configuration.ConfigurationDownload{Version: v}) + } + config := configuration.Configuration{ + Kind: configuration.Http, + Downloads: downloads, + } - versions := []string{"0.0.0", "1.0.0", "3.2.1", "999.999.999"} + generator := NewHttpGenerator(config, nil, regexp.MustCompile(test.pattern)) + defer generator.Close() - for _, version := range versions { - key, err := generator.VersionSortKey(version) - assert.Nil(t, err) - assert.Equal(t, key, int64(0)) + versions, err := generator.Versions() + assert.Nil(t, err, "index %d failed", i) + assert.Equal(t, test.expectedVersions, versions, "index %d failed", i) } } diff --git a/internal/generator/oci.go b/internal/generator/oci.go index c4daaac4d..aaff82281 100644 --- a/internal/generator/oci.go +++ b/internal/generator/oci.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "os" + "regexp" "strconv" "github.com/CustomResourceDefinition/catalog/internal/configuration" @@ -14,9 +15,11 @@ import ( ) type OciGenerator struct { + *GeneratorVersions realmClient realmClient config configuration.Configuration reader crd.CrdReader + filter *regexp.Regexp tmpDir string downloader downloader.ChartDownloader plainHttp bool @@ -24,7 +27,7 @@ type OciGenerator struct { const HELM_OCI_PLAIN_HTTP = "HELM_OCI_PLAIN_HTTP" -func NewOciGenerator(config configuration.Configuration, reader crd.CrdReader) Generator { +func NewOciGenerator(config configuration.Configuration, reader crd.CrdReader, filter *regexp.Regexp) Generator { plainHttp := false env, found := os.LookupEnv(HELM_OCI_PLAIN_HTTP) value, err := strconv.ParseBool(env) @@ -36,6 +39,7 @@ func NewOciGenerator(config configuration.Configuration, reader crd.CrdReader) G realmClient: newRealmClient(plainHttp), config: config, reader: reader, + filter: filter, plainHttp: plainHttp, } } @@ -44,10 +48,6 @@ func (generator *OciGenerator) Close() error { return os.RemoveAll(generator.tmpDir) } -func (generator *OciGenerator) VersionSortKey(version string) (int64, error) { - return 0, nil -} - func (generator *OciGenerator) MetaData(version string) ([]crd.CrdMetaSchema, error) { if err := generator.ensureLoaded(); err != nil { return nil, err @@ -110,17 +110,25 @@ func (generator *OciGenerator) Crds(version string) ([]crd.Crd, error) { return crds, nil } +func (generator *OciGenerator) LatestVersion() (string, error) { + versions, err := generator.Versions() + if err != nil { + return "", err + } + return generator.latest(versions) +} + func (generator *OciGenerator) Versions() ([]string, error) { if err := generator.ensureLoaded(); err != nil { return nil, err } - tags, err := generator.realmClient.ListOciTags(generator.config.Repository) + versions, err := generator.realmClient.ListOciTags(generator.config.Repository) if err != nil { return nil, err } - return tags, nil + return generator.semverSort(versions, generator.filter) } func (generator *OciGenerator) ensureLoaded() error { diff --git a/internal/generator/oci_test.go b/internal/generator/oci_test.go index 8b4a55a6f..05b903d41 100644 --- a/internal/generator/oci_test.go +++ b/internal/generator/oci_test.go @@ -2,6 +2,7 @@ package generator import ( "fmt" + "regexp" "testing" "github.com/CustomResourceDefinition/catalog/internal/configuration" @@ -25,7 +26,7 @@ func TestOciGeneratorVersions(t *testing.T) { Repository: fmt.Sprintf("%s%s", server.URL, "/helm/connect"), } - generator := NewOciGenerator(config, nil) + generator := NewOciGenerator(config, nil, regexp.MustCompile(".*")) defer generator.Close() versions, err := generator.Versions() @@ -50,7 +51,7 @@ func TestOciGeneratorUnknownVersion(t *testing.T) { Repository: fmt.Sprintf("%s%s", server.URL, "/helm/connect"), } - generator := NewOciGenerator(config, nil) + generator := NewOciGenerator(config, nil, regexp.MustCompile(".*")) defer generator.Close() metadata, err := generator.MetaData("4.5.6") @@ -78,7 +79,7 @@ func TestOciGeneratorMetadata(t *testing.T) { reader, err := crd.NewCrdReader(setupLogger()) assert.Nil(t, err) - generator := NewOciGenerator(config, reader) + generator := NewOciGenerator(config, reader, regexp.MustCompile(".*")) defer generator.Close() metadata, err := generator.MetaData("") @@ -88,25 +89,3 @@ func TestOciGeneratorMetadata(t *testing.T) { assert.Equal(t, "onepassworditem", metadata[0].Kind) assert.Equal(t, "v1", metadata[0].Version) } - -func TestOciGeneratorHasInertSortingKeys(t *testing.T) { - config := configuration.Configuration{ - Name: "oci", - Kind: configuration.HelmOci, - Repository: fmt.Sprintf("%s%s", "http://localhost", "/helm/connect"), - } - - reader, err := crd.NewCrdReader(setupLogger()) - assert.Nil(t, err) - - generator := NewOciGenerator(config, reader) - defer generator.Close() - - versions := []string{"0.0.0", "1.0.0", "3.2.1", "999.999.999"} - - for _, version := range versions { - key, err := generator.VersionSortKey(version) - assert.Nil(t, err) - assert.Equal(t, key, int64(0)) - } -} diff --git a/internal/generator/preparedGit.go b/internal/generator/preparedGit.go index 21a75eee4..1d7072a99 100644 --- a/internal/generator/preparedGit.go +++ b/internal/generator/preparedGit.go @@ -2,6 +2,8 @@ package generator import ( "fmt" + "regexp" + "sort" "github.com/CustomResourceDefinition/catalog/internal/crd" ) @@ -9,6 +11,7 @@ import ( type PreparedGitGenerator struct { gitGenerator *GitGenerator versions []versionInfo + filter *regexp.Regexp } type versionInfo struct { @@ -16,28 +19,35 @@ type versionInfo struct { timestamp int64 } -func NewPreparedGitGenerator(gitGenerator *GitGenerator, versions []versionInfo) *PreparedGitGenerator { +func NewPreparedGitGenerator(gitGenerator *GitGenerator, versions []versionInfo, filter *regexp.Regexp) Generator { return &PreparedGitGenerator{ gitGenerator: gitGenerator, versions: versions, + filter: filter, } } -func (g *PreparedGitGenerator) Versions() ([]string, error) { - versions := make([]string, len(g.versions)) - for i, v := range g.versions { - versions[i] = v.name - } - return versions, nil -} - -func (g *PreparedGitGenerator) VersionSortKey(version string) (int64, error) { +func (g *PreparedGitGenerator) LatestVersion() (string, error) { + filtered := make([]versionInfo, 0) for _, v := range g.versions { - if v.name == version { - return v.timestamp, nil + if g.filter.MatchString(v.name) { + filtered = append(filtered, v) } } - return 0, fmt.Errorf("version %q not found", version) + + sort.Slice(filtered, func(i, j int) bool { + return filtered[i].timestamp > filtered[j].timestamp + }) + + if len(filtered) == 0 { + return "", fmt.Errorf("no versions are available") + } + + return filtered[0].name, nil +} + +func (g *PreparedGitGenerator) Versions() ([]string, error) { + return g.gitGenerator.Versions() } func (g *PreparedGitGenerator) MetaData(version string) ([]crd.CrdMetaSchema, error) { diff --git a/internal/generator/preparedGit_test.go b/internal/generator/preparedGit_test.go index 15797b07d..be35f74aa 100644 --- a/internal/generator/preparedGit_test.go +++ b/internal/generator/preparedGit_test.go @@ -1,51 +1,50 @@ package generator import ( + "regexp" "testing" "github.com/stretchr/testify/assert" ) -func TestPreparedGitGeneratorVersions(t *testing.T) { +func TestPreparedGitGeneratorLatestVersionByTimestamp(t *testing.T) { versions := []versionInfo{ - {name: "v1.0.0", timestamp: 1705317600}, - {name: "main", timestamp: 1705749600}, - {name: "develop", timestamp: 1705569600}, + {name: "a", timestamp: 1705317600}, + {name: "b", timestamp: 1706000000}, + {name: "c", timestamp: 1705749600}, } - generator := NewPreparedGitGenerator(nil, versions) + generator := NewPreparedGitGenerator(nil, versions, regexp.MustCompile(".*")) - result, err := generator.Versions() + result, err := generator.LatestVersion() assert.Nil(t, err) - assert.Equal(t, []string{"v1.0.0", "main", "develop"}, result) + assert.Equal(t, "b", result) } -func TestPreparedGitGeneratorVersionSortKey(t *testing.T) { +func TestPreparedGitGeneratorLatestVersionByTimestampNotSemver(t *testing.T) { versions := []versionInfo{ - {name: "v1.0.0", timestamp: 1705317600}, - {name: "main", timestamp: 1705749600}, + {name: "1.0.0", timestamp: 1705000000}, + {name: "2.0.0", timestamp: 1706000000}, + {name: "10.0.0", timestamp: 1704000000}, } - generator := NewPreparedGitGenerator(nil, versions) + generator := NewPreparedGitGenerator(nil, versions, regexp.MustCompile(".*")) - key, err := generator.VersionSortKey("v1.0.0") + result, err := generator.LatestVersion() assert.Nil(t, err) - assert.Equal(t, int64(1705317600), key) - - key, err = generator.VersionSortKey("main") - assert.Nil(t, err) - assert.Equal(t, int64(1705749600), key) - - _, err = generator.VersionSortKey("nonexistent") - assert.NotNil(t, err) + assert.Equal(t, "2.0.0", result) } -func TestPreparedGitGeneratorVersionSortKeyEmpty(t *testing.T) { - versions := []versionInfo{} +func TestPreparedGitGeneratorLatestVersionNoMatchingFilter(t *testing.T) { + versions := []versionInfo{ + {name: "v1.0.0", timestamp: 1705317600}, + {name: "v2.0.0", timestamp: 1706000000}, + } - generator := NewPreparedGitGenerator(nil, versions) + filter := regexp.MustCompile(`^main$`) + generator := NewPreparedGitGenerator(nil, versions, filter) - _, err := generator.VersionSortKey("v1.0.0") + _, err := generator.LatestVersion() assert.NotNil(t, err) - assert.ErrorContains(t, err, "not found") + assert.Contains(t, err.Error(), "no versions are available") }