You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

notification.go 12KB

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