Skip to content

Commit a0c144b

Browse files
Tomasz WojcikGitHub Enterprise
authored andcommitted
Merge pull request #20 from Conjur-Enterprise/cnjr-8736-ruby-thread-dumps
CNJR-8736: Container process list and Ruby thread dumps in report
2 parents ac16e4c + 361c328 commit a0c144b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1630
-52
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
2121
inside a container to the inspect report. CNJR-11978
2222
- Etcd cluster members check that captures the current cluster members
2323
(as reported by `evoke cluster member list`) in the inspect report. CNJR-11979
24+
- Ruby thread dump check that captures thread dumps from Ruby processes running
25+
in the container. CNJR-8736
26+
- Container processes check that records the process list from inside the
27+
container to the inspect report. CNJR-4619
2428

2529
## [0.4.2] - 2025-03-25
2630

pkg/check/check.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,19 @@ type RunContext struct {
3535

3636
ContainerID string
3737
Since time.Duration
38+
39+
// ContainerRuntimeAvailability caches the availability status of container runtimes
40+
// Maps provider names (e.g., "docker", "podman") to availability status and error
41+
ContainerRuntimeAvailability map[string]RuntimeAvailability
42+
43+
// VerboseErrors controls whether to report errors for unavailable container runtimes
44+
VerboseErrors bool
45+
}
46+
47+
// RuntimeAvailability represents the availability status of a container runtime
48+
type RuntimeAvailability struct {
49+
Available bool
50+
Error error // Error encountered when checking availability (e.g., executable not found)
3851
}
3952

4053
// Result is the outcome of a particular check. A check may produce multiple

pkg/checks/conjur_config.go

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,18 @@ func (cc *ConjurConfig) Run(runContext *check.RunContext) []check.Result {
3434
return []check.Result{}
3535
}
3636

37+
// Check if the container runtime is available
38+
runtimeKey := strings.ToLower(cc.Provider.Name())
39+
if !IsRuntimeAvailable(runContext, runtimeKey) {
40+
if runContext.VerboseErrors {
41+
return check.ErrorResult(
42+
cc,
43+
fmt.Errorf("container runtime not available"),
44+
)
45+
}
46+
return []check.Result{}
47+
}
48+
3749
container := cc.Provider.Container(runContext.ContainerID)
3850

