Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ignore failure parsing yaml file when looking for project name #578

Merged
merged 1 commit into from Feb 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
108 changes: 55 additions & 53 deletions loader/loader.go
Expand Up @@ -305,18 +305,17 @@ func LoadWithContext(ctx context.Context, configDetails types.ConfigDetails, opt
}
opts.ResourceLoaders = append(opts.ResourceLoaders, localResourceLoader{configDetails.WorkingDir})

projectName, err := projectName(configDetails, opts)
err := projectName(configDetails, opts)
if err != nil {
return nil, err
}
opts.projectName = projectName

// TODO(milas): this should probably ALWAYS set (overriding any existing)
if _, ok := configDetails.Environment[consts.ComposeProjectName]; !ok && projectName != "" {
if _, ok := configDetails.Environment[consts.ComposeProjectName]; !ok && opts.projectName != "" {
if configDetails.Environment == nil {
configDetails.Environment = map[string]string{}
}
configDetails.Environment[consts.ComposeProjectName] = projectName
configDetails.Environment[consts.ComposeProjectName] = opts.projectName
}

return load(ctx, configDetails, opts, nil)
Expand All @@ -329,7 +328,7 @@ func loadYamlModel(ctx context.Context, config types.ConfigDetails, opts *Option
)
for _, file := range config.ConfigFiles {
fctx := context.WithValue(ctx, consts.ComposeFileKey{}, file.Filename)
if len(file.Content) == 0 && file.Config == nil {
if file.Content == nil && file.Config == nil {
content, err := os.ReadFile(file.Filename)
if err != nil {
return nil, err
Expand Down Expand Up @@ -477,6 +476,10 @@ func load(ctx context.Context, configDetails types.ConfigDetails, opts *Options,
return nil, errors.New("empty compose file")
}

if opts.projectName == "" {
return nil, errors.New("project name must not be empty")
}

project := &types.Project{
Name: opts.projectName,
WorkingDir: configDetails.WorkingDir,
Expand Down Expand Up @@ -544,69 +547,68 @@ func InvalidProjectNameErr(v string) error {
//
// TODO(milas): restructure loading so that we don't need to re-parse the YAML
// here, as it's both wasteful and makes this code error-prone.
func projectName(details types.ConfigDetails, opts *Options) (string, error) {
projectName, projectNameImperativelySet := opts.GetProjectName()
func projectName(details types.ConfigDetails, opts *Options) error {
if opts.projectNameImperativelySet {
if NormalizeProjectName(opts.projectName) != opts.projectName {
return InvalidProjectNameErr(opts.projectName)
}
return nil
}

type named struct {
Name string `yaml:"name"`
}

// if user did NOT provide a name explicitly, then see if one is defined
// in any of the config files
if !projectNameImperativelySet {
var pjNameFromConfigFile string
for _, configFile := range details.ConfigFiles {
content := configFile.Content
if content == nil {
// This can be hit when Filename is set but Content is not. One
// example is when using ToConfigFiles().
d, err := os.ReadFile(configFile.Filename)
if err != nil {
return "", fmt.Errorf("failed to read file %q: %w", configFile.Filename, err)
}
content = d
var pjNameFromConfigFile string
for _, configFile := range details.ConfigFiles {
content := configFile.Content
if content == nil {
// This can be hit when Filename is set but Content is not. One
// example is when using ToConfigFiles().
d, err := os.ReadFile(configFile.Filename)
if err != nil {
return fmt.Errorf("failed to read file %q: %w", configFile.Filename, err)
}
content = d
configFile.Content = d
}
var n named
r := bytes.NewReader(content)
decoder := yaml.NewDecoder(r)
for {
err := decoder.Decode(&n)
if err != nil && errors.Is(err, io.EOF) {
break
}
yml, err := ParseYAML(content)
if err != nil {
// HACK: the way that loading is currently structured, this is
// a duplicative parse just for the `name`. if it fails, we
// give up but don't return the error, knowing that it'll get
// caught downstream for us
return "", nil
}
if val, ok := yml["name"]; ok && val != "" {
sVal, ok := val.(string)
if !ok {
// HACK: see above - this is a temporary parsed version
// that hasn't been schema-validated, but we don't want
// to be the ones to actually report that, so give up,
// knowing that it'll get caught downstream for us
return "", nil
}
pjNameFromConfigFile = sVal
break
}
}
if !opts.SkipInterpolation {
interpolated, err := interp.Interpolate(
map[string]interface{}{"name": pjNameFromConfigFile},
*opts.Interpolate,
)
if err != nil {
return "", err
if n.Name != "" {
pjNameFromConfigFile = n.Name
}
pjNameFromConfigFile = interpolated["name"].(string)
}
pjNameFromConfigFile = NormalizeProjectName(pjNameFromConfigFile)
if pjNameFromConfigFile != "" {
projectName = pjNameFromConfigFile
}
}

if projectName == "" {
return "", errors.New("project name must not be empty")
if !opts.SkipInterpolation {
interpolated, err := interp.Interpolate(
map[string]interface{}{"name": pjNameFromConfigFile},
*opts.Interpolate,
)
if err != nil {
return err
}
pjNameFromConfigFile = interpolated["name"].(string)
}

if NormalizeProjectName(projectName) != projectName {
return "", InvalidProjectNameErr(projectName)
pjNameFromConfigFile = NormalizeProjectName(pjNameFromConfigFile)
if pjNameFromConfigFile != "" {
opts.projectName = pjNameFromConfigFile
}

return projectName, nil
return nil
}

func NormalizeProjectName(s string) string {
Expand Down
24 changes: 24 additions & 0 deletions loader/loader_test.go
Expand Up @@ -3084,3 +3084,27 @@ services:
assert.Check(t, ok)
assert.Equal(t, magic.Foo, "bar")
}

func TestLoadWithEmptyFile(t *testing.T) {
yaml := `
name: test-with-empty-file
services:
test:
image: foo
`

p, err := LoadWithContext(context.Background(), types.ConfigDetails{
ConfigFiles: []types.ConfigFile{
{
Filename: "(inlined)",
Content: []byte(yaml),
},
{
Filename: "(override)",
Content: []byte(""),
},
},
})
assert.NilError(t, err)
assert.Equal(t, p.Name, "test-with-empty-file")
}