├── .gitignore ├── go.mod ├── examples └── user.go ├── pkg └── api │ ├── encodeUrl.go │ ├── api.go │ ├── resources.go │ ├── helpers.go │ └── helpers_test.go ├── LICENSE ├── go.sum └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/QuentinPerez/go-radosgw 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/QuentinPerez/go-encodeUrl v0.0.0-20160615164728-645a9dbeee15 7 | github.com/go-ini/ini v1.42.0 // indirect 8 | github.com/minio/minio-go v6.0.14+incompatible 9 | github.com/mitchellh/go-homedir v1.1.0 // indirect 10 | github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 11 | github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a 12 | github.com/smartystreets/gunit v0.0.0-20190426220047-d9c9211acd48 // indirect 13 | gopkg.in/ini.v1 v1.42.0 // indirect 14 | ) 15 | -------------------------------------------------------------------------------- /examples/user.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "log" 8 | "os" 9 | 10 | "github.com/QuentinPerez/go-radosgw/pkg/api" 11 | ) 12 | 13 | func printRawMode(out io.Writer, data interface{}) error { 14 | js, err := json.MarshalIndent(data, "", " ") 15 | if err != nil { 16 | return err 17 | } 18 | fmt.Fprintf(out, "%s\n", js) 19 | return nil 20 | } 21 | 22 | func main() { 23 | api, err := radosAPI.New(os.Getenv("RADOSGW_API"), os.Getenv("RADOSGW_ACCESS"), os.Getenv("RADOSGW_SECRET")) 24 | if err != nil { 25 | log.Fatal(err) 26 | } 27 | 28 | // create a new user named JohnDoe 29 | user, err := api.CreateUser(radosAPI.UserConfig{ 30 | UID: "JohnDoe", 31 | DisplayName: "John Doe", 32 | }) 33 | if err != nil { 34 | log.Fatal(err) 35 | } 36 | printRawMode(os.Stdout, user) 37 | 38 | // remove JohnDoe 39 | err = api.RemoveUser(radosAPI.UserConfig{ 40 | UID: "JohnDoe", 41 | }) 42 | if err != nil { 43 | log.Fatal(err) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /pkg/api/encodeUrl.go: -------------------------------------------------------------------------------- 1 | package radosAPI 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/QuentinPerez/go-encodeUrl" 9 | ) 10 | 11 | func init() { 12 | encurl.AddEncodeFunc(ifTimeIsNotNilCeph) 13 | encurl.AddEncodeFunc(boolIfNotNil) 14 | } 15 | 16 | func ifTimeIsNotNilCeph(obj interface{}) (string, bool, error) { 17 | if val, ok := obj.(*time.Time); ok { 18 | if val != nil { 19 | return fmt.Sprintf("%d-%02d-%02d %02d:%02d:%02d", 20 | val.Year(), val.Month(), val.Day(), 21 | val.Hour(), val.Minute(), val.Second()), true, nil 22 | } 23 | return "", false, nil 24 | } 25 | return "", false, errors.New("this field should be a *time.Time") 26 | } 27 | 28 | func boolIfNotNil(obj interface{}) (string, bool, error) { 29 | if val, ok := obj.(*bool); ok { 30 | if val != nil { 31 | if *val { 32 | return "True", true, nil 33 | } 34 | return "False", true, nil 35 | } 36 | return "", false, nil 37 | } 38 | return "", false, errors.New("this field should be a *boolean") 39 | } 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Quentin Perez 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /pkg/api/api.go: -------------------------------------------------------------------------------- 1 | package radosAPI 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "io/ioutil" 8 | "net/http" 9 | "net/url" 10 | "time" 11 | 12 | "github.com/smartystreets/go-aws-auth" 13 | ) 14 | 15 | // API contains fields to communicate with the rados-gateway 16 | type API struct { 17 | host string 18 | accessKey string 19 | secretKey string 20 | prefix string 21 | client *http.Client 22 | } 23 | 24 | // New returns client for Ceph RADOS Gateway 25 | func New(host, accessKey, secretKey string, adminPrefix ...string) (*API, error) { 26 | return NewWithClient(&http.Client{}, host, accessKey, secretKey, adminPrefix...) 27 | } 28 | 29 | func NewWithClient(client *http.Client, host, accessKey, secretKey string, adminPrefix ...string) (*API, error) { 30 | prefix := "admin" 31 | if len(adminPrefix) > 0 { 32 | prefix = adminPrefix[0] 33 | } 34 | if host == "" || accessKey == "" || secretKey == "" { 35 | return nil, fmt.Errorf("host, accessKey, secretKey must be not nil") 36 | } 37 | return &API{host, accessKey, secretKey, prefix, client}, nil 38 | } 39 | 40 | func (api *API) makeRequest(verb, url string) (body []byte, statusCode int, err error) { 41 | var apiErr apiError 42 | 43 | // fmt.Printf("URL [%v]: %v\n", verb, url) 44 | req, err := http.NewRequest(verb, url, nil) 45 | if err != nil { 46 | return 47 | } 48 | awsauth.SignS3(req, awsauth.Credentials{ 49 | AccessKeyID: api.accessKey, 50 | SecretAccessKey: api.secretKey, 51 | Expiration: time.Now().Add(1 * time.Minute)}, 52 | ) 53 | resp, err := api.client.Do(req) 54 | if err != nil { 55 | return 56 | } 57 | if resp.Body != nil { 58 | defer resp.Body.Close() 59 | } 60 | statusCode = resp.StatusCode 61 | body, err = ioutil.ReadAll(resp.Body) 62 | if err != nil { 63 | return 64 | } 65 | if errMarshal := json.Unmarshal(body, &apiErr); errMarshal == nil && apiErr.Code != "" { 66 | err = errors.New(apiErr.Code) 67 | } 68 | return 69 | } 70 | 71 | func (api *API) call(verb, route string, args url.Values, usePrefix bool, sub ...string) (body []byte, statusCode int, err error) { 72 | subreq := "" 73 | if len(sub) > 0 { 74 | subreq = fmt.Sprintf("%s&", sub[0]) 75 | } 76 | if usePrefix { 77 | route = fmt.Sprintf("/%s%s", api.prefix, route) 78 | } 79 | body, statusCode, err = api.makeRequest(verb, fmt.Sprintf("%v%v?%v%s", api.host, route, subreq, args.Encode())) 80 | if statusCode != 200 { 81 | err = fmt.Errorf("[%v]: %v", statusCode, err) 82 | } 83 | return 84 | } 85 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/QuentinPerez/go-encodeUrl v0.0.0-20160615164728-645a9dbeee15 h1:HSaBuaUOFXgWMPWy/iTX3VCB4S1udFAzZNOw5fEvKCk= 2 | github.com/QuentinPerez/go-encodeUrl v0.0.0-20160615164728-645a9dbeee15/go.mod h1:oxGWSG4SLa0w9eg9Za8u/zq/V5HGw4+p84Dm8e/PjKE= 3 | github.com/go-ini/ini v1.42.0 h1:TWr1wGj35+UiWHlBA8er89seFXxzwFn11spilrrj+38= 4 | github.com/go-ini/ini v1.42.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= 5 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= 6 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 7 | github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= 8 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 9 | github.com/minio/minio-go v6.0.14+incompatible h1:fnV+GD28LeqdN6vT2XdGKW8Qe/IfjJDswNVuni6km9o= 10 | github.com/minio/minio-go v6.0.14+incompatible/go.mod h1:7guKYtitv8dktvNUGrhzmNlA5wrAABTQXCoesZdFQO8= 11 | github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= 12 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 13 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= 14 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= 15 | github.com/smartystreets/assertions v0.0.0-20190401211740-f487f9de1cd3 h1:hBSHahWMEgzwRyS6dRpxY0XyjZsHyQ61s084wo5PJe0= 16 | github.com/smartystreets/assertions v0.0.0-20190401211740-f487f9de1cd3/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= 17 | github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 h1:hp2CYQUINdZMHdvTdXtPOY2ainKl4IoMcpAXEf2xj3Q= 18 | github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM= 19 | github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a h1:pa8hGb/2YqsZKovtsgrwcDH1RZhVbTKCjLp47XpqCDs= 20 | github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= 21 | github.com/smartystreets/gunit v0.0.0-20190426220047-d9c9211acd48 h1:0rwlrv91WdTeS4HtZDGmntuKOFdeeMWKootgyxTl9TA= 22 | github.com/smartystreets/gunit v0.0.0-20190426220047-d9c9211acd48/go.mod h1:oqKsUQaUkJ2EU1ZzLQFJt1WUp9DDuj1CnZbp4DwPwL4= 23 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= 24 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 25 | golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628= 26 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 27 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= 28 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 29 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= 30 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 31 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 32 | gopkg.in/ini.v1 v1.42.0 h1:7N3gPTt50s8GuLortA00n8AqRTk75qOP98+mTPpgzRk= 33 | gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 34 | -------------------------------------------------------------------------------- /pkg/api/resources.go: -------------------------------------------------------------------------------- 1 | package radosAPI 2 | 3 | type apiError struct { 4 | Code string `json:"Code"` 5 | } 6 | 7 | type Entry struct { 8 | Buckets []struct { 9 | Bucket string `json:"bucket"` 10 | Categories []struct { 11 | BytesReceived int `json:"bytes_received"` 12 | BytesSent int `json:"bytes_sent"` 13 | Category string `json:"category"` 14 | Ops int `json:"ops"` 15 | SuccessfulOps int `json:"successful_ops"` 16 | } `json:"categories"` 17 | Epoch int `json:"epoch"` 18 | Time string `json:"time"` 19 | } `json:"buckets"` 20 | Owner string `json:"owner"` 21 | } 22 | 23 | type Summary struct { 24 | Categories []struct { 25 | BytesReceived int `json:"bytes_received"` 26 | BytesSent int `json:"bytes_sent"` 27 | Category string `json:"category"` 28 | Ops int `json:"ops"` 29 | SuccessfulOps int `json:"successful_ops"` 30 | } `json:"categories"` 31 | Total struct { 32 | BytesReceived int `json:"bytes_received"` 33 | BytesSent int `json:"bytes_sent"` 34 | Ops int `json:"ops"` 35 | SuccessfulOps int `json:"successful_ops"` 36 | } `json:"total"` 37 | User string `json:"user"` 38 | } 39 | 40 | // Usage represents the response of usage requests 41 | type Usage struct { 42 | Entries []Entry `json:"entries"` 43 | Summary []Summary `json:"summary"` 44 | } 45 | 46 | // SubUsers represents the response of subuser requests 47 | type SubUsers []struct { 48 | ID string `json:"id"` 49 | Permissions string `json:"permissions"` 50 | } 51 | 52 | // KeysDefinition represents the response of key requests 53 | type KeysDefinition []struct { 54 | AccessKey string `json:"access_key,omitempty"` 55 | SecretKey string `json:"secret_key"` 56 | User string `json:"user"` 57 | } 58 | 59 | // User represents the response of user requests 60 | type User struct { 61 | Caps []Capability `json:"caps"` 62 | DisplayName string `json:"display_name"` 63 | Email string `json:"email"` 64 | Keys KeysDefinition `json:"keys"` 65 | MaxBuckets int `json:"max_buckets"` 66 | Subusers SubUsers `json:"subusers"` 67 | Suspended int `json:"suspended"` 68 | SwiftKeys KeysDefinition `json:"swift_keys"` 69 | UserID string `json:"user_id"` 70 | } 71 | 72 | type Stats struct { 73 | Bucket string `json:"bucket"` 74 | NumShards int `json:"num_shards"` 75 | PlacementRule string `json:"placement_rule"` 76 | BucketQuota struct { 77 | Enabled bool `json:"enabled"` 78 | MaxObjects int `json:"max_objects"` 79 | MaxSizeKb int `json:"max_size_kb"` 80 | } `json:"bucket_quota"` 81 | ID string `json:"id"` 82 | IndexPool string `json:"index_pool"` 83 | Marker string `json:"marker"` 84 | MasterVer string `json:"master_ver"` 85 | MaxMarker string `json:"max_marker"` 86 | Mtime string `json:"mtime"` 87 | Owner string `json:"owner"` 88 | Pool string `json:"pool"` 89 | Usage struct { 90 | RgwMain struct { 91 | NumObjects int `json:"num_objects"` 92 | SizeKb int `json:"size_kb"` 93 | SizeKbActual int `json:"size_kb_actual"` 94 | } `json:"rgw.main"` 95 | } `json:"usage"` 96 | Ver string `json:"ver"` 97 | ZoneGroup string `json:"zonegroup"` 98 | } 99 | 100 | type Bucket struct { 101 | Name string `json:"name,omitempty"` 102 | Stats *Stats `json:"stats,omitempty"` 103 | } 104 | 105 | // Buckets represents the response of bucket requests 106 | type Buckets []Bucket 107 | 108 | // Policy represents the response of policy requests 109 | type Policy struct { 110 | Acl struct { 111 | AclGroupMap []struct { 112 | Acl int `json:"acl"` 113 | Group int `json:"group"` 114 | } `json:"acl_group_map"` 115 | AclUserMap []struct { 116 | Acl int `json:"acl"` 117 | User string `json:"user"` 118 | } `json:"acl_user_map"` 119 | GrantMap []struct { 120 | Grant struct { 121 | Email string `json:"email"` 122 | Group int `json:"group"` 123 | ID string `json:"id"` 124 | Name string `json:"name"` 125 | Permission struct { 126 | Flags int `json:"flags"` 127 | } `json:"permission"` 128 | Type struct { 129 | Type int `json:"type"` 130 | } `json:"type"` 131 | } `json:"grant"` 132 | ID string `json:"id"` 133 | } `json:"grant_map"` 134 | } `json:"acl"` 135 | Owner struct { 136 | DisplayName string `json:"display_name"` 137 | ID string `json:"id"` 138 | } `json:"owner"` 139 | } 140 | 141 | // Quotas represents the reponse of quotas requests 142 | type Quotas struct { 143 | BucketQuota struct { 144 | Enabled bool `json:"enabled"` 145 | MaxObjects int `json:"max_objects"` 146 | MaxSizeKb int `json:"max_size_kb"` 147 | } `json:"bucket_quota"` 148 | UserQuota struct { 149 | Enabled bool `json:"enabled"` 150 | MaxObjects int `json:"max_objects"` 151 | MaxSizeKb int `json:"max_size_kb"` 152 | } `json:"user_quota"` 153 | } 154 | 155 | // Capability represents the reponse of capability requests 156 | type Capability struct { 157 | Perm string `json:"perm"` 158 | Type string `json:"type"` 159 | } 160 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Overview 3 | 4 | ``` 5 | radosgw-admin is a RADOS gateway user administration utility. 6 | It allows operations on user/bucket/quota/capability. 7 | ``` 8 | 9 | :warning: This library has been tested with **Jewel** but it should work with **Hammer** too. 10 | 11 | |Operation | Implemented | Tests | 12 | |-------------|--------------------|---------------------| 13 | | User | :white_check_mark: |:white_check_mark: | 14 | | Bucket | :white_check_mark: |:white_check_mark: | 15 | | Capability | :white_check_mark: |:white_check_mark: | 16 | | Quota | :white_check_mark: |:white_check_mark: | 17 | 18 | ## Setup 19 | 20 | ``` 21 | $> go get github.com/QuentinPerez/go-radosgw/pkg/api 22 | ``` 23 | 24 | ## How it works 25 | 26 | > Ensure you have cluster ceph with a radosgw. 27 | 28 | ```go 29 | api := radosAPI.New("http://192.168.42.40", "1ZZWD0G5IDP57I0751HE", "3ydvK64eWuWwup0FKtznmf9FDVXhB8jleEFRTH0D") 30 | 31 | // create a new user named JohnDoe 32 | user, err := api.CreateUser(radosAPI.UserConfig{ 33 | UID: "JohnDoe", 34 | DisplayName: "John Doe", 35 | }) 36 | // ... 37 | // remove JohnDoe 38 | err = api.RemoveUser(radosAPI.UserConfig{ 39 | UID: "JohnDoe", 40 | }) 41 | ``` 42 | 43 | ## API 44 | 45 | ```go 46 | // New returns an API object to intertact with Admin RadosGW 47 | func New(host, accessKey, secretKey string, adminPrefix ...string) (*API, error) {} 48 | 49 | // GetUsage requests bandwidth usage information. 50 | func (api *API) GetUsage(conf UsageConfig) (*Usage, error) {} 51 | 52 | // DeleteUsage removes usage information. With no dates specified, removes all usage information 53 | func (api *API) DeleteUsage(conf UsageConfig) error {} 54 | 55 | // GetUIDs gets all UIDs. 56 | func (api *API) GetUIDs() ([]string, error) {} 57 | 58 | // GetUser gets user information. If no user is specified returns the list of all users along with suspension information 59 | func (api *API) GetUser(uid ...string) (*User, error) {} 60 | 61 | // GetUsers get all user information. 62 | func (api *API) GetUsers() ([]*User, error) {} 63 | 64 | // CreateUser creates a new user. By Default, a S3 key pair will be created automatically and returned in the response. 65 | func (api *API) CreateUser(conf UserConfig) (*User, error) {} 66 | 67 | // UpdateUser modifies a user 68 | func (api *API) UpdateUser(conf UserConfig) (*User, error) {} 69 | 70 | // RemoveUser removes an existing user. 71 | func (api *API) RemoveUser(conf UserConfig) error {} 72 | 73 | // CreateSubUser creates a new subuser (primarily useful for clients using the Swift API). 74 | func (api *API) CreateSubUser(conf SubUserConfig) (*SubUsers, error) {} 75 | 76 | // UpdateSubUser modifies an existing subuser 77 | func (api *API) UpdateSubUser(conf SubUserConfig) (*SubUsers, error) {} 78 | 79 | // RemoveSubUser remove an existing subuser 80 | func (api *API) RemoveSubUser(conf SubUserConfig) error {} 81 | 82 | // CreateKey creates a new key. If a subuser is specified then by default created keys will be swift type. 83 | func (api *API) CreateKey(conf KeyConfig) (*KeysDefinition, error) {} 84 | 85 | // RemoveKey removes an existing key 86 | func (api *API) RemoveKey(conf KeyConfig) error {} 87 | 88 | // GetBucket gets information about a subset of the existing buckets. 89 | func (api *API) GetBucket(conf BucketConfig) (Buckets, error) {} 90 | 91 | // RemoveBucket removes an existing bucket. 92 | func (api *API) RemoveBucket(conf BucketConfig) error {} 93 | 94 | // UnlinkBucket unlinks a bucket from a specified user. 95 | func (api *API) UnlinkBucket(conf BucketConfig) error {} 96 | 97 | // CheckBucket checks the index of an existing bucket. 98 | func (api *API) CheckBucket(conf BucketConfig) (string, error) {} 99 | 100 | // LinkBucket links a bucket to a specified user, unlinking the bucket from any previous user. 101 | func (api *API) LinkBucket(conf BucketConfig) error {} 102 | 103 | // RemoveObject removes an existing object. 104 | func (api *API) RemoveObject(conf BucketConfig) error {} 105 | 106 | // GetBucketPolicy reads the bucket policy 107 | func (api *API) GetBucketPolicy(conf BucketConfig) (*Policy, error) {} 108 | 109 | // GetObjectPolicy reads the object policy 110 | func (api *API) GetObjectPolicy(conf BucketConfig) (*Policy, error) {} 111 | 112 | // GetQuotas returns user's quotas 113 | func (api *API) GetQuotas(conf QuotaConfig) (*Quotas, error) {} 114 | 115 | // UpdateQuota updates user's quotas 116 | func (api *API) UpdateQuota(conf QuotaConfig) error {} 117 | 118 | // AddCapability returns user's quotas 119 | func (api *API) AddCapability(conf CapConfig) ([]Capability, error) {} 120 | 121 | // DelCapability returns user's quotas 122 | func (api *API) DelCapability(conf CapConfig) ([]Capability, error) {} 123 | ``` 124 | 125 | ## Changelog 126 | 127 | ### master (unreleased) 128 | 129 | --- 130 | 131 | ## Development 132 | 133 | Feel free to contribute :smiley::beers: 134 | 135 | ## Links 136 | 137 | - **Radowsgw-admin Documentaion**: http://docs.ceph.com/docs/jewel/radosgw/adminops/ 138 | - **Report bugs**: https://github.com/QuentinPerez/go-radosgw/issues 139 | 140 | ## License 141 | 142 | [MIT](https://github.com/QuentinPerez/go-radosgw/blob/master/LICENSE) 143 | -------------------------------------------------------------------------------- /pkg/api/helpers.go: -------------------------------------------------------------------------------- 1 | package radosAPI 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "net/url" 7 | "time" 8 | 9 | "fmt" 10 | 11 | "github.com/QuentinPerez/go-encodeUrl" 12 | ) 13 | 14 | // UsageConfig usage request 15 | type UsageConfig struct { 16 | UID string `url:"uid,ifStringIsNotEmpty"` // The user for which the information is requested. If not specified will apply to all users 17 | Start *time.Time `url:"start,ifTimeIsNotNilCeph"` // Date and (optional) time that specifies the start time of the requested data 18 | End *time.Time `url:"end,ifTimeIsNotNilCeph"` // Date and (optional) time that specifies the end time of the requested data (non-inclusive) 19 | ShowEntries bool `url:"show-entries,ifBoolIsFalse"` // Specifies whether data entries should be returned. 20 | ShowSummary bool `url:"show-summary,ifBoolIsFalse"` // Specifies whether data summary should be returned 21 | RemoveAll bool `url:"remove-all,ifBoolIsTrue"` // Required when uid is not specified, in order to acknowledge multi user data removal. 22 | } 23 | 24 | // GetUsage requests bandwidth usage information. 25 | // 26 | // !! caps: usage=read !! 27 | // 28 | // @UID 29 | // @Start 30 | // @End 31 | // @ShowEntries 32 | // @ShowSummary 33 | // 34 | func (api *API) GetUsage(conf UsageConfig) (*Usage, error) { 35 | var ( 36 | ret = &Usage{} 37 | values = url.Values{} 38 | errs []error 39 | ) 40 | 41 | values, errs = encurl.Translate(conf) 42 | if len(errs) > 0 { 43 | return nil, errs[0] 44 | } 45 | values.Add("format", "json") 46 | body, _, err := api.call("GET", "/usage", values, true) 47 | if err != nil { 48 | return nil, err 49 | } 50 | if err = json.Unmarshal(body, &ret); err != nil { 51 | return nil, err 52 | } 53 | return ret, nil 54 | } 55 | 56 | // DeleteUsage removes usage information. With no dates specified, removes all usage information 57 | // 58 | // !! caps: usage=write !! 59 | // 60 | // @UID 61 | // @Start 62 | // @End 63 | // @RemoveAll 64 | // 65 | func (api *API) DeleteUsage(conf UsageConfig) error { 66 | var ( 67 | values = url.Values{} 68 | errs []error 69 | ) 70 | 71 | values, errs = encurl.Translate(conf) 72 | if len(errs) > 0 { 73 | return errs[0] 74 | } 75 | values.Add("format", "json") 76 | _, _, err := api.call("DELETE", "/usage", values, true) 77 | return err 78 | } 79 | 80 | // GetUser gets user information. If no user is specified returns the list of all users along with suspension information 81 | // 82 | // ** If no user is specified returns the list ... ** Don't works for me 83 | // 84 | // !! caps: users=read !! 85 | // 86 | // @uid 87 | // 88 | func (api *API) GetUser(uid ...string) (*User, error) { 89 | ret := &User{} 90 | values := url.Values{} 91 | 92 | values.Add("format", "json") 93 | if len(uid) != 0 { 94 | values.Add("uid", uid[0]) 95 | } 96 | body, _, err := api.call("GET", "/user", values, true) 97 | if err != nil { 98 | return nil, err 99 | } 100 | if err = json.Unmarshal(body, &ret); err != nil { 101 | return nil, err 102 | } 103 | return ret, nil 104 | } 105 | 106 | // GetUIDs gets all UIDs. 107 | // 108 | // !! caps: users=read !! 109 | // 110 | func (api *API) GetUIDs() ([]string, error) { 111 | var ret []string 112 | values := url.Values{} 113 | 114 | values.Add("format", "json") 115 | body, _, err := api.call("GET", "/metadata/user", values, true) 116 | if err != nil { 117 | return ret, err 118 | } 119 | if err = json.Unmarshal(body, &ret); err != nil { 120 | return ret, err 121 | } 122 | return ret, nil 123 | } 124 | 125 | // GetUsers get all user information. 126 | // 127 | // !! caps: users=read !! 128 | // 129 | func (api *API) GetUsers() ([]*User, error) { 130 | var ret []*User 131 | 132 | uids, err := api.GetUIDs() 133 | if err != nil { 134 | return ret, err 135 | } 136 | ret = make([]*User, len(uids)) 137 | for idx, uid := range uids { 138 | ret[idx], err = api.GetUser(uid) 139 | if err != nil { 140 | return ret, err 141 | } 142 | } 143 | return ret, nil 144 | } 145 | 146 | // UserConfig user request 147 | type UserConfig struct { 148 | UID string `url:"uid,ifStringIsNotEmpty"` // The user ID to be created 149 | DisplayName string `url:"display-name,ifStringIsNotEmpty"` // The display name of the user to be created 150 | Email string `url:"email,ifStringIsNotEmpty"` // The email address associated with the user 151 | KeyType string `url:"key-type,ifStringIsNotEmpty"` // Key type to be generated, options are: swift, s3 (default) 152 | AccessKey string `url:"access-key,ifStringIsNotEmpty"` // Specify access key 153 | SecretKey string `url:"secret-key,ifStringIsNotEmpty"` // Specify secret key 154 | UserCaps string `url:"user-caps,ifStringIsNotEmpty"` // User capabilities 155 | MaxBuckets *int `url:"max-buckets,itoaIfNotNil"` // Specify the maximum number of buckets the user can own 156 | GenerateKey bool `url:"generate-key,ifBoolIsTrue"` // Generate a new key pair and add to the existing keyring 157 | Suspended *bool `url:"suspended,boolIfNotNil"` // Specify whether the user should be suspended 158 | PurgeData bool `url:"purge-data,ifBoolIsTrue"` // When specified the buckets and objects belonging to the user will also be removed 159 | } 160 | 161 | // CreateUser creates a new user. By Default, a S3 key pair will be created automatically and returned in the response. 162 | // If only one of access-key or secret-key is provided, the omitted key will be automatically generated. 163 | // By default, a generated key is added to the keyring without replacing an existing key pair. 164 | // If access-key is specified and refers to an existing key owned by the user then it will be modified 165 | // 166 | // !! caps: users=write !! 167 | // 168 | // @UID 169 | // @DisplayName 170 | // @Email 171 | // @KeyType 172 | // @AccessKey 173 | // @SecretKey 174 | // @UserCaps 175 | // @GenerateKey 176 | // @MaxBuckets 177 | // @Suspended 178 | // 179 | func (api *API) CreateUser(conf UserConfig) (*User, error) { 180 | if conf.UID == "" { 181 | return nil, errors.New("UID field is required") 182 | } 183 | if conf.DisplayName == "" { 184 | return nil, errors.New("DisplayName field is required") 185 | } 186 | 187 | var ( 188 | ret = &User{} 189 | values = url.Values{} 190 | errs []error 191 | ) 192 | 193 | values, errs = encurl.Translate(conf) 194 | if len(errs) > 0 { 195 | return nil, errs[0] 196 | } 197 | values.Add("format", "json") 198 | body, _, err := api.call("PUT", "/user", values, true) 199 | if err != nil { 200 | return nil, err 201 | } 202 | if err = json.Unmarshal(body, &ret); err != nil { 203 | return nil, err 204 | } 205 | return ret, nil 206 | } 207 | 208 | // UpdateUser modifies a user 209 | // 210 | // !! caps: users=write !! 211 | // 212 | // @UID 213 | // @DisplayName 214 | // @Email 215 | // @KeyType 216 | // @AccessKey 217 | // @SecretKey 218 | // @UserCaps 219 | // @GenerateKey 220 | // @MaxBuckets 221 | // @Suspended 222 | // 223 | func (api *API) UpdateUser(conf UserConfig) (*User, error) { 224 | if conf.UID == "" { 225 | return nil, errors.New("UID field is required") 226 | } 227 | 228 | var ( 229 | ret = &User{} 230 | values = url.Values{} 231 | errs []error 232 | ) 233 | 234 | values, errs = encurl.Translate(conf) 235 | if len(errs) > 0 { 236 | return nil, errs[0] 237 | } 238 | values.Add("format", "json") 239 | body, _, err := api.call("POST", "/user", values, true) 240 | if err != nil { 241 | return nil, err 242 | } 243 | if err = json.Unmarshal(body, &ret); err != nil { 244 | return nil, err 245 | } 246 | return ret, nil 247 | } 248 | 249 | // RemoveUser removes an existing user. 250 | // 251 | // !! caps: users=write !! 252 | // 253 | // @UID 254 | // @PurgeData 255 | // 256 | func (api *API) RemoveUser(conf UserConfig) error { 257 | if conf.UID == "" { 258 | return errors.New("UID field is required") 259 | } 260 | var ( 261 | values = url.Values{} 262 | errs []error 263 | ) 264 | 265 | values, errs = encurl.Translate(conf) 266 | if len(errs) > 0 { 267 | return errs[0] 268 | } 269 | values.Add("format", "json") 270 | _, _, err := api.call("DELETE", "/user", values, true) 271 | return err 272 | } 273 | 274 | // SubUserConfig subuser request 275 | type SubUserConfig struct { 276 | UID string `url:"uid,ifStringIsNotEmpty"` // The user ID under which a subuser is to be created 277 | SubUser string `url:"subuser,ifStringIsNotEmpty"` // Specify the subuser ID to be created 278 | KeyType string `url:"key-type,ifStringIsNotEmpty"` // Key type to be generated, options are: swift (default), s3 279 | Access string `url:"access,ifStringIsNotEmpty"` // Set access permissions for sub-user, should be one of read, write, readwrite, full 280 | Secret string `url:"secret,ifStringIsNotEmpty"` // Specify secret key 281 | SecretKey string `url:"secret-key,ifStringIsNotEmpty"` // Specify secret key 282 | GenerateSecret bool `url:"generate-secret,ifBoolIsTrue"` // Generate the secret key 283 | PurgeKeys bool `url:"purge-keys,ifBoolIsTrue"` // Remove keys belonging to the subuser 284 | } 285 | 286 | // CreateSubUser creates a new subuser (primarily useful for clients using the Swift API). 287 | // Note that either gen-subuser or subuser is required for a valid request. 288 | // Note that in general for a subuser to be useful, it must be granted permissions by specifying access. 289 | // As with user creation if subuser is specified without secret, then a secret key will be automatically generated. 290 | // 291 | // !! caps: users=write !! 292 | // 293 | // @UID 294 | // @SubUser 295 | // @KeyType 296 | // @Access 297 | // @SecretKey 298 | // @GenerateSecret 299 | // 300 | func (api *API) CreateSubUser(conf SubUserConfig) (*SubUsers, error) { 301 | if conf.UID == "" { 302 | return nil, errors.New("UID field is required") 303 | } 304 | 305 | var ( 306 | ret = &SubUsers{} 307 | values = url.Values{} 308 | errs []error 309 | ) 310 | 311 | values, errs = encurl.Translate(conf) 312 | if len(errs) > 0 { 313 | return nil, errs[0] 314 | } 315 | values.Add("format", "json") 316 | body, _, err := api.call("PUT", "/user", values, true, "subuser") 317 | if err != nil { 318 | return nil, err 319 | } 320 | if err = json.Unmarshal(body, &ret); err != nil { 321 | return nil, err 322 | } 323 | return ret, nil 324 | } 325 | 326 | // UpdateSubUser modifies an existing subuser 327 | // 328 | // !! caps: users=write !! 329 | // 330 | // @UID 331 | // @SubUser 332 | // @KeyType 333 | // @Access 334 | // @Secret 335 | // @GenerateSecret 336 | // 337 | func (api *API) UpdateSubUser(conf SubUserConfig) (*SubUsers, error) { 338 | if conf.UID == "" { 339 | return nil, errors.New("UID field is required") 340 | } 341 | if conf.SubUser == "" { 342 | return nil, errors.New("SubUser field is required") 343 | } 344 | 345 | var ( 346 | ret = &SubUsers{} 347 | values = url.Values{} 348 | errs []error 349 | ) 350 | 351 | values, errs = encurl.Translate(conf) 352 | if len(errs) > 0 { 353 | return nil, errs[0] 354 | } 355 | values.Add("format", "json") 356 | body, _, err := api.call("POST", "/user", values, true, "subuser") 357 | if err != nil { 358 | return nil, err 359 | } 360 | if err = json.Unmarshal(body, &ret); err != nil { 361 | return nil, err 362 | } 363 | return ret, nil 364 | } 365 | 366 | // RemoveSubUser remove an existing subuser 367 | // 368 | // !! caps: users=write !! 369 | // 370 | // @UID 371 | // @SubUser 372 | // @PurgeKeys 373 | // 374 | func (api *API) RemoveSubUser(conf SubUserConfig) error { 375 | if conf.UID == "" { 376 | return errors.New("UID field is required") 377 | } 378 | if conf.SubUser == "" { 379 | return errors.New("SubUser field is required") 380 | } 381 | var ( 382 | values = url.Values{} 383 | errs []error 384 | ) 385 | 386 | values, errs = encurl.Translate(conf) 387 | if len(errs) > 0 { 388 | return errs[0] 389 | } 390 | values.Add("format", "json") 391 | _, _, err := api.call("DELETE", "/user", values, true, "subuser") 392 | return err 393 | } 394 | 395 | // KeyConfig key request 396 | type KeyConfig struct { 397 | UID string `url:"uid,ifStringIsNotEmpty"` // The user ID to receive the new key 398 | SubUser string `url:"subuser,ifStringIsNotEmpty"` // The subuser ID to receive the new key 399 | KeyType string `url:"key-type,ifStringIsNotEmpty"` // Key type to be generated, options are: swift, s3 (default) 400 | AccessKey string `url:"access-key,ifStringIsNotEmpty"` // Specify the access key 401 | SecretKey string `url:"secret-key,ifStringIsNotEmpty"` // Specify secret key 402 | GenerateSecret bool `url:"generate-secret,ifBoolIsTrue"` // Generate a new key pair and add to the existing keyring 403 | } 404 | 405 | // CreateKey creates a new key. If a subuser is specified then by default created keys will be swift type. 406 | // If only one of access-key or secret-key is provided the committed key will be automatically generated, 407 | // that is if only secret-key is specified then access-key will be automatically generated. 408 | // By default, a generated key is added to the keyring without replacing an existing key pair. 409 | // If access-key is specified and refers to an existing key owned by the user then it will be modified. 410 | // The response is a container listing all keys of the same type as the key created. 411 | // Note that when creating a swift key, specifying the option access-key will have no effect. 412 | // Additionally, only one swift key may be held by each user or subuser. 413 | // 414 | // !! caps: users=write !! 415 | // 416 | // @UID 417 | // @SubUser 418 | // @KeyType 419 | // @AccessKey 420 | // @SecretKey 421 | // @GenerateSecret 422 | // 423 | func (api *API) CreateKey(conf KeyConfig) (*KeysDefinition, error) { 424 | if conf.UID == "" { 425 | return nil, errors.New("UID field is required") 426 | } 427 | 428 | var ( 429 | ret = &KeysDefinition{} 430 | values = url.Values{} 431 | errs []error 432 | ) 433 | 434 | values, errs = encurl.Translate(conf) 435 | if len(errs) > 0 { 436 | return nil, errs[0] 437 | } 438 | values.Add("format", "json") 439 | body, _, err := api.call("PUT", "/user", values, true, "key") 440 | if err != nil { 441 | return nil, err 442 | } 443 | if err = json.Unmarshal(body, &ret); err != nil { 444 | return nil, err 445 | } 446 | return ret, nil 447 | } 448 | 449 | // RemoveKey removes an existing key 450 | // 451 | // !! caps: users=write !! 452 | // 453 | // @UID 454 | // @SubUser 455 | // @KeyType 456 | // @AccessKey 457 | // 458 | func (api *API) RemoveKey(conf KeyConfig) error { 459 | if conf.AccessKey == "" { 460 | return errors.New("AccessKey field is required") 461 | } 462 | 463 | var ( 464 | values = url.Values{} 465 | errs []error 466 | ) 467 | 468 | values, errs = encurl.Translate(conf) 469 | if len(errs) > 0 { 470 | return errs[0] 471 | } 472 | values.Add("format", "json") 473 | _, _, err := api.call("DELETE", "/user", values, true, "key") 474 | return err 475 | } 476 | 477 | // BucketConfig bucket request 478 | type BucketConfig struct { 479 | Bucket string `url:"bucket,ifStringIsNotEmpty"` // The bucket to return info on 480 | UID string `url:"uid,ifStringIsNotEmpty"` // The user to retrieve bucket information for 481 | Stats bool `url:"stats,ifBoolIsTrue"` // Return bucket statistics 482 | CheckObjects bool `url:"check-objects,ifBoolIsTrue"` // Check multipart object accounting 483 | Fix bool `url:"fix,ifBoolIsTrue"` // Also fix the bucket index when checking 484 | PurgeObjects bool `url:"purge-objects,ifBoolIsTrue"` // Remove a buckets objects before deletion 485 | Object string `url:"object,ifStringIsNotEmpty"` // The object to remove 486 | } 487 | 488 | // GetBucket gets information about a subset of the existing buckets. 489 | // If uid is specified without bucket then all buckets beloning to the user will be returned. 490 | // If bucket alone is specified, information for that particular bucket will be retrieved 491 | // 492 | // !! caps: buckets=read !! 493 | // 494 | //@Bucket 495 | //@UID 496 | //@Stats 497 | // 498 | func (api *API) GetBucket(conf BucketConfig) (Buckets, error) { 499 | var ( 500 | ret = Buckets{} 501 | values = url.Values{} 502 | errs []error 503 | variant interface{} 504 | ) 505 | 506 | values, errs = encurl.Translate(conf) 507 | if len(errs) > 0 { 508 | return nil, errs[0] 509 | } 510 | values.Add("format", "json") 511 | body, _, err := api.call("GET", "/bucket", values, true) 512 | if err != nil { 513 | return nil, err 514 | } 515 | if err = json.Unmarshal(body, &variant); err != nil { 516 | return nil, err 517 | } 518 | if tab, ok := variant.([]interface{}); ok { 519 | add := Bucket{} 520 | for _, v := range tab { 521 | if name, ok := v.(string); ok { 522 | if add.Name != "" { 523 | ret = append(ret, add) 524 | add = Bucket{} 525 | } 526 | add.Name = name 527 | } else { 528 | js, errMarshal := json.Marshal(v) 529 | if errMarshal != nil { 530 | return nil, errMarshal 531 | } 532 | if add.Stats != nil { 533 | ret = append(ret, add) 534 | add = Bucket{} 535 | } 536 | add.Stats = new(Stats) 537 | err = json.Unmarshal(js, add.Stats) 538 | if err != nil { 539 | return nil, err 540 | } 541 | } 542 | } 543 | ret = append(ret, add) 544 | } else { 545 | add := Bucket{} 546 | add.Stats = new(Stats) 547 | err = json.Unmarshal(body, add.Stats) 548 | if err != nil { 549 | return nil, err 550 | } 551 | ret = append(ret, add) 552 | } 553 | return ret, nil 554 | } 555 | 556 | // RemoveBucket removes an existing bucket. 557 | // 558 | // !! caps: buckets=write !! 559 | // 560 | //@Bucket 561 | //@PurgeObjects 562 | // 563 | func (api *API) RemoveBucket(conf BucketConfig) error { 564 | var ( 565 | values = url.Values{} 566 | errs []error 567 | ) 568 | 569 | if conf.Bucket == "" { 570 | return errors.New("Bucket field is required") 571 | } 572 | values, errs = encurl.Translate(conf) 573 | if len(errs) > 0 { 574 | return errs[0] 575 | } 576 | values.Add("format", "json") 577 | _, _, err := api.call("DELETE", "/bucket", values, true) 578 | return err 579 | } 580 | 581 | // UnlinkBucket unlinks a bucket from a specified user. 582 | // Primarily useful for changing bucket ownership. 583 | // 584 | // !! caps: buckets=write !! 585 | // 586 | //@Bucket 587 | //@UID 588 | // 589 | func (api *API) UnlinkBucket(conf BucketConfig) error { 590 | var ( 591 | values = url.Values{} 592 | errs []error 593 | ) 594 | 595 | if conf.Bucket == "" { 596 | return errors.New("Bucket field is required") 597 | } 598 | if conf.UID == "" { 599 | return errors.New("UID field is required") 600 | } 601 | values, errs = encurl.Translate(conf) 602 | if len(errs) > 0 { 603 | return errs[0] 604 | } 605 | values.Add("format", "json") 606 | _, _, err := api.call("POST", "/bucket", values, true) 607 | return err 608 | } 609 | 610 | // CheckBucket checks the index of an existing bucket. 611 | // NOTE: to check multipart object accounting with check-objects, fix must be set to True. 612 | // 613 | // !! caps: buckets=write !! 614 | // 615 | //@Bucket 616 | //@CheckObjects 617 | //@Fix 618 | // 619 | func (api *API) CheckBucket(conf BucketConfig) (string, error) { 620 | var ( 621 | values = url.Values{} 622 | errs []error 623 | ) 624 | 625 | if conf.Bucket == "" { 626 | return "", errors.New("Bucket field is required") 627 | } 628 | values, errs = encurl.Translate(conf) 629 | if len(errs) > 0 { 630 | return "", errs[0] 631 | } 632 | values.Add("format", "json") 633 | body, _, err := api.call("GET", "/bucket", values, true, "index") 634 | return string(body), err 635 | } 636 | 637 | // LinkBucket links a bucket to a specified user, unlinking the bucket from any previous user. 638 | // 639 | // !! caps: buckets=write !! 640 | // 641 | //@Bucket 642 | //@UID 643 | // 644 | func (api *API) LinkBucket(conf BucketConfig) error { 645 | var ( 646 | values = url.Values{} 647 | errs []error 648 | ) 649 | 650 | // FIXME doesn't work 651 | return fmt.Errorf("LinkBucket not implemented yet") 652 | if conf.Bucket == "" { 653 | return errors.New("Bucket field is required") 654 | } 655 | if conf.UID == "" { 656 | return errors.New("UID field is required") 657 | } 658 | values, errs = encurl.Translate(conf) 659 | if len(errs) > 0 { 660 | return errs[0] 661 | } 662 | values.Add("format", "json") 663 | body, _, err := api.call("PUT", "/bucket", values, true) 664 | // return string(body), err 665 | _ = body 666 | return err 667 | } 668 | 669 | // RemoveObject removes an existing object. NOTE: Does not require owner to be non-suspended. 670 | // 671 | // !! caps: buckets=write !! 672 | // 673 | //@Bucket 674 | //@Object 675 | // 676 | func (api *API) RemoveObject(conf BucketConfig) error { 677 | var ( 678 | values = url.Values{} 679 | errs []error 680 | ) 681 | 682 | if conf.Bucket == "" { 683 | return errors.New("Bucket field is required") 684 | } 685 | if conf.Object == "" { 686 | return errors.New("Object field is required") 687 | } 688 | values, errs = encurl.Translate(conf) 689 | if len(errs) > 0 { 690 | return errs[0] 691 | } 692 | values.Add("format", "json") 693 | _, _, err := api.call("DELETE", "/bucket", values, true, "object") 694 | return err 695 | } 696 | 697 | // GetBucketPolicy reads the bucket policy 698 | // 699 | // !! caps: buckets=read !! 700 | // 701 | //@Bucket 702 | // 703 | func (api *API) GetBucketPolicy(conf BucketConfig) (*Policy, error) { 704 | var ( 705 | ret = &Policy{} 706 | values = url.Values{} 707 | errs []error 708 | ) 709 | 710 | if conf.Bucket == "" { 711 | return nil, errors.New("Bucket field is required") 712 | } 713 | values, errs = encurl.Translate(conf) 714 | if len(errs) > 0 { 715 | return nil, errs[0] 716 | } 717 | values.Add("format", "json") 718 | body, _, err := api.call("GET", "/bucket", values, true, "policy") 719 | if err = json.Unmarshal(body, &ret); err != nil { 720 | return nil, err 721 | } 722 | return ret, err 723 | 724 | } 725 | 726 | // GetObjectPolicy reads the object policy 727 | // 728 | // !! caps: buckets=read !! 729 | // 730 | //@Bucket 731 | //@Object 732 | // 733 | func (api *API) GetObjectPolicy(conf BucketConfig) (*Policy, error) { 734 | var ( 735 | ret = &Policy{} 736 | values = url.Values{} 737 | errs []error 738 | ) 739 | 740 | if conf.Bucket == "" { 741 | return nil, errors.New("Bucket field is required") 742 | } 743 | if conf.Object == "" { 744 | return nil, errors.New("Object field is required") 745 | } 746 | values, errs = encurl.Translate(conf) 747 | if len(errs) > 0 { 748 | return nil, errs[0] 749 | } 750 | values.Add("format", "json") 751 | body, _, err := api.call("GET", "/bucket", values, true, "policy") 752 | if err = json.Unmarshal(body, &ret); err != nil { 753 | return nil, err 754 | } 755 | return ret, err 756 | 757 | } 758 | 759 | // QuotaConfig quota request 760 | type QuotaConfig struct { 761 | UID string `url:"uid,ifStringIsNotEmpty"` // The user to specify a quota 762 | Bucket string `url:"bucket,ifStringIsNotEmpty"` // The bucket name 763 | MaxObjects string `url:"max-objects,ifStringIsNotEmpty"` // The max-objects setting allows you to specify the maximum number of objects. A negative value disables this setting. 764 | MaxSizeKB string `url:"max-size-kb,ifStringIsNotEmpty"` // The max-size-kb option allows you to specify a quota for the maximum number of bytes. A negative value disables this setting 765 | Enabled string `url:"enabled,ifStringIsNotEmpty"` // The enabled option enables the quotas 766 | QuotaType string `url:"quota-type,ifStringIsNotEmpty"` // The quota-type option sets the scope for the quota. The options are bucket and user. 767 | } 768 | 769 | // GetQuotas returns user's quotas 770 | // 771 | // !! caps: users=read !! 772 | // 773 | //@UID 774 | //@QuotaType 775 | // 776 | func (api *API) GetQuotas(conf QuotaConfig) (*Quotas, error) { 777 | var ( 778 | ret = &Quotas{} 779 | values = url.Values{} 780 | errs []error 781 | ) 782 | 783 | if conf.UID == "" { 784 | return nil, errors.New("UID field is required") 785 | } 786 | 787 | values, errs = encurl.Translate(conf) 788 | if len(errs) > 0 { 789 | return nil, errs[0] 790 | } 791 | values.Add("format", "json") 792 | body, _, err := api.call("GET", "/user", values, true, "quota") 793 | if err = json.Unmarshal(body, &ret); err != nil { 794 | return nil, err 795 | } 796 | return ret, err 797 | 798 | } 799 | 800 | // UpdateQuota updates user's quotas 801 | // 802 | // !! caps: users=write !! 803 | // 804 | //@UID 805 | //@Quota [user,bucket] 806 | // 807 | func (api *API) UpdateQuota(conf QuotaConfig) error { 808 | var ( 809 | values = url.Values{} 810 | errs []error 811 | ) 812 | 813 | if conf.UID == "" { 814 | return errors.New("UID field is required") 815 | } 816 | if conf.QuotaType == "" { 817 | return errors.New("QuotaType field is required") 818 | } 819 | values, errs = encurl.Translate(conf) 820 | if len(errs) > 0 { 821 | return errs[0] 822 | } 823 | values.Add("format", "json") 824 | _, _, err := api.call("PUT", "/user", values, true, "quota") 825 | return err 826 | } 827 | 828 | // UpdateBuckQuota updates individual bucket's quotas 829 | // 830 | // !! caps: buckets=write !! 831 | // 832 | //@Bucket 833 | //@Quota [bucket] 834 | // 835 | func (api *API) UpdateBuckQuota(conf QuotaConfig) error { 836 | var ( 837 | values = url.Values{} 838 | errs []error 839 | ) 840 | 841 | if conf.Bucket == "" { 842 | return errors.New("Bucket field is required") 843 | } 844 | if conf.UID == "" { 845 | return errors.New("UID field is required") 846 | } 847 | values, errs = encurl.Translate(conf) 848 | if len(errs) > 0 { 849 | return errs[0] 850 | } 851 | values.Add("format", "json") 852 | _, _, err := api.call("PUT", "/bucket", values, true, "quota") 853 | return err 854 | } 855 | 856 | // CapConfig capability request 857 | type CapConfig struct { 858 | UID string `url:"uid,ifStringIsNotEmpty"` // The user ID 859 | UserCaps string `url:"user-caps,ifStringIsNotEmpty"` // The administrative capabilities 860 | } 861 | 862 | // AddCapability returns user's quotas 863 | // 864 | // !! caps: users=write !! 865 | // 866 | //@UID 867 | //@UserCaps 868 | // 869 | func (api *API) AddCapability(conf CapConfig) ([]Capability, error) { 870 | var ( 871 | values = url.Values{} 872 | ret = []Capability{} 873 | errs []error 874 | ) 875 | 876 | if conf.UID == "" { 877 | return nil, errors.New("UID field is required") 878 | } 879 | if conf.UserCaps == "" { 880 | return nil, errors.New("UserCaps field is required") 881 | } 882 | 883 | values, errs = encurl.Translate(conf) 884 | if len(errs) > 0 { 885 | return nil, errs[0] 886 | } 887 | values.Add("format", "json") 888 | body, _, err := api.call("PUT", "/user", values, true, "caps") 889 | if err = json.Unmarshal(body, &ret); err != nil { 890 | return nil, err 891 | } 892 | return ret, err 893 | } 894 | 895 | // DelCapability returns user's quotas 896 | // 897 | // !! caps: users=write !! 898 | // 899 | //@UID 900 | //@UserCaps 901 | // 902 | func (api *API) DelCapability(conf CapConfig) ([]Capability, error) { 903 | var ( 904 | values = url.Values{} 905 | ret = []Capability{} 906 | errs []error 907 | ) 908 | 909 | if conf.UID == "" { 910 | return nil, errors.New("UID field is required") 911 | } 912 | if conf.UserCaps == "" { 913 | return nil, errors.New("UserCaps field is required") 914 | } 915 | 916 | values, errs = encurl.Translate(conf) 917 | if len(errs) > 0 { 918 | return nil, errs[0] 919 | } 920 | values.Add("format", "json") 921 | body, _, err := api.call("DELETE", "/user", values, true, "caps") 922 | if err = json.Unmarshal(body, &ret); err != nil { 923 | return nil, err 924 | } 925 | return ret, err 926 | } 927 | -------------------------------------------------------------------------------- /pkg/api/helpers_test.go: -------------------------------------------------------------------------------- 1 | package radosAPI 2 | 3 | import ( 4 | "bytes" 5 | "os" 6 | "testing" 7 | "time" 8 | 9 | "strings" 10 | 11 | minio "github.com/minio/minio-go" 12 | . "github.com/smartystreets/goconvey/convey" 13 | ) 14 | 15 | var ( 16 | Url = os.Getenv("RADOSGW_API") 17 | Access = os.Getenv("RADOSGW_ACCESS") // USER WITH ALL CAPABILITIES 18 | Secret = os.Getenv("RADOSGW_SECRET") 19 | ) 20 | 21 | func createNewAPI() *API { 22 | api, err := New(Url, Access, Secret) 23 | if err != nil { 24 | panic(err) 25 | } 26 | return api 27 | } 28 | 29 | func TestAPI(t *testing.T) { 30 | Convey("Testing New API", t, func() { 31 | api := createNewAPI() 32 | 33 | So(api, ShouldNotBeNil) 34 | }) 35 | 36 | Convey("Testing New API with prefix", t, func() { 37 | api, err := New(Url, Access, Secret, "adminEndpoint") 38 | if err != nil { 39 | panic(err) 40 | } 41 | So(api, ShouldNotBeNil) 42 | }) 43 | 44 | Convey("Testing New API without arguments", t, func() { 45 | api, err := New("", "", "") 46 | So(api, ShouldBeNil) 47 | So(err, ShouldNotBeNil) 48 | }) 49 | } 50 | 51 | func TestUser(t *testing.T) { 52 | Convey("Testing Create user", t, func() { 53 | api := createNewAPI() 54 | 55 | user, err := api.CreateUser(UserConfig{ 56 | UID: "UnitTest", 57 | DisplayName: "Unit Test", 58 | }) 59 | So(err, ShouldBeNil) 60 | So(user, ShouldNotBeNil) 61 | So(user.DisplayName, ShouldEqual, "Unit Test") 62 | }) 63 | 64 | Convey("Testing Create user without UID", t, func() { 65 | api := createNewAPI() 66 | 67 | user, err := api.CreateUser(UserConfig{ 68 | DisplayName: "Unit Test", 69 | }) 70 | So(err, ShouldNotBeNil) 71 | So(user, ShouldBeNil) 72 | }) 73 | 74 | Convey("Testing Create user without name", t, func() { 75 | api := createNewAPI() 76 | 77 | user, err := api.CreateUser(UserConfig{ 78 | UID: "UnitTest", 79 | }) 80 | So(err, ShouldNotBeNil) 81 | So(user, ShouldBeNil) 82 | }) 83 | 84 | Convey("Testing Get user", t, func() { 85 | api := createNewAPI() 86 | 87 | user, err := api.GetUser("UnitTest") 88 | So(err, ShouldBeNil) 89 | So(user, ShouldNotBeNil) 90 | }) 91 | 92 | Convey("Testing Get UIDs", t, func() { 93 | api := createNewAPI() 94 | 95 | uids, err := api.GetUIDs() 96 | So(err, ShouldBeNil) 97 | So(uids, ShouldContain, "UnitTest") 98 | }) 99 | 100 | Convey("Testing Get users", t, func() { 101 | api := createNewAPI() 102 | 103 | user, err := api.GetUser("UnitTest") 104 | So(err, ShouldBeNil) 105 | So(user, ShouldNotBeNil) 106 | 107 | users, err := api.GetUsers() 108 | So(err, ShouldBeNil) 109 | So(users, ShouldContain, user) 110 | }) 111 | 112 | Convey("Testing Update user", t, func() { 113 | api := createNewAPI() 114 | 115 | user, err := api.UpdateUser(UserConfig{ 116 | UID: "UnitTest", 117 | Email: "UnitTest@test.com", 118 | }) 119 | So(err, ShouldBeNil) 120 | So(user, ShouldNotBeNil) 121 | So(user.Email, ShouldEqual, "unittest@test.com") 122 | }) 123 | 124 | Convey("Testing Update user without UID", t, func() { 125 | api := createNewAPI() 126 | 127 | user, err := api.UpdateUser(UserConfig{ 128 | Email: "UnitTest@test.com", 129 | }) 130 | So(err, ShouldNotBeNil) 131 | So(user, ShouldBeNil) 132 | }) 133 | 134 | Convey("Testing Remove user", t, func() { 135 | api := createNewAPI() 136 | 137 | err := api.RemoveUser(UserConfig{ 138 | UID: "UnitTest", 139 | PurgeData: true, 140 | }) 141 | So(err, ShouldBeNil) 142 | user, err := api.GetUser("UnitTest") 143 | So(err, ShouldNotBeNil) 144 | So(user, ShouldBeNil) 145 | }) 146 | 147 | Convey("Testing Remove user without UID", t, func() { 148 | api := createNewAPI() 149 | 150 | err := api.RemoveUser(UserConfig{ 151 | PurgeData: true, 152 | }) 153 | So(err, ShouldNotBeNil) 154 | }) 155 | } 156 | 157 | func TestUsage(t *testing.T) { 158 | Convey("Testing Get Usage with empty struct", t, func() { 159 | api := createNewAPI() 160 | 161 | usage, err := api.GetUsage(UsageConfig{}) 162 | So(err, ShouldBeNil) 163 | So(usage, ShouldNotBeNil) 164 | So(usage.Entries, ShouldBeNil) 165 | So(usage.Summary, ShouldBeNil) 166 | }) 167 | 168 | Convey("Testing Get Usage summary field", t, func() { 169 | api := createNewAPI() 170 | 171 | usage, err := api.GetUsage(UsageConfig{ 172 | ShowSummary: true, 173 | }) 174 | So(err, ShouldBeNil) 175 | So(usage, ShouldNotBeNil) 176 | So(usage.Entries, ShouldBeNil) 177 | So(usage.Summary, ShouldNotBeNil) 178 | }) 179 | 180 | Convey("Testing Get Usage entries field", t, func() { 181 | api := createNewAPI() 182 | 183 | usage, err := api.GetUsage(UsageConfig{ 184 | ShowEntries: true, 185 | }) 186 | So(err, ShouldBeNil) 187 | So(usage, ShouldNotBeNil) 188 | So(usage.Entries, ShouldNotBeNil) 189 | So(usage.Summary, ShouldBeNil) 190 | }) 191 | 192 | Convey("Testing Get Usage entries/summary field", t, func() { 193 | api := createNewAPI() 194 | 195 | usage, err := api.GetUsage(UsageConfig{ 196 | ShowEntries: true, 197 | ShowSummary: true, 198 | }) 199 | So(err, ShouldBeNil) 200 | So(usage, ShouldNotBeNil) 201 | So(usage.Entries, ShouldNotBeNil) 202 | So(usage.Summary, ShouldNotBeNil) 203 | }) 204 | 205 | Convey("Testing Get Usage entries/summary field with specified uid", t, func() { 206 | api := createNewAPI() 207 | 208 | usage, err := api.GetUsage(UsageConfig{ 209 | UID: "UnitTest", 210 | ShowEntries: true, 211 | ShowSummary: true, 212 | }) 213 | So(err, ShouldBeNil) 214 | So(usage, ShouldNotBeNil) 215 | So(usage.Entries, ShouldNotBeNil) 216 | So(usage.Summary, ShouldNotBeNil) 217 | }) 218 | 219 | Convey("Testing Get Usage entries/summary field with specified uid, and start Time", t, func() { 220 | api := createNewAPI() 221 | 222 | now := time.Now().AddDate(0, 0, -1) 223 | usage, err := api.GetUsage(UsageConfig{ 224 | UID: "UnitTest", 225 | ShowEntries: true, 226 | ShowSummary: true, 227 | Start: &now, 228 | }) 229 | So(err, ShouldBeNil) 230 | So(usage, ShouldNotBeNil) 231 | So(usage.Entries, ShouldNotBeNil) 232 | So(usage.Summary, ShouldNotBeNil) 233 | }) 234 | 235 | Convey("Testing Delete all usages", t, func() { 236 | api := createNewAPI() 237 | 238 | err := api.DeleteUsage(UsageConfig{ 239 | UID: "UnitTest", 240 | RemoveAll: true, 241 | }) 242 | So(err, ShouldBeNil) 243 | }) 244 | } 245 | 246 | func TestKey(t *testing.T) { 247 | Convey("Testing Create Key", t, func() { 248 | api := createNewAPI() 249 | 250 | user, err := api.CreateUser(UserConfig{ 251 | UID: "UnitTest", 252 | DisplayName: "Unit Test", 253 | }) 254 | So(err, ShouldBeNil) 255 | So(user, ShouldNotBeNil) 256 | So(user.DisplayName, ShouldEqual, "Unit Test") 257 | 258 | keys, err := api.CreateKey(KeyConfig{ 259 | UID: "UnitTest", 260 | }) 261 | So(err, ShouldBeNil) 262 | So(keys, ShouldNotBeNil) 263 | 264 | err = api.RemoveUser(UserConfig{ 265 | UID: "UnitTest", 266 | PurgeData: true, 267 | }) 268 | So(err, ShouldBeNil) 269 | }) 270 | 271 | Convey("Testing Create Key without UID", t, func() { 272 | api := createNewAPI() 273 | 274 | user, err := api.CreateUser(UserConfig{ 275 | UID: "UnitTest", 276 | DisplayName: "Unit Test", 277 | }) 278 | So(err, ShouldBeNil) 279 | So(user, ShouldNotBeNil) 280 | So(user.DisplayName, ShouldEqual, "Unit Test") 281 | 282 | keys, err := api.CreateKey(KeyConfig{}) 283 | So(err, ShouldNotBeNil) 284 | So(keys, ShouldBeNil) 285 | 286 | err = api.RemoveUser(UserConfig{ 287 | UID: "UnitTest", 288 | PurgeData: true, 289 | }) 290 | So(err, ShouldBeNil) 291 | }) 292 | 293 | Convey("Testing Remove Key", t, func() { 294 | api := createNewAPI() 295 | 296 | user, err := api.CreateUser(UserConfig{ 297 | UID: "UnitTest", 298 | DisplayName: "Unit Test", 299 | }) 300 | So(err, ShouldBeNil) 301 | So(user, ShouldNotBeNil) 302 | So(user.DisplayName, ShouldEqual, "Unit Test") 303 | 304 | keys, err := api.CreateKey(KeyConfig{ 305 | UID: "UnitTest", 306 | }) 307 | So(err, ShouldBeNil) 308 | So(keys, ShouldNotBeNil) 309 | 310 | err = api.RemoveKey(KeyConfig{ 311 | AccessKey: (*keys)[0].AccessKey, 312 | }) 313 | 314 | So(err, ShouldBeNil) 315 | 316 | err = api.RemoveUser(UserConfig{ 317 | UID: "UnitTest", 318 | PurgeData: true, 319 | }) 320 | So(err, ShouldBeNil) 321 | }) 322 | 323 | Convey("Testing Remove Key without access key", t, func() { 324 | api := createNewAPI() 325 | 326 | user, err := api.CreateUser(UserConfig{ 327 | UID: "UnitTest", 328 | DisplayName: "Unit Test", 329 | }) 330 | So(err, ShouldBeNil) 331 | So(user, ShouldNotBeNil) 332 | So(user.DisplayName, ShouldEqual, "Unit Test") 333 | 334 | keys, err := api.CreateKey(KeyConfig{ 335 | UID: "UnitTest", 336 | }) 337 | So(err, ShouldBeNil) 338 | So(keys, ShouldNotBeNil) 339 | 340 | err = api.RemoveKey(KeyConfig{}) 341 | 342 | So(err, ShouldNotBeNil) 343 | 344 | err = api.RemoveUser(UserConfig{ 345 | UID: "UnitTest", 346 | PurgeData: true, 347 | }) 348 | So(err, ShouldBeNil) 349 | }) 350 | } 351 | 352 | func TestSubUser(t *testing.T) { 353 | Convey("Testing CreateSubUser", t, func() { 354 | api := createNewAPI() 355 | 356 | user, err := api.CreateUser(UserConfig{ 357 | UID: "UnitTest", 358 | DisplayName: "Unit Test", 359 | }) 360 | So(err, ShouldBeNil) 361 | So(user, ShouldNotBeNil) 362 | So(user.DisplayName, ShouldEqual, "Unit Test") 363 | 364 | sub, err := api.CreateSubUser(SubUserConfig{ 365 | UID: "UnitTest", 366 | SubUser: "SubUnitTest", 367 | }) 368 | So(err, ShouldBeNil) 369 | So(sub, ShouldNotBeNil) 370 | 371 | err = api.RemoveUser(UserConfig{ 372 | UID: "UnitTest", 373 | PurgeData: true, 374 | }) 375 | So(err, ShouldBeNil) 376 | }) 377 | 378 | Convey("Testing CreateSubUser without UID", t, func() { 379 | api := createNewAPI() 380 | 381 | user, err := api.CreateUser(UserConfig{ 382 | UID: "UnitTest", 383 | DisplayName: "Unit Test", 384 | }) 385 | So(err, ShouldBeNil) 386 | So(user, ShouldNotBeNil) 387 | So(user.DisplayName, ShouldEqual, "Unit Test") 388 | 389 | sub, err := api.CreateSubUser(SubUserConfig{ 390 | SubUser: "SubUnitTest", 391 | }) 392 | So(err, ShouldNotBeNil) 393 | So(sub, ShouldBeNil) 394 | 395 | err = api.RemoveUser(UserConfig{ 396 | UID: "UnitTest", 397 | PurgeData: true, 398 | }) 399 | So(err, ShouldBeNil) 400 | }) 401 | 402 | Convey("Testing UpdateSubUser", t, func() { 403 | api := createNewAPI() 404 | 405 | user, err := api.CreateUser(UserConfig{ 406 | UID: "UnitTest", 407 | DisplayName: "Unit Test", 408 | }) 409 | So(err, ShouldBeNil) 410 | So(user, ShouldNotBeNil) 411 | So(user.DisplayName, ShouldEqual, "Unit Test") 412 | 413 | sub, err := api.CreateSubUser(SubUserConfig{ 414 | UID: "UnitTest", 415 | SubUser: "SubUnitTest", 416 | }) 417 | So(err, ShouldBeNil) 418 | So(sub, ShouldNotBeNil) 419 | 420 | sub, err = api.UpdateSubUser(SubUserConfig{ 421 | UID: "UnitTest", 422 | SubUser: (*sub)[0].ID, 423 | }) 424 | So(err, ShouldBeNil) 425 | So(sub, ShouldNotBeNil) 426 | 427 | err = api.RemoveUser(UserConfig{ 428 | UID: "UnitTest", 429 | PurgeData: true, 430 | }) 431 | So(err, ShouldBeNil) 432 | }) 433 | 434 | Convey("Testing UpdateSubUser without UID", t, func() { 435 | api := createNewAPI() 436 | 437 | user, err := api.CreateUser(UserConfig{ 438 | UID: "UnitTest", 439 | DisplayName: "Unit Test", 440 | }) 441 | So(err, ShouldBeNil) 442 | So(user, ShouldNotBeNil) 443 | So(user.DisplayName, ShouldEqual, "Unit Test") 444 | 445 | sub, err := api.CreateSubUser(SubUserConfig{ 446 | UID: "UnitTest", 447 | SubUser: "SubUnitTest", 448 | }) 449 | So(err, ShouldBeNil) 450 | So(sub, ShouldNotBeNil) 451 | 452 | sub, err = api.UpdateSubUser(SubUserConfig{ 453 | SubUser: (*sub)[0].ID, 454 | }) 455 | So(err, ShouldNotBeNil) 456 | So(sub, ShouldBeNil) 457 | 458 | err = api.RemoveUser(UserConfig{ 459 | UID: "UnitTest", 460 | PurgeData: true, 461 | }) 462 | So(err, ShouldBeNil) 463 | }) 464 | 465 | Convey("Testing UpdateSubUser without SubUID", t, func() { 466 | api := createNewAPI() 467 | 468 | user, err := api.CreateUser(UserConfig{ 469 | UID: "UnitTest", 470 | DisplayName: "Unit Test", 471 | }) 472 | So(err, ShouldBeNil) 473 | So(user, ShouldNotBeNil) 474 | So(user.DisplayName, ShouldEqual, "Unit Test") 475 | 476 | sub, err := api.CreateSubUser(SubUserConfig{ 477 | UID: "UnitTest", 478 | SubUser: "SubUnitTest", 479 | }) 480 | So(err, ShouldBeNil) 481 | So(sub, ShouldNotBeNil) 482 | 483 | sub, err = api.UpdateSubUser(SubUserConfig{ 484 | UID: "UnitTest", 485 | }) 486 | So(err, ShouldNotBeNil) 487 | So(sub, ShouldBeNil) 488 | 489 | err = api.RemoveUser(UserConfig{ 490 | UID: "UnitTest", 491 | PurgeData: true, 492 | }) 493 | So(err, ShouldBeNil) 494 | }) 495 | 496 | Convey("Testing RemoveSubUser", t, func() { 497 | api := createNewAPI() 498 | 499 | user, err := api.CreateUser(UserConfig{ 500 | UID: "UnitTest", 501 | DisplayName: "Unit Test", 502 | }) 503 | So(err, ShouldBeNil) 504 | So(user, ShouldNotBeNil) 505 | So(user.DisplayName, ShouldEqual, "Unit Test") 506 | 507 | sub, err := api.CreateSubUser(SubUserConfig{ 508 | UID: "UnitTest", 509 | SubUser: "SubUnitTest", 510 | }) 511 | So(err, ShouldBeNil) 512 | So(sub, ShouldNotBeNil) 513 | 514 | err = api.RemoveSubUser(SubUserConfig{ 515 | UID: "UnitTest", 516 | SubUser: (*sub)[0].ID, 517 | }) 518 | So(err, ShouldBeNil) 519 | 520 | err = api.RemoveUser(UserConfig{ 521 | UID: "UnitTest", 522 | PurgeData: true, 523 | }) 524 | So(err, ShouldBeNil) 525 | }) 526 | Convey("Testing RemoveSubUser without UID", t, func() { 527 | api := createNewAPI() 528 | 529 | user, err := api.CreateUser(UserConfig{ 530 | UID: "UnitTest", 531 | DisplayName: "Unit Test", 532 | }) 533 | So(err, ShouldBeNil) 534 | So(user, ShouldNotBeNil) 535 | So(user.DisplayName, ShouldEqual, "Unit Test") 536 | 537 | sub, err := api.CreateSubUser(SubUserConfig{ 538 | UID: "UnitTest", 539 | SubUser: "SubUnitTest", 540 | }) 541 | So(err, ShouldBeNil) 542 | So(sub, ShouldNotBeNil) 543 | 544 | err = api.RemoveSubUser(SubUserConfig{ 545 | SubUser: (*sub)[0].ID, 546 | }) 547 | So(err, ShouldNotBeNil) 548 | 549 | err = api.RemoveUser(UserConfig{ 550 | UID: "UnitTest", 551 | PurgeData: true, 552 | }) 553 | So(err, ShouldBeNil) 554 | }) 555 | 556 | Convey("Testing RemoveSubUser without SubUID", t, func() { 557 | api := createNewAPI() 558 | 559 | user, err := api.CreateUser(UserConfig{ 560 | UID: "UnitTest", 561 | DisplayName: "Unit Test", 562 | }) 563 | So(err, ShouldBeNil) 564 | So(user, ShouldNotBeNil) 565 | So(user.DisplayName, ShouldEqual, "Unit Test") 566 | 567 | sub, err := api.CreateSubUser(SubUserConfig{ 568 | UID: "UnitTest", 569 | SubUser: "SubUnitTest", 570 | }) 571 | So(err, ShouldBeNil) 572 | So(sub, ShouldNotBeNil) 573 | 574 | err = api.RemoveSubUser(SubUserConfig{ 575 | UID: "UnitTest", 576 | }) 577 | So(err, ShouldNotBeNil) 578 | 579 | err = api.RemoveUser(UserConfig{ 580 | UID: "UnitTest", 581 | PurgeData: true, 582 | }) 583 | So(err, ShouldBeNil) 584 | }) 585 | } 586 | 587 | func TestBucket(t *testing.T) { 588 | Convey("Testing Get Bucket with stats", t, func() { 589 | api := createNewAPI() 590 | 591 | url := "" 592 | useSSL := false 593 | if strings.HasPrefix(Url, "http://") { 594 | url = Url[7:] 595 | } else if strings.HasPrefix(Url, "https://") { 596 | url = Url[8:] 597 | useSSL = true 598 | } 599 | 600 | user, err := api.CreateUser(UserConfig{ 601 | UID: "UnitTest", 602 | DisplayName: "Unit Test", 603 | }) 604 | So(err, ShouldBeNil) 605 | So(user, ShouldNotBeNil) 606 | 607 | defer func() { 608 | err = api.RemoveUser(UserConfig{ 609 | UID: "UnitTest", 610 | PurgeData: true, 611 | }) 612 | So(err, ShouldBeNil) 613 | }() 614 | 615 | minioClient, err := minio.New(url, user.Keys[0].AccessKey, user.Keys[0].SecretKey, useSSL) 616 | So(err, ShouldBeNil) 617 | err = minioClient.MakeBucket("unittestbucket", "") 618 | So(err, ShouldBeNil) 619 | 620 | buckets, err := api.GetBucket(BucketConfig{ 621 | UID: "UnitTest", 622 | Stats: true, 623 | }) 624 | So(err, ShouldBeNil) 625 | So(buckets, ShouldNotBeNil) 626 | }) 627 | 628 | Convey("Testing Get Bucket without stats", t, func() { 629 | api := createNewAPI() 630 | 631 | url := "" 632 | useSSL := false 633 | if strings.HasPrefix(Url, "http://") { 634 | url = Url[7:] 635 | } else if strings.HasPrefix(Url, "https://") { 636 | url = Url[8:] 637 | useSSL = true 638 | } 639 | 640 | user, err := api.CreateUser(UserConfig{ 641 | UID: "UnitTest", 642 | DisplayName: "Unit Test", 643 | }) 644 | So(err, ShouldBeNil) 645 | So(user, ShouldNotBeNil) 646 | 647 | defer func() { 648 | err = api.RemoveUser(UserConfig{ 649 | UID: "UnitTest", 650 | PurgeData: true, 651 | }) 652 | So(err, ShouldBeNil) 653 | }() 654 | 655 | minioClient, err := minio.New(url, user.Keys[0].AccessKey, user.Keys[0].SecretKey, useSSL) 656 | So(err, ShouldBeNil) 657 | err = minioClient.MakeBucket("unittestbucket", "") 658 | So(err, ShouldBeNil) 659 | 660 | buckets, err := api.GetBucket(BucketConfig{ 661 | UID: "UnitTest", 662 | }) 663 | So(err, ShouldBeNil) 664 | So(buckets, ShouldNotBeNil) 665 | }) 666 | 667 | Convey("Testing Remove Bucket", t, func() { 668 | api := createNewAPI() 669 | 670 | url := "" 671 | useSSL := false 672 | if strings.HasPrefix(Url, "http://") { 673 | url = Url[7:] 674 | } else if strings.HasPrefix(Url, "https://") { 675 | url = Url[8:] 676 | useSSL = true 677 | } 678 | 679 | user, err := api.CreateUser(UserConfig{ 680 | UID: "UnitTest", 681 | DisplayName: "Unit Test", 682 | }) 683 | So(err, ShouldBeNil) 684 | So(user, ShouldNotBeNil) 685 | 686 | defer func() { 687 | err = api.RemoveUser(UserConfig{ 688 | UID: "UnitTest", 689 | PurgeData: true, 690 | }) 691 | So(err, ShouldBeNil) 692 | }() 693 | 694 | minioClient, err := minio.New(url, user.Keys[0].AccessKey, user.Keys[0].SecretKey, useSSL) 695 | So(err, ShouldBeNil) 696 | err = minioClient.MakeBucket("unittestbucket", "") 697 | So(err, ShouldBeNil) 698 | 699 | api.RemoveBucket(BucketConfig{ 700 | Bucket: "unittestbucket", 701 | PurgeObjects: true, 702 | }) 703 | 704 | So(err, ShouldBeNil) 705 | }) 706 | 707 | Convey("Testing Remove Bucket without bucket name", t, func() { 708 | api := createNewAPI() 709 | 710 | err := api.RemoveBucket(BucketConfig{}) 711 | 712 | So(err, ShouldNotBeNil) 713 | }) 714 | 715 | Convey("Testing Unlink Bucket without UID", t, func() { 716 | api := createNewAPI() 717 | 718 | err := api.UnlinkBucket(BucketConfig{ 719 | Bucket: "UnitTest", 720 | }) 721 | 722 | So(err, ShouldNotBeNil) 723 | }) 724 | 725 | Convey("Testing Unlink Bucket without Bucket Name", t, func() { 726 | api := createNewAPI() 727 | 728 | err := api.UnlinkBucket(BucketConfig{ 729 | UID: "unittest", 730 | }) 731 | 732 | So(err, ShouldNotBeNil) 733 | }) 734 | 735 | Convey("Testing Unlink Bucket", t, func() { 736 | api := createNewAPI() 737 | 738 | url := "" 739 | useSSL := false 740 | if strings.HasPrefix(Url, "http://") { 741 | url = Url[7:] 742 | } else if strings.HasPrefix(Url, "https://") { 743 | url = Url[8:] 744 | useSSL = true 745 | } 746 | user, err := api.CreateUser(UserConfig{ 747 | UID: "UnitTest", 748 | DisplayName: "Unit Test", 749 | }) 750 | So(err, ShouldBeNil) 751 | So(user, ShouldNotBeNil) 752 | 753 | defer func() { 754 | err = api.RemoveUser(UserConfig{ 755 | UID: "UnitTest", 756 | PurgeData: true, 757 | }) 758 | So(err, ShouldBeNil) 759 | }() 760 | 761 | minioClient, err := minio.New(url, user.Keys[0].AccessKey, user.Keys[0].SecretKey, useSSL) 762 | So(err, ShouldBeNil) 763 | err = minioClient.MakeBucket("unittestbucket", "") 764 | So(err, ShouldBeNil) 765 | err = api.UnlinkBucket(BucketConfig{ 766 | Bucket: "unittestbucket", 767 | UID: "UnitTest", 768 | }) 769 | So(err, ShouldBeNil) 770 | }) 771 | 772 | Convey("Testing Unlink Invalid Bucket", t, func() { 773 | api := createNewAPI() 774 | 775 | err := api.UnlinkBucket(BucketConfig{ 776 | Bucket: "unittestbucket", 777 | UID: "UnitTest", 778 | }) 779 | So(err, ShouldNotBeNil) 780 | }) 781 | 782 | Convey("Testing Check index without Bucket name", t, func() { 783 | api := createNewAPI() 784 | 785 | _, err := api.CheckBucket(BucketConfig{}) 786 | So(err, ShouldNotBeNil) 787 | }) 788 | 789 | Convey("Testing Check index Bucket", t, func() { 790 | api := createNewAPI() 791 | 792 | url := "" 793 | useSSL := false 794 | if strings.HasPrefix(Url, "http://") { 795 | url = Url[7:] 796 | } else if strings.HasPrefix(Url, "https://") { 797 | url = Url[8:] 798 | useSSL = true 799 | } 800 | user, err := api.CreateUser(UserConfig{ 801 | UID: "UnitTest", 802 | DisplayName: "Unit Test", 803 | }) 804 | So(err, ShouldBeNil) 805 | So(user, ShouldNotBeNil) 806 | 807 | defer func() { 808 | err = api.RemoveUser(UserConfig{ 809 | UID: "UnitTest", 810 | PurgeData: true, 811 | }) 812 | So(err, ShouldBeNil) 813 | }() 814 | 815 | minioClient, err := minio.New(url, user.Keys[0].AccessKey, user.Keys[0].SecretKey, useSSL) 816 | So(err, ShouldBeNil) 817 | err = minioClient.MakeBucket("unittestbucket", "") 818 | So(err, ShouldBeNil) 819 | index, err := api.CheckBucket(BucketConfig{ 820 | Bucket: "unittestbucket", 821 | }) 822 | So(err, ShouldBeNil) 823 | So(index, ShouldNotBeNil) 824 | }) 825 | 826 | Convey("Testing Remove Object", t, func() { 827 | api := createNewAPI() 828 | 829 | url := "" 830 | useSSL := false 831 | if strings.HasPrefix(Url, "http://") { 832 | url = Url[7:] 833 | } else if strings.HasPrefix(Url, "https://") { 834 | url = Url[8:] 835 | useSSL = true 836 | } 837 | user, err := api.CreateUser(UserConfig{ 838 | UID: "UnitTest", 839 | DisplayName: "Unit Test", 840 | }) 841 | So(err, ShouldBeNil) 842 | So(user, ShouldNotBeNil) 843 | 844 | defer func() { 845 | err = api.RemoveUser(UserConfig{ 846 | UID: "UnitTest", 847 | PurgeData: true, 848 | }) 849 | So(err, ShouldBeNil) 850 | }() 851 | 852 | minioClient, err := minio.New(url, user.Keys[0].AccessKey, user.Keys[0].SecretKey, useSSL) 853 | So(err, ShouldBeNil) 854 | err = minioClient.MakeBucket("unittestbucket", "") 855 | So(err, ShouldBeNil) 856 | b := bytes.NewBufferString("content") 857 | _, err = minioClient.PutObject("unittestbucket", "test.txt", b, int64(b.Len()), minio.PutObjectOptions{}) 858 | So(err, ShouldBeNil) 859 | 860 | err = api.RemoveObject(BucketConfig{ 861 | Bucket: "unittestbucket", 862 | Object: "test.txt", 863 | }) 864 | So(err, ShouldBeNil) 865 | }) 866 | 867 | Convey("Testing Remove Object without bucket", t, func() { 868 | api := createNewAPI() 869 | 870 | err := api.RemoveObject(BucketConfig{}) 871 | So(err, ShouldNotBeNil) 872 | }) 873 | 874 | Convey("Testing Remove Object without object", t, func() { 875 | api := createNewAPI() 876 | 877 | err := api.RemoveObject(BucketConfig{ 878 | Bucket: "unittestbucket", 879 | }) 880 | So(err, ShouldNotBeNil) 881 | }) 882 | 883 | Convey("Testing Get Bucket Policy", t, func() { 884 | api := createNewAPI() 885 | 886 | url := "" 887 | useSSL := false 888 | if strings.HasPrefix(Url, "http://") { 889 | url = Url[7:] 890 | } else if strings.HasPrefix(Url, "https://") { 891 | url = Url[8:] 892 | useSSL = true 893 | } 894 | user, err := api.CreateUser(UserConfig{ 895 | UID: "UnitTest", 896 | DisplayName: "Unit Test", 897 | }) 898 | So(err, ShouldBeNil) 899 | So(user, ShouldNotBeNil) 900 | 901 | defer func() { 902 | err = api.RemoveUser(UserConfig{ 903 | UID: "UnitTest", 904 | PurgeData: true, 905 | }) 906 | So(err, ShouldBeNil) 907 | }() 908 | 909 | minioClient, err := minio.New(url, user.Keys[0].AccessKey, user.Keys[0].SecretKey, useSSL) 910 | So(err, ShouldBeNil) 911 | err = minioClient.MakeBucket("unittestbucket", "") 912 | So(err, ShouldBeNil) 913 | 914 | policy, err := api.GetBucketPolicy(BucketConfig{}) 915 | So(err, ShouldNotBeNil) 916 | policy, err = api.GetBucketPolicy(BucketConfig{ 917 | Bucket: "unittestbucket", 918 | }) 919 | So(err, ShouldBeNil) 920 | So(policy, ShouldNotBeNil) 921 | }) 922 | 923 | Convey("Testing Get Object Policy", t, func() { 924 | api := createNewAPI() 925 | 926 | url := "" 927 | useSSL := false 928 | if strings.HasPrefix(Url, "http://") { 929 | url = Url[7:] 930 | } else if strings.HasPrefix(Url, "https://") { 931 | url = Url[8:] 932 | useSSL = true 933 | } 934 | user, err := api.CreateUser(UserConfig{ 935 | UID: "UnitTest", 936 | DisplayName: "Unit Test", 937 | }) 938 | So(err, ShouldBeNil) 939 | So(user, ShouldNotBeNil) 940 | 941 | defer func() { 942 | err = api.RemoveUser(UserConfig{ 943 | UID: "UnitTest", 944 | PurgeData: true, 945 | }) 946 | So(err, ShouldBeNil) 947 | }() 948 | 949 | minioClient, err := minio.New(url, user.Keys[0].AccessKey, user.Keys[0].SecretKey, useSSL) 950 | So(err, ShouldBeNil) 951 | err = minioClient.MakeBucket("unittestbucket", "") 952 | So(err, ShouldBeNil) 953 | b := bytes.NewBufferString("content") 954 | _, err = minioClient.PutObject("unittestbucket", "test.txt", b, int64(b.Len()), minio.PutObjectOptions{}) 955 | So(err, ShouldBeNil) 956 | 957 | policy, err := api.GetObjectPolicy(BucketConfig{}) 958 | So(err, ShouldNotBeNil) 959 | 960 | policy, err = api.GetObjectPolicy(BucketConfig{ 961 | Bucket: "unittestbucket", 962 | }) 963 | So(err, ShouldNotBeNil) 964 | 965 | policy, err = api.GetObjectPolicy(BucketConfig{ 966 | Bucket: "unittestbucket", 967 | Object: "test.txt", 968 | }) 969 | So(err, ShouldBeNil) 970 | So(policy, ShouldNotBeNil) 971 | }) 972 | } 973 | 974 | func TestQuota(t *testing.T) { 975 | Convey("Testing Quota Without arguments", t, func() { 976 | api := createNewAPI() 977 | 978 | quotas, err := api.GetQuotas(QuotaConfig{}) 979 | So(quotas, ShouldBeNil) 980 | So(err, ShouldNotBeNil) 981 | err = api.UpdateQuota(QuotaConfig{}) 982 | So(err, ShouldNotBeNil) 983 | err = api.UpdateQuota(QuotaConfig{ 984 | UID: "UnitTest", 985 | }) 986 | So(err, ShouldNotBeNil) 987 | }) 988 | 989 | Convey("Testing Get Quota", t, func() { 990 | api := createNewAPI() 991 | user, err := api.CreateUser(UserConfig{ 992 | UID: "UnitTest", 993 | DisplayName: "Unit Test", 994 | }) 995 | So(err, ShouldBeNil) 996 | So(user, ShouldNotBeNil) 997 | 998 | defer func() { 999 | err = api.RemoveUser(UserConfig{ 1000 | UID: "UnitTest", 1001 | PurgeData: true, 1002 | }) 1003 | So(err, ShouldBeNil) 1004 | }() 1005 | quotas, err := api.GetQuotas(QuotaConfig{ 1006 | UID: "UnitTest", 1007 | }) 1008 | So(err, ShouldBeNil) 1009 | So(quotas, ShouldNotBeNil) 1010 | }) 1011 | 1012 | Convey("Testing Update Quota", t, func() { 1013 | api := createNewAPI() 1014 | user, err := api.CreateUser(UserConfig{ 1015 | UID: "UnitTest", 1016 | DisplayName: "Unit Test", 1017 | }) 1018 | So(err, ShouldBeNil) 1019 | So(user, ShouldNotBeNil) 1020 | 1021 | defer func() { 1022 | err = api.RemoveUser(UserConfig{ 1023 | UID: "UnitTest", 1024 | PurgeData: true, 1025 | }) 1026 | So(err, ShouldBeNil) 1027 | }() 1028 | quotas, err := api.GetQuotas(QuotaConfig{ 1029 | UID: "UnitTest", 1030 | }) 1031 | So(err, ShouldBeNil) 1032 | So(quotas, ShouldNotBeNil) 1033 | err = api.UpdateQuota(QuotaConfig{ 1034 | UID: "UnitTest", 1035 | MaxSizeKB: "1000", 1036 | QuotaType: "user", 1037 | }) 1038 | So(err, ShouldBeNil) 1039 | quotas, err = api.GetQuotas(QuotaConfig{ 1040 | UID: "UnitTest", 1041 | }) 1042 | So(err, ShouldBeNil) 1043 | So(quotas, ShouldNotBeNil) 1044 | So(quotas.UserQuota.MaxSizeKb, ShouldEqual, 1000) 1045 | }) 1046 | 1047 | Convey("Testing Update Bucket Quota", t, func() { 1048 | api := createNewAPI() 1049 | 1050 | url := "" 1051 | useSSL := false 1052 | if strings.HasPrefix(Url, "http://") { 1053 | url = Url[7:] 1054 | } else if strings.HasPrefix(Url, "https://") { 1055 | url = Url[8:] 1056 | useSSL = true 1057 | } 1058 | 1059 | user, err := api.CreateUser(UserConfig{ 1060 | UID: "UnitTest", 1061 | DisplayName: "Unit Test", 1062 | }) 1063 | So(err, ShouldBeNil) 1064 | So(user, ShouldNotBeNil) 1065 | 1066 | defer func() { 1067 | err = api.RemoveUser(UserConfig{ 1068 | UID: "UnitTest", 1069 | PurgeData: true, 1070 | }) 1071 | So(err, ShouldBeNil) 1072 | }() 1073 | 1074 | minioClient, err := minio.New(url, user.Keys[0].AccessKey, user.Keys[0].SecretKey, useSSL) 1075 | So(err, ShouldBeNil) 1076 | err = minioClient.MakeBucket("unittestbucket", "") 1077 | So(err, ShouldBeNil) 1078 | 1079 | err = api.UpdateBuckQuota(QuotaConfig{ 1080 | UID: "UnitTest", 1081 | Bucket: "unittestbucket", 1082 | MaxSizeKB: "4096", 1083 | }) 1084 | So(err, ShouldBeNil) 1085 | 1086 | buckets, err := api.GetBucket(BucketConfig{ 1087 | UID: "UnitTest", 1088 | Bucket: "unittestbucket", 1089 | Stats: true, 1090 | }) 1091 | So(err, ShouldBeNil) 1092 | So(buckets[0].Stats.BucketQuota.MaxSizeKb, ShouldEqual, 4096) 1093 | }) 1094 | } 1095 | 1096 | func TestCapability(t *testing.T) { 1097 | Convey("Testing AddCapability Without arguments", t, func() { 1098 | api := createNewAPI() 1099 | 1100 | cap, err := api.AddCapability(CapConfig{}) 1101 | So(err, ShouldNotBeNil) 1102 | So(cap, ShouldBeNil) 1103 | cap, err = api.AddCapability(CapConfig{ 1104 | UID: "UnitTest", 1105 | }) 1106 | So(err, ShouldNotBeNil) 1107 | So(cap, ShouldBeNil) 1108 | }) 1109 | 1110 | Convey("Testing AddCapability", t, func() { 1111 | api := createNewAPI() 1112 | 1113 | user, err := api.CreateUser(UserConfig{ 1114 | UID: "UnitTest", 1115 | DisplayName: "Unit Test", 1116 | }) 1117 | So(err, ShouldBeNil) 1118 | So(user, ShouldNotBeNil) 1119 | 1120 | defer func() { 1121 | err = api.RemoveUser(UserConfig{ 1122 | UID: "UnitTest", 1123 | PurgeData: true, 1124 | }) 1125 | So(err, ShouldBeNil) 1126 | }() 1127 | 1128 | cap, err := api.AddCapability(CapConfig{ 1129 | UID: "UnitTest", 1130 | UserCaps: "usage=*", 1131 | }) 1132 | So(cap, ShouldNotBeNil) 1133 | So(err, ShouldBeNil) 1134 | found := false 1135 | for _, c := range cap { 1136 | if c.Type == "usage" && c.Perm == "*" { 1137 | found = true 1138 | } 1139 | } 1140 | So(found, ShouldEqual, true) 1141 | }) 1142 | 1143 | Convey("Testing DelCapability Without arguments", t, func() { 1144 | api := createNewAPI() 1145 | 1146 | cap, err := api.DelCapability(CapConfig{}) 1147 | So(err, ShouldNotBeNil) 1148 | So(cap, ShouldBeNil) 1149 | cap, err = api.DelCapability(CapConfig{ 1150 | UID: "UnitTest", 1151 | }) 1152 | So(err, ShouldNotBeNil) 1153 | So(cap, ShouldBeNil) 1154 | }) 1155 | 1156 | Convey("Testing DelCapability", t, func() { 1157 | api := createNewAPI() 1158 | 1159 | user, err := api.CreateUser(UserConfig{ 1160 | UID: "UnitTest", 1161 | DisplayName: "Unit Test", 1162 | }) 1163 | So(err, ShouldBeNil) 1164 | So(user, ShouldNotBeNil) 1165 | 1166 | defer func() { 1167 | err = api.RemoveUser(UserConfig{ 1168 | UID: "UnitTest", 1169 | PurgeData: true, 1170 | }) 1171 | So(err, ShouldBeNil) 1172 | }() 1173 | 1174 | cap, err := api.AddCapability(CapConfig{ 1175 | UID: "UnitTest", 1176 | UserCaps: "usage=*", 1177 | }) 1178 | So(cap, ShouldNotBeNil) 1179 | So(err, ShouldBeNil) 1180 | found := false 1181 | for _, c := range cap { 1182 | if c.Type == "usage" && c.Perm == "*" { 1183 | found = true 1184 | } 1185 | } 1186 | 1187 | So(found, ShouldEqual, true) 1188 | cap, err = api.DelCapability(CapConfig{ 1189 | UID: "UnitTest", 1190 | UserCaps: "usage=*", 1191 | }) 1192 | So(cap, ShouldNotBeNil) 1193 | So(err, ShouldBeNil) 1194 | found = false 1195 | for _, c := range cap { 1196 | if c.Type == "usage" && c.Perm == "*" { 1197 | found = true 1198 | } 1199 | } 1200 | So(found, ShouldEqual, false) 1201 | }) 1202 | } 1203 | --------------------------------------------------------------------------------