/
mod_resource_tree.go
145 lines (126 loc) · 4.04 KB
/
mod_resource_tree.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
package modconfig
import (
"fmt"
"github.com/hashicorp/hcl/v2"
"github.com/turbot/pipe-fittings/utils"
)
// BuildResourceTree builds the control tree structure by setting the parent property for each control and benchmark
// NOTE: this also builds the sorted benchmark list
func (m *Mod) BuildResourceTree(loadedDependencyMods ModMap) (err error) {
utils.LogTime(fmt.Sprintf("BuildResourceTree %s start", m.Name()))
defer utils.LogTime(fmt.Sprintf("BuildResourceTree %s end", m.Name()))
defer func() {
if err == nil {
err = m.validateResourceTree()
}
}()
// build lookup of children and parents
childrenLookup, err := m.getChildParentsLookup()
if err != nil {
return err
}
if err := m.addResourcesIntoTree(m, childrenLookup); err != nil {
return err
}
if !m.HasDependentMods() {
return nil
}
// add dependent mods into tree
for _, requiredMod := range m.Require.Mods {
// find this mod in installed dependency mods
depMod, ok := loadedDependencyMods[requiredMod.Name]
if !ok {
return fmt.Errorf("dependency mod %s is not loaded", requiredMod.Name)
}
if err := m.addResourcesIntoTree(depMod, childrenLookup); err != nil {
return err
}
}
return nil
}
func (m *Mod) getChildParentsLookup() (map[string][]ModTreeItem, error) {
// build lookup of all children
childrenLookup := make(map[string][]ModTreeItem)
resourceFunc := func(parent HclResource) (bool, error) {
if treeItem, ok := parent.(ModTreeItem); ok {
for _, child := range treeItem.GetChildren() {
childrenLookup[child.Name()] = append(childrenLookup[child.Name()], treeItem)
}
}
// continue walking
return true, nil
}
err := m.ResourceMaps.WalkResources(resourceFunc)
if err != nil {
return nil, err
}
return childrenLookup, nil
}
// add all resource in sourceMod into _our_ resource tree
func (m *Mod) addResourcesIntoTree(sourceMod *Mod, childParentLookup map[string][]ModTreeItem) error {
utils.LogTime(fmt.Sprintf("addResourcesIntoTree %s source %s start", m.Name(), sourceMod.Name()))
defer utils.LogTime(fmt.Sprintf("addResourcesIntoTree %s source %s end", m.Name(), sourceMod.Name()))
var leafNodes []ModTreeItem
var err error
resourceFunc := func(item HclResource) (bool, error) {
// skip mods
if _, ok := item.(*Mod); ok {
return true, nil
}
if treeItem, ok := item.(ModTreeItem); ok {
// NOTE: add resource into _our_ resource tree, i.e. mod 'm'
if err = m.addItemIntoResourceTree(treeItem, childParentLookup); err != nil {
// stop walking
return false, err
}
if len(treeItem.GetChildren()) == 0 {
leafNodes = append(leafNodes, treeItem)
}
}
// continue walking
return true, nil
}
// iterate through all resources in source mod
err = sourceMod.WalkResources(resourceFunc)
if err != nil {
return err
}
// now initialise all Paths properties
for _, l := range leafNodes {
l.SetPaths()
}
return nil
}
func (m *Mod) addItemIntoResourceTree(item ModTreeItem, childParentLookup map[string][]ModTreeItem) error {
parents := childParentLookup[item.Name()]
if len(parents) == 0 {
parents = []ModTreeItem{m}
}
for _, p := range parents {
// if we are the parent, add as a child
if err := item.AddParent(p); err != nil {
return err
}
if p == m {
m.children = append(m.children, item)
}
}
return nil
}
// check whether a resource with the same name has already been added to the mod
// (it is possible to add the same resource to a mod more than once as the parent resource
// may have dependency errors and so be decoded again)
func checkForDuplicate(existing, new HclResource) hcl.Diagnostics {
if existing.GetDeclRange().String() == new.GetDeclRange().String() {
// decl range is the same - this is the same resource - allowable
return nil
}
return hcl.Diagnostics{&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: fmt.Sprintf("Mod defines more than one resource named '%s'", new.Name()),
Detail: fmt.Sprintf("\n- %s\n- %s", existing.GetDeclRange(), new.GetDeclRange()),
}}
}
func (m *Mod) AddResource(item HclResource) hcl.Diagnostics {
return m.ResourceMaps.AddResource(item)
}