Files
package-updater/internal/agent/inventory/collector_test.go
T

212 lines
7.7 KiB
Go

package inventory
import (
"context"
"errors"
"testing"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/image"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/api/types/system"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/shcizo/package-updater/internal/protocol"
)
type fakeDocker struct {
containers []container.Summary
images []image.Summary
services []swarm.Service
info system.Info
containerErr error
serviceListCalls int
inspect map[string]container.InspectResponse
inspectErr error
}
func (f *fakeDocker) ContainerList(context.Context, container.ListOptions) ([]container.Summary, error) {
return f.containers, f.containerErr
}
func (f *fakeDocker) ContainerInspect(_ context.Context, id string) (container.InspectResponse, error) {
return f.inspect[id], f.inspectErr
}
func (f *fakeDocker) ImageList(context.Context, image.ListOptions) ([]image.Summary, error) {
return f.images, nil
}
func (f *fakeDocker) ServiceList(context.Context, swarm.ServiceListOptions) ([]swarm.Service, error) {
f.serviceListCalls++
return f.services, nil
}
func (f *fakeDocker) Info(context.Context) (system.Info, error) { return f.info, nil }
func composeContainer(id, project, service, img, imageID, state string) container.Summary {
return container.Summary{
ID: id, Image: img, ImageID: imageID, State: state, Names: []string{"/" + project + "-" + service + "-1"},
Labels: map[string]string{
labelProject: project,
labelService: service,
labelWorkingDir: "/home/shcizo/self-hosted/" + project,
},
}
}
func managerInfo() system.Info {
var info system.Info
info.Name = "swarm-mgr"
info.Swarm.ControlAvailable = true
info.Swarm.Cluster = &swarm.ClusterInfo{ID: "cluster-1"}
return info
}
func TestSnapshot_ComposeContainerWithRepoDigest(t *testing.T) {
cli := &fakeDocker{
containers: []container.Summary{composeContainer("abc123", "slusk", "web", "gitea.shcizo.se/shcizo/slusk:1.4.2", "sha256:img1", "running")},
images: []image.Summary{{ID: "sha256:img1", RepoDigests: []string{"gitea.shcizo.se/shcizo/slusk@sha256:d1"}}},
}
got, err := New(cli, "").Snapshot(context.Background())
require.NoError(t, err)
assert.Equal(t, []protocol.Service{{
Kind: protocol.KindCompose, ComposeProject: "slusk", ComposeService: "web",
WorkingDir: "/home/shcizo/self-hosted/slusk",
Image: "gitea.shcizo.se/shcizo/slusk:1.4.2", Digest: "sha256:d1", Status: "running",
}}, got)
}
func TestSnapshot_MovedTagUsesConfiguredReference(t *testing.T) {
ct := composeContainer("abc123", "slusk", "web", "sha256:img1", "sha256:img1", "running")
cli := &fakeDocker{
containers: []container.Summary{ct},
images: []image.Summary{{ID: "sha256:img1", RepoDigests: []string{"gitea.shcizo.se/shcizo/slusk@sha256:d1"}}},
inspect: map[string]container.InspectResponse{
"abc123": {Config: &container.Config{Image: "gitea.shcizo.se/shcizo/slusk:1.4.2"}},
},
}
got, err := New(cli, "").Snapshot(context.Background())
require.NoError(t, err)
require.Len(t, got, 1)
assert.Equal(t, "gitea.shcizo.se/shcizo/slusk:1.4.2", got[0].Image)
assert.Equal(t, "sha256:d1", got[0].Digest)
}
func TestSnapshot_InspectErrorPropagates(t *testing.T) {
cli := &fakeDocker{
containers: []container.Summary{composeContainer("abc123", "slusk", "web", "sha256:img1", "sha256:img1", "running")},
inspectErr: errors.New("gone"),
}
_, err := New(cli, "").Snapshot(context.Background())
require.Error(t, err)
assert.Contains(t, err.Error(), "docker container inspect")
}
func TestSnapshot_ReplicasMergeAndRunningWins(t *testing.T) {
cli := &fakeDocker{containers: []container.Summary{
composeContainer("a1", "slusk", "worker", "slusk:1", "sha256:i", "exited"),
composeContainer("a2", "slusk", "worker", "slusk:1", "sha256:i", "running"),
}}
got, err := New(cli, "").Snapshot(context.Background())
require.NoError(t, err)
require.Len(t, got, 1)
assert.Equal(t, "running", got[0].Status)
}
func TestSnapshot_PlainContainerAndSwarmTaskSkipped(t *testing.T) {
cli := &fakeDocker{containers: []container.Summary{
{ID: "p1", Names: []string{"/adhoc"}, Image: "redis:alpine", State: "exited"},
{ID: "t1", Names: []string{"/gitea_gitea.1.x"}, Image: "gitea/gitea:1.27.3", State: "running",
Labels: map[string]string{labelSwarmTask: "svc1"}},
}}
got, err := New(cli, "").Snapshot(context.Background())
require.NoError(t, err)
assert.Equal(t, []protocol.Service{{
Kind: protocol.KindContainer, ContainerName: "adhoc", Image: "redis:alpine", Status: "stopped",
}}, got)
}
func TestSnapshot_SelfFlag(t *testing.T) {
cli := &fakeDocker{containers: []container.Summary{
composeContainer("0123456789abcdef", "updater-agent", "agent", "pu:1", "sha256:i", "running"),
}}
got, err := New(cli, "0123456789ab").Snapshot(context.Background())
require.NoError(t, err)
require.Len(t, got, 1)
assert.True(t, got[0].Self)
}
func TestSnapshot_SwarmServicesOnlyOnManager(t *testing.T) {
svc := swarm.Service{
Spec: swarm.ServiceSpec{
Annotations: swarm.Annotations{Name: "gitea_gitea"},
TaskTemplate: swarm.TaskSpec{ContainerSpec: &swarm.ContainerSpec{Image: "gitea/gitea:1.27.3@sha256:87a6"}},
},
ServiceStatus: &swarm.ServiceStatus{RunningTasks: 1, DesiredTasks: 1},
}
worker := &fakeDocker{services: []swarm.Service{svc}}
got, err := New(worker, "").Snapshot(context.Background())
require.NoError(t, err)
assert.Empty(t, got)
assert.Equal(t, 0, worker.serviceListCalls, "workers must not call the manager-only API")
manager := &fakeDocker{services: []swarm.Service{svc}, info: managerInfo()}
got, err = New(manager, "").Snapshot(context.Background())
require.NoError(t, err)
assert.Equal(t, []protocol.Service{{
Kind: protocol.KindSwarm, SwarmService: "gitea_gitea",
Image: "gitea/gitea:1.27.3@sha256:87a6", Digest: "sha256:87a6", Status: "running",
}}, got)
}
func TestSwarmStatus(t *testing.T) {
cases := []struct {
name string
in *swarm.ServiceStatus
want string
}{
{"no status", nil, "unknown"},
{"scaled to zero", &swarm.ServiceStatus{DesiredTasks: 0}, "stopped"},
{"all running", &swarm.ServiceStatus{RunningTasks: 2, DesiredTasks: 2}, "running"},
{"some running", &swarm.ServiceStatus{RunningTasks: 1, DesiredTasks: 2}, "degraded"},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) { assert.Equal(t, c.want, swarmStatus(c.in)) })
}
}
func TestSnapshot_SortedByKey(t *testing.T) {
cli := &fakeDocker{containers: []container.Summary{
{ID: "2", Names: []string{"/zeta"}, Image: "z", State: "running"},
{ID: "1", Names: []string{"/alpha"}, Image: "a", State: "running"},
}}
got, err := New(cli, "").Snapshot(context.Background())
require.NoError(t, err)
require.Len(t, got, 2)
assert.Equal(t, "alpha", got[0].ContainerName)
}
func TestSnapshot_PropagatesDockerErrors(t *testing.T) {
cli := &fakeDocker{containerErr: errors.New("daemon down")}
_, err := New(cli, "").Snapshot(context.Background())
assert.ErrorContains(t, err, "daemon down")
}
func TestHello(t *testing.T) {
got, err := New(&fakeDocker{info: managerInfo()}, "").Hello(context.Background())
require.NoError(t, err)
assert.Equal(t, protocol.Hello{Hostname: "swarm-mgr", SwarmManager: true, SwarmClusterID: "cluster-1"}, got)
}
func TestSnapshot_ComposeConfigFilesIsRawLabel(t *testing.T) {
ct := composeContainer("abc123", "slusk", "web", "slusk:1", "sha256:img1", "running")
ct.Labels[labelConfigFiles] = "/s/a/compose.yaml,/s/a/compose.override.yaml"
got, err := New(&fakeDocker{containers: []container.Summary{ct}}, "").Snapshot(context.Background())
require.NoError(t, err)
require.Len(t, got, 1)
assert.Equal(t, "/s/a/compose.yaml,/s/a/compose.override.yaml", got[0].ConfigFiles)
}