Skip to content

Commit 7c8ea6d

Browse files
authored
Cache all versions by source (#2779)
1 parent 5d06462 commit 7c8ea6d

7 files changed

Lines changed: 513 additions & 296 deletions

File tree

‎internal/cmd/fetcher/main.go‎

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import (
1313
"os"
1414
"os/exec"
1515
"path/filepath"
16-
"slices"
1716
"strings"
1817
"time"
1918

@@ -94,7 +93,8 @@ func (f *pluginFilter) includes(org, name string) bool {
9493

9594
// Fetcher is an interface for fetching plugin versions from external sources.
9695
type Fetcher interface {
97-
Fetch(ctx context.Context, config *source.Config) (string, error)
96+
// FetchVersions return all non-prerelease versions for the specified source.
97+
FetchVersions(ctx context.Context, src *source.Source) ([]string, error)
9898
}
9999

100100
func main() {
@@ -646,7 +646,7 @@ func fetchPendingCreations(
646646
versionTime func(ctx context.Context, path string) (time.Time, error),
647647
) (map[string]*pluginToCreate, error) {
648648
filter := newPluginFilter(includes)
649-
latestVersions := make(map[string]string, len(configs))
649+
versionsByCacheKey := make(map[string][]string, len(configs))
650650
pendingCreations := make(map[string]*pluginToCreate)
651651

652652
for _, config := range configs {
@@ -670,35 +670,29 @@ func fetchPendingCreations(
670670
continue
671671
}
672672
}
673-
newVersion := latestVersions[config.CacheKey()]
674-
if newVersion == "" {
673+
versions, ok := versionsByCacheKey[config.CacheKey()]
674+
if !ok {
675675
var err error
676-
newVersion, err = fetcher.Fetch(ctx, config)
676+
versions, err = fetcher.FetchVersions(ctx, &config.Source)
677677
if err != nil {
678-
if errors.Is(err, fetchclient.ErrSemverPrerelease) {
679-
logger.InfoContext(ctx, "skipping source", slog.String("filename", config.Filename), slog.Any("error", err))
680-
continue
681-
}
682-
return nil, err
678+
return nil, fmt.Errorf("%s: %w", config.Filename, err)
683679
}
684-
latestVersions[config.CacheKey()] = newVersion
680+
versionsByCacheKey[config.CacheKey()] = versions
685681
}
686-
// Some plugins share the same source but specify different ignore versions.
687-
// Ensure we continue to only fetch the latest version once but still respect ignores.
688-
if slices.Contains(config.Source.IgnoreVersions, newVersion) {
689-
logger.InfoContext(ctx, "skipping source", slog.String("filename", config.Filename), slog.String("version", newVersion))
690-
continue
682+
newVersion, err := config.Source.LatestVersion(versions)
683+
if err != nil {
684+
return nil, fmt.Errorf("%s: %w", config.Filename, err)
691685
}
692686
// Convert to absolute path to match plugin.Walk behavior (which converts paths via filepath.Abs)
693687
pluginDir, err := filepath.Abs(filepath.Dir(config.Filename))
694688
if err != nil {
695689
return nil, err
696690
}
697-
ok, err := checkDirExists(filepath.Join(pluginDir, newVersion))
691+
exists, err := checkDirExists(filepath.Join(pluginDir, newVersion))
698692
if err != nil {
699693
return nil, err
700694
}
701-
if ok {
695+
if exists {
702696
continue
703697
}
704698
previousVersion, err := getLatestVersionFromDir(pluginDir)

‎internal/cmd/fetcher/main_test.go‎

Lines changed: 53 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -203,9 +203,9 @@ func TestRunDependencyOrdering(t *testing.T) {
203203
// Mock fetcher that returns new versions for our test plugins
204204
// Cache keys are formatted as "github-owner-repository"
205205
fetcher := &mockFetcher{
206-
versions: map[string]string{
207-
"github-test-base-plugin": "v2.0.0",
208-
"github-test-consumer-plugin": "v2.0.0",
206+
versions: map[string][]string{
207+
"github-test-base-plugin": {"v1.0.0", "v2.0.0"},
208+
"github-test-consumer-plugin": {"v1.0.0", "v2.0.0"},
209209
},
210210
}
211211

@@ -244,9 +244,9 @@ func TestRunUpdateFrequency(t *testing.T) {
244244
repository: base-plugin
245245
`
246246
fetcher := &mockFetcher{
247-
versions: map[string]string{
248-
"github-test-base-plugin": "v2.0.0",
249-
"github-test-consumer-plugin": "v2.0.0",
247+
versions: map[string][]string{
248+
"github-test-base-plugin": {"v1.0.0", "v2.0.0"},
249+
"github-test-consumer-plugin": {"v1.0.0", "v2.0.0"},
250250
},
251251
}
252252

@@ -294,16 +294,14 @@ func TestRunUpdateFrequency(t *testing.T) {
294294

295295
// mockFetcher returns predetermined versions for testing.
296296
type mockFetcher struct {
297-
versions map[string]string // maps cache key (e.g., "github-owner-repo") -> version to return
297+
versions map[string][]string // maps cache key (e.g., "github-owner-repo") -> versions to return
298298
}
299299

300-
func (m *mockFetcher) Fetch(_ context.Context, config *source.Config) (string, error) {
301-
key := config.CacheKey()
302-
if version, ok := m.versions[key]; ok {
303-
return version, nil
300+
func (m *mockFetcher) FetchVersions(_ context.Context, src *source.Source) ([]string, error) {
301+
if versions, ok := m.versions[src.CacheKey()]; ok {
302+
return versions, nil
304303
}
305-
// Return a default version if not in map
306-
return "v1.0.0", nil
304+
return []string{"v1.0.0"}, nil
307305
}
308306

309307
// setupTestRepository creates a complete test repository structure with:
@@ -614,3 +612,45 @@ func newTestContainer(t *testing.T, root string) appext.Container {
614612
logger := slog.New(slog.NewTextHandler(testWriter{t}, &slog.HandlerOptions{Level: slog.LevelDebug}))
615613
return appext.NewContainer(nameContainer, logger, appext.LogLevelDebug, appext.LogFormatText)
616614
}
615+
616+
func TestFetchPendingCreationsMaxVersionSharedSource(t *testing.T) {
617+
t.Parallel()
618+
pluginsDir := filepath.Join(t.TempDir(), "plugins")
619+
writePlugin := func(name string, sourceYAML string) {
620+
pluginDir := filepath.Join(pluginsDir, "test", name)
621+
require.NoError(t, os.MkdirAll(filepath.Join(pluginDir, "v1.0.0"), 0755))
622+
require.NoError(t, os.WriteFile(filepath.Join(pluginDir, "source.yaml"), []byte(sourceYAML), 0644))
623+
}
624+
writePlugin("unbounded", `source:
625+
github:
626+
owner: test
627+
repository: shared
628+
`)
629+
writePlugin("bounded", `source:
630+
github:
631+
owner: test
632+
repository: shared
633+
max_version: 2.0.0
634+
`)
635+
configs, err := source.GatherConfigs(pluginsDir)
636+
require.NoError(t, err)
637+
fetcher := &mockFetcher{
638+
versions: map[string][]string{
639+
"github-test-shared": {"v1.0.0", "v1.1.0", "v2.0.0"},
640+
},
641+
}
642+
logger := slog.New(slog.DiscardHandler)
643+
pending, err := fetchPendingCreations(t.Context(), logger, fetcher, configs, nil, nil)
644+
require.NoError(t, err)
645+
require.Len(t, pending, 2)
646+
for _, config := range configs {
647+
pluginDir, err := filepath.Abs(filepath.Dir(config.Filename))
648+
require.NoError(t, err)
649+
require.Contains(t, pending, pluginDir)
650+
want := "v2.0.0"
651+
if config.Source.MaxVersion != "" {
652+
want = "v1.1.0"
653+
}
654+
assert.Equal(t, want, pending[pluginDir].newVersion, pluginDir)
655+
}
656+
}

0 commit comments

Comments
 (0)