@shortlink-org/portolan 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +69 -205
- package/cli/init.mjs +285 -0
- package/cli/init.test.mjs +158 -0
- package/cli/portolan.mjs +37 -73
- package/index.html +19 -0
- package/package.json +5 -10
- package/plugins/README.md +66 -27
- package/plugins/portolan-go.wasm +0 -0
- package/portolan.json +52 -73
- package/public/landing/cats/bug-hunter.webp +0 -0
- package/public/landing/cats/compass-nap.webp +0 -0
- package/public/landing/cats/diagrammer.webp +0 -0
- package/public/landing/cats/docs-reader.webp +0 -0
- package/public/landing/cats/laptop.webp +0 -0
- package/public/landing/cats/map-inspector.webp +0 -0
- package/public/landing/cats/server-break.webp +0 -0
- package/public/landing/cats/star-mapper.webp +0 -0
- package/public/landing/cats/system-builder.webp +0 -0
- package/public/landing/cats/thread-tangle.webp +0 -0
- package/public/og.png +0 -0
- package/public/readme-header.webp +0 -0
- package/schema/portolan.schema.json +180 -0
- package/scripts/builtin-plugins.mjs +14 -0
- package/scripts/delivery-presets.mjs +353 -0
- package/scripts/gen.mjs +37 -4
- package/scripts/history.mjs +126 -0
- package/scripts/history.test.mjs +110 -0
- package/scripts/host-plugins/fetch-bsr.mjs +351 -0
- package/{plugins/fetch-bsr/options.schema.json → scripts/host-plugins/fetch-bsr.options.json} +4 -0
- package/scripts/host-plugins/fetch-bsr.test.mjs +212 -0
- package/scripts/host-plugins/fetch-csr.mjs +386 -0
- package/scripts/host-plugins/fetch-csr.test.mjs +178 -0
- package/scripts/host-plugins/fetch-git.mjs +379 -0
- package/scripts/host-plugins/fetch-git.test.mjs +210 -0
- package/scripts/local-api.mjs +22 -4
- package/scripts/local-api.test.mjs +84 -2
- package/scripts/manifest.mjs +3 -2
- package/scripts/package-smoke.mjs +6 -2
- package/scripts/plugin-host.mjs +77 -18
- package/scripts/plugin-host.test.mjs +50 -1
- package/scripts/plugin-wasm-worker.mjs +6 -3
- package/scripts/run-builtin.mjs +6 -26
- package/scripts/schema.mjs +6 -1
- package/src/app/App.tsx +24 -429
- package/src/app/Breadcrumbs.test.ts +7 -0
- package/src/app/Breadcrumbs.tsx +15 -2
- package/src/app/CatalogApp.tsx +436 -0
- package/src/app/Sidebar.tsx +0 -1
- package/src/chat/ChatPanel.tsx +98 -41
- package/src/chat/Composer.tsx +4 -2
- package/src/chat/Conversation.tsx +79 -15
- package/src/chat/Header.tsx +43 -9
- package/src/chat/Starter.tsx +64 -3
- package/src/chat/Steps.tsx +1 -1
- package/src/chat/flags.test.ts +51 -14
- package/src/chat/flags.ts +8 -2
- package/src/chat/page-context.test.ts +51 -0
- package/src/chat/page-context.ts +128 -0
- package/src/chat/prompt.test.ts +49 -1
- package/src/chat/prompt.ts +42 -1
- package/src/chat/tools.ts +7 -2
- package/src/chat/transport.ts +16 -4
- package/src/components/ProblemRow.tsx +182 -0
- package/src/data.ts +20 -5
- package/src/er/ErCanvas.tsx +1 -1
- package/src/graph/DependencyGraph.tsx +10 -1
- package/src/graph/FocusedEventGraph.tsx +1 -1
- package/src/graph/GraphToolbar.tsx +2 -4
- package/src/index.css +220 -2
- package/src/landing/DraggableReveal.tsx +168 -0
- package/src/landing/EstateGraph.tsx +28 -0
- package/src/landing/FlowPlayback.tsx +282 -0
- package/src/landing/HeroMap.tsx +128 -0
- package/src/landing/LandingPage.tsx +757 -0
- package/src/landing/ProductFrame.tsx +68 -0
- package/src/landing/ProductTour.tsx +371 -0
- package/src/landing/catalog.ts +25 -0
- package/src/landing/motion.tsx +52 -0
- package/src/lib/all-problems.ts +28 -0
- package/src/lib/local-api.ts +35 -1
- package/src/lib/setup-info.test.ts +8 -0
- package/src/lib/setup-info.ts +3 -2
- package/src/lib/source-code.ts +2 -1
- package/src/likec4/C4View.tsx +5 -0
- package/src/likec4/InteractiveView.tsx +5 -1
- package/src/map/ContextMapGraph.tsx +47 -20
- package/src/pages/ContextMap.tsx +5 -1
- package/src/pages/FlowDetail.tsx +24 -7
- package/src/pages/Overview.tsx +68 -12
- package/src/pages/Problems.tsx +6 -193
- package/src/pages/Settings.tsx +133 -108
- package/src/pages/settings/DeliverySettings.tsx +229 -0
- package/src/pages/settings/PreferencesSettings.tsx +101 -0
- package/src/routes.test.ts +20 -0
- package/src/routes.ts +13 -1
- package/vite.config.ts +12 -8
- package/catalog/enum_test.go +0 -46
- package/catalog/model.go +0 -932
- package/catalog/roundtrip_test.go +0 -185
- package/catalog/via_test.go +0 -38
- package/go.mod +0 -14
- package/go.sum +0 -14
- package/internal/commands/cargo.go +0 -154
- package/internal/commands/commands.go +0 -143
- package/internal/commands/commands_test.go +0 -323
- package/internal/commands/gradle.go +0 -77
- package/internal/commands/justfile.go +0 -82
- package/internal/commands/makefile.go +0 -106
- package/internal/commands/maven.go +0 -93
- package/internal/commands/packagejson.go +0 -159
- package/internal/commands/pyproject.go +0 -169
- package/internal/commands/taskfile.go +0 -101
- package/internal/commands/testdata/estate/.cargo/config.toml +0 -7
- package/internal/commands/testdata/estate/Makefile +0 -33
- package/internal/commands/testdata/estate/Taskfile.yml +0 -21
- package/internal/commands/testdata/estate/build.gradle.kts +0 -21
- package/internal/commands/testdata/estate/justfile +0 -20
- package/internal/commands/testdata/estate/package.json +0 -13
- package/internal/commands/testdata/estate/pom.xml +0 -29
- package/internal/commands/testdata/estate/pyproject.toml +0 -27
- package/internal/commands/testdata/estate/xtask/src/main.rs +0 -27
- package/internal/commands/testdata/golden/commands.json +0 -305
- package/internal/gohttp/analyze.go +0 -2500
- package/internal/gohttp/endpoints.go +0 -1067
- package/internal/gohttp/roots.go +0 -320
- package/internal/gohttp/typed.go +0 -143
- package/internal/goscan/constants.go +0 -85
- package/internal/goscan/goscan_test.go +0 -227
- package/internal/goscan/names.go +0 -52
- package/internal/goscan/parse_test.go +0 -11
- package/internal/goscan/source.go +0 -36
- package/internal/goscan/tree.go +0 -155
- package/internal/goscan/types.go +0 -99
- package/internal/wsdl/ids.go +0 -127
- package/internal/wsdl/ids_test.go +0 -21
- package/internal/wsdl/model.go +0 -62
- package/internal/wsdl/parse.go +0 -920
- package/internal/wsdl/parse_test.go +0 -133
- package/plugin/describe.go +0 -107
- package/plugin/describe_test.go +0 -114
- package/plugin/protocol.go +0 -117
- package/plugin/schematest/schematest.go +0 -126
- package/plugins/extract-adr/describe.go +0 -19
- package/plugins/extract-adr/describe_test.go +0 -11
- package/plugins/extract-adr/extract.go +0 -153
- package/plugins/extract-adr/extract_test.go +0 -350
- package/plugins/extract-adr/history.go +0 -99
- package/plugins/extract-adr/main.go +0 -65
- package/plugins/extract-adr/parse.go +0 -642
- package/plugins/extract-adr/parse_test.go +0 -405
- package/plugins/extract-asyncapi/describe.go +0 -19
- package/plugins/extract-asyncapi/describe_test.go +0 -11
- package/plugins/extract-asyncapi/extract.go +0 -315
- package/plugins/extract-asyncapi/extract_test.go +0 -197
- package/plugins/extract-asyncapi/main.go +0 -48
- package/plugins/extract-asyncapi/spec.go +0 -151
- package/plugins/extract-commands/describe.go +0 -19
- package/plugins/extract-commands/describe_test.go +0 -11
- package/plugins/extract-commands/extract.go +0 -79
- package/plugins/extract-commands/extract_test.go +0 -92
- package/plugins/extract-commands/main.go +0 -55
- package/plugins/extract-csr/avro.go +0 -258
- package/plugins/extract-csr/describe.go +0 -19
- package/plugins/extract-csr/describe_test.go +0 -11
- package/plugins/extract-csr/extract.go +0 -338
- package/plugins/extract-csr/extract_test.go +0 -374
- package/plugins/extract-csr/jsonschema.go +0 -343
- package/plugins/extract-csr/lock.go +0 -25
- package/plugins/extract-csr/main.go +0 -84
- package/plugins/extract-csr/subject.go +0 -102
- package/plugins/extract-flows/describe.go +0 -19
- package/plugins/extract-flows/describe_test.go +0 -11
- package/plugins/extract-flows/extract.go +0 -91
- package/plugins/extract-flows/main.go +0 -45
- package/plugins/extract-flows/parse.go +0 -593
- package/plugins/extract-flows/parse_test.go +0 -204
- package/plugins/extract-glossary/describe.go +0 -19
- package/plugins/extract-glossary/describe_test.go +0 -11
- package/plugins/extract-glossary/extract.go +0 -115
- package/plugins/extract-glossary/extract_test.go +0 -220
- package/plugins/extract-glossary/main.go +0 -59
- package/plugins/extract-glossary/parse.go +0 -214
- package/plugins/extract-glossary/parse_test.go +0 -203
- package/plugins/extract-go/aggregate.go +0 -214
- package/plugins/extract-go/client.go +0 -409
- package/plugins/extract-go/client_test.go +0 -266
- package/plugins/extract-go/describe.go +0 -19
- package/plugins/extract-go/describe_test.go +0 -11
- package/plugins/extract-go/enum.go +0 -195
- package/plugins/extract-go/enum_test.go +0 -82
- package/plugins/extract-go/event.go +0 -99
- package/plugins/extract-go/extract.go +0 -191
- package/plugins/extract-go/extract_test.go +0 -261
- package/plugins/extract-go/flow.go +0 -1441
- package/plugins/extract-go/flow_test.go +0 -609
- package/plugins/extract-go/httpclient.go +0 -174
- package/plugins/extract-go/httpclient_test.go +0 -296
- package/plugins/extract-go/ids.go +0 -92
- package/plugins/extract-go/layout.go +0 -225
- package/plugins/extract-go/layout_test.go +0 -125
- package/plugins/extract-go/lifecycle.go +0 -324
- package/plugins/extract-go/lifecycle_test.go +0 -108
- package/plugins/extract-go/main.go +0 -79
- package/plugins/extract-go/operation.go +0 -157
- package/plugins/extract-go/source.go +0 -316
- package/plugins/extract-go/transport.go +0 -321
- package/plugins/extract-go/transport_test.go +0 -145
- package/plugins/extract-go/wiring.go +0 -434
- package/plugins/extract-go-nats/describe.go +0 -19
- package/plugins/extract-go-nats/describe_test.go +0 -11
- package/plugins/extract-go-nats/extract.go +0 -177
- package/plugins/extract-go-nats/extract_test.go +0 -389
- package/plugins/extract-go-nats/index.go +0 -379
- package/plugins/extract-go-nats/main.go +0 -42
- package/plugins/extract-go-nats/resolve.go +0 -161
- package/plugins/extract-go-nats/sites.go +0 -224
- package/plugins/extract-graphql/describe.go +0 -19
- package/plugins/extract-graphql/describe_test.go +0 -11
- package/plugins/extract-graphql/extract.go +0 -433
- package/plugins/extract-graphql/extract_test.go +0 -256
- package/plugins/extract-graphql/ids.go +0 -49
- package/plugins/extract-graphql/lex.go +0 -237
- package/plugins/extract-graphql/main.go +0 -51
- package/plugins/extract-graphql/parse.go +0 -621
- package/plugins/extract-graphql/parse_test.go +0 -122
- package/plugins/extract-http-clients/describe.go +0 -19
- package/plugins/extract-http-clients/describe_test.go +0 -11
- package/plugins/extract-http-clients/extract.go +0 -705
- package/plugins/extract-http-clients/extract_test.go +0 -1263
- package/plugins/extract-http-clients/main.go +0 -42
- package/plugins/extract-openapi/describe.go +0 -19
- package/plugins/extract-openapi/describe_test.go +0 -11
- package/plugins/extract-openapi/discover.go +0 -243
- package/plugins/extract-openapi/extract.go +0 -526
- package/plugins/extract-openapi/extract_test.go +0 -545
- package/plugins/extract-openapi/main.go +0 -75
- package/plugins/extract-openapi/spec.go +0 -350
- package/plugins/extract-project/describe.go +0 -19
- package/plugins/extract-project/describe_test.go +0 -11
- package/plugins/extract-project/extract.go +0 -221
- package/plugins/extract-project/extract_test.go +0 -109
- package/plugins/extract-project/main.go +0 -41
- package/plugins/extract-proto/ast.go +0 -125
- package/plugins/extract-proto/consumes.go +0 -77
- package/plugins/extract-proto/describe.go +0 -19
- package/plugins/extract-proto/extract.go +0 -293
- package/plugins/extract-proto/extract_test.go +0 -459
- package/plugins/extract-proto/ids.go +0 -89
- package/plugins/extract-proto/ids_test.go +0 -57
- package/plugins/extract-proto/lex.go +0 -285
- package/plugins/extract-proto/main.go +0 -100
- package/plugins/extract-proto/module.go +0 -120
- package/plugins/extract-proto/parse.go +0 -720
- package/plugins/extract-proto/parse_test.go +0 -307
- package/plugins/extract-proto/provides.go +0 -236
- package/plugins/extract-proto/resolve.go +0 -222
- package/plugins/extract-proto/resolve_test.go +0 -119
- package/plugins/extract-redis/describe.go +0 -19
- package/plugins/extract-redis/describe_test.go +0 -11
- package/plugins/extract-redis/extract.go +0 -183
- package/plugins/extract-redis/extract_test.go +0 -168
- package/plugins/extract-redis/keyspaces.go +0 -469
- package/plugins/extract-redis/main.go +0 -44
- package/plugins/extract-river/describe.go +0 -19
- package/plugins/extract-river/describe_test.go +0 -11
- package/plugins/extract-river/extract.go +0 -507
- package/plugins/extract-river/extract_test.go +0 -132
- package/plugins/extract-river/main.go +0 -42
- package/plugins/extract-sql/ddl.go +0 -893
- package/plugins/extract-sql/ddl_test.go +0 -401
- package/plugins/extract-sql/describe.go +0 -19
- package/plugins/extract-sql/describe_test.go +0 -11
- package/plugins/extract-sql/layout.go +0 -221
- package/plugins/extract-sql/layout_test.go +0 -55
- package/plugins/extract-sql/lineage.go +0 -107
- package/plugins/extract-sql/main.go +0 -141
- package/plugins/extract-sql/maps.go +0 -564
- package/plugins/extract-sql/maps_java.go +0 -117
- package/plugins/extract-sql/maps_rust.go +0 -333
- package/plugins/extract-sql/maps_rust_test.go +0 -70
- package/plugins/extract-sql/maps_test.go +0 -204
- package/plugins/extract-sql/maps_ts.go +0 -398
- package/plugins/extract-sql/maps_ts_test.go +0 -136
- package/plugins/extract-sql/projection.go +0 -70
- package/plugins/extract-sql/projection_test.go +0 -50
- package/plugins/extract-sql/store.go +0 -420
- package/plugins/extract-sql/store_test.go +0 -233
- package/plugins/extract-sql/view.go +0 -295
- package/plugins/extract-watermill/describe.go +0 -19
- package/plugins/extract-watermill/describe_test.go +0 -11
- package/plugins/extract-watermill/extract.go +0 -1228
- package/plugins/extract-watermill/extract_test.go +0 -234
- package/plugins/extract-watermill/main.go +0 -41
- package/plugins/extract-wsdl/describe.go +0 -19
- package/plugins/extract-wsdl/describe_test.go +0 -11
- package/plugins/extract-wsdl/extract.go +0 -148
- package/plugins/extract-wsdl/extract_test.go +0 -45
- package/plugins/extract-wsdl/main.go +0 -53
- package/plugins/fetch-bsr/auth.go +0 -114
- package/plugins/fetch-bsr/bsr.go +0 -240
- package/plugins/fetch-bsr/cache.go +0 -66
- package/plugins/fetch-bsr/describe.go +0 -19
- package/plugins/fetch-bsr/fetch.go +0 -192
- package/plugins/fetch-bsr/fetch_test.go +0 -465
- package/plugins/fetch-bsr/lock.go +0 -71
- package/plugins/fetch-bsr/main.go +0 -117
- package/plugins/fetch-csr/auth.go +0 -95
- package/plugins/fetch-csr/cache.go +0 -69
- package/plugins/fetch-csr/describe.go +0 -19
- package/plugins/fetch-csr/describe_test.go +0 -11
- package/plugins/fetch-csr/fetch.go +0 -254
- package/plugins/fetch-csr/fetch_test.go +0 -494
- package/plugins/fetch-csr/lock.go +0 -95
- package/plugins/fetch-csr/main.go +0 -122
- package/plugins/fetch-csr/registry.go +0 -209
- package/plugins/fetch-git/cache.go +0 -65
- package/plugins/fetch-git/describe.go +0 -19
- package/plugins/fetch-git/describe_test.go +0 -11
- package/plugins/fetch-git/fetch.go +0 -181
- package/plugins/fetch-git/fetch_test.go +0 -332
- package/plugins/fetch-git/git.go +0 -169
- package/plugins/fetch-git/lock.go +0 -72
- package/plugins/fetch-git/main.go +0 -127
- package/plugins/fetch-git/offline.go +0 -39
- package/plugins/fetch-git/pin.go +0 -90
- package/plugins/fetch-git/pin_test.go +0 -74
- package/plugins/gen-backstage/describe.go +0 -17
- package/plugins/gen-backstage/main.go +0 -22
- package/plugins/gen-backstage/plugin.go +0 -473
- package/plugins/gen-backstage/plugin_test.go +0 -145
- package/plugins/gen-backstage.wasm +0 -0
- package/plugins/gen-markdown/adr.go +0 -162
- package/plugins/gen-markdown/aggregate.go +0 -362
- package/plugins/gen-markdown/canonical.go +0 -93
- package/plugins/gen-markdown/context.go +0 -90
- package/plugins/gen-markdown/coverage_test.go +0 -89
- package/plugins/gen-markdown/describe.go +0 -19
- package/plugins/gen-markdown/describe_test.go +0 -11
- package/plugins/gen-markdown/external.go +0 -71
- package/plugins/gen-markdown/flow.go +0 -277
- package/plugins/gen-markdown/glossary.go +0 -76
- package/plugins/gen-markdown/glossary_test.go +0 -148
- package/plugins/gen-markdown/llms.go +0 -302
- package/plugins/gen-markdown/main.go +0 -22
- package/plugins/gen-markdown/markdown.go +0 -254
- package/plugins/gen-markdown/markdown_test.go +0 -100
- package/plugins/gen-markdown/module.go +0 -107
- package/plugins/gen-markdown/plugin.go +0 -39
- package/plugins/gen-markdown/quality_test.go +0 -176
- package/plugins/gen-markdown/redis_test.go +0 -38
- package/plugins/gen-markdown/render.go +0 -344
- package/plugins/gen-markdown/render_test.go +0 -186
- package/plugins/gen-markdown/service.go +0 -456
- package/plugins/gen-markdown/source.go +0 -177
- package/plugins/gen-markdown/store.go +0 -201
- package/plugins/gen-markdown.wasm +0 -0
- package/plugins/gen-mermaid/describe.go +0 -17
- package/plugins/gen-mermaid/main.go +0 -22
- package/plugins/gen-mermaid/plugin.go +0 -104
- package/plugins/gen-mermaid/plugin_test.go +0 -33
- package/plugins/gen-mermaid.wasm +0 -0
- package/plugins/openapi/ids.go +0 -261
- package/plugins/openapi/ids_test.go +0 -98
- package/plugins/verify-codeowners/describe.go +0 -19
- package/plugins/verify-codeowners/describe_test.go +0 -11
- package/plugins/verify-codeowners/main.go +0 -75
- package/plugins/verify-codeowners/match.go +0 -85
- package/plugins/verify-codeowners/match_test.go +0 -47
- package/plugins/verify-codeowners/owners.go +0 -164
- package/plugins/verify-codeowners/owners_test.go +0 -225
- package/plugins/verify-codeowners/parse.go +0 -90
- package/plugins/verify-codeowners/parse_test.go +0 -62
- package/plugins/verify-otel/describe.go +0 -19
- package/plugins/verify-otel/describe_test.go +0 -11
- package/plugins/verify-otel/main.go +0 -53
- package/plugins/verify-otel/match.go +0 -336
- package/plugins/verify-otel/otlp.go +0 -200
- package/plugins/verify-otel/verify.go +0 -734
- package/plugins/verify-otel/verify_test.go +0 -460
- /package/{plugins/fetch-csr/options.schema.json → scripts/host-plugins/fetch-csr.options.json} +0 -0
- /package/{plugins/fetch-git/options.schema.json → scripts/host-plugins/fetch-git.options.json} +0 -0
|
@@ -1,734 +0,0 @@
|
|
|
1
|
-
package main
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
"encoding/json"
|
|
5
|
-
"regexp"
|
|
6
|
-
"sort"
|
|
7
|
-
"strconv"
|
|
8
|
-
"strings"
|
|
9
|
-
|
|
10
|
-
"github.com/shortlink-org/portolan/catalog"
|
|
11
|
-
"github.com/shortlink-org/portolan/plugin"
|
|
12
|
-
)
|
|
13
|
-
|
|
14
|
-
const laneClient = "client"
|
|
15
|
-
const laneBus = "bus"
|
|
16
|
-
|
|
17
|
-
// hop is one span read as a message between two lanes.
|
|
18
|
-
type hop struct {
|
|
19
|
-
from, to catalog.Participant
|
|
20
|
-
kind catalog.StepKind
|
|
21
|
-
ref string
|
|
22
|
-
label string
|
|
23
|
-
status catalog.Status
|
|
24
|
-
entry bool // somebody calling in: the opening of an endpoint flow
|
|
25
|
-
operation string // for an entry, the operation the route answers on
|
|
26
|
-
consume bool
|
|
27
|
-
eventID string
|
|
28
|
-
wire string // for a publish, the event.name it carried
|
|
29
|
-
call *catalog.RpcCall
|
|
30
|
-
file string
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// key is what makes two hops the same hop, for a declared step to match on.
|
|
34
|
-
func (h hop) key() string {
|
|
35
|
-
switch {
|
|
36
|
-
case h.entry:
|
|
37
|
-
return "entry|" + h.to.ID + "|" + h.operation
|
|
38
|
-
case h.kind == catalog.StepEvent && h.consume:
|
|
39
|
-
return "con|" + h.to.ID + "|" + h.eventID
|
|
40
|
-
case h.kind == catalog.StepEvent:
|
|
41
|
-
return "pub|" + h.from.ID + "|" + h.eventID
|
|
42
|
-
case h.kind == catalog.StepRPC:
|
|
43
|
-
return "rpc|" + h.from.ID + "|" + h.ref
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
return "call|" + h.from.ID + "|" + h.to.ID + "|" + h.label
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
type verifier struct {
|
|
50
|
-
l *lookup
|
|
51
|
-
b *plugin.Builder
|
|
52
|
-
children map[string][]*span
|
|
53
|
-
byID map[string]*span
|
|
54
|
-
warned map[string]bool
|
|
55
|
-
|
|
56
|
-
overlays map[string]*overlay // declared flow id -> its raised copy
|
|
57
|
-
observed map[string]*observed
|
|
58
|
-
consumers map[string]map[string]string // event id -> service id -> note
|
|
59
|
-
calls map[string]catalog.RpcCall
|
|
60
|
-
callers map[string]map[string]bool // call id -> service ids that made it
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
type overlay struct {
|
|
64
|
-
flow *catalog.Flow
|
|
65
|
-
svc *catalog.Service
|
|
66
|
-
seen map[string]bool
|
|
67
|
-
traces int
|
|
68
|
-
file string
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
type observed struct {
|
|
72
|
-
svc *catalog.Service
|
|
73
|
-
root hop
|
|
74
|
-
hops []hop
|
|
75
|
-
traces int
|
|
76
|
-
files map[string]bool
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
func verify(req plugin.Request, opts Options) (plugin.Response, error) {
|
|
80
|
-
b := &plugin.Builder{}
|
|
81
|
-
root := req.Input.Root
|
|
82
|
-
|
|
83
|
-
spans, files, err := readTraces(root, opts.Traces)
|
|
84
|
-
if err != nil {
|
|
85
|
-
return plugin.Response{}, err
|
|
86
|
-
}
|
|
87
|
-
if len(files) == 0 {
|
|
88
|
-
b.Warn(root, "no trace files matched "+strings.Join(opts.Traces, ", ")+"; nothing is verified")
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
v := &verifier{
|
|
92
|
-
l: newLookup(&req.Catalog, opts),
|
|
93
|
-
b: b,
|
|
94
|
-
children: map[string][]*span{},
|
|
95
|
-
byID: map[string]*span{},
|
|
96
|
-
warned: map[string]bool{},
|
|
97
|
-
overlays: map[string]*overlay{},
|
|
98
|
-
observed: map[string]*observed{},
|
|
99
|
-
consumers: map[string]map[string]string{},
|
|
100
|
-
calls: map[string]catalog.RpcCall{},
|
|
101
|
-
callers: map[string]map[string]bool{},
|
|
102
|
-
}
|
|
103
|
-
for i := range spans {
|
|
104
|
-
s := &spans[i]
|
|
105
|
-
v.byID[s.spanID] = s
|
|
106
|
-
}
|
|
107
|
-
var roots []*span
|
|
108
|
-
for i := range spans {
|
|
109
|
-
s := &spans[i]
|
|
110
|
-
if s.parentID == "" || v.byID[s.parentID] == nil {
|
|
111
|
-
roots = append(roots, s)
|
|
112
|
-
|
|
113
|
-
continue
|
|
114
|
-
}
|
|
115
|
-
v.children[s.parentID] = append(v.children[s.parentID], s)
|
|
116
|
-
}
|
|
117
|
-
sort.Slice(roots, func(i, j int) bool {
|
|
118
|
-
if roots[i].start != roots[j].start {
|
|
119
|
-
return roots[i].start < roots[j].start
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return roots[i].spanID < roots[j].spanID
|
|
123
|
-
})
|
|
124
|
-
|
|
125
|
-
for _, r := range roots {
|
|
126
|
-
v.trace(r)
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
fragment := v.fragment(req.Input)
|
|
130
|
-
encoded, err := json.MarshalIndent(fragment, "", " ")
|
|
131
|
-
if err != nil {
|
|
132
|
-
return plugin.Response{}, err
|
|
133
|
-
}
|
|
134
|
-
b.File(firstNonEmpty(opts.Out, "observed.json"), string(encoded)+"\n")
|
|
135
|
-
|
|
136
|
-
return b.Response(), nil
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// trace reads one trace from its root: the root's sequence is matched to a
|
|
140
|
-
// declared flow or written down as observed, and every consumer inside it is
|
|
141
|
-
// the opening of a flow of its own, matched the same way.
|
|
142
|
-
func (v *verifier) trace(root *span) {
|
|
143
|
-
hops, spans := v.sequence(root)
|
|
144
|
-
if len(hops) == 0 {
|
|
145
|
-
return
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
for _, h := range hops {
|
|
149
|
-
if h.call != nil {
|
|
150
|
-
if _, seen := v.calls[h.call.ID]; !seen {
|
|
151
|
-
v.calls[h.call.ID] = *h.call
|
|
152
|
-
}
|
|
153
|
-
if v.callers[h.call.ID] == nil {
|
|
154
|
-
v.callers[h.call.ID] = map[string]bool{}
|
|
155
|
-
}
|
|
156
|
-
v.callers[h.call.ID][h.from.ID] = true
|
|
157
|
-
}
|
|
158
|
-
if h.consume && h.eventID != "" {
|
|
159
|
-
if v.consumers[h.eventID] == nil {
|
|
160
|
-
v.consumers[h.eventID] = map[string]string{}
|
|
161
|
-
}
|
|
162
|
-
if _, seen := v.consumers[h.eventID][h.to.ID]; !seen {
|
|
163
|
-
v.consumers[h.eventID][h.to.ID] = "Seen consuming it in " + h.file + "."
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
matched := v.match(hops[0], hops)
|
|
169
|
-
for i := 1; i < len(hops); i++ {
|
|
170
|
-
if hops[i].consume {
|
|
171
|
-
sub, _ := v.sequence(spans[i])
|
|
172
|
-
v.match(sub[0], sub)
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
if !matched {
|
|
176
|
-
v.observe(hops)
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// match finds the declared flow the sequence opens, and raises what the
|
|
181
|
-
// sequence shows. False when no flow opens that way.
|
|
182
|
-
func (v *verifier) match(opening hop, hops []hop) bool {
|
|
183
|
-
var key string
|
|
184
|
-
switch {
|
|
185
|
-
case opening.entry:
|
|
186
|
-
key = openingKey(catalog.StepRPC, opening.to.ID, opening.operation)
|
|
187
|
-
case opening.consume && opening.eventID != "":
|
|
188
|
-
key = openingKey(catalog.StepEvent, opening.to.ID, opening.eventID)
|
|
189
|
-
default:
|
|
190
|
-
return false
|
|
191
|
-
}
|
|
192
|
-
flow := v.l.openings[key]
|
|
193
|
-
if flow == nil {
|
|
194
|
-
return false
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
o := v.overlays[flow.ID]
|
|
198
|
-
if o == nil {
|
|
199
|
-
o = &overlay{flow: copyFlow(flow), svc: v.l.services[opening.to.ID], seen: map[string]bool{}, file: opening.file}
|
|
200
|
-
v.overlays[flow.ID] = o
|
|
201
|
-
}
|
|
202
|
-
o.traces++
|
|
203
|
-
for _, h := range hops {
|
|
204
|
-
o.seen[h.key()] = true
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
return true
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// observe writes a sequence nobody declared down as seen, once per shape.
|
|
211
|
-
func (v *verifier) observe(hops []hop) {
|
|
212
|
-
var parts []string
|
|
213
|
-
for _, h := range hops {
|
|
214
|
-
parts = append(parts, h.from.ID+">"+h.to.ID+":"+string(h.kind)+":"+h.label+":"+h.ref)
|
|
215
|
-
}
|
|
216
|
-
shape := strings.Join(parts, " ")
|
|
217
|
-
|
|
218
|
-
o := v.observed[shape]
|
|
219
|
-
if o == nil {
|
|
220
|
-
o = &observed{svc: v.l.services[hops[0].to.ID], root: hops[0], hops: hops, files: map[string]bool{}}
|
|
221
|
-
if o.svc == nil {
|
|
222
|
-
o.svc = v.l.services[hops[0].from.ID]
|
|
223
|
-
}
|
|
224
|
-
v.observed[shape] = o
|
|
225
|
-
}
|
|
226
|
-
o.traces++
|
|
227
|
-
o.files[hops[0].file] = true
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
// sequence reads a span and everything under it, in the order it ran, into
|
|
231
|
-
// hops. The spans come back alongside so a consumer's subtree can be found.
|
|
232
|
-
//
|
|
233
|
-
// What a span is read as depends on what it sits under: a statement under a
|
|
234
|
-
// database call is the same call, and a publish under a publish of the same
|
|
235
|
-
// event is the same publish - an outbox writes the event and a relay puts it
|
|
236
|
-
// on the bus, each with a producer span, and the event left the service once.
|
|
237
|
-
func (v *verifier) sequence(s *span) ([]hop, []*span) {
|
|
238
|
-
var hops []hop
|
|
239
|
-
var spans []*span
|
|
240
|
-
var walk func(s *span, parentDB bool, publishing string)
|
|
241
|
-
walk = func(s *span, parentDB bool, publishing string) {
|
|
242
|
-
h, isDB := v.hop(s, parentDB, publishing)
|
|
243
|
-
if h != nil {
|
|
244
|
-
hops = append(hops, *h)
|
|
245
|
-
spans = append(spans, s)
|
|
246
|
-
if h.kind == catalog.StepEvent && !h.consume {
|
|
247
|
-
publishing = h.wire
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
kids := v.children[s.spanID]
|
|
251
|
-
sort.Slice(kids, func(i, j int) bool {
|
|
252
|
-
if kids[i].start != kids[j].start {
|
|
253
|
-
return kids[i].start < kids[j].start
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
return kids[i].spanID < kids[j].spanID
|
|
257
|
-
})
|
|
258
|
-
for _, kid := range kids {
|
|
259
|
-
walk(kid, isDB, publishing)
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
walk(s, false, "")
|
|
263
|
-
|
|
264
|
-
return hops, spans
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
var tableOf = regexp.MustCompile(`(?i)\b(?:INTO|FROM|UPDATE|JOIN)\s+([A-Za-z_][A-Za-z0-9_]*)`)
|
|
268
|
-
|
|
269
|
-
// hop reads one span. The second result says whether the span was a database
|
|
270
|
-
// call, so that the statements nested under one - a prepare inside a query -
|
|
271
|
-
// are not read as a second call. publishing is the wire name of the event a
|
|
272
|
-
// producer span above this one is publishing, so that the relay's span under
|
|
273
|
-
// the outbox's is not read as a second publish.
|
|
274
|
-
func (v *verifier) hop(s *span, parentDB bool, publishing string) (*hop, bool) {
|
|
275
|
-
svc := v.l.service(s.service)
|
|
276
|
-
if svc == nil {
|
|
277
|
-
v.warnOnce("service:"+s.service, s.service, "spans from service.name "+strconv.Quote(s.service)+" match no service in the catalog; name it under `services` to say which one it is")
|
|
278
|
-
|
|
279
|
-
return nil, false
|
|
280
|
-
}
|
|
281
|
-
me := v.l.serviceLane(svc)
|
|
282
|
-
a := s.attrs
|
|
283
|
-
op := a["messaging.operation.type"]
|
|
284
|
-
if op == "" {
|
|
285
|
-
op = a["messaging.operation"]
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
switch {
|
|
289
|
-
case s.kind == kindServer && (a["http.route"] != "" || a["url.path"] != ""):
|
|
290
|
-
route := firstNonEmpty(a["http.route"], a["url.path"])
|
|
291
|
-
method := firstNonEmpty(a["http.request.method"], a["http.method"])
|
|
292
|
-
operation, known := v.l.operation(svc, method, route)
|
|
293
|
-
label := operation
|
|
294
|
-
if !known {
|
|
295
|
-
label = method + " " + route
|
|
296
|
-
v.warnOnce("route:"+svc.ID+method+route, svc.ID, "answers on "+method+" "+route+", which no interface it provides declares; the flow opens on the route rather than an operation")
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
return &hop{
|
|
300
|
-
from: catalog.Participant{ID: laneClient, Kind: catalog.ParticipantActor}, to: me,
|
|
301
|
-
kind: catalog.StepRPC, label: label, status: catalog.StatusVerified,
|
|
302
|
-
entry: known, operation: operation, file: s.file,
|
|
303
|
-
}, false
|
|
304
|
-
|
|
305
|
-
case s.kind == kindServer && a["rpc.service"] != "":
|
|
306
|
-
return &hop{
|
|
307
|
-
from: catalog.Participant{ID: laneClient, Kind: catalog.ParticipantActor}, to: me,
|
|
308
|
-
kind: catalog.StepRPC, label: a["rpc.method"], status: catalog.StatusVerified,
|
|
309
|
-
entry: true, operation: a["rpc.method"], file: s.file,
|
|
310
|
-
}, false
|
|
311
|
-
|
|
312
|
-
case s.kind == kindClient && a["http.request.method"] != "":
|
|
313
|
-
// A call over HTTP to another service. The route is the request's
|
|
314
|
-
// path; the provider is whichever service declares an operation on
|
|
315
|
-
// that verb and route, read the way the OpenAPI extractor records
|
|
316
|
-
// them - so the call and the method share one id.
|
|
317
|
-
method := a["http.request.method"]
|
|
318
|
-
path := requestPath(a)
|
|
319
|
-
if path == "" {
|
|
320
|
-
return nil, false
|
|
321
|
-
}
|
|
322
|
-
h := &hop{from: me, kind: catalog.StepRPC, file: s.file}
|
|
323
|
-
if peer, op, iface := v.l.httpProvider(method, path); peer != nil {
|
|
324
|
-
id := iface + "/" + op
|
|
325
|
-
h.to = v.l.serviceLane(peer)
|
|
326
|
-
h.ref = id
|
|
327
|
-
h.label = op
|
|
328
|
-
h.status = catalog.StatusVerified
|
|
329
|
-
h.call = &catalog.RpcCall{ID: id, Peer: peer.ID, Status: catalog.StatusVerified, Source: s.file}
|
|
330
|
-
} else {
|
|
331
|
-
host := firstNonEmpty(a["server.address"], a["net.peer.name"], "http")
|
|
332
|
-
h.to = catalog.Participant{ID: strings.ReplaceAll(host, ".", "-"), Kind: catalog.ParticipantUnknown, Label: host}
|
|
333
|
-
h.label = method + " " + path
|
|
334
|
-
h.status = catalog.StatusUnresolved
|
|
335
|
-
v.warnOnce("http:"+method+path, svc.ID, "calls "+method+" "+path+" on "+host+", which no service in the catalog answers on; the hop is unresolved")
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
return h, false
|
|
339
|
-
|
|
340
|
-
case s.kind == kindClient && a["rpc.service"] != "":
|
|
341
|
-
iface, method := a["rpc.service"], a["rpc.method"]
|
|
342
|
-
id := iface + "/" + method
|
|
343
|
-
h := &hop{from: me, kind: catalog.StepRPC, label: method, file: s.file}
|
|
344
|
-
if v.l.rpcIDs[id] {
|
|
345
|
-
h.ref = id
|
|
346
|
-
}
|
|
347
|
-
if peer := v.l.providers[iface]; peer != nil {
|
|
348
|
-
h.to = v.l.serviceLane(peer)
|
|
349
|
-
h.status = catalog.StatusVerified
|
|
350
|
-
h.call = &catalog.RpcCall{ID: id, Peer: peer.ID, Status: catalog.StatusVerified, Source: s.file}
|
|
351
|
-
} else {
|
|
352
|
-
pkg := iface
|
|
353
|
-
if i := strings.LastIndex(iface, "."); i >= 0 {
|
|
354
|
-
pkg = iface[:i]
|
|
355
|
-
}
|
|
356
|
-
h.to = catalog.Participant{ID: strings.ReplaceAll(pkg, ".", "-"), Kind: catalog.ParticipantUnknown, Label: pkg}
|
|
357
|
-
h.status = catalog.StatusUnresolved
|
|
358
|
-
h.call = &catalog.RpcCall{ID: id, Peer: pkg, Status: catalog.StatusUnresolved, Source: s.file}
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
return h, false
|
|
362
|
-
|
|
363
|
-
case a["db.system.name"] != "" || a["db.system"] != "" || a["db.operation.name"] != "":
|
|
364
|
-
if parentDB {
|
|
365
|
-
return nil, true
|
|
366
|
-
}
|
|
367
|
-
verb := strings.ToUpper(firstNonEmpty(a["db.operation.name"], a["db.operation"]))
|
|
368
|
-
switch verb {
|
|
369
|
-
case "", "BEGIN", "COMMIT", "ROLLBACK":
|
|
370
|
-
return nil, verb != ""
|
|
371
|
-
}
|
|
372
|
-
label := verb
|
|
373
|
-
if m := tableOf.FindStringSubmatch(s.name); m != nil {
|
|
374
|
-
label = verb + " " + m[1]
|
|
375
|
-
}
|
|
376
|
-
to := me
|
|
377
|
-
if lane, ok := v.l.storeLane(svc); ok {
|
|
378
|
-
to = lane
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
return &hop{from: me, to: to, kind: catalog.StepCall, label: label, status: catalog.StatusVerified, file: s.file}, true
|
|
382
|
-
|
|
383
|
-
case s.kind == kindProducer || op == "publish" || op == "create" || op == "send":
|
|
384
|
-
wire := a["event.name"]
|
|
385
|
-
if wire == "" {
|
|
386
|
-
v.warnOnce("publish:"+svc.ID, svc.ID, "publishes without an event.name attribute on the span, so the trace cannot say which event; the hop is left out")
|
|
387
|
-
|
|
388
|
-
return nil, false
|
|
389
|
-
}
|
|
390
|
-
id, ok := v.l.event(svc, wire)
|
|
391
|
-
if ok {
|
|
392
|
-
// Checked on every span of the publish: the inner one, the
|
|
393
|
-
// relay's, is the one that names the bus the event actually
|
|
394
|
-
// went over.
|
|
395
|
-
v.checkChannel(svc, id, a["messaging.destination.name"])
|
|
396
|
-
}
|
|
397
|
-
if wire == publishing {
|
|
398
|
-
return nil, false
|
|
399
|
-
}
|
|
400
|
-
h := &hop{from: me, to: catalog.Participant{ID: laneBus, Kind: catalog.ParticipantBroker}, kind: catalog.StepEvent, label: lastSegment(wire), status: catalog.StatusVerified, eventID: id, wire: wire, file: s.file}
|
|
401
|
-
if ok {
|
|
402
|
-
h.ref = id
|
|
403
|
-
} else {
|
|
404
|
-
h.status = catalog.StatusUnresolved
|
|
405
|
-
v.warnOnce("event:"+wire, svc.ID, "publishes "+strconv.Quote(wire)+", which matches no event of its own in the catalog; name it under `events`")
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
return h, false
|
|
409
|
-
|
|
410
|
-
case s.kind == kindConsumer || op == "receive" || op == "process" || op == "deliver":
|
|
411
|
-
wire := a["event.name"]
|
|
412
|
-
if wire == "" {
|
|
413
|
-
v.warnOnce("consume:"+svc.ID, svc.ID, "consumes without an event.name attribute on the span, so the trace cannot say which event; the hop is left out")
|
|
414
|
-
|
|
415
|
-
return nil, false
|
|
416
|
-
}
|
|
417
|
-
id, ok := v.l.event(nil, wire)
|
|
418
|
-
h := &hop{from: catalog.Participant{ID: laneBus, Kind: catalog.ParticipantBroker}, to: me, kind: catalog.StepEvent, label: lastSegment(wire), status: catalog.StatusVerified, consume: true, eventID: id, file: s.file}
|
|
419
|
-
if ok {
|
|
420
|
-
h.ref = id
|
|
421
|
-
} else {
|
|
422
|
-
h.status = catalog.StatusUnresolved
|
|
423
|
-
h.eventID = ""
|
|
424
|
-
v.warnOnce("event:"+wire, svc.ID, "consumes "+strconv.Quote(wire)+", which matches no event in the catalog; name it under `events`")
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
return h, false
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
return nil, false
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
func (v *verifier) warnOnce(key, ref, message string) {
|
|
434
|
-
if v.warned[key] {
|
|
435
|
-
return
|
|
436
|
-
}
|
|
437
|
-
v.warned[key] = true
|
|
438
|
-
v.b.Warn(ref, message)
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
// requestPath is the path of a client request, off url.full or url.path.
|
|
442
|
-
func requestPath(a map[string]string) string {
|
|
443
|
-
if p := a["url.path"]; p != "" {
|
|
444
|
-
return p
|
|
445
|
-
}
|
|
446
|
-
full := a["url.full"]
|
|
447
|
-
if full == "" {
|
|
448
|
-
full = a["http.url"]
|
|
449
|
-
}
|
|
450
|
-
if full == "" {
|
|
451
|
-
return ""
|
|
452
|
-
}
|
|
453
|
-
if i := strings.Index(full, "://"); i >= 0 {
|
|
454
|
-
full = full[i+3:]
|
|
455
|
-
if j := strings.Index(full, "/"); j >= 0 {
|
|
456
|
-
full = full[j:]
|
|
457
|
-
} else {
|
|
458
|
-
full = "/"
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
if i := strings.IndexAny(full, "?#"); i >= 0 {
|
|
462
|
-
full = full[:i]
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
return full
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
// fragment is what goes back: every declared flow a trace opened, with the
|
|
469
|
-
// steps it showed raised; every sequence nobody declared; and the consumers
|
|
470
|
-
// and calls the traces prove, hung on copies of the entities that own them.
|
|
471
|
-
func (v *verifier) fragment(in plugin.Input) catalog.Catalog {
|
|
472
|
-
out := catalog.Catalog{
|
|
473
|
-
GeneratedAt: in.GeneratedAt,
|
|
474
|
-
Commit: in.Commit,
|
|
475
|
-
Contexts: []catalog.BoundedContext{},
|
|
476
|
-
Defs: map[string]catalog.TypeDef{},
|
|
477
|
-
Flows: []catalog.Flow{},
|
|
478
|
-
Adrs: []catalog.Adr{},
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
ids := make([]string, 0, len(v.overlays))
|
|
482
|
-
for id := range v.overlays {
|
|
483
|
-
ids = append(ids, id)
|
|
484
|
-
}
|
|
485
|
-
sort.Strings(ids)
|
|
486
|
-
for _, id := range ids {
|
|
487
|
-
o := v.overlays[id]
|
|
488
|
-
raise(o)
|
|
489
|
-
out.Flows = append(out.Flows, *o.flow)
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
shapes := make([]string, 0, len(v.observed))
|
|
493
|
-
for shape := range v.observed {
|
|
494
|
-
shapes = append(shapes, shape)
|
|
495
|
-
}
|
|
496
|
-
sort.Strings(shapes)
|
|
497
|
-
for _, shape := range shapes {
|
|
498
|
-
out.Flows = append(out.Flows, v.observedFlow(v.observed[shape]))
|
|
499
|
-
}
|
|
500
|
-
sort.SliceStable(out.Flows, func(i, j int) bool { return out.Flows[i].Slug < out.Flows[j].Slug })
|
|
501
|
-
|
|
502
|
-
// Edges, on the services that hold them.
|
|
503
|
-
services := map[string]*catalog.Service{}
|
|
504
|
-
touch := func(svc *catalog.Service) *catalog.Service {
|
|
505
|
-
if got := services[svc.ID]; got != nil {
|
|
506
|
-
return got
|
|
507
|
-
}
|
|
508
|
-
copied := &catalog.Service{ID: svc.ID, Slug: svc.Slug, Name: svc.Name, Provides: []catalog.RpcService{}, Consumes: []catalog.RpcCall{}, Aggregates: []catalog.Aggregate{}}
|
|
509
|
-
services[svc.ID] = copied
|
|
510
|
-
|
|
511
|
-
return copied
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
callIDs := make([]string, 0, len(v.calls))
|
|
515
|
-
for id := range v.calls {
|
|
516
|
-
callIDs = append(callIDs, id)
|
|
517
|
-
}
|
|
518
|
-
sort.Strings(callIDs)
|
|
519
|
-
for _, id := range callIDs {
|
|
520
|
-
callers := make([]string, 0, len(v.callers[id]))
|
|
521
|
-
for svcID := range v.callers[id] {
|
|
522
|
-
callers = append(callers, svcID)
|
|
523
|
-
}
|
|
524
|
-
sort.Strings(callers)
|
|
525
|
-
for _, svcID := range callers {
|
|
526
|
-
svc := touch(v.l.services[svcID])
|
|
527
|
-
svc.Consumes = append(svc.Consumes, v.calls[id])
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
eventIDs := make([]string, 0, len(v.consumers))
|
|
532
|
-
for id := range v.consumers {
|
|
533
|
-
eventIDs = append(eventIDs, id)
|
|
534
|
-
}
|
|
535
|
-
sort.Strings(eventIDs)
|
|
536
|
-
for _, eventID := range eventIDs {
|
|
537
|
-
owner := v.l.eventOwner[eventID]
|
|
538
|
-
agg := v.l.aggregate[eventID]
|
|
539
|
-
ev := v.l.events[eventID]
|
|
540
|
-
if owner == nil || agg == nil || ev == nil {
|
|
541
|
-
continue
|
|
542
|
-
}
|
|
543
|
-
svc := touch(owner)
|
|
544
|
-
var target *catalog.Aggregate
|
|
545
|
-
for i := range svc.Aggregates {
|
|
546
|
-
if svc.Aggregates[i].ID == agg.ID {
|
|
547
|
-
target = &svc.Aggregates[i]
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
if target == nil {
|
|
551
|
-
svc.Aggregates = append(svc.Aggregates, catalog.Aggregate{
|
|
552
|
-
ID: agg.ID, Slug: agg.Slug, Name: agg.Name, Readme: agg.Readme, Root: agg.Root,
|
|
553
|
-
Entities: []catalog.Block{}, ValueObjects: []catalog.Block{}, Operations: []catalog.Operation{}, Events: []catalog.Event{},
|
|
554
|
-
})
|
|
555
|
-
target = &svc.Aggregates[len(svc.Aggregates)-1]
|
|
556
|
-
}
|
|
557
|
-
consumers := []catalog.EventConsumer{}
|
|
558
|
-
byService := v.consumers[eventID]
|
|
559
|
-
names := make([]string, 0, len(byService))
|
|
560
|
-
for name := range byService {
|
|
561
|
-
names = append(names, name)
|
|
562
|
-
}
|
|
563
|
-
sort.Strings(names)
|
|
564
|
-
for _, name := range names {
|
|
565
|
-
consumers = append(consumers, catalog.EventConsumer{Service: name, Status: catalog.StatusVerified, Note: byService[name]})
|
|
566
|
-
}
|
|
567
|
-
target.Events = append(target.Events, catalog.Event{ID: ev.ID, Slug: ev.Slug, Name: ev.Name, Versions: ev.Versions, Consumers: consumers})
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
svcIDs := make([]string, 0, len(services))
|
|
571
|
-
for id := range services {
|
|
572
|
-
svcIDs = append(svcIDs, id)
|
|
573
|
-
}
|
|
574
|
-
sort.Strings(svcIDs)
|
|
575
|
-
contexts := map[string]*catalog.BoundedContext{}
|
|
576
|
-
var order []string
|
|
577
|
-
for _, id := range svcIDs {
|
|
578
|
-
ctxID := contextOf(id)
|
|
579
|
-
ctx := contexts[ctxID]
|
|
580
|
-
if ctx == nil {
|
|
581
|
-
src := v.l.contexts[ctxID]
|
|
582
|
-
ctx = &catalog.BoundedContext{ID: ctxID, Slug: ctxID, Services: []catalog.Service{}}
|
|
583
|
-
if src != nil {
|
|
584
|
-
ctx.Slug, ctx.Name = src.Slug, src.Name
|
|
585
|
-
}
|
|
586
|
-
contexts[ctxID] = ctx
|
|
587
|
-
order = append(order, ctxID)
|
|
588
|
-
}
|
|
589
|
-
ctx.Services = append(ctx.Services, *services[id])
|
|
590
|
-
}
|
|
591
|
-
for _, id := range order {
|
|
592
|
-
out.Contexts = append(out.Contexts, *contexts[id])
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
return out
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
// raise marks the declared steps the traces showed. Only `declared` moves:
|
|
599
|
-
// `unresolved` means the far end is not in the catalog, and a trace does not
|
|
600
|
-
// put it there.
|
|
601
|
-
func raise(o *overlay) {
|
|
602
|
-
first := firstStep(o.flow.Steps)
|
|
603
|
-
note := "Seen running in " + o.file + " (" + plural(o.traces, "trace") + ")."
|
|
604
|
-
walkSteps(o.flow.Steps, func(step *catalog.Step) {
|
|
605
|
-
if step.Status != catalog.StatusDeclared {
|
|
606
|
-
return
|
|
607
|
-
}
|
|
608
|
-
var shown bool
|
|
609
|
-
switch step.Kind {
|
|
610
|
-
case catalog.StepRPC:
|
|
611
|
-
if step == first && step.Ref == "" {
|
|
612
|
-
shown = o.seen["entry|"+step.To+"|"+step.Label]
|
|
613
|
-
} else if step.Ref != "" {
|
|
614
|
-
shown = o.seen["rpc|"+step.From+"|"+step.Ref]
|
|
615
|
-
}
|
|
616
|
-
case catalog.StepEvent:
|
|
617
|
-
if step.Ref != "" {
|
|
618
|
-
shown = o.seen["pub|"+step.From+"|"+step.Ref] || o.seen["con|"+step.To+"|"+step.Ref]
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
if !shown {
|
|
622
|
-
return
|
|
623
|
-
}
|
|
624
|
-
step.Status = catalog.StatusVerified
|
|
625
|
-
if step.Note == "" {
|
|
626
|
-
step.Note = note
|
|
627
|
-
}
|
|
628
|
-
})
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
// observedFlow writes a sequence nobody declared down as a flow of its own.
|
|
632
|
-
func (v *verifier) observedFlow(o *observed) catalog.Flow {
|
|
633
|
-
var lanes []catalog.Participant
|
|
634
|
-
lane := func(p catalog.Participant) string {
|
|
635
|
-
for _, existing := range lanes {
|
|
636
|
-
if existing.ID == p.ID {
|
|
637
|
-
return p.ID
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
lanes = append(lanes, p)
|
|
641
|
-
|
|
642
|
-
return p.ID
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
steps := catalog.FlowNodes{}
|
|
646
|
-
for i, h := range o.hops {
|
|
647
|
-
from := lane(h.from)
|
|
648
|
-
to := lane(h.to)
|
|
649
|
-
steps = append(steps, &catalog.Step{
|
|
650
|
-
Type: "step", ID: "s" + strconv.Itoa(i+1), From: from, To: to,
|
|
651
|
-
Kind: h.kind, Ref: h.ref, Label: h.label, Status: h.status,
|
|
652
|
-
})
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
files := make([]string, 0, len(o.files))
|
|
656
|
-
for f := range o.files {
|
|
657
|
-
files = append(files, f)
|
|
658
|
-
}
|
|
659
|
-
sort.Strings(files)
|
|
660
|
-
|
|
661
|
-
svc := o.svc
|
|
662
|
-
slugged := "observed-" + svc.Slug + "-" + slugify(o.root.label)
|
|
663
|
-
|
|
664
|
-
return catalog.Flow{
|
|
665
|
-
ID: "flow." + slugged,
|
|
666
|
-
Slug: slugged,
|
|
667
|
-
Name: "Observed: " + o.root.label,
|
|
668
|
-
Summary: "Read from " + plural(o.traces, "trace") + " in " + strings.Join(files, ", ") + ". No flow in the catalog opens this way, so the sequence is written down as it was seen.",
|
|
669
|
-
Source: files[0],
|
|
670
|
-
Owner: contextOf(svc.ID),
|
|
671
|
-
Participants: lanes,
|
|
672
|
-
Steps: steps,
|
|
673
|
-
}
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
// copyFlow is a deep copy, through the wire form, so that raising never writes
|
|
677
|
-
// into the catalog the plugin was handed.
|
|
678
|
-
func copyFlow(flow *catalog.Flow) *catalog.Flow {
|
|
679
|
-
raw, err := json.Marshal(flow)
|
|
680
|
-
if err != nil {
|
|
681
|
-
panic(err)
|
|
682
|
-
}
|
|
683
|
-
var out catalog.Flow
|
|
684
|
-
if err := json.Unmarshal(raw, &out); err != nil {
|
|
685
|
-
panic(err)
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
return &out
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
// checkChannel holds the channel a publish span names against the one the
|
|
692
|
-
// catalog declares for the event. A trace is the one witness that the two
|
|
693
|
-
// agree, and a publisher whose code and whose telemetry name different
|
|
694
|
-
// channels has a bug in one of them; the hop is kept, because the event did
|
|
695
|
-
// go out, and the disagreement is said once.
|
|
696
|
-
func (v *verifier) checkChannel(svc *catalog.Service, eventID, destination string) {
|
|
697
|
-
ev := v.l.events[eventID]
|
|
698
|
-
if ev == nil || ev.Wire == nil || ev.Wire.Channel == "" || destination == "" || destination == ev.Wire.Channel {
|
|
699
|
-
return
|
|
700
|
-
}
|
|
701
|
-
v.warnOnce("channel:"+eventID, svc.ID, "publishes "+strconv.Quote(ev.Wire.Name)+" on "+strconv.Quote(destination)+", but the catalog says it goes on "+strconv.Quote(ev.Wire.Channel))
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
func lastSegment(wire string) string {
|
|
705
|
-
if i := strings.LastIndex(wire, "."); i >= 0 {
|
|
706
|
-
return wire[i+1:]
|
|
707
|
-
}
|
|
708
|
-
|
|
709
|
-
return wire
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
var notSlug = regexp.MustCompile(`[^a-z0-9]+`)
|
|
713
|
-
|
|
714
|
-
func slugify(s string) string {
|
|
715
|
-
return strings.Trim(notSlug.ReplaceAllString(strings.ToLower(s), "-"), "-")
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
func plural(n int, noun string) string {
|
|
719
|
-
if n == 1 {
|
|
720
|
-
return "1 " + noun
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
return strconv.Itoa(n) + " " + noun + "s"
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
func firstNonEmpty(values ...string) string {
|
|
727
|
-
for _, value := range values {
|
|
728
|
-
if value != "" {
|
|
729
|
-
return value
|
|
730
|
-
}
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
return ""
|
|
734
|
-
}
|