Skip to content

Commit 20ec1c5

Browse files
feat(client): refactor data reading conversion and add unit tests
- Replaced `ConvertDataReadingsToCyberarkSnapshot` with a generic `convertDataReadings` function. - Introduced `defaultExtractorFunctions` to map data gatherers to extraction logic. - Added nil checks in `extractServerVersionFromReading` and `extractResourceListFromReading`. - Enhanced test coverage with unit tests for `convertDataReadings`, `extractServerVersionFromReading`, and `extractResourceListFromReading`. - Added helper functions for parsing and handling test data, including `parseDataReadings`, `readGZIP`, and `writeGZIP`. Signed-off-by: Richard Wall <[email protected]>
1 parent 7d79032 commit 20ec1c5

File tree

3 files changed

+406
-210
lines changed

3 files changed

+406
-210
lines changed

pkg/client/client_cyberark.go

Lines changed: 68 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ func (o *CyberArkClient) PostDataReadingsWithOptions(ctx context.Context, readin
5151
return fmt.Errorf("while initializing data upload client: %s", err)
5252
}
5353
var snapshot dataupload.Snapshot
54-
if err := ConvertDataReadingsToCyberarkSnapshot(readings, &snapshot); err != nil {
54+
if err := convertDataReadings(defaultExtractorFunctions, readings, &snapshot); err != nil {
5555
return fmt.Errorf("while converting data readings: %s", err)
5656
}
5757
// Temporary hard coded cluster ID.
@@ -68,6 +68,9 @@ func (o *CyberArkClient) PostDataReadingsWithOptions(ctx context.Context, readin
6868
// extractServerVersionFromReading converts the opaque data from a DiscoveryData
6969
// data reading to allow access to the Kubernetes version fields within.
7070
func extractServerVersionFromReading(reading *api.DataReading, target *string) error {
71+
if reading == nil {
72+
return fmt.Errorf("programmer mistake: the DataReading must not be nil")
73+
}
7174
data, ok := reading.Data.(*api.DiscoveryData)
7275
if !ok {
7376
return fmt.Errorf(
@@ -85,6 +88,9 @@ func extractServerVersionFromReading(reading *api.DataReading, target *string) e
8588
// data reading to runtime.Object resources, to allow access to the metadata and
8689
// other kubernetes API fields.
8790
func extractResourceListFromReading(reading *api.DataReading, target *[]runtime.Object) error {
91+
if reading == nil {
92+
return fmt.Errorf("programmer mistake: the DataReading must not be nil")
93+
}
8894
data, ok := reading.Data.(*api.DynamicData)
8995
if !ok {
9096
return fmt.Errorf(
@@ -105,75 +111,80 @@ func extractResourceListFromReading(reading *api.DataReading, target *[]runtime.
105111
return nil
106112
}
107113

108-
var expectedGathererNames = []string{
109-
"ark/secrets",
110-
"ark/serviceaccounts",
111-
"ark/roles",
112-
"ark/clusterroles",
113-
"ark/rolebindings",
114-
"ark/clusterrolebindings",
115-
"ark/jobs",
116-
"ark/cronjobs",
117-
"ark/deployments",
118-
"ark/statefulsets",
119-
"ark/daemonsets",
120-
"ark/pods",
114+
var defaultExtractorFunctions = map[string]func(*api.DataReading, *dataupload.Snapshot) error{
115+
"ark/discovery": func(r *api.DataReading, s *dataupload.Snapshot) error {
116+
return extractServerVersionFromReading(r, &s.K8SVersion)
117+
},
118+
"ark/secrets": func(r *api.DataReading, s *dataupload.Snapshot) error {
119+
return extractResourceListFromReading(r, &s.Secrets)
120+
},
121+
"ark/serviceaccounts": func(r *api.DataReading, s *dataupload.Snapshot) error {
122+
return extractResourceListFromReading(r, &s.ServiceAccounts)
123+
},
124+
"ark/roles": func(r *api.DataReading, s *dataupload.Snapshot) error {
125+
return extractResourceListFromReading(r, &s.Roles)
126+
},
127+
"ark/clusterroles": func(r *api.DataReading, s *dataupload.Snapshot) error {
128+
return extractResourceListFromReading(r, &s.ClusterRoles)
129+
},
130+
"ark/rolebindings": func(r *api.DataReading, s *dataupload.Snapshot) error {
131+
return extractResourceListFromReading(r, &s.RoleBindings)
132+
},
133+
"ark/clusterrolebindings": func(r *api.DataReading, s *dataupload.Snapshot) error {
134+
return extractResourceListFromReading(r, &s.ClusterRoleBindings)
135+
},
136+
"ark/jobs": func(r *api.DataReading, s *dataupload.Snapshot) error {
137+
return extractResourceListFromReading(r, &s.Jobs)
138+
},
139+
"ark/cronjobs": func(r *api.DataReading, s *dataupload.Snapshot) error {
140+
return extractResourceListFromReading(r, &s.CronJobs)
141+
},
142+
"ark/deployments": func(r *api.DataReading, s *dataupload.Snapshot) error {
143+
return extractResourceListFromReading(r, &s.Deployments)
144+
},
145+
"ark/statefulsets": func(r *api.DataReading, s *dataupload.Snapshot) error {
146+
return extractResourceListFromReading(r, &s.Statefulsets)
147+
},
148+
"ark/daemonsets": func(r *api.DataReading, s *dataupload.Snapshot) error {
149+
return extractResourceListFromReading(r, &s.Daemonsets)
150+
},
151+
"ark/pods": func(r *api.DataReading, s *dataupload.Snapshot) error {
152+
return extractResourceListFromReading(r, &s.Pods)
153+
},
121154
}
122155

123-
// ConvertDataReadingsToCyberarkSnapshot converts a list of DataReadings to a CyberArk Snapshot.
124-
// It extracts the Kubernetes version from the "ark/discovery" DataReading and
125-
// collects resources from other DataReadings based on their DataGatherer names.
126-
// If any required data is missing or cannot be converted, an error is returned.
127-
func ConvertDataReadingsToCyberarkSnapshot(
156+
// convertDataReadings processes a list of DataReadings using the provided
157+
// extractor functions to populate the fields of the target snapshot.
158+
// It ensures that all expected data gatherers are handled and that there are
159+
// no unhandled data gatherers. If any discrepancies are found, or if any
160+
// extractor function returns an error, it returns an error.
161+
// The extractorFunctions map should contain functions for each expected
162+
// DataGatherer name, which will be called with the corresponding DataReading
163+
// and the target snapshot to populate the relevant fields.
164+
func convertDataReadings[T any](
165+
extractorFunctions map[string]func(*api.DataReading, *T) error,
128166
readings []*api.DataReading,
129-
snapshot *dataupload.Snapshot,
167+
target *T,
130168
) error {
131-
allDataGathererNames := make([]string, len(readings))
169+
expectedDataGatherers := sets.StringKeySet(extractorFunctions)
132170
unhandledDataGatherers := sets.New[string]()
133-
expectedDataGatherers := sets.New[string](expectedGathererNames...)
134-
for i, reading := range readings {
171+
missingDataGatherers := sets.New[string](expectedDataGatherers.UnsortedList()...)
172+
for _, reading := range readings {
135173
dataGathererName := reading.DataGatherer
136-
allDataGathererNames[i] = dataGathererName
137-
var err error
138-
switch reading.DataGatherer {
139-
case "ark/discovery":
140-
err = extractServerVersionFromReading(reading, &snapshot.K8SVersion)
141-
case "ark/secrets":
142-
err = extractResourceListFromReading(reading, &snapshot.Secrets)
143-
case "ark/serviceaccounts":
144-
err = extractResourceListFromReading(reading, &snapshot.ServiceAccounts)
145-
case "ark/roles":
146-
err = extractResourceListFromReading(reading, &snapshot.Roles)
147-
case "ark/clusterroles":
148-
err = extractResourceListFromReading(reading, &snapshot.ClusterRoles)
149-
case "ark/rolebindings":
150-
err = extractResourceListFromReading(reading, &snapshot.RoleBindings)
151-
case "ark/clusterrolebindings":
152-
err = extractResourceListFromReading(reading, &snapshot.ClusterRoleBindings)
153-
case "ark/jobs":
154-
err = extractResourceListFromReading(reading, &snapshot.Jobs)
155-
case "ark/cronjobs":
156-
err = extractResourceListFromReading(reading, &snapshot.CronJobs)
157-
case "ark/deployments":
158-
err = extractResourceListFromReading(reading, &snapshot.Deployments)
159-
case "ark/statefulsets":
160-
err = extractResourceListFromReading(reading, &snapshot.Statefulsets)
161-
case "ark/daemonsets":
162-
err = extractResourceListFromReading(reading, &snapshot.Daemonsets)
163-
case "ark/pods":
164-
err = extractResourceListFromReading(reading, &snapshot.Pods)
165-
default:
174+
extractFunc, found := extractorFunctions[dataGathererName]
175+
if !found {
166176
unhandledDataGatherers.Insert(dataGathererName)
177+
continue
167178
}
168-
if err != nil {
179+
missingDataGatherers.Delete(dataGathererName)
180+
// Call the extractor function to populate the relevant field in the target snapshot.
181+
if err := extractFunc(reading, target); err != nil {
169182
return fmt.Errorf("while extracting data reading %s: %s", dataGathererName, err)
170183
}
171184
}
172-
allDataGatherers := sets.New[string](allDataGathererNames...)
173-
missingDataGatherers := expectedDataGatherers.Difference(allDataGatherers)
174185
if missingDataGatherers.Len() > 0 || unhandledDataGatherers.Len() > 0 {
175186
return fmt.Errorf(
176-
"Unexpected data gatherers. missing: %v, unhandled: %v",
187+
"unexpected data gatherers, missing: %v, unhandled: %v",
177188
sets.List(missingDataGatherers),
178189
sets.List(unhandledDataGatherers),
179190
)

0 commit comments

Comments
 (0)