diff --git a/cmd/client-errors.go b/cmd/client-errors.go index 5e6f0ed960..78e3dcb48d 100644 --- a/cmd/client-errors.go +++ b/cmd/client-errors.go @@ -56,6 +56,13 @@ func (e BucketNameEmpty) Error() string { return "Bucket name cannot be empty." } +// ObjectNameEmpty - object name empty. +type ObjectNameEmpty struct{} + +func (e ObjectNameEmpty) Error() string { + return "Object name cannot be empty." +} + // BucketInvalid - bucket name invalid. type BucketInvalid struct { Bucket string diff --git a/cmd/client-fs.go b/cmd/client-fs.go index d8804166c2..14aa8691b8 100644 --- a/cmd/client-fs.go +++ b/cmd/client-fs.go @@ -393,13 +393,13 @@ func (f *fsClient) put(reader io.Reader, size int64, metadata map[string][]strin return totalWritten, probe.NewError(e) } - ctime, e := strconv.ParseInt(attr["ctime"], 10, 64) + mtime, e := strconv.ParseInt(attr["mtime"], 10, 64) if e != nil { return totalWritten, probe.NewError(e) } - // Attempt to change the access, modify and change time - if e := os.Chtimes(objectPath, time.Unix(atime, 0), time.Unix(ctime, 0)); e != nil { + // Attempt to change the access and modify time + if e := os.Chtimes(objectPath, time.Unix(atime, 0), time.Unix(mtime, 0)); e != nil { return totalWritten, probe.NewError(e) } } diff --git a/cmd/client-s3.go b/cmd/client-s3.go index dd1838df71..fdcd0c0338 100644 --- a/cmd/client-s3.go +++ b/cmd/client-s3.go @@ -2108,8 +2108,11 @@ func (c *s3Client) GetObjectTagging() (tagging.Tagging, *probe.Error) { var err error bucketName, objectName := c.url2BucketAndObject() if bucketName == "" || objectName == "" { - err = errors.New("Bucket name or object name cannot be empty") - return tagging.Tagging{}, probe.NewError(err) + pErr := probe.NewError(BucketNameEmpty{}) + if objectName == "" { + pErr = probe.NewError(ObjectNameEmpty{}) + } + return tagging.Tagging{}, pErr } tagXML, err := c.api.GetObjectTagging(bucketName, objectName) if err != nil { @@ -2127,8 +2130,11 @@ func (c *s3Client) SetObjectTagging(tagMap map[string]string) *probe.Error { var err error bucket, object := c.url2BucketAndObject() if bucket == "" || object == "" { - err = errors.New("Bucket name or object name cannot be empty") - return probe.NewError(err) + pErr := probe.NewError(BucketNameEmpty{}) + if object == "" { + pErr = probe.NewError(ObjectNameEmpty{}) + } + return pErr } if err = c.api.PutObjectTagging(bucket, object, tagMap); err != nil { return probe.NewError(err) @@ -2140,8 +2146,11 @@ func (c *s3Client) SetObjectTagging(tagMap map[string]string) *probe.Error { func (c *s3Client) DeleteObjectTagging() *probe.Error { bucket, object := c.url2BucketAndObject() if bucket == "" || object == "" { - err := errors.New("Bucket name or object name cannot be empty") - return probe.NewError(err) + pErr := probe.NewError(BucketNameEmpty{}) + if object == "" { + pErr = probe.NewError(ObjectNameEmpty{}) + } + return pErr } if err := c.api.RemoveObjectTagging(bucket, object); err != nil { return probe.NewError(err) diff --git a/cmd/tag-list.go b/cmd/tag-list.go index 6a61d2712f..83554aae3f 100644 --- a/cmd/tag-list.go +++ b/cmd/tag-list.go @@ -66,7 +66,9 @@ type tagList struct { // tagsetListMessage container for displaying tag type tagsetListMessage struct { Tags []tagList `json:"tagset,omitempty"` - Status string `json:"status,omitempty"` + Status string `json:"status"` + Name string `json:"name"` + Err error `json:"err,omitempty"` } func (t tagsetListMessage) JSON() string { @@ -75,34 +77,44 @@ func (t tagsetListMessage) JSON() string { tagJSONbytes, err = json.MarshalIndent(t, "", " ") tagJSONbytes = bytes.Replace(tagJSONbytes, []byte("\\u0026"), []byte("&"), -1) - fatalIf(probe.NewError(err), "Unable to marshal into JSON.") + fatalIf(probe.NewError(err), "Unable to marshal into JSON for "+t.Name) return string(tagJSONbytes) } func (t tagsetListMessage) String() string { - return console.Colorize(tagPrintMsgTheme, t.Status) + var msg string + if t.Err == nil { + msg = console.Colorize(tagPrintMsgTheme, "Tag(s) set for "+t.Name) + } else { + msg = console.Colorize(tagPrintErrMsgTheme, "Failed to set tags for "+t.Name+". "+t.Err.Error()) + } + return msg } const ( - tagMainHeader string = "Main-Heading" - tagRowTheme string = "Row-Header" - tagPrintMsgTheme string = "Tag-PrintMsg" + tagMainHeader string = "Main-Heading" + tagRowTheme string = "Row-Header" + tagPrintMsgTheme string = "Tag-PrintMsg" + tagPrintErrMsgTheme string = "Tag-PrintMsgErr" ) -// tagStr is in the format key1=value1&key1=value2 -func parseTagListMessage(tags string, urlStr string) tagsetListMessage { +// parseTagListMessage parses the tags(string) and initializes the structure tagsetListMessage. +// tags(string) is in the format key1=value1&key1=value2 +func parseTagListMessage(tags string, urlStr string, err error) tagsetListMessage { var t tagsetListMessage var tagStr string var kvPairStr []string tagStr = strings.Replace(tags, "\\u0026", "&", -1) + t.Name = getTagObjectName(urlStr) + t.Err = nil if tagStr != "" { kvPairStr = strings.SplitN(tagStr, "&", -1) + t.Status = "success" + } else { + t.Status = "error" + t.Err = err } - if len(kvPairStr) == 0 { - t.Status = "Tags not added or not available." - } - for _, kvPair := range kvPairStr { kvPairSplit := splitStr(kvPair, "=", 2) t.Tags = append(t.Tags, tagList{Key: kvPairSplit[0], Value: kvPairSplit[1]}) @@ -113,9 +125,11 @@ func parseTagListMessage(tags string, urlStr string) tagsetListMessage { func getObjTagging(urlStr string) (tagging.Tagging, error) { clnt, pErr := newClient(urlStr) - fatalIf(pErr.Trace(urlStr), "Unable to initialize target "+urlStr+".") + if pErr != nil { + fatalIf(pErr.Trace(urlStr), "Unable to initialize target "+urlStr+". Error: "+pErr.ToGoError().Error()) + } tagObj, pErr := clnt.GetObjectTagging() - fatalIf(pErr, "Failed to get tags for "+urlStr) + fatalIf(pErr, "Failed to get tags for "+getTagObjectName(urlStr)) return tagObj, nil } @@ -125,6 +139,7 @@ func setTagListColorScheme() { console.SetColor(tagRowTheme, color.New(color.FgWhite)) console.SetColor(tagMainHeader, color.New(color.Bold, color.FgCyan)) console.SetColor(tagPrintMsgTheme, color.New(color.FgGreen)) + console.SetColor(tagPrintErrMsgTheme, color.New(color.FgRed)) } func checkListTagSyntax(ctx *cli.Context) { @@ -134,9 +149,17 @@ func checkListTagSyntax(ctx *cli.Context) { } } +func getTagObjectName(urlStr string) string { + if !strings.Contains(urlStr, "/") { + urlStr = filepath.ToSlash(urlStr) + } + splits := splitStr(urlStr, "/", 3) + object := splits[2] + + return object +} + func listTagInfoFieldMultiple(urlStr string, kvpairs []tagging.Tag) { - var object string - var splits []string padLen := len("Name") for _, kv := range kvpairs { if len(kv.Key) > padLen { @@ -144,12 +167,7 @@ func listTagInfoFieldMultiple(urlStr string, kvpairs []tagging.Tag) { } } padLen = listTagPaddingSpace(padLen) - if !strings.Contains(urlStr, "/") { - urlStr = filepath.ToSlash(urlStr) - } - splits = splitStr(urlStr, "/", 3) - object = splits[2] - objectName := fmt.Sprintf("%-*s: %s", padLen, "Name", object) + objectName := fmt.Sprintf("%-*s: %s", padLen, "Name", getTagObjectName(urlStr)) console.Println(console.Colorize(tagMainHeader, objectName)) for idx := 0; idx < len(kvpairs); idx++ { displayField := fmt.Sprintf("%-*s: %s", padLen, kvpairs[idx].Key, kvpairs[idx].Value) @@ -176,26 +194,23 @@ func mainListTag(ctx *cli.Context) error { setTagListColorScheme() args := ctx.Args() objectURL := args.Get(0) - tagObj, err := getObjTagging(objectURL) - - if err != nil { - console.Errorln(err.Error() + ". Error getting tag for " + objectURL) - return err + var tagObj tagging.Tagging + var err error + if tagObj, err = getObjTagging(objectURL); err != nil { + fatal(probe.NewError(err), "Unable to get tags for target "+objectURL+".") } - if globalJSON { - var tMsg tagsetListMessage - tMsg = parseTagListMessage(tagObj.String(), objectURL) - printMsg(tMsg) - return nil - } switch len(tagObj.TagSet.Tags) { case 0: - var tMsg tagsetListMessage - tMsg = parseTagListMessage(tagObj.String(), objectURL) - printMsg(tMsg) + errorIf(errDummy().Trace(objectURL), "Tags not set for "+getTagObjectName(objectURL)+".") default: - listTagInfoFieldMultiple(objectURL, tagObj.TagSet.Tags) + if globalJSON { + var msg tagsetListMessage + msg = parseTagListMessage(tagObj.String(), objectURL, err) + printMsg(msg) + } else { + listTagInfoFieldMultiple(objectURL, tagObj.TagSet.Tags) + } } return nil diff --git a/cmd/tag-main.go b/cmd/tag-main.go index 2476fc1e31..663c856b84 100644 --- a/cmd/tag-main.go +++ b/cmd/tag-main.go @@ -29,7 +29,7 @@ var tagCmd = cli.Command{ Subcommands: []cli.Command{ tagListCmd, tagRemoveCmd, - tagAddCmd, + tagSetCmd, }, } diff --git a/cmd/tag-remove.go b/cmd/tag-remove.go index 3231cd9628..2bf8bcc23b 100644 --- a/cmd/tag-remove.go +++ b/cmd/tag-remove.go @@ -20,7 +20,9 @@ import ( "os" "github.com/minio/cli" + json "github.com/minio/mc/pkg/colorjson" "github.com/minio/mc/pkg/probe" + "github.com/minio/minio/pkg/console" ) var tagRemoveCmd = cli.Command{ @@ -42,42 +44,63 @@ DESCRIPTION: Remove object tags assigned to an object . EXAMPLES: - 1. Remove the tags added to an existing object. + 1. Remove the tags set to an existing object. {{.Prompt}} {{.HelpName}} s3/testbucket/testobject `, } -func checkRemoveTagSyntax(ctx *cli.Context) { - if len(ctx.Args()) != 1 { - cli.ShowCommandHelp(ctx, "remove") - os.Exit(globalErrorExitStatus) - } +// tagSetTagMessage structure will show message depending on the type of console. +type tagRemoveMessage struct { + Status string `json:"status"` + Name string `json:"name"` + Err error `json:"err,omitempty"` +} + +// tagRemoveMessage console colorized output. +func (t tagRemoveMessage) String() string { + return console.Colorize(tagPrintMsgTheme, "Tags removed for "+t.Name+".") +} + +// JSON tagRemoveMessage. +func (t tagRemoveMessage) JSON() string { + msgBytes, e := json.MarshalIndent(t, "", " ") + fatalIf(probe.NewError(e), "Unable to marshal into JSON.") + return string(msgBytes) } -func parseTagRemoveMessage(tags string, urlStr string, err error) tagsetListMessage { - var t tagsetListMessage +func parseTagRemoveMessage(tags string, urlStr string, err error) tagRemoveMessage { + var t tagRemoveMessage + t.Name = getTagObjectName(urlStr) if err != nil { - t.Status = "Remove tags to target " + urlStr + ". Error " + err.Error() + t.Status = "error" + t.Err = err } else { - t.Status = "Tags removed for " + urlStr + "." + t.Status = "success" } return t } +func checkRemoveTagSyntax(ctx *cli.Context) { + if len(ctx.Args()) != 1 { + cli.ShowCommandHelp(ctx, "remove") + os.Exit(globalErrorExitStatus) + } +} + func mainRemoveTag(ctx *cli.Context) error { checkRemoveTagSyntax(ctx) setTagListColorScheme() var pErr *probe.Error + var msg tagRemoveMessage objectURL := ctx.Args().Get(0) clnt, pErr := newClient(objectURL) fatalIf(pErr.Trace(objectURL), "Unable to initialize target "+objectURL+".") pErr = clnt.DeleteObjectTagging() - fatalIf(pErr, "Failed to remove tags") + fatalIf(pErr.Trace(objectURL), "Failed to remove tags") tagObj, err := getObjTagging(objectURL) - var tMsg tagsetListMessage - tMsg = parseTagRemoveMessage(tagObj.String(), objectURL, err) - printMsg(tMsg) + msg = parseTagRemoveMessage(tagObj.String(), objectURL, err) + printMsg(msg) return nil } diff --git a/cmd/tag-add.go b/cmd/tag-set.go similarity index 53% rename from cmd/tag-add.go rename to cmd/tag-set.go index 6b89ee5fc4..5faeba581a 100644 --- a/cmd/tag-add.go +++ b/cmd/tag-set.go @@ -23,21 +23,22 @@ import ( "strings" "github.com/minio/cli" + json "github.com/minio/mc/pkg/colorjson" "github.com/minio/mc/pkg/probe" "github.com/minio/minio/pkg/console" ) -var tagAddCmd = cli.Command{ - Name: "add", - Usage: "add tags for an object", - Action: mainAddTag, +var tagSetCmd = cli.Command{ + Name: "set", + Usage: "set tags for an object", + Action: mainSetTag, Before: setGlobalsFromContext, - Flags: append(tagAddFlags, globalFlags...), + Flags: globalFlags, CustomHelpTemplate: `Name: {{.HelpName}} - {{.Usage}} USAGE: - {{.HelpName}} [COMMAND FLAGS] TARGET + {{.HelpName}} [COMMAND FLAGS] TARGET [TAGS] FLAGS: {{range .VisibleFlags}}{{.}} @@ -47,29 +48,55 @@ DESCRIPTION: EXAMPLES: 1. Assign the tags to an existing object. - {{.Prompt}} {{.HelpName}} s3/testbucket/testobject --tags "key1=value1&key2=value2&key3=value3" + {{.Prompt}} {{.HelpName}} s3/testbucket/testobject "key1=value1&key2=value2&key3=value3" `, } -var tagAddFlags = []cli.Flag{ - cli.StringFlag{ - Name: "tags", - Usage: "format '=&='; = is a key value pair, different key value pairs are separated by '&'", - }, +// tagSetTagMessage structure will show message depending on the type of console. +type tagSetMessage struct { + Status string `json:"status"` + Name string `json:"name"` + Err error `json:"error,omitempty"` } -func checkAddTagSyntax(ctx *cli.Context) { - tagValues := ctx.String("tags") - if len(ctx.Args()) != 1 || len(tagValues) == 0 { - cli.ShowCommandHelp(ctx, "add") +// tagSetMessage console colorized output. +func (t tagSetMessage) String() string { + return console.Colorize(tagPrintMsgTheme, "Tags set for "+t.Name+".") +} + +// JSON tagSetMessage. +func (t tagSetMessage) JSON() string { + msgBytes, e := json.MarshalIndent(t, "", " ") + fatalIf(probe.NewError(e), "Unable to marshal into JSON.") + return string(msgBytes) +} + +func parseTagSetMessage(tags string, urlStr string, err error) tagSetMessage { + var t tagSetMessage + t.Name = getTagObjectName(urlStr) + if err != nil { + t.Status = "error" + t.Err = err + } else { + t.Status = "success" + } + return t +} + +func checkSetTagSyntax(ctx *cli.Context) { + if len(ctx.Args()) != 2 || (len(ctx.Args()) == 2 && len(ctx.Args().Get(1)) == 0) { + cli.ShowCommandHelp(ctx, "set") os.Exit(globalErrorExitStatus) } } func getTaggingMap(ctx *cli.Context) (map[string]string, error) { + if len(ctx.Args()) != 2 { + return nil, errors.New("Tags argument is empty") + } tagKVMap := make(map[string]string) - tagValues := strings.Split(ctx.String("tags"), "&") + tagValues := strings.Split(ctx.Args().Get(1), "&") for tagIdx, tag := range tagValues { var key, val string if !strings.Contains(tag, "=") { @@ -88,36 +115,25 @@ func getTaggingMap(ctx *cli.Context) (map[string]string, error) { return tagKVMap, nil } -func parseTagAddMessage(tags string, urlStr string, err error) tagsetListMessage { - var t tagsetListMessage - if err != nil { - t.Status = "Failed to add tags to target " + urlStr + ". Error: " + err.Error() - } else { - t.Status = "Tags added for " + urlStr + "." - } - - return t -} - -func mainAddTag(ctx *cli.Context) error { - checkAddTagSyntax(ctx) +func mainSetTag(ctx *cli.Context) error { + checkSetTagSyntax(ctx) setTagListColorScheme() objectURL := ctx.Args().Get(0) var err error var pErr *probe.Error var objTagMap map[string]string + var msg tagSetMessage + if objTagMap, err = getTaggingMap(ctx); err != nil { - console.Errorln(err.Error() + ". Key value parsing failed from arguments provided. Please refer to mc " + ctx.Command.FullName() + " --help for details.") - return err + fatalIf(probe.NewError(err), ". Key value parsing failed from arguments provided. Please refer to mc "+ctx.Command.FullName()+" --help for details.") } clnt, pErr := newClient(objectURL) fatalIf(pErr.Trace(objectURL), "Unable to initialize target "+objectURL+".") pErr = clnt.SetObjectTagging(objTagMap) - fatalIf(pErr, "Failed to add tags") + fatalIf(pErr, "Failed to set tags") tagObj, err := getObjTagging(objectURL) - var tMsg tagsetListMessage - tMsg = parseTagAddMessage(tagObj.String(), objectURL, err) - printMsg(tMsg) + msg = parseTagSetMessage(tagObj.String(), objectURL, err) + printMsg(msg) return nil } diff --git a/go.mod b/go.mod index a56d7444c7..87598c955f 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/mattn/go-runewidth v0.0.5 // indirect github.com/minio/cli v1.22.0 github.com/minio/minio v0.0.0-20200312144740-ed4bd20a7cfc - github.com/minio/minio-go/v6 v6.0.50-0.20200306231101-b882ba63d570 + github.com/minio/minio-go/v6 v6.0.51-0.20200319161535-d19a75581dbd github.com/minio/sha256-simd v0.1.1 github.com/mitchellh/go-homedir v1.1.0 github.com/pkg/profile v1.3.0 diff --git a/go.sum b/go.sum index 35f0c837bc..db59b7e3a0 100644 --- a/go.sum +++ b/go.sum @@ -256,6 +256,8 @@ github.com/minio/minio v0.0.0-20200312144740-ed4bd20a7cfc/go.mod h1:QbCnTGb/blyN github.com/minio/minio-go/v6 v6.0.45/go.mod h1:qD0lajrGW49lKZLtXKtCB4X/qkMf0a5tBvN2PaZg7Gg= github.com/minio/minio-go/v6 v6.0.50-0.20200306231101-b882ba63d570 h1:GLTZoRC6rhCTucnkJAQ63LhMU2S4CM71MRc9gfX7ohE= github.com/minio/minio-go/v6 v6.0.50-0.20200306231101-b882ba63d570/go.mod h1:qD0lajrGW49lKZLtXKtCB4X/qkMf0a5tBvN2PaZg7Gg= +github.com/minio/minio-go/v6 v6.0.51-0.20200319161535-d19a75581dbd h1:4c/KLe2C0hGXL+AkQu1RW1WQnQMt1+ovHUe3CzLFYts= +github.com/minio/minio-go/v6 v6.0.51-0.20200319161535-d19a75581dbd/go.mod h1:qD0lajrGW49lKZLtXKtCB4X/qkMf0a5tBvN2PaZg7Gg= github.com/minio/parquet-go v0.0.0-20200125064549-a1e49702e174 h1:WYFHZIJ5LTWd4C3CW26jguaBLLDdX7l1/Xa3QSKGkIc= 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=