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 }