github.com/minio/mc@v0.0.0-20240503112107-b471de8d1882/cmd/tag-set.go (about)

     1  // Copyright (c) 2015-2022 MinIO, Inc.
     2  //
     3  // This file is part of MinIO Object Storage stack
     4  //
     5  // This program is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU Affero General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  //
    10  // This program is distributed in the hope that it will be useful
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13  // GNU Affero General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Affero General Public License
    16  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17  
    18  package cmd
    19  
    20  import (
    21  	"context"
    22  	"strings"
    23  	"time"
    24  
    25  	"github.com/fatih/color"
    26  	"github.com/minio/cli"
    27  	json "github.com/minio/colorjson"
    28  	"github.com/minio/mc/pkg/probe"
    29  	"github.com/minio/pkg/v2/console"
    30  )
    31  
    32  var tagSetFlags = []cli.Flag{
    33  	cli.StringFlag{
    34  		Name:  "version-id, vid",
    35  		Usage: "set tags on a specific object version",
    36  	},
    37  	cli.StringFlag{
    38  		Name:  "rewind",
    39  		Usage: "set tags on a specific object version at specific time",
    40  	},
    41  	cli.BoolFlag{
    42  		Name:  "versions",
    43  		Usage: "set tags on multiple versions for an object",
    44  	},
    45  	cli.BoolFlag{
    46  		Name:  "recursive, r",
    47  		Usage: "recursivley set tags for all objects of subdirs",
    48  	},
    49  	cli.BoolFlag{
    50  		Name:  "exclude-folders",
    51  		Usage: "exclude setting tags on folder objects",
    52  	},
    53  }
    54  
    55  var tagSetCmd = cli.Command{
    56  	Name: "set", Usage: "set tags for a bucket and object(s)",
    57  	Action:       mainSetTag,
    58  	OnUsageError: onUsageError,
    59  	Before:       setGlobalsFromContext,
    60  	Flags:        append(tagSetFlags, globalFlags...),
    61  	CustomHelpTemplate: `NAME:
    62    {{.HelpName}} - {{.Usage}}
    63  
    64  USAGE:
    65    {{.HelpName}} [COMMAND FLAGS] TARGET TAGS
    66  
    67  FLAGS:
    68    {{range .VisibleFlags}}{{.}}
    69    {{end}}
    70  DESCRIPTION:
    71     Assign tags to a bucket or an object.
    72  
    73  EXAMPLES:
    74    1. Assign tags to an object.
    75       {{.Prompt}} {{.HelpName}} play/testbucket/testobject "key1=value1&key2=value2&key3=value3"
    76  
    77    2. Assign tags to a particuler version of an object.
    78       {{.Prompt}} {{.HelpName}} --version-id "ieQq7aXsyhlhDt47YURGlrucYY3GxWHa" play/testbucket/testobject "key1=value1&key2=value2&key3=value3"
    79  
    80    3. Assign tags to a object versions older than one week.
    81       {{.Prompt}} {{.HelpName}} --versions --rewind 7d play/testbucket/testobject "key1=value1&key2=value2&key3=value3"
    82  
    83    4. Assign tags to a bucket.
    84       {{.Prompt}} {{.HelpName}} myminio/testbucket "key1=value1&key2=value2&key3=value3"
    85  
    86    5. Assign tags recursively to all the objects of subdirs of bucket.
    87       {{.Prompt}} {{.HelpName}} myminio/testbucket --recursive "key1=value1&key2=value2&key3=value3"
    88  
    89    6. Assign tags recursively to all versions of all objects of subdirs of bucket.
    90       {{.Prompt}} {{.HelpName}} myminio/testbucket --recursive --versions "key1=value1&key2=value2&key3=value3"
    91  
    92    7. Assign tags to all the objects on a bucket, excluding folders
    93       {{.Prompt}} {{.HelpName}} myminio/testbucket --exclude-folders --recursive "key1=value1&key2=value2&key3=value3"
    94  `,
    95  }
    96  
    97  // tagSetTagMessage structure will show message depending on the type of console.
    98  type tagSetMessage struct {
    99  	Status    string `json:"status"`
   100  	Name      string `json:"name"`
   101  	VersionID string `json:"versionID"`
   102  }
   103  
   104  // tagSetMessage console colorized output.
   105  func (t tagSetMessage) String() string {
   106  	var msg string
   107  	msg += "Tags set for " + t.Name
   108  	if t.VersionID != "" {
   109  		msg += " (" + t.VersionID + ")"
   110  	}
   111  	msg += "."
   112  	return console.Colorize("List", msg)
   113  }
   114  
   115  // JSON tagSetMessage.
   116  func (t tagSetMessage) JSON() string {
   117  	msgBytes, e := json.MarshalIndent(t, "", " ")
   118  	fatalIf(probe.NewError(e), "Unable to marshal into JSON.")
   119  	return string(msgBytes)
   120  }
   121  
   122  func parseSetTagSyntax(ctx *cli.Context) (targetURL, versionID string, timeRef time.Time, withVersions bool, tags string, recursive bool, excludeFolders bool) {
   123  	if len(ctx.Args()) != 2 || ctx.Args().Get(1) == "" {
   124  		showCommandHelpAndExit(ctx, globalErrorExitStatus)
   125  	}
   126  
   127  	targetURL = ctx.Args().Get(0)
   128  	tags = ctx.Args().Get(1)
   129  	versionID = ctx.String("version-id")
   130  	withVersions = ctx.Bool("versions")
   131  	rewind := ctx.String("rewind")
   132  	recursive = ctx.Bool("recursive")
   133  	excludeFolders = ctx.Bool("exclude-folders")
   134  
   135  	if versionID != "" && (rewind != "" || withVersions) {
   136  		fatalIf(errDummy().Trace(), "You cannot specify both --version-id and --rewind or --versions flags at the same time")
   137  	}
   138  
   139  	if excludeFolders && !recursive {
   140  		fatalIf(errDummy().Trace(), "'--exclude-folders' must be used with --recursive only")
   141  	}
   142  
   143  	timeRef = parseRewindFlag(rewind)
   144  	return
   145  }
   146  
   147  // Set tags to a bucket or to a specified object/version
   148  func setTags(ctx context.Context, clnt Client, versionID, tags string) {
   149  	targetName := clnt.GetURL().String()
   150  	if versionID != "" {
   151  		targetName += " (" + versionID + ")"
   152  	}
   153  
   154  	err := clnt.SetTags(ctx, versionID, tags)
   155  	if err != nil {
   156  		fatalIf(err.Trace(tags), "Failed to set tags for "+targetName)
   157  		return
   158  	}
   159  	printMsg(tagSetMessage{
   160  		Status:    "success",
   161  		Name:      clnt.GetURL().String(),
   162  		VersionID: versionID,
   163  	})
   164  }
   165  
   166  func setTagsSingle(ctx context.Context, alias, url, versionID, tags string) *probe.Error {
   167  	newClnt, err := newClientFromAlias(alias, url)
   168  	if err != nil {
   169  		return err
   170  	}
   171  
   172  	setTags(ctx, newClnt, versionID, tags)
   173  	return nil
   174  }
   175  
   176  func mainSetTag(cliCtx *cli.Context) error {
   177  	ctx, cancelSetTag := context.WithCancel(globalContext)
   178  	defer cancelSetTag()
   179  
   180  	console.SetColor("List", color.New(color.FgGreen))
   181  
   182  	targetURL, versionID, timeRef, withVersions, tags, recursive, excludeFolders := parseSetTagSyntax(cliCtx)
   183  	if timeRef.IsZero() && withVersions {
   184  		timeRef = time.Now().UTC()
   185  	}
   186  
   187  	clnt, err := newClient(targetURL)
   188  	fatalIf(err.Trace(cliCtx.Args()...), "Unable to initialize target "+targetURL)
   189  
   190  	alias, urlStr, _ := mustExpandAlias(targetURL)
   191  	if timeRef.IsZero() && !withVersions && !recursive && !excludeFolders {
   192  		err := setTagsSingle(ctx, alias, urlStr, versionID, tags)
   193  		fatalIf(err.Trace(), "Unable to set tags on `%s`", targetURL)
   194  		return nil
   195  	}
   196  	for content := range clnt.List(ctx, ListOptions{TimeRef: timeRef, WithOlderVersions: withVersions, Recursive: recursive}) {
   197  		if content.Err != nil {
   198  			fatalIf(content.Err.Trace(), "Unable to list target "+targetURL)
   199  			continue
   200  		}
   201  
   202  		// Dont set tag for the delete marker
   203  		if content.IsDeleteMarker {
   204  			continue
   205  		}
   206  
   207  		// if excludeFolders dont set tags for subdirs
   208  		_, objName := url2BucketAndObject(&content.URL)
   209  		if strings.Index(objName, string(content.URL.Separator)) > 0 && excludeFolders {
   210  			continue
   211  		}
   212  
   213  		if !recursive && alias+getKey(content) != getStandardizedURL(targetURL) {
   214  			break
   215  		}
   216  
   217  		err := setTagsSingle(ctx, alias, content.URL.String(), content.VersionID, tags)
   218  		if err != nil {
   219  			errorIf(err.Trace(clnt.GetURL().String()), "Invalid URL")
   220  			continue
   221  		}
   222  	}
   223  
   224  	return nil
   225  }