diff --git a/cmd/du-main.go b/cmd/du-main.go new file mode 100644 index 00000000..8d61c387 --- /dev/null +++ b/cmd/du-main.go @@ -0,0 +1,174 @@ +/* + * MinIO Client (C) 2019 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cmd + +import ( + "fmt" + "net/url" + "strings" + + humanize "github.com/dustin/go-humanize" + "github.com/fatih/color" + "github.com/minio/cli" + json "github.com/minio/mc/pkg/colorjson" + "github.com/minio/mc/pkg/console" + "github.com/minio/mc/pkg/probe" +) + +// du specific flags. +var ( + duFlags = []cli.Flag{ + cli.IntFlag{ + Name: "depth, d", + Usage: "print the total for a folder prefix only if it is N or fewer levels below the command line argument", + }, + } +) + +// Summarize disk usage. +var duCmd = cli.Command{ + Name: "du", + Usage: "Summarize disk usage folder prefixes recursively.", + Action: mainDu, + Before: setGlobalsFromContext, + Flags: append(append(duFlags, ioFlags...), globalFlags...), + CustomHelpTemplate: `NAME: + {{.HelpName}} - {{.Usage}} + +USAGE: + {{.HelpName}} [FLAGS] TARGET + +FLAGS: + {{range .VisibleFlags}}{{.}} + {{end}} +ENVIRONMENT VARIABLES: + MC_ENCRYPT_KEY: list of comma delimited prefix=secret values + +EXAMPLES: + 1. Summarize disk usage of 'jazz-songs' bucket recursively. + $ {{.HelpName}} s3/jazz-songs + + 2. Summarize disk usage of 'louis' prefix in 'jazz-songs' bucket upto two levels. + $ {{.HelpName}} --depth=2 s3/jazz-songs/louis/ +`, +} + +// Structured message depending on the type of console. +type duMessage struct { + Prefix string `json:"prefix"` + Size string `json:"size"` + Status string `json:"status"` +} + +// Colorized message for console printing. +func (r duMessage) String() string { + return console.Colorize("Du", fmt.Sprintf("%s\t%s", r.Size, r.Prefix)) +} + +// JSON'ified message for scripting. +func (r duMessage) JSON() string { + msgBytes, e := json.MarshalIndent(r, "", " ") + fatalIf(probe.NewError(e), "Unable to marshal into JSON.") + return string(msgBytes) +} + +func du(urlStr string, depth int, encKeyDB map[string][]prefixSSEPair) (int64, error) { + targetAlias, targetURL, _ := mustExpandAlias(urlStr) + if !strings.HasSuffix(targetURL, "/") { + targetURL += "/" + } + + clnt, pErr := newClientFromAlias(targetAlias, targetURL) + if pErr != nil { + errorIf(pErr.Trace(urlStr), "Failed to summarize disk usage `"+urlStr+"`.") + return 0, exitStatus(globalErrorExitStatus) // End of journey. + } + + isRecursive := false + isIncomplete := false + contentCh := clnt.List(isRecursive, isIncomplete, DirFirst) + size := int64(0) + for content := range contentCh { + if content.Err != nil { + errorIf(content.Err.Trace(urlStr), "Failed to find disk usage of `"+urlStr+"` recursively.") + return 0, exitStatus(globalErrorExitStatus) + } + + if content.URL.String() == targetURL { + continue + } + + if content.Type.IsDir() { + depth := depth + if depth > 0 { + depth-- + } + + subDirAlias := content.URL.Path + if targetAlias != "" { + subDirAlias = targetAlias + "/" + content.URL.Path + } + used, err := du(subDirAlias, depth, encKeyDB) + if err != nil { + return 0, err + } + size += used + } else { + size += content.Size + } + } + + if depth != 0 { + u, err := url.Parse(targetURL) + if err != nil { + panic(err) + } + + printMsg(duMessage{ + Prefix: strings.Trim(u.Path, "/"), + Size: humanize.Bytes(uint64(size)), + Status: "success", + }) + } + + return size, nil +} + +// main for du command. +func mainDu(ctx *cli.Context) error { + // Parse encryption keys per command. + encKeyDB, err := getEncKeys(ctx) + fatalIf(err, "Unable to parse encryption keys.") + + // du specific flags. + depth := ctx.Int("depth") + if depth == 0 { + depth = -1 + } + + // Set color. + console.SetColor("Remove", color.New(color.FgGreen, color.Bold)) + + var duErr error + for _, urlStr := range ctx.Args() { + if _, err := du(urlStr, depth, encKeyDB); duErr == nil { + duErr = err + } + } + + return duErr +} diff --git a/cmd/main.go b/cmd/main.go index c44f8bb8..3db3d7cb 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -328,6 +328,7 @@ var appCmds = []cli.Command{ updateCmd, versionCmd, treeCmd, + duCmd, } func registerApp(name string) *cli.App {