Skip to content

Commit

Permalink
internal/config: walk the graph and merge configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
seilagamo committed Feb 12, 2025
1 parent 51e9814 commit cda2cf1
Show file tree
Hide file tree
Showing 7 changed files with 187 additions and 21 deletions.
9 changes: 1 addition & 8 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,14 +91,7 @@ func Parse(path string) (Config, error) {
if err != nil {
return Config{}, fmt.Errorf("build dag: %w", err)
}
if err = graph.Resolve(); err != nil {
return Config{}, fmt.Errorf("resolve dag: %w", err)
}

cfg, err := graph.Config(path)
if err != nil {
return Config{}, fmt.Errorf("unknown configuration: %w", err)
}
cfg := graph.Resolve()

if err = cfg.validate(); err != nil {
return Config{}, fmt.Errorf("validate config: %w", err)
Expand Down
33 changes: 25 additions & 8 deletions internal/config/configgraph.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,16 +76,33 @@ func discoverConfig(url, parent string, d *dag.DAG, configs map[string]Config) e
return nil
}

// Resolve walks the dag and merge the configuration.
func (d *ConfigGraph) Resolve() error {
// TODO: Walk the dag and merge configurations.
return nil
}

// Config returns a configuration for the given URL.
func (d *ConfigGraph) Config(url string) (Config, error) {
if cfg, ok := d.configs[url]; ok {
func (cg *ConfigGraph) Config(url string) (Config, error) {
if cfg, ok := cg.configs[url]; ok {
return cfg, nil
}
return Config{}, fmt.Errorf("could not find config for %s", url)
}

// Resolve walks the dag and merge the configuration.
func (cg *ConfigGraph) Resolve() Config {
var cfg *Config
cg.dag.DFSWalk(func(vertexID string, vertex interface{}) {
vexCfg, err := cg.Config(vertex.(string))
if err != nil {
panic(err)
}

if cfg == nil {
cfg = &vexCfg
return
}

vexCfg, err = merge(vexCfg, *cfg)
if err != nil {
panic(err)
}
cfg = &vexCfg
})
return *cfg
}
146 changes: 142 additions & 4 deletions internal/config/configgraph_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ func TestNewConfigGraph(t *testing.T) {
"testdata/include/no_includes.yaml": {
LavaVersion: ptr("v1.0.0"),
ChecktypeURLs: []string{
"checktypes.json",
"checktypes_no_includes.json",
},
Targets: []Target{
{
Expand Down Expand Up @@ -61,7 +61,7 @@ func TestNewConfigGraph(t *testing.T) {
"testdata/include/no_includes.yaml": {
LavaVersion: ptr("v1.0.0"),
ChecktypeURLs: []string{
"checktypes.json",
"checktypes_no_includes.json",
},
Targets: []Target{
{
Expand Down Expand Up @@ -103,7 +103,7 @@ func TestNewConfigGraph(t *testing.T) {
"testdata/include/no_includes.yaml": {
LavaVersion: ptr("v1.0.0"),
ChecktypeURLs: []string{
"checktypes.json",
"checktypes_no_includes.json",
},
Targets: []Target{
{
Expand Down Expand Up @@ -136,6 +136,9 @@ func TestNewConfigGraph(t *testing.T) {
AssetType: types.DomainName,
},
},
ReportConfig: ReportConfig{
Severity: ptr(SeverityCritical),
},
},
"testdata/include/common_a.yaml": {
Includes: []string{"testdata/include/no_includes.yaml"},
Expand All @@ -149,6 +152,9 @@ func TestNewConfigGraph(t *testing.T) {
AssetType: types.DomainName,
},
},
ReportConfig: ReportConfig{
Severity: ptr(SeverityMedium),
},
},
"testdata/include/common_b.yaml": {
Includes: []string{"testdata/include/no_includes.yaml"},
Expand All @@ -166,7 +172,7 @@ func TestNewConfigGraph(t *testing.T) {
"testdata/include/no_includes.yaml": {
LavaVersion: ptr("v1.0.0"),
ChecktypeURLs: []string{
"checktypes.json",
"checktypes_no_includes.json",
},
Targets: []Target{
{
Expand Down Expand Up @@ -196,3 +202,135 @@ func TestNewConfigGraph(t *testing.T) {
})
}
}

func TestConfigGraph_Resolve(t *testing.T) {
tests := []struct {
name string
URL string
want Config
wantErr bool
}{
{
name: "no includes",
URL: "testdata/include/no_includes.yaml",
want: Config{
LavaVersion: ptr("v1.0.0"),
ChecktypeURLs: []string{
"checktypes_no_includes.json",
},
Targets: []Target{
{
Identifier: "example.com",
AssetType: types.DomainName,
},
},
},
wantErr: false,
},
{
name: "local file",
URL: "testdata/include/local.yaml",
want: Config{
Includes: []string{"testdata/include/no_includes.yaml"},
LavaVersion: ptr("v1.0.0"),
ChecktypeURLs: []string{
"checktypes_no_includes.json",
"checktypes.json",
},
Targets: []Target{
{
Identifier: "example.com",
AssetType: types.DomainName,
},
{
Identifier: "example.com",
AssetType: types.DomainName,
},
},
},
wantErr: false,
},
{
name: "duplicated",
URL: "testdata/include/duplicated.yaml",
want: Config{
Includes: []string{
"testdata/include/no_includes.yaml",
"testdata/include/no_includes.yaml",
},
LavaVersion: ptr("v1.0.0"),
ChecktypeURLs: []string{
"checktypes_no_includes.json",
"checktypes.json",
},
Targets: []Target{
{
Identifier: "example.com",
AssetType: types.DomainName,
},
{
Identifier: "example.com",
AssetType: types.DomainName,
},
},
},
wantErr: false,
},
{
name: "common includes",
URL: "testdata/include/common.yaml",
want: Config{
Includes: []string{
"testdata/include/no_includes.yaml",
"testdata/include/no_includes.yaml",
"testdata/include/common_a.yaml",
"testdata/include/common_b.yaml",
},
LavaVersion: ptr("v1.0.0"),
ChecktypeURLs: []string{
"checktypes.json",
"checktypes_no_includes.json",
"checktypes.json",
"checktypes.json",
},
Targets: []Target{
{
Identifier: "example.com",
AssetType: types.DomainName,
},
{
Identifier: "example.com",
AssetType: types.DomainName,
},
{
Identifier: "example.com",
AssetType: types.DomainName,
},
{
Identifier: "example.com",
AssetType: types.DomainName,
},
},
ReportConfig: ReportConfig{
Severity: ptr(SeverityCritical),
},
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
graph, err := NewConfigGraph(tt.URL)
if err != nil {
t.Errorf("build dag: %v", err)
}
got := graph.Resolve()
if (err != nil) != tt.wantErr {
t.Errorf("unexpected error: %v", err)
}
if diff := cmp.Diff(tt.want, got); diff != "" {
t.Errorf("configs mismatch (-want +got):\n%v", diff)
}
})
}
}
14 changes: 14 additions & 0 deletions internal/config/dag/dag.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,17 @@ func (d *DAG) getVertexID(s string) string {
}
return ""
}

// WalkFunc represents a function that implements the [hdag.Visitor]
// interface.
type WalkFunc func(vertexID string, vertex interface{})

// Visit is required by the [hdag.Visitor] interface.
func (fn WalkFunc) Visit(v hdag.Vertexer) {
fn(v.Vertex())
}

// DFSWalk walks the [DAG] using a depth first strategy.
func (d *DAG) DFSWalk(fn WalkFunc) {
d.dag.DFSWalk(fn)
}
2 changes: 2 additions & 0 deletions internal/config/testdata/include/common.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ checktypes:
targets:
- identifier: example.com
type: DomainName
report:
severity: critical
2 changes: 2 additions & 0 deletions internal/config/testdata/include/common_a.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ checktypes:
targets:
- identifier: example.com
type: DomainName
report:
severity: medium
2 changes: 1 addition & 1 deletion internal/config/testdata/include/no_includes.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
lava: v1.0.0
checktypes:
- checktypes.json
- checktypes_no_includes.json
targets:
- identifier: example.com
type: DomainName

0 comments on commit cda2cf1

Please sign in to comment.