Skip to content

Commit 4d6b286

Browse files
authored
Merge pull request #374 from milas/fix-proj-name-validation
2 parents 0db574e + 057c586 commit 4d6b286

File tree

4 files changed

+56
-43
lines changed

4 files changed

+56
-43
lines changed

cli/options.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -103,11 +103,8 @@ func WithName(name string) ProjectOptionsFn {
103103
// however, on the options object, the name is optional: if unset,
104104
// a name will be inferred by the loader, so it's legal to set the
105105
// name to an empty string here
106-
if name != "" {
107-
normalized := loader.NormalizeProjectName(name)
108-
if err := loader.CheckOriginalProjectNameIsNormalized(name, normalized); err != nil {
109-
return err
110-
}
106+
if name != loader.NormalizeProjectName(name) {
107+
return loader.InvalidProjectNameErr(name)
111108
}
112109
o.Name = name
113110
return nil
@@ -439,7 +436,10 @@ func withNamePrecedenceLoad(absWorkingDir string, options *ProjectOptions) func(
439436
} else if nameFromEnv, ok := options.Environment[consts.ComposeProjectName]; ok && nameFromEnv != "" {
440437
opts.SetProjectName(nameFromEnv, true)
441438
} else {
442-
opts.SetProjectName(filepath.Base(absWorkingDir), false)
439+
opts.SetProjectName(
440+
loader.NormalizeProjectName(filepath.Base(absWorkingDir)),
441+
false,
442+
)
443443
}
444444
}
445445
}

cli/options_test.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,9 @@ func TestProjectName(t *testing.T) {
8282
assert.NilError(t, err)
8383
p, err := ProjectFromOptions(opts)
8484

85-
// On macOS and Linux, the message will start with "/". On Windows, it will
86-
// start with "\\\\". So we leave that part of the error off here.
87-
assert.ErrorContains(t, err, `is not a valid project name`)
85+
// root directory will resolve to an empty project name since there
86+
// IS no directory name!
87+
assert.ErrorContains(t, err, `project name must not be empty`)
8888
assert.Assert(t, p == nil)
8989
})
9090

@@ -156,6 +156,14 @@ func TestProjectName(t *testing.T) {
156156
assert.Equal(t, p.Name, "simple")
157157
})
158158

159+
t.Run("by compose file parent dir special", func(t *testing.T) {
160+
opts, err := NewProjectOptions([]string{"testdata/UNNORMALIZED PATH/compose.yaml"})
161+
assert.NilError(t, err)
162+
p, err := ProjectFromOptions(opts)
163+
assert.NilError(t, err)
164+
assert.Equal(t, p.Name, "unnormalizedpath")
165+
})
166+
159167
t.Run("by COMPOSE_PROJECT_NAME", func(t *testing.T) {
160168
os.Setenv("COMPOSE_PROJECT_NAME", "my_project_from_env") //nolint:errcheck
161169
defer os.Unsetenv("COMPOSE_PROJECT_NAME") //nolint:errcheck
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
services:
2+
nginx:
3+
image: nginx

loader/loader.go

Lines changed: 36 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -64,16 +64,13 @@ type Options struct {
6464
projectName string
6565
// Indicates when the projectName was imperatively set or guessed from path
6666
projectNameImperativelySet bool
67-
// The value of projectName before normalization
68-
projectNameBeforeNormalization string
6967
// Profiles set profiles to enable
7068
Profiles []string
7169
}
7270

7371
func (o *Options) SetProjectName(name string, imperativelySet bool) {
74-
o.projectName = NormalizeProjectName(name)
72+
o.projectName = name
7573
o.projectNameImperativelySet = imperativelySet
76-
o.projectNameBeforeNormalization = name
7774
}
7875

7976
func (o Options) GetProjectName() (string, bool) {
@@ -267,49 +264,54 @@ func Load(configDetails types.ConfigDetails, options ...func(*Options)) (*types.
267264
return project, err
268265
}
269266

270-
func CheckOriginalProjectNameIsNormalized(original, normalized string) error {
271-
if original != normalized {
272-
return fmt.Errorf("%q is not a valid project name: it must contain only "+
273-
"characters from [a-z0-9_-] and start with [a-z0-9]", original)
274-
} else if normalized == "" {
275-
return fmt.Errorf("project name must not be empty")
276-
}
277-
return nil
267+
func InvalidProjectNameErr(v string) error {
268+
return fmt.Errorf(
269+
"%q is not a valid project name: it must contain only "+
270+
"characters from [a-z0-9_-] and start with [a-z0-9]", v,
271+
)
278272
}
279273

280274
func projectName(details types.ConfigDetails, opts *Options) (string, error) {
281275
projectName, projectNameImperativelySet := opts.GetProjectName()
282-
projectNameBeforeNormalization := opts.projectNameBeforeNormalization
283276

284-
var pjNameFromConfigFile string
285-
286-
for _, configFile := range details.ConfigFiles {
287-
yml, err := ParseYAML(configFile.Content)
288-
if err != nil {
289-
return "", nil
277+
// if user did NOT provide a name explicitly, then see if one is defined
278+
// in any of the config files
279+
if !projectNameImperativelySet {
280+
var pjNameFromConfigFile string
281+
for _, configFile := range details.ConfigFiles {
282+
yml, err := ParseYAML(configFile.Content)
283+
if err != nil {
284+
return "", nil
285+
}
286+
if val, ok := yml["name"]; ok && val != "" {
287+
pjNameFromConfigFile = yml["name"].(string)
288+
}
290289
}
291-
if val, ok := yml["name"]; ok && val != "" {
292-
pjNameFromConfigFile = yml["name"].(string)
290+
if !opts.SkipInterpolation {
291+
interpolated, err := interp.Interpolate(
292+
map[string]interface{}{"name": pjNameFromConfigFile},
293+
*opts.Interpolate,
294+
)
295+
if err != nil {
296+
return "", err
297+
}
298+
pjNameFromConfigFile = interpolated["name"].(string)
293299
}
294-
}
295-
if !opts.SkipInterpolation {
296-
interpolated, err := interp.Interpolate(map[string]interface{}{"name": pjNameFromConfigFile}, *opts.Interpolate)
297-
if err != nil {
298-
return "", err
300+
pjNameFromConfigFile = NormalizeProjectName(pjNameFromConfigFile)
301+
if pjNameFromConfigFile != "" {
302+
projectName = pjNameFromConfigFile
299303
}
300-
pjNameFromConfigFile = interpolated["name"].(string)
301304
}
302-
pjNameFromConfigFileNormalized := NormalizeProjectName(pjNameFromConfigFile)
303-
if !projectNameImperativelySet && pjNameFromConfigFileNormalized != "" {
304-
projectName = pjNameFromConfigFileNormalized
305-
projectNameBeforeNormalization = pjNameFromConfigFile
305+
306+
if projectName == "" {
307+
return "", errors.New("project name must not be empty")
306308
}
307309

308-
if err := CheckOriginalProjectNameIsNormalized(
309-
projectNameBeforeNormalization, projectName); err != nil {
310-
return "", err
310+
if NormalizeProjectName(projectName) != projectName {
311+
return "", InvalidProjectNameErr(projectName)
311312
}
312313

314+
// TODO(milas): this should probably ALWAYS set (overriding any existing)
313315
if _, ok := details.Environment[consts.ComposeProjectName]; !ok && projectName != "" {
314316
details.Environment[consts.ComposeProjectName] = projectName
315317
}

0 commit comments

Comments
 (0)