github.com/Axway/agent-sdk@v1.1.101/pkg/agent/discoverycache.go (about) 1 package agent 2 3 import ( 4 "context" 5 "fmt" 6 "sync" 7 8 "github.com/Axway/agent-sdk/pkg/agent/handler" 9 "github.com/Axway/agent-sdk/pkg/migrate" 10 "github.com/Axway/agent-sdk/pkg/watchmanager/proto" 11 12 apiv1 "github.com/Axway/agent-sdk/pkg/apic/apiserver/models/api/v1" 13 management "github.com/Axway/agent-sdk/pkg/apic/apiserver/models/management/v1alpha1" 14 "github.com/Axway/agent-sdk/pkg/config" 15 "github.com/Axway/agent-sdk/pkg/util/log" 16 ) 17 18 type discoveryCache struct { 19 centralURL string 20 migrator migrate.Migrator 21 logger log.FieldLogger 22 handlers []handler.Handler 23 client resourceClient 24 additionalDiscoveryFuncs []discoverFunc 25 watchTopic *management.WatchTopic 26 } 27 28 type resourceClient interface { 29 GetAPIV1ResourceInstances(query map[string]string, URL string) ([]*apiv1.ResourceInstance, error) 30 } 31 32 // discoverFunc is the func definition for discovering resources to cache 33 type discoverFunc func() error 34 35 // discoveryOpt is a func that updates fields on the discoveryCache 36 type discoveryOpt func(dc *discoveryCache) 37 38 func withAdditionalDiscoverFuncs(funcs ...discoverFunc) discoveryOpt { 39 return func(dc *discoveryCache) { 40 dc.additionalDiscoveryFuncs = append(dc.additionalDiscoveryFuncs, funcs...) 41 } 42 } 43 44 func withMigration(mig migrate.Migrator) discoveryOpt { 45 return func(dc *discoveryCache) { 46 dc.migrator = mig 47 } 48 } 49 50 func newDiscoveryCache( 51 cfg config.CentralConfig, 52 client resourceClient, 53 handlers []handler.Handler, 54 watchTopic *management.WatchTopic, 55 opts ...discoveryOpt, 56 ) *discoveryCache { 57 logger := log.NewFieldLogger(). 58 WithPackage("sdk.agent"). 59 WithComponent("discoveryCache") 60 61 dc := &discoveryCache{ 62 logger: logger, 63 handlers: handlers, 64 centralURL: cfg.GetURL(), 65 client: client, 66 additionalDiscoveryFuncs: make([]discoverFunc, 0), 67 watchTopic: watchTopic, 68 } 69 70 for _, opt := range opts { 71 opt(dc) 72 } 73 return dc 74 } 75 76 // execute rebuilds the discovery cache 77 func (dc *discoveryCache) execute() error { 78 dc.logger.Debug("executing discovery cache") 79 80 discoveryFuncs := dc.buildDiscoveryFuncs() 81 if dc.additionalDiscoveryFuncs != nil { 82 discoveryFuncs = append(discoveryFuncs, dc.additionalDiscoveryFuncs...) 83 } 84 85 err := dc.executeDiscoveryFuncs(discoveryFuncs) 86 if err != nil { 87 return err 88 } 89 90 // Now do the marketplace discovery funcs as the other functions have completed 91 // AccessRequest cache need the APIServiceInstance cache to be fully loaded. 92 93 marketplaceDiscoveryFuncs := dc.buildMarketplaceDiscoveryFuncs() 94 err = dc.executeDiscoveryFuncs(marketplaceDiscoveryFuncs) 95 if err != nil { 96 return err 97 } 98 99 dc.logger.Debug("cache has been updated") 100 101 return nil 102 } 103 104 func (dc *discoveryCache) executeDiscoveryFuncs(discoveryFuncs []discoverFunc) error { 105 errCh := make(chan error, len(discoveryFuncs)) 106 wg := &sync.WaitGroup{} 107 108 for _, fun := range discoveryFuncs { 109 wg.Add(1) 110 111 go func(f func() error) { 112 defer wg.Done() 113 114 err := f() 115 errCh <- err 116 }(fun) 117 } 118 119 wg.Wait() 120 close(errCh) 121 122 for e := range errCh { 123 if e != nil { 124 return e 125 } 126 } 127 128 return nil 129 } 130 131 func (dc *discoveryCache) buildDiscoveryFuncs() []discoverFunc { 132 resources := make(map[string]discoverFunc) 133 134 for _, filter := range dc.watchTopic.Spec.Filters { 135 kind := filter.Kind 136 scope := "" 137 if filter.Scope != nil && filter.Scope.Name != "" { 138 scope = filter.Scope.Name 139 } 140 key := fmt.Sprintf("%s:%s", kind, scope) 141 dc.logger.Debugf("adding function kind:%s,scope:%s to be executed", kind, scope) 142 f := dc.buildResourceFunc(filter) 143 if !isMPResource(filter.Kind) { 144 resources[key] = f 145 } 146 } 147 148 var funcs []discoverFunc 149 for _, f := range resources { 150 funcs = append(funcs, f) 151 } 152 153 return funcs 154 } 155 156 func (dc *discoveryCache) buildMarketplaceDiscoveryFuncs() []discoverFunc { 157 mpResources := make(map[string]discoverFunc) 158 159 for _, filter := range dc.watchTopic.Spec.Filters { 160 if isMPResource(filter.Kind) { 161 f := dc.buildResourceFunc(filter) 162 mpResources[filter.Kind] = f 163 } 164 } 165 166 var funcs []discoverFunc 167 marketplaceFuncs := dc.buildMarketplaceFuncs(mpResources) 168 funcs = append(funcs, dc.handleMarketplaceFuncs(marketplaceFuncs)) 169 return funcs 170 } 171 172 func (dc *discoveryCache) buildMarketplaceFuncs(mpResources map[string]discoverFunc) []discoverFunc { 173 var marketplaceFuncs []discoverFunc 174 175 mApps, ok := mpResources[management.ManagedApplicationGVK().Kind] 176 if ok { 177 marketplaceFuncs = append(marketplaceFuncs, mApps) 178 } 179 180 accessReq, ok := mpResources[management.AccessRequestGVK().Kind] 181 if ok { 182 marketplaceFuncs = append(marketplaceFuncs, accessReq) 183 } 184 185 creds, ok := mpResources[management.CredentialGVK().Kind] 186 if ok { 187 marketplaceFuncs = append(marketplaceFuncs, creds) 188 } 189 190 return marketplaceFuncs 191 } 192 193 func (dc *discoveryCache) handleMarketplaceFuncs(marketplaceFuncs []discoverFunc) discoverFunc { 194 return func() error { 195 for _, f := range marketplaceFuncs { 196 if err := f(); err != nil { 197 return err 198 } 199 } 200 return nil 201 } 202 } 203 204 func (dc *discoveryCache) buildResourceFunc(filter management.WatchTopicSpecFilters) discoverFunc { 205 return func() error { 206 ri := apiv1.ResourceInstance{ 207 ResourceMeta: apiv1.ResourceMeta{ 208 GroupVersionKind: apiv1.GroupVersionKind{ 209 GroupKind: apiv1.GroupKind{ 210 Group: filter.Group, 211 Kind: filter.Kind, 212 }, 213 APIVersion: "v1alpha1", 214 }, 215 }, 216 } 217 if filter.Scope != nil { 218 ri.Metadata.Scope.Kind = filter.Scope.Kind 219 ri.Metadata.Scope.Name = filter.Scope.Name 220 } 221 222 logger := dc.logger.WithField("kind", filter.Kind) 223 logger.Tracef("fetching %s and updating cache", filter.Kind) 224 225 resources, err := dc.client.GetAPIV1ResourceInstances(nil, ri.GetKindLink()) 226 if err != nil { 227 return fmt.Errorf("failed to fetch resources of kind %s: %s", filter.Kind, err) 228 } 229 230 return dc.handleResourcesList(resources) 231 } 232 } 233 234 func (dc *discoveryCache) handleResourcesList(list []*apiv1.ResourceInstance) error { 235 for _, ri := range list { 236 if dc.migrator != nil { 237 ctx := context.Background() 238 ctx = context.WithValue(context.WithValue(ctx, log.KindCtx, ri.Kind), log.NameCtx, ri.Name) 239 240 logger := log.NewLoggerFromContext(ctx) 241 242 logger.Trace("handle migration") 243 var err error 244 ri, err = dc.migrator.Migrate(ctx, ri) 245 if err != nil { 246 dc.logger.WithError(err).Error("failed to migrate resource") 247 } 248 } 249 250 action := getAction(ri.Metadata.State) 251 if err := dc.handleResource(ri, action); err != nil { 252 logger. 253 WithError(err). 254 Error("failed to migrate resource") 255 } 256 } 257 258 return nil 259 } 260 261 func (dc *discoveryCache) handleResource(ri *apiv1.ResourceInstance, action proto.Event_Type) error { 262 ctx := handler.NewEventContext(action, nil, ri.Name, ri.Kind) 263 for _, h := range dc.handlers { 264 err := h.Handle(ctx, nil, ri) 265 if err != nil { 266 return err 267 } 268 } 269 return nil 270 } 271 272 func getAction(state string) proto.Event_Type { 273 if state == apiv1.ResourceDeleting { 274 return proto.Event_UPDATED 275 } 276 return proto.Event_CREATED 277 } 278 279 func isMPResource(kind string) bool { 280 switch kind { 281 case management.ManagedApplicationGVK().Kind: 282 return true 283 case management.AccessRequestGVK().Kind: 284 return true 285 case management.CredentialGVK().Kind: 286 return true 287 default: 288 return false 289 } 290 }