From 9804deaa10ad44b16380214900702c08395e9355 Mon Sep 17 00:00:00 2001 From: Shevchenko Alexey Date: Sun, 9 Jun 2024 20:28:28 +0300 Subject: [PATCH 01/16] unix-sockets WIP, tests broken, cluster broken --- .github/workflows/ci.yml | 6 +- .golangci.yml | 12 +- README.md | 6 +- cmd/decompose/main.go | 32 +++--- go.mod | 26 ++--- go.sum | 54 ++++----- internal/builder/builder.go | 2 +- internal/builder/builder_test.go | 2 +- internal/builder/csv_test.go | 24 ++-- internal/builder/dot_test.go | 48 ++++---- internal/builder/json_test.go | 4 +- internal/builder/puml_test.go | 50 ++++---- internal/builder/sdsl_test.go | 44 ++++---- internal/builder/stat.go | 11 +- internal/builder/tree_test.go | 24 ++-- internal/builder/yaml_test.go | 28 ++--- internal/client/defaults.go | 167 +++++++++++++++++---------- internal/client/docker.go | 172 ++++++++++++++++++++-------- internal/client/docker_test.go | 55 ++++----- internal/client/inodes.go | 182 ++++++++++++++++++++++++++++++ internal/cluster/groups_test.go | 32 +++--- internal/cluster/layers.go | 28 +++-- internal/cluster/layers_test.go | 54 ++++----- internal/cluster/node.go | 3 +- internal/cluster/node_test.go | 6 +- internal/cluster/rules.go | 9 +- internal/cluster/rules_test.go | 52 ++++----- internal/graph/build.go | 49 +++++--- internal/graph/build_test.go | 32 +++--- internal/graph/compress.go | 8 +- internal/graph/compress_test.go | 120 ++++++++++---------- internal/graph/config.go | 2 +- internal/graph/connection.go | 40 ++++--- internal/graph/connection_test.go | 6 +- internal/graph/conngroup.go | 2 +- internal/graph/conngroup_test.go | 42 +++---- internal/graph/container.go | 25 ++-- internal/graph/container_test.go | 16 +-- internal/graph/load.go | 4 - internal/graph/load_test.go | 7 +- internal/graph/netproto.go | 23 ++-- internal/graph/netproto_test.go | 4 +- internal/graph/netstat.go | 32 ++++-- internal/graph/netstat_test.go | 7 ++ internal/node/node_test.go | 14 +-- internal/node/port.go | 8 +- internal/node/port_test.go | 14 +-- internal/node/ports_test.go | 2 +- internal/structurizr/utils.go | 12 +- 49 files changed, 962 insertions(+), 640 deletions(-) create mode 100644 internal/client/inodes.go diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0de8821..cb1aa8a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,7 +22,7 @@ jobs: with: go-version: ^1.22 - name: golangci-lint - uses: golangci/golangci-lint-action@v3 + uses: golangci/golangci-lint-action@v5 - name: goreleaser-check uses: goreleaser/goreleaser-action@v5 with: @@ -43,8 +43,8 @@ jobs: if: ${{ github.event_name == 'pull_request' }} run: make test - name: test-coverage - if: ${{ false && github.event_name == 'push' }} - uses: paambaati/codeclimate-action@v5.0.0 + if: ${{ github.event_name == 'push' }} + uses: paambaati/codeclimate-action@v6.0.0 env: CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }} with: diff --git a/.golangci.yml b/.golangci.yml index 712a2ad..2b45a63 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -11,26 +11,16 @@ linters: enable-all: true disable: - gochecknoglobals - - exhaustivestruct - nonamedreturns - - structcheck - tagliatelle - - nosnakecase - exhaustruct - inamedparam - exhaustive - - interfacer - varnamelen - - scopelint - - deadcode - depguard - - maligned - - varcheck - intrange - - ifshort - ireturn - gofumpt - - golint - gci linters-settings: @@ -69,9 +59,9 @@ issues: - govet # fieldalignment issue, ignored for yaml readability - path: ._test\.go linters: - - goerr113 - gocritic - errcheck - maintidx + - err113 - funlen - dupl diff --git a/README.md b/README.md index f2f2b41..774ac98 100644 --- a/README.md +++ b/README.md @@ -55,14 +55,18 @@ Closest analogs, i can find, that not suit my needs very well: - fast, scans ~470 containers with ~4000 connections in around 5 sec - auto-clusterization based on graph topology - deep inspection mode, in wich connections between procesess inside containers, also collected and shown +- unix-sockets connections - 100% test-coverage + ## known limitations - only established and listen connections are listed (but script like [snapshots.sh](examples/snapshots.sh) can beat this) - `composer-yaml` is not intended to be working out from the box, it can lack some of crucial information (even in `-full` mode), or may contains cycles between nodes (removing `links` section in services may help), its main purpose is for system overview -- [gephi](https://github.com/gephi/gephi) fails to load edges from resulting graphviz, this can be fixed by any auto-replacement tool: `sed -i 's/->/ -> /g' myfile.dot` +- [gephi](https://github.com/gephi/gephi) fails to load edges from resulting graphviz, this can be fixed by any auto-replacement + tool: `sed -i 's/->/ -> /g' myfile.dot` +- unix-sockets works only in root mode on linux, this process involves inode matching to find correct connections ## installation diff --git a/cmd/decompose/main.go b/cmd/decompose/main.go index e754591..0704e3b 100644 --- a/cmd/decompose/main.go +++ b/cmd/decompose/main.go @@ -45,7 +45,7 @@ var ( var ( fSilent, fVersion bool fHelp, fLocal bool - fFull, fNoLoops bool + fUnix, fNoLoops bool fDeep, fCompress bool fProto, fFormat string fOut, fFollow string @@ -85,14 +85,14 @@ func setupFlags() { flag.BoolVar(&fVersion, "version", false, "show version") flag.BoolVar(&fHelp, "help", false, "show this help") flag.BoolVar(&fLocal, "local", false, "skip external hosts") - flag.BoolVar(&fFull, "full", false, "extract full process info: (cmd, args, env) and volumes info") + flag.BoolVar(&fUnix, "unix", false, "extract unix-sockets connection (requires linux/root mode)") flag.BoolVar(&fNoLoops, "no-loops", false, "remove connection loops (node to itself) from output") flag.BoolVar(&fDeep, "deep", false, "process-based introspection") flag.BoolVar(&fCompress, "compress", false, "compress graph") flag.StringVar(&fOut, "out", defaultOutput, "output: filename or \"-\" for stdout") flag.StringVar(&fMeta, "meta", "", "json file with metadata for enrichment") - flag.StringVar(&fProto, "proto", defaultProto, "protocol to scan: tcp, udp or all") + flag.StringVar(&fProto, "proto", defaultProto, "protocol to scan: tcp, udp, unix or all") flag.StringVar(&fFollow, "follow", "", "follow only this container by name(s), comma-separated or from @file") flag.StringVar( &fCluster, @@ -191,7 +191,7 @@ func makeClusterizer( high = 1.0 ) - rv = cluster.NewLayers(b, min(high, max(low, simf))) + rv = cluster.NewLayers(b, min(high, max(low, simf)), "") } else { cr := cluster.NewRules(b, nil) @@ -282,6 +282,12 @@ func prepareConfig() ( } } + if fCompress { + cmp := graph.NewCompressor(bildr, "", defaultDiff, true) + + bildr, nwr = cmp, cmp + } + if fCluster != "" { cb, err := makeClusterizer(bildr, fFormat, fCluster) if err != nil { @@ -291,20 +297,10 @@ func prepareConfig() ( bildr, nwr = cb, cb } - if fCompress { - cmp := graph.NewCompressor(bildr, defaultDiff, true) - - bildr, nwr = cmp, cmp - } - skipKeys := []string{} if fSkipEnv != "" { - if fFull { - skipKeys = strings.Split(fSkipEnv, ",") - } else { - log.Println("skip-env makes no sense without full info - ignoring") - } + skipKeys = strings.Split(fSkipEnv, ",") } cfg = &graph.Config{ @@ -313,9 +309,9 @@ func prepareConfig() ( Proto: proto, Follow: loadSet(fFollow), OnlyLocal: fLocal, - FullInfo: fFull, Deep: fDeep, NoLoops: fNoLoops, + Unix: fUnix, SkipEnv: skipKeys, } @@ -384,6 +380,10 @@ func doBuild( if runtime.GOOS == linuxOS && os.Geteuid() == 0 { opts = append(opts, client.WithNsEnter(client.Nsenter)) mode = client.LinuxNsenter + } else if cfg.Unix { + log.Println("unix-connections requested in non-root mode, ignoring") + + cfg.Unix = false } cli, err := client.NewDocker(append(opts, client.WithMode(mode))...) diff --git a/go.mod b/go.mod index ac1cf2c..8795c9f 100644 --- a/go.mod +++ b/go.mod @@ -3,23 +3,23 @@ module github.com/s0rg/decompose go 1.22 require ( - github.com/docker/docker v26.0.0+incompatible - github.com/emicklei/dot v1.6.1 - github.com/expr-lang/expr v1.16.3 - github.com/prometheus/procfs v0.13.0 + github.com/docker/docker v26.1.4+incompatible + github.com/emicklei/dot v1.6.2 + github.com/expr-lang/expr v1.16.9 + github.com/prometheus/procfs v0.15.1 github.com/s0rg/set v1.2.0 github.com/s0rg/trie v1.3.3 gopkg.in/yaml.v3 v3.0.1 ) require ( - github.com/Microsoft/go-winio v0.6.1 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect github.com/containerd/log v0.1.0 // indirect github.com/distribution/reference v0.6.0 // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/kr/pretty v0.3.0 // indirect @@ -29,17 +29,15 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect github.com/pkg/errors v0.9.1 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.50.0 // indirect - go.opentelemetry.io/otel v1.25.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 // indirect + go.opentelemetry.io/otel v1.27.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0 // indirect - go.opentelemetry.io/otel/metric v1.25.0 // indirect + go.opentelemetry.io/otel/metric v1.27.0 // indirect go.opentelemetry.io/otel/sdk v1.22.0 // indirect - go.opentelemetry.io/otel/trace v1.25.0 // indirect - golang.org/x/mod v0.17.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.19.0 // indirect + go.opentelemetry.io/otel/trace v1.27.0 // indirect + golang.org/x/net v0.24.0 // indirect + golang.org/x/sys v0.21.0 // indirect golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.20.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gotest.tools/v3 v3.5.0 // indirect ) diff --git a/go.sum b/go.sum index d76e73f..e844188 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= @@ -11,21 +11,21 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v26.0.0+incompatible h1:Ng2qi+gdKADUa/VM+6b6YaY2nlZhk/lVJiKR/2bMudU= -github.com/docker/docker v26.0.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v26.1.4+incompatible h1:vuTpXDuoga+Z38m1OZHzl7NKisKWaWlhjQk7IDPSLsU= +github.com/docker/docker v26.1.4+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/emicklei/dot v1.6.1 h1:ujpDlBkkwgWUY+qPId5IwapRW/xEoligRSYjioR6DFI= -github.com/emicklei/dot v1.6.1/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s= -github.com/expr-lang/expr v1.16.3 h1:NLldf786GffptcXNxxJx5dQ+FzeWDKChBDqOOwyK8to= -github.com/expr-lang/expr v1.16.3/go.mod h1:uCkhfG+x7fcZ5A5sXHKuQ07jGZRl6J0FCAaf2k4PtVQ= +github.com/emicklei/dot v1.6.2 h1:08GN+DD79cy/tzN6uLCT84+2Wk9u+wvqP+Hkx/dIR8A= +github.com/emicklei/dot v1.6.2/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s= +github.com/expr-lang/expr v1.16.9 h1:WUAzmR0JNI9JCiF0/ewwHB1gmcGw5wW7nWt8gc6PpCI= +github.com/expr-lang/expr v1.16.9/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= -github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= @@ -60,8 +60,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/procfs v0.13.0 h1:GqzLlQyfsPbaEHaQkO7tbDlriv/4o5Hudv6OXHGKX7o= -github.com/prometheus/procfs v0.13.0/go.mod h1:cd4PFCR54QLnGKPaKGA6l+cfuNXtht43ZKY6tow0Y1g= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/s0rg/set v1.2.0 h1:53b207YMktNQJXYei/oHuTR5oOO2e9+eieZOncYsh9g= @@ -74,20 +74,20 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.50.0 h1:cEPbyTSEHlQR89XVlyo78gqluF8Y3oMeBkXGWzQsfXY= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.50.0/go.mod h1:DKdbWcT4GH1D0Y3Sqt/PFXt2naRKDWtU+eE6oLdFNA8= -go.opentelemetry.io/otel v1.25.0 h1:gldB5FfhRl7OJQbUHt/8s0a7cE8fbsPAtdpRaApKy4k= -go.opentelemetry.io/otel v1.25.0/go.mod h1:Wa2ds5NOXEMkCmUou1WA7ZBfLTHWIsp034OVD7AO+Vg= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 h1:9l89oX4ba9kHbBol3Xin3leYJ+252h0zszDtBwyKe2A= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0/go.mod h1:XLZfZboOJWHNKUv7eH0inh0E9VV6eWDFB/9yJyTLPp0= +go.opentelemetry.io/otel v1.27.0 h1:9BZoF3yMK/O1AafMiQTVu0YDj5Ea4hPhxCs7sGva+cg= +go.opentelemetry.io/otel v1.27.0/go.mod h1:DMpAK8fzYRzs+bi3rS5REupisuqTheUlSZJ1WnZaPAQ= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0 h1:9M3+rhx7kZCIQQhQRYaZCdNu1V73tm4TvXs2ntl98C4= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0/go.mod h1:noq80iT8rrHP1SfybmPiRGc9dc5M8RPmGvtwo7Oo7tc= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0 h1:FyjCyI9jVEfqhUh2MoSkmolPjfh5fp2hnV0b0irxH4Q= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0/go.mod h1:hYwym2nDEeZfG/motx0p7L7J1N1vyzIThemQsb4g2qY= -go.opentelemetry.io/otel/metric v1.25.0 h1:LUKbS7ArpFL/I2jJHdJcqMGxkRdxpPHE0VU/D4NuEwA= -go.opentelemetry.io/otel/metric v1.25.0/go.mod h1:rkDLUSd2lC5lq2dFNrX9LGAbINP5B7WBkC78RXCpH5s= +go.opentelemetry.io/otel/metric v1.27.0 h1:hvj3vdEKyeCi4YaYfNjv2NUje8FqKqUY8IlF0FxV/ik= +go.opentelemetry.io/otel/metric v1.27.0/go.mod h1:mVFgmRlhljgBiuk/MP/oKylr4hs85GZAylncepAX/ak= go.opentelemetry.io/otel/sdk v1.22.0 h1:6coWHw9xw7EfClIC/+O31R8IY3/+EiRFHevmHafB2Gw= go.opentelemetry.io/otel/sdk v1.22.0/go.mod h1:iu7luyVGYovrRpe2fmj3CVKouQNdTOkxtLzPvPz1DOc= -go.opentelemetry.io/otel/trace v1.25.0 h1:tqukZGLwQYRIFtSQM2u2+yfMVTgGVeqRLPUYx1Dq6RM= -go.opentelemetry.io/otel/trace v1.25.0/go.mod h1:hCCs70XM/ljO+BeQkyFnbK28SBIJ/Emuha+ccrCRT7I= +go.opentelemetry.io/otel/trace v1.27.0 h1:IqYb813p7cmbHk0a5y6pD5JPakbVfftRXABGt5/Rscw= +go.opentelemetry.io/otel/trace v1.27.0/go.mod h1:6RiD1hkAprV4/q+yd2ln1HG9GoPx39SuvvstaLBl+l4= go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -95,8 +95,6 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= -golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -106,25 +104,21 @@ golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY= -golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/internal/builder/builder.go b/internal/builder/builder.go index 4bcb52a..3fb6641 100644 --- a/internal/builder/builder.go +++ b/internal/builder/builder.go @@ -51,7 +51,7 @@ func Create(kind string) (b graph.NamedBuilderWriter, ok bool) { func SupportCluster(n string) (yes bool) { switch n { - case KindStructurizr, KindSTAT, KindPlantUML: + case KindStructurizr, KindSTAT, KindDOT, KindPlantUML: return true } diff --git a/internal/builder/builder_test.go b/internal/builder/builder_test.go index 9758c2a..adc425a 100644 --- a/internal/builder/builder_test.go +++ b/internal/builder/builder_test.go @@ -24,13 +24,13 @@ func TestSupportCluster(t *testing.T) { t.Parallel() does := []string{ + builder.KindDOT, builder.KindSTAT, builder.KindStructurizr, builder.KindPlantUML, } doesnt := []string{ - builder.KindDOT, builder.KindJSON, builder.KindTREE, builder.KindYAML, diff --git a/internal/builder/csv_test.go b/internal/builder/csv_test.go index 8184b0b..39a72dc 100644 --- a/internal/builder/csv_test.go +++ b/internal/builder/csv_test.go @@ -20,8 +20,8 @@ func TestCSVGolden(t *testing.T) { Image: "node-image", Ports: makeTestPorts( []*node.Port{ - {Kind: "tcp", Value: 1}, - {Kind: "tcp", Value: 2}, + {Kind: "tcp", Value: "1"}, + {Kind: "tcp", Value: "2"}, }...), Networks: []string{"test-net"}, Meta: &node.Meta{ @@ -37,7 +37,7 @@ func TestCSVGolden(t *testing.T) { ID: "node-2", Name: "2", Ports: makeTestPorts([]*node.Port{ - {Kind: "tcp", Value: 2}, + {Kind: "tcp", Value: "2"}, }...), Networks: []string{"test-net"}, Meta: &node.Meta{ @@ -53,7 +53,7 @@ func TestCSVGolden(t *testing.T) { ID: "node-3", Name: "3", Ports: makeTestPorts([]*node.Port{ - {Kind: "tcp", Value: 3}, + {Kind: "tcp", Value: "3"}, }...), Networks: []string{"test-net"}, Meta: &node.Meta{ @@ -69,49 +69,49 @@ func TestCSVGolden(t *testing.T) { bld.AddEdge(&node.Edge{ SrcID: "node-2", DstID: "node-1", - Port: &node.Port{Kind: "tcp", Value: 1}, + Port: &node.Port{Kind: "tcp", Value: "1"}, }) bld.AddEdge(&node.Edge{ SrcID: "node-2", DstID: "node-1", - Port: &node.Port{Kind: "tcp", Value: 2}, + Port: &node.Port{Kind: "tcp", Value: "2"}, }) bld.AddEdge(&node.Edge{ SrcID: "node-2", DstID: "node-1", - Port: &node.Port{Kind: "tcp", Value: 3}, + Port: &node.Port{Kind: "tcp", Value: "3"}, }) bld.AddEdge(&node.Edge{ SrcID: "node-2", DstID: "node-3", - Port: &node.Port{Kind: "tcp", Value: 3}, + Port: &node.Port{Kind: "tcp", Value: "3"}, }) bld.AddEdge(&node.Edge{ SrcID: "node-1", DstID: "node-3", - Port: &node.Port{Kind: "tcp", Value: 3}, + Port: &node.Port{Kind: "tcp", Value: "3"}, }) bld.AddEdge(&node.Edge{ SrcID: "node-1", DstID: "node-2", - Port: &node.Port{Kind: "tcp", Value: 2}, + Port: &node.Port{Kind: "tcp", Value: "2"}, }) bld.AddEdge(&node.Edge{ SrcID: "node-1", DstID: "3", - Port: &node.Port{Kind: "tcp", Value: 3}, + Port: &node.Port{Kind: "tcp", Value: "3"}, }) bld.AddEdge(&node.Edge{ SrcID: "3", DstID: "node-1", - Port: &node.Port{Kind: "tcp", Value: 3}, + Port: &node.Port{Kind: "tcp", Value: "3"}, }) var buf bytes.Buffer diff --git a/internal/builder/dot_test.go b/internal/builder/dot_test.go index 572cfe5..0b3a5c7 100644 --- a/internal/builder/dot_test.go +++ b/internal/builder/dot_test.go @@ -19,8 +19,8 @@ func TestDOTGolden(t *testing.T) { Image: "node-image", Cluster: "c1", Ports: makeTestPorts([]*node.Port{ - {Kind: "tcp", Value: 1}, - {Kind: "tcp", Value: 2}, + {Kind: "tcp", Value: "1"}, + {Kind: "tcp", Value: "2"}, }...), Networks: []string{"test-net"}, Meta: &node.Meta{ @@ -36,8 +36,8 @@ func TestDOTGolden(t *testing.T) { Image: "node-image", Cluster: "c1", Ports: makeTestPorts([]*node.Port{ - {Kind: "tcp", Value: 1}, - {Kind: "tcp", Value: 2}, + {Kind: "tcp", Value: "1"}, + {Kind: "tcp", Value: "2"}, }...), Networks: []string{"test-net"}, Meta: &node.Meta{ @@ -51,8 +51,8 @@ func TestDOTGolden(t *testing.T) { Image: "node-image", Cluster: "c3", Ports: makeTestPorts([]*node.Port{ - {Kind: "tcp", Value: 1}, - {Kind: "tcp", Value: 2}, + {Kind: "tcp", Value: "1"}, + {Kind: "tcp", Value: "2"}, }...), Networks: []string{"test-net"}, Meta: &node.Meta{ @@ -65,74 +65,74 @@ func TestDOTGolden(t *testing.T) { Name: "2", Cluster: "c2", Ports: makeTestPorts([]*node.Port{ - {Kind: "tcp", Value: 2}, + {Kind: "tcp", Value: "2"}, }...), }) bld.AddEdge(&node.Edge{ SrcID: "2", DstID: "node-1", - Port: &node.Port{Kind: "tcp", Value: 1}, + Port: &node.Port{Kind: "tcp", Value: "1"}, }) bld.AddEdge(&node.Edge{ SrcID: "2", DstID: "node-1", - Port: &node.Port{Kind: "tcp", Value: 2}, + Port: &node.Port{Kind: "tcp", Value: "2"}, }) bld.AddEdge(&node.Edge{ SrcID: "2", DstID: "node-1", - Port: &node.Port{Kind: "tcp", Value: 3}, + Port: &node.Port{Kind: "tcp", Value: "3"}, }) bld.AddEdge(&node.Edge{ SrcID: "node-1", DstID: "2", - Port: &node.Port{Kind: "tcp", Value: 2}, + Port: &node.Port{Kind: "tcp", Value: "2"}, }) bld.AddEdge(&node.Edge{ SrcID: "node-1", DstID: "2", - Port: &node.Port{Kind: "tcp", Value: 3}, + Port: &node.Port{Kind: "tcp", Value: "3"}, }) bld.AddEdge(&node.Edge{ SrcID: "node-1", DstID: "3", - Port: &node.Port{Kind: "tcp", Value: 3}, + Port: &node.Port{Kind: "tcp", Value: "3"}, }) bld.AddEdge(&node.Edge{ SrcID: "3", DstID: "node-1", - Port: &node.Port{Kind: "tcp", Value: 3}, + Port: &node.Port{Kind: "tcp", Value: "3"}, }) bld.AddEdge(&node.Edge{ SrcID: "c1", DstID: "c2", - Port: &node.Port{Kind: "tcp", Value: 2}, + Port: &node.Port{Kind: "tcp", Value: "2"}, }) bld.AddEdge(&node.Edge{ SrcID: "c1", DstID: "c2", - Port: &node.Port{Kind: "tcp", Value: 2}, + Port: &node.Port{Kind: "tcp", Value: "2"}, }) bld.AddEdge(&node.Edge{ SrcID: "c2", DstID: "c1", - Port: &node.Port{Kind: "tcp", Value: 1}, + Port: &node.Port{Kind: "tcp", Value: "1"}, }) bld.AddEdge(&node.Edge{ SrcID: "c2", DstID: "c1", - Port: &node.Port{Kind: "tcp", Value: 1}, + Port: &node.Port{Kind: "tcp", Value: "1"}, }) bld.AddEdge(&node.Edge{ @@ -150,37 +150,37 @@ func TestDOTGolden(t *testing.T) { bld.AddEdge(&node.Edge{ SrcID: "node-1", DstID: "node-2", - Port: &node.Port{Kind: "tcp", Value: 1}, + Port: &node.Port{Kind: "tcp", Value: "1"}, }) bld.AddEdge(&node.Edge{ SrcID: "node-1", DstID: "node-2", - Port: &node.Port{Kind: "tcp", Value: 1}, + Port: &node.Port{Kind: "tcp", Value: "1"}, }) bld.AddEdge(&node.Edge{ SrcID: "node-2", DstID: "node-1", - Port: &node.Port{Kind: "tcp", Value: 2}, + Port: &node.Port{Kind: "tcp", Value: "2"}, }) bld.AddEdge(&node.Edge{ SrcID: "node-2", DstID: "node-1", - Port: &node.Port{Kind: "tcp", Value: 2}, + Port: &node.Port{Kind: "tcp", Value: "2"}, }) bld.AddEdge(&node.Edge{ SrcID: "q", DstID: "node-1", - Port: &node.Port{Kind: "tcp", Value: 2}, + Port: &node.Port{Kind: "tcp", Value: "2"}, }) bld.AddEdge(&node.Edge{ SrcID: "node-2", DstID: "1", - Port: &node.Port{Kind: "tcp", Value: 2}, + Port: &node.Port{Kind: "tcp", Value: "2"}, }) var buf bytes.Buffer diff --git a/internal/builder/json_test.go b/internal/builder/json_test.go index 34f6765..04083df 100644 --- a/internal/builder/json_test.go +++ b/internal/builder/json_test.go @@ -30,8 +30,8 @@ func TestJSON(t *testing.T) { Networks: []string{"test"}, Listen: map[string][]*node.Port{ "foo": { - &node.Port{Kind: "tcp", Value: 2}, - &node.Port{Kind: "udp", Value: 1}, + &node.Port{Kind: "tcp", Value: "2"}, + &node.Port{Kind: "udp", Value: "1"}, }, }, Tags: []string{}, diff --git a/internal/builder/puml_test.go b/internal/builder/puml_test.go index e83e967..57f1663 100644 --- a/internal/builder/puml_test.go +++ b/internal/builder/puml_test.go @@ -19,9 +19,9 @@ func TestPumlGolden(t *testing.T) { Image: "node-image", Cluster: "c1", Ports: makeTestPorts([]*node.Port{ - {Kind: "tcp", Value: 1}, - {Kind: "tcp", Value: 2}, - {Kind: "tcp", Value: 5, Local: true}, + {Kind: "tcp", Value: "1"}, + {Kind: "tcp", Value: "2"}, + {Kind: "tcp", Value: "5", Local: true}, }...), Networks: []string{"test-net"}, Meta: &node.Meta{ @@ -37,8 +37,8 @@ func TestPumlGolden(t *testing.T) { Image: "node-image", Cluster: "c1", Ports: makeTestPorts([]*node.Port{ - {Kind: "tcp", Value: 1}, - {Kind: "tcp", Value: 2}, + {Kind: "tcp", Value: "1"}, + {Kind: "tcp", Value: "2"}, }...), Networks: []string{"test-net"}, Meta: &node.Meta{ @@ -51,8 +51,8 @@ func TestPumlGolden(t *testing.T) { Name: "3", Image: "node-image", Ports: makeTestPorts([]*node.Port{ - {Kind: "tcp", Value: 1}, - {Kind: "tcp", Value: 2}, + {Kind: "tcp", Value: "1"}, + {Kind: "tcp", Value: "2"}, }...), Networks: []string{"test-net"}, Meta: &node.Meta{ @@ -66,7 +66,7 @@ func TestPumlGolden(t *testing.T) { Name: "ext2", Cluster: "c2", Ports: makeTestPorts([]*node.Port{ - {Kind: "tcp", Value: 2}, + {Kind: "tcp", Value: "2"}, }...), }) @@ -75,44 +75,44 @@ func TestPumlGolden(t *testing.T) { Name: "ext2", Cluster: "c2", Ports: makeTestPorts([]*node.Port{ - {Kind: "tcp", Value: 443}, + {Kind: "tcp", Value: "443"}, }...), }) bld.AddEdge(&node.Edge{ SrcID: "ext2", DstID: "node-1", - Port: &node.Port{Kind: "tcp", Value: 1}, + Port: &node.Port{Kind: "tcp", Value: "1"}, }) bld.AddEdge(&node.Edge{ SrcID: "node-1", DstID: "node-1", - Port: &node.Port{Kind: "tcp", Value: 5, Local: true}, + Port: &node.Port{Kind: "tcp", Value: "5", Local: true}, }) bld.AddEdge(&node.Edge{ SrcID: "ext2", DstID: "node-1", - Port: &node.Port{Kind: "tcp", Value: 2}, + Port: &node.Port{Kind: "tcp", Value: "2"}, }) bld.AddEdge(&node.Edge{ SrcID: "ext2", DstID: "node-1", - Port: &node.Port{Kind: "tcp", Value: 3}, + Port: &node.Port{Kind: "tcp", Value: "3"}, }) bld.AddEdge(&node.Edge{ SrcID: "node-1", DstID: "ext2", - Port: &node.Port{Kind: "tcp", Value: 2}, + Port: &node.Port{Kind: "tcp", Value: "2"}, }) bld.AddEdge(&node.Edge{ SrcID: "node-1", DstID: "ext2", - Port: &node.Port{Kind: "tcp", Value: 3}, + Port: &node.Port{Kind: "tcp", Value: "3"}, }) bld.AddEdge(&node.Edge{ @@ -130,61 +130,61 @@ func TestPumlGolden(t *testing.T) { bld.AddEdge(&node.Edge{ SrcID: "node-2", DstID: "node-1", - Port: &node.Port{Kind: "tcp", Value: 1}, + Port: &node.Port{Kind: "tcp", Value: "1"}, }) bld.AddEdge(&node.Edge{ SrcID: "node-2", DstID: "node-1", - Port: &node.Port{Kind: "tcp", Value: 2}, + Port: &node.Port{Kind: "tcp", Value: "2"}, }) bld.AddEdge(&node.Edge{ SrcID: "node-2", DstID: "node-1", - Port: &node.Port{Kind: "tcp", Value: 3}, + Port: &node.Port{Kind: "tcp", Value: "3"}, }) bld.AddEdge(&node.Edge{ SrcID: "node-2", DstID: "node-3", - Port: &node.Port{Kind: "tcp", Value: 3}, + Port: &node.Port{Kind: "tcp", Value: "3"}, }) bld.AddEdge(&node.Edge{ SrcID: "node-1", DstID: "node-3", - Port: &node.Port{Kind: "tcp", Value: 3}, + Port: &node.Port{Kind: "tcp", Value: "3"}, }) bld.AddEdge(&node.Edge{ SrcID: "node-1", DstID: "node-2", - Port: &node.Port{Kind: "tcp", Value: 2}, + Port: &node.Port{Kind: "tcp", Value: "2"}, }) bld.AddEdge(&node.Edge{ SrcID: "node-1", DstID: "c2", - Port: &node.Port{Kind: "tcp", Value: 2}, + Port: &node.Port{Kind: "tcp", Value: "2"}, }) bld.AddEdge(&node.Edge{ SrcID: "node-3", DstID: "node-1", - Port: &node.Port{Kind: "tcp", Value: 1}, + Port: &node.Port{Kind: "tcp", Value: "1"}, }) bld.AddEdge(&node.Edge{ SrcID: "node-3", DstID: "node-1", - Port: &node.Port{Kind: "tcp", Value: 2}, + Port: &node.Port{Kind: "tcp", Value: "2"}, }) bld.AddEdge(&node.Edge{ SrcID: "c1", DstID: "2", - Port: &node.Port{Kind: "tcp", Value: 2}, + Port: &node.Port{Kind: "tcp", Value: "2"}, }) bld.AddEdge(&node.Edge{ diff --git a/internal/builder/sdsl_test.go b/internal/builder/sdsl_test.go index 3c074e5..5be77f7 100644 --- a/internal/builder/sdsl_test.go +++ b/internal/builder/sdsl_test.go @@ -19,8 +19,8 @@ func TestSDSLGolden(t *testing.T) { Image: "node-image", Cluster: "c1", Ports: makeTestPorts([]*node.Port{ - {Kind: "tcp", Value: 1}, - {Kind: "tcp", Value: 2}, + {Kind: "tcp", Value: "1"}, + {Kind: "tcp", Value: "2"}, }...), Networks: []string{"test-net"}, Meta: &node.Meta{ @@ -36,8 +36,8 @@ func TestSDSLGolden(t *testing.T) { Image: "node-image", Cluster: "c1", Ports: makeTestPorts([]*node.Port{ - {Kind: "tcp", Value: 1}, - {Kind: "tcp", Value: 2}, + {Kind: "tcp", Value: "1"}, + {Kind: "tcp", Value: "2"}, }...), Networks: []string{"test-net"}, Meta: &node.Meta{ @@ -50,8 +50,8 @@ func TestSDSLGolden(t *testing.T) { Name: "3", Image: "node-image", Ports: makeTestPorts([]*node.Port{ - {Kind: "tcp", Value: 1}, - {Kind: "tcp", Value: 2}, + {Kind: "tcp", Value: "1"}, + {Kind: "tcp", Value: "2"}, }...), Networks: []string{"test-net"}, Meta: &node.Meta{ @@ -65,7 +65,7 @@ func TestSDSLGolden(t *testing.T) { Name: "ext2", Cluster: "c2", Ports: makeTestPorts([]*node.Port{ - {Kind: "tcp", Value: 2}, + {Kind: "tcp", Value: "2"}, }...), }) @@ -74,31 +74,31 @@ func TestSDSLGolden(t *testing.T) { bld.AddEdge(&node.Edge{ SrcID: "ext2", DstID: "node-1", - Port: &node.Port{Kind: "tcp", Value: 1}, + Port: &node.Port{Kind: "tcp", Value: "1"}, }) bld.AddEdge(&node.Edge{ SrcID: "ext2", DstID: "node-1", - Port: &node.Port{Kind: "tcp", Value: 2}, + Port: &node.Port{Kind: "tcp", Value: "2"}, }) bld.AddEdge(&node.Edge{ SrcID: "ext2", DstID: "node-1", - Port: &node.Port{Kind: "tcp", Value: 3}, + Port: &node.Port{Kind: "tcp", Value: "3"}, }) bld.AddEdge(&node.Edge{ SrcID: "node-1", DstID: "ext2", - Port: &node.Port{Kind: "tcp", Value: 2}, + Port: &node.Port{Kind: "tcp", Value: "2"}, }) bld.AddEdge(&node.Edge{ SrcID: "node-1", DstID: "ext2", - Port: &node.Port{Kind: "tcp", Value: 3}, + Port: &node.Port{Kind: "tcp", Value: "3"}, }) bld.AddEdge(&node.Edge{ @@ -116,61 +116,61 @@ func TestSDSLGolden(t *testing.T) { bld.AddEdge(&node.Edge{ SrcID: "node-2", DstID: "node-1", - Port: &node.Port{Kind: "tcp", Value: 1}, + Port: &node.Port{Kind: "tcp", Value: "1"}, }) bld.AddEdge(&node.Edge{ SrcID: "node-2", DstID: "node-1", - Port: &node.Port{Kind: "tcp", Value: 2}, + Port: &node.Port{Kind: "tcp", Value: "2"}, }) bld.AddEdge(&node.Edge{ SrcID: "node-2", DstID: "node-1", - Port: &node.Port{Kind: "tcp", Value: 3}, + Port: &node.Port{Kind: "tcp", Value: "3"}, }) bld.AddEdge(&node.Edge{ SrcID: "node-2", DstID: "node-3", - Port: &node.Port{Kind: "tcp", Value: 3}, + Port: &node.Port{Kind: "tcp", Value: "3"}, }) bld.AddEdge(&node.Edge{ SrcID: "node-1", DstID: "node-3", - Port: &node.Port{Kind: "tcp", Value: 3}, + Port: &node.Port{Kind: "tcp", Value: "3"}, }) bld.AddEdge(&node.Edge{ SrcID: "node-1", DstID: "node-2", - Port: &node.Port{Kind: "tcp", Value: 2}, + Port: &node.Port{Kind: "tcp", Value: "2"}, }) bld.AddEdge(&node.Edge{ SrcID: "node-1", DstID: "c2", - Port: &node.Port{Kind: "tcp", Value: 2}, + Port: &node.Port{Kind: "tcp", Value: "2"}, }) bld.AddEdge(&node.Edge{ SrcID: "node-3", DstID: "node-1", - Port: &node.Port{Kind: "tcp", Value: 1}, + Port: &node.Port{Kind: "tcp", Value: "1"}, }) bld.AddEdge(&node.Edge{ SrcID: "node-3", DstID: "node-1", - Port: &node.Port{Kind: "tcp", Value: 2}, + Port: &node.Port{Kind: "tcp", Value: "2"}, }) bld.AddEdge(&node.Edge{ SrcID: "c1", DstID: "2", - Port: &node.Port{Kind: "tcp", Value: 2}, + Port: &node.Port{Kind: "tcp", Value: "2"}, }) bld.AddEdge(&node.Edge{ diff --git a/internal/builder/stat.go b/internal/builder/stat.go index 95cfa0c..137a024 100644 --- a/internal/builder/stat.go +++ b/internal/builder/stat.go @@ -12,7 +12,8 @@ import ( ) const minClusters = 2 -const defaultName = "default" + +// const defaultName = "default" type stat struct { Name string @@ -57,13 +58,7 @@ func (s *Stat) AddNode(n *node.Node) error { }) s.conns[n.ID] = make(set.Unordered[string]) - - cluster := n.Cluster - if cluster == "" { - cluster = defaultName - } - - s.clusters[cluster]++ + s.clusters[n.Cluster]++ return nil } diff --git a/internal/builder/tree_test.go b/internal/builder/tree_test.go index 6c6fe92..a66fff9 100644 --- a/internal/builder/tree_test.go +++ b/internal/builder/tree_test.go @@ -18,8 +18,8 @@ func TestTreeGolden(t *testing.T) { Name: "1", Image: "node-image", Ports: makeTestPorts([]*node.Port{ - {Kind: "tcp", Value: 1}, - {Kind: "tcp", Value: 2}, + {Kind: "tcp", Value: "1"}, + {Kind: "tcp", Value: "2"}, }...), Networks: []string{"test-net"}, Meta: &node.Meta{ @@ -35,7 +35,7 @@ func TestTreeGolden(t *testing.T) { ID: "node-2", Name: "2", Ports: makeTestPorts([]*node.Port{ - {Kind: "tcp", Value: 2}, + {Kind: "tcp", Value: "2"}, }...), Networks: []string{"test-net"}, Meta: &node.Meta{ @@ -51,7 +51,7 @@ func TestTreeGolden(t *testing.T) { ID: "node-3", Name: "3", Ports: makeTestPorts([]*node.Port{ - {Kind: "tcp", Value: 3}, + {Kind: "tcp", Value: "3"}, }...), Networks: []string{"test-net"}, Meta: &node.Meta{ @@ -67,49 +67,49 @@ func TestTreeGolden(t *testing.T) { bld.AddEdge(&node.Edge{ SrcID: "node-2", DstID: "node-1", - Port: &node.Port{Kind: "tcp", Value: 1}, + Port: &node.Port{Kind: "tcp", Value: "1"}, }) bld.AddEdge(&node.Edge{ SrcID: "node-2", DstID: "node-1", - Port: &node.Port{Kind: "tcp", Value: 2}, + Port: &node.Port{Kind: "tcp", Value: "2"}, }) bld.AddEdge(&node.Edge{ SrcID: "node-2", DstID: "node-1", - Port: &node.Port{Kind: "tcp", Value: 3}, + Port: &node.Port{Kind: "tcp", Value: "3"}, }) bld.AddEdge(&node.Edge{ SrcID: "node-2", DstID: "node-3", - Port: &node.Port{Kind: "tcp", Value: 3}, + Port: &node.Port{Kind: "tcp", Value: "3"}, }) bld.AddEdge(&node.Edge{ SrcID: "node-1", DstID: "node-3", - Port: &node.Port{Kind: "tcp", Value: 3}, + Port: &node.Port{Kind: "tcp", Value: "3"}, }) bld.AddEdge(&node.Edge{ SrcID: "node-1", DstID: "node-2", - Port: &node.Port{Kind: "tcp", Value: 2}, + Port: &node.Port{Kind: "tcp", Value: "2"}, }) bld.AddEdge(&node.Edge{ SrcID: "node-1", DstID: "3", - Port: &node.Port{Kind: "tcp", Value: 3}, + Port: &node.Port{Kind: "tcp", Value: "3"}, }) bld.AddEdge(&node.Edge{ SrcID: "3", DstID: "node-1", - Port: &node.Port{Kind: "tcp", Value: 3}, + Port: &node.Port{Kind: "tcp", Value: "3"}, }) var buf bytes.Buffer diff --git a/internal/builder/yaml_test.go b/internal/builder/yaml_test.go index af937ec..afc5708 100644 --- a/internal/builder/yaml_test.go +++ b/internal/builder/yaml_test.go @@ -19,8 +19,8 @@ func TestYAMLGolden(t *testing.T) { Name: "1", Image: "node-image", Ports: makeTestPorts([]*node.Port{ - {Kind: "tcp", Value: 1}, - {Kind: "tcp", Value: 2}, + {Kind: "tcp", Value: "1"}, + {Kind: "tcp", Value: "2"}, }...), Networks: []string{"test-net"}, Meta: &node.Meta{ @@ -40,8 +40,8 @@ func TestYAMLGolden(t *testing.T) { Name: "2", Image: "node-image", Ports: makeTestPorts([]*node.Port{ - {Kind: "tcp", Value: 1}, - {Kind: "tcp", Value: 2}, + {Kind: "tcp", Value: "1"}, + {Kind: "tcp", Value: "2"}, }...), Networks: []string{"test-net"}, Meta: &node.Meta{ @@ -61,62 +61,62 @@ func TestYAMLGolden(t *testing.T) { ID: "2", Name: "2", Ports: makeTestPorts([]*node.Port{ - {Kind: "tcp", Value: 2}, + {Kind: "tcp", Value: "2"}, }...), }) bld.AddEdge(&node.Edge{ SrcID: "2", DstID: "node-1", - Port: &node.Port{Kind: "tcp", Value: 1}, + Port: &node.Port{Kind: "tcp", Value: "1"}, }) bld.AddEdge(&node.Edge{ SrcID: "2", DstID: "node-1", - Port: &node.Port{Kind: "tcp", Value: 2}, + Port: &node.Port{Kind: "tcp", Value: "2"}, }) bld.AddEdge(&node.Edge{ SrcID: "2", DstID: "node-1", - Port: &node.Port{Kind: "tcp", Value: 3}, + Port: &node.Port{Kind: "tcp", Value: "3"}, }) bld.AddEdge(&node.Edge{ SrcID: "node-2", DstID: "node-1", - Port: &node.Port{Kind: "tcp", Value: 2}, + Port: &node.Port{Kind: "tcp", Value: "2"}, }) bld.AddEdge(&node.Edge{ SrcID: "node-1", DstID: "node-2", - Port: &node.Port{Kind: "tcp", Value: 2}, + Port: &node.Port{Kind: "tcp", Value: "2"}, }) bld.AddEdge(&node.Edge{ SrcID: "node-1", DstID: "2", - Port: &node.Port{Kind: "tcp", Value: 2}, + Port: &node.Port{Kind: "tcp", Value: "2"}, }) bld.AddEdge(&node.Edge{ SrcID: "node-1", DstID: "2", - Port: &node.Port{Kind: "tcp", Value: 3}, + Port: &node.Port{Kind: "tcp", Value: "3"}, }) bld.AddEdge(&node.Edge{ SrcID: "node-1", DstID: "3", - Port: &node.Port{Kind: "tcp", Value: 3}, + Port: &node.Port{Kind: "tcp", Value: "3"}, }) bld.AddEdge(&node.Edge{ SrcID: "3", DstID: "node-1", - Port: &node.Port{Kind: "tcp", Value: 3}, + Port: &node.Port{Kind: "tcp", Value: "3"}, }) var buf bytes.Buffer diff --git a/internal/client/defaults.go b/internal/client/defaults.go index 107d8d4..2796c37 100644 --- a/internal/client/defaults.go +++ b/internal/client/defaults.go @@ -12,9 +12,9 @@ import ( "github.com/docker/docker/client" "github.com/prometheus/procfs" - "github.com/s0rg/set" "github.com/s0rg/decompose/internal/graph" + "github.com/s0rg/set" ) const ( @@ -54,20 +54,21 @@ func Default() (rv DockerClient, err error) { return dc, nil } -func isValidState(state uint64) (ok bool) { - switch state { - case tcpEstablished, tcpListen: - ok = true - default: +func checkState(state uint64) (listener, valid bool) { + if state == tcpListen { + return true, true + } + + if state == tcpEstablished { + return false, true } - return ok + return false, false } func scanTCP( pfs procfs.FS, name string, - inodes set.Unordered[uint64], onconn func(*graph.Connection), ) (err error) { tcp4, err := pfs.NetTCP() @@ -76,21 +77,20 @@ func scanTCP( } for _, s := range tcp4 { - if !isValidState(s.St) { - continue - } - - if !inodes.Has(s.Inode) { + listener, ok := checkState(s.St) + if !ok { continue } onconn(&graph.Connection{ - LocalIP: s.LocalAddr, - RemoteIP: s.RemAddr, - LocalPort: uint16(s.LocalPort), - RemotePort: uint16(s.RemPort), - Proto: graph.TCP, - Process: name, + Process: name, + Inode: s.Inode, + SrcIP: s.LocalAddr, + DstIP: s.RemAddr, + SrcPort: int(s.LocalPort), + DstPort: int(s.RemPort), + Proto: graph.TCP, + Listen: listener, }) } @@ -100,21 +100,20 @@ func scanTCP( } for _, s := range tcp6 { - if !isValidState(s.St) { - continue - } - - if !inodes.Has(s.Inode) { + listener, ok := checkState(s.St) + if !ok { continue } onconn(&graph.Connection{ - LocalIP: s.LocalAddr, - RemoteIP: s.RemAddr, - LocalPort: uint16(s.LocalPort), - RemotePort: uint16(s.RemPort), - Proto: graph.TCP, - Process: name, + Process: name, + Inode: s.Inode, + SrcIP: s.LocalAddr, + DstIP: s.RemAddr, + SrcPort: int(s.LocalPort), + DstPort: int(s.RemPort), + Proto: graph.TCP, + Listen: listener, }) } @@ -124,7 +123,6 @@ func scanTCP( func scanUDP( pfs procfs.FS, name string, - inodes set.Unordered[uint64], onconn func(*graph.Connection), ) (err error) { udp4, err := pfs.NetUDP() @@ -133,17 +131,14 @@ func scanUDP( } for _, s := range udp4 { - if !inodes.Has(s.Inode) { - continue - } - onconn(&graph.Connection{ - LocalIP: s.LocalAddr, - RemoteIP: s.RemAddr, - LocalPort: uint16(s.LocalPort), - RemotePort: uint16(s.RemPort), - Proto: graph.UDP, - Process: name, + Process: name, + Inode: s.Inode, + SrcIP: s.LocalAddr, + DstIP: s.RemAddr, + SrcPort: int(s.LocalPort), + DstPort: int(s.RemPort), + Proto: graph.UDP, }) } @@ -153,17 +148,37 @@ func scanUDP( } for _, s := range udp6 { - if !inodes.Has(s.Inode) { - continue - } + onconn(&graph.Connection{ + Process: name, + Inode: s.Inode, + SrcIP: s.LocalAddr, + DstIP: s.RemAddr, + SrcPort: int(s.LocalPort), + DstPort: int(s.RemPort), + Proto: graph.UDP, + }) + } + + return nil +} +func scanUNIX( + pfs procfs.FS, + name string, + onconn func(*graph.Connection), +) (err error) { + unix, err := pfs.NetUNIX() + if err != nil { + return fmt.Errorf("procfs/unix: %w", err) + } + + for _, s := range unix.Rows { onconn(&graph.Connection{ - LocalIP: s.LocalAddr, - RemoteIP: s.RemAddr, - LocalPort: uint16(s.LocalPort), - RemotePort: uint16(s.RemPort), - Proto: graph.UDP, - Process: name, + Process: name, + Inode: s.Inode, + Path: s.Path, + Listen: s.Flags != 0, + Proto: graph.UNIX, }) } @@ -172,30 +187,46 @@ func scanUDP( func processInfo(pid int) ( name string, - inodes set.Unordered[uint64], err error, ) { pfs, err := procfs.NewFS(procROOT) if err != nil { - return "", nil, fmt.Errorf("procfs: %w", err) + return "", fmt.Errorf("procfs: %w", err) } proc, err := pfs.Proc(pid) if err != nil { - return "", nil, fmt.Errorf("procfs/pid: %w", err) + return "", fmt.Errorf("procfs/pid: %w", err) } name, err = proc.Executable() if err != nil { - return "", nil, fmt.Errorf("procfs/executable: %w", err) + return "", fmt.Errorf("procfs/executable: %w", err) + } + + return filepath.Base(name), nil +} + +func Inodes( + pid int, + cb func(uint64), +) error { + pfs, err := procfs.NewFS(procROOT) + if err != nil { + return fmt.Errorf("procfs: %w", err) + } + + proc, err := pfs.Proc(pid) + if err != nil { + return fmt.Errorf("procfs/pid: %w", err) } fds, err := proc.FileDescriptorsInfo() if err != nil { - return "", nil, fmt.Errorf("procfs/descriptors: %w", err) + return fmt.Errorf("procfs/descriptors: %w", err) } - inodes = make(set.Unordered[uint64]) + seen := make(set.Unordered[uint64]) for _, f := range fds { ino, err := strconv.ParseUint(f.Ino, 10, 64) @@ -203,37 +234,49 @@ func processInfo(pid int) ( continue } - inodes.Add(ino) + if seen.Add(ino) { + cb(ino) + } } - return filepath.Base(name), inodes, nil + return nil } func Nsenter( pid int, proto graph.NetProto, - onconn func(*graph.Connection), + onconn func(int, *graph.Connection), ) ( err error, ) { - name, inodes, err := processInfo(pid) + name, err := processInfo(pid) if err != nil { return fmt.Errorf("procfs: %w", err) } + connWithPid := func(c *graph.Connection) { + onconn(pid, c) + } + fs, err := procfs.NewFS(filepath.Join(procROOT, strconv.Itoa(pid))) if err != nil { return fmt.Errorf("procfs/net: %w", err) } if proto == graph.ALL || proto == graph.TCP { - if err = scanTCP(fs, name, inodes, onconn); err != nil { + if err = scanTCP(fs, name, connWithPid); err != nil { return fmt.Errorf("scan: %w", err) } } if proto == graph.ALL || proto == graph.UDP { - if err = scanUDP(fs, name, inodes, onconn); err != nil { + if err = scanUDP(fs, name, connWithPid); err != nil { + return fmt.Errorf("scan: %w", err) + } + } + + if proto == graph.ALL || proto == graph.UNIX { + if err = scanUNIX(fs, name, connWithPid); err != nil { return fmt.Errorf("scan: %w", err) } } diff --git a/internal/client/docker.go b/internal/client/docker.go index 427327a..2b0dd0b 100644 --- a/internal/client/docker.go +++ b/internal/client/docker.go @@ -21,15 +21,13 @@ import ( const ( stateRunning = "running" - netstatCmd = "netstat" - netstatArg = "-apn" ) var ErrModeNone = errors.New("mode not set") type ( createClient func() (DockerClient, error) - nsEnter func(int, graph.NetProto, func(*graph.Connection)) error + nsEnter func(int, graph.NetProto, func(int, *graph.Connection)) error ) type DockerClient interface { @@ -73,7 +71,7 @@ func (d *Docker) Mode() string { func (d *Docker) Containers( ctx context.Context, proto graph.NetProto, - detailed, deep bool, + unix, deep bool, skipkeys []string, progress func(int, int), ) (rv []*graph.Container, err error) { @@ -90,51 +88,140 @@ func (d *Docker) Containers( rv = make([]*graph.Container, 0, len(containers)) + var inodes *InodesMap + + if unix { + if inodes, err = d.collectInodes(ctx, containers); err != nil { + return nil, fmt.Errorf("inodes: %w", err) + } + } + + cmap := make(map[string]*graph.Container) + for i := 0; i < len(containers); i++ { - doc := &containers[i] + c := &containers[i] - if doc.State != stateRunning { + if c.State != stateRunning { continue } - con := &graph.Container{ - ID: doc.ID, - Image: doc.Image, - Name: strings.TrimLeft(doc.Names[0], "/"), - Labels: maps.Clone(doc.Labels), - Endpoints: extractEndpoints(doc.NetworkSettings.Networks), + con, cerr := d.extractInfo(ctx, c, proto, unix, deep, skeys, inodes) + if cerr != nil { + return nil, fmt.Errorf("container %s: %w", c.ID, cerr) } - if detailed { - info, err := d.cli.ContainerInspect(ctx, doc.ID) - if err != nil { - return nil, fmt.Errorf("inspect: %w", err) - } + cmap[c.ID] = con - con.Volumes = extractVolumesInfo(info.Mounts) - con.Info = extractContainerInfo(&info, skeys) - } + rv = append(rv, con) - if err := d.connections(ctx, doc.ID, proto, func(conn *graph.Connection) { - if !deep && conn.IsLocal() { + progress(i, len(containers)) + } + + if unix { + inodes.ResolveUnknown(func(srcCID, dstCID, srcName, _, path string) { + dst, ok := cmap[dstCID] + if !ok { return } - con.AddConnection(conn) - }); err != nil { - return nil, fmt.Errorf("container: %s connections: %w", doc.ID, err) + dst.AddConnection(&graph.Connection{ + Proto: graph.UNIX, + Process: srcName, + Path: path, + DstID: srcCID, + }) + }) + } + + progress(len(containers), len(containers)) + + return slices.Clip(rv), nil +} + +func (d *Docker) collectInodes( + ctx context.Context, + containers []types.Container, +) ( + inodes *InodesMap, + err error, +) { + inodes = &InodesMap{} + + for i := 0; i < len(containers); i++ { + c := &containers[i] + + if c.State != stateRunning { + continue } - con.SortConnections() + err = d.processesContainer(ctx, c.ID, func(pid int, name string) (err error) { + inodes.AddProcess(c.ID, pid, name) - rv = append(rv, con) + return Inodes(pid, func(inode uint64) { + inodes.AddInode(c.ID, pid, inode) + }) + }) + if err != nil { + return nil, fmt.Errorf("inodes %s: %w", c.ID, err) + } + } - progress(i, len(containers)) + return inodes, nil +} + +func (d *Docker) extractInfo( + ctx context.Context, + c *types.Container, + proto graph.NetProto, + unix, deep bool, + skeys set.Unordered[string], + inodes *InodesMap, +) (rv *graph.Container, err error) { + rv = &graph.Container{ + ID: c.ID, + Image: c.Image, + Name: strings.TrimLeft(c.Names[0], "/"), + Labels: maps.Clone(c.Labels), + Endpoints: extractEndpoints(c.NetworkSettings.Networks), } - progress(len(containers), len(containers)) + info, err := d.cli.ContainerInspect(ctx, c.ID) + if err != nil { + return nil, fmt.Errorf("inspect: %w", err) + } - return slices.Clip(rv), nil + rv.Volumes = extractVolumesInfo(info.Mounts) + rv.Info = extractContainerInfo(&info, skeys) + + if err := d.connections(ctx, c.ID, proto, func(pid int, conn *graph.Connection) { + if !deep && conn.IsLocal() { + return + } + + if !unix && conn.Proto == graph.UNIX { + return + } + + if conn.Proto == graph.UNIX { + if !inodes.Has(c.ID, pid, conn.Inode) { + inodes.MarkUnknown(c.ID, pid, conn.Inode) + + return + } + + if conn.Listen { + inodes.MarkListener(c.ID, pid, conn.Path) + } + } + + rv.AddConnection(conn) + }); err != nil { + return nil, fmt.Errorf("connections: %w", err) + } + + rv.SortConnections() + + return rv, nil } func (d *Docker) Close() (err error) { @@ -149,19 +236,21 @@ func (d *Docker) connections( ctx context.Context, cid string, proto graph.NetProto, - cb func(*graph.Connection), + cb func(int, *graph.Connection), ) (err error) { switch d.opt.Mode { case InContainer: err = d.connectionsContainer(ctx, cid, proto, func(r io.Reader) (err error) { - if err = graph.ParseNetstat(r, cb); err != nil { + if err = graph.ParseNetstat(r, func(c *graph.Connection) { + cb(1, c) + }); err != nil { return fmt.Errorf("parse: %w", err) } return nil }) case LinuxNsenter: - err = d.processesContainer(ctx, cid, func(pid int) (err error) { + err = d.processesContainer(ctx, cid, func(pid int, _ string) (err error) { if err = d.opt.Nsenter(pid, proto, cb); err != nil { return fmt.Errorf("nsenter: %w", err) } @@ -189,7 +278,7 @@ func (d *Docker) connectionsContainer( Tty: true, AttachStdout: true, Privileged: true, - Cmd: netstat(proto), + Cmd: graph.NetstatCMD(proto), }) if err != nil { return fmt.Errorf("exec-create: %w", err) @@ -214,9 +303,9 @@ func (d *Docker) connectionsContainer( func (d *Docker) processesContainer( ctx context.Context, cid string, - fun func(int) error, + fun func(int, string) error, ) (err error) { - ps, err := d.cli.ContainerTop(ctx, cid, []string{"-o pid"}) + ps, err := d.cli.ContainerTop(ctx, cid, []string{"-o pid,cmd"}) if err != nil { return fmt.Errorf("top: %w", err) } @@ -232,7 +321,9 @@ func (d *Docker) processesContainer( continue } - if err = fun(pid); err != nil { + cmd := strings.Fields(p[1]) + + if err = fun(pid, cmd[0]); err != nil { return fmt.Errorf("[pid: %d] %w", pid, err) } } @@ -240,13 +331,6 @@ func (d *Docker) processesContainer( return nil } -func netstat(p graph.NetProto) []string { - return []string{ - netstatCmd, - netstatArg + p.Flag(), - } -} - func extractContainerInfo( c *types.ContainerJSON, s set.Unordered[string], diff --git a/internal/client/docker_test.go b/internal/client/docker_test.go index 46bd8ff..a2cd4c7 100644 --- a/internal/client/docker_test.go +++ b/internal/client/docker_test.go @@ -188,8 +188,7 @@ func TestDockerClientContainersExecCreateError(t *testing.T) { _, err = cli.Containers( context.Background(), graph.ALL, - false, - false, + false, false, nil, voidProgress, ) @@ -242,8 +241,7 @@ func TestDockerClientContainersInspectError(t *testing.T) { _, err = cli.Containers( context.Background(), graph.ALL, - true, - false, + false, false, nil, voidProgress, ) @@ -300,8 +298,7 @@ func TestDockerClientContainersExecAttachError(t *testing.T) { _, err = cli.Containers( context.Background(), graph.ALL, - false, - false, + false, false, nil, voidProgress, ) @@ -363,8 +360,7 @@ func TestDockerClientContainersParseError(t *testing.T) { _, err = cli.Containers( context.Background(), graph.ALL, - false, - false, + false, false, nil, voidProgress, ) @@ -444,8 +440,7 @@ func TestDockerClientContainersSingle(t *testing.T) { rv, err := cli.Containers( context.Background(), graph.ALL, - false, - false, + false, false, nil, voidProgress, ) @@ -544,8 +539,7 @@ func TestDockerClientContainersSingleFull(t *testing.T) { rv, err := cli.Containers( context.Background(), graph.ALL, - true, - false, + false, false, nil, voidProgress, ) @@ -626,8 +620,7 @@ func TestDockerClientContainersSingleFullSkipEnv(t *testing.T) { rv, err := cli.Containers( context.Background(), graph.ALL, - true, - false, + false, false, []string{"BAZ"}, voidProgress, ) @@ -735,8 +728,7 @@ func TestDockerClientNsEnterInspectError(t *testing.T) { _, err = cli.Containers( context.Background(), graph.ALL, - false, - false, + false, false, nil, voidProgress, ) @@ -785,7 +777,7 @@ func TestDockerClientNsEnterConnectionsError(t *testing.T) { } failEnter := func(_ int, _ graph.NetProto, _ func( - _ *graph.Connection, + _ int, _ *graph.Connection, )) error { return testErr } @@ -804,8 +796,7 @@ func TestDockerClientNsEnterConnectionsError(t *testing.T) { _, err = cli.Containers( context.Background(), graph.ALL, - false, - false, + false, false, nil, voidProgress, ) @@ -856,7 +847,7 @@ func TestDockerClientNsEnterContainerTopVariants(t *testing.T) { var count int enter := func(_ int, _ graph.NetProto, _ func( - _ *graph.Connection, + _ int, _ *graph.Connection, )) error { count++ @@ -877,8 +868,7 @@ func TestDockerClientNsEnterContainerTopVariants(t *testing.T) { if _, err = cli.Containers( context.Background(), graph.ALL, - false, - false, + false, false, nil, voidProgress, ); err != nil { @@ -927,9 +917,9 @@ func TestDockerClientNsEnterOk(t *testing.T) { } testEnter := func(_ int, _ graph.NetProto, fn func( - _ *graph.Connection, + _ int, _ *graph.Connection, )) error { - fn(&graph.Connection{}) + fn(1, &graph.Connection{}) return nil } @@ -948,8 +938,7 @@ func TestDockerClientNsEnterOk(t *testing.T) { _, err = cli.Containers( context.Background(), graph.ALL, - false, - false, + false, false, nil, voidProgress, ) @@ -991,14 +980,14 @@ func TestDockerClientNsEnterLocal(t *testing.T) { return rv } - testEnter := func(_ int, _ graph.NetProto, fn func(*graph.Connection)) error { + testEnter := func(_ int, _ graph.NetProto, fn func(int, *graph.Connection)) error { loc := net.ParseIP("127.0.0.1") nod := net.ParseIP("1.1.1.1") rem := net.ParseIP("2.2.2.2") - fn(&graph.Connection{Process: "1", LocalPort: 1, RemotePort: 0, LocalIP: nod, Proto: graph.TCP}) - fn(&graph.Connection{Process: "1", LocalPort: 10, RemotePort: 2, LocalIP: nod, RemoteIP: rem, Proto: graph.TCP}) - fn(&graph.Connection{Process: "1", LocalPort: 5, LocalIP: loc, Proto: graph.TCP}) + fn(1, &graph.Connection{Process: "1", SrcPort: 1, DstPort: 0, SrcIP: nod, Proto: graph.TCP}) + fn(1, &graph.Connection{Process: "1", SrcPort: 10, DstPort: 2, SrcIP: nod, DstIP: rem, Proto: graph.TCP}) + fn(1, &graph.Connection{Process: "1", SrcPort: 5, SrcIP: loc, Proto: graph.TCP}) return nil } @@ -1017,8 +1006,7 @@ func TestDockerClientNsEnterLocal(t *testing.T) { rv, err := cli.Containers( context.Background(), graph.ALL, - false, - false, + false, false, nil, voidProgress, ) @@ -1037,8 +1025,7 @@ func TestDockerClientNsEnterLocal(t *testing.T) { rv, err = cli.Containers( context.Background(), graph.ALL, - false, - true, + false, true, nil, voidProgress, ) diff --git a/internal/client/inodes.go b/internal/client/inodes.go new file mode 100644 index 0000000..f0e1d7a --- /dev/null +++ b/internal/client/inodes.go @@ -0,0 +1,182 @@ +package client + +import ( + "github.com/s0rg/set" +) + +type InodesMap struct { + m map[string]map[int]set.Unordered[uint64] + u map[string]map[int]set.Unordered[uint64] + n map[string]map[int]string + l map[string]map[int]string +} + +type item struct { + Cid string + Pid int +} + +func (m *InodesMap) AddProcess(containerID string, pid int, name string) { + if m.n == nil { + m.n = make(map[string]map[int]string) + } + + names, ok := m.n[containerID] + if !ok { + names = make(map[int]string) + m.n[containerID] = names + } + + names[pid] = name +} + +func (m *InodesMap) AddInode(containerID string, pid int, inode uint64) { + if m.m == nil { + m.m = make(map[string]map[int]set.Unordered[uint64]) + } + + pids, ok := m.m[containerID] + if !ok { + pids = make(map[int]set.Unordered[uint64]) + m.m[containerID] = pids + } + + inodes, ok := pids[pid] + if !ok { + inodes = make(set.Unordered[uint64]) + pids[pid] = inodes + } + + inodes.Add(inode) +} + +func (m *InodesMap) MarkListener(containerID string, pid int, path string) { + if m.l == nil { + m.l = make(map[string]map[int]string) + } + + pids, ok := m.l[containerID] + if !ok { + pids = make(map[int]string) + m.l[containerID] = pids + } + + pids[pid] = path +} + +func (m *InodesMap) findListener(containerID string, pid int) (path string, ok bool) { + if m.l == nil { + return + } + + pids, ok := m.l[containerID] + if !ok { + return + } + + path, ok = pids[pid] + + return +} + +func (m *InodesMap) nameFor(containerID string, pid int) (name string, ok bool) { + if m.n == nil { + return + } + + names, ok := m.n[containerID] + if !ok { + return + } + + name, ok = names[pid] + + return +} + +func (m *InodesMap) MarkUnknown(containerID string, pid int, inode uint64) { + if m.u == nil { + m.u = make(map[string]map[int]set.Unordered[uint64]) + } + + pids, ok := m.u[containerID] + if !ok { + pids = make(map[int]set.Unordered[uint64]) + m.u[containerID] = pids + } + + inodes, ok := pids[pid] + if !ok { + inodes = make(set.Unordered[uint64]) + pids[pid] = inodes + } + + inodes.Add(inode) +} + +func (m *InodesMap) ResolveUnknown( + cb func(srcCID, dstCID, srcName, dstName, path string), +) { + index := make(map[uint64]*item) + + for c, pids := range m.m { + for p, inodes := range pids { + inodes.Iter(func(k uint64) bool { + index[k] = &item{ + Cid: c, + Pid: p, + } + + return true + }) + } + } + + for dstCID, dstPids := range m.u { + for dstPID, inodes := range dstPids { + inodes.Iter(func(k uint64) bool { + known, ok := index[k] + if !ok { + return true + } + + path, ok := m.findListener(known.Cid, known.Pid) + if !ok { + return true + } + + srcName, ok := m.nameFor(known.Cid, known.Pid) + if !ok { + return true + } + + dstName, ok := m.nameFor(dstCID, dstPID) + if !ok { + return true + } + + cb(known.Cid, dstCID, srcName, dstName, path) + + return true + }) + } + } +} + +func (m *InodesMap) Has(containerID string, pid int, inode uint64) (yes bool) { + if m.m == nil { + return false + } + + pids, ok := m.m[containerID] + if !ok { + return false + } + + inodes, ok := pids[pid] + if !ok { + return false + } + + return inodes.Has(inode) +} diff --git a/internal/cluster/groups_test.go b/internal/cluster/groups_test.go index f70a459..ce83754 100644 --- a/internal/cluster/groups_test.go +++ b/internal/cluster/groups_test.go @@ -27,9 +27,9 @@ func TestAdd(t *testing.T) { Inbounds: make(set.Unordered[string]), Outbounds: make(set.Unordered[string]), Ports: makeTestPorts([]*node.Port{ - {Kind: "tcp", Value: 1}, - {Kind: "tcp", Value: 2}, - {Kind: "tcp", Value: 3}, + {Kind: "tcp", Value: "1"}, + {Kind: "tcp", Value: "2"}, + {Kind: "tcp", Value: "3"}, }), }) @@ -37,9 +37,9 @@ func TestAdd(t *testing.T) { Inbounds: make(set.Unordered[string]), Outbounds: make(set.Unordered[string]), Ports: makeTestPorts([]*node.Port{ - {Kind: "tcp", Value: 1}, - {Kind: "tcp", Value: 2}, - {Kind: "tcp", Value: 3}, + {Kind: "tcp", Value: "1"}, + {Kind: "tcp", Value: "2"}, + {Kind: "tcp", Value: "3"}, }), }) @@ -47,8 +47,8 @@ func TestAdd(t *testing.T) { Inbounds: make(set.Unordered[string]), Outbounds: make(set.Unordered[string]), Ports: makeTestPorts([]*node.Port{ - {Kind: "tcp", Value: 3}, - {Kind: "tcp", Value: 2}, + {Kind: "tcp", Value: "3"}, + {Kind: "tcp", Value: "2"}, }), }) @@ -56,8 +56,8 @@ func TestAdd(t *testing.T) { Inbounds: make(set.Unordered[string]), Outbounds: make(set.Unordered[string]), Ports: makeTestPorts([]*node.Port{ - {Kind: "tcp", Value: 1}, - {Kind: "tcp", Value: 3}, + {Kind: "tcp", Value: "1"}, + {Kind: "tcp", Value: "3"}, }), }) @@ -65,8 +65,8 @@ func TestAdd(t *testing.T) { Inbounds: make(set.Unordered[string]), Outbounds: make(set.Unordered[string]), Ports: makeTestPorts([]*node.Port{ - {Kind: "tcp", Value: 1}, - {Kind: "tcp", Value: 2}, + {Kind: "tcp", Value: "1"}, + {Kind: "tcp", Value: "2"}, }), }) @@ -74,9 +74,9 @@ func TestAdd(t *testing.T) { Inbounds: make(set.Unordered[string]), Outbounds: make(set.Unordered[string]), Ports: makeTestPorts([]*node.Port{ - {Kind: "tcp", Value: 1}, - {Kind: "tcp", Value: 2}, - {Kind: "tcp", Value: 4}, + {Kind: "tcp", Value: "1"}, + {Kind: "tcp", Value: "2"}, + {Kind: "tcp", Value: "4"}, }), }) @@ -84,7 +84,7 @@ func TestAdd(t *testing.T) { Inbounds: make(set.Unordered[string]), Outbounds: make(set.Unordered[string]), Ports: makeTestPorts([]*node.Port{ - {Kind: "tcp", Value: 1}, + {Kind: "tcp", Value: "1"}, }), }) diff --git a/internal/cluster/layers.go b/internal/cluster/layers.go index 57f0433..fb3fc47 100644 --- a/internal/cluster/layers.go +++ b/internal/cluster/layers.go @@ -14,25 +14,28 @@ import ( ) type Layers struct { - edges map[string]map[string]*node.Ports - nodes map[string]*node.Node - remotes set.Unordered[string] - b graph.NamedBuilderWriter - g connGraph - similarity float64 + b graph.NamedBuilderWriter + edges map[string]map[string]*node.Ports + nodes map[string]*node.Node + remotes set.Unordered[string] + g connGraph + defaultName string + similarity float64 } func NewLayers( b graph.NamedBuilderWriter, s float64, + d string, ) *Layers { return &Layers{ - b: NewRules(b, nil), - g: make(connGraph), - edges: make(map[string]map[string]*node.Ports), - nodes: make(map[string]*node.Node), - remotes: make(set.Unordered[string]), - similarity: s, + b: NewRules(b, nil), + g: make(connGraph), + edges: make(map[string]map[string]*node.Ports), + nodes: make(map[string]*node.Node), + remotes: make(set.Unordered[string]), + similarity: s, + defaultName: d, } } @@ -131,6 +134,7 @@ func (l *Layers) Write(w io.Writer) error { // store remains for _, n := range l.nodes { + n.Cluster = l.defaultName _ = l.b.AddNode(n) } diff --git a/internal/cluster/layers_test.go b/internal/cluster/layers_test.go index d84a5a7..2587bc2 100644 --- a/internal/cluster/layers_test.go +++ b/internal/cluster/layers_test.go @@ -17,7 +17,7 @@ func TestLayers(t *testing.T) { const similarity = 0.6 - ca := cluster.NewLayers(tb, similarity) + ca := cluster.NewLayers(tb, similarity, "foo") if !strings.Contains(ca.Name(), strconv.FormatFloat(similarity, 'f', 1, 64)) { t.Fail() @@ -27,9 +27,9 @@ func TestLayers(t *testing.T) { ID: "6", Name: "node-6", Ports: makeTestPorts([]*node.Port{ - {Kind: "tcp", Value: 6}, - {Kind: "tcp", Value: 1234}, - {Kind: "tcp", Value: 8080}, + {Kind: "tcp", Value: "6"}, + {Kind: "tcp", Value: "1234"}, + {Kind: "tcp", Value: "8080"}, }), }) @@ -37,8 +37,8 @@ func TestLayers(t *testing.T) { ID: "1", Name: "node-1", Ports: makeTestPorts([]*node.Port{ - {Kind: "tcp", Value: 80}, - {Kind: "tcp", Value: 443}, + {Kind: "tcp", Value: "80"}, + {Kind: "tcp", Value: "443"}, }), }) @@ -46,9 +46,9 @@ func TestLayers(t *testing.T) { ID: "2", Name: "node-2", Ports: makeTestPorts([]*node.Port{ - {Kind: "tcp", Value: 2}, - {Kind: "tcp", Value: 1234}, - {Kind: "tcp", Value: 8080}, + {Kind: "tcp", Value: "2"}, + {Kind: "tcp", Value: "1234"}, + {Kind: "tcp", Value: "8080"}, }), }) @@ -56,8 +56,8 @@ func TestLayers(t *testing.T) { ID: "3", Name: "node-3", Ports: makeTestPorts([]*node.Port{ - {Kind: "udp", Value: 53}, - {Kind: "tcp", Value: 8080}, + {Kind: "udp", Value: "53"}, + {Kind: "tcp", Value: "8080"}, }), }) @@ -65,7 +65,7 @@ func TestLayers(t *testing.T) { ID: "4", Name: "node-4", Ports: makeTestPorts([]*node.Port{ - {Kind: "tcp", Value: 9090}, + {Kind: "tcp", Value: "9090"}, }), }) @@ -73,8 +73,8 @@ func TestLayers(t *testing.T) { ID: "5", Name: "node-5", Ports: makeTestPorts([]*node.Port{ - {Kind: "tcp", Value: 1234}, - {Kind: "tcp", Value: 8081}, + {Kind: "tcp", Value: "1234"}, + {Kind: "tcp", Value: "8081"}, }), }) @@ -82,7 +82,7 @@ func TestLayers(t *testing.T) { ID: "R", Name: "R", Ports: makeTestPorts([]*node.Port{ - {Kind: "tcp", Value: 22}, + {Kind: "tcp", Value: "22"}, }), }) @@ -90,8 +90,8 @@ func TestLayers(t *testing.T) { ID: "5", Name: "node-5", Ports: makeTestPorts([]*node.Port{ - {Kind: "tcp", Value: 1234}, - {Kind: "tcp", Value: 8081}, + {Kind: "tcp", Value: "1234"}, + {Kind: "tcp", Value: "8081"}, }), }) @@ -104,55 +104,55 @@ func TestLayers(t *testing.T) { ca.AddEdge(&node.Edge{ SrcID: "1", DstID: "2", - Port: &node.Port{Kind: "tcp", Value: 1234}, + Port: &node.Port{Kind: "tcp", Value: "1234"}, }) ca.AddEdge(&node.Edge{ SrcID: "1", DstID: "3", - Port: &node.Port{Kind: "tcp", Value: 8080}, + Port: &node.Port{Kind: "tcp", Value: "8080"}, }) ca.AddEdge(&node.Edge{ SrcID: "1", DstID: "6", - Port: &node.Port{Kind: "tcp", Value: 8080}, + Port: &node.Port{Kind: "tcp", Value: "8080"}, }) ca.AddEdge(&node.Edge{ SrcID: "2", DstID: "4", - Port: &node.Port{Kind: "tcp", Value: 9090}, + Port: &node.Port{Kind: "tcp", Value: "9090"}, }) ca.AddEdge(&node.Edge{ SrcID: "3", DstID: "2", - Port: &node.Port{Kind: "tcp", Value: 9090}, + Port: &node.Port{Kind: "tcp", Value: "9090"}, }) ca.AddEdge(&node.Edge{ SrcID: "4", DstID: "3", - Port: &node.Port{Kind: "tcp", Value: 8080}, + Port: &node.Port{Kind: "tcp", Value: "8080"}, }) ca.AddEdge(&node.Edge{ SrcID: "1", DstID: "5", - Port: &node.Port{Kind: "tcp", Value: 8081}, + Port: &node.Port{Kind: "tcp", Value: "8081"}, }) ca.AddEdge(&node.Edge{ SrcID: "5", DstID: "4", - Port: &node.Port{Kind: "tcp", Value: 9090}, + Port: &node.Port{Kind: "tcp", Value: "9090"}, }) ca.AddEdge(&node.Edge{ SrcID: "5", DstID: "R", - Port: &node.Port{Kind: "tcp", Value: 22}, + Port: &node.Port{Kind: "tcp", Value: "22"}, }) if err := ca.Write(nil); err != nil { @@ -188,7 +188,7 @@ func TestLayersWriteError(t *testing.T) { const similarity = 0.5 - ca := cluster.NewLayers(tb, similarity) + ca := cluster.NewLayers(tb, similarity, "") if err := ca.Write(nil); !errors.Is(err, tb.Err) { t.Fail() diff --git a/internal/cluster/node.go b/internal/cluster/node.go index 5376d41..013c978 100644 --- a/internal/cluster/node.go +++ b/internal/cluster/node.go @@ -79,7 +79,8 @@ func portsToProtos(ports *node.Ports) (rv map[string][]int) { ports.Iter(func(_ string, pl []*node.Port) { for _, p := range pl { - rv[p.Kind] = append(rv[p.Kind], p.Value) + _ = p + // rv[p.Kind] = append(rv[p.Kind], p.Value) } }) diff --git a/internal/cluster/node_test.go b/internal/cluster/node_test.go index 6e91765..74380cb 100644 --- a/internal/cluster/node_test.go +++ b/internal/cluster/node_test.go @@ -49,7 +49,7 @@ func TestNodeMatchPorts(t *testing.T) { Ports: &node.Ports{}, } - a.Ports.Add("", &node.Port{Kind: "tcp", Value: 1}) + a.Ports.Add("", &node.Port{Kind: "tcp", Value: "1"}) b := &cluster.Node{ Inbounds: make(set.Unordered[string]), @@ -57,8 +57,8 @@ func TestNodeMatchPorts(t *testing.T) { Ports: &node.Ports{}, } - b.Ports.Add("", &node.Port{Kind: "tcp", Value: 1}) - b.Ports.Add("", &node.Port{Kind: "tcp", Value: 5}) + b.Ports.Add("", &node.Port{Kind: "tcp", Value: "1"}) + b.Ports.Add("", &node.Port{Kind: "tcp", Value: "5"}) a.Inbounds.Add("1") a.Outbounds.Add("2") diff --git a/internal/cluster/rules.go b/internal/cluster/rules.go index ec9ef2a..cf2d18c 100644 --- a/internal/cluster/rules.go +++ b/internal/cluster/rules.go @@ -3,7 +3,6 @@ package cluster import ( "cmp" "encoding/json" - "errors" "fmt" "io" "slices" @@ -15,12 +14,6 @@ import ( "github.com/s0rg/decompose/internal/node" ) -var ( - ErrInvalidFormat = errors.New("invalid format") - ErrInvalidRange = errors.New("invalid range") - ErrPortCollision = errors.New("ports collision") -) - type ( ruleJSON struct { Name string `json:"name"` @@ -184,7 +177,7 @@ func (cb *Rules) FromReader(r io.Reader) (err error) { func (cb *Rules) Match(n *node.Node) (cluster string, ok bool) { if len(cb.rules) == 0 { - return "", false + return } for _, rule := range cb.rules { diff --git a/internal/cluster/rules_test.go b/internal/cluster/rules_test.go index 72423c8..65ec086 100644 --- a/internal/cluster/rules_test.go +++ b/internal/cluster/rules_test.go @@ -78,41 +78,41 @@ func TestRulesMatch(t *testing.T) { }{ { Node: &node.Node{ - Ports: makeTestPorts([]*node.Port{{Kind: "tcp", Value: 80}}), + Ports: makeTestPorts([]*node.Port{{Kind: "tcp", Value: "80"}}), }, Want: "foo", }, { Node: &node.Node{ - Ports: makeTestPorts([]*node.Port{{Kind: "tcp", Value: 22}}), + Ports: makeTestPorts([]*node.Port{{Kind: "tcp", Value: "22"}}), }, Want: "bar", }, { Node: &node.Node{ - Ports: makeTestPorts([]*node.Port{{Kind: "tcp", Value: 443}}), + Ports: makeTestPorts([]*node.Port{{Kind: "tcp", Value: "443"}}), }, Want: "bar", }, { Node: &node.Node{Ports: makeTestPorts([]*node.Port{ - {Kind: "tcp", Value: 22}, - {Kind: "tcp", Value: 80}, - {Kind: "tcp", Value: 443}, + {Kind: "tcp", Value: "22"}, + {Kind: "tcp", Value: "80"}, + {Kind: "tcp", Value: "443"}, })}, Want: "foo", }, { Node: &node.Node{Ports: makeTestPorts([]*node.Port{ - {Kind: "tcp", Value: 22}, - {Kind: "tcp", Value: 80}, - {Kind: "tcp", Value: 8080}, + {Kind: "tcp", Value: "22"}, + {Kind: "tcp", Value: "80"}, + {Kind: "tcp", Value: "8080"}, })}, Want: "foo", }, { Node: &node.Node{Ports: makeTestPorts([]*node.Port{ - {Kind: "sstp", Value: 5000}, + {Kind: "sstp", Value: "5000"}, })}, Want: "", }, @@ -157,9 +157,9 @@ func TestRulesMatchWeight(t *testing.T) { {"name": "bar", "if": "node.Listen.HasAny('22/tcp', '443/tcp')"}]` testNode := &node.Node{Ports: makeTestPorts([]*node.Port{ - {Kind: "tcp", Value: 22}, - {Kind: "tcp", Value: 80}, - {Kind: "tcp", Value: 8080}, + {Kind: "tcp", Value: "22"}, + {Kind: "tcp", Value: "80"}, + {Kind: "tcp", Value: "8080"}, })} ca := cluster.NewRules(nil, nil) @@ -191,62 +191,62 @@ func TestRules(t *testing.T) { ca.AddNode(&node.Node{ ID: "1", Ports: makeTestPorts([]*node.Port{ - {Kind: "tcp", Value: 80}, + {Kind: "tcp", Value: "80"}, })}) ca.AddNode(&node.Node{ ID: "2", Ports: makeTestPorts([]*node.Port{ - {Kind: "tcp", Value: 22}, + {Kind: "tcp", Value: "22"}, })}) ca.AddNode(&node.Node{ ID: "3", Ports: makeTestPorts([]*node.Port{ - {Kind: "tcp", Value: 443}, - {Kind: "tcp", Value: 8080}, + {Kind: "tcp", Value: "443"}, + {Kind: "tcp", Value: "8080"}, })}) ca.AddNode(&node.Node{ ID: "4", Ports: makeTestPorts([]*node.Port{ - {Kind: "tcp", Value: 8080}, + {Kind: "tcp", Value: "8080"}, })}) ca.AddEdge(&node.Edge{ SrcID: "1", DstID: "3", - Port: &node.Port{Kind: "tcp", Value: 443}, + Port: &node.Port{Kind: "tcp", Value: "443"}, }) ca.AddEdge(&node.Edge{ SrcID: "3", DstID: "1", - Port: &node.Port{Kind: "tcp", Value: 8080}, + Port: &node.Port{Kind: "tcp", Value: "8080"}, }) ca.AddEdge(&node.Edge{ SrcID: "3", DstID: "1", - Port: &node.Port{Kind: "tcp", Value: 80}, + Port: &node.Port{Kind: "tcp", Value: "80"}, }) ca.AddEdge(&node.Edge{ SrcID: "1", DstID: "4", - Port: &node.Port{Kind: "tcp", Value: 8080}, + Port: &node.Port{Kind: "tcp", Value: "8080"}, }) ca.AddEdge(&node.Edge{ SrcID: "5", DstID: "1", - Port: &node.Port{Kind: "tcp", Value: 80}, + Port: &node.Port{Kind: "tcp", Value: "80"}, }) ca.AddEdge(&node.Edge{ SrcID: "1", DstID: "5", - Port: &node.Port{Kind: "tcp", Value: 80}, + Port: &node.Port{Kind: "tcp", Value: "80"}, }) if tb.Edges != 4 || tb.Nodes != 4 { @@ -295,7 +295,7 @@ func TestRulesBuilderAddError(t *testing.T) { err := ca.AddNode(&node.Node{ ID: "1", Ports: makeTestPorts([]*node.Port{ - {Kind: "tcp", Value: 80}, + {Kind: "tcp", Value: "80"}, })}) if !errors.Is(err, myError) { t.Fail() @@ -328,7 +328,7 @@ func TestRulesBuilderWriteError(t *testing.T) { ca.AddNode(&node.Node{ ID: "1", Ports: makeTestPorts([]*node.Port{ - {Kind: "tcp", Value: 80}, + {Kind: "tcp", Value: "80"}, })}) tb.Err = errors.New("test-error") diff --git a/internal/graph/build.go b/internal/graph/build.go index b5004df..c03b174 100644 --- a/internal/graph/build.go +++ b/internal/graph/build.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "log" + "strconv" "github.com/s0rg/decompose/internal/node" ) @@ -52,7 +53,7 @@ func Build( containers, err := cli.Containers( context.Background(), cfg.Proto, - cfg.FullInfo, + cfg.Unix, cfg.Deep, cfg.SkipEnv, func(cur, total int) { @@ -136,11 +137,11 @@ func createNodes( skip = !cfg.MatchName(con.Name) con.IterOutbounds(func(c *Connection) { - if c.RemoteIP.IsLoopback() { + if c.Proto == UNIX || c.DstIP.IsLoopback() { return } - rip := c.RemoteIP.String() + rip := c.DstIP.String() if lc, ok := local[rip]; ok { // destination known if skip && cfg.MatchName(lc.Name) { @@ -163,7 +164,7 @@ func createNodes( rem.Ports.Add(ProcessRemote, &node.Port{ Kind: c.Proto.String(), - Value: int(c.RemotePort), + Value: strconv.Itoa(c.DstPort), }) }) @@ -188,29 +189,41 @@ func buildEdges( } con.IterOutbounds(func(c *Connection) { - key := c.RemoteIP.String() - port := &node.Port{ - Kind: c.Proto.String(), - Value: int(c.RemotePort), - } - var ( - ldst *Container - ok bool + port = &node.Port{ + Kind: c.Proto.String(), + } + dstID string + key string + ok bool ) - if c.RemoteIP.IsLoopback() { - ldst, ok = con, true + if c.Proto == UNIX { + port.Value = c.Path + dstID, ok = c.DstID, true } else { - ldst, ok = local[key] + key = c.DstIP.String() + port.Value = strconv.Itoa(c.DstPort) + + var ldst *Container + + if c.DstIP.IsLoopback() { + ldst, ok = con, true + } else { + ldst, ok = local[key] + } + + if ok { + dstID = ldst.ID + } } if ok { - if cfg.NoLoops && con.ID == ldst.ID { + if cfg.NoLoops && con.ID == dstID { return } - dst, found := nodes[ldst.ID] + dst, found := nodes[dstID] if !found { return } @@ -223,7 +236,7 @@ func buildEdges( cfg.Builder.AddEdge(&node.Edge{ SrcID: src.ID, SrcName: c.Process, - DstID: ldst.ID, + DstID: dstID, DstName: dname, Port: port, }) diff --git a/internal/graph/build_test.go b/internal/graph/build_test.go index 8b78701..3e8c12d 100644 --- a/internal/graph/build_test.go +++ b/internal/graph/build_test.go @@ -136,24 +136,24 @@ func testClientWithEnv() graph.ContainerClient { // node 1 cli.Data[0].AddMany([]*graph.Connection{ - {LocalPort: 1, Proto: graph.TCP}, // listen 1 - {RemoteIP: node2, LocalPort: 10, RemotePort: 2, Proto: graph.TCP}, // connected to node2:2 - {RemoteIP: external, LocalPort: 10, RemotePort: 1, Proto: graph.TCP}, // connected to external:1 + {SrcPort: 1, Proto: graph.TCP}, // listen 1 + {DstIP: node2, SrcPort: 10, DstPort: 2, Proto: graph.TCP}, // connected to node2:2 + {DstIP: external, SrcPort: 10, DstPort: 1, Proto: graph.TCP}, // connected to external:1 }) // node 2 cli.Data[1].AddMany([]*graph.Connection{ - {LocalPort: 2, Proto: graph.TCP}, // listen 2 - {RemoteIP: node3, LocalPort: 10, RemotePort: 3, Proto: graph.TCP}, // connected to node3:3 - {RemoteIP: external, LocalPort: 10, RemotePort: 2, Proto: graph.TCP}, // connected to external:2 + {SrcPort: 2, Proto: graph.TCP}, // listen 2 + {DstIP: node3, SrcPort: 10, DstPort: 3, Proto: graph.TCP}, // connected to node3:3 + {DstIP: external, SrcPort: 10, DstPort: 2, Proto: graph.TCP}, // connected to external:2 }) // node 3 cli.Data[2].AddMany([]*graph.Connection{ - {LocalPort: 3, Proto: graph.TCP}, // listen 3 - {RemoteIP: node1, LocalPort: 10, RemotePort: 1, Proto: graph.TCP}, // connected to node1:1 - {RemoteIP: node2, LocalPort: 122, RemotePort: 22, Proto: graph.TCP}, // connected to node2:22 - {RemoteIP: external, LocalPort: 10, RemotePort: 3, Proto: graph.TCP}, // connected to external:3 + {SrcPort: 3, Proto: graph.TCP}, // listen 3 + {DstIP: node1, SrcPort: 10, DstPort: 1, Proto: graph.TCP}, // connected to node1:1 + {DstIP: node2, SrcPort: 122, DstPort: 22, Proto: graph.TCP}, // connected to node2:22 + {DstIP: external, SrcPort: 10, DstPort: 3, Proto: graph.TCP}, // connected to external:3 }) return cli @@ -302,15 +302,15 @@ func TestBuildLoops(t *testing.T) { }} cli.Data[0].AddMany([]*graph.Connection{ - {LocalPort: 1, Proto: graph.TCP}, // listen 1 - {RemoteIP: node2, LocalPort: 10, RemotePort: 2, Proto: graph.TCP}, // connected to node2:2 - {RemoteIP: node1, LocalPort: 10, RemotePort: 1, Proto: graph.TCP}, // connected to itself + {SrcPort: 1, Proto: graph.TCP}, // listen 1 + {DstIP: node2, SrcPort: 10, DstPort: 2, Proto: graph.TCP}, // connected to node2:2 + {DstIP: node1, SrcPort: 10, DstPort: 1, Proto: graph.TCP}, // connected to itself }) cli.Data[1].AddMany([]*graph.Connection{ - {LocalPort: 2, Proto: graph.TCP}, // listen 2 - {RemoteIP: node1, LocalPort: 10, RemotePort: 1, Proto: graph.TCP}, // connected to node1:1 - {RemoteIP: local, LocalPort: 11, RemotePort: 2, Proto: graph.TCP}, // connected to self:2 + {SrcPort: 2, Proto: graph.TCP}, // listen 2 + {DstIP: node1, SrcPort: 10, DstPort: 1, Proto: graph.TCP}, // connected to node1:1 + {DstIP: local, SrcPort: 11, DstPort: 2, Proto: graph.TCP}, // connected to self:2 }) bld := &testBuilder{} diff --git a/internal/graph/compress.go b/internal/graph/compress.go index d45af21..64eae48 100644 --- a/internal/graph/compress.go +++ b/internal/graph/compress.go @@ -16,7 +16,6 @@ import ( const ( externalGroup = "external" - defaultGroup = "core" ) type Compressor struct { @@ -25,6 +24,7 @@ type Compressor struct { groups map[string]*node.Node // "compressed" nodes groupID -> node index map[string]string // index holds nodeID -> groupID mapping conns map[string]map[string][]*node.Port // "raw" connections nodeID -> nodeID -> []port + group string edges int diff int force bool @@ -32,6 +32,7 @@ type Compressor struct { func NewCompressor( bldr NamedBuilderWriter, + group string, diff int, force bool, ) *Compressor { @@ -39,6 +40,7 @@ func NewCompressor( b: bldr, diff: diff, force: force, + group: group, index: make(map[string]string), nodes: make(map[string]*node.Node), groups: make(map[string]*node.Node), @@ -143,7 +145,7 @@ func (c *Compressor) buildGroups() { nodes = append(nodes, nodeID) }) - grp := defaultGroup + grp := c.group if len(nodes) > 1 { grp = cleanName(key) } @@ -158,7 +160,7 @@ func (c *Compressor) buildGroups() { } seen.Iter(func(id string) (next bool) { - grp := defaultGroup + grp := c.group if c.nodes[id].IsExternal() { grp = externalGroup diff --git a/internal/graph/compress_test.go b/internal/graph/compress_test.go index 0d2037e..1a769ba 100644 --- a/internal/graph/compress_test.go +++ b/internal/graph/compress_test.go @@ -47,7 +47,7 @@ func TestCompressor(t *testing.T) { tb := &testNamedBuilder{} - c := graph.NewCompressor(tb, diff, false) + c := graph.NewCompressor(tb, "", diff, false) // ingress nginx := &node.Node{ @@ -56,7 +56,7 @@ func TestCompressor(t *testing.T) { Ports: &node.Ports{}, } - nginx.Ports.Add("nginx", &node.Port{Kind: "tcp", Value: 443}) + nginx.Ports.Add("nginx", &node.Port{Kind: "tcp", Value: "443"}) c.AddNode(nginx) // apps @@ -66,7 +66,7 @@ func TestCompressor(t *testing.T) { Ports: &node.Ports{}, } - app1.Ports.Add("app", &node.Port{Kind: "tcp", Value: 8080}) + app1.Ports.Add("app", &node.Port{Kind: "tcp", Value: "8080"}) c.AddNode(app1) app2 := &node.Node{ @@ -75,7 +75,7 @@ func TestCompressor(t *testing.T) { Ports: &node.Ports{}, } - app2.Ports.Add("app", &node.Port{Kind: "tcp", Value: 8080}) + app2.Ports.Add("app", &node.Port{Kind: "tcp", Value: "8080"}) c.AddNode(app2) // dbs @@ -85,7 +85,7 @@ func TestCompressor(t *testing.T) { Ports: &node.Ports{}, } - bouncer.Ports.Add("pgbouncer", &node.Port{Kind: "tcp", Value: 5432}) + bouncer.Ports.Add("pgbouncer", &node.Port{Kind: "tcp", Value: "5432"}) c.AddNode(bouncer) db1 := &node.Node{ @@ -94,7 +94,7 @@ func TestCompressor(t *testing.T) { Ports: &node.Ports{}, } - db1.Ports.Add("postgres", &node.Port{Kind: "tcp", Value: 5432}) + db1.Ports.Add("postgres", &node.Port{Kind: "tcp", Value: "5432"}) c.AddNode(db1) db2 := &node.Node{ @@ -103,7 +103,7 @@ func TestCompressor(t *testing.T) { Ports: &node.Ports{}, } - db2.Ports.Add("postgres", &node.Port{Kind: "tcp", Value: 5432}) + db2.Ports.Add("postgres", &node.Port{Kind: "tcp", Value: "5432"}) c.AddNode(db2) // external @@ -113,7 +113,7 @@ func TestCompressor(t *testing.T) { Ports: &node.Ports{}, } - ext.Ports.Add("", &node.Port{Kind: "tcp", Value: 9000}) + ext.Ports.Add("", &node.Port{Kind: "tcp", Value: "9000"}) c.AddNode(ext) // edges @@ -121,67 +121,67 @@ func TestCompressor(t *testing.T) { c.AddEdge(&node.Edge{ SrcID: "1", DstID: "2", - Port: &node.Port{Kind: "tcp", Value: 8080}, + Port: &node.Port{Kind: "tcp", Value: "8080"}, }) c.AddEdge(&node.Edge{ SrcID: "1", DstID: "3", - Port: &node.Port{Kind: "tcp", Value: 8080}, + Port: &node.Port{Kind: "tcp", Value: "8080"}, }) c.AddEdge(&node.Edge{ SrcID: "2", DstID: "4", - Port: &node.Port{Kind: "tcp", Value: 5432}, + Port: &node.Port{Kind: "tcp", Value: "5432"}, }) c.AddEdge(&node.Edge{ SrcID: "3", DstID: "4", - Port: &node.Port{Kind: "tcp", Value: 5432}, + Port: &node.Port{Kind: "tcp", Value: "5432"}, }) c.AddEdge(&node.Edge{ SrcID: "4", DstID: "5", - Port: &node.Port{Kind: "tcp", Value: 5432}, + Port: &node.Port{Kind: "tcp", Value: "5432"}, }) c.AddEdge(&node.Edge{ SrcID: "4", DstID: "5", - Port: &node.Port{Kind: "tcp", Value: 5432}, + Port: &node.Port{Kind: "tcp", Value: "5432"}, }) c.AddEdge(&node.Edge{ SrcID: "1", DstID: "2", - Port: &node.Port{Kind: "udp", Value: 22}, + Port: &node.Port{Kind: "udp", Value: "22"}, }) c.AddEdge(&node.Edge{ SrcID: "X", DstID: "2", - Port: &node.Port{Kind: "udp", Value: 22}, + Port: &node.Port{Kind: "udp", Value: "22"}, }) c.AddEdge(&node.Edge{ SrcID: "1", DstID: "X", - Port: &node.Port{Kind: "udp", Value: 22}, + Port: &node.Port{Kind: "udp", Value: "22"}, }) c.AddEdge(&node.Edge{ SrcID: "2", DstID: "EXT", - Port: &node.Port{Kind: "tcp", Value: 9000}, + Port: &node.Port{Kind: "tcp", Value: "9000"}, }) c.AddEdge(&node.Edge{ SrcID: "3", DstID: "EXT", - Port: &node.Port{Kind: "tcp", Value: 9000}, + Port: &node.Port{Kind: "tcp", Value: "9000"}, }) if err := c.Write(nil); err != nil { @@ -198,7 +198,7 @@ func TestCompressorForce(t *testing.T) { tb := &testNamedBuilder{} - c := graph.NewCompressor(tb, 1, true) + c := graph.NewCompressor(tb, "", 1, true) a1 := &node.Node{ ID: "a1-id", @@ -206,7 +206,7 @@ func TestCompressorForce(t *testing.T) { Ports: &node.Ports{}, } - a1.Ports.Add("app", &node.Port{Kind: "tcp", Value: 8080}) + a1.Ports.Add("app", &node.Port{Kind: "tcp", Value: "8080"}) c.AddNode(a1) a2 := &node.Node{ @@ -215,7 +215,7 @@ func TestCompressorForce(t *testing.T) { Ports: &node.Ports{}, } - a2.Ports.Add("app", &node.Port{Kind: "tcp", Value: 8080}) + a2.Ports.Add("app", &node.Port{Kind: "tcp", Value: "8080"}) c.AddNode(a2) a3 := &node.Node{ @@ -224,7 +224,7 @@ func TestCompressorForce(t *testing.T) { Ports: &node.Ports{}, } - a3.Ports.Add("app", &node.Port{Kind: "tcp", Value: 8080}) + a3.Ports.Add("app", &node.Port{Kind: "tcp", Value: "8080"}) c.AddNode(a3) b1 := &node.Node{ @@ -233,7 +233,7 @@ func TestCompressorForce(t *testing.T) { Ports: &node.Ports{}, } - b1.Ports.Add("app", &node.Port{Kind: "tcp", Value: 8080}) + b1.Ports.Add("app", &node.Port{Kind: "tcp", Value: "8080"}) c.AddNode(b1) b2 := &node.Node{ @@ -242,7 +242,7 @@ func TestCompressorForce(t *testing.T) { Ports: &node.Ports{}, } - b2.Ports.Add("app", &node.Port{Kind: "tcp", Value: 8080}) + b2.Ports.Add("app", &node.Port{Kind: "tcp", Value: "8080"}) c.AddNode(b2) b3 := &node.Node{ @@ -251,7 +251,7 @@ func TestCompressorForce(t *testing.T) { Ports: &node.Ports{}, } - b3.Ports.Add("app", &node.Port{Kind: "tcp", Value: 8080}) + b3.Ports.Add("app", &node.Port{Kind: "tcp", Value: "8080"}) c.AddNode(b3) c1 := &node.Node{ @@ -260,7 +260,7 @@ func TestCompressorForce(t *testing.T) { Ports: &node.Ports{}, } - c1.Ports.Add("app", &node.Port{Kind: "tcp", Value: 8080}) + c1.Ports.Add("app", &node.Port{Kind: "tcp", Value: "8080"}) c.AddNode(c1) c2 := &node.Node{ @@ -269,7 +269,7 @@ func TestCompressorForce(t *testing.T) { Ports: &node.Ports{}, } - c2.Ports.Add("app", &node.Port{Kind: "tcp", Value: 8080}) + c2.Ports.Add("app", &node.Port{Kind: "tcp", Value: "8080"}) c.AddNode(c2) c3 := &node.Node{ @@ -278,7 +278,7 @@ func TestCompressorForce(t *testing.T) { Ports: &node.Ports{}, } - c3.Ports.Add("app", &node.Port{Kind: "tcp", Value: 8080}) + c3.Ports.Add("app", &node.Port{Kind: "tcp", Value: "8080"}) c.AddNode(c3) d1 := &node.Node{ @@ -287,7 +287,7 @@ func TestCompressorForce(t *testing.T) { Ports: &node.Ports{}, } - d1.Ports.Add("app", &node.Port{Kind: "tcp", Value: 5555}) + d1.Ports.Add("app", &node.Port{Kind: "tcp", Value: "5555"}) c.AddNode(d1) d2 := &node.Node{ @@ -296,7 +296,7 @@ func TestCompressorForce(t *testing.T) { Ports: &node.Ports{}, } - d2.Ports.Add("app", &node.Port{Kind: "tcp", Value: 5555}) + d2.Ports.Add("app", &node.Port{Kind: "tcp", Value: "5555"}) c.AddNode(d2) e1 := &node.Node{ @@ -305,7 +305,7 @@ func TestCompressorForce(t *testing.T) { Ports: &node.Ports{}, } - e1.Ports.Add("app", &node.Port{Kind: "tcp", Value: 6666}) + e1.Ports.Add("app", &node.Port{Kind: "tcp", Value: "6666"}) c.AddNode(e1) e2 := &node.Node{ @@ -314,7 +314,7 @@ func TestCompressorForce(t *testing.T) { Ports: &node.Ports{}, } - e2.Ports.Add("app", &node.Port{Kind: "tcp", Value: 6666}) + e2.Ports.Add("app", &node.Port{Kind: "tcp", Value: "6666"}) c.AddNode(e2) f1 := &node.Node{ @@ -323,7 +323,7 @@ func TestCompressorForce(t *testing.T) { Ports: &node.Ports{}, } - f1.Ports.Add("app", &node.Port{Kind: "tcp", Value: 7777}) + f1.Ports.Add("app", &node.Port{Kind: "tcp", Value: "7777"}) c.AddNode(f1) f2 := &node.Node{ @@ -332,139 +332,139 @@ func TestCompressorForce(t *testing.T) { Ports: &node.Ports{}, } - f2.Ports.Add("app", &node.Port{Kind: "tcp", Value: 7777}) + f2.Ports.Add("app", &node.Port{Kind: "tcp", Value: "7777"}) c.AddNode(f2) c.AddEdge(&node.Edge{ SrcID: "a1-id", DstID: "b1-id", - Port: &node.Port{Kind: "tcp", Value: 8080}, + Port: &node.Port{Kind: "tcp", Value: "8080"}, }) c.AddEdge(&node.Edge{ SrcID: "a1-id", DstID: "c1-id", - Port: &node.Port{Kind: "tcp", Value: 8080}, + Port: &node.Port{Kind: "tcp", Value: "8080"}, }) c.AddEdge(&node.Edge{ SrcID: "a2-id", DstID: "b2-id", - Port: &node.Port{Kind: "tcp", Value: 8080}, + Port: &node.Port{Kind: "tcp", Value: "8080"}, }) c.AddEdge(&node.Edge{ SrcID: "a2-id", DstID: "c2-id", - Port: &node.Port{Kind: "tcp", Value: 8080}, + Port: &node.Port{Kind: "tcp", Value: "8080"}, }) c.AddEdge(&node.Edge{ SrcID: "b1-id", DstID: "a1-id", - Port: &node.Port{Kind: "tcp", Value: 8080}, + Port: &node.Port{Kind: "tcp", Value: "8080"}, }) c.AddEdge(&node.Edge{ SrcID: "b1-id", DstID: "c1-id", - Port: &node.Port{Kind: "tcp", Value: 8080}, + Port: &node.Port{Kind: "tcp", Value: "8080"}, }) c.AddEdge(&node.Edge{ SrcID: "b2-id", DstID: "a2-id", - Port: &node.Port{Kind: "tcp", Value: 8080}, + Port: &node.Port{Kind: "tcp", Value: "8080"}, }) c.AddEdge(&node.Edge{ SrcID: "b2-id", DstID: "c2-id", - Port: &node.Port{Kind: "tcp", Value: 8080}, + Port: &node.Port{Kind: "tcp", Value: "8080"}, }) c.AddEdge(&node.Edge{ SrcID: "c1-id", DstID: "a1-id", - Port: &node.Port{Kind: "tcp", Value: 8080}, + Port: &node.Port{Kind: "tcp", Value: "8080"}, }) c.AddEdge(&node.Edge{ SrcID: "c1-id", DstID: "b1-id", - Port: &node.Port{Kind: "tcp", Value: 8080}, + Port: &node.Port{Kind: "tcp", Value: "8080"}, }) c.AddEdge(&node.Edge{ SrcID: "c2-id", DstID: "a2-id", - Port: &node.Port{Kind: "tcp", Value: 8080}, + Port: &node.Port{Kind: "tcp", Value: "8080"}, }) c.AddEdge(&node.Edge{ SrcID: "c2-id", DstID: "a2-id", - Port: &node.Port{Kind: "tcp", Value: 8080}, + Port: &node.Port{Kind: "tcp", Value: "8080"}, }) c.AddEdge(&node.Edge{ SrcID: "a1-id", DstID: "d1-id", - Port: &node.Port{Kind: "tcp", Value: 5555}, + Port: &node.Port{Kind: "tcp", Value: "5555"}, }) c.AddEdge(&node.Edge{ SrcID: "a2-id", DstID: "d2-id", - Port: &node.Port{Kind: "tcp", Value: 5555}, + Port: &node.Port{Kind: "tcp", Value: "5555"}, }) c.AddEdge(&node.Edge{ SrcID: "b1-id", DstID: "e1-id", - Port: &node.Port{Kind: "tcp", Value: 6666}, + Port: &node.Port{Kind: "tcp", Value: "6666"}, }) c.AddEdge(&node.Edge{ SrcID: "b2-id", DstID: "e2-id", - Port: &node.Port{Kind: "tcp", Value: 6666}, + Port: &node.Port{Kind: "tcp", Value: "6666"}, }) c.AddEdge(&node.Edge{ SrcID: "e1-id", DstID: "b2-id", - Port: &node.Port{Kind: "tcp", Value: 8080}, + Port: &node.Port{Kind: "tcp", Value: "8080"}, }) c.AddEdge(&node.Edge{ SrcID: "e2-id", DstID: "b1-id", - Port: &node.Port{Kind: "tcp", Value: 8080}, + Port: &node.Port{Kind: "tcp", Value: "8080"}, }) c.AddEdge(&node.Edge{ SrcID: "f1-id", DstID: "c1-id", - Port: &node.Port{Kind: "tcp", Value: 8080}, + Port: &node.Port{Kind: "tcp", Value: "8080"}, }) c.AddEdge(&node.Edge{ SrcID: "f2-id", DstID: "c2-id", - Port: &node.Port{Kind: "tcp", Value: 8080}, + Port: &node.Port{Kind: "tcp", Value: "8080"}, }) c.AddEdge(&node.Edge{ SrcID: "a1-id", DstID: "a1-id", - Port: &node.Port{Kind: "tcp", Value: 8080}, + Port: &node.Port{Kind: "tcp", Value: "8080"}, }) c.AddEdge(&node.Edge{ SrcID: "a1-id", DstID: "a2-id", - Port: &node.Port{Kind: "tcp", Value: 8080}, + Port: &node.Port{Kind: "tcp", Value: "8080"}, }) if err := c.Write(nil); err != nil { @@ -481,7 +481,7 @@ func TestCompressorName(t *testing.T) { tb := &testNamedBuilder{} - c := graph.NewCompressor(tb, diff, false) + c := graph.NewCompressor(tb, "", diff, false) if !strings.Contains(c.Name(), "compressed") { t.Fail() @@ -496,7 +496,7 @@ func TestCompressorAddError(t *testing.T) { AddError: myErr, } - c := graph.NewCompressor(tb, diff, false) + c := graph.NewCompressor(tb, "", diff, false) if err := c.AddNode(&node.Node{Ports: &node.Ports{}}); err != nil { t.Fail() @@ -516,7 +516,7 @@ func TestCompressorWriteError(t *testing.T) { WriteError: myErr, } - c := graph.NewCompressor(tb, diff, false) + c := graph.NewCompressor(tb, "", diff, false) if err := c.AddNode(&node.Node{Ports: &node.Ports{}}); err != nil { t.Fail() diff --git a/internal/graph/config.go b/internal/graph/config.go index 2df1f5a..b87faa0 100644 --- a/internal/graph/config.go +++ b/internal/graph/config.go @@ -9,8 +9,8 @@ type Config struct { SkipEnv []string Proto NetProto OnlyLocal bool - FullInfo bool NoLoops bool + Unix bool Deep bool } diff --git a/internal/graph/connection.go b/internal/graph/connection.go index 597f1e9..16f722d 100644 --- a/internal/graph/connection.go +++ b/internal/graph/connection.go @@ -8,37 +8,49 @@ import ( ) type Connection struct { - Process string - LocalIP net.IP - RemoteIP net.IP - RemotePort uint16 - LocalPort uint16 - Proto NetProto + Process string + DstID string + Path string + SrcIP net.IP + DstIP net.IP + Inode uint64 + SrcPort int + DstPort int + Proto NetProto + Listen bool } func (c *Connection) IsListener() bool { - return c.RemotePort == 0 + return c.Listen } func (c *Connection) IsInbound() bool { - return c.LocalPort < c.RemotePort + if c.Proto == UNIX { + return c.Listen + } + + return c.SrcPort < c.DstPort } func (c *Connection) IsLocal() bool { - return c.LocalIP.IsLoopback() + if c.Proto == UNIX { + return false + } + + return c.SrcIP.IsLoopback() } func (c *Connection) UniqID() (id uint64, ok bool) { var key string switch { + case c.Proto == UNIX: + key = c.Path case c.IsListener(): - key, ok = c.Proto.String()+strconv.Itoa(int(c.LocalPort)), true + key = c.Proto.String() + strconv.Itoa(c.SrcPort) case !c.IsInbound(): - key, ok = c.RemoteIP.String()+c.Proto.String()+strconv.Itoa(int(c.RemotePort)), true - } - - if !ok { + key = c.DstIP.String() + c.Proto.String() + strconv.Itoa(c.DstPort) + default: return } diff --git a/internal/graph/connection_test.go b/internal/graph/connection_test.go index 71960f6..7e5cee0 100644 --- a/internal/graph/connection_test.go +++ b/internal/graph/connection_test.go @@ -15,7 +15,7 @@ func TestConnectionIsListener(t *testing.T) { t.Fail() } - c.RemotePort = 1 + c.DstPort = 1 if c.IsListener() { t.Fail() @@ -27,13 +27,13 @@ func TestConnectionIsInbound(t *testing.T) { c := graph.Connection{} - c.RemotePort = 1 + c.DstPort = 1 if !c.IsInbound() { t.Fail() } - c.LocalPort = 2 + c.SrcPort = 2 if c.IsInbound() { t.Fail() diff --git a/internal/graph/conngroup.go b/internal/graph/conngroup.go index ff3962a..034a404 100644 --- a/internal/graph/conngroup.go +++ b/internal/graph/conngroup.go @@ -71,7 +71,7 @@ func (cg *ConnGroup) Sort() { func compare(a, b *Connection) int { if a.Proto == b.Proto { - return cmp.Compare(a.LocalPort, b.LocalPort) + return cmp.Compare(a.SrcPort, b.SrcPort) } return cmp.Compare(a.Proto, b.Proto) diff --git a/internal/graph/conngroup_test.go b/internal/graph/conngroup_test.go index 4cad925..d70eb8c 100644 --- a/internal/graph/conngroup_test.go +++ b/internal/graph/conngroup_test.go @@ -15,11 +15,11 @@ func TestConnGroupListeners(t *testing.T) { t.Fail() } - cg.AddListener(&graph.Connection{Proto: graph.TCP, LocalPort: 1}) - cg.AddListener(&graph.Connection{Proto: graph.UDP, LocalPort: 1}) + cg.AddListener(&graph.Connection{Proto: graph.TCP, SrcPort: 1}) + cg.AddListener(&graph.Connection{Proto: graph.UDP, SrcPort: 1}) - cg.AddListener(&graph.Connection{Proto: graph.TCP, LocalPort: 1}) // duplicate - cg.AddListener(&graph.Connection{Proto: graph.TCP, RemotePort: 1}) // invalid + cg.AddListener(&graph.Connection{Proto: graph.TCP, SrcPort: 1}) // duplicate + cg.AddListener(&graph.Connection{Proto: graph.TCP, DstPort: 1}) // invalid if cg.Len() != 2 { t.Fail() @@ -32,7 +32,7 @@ func TestConnGroupListeners(t *testing.T) { }) cg.IterListeners(func(c *graph.Connection) { - if c.LocalPort != 1 { + if c.SrcPort != 1 { t.Fail() } }) @@ -47,11 +47,11 @@ func TestConnGroupOutbounds(t *testing.T) { t.Fail() } - cg.AddOutbound(&graph.Connection{Proto: graph.TCP, LocalPort: 2, RemotePort: 1}) - cg.AddOutbound(&graph.Connection{Proto: graph.UDP, LocalPort: 3, RemotePort: 1}) + cg.AddOutbound(&graph.Connection{Proto: graph.TCP, SrcPort: 2, DstPort: 1}) + cg.AddOutbound(&graph.Connection{Proto: graph.UDP, SrcPort: 3, DstPort: 1}) - cg.AddOutbound(&graph.Connection{Proto: graph.TCP, LocalPort: 2, RemotePort: 1}) - cg.AddOutbound(&graph.Connection{Proto: graph.TCP, RemotePort: 1}) + cg.AddOutbound(&graph.Connection{Proto: graph.TCP, SrcPort: 2, DstPort: 1}) + cg.AddOutbound(&graph.Connection{Proto: graph.TCP, DstPort: 1}) if cg.Len() != 2 { t.Fail() @@ -64,7 +64,7 @@ func TestConnGroupOutbounds(t *testing.T) { }) cg.IterOutbounds(func(c *graph.Connection) { - if c.RemotePort != 1 { + if c.DstPort != 1 { t.Fail() } }) @@ -75,17 +75,17 @@ func TestConnGroupSort(t *testing.T) { cg := &graph.ConnGroup{} - cg.AddListener(&graph.Connection{Proto: graph.TCP, LocalPort: 1}) - cg.AddListener(&graph.Connection{Proto: graph.UDP, LocalPort: 1}) - cg.AddListener(&graph.Connection{Proto: graph.TCP, LocalPort: 2}) - cg.AddListener(&graph.Connection{Proto: graph.UDP, LocalPort: 2}) - cg.AddListener(&graph.Connection{Proto: graph.TCP, LocalPort: 3}) - cg.AddListener(&graph.Connection{Proto: graph.UDP, LocalPort: 3}) - - cg.AddOutbound(&graph.Connection{Proto: graph.TCP, LocalPort: 2, RemotePort: 1}) - cg.AddOutbound(&graph.Connection{Proto: graph.UDP, LocalPort: 3, RemotePort: 1}) - cg.AddOutbound(&graph.Connection{Proto: graph.TCP, LocalPort: 4, RemotePort: 2}) - cg.AddOutbound(&graph.Connection{Proto: graph.UDP, LocalPort: 5, RemotePort: 2}) + cg.AddListener(&graph.Connection{Proto: graph.TCP, SrcPort: 1}) + cg.AddListener(&graph.Connection{Proto: graph.UDP, SrcPort: 1}) + cg.AddListener(&graph.Connection{Proto: graph.TCP, SrcPort: 2}) + cg.AddListener(&graph.Connection{Proto: graph.UDP, SrcPort: 2}) + cg.AddListener(&graph.Connection{Proto: graph.TCP, SrcPort: 3}) + cg.AddListener(&graph.Connection{Proto: graph.UDP, SrcPort: 3}) + + cg.AddOutbound(&graph.Connection{Proto: graph.TCP, SrcPort: 2, DstPort: 1}) + cg.AddOutbound(&graph.Connection{Proto: graph.UDP, SrcPort: 3, DstPort: 1}) + cg.AddOutbound(&graph.Connection{Proto: graph.TCP, SrcPort: 4, DstPort: 2}) + cg.AddOutbound(&graph.Connection{Proto: graph.UDP, SrcPort: 5, DstPort: 2}) cg.Sort() diff --git a/internal/graph/container.go b/internal/graph/container.go index 216b6a3..826ad93 100644 --- a/internal/graph/container.go +++ b/internal/graph/container.go @@ -2,6 +2,7 @@ package graph import ( "slices" + "strconv" "github.com/s0rg/decompose/internal/node" ) @@ -98,7 +99,7 @@ func (c *Container) ToNode() (rv *node.Node) { Name: c.Name, Image: c.Image, Ports: &node.Ports{}, - Volumes: []*node.Volume{}, + Volumes: make([]*node.Volume, len(c.Volumes)), Networks: make([]string, 0, len(c.Endpoints)), } @@ -107,22 +108,24 @@ func (c *Container) ToNode() (rv *node.Node) { } c.IterListeners(func(conn *Connection) { + value := conn.Path + + if conn.Proto != UNIX { + value = strconv.Itoa(conn.SrcPort) + } + rv.Ports.Add(conn.Process, &node.Port{ Local: conn.IsLocal(), Kind: conn.Proto.String(), - Value: int(conn.LocalPort), + Value: value, }) }) - if len(c.Volumes) > 0 { - rv.Volumes = make([]*node.Volume, len(c.Volumes)) - - for idx, v := range c.Volumes { - rv.Volumes[idx] = &node.Volume{ - Type: v.Type, - Src: v.Src, - Dst: v.Dst, - } + for idx, v := range c.Volumes { + rv.Volumes[idx] = &node.Volume{ + Type: v.Type, + Src: v.Src, + Dst: v.Dst, } } diff --git a/internal/graph/container_test.go b/internal/graph/container_test.go index fe105b5..b0aa62a 100644 --- a/internal/graph/container_test.go +++ b/internal/graph/container_test.go @@ -14,9 +14,9 @@ var testCases = []struct { }{ { Conns: []*graph.Connection{ - {LocalPort: 1, RemotePort: 2}, // inbound - {LocalPort: 1, RemotePort: 0}, // listener - {LocalPort: 2, RemotePort: 1}, // outbound + {SrcPort: 1, DstPort: 2}, // inbound + {SrcPort: 1, DstPort: 0}, // listener + {SrcPort: 2, DstPort: 1}, // outbound }, Listeners: 1, Outbounds: 1, @@ -24,8 +24,8 @@ var testCases = []struct { }, { Conns: []*graph.Connection{ - {LocalPort: 1, RemotePort: 2}, // inbound - {LocalPort: 2, RemotePort: 1}, // outbound + {SrcPort: 1, DstPort: 2}, // inbound + {SrcPort: 2, DstPort: 1}, // outbound }, Listeners: 0, Outbounds: 1, @@ -33,8 +33,8 @@ var testCases = []struct { }, { Conns: []*graph.Connection{ - {LocalPort: 1, RemotePort: 2}, // inbound - {LocalPort: 1, RemotePort: 0}, // listener + {SrcPort: 1, DstPort: 2}, // inbound + {SrcPort: 1, DstPort: 0}, // listener }, Listeners: 1, Outbounds: 0, @@ -42,7 +42,7 @@ var testCases = []struct { }, { Conns: []*graph.Connection{ - {LocalPort: 1, RemotePort: 2}, // inbound + {SrcPort: 1, DstPort: 2}, // inbound }, Listeners: 0, Outbounds: 0, diff --git a/internal/graph/load.go b/internal/graph/load.go index e613faa..97ac3bb 100644 --- a/internal/graph/load.go +++ b/internal/graph/load.go @@ -85,10 +85,6 @@ func (l *Loader) createNode(id string, n *node.JSON) (rv *node.Node) { rv.Networks = n.Networks } - if !l.cfg.FullInfo { - return rv - } - if len(n.Volumes) > 0 { rv.Volumes = n.Volumes } diff --git a/internal/graph/load_test.go b/internal/graph/load_test.go index 1169fda..20af584 100644 --- a/internal/graph/load_test.go +++ b/internal/graph/load_test.go @@ -503,10 +503,9 @@ func TestLoaderFull(t *testing.T) { ext := &testEnricher{} cfg := &graph.Config{ - Builder: bldr, - Meta: ext, - FullInfo: true, - Proto: graph.ALL, + Builder: bldr, + Meta: ext, + Proto: graph.ALL, } ldr := graph.NewLoader(cfg) diff --git a/internal/graph/netproto.go b/internal/graph/netproto.go index c674fe1..050ca1d 100644 --- a/internal/graph/netproto.go +++ b/internal/graph/netproto.go @@ -3,22 +3,25 @@ package graph type NetProto byte const ( - ALL NetProto = 0 - TCP NetProto = 1 - UDP NetProto = 2 + ALL NetProto = 0 + TCP NetProto = 1 + UDP NetProto = 2 + UNIX NetProto = 3 ) var ( netKindNames = []string{ - ALL: "tcp+udp", - TCP: "tcp", - UDP: "udp", + ALL: "tcp+udp", + TCP: "tcp", + UDP: "udp", + UNIX: "unix", } netKindFlags = []string{ - ALL: "tu", - TCP: "t", - UDP: "u", + ALL: "tux", + TCP: "t", + UDP: "u", + UNIX: "x", } ) @@ -38,6 +41,8 @@ func ParseNetProto(val string) (p NetProto, ok bool) { return TCP, true case "udp": return UDP, true + case "unix": + return UNIX, true } return diff --git a/internal/graph/netproto_test.go b/internal/graph/netproto_test.go index d19a8a9..6c7fdb7 100644 --- a/internal/graph/netproto_test.go +++ b/internal/graph/netproto_test.go @@ -17,6 +17,7 @@ func TestParseNetproto(t *testing.T) { {Val: "tcp", Valid: true, Want: graph.TCP}, {Val: "udp", Valid: true, Want: graph.UDP}, {Val: "all", Valid: true, Want: graph.ALL}, + {Val: "unix", Valid: true, Want: graph.UNIX}, {Val: "bad", Valid: false}, } @@ -46,7 +47,8 @@ func TestNetprotoString(t *testing.T) { }{ {Val: "tcp", Want: "t"}, {Val: "udp", Want: "u"}, - {Val: "all", Want: "tu"}, + {Val: "unix", Want: "x"}, + {Val: "all", Want: "tux"}, } for i, tc := range testCases { diff --git a/internal/graph/netstat.go b/internal/graph/netstat.go index 199afbe..73bd797 100644 --- a/internal/graph/netstat.go +++ b/internal/graph/netstat.go @@ -12,8 +12,17 @@ import ( const ( stateListen = "LISTEN" stateEstablished = "ESTABLISHED" + netstatCmd = "netstat" + netstatArg = "-apn" ) +func NetstatCMD(p NetProto) []string { + return []string{ + netstatCmd, + netstatArg + p.Flag(), + } +} + func ParseNetstat(r io.Reader, cb func(*Connection)) (err error) { s := bufio.NewScanner(r) s.Split(bufio.ScanLines) @@ -52,17 +61,20 @@ func parseConnection(s string) (conn *Connection, ok bool) { return nil, false } - conn = &Connection{} - - if conn.Proto, ok = parseKind(parts[0], len(parts)); !ok { + proto, ok := parseKind(parts[0], len(parts)) + if !ok { return nil, false } - if conn.LocalIP, conn.LocalPort, ok = splitIP(parts[3]); !ok { + conn = &Connection{ + Proto: proto, + } + + if conn.SrcIP, conn.SrcPort, ok = splitIP(parts[3]); !ok { return nil, false } - if conn.RemoteIP, conn.RemotePort, ok = splitIP(parts[4]); !ok { + if conn.DstIP, conn.DstPort, ok = splitIP(parts[4]); !ok { return nil, false } @@ -102,7 +114,7 @@ func parseKind(kind string, fieldsNum int) (k NetProto, ok bool) { return } -func splitIP(v string) (ip net.IP, port uint16, ok bool) { +func splitIP(v string) (ip net.IP, port int, ok bool) { idx := strings.LastIndexByte(v, ':') if idx < 0 { return @@ -120,7 +132,7 @@ func splitIP(v string) (ip net.IP, port uint16, ok bool) { return } - port = uint16(uval) + port = int(uval) } return ip, port, true @@ -139,7 +151,9 @@ func splitName(v string) (name string, ok bool) { return } - name = fields[0] + if name = fields[0]; name == "" { + return + } - return name, name != "" + return name, true } diff --git a/internal/graph/netstat_test.go b/internal/graph/netstat_test.go index 805af7b..395c3de 100644 --- a/internal/graph/netstat_test.go +++ b/internal/graph/netstat_test.go @@ -38,6 +38,13 @@ tcp 0 0 invalid 172.20.4.198:3306 ESTABLISHED tcp 0 0 172.20.4.198:3306 invalid ESTABLISHED - tcp 0 0 172.20.4.198:bad 172.20.4.198:3306 ESTABLISHED - tcp 0 0 invalid-ip:123 172.20.4.198:3306 ESTABLISHED - +Active UNIX domain sockets (servers and established) +Proto RefCnt Flags Type State I-Node PID/Program name Path +unix 3 [ ] STREAM CONNECTED 38047 1/init /run/systemd/journal/stdout +unix 3 [ ] STREAM CONNECTED 27351 4452/wireplumber +unix 2 [ ACC ] STREAM LISTENING 23216 2645/Xorg @/tmp/.X11-unix/X0 +unix 2 [ ] DGRAM 39148 4797/xdg-desktop-po + some garbage `) diff --git a/internal/node/node_test.go b/internal/node/node_test.go index 9ba3df2..4fc2af1 100644 --- a/internal/node/node_test.go +++ b/internal/node/node_test.go @@ -44,7 +44,7 @@ func TestNodeToJSON(t *testing.T) { t.Parallel() nodeMeta := makeTestNode("test-id", "test-name", "test-image", []*node.Port{ - {Kind: "udp", Value: 53}, + {Kind: "udp", Value: "53"}, }) nodeMeta.Meta = &node.Meta{ @@ -53,7 +53,7 @@ func TestNodeToJSON(t *testing.T) { } nodeContainer := makeTestNode("test-id2", "test-name", "test-image", []*node.Port{ - {Kind: "udp", Value: 53}, + {Kind: "udp", Value: "53"}, }) nodeContainer.Container.Cmd = []string{"foo"} @@ -70,21 +70,21 @@ func TestNodeToJSON(t *testing.T) { }{ { Node: makeTestNode("test-id", "test-name1", "", []*node.Port{ - {Kind: "tcp", Value: 80}, - {Kind: "udp", Value: 53}, + {Kind: "tcp", Value: "80"}, + {Kind: "udp", Value: "53"}, }), Name: "test-name1", }, { Node: makeTestNode("test-id", "test-id", "", []*node.Port{ - {Kind: "tcp", Value: 80}, + {Kind: "tcp", Value: "80"}, }), Name: "test-id", External: true, }, { Node: makeTestNode("test-id3", "test-name", "test-image", []*node.Port{ - {Kind: "udp", Value: 53}, + {Kind: "udp", Value: "53"}, }), Name: "test-name", Image: "test-image", @@ -159,7 +159,7 @@ func TestNodeToView(t *testing.T) { t.Parallel() nodeContainer := makeTestNode("test-id", "test-name", "test-image", []*node.Port{ - {Kind: "udp", Value: 53}, + {Kind: "udp", Value: "53"}, }) nodeContainer.Container.Cmd = []string{"foo", "-arg"} diff --git a/internal/node/port.go b/internal/node/port.go index c03e8cb..6a52e95 100644 --- a/internal/node/port.go +++ b/internal/node/port.go @@ -1,17 +1,13 @@ package node -import ( - "strconv" -) - type Port struct { Kind string `json:"kind"` - Value int `json:"value"` + Value string `json:"value"` Local bool `json:"local"` } func (p *Port) Label() string { - return strconv.Itoa(p.Value) + "/" + p.Kind + return p.Kind + ":" + p.Value } func (p *Port) Equal(v *Port) (yes bool) { diff --git a/internal/node/port_test.go b/internal/node/port_test.go index d498fa6..fea0d8c 100644 --- a/internal/node/port_test.go +++ b/internal/node/port_test.go @@ -22,7 +22,7 @@ func TestPortLabel(t *testing.T) { const want = "100" - p := node.Port{Value: 100, Kind: "tcp"} + p := node.Port{Value: "100", Kind: "tcp"} l := p.Label() if !strings.HasPrefix(l, want) { @@ -55,7 +55,7 @@ func TestPortsHas(t *testing.T) { { Ports: makeTestPorts(&node.Port{ Kind: "tcp", - Value: 80, + Value: "80", }), Labels: []string{"80/tcp"}, Want: true, @@ -63,7 +63,7 @@ func TestPortsHas(t *testing.T) { { Ports: makeTestPorts(&node.Port{ Kind: "tcp", - Value: 81, + Value: "81", }), Labels: []string{"80/tcp"}, Want: false, @@ -71,7 +71,7 @@ func TestPortsHas(t *testing.T) { { Ports: makeTestPorts(&node.Port{ Kind: "tcp", - Value: 80, + Value: "80", }), Labels: []string{"80/tcp", "443/tcp"}, Want: false, @@ -106,7 +106,7 @@ func TestPortsHasAny(t *testing.T) { { Ports: makeTestPorts(&node.Port{ Kind: "tcp", - Value: 80, + Value: "80", }), Labels: []string{"80/tcp"}, Want: true, @@ -114,7 +114,7 @@ func TestPortsHasAny(t *testing.T) { { Ports: makeTestPorts(&node.Port{ Kind: "tcp", - Value: 81, + Value: "81", }), Labels: []string{"80/tcp"}, Want: false, @@ -122,7 +122,7 @@ func TestPortsHasAny(t *testing.T) { { Ports: makeTestPorts(&node.Port{ Kind: "tcp", - Value: 80, + Value: "80", }), Labels: []string{"80/tcp", "443/tcp"}, Want: true, diff --git a/internal/node/ports_test.go b/internal/node/ports_test.go index 25ad438..b5f6727 100644 --- a/internal/node/ports_test.go +++ b/internal/node/ports_test.go @@ -15,7 +15,7 @@ func TestPortsGet(t *testing.T) { t.Fail() } - port := &node.Port{Kind: "tcp", Value: 1} + port := &node.Port{Kind: "tcp", Value: "1"} ps.Add("foo", port) diff --git a/internal/structurizr/utils.go b/internal/structurizr/utils.go index 2588c43..54e30c1 100644 --- a/internal/structurizr/utils.go +++ b/internal/structurizr/utils.go @@ -109,7 +109,7 @@ func SafeID(v string) (id string) { func compactTags(tags []string) (rv []string, ok bool) { if len(tags) == 0 { - return nil, false + return } rv = slices.DeleteFunc(tags, func(v string) bool { @@ -121,11 +121,9 @@ func compactTags(tags []string) (rv []string, ok bool) { return nil, false case 1: return rv, true - } + default: + slices.Sort(rv) - slices.Sort(rv) - - rv = slices.Clip(slices.Compact(rv)) - - return rv, true + return slices.Clip(slices.Compact(rv)), true + } } From fee43168be2c61feef387fa29d37ce2451eb5064 Mon Sep 17 00:00:00 2001 From: Shevchenko Alexey Date: Sun, 16 Jun 2024 16:16:26 +0300 Subject: [PATCH 02/16] linters and tests are red, still WIP --- README.md | 6 +-- cmd/decompose/main.go | 28 +++++++---- internal/client/defaults.go | 6 +-- internal/client/docker.go | 46 ++++++++++++------ internal/client/docker_test.go | 40 ++++++++-------- internal/client/options.go | 13 +++-- internal/graph/build.go | 3 +- internal/graph/build_test.go | 2 +- internal/graph/config.go | 1 - internal/graph/netproto.go | 84 +++++++++++++++++++++------------ internal/graph/netproto_test.go | 22 --------- internal/graph/netstat.go | 22 ++++++++- 12 files changed, 161 insertions(+), 112 deletions(-) diff --git a/README.md b/README.md index 774ac98..6fcbea9 100644 --- a/README.md +++ b/README.md @@ -104,7 +104,7 @@ decompose [flags] -out string output: filename or "-" for stdout (default "-") -proto string - protocol to scan: tcp, udp or all (default "all") + protocol to scan: tcp,udp,unix or all (default "all") -silent suppress progress messages in stderr -skip-env string @@ -274,10 +274,10 @@ Get `dot` file: decompose -format dot > connections.dot ``` -Get only tcp connections as `dot`: +Get tcp and udp connections as `dot`: ```shell -decompose -proto tcp -format dot > tcp.dot +decompose -proto tcp,udp -format dot > tcp.dot ``` Merge graphs from json streams, filter by protocol, skip remote hosts and save as `dot`: diff --git a/cmd/decompose/main.go b/cmd/decompose/main.go index 0704e3b..023ab5c 100644 --- a/cmd/decompose/main.go +++ b/cmd/decompose/main.go @@ -45,7 +45,7 @@ var ( var ( fSilent, fVersion bool fHelp, fLocal bool - fUnix, fNoLoops bool + fNoLoops bool fDeep, fCompress bool fProto, fFormat string fOut, fFollow string @@ -85,14 +85,13 @@ func setupFlags() { flag.BoolVar(&fVersion, "version", false, "show version") flag.BoolVar(&fHelp, "help", false, "show this help") flag.BoolVar(&fLocal, "local", false, "skip external hosts") - flag.BoolVar(&fUnix, "unix", false, "extract unix-sockets connection (requires linux/root mode)") flag.BoolVar(&fNoLoops, "no-loops", false, "remove connection loops (node to itself) from output") flag.BoolVar(&fDeep, "deep", false, "process-based introspection") flag.BoolVar(&fCompress, "compress", false, "compress graph") flag.StringVar(&fOut, "out", defaultOutput, "output: filename or \"-\" for stdout") flag.StringVar(&fMeta, "meta", "", "json file with metadata for enrichment") - flag.StringVar(&fProto, "proto", defaultProto, "protocol to scan: tcp, udp, unix or all") + flag.StringVar(&fProto, "proto", defaultProto, "protocol to scan: tcp,udp,unix or all") flag.StringVar(&fFollow, "follow", "", "follow only this container by name(s), comma-separated or from @file") flag.StringVar( &fCluster, @@ -173,7 +172,7 @@ func makeClusterizer( f, v string, ) (rv graph.NamedBuilderWriter, err error) { if !builder.SupportCluster(f) { - log.Println(b.Name(), "cannot handle graph clusters - ignoring") + log.Println("[-]", b.Name(), "cannot handle graph clusters - ignoring") return b, nil } @@ -311,7 +310,6 @@ func prepareConfig() ( OnlyLocal: fLocal, Deep: fDeep, NoLoops: fNoLoops, - Unix: fUnix, SkipEnv: skipKeys, } @@ -378,12 +376,15 @@ func doBuild( mode := client.InContainer if runtime.GOOS == linuxOS && os.Geteuid() == 0 { - opts = append(opts, client.WithNsEnter(client.Nsenter)) mode = client.LinuxNsenter - } else if cfg.Unix { - log.Println("unix-connections requested in non-root mode, ignoring") + opts = append(opts, + client.WithNsenterFn(client.Nsenter), + client.WithInodesFn(client.Inodes), + ) + } else if cfg.Proto.Has(graph.UNIX) { + log.Println("[-] Unix-connections requested in non-root mode, ignoring") - cfg.Unix = false + cfg.Proto ^= graph.UNIX } cli, err := client.NewDocker(append(opts, client.WithMode(mode))...) @@ -393,7 +394,14 @@ func doBuild( defer cli.Close() - log.Println("Starting with method:", cli.Mode()) + method := cli.Mode() + + if cfg.Deep { + method += " / deep" + } + + log.Println("Starting with method:", method) + log.Println("Scanning for:", cfg.Proto.String()) if err = graph.Build(cfg, cli); err != nil { return fmt.Errorf("graph: %w", err) diff --git a/internal/client/defaults.go b/internal/client/defaults.go index 2796c37..986084e 100644 --- a/internal/client/defaults.go +++ b/internal/client/defaults.go @@ -263,19 +263,19 @@ func Nsenter( return fmt.Errorf("procfs/net: %w", err) } - if proto == graph.ALL || proto == graph.TCP { + if proto.Has(graph.TCP) { if err = scanTCP(fs, name, connWithPid); err != nil { return fmt.Errorf("scan: %w", err) } } - if proto == graph.ALL || proto == graph.UDP { + if proto.Has(graph.UDP) { if err = scanUDP(fs, name, connWithPid); err != nil { return fmt.Errorf("scan: %w", err) } } - if proto == graph.ALL || proto == graph.UNIX { + if proto.Has(graph.UNIX) { if err = scanUNIX(fs, name, connWithPid); err != nil { return fmt.Errorf("scan: %w", err) } diff --git a/internal/client/docker.go b/internal/client/docker.go index 2b0dd0b..c03d7e8 100644 --- a/internal/client/docker.go +++ b/internal/client/docker.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "io" + "log" "maps" "slices" "strconv" @@ -21,13 +22,15 @@ import ( const ( stateRunning = "running" + runcLstatErr = "no such file or directory" ) var ErrModeNone = errors.New("mode not set") type ( createClient func() (DockerClient, error) - nsEnter func(int, graph.NetProto, func(int, *graph.Connection)) error + nsenter func(int, graph.NetProto, func(int, *graph.Connection)) error + inodes func(int, func(uint64)) error ) type DockerClient interface { @@ -71,7 +74,7 @@ func (d *Docker) Mode() string { func (d *Docker) Containers( ctx context.Context, proto graph.NetProto, - unix, deep bool, + deep bool, skipkeys []string, progress func(int, int), ) (rv []*graph.Container, err error) { @@ -90,8 +93,19 @@ func (d *Docker) Containers( var inodes *InodesMap - if unix { - if inodes, err = d.collectInodes(ctx, containers); err != nil { + if proto.Has(graph.UNIX) { + if inodes, err = d.collectInodes(ctx, containers, func(idx int, err error) (stop bool) { + if strings.Contains(err.Error(), runcLstatErr) { + c := &containers[idx] + c.State = "" + + log.Printf("container: %s/%s in fault state", c.Names[0], c.ID) + + return false + } + + return true + }); err != nil { return nil, fmt.Errorf("inodes: %w", err) } } @@ -105,7 +119,7 @@ func (d *Docker) Containers( continue } - con, cerr := d.extractInfo(ctx, c, proto, unix, deep, skeys, inodes) + con, cerr := d.extractInfo(ctx, c, proto, deep, skeys, inodes) if cerr != nil { return nil, fmt.Errorf("container %s: %w", c.ID, cerr) } @@ -117,7 +131,7 @@ func (d *Docker) Containers( progress(i, len(containers)) } - if unix { + if proto.Has(graph.UNIX) { inodes.ResolveUnknown(func(srcCID, dstCID, srcName, _, path string) { dst, ok := cmap[dstCID] if !ok { @@ -141,6 +155,7 @@ func (d *Docker) Containers( func (d *Docker) collectInodes( ctx context.Context, containers []types.Container, + errcb func(int, error) bool, ) ( inodes *InodesMap, err error, @@ -157,12 +172,17 @@ func (d *Docker) collectInodes( err = d.processesContainer(ctx, c.ID, func(pid int, name string) (err error) { inodes.AddProcess(c.ID, pid, name) - return Inodes(pid, func(inode uint64) { + ierr := d.opt.Inodes(pid, func(inode uint64) { inodes.AddInode(c.ID, pid, inode) }) + if ierr != nil { + log.Printf("inodes fail for: %s error: %v", c.Names[0], ierr) + } + + return nil }) - if err != nil { - return nil, fmt.Errorf("inodes %s: %w", c.ID, err) + if err != nil && errcb(i, err) { + return nil, fmt.Errorf("inodes %s: %w", c.Names[0], err) } } @@ -173,7 +193,7 @@ func (d *Docker) extractInfo( ctx context.Context, c *types.Container, proto graph.NetProto, - unix, deep bool, + deep bool, skeys set.Unordered[string], inodes *InodesMap, ) (rv *graph.Container, err error) { @@ -198,10 +218,6 @@ func (d *Docker) extractInfo( return } - if !unix && conn.Proto == graph.UNIX { - return - } - if conn.Proto == graph.UNIX { if !inodes.Has(c.ID, pid, conn.Inode) { inodes.MarkUnknown(c.ID, pid, conn.Inode) @@ -324,7 +340,7 @@ func (d *Docker) processesContainer( cmd := strings.Fields(p[1]) if err = fun(pid, cmd[0]); err != nil { - return fmt.Errorf("[pid: %d] %w", pid, err) + return fmt.Errorf("pid: %d: %w", pid, err) } } diff --git a/internal/client/docker_test.go b/internal/client/docker_test.go index a2cd4c7..abef13a 100644 --- a/internal/client/docker_test.go +++ b/internal/client/docker_test.go @@ -61,7 +61,7 @@ func TestDockerClientContainersError(t *testing.T) { _, err = cli.Containers( context.Background(), graph.ALL, - false, false, + false, nil, voidProgress, ) @@ -92,7 +92,7 @@ func TestDockerClientContainersEmpty(t *testing.T) { rv, err := cli.Containers( context.Background(), graph.ALL, - false, false, + false, nil, voidProgress, ) @@ -131,7 +131,7 @@ func TestDockerClientContainersSingleExited(t *testing.T) { rv, err := cli.Containers( context.Background(), graph.ALL, - false, false, + false, nil, voidProgress, ) @@ -188,7 +188,7 @@ func TestDockerClientContainersExecCreateError(t *testing.T) { _, err = cli.Containers( context.Background(), graph.ALL, - false, false, + false, nil, voidProgress, ) @@ -241,7 +241,7 @@ func TestDockerClientContainersInspectError(t *testing.T) { _, err = cli.Containers( context.Background(), graph.ALL, - false, false, + false, nil, voidProgress, ) @@ -298,7 +298,7 @@ func TestDockerClientContainersExecAttachError(t *testing.T) { _, err = cli.Containers( context.Background(), graph.ALL, - false, false, + false, nil, voidProgress, ) @@ -360,7 +360,7 @@ func TestDockerClientContainersParseError(t *testing.T) { _, err = cli.Containers( context.Background(), graph.ALL, - false, false, + false, nil, voidProgress, ) @@ -440,7 +440,7 @@ func TestDockerClientContainersSingle(t *testing.T) { rv, err := cli.Containers( context.Background(), graph.ALL, - false, false, + false, nil, voidProgress, ) @@ -539,7 +539,7 @@ func TestDockerClientContainersSingleFull(t *testing.T) { rv, err := cli.Containers( context.Background(), graph.ALL, - false, false, + false, nil, voidProgress, ) @@ -620,7 +620,7 @@ func TestDockerClientContainersSingleFullSkipEnv(t *testing.T) { rv, err := cli.Containers( context.Background(), graph.ALL, - false, false, + false, []string{"BAZ"}, voidProgress, ) @@ -728,7 +728,7 @@ func TestDockerClientNsEnterInspectError(t *testing.T) { _, err = cli.Containers( context.Background(), graph.ALL, - false, false, + false, nil, voidProgress, ) @@ -787,7 +787,7 @@ func TestDockerClientNsEnterConnectionsError(t *testing.T) { return cm, nil }), client.WithMode(client.LinuxNsenter), - client.WithNsEnter(failEnter), + client.WithNsenterFn(failEnter), ) if err != nil { t.Fatal("client:", err) @@ -796,7 +796,7 @@ func TestDockerClientNsEnterConnectionsError(t *testing.T) { _, err = cli.Containers( context.Background(), graph.ALL, - false, false, + false, nil, voidProgress, ) @@ -859,7 +859,7 @@ func TestDockerClientNsEnterContainerTopVariants(t *testing.T) { return cm, nil }), client.WithMode(client.LinuxNsenter), - client.WithNsEnter(enter), + client.WithNsenterFn(enter), ) if err != nil { t.Fatal("client:", err) @@ -868,7 +868,7 @@ func TestDockerClientNsEnterContainerTopVariants(t *testing.T) { if _, err = cli.Containers( context.Background(), graph.ALL, - false, false, + false, nil, voidProgress, ); err != nil { @@ -929,7 +929,7 @@ func TestDockerClientNsEnterOk(t *testing.T) { return cm, nil }), client.WithMode(client.LinuxNsenter), - client.WithNsEnter(testEnter), + client.WithNsenterFn(testEnter), ) if err != nil { t.Fatal("client:", err) @@ -938,7 +938,7 @@ func TestDockerClientNsEnterOk(t *testing.T) { _, err = cli.Containers( context.Background(), graph.ALL, - false, false, + false, nil, voidProgress, ) @@ -997,7 +997,7 @@ func TestDockerClientNsEnterLocal(t *testing.T) { return cm, nil }), client.WithMode(client.LinuxNsenter), - client.WithNsEnter(testEnter), + client.WithNsenterFn(testEnter), ) if err != nil { t.Fatal("client:", err) @@ -1006,7 +1006,7 @@ func TestDockerClientNsEnterLocal(t *testing.T) { rv, err := cli.Containers( context.Background(), graph.ALL, - false, false, + false, nil, voidProgress, ) @@ -1025,7 +1025,7 @@ func TestDockerClientNsEnterLocal(t *testing.T) { rv, err = cli.Containers( context.Background(), graph.ALL, - false, true, + true, nil, voidProgress, ) diff --git a/internal/client/options.go b/internal/client/options.go index aa47ba2..cc4aa39 100644 --- a/internal/client/options.go +++ b/internal/client/options.go @@ -4,7 +4,8 @@ type Option func(*options) type options struct { Create createClient - Nsenter nsEnter + Nsenter nsenter + Inodes inodes Mode mode } @@ -20,8 +21,14 @@ func WithClientCreator(c createClient) Option { } } -func WithNsEnter(e nsEnter) Option { +func WithNsenterFn(f nsenter) Option { return func(o *options) { - o.Nsenter = e + o.Nsenter = f + } +} + +func WithInodesFn(f inodes) Option { + return func(o *options) { + o.Inodes = f } } diff --git a/internal/graph/build.go b/internal/graph/build.go index c03b174..4be231f 100644 --- a/internal/graph/build.go +++ b/internal/graph/build.go @@ -22,7 +22,7 @@ const ( var ErrNotEnough = errors.New("not enough items") type ContainerClient interface { - Containers(context.Context, NetProto, bool, bool, []string, func(int, int)) ([]*Container, error) + Containers(context.Context, NetProto, bool, []string, func(int, int)) ([]*Container, error) } type Builder interface { @@ -53,7 +53,6 @@ func Build( containers, err := cli.Containers( context.Background(), cfg.Proto, - cfg.Unix, cfg.Deep, cfg.SkipEnv, func(cur, total int) { diff --git a/internal/graph/build_test.go b/internal/graph/build_test.go index 3e8c12d..8474d0c 100644 --- a/internal/graph/build_test.go +++ b/internal/graph/build_test.go @@ -20,7 +20,7 @@ type testClient struct { func (tc *testClient) Containers( _ context.Context, _ graph.NetProto, - _, _ bool, + _ bool, _ []string, fn func(int, int), ) ([]*graph.Container, error) { diff --git a/internal/graph/config.go b/internal/graph/config.go index b87faa0..9bba578 100644 --- a/internal/graph/config.go +++ b/internal/graph/config.go @@ -10,7 +10,6 @@ type Config struct { Proto NetProto OnlyLocal bool NoLoops bool - Unix bool Deep bool } diff --git a/internal/graph/netproto.go b/internal/graph/netproto.go index 050ca1d..ea615b9 100644 --- a/internal/graph/netproto.go +++ b/internal/graph/netproto.go @@ -1,49 +1,71 @@ package graph -type NetProto byte +import "strings" + +type NetProto int16 const ( - ALL NetProto = 0 - TCP NetProto = 1 - UDP NetProto = 2 - UNIX NetProto = 3 + TCP NetProto = 1 << iota + UDP NetProto = 1 << iota + UNIX NetProto = 1 << iota + NONE NetProto = 0 + ALL NetProto = TCP | UDP | UNIX + pMAX = 3 + + sNONE = "none" + sTCP = "tcp" + sUDP = "udp" + sUNIX = "unix" + sALL = "all" ) -var ( - netKindNames = []string{ - ALL: "tcp+udp", - TCP: "tcp", - UDP: "udp", - UNIX: "unix", +func (p NetProto) String() string { + buf := make([]string, 0, pMAX) + + if p.Has(TCP) { + buf = append(buf, sTCP) } - netKindFlags = []string{ - ALL: "tux", - TCP: "t", - UDP: "u", - UNIX: "x", + if p.Has(UDP) { + buf = append(buf, sUDP) } -) -func (p NetProto) String() string { - return netKindNames[p] + if p.Has(UNIX) { + buf = append(buf, sUNIX) + } + + if len(buf) == 0 { + buf = append(buf, sNONE) + } + + return strings.Join(buf, ",") } -func (p NetProto) Flag() string { - return netKindFlags[p] +func (p *NetProto) Set(mask NetProto) { + *p |= mask +} + +func (p NetProto) Has(mask NetProto) bool { + return (p & mask) == mask } func ParseNetProto(val string) (p NetProto, ok bool) { - switch val { - case "all": - return ALL, true - case "tcp": - return TCP, true - case "udp": - return UDP, true - case "unix": - return UNIX, true + items := strings.Split(val, ",") + + for _, v := range items { + switch v { + case sALL: + p.Set(ALL) + case sTCP: + p.Set(TCP) + case sUDP: + p.Set(UDP) + case sUNIX: + p.Set(UNIX) + default: + return + } } - return + return p, true } diff --git a/internal/graph/netproto_test.go b/internal/graph/netproto_test.go index 6c7fdb7..c7935a8 100644 --- a/internal/graph/netproto_test.go +++ b/internal/graph/netproto_test.go @@ -37,25 +37,3 @@ func TestParseNetproto(t *testing.T) { } } } - -func TestNetprotoString(t *testing.T) { - t.Parallel() - - testCases := []struct { - Val string - Want string - }{ - {Val: "tcp", Want: "t"}, - {Val: "udp", Want: "u"}, - {Val: "unix", Want: "x"}, - {Val: "all", Want: "tux"}, - } - - for i, tc := range testCases { - p, _ := graph.ParseNetProto(tc.Val) - - if p.Flag() != tc.Want { - t.Fatalf("case[%d] failed for '%s' want: %s got: %s", i, tc.Val, tc.Want, p.Flag()) - } - } -} diff --git a/internal/graph/netstat.go b/internal/graph/netstat.go index 73bd797..ebac2a7 100644 --- a/internal/graph/netstat.go +++ b/internal/graph/netstat.go @@ -16,10 +16,30 @@ const ( netstatArg = "-apn" ) +func netstatArgFor(p NetProto) (rv string) { + if p.Has(TCP) { + rv += "t" + } + + if p.Has(UDP) { + rv += "u" + } + + /* + no unix sockets from netstat - it gives only internal pids + + if p.Has(UNIX) { + rv += "x" + } + */ + + return rv +} + func NetstatCMD(p NetProto) []string { return []string{ netstatCmd, - netstatArg + p.Flag(), + netstatArg + netstatArgFor(p), } } From 3acfd0362018e5b6ba5364ccbb62e7990eb89fb8 Mon Sep 17 00:00:00 2001 From: Shevchenko Alexey Date: Sun, 16 Jun 2024 16:20:48 +0300 Subject: [PATCH 03/16] README grouming --- README.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/README.md b/README.md index 6fcbea9..8992e06 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,6 @@ Closest analogs, i can find, that not suit my needs very well: - unix-sockets connections - 100% test-coverage - ## known limitations - only established and listen connections are listed (but script like [snapshots.sh](examples/snapshots.sh) can beat this) @@ -89,8 +88,6 @@ decompose [flags] follow only this container by name(s), comma-separated or from @file -format string output format: csv, dot, json, puml, sdsl, stat, tree, yaml (default "json") --full - extract full process info: (cmd, args, env) and volumes info -help show this help -load value @@ -265,7 +262,7 @@ a float in `(0.0, 1.0]` range, representing how much similar ports nodes must ha Save full json stream: ```shell -decompose -full > nodes-1.json +sudo decompose > nodes-1.json ``` Get `dot` file: From 33c63c3a312d0418aa3e02d2bf800da139bdd597 Mon Sep 17 00:00:00 2001 From: Shevchenko Alexey Date: Sun, 16 Jun 2024 16:21:54 +0300 Subject: [PATCH 04/16] docker v26.1.4 => v27.0.0 --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 8795c9f..a04afdb 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/s0rg/decompose go 1.22 require ( - github.com/docker/docker v26.1.4+incompatible + github.com/docker/docker v27.0.0+incompatible github.com/emicklei/dot v1.6.2 github.com/expr-lang/expr v1.16.9 github.com/prometheus/procfs v0.15.1 diff --git a/go.sum b/go.sum index e844188..a732f10 100644 --- a/go.sum +++ b/go.sum @@ -11,8 +11,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v26.1.4+incompatible h1:vuTpXDuoga+Z38m1OZHzl7NKisKWaWlhjQk7IDPSLsU= -github.com/docker/docker v26.1.4+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v27.0.0+incompatible h1:JRugTYuelmWlW0M3jakcIadDx2HUoUO6+Tf2C5jVfwA= +github.com/docker/docker v27.0.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= From cbbaedfacb56fc6330d1f3241eba4b5a31f2ee52 Mon Sep 17 00:00:00 2001 From: Shevchenko Alexey Date: Mon, 17 Jun 2024 00:29:44 +0300 Subject: [PATCH 05/16] some tests fixed --- internal/client/docker.go | 17 +++--- internal/client/docker_test.go | 92 ++++++++++++++++++++++++++------- internal/client/helpers_test.go | 4 +- internal/graph/build.go | 6 ++- internal/node/port.go | 7 +-- internal/node/port_test.go | 20 +++---- 6 files changed, 100 insertions(+), 46 deletions(-) diff --git a/internal/client/docker.go b/internal/client/docker.go index c03d7e8..baecafd 100644 --- a/internal/client/docker.go +++ b/internal/client/docker.go @@ -36,8 +36,8 @@ type ( type DockerClient interface { ContainerList(context.Context, container.ListOptions) ([]types.Container, error) ContainerInspect(context.Context, string) (types.ContainerJSON, error) - ContainerExecCreate(context.Context, string, types.ExecConfig) (types.IDResponse, error) - ContainerExecAttach(context.Context, string, types.ExecStartCheck) (types.HijackedResponse, error) + ContainerExecCreate(context.Context, string, container.ExecOptions) (types.IDResponse, error) + ContainerExecAttach(context.Context, string, container.ExecStartOptions) (types.HijackedResponse, error) ContainerTop(ctx context.Context, containerID string, arguments []string) (container.ContainerTopOKBody, error) Close() error } @@ -172,11 +172,10 @@ func (d *Docker) collectInodes( err = d.processesContainer(ctx, c.ID, func(pid int, name string) (err error) { inodes.AddProcess(c.ID, pid, name) - ierr := d.opt.Inodes(pid, func(inode uint64) { + if err = d.opt.Inodes(pid, func(inode uint64) { inodes.AddInode(c.ID, pid, inode) - }) - if ierr != nil { - log.Printf("inodes fail for: %s error: %v", c.Names[0], ierr) + }); err != nil { + return fmt.Errorf("%s/%d: %w", name, pid, err) } return nil @@ -268,7 +267,7 @@ func (d *Docker) connections( case LinuxNsenter: err = d.processesContainer(ctx, cid, func(pid int, _ string) (err error) { if err = d.opt.Nsenter(pid, proto, cb); err != nil { - return fmt.Errorf("nsenter: %w", err) + return fmt.Errorf("pid: %d: %w", pid, err) } return nil @@ -290,7 +289,7 @@ func (d *Docker) connectionsContainer( ) ( err error, ) { - exe, err := d.cli.ContainerExecCreate(ctx, containerID, types.ExecConfig{ + exe, err := d.cli.ContainerExecCreate(ctx, containerID, container.ExecOptions{ Tty: true, AttachStdout: true, Privileged: true, @@ -300,7 +299,7 @@ func (d *Docker) connectionsContainer( return fmt.Errorf("exec-create: %w", err) } - resp, err := d.cli.ContainerExecAttach(ctx, exe.ID, types.ExecStartCheck{ + resp, err := d.cli.ContainerExecAttach(ctx, exe.ID, container.ExecStartOptions{ Tty: true, }) if err != nil { diff --git a/internal/client/docker_test.go b/internal/client/docker_test.go index abef13a..3fc6017 100644 --- a/internal/client/docker_test.go +++ b/internal/client/docker_test.go @@ -60,7 +60,7 @@ func TestDockerClientContainersError(t *testing.T) { _, err = cli.Containers( context.Background(), - graph.ALL, + graph.TCP|graph.UDP, false, nil, voidProgress, @@ -91,7 +91,7 @@ func TestDockerClientContainersEmpty(t *testing.T) { rv, err := cli.Containers( context.Background(), - graph.ALL, + graph.TCP|graph.UDP, false, nil, voidProgress, @@ -130,7 +130,7 @@ func TestDockerClientContainersSingleExited(t *testing.T) { rv, err := cli.Containers( context.Background(), - graph.ALL, + graph.TCP|graph.UDP, false, nil, voidProgress, @@ -187,7 +187,7 @@ func TestDockerClientContainersExecCreateError(t *testing.T) { _, err = cli.Containers( context.Background(), - graph.ALL, + graph.TCP|graph.UDP, false, nil, voidProgress, @@ -240,7 +240,7 @@ func TestDockerClientContainersInspectError(t *testing.T) { _, err = cli.Containers( context.Background(), - graph.ALL, + graph.TCP|graph.UDP, false, nil, voidProgress, @@ -297,7 +297,7 @@ func TestDockerClientContainersExecAttachError(t *testing.T) { _, err = cli.Containers( context.Background(), - graph.ALL, + graph.TCP|graph.UDP, false, nil, voidProgress, @@ -336,6 +336,34 @@ func TestDockerClientContainersParseError(t *testing.T) { } } + cm.OnInspect = func() (rv types.ContainerJSON) { + rv.ContainerJSONBase = &types.ContainerJSONBase{} + rv.State = &types.ContainerState{Pid: 1} + rv.Config = &container.Config{ + Cmd: []string{"foo"}, + Env: []string{"BAR=1"}, + } + rv.Mounts = []types.MountPoint{ + { + Type: "bind", + Source: "src", + Destination: "dst", + }, + { + Type: "bind", + Source: "src1", + Destination: "dst1", + }, + { + Type: "mount", + Source: "src2", + Destination: "dst2", + }, + } + + return rv + } + cm.OnExecCreate = func() (rv types.IDResponse) { return } @@ -359,7 +387,7 @@ func TestDockerClientContainersParseError(t *testing.T) { _, err = cli.Containers( context.Background(), - graph.ALL, + graph.TCP|graph.UDP, false, nil, voidProgress, @@ -439,7 +467,7 @@ func TestDockerClientContainersSingle(t *testing.T) { rv, err := cli.Containers( context.Background(), - graph.ALL, + graph.TCP|graph.UDP, false, nil, voidProgress, @@ -538,7 +566,7 @@ func TestDockerClientContainersSingleFull(t *testing.T) { rv, err := cli.Containers( context.Background(), - graph.ALL, + graph.TCP|graph.UDP, false, nil, voidProgress, @@ -619,7 +647,7 @@ func TestDockerClientContainersSingleFullSkipEnv(t *testing.T) { rv, err := cli.Containers( context.Background(), - graph.ALL, + graph.TCP|graph.UDP, false, []string{"BAZ"}, voidProgress, @@ -727,7 +755,7 @@ func TestDockerClientNsEnterInspectError(t *testing.T) { _, err = cli.Containers( context.Background(), - graph.ALL, + graph.TCP|graph.UDP, false, nil, voidProgress, @@ -795,7 +823,7 @@ func TestDockerClientNsEnterConnectionsError(t *testing.T) { _, err = cli.Containers( context.Background(), - graph.ALL, + graph.TCP|graph.UDP, false, nil, voidProgress, @@ -867,7 +895,7 @@ func TestDockerClientNsEnterContainerTopVariants(t *testing.T) { if _, err = cli.Containers( context.Background(), - graph.ALL, + graph.TCP|graph.UDP, false, nil, voidProgress, @@ -908,9 +936,9 @@ func TestDockerClientNsEnterOk(t *testing.T) { } cm.OnContainerTop = func() (rv container.ContainerTopOKBody) { - rv.Titles = []string{"PID"} + rv.Titles = []string{"PID,CMD"} rv.Processes = [][]string{ - {"1"}, + {"1", "test"}, } return rv @@ -937,7 +965,7 @@ func TestDockerClientNsEnterOk(t *testing.T) { _, err = cli.Containers( context.Background(), - graph.ALL, + graph.TCP|graph.UDP, false, nil, voidProgress, @@ -972,9 +1000,32 @@ func TestDockerClientNsEnterLocal(t *testing.T) { } cm.OnContainerTop = func() (rv container.ContainerTopOKBody) { - rv.Titles = []string{"PID"} + rv.Titles = []string{"PID,CMD"} rv.Processes = [][]string{ - {"1"}, + {"1", "test"}, + } + + return rv + } + + cm.OnInspect = func() (rv types.ContainerJSON) { + rv.ContainerJSONBase = &types.ContainerJSONBase{} + rv.State = &types.ContainerState{Pid: 1} + rv.Config = &container.Config{ + Cmd: []string{"foo"}, + Env: []string{"BAR=1"}, + } + rv.Mounts = []types.MountPoint{ + { + Type: "bind", + Source: "src", + Destination: "dst", + }, + { + Type: "bind", + Source: "src1", + Destination: "dst1", + }, } return rv @@ -1005,7 +1056,7 @@ func TestDockerClientNsEnterLocal(t *testing.T) { rv, err := cli.Containers( context.Background(), - graph.ALL, + graph.TCP|graph.UDP, false, nil, voidProgress, @@ -1024,7 +1075,7 @@ func TestDockerClientNsEnterLocal(t *testing.T) { rv, err = cli.Containers( context.Background(), - graph.ALL, + graph.TCP|graph.UDP, true, nil, voidProgress, @@ -1038,6 +1089,7 @@ func TestDockerClientNsEnterLocal(t *testing.T) { } if rv[0].ConnectionsCount() != 3 { + t.Log("4", rv[0].ConnectionsCount()) t.Fail() } } diff --git a/internal/client/helpers_test.go b/internal/client/helpers_test.go index b984d4c..275c6d6 100644 --- a/internal/client/helpers_test.go +++ b/internal/client/helpers_test.go @@ -64,7 +64,7 @@ func (cm *clientMock) ContainerInspect( func (cm *clientMock) ContainerExecCreate( _ context.Context, _ string, - _ types.ExecConfig, + _ container.ExecOptions, ) (rv types.IDResponse, err error) { if cm.Err != nil { err = cm.Err @@ -78,7 +78,7 @@ func (cm *clientMock) ContainerExecCreate( func (cm *clientMock) ContainerExecAttach( _ context.Context, _ string, - _ types.ExecStartCheck, + _ container.ExecStartOptions, ) (rv types.HijackedResponse, err error) { if cm.Err != nil { err = cm.Err diff --git a/internal/graph/build.go b/internal/graph/build.go index 4be231f..f2a6a7f 100644 --- a/internal/graph/build.go +++ b/internal/graph/build.go @@ -162,8 +162,9 @@ func createNodes( } rem.Ports.Add(ProcessRemote, &node.Port{ - Kind: c.Proto.String(), - Value: strconv.Itoa(c.DstPort), + Kind: c.Proto.String(), + Value: strconv.Itoa(c.DstPort), + Number: c.DstPort, }) }) @@ -203,6 +204,7 @@ func buildEdges( } else { key = c.DstIP.String() port.Value = strconv.Itoa(c.DstPort) + port.Number = c.DstPort var ldst *Container diff --git a/internal/node/port.go b/internal/node/port.go index 6a52e95..16b0b24 100644 --- a/internal/node/port.go +++ b/internal/node/port.go @@ -1,9 +1,10 @@ package node type Port struct { - Kind string `json:"kind"` - Value string `json:"value"` - Local bool `json:"local"` + Kind string `json:"kind"` + Value string `json:"value"` + Number int `json:"-"` + Local bool `json:"local"` } func (p *Port) Label() string { diff --git a/internal/node/port_test.go b/internal/node/port_test.go index fea0d8c..47a2f43 100644 --- a/internal/node/port_test.go +++ b/internal/node/port_test.go @@ -25,11 +25,11 @@ func TestPortLabel(t *testing.T) { p := node.Port{Value: "100", Kind: "tcp"} l := p.Label() - if !strings.HasPrefix(l, want) { + if !strings.HasPrefix(l, p.Kind) { t.Fail() } - if !strings.HasSuffix(l, p.Kind) { + if !strings.HasSuffix(l, want) { t.Fail() } } @@ -49,7 +49,7 @@ func TestPortsHas(t *testing.T) { }, { Ports: &node.Ports{}, - Labels: []string{"80/tcp"}, + Labels: []string{"tcp:80"}, Want: false, }, { @@ -57,7 +57,7 @@ func TestPortsHas(t *testing.T) { Kind: "tcp", Value: "80", }), - Labels: []string{"80/tcp"}, + Labels: []string{"tcp:80"}, Want: true, }, { @@ -65,7 +65,7 @@ func TestPortsHas(t *testing.T) { Kind: "tcp", Value: "81", }), - Labels: []string{"80/tcp"}, + Labels: []string{"tcp:80"}, Want: false, }, { @@ -73,7 +73,7 @@ func TestPortsHas(t *testing.T) { Kind: "tcp", Value: "80", }), - Labels: []string{"80/tcp", "443/tcp"}, + Labels: []string{"tcp:80", "tcp:443"}, Want: false, }, } @@ -100,7 +100,7 @@ func TestPortsHasAny(t *testing.T) { }, { Ports: &node.Ports{}, - Labels: []string{"80/tcp"}, + Labels: []string{"tcp:80"}, Want: false, }, { @@ -108,7 +108,7 @@ func TestPortsHasAny(t *testing.T) { Kind: "tcp", Value: "80", }), - Labels: []string{"80/tcp"}, + Labels: []string{"tcp:80"}, Want: true, }, { @@ -116,7 +116,7 @@ func TestPortsHasAny(t *testing.T) { Kind: "tcp", Value: "81", }), - Labels: []string{"80/tcp"}, + Labels: []string{"tcp:80"}, Want: false, }, { @@ -124,7 +124,7 @@ func TestPortsHasAny(t *testing.T) { Kind: "tcp", Value: "80", }), - Labels: []string{"80/tcp", "443/tcp"}, + Labels: []string{"tcp:80", "tcp:443"}, Want: true, }, } From 7fc428e7e71f05e42a83c5de5d8ce8f7670684d4 Mon Sep 17 00:00:00 2001 From: Shevchenko Alexey Date: Mon, 17 Jun 2024 00:50:56 +0300 Subject: [PATCH 06/16] more tests fixed --- internal/client/docker.go | 4 +- internal/client/docker_test.go | 99 ++++++++++++++++++++++++++++++++- internal/cluster/groups_test.go | 32 +++++------ internal/cluster/node.go | 7 ++- internal/cluster/rules_test.go | 58 +++++++++---------- 5 files changed, 149 insertions(+), 51 deletions(-) diff --git a/internal/client/docker.go b/internal/client/docker.go index baecafd..f6b5332 100644 --- a/internal/client/docker.go +++ b/internal/client/docker.go @@ -320,6 +320,8 @@ func (d *Docker) processesContainer( cid string, fun func(int, string) error, ) (err error) { + const minPsFields = 2 + ps, err := d.cli.ContainerTop(ctx, cid, []string{"-o pid,cmd"}) if err != nil { return fmt.Errorf("top: %w", err) @@ -328,7 +330,7 @@ func (d *Docker) processesContainer( var pid int for _, p := range ps.Processes { - if len(p) < len(ps.Titles) { + if len(p) < minPsFields { continue } diff --git a/internal/client/docker_test.go b/internal/client/docker_test.go index 3fc6017..d626db4 100644 --- a/internal/client/docker_test.go +++ b/internal/client/docker_test.go @@ -279,6 +279,34 @@ func TestDockerClientContainersExecAttachError(t *testing.T) { } } + cm.OnInspect = func() (rv types.ContainerJSON) { + rv.ContainerJSONBase = &types.ContainerJSONBase{} + rv.State = &types.ContainerState{Pid: 1} + rv.Config = &container.Config{ + Cmd: []string{"foo"}, + Env: []string{"BAR=1"}, + } + rv.Mounts = []types.MountPoint{ + { + Type: "bind", + Source: "src", + Destination: "dst", + }, + { + Type: "bind", + Source: "src1", + Destination: "dst1", + }, + { + Type: "mount", + Source: "src2", + Destination: "dst2", + }, + } + + return rv + } + cm.OnExecCreate = func() (rv types.IDResponse) { cm.Err = testErr @@ -444,6 +472,33 @@ func TestDockerClientContainersSingle(t *testing.T) { }, } }, + OnInspect: func() (rv types.ContainerJSON) { + rv.ContainerJSONBase = &types.ContainerJSONBase{} + rv.State = &types.ContainerState{Pid: 1} + rv.Config = &container.Config{ + Cmd: []string{"foo"}, + Env: []string{"BAR=1"}, + } + rv.Mounts = []types.MountPoint{ + { + Type: "bind", + Source: "src", + Destination: "dst", + }, + { + Type: "bind", + Source: "src1", + Destination: "dst1", + }, + { + Type: "mount", + Source: "src2", + Destination: "dst2", + }, + } + + return rv + }, OnExecCreate: func() (rv types.IDResponse) { return }, @@ -795,10 +850,22 @@ func TestDockerClientNsEnterConnectionsError(t *testing.T) { } } + cm.OnInspect = func() (rv types.ContainerJSON) { + rv.ContainerJSONBase = &types.ContainerJSONBase{} + rv.State = &types.ContainerState{Pid: 1} + rv.Config = &container.Config{ + Cmd: []string{"foo"}, + Env: []string{"BAR=1", "BAZ=2"}, + } + rv.Mounts = []types.MountPoint{} + + return rv + } + cm.OnContainerTop = func() (rv container.ContainerTopOKBody) { - rv.Titles = []string{"PID"} + rv.Titles = []string{"PID,CMD"} rv.Processes = [][]string{ - {"1"}, + {"1", "test"}, } return rv @@ -861,12 +928,25 @@ func TestDockerClientNsEnterContainerTopVariants(t *testing.T) { } } + cm.OnInspect = func() (rv types.ContainerJSON) { + rv.ContainerJSONBase = &types.ContainerJSONBase{} + rv.State = &types.ContainerState{Pid: 1} + rv.Config = &container.Config{ + Cmd: []string{"foo"}, + Env: []string{"BAR=1", "BAZ=2"}, + } + rv.Mounts = []types.MountPoint{} + + return rv + } + cm.OnContainerTop = func() (rv container.ContainerTopOKBody) { - rv.Titles = []string{"PID"} + rv.Titles = []string{"PID,CMD"} rv.Processes = [][]string{ {}, {"a"}, {"1"}, + {"1", "test"}, } return rv @@ -904,6 +984,7 @@ func TestDockerClientNsEnterContainerTopVariants(t *testing.T) { } if count != 1 { + t.Log("here") t.Fail() } } @@ -935,6 +1016,18 @@ func TestDockerClientNsEnterOk(t *testing.T) { } } + cm.OnInspect = func() (rv types.ContainerJSON) { + rv.ContainerJSONBase = &types.ContainerJSONBase{} + rv.State = &types.ContainerState{Pid: 1} + rv.Config = &container.Config{ + Cmd: []string{"foo"}, + Env: []string{"BAR=1", "BAZ=2"}, + } + rv.Mounts = []types.MountPoint{} + + return rv + } + cm.OnContainerTop = func() (rv container.ContainerTopOKBody) { rv.Titles = []string{"PID,CMD"} rv.Processes = [][]string{ diff --git a/internal/cluster/groups_test.go b/internal/cluster/groups_test.go index ce83754..270b706 100644 --- a/internal/cluster/groups_test.go +++ b/internal/cluster/groups_test.go @@ -27,9 +27,9 @@ func TestAdd(t *testing.T) { Inbounds: make(set.Unordered[string]), Outbounds: make(set.Unordered[string]), Ports: makeTestPorts([]*node.Port{ - {Kind: "tcp", Value: "1"}, - {Kind: "tcp", Value: "2"}, - {Kind: "tcp", Value: "3"}, + {Kind: "tcp", Value: "1", Number: 1}, + {Kind: "tcp", Value: "2", Number: 2}, + {Kind: "tcp", Value: "3", Number: 3}, }), }) @@ -37,9 +37,9 @@ func TestAdd(t *testing.T) { Inbounds: make(set.Unordered[string]), Outbounds: make(set.Unordered[string]), Ports: makeTestPorts([]*node.Port{ - {Kind: "tcp", Value: "1"}, - {Kind: "tcp", Value: "2"}, - {Kind: "tcp", Value: "3"}, + {Kind: "tcp", Value: "1", Number: 1}, + {Kind: "tcp", Value: "2", Number: 2}, + {Kind: "tcp", Value: "3", Number: 3}, }), }) @@ -47,8 +47,8 @@ func TestAdd(t *testing.T) { Inbounds: make(set.Unordered[string]), Outbounds: make(set.Unordered[string]), Ports: makeTestPorts([]*node.Port{ - {Kind: "tcp", Value: "3"}, - {Kind: "tcp", Value: "2"}, + {Kind: "tcp", Value: "3", Number: 3}, + {Kind: "tcp", Value: "2", Number: 2}, }), }) @@ -56,8 +56,8 @@ func TestAdd(t *testing.T) { Inbounds: make(set.Unordered[string]), Outbounds: make(set.Unordered[string]), Ports: makeTestPorts([]*node.Port{ - {Kind: "tcp", Value: "1"}, - {Kind: "tcp", Value: "3"}, + {Kind: "tcp", Value: "1", Number: 1}, + {Kind: "tcp", Value: "3", Number: 3}, }), }) @@ -65,8 +65,8 @@ func TestAdd(t *testing.T) { Inbounds: make(set.Unordered[string]), Outbounds: make(set.Unordered[string]), Ports: makeTestPorts([]*node.Port{ - {Kind: "tcp", Value: "1"}, - {Kind: "tcp", Value: "2"}, + {Kind: "tcp", Value: "1", Number: 1}, + {Kind: "tcp", Value: "2", Number: 2}, }), }) @@ -74,9 +74,9 @@ func TestAdd(t *testing.T) { Inbounds: make(set.Unordered[string]), Outbounds: make(set.Unordered[string]), Ports: makeTestPorts([]*node.Port{ - {Kind: "tcp", Value: "1"}, - {Kind: "tcp", Value: "2"}, - {Kind: "tcp", Value: "4"}, + {Kind: "tcp", Value: "1", Number: 1}, + {Kind: "tcp", Value: "2", Number: 2}, + {Kind: "tcp", Value: "4", Number: 4}, }), }) @@ -84,7 +84,7 @@ func TestAdd(t *testing.T) { Inbounds: make(set.Unordered[string]), Outbounds: make(set.Unordered[string]), Ports: makeTestPorts([]*node.Port{ - {Kind: "tcp", Value: "1"}, + {Kind: "tcp", Value: "1", Number: 1}, }), }) diff --git a/internal/cluster/node.go b/internal/cluster/node.go index 013c978..8e955ff 100644 --- a/internal/cluster/node.go +++ b/internal/cluster/node.go @@ -79,8 +79,11 @@ func portsToProtos(ports *node.Ports) (rv map[string][]int) { ports.Iter(func(_ string, pl []*node.Port) { for _, p := range pl { - _ = p - // rv[p.Kind] = append(rv[p.Kind], p.Value) + if p.Kind == "unix" { + continue + } + + rv[p.Kind] = append(rv[p.Kind], p.Number) } }) diff --git a/internal/cluster/rules_test.go b/internal/cluster/rules_test.go index 65ec086..84d671f 100644 --- a/internal/cluster/rules_test.go +++ b/internal/cluster/rules_test.go @@ -15,8 +15,8 @@ import ( const ( testBuilderName = "testbuilder" - clusterRules = `[{"name": "foo", "if": "node.Listen.Has('80/tcp')"}, -{"name": "bar", "if": "node.Listen.HasAny('22/tcp', '443/tcp')"}]` + clusterRules = `[{"name": "foo", "if": "node.Listen.Has('tcp:80')"}, +{"name": "bar", "if": "node.Listen.HasAny('tcp:22', 'tcp:443')"}]` ) type testNamedBuilder struct { @@ -78,35 +78,35 @@ func TestRulesMatch(t *testing.T) { }{ { Node: &node.Node{ - Ports: makeTestPorts([]*node.Port{{Kind: "tcp", Value: "80"}}), + Ports: makeTestPorts([]*node.Port{{Kind: "tcp", Value: "80", Number: 80}}), }, Want: "foo", }, { Node: &node.Node{ - Ports: makeTestPorts([]*node.Port{{Kind: "tcp", Value: "22"}}), + Ports: makeTestPorts([]*node.Port{{Kind: "tcp", Value: "22", Number: 22}}), }, Want: "bar", }, { Node: &node.Node{ - Ports: makeTestPorts([]*node.Port{{Kind: "tcp", Value: "443"}}), + Ports: makeTestPorts([]*node.Port{{Kind: "tcp", Value: "443", Number: 443}}), }, Want: "bar", }, { Node: &node.Node{Ports: makeTestPorts([]*node.Port{ - {Kind: "tcp", Value: "22"}, - {Kind: "tcp", Value: "80"}, - {Kind: "tcp", Value: "443"}, + {Kind: "tcp", Value: "22", Number: 22}, + {Kind: "tcp", Value: "80", Number: 80}, + {Kind: "tcp", Value: "443", Number: 443}, })}, Want: "foo", }, { Node: &node.Node{Ports: makeTestPorts([]*node.Port{ - {Kind: "tcp", Value: "22"}, - {Kind: "tcp", Value: "80"}, - {Kind: "tcp", Value: "8080"}, + {Kind: "tcp", Value: "22", Number: 22}, + {Kind: "tcp", Value: "80", Number: 80}, + {Kind: "tcp", Value: "8080", Number: 8080}, })}, Want: "foo", }, @@ -153,13 +153,13 @@ func TestRulesMatch(t *testing.T) { func TestRulesMatchWeight(t *testing.T) { t.Parallel() - const clusterRulesWeight = `[{"name": "foo", "weight": 2, "if": "node.Listen.Has('80/tcp')"}, -{"name": "bar", "if": "node.Listen.HasAny('22/tcp', '443/tcp')"}]` + const clusterRulesWeight = `[{"name": "foo", "weight": 2, "if": "node.Listen.Has('tcp:80')"}, +{"name": "bar", "if": "node.Listen.HasAny('tcp:22', 'tcp:443')"}]` testNode := &node.Node{Ports: makeTestPorts([]*node.Port{ - {Kind: "tcp", Value: "22"}, - {Kind: "tcp", Value: "80"}, - {Kind: "tcp", Value: "8080"}, + {Kind: "tcp", Value: "22", Number: 22}, + {Kind: "tcp", Value: "80", Number: 80}, + {Kind: "tcp", Value: "8080", Number: 8080}, })} ca := cluster.NewRules(nil, nil) @@ -191,62 +191,62 @@ func TestRules(t *testing.T) { ca.AddNode(&node.Node{ ID: "1", Ports: makeTestPorts([]*node.Port{ - {Kind: "tcp", Value: "80"}, + {Kind: "tcp", Value: "80", Number: 80}, })}) ca.AddNode(&node.Node{ ID: "2", Ports: makeTestPorts([]*node.Port{ - {Kind: "tcp", Value: "22"}, + {Kind: "tcp", Value: "22", Number: 22}, })}) ca.AddNode(&node.Node{ ID: "3", Ports: makeTestPorts([]*node.Port{ - {Kind: "tcp", Value: "443"}, - {Kind: "tcp", Value: "8080"}, + {Kind: "tcp", Value: "443", Number: 443}, + {Kind: "tcp", Value: "8080", Number: 8080}, })}) ca.AddNode(&node.Node{ ID: "4", Ports: makeTestPorts([]*node.Port{ - {Kind: "tcp", Value: "8080"}, + {Kind: "tcp", Value: "8080", Number: 8080}, })}) ca.AddEdge(&node.Edge{ SrcID: "1", DstID: "3", - Port: &node.Port{Kind: "tcp", Value: "443"}, + Port: &node.Port{Kind: "tcp", Value: "443", Number: 443}, }) ca.AddEdge(&node.Edge{ SrcID: "3", DstID: "1", - Port: &node.Port{Kind: "tcp", Value: "8080"}, + Port: &node.Port{Kind: "tcp", Value: "8080", Number: 8080}, }) ca.AddEdge(&node.Edge{ SrcID: "3", DstID: "1", - Port: &node.Port{Kind: "tcp", Value: "80"}, + Port: &node.Port{Kind: "tcp", Value: "80", Number: 80}, }) ca.AddEdge(&node.Edge{ SrcID: "1", DstID: "4", - Port: &node.Port{Kind: "tcp", Value: "8080"}, + Port: &node.Port{Kind: "tcp", Value: "8080", Number: 8080}, }) ca.AddEdge(&node.Edge{ SrcID: "5", DstID: "1", - Port: &node.Port{Kind: "tcp", Value: "80"}, + Port: &node.Port{Kind: "tcp", Value: "80", Number: 80}, }) ca.AddEdge(&node.Edge{ SrcID: "1", DstID: "5", - Port: &node.Port{Kind: "tcp", Value: "80"}, + Port: &node.Port{Kind: "tcp", Value: "80", Number: 80}, }) if tb.Edges != 4 || tb.Nodes != 4 { @@ -295,7 +295,7 @@ func TestRulesBuilderAddError(t *testing.T) { err := ca.AddNode(&node.Node{ ID: "1", Ports: makeTestPorts([]*node.Port{ - {Kind: "tcp", Value: "80"}, + {Kind: "tcp", Value: "80", Number: 80}, })}) if !errors.Is(err, myError) { t.Fail() @@ -328,7 +328,7 @@ func TestRulesBuilderWriteError(t *testing.T) { ca.AddNode(&node.Node{ ID: "1", Ports: makeTestPorts([]*node.Port{ - {Kind: "tcp", Value: "80"}, + {Kind: "tcp", Value: "80", Number: 80}, })}) tb.Err = errors.New("test-error") From a976a26473c1012451ccf599175c7e8baa0b0b27 Mon Sep 17 00:00:00 2001 From: Shevchenko Alexey Date: Fri, 28 Jun 2024 12:25:03 +0300 Subject: [PATCH 07/16] orphans remover, fixes --- cmd/decompose/main.go | 25 +++++++++----- internal/client/docker.go | 6 ++-- internal/graph/build.go | 8 +++-- internal/graph/orphans.go | 63 ++++++++++++++++++++++++++++++++++ internal/structurizr/system.go | 2 +- 5 files changed, 90 insertions(+), 14 deletions(-) create mode 100644 internal/graph/orphans.go diff --git a/cmd/decompose/main.go b/cmd/decompose/main.go index 023ab5c..08a1daa 100644 --- a/cmd/decompose/main.go +++ b/cmd/decompose/main.go @@ -43,15 +43,15 @@ var ( ) var ( - fSilent, fVersion bool - fHelp, fLocal bool - fNoLoops bool - fDeep, fCompress bool - fProto, fFormat string - fOut, fFollow string - fMeta, fCluster string - fSkipEnv string - fLoad []string + fSilent, fVersion bool + fHelp, fLocal bool + fNoLoops, fNoOrphans bool + fDeep, fCompress bool + fProto, fFormat string + fOut, fFollow string + fMeta, fCluster string + fSkipEnv string + fLoad []string knownBuilders string ErrUnknown = errors.New("unknown") @@ -86,6 +86,7 @@ func setupFlags() { flag.BoolVar(&fHelp, "help", false, "show this help") flag.BoolVar(&fLocal, "local", false, "skip external hosts") flag.BoolVar(&fNoLoops, "no-loops", false, "remove connection loops (node to itself) from output") + flag.BoolVar(&fNoOrphans, "no-orphans", false, "remove orphaned (not connected) nodes from output") flag.BoolVar(&fDeep, "deep", false, "process-based introspection") flag.BoolVar(&fCompress, "compress", false, "compress graph") @@ -296,6 +297,12 @@ func prepareConfig() ( bildr, nwr = cb, cb } + if fNoOrphans { + cb := graph.NewOrphansInspector(bildr) + + bildr, nwr = cb, cb + } + skipKeys := []string{} if fSkipEnv != "" { diff --git a/internal/client/docker.go b/internal/client/docker.go index f6b5332..f250862 100644 --- a/internal/client/docker.go +++ b/internal/client/docker.go @@ -99,7 +99,7 @@ func (d *Docker) Containers( c := &containers[idx] c.State = "" - log.Printf("container: %s/%s in fault state", c.Names[0], c.ID) + log.Printf("container: %s %s has invalid state", c.Names[0], c.ID) return false } @@ -121,7 +121,9 @@ func (d *Docker) Containers( con, cerr := d.extractInfo(ctx, c, proto, deep, skeys, inodes) if cerr != nil { - return nil, fmt.Errorf("container %s: %w", c.ID, cerr) + log.Printf("container: %s %s error: %v", c.Names[0], c.ID, cerr) + + continue } cmap[c.ID] = con diff --git a/internal/graph/build.go b/internal/graph/build.go index f2a6a7f..ca87c9e 100644 --- a/internal/graph/build.go +++ b/internal/graph/build.go @@ -97,7 +97,7 @@ func Build( log.Println("Building edges") - buildEdges(cfg, containers, neighbours, nodes) + log.Printf("Found %d edges", buildEdges(cfg, containers, neighbours, nodes)) return nil } @@ -181,7 +181,7 @@ func buildEdges( cntrs []*Container, local map[string]*Container, nodes map[string]*node.Node, -) { +) (total int) { for _, con := range cntrs { src, ok := nodes[con.ID] if !ok { @@ -241,6 +241,7 @@ func buildEdges( DstName: dname, Port: port, }) + total++ return } @@ -257,11 +258,14 @@ func buildEdges( DstName: ProcessRemote, Port: port, }) + total++ return } }) } + + return total } func percentOf(a, b int) float64 { diff --git a/internal/graph/orphans.go b/internal/graph/orphans.go new file mode 100644 index 0000000..b2d369e --- /dev/null +++ b/internal/graph/orphans.go @@ -0,0 +1,63 @@ +package graph + +import ( + "fmt" + "io" + + "github.com/s0rg/decompose/internal/node" + + "github.com/s0rg/set" +) + +const orphansName = "no-orphans" + +type OrphansInspector struct { + b NamedBuilderWriter + n []*node.Node + e []*node.Edge + o set.Unordered[string] +} + +func NewOrphansInspector(b NamedBuilderWriter) *OrphansInspector { + return &OrphansInspector{ + b: b, + o: make(set.Unordered[string]), + } +} + +func (o *OrphansInspector) Name() string { + return o.b.Name() + " " + orphansName +} + +func (o *OrphansInspector) AddNode(n *node.Node) error { + o.n = append(o.n, n) + o.o.Add(n.ID) + + return nil +} + +func (o *OrphansInspector) AddEdge(e *node.Edge) { + o.e = append(o.e, e) + o.o.Del(e.SrcID) + o.o.Del(e.DstID) +} + +func (o *OrphansInspector) Write(w io.Writer) error { + for _, n := range o.n { + if o.o.Has(n.ID) { + continue + } + + o.b.AddNode(n) + } + + for _, e := range o.e { + o.b.AddEdge(e) + } + + if err := o.b.Write(w); err != nil { + return fmt.Errorf("no-orphans: %w", err) + } + + return nil +} diff --git a/internal/structurizr/system.go b/internal/structurizr/system.go index 4141c3f..aaec73b 100644 --- a/internal/structurizr/system.go +++ b/internal/structurizr/system.go @@ -76,7 +76,7 @@ func (s *System) AddRelation(srcID, dstID, srcName, dstName string) (rv *Relatio return rv, true } - srcID, dstID = SafeID(src.Name), SafeID(dst.Name) + srcID, dstID = SafeID(src.ID), SafeID(dst.ID) dest, ok := s.relationships[srcID] if !ok { From 5e7c4710b3f0d1bdbd948e0ec787d1a4695d5293 Mon Sep 17 00:00:00 2001 From: Shevchenko Alexey Date: Tue, 2 Jul 2024 23:55:11 +0300 Subject: [PATCH 08/16] builder refactoring --- internal/graph/build.go | 194 ++-------------------------------- internal/graph/build_state.go | 189 +++++++++++++++++++++++++++++++++ internal/graph/orphans.go | 12 ++- 3 files changed, 207 insertions(+), 188 deletions(-) create mode 100644 internal/graph/build_state.go diff --git a/internal/graph/build.go b/internal/graph/build.go index ca87c9e..189e452 100644 --- a/internal/graph/build.go +++ b/internal/graph/build.go @@ -6,7 +6,6 @@ import ( "fmt" "io" "log" - "strconv" "github.com/s0rg/decompose/internal/node" ) @@ -48,7 +47,7 @@ func Build( cfg *Config, cli ContainerClient, ) error { - log.Println("Gathering containers info") + log.Println("Gathering containers info, please be patient...") containers, err := cli.Containers( context.Background(), @@ -75,199 +74,28 @@ func Build( return fmt.Errorf("%w: containers", ErrNotEnough) } - neighbours := buildIPMap(containers) + state := newBuilderState(cfg, containers) - log.Println("Building nodes") + log.Println("Building nodes...") - nodes := createNodes(cfg, containers, neighbours) - - log.Printf("Processing %d nodes", len(nodes)) - - if len(nodes) < minItems { - return fmt.Errorf("%w: nodes", ErrNotEnough) + nodes, err := state.BuildNodes() + if err != nil { + return fmt.Errorf("build nodes: %w", err) } - for _, node := range nodes { - cfg.Meta.Enrich(node) + log.Printf("Processing %d nodes", nodes) - if err = cfg.Builder.AddNode(node); err != nil { - return fmt.Errorf("node '%s': %w", node.Name, err) - } + if nodes < minItems { + return fmt.Errorf("%w: nodes", ErrNotEnough) } - log.Println("Building edges") + log.Println("Building edges...") - log.Printf("Found %d edges", buildEdges(cfg, containers, neighbours, nodes)) + log.Printf("Found %d edges", state.BuildEdges()) return nil } -func buildIPMap(cntrs []*Container) (rv map[string]*Container) { - rv = make(map[string]*Container) - - for _, it := range cntrs { - for ip := range it.Endpoints { - rv[ip] = it - } - } - - return rv -} - -func createNodes( - cfg *Config, - cntrs []*Container, - local map[string]*Container, -) (rv map[string]*node.Node) { - var ( - skip bool - notice bool - ) - - rv = make(map[string]*node.Node) - - for _, con := range cntrs { - if con.ConnectionsCount() == 0 && !notice { - log.Printf("No connections for container: %s:%s, try run as root", con.ID, con.Name) - - notice = true - } - - skip = !cfg.MatchName(con.Name) - - con.IterOutbounds(func(c *Connection) { - if c.Proto == UNIX || c.DstIP.IsLoopback() { - return - } - - rip := c.DstIP.String() - - if lc, ok := local[rip]; ok { // destination known - if skip && cfg.MatchName(lc.Name) { - skip = false - } - - return - } - - if cfg.OnlyLocal || skip { - return - } - - // destination is remote host, add it - rem, ok := rv[rip] - if !ok { - rem = node.External(rip) - rv[rip] = rem - } - - rem.Ports.Add(ProcessRemote, &node.Port{ - Kind: c.Proto.String(), - Value: strconv.Itoa(c.DstPort), - Number: c.DstPort, - }) - }) - - if !skip { - rv[con.ID] = con.ToNode() - } - } - - return rv -} - -func buildEdges( - cfg *Config, - cntrs []*Container, - local map[string]*Container, - nodes map[string]*node.Node, -) (total int) { - for _, con := range cntrs { - src, ok := nodes[con.ID] - if !ok { - continue - } - - con.IterOutbounds(func(c *Connection) { - var ( - port = &node.Port{ - Kind: c.Proto.String(), - } - dstID string - key string - ok bool - ) - - if c.Proto == UNIX { - port.Value = c.Path - dstID, ok = c.DstID, true - } else { - key = c.DstIP.String() - port.Value = strconv.Itoa(c.DstPort) - port.Number = c.DstPort - - var ldst *Container - - if c.DstIP.IsLoopback() { - ldst, ok = con, true - } else { - ldst, ok = local[key] - } - - if ok { - dstID = ldst.ID - } - } - - if ok { - if cfg.NoLoops && con.ID == dstID { - return - } - - dst, found := nodes[dstID] - if !found { - return - } - - dname, found := dst.Ports.Get(port) - if !found { - dname = ProcessUnknown - } - - cfg.Builder.AddEdge(&node.Edge{ - SrcID: src.ID, - SrcName: c.Process, - DstID: dstID, - DstName: dname, - Port: port, - }) - total++ - - return - } - - if !cfg.MatchName(con.Name) { - return - } - - if rdst, ok := nodes[key]; ok { - cfg.Builder.AddEdge(&node.Edge{ - SrcID: src.ID, - SrcName: c.Process, - DstID: rdst.ID, - DstName: ProcessRemote, - Port: port, - }) - total++ - - return - } - }) - } - - return total -} - func percentOf(a, b int) float64 { const hundred = 100.0 diff --git a/internal/graph/build_state.go b/internal/graph/build_state.go new file mode 100644 index 0000000..58d4087 --- /dev/null +++ b/internal/graph/build_state.go @@ -0,0 +1,189 @@ +package graph + +import ( + "fmt" + "log" + "strconv" + + "github.com/s0rg/decompose/internal/node" +) + +type builderState struct { + Config *Config + KnownIP map[string]*Container + Nodes map[string]*node.Node + Containers []*Container +} + +func newBuilderState( + cfg *Config, + cntrs []*Container, +) (bs *builderState) { + bs = &builderState{ + Config: cfg, + Containers: cntrs, + KnownIP: make(map[string]*Container, len(cntrs)), + Nodes: make(map[string]*node.Node, len(cntrs)), + } + + for _, it := range cntrs { + for ip := range it.Endpoints { + bs.KnownIP[ip] = it + } + } + + return bs +} + +func (bs *builderState) BuildNodes() (total int, err error) { + var notice bool + + for _, con := range bs.Containers { + if con.ConnectionsCount() == 0 && !notice { + log.Printf("No connections for container: %s:%s, try run as root", con.ID, con.Name) + + notice = true + } + + if !bs.matchContainer(con) { + continue + } + + n := con.ToNode() + + bs.Config.Meta.Enrich(n) + + if err = bs.Config.Builder.AddNode(n); err != nil { + return 0, fmt.Errorf("node '%s': %w", n.Name, err) + } + + bs.Nodes[con.ID] = n + + total++ + } + + return total, nil +} + +func (bs *builderState) BuildEdges() (total int) { + for _, con := range bs.Containers { + src, ok := bs.Nodes[con.ID] + if !ok { + continue + } + + con.IterOutbounds(func(c *Connection) { + if edge, ok := bs.findEdge(con.ID, con.Name, c); ok { + edge.SrcID = src.ID + + bs.Config.Builder.AddEdge(edge) + + total++ + } + }) + } + + return total +} + +func (bs *builderState) matchContainer(cn *Container) (yes bool) { + yes = bs.Config.MatchName(cn.Name) + + cn.IterOutbounds(func(c *Connection) { + if c.Proto == UNIX || c.DstIP.IsLoopback() { + return + } + + rip := c.DstIP.String() + + if lc, ok := bs.KnownIP[rip]; ok { // destination known + if !yes && bs.Config.MatchName(lc.Name) { + yes = true + } + + return + } + + if bs.Config.OnlyLocal || !yes { + return + } + + // destination is remote host, add it + rem, ok := bs.Nodes[rip] + if !ok { + rem = node.External(rip) + bs.Nodes[rip] = rem + } + + rem.Ports.Add(ProcessRemote, &node.Port{ + Kind: c.Proto.String(), + Value: strconv.Itoa(c.DstPort), + Number: c.DstPort, + }) + }) + + return yes +} + +func (bs *builderState) findEdge(cid, cname string, conn *Connection) (rv *node.Edge, ok bool) { + var ( + port = &node.Port{ + Kind: conn.Proto.String(), + } + key string + ) + + rv = &node.Edge{ + SrcName: conn.Process, + Port: port, + } + + switch conn.Proto { + case UNIX: + port.Value = conn.Path + rv.DstID = conn.DstID + default: + key = conn.DstIP.String() + port.Value = strconv.Itoa(conn.DstPort) + port.Number = conn.DstPort + + if conn.DstIP.IsLoopback() { + rv.DstID = cid + } else if ldst, found := bs.KnownIP[key]; found { + rv.DstID = ldst.ID + } + } + + if rv.DstID != "" { + if bs.Config.NoLoops && cid == rv.DstID { + return nil, false + } + + dst, found := bs.Nodes[rv.DstID] + if !found { + return nil, false + } + + dname, found := dst.Ports.Get(port) + if !found { + dname = ProcessUnknown + } + + rv.DstName = dname + + return rv, true + } + + if !bs.Config.MatchName(cname) { + return nil, false + } + + if rdst, found := bs.Nodes[key]; found { + rv.DstID = rdst.ID + rv.DstName = ProcessRemote + + return rv, true + } + + return nil, false +} diff --git a/internal/graph/orphans.go b/internal/graph/orphans.go index b2d369e..03dd4aa 100644 --- a/internal/graph/orphans.go +++ b/internal/graph/orphans.go @@ -13,9 +13,9 @@ const orphansName = "no-orphans" type OrphansInspector struct { b NamedBuilderWriter + o set.Unordered[string] n []*node.Node e []*node.Edge - o set.Unordered[string] } func NewOrphansInspector(b NamedBuilderWriter) *OrphansInspector { @@ -42,21 +42,23 @@ func (o *OrphansInspector) AddEdge(e *node.Edge) { o.o.Del(e.DstID) } -func (o *OrphansInspector) Write(w io.Writer) error { +func (o *OrphansInspector) Write(w io.Writer) (err error) { for _, n := range o.n { if o.o.Has(n.ID) { continue } - o.b.AddNode(n) + if err = o.b.AddNode(n); err != nil { + return fmt.Errorf("no-orphans add node: %w", err) + } } for _, e := range o.e { o.b.AddEdge(e) } - if err := o.b.Write(w); err != nil { - return fmt.Errorf("no-orphans: %w", err) + if err = o.b.Write(w); err != nil { + return fmt.Errorf("no-orphans write: %w", err) } return nil From 2bac67c736625d7e2780c356eee98957e1e30ff8 Mon Sep 17 00:00:00 2001 From: Shevchenko Alexey Date: Thu, 4 Jul 2024 01:52:01 +0300 Subject: [PATCH 09/16] builder tests done --- internal/graph/build_state.go | 26 +++++++++++++++++++++++--- internal/graph/build_test.go | 20 +++++++++++++------- internal/graph/connection_test.go | 6 +++--- internal/graph/conngroup_test.go | 12 ++++++------ internal/graph/container_test.go | 10 +++++----- internal/graph/netstat.go | 14 +++++--------- internal/graph/netstat_test.go | 7 ++++--- 7 files changed, 59 insertions(+), 36 deletions(-) diff --git a/internal/graph/build_state.go b/internal/graph/build_state.go index 58d4087..3f84391 100644 --- a/internal/graph/build_state.go +++ b/internal/graph/build_state.go @@ -6,12 +6,14 @@ import ( "strconv" "github.com/s0rg/decompose/internal/node" + "github.com/s0rg/set" ) type builderState struct { Config *Config KnownIP map[string]*Container Nodes map[string]*node.Node + Remotes set.Unordered[string] Containers []*Container } @@ -24,6 +26,7 @@ func newBuilderState( Containers: cntrs, KnownIP: make(map[string]*Container, len(cntrs)), Nodes: make(map[string]*node.Node, len(cntrs)), + Remotes: make(set.Unordered[string]), } for _, it := range cntrs { @@ -62,7 +65,23 @@ func (bs *builderState) BuildNodes() (total int, err error) { total++ } - return total, nil + if !bs.Config.OnlyLocal { + bs.Remotes.Iter(func(rip string) bool { + n := bs.Nodes[rip] + + if err = bs.Config.Builder.AddNode(n); err != nil { + err = fmt.Errorf("node '%s': %w", n.Name, err) + + return false + } + + total++ + + return true + }) + } + + return total, err } func (bs *builderState) BuildEdges() (total int) { @@ -104,7 +123,7 @@ func (bs *builderState) matchContainer(cn *Container) (yes bool) { return } - if bs.Config.OnlyLocal || !yes { + if bs.Config.OnlyLocal && !yes { return } @@ -113,6 +132,7 @@ func (bs *builderState) matchContainer(cn *Container) (yes bool) { if !ok { rem = node.External(rip) bs.Nodes[rip] = rem + bs.Remotes.Add(rip) } rem.Ports.Add(ProcessRemote, &node.Port{ @@ -174,7 +194,7 @@ func (bs *builderState) findEdge(cid, cname string, conn *Connection) (rv *node. return rv, true } - if !bs.Config.MatchName(cname) { + if !bs.Config.MatchName(cname) || bs.Config.OnlyLocal { return nil, false } diff --git a/internal/graph/build_test.go b/internal/graph/build_test.go index 8474d0c..0ff5723 100644 --- a/internal/graph/build_test.go +++ b/internal/graph/build_test.go @@ -3,6 +3,7 @@ package graph_test import ( "context" "errors" + "log" "net" "testing" @@ -47,7 +48,9 @@ type testBuilder struct { Edges int } -func (tb *testBuilder) AddNode(_ *node.Node) error { +func (tb *testBuilder) AddNode(n *node.Node) error { + log.Printf("%+v", n) + if tb.Err != nil { return tb.Err } @@ -57,7 +60,9 @@ func (tb *testBuilder) AddNode(_ *node.Node) error { return nil } -func (tb *testBuilder) AddEdge(_ *node.Edge) { +func (tb *testBuilder) AddEdge(e *node.Edge) { + + log.Printf("%+v", e) tb.Edges++ } @@ -136,21 +141,21 @@ func testClientWithEnv() graph.ContainerClient { // node 1 cli.Data[0].AddMany([]*graph.Connection{ - {SrcPort: 1, Proto: graph.TCP}, // listen 1 + {SrcPort: 1, Proto: graph.TCP, Listen: true}, // listen 1 {DstIP: node2, SrcPort: 10, DstPort: 2, Proto: graph.TCP}, // connected to node2:2 {DstIP: external, SrcPort: 10, DstPort: 1, Proto: graph.TCP}, // connected to external:1 }) // node 2 cli.Data[1].AddMany([]*graph.Connection{ - {SrcPort: 2, Proto: graph.TCP}, // listen 2 + {SrcPort: 2, Proto: graph.TCP, Listen: true}, // listen 2 {DstIP: node3, SrcPort: 10, DstPort: 3, Proto: graph.TCP}, // connected to node3:3 {DstIP: external, SrcPort: 10, DstPort: 2, Proto: graph.TCP}, // connected to external:2 }) // node 3 cli.Data[2].AddMany([]*graph.Connection{ - {SrcPort: 3, Proto: graph.TCP}, // listen 3 + {SrcPort: 3, Proto: graph.TCP, Listen: true}, // listen 3 {DstIP: node1, SrcPort: 10, DstPort: 1, Proto: graph.TCP}, // connected to node1:1 {DstIP: node2, SrcPort: 122, DstPort: 22, Proto: graph.TCP}, // connected to node2:22 {DstIP: external, SrcPort: 10, DstPort: 3, Proto: graph.TCP}, // connected to external:3 @@ -235,8 +240,9 @@ func TestBuildNoNodes(t *testing.T) { cli := testClientWithEnv() cfg := &graph.Config{ - Follow: flw, - Proto: graph.ALL, + Follow: flw, + OnlyLocal: true, + Proto: graph.ALL, } if err := graph.Build(cfg, cli); err == nil { diff --git a/internal/graph/connection_test.go b/internal/graph/connection_test.go index 7e5cee0..4ca3e7c 100644 --- a/internal/graph/connection_test.go +++ b/internal/graph/connection_test.go @@ -11,13 +11,13 @@ func TestConnectionIsListener(t *testing.T) { c := graph.Connection{} - if !c.IsListener() { + if c.IsListener() { t.Fail() } - c.DstPort = 1 + c.Listen = true - if c.IsListener() { + if !c.IsListener() { t.Fail() } } diff --git a/internal/graph/conngroup_test.go b/internal/graph/conngroup_test.go index d70eb8c..60de608 100644 --- a/internal/graph/conngroup_test.go +++ b/internal/graph/conngroup_test.go @@ -75,12 +75,12 @@ func TestConnGroupSort(t *testing.T) { cg := &graph.ConnGroup{} - cg.AddListener(&graph.Connection{Proto: graph.TCP, SrcPort: 1}) - cg.AddListener(&graph.Connection{Proto: graph.UDP, SrcPort: 1}) - cg.AddListener(&graph.Connection{Proto: graph.TCP, SrcPort: 2}) - cg.AddListener(&graph.Connection{Proto: graph.UDP, SrcPort: 2}) - cg.AddListener(&graph.Connection{Proto: graph.TCP, SrcPort: 3}) - cg.AddListener(&graph.Connection{Proto: graph.UDP, SrcPort: 3}) + cg.AddListener(&graph.Connection{Proto: graph.TCP, SrcPort: 1, Listen: true}) + cg.AddListener(&graph.Connection{Proto: graph.UDP, SrcPort: 1, Listen: true}) + cg.AddListener(&graph.Connection{Proto: graph.TCP, SrcPort: 2, Listen: true}) + cg.AddListener(&graph.Connection{Proto: graph.UDP, SrcPort: 2, Listen: true}) + cg.AddListener(&graph.Connection{Proto: graph.TCP, SrcPort: 3, Listen: true}) + cg.AddListener(&graph.Connection{Proto: graph.UDP, SrcPort: 3, Listen: true}) cg.AddOutbound(&graph.Connection{Proto: graph.TCP, SrcPort: 2, DstPort: 1}) cg.AddOutbound(&graph.Connection{Proto: graph.UDP, SrcPort: 3, DstPort: 1}) diff --git a/internal/graph/container_test.go b/internal/graph/container_test.go index b0aa62a..9551b77 100644 --- a/internal/graph/container_test.go +++ b/internal/graph/container_test.go @@ -14,9 +14,9 @@ var testCases = []struct { }{ { Conns: []*graph.Connection{ - {SrcPort: 1, DstPort: 2}, // inbound - {SrcPort: 1, DstPort: 0}, // listener - {SrcPort: 2, DstPort: 1}, // outbound + {SrcPort: 1, DstPort: 2}, // inbound + {SrcPort: 1, Listen: true}, // listener + {SrcPort: 2, DstPort: 1}, // outbound }, Listeners: 1, Outbounds: 1, @@ -33,8 +33,8 @@ var testCases = []struct { }, { Conns: []*graph.Connection{ - {SrcPort: 1, DstPort: 2}, // inbound - {SrcPort: 1, DstPort: 0}, // listener + {SrcPort: 1, DstPort: 2}, // inbound + {SrcPort: 1, Listen: true}, // listener }, Listeners: 1, Outbounds: 0, diff --git a/internal/graph/netstat.go b/internal/graph/netstat.go index ebac2a7..dee5e74 100644 --- a/internal/graph/netstat.go +++ b/internal/graph/netstat.go @@ -25,14 +25,6 @@ func netstatArgFor(p NetProto) (rv string) { rv += "u" } - /* - no unix sockets from netstat - it gives only internal pids - - if p.Has(UNIX) { - rv += "x" - } - */ - return rv } @@ -104,10 +96,14 @@ func parseConnection(s string) (conn *Connection, ok bool) { nProcField = 6 switch parts[5] { - case stateListen, stateEstablished: + case stateListen: + conn.Listen = true + case stateEstablished: default: // skip all other states return nil, false } + } else { + conn.Listen = (conn.DstPort > 0 && conn.SrcPort < conn.DstPort) || conn.DstPort == 0 } if conn.Process, ok = splitName(parts[nProcField]); !ok { diff --git a/internal/graph/netstat_test.go b/internal/graph/netstat_test.go index 395c3de..dfdfacb 100644 --- a/internal/graph/netstat_test.go +++ b/internal/graph/netstat_test.go @@ -32,7 +32,8 @@ tcp6 0 0 :::1234 :::* LISTEN 1/foo tcp6 0 0 127.0.0.1:6501 127.0.0.1:43706 ESTABLISHED 2/bar tcp 1 0 172.20.4.209:43634 172.20.4.129:53 ESTABLISHED bar/ tcp 1 0 172.20.4.209:43634 172.20.4.129:53 ESTABLISHED bar -udp 0 0 127.0.0.11:56688 0.0.0.0:* - +udp 0 0 127.0.0.1:56688 10.10.0.1:54 11/ntpd +udp 0 0 0.0.0.0:455 0.0.0.0:* 10/ntpd bgp 1 1 127.0.0.11:56689 0.0.0.0:* LISTEN 1/foo tcp 0 0 invalid 172.20.4.198:3306 ESTABLISHED - tcp 0 0 172.20.4.198:3306 invalid ESTABLISHED - @@ -56,7 +57,7 @@ some garbage t.Fatal(err) } - if con.ConnectionsCount() != 5 { + if con.ConnectionsCount() != 7 { t.Log("total:", con.ConnectionsCount()) t.Fail() } @@ -70,7 +71,7 @@ some garbage noutbound++ }) - if nlisten != 4 || noutbound != 1 { + if nlisten != 5 || noutbound != 2 { t.Log("listen/outbound:", nlisten, noutbound) t.Fail() } From bf5f38ab7480ca265731d8193a7c397a9d0ab4eb Mon Sep 17 00:00:00 2001 From: Shevchenko Alexey Date: Thu, 4 Jul 2024 10:51:24 +0300 Subject: [PATCH 10/16] more tests fixed --- internal/graph/build_test.go | 9 +-- internal/graph/load_test.go | 90 ++++++++++++++--------------- internal/node/port.go | 30 ++++++++++ internal/structurizr/system_test.go | 2 +- 4 files changed, 78 insertions(+), 53 deletions(-) diff --git a/internal/graph/build_test.go b/internal/graph/build_test.go index 0ff5723..1de6310 100644 --- a/internal/graph/build_test.go +++ b/internal/graph/build_test.go @@ -3,7 +3,6 @@ package graph_test import ( "context" "errors" - "log" "net" "testing" @@ -48,9 +47,7 @@ type testBuilder struct { Edges int } -func (tb *testBuilder) AddNode(n *node.Node) error { - log.Printf("%+v", n) - +func (tb *testBuilder) AddNode(_ *node.Node) error { if tb.Err != nil { return tb.Err } @@ -60,9 +57,7 @@ func (tb *testBuilder) AddNode(n *node.Node) error { return nil } -func (tb *testBuilder) AddEdge(e *node.Edge) { - - log.Printf("%+v", e) +func (tb *testBuilder) AddEdge(_ *node.Edge) { tb.Edges++ } diff --git a/internal/graph/load_test.go b/internal/graph/load_test.go index 20af584..ba714b8 100644 --- a/internal/graph/load_test.go +++ b/internal/graph/load_test.go @@ -47,8 +47,8 @@ func TestLoaderBuildError(t *testing.T) { "is_remote": false, "image": "test-image", "listen": {"foo":[ - {"kind": "tcp", "value": 1}, - {"kind": "udp", "value": 2} + {"kind": "tcp", "value": "1"}, + {"kind": "udp", "value": "2"} ]}, "connected": null }`) @@ -86,8 +86,8 @@ func TestLoaderSingle(t *testing.T) { "is_remote": false, "image": "test-image", "listen": {"foo": [ - {"kind": "tcp", "value": 1}, - {"kind": "udp", "value": 2} + {"kind": "tcp", "value": "1"}, + {"kind": "udp", "value": "2"} ]}, "connected": null }`) @@ -156,16 +156,16 @@ func TestLoaderEdges(t *testing.T) { buf := bytes.NewBufferString(`{ "name": "test1", "listen": {"foo": [ - {"kind": "tcp", "value": 1} + {"kind": "tcp", "value": "1"} ]}, - "connected": {"test2":[{"src": "foo", "dst": "bar", "port": {"kind": "tcp", "value": 2}}]} + "connected": {"test2":[{"src": "foo", "dst": "bar", "port": {"kind": "tcp", "value": "2"}}]} } { "name": "test2", "listen": {"bar": [ - {"kind": "tcp", "value": 2} + {"kind": "tcp", "value": "2"} ]}, - "connected": {"test1":[{"src": "bar", "dst": "foo", "port": {"kind": "tcp", "value": 1}}]} + "connected": {"test1":[{"src": "bar", "dst": "foo", "port": {"kind": "tcp", "value": "1"}}]} }`) if err := ldr.FromReader(buf); err != nil { @@ -199,9 +199,9 @@ func TestLoaderSeveral(t *testing.T) { "name": "test1", "networks": ["foo"], "listen": {"foo": [ - {"kind": "tcp", "value": 1} + {"kind": "tcp", "value": "1"} ]}, - "connected": {"test2":[{"src": "foo", "dst": "bar", "port": {"kind": "tcp", "value": 2}}]} + "connected": {"test2":[{"src": "foo", "dst": "bar", "port": {"kind": "tcp", "value": "2"}}]} }`)); err != nil { t.Fatal("load1 err=", err) } @@ -210,9 +210,9 @@ func TestLoaderSeveral(t *testing.T) { "name": "test2", "networks": ["foo"], "listen": {"bar": [ - {"kind": "tcp", "value": 2} + {"kind": "tcp", "value": "2"} ]}, - "connected": {"test1":[{"src": "bar", "dst": "foo", "port": {"kind": "tcp", "value": 1}}]} + "connected": {"test1":[{"src": "bar", "dst": "foo", "port": {"kind": "tcp", "value": "1"}}]} }`)); err != nil { t.Fatal("load2 err=", err) } @@ -244,17 +244,17 @@ func TestLoaderEdgesProto(t *testing.T) { "name": "test1", "networks": ["foo"], "listen": {"foo": [ - {"kind": "udp", "value": 1} + {"kind": "udp", "value": "1"} ]}, - "connected": {"test2":[{"src": "foo", "dst": "bar", "port": {"kind": "tcp", "value": 2}}]} + "connected": {"test2":[{"src": "foo", "dst": "bar", "port": {"kind": "tcp", "value": "2"}}]} } { "name": "test2", "networks": ["foo"], "listen": {"bar": [ - {"kind": "tcp", "value": 2} + {"kind": "tcp", "value": "2"} ]}, - "connected": {"test1":[{"src": "bar", "dst": "foo", "port": {"kind": "udp", "value": 1}}]} + "connected": {"test1":[{"src": "bar", "dst": "foo", "port": {"kind": "udp", "value": "1"}}]} }`) if err := ldr.FromReader(buf); err != nil { @@ -292,17 +292,17 @@ func TestLoaderEdgesFollowNone(t *testing.T) { "name": "test1", "networks": ["foo"], "listen": {"foo": [ - {"kind": "udp", "value": 1} + {"kind": "udp", "value": "1"} ]}, - "connected": {"test2":[{"src": "foo", "dst": "bar", "port": {"kind": "tcp", "value": 2}}]} + "connected": {"test2":[{"src": "foo", "dst": "bar", "port": {"kind": "tcp", "value": "2"}}]} } { "name": "test2", "networks": ["foo"], "listen": {"bar":[ - {"kind": "tcp", "value": 2} + {"kind": "tcp", "value": "2"} ]}, - "connected": {"test1":[{"src": "bar", "dst": "foo", "port": {"kind": "udp", "value": 1}}]} + "connected": {"test1":[{"src": "bar", "dst": "foo", "port": {"kind": "udp", "value": "1"}}]} }`) if err := ldr.FromReader(buf); err != nil { @@ -325,32 +325,32 @@ func TestLoaderEdgesFollowOne(t *testing.T) { "name": "test1", "networks": ["foo"], "listen": {"foo": [ - {"kind": "udp", "value": 1} + {"kind": "udp", "value": "1"} ]}, "connected": { - "test2":[{"src": "foo", "dst": "bar", "port": {"kind": "tcp", "value": 2}}], - "test3":[{"src": "foo", "dst": "baz", "port": {"kind": "udp", "value": 3}}] + "test2":[{"src": "foo", "dst": "bar", "port": {"kind": "tcp", "value": "2"}}], + "test3":[{"src": "foo", "dst": "baz", "port": {"kind": "udp", "value": "3"}}] } } { "name": "test2", "networks": ["foo"], "listen": {"bar": [ - {"kind": "tcp", "value": 2} + {"kind": "tcp", "value": "2"} ]}, "connected": { - "test1":[{"src": "bar", "dst": "foo", "port": {"kind": "udp", "value": 1}}] + "test1":[{"src": "bar", "dst": "foo", "port": {"kind": "udp", "value": "1"}}] } } { "name": "test3", "networks": ["foo"], "listen": {"bar":[ - {"kind": "tcp", "value": 3}, - {"kind": "udp", "value": 3} + {"kind": "tcp", "value": "3"}, + {"kind": "udp", "value": "3"} ]}, "connected": { - "test1":[{"src": "baz", "dst": "foo", "port": {"kind": "udp", "value": 1}}] + "test1":[{"src": "baz", "dst": "foo", "port": {"kind": "udp", "value": "1"}}] } }`) @@ -389,18 +389,18 @@ func TestLoaderLocal(t *testing.T) { "name": "test1", "networks": ["foo"], "listen": {"foo": [ - {"kind": "udp", "value": 1} + {"kind": "udp", "value": "1"} ]}, - "connected": {"test2":[{"src": "foo", "dst": "bar", "port": {"kind": "tcp", "value": 2}}]} + "connected": {"test2":[{"src": "foo", "dst": "bar", "port": {"kind": "tcp", "value": "2"}}]} } { "name": "test2", "is_external": true, "networks": ["foo"], "listen": {"bar": [ - {"kind": "tcp", "value": 2} + {"kind": "tcp", "value": "2"} ]}, - "connected": {"test1":[{"src": "bar", "dst": "foo", "port": {"kind": "udp", "value": 1}}]} + "connected": {"test1":[{"src": "bar", "dst": "foo", "port": {"kind": "udp", "value": "1"}}]} } `) @@ -437,18 +437,18 @@ func TestLoaderMeta(t *testing.T) { "tags": ["test"], "networks": ["foo"], "listen": {"foo": [ - {"kind": "udp", "value": 1} + {"kind": "udp", "value": "1"} ]}, - "connected": {"test2":[{"src": "foo", "dst": "bar", "port": {"kind": "tcp", "value": 2}}]} + "connected": {"test2":[{"src": "foo", "dst": "bar", "port": {"kind": "tcp", "value": "2"}}]} } { "name": "test2", "is_external": true, "networks": ["foo"], "listen": {"bar": [ - {"kind": "tcp", "value": 2} + {"kind": "tcp", "value": "2"} ]}, - "connected": {"test1":[{"src": "bar", "dst": "foo", "port": {"kind": "udp", "value": 1}}]} + "connected": {"test1":[{"src": "bar", "dst": "foo", "port": {"kind": "udp", "value": "1"}}]} }`) bldr := &testBuilder{} @@ -485,18 +485,18 @@ func TestLoaderFull(t *testing.T) { "volumes": [{"type": "bind", "src": "", "dst": ""}], "networks": ["foo"], "listen": {"foo": [ - {"kind": "udp", "value": 1} + {"kind": "udp", "value": "1"} ]}, - "connected": {"test2":[{"src": "foo", "dst": "bar", "port": {"kind": "tcp", "value": 2}}]} + "connected": {"test2":[{"src": "foo", "dst": "bar", "port": {"kind": "tcp", "value": "2"}}]} } { "name": "test2", "is_external": true, "networks": ["foo"], "listen": {"bar": [ - {"kind": "tcp", "value": 2} + {"kind": "tcp", "value": "2"} ]}, - "connected": {"test1":[{"src": "bar", "dst": "foo", "port": {"kind": "udp", "value": 1}}]} + "connected": {"test1":[{"src": "bar", "dst": "foo", "port": {"kind": "udp", "value": "1"}}]} }`) bldr := &testBuilder{} @@ -530,11 +530,11 @@ func TestLoaderLoops(t *testing.T) { "name": "test1", "networks": ["foo"], "listen": {"foo": [ - {"kind": "udp", "value": 1} + {"kind": "udp", "value": "1"} ]}, "connected": { - "test2":[{"src": "foo", "dst": "bar", "port": {"kind": "tcp", "value": 2}}], - "test1":[{"src": "foo", "dst": "foo", "port": {"kind": "udp", "value": 1}}] + "test2":[{"src": "foo", "dst": "bar", "port": {"kind": "tcp", "value": "2"}}], + "test1":[{"src": "foo", "dst": "foo", "port": {"kind": "udp", "value": "1"}}] } } { @@ -542,9 +542,9 @@ func TestLoaderLoops(t *testing.T) { "is_external": true, "networks": ["foo"], "listen": {"bar": [ - {"kind": "tcp", "value": 2} + {"kind": "tcp", "value": "2"} ]}, - "connected": {"test1":[{"src": "bar", "dst": "foo", "port": {"kind": "udp", "value": 1}}]} + "connected": {"test1":[{"src": "bar", "dst": "foo", "port": {"kind": "udp", "value": "1"}}]} }` bldr := &testBuilder{} diff --git a/internal/node/port.go b/internal/node/port.go index 16b0b24..d325f1a 100644 --- a/internal/node/port.go +++ b/internal/node/port.go @@ -1,5 +1,17 @@ package node +import ( + "encoding/json" + "fmt" + "strconv" +) + +type portJSON struct { + Kind string `json:"kind"` + Value string `json:"value"` + Local bool `json:"local"` +} + type Port struct { Kind string `json:"kind"` Value string `json:"value"` @@ -15,3 +27,21 @@ func (p *Port) Equal(v *Port) (yes bool) { return p.Kind == v.Kind && p.Value == v.Value } + +func (p *Port) UnmarshalJSON(b []byte) (err error) { + var v portJSON + + if err = json.Unmarshal(b, &v); err != nil { + return fmt.Errorf("unmarshal json: %w", err) + } + + if p.Number, err = strconv.Atoi(v.Value); err != nil { + return fmt.Errorf("invalid port: '%s' atoi: %w", v.Value, err) + } + + p.Kind = v.Kind + p.Value = v.Value + p.Local = v.Local + + return nil +} diff --git a/internal/structurizr/system_test.go b/internal/structurizr/system_test.go index 2b89e0d..2e8cdce 100644 --- a/internal/structurizr/system_test.go +++ b/internal/structurizr/system_test.go @@ -43,7 +43,7 @@ func TestSystemRelation(t *testing.T) { s.WriteRelations(&b, 0) - if strings.Count(b.String(), "name1 -> name2") != 1 { + if strings.Count(b.String(), "id1 -> id2") != 1 { t.Fail() } } From 51f1fac86f9f16077e94b4b5f366560b1d1bb957 Mon Sep 17 00:00:00 2001 From: Shevchenko Alexey Date: Fri, 5 Jul 2024 01:33:13 +0300 Subject: [PATCH 11/16] core tests pass --- internal/client/docker.go | 4 ++-- internal/client/docker_test.go | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/internal/client/docker.go b/internal/client/docker.go index f250862..161ecec 100644 --- a/internal/client/docker.go +++ b/internal/client/docker.go @@ -268,8 +268,8 @@ func (d *Docker) connections( }) case LinuxNsenter: err = d.processesContainer(ctx, cid, func(pid int, _ string) (err error) { - if err = d.opt.Nsenter(pid, proto, cb); err != nil { - return fmt.Errorf("pid: %d: %w", pid, err) + if err := d.opt.Nsenter(pid, proto, cb); err != nil { + return err } return nil diff --git a/internal/client/docker_test.go b/internal/client/docker_test.go index d626db4..0b5935e 100644 --- a/internal/client/docker_test.go +++ b/internal/client/docker_test.go @@ -808,15 +808,15 @@ func TestDockerClientNsEnterInspectError(t *testing.T) { t.Fatal("client:", err) } - _, err = cli.Containers( + rv, _ := cli.Containers( context.Background(), - graph.TCP|graph.UDP, + graph.ALL, false, nil, voidProgress, ) - if !errors.Is(err, testErr) { + if len(rv) > 0 { t.Fail() } } @@ -888,7 +888,7 @@ func TestDockerClientNsEnterConnectionsError(t *testing.T) { t.Fatal("client:", err) } - _, err = cli.Containers( + rv, _ := cli.Containers( context.Background(), graph.TCP|graph.UDP, false, @@ -896,7 +896,7 @@ func TestDockerClientNsEnterConnectionsError(t *testing.T) { voidProgress, ) - if !errors.Is(err, testErr) { + if len(rv) > 0 { t.Fail() } } @@ -1129,9 +1129,9 @@ func TestDockerClientNsEnterLocal(t *testing.T) { nod := net.ParseIP("1.1.1.1") rem := net.ParseIP("2.2.2.2") - fn(1, &graph.Connection{Process: "1", SrcPort: 1, DstPort: 0, SrcIP: nod, Proto: graph.TCP}) + fn(1, &graph.Connection{Process: "1", SrcPort: 1, Listen: true, SrcIP: nod, Proto: graph.TCP}) fn(1, &graph.Connection{Process: "1", SrcPort: 10, DstPort: 2, SrcIP: nod, DstIP: rem, Proto: graph.TCP}) - fn(1, &graph.Connection{Process: "1", SrcPort: 5, SrcIP: loc, Proto: graph.TCP}) + fn(1, &graph.Connection{Process: "1", SrcPort: 5, Listen: true, SrcIP: loc, Proto: graph.TCP}) return nil } From e7da421961fac33ac5148a3eb761e16bc5018072 Mon Sep 17 00:00:00 2001 From: Shevchenko Alexey Date: Fri, 5 Jul 2024 01:48:55 +0300 Subject: [PATCH 12/16] all green --- internal/builder/json_test.go | 8 +-- internal/builder/stat_test.go | 58 +++++++++---------- internal/builder/testdata/compose-yaml.golden | 8 +-- internal/builder/testdata/csv.golden | 12 ++-- internal/builder/testdata/graphviz-dot.golden | 4 +- internal/builder/testdata/plant-uml.golden | 48 +++++++-------- .../builder/testdata/structurizr-dsl.golden | 22 +++---- internal/builder/testdata/text-tree.golden | 14 ++--- 8 files changed, 87 insertions(+), 87 deletions(-) diff --git a/internal/builder/json_test.go b/internal/builder/json_test.go index 04083df..92d6f64 100644 --- a/internal/builder/json_test.go +++ b/internal/builder/json_test.go @@ -100,18 +100,18 @@ func TestJSONAddEdge(t *testing.T) { "is_external": false, "networks": ["test"], "listen": {"foo":[ - {"kind": "udp", "value": 1} + {"kind": "udp", "value": "1"} ]}, - "connected": {"test2":[{"src": "foo", "dst": "bar", "port": {"kind": "tcp", "value": 2}}]} + "connected": {"test2":[{"src": "foo", "dst": "bar", "port": {"kind": "tcp", "value": "2"}}]} } { "name": "test2", "is_external": false, "networks": ["test"], "listen": {"bar": [ - {"kind": "tcp", "value": 2} + {"kind": "tcp", "value": "2"} ]}, - "connected": {"test1":[{"src": "bar", "dst": "foo", "port": {"kind": "udp", "value": 1}}]} + "connected": {"test1":[{"src": "bar", "dst": "foo", "port": {"kind": "udp", "value": "1"}}]} }` bldr := builder.NewJSON() diff --git a/internal/builder/stat_test.go b/internal/builder/stat_test.go index ba44588..9afb30f 100644 --- a/internal/builder/stat_test.go +++ b/internal/builder/stat_test.go @@ -35,16 +35,16 @@ func TestStat(t *testing.T) { "is_external": false, "networks": ["test"], "listen": {"1": [ - {"kind": "tcp", "value": 1}, - {"kind": "udp", "value": 1} + {"kind": "tcp", "value": "1"}, + {"kind": "udp", "value": "1"} ]}, "connected": { "test2":[ - {"src": "1", "dst": "2", "port": {"kind": "tcp", "value": 2}}, - {"src": "1", "dst": "2", "port": {"kind": "udp", "value": 2}} + {"src": "1", "dst": "2", "port": {"kind": "tcp", "value": "2"}}, + {"src": "1", "dst": "2", "port": {"kind": "udp", "value": "2"}} ], "test3":[ - {"src": "1", "dst": "3", "port": {"kind": "tcp", "value": 3}} + {"src": "1", "dst": "3", "port": {"kind": "tcp", "value": "3"}} ] } } @@ -53,16 +53,16 @@ func TestStat(t *testing.T) { "is_external": false, "networks": ["test"], "listen": {"2":[ - {"kind": "tcp", "value": 2}, - {"kind": "udp", "value": 2} + {"kind": "tcp", "value": "2"}, + {"kind": "udp", "value": "2"} ]}, "connected": { "test1":[ - {"src": "2", "dst": "1", "port": {"kind": "tcp", "value": 1}}, - {"src": "2", "dst": "1", "port": {"kind": "udp", "value": 1}} + {"src": "2", "dst": "1", "port": {"kind": "tcp", "value": "1"}}, + {"src": "2", "dst": "1", "port": {"kind": "udp", "value": "1"}} ], "test3": [ - {"src": "2", "dst": "3", "port": {"kind": "udp", "value": 3}} + {"src": "2", "dst": "3", "port": {"kind": "udp", "value": "3"}} ] } } @@ -71,8 +71,8 @@ func TestStat(t *testing.T) { "is_external": true, "networks": ["test"], "listen": {"3":[ - {"kind": "tcp", "value": 3}, - {"kind": "udp", "value": 3} + {"kind": "tcp", "value": "3"}, + {"kind": "udp", "value": "3"} ]}, "connected": {} }` @@ -106,23 +106,23 @@ func TestStat(t *testing.T) { t.Fail() } - if strings.Count(res, "1/tcp") != 1 { + if strings.Count(res, "tcp:1") != 1 { t.Fail() } - if strings.Count(res, "2/tcp") != 1 { + if strings.Count(res, "tcp:2") != 1 { t.Fail() } - if strings.Count(res, "1/udp") != 1 { + if strings.Count(res, "udp:1") != 1 { t.Fail() } - if strings.Count(res, "2/udp") != 1 { + if strings.Count(res, "udp:2") != 1 { t.Fail() } - if strings.Contains(res, "3/tcp") || strings.Contains(res, "3/udp") { + if strings.Contains(res, "tcp:3") || strings.Contains(res, "udp:3") { t.Fail() } } @@ -130,8 +130,8 @@ func TestStat(t *testing.T) { func TestStatCluster(t *testing.T) { t.Parallel() - const rules = `[{"name": "foo", "if": "node.Listen.Has('1/tcp')"}, -{"name": "bar", "if": "node.Listen.HasAny('2/tcp')"}]` + const rules = `[{"name": "foo", "if": "node.Listen.Has('tcp:1')"}, +{"name": "bar", "if": "node.Listen.HasAny('tcp:2')"}]` cb := cluster.NewRules(builder.NewStat(), nil) @@ -152,16 +152,16 @@ func TestStatCluster(t *testing.T) { "is_external": false, "networks": ["test"], "listen": {"1": [ - {"kind": "tcp", "value": 1}, - {"kind": "udp", "value": 1} + {"kind": "tcp", "value": "1"}, + {"kind": "udp", "value": "1"} ]}, "connected": { "test2":[ - {"src": "1", "dst": "2", "port": {"kind": "tcp", "value": 2}}, - {"src": "1", "dst": "2", "port": {"kind": "udp", "value": 2}} + {"src": "1", "dst": "2", "port": {"kind": "tcp", "value": "2"}}, + {"src": "1", "dst": "2", "port": {"kind": "udp", "value": "2"}} ], "test3":[ - {"src": "1", "dst": "3", "port": {"kind": "tcp", "value": 3}} + {"src": "1", "dst": "3", "port": {"kind": "tcp", "value": "3"}} ] } } @@ -170,16 +170,16 @@ func TestStatCluster(t *testing.T) { "is_external": false, "networks": ["test"], "listen": {"2":[ - {"kind": "tcp", "value": 2}, - {"kind": "udp", "value": 2} + {"kind": "tcp", "value": "2"}, + {"kind": "udp", "value": "2"} ]}, "connected": { "test1":[ - {"src": "2", "dst": "1", "port": {"kind": "tcp", "value": 1}}, - {"src": "2", "dst": "1", "port": {"kind": "udp", "value": 1}} + {"src": "2", "dst": "1", "port": {"kind": "tcp", "value": "1"}}, + {"src": "2", "dst": "1", "port": {"kind": "udp", "value": "1"}} ], "test3": [ - {"src": "2", "dst": "3", "port": {"kind": "udp", "value": 3}} + {"src": "2", "dst": "3", "port": {"kind": "udp", "value": "3"}} ] } }` diff --git a/internal/builder/testdata/compose-yaml.golden b/internal/builder/testdata/compose-yaml.golden index f5c2d33..c0082fd 100644 --- a/internal/builder/testdata/compose-yaml.golden +++ b/internal/builder/testdata/compose-yaml.golden @@ -2,8 +2,8 @@ services: "1": image: node-image expose: - - "1/tcp" - - "2/tcp" + - "tcp:1" + - "tcp:2" links: - "2" volumes: @@ -18,8 +18,8 @@ services: "2": image: node-image expose: - - "1/tcp" - - "2/tcp" + - "tcp:1" + - "tcp:2" links: - "1" volumes: diff --git a/internal/builder/testdata/csv.golden b/internal/builder/testdata/csv.golden index 0931f4e..3facc86 100644 --- a/internal/builder/testdata/csv.golden +++ b/internal/builder/testdata/csv.golden @@ -1,9 +1,9 @@ service,listen,outbounds -1,"1/tcp -2/tcp","2: 2/tcp -3: 3/tcp +1,"tcp:1 +tcp:2","2: tcp:2 +3: tcp:3 " -2,2/tcp,"1: 1/tcp; 2/tcp; 3/tcp -3: 3/tcp +2,tcp:2,"1: tcp:1; tcp:2; tcp:3 +3: tcp:3 " -3,3/tcp, +3,tcp:3, diff --git a/internal/builder/testdata/graphviz-dot.golden b/internal/builder/testdata/graphviz-dot.golden index 16090e1..0a514a5 100644 --- a/internal/builder/testdata/graphviz-dot.golden +++ b/internal/builder/testdata/graphviz-dot.golden @@ -4,7 +4,7 @@ digraph { n1[color="black",label="1"]; n2[color="black",label="2"]; n3[color="black",label="3"]; - n4->n1[label="1/tcp,2/tcp,3/tcp,2/tcp,3/tcp"]; - n1->n2[label="1/tcp,1/tcp,2/tcp,2/tcp"]; + n4->n1[label="tcp:1,tcp:2,tcp:3,tcp:2,tcp:3"]; + n1->n2[label="tcp:1,tcp:1,tcp:2,tcp:2"]; } diff --git a/internal/builder/testdata/plant-uml.golden b/internal/builder/testdata/plant-uml.golden index e3ca132..e85f5cd 100644 --- a/internal/builder/testdata/plant-uml.golden +++ b/internal/builder/testdata/plant-uml.golden @@ -4,47 +4,47 @@ skinparam nodesep 5 skinparam ranksep 5 component "1" as id_b8d1bdeb90c390c3f601 { component "" as id_b8d1bdeb90c390c3f601 { - portin "1/tcp" as id_d39ac7cbd4ded393cd01 - portin "2/tcp" as id_f692a2faacfbd4a6b501 - portin "5/tcp" as id_ffbfeddcc8fcb4e09901 + portin "tcp:1" as id_baafedd79f8dae92d601 + portin "tcp:2" as id_87acedd79fedad92d601 + portin "tcp:5" as id_eea1edd79f8dad92d601 } - portin "1/tcp" as id_d39ac7cbd4ded393cd01 - portin "2/tcp" as id_f692a2faacfbd4a6b501 + portin "tcp:1" as id_baafedd79f8dae92d601 + portin "tcp:2" as id_87acedd79fedad92d601 } component "2" as id_d1dbbdeb90a391c3f601 { component "" as id_d1dbbdeb90a391c3f601 { - portin "1/tcp" as id_988bfed49acfb0f020 - portin "2/tcp" as id_d9c5d0b2bdec95fb60 + portin "tcp:1" as id_a9bca8f08cc3a2cf18 + portin "tcp:2" as id_90b2a8f08ce3a1cf18 } - portin "1/tcp" as id_988bfed49acfb0f020 - portin "2/tcp" as id_d9c5d0b2bdec95fb60 + portin "tcp:1" as id_a9bca8f08cc3a2cf18 + portin "tcp:2" as id_90b2a8f08ce3a1cf18 } component "3" as id_e2bc86b0c8c9ebb1af01 { component "" as id_e2bc86b0c8c9ebb1af01 { - portin "1/tcp" as id_a9c28885c4aedcb323 - portin "2/tcp" as id_a89eb990a1ceb69b8e01 + portin "tcp:1" as id_9885fdc88aa7fef313 + portin "tcp:2" as id_b18ffdc88a87fff313 } - portin "1/tcp" as id_a9c28885c4aedcb323 - portin "2/tcp" as id_a89eb990a1ceb69b8e01 + portin "tcp:1" as id_9885fdc88aa7fef313 + portin "tcp:2" as id_b18ffdc88a87fff313 } cloud "Externals" as ext { component "ext2" as id_a7fadbdd96b2cbabd101 { - portin "443/tcp" as id_b494f7ce9df8ea8aad01 + portin "tcp:443" as id_e1dee4c2d0c6cdbcfa01 } } component "ext2" as id_a7fadbdd96b2cbabd101 { - portin "443/tcp" as id_b494f7ce9df8ea8aad01 + portin "tcp:443" as id_e1dee4c2d0c6cdbcfa01 } } } -id_d39ac7cbd4ded393cd01 -> id_d39ac7cbd4ded393cd01 -id_f692a2faacfbd4a6b501 -> id_f692a2faacfbd4a6b501 -id_b8d1bdeb90c390c3f601 --> id_ffbfeddcc8fcb4e09901 -id_b8d1bdeb90c390c3f601 -----> id_d9c5d0b2bdec95fb60: 2/tcp -id_988bfed49acfb0f020 -> id_988bfed49acfb0f020 -id_d9c5d0b2bdec95fb60 -> id_d9c5d0b2bdec95fb60 -id_d1dbbdeb90a391c3f601 -----> id_d39ac7cbd4ded393cd01: 1/tcp -id_d1dbbdeb90a391c3f601 -----> id_f692a2faacfbd4a6b501: 2/tcp -id_d1dbbdeb90a391c3f601 -----> id_a1aca2e8affb90bf9701: 3/tcp +id_baafedd79f8dae92d601 -> id_baafedd79f8dae92d601 +id_87acedd79fedad92d601 -> id_87acedd79fedad92d601 +id_b8d1bdeb90c390c3f601 --> id_eea1edd79f8dad92d601 +id_b8d1bdeb90c390c3f601 -----> id_90b2a8f08ce3a1cf18: tcp:2 +id_a9bca8f08cc3a2cf18 -> id_a9bca8f08cc3a2cf18 +id_90b2a8f08ce3a1cf18 -> id_90b2a8f08ce3a1cf18 +id_d1dbbdeb90a391c3f601 -----> id_baafedd79f8dae92d601: tcp:1 +id_d1dbbdeb90a391c3f601 -----> id_87acedd79fedad92d601: tcp:2 +id_d1dbbdeb90a391c3f601 -----> id_d4a8edd79fcdad92d601: tcp:3 @enduml diff --git a/internal/builder/testdata/structurizr-dsl.golden b/internal/builder/testdata/structurizr-dsl.golden index 3784dab..4fa6c7d 100644 --- a/internal/builder/testdata/structurizr-dsl.golden +++ b/internal/builder/testdata/structurizr-dsl.golden @@ -7,9 +7,9 @@ workspace { node_3 = container "3" { description "info 3" technology "node-image" - tags "3,listen:1/tcp,listen:2/tcp,net:test-net" + tags "3,listen:tcp:1,listen:tcp:2,net:test-net" node_3_ = component "" { - tags "listen:1/tcp,listen:2/tcp" + tags "listen:tcp:1,listen:tcp:2" } } } @@ -20,35 +20,35 @@ workspace { docs-url \ repo-url" technology "node-image" - tags "1,listen:1/tcp,listen:2/tcp,net:test-net" + tags "1,listen:tcp:1,listen:tcp:2,net:test-net" node_1_ = component "" { - tags "listen:1/tcp,listen:2/tcp" + tags "listen:tcp:1,listen:tcp:2" } } node_2 = container "2" { description "info 2" technology "node-image" - tags "2,listen:1/tcp,listen:2/tcp,net:test-net" + tags "2,listen:tcp:1,listen:tcp:2,net:test-net" node_2_ = component "" { - tags "listen:1/tcp,listen:2/tcp" + tags "listen:tcp:1,listen:tcp:2" } } } c2 = softwareSystem "c2" { tags "ext2" ext2 = container "ext2" { - tags "external,listen:2/tcp" + tags "external,listen:tcp:2" ext2_ = component "" { - tags "listen:2/tcp" + tags "listen:tcp:2" } } } - c1 -> c2 "0/" { + c1 -> c2 ":" { } - c1 -> default "0/" { + c1 -> default ":" { } - default -> c2 "0/" { + default -> c2 ":" { } } diff --git a/internal/builder/testdata/text-tree.golden b/internal/builder/testdata/text-tree.golden index 53d84bc..66a7ea4 100644 --- a/internal/builder/testdata/text-tree.golden +++ b/internal/builder/testdata/text-tree.golden @@ -4,25 +4,25 @@ │ image: node-image │ tags: 1 │ cmd: 'echo 'test 1'' -│ listen: 1/tcp, 2/tcp +│ listen: tcp:1, tcp:2 │ networks: test-net │ │ -│ ├─ 2: 2/tcp -│ └─ 3: 3/tcp +│ ├─ 2: tcp:2 +│ └─ 3: tcp:3 │ ├─ 2 │ external: false │ tags: 2 │ cmd: 'echo 'test 2'' -│ listen: 2/tcp +│ listen: tcp:2 │ networks: test-net │ │ -│ ├─ 1: 1/tcp, 2/tcp, 3/tcp -│ └─ 3: 3/tcp +│ ├─ 1: tcp:1, tcp:2, tcp:3 +│ └─ 3: tcp:3 │ └─ 3 external: false tags: 3 cmd: 'echo 'test 3'' - listen: 3/tcp + listen: tcp:3 networks: test-net From d161d840941e3efc1cc36751064588a3378f2b0c Mon Sep 17 00:00:00 2001 From: Shevchenko Alexey Date: Fri, 5 Jul 2024 01:51:37 +0300 Subject: [PATCH 13/16] deps update --- go.mod | 12 ++++++------ go.sum | 24 ++++++++++++------------ 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/go.mod b/go.mod index a04afdb..03d16cc 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/s0rg/decompose go 1.22 require ( - github.com/docker/docker v27.0.0+incompatible + github.com/docker/docker v27.0.3+incompatible github.com/emicklei/dot v1.6.2 github.com/expr-lang/expr v1.16.9 github.com/prometheus/procfs v0.15.1 @@ -29,14 +29,14 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect github.com/pkg/errors v0.9.1 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 // indirect - go.opentelemetry.io/otel v1.27.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect + go.opentelemetry.io/otel v1.28.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0 // indirect - go.opentelemetry.io/otel/metric v1.27.0 // indirect + go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/otel/sdk v1.22.0 // indirect - go.opentelemetry.io/otel/trace v1.27.0 // indirect + go.opentelemetry.io/otel/trace v1.28.0 // indirect golang.org/x/net v0.24.0 // indirect - golang.org/x/sys v0.21.0 // indirect + golang.org/x/sys v0.22.0 // indirect golang.org/x/time v0.3.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gotest.tools/v3 v3.5.0 // indirect diff --git a/go.sum b/go.sum index a732f10..a2efe59 100644 --- a/go.sum +++ b/go.sum @@ -11,8 +11,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v27.0.0+incompatible h1:JRugTYuelmWlW0M3jakcIadDx2HUoUO6+Tf2C5jVfwA= -github.com/docker/docker v27.0.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v27.0.3+incompatible h1:aBGI9TeQ4MPlhquTQKq9XbK79rKFVwXNUAYz9aXyEBE= +github.com/docker/docker v27.0.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -74,20 +74,20 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 h1:9l89oX4ba9kHbBol3Xin3leYJ+252h0zszDtBwyKe2A= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0/go.mod h1:XLZfZboOJWHNKUv7eH0inh0E9VV6eWDFB/9yJyTLPp0= -go.opentelemetry.io/otel v1.27.0 h1:9BZoF3yMK/O1AafMiQTVu0YDj5Ea4hPhxCs7sGva+cg= -go.opentelemetry.io/otel v1.27.0/go.mod h1:DMpAK8fzYRzs+bi3rS5REupisuqTheUlSZJ1WnZaPAQ= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg= +go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= +go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0 h1:9M3+rhx7kZCIQQhQRYaZCdNu1V73tm4TvXs2ntl98C4= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0/go.mod h1:noq80iT8rrHP1SfybmPiRGc9dc5M8RPmGvtwo7Oo7tc= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0 h1:FyjCyI9jVEfqhUh2MoSkmolPjfh5fp2hnV0b0irxH4Q= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0/go.mod h1:hYwym2nDEeZfG/motx0p7L7J1N1vyzIThemQsb4g2qY= -go.opentelemetry.io/otel/metric v1.27.0 h1:hvj3vdEKyeCi4YaYfNjv2NUje8FqKqUY8IlF0FxV/ik= -go.opentelemetry.io/otel/metric v1.27.0/go.mod h1:mVFgmRlhljgBiuk/MP/oKylr4hs85GZAylncepAX/ak= +go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= +go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= go.opentelemetry.io/otel/sdk v1.22.0 h1:6coWHw9xw7EfClIC/+O31R8IY3/+EiRFHevmHafB2Gw= go.opentelemetry.io/otel/sdk v1.22.0/go.mod h1:iu7luyVGYovrRpe2fmj3CVKouQNdTOkxtLzPvPz1DOc= -go.opentelemetry.io/otel/trace v1.27.0 h1:IqYb813p7cmbHk0a5y6pD5JPakbVfftRXABGt5/Rscw= -go.opentelemetry.io/otel/trace v1.27.0/go.mod h1:6RiD1hkAprV4/q+yd2ln1HG9GoPx39SuvvstaLBl+l4= +go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= +go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -107,8 +107,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= From 46838a81d35ce90108fcc1f8f92d0929c3820d15 Mon Sep 17 00:00:00 2001 From: Shevchenko Alexey Date: Fri, 5 Jul 2024 02:02:23 +0300 Subject: [PATCH 14/16] readme and examples update --- README.md | 10 ++++++---- examples/cluster.json | 10 +++++----- examples/stream.json | 28 ++++++++++++++-------------- 3 files changed, 25 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 8992e06..a6cbaa4 100644 --- a/README.md +++ b/README.md @@ -98,6 +98,8 @@ decompose [flags] json file with metadata for enrichment -no-loops remove connection loops (node to itself) from output +-no-orphans + remove orphaned (not connected) nodes from output -out string output: filename or "-" for stdout (default "-") -proto string @@ -130,8 +132,8 @@ type Item struct { Labels map[string]string `json:"labels"` } `json:"container"` // container info Listen map[string][]{ - Kind string `json:"kind"` // tcp / udp - Value int `json:"value"` + Kind string `json:"kind"` // tcp / udp / unix + Value string `json:"value"` Local bool `json:"local"` // bound to loopback } `json:"listen"` // ports with process names Networks []string `json:"networks"` // network names @@ -163,7 +165,7 @@ Single node example with full info and metadata filled: "labels": {} }, "listen": {"foo": [ - {"kind": "tcp", "value": 80} + {"kind": "tcp", "value": "80"} ]}, "networks": ["test-net"], "tags": ["some"], @@ -181,7 +183,7 @@ Single node example with full info and metadata filled: ], "connected": { "bar-1": [ - {"src": "foo", "dst": "[remote]", "port": {"kind": "tcp", "value": 443}} + {"src": "foo", "dst": "[remote]", "port": {"kind": "tcp", "value": "443"}} ] } } diff --git a/examples/cluster.json b/examples/cluster.json index 50030b9..0c4934b 100644 --- a/examples/cluster.json +++ b/examples/cluster.json @@ -6,23 +6,23 @@ }, { "name": "ingress", - "if": "node.Listen.HasAny('80/tcp', '443/tcp')" + "if": "node.Listen.HasAny('tcp:80', 'tcp:443')" }, { "name": "backend", - "if": "node.Name startsWith 'back' && node.Listen.Has('8080/tcp', '8081/tcp')", + "if": "node.Name startsWith 'back' && node.Listen.Has('tcp:8080', 'tcp:8081')", "weight": 2 }, { "name": "store", - "if": "node.Listen.HasAny('3306/tcp', '5432/tcp')" + "if": "node.Listen.HasAny('tcp:3306', 'tcp:5432')" }, { "name": "redis", - "if": "node.Listen.Has('6379/tcp')" + "if": "node.Listen.Has('tcp:6379')" }, { "name": "queue", - "if": "node.Listen.HasAny('9092/tcp', '4222/tcp')" + "if": "node.Listen.HasAny('tcp:9092', 'tcp:4222')" } ] diff --git a/examples/stream.json b/examples/stream.json index d2174b3..cd40e9e 100644 --- a/examples/stream.json +++ b/examples/stream.json @@ -1,45 +1,45 @@ { "name": "nginx1", - "listen": {"nginx": [{"kind": "tcp", "value": 80}]}, + "listen": {"nginx": [{"kind": "tcp", "value": "80"}]}, "connected": { - "back1": [{"src": "nginx", "dst": "app", "port": {"kind": "tcp", "value": 8080}}], - "back2": [{"src": "nginx", "dst": "app", "port": {"kind": "tcp", "value": 8081}}] + "back1": [{"src": "nginx", "dst": "app", "port": {"kind": "tcp", "value": "8080"}}], + "back2": [{"src": "nginx", "dst": "app", "port": {"kind": "tcp", "value": "8081"}}] } } { "name": "db1", - "listen": {"postgres": [{"kind": "tcp", "value": 5432}]}, + "listen": {"postgres": [{"kind": "tcp", "value": "5432"}]}, "connected": {} } { "name": "back1", "listen": {"app": [ - {"kind": "tcp", "value": 8080}, - {"kind": "tcp", "value": 8081}, - {"kind": "tcp", "value": 9000} + {"kind": "tcp", "value": "8080"}, + {"kind": "tcp", "value": "8081"}, + {"kind": "tcp", "value": "9000"} ]}, "connected": { - "db1": [{"src": "app", "dst": "postgres", "port": {"kind": "tcp", "value": 5432}}] + "db1": [{"src": "app", "dst": "postgres", "port": {"kind": "tcp", "value": "5432"}}] } } { "name": "back2", "listen": {"app": [ - {"kind": "tcp", "value": 8080}, - {"kind": "tcp", "value": 8081} + {"kind": "tcp", "value": "8080"}, + {"kind": "tcp", "value": "8081"} ]}, "connected": { - "db1": [{"src": "app", "dst": "postgres", "port": {"kind": "tcp", "value": 5432}}], - "foo1": [{"src": "app", "dst": "[remote]", "port": {"kind": "tcp", "value": 9500}}] + "db1": [{"src": "app", "dst": "postgres", "port": {"kind": "tcp", "value": "5432"}}], + "foo1": [{"src": "app", "dst": "[remote]", "port": {"kind": "tcp", "value": "9500"}}] } } { "name": "foo1", "is_external": true, - "listen": {"[remote]": [{"kind": "tcp", "value": 9500}]}, + "listen": {"[remote]": [{"kind": "tcp", "value": "9500"}]}, "connected": { "back1": [ - {"src": "[remote]", "dst": "app", "port": {"kind": "tcp", "value": 9000}} + {"src": "[remote]", "dst": "app", "port": {"kind": "tcp", "value": "9000"}} ] } } From 188b94db806691b9decd9716ad09f82503aa9823 Mon Sep 17 00:00:00 2001 From: Shevchenko Alexey Date: Tue, 23 Jul 2024 21:49:07 +0300 Subject: [PATCH 15/16] deps up, more test coverage --- go.mod | 2 +- go.sum | 4 ++-- internal/client/inodes_test.go | 40 ++++++++++++++++++++++++++++++++++ internal/graph/orphans_test.go | 36 ++++++++++++++++++++++++++++++ 4 files changed, 79 insertions(+), 3 deletions(-) create mode 100644 internal/client/inodes_test.go create mode 100644 internal/graph/orphans_test.go diff --git a/go.mod b/go.mod index 03d16cc..ded7cf4 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/s0rg/decompose go 1.22 require ( - github.com/docker/docker v27.0.3+incompatible + github.com/docker/docker v27.1.0+incompatible github.com/emicklei/dot v1.6.2 github.com/expr-lang/expr v1.16.9 github.com/prometheus/procfs v0.15.1 diff --git a/go.sum b/go.sum index a2efe59..00bfcd5 100644 --- a/go.sum +++ b/go.sum @@ -11,8 +11,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v27.0.3+incompatible h1:aBGI9TeQ4MPlhquTQKq9XbK79rKFVwXNUAYz9aXyEBE= -github.com/docker/docker v27.0.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v27.1.0+incompatible h1:rEHVQc4GZ0MIQKifQPHSFGV/dVgaZafgRf8fCPtDYBs= +github.com/docker/docker v27.1.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= diff --git a/internal/client/inodes_test.go b/internal/client/inodes_test.go new file mode 100644 index 0000000..43b2542 --- /dev/null +++ b/internal/client/inodes_test.go @@ -0,0 +1,40 @@ +package client_test + +import ( + "testing" + + "github.com/s0rg/decompose/internal/client" +) + +func TestInodes(t *testing.T) { + t.Parallel() + + m := &client.InodesMap{} + + if m.Has("1", 1, 1) { + t.Fail() + } + + // listener + m.AddProcess("1", 1, "app1") + m.AddInode("1", 1, 101) + + // client + m.AddProcess("2", 2, "app2") + m.MarkUnknown("2", 2, 101) + m.MarkListener("1", 1, "/some/sock") + + m.ResolveUnknown(func(srcCID, dstCID, srcName, dstName, path string) { + if srcCID != "1" || dstCID != "2" || srcName != "app1" || dstName != "app2" || path != "/some/sock" { + t.Fail() + } + }) + + if !m.Has("1", 1, 101) { + t.Fail() + } + + if m.Has("1", 2, 1) || m.Has("3", 1, 1) { + t.Fail() + } +} diff --git a/internal/graph/orphans_test.go b/internal/graph/orphans_test.go new file mode 100644 index 0000000..ffa5cfe --- /dev/null +++ b/internal/graph/orphans_test.go @@ -0,0 +1,36 @@ +package graph_test + +import ( + "io" + "strings" + "testing" + + "github.com/s0rg/decompose/internal/graph" + "github.com/s0rg/decompose/internal/node" +) + +func TestOrphans(t *testing.T) { + t.Parallel() + + tb := testNamedBuilder{} + op := graph.NewOrphansInspector(&tb) + + name := op.Name() + + if !strings.Contains(name, tb.Name()) { + t.Fail() + } + + op.AddNode(&node.Node{ID: "1"}) + op.AddNode(&node.Node{ID: "2"}) + op.AddNode(&node.Node{ID: "3"}) + + op.AddEdge(&node.Edge{SrcID: "1", DstID: "3"}) + op.AddEdge(&node.Edge{SrcID: "3", DstID: "1"}) + + _ = op.Write(io.Discard) + + if tb.Edges != 2 || tb.Nodes != 2 { + t.Fail() + } +} From 0eb11f79f0a2cb8c1e4cfa2c38ed8b96b0e9c65b Mon Sep 17 00:00:00 2001 From: Shevchenko Alexey Date: Thu, 25 Jul 2024 22:08:27 +0300 Subject: [PATCH 16/16] coverage up to 97%+ --- go.mod | 2 +- go.sum | 4 +- internal/client/docker_test.go | 97 ++++++++++++++++++++++++++++++++++ internal/client/inodes_test.go | 2 + internal/structurizr/system.go | 2 +- 5 files changed, 103 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index ded7cf4..83dd537 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/s0rg/decompose go 1.22 require ( - github.com/docker/docker v27.1.0+incompatible + github.com/docker/docker v27.1.1+incompatible github.com/emicklei/dot v1.6.2 github.com/expr-lang/expr v1.16.9 github.com/prometheus/procfs v0.15.1 diff --git a/go.sum b/go.sum index 00bfcd5..fd5cf88 100644 --- a/go.sum +++ b/go.sum @@ -11,8 +11,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v27.1.0+incompatible h1:rEHVQc4GZ0MIQKifQPHSFGV/dVgaZafgRf8fCPtDYBs= -github.com/docker/docker v27.1.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= +github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= diff --git a/internal/client/docker_test.go b/internal/client/docker_test.go index 0b5935e..9d1e789 100644 --- a/internal/client/docker_test.go +++ b/internal/client/docker_test.go @@ -1186,3 +1186,100 @@ func TestDockerClientNsEnterLocal(t *testing.T) { t.Fail() } } + +func TestDockerClientUnixSockets(t *testing.T) { + t.Parallel() + + cm := &clientMock{} + + cm.OnList = func() (rv []types.Container) { + return []types.Container{ + { + ID: "1", + Names: []string{"test1"}, + Image: "test-image", + State: "running", + NetworkSettings: &types.SummaryNetworkSettings{ + Networks: map[string]*network.EndpointSettings{ + "test-net": { + EndpointID: "1", + IPAddress: "1.1.1.1", + }, + }, + }, + }, + } + } + + cm.OnContainerTop = func() (rv container.ContainerTopOKBody) { + rv.Titles = []string{"PID,CMD"} + rv.Processes = [][]string{ + {"1", "test"}, + } + + return rv + } + + cm.OnInspect = func() (rv types.ContainerJSON) { + rv.ContainerJSONBase = &types.ContainerJSONBase{} + rv.State = &types.ContainerState{Pid: 1} + rv.Config = &container.Config{ + Cmd: []string{"foo"}, + Env: []string{"BAR=1"}, + } + rv.Mounts = []types.MountPoint{} + + return rv + } + + const myTestInode uint64 = 12345 + + testInodes := func(_ int, cb func(uint64)) error { + cb(myTestInode) + + return nil + } + + testEnter := func(_ int, _ graph.NetProto, fn func(int, *graph.Connection)) error { + fn(1, &graph.Connection{ + Process: "1", + Listen: true, + Path: "/test/unix", + Proto: graph.UNIX, + Inode: myTestInode, + }) + + return nil + } + + cli, err := client.NewDocker( + client.WithClientCreator(func() (client.DockerClient, error) { + return cm, nil + }), + client.WithMode(client.LinuxNsenter), + client.WithNsenterFn(testEnter), + client.WithInodesFn(testInodes), + ) + if err != nil { + t.Fatal("client:", err) + } + + rv, err := cli.Containers( + context.Background(), + graph.UNIX, + true, + nil, + voidProgress, + ) + if err != nil { + t.Fatal("containers:", err) + } + + if len(rv) != 1 { + t.Fail() + } + + if rv[0].ConnectionsCount() != 1 { + t.Fail() + } +} diff --git a/internal/client/inodes_test.go b/internal/client/inodes_test.go index 43b2542..ee64d19 100644 --- a/internal/client/inodes_test.go +++ b/internal/client/inodes_test.go @@ -21,6 +21,8 @@ func TestInodes(t *testing.T) { // client m.AddProcess("2", 2, "app2") + + // inodes m.MarkUnknown("2", 2, 101) m.MarkListener("1", 1, "/some/sock") diff --git a/internal/structurizr/system.go b/internal/structurizr/system.go index aaec73b..183dda9 100644 --- a/internal/structurizr/system.go +++ b/internal/structurizr/system.go @@ -72,7 +72,7 @@ func (s *System) AddRelation(srcID, dstID, srcName, dstName string) (rv *Relatio return nil, false } - if rv, ok = s.findRelation(src.Name, dst.Name); ok { + if rv, ok = s.findRelation(src.ID, dst.ID); ok { return rv, true }