/
resource_viewer.go
159 lines (129 loc) · 3.88 KB
/
resource_viewer.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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
/*
Copyright (c) 2019 VMware, Inc. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package component
import (
"encoding/json"
"strings"
"github.com/pkg/errors"
)
// AdjList is an adjacency list - it maps nodes to edges
type AdjList map[string][]Edge
// Edge represents a directed edge in a graph
type Edge struct {
Node string `json:"node"`
Type EdgeType `json:"edge"`
}
// Add adds a directed edge to the adjacency list
func (al AdjList) Add(src string, edge Edge) {
edges, ok := al[src]
if !ok || edges == nil {
edges = make([]Edge, 0)
}
edges = append(edges, edge)
al[src] = edges
}
type NodeStatus string
const (
// NodeStatusOK means a node is in a health state
NodeStatusOK NodeStatus = "ok"
// NodeStatusWarning means ...
NodeStatusWarning NodeStatus = "warning"
// NodeStatusError means ...
NodeStatusError NodeStatus = "error"
)
// EdgeType represents whether a relationship between resources is implicit or explicit
type EdgeType string
const (
// EdgeTypeImplicit is an implicit edge
EdgeTypeImplicit = "implicit"
// EdgeTypeExplicit is an explicit edge
EdgeTypeExplicit = "explicit"
)
// Nodes is a set of graph nodes
type Nodes map[string]Node
// Node is a node in a graph, representing a kubernetes object
// IsNetwork is a hint to the layout engine.
type Node struct {
Name string `json:"name,omitempty"`
APIVersion string `json:"apiVersion,omitempty"`
Kind string `json:"kind,omitempty"`
Status NodeStatus `json:"status,omitempty"`
Details []Component `json:"details,omitempty"`
Path *Link `json:"path,omitempty"`
}
// ResourceViewerConfig is configuration for a resource viewer.
type ResourceViewerConfig struct {
Edges AdjList `json:"edges,omitempty"`
Nodes Nodes `json:"nodes,omitempty"`
Selected string `json:"selected,omitempty"`
}
// ResourceView is a resource viewer component.
type ResourceViewer struct {
base
Config ResourceViewerConfig `json:"config,omitempty"`
}
// NewResourceViewer creates a resource viewer component.
func NewResourceViewer(title string) *ResourceViewer {
return &ResourceViewer{
base: newBase(typeResourceViewer, TitleFromString(title)),
Config: ResourceViewerConfig{
Edges: AdjList{},
Nodes: Nodes{},
},
}
}
func (rv *ResourceViewer) AddEdge(nodeID, childID string, edgeType EdgeType) error {
if _, ok := rv.Config.Nodes[childID]; !ok {
var nodeIDs []string
for k := range rv.Config.Nodes {
nodeIDs = append(nodeIDs, k)
}
return errors.Errorf("node %q does not exist in graph. available [%s]",
childID, strings.Join(nodeIDs, ", "))
}
edge := Edge{
Node: childID,
Type: edgeType,
}
rv.Config.Edges[nodeID] = append(rv.Config.Edges[nodeID], edge)
return nil
}
func (rv *ResourceViewer) AddNode(id string, node Node) {
rv.Config.Nodes[id] = node
}
func (rv *ResourceViewer) Select(id string) {
rv.Config.Selected = id
}
func (rv *ResourceViewer) GetMetadata() Metadata {
return rv.Metadata
}
func (rv *ResourceViewer) Validate() error {
for nodeID, edges := range rv.Config.Edges {
if _, ok := rv.Config.Nodes[nodeID]; !ok {
var nodes []string
for node := range rv.Config.Nodes {
nodes = append(nodes, node)
}
return errors.Errorf("node %q in edges does not have a node entry. existing nodes: %s", nodeID, strings.Join(nodes, ", "))
}
for _, edge := range edges {
if _, ok := rv.Config.Nodes[edge.Node]; !ok {
return errors.Errorf("edge %q from node %q does not have a node entry", edge.Node, nodeID)
}
}
}
return nil
}
type resourceViewerMarshal ResourceViewer
// MarshalJSON implements json.Marshaler
func (rv *ResourceViewer) MarshalJSON() ([]byte, error) {
if err := rv.Validate(); err != nil {
return nil, errors.WithMessage(err, "validate resource viewer component")
}
m := resourceViewerMarshal(*rv)
m.Metadata.Type = typeResourceViewer
m.Metadata.Title = rv.Metadata.Title
return json.Marshal(&m)
}