1/* 2 * MinIO Go Library for Amazon S3 Compatible Cloud Storage 3 * Copyright 2020 MinIO, Inc. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18package notification 19 20import ( 21 "encoding/xml" 22 "errors" 23 "fmt" 24 25 "github.com/minio/minio-go/v7/pkg/set" 26) 27 28// EventType is a S3 notification event associated to the bucket notification configuration 29type EventType string 30 31// The role of all event types are described in : 32// http://docs.aws.amazon.com/AmazonS3/latest/dev/NotificationHowTo.html#notification-how-to-event-types-and-destinations 33const ( 34 ObjectCreatedAll EventType = "s3:ObjectCreated:*" 35 ObjectCreatedPut = "s3:ObjectCreated:Put" 36 ObjectCreatedPost = "s3:ObjectCreated:Post" 37 ObjectCreatedCopy = "s3:ObjectCreated:Copy" 38 ObjectCreatedCompleteMultipartUpload = "s3:ObjectCreated:CompleteMultipartUpload" 39 ObjectAccessedGet = "s3:ObjectAccessed:Get" 40 ObjectAccessedHead = "s3:ObjectAccessed:Head" 41 ObjectAccessedAll = "s3:ObjectAccessed:*" 42 ObjectRemovedAll = "s3:ObjectRemoved:*" 43 ObjectRemovedDelete = "s3:ObjectRemoved:Delete" 44 ObjectRemovedDeleteMarkerCreated = "s3:ObjectRemoved:DeleteMarkerCreated" 45 ObjectReducedRedundancyLostObject = "s3:ReducedRedundancyLostObject" 46 BucketCreatedAll = "s3:BucketCreated:*" 47 BucketRemovedAll = "s3:BucketRemoved:*" 48) 49 50// FilterRule - child of S3Key, a tag in the notification xml which 51// carries suffix/prefix filters 52type FilterRule struct { 53 Name string `xml:"Name"` 54 Value string `xml:"Value"` 55} 56 57// S3Key - child of Filter, a tag in the notification xml which 58// carries suffix/prefix filters 59type S3Key struct { 60 FilterRules []FilterRule `xml:"FilterRule,omitempty"` 61} 62 63// Filter - a tag in the notification xml structure which carries 64// suffix/prefix filters 65type Filter struct { 66 S3Key S3Key `xml:"S3Key,omitempty"` 67} 68 69// Arn - holds ARN information that will be sent to the web service, 70// ARN desciption can be found in http://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html 71type Arn struct { 72 Partition string 73 Service string 74 Region string 75 AccountID string 76 Resource string 77} 78 79// NewArn creates new ARN based on the given partition, service, region, account id and resource 80func NewArn(partition, service, region, accountID, resource string) Arn { 81 return Arn{Partition: partition, 82 Service: service, 83 Region: region, 84 AccountID: accountID, 85 Resource: resource} 86} 87 88// String returns the string format of the ARN 89func (arn Arn) String() string { 90 return "arn:" + arn.Partition + ":" + arn.Service + ":" + arn.Region + ":" + arn.AccountID + ":" + arn.Resource 91} 92 93// Config - represents one single notification configuration 94// such as topic, queue or lambda configuration. 95type Config struct { 96 ID string `xml:"Id,omitempty"` 97 Arn Arn `xml:"-"` 98 Events []EventType `xml:"Event"` 99 Filter *Filter `xml:"Filter,omitempty"` 100} 101 102// NewConfig creates one notification config and sets the given ARN 103func NewConfig(arn Arn) Config { 104 return Config{Arn: arn, Filter: &Filter{}} 105} 106 107// AddEvents adds one event to the current notification config 108func (t *Config) AddEvents(events ...EventType) { 109 t.Events = append(t.Events, events...) 110} 111 112// AddFilterSuffix sets the suffix configuration to the current notification config 113func (t *Config) AddFilterSuffix(suffix string) { 114 if t.Filter == nil { 115 t.Filter = &Filter{} 116 } 117 newFilterRule := FilterRule{Name: "suffix", Value: suffix} 118 // Replace any suffix rule if existing and add to the list otherwise 119 for index := range t.Filter.S3Key.FilterRules { 120 if t.Filter.S3Key.FilterRules[index].Name == "suffix" { 121 t.Filter.S3Key.FilterRules[index] = newFilterRule 122 return 123 } 124 } 125 t.Filter.S3Key.FilterRules = append(t.Filter.S3Key.FilterRules, newFilterRule) 126} 127 128// AddFilterPrefix sets the prefix configuration to the current notification config 129func (t *Config) AddFilterPrefix(prefix string) { 130 if t.Filter == nil { 131 t.Filter = &Filter{} 132 } 133 newFilterRule := FilterRule{Name: "prefix", Value: prefix} 134 // Replace any prefix rule if existing and add to the list otherwise 135 for index := range t.Filter.S3Key.FilterRules { 136 if t.Filter.S3Key.FilterRules[index].Name == "prefix" { 137 t.Filter.S3Key.FilterRules[index] = newFilterRule 138 return 139 } 140 } 141 t.Filter.S3Key.FilterRules = append(t.Filter.S3Key.FilterRules, newFilterRule) 142} 143 144// EqualEventTypeList tells whether a and b contain the same events 145func EqualEventTypeList(a, b []EventType) bool { 146 if len(a) != len(b) { 147 return false 148 } 149 setA := set.NewStringSet() 150 for _, i := range a { 151 setA.Add(string(i)) 152 } 153 154 setB := set.NewStringSet() 155 for _, i := range b { 156 setB.Add(string(i)) 157 } 158 159 return setA.Difference(setB).IsEmpty() 160} 161 162// EqualFilterRuleList tells whether a and b contain the same filters 163func EqualFilterRuleList(a, b []FilterRule) bool { 164 if len(a) != len(b) { 165 return false 166 } 167 168 setA := set.NewStringSet() 169 for _, i := range a { 170 setA.Add(fmt.Sprintf("%s-%s", i.Name, i.Value)) 171 } 172 173 setB := set.NewStringSet() 174 for _, i := range b { 175 setB.Add(fmt.Sprintf("%s-%s", i.Name, i.Value)) 176 } 177 178 return setA.Difference(setB).IsEmpty() 179} 180 181// Equal returns whether this `Config` is equal to another defined by the passed parameters 182func (t *Config) Equal(events []EventType, prefix, suffix string) bool { 183 if t == nil { 184 return false 185 } 186 187 // Compare events 188 passEvents := EqualEventTypeList(t.Events, events) 189 190 // Compare filters 191 var newFilterRules []FilterRule 192 if prefix != "" { 193 newFilterRules = append(newFilterRules, FilterRule{Name: "prefix", Value: prefix}) 194 } 195 if suffix != "" { 196 newFilterRules = append(newFilterRules, FilterRule{Name: "suffix", Value: suffix}) 197 } 198 199 var currentFilterRules []FilterRule 200 if t.Filter != nil { 201 currentFilterRules = t.Filter.S3Key.FilterRules 202 } 203 204 passFilters := EqualFilterRuleList(currentFilterRules, newFilterRules) 205 return passEvents && passFilters 206} 207 208// TopicConfig carries one single topic notification configuration 209type TopicConfig struct { 210 Config 211 Topic string `xml:"Topic"` 212} 213 214// QueueConfig carries one single queue notification configuration 215type QueueConfig struct { 216 Config 217 Queue string `xml:"Queue"` 218} 219 220// LambdaConfig carries one single cloudfunction notification configuration 221type LambdaConfig struct { 222 Config 223 Lambda string `xml:"CloudFunction"` 224} 225 226// Configuration - the struct that represents the whole XML to be sent to the web service 227type Configuration struct { 228 XMLName xml.Name `xml:"NotificationConfiguration"` 229 LambdaConfigs []LambdaConfig `xml:"CloudFunctionConfiguration"` 230 TopicConfigs []TopicConfig `xml:"TopicConfiguration"` 231 QueueConfigs []QueueConfig `xml:"QueueConfiguration"` 232} 233 234// AddTopic adds a given topic config to the general bucket notification config 235func (b *Configuration) AddTopic(topicConfig Config) bool { 236 newTopicConfig := TopicConfig{Config: topicConfig, Topic: topicConfig.Arn.String()} 237 for _, n := range b.TopicConfigs { 238 // If new config matches existing one 239 if n.Topic == newTopicConfig.Arn.String() && newTopicConfig.Filter == n.Filter { 240 241 existingConfig := set.NewStringSet() 242 for _, v := range n.Events { 243 existingConfig.Add(string(v)) 244 } 245 246 newConfig := set.NewStringSet() 247 for _, v := range topicConfig.Events { 248 newConfig.Add(string(v)) 249 } 250 251 if !newConfig.Intersection(existingConfig).IsEmpty() { 252 return false 253 } 254 } 255 } 256 b.TopicConfigs = append(b.TopicConfigs, newTopicConfig) 257 return true 258} 259 260// AddQueue adds a given queue config to the general bucket notification config 261func (b *Configuration) AddQueue(queueConfig Config) bool { 262 newQueueConfig := QueueConfig{Config: queueConfig, Queue: queueConfig.Arn.String()} 263 for _, n := range b.QueueConfigs { 264 if n.Queue == newQueueConfig.Arn.String() && newQueueConfig.Filter == n.Filter { 265 266 existingConfig := set.NewStringSet() 267 for _, v := range n.Events { 268 existingConfig.Add(string(v)) 269 } 270 271 newConfig := set.NewStringSet() 272 for _, v := range queueConfig.Events { 273 newConfig.Add(string(v)) 274 } 275 276 if !newConfig.Intersection(existingConfig).IsEmpty() { 277 return false 278 } 279 } 280 } 281 b.QueueConfigs = append(b.QueueConfigs, newQueueConfig) 282 return true 283} 284 285// AddLambda adds a given lambda config to the general bucket notification config 286func (b *Configuration) AddLambda(lambdaConfig Config) bool { 287 newLambdaConfig := LambdaConfig{Config: lambdaConfig, Lambda: lambdaConfig.Arn.String()} 288 for _, n := range b.LambdaConfigs { 289 if n.Lambda == newLambdaConfig.Arn.String() && newLambdaConfig.Filter == n.Filter { 290 291 existingConfig := set.NewStringSet() 292 for _, v := range n.Events { 293 existingConfig.Add(string(v)) 294 } 295 296 newConfig := set.NewStringSet() 297 for _, v := range lambdaConfig.Events { 298 newConfig.Add(string(v)) 299 } 300 301 if !newConfig.Intersection(existingConfig).IsEmpty() { 302 return false 303 } 304 } 305 } 306 b.LambdaConfigs = append(b.LambdaConfigs, newLambdaConfig) 307 return true 308} 309 310// RemoveTopicByArn removes all topic configurations that match the exact specified ARN 311func (b *Configuration) RemoveTopicByArn(arn Arn) { 312 var topics []TopicConfig 313 for _, topic := range b.TopicConfigs { 314 if topic.Topic != arn.String() { 315 topics = append(topics, topic) 316 } 317 } 318 b.TopicConfigs = topics 319} 320 321// ErrNoConfigMatch is returned when a notification configuration (sqs,sns,lambda) is not found when trying to delete 322var ErrNoConfigMatch = errors.New("no notification configuration matched") 323 324// RemoveTopicByArnEventsPrefixSuffix removes a topic configuration that match the exact specified ARN, events, prefix and suffix 325func (b *Configuration) RemoveTopicByArnEventsPrefixSuffix(arn Arn, events []EventType, prefix, suffix string) error { 326 removeIndex := -1 327 for i, v := range b.TopicConfigs { 328 // if it matches events and filters, mark the index for deletion 329 if v.Topic == arn.String() && v.Config.Equal(events, prefix, suffix) { 330 removeIndex = i 331 break // since we have at most one matching config 332 } 333 } 334 if removeIndex >= 0 { 335 b.TopicConfigs = append(b.TopicConfigs[:removeIndex], b.TopicConfigs[removeIndex+1:]...) 336 return nil 337 } 338 return ErrNoConfigMatch 339} 340 341// RemoveQueueByArn removes all queue configurations that match the exact specified ARN 342func (b *Configuration) RemoveQueueByArn(arn Arn) { 343 var queues []QueueConfig 344 for _, queue := range b.QueueConfigs { 345 if queue.Queue != arn.String() { 346 queues = append(queues, queue) 347 } 348 } 349 b.QueueConfigs = queues 350} 351 352// RemoveQueueByArnEventsPrefixSuffix removes a queue configuration that match the exact specified ARN, events, prefix and suffix 353func (b *Configuration) RemoveQueueByArnEventsPrefixSuffix(arn Arn, events []EventType, prefix, suffix string) error { 354 removeIndex := -1 355 for i, v := range b.QueueConfigs { 356 // if it matches events and filters, mark the index for deletion 357 if v.Queue == arn.String() && v.Config.Equal(events, prefix, suffix) { 358 removeIndex = i 359 break // since we have at most one matching config 360 } 361 } 362 if removeIndex >= 0 { 363 b.QueueConfigs = append(b.QueueConfigs[:removeIndex], b.QueueConfigs[removeIndex+1:]...) 364 return nil 365 } 366 return ErrNoConfigMatch 367} 368 369// RemoveLambdaByArn removes all lambda configurations that match the exact specified ARN 370func (b *Configuration) RemoveLambdaByArn(arn Arn) { 371 var lambdas []LambdaConfig 372 for _, lambda := range b.LambdaConfigs { 373 if lambda.Lambda != arn.String() { 374 lambdas = append(lambdas, lambda) 375 } 376 } 377 b.LambdaConfigs = lambdas 378} 379 380// RemoveLambdaByArnEventsPrefixSuffix removes a topic configuration that match the exact specified ARN, events, prefix and suffix 381func (b *Configuration) RemoveLambdaByArnEventsPrefixSuffix(arn Arn, events []EventType, prefix, suffix string) error { 382 removeIndex := -1 383 for i, v := range b.LambdaConfigs { 384 // if it matches events and filters, mark the index for deletion 385 if v.Lambda == arn.String() && v.Config.Equal(events, prefix, suffix) { 386 removeIndex = i 387 break // since we have at most one matching config 388 } 389 } 390 if removeIndex >= 0 { 391 b.LambdaConfigs = append(b.LambdaConfigs[:removeIndex], b.LambdaConfigs[removeIndex+1:]...) 392 return nil 393 } 394 return ErrNoConfigMatch 395} 396