@@ -5,10 +5,12 @@ import (
55 "fmt"
66 "net/http"
77
8+ "k8s.io/apimachinery/pkg/runtime"
9+ "k8s.io/apimachinery/pkg/util/sets"
10+
811 "github.com/jetstack/preflight/api"
912 "github.com/jetstack/preflight/pkg/internal/cyberark"
1013 "github.com/jetstack/preflight/pkg/internal/cyberark/dataupload"
11- "github.com/jetstack/preflight/pkg/version"
1214)
1315
1416// 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
4850 if err != nil {
4951 return fmt .Errorf ("while initializing data upload client: %s" , err )
5052 }
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"
5160
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 )
5862 if err != nil {
5963 return fmt .Errorf ("while uploading snapshot: %s" , err )
6064 }
6165 return nil
6266}
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