Skip to content

Commit

Permalink
mc tag command
Browse files Browse the repository at this point in the history
  • Loading branch information
BigUstad committed Mar 18, 2020
2 parents 577f712 + fc04dc6 commit c1207f7
Show file tree
Hide file tree
Showing 14 changed files with 576 additions and 20 deletions.
20 changes: 17 additions & 3 deletions cmd/admin-heal.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,11 +150,24 @@ type backgroundHealStatusMessage struct {

// String colorized to show background heal status message.
func (s backgroundHealStatusMessage) String() string {
dot := console.Colorize("Dot", " ● ")

healPrettyMsg := console.Colorize("HealBackgroundTitle", "Background healing status:\n")
healPrettyMsg += fmt.Sprintf(" Total items scanned: %s\n",
healPrettyMsg += dot + fmt.Sprintf("%s item(s) scanned in total\n",
console.Colorize("HealBackground", s.HealInfo.ScannedItemsCount))
healPrettyMsg += fmt.Sprintf(" Last background heal check: %s\n",
console.Colorize("HealBackground", timeDurationToHumanizedDuration(time.Since(s.HealInfo.LastHealActivity)).String()+" ago"))

lastHealingTime := dot + "Never executed"
if !s.HealInfo.LastHealActivity.IsZero() {
lastHealingTime = dot + "Completed " + timeDurationToHumanizedDuration(time.Since(s.HealInfo.LastHealActivity)).StringShort() + " ago"
}
healPrettyMsg += console.Colorize("HealBackground", lastHealingTime) + "\n"

now := time.Now()
if !s.HealInfo.NextHealRound.IsZero() && s.HealInfo.NextHealRound.After(now) {
nextHealingRound := timeDurationToHumanizedDuration(s.HealInfo.NextHealRound.Sub(now)).StringShort()
healPrettyMsg += dot + fmt.Sprintf("Next scheduled in %s\n", console.Colorize("HealBackground", nextHealingRound))
}

return healPrettyMsg
}

Expand Down Expand Up @@ -185,6 +198,7 @@ func mainAdminHeal(ctx *cli.Context) error {
aliasedURL := args.Get(0)

console.SetColor("Heal", color.New(color.FgGreen, color.Bold))
console.SetColor("Dot", color.New(color.FgGreen, color.Bold))
console.SetColor("HealBackgroundTitle", color.New(color.FgGreen, color.Bold))
console.SetColor("HealBackground", color.New(color.Bold))
console.SetColor("HealUpdateUI", color.New(color.FgYellow, color.Bold))
Expand Down
14 changes: 9 additions & 5 deletions cmd/admin-info.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"fmt"
"reflect"
"strconv"
"strings"
"time"

humanize "github.com/dustin/go-humanize"
Expand Down Expand Up @@ -79,6 +80,7 @@ func (u clusterStruct) String() (msg string) {
if u.Status == "error" {
fatal(probe.NewError(errors.New(u.Error)), "Cannot get service status")
}

// If nothing has been collected, error out
if u.Info.Servers == nil {
fatal(probe.NewError(errors.New("Cannot get service status")), "")
Expand Down Expand Up @@ -168,27 +170,29 @@ func (u clusterStruct) String() (msg string) {
msg += fmt.Sprintf(" Drives: %s %s\n", dispNoOfDisks, console.Colorize("Info", "OK "))

}
if u.Info.Buckets.Count != 0 {
msg += "\n"
}

msg += "\n"
}

// Summary on used space, total no of buckets and
// total no of objects at the Cluster level
usedTotal := humanize.IBytes(uint64(u.Info.Usage.Size))
if u.Info.Buckets.Count > 0 {
msg += fmt.Sprintf("%s Used, %s, %s", usedTotal,
msg += fmt.Sprintf("%s Used, %s, %s\n", usedTotal,
english.Plural(int(u.Info.Buckets.Count), "Bucket", ""),
english.Plural(int(u.Info.Objects.Count), "Object", ""))
}
if backendType != "FS" {
// Summary on total no of online and total
// number of offline disks at the Cluster level
msg += fmt.Sprintf("\n%s online, %s offline",
msg += fmt.Sprintf("%s online, %s offline\n",
english.Plural(totalOnlineDisksCluster, "drive", ""),
english.Plural(totalOfflineDisksCluster, "drive", ""))
}

// Remove the last new line if any
// since this is a String() function
msg = strings.TrimSuffix(msg, "\n")
return
}

Expand Down
22 changes: 20 additions & 2 deletions cmd/client-fs.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import (
"github.com/minio/mc/pkg/probe"
minio "github.com/minio/minio-go/v6"
"github.com/minio/minio-go/v6/pkg/encrypt"
"github.com/minio/minio/pkg/bucket/object/tagging"
)

// filesystem client
Expand Down Expand Up @@ -1090,9 +1091,26 @@ func (f *fsClient) fsStat(isIncomplete bool) (os.FileInfo, *probe.Error) {
func (f *fsClient) AddUserAgent(_, _ string) {
}

func (f *fsClient) DeleteObjectTag() *probe.Error {
// Get Object Tags
func (f *fsClient) GetObjectTagging() (tagging.Tagging, *probe.Error) {
return tagging.Tagging{}, probe.NewError(APINotImplemented{
API: "GetObjectTagging",
APIType: "filesystem",
})
}

// Set Object tags
func (f *fsClient) SetObjectTagging(tagMap map[string]string) *probe.Error {
return probe.NewError(APINotImplemented{
API: "SetObjectLockConfig",
API: "SetObjectTagging",
APIType: "filesystem",
})
}

// Delete object tags
func (f *fsClient) DeleteObjectTagging() *probe.Error {
return probe.NewError(APINotImplemented{
API: "DeleteObjectTagging",
APIType: "filesystem",
})
}
43 changes: 42 additions & 1 deletion cmd/client-s3.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"context"
"crypto/tls"
"encoding/json"
"encoding/xml"
"errors"
"hash/fnv"
"io"
Expand All @@ -42,6 +43,7 @@ import (
"github.com/minio/minio-go/v6/pkg/encrypt"
"github.com/minio/minio-go/v6/pkg/policy"
"github.com/minio/minio-go/v6/pkg/s3utils"
"github.com/minio/minio/pkg/bucket/object/tagging"
"github.com/minio/minio/pkg/mimedb"
)

Expand Down Expand Up @@ -2101,7 +2103,46 @@ func (c *s3Client) GetObjectLockConfig() (mode *minio.RetentionMode, validity *u
return mode, validity, unit, nil
}

func (c *s3Client) DeleteObjectTag(bucket, object string) *probe.Error {
// Get Object Tags
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)
}
tagXML, err := c.api.GetObjectTagging(bucketName, objectName)
if err != nil {
return tagging.Tagging{}, probe.NewError(err)
}
var tagObj tagging.Tagging
if err = xml.Unmarshal([]byte(tagXML), &tagObj); err != nil {
return tagging.Tagging{}, probe.NewError(err)
}
return tagObj, nil
}

// Set Object tags
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)
}
if err = c.api.PutObjectTagging(bucket, object, tagMap); err != nil {
return probe.NewError(err)
}
return nil
}

// Delete object tags
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)
}
if err := c.api.RemoveObjectTagging(bucket, object); err != nil {
return probe.NewError(err)
}
Expand Down
6 changes: 6 additions & 0 deletions cmd/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/minio/mc/pkg/probe"
minio "github.com/minio/minio-go/v6"
"github.com/minio/minio-go/v6/pkg/encrypt"
"github.com/minio/minio/pkg/bucket/object/tagging"
)

