Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

optimize check runs & split static/plan checks #7

Merged
merged 1 commit into from
Aug 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions checks/all.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package checks

import (
"guacamole/data"
"sync"
)

func All(layers []data.Layer) []data.Check {
// List of checks to perform

var checkResults []data.Check

checkTypes := 2

wg := new(sync.WaitGroup)
wg.Add(checkTypes)

c := make(chan []data.Check, checkTypes)
defer close(c)

go func() {
defer wg.Done()
checks := PlanChecks(layers)
c <- checks
}()

go func() {
defer wg.Done()
checks := StaticChecks()
c <- checks
}()

wg.Wait()

for i := 0; i < checkTypes; i++ {
checkResults = append(checkResults, <-c...)
}

return checkResults
}
56 changes: 0 additions & 56 deletions checks/check_all.go

This file was deleted.

86 changes: 17 additions & 69 deletions checks/iterate_no_use_count.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
package checks

import (
"context"
"fmt"
"guacamole/data"
"guacamole/helpers"
"regexp"
"strconv"
"strings"
"sync"

tfexec "github.com/hashicorp/terraform-exec/tfexec"
)

type Change struct {
Expand All @@ -31,45 +26,33 @@ type ResourceChanges struct {
ResourceChanges []ResourceChange `json:"resource_changes"`
}

func IterateNoUseCount() (data.Check, error) {
func IterateNoUseCount(layers []data.Layer) (data.Check, error) {
name := "Don't use count to create multiple resources"
// relatedGuidelines := "https://padok-team.github.io/docs-terraform-guidelines/terraform/iterate_on_your_resources.html#list-iteration-count"
relatedGuidelines := "http://bitly.ws/K5WA"
status := "✅"
errors := []string{}

layers, err := helpers.GetLayers()
if err != nil {
return data.Check{}, err
}

// Create a channel to receive the results
results, checkErrors, errs := make(chan string), make(chan string), make(chan error)
defer close(results)
defer close(checkErrors)
defer close(errs)
c := make(chan []string, len(layers))

wg := new(sync.WaitGroup)
wg.Add(len(layers))

for _, layer := range layers {
go checkLayer(layer, results, checkErrors, errs, wg)
for i := range layers {
go func(layer *data.Layer) {
defer wg.Done()
c <- checkLayer(layer)
}(&layers[i])
}

wg.Wait() // Here we wait for all the goroutines to finish
wg.Wait()

// Wait for all goroutines to finish
for i := 0; i < len(layers); i++ {
select {
case result := <-results:
if result == "❌" {
status = "❌"
}
case checkError := <-checkErrors:
errors = append(errors, checkError)

case err := <-errs:
return data.Check{}, fmt.Errorf("error while checking layer: %w", err)
checkErrors := <-c
if len(checkErrors) > 0 {
status = "❌"
errors = append(errors, checkErrors...)
}
}

Expand All @@ -83,44 +66,13 @@ func IterateNoUseCount() (data.Check, error) {
return data, nil
}

func checkLayer(layer data.Layer, result, checkError chan string, errs chan error, wg *sync.WaitGroup) {
status := "✅"

dirPath := "/tmp/" + strings.ReplaceAll(layer.Name, "/", "_")

fmt.Println("Checking layer", layer.Name)
tf, err := tfexec.NewTerraform(layer.FullPath, "terragrunt")
if err != nil {
errs <- fmt.Errorf("failed to create Terraform instance: %w", err)
}

fmt.Println("Initializing Terraform for layer", layer.Name)
err = tf.Init(context.TODO(), tfexec.Upgrade(true))
if err != nil {
errs <- fmt.Errorf("failed to initialize Terraform: %w", err)
}

// Create Terraform plan
fmt.Println("Creating plan for layer", layer.Name)
_, err = tf.Plan(context.Background(), tfexec.Out(dirPath+"_plan.json"))
if err != nil {
errs <- fmt.Errorf("failed to create plan: %w", err)
}

// Create JSON plan
fmt.Println("Showing JSON plan for layer", layer.Name)
jsonPlan, err := tf.ShowPlanFile(context.Background(), dirPath+"_plan.json")
if err != nil {
errs <- fmt.Errorf("failed to create JSON plan: %w", err)
}

func checkLayer(layer *data.Layer) []string {
var index int
var indexString string
var checkedResources []string
var checkedResources, checkErrors []string

// Analyze plan for resources with count > 1
fmt.Println("Analyzing plan for layer", layer.Name)
for _, rc := range jsonPlan.ResourceChanges {
for _, rc := range layer.Plan.ResourceChanges {
// Parse the module address to find numbers inside of []
regexpIndexMatch := regexp.MustCompile(`\[(.*?)\]`).FindStringSubmatch(rc.Address)
if len(regexpIndexMatch) > 0 {
Expand All @@ -140,14 +92,10 @@ func checkLayer(layer data.Layer, result, checkError chan string, errs chan erro
}
}
if !alreadyChecked {
status = "❌"
checkedResources = append(checkedResources, rc.ModuleAddress)
fmt.Println("Resource", rc.Address, "in layer", layer.Name, "has count more than 1")
checkError <- fmt.Sprintf("Resource %s in layer %s has count more than 1\n", rc.Address, layer.Name)
checkErrors = append(checkErrors, fmt.Sprintf("%s --> %s", layer.Name, rc.Address))
}
}
}
fmt.Println("Done checking layer", layer.Name)
result <- status
wg.Done()
return checkErrors
}
42 changes: 42 additions & 0 deletions checks/plan_checks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package checks

import (
"guacamole/data"
"sync"
)

func PlanChecks(layers []data.Layer) []data.Check {
// Add plan checks here
checks := map[string]func([]data.Layer) (data.Check, error){
"IterateNoUseCount": IterateNoUseCount,
"RefreshTime": RefreshTime,
}

var checkResults []data.Check

wg := new(sync.WaitGroup)
wg.Add(len(checks))

c := make(chan data.Check, len(checks))
defer close(c)

for _, checkFunction := range checks {
go func(checkFunction func([]data.Layer) (data.Check, error)) {
defer wg.Done()

check, err := checkFunction(layers)
if err != nil {
panic(err)
}
c <- check
}(checkFunction)
}

wg.Wait()

for i := 0; i < len(checks); i++ {
checkResults = append(checkResults, <-c)
}

return checkResults
}
File renamed without changes.
31 changes: 17 additions & 14 deletions checks/refresh_time.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@ package checks

import (
"guacamole/data"
"guacamole/helpers"
)

func RefreshTime() data.Check {
func RefreshTime(layers []data.Layer) (data.Check, error) {
checkResult := data.Check{
Name: "Layers' refresh time",
Status: "✅",
Expand All @@ -14,24 +13,28 @@ func RefreshTime() data.Check {
Errors: []string{},
}

layers, _ := helpers.GetLayers()
for _, layer := range layers {
err := layer.Init()
if err != nil {
panic(err)
}
err = layer.GetRefreshTime()
if err != nil {
panic(err)
}
if layer.RefreshTime > 120 {
checkResult.Errors = append(checkResult.Errors, layer.Name)
refreshTime := 0
if layer.State.Values == nil {
continue
} else {
// TODO: check if this way of counting resources counts all nested resources
// refreshTime := len(layer.State.Values.RootModule.Resources)
for _, resource := range layer.Plan.ResourceChanges {
if !resource.Change.Actions.Create() {
refreshTime++
}
}

if refreshTime > 120 {
checkResult.Errors = append(checkResult.Errors, layer.Name)
}
}
}

if len(checkResult.Errors) > 0 {
checkResult.Status = "❌"
}

return checkResult
return checkResult, nil
}
42 changes: 42 additions & 0 deletions checks/static_checks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package checks

import (
"guacamole/data"
"sync"
)

func StaticChecks() []data.Check {
// Add static checks here
checks := map[string]func() (data.Check, error){
"ProviderInModule": ProviderInModule,
"Stuttering": Stuttering,
}

var checkResults []data.Check

wg := new(sync.WaitGroup)
wg.Add(len(checks))

c := make(chan data.Check, len(checks))
defer close(c)

for _, checkFunction := range checks {
go func(checkFunction func() (data.Check, error)) {
defer wg.Done()

check, err := checkFunction()
if err != nil {
panic(err)
}
c <- check
}(checkFunction)
}

wg.Wait()

for i := 0; i < len(checks); i++ {
checkResults = append(checkResults, <-c)
}

return checkResults
}
2 changes: 1 addition & 1 deletion checks/stuttering.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
"github.com/hashicorp/terraform-config-inspect/tfconfig"
)

func NoStuttering() (data.Check, error) {
func Stuttering() (data.Check, error) {
name := "Stuttering in the naming of the resources"
// relatedGuidelines := "https://padok-team.github.io/docs-terraform-guidelines/terraform/terraform_naming.html#resource-andor-data-source-naming"
relatedGuidelines := "http://bitly.ws/K5Wm"
Expand Down
Loading