Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add minio_s3_bucket_notification resource #396

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ services:
MINIO_ROOT_USER: minio
MINIO_ROOT_PASSWORD: minio123
MINIO_CI_CD: "1"
MINIO_NOTIFY_WEBHOOK_ENABLE_primary: "on"
MINIO_NOTIFY_WEBHOOK_ENDPOINT_primary: https://webhook.example.com
felladrin marked this conversation as resolved.
Show resolved Hide resolved
command: server --console-address :9001 /data{0...3}
adminio-ui:
image: docker.io/rzrbld/adminio-ui:v1.93
Expand Down
65 changes: 65 additions & 0 deletions docs/resources/s3_bucket_notification.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "minio_s3_bucket_notification Resource - terraform-provider-minio"
subcategory: ""
description: |-

---

# minio_s3_bucket_notification (Resource)

## Example Usage

```terraform
resource "minio_s3_bucket" "bucket" {
bucket = "example-bucket"
}

resource "minio_s3_bucket_notification" "bucket" {
bucket = minio_s3_bucket.state_terraform_s3.bucket

queue {
id = "notification-queue"
queue_arn = "arn:minio:sqs::primary:webhook"

events = [
"s3:ObjectCreated:*",
"s3:ObjectRemoved:Delete",
]

filter_prefix = "example/"
filter_suffix = ".png"
}
}
```

<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `bucket` (String)

### Optional

- `queue` (Block List) (see [below for nested schema](#nested-schema-for-queue))

### Read-Only

- `id` (String) The ID of this resource.

### Nested Schema for `queue`

Required:

- `events` (Set of String)
- `queue_arn` (String)

Optional:

- `filter_prefix` (String)
- `filter_suffix` (String)

Read-Only:

- `id` (String) The ID of this resource.
17 changes: 17 additions & 0 deletions examples/bucket/bucket.tf
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,20 @@ resource "minio_s3_bucket_versioning" "bucket" {
status = "Enabled"
}
}

resource "minio_s3_bucket_notification" "bucket" {
bucket = minio_s3_bucket.state_terraform_s3.bucket

queue {
id = "notification-queue"
queue_arn = "arn:minio:sqs::primary:webhook"

events = [
"s3:ObjectCreated:*",
"s3:ObjectRemoved:Delete",
]

filter_prefix = "example/"
filter_suffix = ".png"
}
}
12 changes: 12 additions & 0 deletions minio/check_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,18 @@ func BucketVersioningConfig(d *schema.ResourceData, meta interface{}) *S3MinioBu
}
}

// BucketNotificationConfig creates config for managing minio bucket notifications
func BucketNotificationConfig(d *schema.ResourceData, meta interface{}) *S3MinioBucketNotification {
m := meta.(*S3MinioClient)
config := getNotificationConfiguration(d)

return &S3MinioBucketNotification{
MinioClient: m.S3Client,
MinioBucket: d.Get("bucket").(string),
Configuration: &config,
}
}

