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  }