// DirOpt - list directory option.
Expand Down Expand Up @@ -84,6 +85,11 @@ type Client interface {
GetURL() clientURL

AddUserAgent(app, version string)

// Object Tag operations
GetObjectTagging() (tagging.Tagging, *probe.Error)
SetObjectTagging(tagMap map[string]string) *probe.Error
DeleteObjectTagging() *probe.Error
}

// Content container for content metadata
Expand Down
5 changes: 5 additions & 0 deletions cmd/cp-url.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,11 @@ func makeCopyContentTypeC(sourceAlias string, sourceURL clientURL, sourceContent
newSourceSuffix := filepath.ToSlash(newSourceURL.Path)
if pathSeparatorIndex > 1 {
sourcePrefix := filepath.ToSlash(sourceURL.Path[:pathSeparatorIndex])
// do not preserve unix cp behavior when copying from filesytem to
// objectstore.
if sourceAlias == "" && targetAlias != "" {
sourcePrefix = sourceURL.Path
}
newSourceSuffix = strings.TrimPrefix(newSourceSuffix, sourcePrefix)
}
newTargetURL := urlJoinPath(targetURL, newSourceSuffix)
Expand Down
13 changes: 9 additions & 4 deletions cmd/humanized-duration.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,18 @@ type humanizedDuration struct {
// StringShort() humanizes humanizedDuration to human readable short format.
// This does not print at seconds.
func (r humanizedDuration) StringShort() string {
if r.Days == 0 && r.Hours == 0 {
switch {
case r.Days == 0 && r.Hours == 0 && r.Minutes == 0:
return fmt.Sprintf("%d seconds", r.Seconds)
case r.Days == 0 && r.Hours == 0:
return fmt.Sprintf("%d minutes", r.Minutes)
}
if r.Days == 0 {
case r.Days == 0:
return fmt.Sprintf("%d hours %d minutes", r.Hours, r.Minutes)
case r.Days <= 2:
return fmt.Sprintf("%d days, %d hours", r.Days, r.Hours)
default:
return fmt.Sprintf("%d days", r.Days)
}
return fmt.Sprintf("%d days %d hours %d minutes", r.Days, r.Hours, r.Minutes)
}

// String() humanizes humanizedDuration to human readable,
Expand Down
123 changes: 123 additions & 0 deletions cmd/tag-add.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/*
* MinIO Client (C) 2020 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package cmd

import (
"errors"
"os"
"strconv"
"strings"

"github.com/minio/cli"
"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,
Before: setGlobalsFromContext,
Flags: append(tagAddFlags, globalFlags...),
CustomHelpTemplate: `Name:
{{.HelpName}} - {{.Usage}}
USAGE:
{{.HelpName}} [COMMAND FLAGS] TARGET
FLAGS:
{{range .VisibleFlags}}{{.}}
{{end}}
DESCRIPTION:
Assign object tags (key,value) to target.
EXAMPLES:
1. Assign the tags to an existing object.
{{.Prompt}} {{.HelpName}} s3/testbucket/testobject --tags "key1=value1&key2=value2&key3=value3"
`,
}

var tagAddFlags = []cli.Flag{
cli.StringFlag{
Name: "tags",
Usage: "format '<key1>=<value1>&<key2>=<value2>'; <key1>=<value1> is a key value pair, different key value pairs are separated by '&'",
},
}

func checkAddTagSyntax(ctx *cli.Context) {
tagValues := ctx.String("tags")
if len(ctx.Args()) != 1 || len(tagValues) == 0 {
cli.ShowCommandHelp(ctx, "add")
os.Exit(globalErrorExitStatus)
}
}

func getTaggingMap(ctx *cli.Context) (map[string]string, error) {
tagKVMap := make(map[string]string)
tagValues := strings.Split(ctx.String("tags"), "&")
for tagIdx, tag := range tagValues {
var key, val string
if !strings.Contains(tag, "=") {
key = tag
val = ""
} else {
key = splitStr(tag, "=", 2)[0]
val = splitStr(tag, "=", 2)[1]
}
if key != "" {
tagKVMap[key] = val
} else {
return nil, errors.New("error extracting tag argument(#" + strconv.Itoa(tagIdx+1) + ") " + tag)
}
}
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)
setTagListColorScheme()
objectURL := ctx.Args().Get(0)
var err error
var pErr *probe.Error
var objTagMap map[string]string
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
}
clnt, pErr := newClient(objectURL)
fatalIf(pErr.Trace(objectURL), "Unable to initialize target "+objectURL+".")
pErr = clnt.SetObjectTagging(objTagMap)
fatalIf(pErr, "Failed to add tags")
tagObj, err := getObjTagging(objectURL)
var tMsg tagsetListMessage
tMsg = parseTagAddMessage(tagObj.String(), objectURL, err)
printMsg(tMsg)

return nil
}
Loading

0 comments on commit c1207f7

Please sign in to comment.