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