Skip to content

Commit a2ba2aa

Browse files
committed
frontend: add build-time source filters
Add a source-options context that can provide a YAML source filter config at build time. The config currently supports global_excludes which can be used to filter out paths from all sources. Signed-off-by: Brian Goff <cpuguy83@gmail.com>
1 parent 5751801 commit a2ba2aa

18 files changed

Lines changed: 599 additions & 29 deletions

frontend/debug/handle_sources.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@ func Sources(ctx context.Context, client gwclient.Client) (*gwclient.Result, err
1818
return nil, nil, err
1919
}
2020

21-
sources := dalec.Sources(spec, sOpt)
22-
2321
pg := dalec.ProgressGroup("Sources for " + targetKey + " rpm build: " + spec.Name)
2422

23+
sources := dalec.Sources(spec, sOpt, pg)
24+
2525
def, err := dalec.MergeAtPath(llb.Scratch(), dalec.SortedMapValues(sources), "/", pg).Marshal(ctx)
2626
if err != nil {
2727
return nil, nil, err

frontend/gateway.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ func GetBuildArg(client gwclient.Client, k string) (string, bool) {
103103
}
104104

105105
func SourceOptFromUIClient(ctx context.Context, c gwclient.Client, dc *dockerui.Client, platform *ocispecs.Platform) dalec.SourceOpts {
106-
return dalec.SourceOpts{
106+
sOpt := dalec.SourceOpts{
107107
TargetPlatform: platform,
108108
Resolver: c,
109109
Forward: ForwarderFromClient(ctx, c),
@@ -129,6 +129,12 @@ func SourceOptFromUIClient(ctx context.Context, c gwclient.Client, dc *dockerui.
129129
},
130130
GitCredHelperOpt: withCredHelper(c),
131131
}
132+
133+
sOpt.SourceFilter = sync.OnceValues(func() (dalec.SourceFilterConfig, error) {
134+
return loadSourceFilterConfig(ctx, c, sOpt.GetContext)
135+
})
136+
137+
return sOpt
132138
}
133139

134140
func SourceOptFromClient(ctx context.Context, c gwclient.Client, platform *ocispecs.Platform) (dalec.SourceOpts, error) {

frontend/request.go

Lines changed: 76 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -142,39 +142,76 @@ func marshalDockerfile(ctx context.Context, dt []byte, opts ...llb.ConstraintsOp
142142
}
143143

144144
func getSigningConfigFromContext(ctx context.Context, client gwclient.Client, cfgPath string, configCtxName string, sOpt dalec.SourceOpts, opts ...llb.ConstraintsOpt) (*dalec.PackageSigner, error) {
145-
src := dalec.Source{Path: cfgPath, Context: &dalec.SourceContext{Name: configCtxName}}
146-
signConfigState := src.ToState("", sOpt, opts...)
147-
148-
scDef, err := signConfigState.Marshal(ctx)
145+
dt, err := readConfigFromContext(ctx, client, cfgPath, configCtxName, sOpt, opts...)
149146
if err != nil {
150147
return nil, err
151148
}
152149

150+
var pc dalec.PackageConfig
151+
if err := yaml.Unmarshal(dt, &pc); err != nil {
152+
return nil, err
153+
}
154+
155+
return pc.Signer, nil
156+
}
157+
158+
func getSourceFilterConfigFromContext(ctx context.Context, client gwclient.Client, cfgPath string, configState llb.State) (dalec.SourceFilterConfig, error) {
159+
scDef, err := configState.Marshal(ctx)
160+
if err != nil {
161+
return dalec.SourceFilterConfig{}, err
162+
}
163+
153164
res, err := client.Solve(ctx, gwclient.SolveRequest{
154165
Definition: scDef.ToPB(),
155166
})
156167
if err != nil {
157-
return nil, err
168+
return dalec.SourceFilterConfig{}, err
158169
}
159170

160171
ref, err := res.SingleRef()
161172
if err != nil {
162-
return nil, err
173+
return dalec.SourceFilterConfig{}, err
163174
}
164175

165176
dt, err := ref.ReadFile(ctx, gwclient.ReadRequest{
166177
Filename: cfgPath,
167178
})
179+
if err != nil {
180+
return dalec.SourceFilterConfig{}, err
181+
}
182+
183+
var cfg dalec.SourceFilterConfig
184+
if err := yaml.Unmarshal(dt, &cfg); err != nil {
185+
return dalec.SourceFilterConfig{}, err
186+
}
187+
188+
return cfg, nil
189+
}
190+
191+
func readConfigFromContext(ctx context.Context, client gwclient.Client, cfgPath string, configCtxName string, sOpt dalec.SourceOpts, opts ...llb.ConstraintsOpt) ([]byte, error) {
192+
src := dalec.Source{Path: cfgPath, Context: &dalec.SourceContext{Name: configCtxName}}
193+
configState := src.ToState("", sOpt.WithoutSourceFilter(), opts...)
194+
195+
scDef, err := configState.Marshal(ctx)
168196
if err != nil {
169197
return nil, err
170198
}
171199

172-
var pc dalec.PackageConfig
173-
if err := yaml.Unmarshal(dt, &pc); err != nil {
200+
res, err := client.Solve(ctx, gwclient.SolveRequest{
201+
Definition: scDef.ToPB(),
202+
})
203+
if err != nil {
174204
return nil, err
175205
}
176206

177-
return pc.Signer, nil
207+
ref, err := res.SingleRef()
208+
if err != nil {
209+
return nil, err
210+
}
211+
212+
return ref.ReadFile(ctx, gwclient.ReadRequest{
213+
Filename: cfgPath,
214+
})
178215
}
179216

180217
func MaybeSign(ctx context.Context, client gwclient.Client, st llb.State, spec *dalec.Spec, targetKey string, sOpt dalec.SourceOpts, opts ...llb.ConstraintsOpt) llb.State {
@@ -242,6 +279,36 @@ func getSignConfigCtxName(client gwclient.Client) string {
242279
return client.BuildOpts().Opts["build-arg:"+buildArgDalecSigningConfigContextName]
243280
}
244281

282+
func getSourceFilterConfigPath(client gwclient.Client) string {
283+
return client.BuildOpts().Opts["build-arg:"+dalec.BuildArgDalecSourceFilterConfigPath]
284+
}
285+
286+
func getSourceFilterContextNameWithDefault(client gwclient.Client) string {
287+
configCtxName := dalec.DefaultSourceOptionsContextName
288+
if cn := client.BuildOpts().Opts["build-arg:"+dalec.BuildArgDalecSourceFilterContextName]; cn != "" {
289+
configCtxName = cn
290+
}
291+
return configCtxName
292+
}
293+
294+
func loadSourceFilterConfig(ctx context.Context, client gwclient.Client, getContext func(string, ...llb.LocalOption) (*llb.State, error), opts ...llb.LocalOption) (dalec.SourceFilterConfig, error) {
295+
cfgPath := getSourceFilterConfigPath(client)
296+
if cfgPath == "" {
297+
return dalec.SourceFilterConfig{}, nil
298+
}
299+
300+
configCtxName := getSourceFilterContextNameWithDefault(client)
301+
configState, err := getContext(configCtxName, opts...)
302+
if err != nil {
303+
return dalec.SourceFilterConfig{}, err
304+
}
305+
if configState == nil {
306+
return dalec.SourceFilterConfig{}, errors.Errorf("context %q not found", configCtxName)
307+
}
308+
309+
return getSourceFilterConfigFromContext(ctx, client, cfgPath, *configState)
310+
}
311+
245312
func forwardToSigner(ctx context.Context, client gwclient.Client, cfg *dalec.PackageSigner, s llb.State, opts ...llb.ConstraintsOpt) (llb.State, error) {
246313
const (
247314
// See https://github.com/moby/buildkit/blob/d8d946b85c52095d34a52ce210960832f4e06775/frontend/dockerui/context.go#L29

generator_cargohome.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ func (s *Spec) CargohomeDeps(sOpt SourceOpts, worker llb.State, opts ...llb.Cons
101101
})
102102
}
103103

104+
deps = deps.With(sourceFilter(sOpt, opts...))
104105
return &deps
105106
}
106107

generator_gomod.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,7 @@ func (s *Spec) GomodDeps(sOpt SourceOpts, worker llb.State, opts ...llb.Constrai
245245
})
246246
}
247247

248+
deps = deps.With(sourceFilter(sOpt, opts...))
248249
return &deps
249250
}
250251

generator_pip.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ func (s *Spec) PipDeps(sOpt SourceOpts, worker llb.State, opts ...llb.Constraint
133133

134134
// Merge all cache states into a single state
135135
merged := MergeAtPath(llb.Scratch(), cacheStates, "/", opts...)
136+
merged = merged.With(sourceFilter(sOpt, opts...))
136137
return &merged
137138
}
138139

load.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ func knownArg(key string) bool {
3939
return true
4040
case "DALEC_SKIP_TESTS":
4141
return true
42+
case "DALEC_SOURCE_FILTER_CONFIG_PATH":
43+
return true
44+
case "DALEC_SOURCE_FILTER_CONFIG_CONTEXT_NAME":
45+
return true
4246
case KeyDalecTarget:
4347
return true
4448
}

packaging/linux/rpm/buildroot.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ import (
1111
)
1212

1313
func RPMSpec(spec *dalec.Spec, in llb.State, targetKey, dir string, opts ...llb.ConstraintsOpt) llb.State {
14+
return RPMSpecWithSourceFilter(spec, in, targetKey, dir, dalec.SourceFilterConfig{}, opts...)
15+
}
16+
17+
func RPMSpecWithSourceFilter(spec *dalec.Spec, in llb.State, targetKey, dir string, filter dalec.SourceFilterConfig, opts ...llb.ConstraintsOpt) llb.State {
1418
if err := ValidateSpec(spec); err != nil {
1519
return dalec.ErrorState(llb.Scratch(), fmt.Errorf("invalid spec: %w", err))
1620
}
@@ -20,7 +24,7 @@ func RPMSpec(spec *dalec.Spec, in llb.State, targetKey, dir string, opts ...llb.
2024
buf.WriteString("# Automatically generated by " + info.Main.Path + "\n")
2125
buf.WriteString("\n")
2226

23-
if err := WriteSpec(spec, targetKey, buf); err != nil {
27+
if err := WriteSpecWithSourceFilter(spec, targetKey, filter, buf); err != nil {
2428
return dalec.ErrorState(llb.Scratch(), err)
2529
}
2630

@@ -36,5 +40,9 @@ func RPMSpec(spec *dalec.Spec, in llb.State, targetKey, dir string, opts ...llb.
3640
func BuildRoot(worker llb.State, spec *dalec.Spec, sOpt dalec.SourceOpts, targetKey string, opts ...llb.ConstraintsOpt) llb.State {
3741
opts = append(opts, dalec.ProgressGroup("Create RPM buildroot"))
3842
sources := Sources(worker, spec, sOpt, opts...)
39-
return RPMSpec(spec, dalec.MergeAtPath(llb.Scratch(), sources, "SOURCES", opts...), targetKey, "", opts...)
43+
filter, err := sOpt.GetSourceFilter()
44+
if err != nil {
45+
return dalec.ErrorState(llb.Scratch(), err)
46+
}
47+
return RPMSpecWithSourceFilter(spec, dalec.MergeAtPath(llb.Scratch(), sources, "SOURCES", opts...), targetKey, "", filter, opts...)
4048
}

packaging/linux/rpm/template.go

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@ var tmplFuncs = map[string]any{
6767

6868
type specWrapper struct {
6969
*dalec.Spec
70-
Target string
70+
Target string
71+
SourceFilter dalec.SourceFilterConfig
7172
}
7273

7374
func (w *specWrapper) Changelog() (fmt.Stringer, error) {
@@ -337,21 +338,38 @@ func (w *specWrapper) Sources() (fmt.Stringer, error) {
337338
if scanner.Err() != nil {
338339
return nil, scanner.Err()
339340
}
341+
if !w.SourceFilter.IsEmpty() && isDir {
342+
docSourceFilter(b, "Exclusions", w.SourceFilter.GlobalExcludes)
343+
}
340344
fmt.Fprintf(b, "Source%d: %s\n", idx, ref)
341345
}
342346

343347
sourceIdx := len(keys)
344348

345349
if w.Spec.HasGomods() {
350+
if !w.SourceFilter.IsEmpty() {
351+
docSourceFilter(b, "Exclusions", w.SourceFilter.GlobalExcludes)
352+
}
346353
fmt.Fprintf(b, "Source%d: %s.tar.gz\n", sourceIdx, gomodsName)
347354
sourceIdx += 1
348355
}
349356

350357
if w.Spec.HasCargohomes() {
358+
if !w.SourceFilter.IsEmpty() {
359+
docSourceFilter(b, "Exclusions", w.SourceFilter.GlobalExcludes)
360+
}
351361
fmt.Fprintf(b, "Source%d: %s.tar.gz\n", sourceIdx, cargohomeName)
352362
sourceIdx += 1
353363
}
354364

365+
if w.Spec.HasPips() {
366+
if !w.SourceFilter.IsEmpty() {
367+
docSourceFilter(b, "Exclusions", w.SourceFilter.GlobalExcludes)
368+
}
369+
fmt.Fprintf(b, "Source%d: %s.tar.gz\n", sourceIdx, pipDepsName)
370+
sourceIdx += 1
371+
}
372+
355373
if len(w.Spec.Build.Steps) > 0 {
356374
fmt.Fprintf(b, "Source%d: %s\n", sourceIdx, buildScriptName)
357375
}
@@ -362,6 +380,13 @@ func (w *specWrapper) Sources() (fmt.Stringer, error) {
362380
return b, nil
363381
}
364382

383+
func docSourceFilter(w io.Writer, name string, values []string) {
384+
fmt.Fprintf(w, "# %s:\n", name)
385+
for _, value := range values {
386+
fmt.Fprintf(w, "# \t%s\n", value)
387+
}
388+
}
389+
365390
func (w *specWrapper) Release() string {
366391
if w.Spec.Revision == "" {
367392
return "1"
@@ -1133,7 +1158,11 @@ func (w *specWrapper) DisableAutoReq() string {
11331158

11341159
// WriteSpec generates an rpm spec from the provided [dalec.Spec] and distro target and writes it to the passed in writer
11351160
func WriteSpec(spec *dalec.Spec, target string, w io.Writer) error {
1136-
s := &specWrapper{spec, target}
1161+
return WriteSpecWithSourceFilter(spec, target, dalec.SourceFilterConfig{}, w)
1162+
}
1163+
1164+
func WriteSpecWithSourceFilter(spec *dalec.Spec, target string, filter dalec.SourceFilterConfig, w io.Writer) error {
1165+
s := &specWrapper{Spec: spec, Target: target, SourceFilter: filter}
11371166

11381167
err := specTmpl.Execute(w, s)
11391168
if err != nil {

packaging/linux/rpm/template_test.go

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -161,8 +161,16 @@ func TestTemplateSources(t *testing.T) {
161161
t.Fatalf("unexpected error: %v", err)
162162
}
163163
s2 := out2.String()
164-
if s2 != s {
165-
t.Fatalf("expected no additional sources for pip, got: %q", s2)
164+
// trim last newline from the first output since that has shifted
165+
s3 := s[:len(s)-1]
166+
if !strings.HasPrefix(s2, s3) {
167+
t.Fatalf("expected output to start with %q, got %q", s, out2.String())
168+
}
169+
170+
s2 = strings.TrimPrefix(out2.String(), s3)
171+
expected := "Source1: " + pipDepsName + ".tar.gz\n\n"
172+
if s2 != expected {
173+
t.Fatalf("unexpected sources: expected %q, got: %q", expected, s2)
166174
}
167175
})
168176

@@ -253,11 +261,10 @@ func TestTemplateSources(t *testing.T) {
253261
s = s[len(expected):]
254262
}
255263

256-
// Now we should have entries for gomods and cargohome.
264+
// Now we should have entries for gomods, cargohome, and pip deps.
257265
// Note there are 2 gomod sources but they should be combined into one entry.
258-
// Pip no longer creates a separate cache source.
259266

260-
expected := "Source7: " + gomodsName + ".tar.gz\nSource8: " + cargohomeName + ".tar.gz\n\n"
267+
expected := "Source7: " + gomodsName + ".tar.gz\nSource8: " + cargohomeName + ".tar.gz\nSource9: " + pipDepsName + ".tar.gz\n\n"
261268
if s != expected {
262269
t.Fatalf("generators: unexpected sources: expected %q, got: %q", expected, s)
263270
}
@@ -266,6 +273,30 @@ func TestTemplateSources(t *testing.T) {
266273
t.Fatalf("unexpected trailing sources: %q", s)
267274
}
268275
})
276+
277+
t.Run("source filter docs", func(t *testing.T) {
278+
w := &specWrapper{
279+
Spec: &dalec.Spec{
280+
Sources: map[string]dalec.Source{
281+
"src1": {
282+
Includes: []string{"cmd/**"},
283+
Excludes: []string{"testdata/**"},
284+
Inline: &dalec.SourceInline{
285+
Dir: &dalec.SourceInlineDir{},
286+
},
287+
},
288+
},
289+
},
290+
SourceFilter: dalec.SourceFilterConfig{GlobalExcludes: []string{"vendor/**"}},
291+
}
292+
293+
out, err := w.Sources()
294+
assert.NilError(t, err)
295+
s := out.String()
296+
assert.Check(t, strings.Contains(s, "# \tIncludes:\n# \t\t cmd/**\n"))
297+
assert.Check(t, strings.Contains(s, "# \tExcludes:\n# \t\t testdata/**\n"))
298+
assert.Check(t, strings.Contains(s, "# Exclusions:\n# \tvendor/**\n"))
299+
})
269300
}
270301

271302
func TestTemplate_Artifacts(t *testing.T) {

0 commit comments

Comments
 (0)