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 }