Browse Source

got terraform planned action decorations looking nice.

master
forest 2 years ago
parent
commit
399d1adece
  1. 6
      test-status.json
  2. 229
      test.go

6
test-status.json

@ -7,7 +7,7 @@
{
"ResourceType": "ansible_role",
"DisplayName": "threshold",
"Plan": "none",
"Plan": "create",
"State": "ok",
"Progress": 0,
"ProgressTotal": 3
@ -15,7 +15,7 @@
{
"ResourceType": "ansible_role",
"DisplayName": "threshold-server-config",
"Plan": "none",
"Plan": "update",
"State": "ok",
"Progress": 0,
"ProgressTotal": 5
@ -29,7 +29,7 @@
{
"ResourceType": "gandi_livedns_record",
"DisplayName": "gandi_livedns_record.dns_entries",
"Plan": "none",
"Plan": "delete",
"State": "ok",
"Progress": 0,
"ProgressTotal": 0

229
test.go

@ -74,6 +74,17 @@ type Rect2D struct {
Right float64
}
const terraformSvgFontFamily = "-apple-system,system-ui,BlinkMacSystemFont,Ubuntu,Roboto,Segoe UI,sans-serif"
const terraformActionRedClass = "red"
const terraformActionYellowClass = "yellow"
const terraformActionGreenClass = "green"
const terraformActionCircleRadius = 14
const terraformActionFontSize = 18
const terraformActionTextOffsetY = 5
const terraformActionOffsetY = 4
const terraformActionOffsetX = -4
const terraformActionTextSpacingX = 7
func (node *XMLNode) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
attrPointers := []*XMLAttr{}
for _, attr := range start.Attr {
@ -105,25 +116,6 @@ func (node *XMLNode) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error
return d.DecodeElement((*dontCauseInfiniteLoop)(node), &start)
}
//https://stackoverflow.com/questions/39780433/golang-xml-customize-output
// // MarshalXML generate XML output for PrecsontructedInfo
// func (preconstructed PreconstructedInfo) MarshalXML(e *xml.Encoder, start xml.StartElement) (err error) {
// if (PreconstructedInfo{} == preconstructed) {
// return nil
// }
// if preconstructed.Decks > 0 {
// start.Attr = []xml.Attr{xml.Attr{Name: xml.Name{Local: "decks"}, Value: strconv.Itoa(preconstructed.Size)}}
// }
// if strings.Compare(preconstructed.Type, "") != 0 {
// start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "type"}, Value: preconstructed.Type})
// }
// err = e.EncodeToken(start)
// e.EncodeElement(preconstructed.Size, xml.StartElement{Name: xml.Name{Local: "size"}})
// return e.EncodeToken(xml.EndElement{Name: start.Name})
// }
func (node XMLNode) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
attrsToEncode := []xml.Attr{}
@ -220,16 +212,6 @@ func main() {
},
)
// TODO is this needed ? I don't think so...
// remove the white background from the edges
// svgDoc.WithQuerySelector(
// // 'g.edge path'
// []XMLQuery{XMLQuery{NodeType: "g", Class: "edge"}, XMLQuery{NodeType: "path"}},
// func(node *XMLNode) {
// node.SetAttr("fill", "none")
// },
// )
// correctly set the dot property for resources, variables, and parameters
svgDoc.WithQuerySelector(
// 'g.node > title'
@ -301,7 +283,7 @@ func main() {
// '[font-family]'
[]XMLQuery{XMLQuery{Attr: "font-family"}},
func(node *XMLNode) {
node.SetAttr("font-family", "-apple-system,system-ui,BlinkMacSystemFont,Ubuntu,Roboto,Segoe UI,sans-serif")
node.SetAttr("font-family", terraformSvgFontFamily)
},
)
@ -314,69 +296,103 @@ func main() {
for moduleName, module := range status.Modules {
moduleId := regexp.MustCompile(`[.-]`).ReplaceAllString(moduleName, "_")
// drawRect := func(rect *Rect2D) {
// svgDoc.WithQuerySelector(
// // 'g.graph > polygon'
// []XMLQuery{XMLQuery{NodeType: "g", Class: "graph"}, XMLQuery{NodeType: "polygon", DirectChild: true}},
// func(node *XMLNode) {
// coordStrings := []string{}
// coordStrings = append(coordStrings, fmt.Sprintf("%.4f,%.4f", rect.Left-float64(1), rect.Top-float64(1)))
// coordStrings = append(coordStrings, fmt.Sprintf("%.4f,%.4f", rect.Right+float64(1), rect.Top-float64(1)))
// coordStrings = append(coordStrings, fmt.Sprintf("%.4f,%.4f", rect.Right+float64(1), rect.Bottom+float64(1)))
// coordStrings = append(coordStrings, fmt.Sprintf("%.4f,%.4f", rect.Left-float64(1), rect.Bottom+float64(1)))
// toAppend := XMLNode{
// XMLName: xml.Name{Local: "g", Space: "http://www.w3.org/2000/svg"},
// Children: []*XMLNode{
// &XMLNode{
// XMLName: xml.Name{Local: "polygon", Space: "http://www.w3.org/2000/svg"},
// Attrs: []*XMLAttr{
// &XMLAttr{
// Local: "stroke",
// Value: "#ff00ff",
// },
// &XMLAttr{
// Local: "fill",
// Value: "none",
// },
// &XMLAttr{
// Local: "points",
// Value: strings.Join(coordStrings, " "),
// },
// },
// },
// },
// }
// fmt.Println("append!!")
// node.Parent.Children = append(node.Parent.Children, &toAppend)
// },
// )
// }
//fmt.Printf("try %s..\n", moduleId)
svgDoc.WithQuerySelector(
// '[data-dot=module_dns_gandi]' for example
[]XMLQuery{XMLQuery{Attr: "data-dot", AttrValue: moduleId}},
func(node *XMLNode) {
moduleRect := node.GetBoundingBox()
//drawRect(moduleRect)
//fmt.Printf("%s: \n%v\n", moduleId, moduleRect)
for _, resource := range module.Resources {
resourceId := fmt.Sprintf("%s_%s", moduleId, regexp.MustCompile(`[.-]`).ReplaceAllString(resource.DisplayName, "_"))
//fmt.Printf("try %s..\n", resourceId)
svgDoc.WithQuerySelector(
// '[data-dot=module_dns_gandi_gandi_livedns_record_dns_entries]' for example
[]XMLQuery{XMLQuery{Attr: "data-dot", AttrValue: resourceId}},
func(node *XMLNode) {
resourceRect := node.GetBoundingBox()
//drawRect(resourceRect)
// Step 1 center the resources
nudgeX := ((moduleRect.Left + moduleRect.Right) - (resourceRect.Left + resourceRect.Right)) * float64(0.5)
//fmt.Printf("%s: nudgeX: %.4f\n\n", resourceId, nudgeX)
node.SetAttr("transform", fmt.Sprintf("scale(1 1) rotate(0) translate(%d %d)", int(nudgeX), 0))
//node.Translate(nudgeX, 0)
// Step 2 add the terraform action display if there is a planned action
if resource.Plan != "none" && resource.Plan != "" {
actionX := resourceRect.Left + nudgeX + float64(terraformActionOffsetX)
actionY := resourceRect.Top + float64(terraformActionOffsetY)
// start with a circle element
actionContents := []*XMLNode{
&XMLNode{
XMLName: xml.Name{Local: "circle", Space: "http://www.w3.org/2000/svg"},
Attrs: []*XMLAttr{
&XMLAttr{
Local: "cx",
Value: fmt.Sprintf("%.4f", actionX),
},
&XMLAttr{
Local: "fill",
Value: "#ffffff",
},
&XMLAttr{
Local: "cy",
Value: fmt.Sprintf("%.4f", actionY),
},
&XMLAttr{
Local: "r",
Value: strconv.Itoa(terraformActionCircleRadius),
},
},
},
}
// macro to add text to the inside of the circle
createText := func(str, class string, offset int) {
actionContents = append(actionContents, &XMLNode{
XMLName: xml.Name{Local: "text", Space: "http://www.w3.org/2000/svg"},
Attrs: []*XMLAttr{
&XMLAttr{Local: "text-anchor", Value: "middle"},
&XMLAttr{Local: "font-family", Value: terraformSvgFontFamily},
&XMLAttr{Local: "text-size", Value: strconv.Itoa(terraformActionFontSize)},
&XMLAttr{Local: "class", Value: class},
&XMLAttr{Local: "x", Value: fmt.Sprintf("%.4f", actionX+float64(offset*terraformActionTextSpacingX))},
&XMLAttr{Local: "y", Value: fmt.Sprintf("%.4f", actionY+float64(terraformActionTextOffsetY))},
},
Content: []byte(str),
})
}
// add text to the circle depending on the planned action
if resource.Plan == "recreate" {
createText("-", terraformActionRedClass, -1)
createText("/", "", 0)
createText("+", terraformActionGreenClass, 1)
}
if resource.Plan == "update" {
createText("~", terraformActionYellowClass, 0)
}
if resource.Plan == "create" {
createText("+", terraformActionGreenClass, 0)
}
if resource.Plan == "delete" {
createText("-", terraformActionRedClass, 0)
}
// Finally, add all the action content inside a single <g> tag & append to the svg
node.Parent.Children = append(node.Parent.Children, &XMLNode{
XMLName: xml.Name{Local: "g", Space: "http://www.w3.org/2000/svg"},
Attrs: []*XMLAttr{
&XMLAttr{
Local: "class",
Value: "action-circle",
},
&XMLAttr{
Local: "data-dot",
Value: fmt.Sprintf("%s_action_circle", resourceId),
},
},
Children: actionContents,
})
}
},
)
@ -387,23 +403,11 @@ func main() {
}
// this encoder doesn't want to include my custom attributes...
// so we will hijack the id attribute! Record the value of the custom attribute so we can
// set the value of Id later.
var cleanUpSvgRecurse func(*XMLNode)
cleanUpSvgRecurse = func(node *XMLNode) {
// We cant serialize cyclical relationships like parent -> child -> parent
node.Parent = nil
// TODO JK i f ixed the attrs
// // the XML serializer does not like my custom attributes like `data-dot` or `dot`.
// // so we will just hijack the id attribute instead, poggers
// dot := node.GetAttr("data-dot")
// if dot != "" {
// node.SetAttr("id", dot)
// }
// if we don't zero out the content for non-leaf nodes, we will get duplicates of everything
if len(node.Children) != 0 {
node.Content = []byte{}
@ -659,12 +663,7 @@ func (node *XMLNode) GetBoundingBox() *Rect2D {
func (node *XMLNode) GetSVGPolygonPoints() ([][]float64, error) {
toReturn := [][]float64{}
var err error
// endsWithSpace := false
// pointsString := node.GetAttr("points")
// if strings.HasSuffix(pointsString, " ") {
// endsWithSpace = true
// pointsString = strings.TrimSpace(pointsString)
// }
points := strings.Split(node.GetAttr("points"), " ")
for _, point := range points {
coords := strings.Split(point, ",")
@ -690,39 +689,3 @@ func (node *XMLNode) SetSVGPolygonPoints(points [][]float64) {
}
node.SetAttr("points", strings.Join(coordStrings, " "))
}
// func (node *XMLNode) Translate(x, y float64) {
// polygons := node.QuerySelectorAll([]XMLQuery{XMLQuery{NodeType: "polygon"}})
// polygons = append(polygons, node.QuerySelectorAll([]XMLQuery{XMLQuery{NodeType: "polyline"}})...)
// ellipses := node.QuerySelectorAll([]XMLQuery{XMLQuery{NodeType: "ellipse"}})
// texts := node.QuerySelectorAll([]XMLQuery{XMLQuery{NodeType: "text"}})
// for _, polygon := range polygons {
// points, err := polygon.GetSVGPolygonPoints()
// if err == nil {
// for _, point := range points {
// point[0] = point[0] + x
// point[1] = point[1] + y
// }
// polygon.SetSVGPolygonPoints(points)
// }
// }
// for _, ellipse := range ellipses {
// cx, errCx := strconv.ParseFloat(ellipse.GetAttr("cx"), 64)
// cy, errCy := strconv.ParseFloat(ellipse.GetAttr("cy"), 64)
// if errCx == nil && errCy == nil {
// ellipse.SetAttr("cx", fmt.Sprintf("%.4f", cx+x))
// ellipse.SetAttr("cy", fmt.Sprintf("%.4f", cy+y))
// }
// }
// for _, text := range texts {
// text_x, errX := strconv.ParseFloat(text.GetAttr("x"), 64)
// text_y, errY := strconv.ParseFloat(text.GetAttr("y"), 64)
// if errX == nil && errY == nil {
// text.SetAttr("x", fmt.Sprintf("%.4f", text_x+x))
// text.SetAttr("y", fmt.Sprintf("%.4f", text_y+y))
// }
// }
// }

Loading…
Cancel
Save