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 3, 2025
1 parent 51e9814 commit 13ffca9
Show file tree
Hide file tree
Showing 7 changed files with 184 additions and 26 deletions.
12 changes: 3 additions & 9 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,20 +91,14 @@ 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)
}
graph.Resolve()
cfg := graph.cfg

if err = cfg.validate(); err != nil {
return Config{}, fmt.Errorf("validate config: %w", err)
}

return cfg, nil
return *cfg, nil
}

// Decode decodes from a slice of bytes to a [Config] structure.
Expand Down
37 changes: 29 additions & 8 deletions internal/config/configgraph.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import (
"fmt"
"io"
"log/slog"
"os"

hdag "github.com/heimdalr/dag"

"github.com/adevinta/lava/internal/config/dag"
"github.com/adevinta/lava/internal/urlutil"
Expand All @@ -16,6 +19,7 @@ import (
type ConfigGraph struct {
dag *dag.DAG
configs map[string]Config
cfg *Config
}

// NewConfigGraph creates a new [ConfigGraph] that represents the whole configuration.
Expand Down Expand Up @@ -76,16 +80,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)
}

// Visit executes the merge when a vertex is visited.
func (cg *ConfigGraph) Visit(v hdag.Vertexer) {
_, cfg := v.Vertex()
config, err := cg.Config(cfg.(string))
if err != nil {
os.Exit(1)
}
if cg.cfg == nil {
cg.cfg = &config
} else {
mergedConfig, err := merge(config, *cg.cfg)
if err != nil {
os.Exit(1)
}
cg.cfg = &mergedConfig
}
}

// Resolve walks the dag and merge the configuration.
func (cg *ConfigGraph) Resolve() {
cg.dag.Dag.DFSWalk(cg)
}
147 changes: 143 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",
"commonchecktypes.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",
"commonchecktypes.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",
"commonchecktypes.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",
"commonchecktypes.json",
},
Targets: []Target{
{
Expand Down Expand Up @@ -196,3 +202,136 @@ 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{
"commonchecktypes.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{
"commonchecktypes.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{
"commonchecktypes.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",
"commonchecktypes.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)
}
graph.Resolve()
got := graph.cfg
if (err != nil) != tt.wantErr {
t.Errorf("unexpected error: want: %v, got: %v", tt.wantErr, err)
}
if diff := cmp.Diff(tt.want, *got); diff != "" {
t.Errorf("configs mismatch (-want +got):\n%v", diff)
}
})
}
}
8 changes: 4 additions & 4 deletions internal/config/dag/dag.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,22 +29,22 @@ var (

// DAG represents a Direct Acyclic Graph object.
type DAG struct {
dag *hdag.DAG
Dag *hdag.DAG
vertices map[string]string
}

// New returns a Directed Acyclic Graph object.
func New() *DAG {
return &DAG{
dag: hdag.NewDAG(),
Dag: hdag.NewDAG(),
vertices: make(map[string]string),
}
}

// AddVertex adds the vertex x to the DAG. AddVertex returns an error if s is
// nil or s is already part of the graph.
func (d *DAG) AddVertex(s string) (string, error) {
id, err := d.dag.AddVertex(s)
id, err := d.Dag.AddVertex(s)
if err != nil {
if errors.As(err, &hdag.VertexDuplicateError{}) {
return id, fmt.Errorf("%w: %s", ErrDuplicatedVertex, s)
Expand All @@ -66,7 +66,7 @@ func (d *DAG) AddEdge(from, to string) error {
if toID == "" {
return fmt.Errorf("%w: %s", ErrUnknownVertex, to)
}
err := d.dag.AddEdge(fromID, toID)
err := d.Dag.AddEdge(fromID, toID)
if err != nil {
if errors.As(err, &hdag.EdgeDuplicateError{}) {
return fmt.Errorf("%w: from %s to %s", ErrDuplicatedEdge, from, to)
Expand Down
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
- commonchecktypes.json
targets:
- identifier: example.com
type: DomainName

0 comments on commit 13ffca9

Please sign in to comment.