Skip to content

Commit

Permalink
client refactored and tested (#43)
Browse files Browse the repository at this point in the history
  • Loading branch information
s0rg authored Sep 9, 2023
1 parent 5b47a8b commit a2e2996
Show file tree
Hide file tree
Showing 19 changed files with 1,195 additions and 130 deletions.
1 change: 1 addition & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ issues:
- path: ._test\.go
linters:
- goerr113
- gocritic
- errcheck
- funlen
- dupl
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ GIT_TAG=`git describe --abbrev=0 2>/dev/null || echo -n "no-tag"`
GIT_HASH=`git rev-parse --short HEAD 2>/dev/null || echo -n "no-git"`
BUILD_AT=`date +%FT%T%z`

LDFLAGS=-w -s -X main.gitHash=${GIT_HASH} -X main.buildDate=${BUILD_AT} -X main.gitVersion=${GIT_TAG}
LDFLAGS=-w -s \
-X main.buildDate=${BUILD_AT} \
-X main.gitVersion=${GIT_TAG} \
-X main.gitHash=${GIT_HASH}

export CGO_ENABLED=0

Expand Down
43 changes: 29 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,22 @@ Takes all network connections from your docker containers and exports them as:
- json stream
- statistics (nodes, edges and listen ports)

# features
## rationale

I was in need for a tool to visualize and inspect big (more than 470 containers) dockerized legacy system without any schemes and having a bare minimum of documentation.

## analogs

Closest analogs, i can find, that not suit my needs very well:

- [Red5d/docker-autocompose](https://github.com/Red5d/docker-autocompose) - produces only `compose yaml`
- [justone/dockviz](https://github.com/justone/dockviz) - produces only `dot`, links and ports are taken
from compose configuration (`links` and `ports` sections) directly, therefore can miss some of them
- [LeoVerto/docker-network-graph](https://github.com/LeoVerto/docker-network-graph) - very same as above, build in
python
- [weaveworks/scope](https://github.com/weaveworks/scope) - deprecated, no cli

## features

- os-independent, it uses different strategies to get container connections:
- running on **linux as root** is the fastest way and it will work with all types of containers (even
Expand All @@ -33,20 +48,20 @@ Takes all network connections from your docker containers and exports them as:
(i.e. for missing `netstat` binary), no connections for such container will be gathered
- produces detailed connections graph **with ports**
- save `json` stream once and process it later in any way you want
- fast, scans ~400 containers in around 5 sec
- fast, scans ~470 containers in around 5 sec
- 100% test-coverage

# known limitations
## 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

# installation
## installation

- [binaries / deb / rpm](https://github.com/s0rg/decompose/releases) for Linux, FreeBSD, macOS and Windows.

# usage
## usage

```
decompose [flags]
Expand Down Expand Up @@ -83,13 +98,13 @@ possible flags with default values:
show version
```

## environment variables:
### environment variables:

- `DOCKER_HOST` - connection uri
- `DOCKER_CERT_PATH` - directory path containing key.pem, cert.pm and ca.pem
- `DOCKER_TLS_VERIFY` - enable client TLS verification

# json stream format
## json stream format

```go
type Item struct {
Expand Down Expand Up @@ -155,7 +170,7 @@ example with full info and metadata filled:

See [stream.json](examples/stream.json) for simple stream example.

# metadata format
## metadata format

To enrich output with detailed descriptions, you can provide additional `json` file, with metadata i.e.:

Expand All @@ -178,10 +193,10 @@ one of provided keys, like `foo-1` or `bar1` for this example.
See [csv2meta.py](examples/csv2meta.py) for example how to create such `json` fom csv, and
[meta.json](examples/meta.json) for metadata sample.

# clusterization rules
## clusterization rules

You can join your services into `clusters` by exposed ports, in `dot` or `structurizr` output formats.
With clusterization rules, in `json` (order matters):
You can join your services into `clusters` by flexible rules, in `dot`, `structurizr` and `stat` output formats.
Example `json` (order matters):

```json
[
Expand Down Expand Up @@ -213,7 +228,7 @@ type Node struct {

See: [cluster.json](examples/cluster.json) for detailed example.

# examples
## examples

Save full json stream:

Expand Down Expand Up @@ -245,7 +260,7 @@ Load json stream, enrich and save as `structurizr dsl`:
decompose -load nodes-1.json -meta metadata.json -format sdsl > workspace.dsl
```

# example result
## example result

Scheme taken from [redis-cluster](https://github.com/s0rg/redis-cluster-compose):

Expand All @@ -266,6 +281,6 @@ in other terminal:
decompose -format dot | dot -Tsvg > redis-cluster.svg
```

# license
## license

[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fs0rg%2Fdecompose.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fs0rg%2Fdecompose?ref=badge_large)
28 changes: 16 additions & 12 deletions cmd/decompose/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
const (
appName = "Decompose"
appSite = "https://github.com/s0rg/decompose"
linuxOS = "linux"
defaultProto = "all"
defaultOutput = "-"
)
Expand Down Expand Up @@ -250,15 +251,7 @@ func doLoad(
ldr := graph.NewLoader(cfg)

for _, fn := range files {
fd, err := os.Open(fn)
if err != nil {
return fmt.Errorf("open %s: %w", fn, err)
}

err = ldr.LoadStream(fd)
fd.Close()

if err != nil {
if err := feed(fn, ldr.FromReader); err != nil {
return fmt.Errorf("load %s: %w", fn, err)
}
}
Expand All @@ -273,14 +266,25 @@ func doLoad(
func doBuild(
cfg *graph.Config,
) error {
cli, err := client.NewDocker()
opts := []client.Option{
client.WithClientCreator(client.Default),
}

mode := client.InContainer

if runtime.GOOS == linuxOS && os.Geteuid() == 0 {
opts = append(opts, client.WithNsEnter(client.Nsenter))
mode = client.LinuxNsenter
}

cli, err := client.NewDocker(append(opts, client.WithMode(mode))...)
if err != nil {
return fmt.Errorf("docker: %w", err)
return fmt.Errorf("client: %w", err)
}

defer cli.Close()

log.Println("Starting with method:", cli.Kind())
log.Println("Starting with method:", cli.Mode())

if err = graph.Build(cfg, cli); err != nil {
return fmt.Errorf("graph: %w", err)
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/s0rg/decompose

go 1.21.0
go 1.21.1

require (
github.com/antonmedv/expr v1.15.1
Expand Down
2 changes: 2 additions & 0 deletions internal/builder/dot.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
//go:build !test

// i/o here, nothing to test

package builder

import (
Expand Down
6 changes: 3 additions & 3 deletions internal/builder/json_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func TestJSON(t *testing.T) {

ldr := graph.NewLoader(cfg)

if err := ldr.LoadStream(bytes.NewBuffer(jnode)); err != nil {
if err := ldr.FromReader(bytes.NewBuffer(jnode)); err != nil {
t.Fatal("load err=", err)
}

Expand Down Expand Up @@ -108,7 +108,7 @@ func TestJSONAddEdge(t *testing.T) {

ldr := graph.NewLoader(cfg)

if err := ldr.LoadStream(bytes.NewBufferString(raw)); err != nil {
if err := ldr.FromReader(bytes.NewBufferString(raw)); err != nil {
t.Fatal("load err=", err)
}

Expand Down Expand Up @@ -166,7 +166,7 @@ func TestJSONAddBadEdges(t *testing.T) {

ldr := graph.NewLoader(cfg)

if err := ldr.LoadStream(&buf); err != nil {
if err := ldr.FromReader(&buf); err != nil {
t.Fatal("load err=", err)
}

Expand Down
2 changes: 2 additions & 0 deletions internal/builder/sdsl.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
//go:build !test

// i/o here, nothing to test

package builder

import (
Expand Down
4 changes: 2 additions & 2 deletions internal/builder/stat_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ func TestStat(t *testing.T) {
}
`

if err := ldr.LoadStream(bytes.NewBufferString(raw)); err != nil {
if err := ldr.FromReader(bytes.NewBufferString(raw)); err != nil {
t.Fatal("load err=", err)
}

Expand Down Expand Up @@ -137,7 +137,7 @@ func TestStatCluster(t *testing.T) {
}
`

if err := ldr.LoadStream(bytes.NewBufferString(raw)); err != nil {
if err := ldr.FromReader(bytes.NewBufferString(raw)); err != nil {
t.Fatal("load err=", err)
}

Expand Down
2 changes: 2 additions & 0 deletions internal/builder/yaml.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
//go:build !test

// i/o here, nothing to test

package builder

import (
Expand Down
55 changes: 55 additions & 0 deletions internal/client/defaults.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
//go:build !test

package client

import (
"context"
"fmt"
"io"
"os/exec"
"strconv"

"github.com/docker/docker/client"
"github.com/s0rg/decompose/internal/graph"
)

func Default() (rv DockerClient, err error) {
rv, err = client.NewClientWithOpts(
client.FromEnv,
client.WithAPIVersionNegotiation(),
)
if err != nil {
return nil, fmt.Errorf("docker: %w", err)
}

return rv, nil
}

func Nsenter(
ctx context.Context,
pid int,
proto graph.NetProto,
parse func(io.Reader) error,
) (
err error,
) {
arg := append([]string{"-t", strconv.Itoa(pid), "-n"}, netstat(proto)...)
cmd := exec.CommandContext(ctx, nsenterCmd, arg...)

pipe, err := cmd.StdoutPipe()
if err != nil {
return fmt.Errorf("pipe: %w", err)
}

defer pipe.Close()

if err = cmd.Start(); err != nil {
return fmt.Errorf("exec: %w", err)
}

if err = parse(pipe); err != nil {
return fmt.Errorf("parse: %w", err)
}

return nil
}
Loading

0 comments on commit a2e2996

Please sign in to comment.