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  }