github.com/minio/madmin-go@v1.7.5/remote-target-commands.go (about)

     1  //
     2  // MinIO Object Storage (c) 2021 MinIO, Inc.
     3  //
     4  // Licensed under the Apache License, Version 2.0 (the "License");
     5  // you may not use this file except in compliance with the License.
     6  // You may obtain a copy of the License at
     7  //
     8  //      http://www.apache.org/licenses/LICENSE-2.0
     9  //
    10  // Unless required by applicable law or agreed to in writing, software
    11  // distributed under the License is distributed on an "AS IS" BASIS,
    12  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  // See the License for the specific language governing permissions and
    14  // limitations under the License.
    15  //
    16  
    17  package madmin
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"fmt"
    23  	"io/ioutil"
    24  	"net/http"
    25  	"net/url"
    26  	"strings"
    27  	"time"
    28  )
    29  
    30  // ServiceType represents service type
    31  type ServiceType string
    32  
    33  const (
    34  	// ReplicationService specifies replication service
    35  	ReplicationService ServiceType = "replication"
    36  )
    37  
    38  // IsValid returns true if ARN type represents replication
    39  func (t ServiceType) IsValid() bool {
    40  	return t == ReplicationService
    41  }
    42  
    43  // ARN is a struct to define arn.
    44  type ARN struct {
    45  	Type   ServiceType
    46  	ID     string
    47  	Region string
    48  	Bucket string
    49  }
    50  
    51  // Empty returns true if arn struct is empty
    52  func (a ARN) Empty() bool {
    53  	return !a.Type.IsValid()
    54  }
    55  
    56  func (a ARN) String() string {
    57  	return fmt.Sprintf("arn:minio:%s:%s:%s:%s", a.Type, a.Region, a.ID, a.Bucket)
    58  }
    59  
    60  // ParseARN return ARN struct from string in arn format.
    61  func ParseARN(s string) (*ARN, error) {
    62  	// ARN must be in the format of arn:minio:<Type>:<REGION>:<ID>:<remote-bucket>
    63  	if !strings.HasPrefix(s, "arn:minio:") {
    64  		return nil, fmt.Errorf("Invalid ARN %s", s)
    65  	}
    66  
    67  	tokens := strings.Split(s, ":")
    68  	if len(tokens) != 6 {
    69  		return nil, fmt.Errorf("Invalid ARN %s", s)
    70  	}
    71  
    72  	if tokens[4] == "" || tokens[5] == "" {
    73  		return nil, fmt.Errorf("Invalid ARN %s", s)
    74  	}
    75  
    76  	return &ARN{
    77  		Type:   ServiceType(tokens[2]),
    78  		Region: tokens[3],
    79  		ID:     tokens[4],
    80  		Bucket: tokens[5],
    81  	}, nil
    82  }
    83  
    84  // BucketTarget represents the target bucket and site association.
    85  type BucketTarget struct {
    86  	SourceBucket        string        `json:"sourcebucket"`
    87  	Endpoint            string        `json:"endpoint"`
    88  	Credentials         *Credentials  `json:"credentials"`
    89  	TargetBucket        string        `json:"targetbucket"`
    90  	Secure              bool          `json:"secure"`
    91  	Path                string        `json:"path,omitempty"`
    92  	API                 string        `json:"api,omitempty"`
    93  	Arn                 string        `json:"arn,omitempty"`
    94  	Type                ServiceType   `json:"type"`
    95  	Region              string        `json:"omitempty"`
    96  	BandwidthLimit      int64         `json:"bandwidthlimit,omitempty"`
    97  	ReplicationSync     bool          `json:"replicationSync"`
    98  	StorageClass        string        `json:"storageclass,omitempty"`
    99  	HealthCheckDuration time.Duration `json:"healthCheckDuration,omitempty"`
   100  	DisableProxy        bool          `json:"disableProxy"`
   101  	ResetBeforeDate     time.Time     `json:"resetBeforeDate,omitempty"`
   102  	ResetID             string        `json:"resetID,omitempty"`
   103  }
   104  
   105  // Clone returns shallow clone of BucketTarget without secret key in credentials
   106  func (t *BucketTarget) Clone() BucketTarget {
   107  	return BucketTarget{
   108  		SourceBucket:        t.SourceBucket,
   109  		Endpoint:            t.Endpoint,
   110  		TargetBucket:        t.TargetBucket,
   111  		Credentials:         &Credentials{AccessKey: t.Credentials.AccessKey},
   112  		Secure:              t.Secure,
   113  		Path:                t.Path,
   114  		API:                 t.API,
   115  		Arn:                 t.Arn,
   116  		Type:                t.Type,
   117  		Region:              t.Region,
   118  		BandwidthLimit:      t.BandwidthLimit,
   119  		ReplicationSync:     t.ReplicationSync,
   120  		StorageClass:        t.StorageClass, // target storage class
   121  		HealthCheckDuration: t.HealthCheckDuration,
   122  		DisableProxy:        t.DisableProxy,
   123  		ResetBeforeDate:     t.ResetBeforeDate,
   124  		ResetID:             t.ResetID,
   125  	}
   126  }
   127  
   128  // URL returns target url
   129  func (t BucketTarget) URL() *url.URL {
   130  	scheme := "http"
   131  	if t.Secure {
   132  		scheme = "https"
   133  	}
   134  	return &url.URL{
   135  		Scheme: scheme,
   136  		Host:   t.Endpoint,
   137  	}
   138  }
   139  
   140  // Empty returns true if struct is empty.
   141  func (t BucketTarget) Empty() bool {
   142  	return t.String() == "" || t.Credentials == nil
   143  }
   144  
   145  func (t *BucketTarget) String() string {
   146  	return fmt.Sprintf("%s %s", t.Endpoint, t.TargetBucket)
   147  }
   148  
   149  // BucketTargets represents a slice of bucket targets by type and endpoint
   150  type BucketTargets struct {
   151  	Targets []BucketTarget
   152  }
   153  
   154  // Empty returns true if struct is empty.
   155  func (t BucketTargets) Empty() bool {
   156  	if len(t.Targets) == 0 {
   157  		return true
   158  	}
   159  	empty := true
   160  	for _, t := range t.Targets {
   161  		if !t.Empty() {
   162  			return false
   163  		}
   164  	}
   165  	return empty
   166  }
   167  
   168  // ListRemoteTargets - gets target(s) for this bucket
   169  func (adm *AdminClient) ListRemoteTargets(ctx context.Context, bucket, arnType string) (targets []BucketTarget, err error) {
   170  	queryValues := url.Values{}
   171  	queryValues.Set("bucket", bucket)
   172  	queryValues.Set("type", arnType)
   173  
   174  	reqData := requestData{
   175  		relPath:     adminAPIPrefix + "/list-remote-targets",
   176  		queryValues: queryValues,
   177  	}
   178  
   179  	// Execute GET on /minio/admin/v3/list-remote-targets
   180  	resp, err := adm.executeMethod(ctx, http.MethodGet, reqData)
   181  
   182  	defer closeResponse(resp)
   183  	if err != nil {
   184  		return targets, err
   185  	}
   186  
   187  	if resp.StatusCode != http.StatusOK {
   188  		return targets, httpRespToErrorResponse(resp)
   189  	}
   190  
   191  	b, err := ioutil.ReadAll(resp.Body)
   192  	if err != nil {
   193  		return targets, err
   194  	}
   195  	if err = json.Unmarshal(b, &targets); err != nil {
   196  		return targets, err
   197  	}
   198  	return targets, nil
   199  }
   200  
   201  // SetRemoteTarget sets up a remote target for this bucket
   202  func (adm *AdminClient) SetRemoteTarget(ctx context.Context, bucket string, target *BucketTarget) (string, error) {
   203  	data, err := json.Marshal(target)
   204  	if err != nil {
   205  		return "", err
   206  	}
   207  	encData, err := EncryptData(adm.getSecretKey(), data)
   208  	if err != nil {
   209  		return "", err
   210  	}
   211  	queryValues := url.Values{}
   212  	queryValues.Set("bucket", bucket)
   213  
   214  	reqData := requestData{
   215  		relPath:     adminAPIPrefix + "/set-remote-target",
   216  		queryValues: queryValues,
   217  		content:     encData,
   218  	}
   219  
   220  	// Execute PUT on /minio/admin/v3/set-remote-target to set a target for this bucket of specific arn type.
   221  	resp, err := adm.executeMethod(ctx, http.MethodPut, reqData)
   222  
   223  	defer closeResponse(resp)
   224  	if err != nil {
   225  		return "", err
   226  	}
   227  
   228  	if resp.StatusCode != http.StatusOK {
   229  		return "", httpRespToErrorResponse(resp)
   230  	}
   231  	b, err := ioutil.ReadAll(resp.Body)
   232  	if err != nil {
   233  		return "", err
   234  	}
   235  	var arn string
   236  	if err = json.Unmarshal(b, &arn); err != nil {
   237  		return "", err
   238  	}
   239  	return arn, nil
   240  }
   241  
   242  // TargetUpdateType -  type of update on the remote target
   243  type TargetUpdateType int
   244  
   245  const (
   246  	// CredentialsUpdateType update creds
   247  	CredentialsUpdateType TargetUpdateType = 1 + iota
   248  	// SyncUpdateType update synchronous replication setting
   249  	SyncUpdateType
   250  	// ProxyUpdateType update proxy setting
   251  	ProxyUpdateType
   252  	// BandwidthLimitUpdateType update bandwidth limit
   253  	BandwidthLimitUpdateType
   254  	// HealthCheckDurationUpdateType update health check duration
   255  	HealthCheckDurationUpdateType
   256  	// PathUpdateType update Path
   257  	PathUpdateType
   258  	// ResetUpdateType sets ResetBeforeDate and ResetID on a bucket target
   259  	ResetUpdateType
   260  )
   261  
   262  // GetTargetUpdateOps returns a slice of update operations being
   263  // performed with `mc admin bucket remote edit`
   264  func GetTargetUpdateOps(values url.Values) []TargetUpdateType {
   265  	var ops []TargetUpdateType
   266  	if values.Get("update") != "true" {
   267  		return ops
   268  	}
   269  	if values.Get("creds") == "true" {
   270  		ops = append(ops, CredentialsUpdateType)
   271  	}
   272  	if values.Get("sync") == "true" {
   273  		ops = append(ops, SyncUpdateType)
   274  	}
   275  	if values.Get("proxy") == "true" {
   276  		ops = append(ops, ProxyUpdateType)
   277  	}
   278  	if values.Get("healthcheck") == "true" {
   279  		ops = append(ops, HealthCheckDurationUpdateType)
   280  	}
   281  	if values.Get("bandwidth") == "true" {
   282  		ops = append(ops, BandwidthLimitUpdateType)
   283  	}
   284  	if values.Get("path") == "true" {
   285  		ops = append(ops, PathUpdateType)
   286  	}
   287  	return ops
   288  }
   289  
   290  // UpdateRemoteTarget updates credentials for a remote bucket target
   291  func (adm *AdminClient) UpdateRemoteTarget(ctx context.Context, target *BucketTarget, ops ...TargetUpdateType) (string, error) {
   292  	if target == nil {
   293  		return "", fmt.Errorf("target cannot be nil")
   294  	}
   295  	data, err := json.Marshal(target)
   296  	if err != nil {
   297  		return "", err
   298  	}
   299  	encData, err := EncryptData(adm.getSecretKey(), data)
   300  	if err != nil {
   301  		return "", err
   302  	}
   303  	queryValues := url.Values{}
   304  	queryValues.Set("bucket", target.SourceBucket)
   305  	queryValues.Set("update", "true")
   306  
   307  	for _, op := range ops {
   308  		switch op {
   309  		case CredentialsUpdateType:
   310  			queryValues.Set("creds", "true")
   311  		case SyncUpdateType:
   312  			queryValues.Set("sync", "true")
   313  		case ProxyUpdateType:
   314  			queryValues.Set("proxy", "true")
   315  		case BandwidthLimitUpdateType:
   316  			queryValues.Set("bandwidth", "true")
   317  		case HealthCheckDurationUpdateType:
   318  			queryValues.Set("healthcheck", "true")
   319  		case PathUpdateType:
   320  			queryValues.Set("path", "true")
   321  		}
   322  	}
   323  
   324  	reqData := requestData{
   325  		relPath:     adminAPIPrefix + "/set-remote-target",
   326  		queryValues: queryValues,
   327  		content:     encData,
   328  	}
   329  
   330  	// Execute PUT on /minio/admin/v3/set-remote-target to set a target for this bucket of specific arn type.
   331  	resp, err := adm.executeMethod(ctx, http.MethodPut, reqData)
   332  
   333  	defer closeResponse(resp)
   334  	if err != nil {
   335  		return "", err
   336  	}
   337  
   338  	if resp.StatusCode != http.StatusOK {
   339  		return "", httpRespToErrorResponse(resp)
   340  	}
   341  	b, err := ioutil.ReadAll(resp.Body)
   342  	if err != nil {
   343  		return "", err
   344  	}
   345  	var arn string
   346  	if err = json.Unmarshal(b, &arn); err != nil {
   347  		return "", err
   348  	}
   349  	return arn, nil
   350  }
   351  
   352  // RemoveRemoteTarget removes a remote target associated with particular ARN for this bucket
   353  func (adm *AdminClient) RemoveRemoteTarget(ctx context.Context, bucket, arn string) error {
   354  	queryValues := url.Values{}
   355  	queryValues.Set("bucket", bucket)
   356  	queryValues.Set("arn", arn)
   357  
   358  	reqData := requestData{
   359  		relPath:     adminAPIPrefix + "/remove-remote-target",
   360  		queryValues: queryValues,
   361  	}
   362  
   363  	// Execute PUT on /minio/admin/v3/remove-remote-target to remove a target for this bucket
   364  	// with specific ARN
   365  	resp, err := adm.executeMethod(ctx, http.MethodDelete, reqData)
   366  	defer closeResponse(resp)
   367  	if err != nil {
   368  		return err
   369  	}
   370  
   371  	if resp.StatusCode != http.StatusNoContent {
   372  		return httpRespToErrorResponse(resp)
   373  	}
   374  	return nil
   375  }