Skip to content

Commit bada177

Browse files
authored
feat(render): add WithSkipLookaside preparer option (#61)
1 parent 8987b09 commit bada177

File tree

6 files changed

+227
-17
lines changed

6 files changed

+227
-17
lines changed

internal/app/azldev/core/sources/sourceprep.go

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,19 @@ func WithGitRepo() PreparerOption {
6868
}
6969
}
7070

71+
// WithSkipLookaside returns a [PreparerOption] that skips all lookaside cache
72+
// downloads during source preparation. This includes both explicit source file
73+
// downloads ([SourceManager.FetchFiles]) and lookaside extraction during
74+
// component fetching ([FedoraSourceDownloader.ExtractSourcesFromRepo]).
75+
// Git-tracked files (spec, patches, scripts, configs) are still fetched from
76+
// the upstream clone. This is useful for rendering, where only the spec and
77+
// sidecar files are needed and downloading large source tarballs is unnecessary.
78+
func WithSkipLookaside() PreparerOption {
79+
return func(p *sourcePreparerImpl) {
80+
p.skipLookaside = true
81+
}
82+
}
83+
7184
// Standard implementation of the [SourcePreparer] interface.
7285
type sourcePreparerImpl struct {
7386
sourceManager sourceproviders.SourceManager
@@ -78,6 +91,10 @@ type sourcePreparerImpl struct {
7891
// withGitRepo, when true, enables dist-git creation by preserving the
7992
// upstream .git directory and generating synthetic commit history.
8093
withGitRepo bool
94+
95+
// skipLookaside, when true, skips all lookaside cache downloads during
96+
// source preparation. Git-tracked files are still fetched.
97+
skipLookaside bool
8198
}
8299

83100
// NewPreparer creates a new [SourcePreparer] instance. All positional arguments
@@ -126,10 +143,14 @@ func (p *sourcePreparerImpl) PrepareSources(
126143
ctx context.Context, component components.Component, outputDir string, applyOverlays bool,
127144
) error {
128145
// Use the source manager to fetch source files (archives, patches, etc.)
129-
err := p.sourceManager.FetchFiles(ctx, component, outputDir)
130-
if err != nil {
131-
return fmt.Errorf("failed to fetch source files for component %#q:\n%w",
132-
component.GetName(), err)
146+
// Skip this step when skipLookaside is set — source tarballs are not needed
147+
// for rendering and are the most expensive download.
148+
if !p.skipLookaside {
149+
err := p.sourceManager.FetchFiles(ctx, component, outputDir)
150+
if err != nil {
151+
return fmt.Errorf("failed to fetch source files for component %#q:\n%w",
152+
component.GetName(), err)
153+
}
133154
}
134155

135156
// Preserve the upstream .git directory only when dist-git creation is
@@ -140,8 +161,12 @@ func (p *sourcePreparerImpl) PrepareSources(
140161
fetchOpts = append(fetchOpts, sourceproviders.WithPreserveGitDir())
141162
}
142163

164+
if p.skipLookaside {
165+
fetchOpts = append(fetchOpts, sourceproviders.WithSkipLookaside())
166+
}
167+
143168
// Use the source manager to fetch the component (spec file and sidecar files).
144-
err = p.sourceManager.FetchComponent(ctx, component, outputDir, fetchOpts...)
169+
err := p.sourceManager.FetchComponent(ctx, component, outputDir, fetchOpts...)
145170
if err != nil {
146171
return fmt.Errorf("failed to fetch sources for component %#q:\n%w",
147172
component.GetName(), err)

internal/app/azldev/core/sources/sourceprep_test.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,69 @@ func TestPrepareSources_SourceManagerError(t *testing.T) {
103103
require.ErrorIs(t, err, expectedErr)
104104
}
105105

106+
func TestPrepareSources_WithSkipLookaside_SkipsFetchFiles(t *testing.T) {
107+
const (
108+
testSpecName = "test-component.spec"
109+
outputSpecPath = testOutputDir + "/" + testSpecName
110+
)
111+
112+
ctrl := gomock.NewController(t)
113+
component := components_testutils.NewMockComponent(ctrl)
114+
sourceManager := sourceproviders_test.NewMockSourceManager(ctrl)
115+
ctx := testctx.NewCtx()
116+
117+
component.EXPECT().GetName().AnyTimes().Return("test-component")
118+
component.EXPECT().GetConfig().AnyTimes().Return(&projectconfig.ComponentConfig{})
119+
120+
// FetchFiles must NOT be called when WithSkipLookaside is set.
121+
// (No sourceManager.EXPECT().FetchFiles(...) — gomock will fail if it's called.)
122+
123+
// FetchComponent should still be called, with at least the SkipLookaside option.
124+
sourceManager.EXPECT().FetchComponent(gomock.Any(), component, testOutputDir, gomock.Any()).DoAndReturn(
125+
func(_ interface{}, _ interface{}, outputDir string, opts ...sourceproviders.FetchComponentOption) error {
126+
// At least one option (SkipLookaside) should be passed.
127+
assert.NotEmpty(t, opts, "FetchComponent should receive at least one option (SkipLookaside)")
128+
129+
return fileutils.WriteFile(ctx.FS(), outputSpecPath, []byte("# test spec"), 0o644)
130+
},
131+
)
132+
133+
preparer, err := sources.NewPreparer(sourceManager, ctx.FS(), ctx, ctx, sources.WithSkipLookaside())
134+
require.NoError(t, err)
135+
136+
err = preparer.PrepareSources(ctx, component, testOutputDir, true /*applyOverlays?*/)
137+
require.NoError(t, err)
138+
}
139+
140+
func TestPrepareSources_WithoutSkipLookaside_CallsFetchFiles(t *testing.T) {
141+
const (
142+
testSpecName = "test-component.spec"
143+
outputSpecPath = testOutputDir + "/" + testSpecName
144+
)
145+
146+
ctrl := gomock.NewController(t)
147+
component := components_testutils.NewMockComponent(ctrl)
148+
sourceManager := sourceproviders_test.NewMockSourceManager(ctrl)
149+
ctx := testctx.NewCtx()
150+
151+
component.EXPECT().GetName().AnyTimes().Return("test-component")
152+
component.EXPECT().GetConfig().AnyTimes().Return(&projectconfig.ComponentConfig{})
153+
154+
// Without WithSkipLookaside, FetchFiles MUST be called.
155+
sourceManager.EXPECT().FetchFiles(gomock.Any(), component, testOutputDir).Return(nil)
156+
sourceManager.EXPECT().FetchComponent(gomock.Any(), component, testOutputDir, gomock.Any()).DoAndReturn(
157+
func(_ interface{}, _ interface{}, outputDir string, _ ...sourceproviders.FetchComponentOption) error {
158+
return fileutils.WriteFile(ctx.FS(), outputSpecPath, []byte("# test spec"), 0o644)
159+
},
160+
)
161+
162+
preparer, err := sources.NewPreparer(sourceManager, ctx.FS(), ctx, ctx)
163+
require.NoError(t, err)
164+
165+
err = preparer.PrepareSources(ctx, component, testOutputDir, true /*applyOverlays?*/)
166+
require.NoError(t, err)
167+
}
168+
106169
func TestPrepareSources_WritesMacrosFile(t *testing.T) {
107170
ctrl := gomock.NewController(t)
108171
component := components_testutils.NewMockComponent(ctrl)

internal/providers/sourceproviders/fedorasourceprovider.go

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -174,11 +174,14 @@ func (g *FedoraSourcesProviderImpl) processClonedRepo(
174174

175175
// Extract sources from repo (downloads lookaside files into the temp dir).
176176
// Files in skipFilenames are not downloaded — they were already fetched by FetchFiles.
177-
err := g.downloader.ExtractSourcesFromRepo(
178-
ctx, tempDir, upstreamName, g.lookasideBaseURI, skipFilenames,
179-
)
180-
if err != nil {
181-
return fmt.Errorf("failed to extract sources from git repository:\n%w", err)
177+
// Skip this step entirely when SkipLookaside is set (e.g., during rendering).
178+
if !opts.SkipLookaside {
179+
err := g.downloader.ExtractSourcesFromRepo(
180+
ctx, tempDir, upstreamName, g.lookasideBaseURI, skipFilenames,
181+
)
182+
if err != nil {
183+
return fmt.Errorf("failed to extract sources from git repository:\n%w", err)
184+
}
182185
}
183186

184187
// If the upstream name differs from the component name, rename the spec in temp dir.

internal/providers/sourceproviders/fedorasourceprovider_test.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -601,6 +601,57 @@ func TestGetComponentFromGit(t *testing.T) {
601601
require.Error(t, err)
602602
assert.Contains(t, err.Error(), "failed to rename fetched spec file")
603603
})
604+
605+
t.Run("skip lookaside does not call ExtractSourcesFromRepo", func(t *testing.T) {
606+
ctrl := gomock.NewController(t)
607+
mockGitProvider := git_test.NewMockGitProvider(ctrl)
608+
mockExtractor := fedorasource_test.NewMockFedoraSourceDownloader(ctrl)
609+
610+
provider, err := sourceproviders.NewFedoraSourcesProviderImpl(
611+
env.FS(),
612+
env.DryRunnable,
613+
mockGitProvider,
614+
mockExtractor,
615+
testResolvedDistro(),
616+
retry.Disabled(),
617+
)
618+
require.NoError(t, err)
619+
620+
mockComponent := components_testutils.NewMockComponent(ctrl)
621+
mockComponent.EXPECT().GetName().AnyTimes().Return(testPackageName)
622+
mockComponent.EXPECT().GetConfig().AnyTimes().Return(&projectconfig.ComponentConfig{
623+
Name: testPackageName,
624+
})
625+
626+
// Git clone creates spec file.
627+
mockGitProvider.EXPECT().
628+
Clone(gomock.Any(), repoURL, gomock.Any(), gomock.Any()).
629+
DoAndReturn(func(ctx context.Context, repoURL, cloneDir string, opts ...git.GitOptions) error {
630+
specPath := cloneDir + "/" + testPackageName + ".spec"
631+
632+
return fileutils.WriteFile(env.FS(), specPath, []byte("Name: "+testPackageName), fileperms.PublicFile)
633+
})
634+
635+
mockGitProvider.EXPECT().
636+
GetCurrentCommit(gomock.Any(), gomock.Any()).
637+
Return("head123abc", nil)
638+
639+
mockGitProvider.EXPECT().
640+
Checkout(gomock.Any(), gomock.Any(), "head123abc").
641+
Return(nil)
642+
643+
// ExtractSourcesFromRepo must NOT be called — no EXPECT set for it.
644+
// gomock will fail if it's called unexpectedly.
645+
646+
err = provider.GetComponent(context.Background(), mockComponent, destDir, sourceproviders.WithSkipLookaside())
647+
require.NoError(t, err)
648+
649+
// Verify spec file was still copied to destination.
650+
specPath := destDir + "/" + testPackageName + ".spec"
651+
exists, checkErr := fileutils.Exists(env.FS(), specPath)
652+
require.NoError(t, checkErr)
653+
assert.True(t, exists, "spec file should exist in destination even with skip-lookaside")
654+
})
604655
}
605656

606657
// testResolvedDistroWithSnapshot returns a [sourceproviders.ResolvedDistro] with the given snapshot time.

internal/providers/sourceproviders/sourcemanager.go

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,11 @@ type FetchComponentOptions struct {
5555
// in the fetched component sources instead of deleting it. This is required for building
5656
// synthetic git history from overlay blame metadata.
5757
PreserveGitDir bool
58+
59+
// SkipLookaside, when true, skips all lookaside cache downloads during component
60+
// fetching. Git-tracked files (spec, patches, scripts) are still fetched from the
61+
// upstream clone. The sources manifest file remains available for validation.
62+
SkipLookaside bool
5863
}
5964

6065
// FetchComponentOption is a functional option for configuring component fetch behavior.
@@ -68,6 +73,14 @@ func WithPreserveGitDir() FetchComponentOption {
6873
}
6974
}
7075

76+
// WithSkipLookaside returns a [FetchComponentOption] that skips lookaside cache
77+
// downloads during component fetching. Git-tracked files are still fetched.
78+
func WithSkipLookaside() FetchComponentOption {
79+
return func(o *FetchComponentOptions) {
80+
o.SkipLookaside = true
81+
}
82+
}
83+
7184
// resolveFetchComponentOptions applies all functional options and returns the resolved options.
7285
func resolveFetchComponentOptions(opts []FetchComponentOption) FetchComponentOptions {
7386
var resolved FetchComponentOptions
@@ -449,9 +462,11 @@ func (m *sourceManager) FetchComponent(
449462

450463
sourceType := component.GetConfig().Spec.SourceType
451464

465+
resolved := resolveFetchComponentOptions(opts)
466+
452467
switch sourceType {
453468
case projectconfig.SpecSourceTypeLocal, projectconfig.SpecSourceTypeUnspecified:
454-
return m.fetchLocalComponent(ctx, component, destDirPath)
469+
return m.fetchLocalComponent(ctx, component, destDirPath, resolved)
455470

456471
case projectconfig.SpecSourceTypeUpstream:
457472
return m.fetchUpstreamComponent(ctx, component, destDirPath, opts...)
@@ -511,19 +526,22 @@ func (m *sourceManager) resolveUpstreamSourceIdentity(
511526
}
512527

513528
func (m *sourceManager) fetchLocalComponent(
514-
ctx context.Context, component components.Component, destDirPath string,
529+
ctx context.Context, component components.Component, destDirPath string, opts FetchComponentOptions,
515530
) error {
516531
err := FetchLocalComponent(m.dryRunnable, m.eventListener, m.fs, component, destDirPath, false)
517532
if err != nil {
518533
return fmt.Errorf("failed to fetch local component %#q:\n%w",
519534
component.GetName(), err)
520535
}
521536

522-
// Download source files from lookaside cache if available
523-
err = m.downloadLookasideSources(ctx, component, destDirPath)
524-
if err != nil {
525-
return fmt.Errorf("failed to download lookaside sources for component %#q:\n%w",
526-
component.GetName(), err)
537+
// Download source files from lookaside cache if available.
538+
// Skip this step when SkipLookaside is set (e.g., during rendering).
539+
if !opts.SkipLookaside {
540+
err = m.downloadLookasideSources(ctx, component, destDirPath)
541+
if err != nil {
542+
return fmt.Errorf("failed to download lookaside sources for component %#q:\n%w",
543+
component.GetName(), err)
544+
}
527545
}
528546

529547
return nil

internal/providers/sourceproviders/sourcemanager_test.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,56 @@ func TestSourceManager_ResolveSourceIdentity_UpstreamAllProvidersFail(t *testing
486486
require.Contains(t, err.Error(), "failed to resolve source identity")
487487
}
488488

489+
func TestSourceManager_FetchComponent_LocalComponent_WithSkipLookaside(t *testing.T) {
490+
env := testutils.NewTestEnv(t)
491+
ctrl := gomock.NewController(t)
492+
component := components_testutils.NewMockComponent(ctrl)
493+
spec := specs_testutils.NewMockComponentSpec(ctrl)
494+
495+
const (
496+
specDir = "/specs/test-component"
497+
specPath = specDir + "/test-component.spec"
498+
)
499+
500+
componentConfig := &projectconfig.ComponentConfig{
501+
Spec: projectconfig.SpecSource{
502+
SourceType: projectconfig.SpecSourceTypeLocal,
503+
Path: specPath,
504+
},
505+
}
506+
507+
component.EXPECT().GetName().AnyTimes().Return("test-component")
508+
component.EXPECT().GetConfig().AnyTimes().Return(componentConfig)
509+
component.EXPECT().GetSpec().AnyTimes().Return(spec)
510+
spec.EXPECT().GetPath().AnyTimes().Return(specPath, nil)
511+
512+
// Create spec file and a lookaside sources manifest in the source directory.
513+
// The sources file references a tarball that would require downloading from
514+
// the lookaside cache. If downloadLookasideSources runs despite SkipLookaside,
515+
// the download will fail (no real server at example.com) and the test will fail.
516+
require.NoError(t, fileutils.WriteFile(env.TestFS, specPath,
517+
[]byte("Name: test-component\nVersion: 1.0\n"), fileperms.PrivateFile))
518+
require.NoError(t, fileutils.WriteFile(env.TestFS, specDir+"/sources",
519+
[]byte("SHA512 (big-source.tar.gz) = abc123def456\n"), fileperms.PrivateFile))
520+
521+
sourceManager, err := sourceproviders.NewSourceManager(env.Env, testDefaultDistro())
522+
require.NoError(t, err)
523+
524+
// With SkipLookaside, downloadLookasideSources is not called and the fetch succeeds.
525+
err = sourceManager.FetchComponent(t.Context(), component, testDestDir, sourceproviders.WithSkipLookaside())
526+
require.NoError(t, err)
527+
528+
// Spec was copied to destination (FetchLocalComponent still ran).
529+
specExists, err := fileutils.Exists(env.TestFS, filepath.Join(testDestDir, "test-component.spec"))
530+
require.NoError(t, err)
531+
assert.True(t, specExists, "spec file should exist in destination")
532+
533+
// Source tarball was NOT downloaded (lookaside was skipped).
534+
tarballExists, err := fileutils.Exists(env.TestFS, filepath.Join(testDestDir, "big-source.tar.gz"))
535+
require.NoError(t, err)
536+
assert.False(t, tarballExists, "source tarball should not be present when SkipLookaside is set")
537+
}
538+
489539
func TestSourceManager_ResolveSourceIdentity_UnknownSourceType(t *testing.T) {
490540
env := testutils.NewTestEnv(t)
491541
ctrl := gomock.NewController(t)

0 commit comments

Comments
 (0)