Skip to content

Commit f00a013

Browse files
committed
CNJR-7307: Add network inspect check
1 parent 53279b3 commit f00a013

11 files changed

+511
-11
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
2828
- Resource usage check that captures the `top` command output from inside
2929
the container to the inspect report. CNJR-4618
3030
- Capture the `/etc/hosts` file from the container and the host. CNJR-7305
31+
- Capture the container runtime's `network inspect` output. CNJR-7307
3132

3233
## [0.4.2] - 2025-03-25
3334

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// Package checks defines all of the possible Conjur Inspect checks that can
2+
// be run.
3+
package checks
4+
5+
import (
6+
"fmt"
7+
"io"
8+
"strings"
9+
10+
"github.com/cyberark/conjur-inspect/pkg/check"
11+
"github.com/cyberark/conjur-inspect/pkg/container"
12+
"github.com/cyberark/conjur-inspect/pkg/log"
13+
)
14+
15+
// ContainerNetworkInspect collects the network inspection data from the
16+
// container runtime and saves it to the output store.
17+
type ContainerNetworkInspect struct {
18+
Provider container.ContainerProvider
19+
}
20+
21+
// Describe provides a textual description of what this check gathers info on
22+
func (cni *ContainerNetworkInspect) Describe() string {
23+
return fmt.Sprintf("%s network inspect", cni.Provider.Name())
24+
}
25+
26+
// Run performs the network inspection check
27+
func (cni *ContainerNetworkInspect) Run(runContext *check.RunContext) []check.Result {
28+
// Check if the container runtime is available
29+
runtimeKey := strings.ToLower(cni.Provider.Name())
30+
if !IsRuntimeAvailable(runContext, runtimeKey) {
31+
if runContext.VerboseErrors {
32+
return check.ErrorResult(
33+
cni,
34+
fmt.Errorf("container runtime not available"),
35+
)
36+
}
37+
return []check.Result{}
38+
}
39+
40+
networkInspectOutput, err := cni.Provider.NetworkInspect()
41+
if err != nil {
42+
if runContext.VerboseErrors {
43+
return check.ErrorResult(
44+
cni,
45+
fmt.Errorf("failed to inspect networks: %w", err),
46+
)
47+
}
48+
return []check.Result{}
49+
}
50+
51+
// Read the output to save it
52+
outputBytes, err := io.ReadAll(networkInspectOutput)
53+
if err != nil {
54+
if runContext.VerboseErrors {
55+
return check.ErrorResult(
56+
cni,
57+
fmt.Errorf("failed to read network inspect output: %w", err),
58+
)
59+
}
60+
return []check.Result{}
61+
}
62+
63+
// Save raw network inspect output
64+
outputFileName := fmt.Sprintf(
65+
"%s-network-inspect.json",
66+
strings.ToLower(cni.Provider.Name()),
67+
)
68+
_, err = runContext.OutputStore.Save(outputFileName, strings.NewReader(string(outputBytes)))
69+
if err != nil {
70+
log.Warn(
71+
"Failed to save %s network inspect output: %s",
72+
cni.Provider.Name(),
73+
err,
74+
)
75+
}
76+
77+
return []check.Result{}
78+
}
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
// Package checks defines all of the possible Conjur Inspect checks that can
2+
// be run.
3+
package checks
4+
5+
import (
6+
"errors"
7+
"io"
8+
"strings"
9+
"testing"
10+
11+
"github.com/cyberark/conjur-inspect/pkg/check"
12+
"github.com/cyberark/conjur-inspect/pkg/test"
13+
"github.com/stretchr/testify/assert"
14+
)
15+
16+
func TestContainerNetworkInspectRun(t *testing.T) {
17+
rawOutput := `[{"Name":"bridge","Id":"abc123"}]`
18+
19+
testCheck := &ContainerNetworkInspect{
20+
Provider: &test.ContainerProvider{
21+
NetworkInspectResult: strings.NewReader(rawOutput),
22+
},
23+
}
24+
25+
testOutputStore := test.NewOutputStore()
26+
27+
results := testCheck.Run(
28+
&check.RunContext{
29+
OutputStore: testOutputStore,
30+
ContainerRuntimeAvailability: map[string]check.RuntimeAvailability{
31+
"test container provider": {Available: true},
32+
},
33+
},
34+
)
35+
36+
assert.Empty(t, results)
37+
38+
outputStoreItems, err := testOutputStore.Items()
39+
assert.NoError(t, err)
40+
41+
assert.Equal(t, 1, len(outputStoreItems))
42+
43+
itemInfo, err := outputStoreItems[0].Info()
44+
assert.NoError(t, err)
45+
assert.Equal(t, "test container provider-network-inspect.json", itemInfo.Name())
46+
47+
reader, cleanup, err := outputStoreItems[0].Open()
48+
assert.NoError(t, err)
49+
defer cleanup()
50+
51+
output, err := io.ReadAll(reader)
52+
assert.NoError(t, err)
53+
54+
assert.Equal(t, rawOutput, string(output))
55+
}
56+
57+
func TestContainerNetworkInspectRunEmpty(t *testing.T) {
58+
rawOutput := `[]`
59+
60+
testCheck := &ContainerNetworkInspect{
61+
Provider: &test.ContainerProvider{
62+
NetworkInspectResult: strings.NewReader(rawOutput),
63+
},
64+
}
65+
66+
testOutputStore := test.NewOutputStore()
67+
68+
results := testCheck.Run(
69+
&check.RunContext{
70+
OutputStore: testOutputStore,
71+
ContainerRuntimeAvailability: map[string]check.RuntimeAvailability{
72+
"test container provider": {Available: true},
73+
},
74+
},
75+
)
76+
77+
assert.Empty(t, results)
78+
79+
outputStoreItems, err := testOutputStore.Items()
80+
assert.NoError(t, err)
81+
82+
assert.Equal(t, 1, len(outputStoreItems))
83+
84+
_, err = outputStoreItems[0].Info()
85+
assert.NoError(t, err)
86+
87+
reader, cleanup, err := outputStoreItems[0].Open()
88+
assert.NoError(t, err)
89+
defer cleanup()
90+
91+
output, err := io.ReadAll(reader)
92+
assert.NoError(t, err)
93+
94+
assert.Equal(t, rawOutput, string(output))
95+
}
96+
97+
func TestContainerNetworkInspectRunError(t *testing.T) {
98+
testCheck := &ContainerNetworkInspect{
99+
Provider: &test.ContainerProvider{
100+
NetworkInspectError: errors.New("network inspect failed"),
101+
},
102+
}
103+
104+
results := testCheck.Run(
105+
&check.RunContext{
106+
ContainerRuntimeAvailability: map[string]check.RuntimeAvailability{
107+
"test container provider": {Available: true},
108+
},
109+
},
110+
)
111+
112+
assert.Empty(t, results)
113+
}
114+
115+
func TestContainerNetworkInspectRunErrorVerboseErrors(t *testing.T) {
116+
testCheck := &ContainerNetworkInspect{
117+
Provider: &test.ContainerProvider{
118+
NetworkInspectError: errors.New("network inspect failed"),
119+
},
120+
}
121+
122+
results := testCheck.Run(
123+
&check.RunContext{
124+
VerboseErrors: true,
125+
ContainerRuntimeAvailability: map[string]check.RuntimeAvailability{
126+
"test container provider": {Available: true},
127+
},
128+
},
129+
)
130+
131+
assert.Len(t, results, 1)
132+
assert.Equal(t, check.StatusError, results[0].Status)
133+
assert.Contains(t, results[0].Message, "network inspect failed")
134+
}
135+
136+
func TestContainerNetworkInspectRuntimeNotAvailable(t *testing.T) {
137+
testCheck := &ContainerNetworkInspect{
138+
Provider: &test.ContainerProvider{},
139+
}
140+
141+
results := testCheck.Run(
142+
&check.RunContext{
143+
ContainerRuntimeAvailability: map[string]check.RuntimeAvailability{
144+
"test container provider": {Available: false},
145+
},
146+
},
147+
)
148+
149+
assert.Empty(t, results)
150+
}
151+
152+
func TestContainerNetworkInspectRuntimeNotAvailableVerboseErrors(t *testing.T) {
153+
testCheck := &ContainerNetworkInspect{
154+
Provider: &test.ContainerProvider{},
155+
}
156+
157+
results := testCheck.Run(
158+
&check.RunContext{
159+
VerboseErrors: true,
160+
ContainerRuntimeAvailability: map[string]check.RuntimeAvailability{
161+
"test container provider": {Available: false},
162+
},
163+
},
164+
)
165+
166+
assert.Len(t, results, 1)
167+
assert.Equal(t, check.StatusError, results[0].Status)
168+
assert.Contains(t, results[0].Message, "runtime not available")
169+
}

