github.com/Axway/agent-sdk@v1.1.101/pkg/agent/events/watchtopic.go (about)

     1  package events
     2  
     3  import (
     4  	"bytes"
     5  	_ "embed" // load of the watch topic template
     6  	"encoding/json"
     7  	"fmt"
     8  	"strings"
     9  	"text/template"
    10  
    11  	"github.com/Axway/agent-sdk/pkg/agent/resource"
    12  	"github.com/Axway/agent-sdk/pkg/config"
    13  
    14  	v1 "github.com/Axway/agent-sdk/pkg/apic/apiserver/models/api/v1"
    15  	management "github.com/Axway/agent-sdk/pkg/apic/apiserver/models/management/v1alpha1"
    16  )
    17  
    18  //go:embed assets/watch-topic-template.json
    19  var agentTemplate string
    20  
    21  var agentTypesMap = map[config.AgentType]string{
    22  	config.DiscoveryAgent:    "discoveryagents",
    23  	config.TraceabilityAgent: "traceabilityagents",
    24  	config.GovernanceAgent:   "governanceagents",
    25  }
    26  
    27  type watchTopicFeatures interface {
    28  	GetAgentType() config.AgentType
    29  	GetWatchResourceFilters() []config.ResourceFilter
    30  }
    31  
    32  const (
    33  	desc = "Watch Topic used by a %s agent for resources in the %s environment."
    34  	// WatchTopicFilterTypeCreated filter type name
    35  	WatchTopicFilterTypeCreated = "created"
    36  	// WatchTopicFilterTypeUpdated filter type name
    37  	WatchTopicFilterTypeUpdated = "updated"
    38  	// WatchTopicFilterTypeDeleted filter type name
    39  	WatchTopicFilterTypeDeleted = "deleted"
    40  )
    41  
    42  var (
    43  	created          = []string{WatchTopicFilterTypeCreated}
    44  	updated          = []string{WatchTopicFilterTypeUpdated}
    45  	deleted          = []string{WatchTopicFilterTypeDeleted}
    46  	createdOrUpdated = append(created, updated...)
    47  	all              = append(createdOrUpdated, deleted...)
    48  )
    49  
    50  // getOrCreateWatchTopic attempts to retrieve a watch topic from central, or create one if it does not exist.
    51  func getOrCreateWatchTopic(name, scope string, client APIClient, features watchTopicFeatures) (*management.WatchTopic, error) {
    52  	wt := management.NewWatchTopic("")
    53  	ri, err := client.GetResource(fmt.Sprintf("%s/%s", wt.GetKindLink(), name))
    54  
    55  	if err == nil {
    56  		err = wt.FromInstance(ri)
    57  		if err != nil {
    58  			return nil, err
    59  		}
    60  	}
    61  
    62  	var agentResourceGroupKind v1.GroupKind
    63  	var tmplValuesFunc func(string, string, v1.GroupKind, watchTopicFeatures) WatchTopicValues
    64  
    65  	switch features.GetAgentType() {
    66  	case config.DiscoveryAgent:
    67  		agentResourceGroupKind = management.DiscoveryAgentGVK().GroupKind
    68  		tmplValuesFunc = NewDiscoveryWatchTopic
    69  	case config.TraceabilityAgent:
    70  		agentResourceGroupKind = management.TraceabilityAgentGVK().GroupKind
    71  		tmplValuesFunc = NewTraceWatchTopic
    72  	default:
    73  		return nil, resource.ErrUnsupportedAgentType
    74  	}
    75  
    76  	newWT, err := parseWatchTopicTemplate(tmplValuesFunc(name, scope, agentResourceGroupKind, features))
    77  	if err != nil {
    78  		return nil, err
    79  	}
    80  
    81  	filters := features.GetWatchResourceFilters()
    82  	for _, filter := range filters {
    83  		eventTypes := make([]string, 0)
    84  		for _, filterEventType := range filter.EventTypes {
    85  			eventTypes = append(eventTypes, (string(filterEventType)))
    86  		}
    87  
    88  		wf := management.WatchTopicSpecFilters{
    89  			Group: filter.Group,
    90  			Kind:  filter.Kind,
    91  			Name:  filter.Name,
    92  			Type:  eventTypes,
    93  		}
    94  
    95  		if filter.Scope != nil {
    96  			wf.Scope = &management.WatchTopicSpecScope{
    97  				Kind: filter.Scope.Kind,
    98  				Name: filter.Scope.Name,
    99  			}
   100  		}
   101  
   102  		newWT.Spec.Filters = append(newWT.Spec.Filters, wf)
   103  	}
   104  
   105  	// if the existing wt has no name then it does not exist yet
   106  	if wt.Name == "" {
   107  		return createOrUpdateWatchTopic(newWT, client)
   108  	}
   109  
   110  	// compare the generated WT and the existing WT for changes
   111  	if shouldPushUpdate(wt, newWT) {
   112  		// update the spec in the existing watch topic
   113  		wt.Spec = newWT.Spec
   114  		return createOrUpdateWatchTopic(wt, client)
   115  	}
   116  
   117  	return wt, nil
   118  }
   119  
   120  func shouldPushUpdate(cur, new *management.WatchTopic) bool {
   121  	filtersDiff := func(a, b []management.WatchTopicSpecFilters) bool {
   122  		for _, aFilter := range a {
   123  			found := false
   124  			for _, bFilter := range b {
   125  				if filtersEqual(aFilter, bFilter) {
   126  					found = true
   127  					break
   128  				}
   129  			}
   130  			if !found {
   131  				// update required
   132  				return true
   133  			}
   134  		}
   135  		return false
   136  	}
   137  
   138  	if filtersDiff(cur.Spec.Filters, new.Spec.Filters) {
   139  		return true
   140  	}
   141  	return filtersDiff(new.Spec.Filters, cur.Spec.Filters)
   142  }
   143  
   144  func filtersEqual(a, b management.WatchTopicSpecFilters) (equal bool) {
   145  	if a.Group != b.Group ||
   146  		a.Kind != b.Kind ||
   147  		a.Name != b.Name ||
   148  		a.Scope == nil && b.Scope != nil ||
   149  		a.Scope != nil && b.Scope == nil {
   150  		return
   151  	}
   152  
   153  	if a.Scope != nil && b.Scope != nil {
   154  		if a.Scope.Kind != b.Scope.Kind ||
   155  			a.Scope.Name != b.Scope.Name {
   156  			return
   157  		}
   158  	}
   159  
   160  	if areTypesEqual(a.Type, b.Type) {
   161  		return false
   162  	}
   163  	return !areTypesEqual(b.Type, a.Type)
   164  }
   165  
   166  func areTypesEqual(aTypes, bTypes []string) bool {
   167  	for _, aType := range aTypes {
   168  		found := false
   169  		for _, bType := range bTypes {
   170  			if aType == bType {
   171  				found = true
   172  				break
   173  			}
   174  		}
   175  		if !found {
   176  			return true
   177  		}
   178  	}
   179  	return false
   180  }
   181  
   182  // executeTemplate parses a WatchTopic from a template
   183  func parseWatchTopicTemplate(values WatchTopicValues) (*management.WatchTopic, error) {
   184  	tmpl, err := template.New("watch-topic-tmpl").Funcs(template.FuncMap{"StringsJoin": strings.Join}).Parse(agentTemplate)
   185  	if err != nil {
   186  		return nil, err
   187  	}
   188  
   189  	buf := bytes.NewBuffer([]byte{})
   190  	err = tmpl.Execute(buf, values)
   191  	if err != nil {
   192  		return nil, err
   193  	}
   194  
   195  	wt := management.NewWatchTopic("")
   196  	err = json.Unmarshal(buf.Bytes(), wt)
   197  	return wt, err
   198  }
   199  
   200  // createOrUpdateWatchTopic creates a WatchTopic
   201  func createOrUpdateWatchTopic(wt *management.WatchTopic, rc APIClient) (*management.WatchTopic, error) {
   202  	if wt.Metadata.ID != "" {
   203  		err := rc.DeleteResourceInstance(wt)
   204  		if err != nil {
   205  			return nil, err
   206  		}
   207  	}
   208  
   209  	ri, err := rc.CreateResourceInstance(wt)
   210  	if err != nil {
   211  		return nil, err
   212  	}
   213  
   214  	err = wt.FromInstance(ri)
   215  
   216  	return wt, err
   217  }
   218  
   219  type kindValues struct {
   220  	v1.GroupKind
   221  	EventTypes []string
   222  	ScopeKind  string // blank defaults to Environment in template
   223  	ScopeName  string // blank generates no scope in template
   224  	Name       string
   225  }
   226  
   227  // WatchTopicValues values to populate the watch topic template
   228  type WatchTopicValues struct {
   229  	Name        string
   230  	Title       string
   231  	Description string
   232  	Kinds       []kindValues
   233  }
   234  
   235  // NewDiscoveryWatchTopic creates a WatchTopic template string.
   236  // Using a template instead of unmarshalling into a struct to avoid sending a request with empty fields
   237  func NewDiscoveryWatchTopic(name, scope string, agentResourceGroupKind v1.GroupKind, features watchTopicFeatures) WatchTopicValues {
   238  	kinds := []kindValues{
   239  		{GroupKind: agentResourceGroupKind, ScopeName: scope, ScopeKind: management.EnvironmentGVK().Kind, EventTypes: updated},
   240  		{GroupKind: management.APIServiceGVK().GroupKind, ScopeName: scope, ScopeKind: management.EnvironmentGVK().Kind, EventTypes: all},
   241  		{GroupKind: management.APIServiceInstanceGVK().GroupKind, ScopeName: scope, ScopeKind: management.EnvironmentGVK().Kind, EventTypes: all},
   242  		{GroupKind: management.AccessControlListGVK().GroupKind, ScopeName: scope, ScopeKind: management.EnvironmentGVK().Kind, EventTypes: all},
   243  		{GroupKind: management.CredentialGVK().GroupKind, ScopeName: scope, ScopeKind: management.EnvironmentGVK().Kind, EventTypes: createdOrUpdated},
   244  		{GroupKind: management.AccessRequestGVK().GroupKind, ScopeName: scope, ScopeKind: management.EnvironmentGVK().Kind, EventTypes: createdOrUpdated},
   245  		{GroupKind: management.ManagedApplicationGVK().GroupKind, ScopeName: scope, ScopeKind: management.EnvironmentGVK().Kind, EventTypes: createdOrUpdated},
   246  		{GroupKind: management.CredentialRequestDefinitionGVK().GroupKind, ScopeName: scope, ScopeKind: management.EnvironmentGVK().Kind, EventTypes: all},
   247  		{GroupKind: management.AccessRequestDefinitionGVK().GroupKind, ScopeName: scope, ScopeKind: management.EnvironmentGVK().Kind, EventTypes: all},
   248  		{GroupKind: management.EnvironmentGVK().GroupKind, Name: scope, EventTypes: updated},
   249  	}
   250  
   251  	return WatchTopicValues{
   252  		Name:        name,
   253  		Title:       name,
   254  		Description: fmt.Sprintf(desc, "discovery", scope),
   255  		Kinds:       kinds,
   256  	}
   257  }
   258  
   259  // NewTraceWatchTopic creates a WatchTopic template string
   260  func NewTraceWatchTopic(name, scope string, agentResourceGroupKind v1.GroupKind, features watchTopicFeatures) WatchTopicValues {
   261  	kinds := []kindValues{
   262  		{GroupKind: agentResourceGroupKind, ScopeName: scope, ScopeKind: management.EnvironmentGVK().Kind, EventTypes: updated},
   263  		{GroupKind: management.APIServiceGVK().GroupKind, ScopeName: scope, ScopeKind: management.EnvironmentGVK().Kind, EventTypes: all},
   264  		{GroupKind: management.APIServiceInstanceGVK().GroupKind, ScopeName: scope, ScopeKind: management.EnvironmentGVK().Kind, EventTypes: all},
   265  		{GroupKind: management.AccessRequestGVK().GroupKind, ScopeName: scope, ScopeKind: management.EnvironmentGVK().Kind, EventTypes: all},
   266  		{GroupKind: management.ManagedApplicationGVK().GroupKind, ScopeName: scope, ScopeKind: management.EnvironmentGVK().Kind, EventTypes: all},
   267  	}
   268  
   269  	return WatchTopicValues{
   270  		Name:        name,
   271  		Title:       name,
   272  		Description: fmt.Sprintf(desc, "traceability", scope),
   273  		Kinds:       kinds,
   274  	}
   275  }
   276  
   277  // NewGovernanceAgentWatchTopic creates a WatchTopic template string
   278  func NewGovernanceAgentWatchTopic(name, scope string, agentResourceGroupKind v1.GroupKind, features watchTopicFeatures) WatchTopicValues {
   279  	kinds := []kindValues{
   280  		{GroupKind: management.APIServiceGVK().GroupKind, ScopeName: scope, EventTypes: all},
   281  		{GroupKind: management.APIServiceInstanceGVK().GroupKind, ScopeName: scope, EventTypes: all},
   282  		{GroupKind: agentResourceGroupKind, ScopeName: scope, EventTypes: updated},
   283  		{GroupKind: management.CredentialGVK().GroupKind, ScopeName: scope, EventTypes: createdOrUpdated},
   284  		{GroupKind: management.AccessRequestGVK().GroupKind, ScopeName: scope, EventTypes: createdOrUpdated},
   285  		{GroupKind: management.ManagedApplicationGVK().GroupKind, ScopeName: scope, EventTypes: createdOrUpdated},
   286  		{GroupKind: management.CredentialRequestDefinitionGVK().GroupKind, ScopeName: scope, EventTypes: all},
   287  		{GroupKind: management.AccessRequestDefinitionGVK().GroupKind, ScopeName: scope, EventTypes: all},
   288  	}
   289  
   290  	return WatchTopicValues{
   291  		Name:        name,
   292  		Title:       name,
   293  		Description: fmt.Sprintf(desc, "governance", scope),
   294  		Kinds:       kinds,
   295  	}
   296  }
   297  
   298  // GetWatchTopic retrieves a watch topic based on the agent config. Creates a watch topic if one does not exist.
   299  func GetWatchTopic(cfg config.CentralConfig, client APIClient) (*management.WatchTopic, error) {
   300  	env := cfg.GetEnvironmentName()
   301  
   302  	wtName := getWatchTopicName(env, cfg.GetAgentType())
   303  	wt, err := getOrCreateWatchTopic(wtName, env, client, cfg)
   304  	if err != nil {
   305  		return nil, err
   306  	}
   307  
   308  	return wt, err
   309  }
   310  
   311  func getWatchTopicName(envName string, agentType config.AgentType) string {
   312  	return envName + getWatchTopicNameSuffix(agentType)
   313  }
   314  
   315  func getWatchTopicNameSuffix(agentType config.AgentType) string {
   316  	return "-" + agentTypesMap[agentType]
   317  }