notification.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  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. if t == nil {
  160. return false
  161. }
  162. // Compare events
  163. passEvents := EqualEventTypeList(t.Events, events)
  164. // Compare filters
  165. var newFilterRules []FilterRule
  166. if prefix != "" {
  167. newFilterRules = append(newFilterRules, FilterRule{Name: "prefix", Value: prefix})
  168. }
  169. if suffix != "" {
  170. newFilterRules = append(newFilterRules, FilterRule{Name: "suffix", Value: suffix})
  171. }
  172. var currentFilterRules []FilterRule
  173. if t.Filter != nil {
  174. currentFilterRules = t.Filter.S3Key.FilterRules
  175. }
  176. passFilters := EqualFilterRuleList(currentFilterRules, newFilterRules)
  177. return passEvents && passFilters
  178. }
  179. // TopicConfig carries one single topic notification configuration
  180. type TopicConfig struct {
  181. Config
  182. Topic string `xml:"Topic"`
  183. }
  184. // QueueConfig carries one single queue notification configuration
  185. type QueueConfig struct {
  186. Config
  187. Queue string `xml:"Queue"`
  188. }
  189. // LambdaConfig carries one single cloudfunction notification configuration
  190. type LambdaConfig struct {
  191. Config
  192. Lambda string `xml:"CloudFunction"`
  193. }
  194. // Configuration - the struct that represents the whole XML to be sent to the web service
  195. type Configuration struct {
  196. XMLName xml.Name `xml:"NotificationConfiguration"`
  197. LambdaConfigs []LambdaConfig `xml:"CloudFunctionConfiguration"`
  198. TopicConfigs []TopicConfig `xml:"TopicConfiguration"`
  199. QueueConfigs []QueueConfig `xml:"QueueConfiguration"`
  200. }
  201. // AddTopic adds a given topic config to the general bucket notification config
  202. func (b *Configuration) AddTopic(topicConfig Config) bool {
  203. newTopicConfig := TopicConfig{Config: topicConfig, Topic: topicConfig.Arn.String()}
  204. for _, n := range b.TopicConfigs {
  205. // If new config matches existing one
  206. if n.Topic == newTopicConfig.Arn.String() && newTopicConfig.Filter == n.Filter {
  207. existingConfig := set.NewStringSet()
  208. for _, v := range n.Events {
  209. existingConfig.Add(string(v))
  210. }
  211. newConfig := set.NewStringSet()
  212. for _, v := range topicConfig.Events {
  213. newConfig.Add(string(v))
  214. }
  215. if !newConfig.Intersection(existingConfig).IsEmpty() {
  216. return false
  217. }
  218. }
  219. }
  220. b.TopicConfigs = append(b.TopicConfigs, newTopicConfig)
  221. return true
  222. }
  223. // AddQueue adds a given queue config to the general bucket notification config
  224. func (b *Configuration) AddQueue(queueConfig Config) bool {
  225. newQueueConfig := QueueConfig{Config: queueConfig, Queue: queueConfig.Arn.String()}
  226. for _, n := range b.QueueConfigs {
  227. if n.Queue == newQueueConfig.Arn.String() && newQueueConfig.Filter == n.Filter {
  228. existingConfig := set.NewStringSet()
  229. for _, v := range n.Events {
  230. existingConfig.Add(string(v))
  231. }
  232. newConfig := set.NewStringSet()
  233. for _, v := range queueConfig.Events {
  234. newConfig.Add(string(v))
  235. }
  236. if !newConfig.Intersection(existingConfig).IsEmpty() {
  237. return false
  238. }
  239. }
  240. }
  241. b.QueueConfigs = append(b.QueueConfigs, newQueueConfig)
  242. return true
  243. }
  244. // AddLambda adds a given lambda config to the general bucket notification config
  245. func (b *Configuration) AddLambda(lambdaConfig Config) bool {
  246. newLambdaConfig := LambdaConfig{Config: lambdaConfig, Lambda: lambdaConfig.Arn.String()}
  247. for _, n := range b.LambdaConfigs {
  248. if n.Lambda == newLambdaConfig.Arn.String() && newLambdaConfig.Filter == n.Filter {
  249. existingConfig := set.NewStringSet()
  250. for _, v := range n.Events {
  251. existingConfig.Add(string(v))
  252. }
  253. newConfig := set.NewStringSet()
  254. for _, v := range lambdaConfig.Events {
  255. newConfig.Add(string(v))
  256. }
  257. if !newConfig.Intersection(existingConfig).IsEmpty() {
  258. return false
  259. }
  260. }
  261. }
  262. b.LambdaConfigs = append(b.LambdaConfigs, newLambdaConfig)
  263. return true
  264. }
  265. // RemoveTopicByArn removes all topic configurations that match the exact specified ARN
  266. func (b *Configuration) RemoveTopicByArn(arn Arn) {
  267. var topics []TopicConfig
  268. for _, topic := range b.TopicConfigs {
  269. if topic.Topic != arn.String() {
  270. topics = append(topics, topic)
  271. }
  272. }
  273. b.TopicConfigs = topics
  274. }
  275. // ErrNoConfigMatch is returned when a notification configuration (sqs,sns,lambda) is not found when trying to delete
  276. var ErrNoConfigMatch = errors.New("no notification configuration matched")
  277. // RemoveTopicByArnEventsPrefixSuffix removes a topic configuration that match the exact specified ARN, events, prefix and suffix
  278. func (b *Configuration) RemoveTopicByArnEventsPrefixSuffix(arn Arn, events []EventType, prefix, suffix string) error {
  279. removeIndex := -1
  280. for i, v := range b.TopicConfigs {
  281. // if it matches events and filters, mark the index for deletion
  282. if v.Topic == arn.String() && v.Config.Equal(events, prefix, suffix) {
  283. removeIndex = i
  284. break // since we have at most one matching config
  285. }
  286. }
  287. if removeIndex >= 0 {
  288. b.TopicConfigs = append(b.TopicConfigs[:removeIndex], b.TopicConfigs[removeIndex+1:]...)
  289. return nil
  290. }
  291. return ErrNoConfigMatch
  292. }
  293. // RemoveQueueByArn removes all queue configurations that match the exact specified ARN
  294. func (b *Configuration) RemoveQueueByArn(arn Arn) {
  295. var queues []QueueConfig
  296. for _, queue := range b.QueueConfigs {
  297. if queue.Queue != arn.String() {
  298. queues = append(queues, queue)
  299. }
  300. }
  301. b.QueueConfigs = queues
  302. }
  303. // RemoveQueueByArnEventsPrefixSuffix removes a queue configuration that match the exact specified ARN, events, prefix and suffix
  304. func (b *Configuration) RemoveQueueByArnEventsPrefixSuffix(arn Arn, events []EventType, prefix, suffix string) error {
  305. removeIndex := -1
  306. for i, v := range b.QueueConfigs {
  307. // if it matches events and filters, mark the index for deletion
  308. if v.Queue == arn.String() && v.Config.Equal(events, prefix, suffix) {
  309. removeIndex = i
  310. break // since we have at most one matching config
  311. }
  312. }
  313. if removeIndex >= 0 {
  314. b.QueueConfigs = append(b.QueueConfigs[:removeIndex], b.QueueConfigs[removeIndex+1:]...)
  315. return nil
  316. }
  317. return ErrNoConfigMatch
  318. }
  319. // RemoveLambdaByArn removes all lambda configurations that match the exact specified ARN
  320. func (b *Configuration) RemoveLambdaByArn(arn Arn) {
  321. var lambdas []LambdaConfig
  322. for _, lambda := range b.LambdaConfigs {
  323. if lambda.Lambda != arn.String() {
  324. lambdas = append(lambdas, lambda)
  325. }
  326. }
  327. b.LambdaConfigs = lambdas
  328. }
  329. // RemoveLambdaByArnEventsPrefixSuffix removes a topic configuration that match the exact specified ARN, events, prefix and suffix
  330. func (b *Configuration) RemoveLambdaByArnEventsPrefixSuffix(arn Arn, events []EventType, prefix, suffix string) error {
  331. removeIndex := -1
  332. for i, v := range b.LambdaConfigs {
  333. // if it matches events and filters, mark the index for deletion
  334. if v.Lambda == arn.String() && v.Config.Equal(events, prefix, suffix) {
  335. removeIndex = i
  336. break // since we have at most one matching config
  337. }
  338. }
  339. if removeIndex >= 0 {
  340. b.LambdaConfigs = append(b.LambdaConfigs[:removeIndex], b.LambdaConfigs[removeIndex+1:]...)
  341. return nil
  342. }
  343. return ErrNoConfigMatch
  344. }