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