github.com/minio/mc@v0.0.0-20240503112107-b471de8d1882/cmd/legalhold-info.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  	"fmt"
    23  	"path/filepath"
    24  	"strings"
    25  	"time"
    26  
    27  	"github.com/fatih/color"
    28  	"github.com/minio/cli"
    29  	json "github.com/minio/colorjson"
    30  	"github.com/minio/mc/pkg/probe"
    31  	"github.com/minio/minio-go/v7"
    32  	"github.com/minio/pkg/v2/console"
    33  )
    34  
    35  var lhInfoFlags = []cli.Flag{
    36  	cli.BoolFlag{
    37  		Name:  "recursive, r",
    38  		Usage: "show legal hold status recursively",
    39  	},
    40  	cli.StringFlag{
    41  		Name:  "version-id, vid",
    42  		Usage: "show legal hold status of a specific object version",
    43  	},
    44  	cli.StringFlag{
    45  		Name:  "rewind",
    46  		Usage: "show legal hold status of an object version at specified time",
    47  	},
    48  	cli.BoolFlag{
    49  		Name:  "versions",
    50  		Usage: "show legal hold status of multiple versions of object(s)",
    51  	},
    52  }
    53  
    54  var legalHoldInfoCmd = cli.Command{
    55  	Name:         "info",
    56  	Usage:        "show legal hold info for object(s)",
    57  	Action:       mainLegalHoldInfo,
    58  	OnUsageError: onUsageError,
    59  	Before:       setGlobalsFromContext,
    60  	Flags:        append(lhInfoFlags, globalFlags...),
    61  	CustomHelpTemplate: `NAME:
    62    {{.HelpName}} - {{.Usage}}
    63  
    64  USAGE:
    65    {{.HelpName}} [FLAGS] TARGET
    66  
    67  FLAGS:
    68    {{range .VisibleFlags}}{{.}}
    69    {{end}}
    70  
    71  EXAMPLES:
    72     1. Show legal hold on a specific object
    73        $ {{.HelpName}} myminio/mybucket/prefix/obj.csv
    74  
    75     2. Show legal hold on a specific object version
    76        $ {{.HelpName}} myminio/mybucket/prefix/obj.csv --version-id "HiMFUTOowG6ylfNi4LKxD3ieHbgfgrvC"
    77  
    78     3. Show object legal hold recursively for all objects at a prefix
    79        $ {{.HelpName}} myminio/mybucket/prefix --recursive
    80  
    81     4. Show object legal hold recursively for all objects versions older than one year
    82        $ {{.HelpName}} myminio/mybucket/prefix --recursive --rewind 365d --versions
    83  `,
    84  }
    85  
    86  // Structured message depending on the type of console.
    87  type legalHoldInfoMessage struct {
    88  	LegalHold minio.LegalHoldStatus `json:"legalhold"`
    89  	URLPath   string                `json:"urlpath"`
    90  	Key       string                `json:"key"`
    91  	VersionID string                `json:"versionID"`
    92  	Status    string                `json:"status"`
    93  	Err       error                 `json:"error,omitempty"`
    94  }
    95  
    96  // Colorized message for console printing.
    97  func (l legalHoldInfoMessage) String() string {
    98  	if l.Err != nil {
    99  		return console.Colorize("LegalHoldMessageFailure", "Unable to get object legal hold status `"+l.Key+"`. "+l.Err.Error())
   100  	}
   101  	var msg string
   102  
   103  	var legalhold string
   104  	switch l.LegalHold {
   105  	case "":
   106  		legalhold = console.Colorize("LegalHoldNotSet", "Not set")
   107  	case minio.LegalHoldEnabled:
   108  		legalhold = console.Colorize("LegalHoldOn", l.LegalHold)
   109  	case minio.LegalHoldDisabled:
   110  		legalhold = console.Colorize("LegalHoldOff", l.LegalHold)
   111  	}
   112  
   113  	msg += "[ " + centerText(legalhold, 8) + " ] "
   114  
   115  	if l.VersionID != "" {
   116  		msg += " " + console.Colorize("LegalHoldVersion", l.VersionID) + " "
   117  	}
   118  
   119  	msg += " "
   120  	msg += l.Key
   121  	return msg
   122  }
   123  
   124  // JSON'ified message for scripting.
   125  func (l legalHoldInfoMessage) JSON() string {
   126  	msgBytes, e := json.MarshalIndent(l, "", " ")
   127  	fatalIf(probe.NewError(e), "Unable to marshal into JSON.")
   128  	return string(msgBytes)
   129  }
   130  
   131  // showLegalHoldInfo - show legalhold for one or many objects within a given prefix, with or without versioning
   132  func showLegalHoldInfo(ctx context.Context, urlStr, versionID string, timeRef time.Time, withOlderVersions, recursive bool) error {
   133  	clnt, err := newClient(urlStr)
   134  	if err != nil {
   135  		fatalIf(err.Trace(), "Unable to parse the provided url.")
   136  	}
   137  
   138  	prefixPath := clnt.GetURL().Path
   139  	prefixPath = filepath.ToSlash(prefixPath)
   140  	if !strings.HasSuffix(prefixPath, "/") {
   141  		prefixPath = prefixPath[:strings.LastIndex(prefixPath, "/")+1]
   142  	}
   143  	prefixPath = strings.TrimPrefix(prefixPath, "./")
   144  
   145  	if !recursive && !withOlderVersions {
   146  		lhold, err := clnt.GetObjectLegalHold(ctx, versionID)
   147  		if err != nil {
   148  			fatalIf(err.Trace(urlStr), "Failed to show legal hold information of `"+urlStr+"`.")
   149  		} else {
   150  			contentURL := filepath.ToSlash(clnt.GetURL().Path)
   151  			key := strings.TrimPrefix(contentURL, prefixPath)
   152  
   153  			printMsg(legalHoldInfoMessage{
   154  				LegalHold: lhold,
   155  				Status:    "success",
   156  				URLPath:   clnt.GetURL().String(),
   157  				Key:       key,
   158  				VersionID: versionID,
   159  			})
   160  		}
   161  		return nil
   162  	}
   163  
   164  	alias, _, _ := mustExpandAlias(urlStr)
   165  	var cErr error
   166  	errorsFound := false
   167  	objectsFound := false
   168  	lstOptions := ListOptions{Recursive: recursive, ShowDir: DirNone}
   169  	if !timeRef.IsZero() {
   170  		lstOptions.WithOlderVersions = withOlderVersions
   171  		lstOptions.TimeRef = timeRef
   172  	}
   173  	for content := range clnt.List(ctx, lstOptions) {
   174  		if content.Err != nil {
   175  			errorIf(content.Err.Trace(clnt.GetURL().String()), "Unable to list folder.")
   176  			cErr = exitStatus(globalErrorExitStatus) // Set the exit status.
   177  			continue
   178  		}
   179  
   180  		if !recursive && alias+getKey(content) != getStandardizedURL(urlStr) {
   181  			break
   182  		}
   183  
   184  		objectsFound = true
   185  		newClnt, perr := newClientFromAlias(alias, content.URL.String())
   186  		if perr != nil {
   187  			errorIf(content.Err.Trace(clnt.GetURL().String()), "Invalid URL")
   188  			continue
   189  		}
   190  		lhold, probeErr := newClnt.GetObjectLegalHold(ctx, content.VersionID)
   191  		if probeErr != nil {
   192  			errorsFound = true
   193  			errorIf(probeErr.Trace(content.URL.Path), "Failed to get legal hold information on `"+content.URL.Path+"`")
   194  		} else {
   195  			if !globalJSON {
   196  
   197  				contentURL := filepath.ToSlash(content.URL.Path)
   198  				key := strings.TrimPrefix(contentURL, prefixPath)
   199  
   200  				printMsg(legalHoldInfoMessage{
   201  					LegalHold: lhold,
   202  					Status:    "success",
   203  					URLPath:   content.URL.String(),
   204  					Key:       key,
   205  					VersionID: content.VersionID,
   206  				})
   207  			}
   208  		}
   209  	}
   210  
   211  	if cErr == nil && !globalJSON {
   212  		switch {
   213  		case errorsFound:
   214  			console.Print(console.Colorize("LegalHoldPartialFailure", fmt.Sprintf("Errors found while getting legal hold status on objects with prefix `%s`. \n", urlStr)))
   215  		case !objectsFound:
   216  			console.Print(console.Colorize("LegalHoldMessageFailure", fmt.Sprintf("No objects/versions found while getting legal hold status with prefix `%s`. \n", urlStr)))
   217  		}
   218  	}
   219  	return cErr
   220  }
   221  
   222  // main for legalhold info command.
   223  func mainLegalHoldInfo(cliCtx *cli.Context) error {
   224  	console.SetColor("LegalHoldSuccess", color.New(color.FgGreen, color.Bold))
   225  	console.SetColor("LegalHoldNotSet", color.New(color.FgYellow))
   226  	console.SetColor("LegalHoldOn", color.New(color.FgGreen, color.Bold))
   227  	console.SetColor("LegalHoldOff", color.New(color.FgRed, color.Bold))
   228  	console.SetColor("LegalHoldVersion", color.New(color.FgGreen))
   229  	console.SetColor("LegalHoldPartialFailure", color.New(color.FgRed, color.Bold))
   230  	console.SetColor("LegalHoldMessageFailure", color.New(color.FgYellow))
   231  
   232  	targetURL, versionID, timeRef, recursive, withVersions := parseLegalHoldArgs(cliCtx)
   233  	if timeRef.IsZero() && withVersions {
   234  		timeRef = time.Now().UTC()
   235  	}
   236  
   237  	ctx, cancelLegalHold := context.WithCancel(globalContext)
   238  	defer cancelLegalHold()
   239  
   240  	enabled, err := isBucketLockEnabled(ctx, targetURL)
   241  	if err != nil {
   242  		fatalIf(err, "Unable to get legalhold info of `%s`", targetURL)
   243  	}
   244  	if !enabled {
   245  		fatalIf(errDummy().Trace(), "Bucket lock needs to be enabled in order to use this feature.")
   246  	}
   247  
   248  	return showLegalHoldInfo(ctx, targetURL, versionID, timeRef, withVersions, recursive)
   249  }