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 }