Skip to content

Commit 30894bf

Browse files
authored
Merge pull request #3431 from simonbaird/fix-cache-index-refetch
Fix pinned ref cache miss to avoid policy refetch
2 parents c52abe0 + 2d19ccd commit 30894bf

3 files changed

Lines changed: 71 additions & 9 deletions

File tree

internal/policy/source/source.go

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ type PolicyUrl struct {
7474
Url string
7575
Kind PolicyType
7676
pinOnce sync.Once
77+
urlMu sync.RWMutex
7778
}
7879

7980
// downloadCache is a concurrent map used to cache downloaded files.
@@ -171,7 +172,7 @@ func (p *PolicyUrl) GetPolicy(ctx context.Context, workDir string, showMsg bool)
171172
if trace.IsEnabled() {
172173
region := trace.StartRegion(ctx, "ec:get-policy")
173174
defer region.End()
174-
trace.Logf(ctx, "", "policy=%q", p.Url)
175+
trace.Logf(ctx, "", "policy=%q", p.url())
175176
}
176177

177178
dl := func(source string, dest string) (metadata.Metadata, error) {
@@ -189,8 +190,19 @@ func (p *PolicyUrl) GetPolicy(ctx context.Context, workDir string, showMsg bool)
189190

190191
var pinErr error
191192
p.pinOnce.Do(func() {
193+
originalUrl := p.url()
194+
p.urlMu.Lock()
192195
p.Url, pinErr = metadata.GetPinnedURL(p.Url)
193-
log.Debug("Pinned URL: ", p.Url)
196+
p.urlMu.Unlock()
197+
log.Debug("Pinned URL: ", p.url())
198+
// Register the cached download under the pinned URL too, so
199+
// subsequent lookups (which use the now-mutated p.Url) still hit.
200+
pinnedUrl := p.url()
201+
if pinErr == nil && pinnedUrl != originalUrl {
202+
if cached, ok := downloadCache.Load(originalUrl); ok {
203+
downloadCache.LoadOrStore(pinnedUrl, cached)
204+
}
205+
}
194206
})
195207
if pinErr != nil {
196208
return "", pinErr
@@ -199,10 +211,16 @@ func (p *PolicyUrl) GetPolicy(ctx context.Context, workDir string, showMsg bool)
199211
return dest, nil
200212
}
201213

202-
func (p *PolicyUrl) PolicyUrl() string {
214+
func (p *PolicyUrl) url() string {
215+
p.urlMu.RLock()
216+
defer p.urlMu.RUnlock()
203217
return p.Url
204218
}
205219

220+
func (p *PolicyUrl) PolicyUrl() string {
221+
return p.url()
222+
}
223+
206224
func (p *PolicyUrl) Subdir() string {
207225
// Be lazy and assume the kind value is the same as the subdirectory we want
208226
return string(p.Kind)

internal/policy/source/source_test.go

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ import (
2626
"path"
2727
"path/filepath"
2828
"regexp"
29-
"sync"
3029
"testing"
3130

3231
ecc "github.com/conforma/crds/api/v1alpha1"
@@ -163,7 +162,7 @@ func TestInlineDataSource(t *testing.T) {
163162
t.Run(tt.name, func(t *testing.T) {
164163
// Clear download cache for each test
165164
t.Cleanup(func() {
166-
downloadCache = sync.Map{}
165+
ClearDownloadCache()
167166
})
168167

169168
s := InlineData(tt.inputData)
@@ -276,7 +275,7 @@ func TestInlineDataGetPolicy(t *testing.T) {
276275
t.Run(tt.name, func(t *testing.T) {
277276
// Clear download cache for each test
278277
t.Cleanup(func() {
279-
downloadCache = sync.Map{}
278+
ClearDownloadCache()
280279
})
281280

282281
s := InlineData(tt.inputData)
@@ -539,7 +538,7 @@ func (m mockPolicySource) Type() PolicyType {
539538
func TestGetPolicyThroughCache(t *testing.T) {
540539
test := func(t *testing.T, fs afero.Fs, expectedDownloads int) {
541540
t.Cleanup(func() {
542-
downloadCache = sync.Map{}
541+
ClearDownloadCache()
543542
})
544543

545544
ctx := utils.WithFS(context.Background(), fs)
@@ -604,7 +603,7 @@ func TestGetPolicyThroughCache(t *testing.T) {
604603
// causing Rego compile issue
605604
func TestDownloadCacheWorkdirMismatch(t *testing.T) {
606605
t.Cleanup(func() {
607-
downloadCache = sync.Map{}
606+
ClearDownloadCache()
608607
})
609608
tmp := t.TempDir()
610609

@@ -634,12 +633,55 @@ func TestDownloadCacheWorkdirMismatch(t *testing.T) {
634633
assert.Equal(t, destination1, destination2)
635634
}
636635

636+
// TestGetPolicyPinnedURLCacheConsistency verifies that URL pinning doesn't
637+
// cause duplicate policy directories. After the first GetPolicy call, the URL
638+
// is pinned (e.g. github.com/org/repo -> git::github.com/org/repo?ref=abc123),
639+
// which changes the cache key. Without the fix, the second call would miss the
640+
// cache and re-download into a new directory, causing OPA duplicate package errors.
641+
func TestGetPolicyPinnedURLCacheConsistency(t *testing.T) {
642+
t.Cleanup(ClearDownloadCache)
643+
644+
workDir := t.TempDir()
645+
policyDir := filepath.Join(workDir, "policy")
646+
require.NoError(t, os.MkdirAll(policyDir, 0o755))
647+
648+
originalUrl := "github.com/org/repo//policy"
649+
p := &PolicyUrl{Url: originalUrl, Kind: PolicyKind}
650+
651+
dl := &mockDownloader{}
652+
dl.On("Download", mock.Anything, mock.Anything, originalUrl, false).
653+
Run(func(args mock.Arguments) {
654+
dest := args.String(1)
655+
require.NoError(t, os.MkdirAll(dest, 0o755))
656+
}).
657+
Return(&gitMetadata.GitMetadata{LatestCommit: "abc123def456"}, nil)
658+
659+
ctx := usingDownloader(context.TODO(), dl)
660+
661+
// First call: downloads and pins URL
662+
dest1, err := p.GetPolicy(ctx, workDir, false)
663+
require.NoError(t, err)
664+
assert.NotEqual(t, originalUrl, p.Url, "URL should be pinned after first call")
665+
666+
// Second call: should hit cache despite pinned URL
667+
dest2, err := p.GetPolicy(ctx, workDir, false)
668+
require.NoError(t, err)
669+
assert.Equal(t, dest1, dest2, "second call should return same directory")
670+
671+
// Verify only one directory exists under policy/
672+
entries, err := os.ReadDir(policyDir)
673+
require.NoError(t, err)
674+
assert.Len(t, entries, 1, "only one policy directory should exist, not a duplicate from pinned URL")
675+
676+
dl.AssertNumberOfCalls(t, "Download", 1)
677+
}
678+
637679
// TestConcurrentPolicyCachingRaceCondition reproduces the "file exists" error
638680
// that occurs when multiple workers simultaneously try to create symlinks from
639681
// cached policy downloads to their individual work directories
640682
func TestConcurrentPolicyCachingRaceCondition(t *testing.T) {
641683
t.Cleanup(func() {
642-
downloadCache = sync.Map{}
684+
ClearDownloadCache()
643685
})
644686

645687
tmp := t.TempDir()

internal/server/server.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import (
3030
"github.com/conforma/cli/internal/evaluation_target/input"
3131
"github.com/conforma/cli/internal/evaluator"
3232
"github.com/conforma/cli/internal/policy"
33+
"github.com/conforma/cli/internal/version"
3334
)
3435

3536
type Config struct {
@@ -69,6 +70,7 @@ func (s *Server) Start(ctx context.Context) error {
6970
"address": s.cfg.Address,
7071
"port": s.cfg.Port,
7172
"sources": len(s.cfg.Policy.Spec().Sources),
73+
"version": version.Version,
7274
}).Info("Starting server")
7375

7476
log.Info("Loading policy sources...")

0 commit comments

Comments
 (0)