github.com/minio/madmin-go/v2@v2.2.1/batch-job.go (about)

     1  //
     2  // Copyright (c) 2015-2022 MinIO, Inc.
     3  //
     4  // This file is part of MinIO Object Storage stack
     5  //
     6  // This program is free software: you can redistribute it and/or modify
     7  // it under the terms of the GNU Affero General Public License as
     8  // published by the Free Software Foundation, either version 3 of the
     9  // License, or (at your option) any later version.
    10  //
    11  // This program is distributed in the hope that it will be useful,
    12  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    13  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    14  // GNU Affero General Public License for more details.
    15  //
    16  // You should have received a copy of the GNU Affero General Public License
    17  // along with this program. If not, see <http://www.gnu.org/licenses/>.
    18  //
    19  
    20  package madmin
    21  
    22  import (
    23  	"context"
    24  	"encoding/json"
    25  	"errors"
    26  	"fmt"
    27  	"io"
    28  	"net/http"
    29  	"net/url"
    30  	"time"
    31  )
    32  
    33  // BatchJobType type to describe batch job types
    34  type BatchJobType string
    35  
    36  const (
    37  	BatchJobReplicate BatchJobType = "replicate"
    38  	BatchJobKeyRotate BatchJobType = "keyrotate"
    39  )
    40  
    41  // SupportedJobTypes supported job types
    42  var SupportedJobTypes = []BatchJobType{
    43  	BatchJobReplicate,
    44  	BatchJobKeyRotate,
    45  	// add new job types
    46  }
    47  
    48  // BatchJobReplicateTemplate provides a sample template
    49  // for batch replication
    50  const BatchJobReplicateTemplate = `replicate:
    51    apiVersion: v1
    52    # source of the objects to be replicated
    53    source:
    54      type: TYPE # valid values are "minio"
    55      bucket: BUCKET
    56      prefix: PREFIX # 'PREFIX' is optional
    57      # NOTE: if source is remote then target must be "local"
    58      # endpoint: ENDPOINT
    59      # credentials:
    60      #   accessKey: ACCESS-KEY
    61      #   secretKey: SECRET-KEY
    62      #   sessionToken: SESSION-TOKEN # Optional only available when rotating credentials are used
    63  
    64    # target where the objects must be replicated
    65    target:
    66      type: TYPE # valid values are "minio"
    67      bucket: BUCKET
    68      prefix: PREFIX # 'PREFIX' is optional
    69      # NOTE: if target is remote then source must be "local"
    70      # endpoint: ENDPOINT
    71      # credentials:
    72      #   accessKey: ACCESS-KEY
    73      #   secretKey: SECRET-KEY
    74      #   sessionToken: SESSION-TOKEN # Optional only available when rotating credentials are used
    75  
    76    # NOTE: All flags are optional
    77    # - filtering criteria only applies for all source objects match the criteria
    78    # - configurable notification endpoints
    79    # - configurable retries for the job (each retry skips successfully previously replaced objects)
    80    flags:
    81      filter:
    82        newerThan: "7d" # match objects newer than this value (e.g. 7d10h31s)
    83        olderThan: "7d" # match objects older than this value (e.g. 7d10h31s)
    84        createdAfter: "date" # match objects created after "date"
    85        createdBefore: "date" # match objects created before "date"
    86  
    87        ## NOTE: tags are not supported when "source" is remote.
    88        # tags:
    89        #   - key: "name"
    90        #     value: "pick*" # match objects with tag 'name', with all values starting with 'pick'
    91  
    92        ## NOTE: metadata filter not supported when "source" is non MinIO.
    93        # metadata:
    94        #   - key: "content-type"
    95        #     value: "image/*" # match objects with 'content-type', with all values starting with 'image/'
    96  
    97      notify:
    98        endpoint: "https://notify.endpoint" # notification endpoint to receive job status events
    99        token: "Bearer xxxxx" # optional authentication token for the notification endpoint
   100  
   101      retry:
   102        attempts: 10 # number of retries for the job before giving up
   103        delay: "500ms" # least amount of delay between each retry
   104  `
   105  
   106  // BatchJobKeyRotateTemplate provides a sample template
   107  // for batch key rotation
   108  const BatchJobKeyRotateTemplate = `keyrotate:
   109    apiVersion: v1
   110    bucket: BUCKET
   111    prefix: PREFIX
   112    encryption:
   113      type: sse-s3 # valid values are sse-s3 and sse-kms
   114      key: <new-kms-key> # valid only for sse-kms
   115      context: <new-kms-key-context> # valid only for sse-kms
   116  
   117    # optional flags based filtering criteria
   118    # for all objects
   119    flags:
   120      filter:
   121        newerThan: "7d" # match objects newer than this value (e.g. 7d10h31s)
   122        olderThan: "7d" # match objects older than this value (e.g. 7d10h31s)
   123        createdAfter: "date" # match objects created after "date"
   124        createdBefore: "date" # match objects created before "date"
   125        tags:
   126          - key: "name"
   127            value: "pick*" # match objects with tag 'name', with all values starting with 'pick'
   128        metadata:
   129          - key: "content-type"
   130            value: "image/*" # match objects with 'content-type', with all values starting with 'image/'
   131        kmskey: "key-id" # match objects with KMS key-id (applicable only for sse-kms)
   132      notify:
   133        endpoint: "https://notify.endpoint" # notification endpoint to receive job status events
   134        token: "Bearer xxxxx" # optional authentication token for the notification endpoint
   135      retry:
   136        attempts: 10 # number of retries for the job before giving up
   137        delay: "500ms" # least amount of delay between each retry
   138  `
   139  
   140  // BatchJobResult returned by StartBatchJob
   141  type BatchJobResult struct {
   142  	ID      string        `json:"id"`
   143  	Type    BatchJobType  `json:"type"`
   144  	User    string        `json:"user,omitempty"`
   145  	Started time.Time     `json:"started"`
   146  	Elapsed time.Duration `json:"elapsed,omitempty"`
   147  }
   148  
   149  // StartBatchJob start a new batch job, input job description is in YAML.
   150  func (adm *AdminClient) StartBatchJob(ctx context.Context, job string) (BatchJobResult, error) {
   151  	resp, err := adm.executeMethod(ctx, http.MethodPost,
   152  		requestData{
   153  			relPath: adminAPIPrefix + "/start-job",
   154  			content: []byte(job),
   155  		},
   156  	)
   157  	if err != nil {
   158  		return BatchJobResult{}, err
   159  	}
   160  	defer closeResponse(resp)
   161  	if resp.StatusCode != http.StatusOK {
   162  		return BatchJobResult{}, httpRespToErrorResponse(resp)
   163  	}
   164  
   165  	res := BatchJobResult{}
   166  	dec := json.NewDecoder(resp.Body)
   167  	if err = dec.Decode(&res); err != nil {
   168  		return res, err
   169  	}
   170  
   171  	return res, nil
   172  }
   173  
   174  // DescribeBatchJob - describes a currently running Job.
   175  func (adm *AdminClient) DescribeBatchJob(ctx context.Context, jobID string) (string, error) {
   176  	values := make(url.Values)
   177  	values.Set("jobId", jobID)
   178  
   179  	resp, err := adm.executeMethod(ctx, http.MethodGet,
   180  		requestData{
   181  			relPath:     adminAPIPrefix + "/describe-job",
   182  			queryValues: values,
   183  		},
   184  	)
   185  	if err != nil {
   186  		return "", err
   187  	}
   188  	defer closeResponse(resp)
   189  	if resp.StatusCode != http.StatusOK {
   190  		return "", httpRespToErrorResponse(resp)
   191  	}
   192  
   193  	buf, err := io.ReadAll(resp.Body)
   194  	if err != nil {
   195  		return "", err
   196  	}
   197  
   198  	return string(buf), nil
   199  }
   200  
   201  // GenerateBatchJobOpts is to be implemented in future.
   202  type GenerateBatchJobOpts struct {
   203  	Type BatchJobType
   204  }
   205  
   206  // GenerateBatchJob creates a new job template from standard template
   207  // TODO: allow configuring yaml values
   208  func (adm *AdminClient) GenerateBatchJob(_ context.Context, opts GenerateBatchJobOpts) (string, error) {
   209  	switch opts.Type {
   210  	case BatchJobReplicate:
   211  		// TODO: allow configuring the template to fill values from GenerateBatchJobOpts
   212  		return BatchJobReplicateTemplate, nil
   213  	case BatchJobKeyRotate:
   214  		return BatchJobKeyRotateTemplate, nil
   215  	}
   216  	return "", fmt.Errorf("unsupported batch type requested: %s", opts.Type)
   217  }
   218  
   219  // ListBatchJobsResult contains entries for all current jobs.
   220  type ListBatchJobsResult struct {
   221  	Jobs []BatchJobResult `json:"jobs"`
   222  }
   223  
   224  // ListBatchJobsFilter returns list based on following
   225  // filtering params.
   226  type ListBatchJobsFilter struct {
   227  	ByJobType string
   228  }
   229  
   230  // ListBatchJobs list all the currently active batch jobs
   231  func (adm *AdminClient) ListBatchJobs(ctx context.Context, fl *ListBatchJobsFilter) (ListBatchJobsResult, error) {
   232  	if fl == nil {
   233  		return ListBatchJobsResult{}, errors.New("ListBatchJobsFilter cannot be nil")
   234  	}
   235  
   236  	values := make(url.Values)
   237  	values.Set("jobType", fl.ByJobType)
   238  
   239  	resp, err := adm.executeMethod(ctx, http.MethodGet,
   240  		requestData{
   241  			relPath:     adminAPIPrefix + "/list-jobs",
   242  			queryValues: values,
   243  		},
   244  	)
   245  	if err != nil {
   246  		return ListBatchJobsResult{}, err
   247  	}
   248  	defer closeResponse(resp)
   249  
   250  	if resp.StatusCode != http.StatusOK {
   251  		return ListBatchJobsResult{}, httpRespToErrorResponse(resp)
   252  	}
   253  
   254  	d := json.NewDecoder(resp.Body)
   255  	result := ListBatchJobsResult{}
   256  	if err = d.Decode(&result); err != nil {
   257  		return result, err
   258  	}
   259  
   260  	return result, nil
   261  }
   262  
   263  // CancelBatchJob cancels ongoing batch job.
   264  func (adm *AdminClient) CancelBatchJob(ctx context.Context, jobID string) error {
   265  	values := make(url.Values)
   266  	values.Set("id", jobID)
   267  
   268  	resp, err := adm.executeMethod(ctx, http.MethodDelete,
   269  		requestData{
   270  			relPath:     adminAPIPrefix + "/cancel-job",
   271  			queryValues: values,
   272  		},
   273  	)
   274  	if err != nil {
   275  		return err
   276  	}
   277  	defer closeResponse(resp)
   278  	if resp.StatusCode != http.StatusNoContent {
   279  		return httpRespToErrorResponse(resp)
   280  	}
   281  	return nil
   282  }