Skip to content

Commit 7fc0481

Browse files
Merge pull request #707 from jetstack/VC-43403-outputmode-integration-tests
CyberArk: Get `--input-file` working with machinehub mode
2 parents f3db885 + d825569 commit 7fc0481

File tree

7 files changed

+239
-32
lines changed

7 files changed

+239
-32
lines changed

api/datareading.go

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
package api
22

33
import (
4+
"bytes"
45
"encoding/json"
6+
"fmt"
57
"time"
68

9+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
710
"k8s.io/apimachinery/pkg/version"
811
)
912

@@ -26,12 +29,64 @@ type DataReading struct {
2629
SchemaVersion string `json:"schema_version"`
2730
}
2831

32+
// UnmarshalJSON implements the json.Unmarshaler interface for DataReading.
33+
// It handles the dynamic parsing of the Data field based on the DataGatherer.
34+
func (o *DataReading) UnmarshalJSON(data []byte) error {
35+
var tmp struct {
36+
ClusterID string `json:"cluster_id,omitempty"`
37+
DataGatherer string `json:"data-gatherer"`
38+
Timestamp Time `json:"timestamp"`
39+
Data json.RawMessage `json:"data"`
40+
SchemaVersion string `json:"schema_version"`
41+
}
42+
43+
d := json.NewDecoder(bytes.NewReader(data))
44+
d.DisallowUnknownFields()
45+
46+
if err := d.Decode(&tmp); err != nil {
47+
return err
48+
}
49+
o.ClusterID = tmp.ClusterID
50+
o.DataGatherer = tmp.DataGatherer
51+
o.Timestamp = tmp.Timestamp
52+
o.SchemaVersion = tmp.SchemaVersion
53+
54+
{
55+
var discoveryData DiscoveryData
56+
d := json.NewDecoder(bytes.NewReader(tmp.Data))
57+
d.DisallowUnknownFields()
58+
if err := d.Decode(&discoveryData); err == nil {
59+
o.Data = &discoveryData
60+
return nil
61+
}
62+
}
63+
{
64+
var dynamicData DynamicData
65+
d := json.NewDecoder(bytes.NewReader(tmp.Data))
66+
d.DisallowUnknownFields()
67+
if err := d.Decode(&dynamicData); err == nil {
68+
o.Data = &dynamicData
69+
return nil
70+
}
71+
}
72+
{
73+
var genericData map[string]interface{}
74+
d := json.NewDecoder(bytes.NewReader(tmp.Data))
75+
d.DisallowUnknownFields()
76+
if err := d.Decode(&genericData); err == nil {
77+
o.Data = genericData
78+
return nil
79+
}
80+
}
81+
return fmt.Errorf("failed to parse DataReading.Data for gatherer %s", o.DataGatherer)
82+
}
83+
2984
// GatheredResource wraps the raw k8s resource that is sent to the jetstack secure backend
3085
type GatheredResource struct {
3186
// Resource is a reference to a k8s object that was found by the informer
3287
// should be of type unstructured.Unstructured, raw Object
33-
Resource interface{}
34-
DeletedAt Time
88+
Resource interface{} `json:"resource"`
89+
DeletedAt Time `json:"deleted_at,omitempty"`
3590
}
3691