3951
configPaths := []string{
@@ -74,10 +86,13 @@ func (cc *ConjurConfig) collectConfigFile(
7486
)
7587

7688
if err != nil {
89+
if !runContext.VerboseErrors {
90+
return nil
91+
}
7792
return &check.Result{
78-
Title: cc.Describe(),
79-
Status: check.StatusError,
80-
Value: "N/A",
93+
Title: cc.Describe(),
94+
Status: check.StatusError,
95+
Value: "N/A",
8196
Message: fmt.Sprintf(
8297
"failed to collect '%s' : %s (%s))",
8398
path,

pkg/checks/conjur_config_permissions.go

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,18 @@ func (ccp *ConjurConfigPermissions) Run(runContext *check.RunContext) []check.Re
3131
return []check.Result{}
3232
}
3333

34+
// Check if the container runtime is available
35+
runtimeKey := strings.ToLower(ccp.Provider.Name())
36+
if !IsRuntimeAvailable(runContext, runtimeKey) {
37+
if runContext.VerboseErrors {
38+
return check.ErrorResult(
39+
ccp,
40+
fmt.Errorf("container runtime not available"),
41+
)
42+
}
43+
return []check.Result{}
44+
}
45+
3446
container := ccp.Provider.Container(runContext.ContainerID)
3547

3648
results := []check.Result{}
@@ -53,9 +65,9 @@ func (ccp *ConjurConfigPermissions) collectConjurConfigPermissions(
5365

5466
if err != nil {
5567
return &check.Result{
56-
Title: ccp.Describe(),
57-
Status: check.StatusError,
58-
Value: "N/A",
68+
Title: ccp.Describe(),
69+
Status: check.StatusError,
70+
Value: "N/A",
5971
Message: fmt.Sprintf(
6072
"failed to collect Conjur config permissions: %s (%s))",
6173
err,

pkg/checks/conjur_config_permissions_test.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ func TestConjurConfigPermissions_Run_ExecError(t *testing.T) {
6060
provider := &test.ContainerProvider{
6161
ExecResponses: map[string]test.ExecResponse{
6262
"ls -la /etc/conjur/config": {
63-
Error: errors.New("file not found"),
63+
Error: errors.New("file not found"),
6464
Stderr: strings.NewReader("permission denied"),
6565
},
6666
},
@@ -76,7 +76,6 @@ func TestConjurConfigPermissions_Run_ExecError(t *testing.T) {
7676
assert.Contains(t, results[0].Message, "failed to collect")
7777
}
7878

79-
8079
func TestConjurConfigPermissions_Run_ReadAllError(t *testing.T) {
8180
// Save original readAllFunc to restore after test
8281
originalReadAllFunc := readAllFunc

pkg/checks/conjur_config_test.go

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ func TestConjurConfig_Run_FileReadError(t *testing.T) {
8383
provider := &test.ContainerProvider{
8484
ExecResponses: map[string]test.ExecResponse{
8585
"cat /etc/conjur/config/conjur.yml": {
86-
Error: errors.New("file not found"),
86+
Error: errors.New("file not found"),
8787
Stderr: strings.NewReader("permission denied"),
8888
},
8989
},
@@ -92,13 +92,34 @@ func TestConjurConfig_Run_FileReadError(t *testing.T) {
9292
cc := &ConjurConfig{Provider: provider}
9393

9494
runContext := test.NewRunContext("test-container")
95+
runContext.VerboseErrors = true
9596
results := cc.Run(&runContext)
9697

9798
assert.NotEmpty(t, results)
9899
assert.Equal(t, check.StatusError, results[0].Status)
99100
assert.Contains(t, results[0].Message, "failed to collect")
100101
}
101102

103+
func TestConjurConfig_Run_FileReadErrorNoVerboseErrors(t *testing.T) {
104+
provider := &test.ContainerProvider{
105+
ExecResponses: map[string]test.ExecResponse{
106+
"cat /etc/conjur/config/conjur.yml": {
107+
Error: errors.New("file not found"),
108+
Stderr: strings.NewReader("permission denied"),
109+
},
110+
},
111+
}
112+
113+
cc := &ConjurConfig{Provider: provider}
114+
115+
runContext := test.NewRunContext("test-container")
116+
runContext.VerboseErrors = false
117+
results := cc.Run(&runContext)
118+
119+
// Expect no results when VerboseErrors is false
120+
assert.Empty(t, results)
121+
}
122+
102123
func TestConjurConfig_Run_ReadAllError(t *testing.T) {
103124
// Save original readAllFunc to restore after test
104125
originalReadAllFunc := readAllFunc

pkg/checks/conjur_health.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,18 @@ func (ch *ConjurHealth) Run(runContext *check.RunContext) []check.Result {
3939
return []check.Result{}
4040
}
4141

42+
// Check if the container runtime is available
43+
runtimeKey := strings.ToLower(ch.Provider.Name())
44+
if !IsRuntimeAvailable(runContext, runtimeKey) {
45+
if runContext.VerboseErrors {
46+
return check.ErrorResult(
47+
ch,
48+
fmt.Errorf("container runtime not available"),
49+
)
50+
}
51+
return []check.Result{}
52+
}
53+
4254
container := ch.Provider.Container(runContext.ContainerID)
4355
stdout, stderr, err := container.Exec(
4456
"curl", "-k", "https://localhost/health",

pkg/checks/conjur_health_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ func TestConjurHealthRun_ExecError(t *testing.T) {
9999
ExecResponses: map[string]test.ExecResponse{
100100
"curl -k https://localhost/health": test.ExecResponse{
101101
Stderr: strings.NewReader("test stderr"),
102-
Error: errors.New("test error"),
102+
Error: errors.New("test error"),
103103
},
104104
},
105105
}

pkg/checks/conjur_info.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,18 @@ func (ci *ConjurInfo) Run(runContext *check.RunContext) []check.Result {
3939
return []check.Result{}
4040
}
4141

42+
// Check if the container runtime is available
43+
runtimeKey := strings.ToLower(ci.Provider.Name())
44+
if !IsRuntimeAvailable(runContext, runtimeKey) {
45+
if runContext.VerboseErrors {
46+
return check.ErrorResult(
47+
ci,
48+
fmt.Errorf("container runtime not available"),
49+
)
50+
}
51+
return []check.Result{}
52+
}
53+
4254
container := ci.Provider.Container(runContext.ContainerID)
4355

4456
stdout, stderr, err := container.Exec(
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package checks
2+
3+
import (
4+
"fmt"
5+
"os/exec"
6+
7+
"github.com/cyberark/conjur-inspect/pkg/check"
8+
"github.com/cyberark/conjur-inspect/pkg/log"
9+
)
10+
11+
// ContainerAvailability checks for the availability of container runtimes
12+
// (Docker and Podman) and caches the results in the RunContext to prevent
13+
// duplicate error messages for unavailable runtimes.
14+
type ContainerAvailability struct{}
15+
16+
// Describe provides a textual description of what this check gathers info on
17+
func (ca *ContainerAvailability) Describe() string {
18+
return "Container runtime availability"
19+
}
20+
21+
// Run checks the availability of Docker and Podman runtimes
22+
func (ca *ContainerAvailability) Run(runContext *check.RunContext) []check.Result {
23+
// If not already initialized, this shouldn't happen but be safe
24+
if runContext.ContainerRuntimeAvailability == nil {
25+
runContext.ContainerRuntimeAvailability = make(map[string]check.RuntimeAvailability)
26+
}
27+
28+
// Check Docker availability
29+
dockerAvailability := ca.checkRuntimeAvailability("docker")
30+
runContext.ContainerRuntimeAvailability["docker"] = dockerAvailability
31+
32+
// Check Podman availability
33+
podmanAvailability := ca.checkRuntimeAvailability("podman")
34+
runContext.ContainerRuntimeAvailability["podman"] = podmanAvailability
35+
36+
results := []check.Result{}
37+
38+
// Log availability for debugging
39+
if dockerAvailability.Available {
40+
log.Debug("Docker runtime is available")
41+
} else {
42+
log.Debug("Docker runtime is not available: %v", dockerAvailability.Error)
43+
}
44+
45+
if podmanAvailability.Available {
46+
log.Debug("Podman runtime is available")
47+
} else {
48+
log.Debug("Podman runtime is not available: %v", podmanAvailability.Error)
49+
}
50+
51+
// Only return results in verbose mode or if no runtimes are available
52+
if runContext.VerboseErrors {
53+
if !dockerAvailability.Available {
54+
results = append(results, check.Result{
55+
Title: "Docker availability",
56+
Status: check.StatusWarn,
57+
Value: "N/A",
58+
Message: fmt.Sprintf("Docker is not available: %v", dockerAvailability.Error),
59+
})
60+
}
61+
62+
if !podmanAvailability.Available {
63+
results = append(results, check.Result{
64+
Title: "Podman availability",
65+
Status: check.StatusWarn,
66+
Value: "N/A",
67+
Message: fmt.Sprintf("Podman is not available: %v", podmanAvailability.Error),
68+
})
69+
}
70+
} else if !dockerAvailability.Available && !podmanAvailability.Available {
71+
// Warn if no container runtimes are available
72+
results = append(results, check.Result{
73+
Title: "Container runtimes",
74+
Status: check.StatusWarn,
75+
Value: "N/A",
76+
Message: "No container runtimes (Docker or Podman) are available. Container-related checks will be skipped.",
77+
})
78+
}
79+
80+
return results
81+
}
82+
83+
// checkRuntimeAvailability checks if a runtime executable is available
84+
func (ca *ContainerAvailability) checkRuntimeAvailability(runtimeName string) check.RuntimeAvailability {
85+
_, err := exec.LookPath(runtimeName)
86+
if err != nil {
87+
return check.RuntimeAvailability{
88+
Available: false,
89+
Error: err,
90+
}
91+
}
92+
return check.RuntimeAvailability{
93+
Available: true,
94+
Error: nil,
95+
}
96+
}
97+
98+
// IsRuntimeAvailable is a helper function to check if a runtime is available
99+
// from any check
100+
func IsRuntimeAvailable(runContext *check.RunContext, runtimeName string) bool {
101+
if runContext.ContainerRuntimeAvailability == nil {
102+
return true // Assume available if cache not initialized
103+
}
104+
availability, exists := runContext.ContainerRuntimeAvailability[runtimeName]
105+
if !exists {
106+
return true // Assume available if not cached
107+
}
108+
return availability.Available
109+
}

0 commit comments

Comments
 (0)