pkg/checks/etcd_perf_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ type mockExecResult struct {
2727

2828
func (m *mockContainerProvider) Name() string { return "MockProvider" }
2929
func (m *mockContainerProvider) Info() (container.ContainerProviderInfo, error) { return nil, nil }
30+
func (m *mockContainerProvider) NetworkInspect() (io.Reader, error) { return nil, nil }
3031
func (m *mockContainerProvider) Container(id string) container.Container {
3132
return &mockContainer{
3233
execMap: m.execMap,

pkg/cmd/default_report.go

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,27 @@ func defaultReportSections() []report.Section {
7474
&checks.Host{},
7575
&checks.CommandHistory{},
7676
&checks.HostEtcHosts{},
77+
78+
// Check container runtime availability before runtime checks to
79+
// cache the results.
80+
&checks.ContainerAvailability{},
81+
82+
// Runtime
83+
&checks.ContainerRuntime{
84+
Provider: &container.DockerProvider{},
85+
},
86+
&checks.ContainerRuntime{
87+
Provider: &container.PodmanProvider{},
88+
},
89+
90+
// Network
91+
&checks.ContainerNetworkInspect{
92+
Provider: &container.DockerProvider{},
93+
},
94+
&checks.ContainerNetworkInspect{
95+
Provider: &container.PodmanProvider{},
96+
},
97+
7798
},
7899
},
79100
{
@@ -85,17 +106,6 @@ func defaultReportSections() []report.Section {
85106
{
86107
Title: "Container",
87108
Checks: []check.Check{
88-
// Check container runtime availability first to cache the results
89-
&checks.ContainerAvailability{},
90-
91-
// Runtime
92-
&checks.ContainerRuntime{
93-
Provider: &container.DockerProvider{},
94-
},
95-
&checks.ContainerRuntime{
96-
Provider: &container.PodmanProvider{},
97-
},
98-
99109
// Container inspect
100110
&checks.ContainerInspect{
101111
Provider: &container.DockerProvider{},

pkg/container/container_provider.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ type ContainerProvider interface {
1515
Name() string
1616
Info() (ContainerProviderInfo, error)
1717
Container(containerID string) Container
18+
NetworkInspect() (io.Reader, error)
1819
}
1920

2021
// Container is an interface for a container instance

pkg/container/docker_provider.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313

1414
// Function variable for dependency injection
1515
var executeDockerInfoFunc = executeDockerInfo
16+
var executeDockerNetworkInspectFunc = executeDockerNetworkInspect
1617

1718
// DockerProvider is a concrete implementation of the
1819
// ContainerProvider interface for Docker
@@ -69,6 +70,11 @@ func (*DockerProvider) Container(containerID string) Container {
6970
return &DockerContainer{ContainerID: containerID}
7071
}
7172

73+
// NetworkInspect returns the JSON output of all Docker networks
74+
func (*DockerProvider) NetworkInspect() (io.Reader, error) {
75+
return executeDockerNetworkInspectFunc()
76+
}
77+
7278
func executeDockerInfo() (stdout, stderr io.Reader, err error) {
7379
return shell.NewCommandWrapper(
7480
"docker",
@@ -78,3 +84,49 @@ func executeDockerInfo() (stdout, stderr io.Reader, err error) {
7884
"{{json .}}",
7985
).Run()
8086
}
87+
88+
func executeDockerNetworkInspect() (io.Reader, error) {
89+
// First, get the list of network IDs
90+
stdout, stderr, err := shell.NewCommandWrapper(
91+
"docker",
92+
"network",
93+
"ls",
94+
"-q",
95+
).Run()
96+
97+
if err != nil {
98+
return nil, fmt.Errorf(
99+
"failed to list Docker networks: %w (%s)",
100+
err,
101+
strings.TrimSpace(shell.ReadOrDefault(stderr, "N/A")),
102+
)
103+
}
104+
105+
// Read the network IDs
106+
networkIDsBytes, err := io.ReadAll(stdout)
107+
if err != nil {
108+
return nil, fmt.Errorf("failed to read Docker network IDs: %w", err)
109+
}
110+
111+
networkIDs := strings.TrimSpace(string(networkIDsBytes))
112+
113+
// If there are no networks, return empty JSON array
114+
if networkIDs == "" {
115+
return strings.NewReader("[]"), nil
116+
}
117+
118+
// Split network IDs and inspect them all at once
119+
ids := strings.Fields(networkIDs)
120+
args := append([]string{"network", "inspect"}, ids...)
121+
122+
stdout, stderr, err = shell.NewCommandWrapper("docker", args...).Run()
123+
if err != nil {
124+
return nil, fmt.Errorf(
125+
"failed to inspect Docker networks: %w (%s)",
126+
err,
127+
strings.TrimSpace(shell.ReadOrDefault(stderr, "N/A")),
128+
)
129+
}
130+
131+
return stdout, nil
132+
}

0 commit comments

Comments
 (0)