Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 13 additions & 4 deletions internal/projectconfig/component_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -942,7 +942,7 @@ func TestValidateComponentGroupMembership(t *testing.T) {
Components: []string{"missing-component"},
}

err := cfg.Validate()
err := cfg.Validate(false)
require.Error(t, err)
require.ErrorIs(t, err, projectconfig.ErrUndefinedComponent)
assert.Contains(t, err.Error(), "my-group")
Expand All @@ -956,7 +956,7 @@ func TestValidateComponentGroupMembership(t *testing.T) {
Components: []string{"real-component"},
}

assert.NoError(t, cfg.Validate())
assert.NoError(t, cfg.Validate(false))
})

t.Run("group with only spec patterns is valid", func(t *testing.T) {
Expand All @@ -965,7 +965,7 @@ func TestValidateComponentGroupMembership(t *testing.T) {
SpecPathPatterns: []string{"SPECS/**/*.spec"},
}

assert.NoError(t, cfg.Validate())
assert.NoError(t, cfg.Validate(false))
})

t.Run("reports all undefined references together", func(t *testing.T) {
Expand All @@ -978,11 +978,20 @@ func TestValidateComponentGroupMembership(t *testing.T) {
Components: []string{"missing-3"},
}

err := cfg.Validate()
err := cfg.Validate(false)
require.Error(t, err)
require.ErrorIs(t, err, projectconfig.ErrUndefinedComponent)
assert.Contains(t, err.Error(), "missing-1")
assert.Contains(t, err.Error(), "missing-2")
assert.Contains(t, err.Error(), "missing-3")
})

t.Run("permissive parsing ignores undefined component reference", func(t *testing.T) {
cfg := projectconfig.NewProjectConfig()
cfg.ComponentGroups["my-group"] = projectconfig.ComponentGroupConfig{
Components: []string{"missing-component"},
}

assert.NoError(t, cfg.Validate(true))
})
}
2 changes: 1 addition & 1 deletion internal/projectconfig/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func loadAndResolveProjectConfig(
}

// Validate the resulting configuration.
err := resolvedCfg.Validate()
err := resolvedCfg.Validate(permissiveConfigParsing)
if err != nil {
return nil, err
}
Expand Down
21 changes: 21 additions & 0 deletions internal/projectconfig/package_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,27 @@ func TestPackageGroupConfig_Validate(t *testing.T) {
})
}

func TestValidatePackageGroupMembership(t *testing.T) {
t.Run("same package in two groups is rejected", func(t *testing.T) {
cfg := projectconfig.NewProjectConfig()
cfg.PackageGroups["group-a"] = projectconfig.PackageGroupConfig{Packages: []string{"curl"}}
cfg.PackageGroups["group-b"] = projectconfig.PackageGroupConfig{Packages: []string{"curl"}}

err := cfg.Validate(false)
require.Error(t, err)
assert.Contains(t, err.Error(), "curl")
assert.Contains(t, err.Error(), "may only belong to one group")
})

t.Run("permissive parsing ignores package in two groups", func(t *testing.T) {
cfg := projectconfig.NewProjectConfig()
cfg.PackageGroups["group-a"] = projectconfig.PackageGroupConfig{Packages: []string{"curl"}}
cfg.PackageGroups["group-b"] = projectconfig.PackageGroupConfig{Packages: []string{"curl"}}

assert.NoError(t, cfg.Validate(true))
})
}

func TestPackageConfig_MergeUpdatesFrom(t *testing.T) {
t.Run("non-zero other overrides zero base", func(t *testing.T) {
base := projectconfig.PackageConfig{}
Expand Down
31 changes: 26 additions & 5 deletions internal/projectconfig/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package projectconfig
import (
"errors"
"fmt"
"log/slog"
"sort"

"dario.cat/mergo"
Expand Down Expand Up @@ -68,23 +69,43 @@ func NewProjectConfig() ProjectConfig {
}
}

// Validates the configuration, returning an error if any semantic errors are found.
func (cfg *ProjectConfig) Validate() error {
// Validate performs semantic validation of the configuration, returning an error if any
// semantic errors are found. When permissive is true, cross-reference consistency checks
// (component-group membership, package-group membership, and image/test-suite references)
// are downgraded to informational logs rather than hard errors.
//
// Permissive validation exists for best-effort loads such as historical overlay replay,
// where a partial or point-in-time config may legitimately reference entities that are
// defined in a different revision. Structural validation (required fields, value formats)
// is always enforced.
func (cfg *ProjectConfig) Validate(permissive bool) error {
err := validator.New().Struct(cfg)
if err != nil {
return fmt.Errorf("config error:\n%w", err)
}

if err := validateComponentGroupMembership(cfg.ComponentGroups, cfg.Components); err != nil {
return err
if !permissive {
return err
}

slog.Info("Ignoring component group membership error (permissive parsing)", "err", err)
}

if err := validatePackageGroupMembership(cfg.PackageGroups); err != nil {
return err
if !permissive {
return err
}

slog.Info("Ignoring package group membership error (permissive parsing)", "err", err)
}

if err := validateImageTestReferences(cfg.Images, cfg.TestSuites); err != nil {
return err
if !permissive {
return err
}

slog.Info("Ignoring image test suite reference error (permissive parsing)", "err", err)
}

if err := validateRpmRepos(cfg.Resources.RpmRepos); err != nil {
Expand Down
24 changes: 21 additions & 3 deletions internal/projectconfig/testsuite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ func TestValidateTestSuiteReferences(t *testing.T) {
GroupsByComponent: make(map[string][]string),
PackageGroups: make(map[string]projectconfig.PackageGroupConfig),
}
assert.NoError(t, cfg.Validate())
assert.NoError(t, cfg.Validate(false))
})

t.Run("undefined test reference", func(t *testing.T) {
Expand All @@ -330,7 +330,7 @@ func TestValidateTestSuiteReferences(t *testing.T) {
GroupsByComponent: make(map[string][]string),
PackageGroups: make(map[string]projectconfig.PackageGroupConfig),
}
err := cfg.Validate()
err := cfg.Validate(false)
require.Error(t, err)
require.ErrorIs(t, err, projectconfig.ErrUndefinedTestSuite)
assert.Contains(t, err.Error(), "nonexistent")
Expand All @@ -348,6 +348,24 @@ func TestValidateTestSuiteReferences(t *testing.T) {
GroupsByComponent: make(map[string][]string),
PackageGroups: make(map[string]projectconfig.PackageGroupConfig),
}
assert.NoError(t, cfg.Validate())
assert.NoError(t, cfg.Validate(false))
})

t.Run("permissive parsing ignores undefined test reference", func(t *testing.T) {
cfg := projectconfig.ProjectConfig{
Images: map[string]projectconfig.ImageConfig{
"myimage": {
Name: "myimage",
Tests: projectconfig.ImageTestsConfig{TestSuites: []projectconfig.TestSuiteRef{{Name: "nonexistent"}}},
},
},
TestSuites: make(map[string]projectconfig.TestSuiteConfig),
Components: make(map[string]projectconfig.ComponentConfig),
ComponentGroups: make(map[string]projectconfig.ComponentGroupConfig),
Distros: make(map[string]projectconfig.DistroDefinition),
GroupsByComponent: make(map[string][]string),
PackageGroups: make(map[string]projectconfig.PackageGroupConfig),
}
assert.NoError(t, cfg.Validate(true))
})
}
Loading