Skip to content

Commit

Permalink
Add support for multiple Docker hosts
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexGustafsson committed Jan 12, 2025
1 parent 262f13b commit bdf1fcc
Show file tree
Hide file tree
Showing 6 changed files with 184 additions and 10 deletions.
26 changes: 17 additions & 9 deletions cmd/cupdate/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@ type Config struct {
} `envPrefix:"KUBERNETES_"`

Docker struct {
Host string `env:"HOST"`
IncludeAllContainers bool `env:"INCLUDE_ALL_CONTAINERS"`
Hosts []string `env:"HOST"`
IncludeAllContainers bool `env:"INCLUDE_ALL_CONTAINERS"`
} `envPrefix:"DOCKER_"`

OTEL struct {
Expand Down Expand Up @@ -135,7 +135,7 @@ func main() {
// Set up the configured platform (Docker if specified, auto discovery of
// Kubernetes otherwise)
var targetPlatform platform.Grapher
if config.Docker.Host == "" {
if len(config.Docker.Hosts) == 0 {
var kubernetesConfig *rest.Config
if config.Kubernetes.Host == "" {
var err error
Expand All @@ -156,12 +156,20 @@ func main() {
os.Exit(1)
}
} else {
targetPlatform, err = docker.NewPlatform(context.Background(), config.Docker.Host, &docker.Options{
IncludeAllContainers: config.Docker.IncludeAllContainers,
})
if err != nil {
slog.ErrorContext(ctx, "Failed to create docker source", slog.Any("error", err))
os.Exit(1)
graphers := make([]platform.Grapher, 0)
for _, host := range config.Docker.Hosts {
platform, err := docker.NewPlatform(context.Background(), host, &docker.Options{
IncludeAllContainers: config.Docker.IncludeAllContainers,
})
if err != nil {
slog.ErrorContext(ctx, "Failed to create docker source", slog.Any("error", err))
os.Exit(1)
}

graphers = append(graphers, platform)
}
targetPlatform = &platform.CompoundGrapher{
Graphers: graphers,
}
}

Expand Down
2 changes: 1 addition & 1 deletion docs/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ done using environment variables.
| `CUPDATE_PROCESSING_QUEUE_RATE` | The desired processing rate under normal circumstances. | `1m` |
| `CUPDATE_KUBERNETES_HOST` | The host of the Kubernetes API. For use with proxying. | Required to use Kubernetes. |
| `CUPDATE_KUBERNETES_INCLUDE_OLD_REPLICAS` | Whether or not to include old replica sets when scraping. | `false` |
| `CUPDATE_DOCKER_HOST` | Docker host address. | Required to use Docker. |
| `CUPDATE_DOCKER_HOST` | One or more comma-separated Docker host URIs. | Required to use Docker. |
| `CUPDATE_DOCKER_INCLUDE_ALL_CONTAINERS` | Whether or not to include containers in any state, not just running containers. | `false` |
| `CUPDATE_OTEL_TARGET` | Target URL to an Open Telemetry GRPC ingest endpoint. | Required to use Open Telemetry. |
| `CUPDATE_OTEL_INSECURE` | Disable client transport security for the Open Telemetry GRPC connection. | `false` |
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ require (
github.com/prometheus/common v0.55.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/x448/float16 v0.8.4 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWN
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
Expand Down
35 changes: 35 additions & 0 deletions internal/platform/platform.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package platform

import (
"context"
"errors"
"fmt"
"log/slog"
"sync"
"time"

"github.com/AlexGustafsson/cupdate/internal/graph"
Expand Down Expand Up @@ -89,6 +91,39 @@ func (g *PollGrapher) GraphContinously(ctx context.Context) (<-chan Graph, error
return ch, nil
}

// CompoundGrapher creates a graph from one or more [Grapher] simultaneously.
type CompoundGrapher struct {
Graphers []Grapher
}

func (g *CompoundGrapher) Graph(ctx context.Context) (Graph, error) {
graphs := make([]Graph, len(g.Graphers))
errs := make([]error, len(g.Graphers))

// Don't use ErrGroup in order to retain all errors to help users debug any
// issues
var wg sync.WaitGroup
for i, grapher := range g.Graphers {
wg.Add(1)
go func() {
defer wg.Done()
graphs[i], errs[i] = grapher.Graph(ctx)
}()
}
wg.Wait()

if err := errors.Join(errs...); err != nil {
return nil, err
}

compoundGraph := NewGraph()
for _, graph := range graphs {
compoundGraph.InsertGraph(graph)
}

return compoundGraph, nil
}

func NewGraph() Graph {
return graph.New[Node]()
}
128 changes: 128 additions & 0 deletions internal/platform/platform_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package platform

import (
"context"
"errors"
"strings"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
)

var _ Grapher = (*MockGrapher)(nil)

type MockGrapher struct {
mock.Mock
}

// Graph implements Grapher.
func (m *MockGrapher) Graph(ctx context.Context) (Graph, error) {
args := m.Called(ctx)
return args.Get(0).(Graph), args.Error(1)
}

var _ Node = (*TestNode)(nil)

type TestNode struct {
id string
nodeType string
}

// ID implements Node.
func (m *TestNode) ID() string {
return m.id
}

// Type implements Node.
func (m *TestNode) Type() string {
return m.nodeType
}

func TestCompoundGrapherHappyPath(t *testing.T) {
graph1 := NewGraph()
graph1.InsertTree(
&TestNode{id: "graph1/node1", nodeType: "testnode"},
&TestNode{id: "graph1/node2", nodeType: "testnode"},
)

grapher1 := &MockGrapher{}
grapher1.On("Graph", mock.Anything).Return(graph1, nil)

graph2 := NewGraph()
graph2.InsertTree(
&TestNode{id: "graph2/node1", nodeType: "testnode"},
&TestNode{id: "graph2/node2", nodeType: "testnode"},
)

grapher2 := &MockGrapher{}
grapher2.On("Graph", mock.Anything).Return(graph2, nil)

compoundGrapher := CompoundGrapher{
Graphers: []Grapher{grapher1, grapher2},
}

graph, err := compoundGrapher.Graph(context.TODO())
require.NoError(t, err)
grapher1.AssertExpectations(t)
grapher2.AssertExpectations(t)

expectedString := `graph1/node1->graph1/node2
graph2/node1->graph2/node2`

actualString := graph.String()

// Ignore order when matching
expected := strings.Split(expectedString, "\n")
actual := strings.Split(actualString, "\n")

assert.ElementsMatch(t, expected, actual)
}

func TestCompoundGrapherError(t *testing.T) {
testCases := []struct {
Name string
Err1 error
Err2 error
ExpectedErr string
}{
{
Name: "grapher 1 fails",
Err1: errors.New("failed to graph"),
Err2: nil,
ExpectedErr: "failed to graph",
},
{
Name: "grapher 2 fails",
Err1: nil,
Err2: errors.New("failed to graph"),
ExpectedErr: "failed to graph",
},
{
Name: "grapher 1 and 2 fail",
Err1: errors.New("failed to graph"),
Err2: errors.New("failed to graph"),
ExpectedErr: "failed to graph\nfailed to graph",
},
}

for _, testCase := range testCases {
t.Run(testCase.Name, func(t *testing.T) {
grapher1 := &MockGrapher{}
grapher1.On("Graph", mock.Anything).Return(Graph(nil), testCase.Err1)

grapher2 := &MockGrapher{}
grapher2.On("Graph", mock.Anything).Return(Graph(nil), testCase.Err2)

compoundGrapher := CompoundGrapher{
Graphers: []Grapher{grapher1, grapher2},
}

_, err := compoundGrapher.Graph(context.TODO())
require.EqualError(t, err, testCase.ExpectedErr)
grapher1.AssertExpectations(t)
grapher2.AssertExpectations(t)
})
}
}

0 comments on commit bdf1fcc

Please sign in to comment.