-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtopology.go
More file actions
325 lines (312 loc) · 10.4 KB
/
topology.go
File metadata and controls
325 lines (312 loc) · 10.4 KB
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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
package cli
import (
"fmt"
"path/filepath"
"github.com/randomcodespace/codeiq/internal/graph"
"github.com/randomcodespace/codeiq/internal/query"
"github.com/spf13/cobra"
)
func init() {
registerSubcommand(newTopologyCommand)
}
// newTopologyCommand assembles the `topology` parent and its sub-views.
// The bare parent renders the full service map; sub-views surface specific
// analyses (service-detail / blast-radius / bottlenecks / circular / dead).
func newTopologyCommand() *cobra.Command {
var graphDir string
cmd := &cobra.Command{
Use: "topology [path]",
Short: "Show the service topology map (services + cross-service connections).",
Long: `Render the service topology: every SERVICE node ServiceDetector
synthesised plus every cross-service runtime edge (CALLS / PRODUCES /
CONSUMES / QUERIES / CONNECTS_TO / PUBLISHES / LISTENS / SENDS_TO /
RECEIVES_FROM / INVOKES_RMI / EXPORTS_RMI). The output carries
` + "`services`" + `, ` + "`connections`" + `, and ` + "`service_count`" + ` / ` + "`connection_count`" +
` aggregates.
Subcommands narrow the view:
service-detail <name> endpoints / entities / guards / databases /
queues for one service.
blast-radius <node-id> nodes reachable from the given node.
bottlenecks services ordered by total connection count.
circular cross-service dependency cycles.
dead services with no incoming runtime edges.`,
Example: ` # Bare topology map
codeiq topology .
# Detail for one service
codeiq topology service-detail checkout-svc
# Blast radius for a node
codeiq topology blast-radius svc:checkout-svc --depth 3`,
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
root, err := resolvePath(args)
if err != nil {
return err
}
gdir := graphDir
if gdir == "" {
gdir = filepath.Join(root, ".codeiq", "graph", "codeiq.kuzu")
}
store, err := graph.Open(gdir)
if err != nil {
return fmt.Errorf("open graph %s: %w", gdir, err)
}
defer store.Close()
t := query.NewTopology(store)
out, err := t.GetTopology()
if err != nil {
return err
}
return printOrdered(cmd.OutOrStdout(), out)
},
}
cmd.Flags().StringVar(&graphDir, "graph-dir", "",
"Path to the Kuzu graph store (default: <path>/.codeiq/graph/codeiq.kuzu).")
cmd.AddCommand(newTopologyServiceDetail())
cmd.AddCommand(newTopologyBlastRadius())
cmd.AddCommand(newTopologyBottlenecks())
cmd.AddCommand(newTopologyCircular())
cmd.AddCommand(newTopologyDead())
cmd.AddCommand(newTopologyPath())
return cmd
}
func newTopologyServiceDetail() *cobra.Command {
var graphDir string
cmd := &cobra.Command{
Use: "service-detail <name> [path]",
Short: "Show endpoints / entities / guards / databases / queues for one service.",
Long: `Render the detail object for the named SERVICE — endpoints,
entities, guards, databases, and queues that ServiceDetector pivoted under
this service via CONTAINS edges. Use ` + "`codeiq find services`" + ` to list
candidate names.`,
Example: ` codeiq topology service-detail checkout-svc
codeiq topology service-detail web-ui /repo
codeiq topology service-detail notifier --graph-dir /tmp/scratch.kuzu`,
Args: cobra.MinimumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
name := args[0]
root, err := resolvePath(args[1:])
if err != nil {
return err
}
gdir := graphDir
if gdir == "" {
gdir = filepath.Join(root, ".codeiq", "graph", "codeiq.kuzu")
}
store, err := graph.Open(gdir)
if err != nil {
return fmt.Errorf("open graph %s: %w", gdir, err)
}
defer store.Close()
t := query.NewTopology(store)
out, err := t.ServiceDetail(name)
if err != nil {
return err
}
return printOrdered(cmd.OutOrStdout(), out)
},
}
cmd.Flags().StringVar(&graphDir, "graph-dir", "",
"Path to the Kuzu graph store (default: <path>/.codeiq/graph/codeiq.kuzu).")
return cmd
}
func newTopologyBlastRadius() *cobra.Command {
var (
graphDir string
depth int
)
cmd := &cobra.Command{
Use: "blast-radius <node-id> [path]",
Short: "Show nodes reachable from the given node, up to --depth hops.",
Long: `Render the blast-radius object for the given node — the set of
reachable nodes (via any runtime edge) and the services those nodes belong
to. Default depth is 5 hops; cap with ` + "`--depth`" + ` for tighter scopes.`,
Example: ` codeiq topology blast-radius svc:checkout-svc
codeiq topology blast-radius svc:checkout-svc --depth 3
codeiq topology blast-radius method:com.foo.Bar#baz --depth 2`,
Args: cobra.MinimumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
id := args[0]
root, err := resolvePath(args[1:])
if err != nil {
return err
}
gdir := graphDir
if gdir == "" {
gdir = filepath.Join(root, ".codeiq", "graph", "codeiq.kuzu")
}
store, err := graph.Open(gdir)
if err != nil {
return fmt.Errorf("open graph %s: %w", gdir, err)
}
defer store.Close()
t := query.NewTopology(store)
out, err := t.BlastRadius(id, depth)
if err != nil {
return err
}
return printOrdered(cmd.OutOrStdout(), out)
},
}
cmd.Flags().StringVar(&graphDir, "graph-dir", "",
"Path to the Kuzu graph store (default: <path>/.codeiq/graph/codeiq.kuzu).")
cmd.Flags().IntVar(&depth, "depth", 5,
"Maximum traversal depth in hops (default: 5).")
return cmd
}
func newTopologyBottlenecks() *cobra.Command {
var graphDir string
cmd := &cobra.Command{
Use: "bottlenecks [path]",
Short: "List services ordered by total connection count (in + out).",
Long: `Render services ranked by combined connection degree.
Services with zero connections are omitted. Sort order: total desc, then
service name asc — deterministic for diffing.`,
Example: ` codeiq topology bottlenecks
codeiq topology bottlenecks /repo
codeiq topology bottlenecks --graph-dir /tmp/scratch.kuzu`,
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
root, err := resolvePath(args)
if err != nil {
return err
}
gdir := graphDir
if gdir == "" {
gdir = filepath.Join(root, ".codeiq", "graph", "codeiq.kuzu")
}
store, err := graph.Open(gdir)
if err != nil {
return fmt.Errorf("open graph %s: %w", gdir, err)
}
defer store.Close()
t := query.NewTopology(store)
out, err := t.FindBottlenecks()
if err != nil {
return err
}
return printOrdered(cmd.OutOrStdout(), out)
},
}
cmd.Flags().StringVar(&graphDir, "graph-dir", "",
"Path to the Kuzu graph store (default: <path>/.codeiq/graph/codeiq.kuzu).")
return cmd
}
func newTopologyCircular() *cobra.Command {
var graphDir string
cmd := &cobra.Command{
Use: "circular [path]",
Short: "Show cross-service dependency cycles.",
Long: `Render the list of cross-service cycles — each entry is a
service-name slice with the same first and last element (closed loop).
Cycles are normalised so the smallest service name is at index 0 for
stable comparison across runs.`,
Example: ` codeiq topology circular
codeiq topology circular /repo
codeiq topology circular --graph-dir /tmp/scratch.kuzu`,
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
root, err := resolvePath(args)
if err != nil {
return err
}
gdir := graphDir
if gdir == "" {
gdir = filepath.Join(root, ".codeiq", "graph", "codeiq.kuzu")
}
store, err := graph.Open(gdir)
if err != nil {
return fmt.Errorf("open graph %s: %w", gdir, err)
}
defer store.Close()
t := query.NewTopology(store)
out, err := t.FindCircular()
if err != nil {
return err
}
return printOrdered(cmd.OutOrStdout(), out)
},
}
cmd.Flags().StringVar(&graphDir, "graph-dir", "",
"Path to the Kuzu graph store (default: <path>/.codeiq/graph/codeiq.kuzu).")
return cmd
}
func newTopologyDead() *cobra.Command {
var graphDir string
cmd := &cobra.Command{
Use: "dead [path]",
Short: "List services with no incoming runtime edges.",
Long: `Render services that have no incoming cross-service runtime
edge. Useful for spotting services nobody consumes (potential dead code,
or services with only outbound publishes). Excludes structural CONTAINS
edges by design.`,
Example: ` codeiq topology dead
codeiq topology dead /repo
codeiq topology dead --graph-dir /tmp/scratch.kuzu`,
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
root, err := resolvePath(args)
if err != nil {
return err
}
gdir := graphDir
if gdir == "" {
gdir = filepath.Join(root, ".codeiq", "graph", "codeiq.kuzu")
}
store, err := graph.Open(gdir)
if err != nil {
return fmt.Errorf("open graph %s: %w", gdir, err)
}
defer store.Close()
t := query.NewTopology(store)
out, err := t.FindDeadServices()
if err != nil {
return err
}
return printOrdered(cmd.OutOrStdout(), out)
},
}
cmd.Flags().StringVar(&graphDir, "graph-dir", "",
"Path to the Kuzu graph store (default: <path>/.codeiq/graph/codeiq.kuzu).")
return cmd
}
func newTopologyPath() *cobra.Command {
var graphDir string
cmd := &cobra.Command{
Use: "path <source> <target> [path]",
Short: "Find the shortest cross-service path between two services.",
Long: `Render the list of hops between two services via BFS over the
cross-service runtime adjacency. Each hop is ` + "`{from, to, type}`" + `; the
` + "`type`" + ` is the lowercased edge kind that linked the two hops in the
underlying graph.`,
Example: ` codeiq topology path checkout-svc payments-svc
codeiq topology path web-ui notifier /repo
codeiq topology path checkout-svc fulfilment --graph-dir /tmp/scratch.kuzu`,
Args: cobra.MinimumNArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
source := args[0]
target := args[1]
root, err := resolvePath(args[2:])
if err != nil {
return err
}
gdir := graphDir
if gdir == "" {
gdir = filepath.Join(root, ".codeiq", "graph", "codeiq.kuzu")
}
store, err := graph.Open(gdir)
if err != nil {
return fmt.Errorf("open graph %s: %w", gdir, err)
}
defer store.Close()
t := query.NewTopology(store)
out, err := t.FindPath(source, target)
if err != nil {
return err
}
return printOrdered(cmd.OutOrStdout(), out)
},
}
cmd.Flags().StringVar(&graphDir, "graph-dir", "",
"Path to the Kuzu graph store (default: <path>/.codeiq/graph/codeiq.kuzu).")
return cmd
}