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  }