storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/pkg/madmin/remote-target-commands.go (about)

     1  /*
     2   * MinIO Cloud Storage, (C) 2020 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  
    18  package madmin
    19  
    20  import (
    21  	"context"
    22  	"encoding/json"
    23  	"fmt"
    24  	"io/ioutil"
    25  	"net/http"
    26  	"net/url"
    27  	"strings"
    28  	"time"
    29  
    30  	"storj.io/minio/pkg/auth"
    31  )
    32  
    33  // ServiceType represents service type
    34  type ServiceType string
    35  
    36  const (
    37  	// ReplicationService specifies replication service
    38  	ReplicationService ServiceType = "replication"
    39  	// ILMService specifies ilm service
    40  	ILMService ServiceType = "ilm"
    41  )
    42  
    43  // IsValid returns true if ARN type represents replication or ilm
    44  func (t ServiceType) IsValid() bool {
    45  	return t == ReplicationService || t == ILMService
    46  }
    47  
    48  // ARN is a struct to define arn.
    49  type ARN struct {
    50  	Type   ServiceType
    51  	ID     string
    52  	Region string
    53  	Bucket string
    54  }
    55  
    56  // Empty returns true if arn struct is empty
    57  func (a ARN) Empty() bool {
    58  	return !a.Type.IsValid()
    59  }
    60  func (a ARN) String() string {
    61  	return fmt.Sprintf("arn:minio:%s:%s:%s:%s", a.Type, a.Region, a.ID, a.Bucket)
    62  }
    63  
    64  // ParseARN return ARN struct from string in arn format.
    65  func ParseARN(s string) (*ARN, error) {
    66  	// ARN must be in the format of arn:minio:<Type>:<REGION>:<ID>:<remote-bucket>
    67  	if !strings.HasPrefix(s, "arn:minio:") {
    68  		return nil, fmt.Errorf("Invalid ARN %s", s)
    69  	}
    70  
    71  	tokens := strings.Split(s, ":")
    72  	if len(tokens) != 6 {
    73  		return nil, fmt.Errorf("Invalid ARN %s", s)
    74  	}
    75  
    76  	if tokens[4] == "" || tokens[5] == "" {
    77  		return nil, fmt.Errorf("Invalid ARN %s", s)
    78  	}
    79  
    80  	return &ARN{
    81  		Type:   ServiceType(tokens[2]),
    82  		Region: tokens[3],
    83  		ID:     tokens[4],
    84  		Bucket: tokens[5],
    85  	}, nil
    86  }
    87  
    88  // BucketTarget represents the target bucket and site association.
    89  type BucketTarget struct {
    90  	SourceBucket        string            `json:"sourcebucket"`
    91  	Endpoint            string            `json:"endpoint"`
    92  	Credentials         *auth.Credentials `json:"credentials"`
    93  	TargetBucket        string            `json:"targetbucket"`
    94  	Secure              bool              `json:"secure"`
    95  	Path                string            `json:"path,omitempty"`
    96  	API                 string            `json:"api,omitempty"`
    97  	Arn                 string            `json:"arn,omitempty"`
    98  	Type                ServiceType       `json:"type"`
    99  	Region              string            `json:"omitempty"`
   100  	Label               string            `json:"label,omitempty"`
   101  	BandwidthLimit      int64             `json:"bandwidthlimit,omitempty"`
   102  	ReplicationSync     bool              `json:"replicationSync"`
   103  	HealthCheckDuration time.Duration     `json:"healthCheckDuration,omitempty"`
   104  }
   105  
   106  // Clone returns shallow clone of BucketTarget without secret key in credentials
   107  func (t *BucketTarget) Clone() BucketTarget {
   108  	return BucketTarget{
   109  		SourceBucket:        t.SourceBucket,
   110  		Endpoint:            t.Endpoint,
   111  		TargetBucket:        t.TargetBucket,
   112  		Credentials:         &auth.Credentials{AccessKey: t.Credentials.AccessKey},
   113  		Secure:              t.Secure,
   114  		Path:                t.Path,
   115  		API:                 t.Path,
   116  		Arn:                 t.Arn,
   117  		Type:                t.Type,
   118  		Region:              t.Region,
   119  		Label:               t.Label,
   120  		BandwidthLimit:      t.BandwidthLimit,
   121  		ReplicationSync:     t.ReplicationSync,
   122  		HealthCheckDuration: t.HealthCheckDuration,
   123  	}
   124  }
   125  
   126  // URL returns target url
   127  func (t BucketTarget) URL() *url.URL {
   128  	scheme := "http"
   129  	if t.Secure {
   130  		scheme = "https"
   131  	}
   132  	return &url.URL{
   133  		Scheme: scheme,
   134  		Host:   t.Endpoint,
   135  	}
   136  }
   137  
   138  // Empty returns true if struct is empty.
   139  func (t BucketTarget) Empty() bool {
   140  	return t.String() == "" || t.Credentials == nil
   141  }
   142  
   143  func (t *BucketTarget) String() string {
   144  	return fmt.Sprintf("%s %s", t.Endpoint, t.TargetBucket)
   145  }
   146  
   147  // BucketTargets represents a slice of bucket targets by type and endpoint
   148  type BucketTargets struct {
   149  	Targets []BucketTarget
   150  }
   151  
   152  // Empty returns true if struct is empty.
   153  func (t BucketTargets) Empty() bool {
   154  	if len(t.Targets) == 0 {
   155  		return true
   156  	}
   157  	empty := true
   158  	for _, t := range t.Targets {
   159  		if !t.Empty() {
   160  			return false
   161  		}
   162  	}
   163  	return empty
   164  }
   165  
   166  // ListRemoteTargets - gets target(s) for this bucket
   167  func (adm *AdminClient) ListRemoteTargets(ctx context.Context, bucket, arnType string) (targets []BucketTarget, err error) {
   168  	queryValues := url.Values{}
   169  	queryValues.Set("bucket", bucket)
   170  	queryValues.Set("type", arnType)
   171  
   172  	reqData := requestData{
   173  		relPath:     adminAPIPrefix + "/list-remote-targets",
   174  		queryValues: queryValues,
   175  	}
   176  
   177  	// Execute GET on /minio/admin/v3/list-remote-targets
   178  	resp, err := adm.executeMethod(ctx, http.MethodGet, reqData)
   179  
   180  	defer closeResponse(resp)
   181  	if err != nil {
   182  		return targets, err
   183  	}
   184  
   185  	if resp.StatusCode != http.StatusOK {
   186  		return targets, httpRespToErrorResponse(resp)
   187  	}
   188  
   189  	b, err := ioutil.ReadAll(resp.Body)
   190  	if err != nil {
   191  		return targets, err
   192  	}
   193  	if err = json.Unmarshal(b, &targets); err != nil {
   194  		return targets, err
   195  	}
   196  	return targets, nil
   197  }
   198  
   199  // SetRemoteTarget sets up a remote target for this bucket
   200  func (adm *AdminClient) SetRemoteTarget(ctx context.Context, bucket string, target *BucketTarget) (string, error) {
   201  	data, err := json.Marshal(target)
   202  	if err != nil {
   203  		return "", err
   204  	}
   205  	encData, err := EncryptData(adm.getSecretKey(), data)
   206  	if err != nil {
   207  		return "", err
   208  	}
   209  	queryValues := url.Values{}
   210  	queryValues.Set("bucket", bucket)
   211  
   212  	reqData := requestData{
   213  		relPath:     adminAPIPrefix + "/set-remote-target",
   214  		queryValues: queryValues,
   215  		content:     encData,
   216  	}
   217  
   218  	// Execute PUT on /minio/admin/v3/set-remote-target to set a target for this bucket of specific arn type.
   219  	resp, err := adm.executeMethod(ctx, http.MethodPut, reqData)
   220  
   221  	defer closeResponse(resp)
   222  	if err != nil {
   223  		return "", err
   224  	}
   225  
   226  	if resp.StatusCode != http.StatusOK {
   227  		return "", httpRespToErrorResponse(resp)
   228  	}
   229  	b, err := ioutil.ReadAll(resp.Body)
   230  	if err != nil {
   231  		return "", err
   232  	}
   233  	var arn string
   234  	if err = json.Unmarshal(b, &arn); err != nil {
   235  		return "", err
   236  	}
   237  	return arn, nil
   238  }
   239  
   240  // UpdateRemoteTarget updates credentials for a remote bucket target
   241  func (adm *AdminClient) UpdateRemoteTarget(ctx context.Context, target *BucketTarget) (string, error) {
   242  	if target == nil {
   243  		return "", fmt.Errorf("target cannot be nil")
   244  	}
   245  	data, err := json.Marshal(target)
   246  	if err != nil {
   247  		return "", err
   248  	}
   249  	encData, err := EncryptData(adm.getSecretKey(), data)
   250  	if err != nil {
   251  		return "", err
   252  	}
   253  	queryValues := url.Values{}
   254  	queryValues.Set("bucket", target.SourceBucket)
   255  	queryValues.Set("update", "true")
   256  
   257  	reqData := requestData{
   258  		relPath:     adminAPIPrefix + "/set-remote-target",
   259  		queryValues: queryValues,
   260  		content:     encData,
   261  	}
   262  
   263  	// Execute PUT on /minio/admin/v3/set-remote-target to set a target for this bucket of specific arn type.
   264  	resp, err := adm.executeMethod(ctx, http.MethodPut, reqData)
   265  
   266  	defer closeResponse(resp)
   267  	if err != nil {
   268  		return "", err
   269  	}
   270  
   271  	if resp.StatusCode != http.StatusOK {
   272  		return "", httpRespToErrorResponse(resp)
   273  	}
   274  	b, err := ioutil.ReadAll(resp.Body)
   275  	if err != nil {
   276  		return "", err
   277  	}
   278  	var arn string
   279  	if err = json.Unmarshal(b, &arn); err != nil {
   280  		return "", err
   281  	}
   282  	return arn, nil
   283  }
   284  
   285  // RemoveRemoteTarget removes a remote target associated with particular ARN for this bucket
   286  func (adm *AdminClient) RemoveRemoteTarget(ctx context.Context, bucket, arn string) error {
   287  	queryValues := url.Values{}
   288  	queryValues.Set("bucket", bucket)
   289  	queryValues.Set("arn", arn)
   290  
   291  	reqData := requestData{
   292  		relPath:     adminAPIPrefix + "/remove-remote-target",
   293  		queryValues: queryValues,
   294  	}
   295  
   296  	// Execute PUT on /minio/admin/v3/remove-remote-target to remove a target for this bucket
   297  	// with specific ARN
   298  	resp, err := adm.executeMethod(ctx, http.MethodDelete, reqData)
   299  	defer closeResponse(resp)
   300  	if err != nil {
   301  		return err
   302  	}
   303  
   304  	if resp.StatusCode != http.StatusNoContent {
   305  		return httpRespToErrorResponse(resp)
   306  	}
   307  	return nil
   308  }