-
Notifications
You must be signed in to change notification settings - Fork 0
/
getters.go
211 lines (193 loc) · 6.74 KB
/
getters.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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
package helpers
import (
"bufio"
"fmt"
"os"
"path/filepath"
"regexp"
"sort"
"strings"
"github.com/hashicorp/terraform-config-inspect/tfconfig"
"github.com/padok-team/guacamole/data"
"github.com/spf13/viper"
)
func GetModules() (map[string]data.TerraformModule, error) {
codebasePath := viper.GetString("codebase-path")
modules := make(map[string]data.TerraformModule)
//Get all subdirectories in root path
err := filepath.Walk(codebasePath, func(path string, info os.FileInfo, err error) error {
if err != nil {
return fmt.Errorf("failed to get subdirectories: %w", err)
}
// Check if the path is a file and its name matches "*.tf"
if !info.IsDir() && strings.HasSuffix(info.Name(), ".tf") {
// exclude the files which are in the .terragrunt-cache or .terraform directory
if !regexp.MustCompile(`\.terragrunt-cache|\.terraform`).MatchString(path) {
// Get all whitelisting comments
whitelistCommentOfFile, _ := GetWhitelistingComments(path)
// Check if the module is already in the list
alreadyInList := false
for _, m := range modules {
if m.FullPath == filepath.Dir(path) {
alreadyInList = true
}
}
// If not in list create the module
if !alreadyInList {
modules[filepath.Dir(path)], _ = LoadModule(filepath.Dir(path))
}
// Create a temporary object with all resources in order
resourcesInFile := make(map[string]data.TerraformCodeBlock)
for index, resource := range modules[filepath.Dir(path)].Resources {
if path == resource.FilePath {
resourcesInFile[index] = resource
}
}
// Order the list of resources from top to bottom via a key array
keys := make([]string, 0, len(resourcesInFile))
for k := range resourcesInFile {
keys = append(keys, k)
}
sort.SliceStable(keys, func(i, j int) bool {
return resourcesInFile[keys[i]].Pos < resourcesInFile[keys[j]].Pos
})
// Associate the whitelisting comments to a resource (Resource, Data, Variable or Output)
for _, whitelistingComment := range whitelistCommentOfFile {
previousPos := 0
for _, i := range keys {
// Find closest code block within the file
if previousPos < whitelistingComment.LineNumber && whitelistingComment.LineNumber < resourcesInFile[i].Pos {
r := modules[filepath.Dir(path)].Resources[i]
r.WhitelistComments = append(r.WhitelistComments, whitelistingComment)
modules[filepath.Dir(path)].Resources[i] = r
break
}
previousPos = resourcesInFile[i].Pos
}
}
}
}
return nil
})
return modules, err
}
func GetLayers() ([]*data.Layer, error) {
codebasePath := viper.GetString("codebase-path")
codebaseAbsPath, err := filepath.Abs(codebasePath)
if err != nil {
return nil, fmt.Errorf("failed to get: %w", err)
}
layers := []*data.Layer{}
err = filepath.Walk(codebasePath, func(path string, info os.FileInfo, err error) error {
if err != nil {
return fmt.Errorf("failed to get layer subdirectory: %w", err)
}
// Check if the current path is a file and its name matches "terragrunt.hcl"
if !info.IsDir() && info.Name() == "terragrunt.hcl" {
// exclude the files which are in the .terragrunt-cache or .terraform directory
if !regexp.MustCompile(`\.terragrunt-cache|\.terraform`).MatchString(path) {
// TODO: start from the codebase path instead of the relative path
absPath, err := filepath.Abs(path)
if err != nil {
return fmt.Errorf("failed to get absolute path: %w", err)
}
fullPath := filepath.Dir(absPath)
// Get abs path from codebase path
name := strings.Split(absPath, codebaseAbsPath+"/")[1]
name = strings.ReplaceAll(name, "/terragrunt.hcl", "")
layers = append(layers, &data.Layer{Name: name, FullPath: fullPath})
}
}
return nil
})
if err != nil {
return nil, err
}
return layers, nil
}
func LoadModule(path string) (data.TerraformModule, error) {
// Get tfconfig of module
moduleConfig, diags := tfconfig.LoadModule(path)
if diags.HasErrors() {
return data.TerraformModule{}, diags.Err()
}
resources := make(map[string]data.TerraformCodeBlock)
// Create map of code blocks (resources, data, variables and outputs) of module
for _, resource := range moduleConfig.ManagedResources {
resources[resource.Type+resource.Name] = data.TerraformCodeBlock{
Name: resource.Type + " " + resource.Name,
ModulePath: path,
Pos: resource.Pos.Line,
FilePath: resource.Pos.Filename,
WhitelistComments: []data.WhitelistComment{},
}
}
// Load data
for _, resource := range moduleConfig.DataResources {
resources[resource.Type+resource.Name] = data.TerraformCodeBlock{
Name: resource.Type + " " + resource.Name,
ModulePath: path,
Pos: resource.Pos.Line,
WhitelistComments: []data.WhitelistComment{},
FilePath: resource.Pos.Filename,
}
}
// Load variables
for _, variable := range moduleConfig.Variables {
resources["variable "+variable.Type+variable.Name] = data.TerraformCodeBlock{
Name: "variable " + variable.Type + " " + variable.Name,
ModulePath: path,
Pos: variable.Pos.Line,
WhitelistComments: []data.WhitelistComment{},
FilePath: variable.Pos.Filename,
}
}
// Load outputs
for _, output := range moduleConfig.Outputs {
resources["output"+output.Name] = data.TerraformCodeBlock{
Name: "output " + output.Name,
ModulePath: path,
Pos: output.Pos.Line,
WhitelistComments: []data.WhitelistComment{},
FilePath: output.Pos.Filename,
}
}
module := data.TerraformModule{
FullPath: path,
Name: filepath.Base(path),
ModuleConfig: *moduleConfig,
Resources: resources,
}
return module, nil
}
func GetWhitelistingComments(path string) ([]data.WhitelistComment, error) {
whitelistComments := []data.WhitelistComment{}
// Parse the file to get whitelist comments
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
// Read the file and find comments containing guacamole-ignore
scanner := bufio.NewScanner(file)
i := 1 //Set cursor to the start of the file
for scanner.Scan() {
line := scanner.Text()
if strings.Contains(line, "guacamole-ignore") {
whitelistComment := data.WhitelistComment{}
// Regex to match the check ID in the form of TF/TG_XXX_0XX
regexp := regexp.MustCompile(`(T[F|G]_(\w+)_\d+)`)
match := regexp.FindStringSubmatch(line)
if len(match) > 0 {
whitelistComment.CheckID = match[0]
whitelistComment.LineNumber = i
whitelistComment.Path = path
// Attach comment to an object
}
whitelistComments = append(whitelistComments, whitelistComment)
}
i++
}
return whitelistComments, nil
}
// TODO: add init and plan layers function