diff --git a/automation/terraformCodeGeneration.go b/automation/terraformCodeGeneration.go index f29c84a..be8ccd6 100644 --- a/automation/terraformCodeGeneration.go +++ b/automation/terraformCodeGeneration.go @@ -15,6 +15,7 @@ import ( ) type TerraformConfiguration struct { + BuildNumber int TargetedModules []string TerraformProject string RemoteState string diff --git a/automation/terraformSvg.go b/automation/terraformSvg.go index dde6ce4..3d61a4a 100644 --- a/automation/terraformSvg.go +++ b/automation/terraformSvg.go @@ -52,6 +52,8 @@ const terraformActionTextOffsetY = 5 const terraformActionOffsetY = 4 const terraformActionOffsetX = -4 const terraformActionTextSpacingX = 7 +const dotStringCharactersMinPadding = 12 +const dotStringLinearPadding = float32(0) func (node *XMLNode) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { attrPointers := []*XMLAttr{} @@ -129,7 +131,7 @@ func makeSVGFromSimpleStatus(simpleStatus *SimplifiedTerraformStatus) ([]byte, e } resourceDot := fmt.Sprintf(` "%s" [label = "%s"%s%s]`, - id, padStringForDot(resource.DisplayName, 0.1), tooltip, resourceStyle, + id, padStringForDot(resource.DisplayName), tooltip, resourceStyle, ) resourceDots = append(resourceDots, resourceDot) } @@ -156,7 +158,7 @@ func makeSVGFromSimpleStatus(simpleStatus *SimplifiedTerraformStatus) ([]byte, e }`, strings.ReplaceAll(strings.ReplaceAll(moduleName, ".", "_"), "-", "_"), moduleName, - padStringForDot(moduleName, 0.1), + padStringForDot(moduleName), strings.Join(resourceDots, ""), ) //fmt.Println(moduleName) @@ -170,7 +172,7 @@ func makeSVGFromSimpleStatus(simpleStatus *SimplifiedTerraformStatus) ([]byte, e variableDot := fmt.Sprintf(` "%s" [label = "%s", tooltip = "%s", margin = 0.1, shape = "note"];`, strings.ReplaceAll(strings.ReplaceAll(connection.From, ".", "_"), "-", "_"), - padStringForDot(remote_state_placeholder_text, 0.1), + padStringForDot(remote_state_placeholder_text), connection.From, ) variableDots = append(variableDots, variableDot) @@ -183,7 +185,7 @@ func makeSVGFromSimpleStatus(simpleStatus *SimplifiedTerraformStatus) ([]byte, e if connectedVariables[variableName] { variableDot := fmt.Sprintf(` "var_%s" [label = "%s", tooltip = "%s", margin = 0.1, shape = "note"];`, - strings.ReplaceAll(variableName, "-", "_"), padStringForDot(value, 0.1), fmt.Sprintf("var.%s", variableName), + strings.ReplaceAll(variableName, "-", "_"), padStringForDot(value), fmt.Sprintf("var.%s", variableName), ) variableDots = append(variableDots, variableDot) } @@ -236,7 +238,7 @@ func makeSVGFromSimpleStatus(simpleStatus *SimplifiedTerraformStatus) ([]byte, e } connectionDot := fmt.Sprintf(` - "param_%s" [label = "%s", shape = "invhouse", style=filled, color=lightgrey]; %s %s`, + "param_%s" [label = "%s", shape = "box", style=filled, color=lightgrey]; %s %s`, paramName, connection.DisplayName, sourceEdge, destEdge, ) connectionDots = append(connectionDots, connectionDot) @@ -319,6 +321,37 @@ func makeSVGFromSimpleStatus(simpleStatus *SimplifiedTerraformStatus) ([]byte, e } if strings.HasPrefix(title, "param_") { node.Parent.SetAttr("class", "parameter") + + // the parameters start out as boxes + //but lets squeeze them into keystone shape to differentiate them a bit + node.Parent.WithQuerySelector([]XMLQuery{XMLQuery{NodeType: "polygon"}}, func(polygonNode *XMLNode) { + vertices, err := polygonNode.GetSVGPolygonVertices() + if err == nil { + center := []float64{0, 0} + for _, vertex := range vertices { + center[0] += vertex[0] + center[1] += vertex[1] + } + center[0] = center[0] / float64(len(vertices)) + center[1] = center[1] / float64(len(vertices)) + + newVertices := [][]float64{} + for _, vertex := range vertices { + x := vertex[0] + y := vertex[1] + if y > center[1] { + if x > center[0] { + x -= 10 + } else { + x += 10 + } + } + newVertices = append(newVertices, []float64{x, y}) + } + polygonNode.SetSVGPolygonVertices(newVertices) + } + + }) } if strings.HasPrefix(title, "module_") { node.Parent.SetAttr("class", "resource") @@ -541,8 +574,8 @@ func makeSVGFromSimpleStatus(simpleStatus *SimplifiedTerraformStatus) ([]byte, e return svgBytes, nil } -func padStringForDot(str string, padAmount float32) string { - pad := strings.Repeat(" ", int(float32(len(str)+12)/(float32(1)/padAmount))) +func padStringForDot(str string) string { + pad := strings.Repeat(" ", int(float32(len(str)+dotStringCharactersMinPadding)*dotStringLinearPadding)) return fmt.Sprintf("%s%s%s", pad, str, pad) } @@ -676,10 +709,10 @@ func (node *XMLNode) GetBoundingBox() *Rect2D { ellipses := node.QuerySelectorAll([]XMLQuery{XMLQuery{NodeType: "ellipse"}}) for _, polygon := range polygons { - points, err := polygon.GetSVGPolygonPoints() + vertices, err := polygon.GetSVGPolygonVertices() if err == nil { - for _, point := range points { - toReturn.Encapsulate(point[0], point[1]) + for _, vertex := range vertices { + toReturn.Encapsulate(vertex[0], vertex[1]) } } } @@ -700,7 +733,7 @@ func (node *XMLNode) GetBoundingBox() *Rect2D { return &toReturn } -func (node *XMLNode) GetSVGPolygonPoints() ([][]float64, error) { +func (node *XMLNode) GetSVGPolygonVertices() ([][]float64, error) { toReturn := [][]float64{} var err error @@ -721,3 +754,11 @@ func (node *XMLNode) GetSVGPolygonPoints() ([][]float64, error) { } return toReturn, err } + +func (node *XMLNode) SetSVGPolygonVertices(points [][]float64) { + coordStrings := []string{} + for _, point := range points { + coordStrings = append(coordStrings, fmt.Sprintf("%.4f,%.4f", point[0], point[1])) + } + node.SetAttr("points", strings.Join(coordStrings, " ")) +} diff --git a/host-key-poller/main.go b/host-key-poller/main.go index 182ddeb..94a0131 100644 --- a/host-key-poller/main.go +++ b/host-key-poller/main.go @@ -27,7 +27,7 @@ func main() { } iterations := 0 - for iterations < 60 { + for iterations < 200 { // waits for a maximum of 200*5 seconds = 1000 seconds iterations++ filename := fmt.Sprintf("rootsystem/known-hosts/%s", os.Args[1]) diff --git a/main.go b/main.go index 158931f..1dd38f6 100644 --- a/main.go +++ b/main.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "net/http" + "strconv" "strings" errors "git.sequentialread.com/forest/pkg-errors" @@ -65,11 +66,31 @@ func main() { panic(fmt.Sprintf("rootsystem can't start because can't create certs for threshold: %+v", err)) } + // Add 1 to the build number, each time rootsystem runs is a different build. + file, notFound, err := global.storage.Get("rootsystem/terraform-logs/build-number.txt") + if err != nil && !notFound { + panic(fmt.Sprintf("rootsystem can't start because can't download build-number.txt: %+v", err)) + } + buildNumber := 1 + if !notFound { + n, err := strconv.Atoi(string(file.Content)) + if err != nil { + log.Printf("warning: build-number.txt did not contain a number. defaulting to build number 1. contents: %s\n", string(file.Content)) + } else { + buildNumber = n + 1 + } + } + err = global.storage.Put("rootsystem/terraform-logs/build-number.txt", []byte(strconv.Itoa(buildNumber))) + if err != nil { + panic(fmt.Sprintf("rootsystem can't start because can't upload build-number.txt: %+v", err)) + } + // First, run the terraform build for the GLOBAL components, meaning the components // that exist in the cloud, independent of how many server.garden nodes are being used. outputVariables, success, err := terraformBuild( config, automation.TerraformConfiguration{ + BuildNumber: buildNumber, TargetedModules: config.Terraform.GlobalModules, TerraformProject: configuration.GLOBAL_TERRAFORM_PROJECT, HostKeysObjectStorageCredentials: knownHostsCredentials, @@ -90,6 +111,7 @@ func main() { _, success, err = terraformBuild( config, automation.TerraformConfiguration{ + BuildNumber: buildNumber, TargetedModules: config.Terraform.LocalModules, TerraformProject: projectName, RemoteState: configuration.GLOBAL_TERRAFORM_PROJECT, @@ -132,7 +154,12 @@ func terraformBuild( fmt.Println("TerraformPlanAndApply kicked off") - err = global.storage.Put(fmt.Sprintf("rootsystem/terraform/%s/diagram.svg", terraformConfig.TerraformProject), svg) + diagramPath := fmt.Sprintf( + "rootsystem/terraform-logs/%s/%d/diagram.svg", + terraformConfig.TerraformProject, terraformConfig.BuildNumber, + ) + err = global.storage.Put(diagramPath, svg) + if err != nil { return []string{}, false, err } @@ -164,8 +191,11 @@ func terraformBuild( // return []string{}, false, err // } // log.Println(string(statusJson1)) - - err = global.storage.Put(fmt.Sprintf("rootsystem/terraform/%s/status.json", terraformConfig.TerraformProject), statusJson) + statusPath := fmt.Sprintf( + "rootsystem/terraform-logs/%s/%d/status.json", + terraformConfig.TerraformProject, terraformConfig.BuildNumber, + ) + err = global.storage.Put(statusPath, statusJson) if err != nil { log.Printf("can't upload terraform status update to object storage: %+v", err) } diff --git a/terraform-modules/ansible-threshold-server/main.tf b/terraform-modules/ansible-threshold-server/main.tf index f0ead70..70418c6 100644 --- a/terraform-modules/ansible-threshold-server/main.tf +++ b/terraform-modules/ansible-threshold-server/main.tf @@ -19,6 +19,11 @@ variable "ingress_host_list" { resource "null_resource" "ansible_playbook" { count = length(var.ingress_host_list) + // things that trigger this playbook to run: + // - when the ingress host changes + // ( known_hosts_file_name is the name of the known-hosts file in object storage, + // which is unique to the cloud instance ) + // - when the domain name changes triggers = { id = var.ingress_host_list[count.index].known_hosts_file_name domain = var.domain_name diff --git a/terraform-modules/gateway-instance-digitalocean/main.tf b/terraform-modules/gateway-instance-digitalocean/main.tf index d0a5bba..186be17 100644 --- a/terraform-modules/gateway-instance-digitalocean/main.tf +++ b/terraform-modules/gateway-instance-digitalocean/main.tf @@ -43,6 +43,7 @@ resource "null_resource" "host_key_poller" { // we always want to run the host_key_poller because its possible that another instance created the // gateway instance initially, so we might not have its host key added yet. + // host key poller runs quick so should be no problem triggers = { always_run = timestamp() } diff --git a/terraformStateHandler.go b/terraformStateHandler.go index a84efb1..70b03b4 100644 --- a/terraformStateHandler.go +++ b/terraformStateHandler.go @@ -21,7 +21,7 @@ func (handler terraformStateHandler) ServeHTTP(response http.ResponseWriter, req response.Write([]byte("file path cannot contain .")) return } - objectStoragePath := filepath.Join("/rootsystem/terraform", request.URL.Path, "terraform.tfstate") + objectStoragePath := filepath.Join("/rootsystem/terraform-state", request.URL.Path, "terraform.tfstate") if request.Method == "GET" { file, notFound, err := global.storage.Get(objectStoragePath) if notFound {