github.com/minio/minio-go/v6@v6.0.57/bucket-notification.go (about) 1 /* 2 * MinIO Go Library for Amazon S3 Compatible Cloud Storage 3 * Copyright 2015-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 minio 19 20 import ( 21 "encoding/xml" 22 "errors" 23 "fmt" 24 25 "github.com/minio/minio-go/v6/pkg/set" 26 ) 27 28 // NotificationEventType is a S3 notification event associated to the bucket notification configuration 29 type NotificationEventType 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 33 const ( 34 ObjectCreatedAll NotificationEventType = "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 ) 47 48 // FilterRule - child of S3Key, a tag in the notification xml which 49 // carries suffix/prefix filters 50 type FilterRule struct { 51 Name string `xml:"Name"` 52 Value string `xml:"Value"` 53 } 54 55 // S3Key - child of Filter, a tag in the notification xml which 56 // carries suffix/prefix filters 57 type S3Key struct { 58 FilterRules []FilterRule `xml:"FilterRule,omitempty"` 59 } 60 61 // Filter - a tag in the notification xml structure which carries 62 // suffix/prefix filters 63 type Filter struct { 64 S3Key S3Key `xml:"S3Key,omitempty"` 65 } 66 67 // Arn - holds ARN information that will be sent to the web service, 68 // ARN desciption can be found in http://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html 69 type Arn struct { 70 Partition string 71 Service string 72 Region string 73 AccountID string 74 Resource string 75 } 76 77 // NewArn creates new ARN based on the given partition, service, region, account id and resource 78 func NewArn(partition, service, region, accountID, resource string) Arn { 79 return Arn{Partition: partition, 80 Service: service, 81 Region: region, 82 AccountID: accountID, 83 Resource: resource} 84 } 85 86 // String returns the string format of the ARN 87 func (arn Arn) String() string { 88 return "arn:" + arn.Partition + ":" + arn.Service + ":" + arn.Region + ":" + arn.AccountID + ":" + arn.Resource 89 } 90 91 // NotificationConfig - represents one single notification configuration 92 // such as topic, queue or lambda configuration. 93 type NotificationConfig struct { 94 ID string `xml:"Id,omitempty"` 95 Arn Arn `xml:"-"` 96 Events []NotificationEventType `xml:"Event"` 97 Filter *Filter `xml:"Filter,omitempty"` 98 } 99 100 // NewNotificationConfig creates one notification config and sets the given ARN 101 func NewNotificationConfig(arn Arn) NotificationConfig { 102 return NotificationConfig{Arn: arn, Filter: &Filter{}} 103 } 104 105 // AddEvents adds one event to the current notification config 106 func (t *NotificationConfig) AddEvents(events ...NotificationEventType) { 107 t.Events = append(t.Events, events...) 108 } 109 110 // AddFilterSuffix sets the suffix configuration to the current notification config 111 func (t *NotificationConfig) AddFilterSuffix(suffix string) { 112 if t.Filter == nil { 113 t.Filter = &Filter{} 114 } 115 newFilterRule := FilterRule{Name: "suffix", Value: suffix} 116 // Replace any suffix rule if existing and add to the list otherwise 117 for index := range t.Filter.S3Key.FilterRules { 118 if t.Filter.S3Key.FilterRules[index].Name == "suffix" { 119 t.Filter.S3Key.FilterRules[index] = newFilterRule 120 return 121 } 122 } 123 t.Filter.S3Key.FilterRules = append(t.Filter.S3Key.FilterRules, newFilterRule) 124 } 125 126 // AddFilterPrefix sets the prefix configuration to the current notification config 127 func (t *NotificationConfig) AddFilterPrefix(prefix string) { 128 if t.Filter == nil { 129 t.Filter = &Filter{} 130 } 131 newFilterRule := FilterRule{Name: "prefix", Value: prefix} 132 // Replace any prefix rule if existing and add to the list otherwise 133 for index := range t.Filter.S3Key.FilterRules { 134 if t.Filter.S3Key.FilterRules[index].Name == "prefix" { 135 t.Filter.S3Key.FilterRules[index] = newFilterRule 136 return 137 } 138 } 139 t.Filter.S3Key.FilterRules = append(t.Filter.S3Key.FilterRules, newFilterRule) 140 } 141 142 // EqualNotificationEventTypeList tells whether a and b contain the same events 143 func EqualNotificationEventTypeList(a, b []NotificationEventType) bool { 144 if len(a) != len(b) { 145 return false 146 } 147 setA := set.NewStringSet() 148 for _, i := range a { 149 setA.Add(string(i)) 150 } 151 152 setB := set.NewStringSet() 153 for _, i := range b { 154 setB.Add(string(i)) 155 } 156 157 return setA.Difference(setB).IsEmpty() 158 } 159 160 // EqualFilterRuleList tells whether a and b contain the same filters 161 func EqualFilterRuleList(a, b []FilterRule) bool { 162 if len(a) != len(b) { 163 return false 164 } 165 166 setA := set.NewStringSet() 167 for _, i := range a { 168 setA.Add(fmt.Sprintf("%s-%s", i.Name, i.Value)) 169 } 170 171 setB := set.NewStringSet() 172 for _, i := range b { 173 setB.Add(fmt.Sprintf("%s-%s", i.Name, i.Value)) 174 } 175 176 return setA.Difference(setB).IsEmpty() 177 } 178 179 // Equal returns whether this `NotificationConfig` is equal to another defined by the passed parameters 180 func (t *NotificationConfig) Equal(events []NotificationEventType, prefix, suffix string) bool { 181 //Compare events 182 passEvents := EqualNotificationEventTypeList(t.Events, events) 183 184 //Compare filters 185 var newFilter []FilterRule 186 if prefix != "" { 187 newFilter = append(newFilter, FilterRule{Name: "prefix", Value: prefix}) 188 } 189 if suffix != "" { 190 newFilter = append(newFilter, FilterRule{Name: "suffix", Value: suffix}) 191 } 192 193 passFilters := EqualFilterRuleList(t.Filter.S3Key.FilterRules, newFilter) 194 // if it matches events and filters, mark the index for deletion 195 return passEvents && passFilters 196 } 197 198 // TopicConfig carries one single topic notification configuration 199 type TopicConfig struct { 200 NotificationConfig 201 Topic string `xml:"Topic"` 202 } 203 204 // QueueConfig carries one single queue notification configuration 205 type QueueConfig struct { 206 NotificationConfig 207 Queue string `xml:"Queue"` 208 } 209 210 // LambdaConfig carries one single cloudfunction notification configuration 211 type LambdaConfig struct { 212 NotificationConfig 213 Lambda string `xml:"CloudFunction"` 214 } 215 216 // BucketNotification - the struct that represents the whole XML to be sent to the web service 217 type BucketNotification struct { 218 XMLName xml.Name `xml:"NotificationConfiguration"` 219 LambdaConfigs []LambdaConfig `xml:"CloudFunctionConfiguration"` 220 TopicConfigs []TopicConfig `xml:"TopicConfiguration"` 221 QueueConfigs []QueueConfig `xml:"QueueConfiguration"` 222 } 223 224 // AddTopic adds a given topic config to the general bucket notification config 225 func (b *BucketNotification) AddTopic(topicConfig NotificationConfig) bool { 226 newTopicConfig := TopicConfig{NotificationConfig: topicConfig, Topic: topicConfig.Arn.String()} 227 for _, n := range b.TopicConfigs { 228 // If new config matches existing one 229 if n.Topic == newTopicConfig.Arn.String() && newTopicConfig.Filter == n.Filter { 230 231 existingConfig := set.NewStringSet() 232 for _, v := range n.Events { 233 existingConfig.Add(string(v)) 234 } 235 236 newConfig := set.NewStringSet() 237 for _, v := range topicConfig.Events { 238 newConfig.Add(string(v)) 239 } 240 241 if !newConfig.Intersection(existingConfig).IsEmpty() { 242 return false 243 } 244 } 245 } 246 b.TopicConfigs = append(b.TopicConfigs, newTopicConfig) 247 return true 248 } 249 250 // AddQueue adds a given queue config to the general bucket notification config 251 func (b *BucketNotification) AddQueue(queueConfig NotificationConfig) bool { 252 newQueueConfig := QueueConfig{NotificationConfig: queueConfig, Queue: queueConfig.Arn.String()} 253 for _, n := range b.QueueConfigs { 254 if n.Queue == newQueueConfig.Arn.String() && newQueueConfig.Filter == n.Filter { 255 256 existingConfig := set.NewStringSet() 257 for _, v := range n.Events { 258 existingConfig.Add(string(v)) 259 } 260 261 newConfig := set.NewStringSet() 262 for _, v := range queueConfig.Events { 263 newConfig.Add(string(v)) 264 } 265 266 if !newConfig.Intersection(existingConfig).IsEmpty() { 267 return false 268 } 269 } 270 } 271 b.QueueConfigs = append(b.QueueConfigs, newQueueConfig) 272 return true 273 } 274 275 // AddLambda adds a given lambda config to the general bucket notification config 276 func (b *BucketNotification) AddLambda(lambdaConfig NotificationConfig) bool { 277 newLambdaConfig := LambdaConfig{NotificationConfig: lambdaConfig, Lambda: lambdaConfig.Arn.String()} 278 for _, n := range b.LambdaConfigs { 279 if n.Lambda == newLambdaConfig.Arn.String() && newLambdaConfig.Filter == n.Filter { 280 281 existingConfig := set.NewStringSet() 282 for _, v := range n.Events { 283 existingConfig.Add(string(v)) 284 } 285 286 newConfig := set.NewStringSet() 287 for _, v := range lambdaConfig.Events { 288 newConfig.Add(string(v)) 289 } 290 291 if !newConfig.Intersection(existingConfig).IsEmpty() { 292 return false 293 } 294 } 295 } 296 b.LambdaConfigs = append(b.LambdaConfigs, newLambdaConfig) 297 return true 298 } 299 300 // RemoveTopicByArn removes all topic configurations that match the exact specified ARN 301 func (b *BucketNotification) RemoveTopicByArn(arn Arn) { 302 var topics []TopicConfig 303 for _, topic := range b.TopicConfigs { 304 if topic.Topic != arn.String() { 305 topics = append(topics, topic) 306 } 307 } 308 b.TopicConfigs = topics 309 } 310 311 // ErrNoNotificationConfigMatch is returned when a notification configuration (sqs,sns,lambda) is not found when trying to delete 312 var ErrNoNotificationConfigMatch = errors.New("no notification configuration matched") 313 314 // RemoveTopicByArnEventsPrefixSuffix removes a topic configuration that match the exact specified ARN, events, prefix and suffix 315 func (b *BucketNotification) RemoveTopicByArnEventsPrefixSuffix(arn Arn, events []NotificationEventType, prefix, suffix string) error { 316 removeIndex := -1 317 for i, v := range b.TopicConfigs { 318 // if it matches events and filters, mark the index for deletion 319 if v.Topic == arn.String() && v.NotificationConfig.Equal(events, prefix, suffix) { 320 removeIndex = i 321 break // since we have at most one matching config 322 } 323 } 324 if removeIndex >= 0 { 325 b.TopicConfigs = append(b.TopicConfigs[:removeIndex], b.TopicConfigs[removeIndex+1:]...) 326 return nil 327 } 328 return ErrNoNotificationConfigMatch 329 } 330 331 // RemoveQueueByArn removes all queue configurations that match the exact specified ARN 332 func (b *BucketNotification) RemoveQueueByArn(arn Arn) { 333 var queues []QueueConfig 334 for _, queue := range b.QueueConfigs { 335 if queue.Queue != arn.String() { 336 queues = append(queues, queue) 337 } 338 } 339 b.QueueConfigs = queues 340 } 341 342 // RemoveQueueByArnEventsPrefixSuffix removes a queue configuration that match the exact specified ARN, events, prefix and suffix 343 func (b *BucketNotification) RemoveQueueByArnEventsPrefixSuffix(arn Arn, events []NotificationEventType, prefix, suffix string) error { 344 removeIndex := -1 345 for i, v := range b.QueueConfigs { 346 // if it matches events and filters, mark the index for deletion 347 if v.Queue == arn.String() && v.NotificationConfig.Equal(events, prefix, suffix) { 348 removeIndex = i 349 break // since we have at most one matching config 350 } 351 } 352 if removeIndex >= 0 { 353 b.QueueConfigs = append(b.QueueConfigs[:removeIndex], b.QueueConfigs[removeIndex+1:]...) 354 return nil 355 } 356 return ErrNoNotificationConfigMatch 357 } 358 359 // RemoveLambdaByArn removes all lambda configurations that match the exact specified ARN 360 func (b *BucketNotification) RemoveLambdaByArn(arn Arn) { 361 var lambdas []LambdaConfig 362 for _, lambda := range b.LambdaConfigs { 363 if lambda.Lambda != arn.String() { 364 lambdas = append(lambdas, lambda) 365 } 366 } 367 b.LambdaConfigs = lambdas 368 } 369 370 // RemoveLambdaByArnEventsPrefixSuffix removes a topic configuration that match the exact specified ARN, events, prefix and suffix 371 func (b *BucketNotification) RemoveLambdaByArnEventsPrefixSuffix(arn Arn, events []NotificationEventType, prefix, suffix string) error { 372 removeIndex := -1 373 for i, v := range b.LambdaConfigs { 374 // if it matches events and filters, mark the index for deletion 375 if v.Lambda == arn.String() && v.NotificationConfig.Equal(events, prefix, suffix) { 376 removeIndex = i 377 break // since we have at most one matching config 378 } 379 } 380 if removeIndex >= 0 { 381 b.LambdaConfigs = append(b.LambdaConfigs[:removeIndex], b.LambdaConfigs[removeIndex+1:]...) 382 return nil 383 } 384 return ErrNoNotificationConfigMatch 385 }