|
|
|
|
@ -11,7 +11,7 @@ import (
|
|
|
|
|
"github.com/hashicorp/terraform/digraph"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Graph is used to represent the entire dependency graph
|
|
|
|
|
// Graph is used to represent a dependency graph.
|
|
|
|
|
type Graph struct {
|
|
|
|
|
Name string
|
|
|
|
|
Meta interface{}
|
|
|
|
|
@ -19,58 +19,8 @@ type Graph struct {
|
|
|
|
|
Root *Noun
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Validate is used to ensure that a few properties of the graph are not violated:
|
|
|
|
|
// 1) There must be a single "root", or source on which nothing depends.
|
|
|
|
|
// 2) All nouns in the graph must be reachable from the root
|
|
|
|
|
// 3) The graph must be cycle free, meaning there are no cicular dependencies
|
|
|
|
|
func (g *Graph) Validate() error {
|
|
|
|
|
// Convert to node list
|
|
|
|
|
nodes := make([]digraph.Node, len(g.Nouns))
|
|
|
|
|
for i, n := range g.Nouns {
|
|
|
|
|
nodes[i] = n
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create a validate erro
|
|
|
|
|
vErr := &ValidateError{}
|
|
|
|
|
|
|
|
|
|
// Search for all the sources, if we have only 1, it must be the root
|
|
|
|
|
if sources := digraph.Sources(nodes); len(sources) != 1 {
|
|
|
|
|
vErr.MissingRoot = true
|
|
|
|
|
goto CHECK_CYCLES
|
|
|
|
|
} else {
|
|
|
|
|
g.Root = sources[0].(*Noun)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check reachability
|
|
|
|
|
if unreached := digraph.Unreachable(g.Root, nodes); len(unreached) > 0 {
|
|
|
|
|
vErr.Unreachable = make([]*Noun, len(unreached))
|
|
|
|
|
for i, u := range unreached {
|
|
|
|
|
vErr.Unreachable[i] = u.(*Noun)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CHECK_CYCLES:
|
|
|
|
|
// Check for cycles
|
|
|
|
|
if cycles := digraph.StronglyConnectedComponents(nodes, true); len(cycles) > 0 {
|
|
|
|
|
vErr.Cycles = make([][]*Noun, len(cycles))
|
|
|
|
|
for i, cycle := range cycles {
|
|
|
|
|
group := make([]*Noun, len(cycle))
|
|
|
|
|
for j, n := range cycle {
|
|
|
|
|
group[j] = n.(*Noun)
|
|
|
|
|
}
|
|
|
|
|
vErr.Cycles[i] = group
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Return the detailed error
|
|
|
|
|
if vErr.MissingRoot || vErr.Unreachable != nil || vErr.Cycles != nil {
|
|
|
|
|
return vErr
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ValidateError implements the Error interface but provides
|
|
|
|
|
// additional information on a validation error
|
|
|
|
|
// additional information on a validation error.
|
|
|
|
|
type ValidateError struct {
|
|
|
|
|
// If set, then the graph is missing a single root, on which
|
|
|
|
|
// there are no depdendencies
|
|
|
|
|
@ -89,6 +39,31 @@ func (v *ValidateError) Error() string {
|
|
|
|
|
return "The depedency graph is not valid"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ConstraintError is used to return detailed violation
|
|
|
|
|
// information from CheckConstraints
|
|
|
|
|
type ConstraintError struct {
|
|
|
|
|
Violations []*Violation
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *ConstraintError) Error() string {
|
|
|
|
|
return fmt.Sprintf("%d constraint violations", len(c.Violations))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Violation is used to pass along information about
|
|
|
|
|
// a constraint violation
|
|
|
|
|
type Violation struct {
|
|
|
|
|
Source *Noun
|
|
|
|
|
Target *Noun
|
|
|
|
|
Dependency *Dependency
|
|
|
|
|
Constraint Constraint
|
|
|
|
|
Err error
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (v *Violation) Error() string {
|
|
|
|
|
return fmt.Sprintf("Constraint %v between %v and %v violated: %v",
|
|
|
|
|
v.Constraint, v.Source, v.Target, v.Err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// CheckConstraints walks the graph and ensures that all
|
|
|
|
|
// user imposed constraints are satisfied.
|
|
|
|
|
func (g *Graph) CheckConstraints() error {
|
|
|
|
|
@ -129,27 +104,52 @@ func (g *Graph) CheckConstraints() error {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ConstraintError is used to return detailed violation
|
|
|
|
|
// information from CheckConstraints
|
|
|
|
|
type ConstraintError struct {
|
|
|
|
|
Violations []*Violation
|
|
|
|
|
}
|
|
|
|
|
// Validate is used to ensure that a few properties of the graph are not violated:
|
|
|
|
|
// 1) There must be a single "root", or source on which nothing depends.
|
|
|
|
|
// 2) All nouns in the graph must be reachable from the root
|
|
|
|
|
// 3) The graph must be cycle free, meaning there are no cicular dependencies
|
|
|
|
|
func (g *Graph) Validate() error {
|
|
|
|
|
// Convert to node list
|
|
|
|
|
nodes := make([]digraph.Node, len(g.Nouns))
|
|
|
|
|
for i, n := range g.Nouns {
|
|
|
|
|
nodes[i] = n
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *ConstraintError) Error() string {
|
|
|
|
|
return fmt.Sprintf("%d constraint violations", len(c.Violations))
|
|
|
|
|
}
|
|
|
|
|
// Create a validate erro
|
|
|
|
|
vErr := &ValidateError{}
|
|
|
|
|
|
|
|
|
|
// Violation is used to pass along information about
|
|
|
|
|
// a constraint violation
|
|
|
|
|
type Violation struct {
|
|
|
|
|
Source *Noun
|
|
|
|
|
Target *Noun
|
|
|
|
|
Dependency *Dependency
|
|
|
|
|
Constraint Constraint
|
|
|
|
|
Err error
|
|
|
|
|
}
|
|
|
|
|
// Search for all the sources, if we have only 1, it must be the root
|
|
|
|
|
if sources := digraph.Sources(nodes); len(sources) != 1 {
|
|
|
|
|
vErr.MissingRoot = true
|
|
|
|
|
goto CHECK_CYCLES
|
|
|
|
|
} else {
|
|
|
|
|
g.Root = sources[0].(*Noun)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (v *Violation) Error() string {
|
|
|
|
|
return fmt.Sprintf("Constraint %v between %v and %v violated: %v",
|
|
|
|
|
v.Constraint, v.Source, v.Target, v.Err)
|
|
|
|
|
// Check reachability
|
|
|
|
|
if unreached := digraph.Unreachable(g.Root, nodes); len(unreached) > 0 {
|
|
|
|
|
vErr.Unreachable = make([]*Noun, len(unreached))
|
|
|
|
|
for i, u := range unreached {
|
|
|
|
|
vErr.Unreachable[i] = u.(*Noun)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CHECK_CYCLES:
|
|
|
|
|
// Check for cycles
|
|
|
|
|
if cycles := digraph.StronglyConnectedComponents(nodes, true); len(cycles) > 0 {
|
|
|
|
|
vErr.Cycles = make([][]*Noun, len(cycles))
|
|
|
|
|
for i, cycle := range cycles {
|
|
|
|
|
group := make([]*Noun, len(cycle))
|
|
|
|
|
for j, n := range cycle {
|
|
|
|
|
group[j] = n.(*Noun)
|
|
|
|
|
}
|
|
|
|
|
vErr.Cycles[i] = group
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Return the detailed error
|
|
|
|
|
if vErr.MissingRoot || vErr.Unreachable != nil || vErr.Cycles != nil {
|
|
|
|
|
return vErr
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|