github.com/minio/mc@v0.0.0-20240507152021-646712d5e5fb/cmd/tag-remove.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 tagRemoveFlags = []cli.Flag{ 33 cli.StringFlag{ 34 Name: "version-id, vid", 35 Usage: "remove tags on a specific object version", 36 }, 37 cli.StringFlag{ 38 Name: "rewind", 39 Usage: "remove tags on an object version at specified time", 40 }, 41 cli.BoolFlag{ 42 Name: "versions", 43 Usage: "remove tags on multiple versions of an object", 44 }, 45 cli.BoolFlag{ 46 Name: "recursive, r", 47 Usage: "recursivley remove tags for all objects", 48 }, 49 } 50 51 var tagRemoveCmd = cli.Command{ 52 Name: "remove", 53 Usage: "remove tags assigned to a bucket or an object", 54 Action: mainRemoveTag, 55 OnUsageError: onUsageError, 56 Before: setGlobalsFromContext, 57 Flags: append(tagRemoveFlags, globalFlags...), 58 CustomHelpTemplate: `NAME: 59 {{.HelpName}} - {{.Usage}} 60 61 USAGE: 62 {{.HelpName}} [COMMAND FLAGS] TARGET 63 64 FLAGS: 65 {{range .VisibleFlags}}{{.}} 66 {{end}} 67 DESCRIPTION: 68 Remove tags assigned to a bucket or an object. 69 70 EXAMPLES: 71 1. Remove the tags assigned to an object. 72 {{.Prompt}} {{.HelpName}} myminio/testbucket/testobject 73 74 2. Remove the tags assigned to a particular version of an object. 75 {{.Prompt}} {{.HelpName}} --version-id "ieQq7aXsyhlhDt47YURGlrucYY3GxWHa" myminio/testbucket/testobject 76 77 3. Remove the tags assigned to an object versions that are older than one week 78 {{.Prompt}} {{.HelpName}} --versions --rewind 7d myminio/testbucket/testobject 79 80 4. Remove the tags assigned to a bucket. 81 {{.Prompt}} {{.HelpName}} play/testbucket 82 83 5. Remove the tags recursively for all the objects of subdirs of bucket. 84 {{.Prompt}} {{.HelpName}} --recursive myminio/testbucket 85 86 6. Remove the tags recursively for all versions of all objects of subdirs of bucket. 87 {{.Prompt}} {{.HelpName}} --recursive --versions myminio/testbucket 88 `, 89 } 90 91 // tagSetTagMessage structure will show message depending on the type of console. 92 type tagRemoveMessage struct { 93 Status string `json:"status"` 94 Name string `json:"name"` 95 VersionID string `json:"versionID"` 96 } 97 98 // tagRemoveMessage console colorized output. 99 func (t tagRemoveMessage) String() string { 100 var msg string 101 msg += "Tags removed for " + t.Name 102 if strings.TrimSpace(t.VersionID) != "" { 103 msg += " (" + t.VersionID + ")" 104 } 105 msg += "." 106 return console.Colorize("Remove", msg) 107 } 108 109 // JSON tagRemoveMessage. 110 func (t tagRemoveMessage) JSON() string { 111 msgBytes, e := json.MarshalIndent(t, "", " ") 112 fatalIf(probe.NewError(e), "Unable to marshal into JSON.") 113 return string(msgBytes) 114 } 115 116 func parseRemoveTagSyntax(ctx *cli.Context) (targetURL, versionID string, timeRef time.Time, withVersions, recursive bool) { 117 if len(ctx.Args()) != 1 { 118 showCommandHelpAndExit(ctx, globalErrorExitStatus) 119 } 120 121 targetURL = ctx.Args().Get(0) 122 versionID = ctx.String("version-id") 123 withVersions = ctx.Bool("versions") 124 rewind := ctx.String("rewind") 125 recursive = ctx.Bool("recursive") 126 127 if versionID != "" && (rewind != "" || withVersions) { 128 fatalIf(errDummy().Trace(), "You cannot specify both --version-id and --rewind or --versions flags at the same time") 129 } 130 131 timeRef = parseRewindFlag(rewind) 132 return 133 } 134 135 // Delete tags of a bucket or a specified object/version 136 func deleteTags(ctx context.Context, clnt Client, versionID string) { 137 targetName := clnt.GetURL().String() 138 if versionID != "" { 139 targetName += " (" + versionID + ")" 140 } 141 142 err := clnt.DeleteTags(ctx, versionID) 143 if err != nil { 144 fatalIf(err, "Unable to remove tags for "+targetName) 145 return 146 } 147 148 printMsg(tagRemoveMessage{ 149 Status: "success", 150 Name: clnt.GetURL().String(), 151 VersionID: versionID, 152 }) 153 } 154 155 func deleteTagsSingle(ctx context.Context, alias, url, versionID string) *probe.Error { 156 newClnt, err := newClientFromAlias(alias, url) 157 if err != nil { 158 return err 159 } 160 161 deleteTags(ctx, newClnt, versionID) 162 return nil 163 } 164 165 func mainRemoveTag(cliCtx *cli.Context) error { 166 ctx, cancelList := context.WithCancel(globalContext) 167 defer cancelList() 168 169 console.SetColor("Remove", color.New(color.FgGreen)) 170 171 targetURL, versionID, timeRef, withVersions, recursive := parseRemoveTagSyntax(cliCtx) 172 if timeRef.IsZero() && withVersions { 173 timeRef = time.Now().UTC() 174 } 175 176 clnt, pErr := newClient(targetURL) 177 fatalIf(pErr, "Unable to initialize target "+targetURL) 178 179 alias, urlStr, _ := mustExpandAlias(targetURL) 180 if timeRef.IsZero() && !withVersions && !recursive { 181 err := deleteTagsSingle(ctx, alias, urlStr, versionID) 182 fatalIf(err.Trace(), "Unable to remove tags on `%s`", targetURL) 183 return nil 184 } 185 for content := range clnt.List(ctx, ListOptions{TimeRef: timeRef, WithOlderVersions: withVersions, Recursive: recursive}) { 186 if content.Err != nil { 187 fatalIf(content.Err.Trace(), "Unable to list target "+targetURL) 188 } 189 190 // Skip if its delete marker 191 if content.IsDeleteMarker { 192 continue 193 } 194 195 if !recursive && alias+getKey(content) != getStandardizedURL(targetURL) { 196 break 197 } 198 199 err := deleteTagsSingle(ctx, alias, content.URL.String(), content.VersionID) 200 if err != nil { 201 errorIf(err.Trace(clnt.GetURL().String()), "Invalid URL") 202 continue 203 } 204 } 205 return nil 206 }