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  }