github.com/myhau/pulumi/pkg/v3@v3.70.2-0.20221116134521-f2775972e587/graph/dotconv/print.go (about)

     1  // Copyright 2016-2018, Pulumi Corporation.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package dotconv converts a resource graph into its DOT digraph equivalent.  This is useful for integration with
    16  // various visualization tools, like Graphviz.  Please see http://www.graphviz.org/content/dot-language for a thorough
    17  // specification of the DOT file format.
    18  package dotconv
    19  
    20  import (
    21  	"bufio"
    22  	"fmt"
    23  	"io"
    24  	"strconv"
    25  	"strings"
    26  
    27  	"github.com/pulumi/pulumi/pkg/v3/graph"
    28  	"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
    29  )
    30  
    31  // Print prints a resource graph.
    32  func Print(g graph.Graph, w io.Writer) error {
    33  	// Allocate a new writer.  In general, we will ignore write errors throughout this function, for simplicity, opting
    34  	// instead to return the result of flushing the buffer at the end, which is generally latching.
    35  	b := bufio.NewWriter(w)
    36  
    37  	// Print the graph header.
    38  	if _, err := b.WriteString("strict digraph {\n"); err != nil {
    39  		return err
    40  	}
    41  
    42  	// Initialize the frontier with unvisited graph vertices.
    43  	queued := make(map[graph.Vertex]bool)
    44  	frontier := make([]graph.Vertex, 0, len(g.Roots()))
    45  	for _, root := range g.Roots() {
    46  		to := root.To()
    47  		queued[to] = true
    48  		frontier = append(frontier, to)
    49  	}
    50  
    51  	// For now, we auto-generate IDs.
    52  	// TODO[pulumi/pulumi#76]: use the object URNs instead, once we have them.
    53  	c := 0
    54  	ids := make(map[graph.Vertex]string)
    55  	getID := func(v graph.Vertex) string {
    56  		if id, has := ids[v]; has {
    57  			return id
    58  		}
    59  		id := "Resource" + strconv.Itoa(c)
    60  		c++
    61  		ids[v] = id
    62  		return id
    63  	}
    64  
    65  	// Now, until the frontier is empty, emit entries into the stream.
    66  	indent := "    "
    67  	emitted := make(map[graph.Vertex]bool)
    68  	for len(frontier) > 0 {
    69  		// Dequeue the head of the frontier.
    70  		v := frontier[0]
    71  		frontier = frontier[1:]
    72  		contract.Assert(!emitted[v])
    73  		emitted[v] = true
    74  
    75  		// Get and lazily allocate the ID for this vertex.
    76  		id := getID(v)
    77  
    78  		// Print this vertex; first its "label" (type) and then its direct dependencies.
    79  		// IDEA: consider serializing properties on the node also.
    80  		if _, err := b.WriteString(fmt.Sprintf("%v%v", indent, id)); err != nil {
    81  			return err
    82  		}
    83  		if label := v.Label(); label != "" {
    84  			if _, err := b.WriteString(fmt.Sprintf(" [label=\"%v\"]", label)); err != nil {
    85  				return err
    86  			}
    87  		}
    88  		if _, err := b.WriteString(";\n"); err != nil {
    89  			return err
    90  		}
    91  
    92  		// Now print out all dependencies as "ID -> {A ... Z}".
    93  		outs := v.Outs()
    94  		if len(outs) > 0 {
    95  			base := fmt.Sprintf("%v%v", indent, id)
    96  			// Print the ID of each dependency and, for those we haven't seen, add them to the frontier.
    97  			for _, out := range outs {
    98  				to := out.To()
    99  				if _, err := b.WriteString(fmt.Sprintf("%s -> %s", base, getID(to))); err != nil {
   100  					return err
   101  				}
   102  
   103  				var attrs []string
   104  				if out.Color() != "" {
   105  					attrs = append(attrs, fmt.Sprintf("color = \"%s\"", out.Color()))
   106  				}
   107  				if out.Label() != "" {
   108  					attrs = append(attrs, fmt.Sprintf("label = \"%s\"", out.Label()))
   109  				}
   110  				if len(attrs) > 0 {
   111  					if _, err := b.WriteString(fmt.Sprintf(" [%s]", strings.Join(attrs, ", "))); err != nil {
   112  						return err
   113  					}
   114  				}
   115  
   116  				if _, err := b.WriteString(";\n"); err != nil {
   117  					return err
   118  				}
   119  
   120  				if _, q := queued[to]; !q {
   121  					queued[to] = true
   122  					frontier = append(frontier, to)
   123  				}
   124  			}
   125  		}
   126  	}
   127  
   128  	// Finish the graph.
   129  	if _, err := b.WriteString("}\n"); err != nil {
   130  		return err
   131  	}
   132  	return b.Flush()
   133  }