// NewConfig creates a new config for minio
func NewConfig(d *schema.ResourceData) *S3MinioConfig {
user := d.Get("minio_user").(string)
Expand Down
8 changes: 8 additions & 0 deletions minio/payload.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package minio
import (
"github.com/minio/madmin-go"
minio "github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/notification"
"github.com/minio/minio-go/v7/pkg/policy"
"github.com/minio/minio-go/v7/pkg/set"
)
Expand Down Expand Up @@ -63,6 +64,13 @@ type S3MinioBucketVersioning struct {
VersioningConfiguration *S3MinioBucketVersioningConfiguration
}

// S3MinioBucketNotification
type S3MinioBucketNotification struct {
MinioClient *minio.Client
MinioBucket string
Configuration *notification.Configuration
}

// S3MinioServiceAccountConfig defines service account config
type S3MinioServiceAccountConfig struct {
MinioAdmin *madmin.AdminClient
Expand Down
1 change: 1 addition & 0 deletions minio/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ func Provider() *schema.Provider {
"minio_s3_bucket": resourceMinioBucket(),
"minio_s3_bucket_policy": resourceMinioBucketPolicy(),
"minio_s3_bucket_versioning": resourceMinioBucketVersioning(),
"minio_s3_bucket_notification": resourceMinioBucketNotification(),
"minio_s3_object": resourceMinioObject(),
"minio_iam_group": resourceMinioIAMGroup(),
"minio_iam_group_membership": resourceMinioIAMGroupMembership(),
Expand Down
222 changes: 222 additions & 0 deletions minio/resource_minio_s3_bucket_notification.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
package minio

import (
"context"
"fmt"
"log"

"github.com/hashicorp/go-cty/cty"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/minio/minio-go/v7/pkg/notification"
)

func resourceMinioBucketNotification() *schema.Resource {
return &schema.Resource{
CreateContext: minioPutBucketNotification,
ReadContext: minioReadBucketNotification,
UpdateContext: minioPutBucketNotification,
DeleteContext: minioDeleteBucketNotification,
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Schema: map[string]*schema.Schema{
"bucket": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"queue": {
Type: schema.TypeList,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"id": {
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"filter_prefix": {
Type: schema.TypeString,
Optional: true,
},
"filter_suffix": {
Type: schema.TypeString,
Optional: true,
},
"queue_arn": {
Type: schema.TypeString,
Required: true,
ValidateDiagFunc: validateMinioArn,
},
"events": {
Type: schema.TypeSet,
Required: true,
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
},
},
},
},
},
}
}

func minioPutBucketNotification(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
bucketNotificationConfig := BucketNotificationConfig(d, meta)

log.Printf("[DEBUG] S3 bucket: %s, put notification configuration: %v", bucketNotificationConfig.MinioBucket, bucketNotificationConfig.Configuration)

err := bucketNotificationConfig.MinioClient.SetBucketNotification(
ctx,
bucketNotificationConfig.MinioBucket,
*bucketNotificationConfig.Configuration,
)

if err != nil {
return NewResourceError("error putting bucket notification configuration: %v", d.Id(), err)
}

d.SetId(bucketNotificationConfig.MinioBucket)

return nil
}

func minioReadBucketNotification(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
bucketNotificationConfig := BucketNotificationConfig(d, meta)

log.Printf("[DEBUG] S3 bucket notification configuration, read for bucket: %s", d.Id())

notificationConfig, err := bucketNotificationConfig.MinioClient.GetBucketNotification(ctx, d.Id())
if err != nil {
return NewResourceError("failed to load bucket notification configuration", d.Id(), err)
}

_ = d.Set("bucket", d.Id())

if err := d.Set("queue", flattenQueueNotificationConfiguration(notificationConfig.QueueConfigs)); err != nil {
return NewResourceError("failed to load bucket queue notifications", d.Id(), err)
}

return nil
}

func minioDeleteBucketNotification(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
bucketNotificationConfig := BucketNotificationConfig(d, meta)

log.Printf("[DEBUG] S3 bucket: %s, removing notification configuration", bucketNotificationConfig.MinioBucket)

err := bucketNotificationConfig.MinioClient.SetBucketNotification(
ctx,
bucketNotificationConfig.MinioBucket,
notification.Configuration{},
)

if err != nil {
return NewResourceError("error removing bucket notifications: %s", bucketNotificationConfig.MinioBucket, err)
}

return nil
}

func flattenNotificationConfigurationFilter(filter *notification.Filter) map[string]interface{} {
filterRules := map[string]interface{}{}
if filter.S3Key.FilterRules == nil {
return filterRules
}

for _, f := range filter.S3Key.FilterRules {
if f.Name == "prefix" {
filterRules["filter_prefix"] = f.Value
}
if f.Name == "suffix" {
filterRules["filter_suffix"] = f.Value
}
}
return filterRules
}

func flattenQueueNotificationConfiguration(configs []notification.QueueConfig) []map[string]interface{} {
queueNotifications := make([]map[string]interface{}, 0, len(configs))
for _, notification := range configs {
var conf map[string]interface{}
if filter := notification.Filter; filter != nil {
conf = flattenNotificationConfigurationFilter(filter)
} else {
conf = map[string]interface{}{}
}

conf["id"] = notification.ID
conf["events"] = notification.Events
// The Config.Arn value is not set to the queue ARN even though it's
// expected in the submission, so we're getting the correct value
// from the Queue attribute on the response object
conf["queue_arn"] = notification.Queue
queueNotifications = append(queueNotifications, conf)
}

return queueNotifications
}

func getNotificationConfiguration(d *schema.ResourceData) notification.Configuration {
var config notification.Configuration
queueConfigs := getNotificationQueueConfigs(d)

for _, c := range queueConfigs {
config.AddQueue(c)
}

return config
}

func getNotificationQueueConfigs(d *schema.ResourceData) []notification.Config {
queueFunctionNotifications := d.Get("queue").([]interface{})
configs := make([]notification.Config, 0, len(queueFunctionNotifications))

for i, c := range queueFunctionNotifications {
config := notification.Config{Filter: &notification.Filter{}}
c := c.(map[string]interface{})

if queueArnStr, ok := c["queue_arn"].(string); ok {
queueArn, err := notification.NewArnFromString(queueArnStr)
if err != nil {
continue
}
config.Arn = queueArn
}

if val, ok := c["id"].(string); ok && val != "" {
config.ID = val
} else {
config.ID = resource.PrefixedUniqueId("tf-s3-queue-")
}

events := d.Get(fmt.Sprintf("queue.%d.events", i)).(*schema.Set).List()
for _, e := range events {
config.AddEvents(notification.EventType(e.(string)))
}

if val, ok := c["filter_prefix"].(string); ok && val != "" {
config.AddFilterPrefix(val)
}
if val, ok := c["filter_suffix"].(string); ok && val != "" {
config.AddFilterSuffix(val)
}

configs = append(configs, config)
}

return configs
}

func validateMinioArn(v interface{}, p cty.Path) (errors diag.Diagnostics) {
value := v.(string)
_, err := notification.NewArnFromString(value)

if err != nil {
return diag.Errorf("value: %s is not a valid ARN", value)
}

return nil
}
Loading