1
0
mirror of https://github.com/minio/mc.git synced 2025-11-12 01:02:26 +03:00
Files
mc/cmd/rb-main.go

250 lines
6.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 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 (
"encoding/json"
"fmt"
"path/filepath"
"strings"
"github.com/fatih/color"
"github.com/minio/cli"
"github.com/minio/mc/pkg/console"
"github.com/minio/mc/pkg/probe"
)
var (
rbFlags = []cli.Flag{
cli.BoolFlag{
Name: "force",
Usage: "allow a recursive remove operation",
},
cli.BoolFlag{
Name: "dangerous",
Usage: "allow site-wide removal of objects",
},
}
)
// remove a bucket.
var rbCmd = cli.Command{
Name: "rb",
Usage: "remove a bucket",
Action: mainRemoveBucket,
Before: setGlobalsFromContext,
Flags: append(rbFlags, globalFlags...),
CustomHelpTemplate: `NAME:
{{.HelpName}} - {{.Usage}}
USAGE:
{{.HelpName}} [FLAGS] TARGET [TARGET...]
{{if .VisibleFlags}}
FLAGS:
{{range .VisibleFlags}}{{.}}
{{end}}{{end}}
EXAMPLES:
1. Remove an empty bucket on Amazon S3 cloud storage
$ {{.HelpName}} s3/mybucket
2. Remove a directory hierarchy.
$ {{.HelpName}} /tmp/this/new/dir1
3. Remove bucket 'jazz-songs' and all its contents
$ {{.HelpName}} --force s3/jazz-songs
4. Remove all buckets and objects recursively from S3 host
$ {{.HelpName}} --force --dangerous s3
`,
}
// removeBucketMessage is container for delete bucket success and failure messages.
type removeBucketMessage struct {
Status string `json:"status"`
Bucket string `json:"bucket"`
}
// String colorized delete bucket message.
func (s removeBucketMessage) String() string {
return console.Colorize("RemoveBucket", fmt.Sprintf("Removed `%s` successfully.", s.Bucket))
}
// JSON jsonified remove bucket message.
func (s removeBucketMessage) JSON() string {
removeBucketJSONBytes, e := json.Marshal(s)
fatalIf(probe.NewError(e), "Unable to marshal into JSON.")
return string(removeBucketJSONBytes)
}
// Validate command line arguments.
func checkRbSyntax(ctx *cli.Context) {
if !ctx.Args().Present() {
exitCode := 1
cli.ShowCommandHelpAndExit(ctx, "rb", exitCode)
}
// Set command flags from context.
isForce := ctx.Bool("force")
isDangerous := ctx.Bool("dangerous")
for _, url := range ctx.Args() {
if isNamespaceRemoval(url) {
if isForce && isDangerous {
continue
}
fatalIf(errDummy().Trace(),
"This operation results in **site-wide** removal of buckets. If you are really sure, retry this command with --force and --dangerous flags.")
}
}
}
// deletes a bucket and all its contents
func deleteBucket(url string) *probe.Error {
targetAlias, targetURL, _ := mustExpandAlias(url)
clnt, pErr := newClientFromAlias(targetAlias, targetURL)
if pErr != nil {
return pErr
}
var isIncomplete bool
isRemoveBucket := true
contentCh := make(chan *clientContent)
errorCh := clnt.Remove(isIncomplete, isRemoveBucket, contentCh)
for content := range clnt.List(true, false, DirLast) {
if content.Err != nil {
switch content.Err.ToGoError().(type) {
case PathInsufficientPermission:
errorIf(content.Err.Trace(url), "Failed to remove `"+url+"`.")
// Ignore Permission error.
continue
}
close(contentCh)
return content.Err
}
urlString := content.URL.Path
sent := false
for !sent {
select {
case contentCh <- content:
sent = true
case pErr := <-errorCh:
switch pErr.ToGoError().(type) {
case PathInsufficientPermission:
errorIf(pErr.Trace(urlString), "Failed to remove `"+urlString+"`.")
// Ignore Permission error.
continue
}
close(contentCh)
return pErr
}
}
// list internally mimics recursive directory listing of object prefixes for s3 similar to FS.
// The rmMessage needs to be printed only for actual buckets being deleted and not objects.
tgt := strings.TrimPrefix(urlString, string(filepath.Separator))
if !strings.Contains(tgt, string(filepath.Separator)) && tgt != targetAlias {
printMsg(removeBucketMessage{
Bucket: targetAlias + urlString, Status: "success",
})
}
}
// Remove the given url since the user will always want to remove it.
alias, _ := url2Alias(targetURL)
if alias != "" {
contentCh <- &clientContent{URL: *newClientURL(targetURL)}
}
// Finish removing and print all the remaining errors
close(contentCh)
for pErr := range errorCh {
switch pErr.ToGoError().(type) {
case PathInsufficientPermission:
errorIf(pErr.Trace(url), "Failed to remove `"+url+"`.")
// Ignore Permission error.
continue
}
return pErr
}
return nil
}
// isNamespaceRemoval returns true if alias
// is not qualified by bucket
func isNamespaceRemoval(url string) bool {
// clean path for aliases like s3/.
//Note: UNC path using / works properly in go 1.9.2 even though it breaks the UNC specification.
url = filepath.ToSlash(filepath.Clean(url))
// namespace removal applies only for non FS. So filter out if passed url represents a directory
if !isAliasURLDir(url, nil) {
_, path := url2Alias(url)
return (path == "")
}
return false
}
// mainRemoveBucket is entry point for rb command.
func mainRemoveBucket(ctx *cli.Context) error {
// check 'rb' cli arguments.
checkRbSyntax(ctx)
isForce := ctx.Bool("force")
// Additional command specific theme customization.
console.SetColor("RemoveBucket", color.New(color.FgGreen, color.Bold))
var cErr error
for _, targetURL := range ctx.Args() {
// Instantiate client for URL.
clnt, err := newClient(targetURL)
if err != nil {
errorIf(err.Trace(targetURL), "Invalid target `"+targetURL+"`.")
cErr = exitStatus(globalErrorExitStatus)
continue
}
_, err = clnt.Stat(false, false, nil)
if err != nil {
switch err.ToGoError().(type) {
case BucketNameEmpty:
default:
errorIf(err.Trace(targetURL), "Unable to validate target `"+targetURL+"`.")
cErr = exitStatus(globalErrorExitStatus)
continue
}
}
isEmpty := true
for range clnt.List(true, false, DirNone) {
isEmpty = false
break
}
// For all recursive operations make sure to check for 'force' flag.
if !isForce && !isEmpty {
fatalIf(errDummy().Trace(), "`"+targetURL+"` is not empty. Retry this command with --force flag if you want to remove `"+targetURL+"` and all its contents")
}
e := deleteBucket(targetURL)
fatalIf(e.Trace(targetURL), "Failed to remove `"+targetURL+"`.")
if !isNamespaceRemoval(targetURL) {
printMsg(removeBucketMessage{
Bucket: targetURL, Status: "success",
})
}
}
return cErr
}