@@ -14,6 +14,7 @@ import (
1414 "time"
1515
1616 "github.com/cenkalti/backoff/v4"
17+ "github.com/kubescape/storage/pkg/apis/softwarecomposition/v1beta1"
1718 "go.uber.org/multierr"
1819 "k8s.io/apimachinery/pkg/runtime"
1920 "k8s.io/client-go/tools/pager"
@@ -64,19 +65,20 @@ type resourceVersionGetter interface {
6465}
6566
6667type Client struct {
67- dynamicClient dynamic.Interface
68- storageClient spdxv1beta1.SpdxV1beta1Interface
6968 account string
69+ batchProcessingFunc map [domain.BatchType ]BatchProcessingFunc
70+ callbacks domain.Callbacks
7071 cluster string
72+ dynamicClient dynamic.Interface
7173 excludeNamespaces []string
7274 includeNamespaces []string
73- operatorNamespace string // the namespace where the kubescape operator is running
7475 kind * domain.Kind
75- callbacks domain.Callbacks
76+ listPeriod time.Duration
77+ operatorNamespace string // the namespace where the kubescape operator is running
7678 res schema.GroupVersionResource
79+ storageClient spdxv1beta1.SpdxV1beta1Interface
7780 ShadowObjects map [string ][]byte
7881 Strategy domain.Strategy
79- batchProcessingFunc map [domain.BatchType ]BatchProcessingFunc
8082}
8183
8284var errWatchClosed = errors .New ("watch channel closed" )
@@ -89,52 +91,42 @@ func NewClient(dynamicClient dynamic.Interface, storageClient spdxv1beta1.SpdxV1
8991 logger .L ().Warning ("event multiplier config detected, but it is deprecated" , helpers .String ("resource" , res .String ()), helpers .Int ("multiplier" , multiplier ))
9092 }
9193 return & Client {
92- account : cfg .Account ,
94+ account : cfg .Account ,
95+ batchProcessingFunc : map [domain.BatchType ]BatchProcessingFunc {
96+ domain .DefaultBatch : defaultBatchProcessingFunc , // regular processing, when batch type is not set
97+ domain .ReconciliationBatch : reconcileBatchProcessingFunc ,
98+ },
99+ cluster : cfg .ClusterName ,
93100 dynamicClient : dynamicClient ,
94- storageClient : storageClient ,
95101 excludeNamespaces : cfg .ExcludeNamespaces ,
96102 includeNamespaces : cfg .IncludeNamespaces ,
97- operatorNamespace : cfg .Namespace ,
98- cluster : cfg .ClusterName ,
99103 kind : & domain.Kind {
100104 Group : res .Group ,
101105 Version : res .Version ,
102106 Resource : res .Resource ,
103107 },
104- res : res ,
105- ShadowObjects : map [string ][]byte {},
106- Strategy : r .Strategy ,
107- batchProcessingFunc : map [domain.BatchType ]BatchProcessingFunc {
108- domain .DefaultBatch : defaultBatchProcessingFunc , // regular processing, when batch type is not set
109- domain .ReconciliationBatch : reconcileBatchProcessingFunc ,
110- },
108+ listPeriod : cfg .ListPeriod ,
109+ operatorNamespace : cfg .Namespace ,
110+ res : res ,
111+ storageClient : storageClient ,
112+ ShadowObjects : map [string ][]byte {},
113+ Strategy : r .Strategy ,
111114 }
112115}
113116
114117var _ adapters.Client = (* Client )(nil )
115118
116119func (c * Client ) Start (ctx context.Context ) error {
117120 logger .L ().Info ("starting incluster client" , helpers .String ("resource" , c .res .Resource ))
118- watchOpts := metav1.ListOptions {}
119- // for our storage, we need to list all resources and get them one by one
120- // as list returns objects with empty spec
121- // and watch does not return existing objects
122- if c .res .Group == kubescapeCustomResourceGroup {
123- if err := backoff .RetryNotify (func () error {
124- var err error
125- watchOpts .ResourceVersion , err = c .getExistingStorageObjects (ctx )
126- return err
127- }, utils .NewBackOff (true ), func (err error , d time.Duration ) {
128- logger .L ().Ctx (ctx ).Warning ("get existing storage objects" , helpers .Error (err ),
129- helpers .String ("resource" , c .res .Resource ),
130- helpers .String ("retry in" , d .String ()))
131- }); err != nil {
132- return fmt .Errorf ("giving up get existing storage objects: %w" , err )
133- }
134- }
135121 // begin watch
136122 eventQueue := utils .NewCooldownQueue ()
137- go c .watchRetry (ctx , watchOpts , eventQueue )
123+ if c .res .Group == kubescapeCustomResourceGroup {
124+ // our custom resources no longer support watch, use periodic listing
125+ go c .periodicList (ctx , eventQueue , c .listPeriod )
126+ } else {
127+ watchOpts := metav1.ListOptions {}
128+ go c .watchRetry (ctx , watchOpts , eventQueue )
129+ }
138130 // process events
139131 for event := range eventQueue .ResultChan {
140132 // skip non-objects
@@ -203,7 +195,7 @@ func (c *Client) Stop(_ context.Context) error {
203195func (c * Client ) watchRetry (ctx context.Context , watchOpts metav1.ListOptions , eventQueue * utils.CooldownQueue ) {
204196 exitFatal := true
205197 if err := backoff .RetryNotify (func () error {
206- watcher , err := c .chooseWatcher ( watchOpts )
198+ watcher , err := c .dynamicClient . Resource ( c . res ). Namespace ( "" ). Watch ( context . Background (), watchOpts )
207199 if err != nil {
208200 if k8sErrors .ReasonForError (err ) == metav1 .StatusReasonNotFound {
209201 exitFatal = false
@@ -534,37 +526,40 @@ func (c *Client) verifyObject(id domain.KindName, newChecksum string) ([]byte, e
534526 return object , nil
535527}
536528
537- func (c * Client ) getExistingStorageObjects (ctx context.Context ) (string , error ) {
538- logger .L ().Debug ("getting existing objects from storage" , helpers .String ("resource" , c .res .Resource ))
539- var resourceVersion string
540- if err := pager .New (func (ctx context.Context , opts metav1.ListOptions ) (runtime.Object , error ) {
541- return c .chooseLister (opts )
542- }).EachListItem (context .Background (), metav1.ListOptions {}, func (run runtime.Object ) error {
543- d := run .(metav1.Object )
544- resourceVersion = d .GetResourceVersion ()
545- // no need for skip ns since these are our CRDs
546- id := domain.KindName {
547- Kind : c .kind ,
548- Name : d .GetName (),
549- Namespace : d .GetNamespace (),
550- ResourceVersion : domain .ToResourceVersion (d .GetResourceVersion ()),
551- }
552- // get checksum
553- checksum , err := c .getChecksum (d )
554- if err != nil {
555- logger .L ().Ctx (ctx ).Error ("cannot get checksums" , helpers .Error (err ), helpers .String ("id" , id .String ()))
556- return nil
557- }
558- err = c .callbacks .VerifyObject (ctx , id , checksum )
559- if err != nil {
560- logger .L ().Ctx (ctx ).Error ("cannot handle added resource" , helpers .Error (err ), helpers .String ("id" , id .String ()))
529+ func (c * Client ) periodicList (ctx context.Context , queue * utils.CooldownQueue , duration time.Duration ) {
530+ ticker := time .NewTicker (duration )
531+ defer ticker .Stop ()
532+ var since string
533+ for {
534+ select {
535+ case <- ctx .Done ():
536+ return
537+ case <- ticker .C :
538+ var continueToken string
539+ for {
540+ logger .L ().Debug ("periodicList - listing resources" , helpers .String ("resource" , c .res .Resource ), helpers .String ("continueToken" , continueToken ), helpers .String ("since" , since ))
541+ items , nextToken , lastUpdated , err := c .listFunc (metav1.ListOptions {
542+ Limit : int64 (100 ),
543+ Continue : continueToken ,
544+ ResourceVersion : since , // ensure we only get changes since the last check
545+ })
546+ if err != nil {
547+ logger .L ().Ctx (ctx ).Error ("periodicList - error in listFunc" , helpers .Error (err ))
548+ break
549+ }
550+ for _ , obj := range items {
551+ // added and modified events are treated the same, so we enqueue a Modified event for both
552+ // deleted events are not possible with listing, so we rely on the reconciliation batch to detect deletions
553+ queue .Enqueue (watch.Event {Type : watch .Modified , Object : obj })
554+ }
555+ since = lastUpdated
556+ if nextToken == "" {
557+ break
558+ }
559+ continueToken = nextToken
560+ }
561561 }
562- return nil
563- }); err != nil {
564- return "" , fmt .Errorf ("list resources: %w" , err )
565562 }
566- // set resource version to watch from
567- return resourceVersion , nil
568563}
569564
570565func (c * Client ) filterAndMarshal (d metav1.Object ) ([]byte , error ) {
@@ -787,6 +782,8 @@ func (c *Client) chooseLister(opts metav1.ListOptions) (runtime.Object, error) {
787782 switch c .res .Resource {
788783 case "applicationprofiles" :
789784 return c .storageClient .ApplicationProfiles ("" ).List (context .Background (), opts )
785+ case "knownservers" :
786+ return c .storageClient .KnownServers ("" ).List (context .Background (), opts )
790787 case "networkneighborhoods" :
791788 return c .storageClient .NetworkNeighborhoods ("" ).List (context .Background (), opts )
792789 case "sbomsyfts" :
@@ -800,22 +797,52 @@ func (c *Client) chooseLister(opts metav1.ListOptions) (runtime.Object, error) {
800797 return c .dynamicClient .Resource (c .res ).Namespace ("" ).List (context .Background (), opts )
801798}
802799
803- func (c * Client ) chooseWatcher (opts metav1.ListOptions ) (watch. Interface , error ) {
800+ func (c * Client ) listFunc (opts metav1.ListOptions ) ([]runtime. Object , string , string , error ) {
804801 if c .storageClient != nil {
805- switch c .res .Resource {
806- case "applicationprofiles" :
807- return c .storageClient .ApplicationProfiles ("" ).Watch (context .Background (), opts )
808- case "networkneighborhoods" :
809- return c .storageClient .NetworkNeighborhoods ("" ).Watch (context .Background (), opts )
810- case "sbomsyfts" :
811- return c .storageClient .SBOMSyfts ("" ).Watch (context .Background (), opts )
812- case "seccompprofiles" :
813- return c .storageClient .SeccompProfiles ("" ).Watch (context .Background (), opts )
814- case "vulnerabilitymanifests" :
815- return c .storageClient .VulnerabilityManifests ("" ).Watch (context .Background (), opts )
802+ list , err := c .chooseLister (opts )
803+ if err != nil {
804+ return nil , "" , "" , err
805+ }
806+ switch l := list .(type ) {
807+ case * v1beta1.ApplicationProfileList :
808+ items := make ([]runtime.Object , len (l .Items ))
809+ for i := range l .Items {
810+ items [i ] = & l .Items [i ]
811+ }
812+ return items , l .Continue , l .ResourceVersion , nil
813+ case * v1beta1.KnownServerList :
814+ items := make ([]runtime.Object , len (l .Items ))
815+ for i := range l .Items {
816+ items [i ] = & l .Items [i ]
817+ }
818+ return items , l .Continue , l .ResourceVersion , nil
819+ case * v1beta1.NetworkNeighborhoodList :
820+ items := make ([]runtime.Object , len (l .Items ))
821+ for i := range l .Items {
822+ items [i ] = & l .Items [i ]
823+ }
824+ return items , l .Continue , l .ResourceVersion , nil
825+ case * v1beta1.SBOMSyftList :
826+ items := make ([]runtime.Object , len (l .Items ))
827+ for i := range l .Items {
828+ items [i ] = & l .Items [i ]
829+ }
830+ return items , l .Continue , l .ResourceVersion , nil
831+ case * v1beta1.SeccompProfileList :
832+ items := make ([]runtime.Object , len (l .Items ))
833+ for i := range l .Items {
834+ items [i ] = & l .Items [i ]
835+ }
836+ return items , l .Continue , l .ResourceVersion , nil
837+ case * v1beta1.VulnerabilityManifestList :
838+ items := make ([]runtime.Object , len (l .Items ))
839+ for i := range l .Items {
840+ items [i ] = & l .Items [i ]
841+ }
842+ return items , l .Continue , l .ResourceVersion , nil
816843 }
817844 }
818- return c . dynamicClient . Resource ( c . res ). Namespace ( "" ). Watch ( context . Background (), opts )
845+ return nil , "" , "" , fmt . Errorf ( "list function not implemented for resource %s" , c . res . Resource )
819846}
820847
821848func (c * Client ) getResource (namespace string , name string ) (metav1.Object , error ) {
0 commit comments