3792
func (v GatheredResource) MarshalJSON() ([]byte, error) {
@@ -51,6 +106,23 @@ func (v GatheredResource) MarshalJSON() ([]byte, error) {
51106
return json.Marshal(data)
52107
}
53108

109+
func (v *GatheredResource) UnmarshalJSON(data []byte) error {
110+
var tmpResource struct {
111+
Resource *unstructured.Unstructured `json:"resource"`
112+
DeletedAt Time `json:"deleted_at,omitempty"`
113+
}
114+
115+
d := json.NewDecoder(bytes.NewReader(data))
116+
d.DisallowUnknownFields()
117+
118+
if err := d.Decode(&tmpResource); err != nil {
119+
return err
120+
}
121+
v.Resource = tmpResource.Resource
122+
v.DeletedAt = tmpResource.DeletedAt
123+
return nil
124+
}
125+
54126
// DynamicData is the DataReading.Data returned by the k8s.DataGathererDynamic
55127
// gatherer
56128
type DynamicData struct {

cmd/agent_test.go

Lines changed: 58 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,49 +3,81 @@ package cmd
33
import (
44
"bytes"
55
"context"
6+
"fmt"
67
"os"
78
"os/exec"
9+
"path/filepath"
810
"testing"
911
"time"
1012

1113
"github.com/stretchr/testify/require"
14+
15+
arktesting "github.com/jetstack/preflight/internal/cyberark/testing"
1216
)
1317

14-
// TestAgentRunOneShot runs the agent in `--one-shot` mode and verifies that it exits
15-
// after the first data gathering iteration.
16-
func TestAgentRunOneShot(t *testing.T) {
18+
// TestOutputModes tests the different output modes of the agent command.
19+
// It does this by running the agent command in a subprocess with the
20+
// appropriate flags and configuration files.
21+
// It assumes that the test is being run from the "cmd" directory and that
22+
// the repository root is the parent directory of the current working directory.
23+
func TestOutputModes(t *testing.T) {
24+
repoRoot := findRepoRoot(t)
25+
26+
t.Run("localfile", func(t *testing.T) {
27+
runSubprocess(t, repoRoot, []string{
28+
"--agent-config-file", filepath.Join(repoRoot, "examples/localfile/config.yaml"),
29+
"--input-path", filepath.Join(repoRoot, "examples/localfile/input.json"),
30+
"--output-path", "/dev/null",
31+
})
32+
})
33+
34+
t.Run("machinehub", func(t *testing.T) {
35+
arktesting.SkipIfNoEnv(t)
36+
runSubprocess(t, repoRoot, []string{
37+
"--agent-config-file", filepath.Join(repoRoot, "examples/machinehub/config.yaml"),
38+
"--input-path", filepath.Join(repoRoot, "examples/machinehub/input.json"),
39+
"--machine-hub",
40+
})
41+
})
42+
}
43+
44+
// findRepoRoot returns the absolute path to the repository root.
45+
// It assumes that the test is being run from the "cmd" directory.
46+
func findRepoRoot(t *testing.T) string {
47+
cwd, err := os.Getwd()
48+
require.NoError(t, err)
49+
repoRoot, err := filepath.Abs(filepath.Join(cwd, ".."))
50+
require.NoError(t, err)
51+
return repoRoot
52+
}
53+
54+
// runSubprocess runs the current test in a subprocess with the given args.
55+
// It sets the GO_CHILD environment variable to indicate to the subprocess
56+
// that it should run the main function instead of the test function.
57+
// It captures and logs the stdout and stderr of the subprocess.
58+
// It fails the test if the subprocess exits with a non-zero status.
59+
// It uses a timeout to avoid hanging indefinitely.
60+
func runSubprocess(t *testing.T, repoRoot string, args []string) {
1761
if _, found := os.LookupEnv("GO_CHILD"); found {
18-
os.Args = []string{
62+
os.Args = append([]string{
1963
"preflight",
2064
"agent",
65+
"--log-level", "6",
2166
"--one-shot",
22-
"--agent-config-file=testdata/agent/one-shot/success/config.yaml",
23-
"--input-path=testdata/agent/one-shot/success/input.json",
24-
"--output-path=/dev/null",
25-
"-v=9",
26-
}
67+
}, args...)
2768
Execute()
2869
return
2970
}
30-
t.Log("Running child process")
31-
ctx, cancel := context.WithTimeout(t.Context(), time.Second*3)
71+
t.Log("Running child process", os.Args[0], "-test.run=^"+t.Name()+"$")
72+
ctx, cancel := context.WithTimeout(t.Context(), time.Second*10)
3273
defer cancel()
33-
cmd := exec.CommandContext(ctx, os.Args[0], "-test.run=^TestAgentRunOneShot$")
34-
var (
35-
stdout bytes.Buffer
36-
stderr bytes.Buffer
37-
)
74+
cmd := exec.CommandContext(ctx, os.Args[0], "-test.run=^"+t.Name()+"$")
75+
var stdout, stderr bytes.Buffer
3876
cmd.Stdout = &stdout
3977
cmd.Stderr = &stderr
40-
cmd.Env = append(
41-
os.Environ(),
42-
"GO_CHILD=true",
43-
)
78+
cmd.Env = append(os.Environ(), "GO_CHILD=true")
4479
err := cmd.Run()
45-
46-
stdoutStr := stdout.String()
47-
stderrStr := stderr.String()
48-
t.Logf("STDOUT\n%s\n", stdoutStr)
49-
t.Logf("STDERR\n%s\n", stderrStr)
50-
require.NoError(t, err, context.Cause(ctx))
80+
t.Logf("STDOUT\n%s\n", stdout.String())
81+
t.Logf("STDERR\n%s\n", stderr.String())
82+
require.NoError(t, err, fmt.Sprintf("Error: %v\nSTDERR: %s", err, stderr.String()))
5183
}

cmd/testdata/agent/one-shot/success/config.yaml

Lines changed: 0 additions & 4 deletions
This file was deleted.

examples/localfile/config.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# No config is required to run the agent with an input file and an output file.
File renamed without changes.

examples/machinehub/config.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Not used

examples/machinehub/input.json

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
[
2+
{
3+
"data-gatherer": "ark/discovery",
4+
"data": {
5+
"cluster_id": "0e069229-d83b-4075-a4c8-95838ff5c437",
6+
"server_version": {
7+
"gitVersion": "v1.27.6"
8+
}
9+
}
10+
},
11+
{
12+
"data-gatherer": "ark/secrets",
13+
"data": {
14+
"items": [
15+
{
16+
"resource": {
17+
"kind": "Secret",
18+
"apiVersion": "v1",
19+
"metadata": {
20+
"name": "app-1-secret-1",
21+
"namespace": "team-1"
22+
}
23+
}
24+
}
25+
]
26+
}
27+
},
28+
{
29+
"data-gatherer": "ark/pods",
30+
"data": {
31+
"items": [
32+
{
33+
"resource": {
34+
"kind": "Pod",
35+
"apiVersion": "v1",
36+
"metadata": {
37+
"name": "app-1-pod-1",
38+
"namespace": "team-1"
39+
}
40+
}
41+
}
42+
]
43+
}
44+
},
45+
{
46+
"data-gatherer": "ark/statefulsets",
47+
"data": {
48+
"items": []
49+
}
50+
},
51+
{
52+
"data-gatherer": "ark/deployments",
53+
"data": {
54+
"items": []
55+
}
56+
},
57+
{
58+
"data-gatherer": "ark/clusterroles",
59+
"data": {
60+
"items": []
61+
}
62+
},
63+
{
64+
"data-gatherer": "ark/roles",
65+
"data": {
66+
"items": []
67+
}
68+
},
69+
{
70+
"data-gatherer": "ark/clusterrolebindings",
71+
"data": {
72+
"items": []
73+
}
74+
},
75+
{
76+
"data-gatherer": "ark/rolebindings",
77+
"data": {
78+
"items": []
79+
}
80+
},
81+
{
82+
"data-gatherer": "ark/cronjobs",
83+
"data": {
84+
"items": []
85+
}
86+
},
87+
{
88+
"data-gatherer": "ark/jobs",
89+
"data": {
90+
"items": []
91+
}
92+
},
93+
{
94+
"data-gatherer": "ark/daemonsets",
95+
"data": {
96+
"items": []
97+
}
98+
},
99+
{
100+
"data-gatherer": "ark/serviceaccounts",
101+
"data": {
102+
"items": []
103+
}
104+
}
105+
]

0 commit comments

Comments
 (0)