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 }