yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/objectstore/shell.go (about)

     1  // Copyright 2019 Yunion
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package objectstore
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"io"
    21  	"mime"
    22  	"net/http"
    23  	"os"
    24  	"path/filepath"
    25  	"strings"
    26  	"time"
    27  
    28  	"yunion.io/x/pkg/errors"
    29  
    30  	"yunion.io/x/cloudmux/pkg/cloudprovider"
    31  	"yunion.io/x/onecloud/pkg/util/fileutils2"
    32  	"yunion.io/x/onecloud/pkg/util/printutils"
    33  	"yunion.io/x/onecloud/pkg/util/shellutils"
    34  	"yunion.io/x/onecloud/pkg/util/streamutils"
    35  )
    36  
    37  type ObjectHeaderOptions struct {
    38  	CacheControl       string `help:"Cache-Control"`
    39  	ContentType        string `help:"Content-Type"`
    40  	ContentEncoding    string `help:"Content-Encoding"`
    41  	ContentLanguage    string `help:"Content-Language"`
    42  	ContentDisposition string `help:"Content-Disposition"`
    43  	ContentMD5         string `help:"Content-MD5"`
    44  
    45  	Meta []string `help:"header, common seperatored key and value, e.g. max-age:100"`
    46  }
    47  
    48  func (args ObjectHeaderOptions) Options2Header() http.Header {
    49  	meta := http.Header{}
    50  	for _, kv := range args.Meta {
    51  		parts := strings.Split(kv, ":")
    52  		if len(parts) == 2 {
    53  			key := strings.TrimSpace(parts[0])
    54  			value := strings.TrimSpace(parts[1])
    55  			if len(key) > 0 && len(value) > 0 {
    56  				meta.Add(key, value)
    57  			}
    58  		}
    59  	}
    60  	if len(args.CacheControl) > 0 {
    61  		meta.Set(cloudprovider.META_HEADER_CACHE_CONTROL, args.CacheControl)
    62  	}
    63  	if len(args.ContentType) > 0 {
    64  		meta.Set(cloudprovider.META_HEADER_CONTENT_TYPE, args.ContentType)
    65  	}
    66  	if len(args.ContentEncoding) > 0 {
    67  		meta.Set(cloudprovider.META_HEADER_CONTENT_ENCODING, args.ContentEncoding)
    68  	}
    69  	if len(args.ContentMD5) > 0 {
    70  		meta.Set(cloudprovider.META_HEADER_CONTENT_MD5, args.ContentMD5)
    71  	}
    72  	if len(args.ContentLanguage) > 0 {
    73  		meta.Set(cloudprovider.META_HEADER_CONTENT_LANGUAGE, args.ContentLanguage)
    74  	}
    75  	if len(args.ContentDisposition) > 0 {
    76  		meta.Set(cloudprovider.META_HEADER_CONTENT_DISPOSITION, args.ContentDisposition)
    77  	}
    78  	return meta
    79  }
    80  
    81  func printList(data interface{}, total, offset, limit int, columns []string) {
    82  	printutils.PrintInterfaceList(data, total, offset, limit, columns)
    83  }
    84  
    85  func printObject(obj interface{}) {
    86  	printutils.PrintInterfaceObject(obj)
    87  }
    88  
    89  func S3Shell() {
    90  	type BucketListOptions struct {
    91  	}
    92  	shellutils.R(&BucketListOptions{}, "bucket-list", "List all bucket", func(cli cloudprovider.ICloudRegion, args *BucketListOptions) error {
    93  		buckets, err := cli.GetIBuckets()
    94  		if err != nil {
    95  			return err
    96  		}
    97  		printutils.PrintGetterList(buckets, nil)
    98  		return nil
    99  	})
   100  
   101  	type BucketCreateOptions struct {
   102  		NAME         string `help:"name of bucket to create"`
   103  		Acl          string `help:"ACL string" choices:"private|public-read|public-read-write"`
   104  		StorageClass string `help:"StorageClass"`
   105  	}
   106  	shellutils.R(&BucketCreateOptions{}, "bucket-create", "Create bucket", func(cli cloudprovider.ICloudRegion, args *BucketCreateOptions) error {
   107  		err := cli.CreateIBucket(args.NAME, args.StorageClass, args.Acl)
   108  		if err != nil {
   109  			return err
   110  		}
   111  		return nil
   112  	})
   113  
   114  	type BucketAclOptions struct {
   115  		BUCKET string `help:"name of bucket"`
   116  		ACL    string `help:"ACL string" choices:"private|public-read|public-read-write"`
   117  	}
   118  	shellutils.R(&BucketAclOptions{}, "bucket-set-acl", "Create bucket", func(cli cloudprovider.ICloudRegion, args *BucketAclOptions) error {
   119  		bucket, err := cli.GetIBucketById(args.BUCKET)
   120  		if err != nil {
   121  			return err
   122  		}
   123  		err = bucket.SetAcl(cloudprovider.TBucketACLType(args.ACL))
   124  		if err != nil {
   125  			return err
   126  		}
   127  		return nil
   128  	})
   129  
   130  	type BucketDeleteOptions struct {
   131  		NAME string `help:"name of bucket to delete"`
   132  	}
   133  	shellutils.R(&BucketDeleteOptions{}, "bucket-delete", "Delete bucket", func(cli cloudprovider.ICloudRegion, args *BucketDeleteOptions) error {
   134  		err := cli.DeleteIBucket(args.NAME)
   135  		if err != nil {
   136  			return err
   137  		}
   138  		return nil
   139  	})
   140  
   141  	type BucketLimitOptions struct {
   142  		NAME    string `help:"name of bucket to set limit"`
   143  		SizeGB  int    `help:"limit of volumes in GB"`
   144  		Objects int    `help:"limit of object count"`
   145  		Off     bool   `help:"Turn off limit"`
   146  	}
   147  	shellutils.R(&BucketLimitOptions{}, "bucket-set-limit", "Set bucket limit", func(cli cloudprovider.ICloudRegion, args *BucketLimitOptions) error {
   148  		bucket, err := cli.GetIBucketByName(args.NAME)
   149  		if err != nil {
   150  			return err
   151  		}
   152  		if args.Off {
   153  			err = bucket.SetLimit(cloudprovider.SBucketStats{})
   154  		} else {
   155  			fmt.Println("set limit")
   156  			err = bucket.SetLimit(cloudprovider.SBucketStats{SizeBytes: int64(args.SizeGB * 1000 * 1000 * 1000), ObjectCount: args.Objects})
   157  		}
   158  		if err != nil {
   159  			return err
   160  		}
   161  		return nil
   162  	})
   163  
   164  	type BucketExistOptions struct {
   165  		NAME string `help:"name of bucket to delete"`
   166  	}
   167  	shellutils.R(&BucketExistOptions{}, "bucket-exist", "Test existence of a bucket", func(cli cloudprovider.ICloudRegion, args *BucketExistOptions) error {
   168  		exist, err := cli.IBucketExist(args.NAME)
   169  		if err != nil {
   170  			return err
   171  		}
   172  		fmt.Printf("Exist: %v\n", exist)
   173  		return nil
   174  	})
   175  
   176  	type BucketObjectsOptions struct {
   177  		BUCKET    string `help:"name of bucket to list objects"`
   178  		Prefix    string `help:"prefix"`
   179  		Marker    string `help:"marker"`
   180  		Demiliter string `help:"delimiter"`
   181  		Max       int    `help:"Max count"`
   182  	}
   183  	shellutils.R(&BucketObjectsOptions{}, "bucket-object", "List objects in a bucket", func(cli cloudprovider.ICloudRegion, args *BucketObjectsOptions) error {
   184  		bucket, err := cli.GetIBucketById(args.BUCKET)
   185  		if err != nil {
   186  			return err
   187  		}
   188  		result, err := bucket.ListObjects(args.Prefix, args.Marker, args.Demiliter, args.Max)
   189  		if err != nil {
   190  			return err
   191  		}
   192  		if result.IsTruncated {
   193  			fmt.Printf("NextMarker: %s IsTruncated: %v\n", result.NextMarker, result.IsTruncated)
   194  		}
   195  		fmt.Println("Common prefixes:")
   196  		printutils.PrintGetterList(result.CommonPrefixes, []string{"key", "size_bytes"})
   197  		fmt.Println("Objects:")
   198  		printutils.PrintGetterList(result.Objects, []string{"key", "size_bytes"})
   199  		return nil
   200  	})
   201  
   202  	type BucketListObjectsOptions struct {
   203  		BUCKET string `help:"name of bucket to list objects"`
   204  		Prefix string `help:"prefix"`
   205  		Limit  int    `help:"limit per page request" default:"20"`
   206  		Marker string `help:"offset marker"`
   207  	}
   208  	shellutils.R(&BucketListObjectsOptions{}, "bucket-list-object", "List objects in a bucket", func(cli cloudprovider.ICloudRegion, args *BucketListObjectsOptions) error {
   209  		bucket, err := cli.GetIBucketById(args.BUCKET)
   210  		if err != nil {
   211  			return err
   212  		}
   213  		objects, marker, err := cloudprovider.GetPagedObjects(bucket, args.Prefix, true, args.Marker, args.Limit)
   214  		if err != nil {
   215  			return err
   216  		}
   217  		printutils.PrintGetterList(objects, []string{"key", "size_bytes"})
   218  		if len(marker) > 0 {
   219  			fmt.Println("Next marker:", marker)
   220  		}
   221  		return nil
   222  	})
   223  
   224  	shellutils.R(&BucketListObjectsOptions{}, "bucket-dir-object", "List objects in a bucket like directory", func(cli cloudprovider.ICloudRegion, args *BucketListObjectsOptions) error {
   225  		bucket, err := cli.GetIBucketById(args.BUCKET)
   226  		if err != nil {
   227  			return err
   228  		}
   229  		objects, marker, err := cloudprovider.GetPagedObjects(bucket, args.Prefix, false, args.Marker, args.Limit)
   230  		if err != nil {
   231  			return err
   232  		}
   233  		printutils.PrintGetterList(objects, []string{"key", "size_bytes"})
   234  		if len(marker) > 0 {
   235  			fmt.Println("Next marker:", marker)
   236  		}
   237  		return nil
   238  	})
   239  
   240  	type BucketMakrdirOptions struct {
   241  		BUCKET string `help:"name of bucket to put object"`
   242  		DIR    string `help:"dir to make"`
   243  	}
   244  	shellutils.R(&BucketMakrdirOptions{}, "bucket-mkdir", "Mkdir in a bucket", func(cli cloudprovider.ICloudRegion, args *BucketMakrdirOptions) error {
   245  		bucket, err := cli.GetIBucketById(args.BUCKET)
   246  		if err != nil {
   247  			return err
   248  		}
   249  		err = cloudprovider.Makedir(context.Background(), bucket, args.DIR)
   250  		if err != nil {
   251  			return err
   252  		}
   253  		fmt.Printf("Mkdir success\n")
   254  		return nil
   255  	})
   256  
   257  	type BucketPutObjectOptions struct {
   258  		BUCKET string `help:"name of bucket to put object"`
   259  		KEY    string `help:"key of object"`
   260  		Path   string `help:"Path of file to upload"`
   261  
   262  		BlockSize int64 `help:"blocksz in MB" default:"100"`
   263  
   264  		Acl string `help:"acl" choices:"private|public-read|public-read-write"`
   265  
   266  		StorageClass string `help:"storage class"`
   267  
   268  		ObjectHeaderOptions
   269  	}
   270  	shellutils.R(&BucketPutObjectOptions{}, "put-object", "Put object into a bucket", func(cli cloudprovider.ICloudRegion, args *BucketPutObjectOptions) error {
   271  		bucket, err := cli.GetIBucketById(args.BUCKET)
   272  		if err != nil {
   273  			return err
   274  		}
   275  
   276  		originMeta := args.ObjectHeaderOptions.Options2Header()
   277  
   278  		if len(args.Path) > 0 {
   279  			uploadFile := func(key, path string) error {
   280  				meta := http.Header{}
   281  				for k, v := range originMeta {
   282  					meta[k] = v
   283  				}
   284  
   285  				finfo, err := os.Stat(path)
   286  				if err != nil {
   287  					return errors.Wrap(err, "os.Stat")
   288  				}
   289  				fSize := finfo.Size()
   290  				file, err := os.Open(path)
   291  				if err != nil {
   292  					return errors.Wrap(err, "os.Open")
   293  				}
   294  				defer file.Close()
   295  
   296  				if contTypes, ok := meta[cloudprovider.META_HEADER_CONTENT_TYPE]; !ok || len(contTypes) == 0 {
   297  					var ext string
   298  					lastSlashPos := strings.LastIndex(path, "/")
   299  					lastExtPos := strings.LastIndex(path, ".")
   300  					if lastExtPos >= 0 && lastExtPos > lastSlashPos {
   301  						ext = path[lastExtPos:]
   302  					}
   303  					if len(ext) > 0 {
   304  						contType := mime.TypeByExtension(ext)
   305  						meta.Set(cloudprovider.META_HEADER_CONTENT_TYPE, contType)
   306  					}
   307  				}
   308  
   309  				err = cloudprovider.UploadObject(context.Background(), bucket, key, args.BlockSize*1000*1000, file, fSize, cloudprovider.TBucketACLType(args.Acl), args.StorageClass, meta, true)
   310  				if err != nil {
   311  					return err
   312  				}
   313  
   314  				return nil
   315  			}
   316  			if fileutils2.IsFile(args.Path) {
   317  				err := uploadFile(args.KEY, args.Path)
   318  				if err != nil {
   319  					return errors.Wrap(err, "uploadFile")
   320  				}
   321  			} else if fileutils2.IsDir(args.Path) {
   322  				return filepath.Walk(args.Path, func(path string, info os.FileInfo, err error) error {
   323  					if err != nil {
   324  						return err
   325  					}
   326  					if info.Mode().IsRegular() {
   327  						rel, _ := filepath.Rel(args.Path, path)
   328  						src := path
   329  						dst := filepath.Join(args.KEY, rel)
   330  						fmt.Println("upload", src, "to", dst)
   331  						uploadErr := uploadFile(dst, src)
   332  						if uploadErr != nil {
   333  							return uploadErr
   334  						}
   335  					}
   336  					return nil
   337  				})
   338  			}
   339  		} else {
   340  			err = cloudprovider.UploadObject(context.Background(), bucket, args.KEY, args.BlockSize*1000*1000, os.Stdin, 0, cloudprovider.TBucketACLType(args.Acl), args.StorageClass, originMeta, true)
   341  			if err != nil {
   342  				return err
   343  			}
   344  		}
   345  
   346  		fmt.Printf("Upload success\n")
   347  		return nil
   348  	})
   349  
   350  	type BucketDeleteObjectOptions struct {
   351  		BUCKET string `help:"name of bucket to put object"`
   352  		KEY    string `help:"key of object"`
   353  	}
   354  	shellutils.R(&BucketDeleteObjectOptions{}, "delete-object", "Delete object from a bucket", func(cli cloudprovider.ICloudRegion, args *BucketDeleteObjectOptions) error {
   355  		bucket, err := cli.GetIBucketById(args.BUCKET)
   356  		if err != nil {
   357  			return err
   358  		}
   359  		err = bucket.DeleteObject(context.Background(), args.KEY)
   360  		if err != nil {
   361  			return err
   362  		}
   363  		fmt.Printf("Delete success\n")
   364  		return nil
   365  	})
   366  
   367  	shellutils.R(&BucketDeleteObjectOptions{}, "delete-prefix", "Delete object from a bucket", func(cli cloudprovider.ICloudRegion, args *BucketDeleteObjectOptions) error {
   368  		bucket, err := cli.GetIBucketById(args.BUCKET)
   369  		if err != nil {
   370  			return err
   371  		}
   372  		err = cloudprovider.DeletePrefix(context.Background(), bucket, args.KEY)
   373  		if err != nil {
   374  			return err
   375  		}
   376  		fmt.Printf("Delete success\n")
   377  		return nil
   378  	})
   379  
   380  	type BucketTempUrlOption struct {
   381  		BUCKET   string `help:"name of bucket to put object"`
   382  		METHOD   string `help:"http method" choices:"GET|PUT|DELETE"`
   383  		KEY      string `help:"key of object"`
   384  		Duration int    `help:"duration in seconds" default:"60"`
   385  	}
   386  	shellutils.R(&BucketTempUrlOption{}, "temp-url", "generate temp url", func(cli cloudprovider.ICloudRegion, args *BucketTempUrlOption) error {
   387  		bucket, err := cli.GetIBucketById(args.BUCKET)
   388  		if err != nil {
   389  			return err
   390  		}
   391  		urlStr, err := bucket.GetTempUrl(args.METHOD, args.KEY, time.Duration(args.Duration)*time.Second)
   392  		if err != nil {
   393  			return err
   394  		}
   395  		fmt.Println(urlStr)
   396  		return nil
   397  	})
   398  
   399  	type BucketAclOption struct {
   400  		BUCKET string `help:"name of bucket to put object"`
   401  		KEY    string `help:"key of object"`
   402  	}
   403  	shellutils.R(&BucketAclOption{}, "object-acl", "Get object acl", func(cli cloudprovider.ICloudRegion, args *BucketAclOption) error {
   404  		bucket, err := cli.GetIBucketById(args.BUCKET)
   405  		if err != nil {
   406  			return err
   407  		}
   408  		object, err := cloudprovider.GetIObject(bucket, args.KEY)
   409  		if err != nil {
   410  			return err
   411  		}
   412  		fmt.Println(object.GetAcl())
   413  		return nil
   414  	})
   415  
   416  	type BucketSetAclOption struct {
   417  		BUCKET string `help:"name of bucket to put object"`
   418  		KEY    string `help:"key of object"`
   419  		ACL    string `help:"Target acl" choices:"default|private|public-read|public-read-write"`
   420  	}
   421  	shellutils.R(&BucketSetAclOption{}, "object-set-acl", "Get object acl", func(cli cloudprovider.ICloudRegion, args *BucketSetAclOption) error {
   422  		bucket, err := cli.GetIBucketById(args.BUCKET)
   423  		if err != nil {
   424  			return err
   425  		}
   426  		object, err := cloudprovider.GetIObject(bucket, args.KEY)
   427  		if err != nil {
   428  			return err
   429  		}
   430  		err = object.SetAcl(cloudprovider.TBucketACLType(args.ACL))
   431  		if err != nil {
   432  			return err
   433  		}
   434  		fmt.Println("Success!")
   435  		return nil
   436  	})
   437  
   438  	type BucketSetWebsiteOption struct {
   439  		BUCKET string `help:"name of bucket to put object"`
   440  		// 主页
   441  		Index string `help:"main page"`
   442  		// 错误时返回的文档
   443  		ErrorDocument string `help:"error return"`
   444  		// http或https
   445  		Protocol string `help:"force https" choices:"http|https"`
   446  	}
   447  	shellutils.R(&BucketSetWebsiteOption{}, "bucket-set-website", "Set bucket website", func(cli cloudprovider.ICloudRegion, args *BucketSetWebsiteOption) error {
   448  		bucket, err := cli.GetIBucketById(args.BUCKET)
   449  		if err != nil {
   450  			return err
   451  		}
   452  		conf := cloudprovider.SBucketWebsiteConf{
   453  			Index:         args.Index,
   454  			ErrorDocument: args.ErrorDocument,
   455  			Protocol:      args.Protocol,
   456  		}
   457  		err = bucket.SetWebsite(conf)
   458  		if err != nil {
   459  			return err
   460  		}
   461  		fmt.Println("Success!")
   462  		return nil
   463  	})
   464  
   465  	type BucketGetWebsiteConfOption struct {
   466  		BUCKET string `help:"name of bucket to put object"`
   467  	}
   468  	shellutils.R(&BucketGetWebsiteConfOption{}, "bucket-get-website", "Get bucket website", func(cli cloudprovider.ICloudRegion, args *BucketGetWebsiteConfOption) error {
   469  		bucket, err := cli.GetIBucketById(args.BUCKET)
   470  		if err != nil {
   471  			return err
   472  		}
   473  		conf, err := bucket.GetWebsiteConf()
   474  		if err != nil {
   475  			return err
   476  		}
   477  		printObject(conf)
   478  		return nil
   479  	})
   480  
   481  	type BucketDeleteWebsiteConfOption struct {
   482  		BUCKET string `help:"name of bucket to put object"`
   483  	}
   484  	shellutils.R(&BucketDeleteWebsiteConfOption{}, "bucket-delete-website", "Delete bucket website", func(cli cloudprovider.ICloudRegion, args *BucketDeleteWebsiteConfOption) error {
   485  		bucket, err := cli.GetIBucketById(args.BUCKET)
   486  		if err != nil {
   487  			return err
   488  		}
   489  		err = bucket.DeleteWebSiteConf()
   490  		if err != nil {
   491  			return err
   492  		}
   493  		fmt.Println("Success!")
   494  		return nil
   495  	})
   496  
   497  	type BucketSetCorsOption struct {
   498  		BUCKET         string `help:"name of bucket to put object"`
   499  		AllowedMethods []string
   500  		// 允许的源站,可以设为*
   501  		AllowedOrigins []string
   502  		AllowedHeaders []string
   503  		MaxAgeSeconds  int
   504  		ExposeHeaders  []string
   505  		Id             string
   506  	}
   507  	shellutils.R(&BucketSetCorsOption{}, "bucket-set-cors", "Set bucket cors", func(cli cloudprovider.ICloudRegion, args *BucketSetCorsOption) error {
   508  		bucket, err := cli.GetIBucketById(args.BUCKET)
   509  		if err != nil {
   510  			return err
   511  		}
   512  		rule := cloudprovider.SBucketCORSRule{
   513  			AllowedOrigins: args.AllowedOrigins,
   514  			AllowedMethods: args.AllowedMethods,
   515  			AllowedHeaders: args.AllowedHeaders,
   516  			MaxAgeSeconds:  args.MaxAgeSeconds,
   517  			ExposeHeaders:  args.ExposeHeaders,
   518  			Id:             args.Id,
   519  		}
   520  		err = cloudprovider.SetBucketCORS(bucket, []cloudprovider.SBucketCORSRule{rule})
   521  		if err != nil {
   522  			return err
   523  		}
   524  		fmt.Println("Success!")
   525  		return nil
   526  	})
   527  
   528  	type BucketGetCorsOption struct {
   529  		BUCKET string `help:"name of bucket to put object"`
   530  	}
   531  	shellutils.R(&BucketGetCorsOption{}, "bucket-get-cors", "Get bucket cors", func(cli cloudprovider.ICloudRegion, args *BucketGetCorsOption) error {
   532  		bucket, err := cli.GetIBucketById(args.BUCKET)
   533  		if err != nil {
   534  			return err
   535  		}
   536  		rules, err := bucket.GetCORSRules()
   537  		if err != nil {
   538  			return err
   539  		}
   540  		printList(rules, len(rules), 0, len(rules), nil)
   541  		return nil
   542  	})
   543  
   544  	type BucketDeleteCorsOption struct {
   545  		BUCKET string   `help:"name of bucket to put object"`
   546  		Ids    []string `help:"rule ids to delete"`
   547  	}
   548  	shellutils.R(&BucketDeleteCorsOption{}, "bucket-delete-cors", "Delete bucket cors", func(cli cloudprovider.ICloudRegion, args *BucketDeleteCorsOption) error {
   549  		bucket, err := cli.GetIBucketById(args.BUCKET)
   550  		if err != nil {
   551  			return err
   552  		}
   553  		result, err := cloudprovider.DeleteBucketCORS(bucket, args.Ids)
   554  		if err != nil {
   555  			return err
   556  		}
   557  		printList(result, len(result), 0, len(result), nil)
   558  		fmt.Println("Success!")
   559  		return nil
   560  	})
   561  
   562  	type BucketSetRefererOption struct {
   563  		BUCKET      string `help:"name of bucket to put object"`
   564  		RefererType string `help:"referer type" choices:"Black-List|White-List" default:"Black-List"`
   565  		DomainList  []string
   566  		// 是否允许空refer 访问
   567  		AllowEmptyRefer bool `help:"all empty refer access"`
   568  		Disable         bool
   569  	}
   570  	shellutils.R(&BucketSetRefererOption{}, "bucket-set-referer", "Set bucket referer", func(cli cloudprovider.ICloudRegion, args *BucketSetRefererOption) error {
   571  		bucket, err := cli.GetIBucketById(args.BUCKET)
   572  		if err != nil {
   573  			return err
   574  		}
   575  		conf := cloudprovider.SBucketRefererConf{
   576  			DomainList:      args.DomainList,
   577  			RefererType:     args.RefererType,
   578  			AllowEmptyRefer: args.AllowEmptyRefer,
   579  			Enabled:         true,
   580  		}
   581  		if args.Disable {
   582  			conf.Enabled = false
   583  		}
   584  		err = bucket.SetReferer(conf)
   585  		if err != nil {
   586  			return err
   587  		}
   588  		fmt.Println("Success!")
   589  		return nil
   590  
   591  	})
   592  
   593  	type BucketGetPolicyOption struct {
   594  		BUCKET string `help:"name of bucket to put object"`
   595  	}
   596  	shellutils.R(&BucketGetPolicyOption{}, "bucket-get-policy", "get bucket policy", func(cli cloudprovider.ICloudRegion, args *BucketGetPolicyOption) error {
   597  		bucket, err := cli.GetIBucketById(args.BUCKET)
   598  		if err != nil {
   599  			return err
   600  		}
   601  		policy, err := bucket.GetPolicy()
   602  		if err != nil {
   603  			return err
   604  		}
   605  		printList(policy, len(policy), 0, len(policy), nil)
   606  		return nil
   607  	})
   608  
   609  	type BucketSetPolicyOption struct {
   610  		BUCKET string `help:"name of bucket to put object"`
   611  		// 格式主账号id:子账号id
   612  		PrincipalId []string
   613  		// Read|ReadWrite|FullControl
   614  		CannedAction string
   615  		// Allow|Deny
   616  		Effect string
   617  		// 被授权的资源地址
   618  		ResourcePath []string
   619  		// ip 条件
   620  		IpEquals    []string
   621  		IpNotEquals []string
   622  	}
   623  	shellutils.R(&BucketSetPolicyOption{}, "bucket-set-policy", "set bucket policy", func(cli cloudprovider.ICloudRegion, args *BucketSetPolicyOption) error {
   624  		bucket, err := cli.GetIBucketById(args.BUCKET)
   625  		if err != nil {
   626  			return err
   627  		}
   628  		opts := cloudprovider.SBucketPolicyStatementInput{}
   629  		opts.CannedAction = args.CannedAction
   630  		opts.Effect = args.Effect
   631  		opts.IpEquals = args.IpEquals
   632  		opts.IpNotEquals = args.IpNotEquals
   633  		opts.ResourcePath = args.ResourcePath
   634  		opts.PrincipalId = args.PrincipalId
   635  
   636  		err = bucket.SetPolicy(opts)
   637  		if err != nil {
   638  			return err
   639  		}
   640  		return nil
   641  	})
   642  
   643  	type BucketDeletePolicyOption struct {
   644  		BUCKET string `help:"name of bucket to put object"`
   645  		Id     []string
   646  	}
   647  	shellutils.R(&BucketDeletePolicyOption{}, "bucket-delete-policy", "delete bucket policy", func(cli cloudprovider.ICloudRegion, args *BucketDeletePolicyOption) error {
   648  		bucket, err := cli.GetIBucketById(args.BUCKET)
   649  		if err != nil {
   650  			return err
   651  		}
   652  		result, err := bucket.DeletePolicy(args.Id)
   653  		if err != nil {
   654  			return err
   655  		}
   656  		printList(result, len(result), 0, len(result), nil)
   657  		return nil
   658  	})
   659  
   660  	type BucketGetRefererOption struct {
   661  		BUCKET string `help:"name of bucket to put object"`
   662  	}
   663  	shellutils.R(&BucketGetRefererOption{}, "bucket-get-referer", "get bucket referer", func(cli cloudprovider.ICloudRegion, args *BucketGetRefererOption) error {
   664  		bucket, err := cli.GetIBucketById(args.BUCKET)
   665  		if err != nil {
   666  			return err
   667  		}
   668  		conf, err := bucket.GetReferer()
   669  		if err != nil {
   670  			return err
   671  		}
   672  		printObject(conf)
   673  		return nil
   674  	})
   675  
   676  	type BucketGetCdnDomainOption struct {
   677  		BUCKET string `help:"name of bucket to put object"`
   678  	}
   679  	shellutils.R(&BucketGetCdnDomainOption{}, "bucket-get-cdn-domains", "get bucket cdn domains", func(cli cloudprovider.ICloudRegion, args *BucketGetCdnDomainOption) error {
   680  		bucket, err := cli.GetIBucketById(args.BUCKET)
   681  		if err != nil {
   682  			return err
   683  		}
   684  		domains, err := bucket.GetCdnDomains()
   685  		if err != nil {
   686  			return err
   687  		}
   688  		printList(domains, len(domains), 0, len(domains), nil)
   689  		return nil
   690  	})
   691  
   692  	type BucketGetMetadata struct {
   693  		BUCKET string `help:"name of bucket to put object"`
   694  	}
   695  	shellutils.R(&BucketGetMetadata{}, "bucket-tag-list", "List bucket tag", func(cli cloudprovider.ICloudRegion, args *BucketGetMetadata) error {
   696  		bucket, err := cli.GetIBucketById(args.BUCKET)
   697  		if err != nil {
   698  			return err
   699  		}
   700  		meta, err := bucket.GetTags()
   701  		if err != nil {
   702  			return err
   703  		}
   704  		printObject(meta)
   705  		return nil
   706  	})
   707  
   708  	type BucketSetMetadate struct {
   709  		BUCKET  string   `help:"name of bucket to put object"`
   710  		Tags    []string `help:"Tags info, eg: hypervisor=aliyun、os_type=Linux、os_version"`
   711  		Replace bool
   712  	}
   713  	shellutils.R(&BucketSetMetadate{}, "bucket-set-tag", "set bucket tag", func(cli cloudprovider.ICloudRegion, args *BucketSetMetadate) error {
   714  		bucket, err := cli.GetIBucketById(args.BUCKET)
   715  		if err != nil {
   716  			return err
   717  		}
   718  		tags := map[string]string{}
   719  		for _, tag := range args.Tags {
   720  			pair := strings.Split(tag, "=")
   721  			if len(pair) == 2 {
   722  				tags[pair[0]] = pair[1]
   723  			}
   724  		}
   725  		_, err = cloudprovider.SetBucketTags(context.Background(), bucket, "", tags)
   726  		if err != nil {
   727  			return err
   728  		}
   729  		fmt.Println("Success!")
   730  		return nil
   731  	})
   732  
   733  	type BucketGetUploads struct {
   734  		BUCKET string `help:"name of bucket to put object"`
   735  	}
   736  	shellutils.R(&BucketGetUploads{}, "bucket-get-uploads", "get bucket uploads", func(cli cloudprovider.ICloudRegion, args *BucketGetUploads) error {
   737  		bucket, err := cli.GetIBucketById(args.BUCKET)
   738  		if err != nil {
   739  			return err
   740  		}
   741  		uplaods, err := bucket.ListMultipartUploads()
   742  		printList(uplaods, len(uplaods), 0, len(uplaods), nil)
   743  		return nil
   744  	})
   745  
   746  	type BucketObjectDownloadOptions struct {
   747  		BUCKET string `help:"name of bucket"`
   748  		KEY    string `help:"Key of object"`
   749  		Output string `help:"target output, default to stdout"`
   750  		Start  int64  `help:"partial download start"`
   751  		End    int64  `help:"partial download end"`
   752  	}
   753  	shellutils.R(&BucketObjectDownloadOptions{}, "object-download", "Download", func(cli cloudprovider.ICloudRegion, args *BucketObjectDownloadOptions) error {
   754  		bucket, err := cli.GetIBucketById(args.BUCKET)
   755  		if err != nil {
   756  			return err
   757  		}
   758  		obj, err := cloudprovider.GetIObject(bucket, args.KEY)
   759  		if err != nil {
   760  			return err
   761  		}
   762  
   763  		var rangeOpt *cloudprovider.SGetObjectRange
   764  		if args.Start != 0 || args.End != 0 {
   765  			if args.End <= 0 {
   766  				args.End = obj.GetSizeBytes() - 1
   767  			}
   768  			rangeOpt = &cloudprovider.SGetObjectRange{Start: args.Start, End: args.End}
   769  		}
   770  		output, err := bucket.GetObject(context.Background(), args.KEY, rangeOpt)
   771  		if err != nil {
   772  			return err
   773  		}
   774  		defer output.Close()
   775  		var target io.Writer
   776  		if len(args.Output) == 0 {
   777  			target = os.Stdout
   778  		} else {
   779  			fp, err := os.Create(args.Output)
   780  			if err != nil {
   781  				return err
   782  			}
   783  			defer fp.Close()
   784  			target = fp
   785  		}
   786  		prop, err := streamutils.StreamPipe(output, target, false, nil)
   787  		if err != nil {
   788  			return err
   789  		}
   790  		if len(args.Output) > 0 {
   791  			fmt.Println("Success:", prop.Size, "written")
   792  		}
   793  		return nil
   794  	})
   795  
   796  	type BucketObjectTempUrlOptions struct {
   797  		BUCKET string `help:"name of bucket"`
   798  		KEY    string `help:"Key of object"`
   799  		Method string `default:"GET" choices:"GET|PUT|POST"`
   800  		Hour   int64  `default:"1"`
   801  	}
   802  
   803  	shellutils.R(&BucketObjectTempUrlOptions{}, "object-temp-url", "Show object temp url", func(cli cloudprovider.ICloudRegion, args *BucketObjectTempUrlOptions) error {
   804  		bucket, err := cli.GetIBucketById(args.BUCKET)
   805  		if err != nil {
   806  			return err
   807  		}
   808  		url, err := bucket.GetTempUrl(args.Method, args.KEY, time.Duration(args.Hour)*time.Hour)
   809  		if err != nil {
   810  			return err
   811  		}
   812  		fmt.Println(url)
   813  		return nil
   814  	})
   815  
   816  	type BucketObjectCopyOptions struct {
   817  		SRC       string `help:"name of source bucket"`
   818  		SRCKEY    string `help:"Key of source object"`
   819  		DST       string `help:"name of destination bucket"`
   820  		DSTKEY    string `help:"key of destination object"`
   821  		Debug     bool   `help:"show debug info"`
   822  		BlockSize int64  `help:"block size in MB"`
   823  		Native    bool   `help:"Use native copy"`
   824  
   825  		ObjectHeaderOptions
   826  	}
   827  	shellutils.R(&BucketObjectCopyOptions{}, "object-copy", "Copy object", func(cli cloudprovider.ICloudRegion, args *BucketObjectCopyOptions) error {
   828  		ctx := context.Background()
   829  		dstBucket, err := cli.GetIBucketByName(args.DST)
   830  		if err != nil {
   831  			return err
   832  		}
   833  		srcBucket, err := cli.GetIBucketByName(args.SRC)
   834  		if err != nil {
   835  			return err
   836  		}
   837  		srcObj, err := cloudprovider.GetIObject(srcBucket, args.SRCKEY)
   838  		if err != nil {
   839  			return err
   840  		}
   841  		meta := args.ObjectHeaderOptions.Options2Header()
   842  		if args.Native {
   843  			err = dstBucket.CopyObject(ctx, args.DSTKEY, args.SRC, args.SRCKEY, srcObj.GetAcl(), srcObj.GetStorageClass(), meta)
   844  			if err != nil {
   845  				return err
   846  			}
   847  		} else {
   848  			err = cloudprovider.CopyObject(ctx, args.BlockSize*1000*1000, dstBucket, args.DSTKEY, srcBucket, args.SRCKEY, meta, args.Debug)
   849  			if err != nil {
   850  				return err
   851  			}
   852  		}
   853  		fmt.Println("Success!")
   854  		return nil
   855  	})
   856  
   857  	type ObjectMetaOptions struct {
   858  		BUCKET string `help:"bucket name"`
   859  		KEY    string `help:"object key"`
   860  	}
   861  	shellutils.R(&ObjectMetaOptions{}, "object-meta", "Show object meta header", func(cli cloudprovider.ICloudRegion, args *ObjectMetaOptions) error {
   862  		bucket, err := cli.GetIBucketByName(args.BUCKET)
   863  		if err != nil {
   864  			return err
   865  		}
   866  		obj, err := cloudprovider.GetIObject(bucket, args.KEY)
   867  		if err != nil {
   868  			return err
   869  		}
   870  		meta := obj.GetMeta()
   871  		for k, v := range meta {
   872  			fmt.Println(k, ": ", v[0])
   873  		}
   874  		return nil
   875  	})
   876  
   877  	type ObjectSetMetaOptions struct {
   878  		BUCKET string `help:"bucket name"`
   879  		KEY    string `help:"object key"`
   880  
   881  		ObjectHeaderOptions
   882  	}
   883  	shellutils.R(&ObjectSetMetaOptions{}, "object-set-meta", "Set object meta header", func(cli cloudprovider.ICloudRegion, args *ObjectSetMetaOptions) error {
   884  		bucket, err := cli.GetIBucketByName(args.BUCKET)
   885  		if err != nil {
   886  			return err
   887  		}
   888  		obj, err := cloudprovider.GetIObject(bucket, args.KEY)
   889  		if err != nil {
   890  			return err
   891  		}
   892  		meta := args.ObjectHeaderOptions.Options2Header()
   893  		err = obj.SetMeta(context.Background(), meta)
   894  		if err != nil {
   895  			return err
   896  		}
   897  		return nil
   898  	})
   899  }