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