github.com/mweagle/Sparta@v1.15.0/describe_xplat.go (about) 1 package sparta 2 3 import ( 4 "crypto/sha1" 5 "encoding/base64" 6 "encoding/hex" 7 "encoding/json" 8 "fmt" 9 "math/rand" 10 "strings" 11 12 "github.com/pkg/errors" 13 "github.com/sirupsen/logrus" 14 ) 15 16 // Utility discovery information that is necessary for compilation 17 // in both local and AWS Binary mode 18 19 const ( 20 nodeColorService = "#720502" 21 nodeColorEventSource = "#BF2803" 22 nodeColorLambda = "#F35B05" 23 nodeColorAPIGateway = "#06B5F5" 24 nodeNameAPIGateway = "API Gateway" 25 26 labelWeightNormal = "normal" 27 labelWeightBold = "bolder" 28 ) 29 30 // This is the `go` type that's shuttled through the JSON data 31 // and parsed by the sparta.js script that's executed in the browser 32 type cytoscapeData struct { 33 ID string `json:"id"` 34 Parent string `json:"parent"` 35 Image string `json:"image"` 36 Width string `json:"width"` 37 Height string `json:"height"` 38 BackgroundColor string `json:"backgroundColor,omitempty"` 39 Source string `json:"source,omitempty"` 40 Target string `json:"target,omitempty"` 41 Label string `json:"label,omitempty"` 42 LabelWeight string `json:"labelWeight,omitempty"` 43 DegreeCentrality int `json:"degreeCentrality"` 44 } 45 type cytoscapeNode struct { 46 Data cytoscapeData `json:"data"` 47 Classes string `json:"classes,omitempty"` 48 } 49 type templateResource struct { 50 KeyName string 51 Data string 52 } 53 54 func cytoscapeNodeID(rawData interface{}) (string, error) { 55 bytes, bytesErr := json.Marshal(rawData) 56 if bytesErr != nil { 57 return "", bytesErr 58 } 59 hash := sha1.New() 60 _, writeErr := hash.Write(bytes) 61 if writeErr != nil { 62 return "", writeErr 63 } 64 return hex.EncodeToString(hash.Sum(nil)), nil 65 } 66 67 type descriptionWriter struct { 68 nodes []*cytoscapeNode 69 logger *logrus.Logger 70 } 71 72 func (dw *descriptionWriter) writeNodeWithParent(nodeName string, 73 nodeColor string, 74 nodeImage string, 75 nodeParent string, 76 labelWeight string) error { 77 78 nodeID, nodeErr := cytoscapeNodeID(nodeName) 79 if nodeErr != nil { 80 return errors.Wrapf(nodeErr, 81 "Failed to create nodeID for entry: %s", 82 nodeName) 83 } 84 parentID := "" 85 if nodeParent != "" { 86 tmpParentID, tmpParentIDErr := cytoscapeNodeID(nodeParent) 87 if tmpParentIDErr != nil { 88 return errors.Wrapf(nodeErr, 89 "Failed to create nodeID for entry: %s", 90 nodeParent) 91 } 92 parentID = tmpParentID 93 } 94 if labelWeight == "" { 95 labelWeight = labelWeightNormal 96 } 97 appendNode := &cytoscapeNode{ 98 Data: cytoscapeData{ 99 ID: nodeID, 100 Parent: parentID, 101 Width: "128", 102 Height: "128", 103 Label: strings.Trim(nodeName, "\""), 104 LabelWeight: labelWeight, 105 }, 106 } 107 if nodeImage != "" { 108 resourceItem := templateResourceForKey(nodeImage, dw.logger) 109 if resourceItem != nil { 110 appendNode.Data.Image = fmt.Sprintf("data:image/png;base64,%s", 111 base64.StdEncoding.EncodeToString([]byte(resourceItem.Data))) 112 } 113 } 114 dw.nodes = append(dw.nodes, appendNode) 115 return nil 116 } 117 118 // func (dw *descriptionWriter) writeNode(nodeName string, 119 // nodeColor string, 120 // nodeImage string) error { 121 // return dw.writeNodeWithParent(nodeName, nodeColor, nodeImage, "") 122 // } 123 124 func (dw *descriptionWriter) writeEdge(fromNode string, 125 toNode string, 126 label string) error { 127 128 nodeSource, nodeSourceErr := cytoscapeNodeID(fromNode) 129 if nodeSourceErr != nil { 130 return errors.Wrapf(nodeSourceErr, 131 "Failed to create nodeID for entry: %s", 132 fromNode) 133 } 134 nodeTarget, nodeTargetErr := cytoscapeNodeID(toNode) 135 if nodeTargetErr != nil { 136 return errors.Wrapf(nodeSourceErr, 137 "Failed to create nodeID for entry: %s", 138 toNode) 139 } 140 141 dw.nodes = append(dw.nodes, &cytoscapeNode{ 142 Data: cytoscapeData{ 143 ID: fmt.Sprintf("%d", rand.Uint64()), 144 Source: nodeSource, 145 Target: nodeTarget, 146 Label: label, 147 }, 148 }) 149 return nil 150 } 151 152 func templateResourceForKey(resourceKeyName string, logger *logrus.Logger) *templateResource { 153 var resource *templateResource 154 resourcePath := fmt.Sprintf("/resources/describe/%s", 155 strings.TrimLeft(resourceKeyName, "/")) 156 data, dataErr := _escFSString(false, resourcePath) 157 if dataErr == nil { 158 keyParts := strings.Split(resourcePath, "/") 159 keyName := keyParts[len(keyParts)-1] 160 resource = &templateResource{ 161 KeyName: keyName, 162 Data: data, 163 } 164 logger.WithFields(logrus.Fields{ 165 "Path": resourcePath, 166 "KeyName": keyName, 167 }).Debug("Embedded resource") 168 169 } else { 170 logger.WithFields(logrus.Fields{ 171 "Path": resourcePath, 172 }).Warn("Failed to embed resource") 173 } 174 return resource 175 } 176 177 func templateResourcesForKeys(resourceKeyNames []string, logger *logrus.Logger) []*templateResource { 178 var resources []*templateResource 179 180 for _, eachKey := range resourceKeyNames { 181 loadedResource := templateResourceForKey(eachKey, logger) 182 if loadedResource != nil { 183 resources = append(resources, loadedResource) 184 } 185 } 186 return resources 187 } 188 189 func templateCSSFiles(logger *logrus.Logger) []*templateResource { 190 cssFiles := []string{"bootstrap-4.0.0/dist/css/bootstrap.min.css", 191 "highlight.js/styles/xcode.css", 192 } 193 return templateResourcesForKeys(cssFiles, logger) 194 } 195 196 func templateJSFiles(logger *logrus.Logger) []*templateResource { 197 jsFiles := []string{"jquery/jquery-3.3.1.min.js", 198 "popper/popper.min.js", 199 "bootstrap-4.0.0/dist/js/bootstrap.min.js", 200 "highlight.js/highlight.pack.js", 201 "dagre-0.8.4/dist/dagre.min.js", 202 "cytoscape.js/dist/cytoscape.min.js", 203 "cytoscape.js-dagre/cytoscape-dagre.js", 204 "sparta.js", 205 } 206 return templateResourcesForKeys(jsFiles, logger) 207 } 208 209 func templateImageMap(logger *logrus.Logger) map[string]string { 210 images := []string{"SpartaHelmet256.png", 211 "AWS-Architecture-Icons_PNG/PNG Light/Compute/AWS-Lambda_Lambda-Function_light-bg@4x.png", 212 "AWS-Architecture-Icons_PNG/PNG Light/Management & Governance/AWS-CloudFormation_light-bg@4x.png", 213 } 214 resources := templateResourcesForKeys(images, logger) 215 imageMap := make(map[string]string) 216 for _, eachResource := range resources { 217 imageMap[eachResource.KeyName] = base64.StdEncoding.EncodeToString([]byte(eachResource.Data)) 218 } 219 return imageMap 220 } 221 222 // TODO - this should really be smarter, including 223 // looking at the referred resource to understand it's 224 // type 225 func iconForAWSResource(rawEmitter interface{}) *DescriptionIcon { 226 jsonBytes, jsonBytesErr := json.Marshal(rawEmitter) 227 if jsonBytesErr != nil { 228 jsonBytes = make([]byte, 0) 229 } 230 canonicalRaw := strings.ToLower(string(jsonBytes)) 231 iconMappings := map[string]*DescriptionIcon{ 232 "dynamodb": { 233 Category: "Database", 234 Name: "Amazon-DynamoDB@4x.png", 235 }, 236 "sqs": { 237 Category: "Application Integration", 238 Name: "Amazon-Simple-Queue-Service-SQS@4x.png", 239 }, 240 "sns": { 241 Category: "Application Integration", 242 Name: "Amazon-Simple-Notification-Service-SNS@4x.png", 243 }, 244 "cloudwatch": { 245 Category: "Management & Governance", 246 Name: "Amazon-Simple-Notification-Service-SNS@4x.png", 247 }, 248 "kinesis": { 249 Category: "Analytics", 250 Name: "Amazon-Kinesis@4x.png", 251 }, 252 //lint:ignore ST1018 This is the name of the icon 253 "s3": { 254 Category: "Storage", 255 Name: "Amazon-Simple-Storage-Service-S3@4x.png", 256 }, 257 "codecommit": { 258 Category: "Developer Tools", 259 Name: "AWS-CodeCommit_light-bg.svg", 260 }, 261 } 262 // Return it if we have it... 263 for eachKey, eachIcon := range iconMappings { 264 if strings.Contains(canonicalRaw, eachKey) { 265 return eachIcon 266 } 267 } 268 return nil 269 } 270 271 // DescriptionIcon is the struct that contains the category & icon 272 // to use when rendering a ndoe 273 type DescriptionIcon struct { 274 Category string 275 Name string 276 } 277 278 // DescriptionDisplayInfo encapsulates information that is for display only 279 type DescriptionDisplayInfo struct { 280 SourceNodeColor string 281 SourceIcon *DescriptionIcon 282 } 283 284 // DescriptionTriplet is a node that should be included in the final 285 // describe output. 286 type DescriptionTriplet struct { 287 SourceNodeName string 288 ArcLabel string 289 DisplayInfo *DescriptionDisplayInfo 290 TargetNodeName string 291 } 292 293 // DescriptionInfo is the set of information that represents a DescribeableResource 294 type DescriptionInfo struct { 295 Name string 296 Nodes []*DescriptionTriplet 297 } 298 299 // Describable represents the interface for something that can 300 // provide a description 301 type Describable interface { 302 Describe(targetNodeName string) (*DescriptionInfo, error) 303 }