Skip to content

Commit 22a186d

Browse files
committed
Keep subpackage metadata static
Reject build argument references in supplemental package names and descriptions while preserving substitution for dependency constraints. Signed-off-by: Brian Goff <cpuguy83@gmail.com>
1 parent 8960896 commit 22a186d

3 files changed

Lines changed: 136 additions & 17 deletions

File tree

docs/spec.schema.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2230,14 +2230,14 @@
22302230
"type": [
22312231
"string"
22322232
],
2233-
"description": "Description is the package description. This is required — both RPM and\nDebian require a description/summary for every subpackage."
2233+
"description": "Description is the package description. This is required — both RPM and\nDebian require a description/summary for every subpackage.\nBuild arguments are not supported in this field."
22342234
},
22352235
"name": {
22362236
"type": [
22372237
"string",
22382238
"null"
22392239
],
2240-
"description": "Name overrides the default package name.\nBy default, the package name is \"\u003cparent\u003e-\u003ckey\u003e\" where \u003ckey\u003e is the map\nkey under which this SubPackage is defined. Set this to use a fully custom\npackage name instead."
2240+
"description": "Name overrides the default package name.\nBy default, the package name is \"\u003cparent\u003e-\u003ckey\u003e\" where \u003ckey\u003e is the map\nkey under which this SubPackage is defined. Set this to use a fully custom\npackage name instead. Build arguments are not supported in this field."
22412241
},
22422242
"provides": {
22432243
"$ref": "#/$defs/PackageDependencyList",

subpackage.go

Lines changed: 47 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package dalec
33
import (
44
goerrors "errors"
55
"fmt"
6+
"slices"
7+
"unicode"
68

79
"github.com/moby/buildkit/frontend/dockerfile/shell"
810
"github.com/pkg/errors"
@@ -77,11 +79,12 @@ type SubPackage struct {
7779
// Name overrides the default package name.
7880
// By default, the package name is "<parent>-<key>" where <key> is the map
7981
// key under which this SubPackage is defined. Set this to use a fully custom
80-
// package name instead.
82+
// package name instead. Build arguments are not supported in this field.
8183
Name string `yaml:"name,omitempty" json:"name,omitempty"`
8284

8385
// Description is the package description. This is required — both RPM and
8486
// Debian require a description/summary for every subpackage.
87+
// Build arguments are not supported in this field.
8588
Description string `yaml:"description" json:"description" jsonschema:"required"`
8689

8790
// Artifacts specifies which build outputs go into this supplemental package.
@@ -120,6 +123,14 @@ func (s *SubPackage) validate() error {
120123
errs = append(errs, fmt.Errorf("description is required"))
121124
}
122125

126+
if err := validateNoBuildArgReferences(s.Name); err != nil {
127+
errs = append(errs, errors.Wrap(err, "name"))
128+
}
129+
130+
if err := validateNoBuildArgReferences(s.Description); err != nil {
131+
errs = append(errs, errors.Wrap(err, "description"))
132+
}
133+
123134
if s.Artifacts != nil {
124135
if err := s.Artifacts.validate(); err != nil {
125136
errs = append(errs, errors.Wrap(err, "artifacts"))
@@ -132,22 +143,12 @@ func (s *SubPackage) validate() error {
132143
func (s *SubPackage) processBuildArgs(lex *shell.Lex, args map[string]string, allowArg func(string) bool) error {
133144
var errs []error
134145

135-
if s.Name != "" {
136-
updated, err := expandArgs(lex, s.Name, args, allowArg)
137-
if err != nil {
138-
errs = append(errs, errors.Wrap(err, "name"))
139-
} else {
140-
s.Name = updated
141-
}
146+
if err := validateNoBuildArgReferences(s.Name); err != nil {
147+
errs = append(errs, errors.Wrap(err, "name"))
142148
}
143149

144-
if s.Description != "" {
145-
updated, err := expandArgs(lex, s.Description, args, allowArg)
146-
if err != nil {
147-
errs = append(errs, errors.Wrap(err, "description"))
148-
} else {
149-
s.Description = updated
150-
}
150+
if err := validateNoBuildArgReferences(s.Description); err != nil {
151+
errs = append(errs, errors.Wrap(err, "description"))
151152
}
152153

153154
if err := s.Dependencies.processBuildArgs(lex, args, allowArg); err != nil {
@@ -190,6 +191,37 @@ func (s *SubPackage) processBuildArgs(lex *shell.Lex, args map[string]string, al
190191
return goerrors.Join(errs...)
191192
}
192193

194+
func validateNoBuildArgReferences(value string) error {
195+
lex := shell.NewLex('\\')
196+
lex.SkipProcessQuotes = true
197+
198+
result, _ := lex.ProcessWordWithMatches(value, envGetterMap(nil))
199+
refs := make([]string, 0, len(result.Unmatched))
200+
for ref := range result.Unmatched {
201+
if isBuildArgName(ref) {
202+
refs = append(refs, ref)
203+
}
204+
}
205+
if len(refs) == 0 {
206+
return nil
207+
}
208+
209+
slices.Sort(refs)
210+
return fmt.Errorf("build arguments are not supported (found %q)", refs)
211+
}
212+
213+
func isBuildArgName(value string) bool {
214+
for i, r := range value {
215+
if i == 0 && r != '_' && !unicode.IsLetter(r) {
216+
return false
217+
}
218+
if i > 0 && r != '_' && !unicode.IsLetter(r) && !unicode.IsDigit(r) {
219+
return false
220+
}
221+
}
222+
return value != ""
223+
}
224+
193225
func (s *SubPackage) fillDefaults() {
194226
}
195227

subpackage_test.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,93 @@ func TestSubPackageValidation(t *testing.T) {
115115
}
116116
}
117117

118+
func TestSubPackageMetadataIsStatic(t *testing.T) {
119+
t.Parallel()
120+
121+
testCases := []struct {
122+
name string
123+
pkg SubPackage
124+
errSubstr string
125+
}{
126+
{
127+
name: "A name containing a braced build argument is rejected",
128+
pkg: SubPackage{
129+
Name: "tools-${PACKAGE_SUFFIX}",
130+
Description: "Tools package",
131+
},
132+
errSubstr: `name: build arguments are not supported (found ["PACKAGE_SUFFIX"])`,
133+
},
134+
{
135+
name: "A description containing an unbraced build argument is rejected",
136+
pkg: SubPackage{
137+
Name: "tools",
138+
Description: "Tools for $TARGETARCH",
139+
},
140+
errSubstr: `description: build arguments are not supported (found ["TARGETARCH"])`,
141+
},
142+
{
143+
name: "Literal dollar signs do not make static metadata invalid",
144+
pkg: SubPackage{
145+
Name: "tools-$",
146+
Description: "Tools costing $5",
147+
},
148+
},
149+
}
150+
151+
for _, tc := range testCases {
152+
t.Run(tc.name, func(t *testing.T) {
153+
t.Parallel()
154+
155+
err := tc.pkg.validate()
156+
157+
if tc.errSubstr == "" {
158+
assert.NilError(t, err)
159+
return
160+
}
161+
assert.ErrorContains(t, err, tc.errSubstr)
162+
})
163+
}
164+
}
165+
166+
func TestSubPackageBuildArgumentSubstitution(t *testing.T) {
167+
t.Parallel()
168+
169+
t.Run("A runtime dependency version containing a build argument is substituted", func(t *testing.T) {
170+
t.Parallel()
171+
172+
spec := Spec{
173+
Args: map[string]string{
174+
"PACKAGE_VERSION": "",
175+
},
176+
Targets: map[string]Target{
177+
"linux": {
178+
Packages: map[string]SubPackage{
179+
"tools": {
180+
Name: "tools",
181+
Description: "Tools package",
182+
Dependencies: &SubPackageDependencies{
183+
Runtime: PackageDependencyList{
184+
"runtime": {
185+
Version: []string{"=${PACKAGE_VERSION}"},
186+
},
187+
},
188+
},
189+
},
190+
},
191+
},
192+
},
193+
}
194+
195+
err := spec.SubstituteArgs(map[string]string{"PACKAGE_VERSION": "1.2.3"})
196+
197+
assert.NilError(t, err)
198+
pkg := spec.Targets["linux"].Packages["tools"]
199+
assert.Equal(t, pkg.Name, "tools")
200+
assert.Equal(t, pkg.Description, "Tools package")
201+
assert.DeepEqual(t, pkg.Dependencies.Runtime["runtime"].Version, []string{"=1.2.3"})
202+
})
203+
}
204+
118205
func TestValidateSubPackageNames(t *testing.T) {
119206
t.Parallel()
120207

0 commit comments

Comments
 (0)