github.com/minio/console@v1.4.1/api/user_buckets_events.go (about)

     1  // This file is part of MinIO Console Server
     2  // Copyright (c) 2021 MinIO, Inc.
     3  //
     4  // This program is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Affero General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // This program is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    12  // GNU Affero General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Affero General Public License
    15  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package api
    18  
    19  import (
    20  	"context"
    21  	"strings"
    22  
    23  	"github.com/go-openapi/runtime/middleware"
    24  	"github.com/go-openapi/swag"
    25  	"github.com/minio/console/api/operations"
    26  	bucketApi "github.com/minio/console/api/operations/bucket"
    27  	"github.com/minio/console/models"
    28  	"github.com/minio/minio-go/v7/pkg/notification"
    29  )
    30  
    31  func registerBucketEventsHandlers(api *operations.ConsoleAPI) {
    32  	// list bucket events
    33  	api.BucketListBucketEventsHandler = bucketApi.ListBucketEventsHandlerFunc(func(params bucketApi.ListBucketEventsParams, session *models.Principal) middleware.Responder {
    34  		listBucketEventsResponse, err := getListBucketEventsResponse(session, params)
    35  		if err != nil {
    36  			return bucketApi.NewListBucketEventsDefault(err.Code).WithPayload(err.APIError)
    37  		}
    38  		return bucketApi.NewListBucketEventsOK().WithPayload(listBucketEventsResponse)
    39  	})
    40  	// create bucket event
    41  	api.BucketCreateBucketEventHandler = bucketApi.CreateBucketEventHandlerFunc(func(params bucketApi.CreateBucketEventParams, session *models.Principal) middleware.Responder {
    42  		if err := getCreateBucketEventsResponse(session, params); err != nil {
    43  			return bucketApi.NewCreateBucketEventDefault(err.Code).WithPayload(err.APIError)
    44  		}
    45  		return bucketApi.NewCreateBucketEventCreated()
    46  	})
    47  	// delete bucket event
    48  	api.BucketDeleteBucketEventHandler = bucketApi.DeleteBucketEventHandlerFunc(func(params bucketApi.DeleteBucketEventParams, session *models.Principal) middleware.Responder {
    49  		if err := getDeleteBucketEventsResponse(session, params); err != nil {
    50  			return bucketApi.NewDeleteBucketEventDefault(err.Code).WithPayload(err.APIError)
    51  		}
    52  		return bucketApi.NewDeleteBucketEventNoContent()
    53  	})
    54  }
    55  
    56  // listBucketEvents fetches a list of all events set for a bucket and serializes them for a proper output
    57  func listBucketEvents(client MinioClient, bucketName string) ([]*models.NotificationConfig, error) {
    58  	var configs []*models.NotificationConfig
    59  	bn, err := client.getBucketNotification(context.Background(), bucketName)
    60  	if err != nil {
    61  		return nil, err
    62  	}
    63  
    64  	// Generate pretty event names from event types
    65  	prettyEventNames := func(eventsTypes []notification.EventType) []models.NotificationEventType {
    66  		var result []models.NotificationEventType
    67  		for _, eventType := range eventsTypes {
    68  			var eventTypePretty models.NotificationEventType
    69  			switch eventType {
    70  			case notification.ObjectAccessedAll:
    71  				eventTypePretty = models.NotificationEventTypeGet
    72  			case notification.ObjectCreatedAll:
    73  				eventTypePretty = models.NotificationEventTypePut
    74  			case notification.ObjectRemovedAll:
    75  				eventTypePretty = models.NotificationEventTypeDelete
    76  			case notification.ObjectReplicationAll:
    77  				eventTypePretty = models.NotificationEventTypeReplica
    78  			case notification.ObjectTransitionAll:
    79  				eventTypePretty = models.NotificationEventTypeIlm
    80  			case notification.ObjectScannerManyVersions, notification.ObjectScannerBigPrefix:
    81  				eventTypePretty = models.NotificationEventTypeScanner
    82  			default:
    83  				continue
    84  			}
    85  
    86  			result = append(result, eventTypePretty)
    87  		}
    88  		return result
    89  	}
    90  	// part of implementation taken from minio/mc
    91  	// s3Client.ListNotificationConfigs()... to serialize configurations
    92  	getFilters := func(config notification.Config) (prefix, suffix string) {
    93  		if config.Filter == nil {
    94  			return
    95  		}
    96  		for _, filter := range config.Filter.S3Key.FilterRules {
    97  			if strings.ToLower(filter.Name) == "prefix" {
    98  				prefix = filter.Value
    99  			}
   100  			if strings.ToLower(filter.Name) == "suffix" {
   101  				suffix = filter.Value
   102  			}
   103  
   104  		}
   105  		return prefix, suffix
   106  	}
   107  	for _, embed := range bn.TopicConfigs {
   108  		prefix, suffix := getFilters(embed.Config)
   109  		configs = append(configs, &models.NotificationConfig{
   110  			ID:     embed.ID,
   111  			Arn:    swag.String(embed.Topic),
   112  			Events: prettyEventNames(embed.Events),
   113  			Prefix: prefix,
   114  			Suffix: suffix,
   115  		})
   116  	}
   117  	for _, embed := range bn.QueueConfigs {
   118  		prefix, suffix := getFilters(embed.Config)
   119  		configs = append(configs, &models.NotificationConfig{
   120  			ID:     embed.ID,
   121  			Arn:    swag.String(embed.Queue),
   122  			Events: prettyEventNames(embed.Events),
   123  			Prefix: prefix,
   124  			Suffix: suffix,
   125  		})
   126  	}
   127  	for _, embed := range bn.LambdaConfigs {
   128  		prefix, suffix := getFilters(embed.Config)
   129  		configs = append(configs, &models.NotificationConfig{
   130  			ID:     embed.ID,
   131  			Arn:    swag.String(embed.Lambda),
   132  			Events: prettyEventNames(embed.Events),
   133  			Prefix: prefix,
   134  			Suffix: suffix,
   135  		})
   136  	}
   137  	return configs, nil
   138  }
   139  
   140  // getListBucketsResponse performs listBucketEvents() and serializes it to the handler's output
   141  func getListBucketEventsResponse(session *models.Principal, params bucketApi.ListBucketEventsParams) (*models.ListBucketEventsResponse, *CodedAPIError) {
   142  	ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
   143  	defer cancel()
   144  	mClient, err := newMinioClient(session, getClientIP(params.HTTPRequest))
   145  	if err != nil {
   146  		return nil, ErrorWithContext(ctx, err)
   147  	}
   148  	// create a minioClient interface implementation
   149  	// defining the client to be used
   150  	minioClient := minioClient{client: mClient}
   151  
   152  	bucketEvents, err := listBucketEvents(minioClient, params.BucketName)
   153  	if err != nil {
   154  		return nil, ErrorWithContext(ctx, err)
   155  	}
   156  	// serialize output
   157  	listBucketsResponse := &models.ListBucketEventsResponse{
   158  		Events: bucketEvents,
   159  		Total:  int64(len(bucketEvents)),
   160  	}
   161  	return listBucketsResponse, nil
   162  }
   163  
   164  // createBucketEvent calls mc AddNotificationConfig() to create a bucket nofication
   165  //
   166  // If notificationEvents is empty, by default will set [get, put, delete], else the provided
   167  // ones will be set.
   168  // this function follows same behavior as minio/mc for adding a bucket event
   169  func createBucketEvent(ctx context.Context, client MCClient, arn string, notificationEvents []models.NotificationEventType, prefix, suffix string, ignoreExisting bool) error {
   170  	var events []string
   171  	if len(notificationEvents) == 0 {
   172  		// default event values are [get, put, delete]
   173  		events = []string{
   174  			string(models.NotificationEventTypeGet),
   175  			string(models.NotificationEventTypePut),
   176  			string(models.NotificationEventTypeDelete),
   177  		}
   178  	} else {
   179  		// else use defined events in request
   180  		// cast type models.NotificationEventType to string
   181  		for _, e := range notificationEvents {
   182  			events = append(events, string(e))
   183  		}
   184  	}
   185  
   186  	perr := client.addNotificationConfig(ctx, arn, events, prefix, suffix, ignoreExisting)
   187  	if perr != nil {
   188  		return perr.Cause
   189  	}
   190  	return nil
   191  }
   192  
   193  // getCreateBucketEventsResponse calls createBucketEvent to add a bucket event notification
   194  func getCreateBucketEventsResponse(session *models.Principal, params bucketApi.CreateBucketEventParams) *CodedAPIError {
   195  	ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
   196  	defer cancel()
   197  	bucketName := params.BucketName
   198  	eventReq := params.Body
   199  	s3Client, err := newS3BucketClient(session, bucketName, "", getClientIP(params.HTTPRequest))
   200  	if err != nil {
   201  		return ErrorWithContext(ctx, err)
   202  	}
   203  	// create a mc S3Client interface implementation
   204  	// defining the client to be used
   205  	mcClient := mcClient{client: s3Client}
   206  	err = createBucketEvent(ctx, mcClient, *eventReq.Configuration.Arn, eventReq.Configuration.Events, eventReq.Configuration.Prefix, eventReq.Configuration.Suffix, eventReq.IgnoreExisting)
   207  	if err != nil {
   208  		return ErrorWithContext(ctx, err)
   209  	}
   210  	return nil
   211  }
   212  
   213  // deleteBucketEventNotification calls S3Client.RemoveNotificationConfig to remove a bucket event notification
   214  func deleteBucketEventNotification(ctx context.Context, client MCClient, arn string, events []models.NotificationEventType, prefix, suffix *string) error {
   215  	eventSingleString := joinNotificationEvents(events)
   216  	perr := client.removeNotificationConfig(ctx, arn, eventSingleString, *prefix, *suffix)
   217  	if perr != nil {
   218  		return perr.Cause
   219  	}
   220  	return nil
   221  }
   222  
   223  func joinNotificationEvents(events []models.NotificationEventType) string {
   224  	var eventsArn []string
   225  	for _, e := range events {
   226  		eventsArn = append(eventsArn, string(e))
   227  	}
   228  	return strings.Join(eventsArn, ",")
   229  }
   230  
   231  // getDeleteBucketEventsResponse calls deleteBucketEventNotification() to delete a bucket event notification
   232  func getDeleteBucketEventsResponse(session *models.Principal, params bucketApi.DeleteBucketEventParams) *CodedAPIError {
   233  	ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
   234  	defer cancel()
   235  	bucketName := params.BucketName
   236  	arn := params.Arn
   237  	events := params.Body.Events
   238  	prefix := params.Body.Prefix
   239  	suffix := params.Body.Suffix
   240  	s3Client, err := newS3BucketClient(session, bucketName, "", getClientIP(params.HTTPRequest))
   241  	if err != nil {
   242  		return ErrorWithContext(ctx, err)
   243  	}
   244  	// create a mc S3Client interface implementation
   245  	// defining the client to be used
   246  	mcClient := mcClient{client: s3Client}
   247  	err = deleteBucketEventNotification(ctx, mcClient, arn, events, prefix, suffix)
   248  	if err != nil {
   249  		return ErrorWithContext(ctx, err)
   250  	}
   251  	return nil
   252  }