@@ -5,10 +5,12 @@ import (
5
5
"fmt"
6
6
"net/http"
7
7
8
+ "k8s.io/apimachinery/pkg/runtime"
9
+ "k8s.io/apimachinery/pkg/util/sets"
10
+
8
11
"github.com/jetstack/preflight/api"
9
12
"github.com/jetstack/preflight/pkg/internal/cyberark"
10
13
"github.com/jetstack/preflight/pkg/internal/cyberark/dataupload"
11
- "github.com/jetstack/preflight/pkg/version"
12
14
)
13
15
14
16
// CyberArkClient is a client for publishing data readings to CyberArk's discoverycontext API.
@@ -48,15 +50,144 @@ func (o *CyberArkClient) PostDataReadingsWithOptions(ctx context.Context, readin
48
50
if err != nil {
49
51
return fmt .Errorf ("while initializing data upload client: %s" , err )
50
52
}
53
+ var snapshot dataupload.Snapshot
54
+ if err := convertDataReadings (defaultExtractorFunctions , readings , & snapshot ); err != nil {
55
+ return fmt .Errorf ("while converting data readings: %s" , err )
56
+ }
57
+ // Temporary hard coded cluster ID.
58
+ // TODO(wallrj): The clusterID will eventually be extracted from the supplied readings.
59
+ snapshot .ClusterID = "success-cluster-id"
51
60
52
- err = datauploadClient .PutSnapshot (ctx , dataupload.Snapshot {
53
- // Temporary hard coded cluster ID.
54
- // TODO(wallrj): The clusterID will eventually be extracted from the supplied readings.
55
- ClusterID : "success-cluster-id" ,
56
- AgentVersion : version .PreflightVersion ,
57
- })
61
+ err = datauploadClient .PutSnapshot (ctx , snapshot )
58
62
if err != nil {
59
63
return fmt .Errorf ("while uploading snapshot: %s" , err )
60
64
}
61
65
return nil
62
66
}
67
+
68
+ // extractServerVersionFromReading converts the opaque data from a DiscoveryData
69
+ // data reading to allow access to the Kubernetes version fields within.
70
+ 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
+ }
74
+ data , ok := reading .Data .(* api.DiscoveryData )
75
+ if ! ok {
76
+ return fmt .Errorf (
77
+ "programmer mistake: the DataReading must have data type *api.DiscoveryData. " +
78
+ "This DataReading (%s) has data type %T" , reading .DataGatherer , reading .Data )
79
+ }
80
+ if data .ServerVersion == nil {
81
+ return nil
82
+ }
83
+ * target = data .ServerVersion .GitVersion
84
+ return nil
85
+ }
86
+
87
+ // extractResourceListFromReading converts the opaque data from a DynamicData
88
+ // data reading to runtime.Object resources, to allow access to the metadata and
89
+ // other kubernetes API fields.
90
+ 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
+ }
94
+ data , ok := reading .Data .(* api.DynamicData )
95
+ if ! ok {
96
+ return fmt .Errorf (
97
+ "programmer mistake: the DataReading must have data type *api.DynamicData. " +
98
+ "This DataReading (%s) has data type %T" , reading .DataGatherer , reading .Data )
99
+ }
100
+ resources := make ([]runtime.Object , len (data .Items ))
101
+ for i , item := range data .Items {
102
+ if resource , ok := item .Resource .(runtime.Object ); ok {
103
+ resources [i ] = resource
104
+ } else {
105
+ return fmt .Errorf (
106
+ "programmer mistake: the DynamicData items must have Resource type runtime.Object. " +
107
+ "This item (%d) has Resource type %T" , i , item .Resource )
108
+ }
109
+ }
110
+ * target = resources
111
+ return nil
112
+ }
113
+
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
+ },
154
+ }
155
+
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 ,
166
+ readings []* api.DataReading ,
167
+ target * T ,
168
+ ) error {
169
+ expectedDataGatherers := sets .StringKeySet (extractorFunctions )
170
+ unhandledDataGatherers := sets .New [string ]()
171
+ missingDataGatherers := sets .New [string ](expectedDataGatherers .UnsortedList ()... )
172
+ for _ , reading := range readings {
173
+ dataGathererName := reading .DataGatherer
174
+ extractFunc , found := extractorFunctions [dataGathererName ]
175
+ if ! found {
176
+ unhandledDataGatherers .Insert (dataGathererName )
177
+ continue
178
+ }
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 {
182
+ return fmt .Errorf ("while extracting data reading %s: %s" , dataGathererName , err )
183
+ }
184
+ }
185
+ if missingDataGatherers .Len () > 0 || unhandledDataGatherers .Len () > 0 {
186
+ return fmt .Errorf (
187
+ "unexpected data gatherers, missing: %v, unhandled: %v" ,
188
+ sets .List (missingDataGatherers ),
189
+ sets .List (unhandledDataGatherers ),
190
+ )
191
+ }
192
+ return nil
193
+ }
0 commit comments