1
0
mirror of https://github.com/minio/mc.git synced 2025-11-10 13:42:32 +03:00

cp: Add object lock related flags (#3127)

- Adds --retention-mode,--retention-duration &
  --legal-hold flags in cp command

- Adds --bypass flag in rm command
This commit is contained in:
Nitish Tiwari
2020-04-08 08:21:20 +05:30
committed by GitHub
parent 8cae137525
commit 18f81e5458
13 changed files with 232 additions and 101 deletions

View File

@@ -503,8 +503,8 @@ func deleteFile(deletePath string) error {
return nil return nil
} }
// Remove - remove entry read from ClientContent channel. // Remove - remove entry read from clientContent channel.
func (f *fsClient) Remove(isIncomplete, isRemoveBucket bool, contentCh <-chan *ClientContent) <-chan *probe.Error { func (f *fsClient) Remove(isIncomplete, isRemoveBucket, isBypass bool, contentCh <-chan *ClientContent) <-chan *probe.Error {
errorCh := make(chan *probe.Error) errorCh := make(chan *probe.Error)
// Goroutine reads from contentCh and removes the entry in content. // Goroutine reads from contentCh and removes the entry in content.

View File

@@ -77,6 +77,8 @@ const (
AmzObjectLockMode = "X-Amz-Object-Lock-Mode" AmzObjectLockMode = "X-Amz-Object-Lock-Mode"
// AmzObjectLockRetainUntilDate sets object lock retain until date // AmzObjectLockRetainUntilDate sets object lock retain until date
AmzObjectLockRetainUntilDate = "X-Amz-Object-Lock-Retain-Until-Date" AmzObjectLockRetainUntilDate = "X-Amz-Object-Lock-Retain-Until-Date"
// AmzObjectLockLegalHold sets object lock legal hold
AmzObjectLockLegalHold = "X-Amz-Object-Lock-Legal-Hold"
) )
var timeSentinel = time.Unix(0, 0).UTC() var timeSentinel = time.Unix(0, 0).UTC()
@@ -785,8 +787,32 @@ func (c *S3Client) Copy(source string, size int64, progress io.Reader, srcSSE, t
// Source object // Source object
src := minio.NewSourceInfo(tokens[1], tokens[2], srcSSE) src := minio.NewSourceInfo(tokens[1], tokens[2], srcSSE)
destOpts := minio.DestInfoOptions{
Encryption: tgtSSE,
}
if lockModeStr, ok := metadata[AmzObjectLockMode]; ok {
destOpts.Mode = minio.RetentionMode(strings.ToUpper(lockModeStr))
delete(metadata, AmzObjectLockMode)
}
if retainUntilDateStr, ok := metadata[AmzObjectLockRetainUntilDate]; ok {
delete(metadata, AmzObjectLockRetainUntilDate)
if t, e := time.Parse(time.RFC3339, retainUntilDateStr); e == nil {
destOpts.RetainUntilDate = t.UTC()
}
}
if lh, ok := metadata[AmzObjectLockLegalHold]; ok {
destOpts.LegalHold = minio.LegalHoldStatus(lh)
delete(metadata, AmzObjectLockLegalHold)
}
// Assign metadata after irrelevant parts are delete above
destOpts.UserMeta = metadata
// Destination object // Destination object
dst, e := minio.NewDestinationInfo(dstBucket, dstObject, tgtSSE, metadata) dst, e := minio.NewDestinationInfoWithOptions(dstBucket, dstObject, destOpts)
if e != nil { if e != nil {
return probe.NewError(e) return probe.NewError(e)
} }
@@ -865,7 +891,7 @@ func (c *S3Client) Put(ctx context.Context, reader io.Reader, size int64, metada
lockModeStr, ok := metadata[AmzObjectLockMode] lockModeStr, ok := metadata[AmzObjectLockMode]
lockMode := minio.RetentionMode("") lockMode := minio.RetentionMode("")
if ok { if ok {
lockMode = minio.RetentionMode(lockModeStr) lockMode = minio.RetentionMode(strings.ToUpper(lockModeStr))
delete(metadata, AmzObjectLockMode) delete(metadata, AmzObjectLockMode)
} }
@@ -892,10 +918,18 @@ func (c *S3Client) Put(ctx context.Context, reader io.Reader, size int64, metada
} }
if retainUntilDate != timeSentinel { if retainUntilDate != timeSentinel {
opts.RetainUntilDate = &retainUntilDate opts.RetainUntilDate = &retainUntilDate
opts.SendContentMd5 = true
} }
if lockModeStr != "" { if lockModeStr != "" {
opts.Mode = &lockMode opts.Mode = &lockMode
opts.SendContentMd5 = true
} }
if lh, ok := metadata[AmzObjectLockLegalHold]; ok {
delete(metadata, AmzObjectLockLegalHold)
opts.LegalHold = minio.LegalHoldStatus(strings.ToUpper(lh))
opts.SendContentMd5 = true
}
n, e := c.api.PutObjectWithContext(ctx, bucket, object, reader, size, opts) n, e := c.api.PutObjectWithContext(ctx, bucket, object, reader, size, opts)
if e != nil { if e != nil {
errResponse := minio.ToErrorResponse(e) errResponse := minio.ToErrorResponse(e)
@@ -962,13 +996,16 @@ func (c *S3Client) AddUserAgent(app string, version string) {
} }
// Remove - remove object or bucket(s). // Remove - remove object or bucket(s).
func (c *S3Client) Remove(isIncomplete, isRemoveBucket bool, contentCh <-chan *ClientContent) <-chan *probe.Error { func (c *S3Client) Remove(isIncomplete, isRemoveBucket, isBypass bool, contentCh <-chan *ClientContent) <-chan *probe.Error {
errorCh := make(chan *probe.Error) errorCh := make(chan *probe.Error)
prevBucket := "" prevBucket := ""
// Maintain objectsCh, statusCh for each bucket // Maintain objectsCh, statusCh for each bucket
var objectsCh chan string var objectsCh chan string
var statusCh <-chan minio.RemoveObjectError var statusCh <-chan minio.RemoveObjectError
opts := minio.RemoveObjectsOptions{
GovernanceBypass: isBypass,
}
go func() { go func() {
defer close(errorCh) defer close(errorCh)
@@ -995,7 +1032,7 @@ func (c *S3Client) Remove(isIncomplete, isRemoveBucket bool, contentCh <-chan *C
if isIncomplete { if isIncomplete {
statusCh = c.removeIncompleteObjects(bucket, objectsCh) statusCh = c.removeIncompleteObjects(bucket, objectsCh)
} else { } else {
statusCh = c.api.RemoveObjects(bucket, objectsCh) statusCh = c.api.RemoveObjectsWithOptions(bucket, objectsCh, opts)
} }
} }
@@ -1017,7 +1054,7 @@ func (c *S3Client) Remove(isIncomplete, isRemoveBucket bool, contentCh <-chan *C
if isIncomplete { if isIncomplete {
statusCh = c.removeIncompleteObjects(bucket, objectsCh) statusCh = c.removeIncompleteObjects(bucket, objectsCh)
} else { } else {
statusCh = c.api.RemoveObjects(bucket, objectsCh) statusCh = c.api.RemoveObjectsWithOptions(bucket, objectsCh, opts)
} }
prevBucket = bucket prevBucket = bucket
} }

View File

@@ -80,8 +80,7 @@ type Client interface {
Watch(params watchParams) (*WatchObject, *probe.Error) Watch(params watchParams) (*WatchObject, *probe.Error)
// Delete operations // Delete operations
Remove(isIncomplete, isRemoveBucket bool, contentCh <-chan *ClientContent) (errorCh <-chan *probe.Error) Remove(isIncomplete, isRemoveBucket, isBypass bool, contentCh <-chan *ClientContent) (errorCh <-chan *probe.Error)
// GetURL returns back internal url // GetURL returns back internal url
GetURL() ClientURL GetURL() ClientURL
@@ -95,17 +94,21 @@ type Client interface {
// ClientContent - Content container for content metadata // ClientContent - Content container for content metadata
type ClientContent struct { type ClientContent struct {
URL ClientURL URL ClientURL
Time time.Time Time time.Time
Size int64 Size int64
Type os.FileMode Type os.FileMode
StorageClass string StorageClass string
Metadata map[string]string Metadata map[string]string
UserMetadata map[string]string UserMetadata map[string]string
ETag string ETag string
Expires time.Time Expires time.Time
Retention bool Retention bool
Err *probe.Error RetentionMode string
RetentionDuration string
BypassGovernance bool
LegalHold string
Err *probe.Error
} }
// Config - see http://docs.amazonwebservices.com/AmazonS3/latest/dev/index.html?RESTAuthentication.html // Config - see http://docs.amazonwebservices.com/AmazonS3/latest/dev/index.html?RESTAuthentication.html

View File

@@ -241,11 +241,14 @@ func putTargetRetention(ctx context.Context, alias string, urlStr string, metada
} }
// putTargetStream writes to URL from Reader. // putTargetStream writes to URL from Reader.
func putTargetStream(ctx context.Context, alias string, urlStr string, reader io.Reader, size int64, metadata map[string]string, progress io.Reader, sse encrypt.ServerSide, disableMultipart bool) (int64, *probe.Error) { func putTargetStream(ctx context.Context, alias, urlStr, mode, until, legalHold string, reader io.Reader, size int64, metadata map[string]string, progress io.Reader, sse encrypt.ServerSide, disableMultipart bool) (int64, *probe.Error) {
targetClnt, err := newClientFromAlias(alias, urlStr) targetClnt, err := newClientFromAlias(alias, urlStr)
if err != nil { if err != nil {
return 0, err.Trace(alias, urlStr) return 0, err.Trace(alias, urlStr)
} }
metadata[AmzObjectLockMode] = mode
metadata[AmzObjectLockRetainUntilDate] = until
metadata[AmzObjectLockLegalHold] = legalHold
n, err := targetClnt.Put(ctx, reader, size, metadata, progress, sse, disableMultipart) n, err := targetClnt.Put(ctx, reader, size, metadata, progress, sse, disableMultipart)
if err != nil { if err != nil {
return n, err.Trace(alias, urlStr) return n, err.Trace(alias, urlStr)
@@ -263,16 +266,22 @@ func putTargetStreamWithURL(urlStr string, reader io.Reader, size int64, sse enc
metadata := map[string]string{ metadata := map[string]string{
"Content-Type": contentType, "Content-Type": contentType,
} }
return putTargetStream(context.Background(), alias, urlStrFull, reader, size, metadata, nil, sse, disableMultipart) return putTargetStream(context.Background(), alias, urlStrFull, "", "", "", reader, size, metadata, nil, sse, disableMultipart)
} }
// copySourceToTargetURL copies to targetURL from source. // copySourceToTargetURL copies to targetURL from source.
func copySourceToTargetURL(alias string, urlStr string, source string, size int64, progress io.Reader, srcSSE, tgtSSE encrypt.ServerSide, metadata map[string]string, disableMultipart bool) *probe.Error { func copySourceToTargetURL(alias, urlStr, source, mode, until, legalHold string, size int64, progress io.Reader, srcSSE, tgtSSE encrypt.ServerSide, metadata map[string]string, disableMultipart bool) *probe.Error {
targetClnt, err := newClientFromAlias(alias, urlStr) targetClnt, err := newClientFromAlias(alias, urlStr)
if err != nil { if err != nil {
return err.Trace(alias, urlStr) return err.Trace(alias, urlStr)
} }
metadata[AmzObjectLockMode] = mode
metadata[AmzObjectLockRetainUntilDate] = until
metadata[AmzObjectLockLegalHold] = legalHold
err = targetClnt.Copy(source, size, progress, srcSSE, tgtSSE, metadata, disableMultipart) err = targetClnt.Copy(source, size, progress, srcSSE, tgtSSE, metadata, disableMultipart)
if err != nil { if err != nil {
return err.Trace(alias, urlStr) return err.Trace(alias, urlStr)
} }
@@ -336,6 +345,21 @@ func uploadSourceToTargetURL(ctx context.Context, urls URLs, progress io.Reader,
var err *probe.Error var err *probe.Error
var metadata = map[string]string{} var metadata = map[string]string{}
var mode, until string
var pErr error
// add object retention fields in metadata
if urls.TargetContent.RetentionDuration != "" && urls.TargetContent.RetentionMode != "" {
m := minio.RetentionMode(strings.ToUpper(urls.TargetContent.RetentionMode))
dur, unit := parseRetentionValidity(urls.TargetContent.RetentionDuration, m)
mode = urls.TargetContent.RetentionMode
until, pErr = getRetainUntilDate(dur, unit)
if err != nil {
return urls.WithError(probe.NewError(pErr).Trace(sourceURL.String()))
}
}
// Optimize for server side copy if the host is same. // Optimize for server side copy if the host is same.
if sourceAlias == targetAlias { if sourceAlias == targetAlias {
for k, v := range urls.SourceContent.UserMetadata { for k, v := range urls.SourceContent.UserMetadata {
@@ -358,7 +382,7 @@ func uploadSourceToTargetURL(ctx context.Context, urls URLs, progress io.Reader,
err = putTargetRetention(ctx, targetAlias, targetURL.String(), metadata) err = putTargetRetention(ctx, targetAlias, targetURL.String(), metadata)
return urls.WithError(err.Trace(sourceURL.String())) return urls.WithError(err.Trace(sourceURL.String()))
} }
err = copySourceToTargetURL(targetAlias, targetURL.String(), sourcePath, length, err = copySourceToTargetURL(targetAlias, targetURL.String(), sourcePath, mode, until, urls.TargetContent.LegalHold, length,
progress, srcSSE, tgtSSE, filterMetadata(metadata), urls.DisableMultipart) progress, srcSSE, tgtSSE, filterMetadata(metadata), urls.DisableMultipart)
} else { } else {
if len(metadata) == 0 { if len(metadata) == 0 {
@@ -386,7 +410,7 @@ func uploadSourceToTargetURL(ctx context.Context, urls URLs, progress io.Reader,
for k, v := range urls.TargetContent.UserMetadata { for k, v := range urls.TargetContent.UserMetadata {
metadata[k] = v metadata[k] = v
} }
_, err = putTargetStream(ctx, targetAlias, targetURL.String(), reader, length, filterMetadata(metadata), _, err = putTargetStream(ctx, targetAlias, targetURL.String(), mode, until, urls.TargetContent.LegalHold, reader, length, filterMetadata(metadata),
progress, tgtSSE, urls.DisableMultipart) progress, tgtSSE, urls.DisableMultipart)
} }
if err != nil { if err != nil {

View File

@@ -73,9 +73,25 @@ var (
Name: "disable-multipart", Name: "disable-multipart",
Usage: "disable multipart upload feature", Usage: "disable multipart upload feature",
}, },
cli.StringFlag{
Name: rmFlag,
Usage: "retention mode to be applied on the object (governance, compliance)",
},
cli.StringFlag{
Name: rdFlag,
Usage: "retention duration for the object in d days or y years",
},
cli.StringFlag{
Name: lhFlag,
Usage: "apply legal hold to the copied object (on, off)",
},
} }
) )
var rmFlag = "retention-mode"
var rdFlag = "retention-duration"
var lhFlag = "legal-hold"
// ErrInvalidMetadata reflects invalid metadata format // ErrInvalidMetadata reflects invalid metadata format
var ErrInvalidMetadata = errors.New("specified metadata should be of form key1=value1;key2=value2;... and so on") var ErrInvalidMetadata = errors.New("specified metadata should be of form key1=value1;key2=value2;... and so on")
@@ -146,10 +162,13 @@ EXAMPLES:
15. Copy a text file to an object storage and preserve the file system attribute as metadata. 15. Copy a text file to an object storage and preserve the file system attribute as metadata.
{{.Prompt}} {{.HelpName}} -a myobject.txt play/mybucket {{.Prompt}} {{.HelpName}} -a myobject.txt play/mybucket
16. Copy a text file to an object storage with object lock mode set to 'GOVERNANCE' with retention date. 16. Copy a text file to an object storage with object lock mode set to 'GOVERNANCE' with retention duration 1 day.
{{.Prompt}} {{.HelpName}} --attr "x-amz-object-lock-mode=GOVERNANCE;x-amz-object-lock-retain-until-date=2020-01-11T01:57:02Z" locked.txt play/locked-bucket/ {{.Prompt}} {{.HelpName}} --retention-mode governance --retention-duration 1d locked.txt play/locked-bucket/
17. Copy a text file to an object storage and disable multipart upload feature. 17. Copy a text file to an object storage with legal-hold enabled.
{{.Prompt}} {{.HelpName}} --legal-hold on locked.txt play/locked-bucket/
18. Copy a text file to an object storage and disable multipart upload feature.
{{.Prompt}} {{.HelpName}} --disable-multipart myobject.txt play/mybucket {{.Prompt}} {{.HelpName}} --disable-multipart myobject.txt play/mybucket
`, `,
} }
@@ -192,7 +211,7 @@ type ProgressReader interface {
Progress Progress
} }
// doCopy - Copy a singe file from source to destination // doCopy - Copy a single file from source to destination
func doCopy(ctx context.Context, cpURLs URLs, pg ProgressReader, encKeyDB map[string][]prefixSSEPair) URLs { func doCopy(ctx context.Context, cpURLs URLs, pg ProgressReader, encKeyDB map[string][]prefixSSEPair) URLs {
if cpURLs.Error != nil { if cpURLs.Error != nil {
cpURLs.Error = cpURLs.Error.Trace() cpURLs.Error = cpURLs.Error.Trace()
@@ -435,6 +454,22 @@ func doCopySession(cli *cli.Context, session *sessionV8, encKeyDB map[string][]p
cpURLs.TargetContent.Metadata["X-Amz-Storage-Class"] = storageClass cpURLs.TargetContent.Metadata["X-Amz-Storage-Class"] = storageClass
} }
// update Object retention related fields
if session != nil {
cpURLs.TargetContent.RetentionMode = session.Header.CommandStringFlags[rmFlag]
cpURLs.TargetContent.RetentionDuration = session.Header.CommandStringFlags[rdFlag]
cpURLs.TargetContent.LegalHold = session.Header.CommandStringFlags[lhFlag]
} else {
if rm := cli.String(rmFlag); rm != "" {
cpURLs.TargetContent.RetentionMode = rm
}
if rd := cli.String(rdFlag); rd != "" {
cpURLs.TargetContent.RetentionDuration = rd
}
if lh := cli.String(lhFlag); lh != "" {
cpURLs.TargetContent.LegalHold = lh
}
}
if cli.String("attr") != "" { if cli.String("attr") != "" {
userMetaMap, _ := getMetaDataEntry(cli.String("attr")) userMetaMap, _ := getMetaDataEntry(cli.String("attr"))
for metaDataKey, metaDataVal := range userMetaMap { for metaDataKey, metaDataVal := range userMetaMap {
@@ -565,13 +600,16 @@ func mainCopy(ctx *cli.Context) error {
// check 'copy' cli arguments. // check 'copy' cli arguments.
checkCopySyntax(ctx, encKeyDB) checkCopySyntax(ctx, encKeyDB)
// Additional command speific theme customization. // Additional command specific theme customization.
console.SetColor("Copy", color.New(color.FgGreen, color.Bold)) console.SetColor("Copy", color.New(color.FgGreen, color.Bold))
recursive := ctx.Bool("recursive") recursive := ctx.Bool("recursive")
olderThan := ctx.String("older-than") olderThan := ctx.String("older-than")
newerThan := ctx.String("newer-than") newerThan := ctx.String("newer-than")
storageClass := ctx.String("storage-class") storageClass := ctx.String("storage-class")
retentionMode := ctx.String(rmFlag)
retentionDuration := ctx.String(rdFlag)
legalHold := ctx.String(lhFlag)
sseKeys := os.Getenv("MC_ENCRYPT_KEY") sseKeys := os.Getenv("MC_ENCRYPT_KEY")
if key := ctx.String("encrypt-key"); key != "" { if key := ctx.String("encrypt-key"); key != "" {
sseKeys = key sseKeys = key
@@ -597,6 +635,9 @@ func mainCopy(ctx *cli.Context) error {
session.Header.CommandStringFlags["older-than"] = olderThan session.Header.CommandStringFlags["older-than"] = olderThan
session.Header.CommandStringFlags["newer-than"] = newerThan session.Header.CommandStringFlags["newer-than"] = newerThan
session.Header.CommandStringFlags["storage-class"] = storageClass session.Header.CommandStringFlags["storage-class"] = storageClass
session.Header.CommandStringFlags[rmFlag] = retentionMode
session.Header.CommandStringFlags[rdFlag] = retentionDuration
session.Header.CommandStringFlags[lhFlag] = legalHold
session.Header.CommandStringFlags["encrypt-key"] = sseKeys session.Header.CommandStringFlags["encrypt-key"] = sseKeys
session.Header.CommandStringFlags["encrypt"] = sse session.Header.CommandStringFlags["encrypt"] = sse
session.Header.CommandBoolFlags["session"] = ctx.Bool("continue") session.Header.CommandBoolFlags["session"] = ctx.Bool("continue")

View File

@@ -55,6 +55,14 @@ func checkCopySyntax(ctx *cli.Context, encKeyDB map[string][]prefixSSEPair) {
} }
} }
if ctx.String(rdFlag) != "" && ctx.String(rmFlag) == "" {
fatalIf(errInvalidArgument().Trace(), fmt.Sprintf("Both object retention flags `--%s` and `--%s` are required.\n", rdFlag, rmFlag))
}
if ctx.String(rdFlag) == "" && ctx.String(rmFlag) != "" {
fatalIf(errInvalidArgument().Trace(), fmt.Sprintf("Both object retention flags `--%s` and `--%s` are required.\n", rdFlag, rmFlag))
}
// Guess CopyURLsType based on source and target URLs. // Guess CopyURLsType based on source and target URLs.
copyURLsType, err := guessCopyURLType(srcURLs, tgtURL, isRecursive, encKeyDB) copyURLsType, err := guessCopyURLType(srcURLs, tgtURL, isRecursive, encKeyDB)
if err != nil { if err != nil {

View File

@@ -136,6 +136,35 @@ func lock(urlStr string, mode *minio.RetentionMode, validity *uint, unit *minio.
return nil return nil
} }
func parseRetentionValidity(validityStr string, m minio.RetentionMode) (*uint, *minio.ValidityUnit) {
if !m.IsValid() {
fatalIf(probe.NewError(errors.New("invalid argument")), "invalid retention mode '%v'", m)
}
unitStr := string(validityStr[len(validityStr)-1])
validityStr = validityStr[:len(validityStr)-1]
ui64, err := strconv.ParseUint(validityStr, 10, 64)
if err != nil {
fatalIf(probe.NewError(errors.New("invalid argument")), "invalid validity '%v'", validityStr)
}
u := uint(ui64)
validity := &u
var unit *minio.ValidityUnit
switch unitStr {
case "d", "D":
d := minio.Days
unit = &d
case "y", "Y":
y := minio.Years
unit = &y
default:
fatalIf(probe.NewError(errors.New("invalid argument")), "invalid validity format '%v'", unitStr)
}
return validity, unit
}
// main for lock command. // main for lock command.
func mainLock(ctx *cli.Context) error { func mainLock(ctx *cli.Context) error {
console.SetColor("Mode", color.New(color.FgCyan, color.Bold)) console.SetColor("Mode", color.New(color.FgCyan, color.Bold))
@@ -162,33 +191,9 @@ func mainLock(ctx *cli.Context) error {
} }
m := minio.RetentionMode(strings.ToUpper(args[1])) m := minio.RetentionMode(strings.ToUpper(args[1]))
if !m.IsValid() {
fatalIf(probe.NewError(errors.New("invalid argument")), "invalid retention mode '%v'", m)
}
mode = &m mode = &m
validity, unit = parseRetentionValidity(args[2], m)
validityStr := args[2]
unitStr := string(validityStr[len(validityStr)-1])
validityStr = validityStr[:len(validityStr)-1]
ui64, err := strconv.ParseUint(validityStr, 10, 64)
if err != nil {
fatalIf(probe.NewError(errors.New("invalid argument")), "invalid validity '%v'", args[2])
}
u := uint(ui64)
validity = &u
switch unitStr {
case "d", "D":
d := minio.Days
unit = &d
case "y", "Y":
y := minio.Years
unit = &y
default:
fatalIf(probe.NewError(errors.New("invalid argument")), "invalid validity format '%v'", args[2])
}
default: default:
cli.ShowCommandHelpAndExit(ctx, "lock", 1) cli.ShowCommandHelpAndExit(ctx, "lock", 1)
} }

View File

@@ -253,7 +253,7 @@ func (mj *mirrorJob) doRemove(sURLs URLs) URLs {
contentCh <- &ClientContent{URL: *newClientURL(sURLs.TargetContent.URL.Path)} contentCh <- &ClientContent{URL: *newClientURL(sURLs.TargetContent.URL.Path)}
close(contentCh) close(contentCh)
isRemoveBucket := false isRemoveBucket := false
errorCh := clnt.Remove(false, isRemoveBucket, contentCh) errorCh := clnt.Remove(false, isRemoveBucket, false, contentCh)
for pErr := range errorCh { for pErr := range errorCh {
if pErr != nil { if pErr != nil {
switch pErr.ToGoError().(type) { switch pErr.ToGoError().(type) {

View File

@@ -122,7 +122,7 @@ func deleteBucket(url string) *probe.Error {
var isIncomplete bool var isIncomplete bool
isRemoveBucket := true isRemoveBucket := true
contentCh := make(chan *ClientContent) contentCh := make(chan *ClientContent)
errorCh := clnt.Remove(isIncomplete, isRemoveBucket, contentCh) errorCh := clnt.Remove(isIncomplete, isRemoveBucket, false, contentCh)
for content := range clnt.List(true, false, false, DirLast) { for content := range clnt.List(true, false, false, DirLast) {
if content.Err != nil { if content.Err != nil {

View File

@@ -38,11 +38,14 @@ var (
Usage: "apply retention recursively", Usage: "apply retention recursively",
}, },
cli.BoolFlag{ cli.BoolFlag{
Name: "bypass", Name: bypass,
Usage: "bypass governance", Usage: "bypass governance",
}, },
} }
) )
var bypass = "bypass"
var retentionCmd = cli.Command{ var retentionCmd = cli.Command{
Name: "retention", Name: "retention",
Usage: "set object retention for objects with a given prefix", Usage: "set object retention for objects with a given prefix",
@@ -95,6 +98,21 @@ func (m retentionCmdMessage) JSON() string {
return string(msgBytes) return string(msgBytes)
} }
func getRetainUntilDate(validity *uint, unit *minio.ValidityUnit) (string, error) {
if validity == nil {
return "", fmt.Errorf("invalid validity '%v'", validity)
}
t := UTCNow()
if *unit == minio.Years {
t = t.AddDate(int(*validity), 0, 0)
} else {
t = t.AddDate(0, 0, int(*validity))
}
timeStr := t.Format(time.RFC3339)
return timeStr, nil
}
// setRetention - Set Retention for all objects within a given prefix. // setRetention - Set Retention for all objects within a given prefix.
func setRetention(urlStr string, mode *minio.RetentionMode, validity *uint, unit *minio.ValidityUnit, bypassGovernance, isRecursive bool) error { func setRetention(urlStr string, mode *minio.RetentionMode, validity *uint, unit *minio.ValidityUnit, bypassGovernance, isRecursive bool) error {
clnt, err := newClient(urlStr) clnt, err := newClient(urlStr)
@@ -110,26 +128,6 @@ func setRetention(urlStr string, mode *minio.RetentionMode, validity *uint, unit
alias, _, _ := mustExpandAlias(urlStr) alias, _, _ := mustExpandAlias(urlStr)
retainUntilDate := func() (time.Time, error) {
if validity == nil {
return timeSentinel, fmt.Errorf("invalid validity '%v'", validity)
}
t := UTCNow()
if *unit == minio.Years {
t = t.AddDate(int(*validity), 0, 0)
} else {
t = t.AddDate(0, 0, int(*validity))
}
timeStr := t.Format(time.RFC3339)
t1, e := time.Parse(
time.RFC3339,
timeStr)
if e != nil {
return timeSentinel, e
}
return t1, nil
}
validityStr := func() *string { validityStr := func() *string {
if validity == nil { if validity == nil {
return nil return nil
@@ -151,11 +149,17 @@ func setRetention(urlStr string, mode *minio.RetentionMode, validity *uint, unit
cErr = exitStatus(globalErrorExitStatus) // Set the exit status. cErr = exitStatus(globalErrorExitStatus) // Set the exit status.
continue continue
} }
retainUntil, err := retainUntilDate() timeStr, err := getRetainUntilDate(validity, unit)
if err != nil { if err != nil {
errorIf(content.Err.Trace(clnt.GetURL().String()), "Invalid retention date") errorIf(content.Err.Trace(clnt.GetURL().String()), "Invalid retention date")
continue continue
} }
retainUntil, e := time.Parse(time.RFC3339, timeStr)
if e != nil {
errorIf(content.Err.Trace(clnt.GetURL().String()), "Invalid retention date")
continue
}
newClnt, perr := newClientFromAlias(alias, content.URL.String()) newClnt, perr := newClientFromAlias(alias, content.URL.String())
if perr != nil { if perr != nil {
errorIf(content.Err.Trace(clnt.GetURL().String()), "Invalid URL") errorIf(content.Err.Trace(clnt.GetURL().String()), "Invalid URL")

View File

@@ -65,6 +65,10 @@ var (
Name: "newer-than", Name: "newer-than",
Usage: "remove objects newer than L days, M hours and N minutes", Usage: "remove objects newer than L days, M hours and N minutes",
}, },
cli.BoolFlag{
Name: bypass,
Usage: "bypass governance",
},
} }
) )
@@ -117,6 +121,9 @@ EXAMPLES:
10. Remove an encrypted object from Amazon S3 cloud storage. 10. Remove an encrypted object from Amazon S3 cloud storage.
{{.Prompt}} {{.HelpName}} --encrypt-key "s3/sql-backups/=32byteslongsecretkeymustbegiven1" s3/sql-backups/1999/old-backup.tgz {{.Prompt}} {{.HelpName}} --encrypt-key "s3/sql-backups/=32byteslongsecretkeymustbegiven1" s3/sql-backups/1999/old-backup.tgz
11. Bypass object retention in governance mode and delete the object.
{{.Prompt}} {{.HelpName}} --bypass s3/pop-songs/
`, `,
} }
@@ -180,7 +187,7 @@ func checkRmSyntax(ctx *cli.Context, encKeyDB map[string][]prefixSSEPair) {
} }
} }
func removeSingle(url string, isIncomplete bool, isFake, isForce bool, olderThan, newerThan string, encKeyDB map[string][]prefixSSEPair) error { func removeSingle(url string, isIncomplete, isFake, isForce, isBypass bool, olderThan, newerThan string, encKeyDB map[string][]prefixSSEPair) error {
isRecursive := false isRecursive := false
contents, pErr := statURL(url, isIncomplete, isRecursive, encKeyDB) contents, pErr := statURL(url, isIncomplete, isRecursive, encKeyDB)
if pErr != nil { if pErr != nil {
@@ -227,7 +234,7 @@ func removeSingle(url string, isIncomplete bool, isFake, isForce bool, olderThan
contentCh <- &ClientContent{URL: *newClientURL(targetURL)} contentCh <- &ClientContent{URL: *newClientURL(targetURL)}
close(contentCh) close(contentCh)
isRemoveBucket := false isRemoveBucket := false
errorCh := clnt.Remove(isIncomplete, isRemoveBucket, contentCh) errorCh := clnt.Remove(isIncomplete, isRemoveBucket, isBypass, contentCh)
for pErr := range errorCh { for pErr := range errorCh {
if pErr != nil { if pErr != nil {
errorIf(pErr.Trace(url), "Failed to remove `"+url+"`.") errorIf(pErr.Trace(url), "Failed to remove `"+url+"`.")
@@ -243,7 +250,7 @@ func removeSingle(url string, isIncomplete bool, isFake, isForce bool, olderThan
return nil return nil
} }
func removeRecursive(url string, isIncomplete bool, isFake bool, olderThan, newerThan string, encKeyDB map[string][]prefixSSEPair) error { func removeRecursive(url string, isIncomplete, isFake, isBypass bool, olderThan, newerThan string, encKeyDB map[string][]prefixSSEPair) error {
targetAlias, targetURL, _ := mustExpandAlias(url) targetAlias, targetURL, _ := mustExpandAlias(url)
clnt, pErr := newClientFromAlias(targetAlias, targetURL) clnt, pErr := newClientFromAlias(targetAlias, targetURL)
if pErr != nil { if pErr != nil {
@@ -253,7 +260,7 @@ func removeRecursive(url string, isIncomplete bool, isFake bool, olderThan, newe
contentCh := make(chan *ClientContent) contentCh := make(chan *ClientContent)
isRemoveBucket := false isRemoveBucket := false
errorCh := clnt.Remove(isIncomplete, isRemoveBucket, contentCh) errorCh := clnt.Remove(isIncomplete, isRemoveBucket, isBypass, contentCh)
isRecursive := true isRecursive := true
for content := range clnt.List(isRecursive, isIncomplete, false, DirNone) { for content := range clnt.List(isRecursive, isIncomplete, false, DirNone) {
@@ -337,6 +344,7 @@ func mainRm(ctx *cli.Context) error {
isRecursive := ctx.Bool("recursive") isRecursive := ctx.Bool("recursive")
isFake := ctx.Bool("fake") isFake := ctx.Bool("fake")
isStdin := ctx.Bool("stdin") isStdin := ctx.Bool("stdin")
isBypass := ctx.Bool(bypass)
olderThan := ctx.String("older-than") olderThan := ctx.String("older-than")
newerThan := ctx.String("newer-than") newerThan := ctx.String("newer-than")
isForce := ctx.Bool("force") isForce := ctx.Bool("force")
@@ -349,9 +357,9 @@ func mainRm(ctx *cli.Context) error {
// Support multiple targets. // Support multiple targets.
for _, url := range ctx.Args() { for _, url := range ctx.Args() {
if isRecursive { if isRecursive {
e = removeRecursive(url, isIncomplete, isFake, olderThan, newerThan, encKeyDB) e = removeRecursive(url, isIncomplete, isFake, isBypass, olderThan, newerThan, encKeyDB)
} else { } else {
e = removeSingle(url, isIncomplete, isFake, isForce, olderThan, newerThan, encKeyDB) e = removeSingle(url, isIncomplete, isFake, isForce, isBypass, olderThan, newerThan, encKeyDB)
} }
if rerr == nil { if rerr == nil {
@@ -367,9 +375,9 @@ func mainRm(ctx *cli.Context) error {
for scanner.Scan() { for scanner.Scan() {
url := scanner.Text() url := scanner.Text()
if isRecursive { if isRecursive {
e = removeRecursive(url, isIncomplete, isFake, olderThan, newerThan, encKeyDB) e = removeRecursive(url, isIncomplete, isFake, isBypass, olderThan, newerThan, encKeyDB)
} else { } else {
e = removeSingle(url, isIncomplete, isFake, isForce, olderThan, newerThan, encKeyDB) e = removeSingle(url, isIncomplete, isFake, isForce, isBypass, olderThan, newerThan, encKeyDB)
} }
if rerr == nil { if rerr == nil {

6
go.mod
View File

@@ -16,8 +16,8 @@ require (
github.com/mattn/go-isatty v0.0.8 github.com/mattn/go-isatty v0.0.8
github.com/mattn/go-runewidth v0.0.5 // indirect github.com/mattn/go-runewidth v0.0.5 // indirect
github.com/minio/cli v1.22.0 github.com/minio/cli v1.22.0
github.com/minio/minio v0.0.0-20200404010650-2155e74951bf github.com/minio/minio v0.0.0-20200327214830-6f992134a25f
github.com/minio/minio-go/v6 v6.0.52 github.com/minio/minio-go/v6 v6.0.52-0.20200403112139-73469ba42c49
github.com/minio/sha256-simd v0.1.1 github.com/minio/sha256-simd v0.1.1
github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/go-homedir v1.1.0
github.com/pkg/profile v1.3.0 github.com/pkg/profile v1.3.0
@@ -31,6 +31,6 @@ require (
golang.org/x/text v0.3.2 golang.org/x/text v0.3.2
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127
gopkg.in/h2non/filetype.v1 v1.0.5 gopkg.in/h2non/filetype.v1 v1.0.5
gopkg.in/ini.v1 v1.52.0 // indirect gopkg.in/ini.v1 v1.55.0 // indirect
gopkg.in/yaml.v2 v2.2.4 gopkg.in/yaml.v2 v2.2.4
) )

23
go.sum
View File

@@ -220,8 +220,7 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kurin/blazer v0.5.4-0.20200327014341-8f90a40f8af7 h1:smZXPopqRVVywwzou4WYWvUbJvSAzIDFizfWElpmAqY= github.com/kurin/blazer v0.5.4-0.20190613185654-cf2f27cc0be3/go.mod h1:4FCXMUWo9DllR2Do4TtBd377ezyAJ51vB5uTBjt0pGU=
github.com/kurin/blazer v0.5.4-0.20200327014341-8f90a40f8af7/go.mod h1:4FCXMUWo9DllR2Do4TtBd377ezyAJ51vB5uTBjt0pGU=
github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4= github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4=
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/mailru/easyjson v0.0.0-20180730094502-03f2033d19d5 h1:0x4qcEHDpruK6ML/m/YSlFUUu0UpRD3I2PHsNCuGnyA= github.com/mailru/easyjson v0.0.0-20180730094502-03f2033d19d5 h1:0x4qcEHDpruK6ML/m/YSlFUUu0UpRD3I2PHsNCuGnyA=
@@ -255,12 +254,13 @@ github.com/minio/highwayhash v1.0.0 h1:iMSDhgUILCr0TNm8LWlSjF8N0ZIj2qbO8WHp6Q/J2
github.com/minio/highwayhash v1.0.0/go.mod h1:xQboMTeM9nY9v/LlAOxFctujiv5+Aq2hR5dxBpaMbdc= github.com/minio/highwayhash v1.0.0/go.mod h1:xQboMTeM9nY9v/LlAOxFctujiv5+Aq2hR5dxBpaMbdc=
github.com/minio/lsync v1.0.1 h1:AVvILxA976xc27hstd1oR+X9PQG0sPSom1MNb1ImfUs= github.com/minio/lsync v1.0.1 h1:AVvILxA976xc27hstd1oR+X9PQG0sPSom1MNb1ImfUs=
github.com/minio/lsync v1.0.1/go.mod h1:tCFzfo0dlvdGl70IT4IAK/5Wtgb0/BrTmo/jE8pArKA= github.com/minio/lsync v1.0.1/go.mod h1:tCFzfo0dlvdGl70IT4IAK/5Wtgb0/BrTmo/jE8pArKA=
github.com/minio/minio v0.0.0-20200404010650-2155e74951bf h1:pyWpAtXni2XVeObXm5clbHw5m8YWVOEQNSMcZXTjf94= github.com/minio/minio v0.0.0-20200327214830-6f992134a25f h1:RoOBi0vhXkZqe2b6RTROOsVJUwMqLMoet9r7eL01euo=
github.com/minio/minio v0.0.0-20200404010650-2155e74951bf/go.mod h1:7HGxQRqSt4Jfb86tZUQ+ZOWHDLswFu5uB+q2mjFW3O8= github.com/minio/minio v0.0.0-20200327214830-6f992134a25f/go.mod h1:BzbIyKUJPp+4f03i2XF7+GsijXnxMakUe5x+lm2WNc8=
github.com/minio/minio-go/v6 v6.0.45/go.mod h1:qD0lajrGW49lKZLtXKtCB4X/qkMf0a5tBvN2PaZg7Gg= github.com/minio/minio-go/v6 v6.0.45/go.mod h1:qD0lajrGW49lKZLtXKtCB4X/qkMf0a5tBvN2PaZg7Gg=
github.com/minio/minio-go/v6 v6.0.52 h1:sS8NjhahYzX71nuUvO14PBpdNUTjeaNvjySmVnozGmw= github.com/minio/minio-go/v6 v6.0.51-0.20200319192131-097caa7760c7 h1:WQmYVUDRGdcEWhJeb42/Fn1IO7SBLem173DTE4+jp/E=
github.com/minio/minio-go/v6 v6.0.52/go.mod h1:DIvC/IApeHX8q1BAMVCXSXwpmrmM+I+iBvhvztQorfI= github.com/minio/minio-go/v6 v6.0.51-0.20200319192131-097caa7760c7/go.mod h1:qD0lajrGW49lKZLtXKtCB4X/qkMf0a5tBvN2PaZg7Gg=
github.com/minio/parquet-go v0.0.0-20200125064549-a1e49702e174 h1:WYFHZIJ5LTWd4C3CW26jguaBLLDdX7l1/Xa3QSKGkIc= github.com/minio/minio-go/v6 v6.0.52-0.20200403112139-73469ba42c49 h1:eOc5mvyNftWN13g0ubArdVjWisj20TPGh6lSpb55xvk=
github.com/minio/minio-go/v6 v6.0.52-0.20200403112139-73469ba42c49/go.mod h1:DIvC/IApeHX8q1BAMVCXSXwpmrmM+I+iBvhvztQorfI=
github.com/minio/parquet-go v0.0.0-20200125064549-a1e49702e174/go.mod h1:PXYM9yI2l0YPmxHUXe6mFTmkQcyaVasDshAPTbGpDoo= github.com/minio/parquet-go v0.0.0-20200125064549-a1e49702e174/go.mod h1:PXYM9yI2l0YPmxHUXe6mFTmkQcyaVasDshAPTbGpDoo=
github.com/minio/sha256-simd v0.1.1 h1:5QHSlgo3nt5yKOJrC7W8w7X+NFl8cMPZm96iu8kKUJU= github.com/minio/sha256-simd v0.1.1 h1:5QHSlgo3nt5yKOJrC7W8w7X+NFl8cMPZm96iu8kKUJU=
github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
@@ -365,10 +365,10 @@ github.com/secure-io/sio-go v0.3.0/go.mod h1:D3KmXgKETffyYxBdFRN+Hpd2WzhzqS0EQwT
github.com/shirou/gopsutil v2.20.3-0.20200314133625-53cec6b37e6a+incompatible h1:YiKUe2ZOmfpDBH4OSyxwkx/mjNqHHnNhOtZ2mPyRme8= github.com/shirou/gopsutil v2.20.3-0.20200314133625-53cec6b37e6a+incompatible h1:YiKUe2ZOmfpDBH4OSyxwkx/mjNqHHnNhOtZ2mPyRme8=
github.com/shirou/gopsutil v2.20.3-0.20200314133625-53cec6b37e6a+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shirou/gopsutil v2.20.3-0.20200314133625-53cec6b37e6a+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.5.0 h1:1N5EYkVAPEywqZRJd7cwnRtCb6xJx7NH3T3WUTF980Q= github.com/sirupsen/logrus v1.5.0 h1:1N5EYkVAPEywqZRJd7cwnRtCb6xJx7NH3T3WUTF980Q=
github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo= github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo=
github.com/skyrings/skyring-common v0.0.0-20160929130248-d1c0bb1cbd5e h1:jrZSSgPUDtBeJbGXqgGUeupQH8I+ZvGXfhpIahye2Bc=
github.com/skyrings/skyring-common v0.0.0-20160929130248-d1c0bb1cbd5e/go.mod h1:d8hQseuYt4rJoOo21lFzYJdhMjmDqLY++ayArbgYjWI= github.com/skyrings/skyring-common v0.0.0-20160929130248-d1c0bb1cbd5e/go.mod h1:d8hQseuYt4rJoOo21lFzYJdhMjmDqLY++ayArbgYjWI=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/assertions v0.0.0-20190401211740-f487f9de1cd3 h1:hBSHahWMEgzwRyS6dRpxY0XyjZsHyQ61s084wo5PJe0= github.com/smartystreets/assertions v0.0.0-20190401211740-f487f9de1cd3 h1:hBSHahWMEgzwRyS6dRpxY0XyjZsHyQ61s084wo5PJe0=
@@ -453,6 +453,7 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -480,6 +481,7 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190523142557-0e01d883c5c5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190523142557-0e01d883c5c5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200320181252-af34d8274f85/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -532,9 +534,8 @@ gopkg.in/h2non/filetype.v1 v1.0.5 h1:CC1jjJjoEhNVbMhXYalmGBhOBK2V70Q1N850wt/98/Y
gopkg.in/h2non/filetype.v1 v1.0.5/go.mod h1:M0yem4rwSX5lLVrkEuRRp2/NinFMD5vgJ4DlAhZcfNo= gopkg.in/h2non/filetype.v1 v1.0.5/go.mod h1:M0yem4rwSX5lLVrkEuRRp2/NinFMD5vgJ4DlAhZcfNo=
gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.48.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.48.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.52.0 h1:j+Lt/M1oPPejkniCg1TkWE2J3Eh1oZTsHSXzMTzUXn4= gopkg.in/ini.v1 v1.55.0 h1:E8yzL5unfpW3M6fz/eB7Cb5MQAYSZ7GKo4Qth+N2sgQ=
gopkg.in/ini.v1 v1.52.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.55.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/jcmturner/aescts.v1 v1.0.1 h1:cVVZBK2b1zY26haWB4vbBiZrfFQnfbTVrE3xZq6hrEw=
gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo= gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo=
gopkg.in/jcmturner/dnsutils.v1 v1.0.1 h1:cIuC1OLRGZrld+16ZJvvZxVJeKPsvd5eUIvxfoN5hSM= gopkg.in/jcmturner/dnsutils.v1 v1.0.1 h1:cIuC1OLRGZrld+16ZJvvZxVJeKPsvd5eUIvxfoN5hSM=
gopkg.in/jcmturner/dnsutils.v1 v1.0.1/go.mod h1:m3v+5svpVOhtFAP/wSz+yzh4Mc0Fg7eRhxkJMWSIz9Q= gopkg.in/jcmturner/dnsutils.v1 v1.0.1/go.mod h1:m3v+5svpVOhtFAP/wSz+yzh4Mc0Fg7eRhxkJMWSIz9Q=