github.com/minio/console@v1.4.1/api/admin_remote_buckets.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  	"errors"
    22  	"fmt"
    23  	"net/url"
    24  	"strconv"
    25  	"time"
    26  
    27  	"github.com/minio/console/pkg/utils"
    28  
    29  	"github.com/minio/madmin-go/v3"
    30  
    31  	"github.com/go-openapi/runtime/middleware"
    32  	"github.com/go-openapi/swag"
    33  	"github.com/minio/console/api/operations"
    34  	bucketApi "github.com/minio/console/api/operations/bucket"
    35  	"github.com/minio/console/models"
    36  	"github.com/minio/minio-go/v7/pkg/replication"
    37  )
    38  
    39  type RemoteBucketResult struct {
    40  	OriginBucket string
    41  	TargetBucket string
    42  	Error        string
    43  }
    44  
    45  func registerAdminBucketRemoteHandlers(api *operations.ConsoleAPI) {
    46  	// return list of remote buckets
    47  	api.BucketListRemoteBucketsHandler = bucketApi.ListRemoteBucketsHandlerFunc(func(params bucketApi.ListRemoteBucketsParams, session *models.Principal) middleware.Responder {
    48  		listResp, err := getListRemoteBucketsResponse(session, params)
    49  		if err != nil {
    50  			return bucketApi.NewListRemoteBucketsDefault(err.Code).WithPayload(err.APIError)
    51  		}
    52  		return bucketApi.NewListRemoteBucketsOK().WithPayload(listResp)
    53  	})
    54  
    55  	// return information about a specific bucket
    56  	api.BucketRemoteBucketDetailsHandler = bucketApi.RemoteBucketDetailsHandlerFunc(func(params bucketApi.RemoteBucketDetailsParams, session *models.Principal) middleware.Responder {
    57  		response, err := getRemoteBucketDetailsResponse(session, params)
    58  		if err != nil {
    59  			return bucketApi.NewRemoteBucketDetailsDefault(err.Code).WithPayload(err.APIError)
    60  		}
    61  		return bucketApi.NewRemoteBucketDetailsOK().WithPayload(response)
    62  	})
    63  
    64  	// delete remote bucket
    65  	api.BucketDeleteRemoteBucketHandler = bucketApi.DeleteRemoteBucketHandlerFunc(func(params bucketApi.DeleteRemoteBucketParams, session *models.Principal) middleware.Responder {
    66  		err := getDeleteRemoteBucketResponse(session, params)
    67  		if err != nil {
    68  			return bucketApi.NewDeleteRemoteBucketDefault(err.Code).WithPayload(err.APIError)
    69  		}
    70  		return bucketApi.NewDeleteRemoteBucketNoContent()
    71  	})
    72  
    73  	// set remote bucket
    74  	api.BucketAddRemoteBucketHandler = bucketApi.AddRemoteBucketHandlerFunc(func(params bucketApi.AddRemoteBucketParams, session *models.Principal) middleware.Responder {
    75  		err := getAddRemoteBucketResponse(session, params)
    76  		if err != nil {
    77  			return bucketApi.NewAddRemoteBucketDefault(err.Code).WithPayload(err.APIError)
    78  		}
    79  		return bucketApi.NewAddRemoteBucketCreated()
    80  	})
    81  
    82  	// set multi-bucket replication
    83  	api.BucketSetMultiBucketReplicationHandler = bucketApi.SetMultiBucketReplicationHandlerFunc(func(params bucketApi.SetMultiBucketReplicationParams, session *models.Principal) middleware.Responder {
    84  		response, err := setMultiBucketReplicationResponse(session, params)
    85  		if err != nil {
    86  			return bucketApi.NewSetMultiBucketReplicationDefault(err.Code).WithPayload(err.APIError)
    87  		}
    88  
    89  		return bucketApi.NewSetMultiBucketReplicationOK().WithPayload(response)
    90  	})
    91  
    92  	// list external buckets
    93  	api.BucketListExternalBucketsHandler = bucketApi.ListExternalBucketsHandlerFunc(func(params bucketApi.ListExternalBucketsParams, _ *models.Principal) middleware.Responder {
    94  		response, err := listExternalBucketsResponse(params)
    95  		if err != nil {
    96  			return bucketApi.NewListExternalBucketsDefault(err.Code).WithPayload(err.APIError)
    97  		}
    98  
    99  		return bucketApi.NewListExternalBucketsOK().WithPayload(response)
   100  	})
   101  
   102  	// delete replication rule
   103  	api.BucketDeleteBucketReplicationRuleHandler = bucketApi.DeleteBucketReplicationRuleHandlerFunc(func(params bucketApi.DeleteBucketReplicationRuleParams, session *models.Principal) middleware.Responder {
   104  		err := deleteReplicationRuleResponse(session, params)
   105  		if err != nil {
   106  			return bucketApi.NewDeleteBucketReplicationRuleDefault(err.Code).WithPayload(err.APIError)
   107  		}
   108  
   109  		return bucketApi.NewDeleteBucketReplicationRuleNoContent()
   110  	})
   111  
   112  	// delete all replication rules for a bucket
   113  	api.BucketDeleteAllReplicationRulesHandler = bucketApi.DeleteAllReplicationRulesHandlerFunc(func(params bucketApi.DeleteAllReplicationRulesParams, session *models.Principal) middleware.Responder {
   114  		err := deleteBucketReplicationRulesResponse(session, params)
   115  		if err != nil {
   116  			if err.Code == 500 && err.APIError.DetailedMessage == "The remote target does not exist" {
   117  				// We should ignore this MinIO error when deleting all replication rules
   118  				return bucketApi.NewDeleteAllReplicationRulesNoContent() // This will return 204 as per swagger spec
   119  			}
   120  			// If there is a different error, then we should handle it
   121  			// This will return a generic error with err.Code (likely a 500 or 404) and its *err.DetailedMessage
   122  			return bucketApi.NewDeleteAllReplicationRulesDefault(err.Code).WithPayload(err.APIError)
   123  		}
   124  		return bucketApi.NewDeleteAllReplicationRulesNoContent()
   125  	})
   126  
   127  	// delete selected replication rules for a bucket
   128  	api.BucketDeleteSelectedReplicationRulesHandler = bucketApi.DeleteSelectedReplicationRulesHandlerFunc(func(params bucketApi.DeleteSelectedReplicationRulesParams, session *models.Principal) middleware.Responder {
   129  		err := deleteSelectedReplicationRulesResponse(session, params)
   130  		if err != nil {
   131  			return bucketApi.NewDeleteSelectedReplicationRulesDefault(err.Code).WithPayload(err.APIError)
   132  		}
   133  
   134  		return bucketApi.NewDeleteSelectedReplicationRulesNoContent()
   135  	})
   136  
   137  	// update local bucket replication config item
   138  	api.BucketUpdateMultiBucketReplicationHandler = bucketApi.UpdateMultiBucketReplicationHandlerFunc(func(params bucketApi.UpdateMultiBucketReplicationParams, session *models.Principal) middleware.Responder {
   139  		err := updateBucketReplicationResponse(session, params)
   140  		if err != nil {
   141  			return bucketApi.NewUpdateMultiBucketReplicationDefault(err.Code).WithPayload(err.APIError)
   142  		}
   143  		return bucketApi.NewUpdateMultiBucketReplicationCreated()
   144  	})
   145  }
   146  
   147  func getListRemoteBucketsResponse(session *models.Principal, params bucketApi.ListRemoteBucketsParams) (*models.ListRemoteBucketsResponse, *CodedAPIError) {
   148  	ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
   149  	defer cancel()
   150  	mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
   151  	if err != nil {
   152  		return nil, ErrorWithContext(ctx, fmt.Errorf("error creating Madmin Client: %v", err))
   153  	}
   154  	adminClient := AdminClient{Client: mAdmin}
   155  	return listRemoteBuckets(ctx, adminClient)
   156  }
   157  
   158  func getRemoteBucketDetailsResponse(session *models.Principal, params bucketApi.RemoteBucketDetailsParams) (*models.RemoteBucket, *CodedAPIError) {
   159  	ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
   160  	defer cancel()
   161  	mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
   162  	if err != nil {
   163  		return nil, ErrorWithContext(ctx, fmt.Errorf("error creating Madmin Client: %v", err))
   164  	}
   165  	adminClient := AdminClient{Client: mAdmin}
   166  	return getRemoteBucket(ctx, adminClient, params.Name)
   167  }
   168  
   169  func getDeleteRemoteBucketResponse(session *models.Principal, params bucketApi.DeleteRemoteBucketParams) *CodedAPIError {
   170  	ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
   171  	defer cancel()
   172  	mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
   173  	if err != nil {
   174  		return ErrorWithContext(ctx, fmt.Errorf("error creating Madmin Client: %v", err))
   175  	}
   176  	adminClient := AdminClient{Client: mAdmin}
   177  	err = deleteRemoteBucket(ctx, adminClient, params.SourceBucketName, params.Arn)
   178  	if err != nil {
   179  		return ErrorWithContext(ctx, fmt.Errorf("error deleting remote bucket: %v", err))
   180  	}
   181  	return nil
   182  }
   183  
   184  func getAddRemoteBucketResponse(session *models.Principal, params bucketApi.AddRemoteBucketParams) *CodedAPIError {
   185  	ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
   186  	defer cancel()
   187  	mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
   188  	if err != nil {
   189  		return ErrorWithContext(ctx, fmt.Errorf("error creating Madmin Client: %v", err))
   190  	}
   191  	adminClient := AdminClient{Client: mAdmin}
   192  	_, err = addRemoteBucket(ctx, adminClient, *params.Body)
   193  	if err != nil {
   194  		return ErrorWithContext(ctx, fmt.Errorf("error adding remote bucket: %v", err))
   195  	}
   196  	return nil
   197  }
   198  
   199  func listRemoteBuckets(ctx context.Context, client MinioAdmin) (*models.ListRemoteBucketsResponse, *CodedAPIError) {
   200  	var remoteBuckets []*models.RemoteBucket
   201  	buckets, err := client.listRemoteBuckets(ctx, "", "")
   202  	if err != nil {
   203  		return nil, ErrorWithContext(ctx, fmt.Errorf("error listing remote buckets: %v", err))
   204  	}
   205  	for _, bucket := range buckets {
   206  		remoteBucket := &models.RemoteBucket{
   207  			AccessKey:         swag.String(bucket.Credentials.AccessKey),
   208  			RemoteARN:         swag.String(bucket.Arn),
   209  			SecretKey:         bucket.Credentials.SecretKey,
   210  			Service:           "replication",
   211  			SourceBucket:      swag.String(bucket.SourceBucket),
   212  			Status:            "",
   213  			TargetBucket:      bucket.TargetBucket,
   214  			TargetURL:         bucket.Endpoint,
   215  			SyncMode:          "async",
   216  			Bandwidth:         bucket.BandwidthLimit,
   217  			HealthCheckPeriod: int64(bucket.HealthCheckDuration.Seconds()),
   218  		}
   219  		if bucket.ReplicationSync {
   220  			remoteBucket.SyncMode = "sync"
   221  		}
   222  		remoteBuckets = append(remoteBuckets, remoteBucket)
   223  	}
   224  
   225  	return &models.ListRemoteBucketsResponse{
   226  		Buckets: remoteBuckets,
   227  		Total:   int64(len(remoteBuckets)),
   228  	}, nil
   229  }
   230  
   231  func getRemoteBucket(ctx context.Context, client MinioAdmin, name string) (*models.RemoteBucket, *CodedAPIError) {
   232  	remoteBucket, err := client.getRemoteBucket(ctx, name, "")
   233  	if err != nil {
   234  		return nil, ErrorWithContext(ctx, fmt.Errorf("error getting remote bucket details: %v", err))
   235  	}
   236  	if remoteBucket == nil {
   237  		return nil, ErrorWithContext(ctx, "error getting remote bucket details: bucket not found")
   238  	}
   239  	return &models.RemoteBucket{
   240  		AccessKey:    &remoteBucket.Credentials.AccessKey,
   241  		RemoteARN:    &remoteBucket.Arn,
   242  		SecretKey:    remoteBucket.Credentials.SecretKey,
   243  		Service:      "replication",
   244  		SourceBucket: &remoteBucket.SourceBucket,
   245  		Status:       "",
   246  		TargetBucket: remoteBucket.TargetBucket,
   247  		TargetURL:    remoteBucket.Endpoint,
   248  	}, nil
   249  }
   250  
   251  func deleteRemoteBucket(ctx context.Context, client MinioAdmin, sourceBucketName, arn string) error {
   252  	return client.removeRemoteBucket(ctx, sourceBucketName, arn)
   253  }
   254  
   255  func addRemoteBucket(ctx context.Context, client MinioAdmin, params models.CreateRemoteBucket) (string, error) {
   256  	TargetURL := *params.TargetURL
   257  	accessKey := *params.AccessKey
   258  	secretKey := *params.SecretKey
   259  	u, err := url.Parse(TargetURL)
   260  	if err != nil {
   261  		return "", errors.New("malformed Remote target URL")
   262  	}
   263  	secure := u.Scheme == "https"
   264  	host := u.Host
   265  	if u.Port() == "" {
   266  		port := 80
   267  		if secure {
   268  			port = 443
   269  		}
   270  		host = host + ":" + strconv.Itoa(port)
   271  	}
   272  	creds := &madmin.Credentials{AccessKey: accessKey, SecretKey: secretKey}
   273  	remoteBucket := &madmin.BucketTarget{
   274  		TargetBucket:    *params.TargetBucket,
   275  		Secure:          secure,
   276  		Credentials:     creds,
   277  		Endpoint:        host,
   278  		Path:            "",
   279  		API:             "s3v4",
   280  		Type:            "replication",
   281  		Region:          params.Region,
   282  		ReplicationSync: *params.SyncMode == "sync",
   283  	}
   284  	if *params.SyncMode == "async" {
   285  		remoteBucket.BandwidthLimit = params.Bandwidth
   286  	}
   287  	if params.HealthCheckPeriod > 0 {
   288  		remoteBucket.HealthCheckDuration = time.Duration(params.HealthCheckPeriod) * time.Second
   289  	}
   290  	bucketARN, err := client.addRemoteBucket(ctx, *params.SourceBucket, remoteBucket)
   291  
   292  	return bucketARN, err
   293  }
   294  
   295  func addBucketReplicationItem(ctx context.Context, session *models.Principal, minClient minioClient, bucketName, prefix, destinationARN string, repExistingObj, repDelMark, repDels, repMeta bool, tags string, priority int32, storageClass string) error {
   296  	// we will tolerate this call failing
   297  	cfg, err := minClient.getBucketReplication(ctx, bucketName)
   298  	if err != nil {
   299  		ErrorWithContext(ctx, fmt.Errorf("error fetching replication configuration for bucket %s: %v", bucketName, err))
   300  	}
   301  
   302  	// add rule
   303  	maxPrio := 0
   304  
   305  	if priority <= 0 { // We pick next priority by default
   306  		for _, r := range cfg.Rules {
   307  			if r.Priority > maxPrio {
   308  				maxPrio = r.Priority
   309  			}
   310  		}
   311  		maxPrio++
   312  	} else { // User picked priority, we try to set this manually
   313  		maxPrio = int(priority)
   314  	}
   315  	clientIP := utils.ClientIPFromContext(ctx)
   316  	s3Client, err := newS3BucketClient(session, bucketName, prefix, clientIP)
   317  	if err != nil {
   318  		ErrorWithContext(ctx, fmt.Errorf("error creating S3Client: %v", err))
   319  		return err
   320  	}
   321  	// create a mc S3Client interface implementation
   322  	// defining the client to be used
   323  	mcClient := mcClient{client: s3Client}
   324  
   325  	repDelMarkStatus := "disable"
   326  	if repDelMark {
   327  		repDelMarkStatus = "enable"
   328  	}
   329  
   330  	repDelsStatus := "disable"
   331  	if repDels {
   332  		repDelsStatus = "enable"
   333  	}
   334  
   335  	repMetaStatus := "disable"
   336  	if repMeta {
   337  		repMetaStatus = "enable"
   338  	}
   339  
   340  	existingRepStatus := "disable"
   341  	if repExistingObj {
   342  		existingRepStatus = "enable"
   343  	}
   344  
   345  	opts := replication.Options{
   346  		Priority:                fmt.Sprintf("%d", maxPrio),
   347  		RuleStatus:              "enable",
   348  		DestBucket:              destinationARN,
   349  		Op:                      replication.AddOption,
   350  		TagString:               tags,
   351  		ExistingObjectReplicate: existingRepStatus,
   352  		ReplicateDeleteMarkers:  repDelMarkStatus,
   353  		ReplicateDeletes:        repDelsStatus,
   354  		ReplicaSync:             repMetaStatus,
   355  		StorageClass:            storageClass,
   356  	}
   357  
   358  	err2 := mcClient.setReplication(ctx, &cfg, opts)
   359  	if err2 != nil {
   360  		ErrorWithContext(ctx, fmt.Errorf("error creating replication for bucket: %v", err2.Cause))
   361  		return err2.Cause
   362  	}
   363  	return nil
   364  }
   365  
   366  func editBucketReplicationItem(ctx context.Context, session *models.Principal, minClient minioClient, ruleID, bucketName, prefix, destinationARN string, ruleStatus, repDelMark, repDels, repMeta, existingObjectRep bool, tags string, priority int32, storageClass string) error {
   367  	// we will tolerate this call failing
   368  	cfg, err := minClient.getBucketReplication(ctx, bucketName)
   369  	if err != nil {
   370  		ErrorWithContext(ctx, fmt.Errorf("error fetching replication configuration for bucket %s: %v", bucketName, err))
   371  	}
   372  
   373  	maxPrio := int(priority)
   374  
   375  	clientIP := utils.ClientIPFromContext(ctx)
   376  	s3Client, err := newS3BucketClient(session, bucketName, prefix, clientIP)
   377  	if err != nil {
   378  		return fmt.Errorf("error creating S3Client: %v", err)
   379  	}
   380  	// create a mc S3Client interface implementation
   381  	// defining the client to be used
   382  	mcClient := mcClient{client: s3Client}
   383  
   384  	ruleState := "disable"
   385  	if ruleStatus {
   386  		ruleState = "enable"
   387  	}
   388  
   389  	repDelMarkStatus := "disable"
   390  	if repDelMark {
   391  		repDelMarkStatus = "enable"
   392  	}
   393  
   394  	repDelsStatus := "disable"
   395  	if repDels {
   396  		repDelsStatus = "enable"
   397  	}
   398  
   399  	repMetaStatus := "disable"
   400  	if repMeta {
   401  		repMetaStatus = "enable"
   402  	}
   403  
   404  	existingRepStatus := "disable"
   405  	if existingObjectRep {
   406  		existingRepStatus = "enable"
   407  	}
   408  
   409  	opts := replication.Options{
   410  		ID:                      ruleID,
   411  		Priority:                fmt.Sprintf("%d", maxPrio),
   412  		RuleStatus:              ruleState,
   413  		DestBucket:              destinationARN,
   414  		Op:                      replication.SetOption,
   415  		TagString:               tags,
   416  		IsTagSet:                true,
   417  		ExistingObjectReplicate: existingRepStatus,
   418  		ReplicateDeleteMarkers:  repDelMarkStatus,
   419  		ReplicateDeletes:        repDelsStatus,
   420  		ReplicaSync:             repMetaStatus,
   421  		StorageClass:            storageClass,
   422  		IsSCSet:                 true,
   423  	}
   424  
   425  	err2 := mcClient.setReplication(ctx, &cfg, opts)
   426  	if err2 != nil {
   427  		return fmt.Errorf("error modifying replication for bucket: %v", err2.Cause)
   428  	}
   429  	return nil
   430  }
   431  
   432  func setMultiBucketReplication(ctx context.Context, session *models.Principal, client MinioAdmin, minClient minioClient, params bucketApi.SetMultiBucketReplicationParams) []RemoteBucketResult {
   433  	bucketsRelation := params.Body.BucketsRelation
   434  
   435  	// Parallel remote bucket adding
   436  	parallelRemoteBucket := func(bucketRelationData *models.MultiBucketsRelation) chan RemoteBucketResult {
   437  		remoteProc := make(chan RemoteBucketResult)
   438  		sourceBucket := bucketRelationData.OriginBucket
   439  		targetBucket := bucketRelationData.DestinationBucket
   440  
   441  		go func() {
   442  			defer close(remoteProc)
   443  
   444  			createRemoteBucketParams := models.CreateRemoteBucket{
   445  				AccessKey:         params.Body.AccessKey,
   446  				SecretKey:         params.Body.SecretKey,
   447  				SourceBucket:      &sourceBucket,
   448  				TargetBucket:      &targetBucket,
   449  				Region:            params.Body.Region,
   450  				TargetURL:         params.Body.TargetURL,
   451  				SyncMode:          params.Body.SyncMode,
   452  				Bandwidth:         params.Body.Bandwidth,
   453  				HealthCheckPeriod: params.Body.HealthCheckPeriod,
   454  			}
   455  
   456  			// We add the remote bucket reference & store the arn or errors returned
   457  			arn, err := addRemoteBucket(ctx, client, createRemoteBucketParams)
   458  
   459  			if err == nil {
   460  				err = addBucketReplicationItem(
   461  					ctx,
   462  					session,
   463  					minClient,
   464  					sourceBucket,
   465  					params.Body.Prefix,
   466  					arn,
   467  					params.Body.ReplicateExistingObjects,
   468  					params.Body.ReplicateDeleteMarkers,
   469  					params.Body.ReplicateDeletes,
   470  					params.Body.ReplicateMetadata,
   471  					params.Body.Tags,
   472  					params.Body.Priority,
   473  					params.Body.StorageClass)
   474  			}
   475  
   476  			errorReturn := ""
   477  
   478  			if err != nil {
   479  				deleteRemoteBucket(ctx, client, sourceBucket, arn)
   480  				errorReturn = err.Error()
   481  			}
   482  
   483  			retParams := RemoteBucketResult{
   484  				OriginBucket: sourceBucket,
   485  				TargetBucket: targetBucket,
   486  				Error:        errorReturn,
   487  			}
   488  
   489  			remoteProc <- retParams
   490  		}()
   491  		return remoteProc
   492  	}
   493  
   494  	var bucketsManagement []chan RemoteBucketResult
   495  
   496  	for _, bucketName := range bucketsRelation {
   497  		// We generate the ARNs for each bucket
   498  		rBucket := parallelRemoteBucket(bucketName)
   499  		bucketsManagement = append(bucketsManagement, rBucket)
   500  	}
   501  
   502  	resultsList := []RemoteBucketResult{}
   503  	for _, result := range bucketsManagement {
   504  		res := <-result
   505  		resultsList = append(resultsList, res)
   506  	}
   507  
   508  	return resultsList
   509  }
   510  
   511  func setMultiBucketReplicationResponse(session *models.Principal, params bucketApi.SetMultiBucketReplicationParams) (*models.MultiBucketResponseState, *CodedAPIError) {
   512  	ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
   513  	defer cancel()
   514  
   515  	mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
   516  	if err != nil {
   517  		return nil, ErrorWithContext(ctx, fmt.Errorf("error creating Madmin Client: %v", err))
   518  	}
   519  	adminClient := AdminClient{Client: mAdmin}
   520  
   521  	mClient, err := newMinioClient(session, getClientIP(params.HTTPRequest))
   522  	if err != nil {
   523  		return nil, ErrorWithContext(ctx, fmt.Errorf("error creating MinIO Client: %v", err))
   524  	}
   525  	// create a minioClient interface implementation
   526  	// defining the client to be used
   527  	mnClient := minioClient{client: mClient}
   528  
   529  	replicationResults := setMultiBucketReplication(ctx, session, adminClient, mnClient, params)
   530  
   531  	if replicationResults == nil {
   532  		return nil, ErrorWithContext(ctx, errors.New("error setting buckets replication"))
   533  	}
   534  
   535  	resParsed := []*models.MultiBucketResponseItem{}
   536  
   537  	for _, repResult := range replicationResults {
   538  		responseItem := models.MultiBucketResponseItem{
   539  			ErrorString:  repResult.Error,
   540  			OriginBucket: repResult.OriginBucket,
   541  			TargetBucket: repResult.TargetBucket,
   542  		}
   543  
   544  		resParsed = append(resParsed, &responseItem)
   545  	}
   546  
   547  	resultsParsed := models.MultiBucketResponseState{
   548  		ReplicationState: resParsed,
   549  	}
   550  
   551  	return &resultsParsed, nil
   552  }
   553  
   554  func listExternalBucketsResponse(params bucketApi.ListExternalBucketsParams) (*models.ListBucketsResponse, *CodedAPIError) {
   555  	ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
   556  	defer cancel()
   557  	remoteAdmin, err := newAdminFromCreds(*params.Body.AccessKey, *params.Body.SecretKey, *params.Body.TargetURL, *params.Body.UseTLS)
   558  	if err != nil {
   559  		return nil, ErrorWithContext(ctx, err)
   560  	}
   561  	return listExternalBuckets(ctx, AdminClient{Client: remoteAdmin})
   562  }
   563  
   564  func listExternalBuckets(ctx context.Context, client MinioAdmin) (*models.ListBucketsResponse, *CodedAPIError) {
   565  	buckets, err := getAccountBuckets(ctx, client)
   566  	if err != nil {
   567  		return nil, ErrorWithContext(ctx, err)
   568  	}
   569  
   570  	return &models.ListBucketsResponse{
   571  		Buckets: buckets,
   572  		Total:   int64(len(buckets)),
   573  	}, nil
   574  }
   575  
   576  func getARNFromID(conf *replication.Config, rule string) string {
   577  	for i := range conf.Rules {
   578  		if conf.Rules[i].ID == rule {
   579  			return conf.Rules[i].Destination.Bucket
   580  		}
   581  	}
   582  	return ""
   583  }
   584  
   585  func getARNsFromIDs(conf *replication.Config, rules []string) []string {
   586  	temp := make(map[string]string)
   587  	for i := range conf.Rules {
   588  		temp[conf.Rules[i].ID] = conf.Rules[i].Destination.Bucket
   589  	}
   590  	var retval []string
   591  	for i := range rules {
   592  		if val, ok := temp[rules[i]]; ok {
   593  			retval = append(retval, val)
   594  		}
   595  	}
   596  	return retval
   597  }
   598  
   599  func deleteReplicationRule(ctx context.Context, session *models.Principal, bucketName, ruleID string) error {
   600  	clientIP := utils.ClientIPFromContext(ctx)
   601  	mClient, err := newMinioClient(session, clientIP)
   602  	if err != nil {
   603  		return fmt.Errorf("error creating MinIO Client: %v", err)
   604  	}
   605  	// create a minioClient interface implementation
   606  	// defining the client to be used
   607  	minClient := minioClient{client: mClient}
   608  
   609  	cfg, err := minClient.getBucketReplication(ctx, bucketName)
   610  	if err != nil {
   611  		ErrorWithContext(ctx, fmt.Errorf("error versioning bucket: %v", err))
   612  	}
   613  
   614  	s3Client, err := newS3BucketClient(session, bucketName, "", clientIP)
   615  	if err != nil {
   616  		return fmt.Errorf("error creating S3Client: %v", err)
   617  	}
   618  	mAdmin, err := NewMinioAdminClient(ctx, session)
   619  	if err != nil {
   620  		return fmt.Errorf("error creating Admin Client: %v", err)
   621  	}
   622  	admClient := AdminClient{Client: mAdmin}
   623  
   624  	// create a mc S3Client interface implementation
   625  	// defining the client to be used
   626  	mcClient := mcClient{client: s3Client}
   627  
   628  	opts := replication.Options{
   629  		ID: ruleID,
   630  		Op: replication.RemoveOption,
   631  	}
   632  
   633  	err2 := mcClient.setReplication(ctx, &cfg, opts)
   634  	if err2 != nil {
   635  		return err2.Cause
   636  	}
   637  
   638  	// Replication rule was successfully deleted. We remove remote bucket
   639  	err3 := deleteRemoteBucket(ctx, admClient, bucketName, getARNFromID(&cfg, ruleID))
   640  	if err3 != nil {
   641  		return err3
   642  	}
   643  
   644  	return nil
   645  }
   646  
   647  func deleteAllReplicationRules(ctx context.Context, session *models.Principal, bucketName string) error {
   648  	clientIP := utils.ClientIPFromContext(ctx)
   649  
   650  	s3Client, err := newS3BucketClient(session, bucketName, "", clientIP)
   651  	if err != nil {
   652  		return fmt.Errorf("error creating S3Client: %v", err)
   653  	}
   654  	// create a mc S3Client interface implementation
   655  	// defining the client to be used
   656  	mcClient := mcClient{client: s3Client}
   657  	mClient, err := newMinioClient(session, clientIP)
   658  	if err != nil {
   659  		return fmt.Errorf("error creating MinIO Client: %v", err)
   660  	}
   661  	// create a minioClient interface implementation
   662  	// defining the client to be used
   663  	minClient := minioClient{client: mClient}
   664  
   665  	cfg, err := minClient.getBucketReplication(ctx, bucketName)
   666  	if err != nil {
   667  		ErrorWithContext(ctx, fmt.Errorf("error versioning bucket: %v", err))
   668  	}
   669  
   670  	mAdmin, err := NewMinioAdminClient(ctx, session)
   671  	if err != nil {
   672  		return fmt.Errorf("error creating Admin Client: %v", err)
   673  	}
   674  	admClient := AdminClient{Client: mAdmin}
   675  
   676  	err2 := mcClient.deleteAllReplicationRules(ctx)
   677  
   678  	if err2 != nil {
   679  		return err2.ToGoError()
   680  	}
   681  
   682  	for i := range cfg.Rules {
   683  		err3 := deleteRemoteBucket(ctx, admClient, bucketName, cfg.Rules[i].Destination.Bucket)
   684  		if err3 != nil {
   685  			return err3
   686  		}
   687  	}
   688  
   689  	return nil
   690  }
   691  
   692  func deleteSelectedReplicationRules(ctx context.Context, session *models.Principal, bucketName string, rules []string) error {
   693  	clientIP := utils.ClientIPFromContext(ctx)
   694  	mClient, err := newMinioClient(session, clientIP)
   695  	if err != nil {
   696  		return fmt.Errorf("error creating MinIO Client: %v", err)
   697  	}
   698  	// create a minioClient interface implementation
   699  	// defining the client to be used
   700  	minClient := minioClient{client: mClient}
   701  
   702  	cfg, err := minClient.getBucketReplication(ctx, bucketName)
   703  	if err != nil {
   704  		ErrorWithContext(ctx, fmt.Errorf("error versioning bucket: %v", err))
   705  	}
   706  
   707  	s3Client, err := newS3BucketClient(session, bucketName, "", clientIP)
   708  	if err != nil {
   709  		return fmt.Errorf("error creating S3Client: %v", err)
   710  	}
   711  	// create a mc S3Client interface implementation
   712  	// defining the client to be used
   713  	mcClient := mcClient{client: s3Client}
   714  
   715  	mAdmin, err := NewMinioAdminClient(ctx, session)
   716  	if err != nil {
   717  		return fmt.Errorf("error creating Admin Client: %v", err)
   718  	}
   719  	admClient := AdminClient{Client: mAdmin}
   720  
   721  	ARNs := getARNsFromIDs(&cfg, rules)
   722  
   723  	for i := range rules {
   724  		opts := replication.Options{
   725  			ID: rules[i],
   726  			Op: replication.RemoveOption,
   727  		}
   728  		err2 := mcClient.setReplication(ctx, &cfg, opts)
   729  		if err2 != nil {
   730  			return err2.Cause
   731  		}
   732  
   733  		// In case replication rule was deleted successfully, we remove the remote bucket ARN
   734  		err3 := deleteRemoteBucket(ctx, admClient, bucketName, ARNs[i])
   735  		if err3 != nil {
   736  			return err3
   737  		}
   738  	}
   739  	return nil
   740  }
   741  
   742  func deleteReplicationRuleResponse(session *models.Principal, params bucketApi.DeleteBucketReplicationRuleParams) *CodedAPIError {
   743  	ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
   744  	defer cancel()
   745  	ctx = context.WithValue(ctx, utils.ContextClientIP, getClientIP(params.HTTPRequest))
   746  	err := deleteReplicationRule(ctx, session, params.BucketName, params.RuleID)
   747  	if err != nil {
   748  		return ErrorWithContext(ctx, err)
   749  	}
   750  	return nil
   751  }
   752  
   753  func deleteBucketReplicationRulesResponse(session *models.Principal, params bucketApi.DeleteAllReplicationRulesParams) *CodedAPIError {
   754  	ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
   755  	defer cancel()
   756  	ctx = context.WithValue(ctx, utils.ContextClientIP, getClientIP(params.HTTPRequest))
   757  	err := deleteAllReplicationRules(ctx, session, params.BucketName)
   758  	if err != nil {
   759  		return ErrorWithContext(ctx, err)
   760  	}
   761  	return nil
   762  }
   763  
   764  func deleteSelectedReplicationRulesResponse(session *models.Principal, params bucketApi.DeleteSelectedReplicationRulesParams) *CodedAPIError {
   765  	ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
   766  	defer cancel()
   767  
   768  	ctx = context.WithValue(ctx, utils.ContextClientIP, getClientIP(params.HTTPRequest))
   769  
   770  	err := deleteSelectedReplicationRules(ctx, session, params.BucketName, params.Rules.Rules)
   771  	if err != nil {
   772  		return ErrorWithContext(ctx, err)
   773  	}
   774  	return nil
   775  }
   776  
   777  func updateBucketReplicationResponse(session *models.Principal, params bucketApi.UpdateMultiBucketReplicationParams) *CodedAPIError {
   778  	ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
   779  	defer cancel()
   780  
   781  	mClient, err := newMinioClient(session, getClientIP(params.HTTPRequest))
   782  	if err != nil {
   783  		return ErrorWithContext(ctx, err)
   784  	}
   785  	// create a minioClient interface implementation
   786  	// defining the client to be used
   787  	minClient := minioClient{client: mClient}
   788  
   789  	err = editBucketReplicationItem(
   790  		ctx,
   791  		session,
   792  		minClient,
   793  		params.RuleID,
   794  		params.BucketName,
   795  		params.Body.Prefix,
   796  		params.Body.Arn,
   797  		params.Body.RuleState,
   798  		params.Body.ReplicateDeleteMarkers,
   799  		params.Body.ReplicateDeletes,
   800  		params.Body.ReplicateMetadata,
   801  		params.Body.ReplicateExistingObjects,
   802  		params.Body.Tags,
   803  		params.Body.Priority,
   804  		params.Body.StorageClass)
   805  	if err != nil {
   806  		return ErrorWithContext(ctx, err)
   807  	}
   808  
   809  	return nil
   810  }