Skip to content

Commit

Permalink
feat(support): collect system info bundle to assist support troublesh…
Browse files Browse the repository at this point in the history
…ooting [r8s-157] (#154)
  • Loading branch information
segfault88 authored Dec 6, 2024
1 parent a028181 commit 2694f8a
Show file tree
Hide file tree
Showing 12 changed files with 142 additions and 270 deletions.
221 changes: 8 additions & 213 deletions docker/snapshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,12 @@ package docker

import (
"context"
"strings"
"time"

portainer "github.com/portainer/portainer/api"

"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/image"
"github.com/docker/docker/api/types/volume"
"github.com/docker/docker/client"
"github.com/rs/zerolog/log"
"github.com/portainer/portainer/pkg/snapshot"
)

func CreateSnapshot() (*portainer.DockerSnapshot, error) {
func CreateSnapshot(edgeKey string) (*portainer.DockerSnapshot, error) {
cli, err := NewClient()
if err != nil {
return nil, err
Expand All @@ -27,213 +19,16 @@ func CreateSnapshot() (*portainer.DockerSnapshot, error) {
return nil, err
}

snapshot := &portainer.DockerSnapshot{
StackCount: 0,
}

err = snapshotInfo(snapshot, cli)
if err != nil {
log.Warn().Err(err).Msg("unable to snapshot engine information")
}

if snapshot.Swarm {
err = snapshotSwarmServices(snapshot, cli)
if err != nil {
log.Warn().Err(err).Msg("unable to snapshot Swarm services")
}

err = snapshotNodes(snapshot, cli)
if err != nil {
log.Warn().Err(err).Msg("unable to snapshot Swarm nodes")
}
}

err = snapshotContainers(snapshot, cli)
if err != nil {
log.Warn().Err(err).Msg("unable to snapshot containers")
}

err = snapshotImages(snapshot, cli)
if err != nil {
log.Warn().Err(err).Msg("unable to snapshot images")
}

err = snapshotVolumes(snapshot, cli)
if err != nil {
log.Warn().Err(err).Msg("unable to snapshot volumes")
}

err = snapshotNetworks(snapshot, cli)
if err != nil {
log.Warn().Err(err).Msg("unable to snapshot networks")
}

err = snapshotVersion(snapshot, cli)
if err != nil {
log.Warn().Err(err).Msg("unable to snapshot engine version")
}

snapshot.Time = time.Now().Unix()

return snapshot, nil
}

func snapshotInfo(snapshot *portainer.DockerSnapshot, cli *client.Client) error {
info, err := cli.Info(context.Background())
if err != nil {
return err
}

snapshot.Swarm = info.Swarm.ControlAvailable
snapshot.DockerVersion = info.ServerVersion
snapshot.TotalCPU = info.NCPU
snapshot.TotalMemory = info.MemTotal
snapshot.SnapshotRaw.Info = info

return nil
}

func snapshotNodes(snapshot *portainer.DockerSnapshot, cli *client.Client) error {
nodes, err := cli.NodeList(context.Background(), types.NodeListOptions{})
if err != nil {
return err
}
var nanoCpus int64
var totalMem int64
for _, node := range nodes {
nanoCpus += node.Description.Resources.NanoCPUs
totalMem += node.Description.Resources.MemoryBytes
}
snapshot.TotalCPU = int(nanoCpus / 1e9)
snapshot.TotalMemory = totalMem
snapshot.NodeCount = len(nodes)

return nil
}

func snapshotSwarmServices(snapshot *portainer.DockerSnapshot, cli *client.Client) error {
stacks := make(map[string]struct{})

services, err := cli.ServiceList(context.Background(), types.ServiceListOptions{})
if err != nil {
return err
}

for _, service := range services {
for k, v := range service.Spec.Labels {
if k == "com.docker.stack.namespace" {
stacks[v] = struct{}{}
}
}
}

snapshot.ServiceCount = len(services)
snapshot.StackCount += len(stacks)

return nil
}

func snapshotContainers(snapshot *portainer.DockerSnapshot, cli *client.Client) error {
rawContainers, err := cli.ContainerList(context.Background(), container.ListOptions{All: true})
dockerSnapshot, err := snapshot.CreateDockerSnapshot(cli)
if err != nil {
return err
}

runningContainers := 0
stoppedContainers := 0
healthyContainers := 0
unhealthyContainers := 0
stacks := make(map[string]struct{})

containers := make([]portainer.DockerContainerSnapshot, 0)

for _, container := range rawContainers {
response, err := cli.ContainerInspect(context.Background(), container.ID)
if err != nil {
log.Warn().Err(err).Msg("failed to retrieve env for container " + container.ID + ". Skipping.")
containers = append(containers, portainer.DockerContainerSnapshot{Container: container})

continue
}

containers = append(containers, portainer.DockerContainerSnapshot{
Container: container,
Env: response.Config.Env,
})
}

for _, container := range containers {
if container.State == "exited" {
stoppedContainers++
} else if container.State == "running" {
runningContainers++
}

if strings.Contains(container.Status, "(healthy)") {
healthyContainers++
} else if strings.Contains(container.Status, "(unhealthy)") {
unhealthyContainers++
}

for k, v := range container.Labels {
if k == "com.docker.compose.project" {
stacks[v] = struct{}{}
}
}
}

snapshot.RunningContainerCount = runningContainers
snapshot.StoppedContainerCount = stoppedContainers
snapshot.HealthyContainerCount = healthyContainers
snapshot.UnhealthyContainerCount = unhealthyContainers
snapshot.StackCount += len(stacks)
snapshot.SnapshotRaw.Containers = containers

return nil
}

func snapshotImages(snapshot *portainer.DockerSnapshot, cli *client.Client) error {
images, err := cli.ImageList(context.Background(), image.ListOptions{})
if err != nil {
return err
}

snapshot.ImageCount = len(images)
snapshot.SnapshotRaw.Images = images

return nil
}

func snapshotVolumes(snapshot *portainer.DockerSnapshot, cli *client.Client) error {
volumes, err := cli.VolumeList(context.Background(), volume.ListOptions{})
if err != nil {
return err
}

snapshot.VolumeCount = len(volumes.Volumes)
snapshot.SnapshotRaw.Volumes = volumes

return nil
}

func snapshotNetworks(snapshot *portainer.DockerSnapshot, cli *client.Client) error {
networks, err := cli.NetworkList(context.Background(), types.NetworkListOptions{})
if err != nil {
return err
return nil, err
}

snapshot.SnapshotRaw.Networks = networks

return nil
}

func snapshotVersion(snapshot *portainer.DockerSnapshot, cli *client.Client) error {
version, err := cli.ServerVersion(context.Background())
diagnosticsData, err := snapshot.DockerSnapshotDiagnostics(cli, edgeKey)
if err != nil {
return err
return nil, err
}
dockerSnapshot.DiagnosticsData = diagnosticsData

snapshot.SnapshotRaw.Version = version

return nil
return dockerSnapshot, nil
}
4 changes: 2 additions & 2 deletions edge/client/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,9 @@ type setEndpointIDFn func(portainer.EndpointID)
type getEndpointIDFn func() portainer.EndpointID

// NewPortainerClient returns a pointer to a new PortainerClient instance
func NewPortainerClient(serverAddress string, setEIDFn setEndpointIDFn, getEIDFn getEndpointIDFn, edgeID string, edgeAsyncMode bool, agentPlatform agent.ContainerPlatform, metaFields agent.EdgeMetaFields, httpClient *edgeHTTPClient) PortainerClient {
func NewPortainerClient(serverAddress string, setEIDFn setEndpointIDFn, getEIDFn getEndpointIDFn, edgeID string, edgeKey string, edgeAsyncMode bool, agentPlatform agent.ContainerPlatform, metaFields agent.EdgeMetaFields, httpClient *edgeHTTPClient) PortainerClient {
if edgeAsyncMode {
return NewPortainerAsyncClient(serverAddress, setEIDFn, getEIDFn, edgeID, agentPlatform, metaFields, httpClient)
return NewPortainerAsyncClient(serverAddress, setEIDFn, getEIDFn, edgeID, edgeKey, agentPlatform, metaFields, httpClient)
}

return NewPortainerEdgeClient(serverAddress, setEIDFn, getEIDFn, edgeID, agentPlatform, metaFields, httpClient)
Expand Down
9 changes: 5 additions & 4 deletions edge/client/portainer_edge_async_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ type PortainerAsyncClient struct {
setEndpointIDFn setEndpointIDFn
getEndpointIDFn getEndpointIDFn
edgeID string
edgeKey string
agentPlatformIdentifier agent.ContainerPlatform
commandTimestamp *time.Time
metaFields agent.EdgeMetaFields
Expand All @@ -45,13 +46,14 @@ type PortainerAsyncClient struct {
}

// NewPortainerAsyncClient returns a pointer to a new PortainerAsyncClient instance
func NewPortainerAsyncClient(serverAddress string, setEIDFn setEndpointIDFn, getEIDFn getEndpointIDFn, edgeID string, containerPlatform agent.ContainerPlatform, metaFields agent.EdgeMetaFields, httpClient *edgeHTTPClient) *PortainerAsyncClient {
func NewPortainerAsyncClient(serverAddress string, setEIDFn setEndpointIDFn, getEIDFn getEndpointIDFn, edgeID string, edgeKey string, containerPlatform agent.ContainerPlatform, metaFields agent.EdgeMetaFields, httpClient *edgeHTTPClient) *PortainerAsyncClient {
initialCommandTimestamp := time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)
return &PortainerAsyncClient{
serverAddress: serverAddress,
setEndpointIDFn: setEIDFn,
getEndpointIDFn: getEIDFn,
edgeID: edgeID,
edgeKey: edgeKey,
httpClient: httpClient,
agentPlatformIdentifier: containerPlatform,
commandTimestamp: &initialCommandTimestamp,
Expand Down Expand Up @@ -184,10 +186,9 @@ func (client *PortainerAsyncClient) GetEnvironmentStatus(flags ...string) (*Poll
var currentSnapshot snapshot
if doSnapshot {
payload.Snapshot = &snapshot{}

switch client.agentPlatformIdentifier {
case agent.PlatformDocker:
dockerSnapshot, err := docker.CreateSnapshot()
dockerSnapshot, err := docker.CreateSnapshot(client.edgeKey)
if err != nil {
log.Warn().Err(err).Msg("could not create the Docker snapshot")
}
Expand Down Expand Up @@ -259,7 +260,7 @@ func (client *PortainerAsyncClient) GetEnvironmentStatus(flags ...string) (*Poll
}

case agent.PlatformKubernetes:
kubeSnapshot, err := kubernetes.CreateSnapshot()
kubeSnapshot, err := kubernetes.CreateSnapshot(client.edgeKey)
if err != nil {
log.Warn().Err(err).Msg("could not create the Kubernetes snapshot")
}
Expand Down
1 change: 1 addition & 0 deletions edge/edge.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ func (manager *Manager) Start() error {
manager.SetEndpointID,
manager.GetEndpointID,
manager.agentOptions.EdgeID,
manager.agentOptions.EdgeKey,
manager.agentOptions.EdgeAsyncMode,
agentPlatform,
manager.agentOptions.EdgeMetaFields,
Expand Down
1 change: 1 addition & 0 deletions edge/scheduler/logs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ func TestDataRace(t *testing.T) {
func(portainer.EndpointID) {},
func() portainer.EndpointID { return 1 },
"edgeID",
"edgeKey",
false,
agent.PlatformDocker,
agent.EdgeMetaFields{},
Expand Down
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ require (
github.com/pkg/errors v0.9.1
github.com/portainer/portainer v0.6.1-0.20241105012540-458d722d4754
github.com/rs/zerolog v1.29.0
github.com/stretchr/testify v1.9.0
github.com/stretchr/testify v1.10.0
github.com/wI2L/jsondiff v0.2.0
go.uber.org/mock v0.4.0
gopkg.in/alecthomas/kingpin.v2 v2.2.6
Expand Down Expand Up @@ -160,6 +160,7 @@ require (
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opentracing/opentracing-go v1.2.0 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.17.0 // indirect
Expand All @@ -168,6 +169,7 @@ require (
github.com/prometheus/procfs v0.12.0 // indirect
github.com/r3labs/sse v0.0.0-20210224172625-26fe804710bc // indirect
github.com/rivo/uniseg v0.4.4 // indirect
github.com/rogpeppe/go-internal v1.11.0 // indirect
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect
github.com/secure-systems-lab/go-securesystemslib v0.8.0 // indirect
github.com/segmentio/asm v1.1.3 // indirect
Expand Down
11 changes: 6 additions & 5 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -506,8 +506,9 @@ github.com/opencontainers/runtime-spec v1.2.0 h1:z97+pHb3uELt/yiAWD691HNHQIF07bE
github.com/opencontainers/runtime-spec v1.2.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/selinux v1.11.0 h1:+5Zbo97w3Lbmb3PeqQtpmTkMwsW5nRI3YaLpt7tQ7oU=
github.com/opencontainers/selinux v1.11.0/go.mod h1:E5dMC3VPuVvVHDYmi78qvhJp8+M586T4DlDRYpFkyec=
github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
Expand Down Expand Up @@ -552,8 +553,8 @@ github.com/r3labs/sse v0.0.0-20210224172625-26fe804710bc/go.mod h1:S8xSOnV3CgpNr
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.29.0 h1:Zes4hju04hjbvkVkOhdl2HpZa+0PmVwigmo8XoORE5w=
github.com/rs/zerolog v1.29.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0=
Expand Down Expand Up @@ -612,8 +613,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/theupdateframework/notary v0.7.0 h1:QyagRZ7wlSpjT5N2qQAh/pN+DVqgekv4DzbAiAiEL3c=
github.com/theupdateframework/notary v0.7.0/go.mod h1:c9DRxcmhHmVLDay4/2fUYdISnHqbFDGRSlXPO0AhYWw=
github.com/tidwall/gjson v1.14.0 h1:6aeJ0bzojgWLa82gDQHcx3S0Lr/O51I9bJ5nv6JFx5w=
Expand Down
Loading

0 comments on commit 2694f8a

Please sign in to comment.