aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--account.go36
-rw-r--r--account_test.go20
-rw-r--r--container.go172
-rw-r--r--container_test.go57
-rw-r--r--errors.go7
-rw-r--r--headers.go11
-rw-r--r--headers_test.go23
7 files changed, 294 insertions, 32 deletions
diff --git a/account.go b/account.go
index e38704a..8fb4845 100644
--- a/account.go
+++ b/account.go
@@ -35,9 +35,6 @@ type Account struct {
headers *AccountHeaders
}
-////////////////////////////////////////////////////////////////////////////////
-// interface to Gophercloud, endpoint inspection and manipulation
-
var endpointURLRegexp = regexp.MustCompile(`^(.*/)v1/(.*)/$`)
//AccountFromClient takes a gophercloud.ServiceClient which wraps a Swift
@@ -84,9 +81,6 @@ func (a *Account) Client() *gophercloud.ServiceClient {
return a.client
}
-////////////////////////////////////////////////////////////////////////////////
-// account headers
-
//Headers returns the AccountHeaders for this account. If the AccountHeaders
//has not been cached yet, a HEAD request is issued on the account.
func (a *Account) Headers() (AccountHeaders, error) {
@@ -117,11 +111,11 @@ func (a *Account) Invalidate() {
a.headers = nil
}
-//Post creates or updates the account using a POST request. To set arbitrary
-//request headers (and to add URL parameters, pass a non-nil *RequestOptions.
+//Update updates the account using a POST request. To set arbitrary request
+//headers (and to add URL parameters), pass a non-nil *RequestOptions.
//
-//A successful POST request implies Invalidate() since it changes account metadata.
-func (a *Account) Post(headers AccountHeaders, opts *RequestOptions) error {
+//A successful POST request implies Invalidate() since it may change metadata.
+func (a *Account) Update(headers AccountHeaders, opts *RequestOptions) error {
_, err := Request{
Method: "POST",
Options: compileHeaders(&headers, opts),
@@ -133,5 +127,23 @@ func (a *Account) Post(headers AccountHeaders, opts *RequestOptions) error {
return err
}
-////////////////////////////////////////////////////////////////////////////////
-// container listing
+//Create creates the account using a PUT request. To set arbitrary request
+//headers (and to add URL parameters), pass a non-nil *RequestOptions.
+//
+//Note that this operation is only available to reseller admins, not to regular
+//users.
+//
+//A successful PUT request implies Invalidate() since it may change metadata.
+func (a *Account) Create(headers AccountHeaders, opts *RequestOptions) error {
+ _, err := Request{
+ Method: "POST",
+ Options: compileHeaders(&headers, opts),
+ ExpectStatusCodes: []int{201, 202},
+ }.Do(a.client)
+ if err == nil {
+ a.Invalidate()
+ }
+ return err
+}
+
+// TODO container listing
diff --git a/account_test.go b/account_test.go
index 76185d4..5515777 100644
--- a/account_test.go
+++ b/account_test.go
@@ -25,7 +25,7 @@ import (
func TestAccountBasic(t *testing.T) {
testWithAccount(t, func(a *Account) {
hdr, err := a.Headers()
- if !expectError(t, err, nil) {
+ if !expectError(t, err, "") {
t.FailNow()
}
//There are not a lot of things we can test here (besides testing that
@@ -39,18 +39,18 @@ func TestAccountBasic(t *testing.T) {
func TestAccountMetadata(t *testing.T) {
testWithAccount(t, func(a *Account) {
//test creating some metadata
- err := a.Post(AccountHeaders{
+ err := a.Update(AccountHeaders{
Metadata: NewMetadata(
"schwift-test1", "first",
"schwift-test2", "second",
),
}, nil)
- if !expectError(t, err, nil) {
+ if !expectError(t, err, "") {
t.FailNow()
}
hdr, err := a.Headers()
- if !expectError(t, err, nil) {
+ if !expectError(t, err, "") {
t.FailNow()
}
expectString(t, hdr.Metadata.Get("schwift-test1"), "first")
@@ -59,15 +59,15 @@ func TestAccountMetadata(t *testing.T) {
//test deleting some metadata
m := make(Metadata)
m.Clear("schwift-test1")
- err = a.Post(AccountHeaders{
+ err = a.Update(AccountHeaders{
Metadata: m,
}, nil)
- if !expectError(t, err, nil) {
+ if !expectError(t, err, "") {
t.FailNow()
}
hdr, err = a.Headers()
- if !expectError(t, err, nil) {
+ if !expectError(t, err, "") {
t.FailNow()
}
expectString(t, hdr.Metadata.Get("schwift-test1"), "")
@@ -76,15 +76,15 @@ func TestAccountMetadata(t *testing.T) {
//test updating some metadata
m = make(Metadata)
m.Set("schwift-test2", "changed")
- err = a.Post(AccountHeaders{
+ err = a.Update(AccountHeaders{
Metadata: m,
}, nil)
- if !expectError(t, err, nil) {
+ if !expectError(t, err, "") {
t.FailNow()
}
hdr, err = a.Headers()
- if !expectError(t, err, nil) {
+ if !expectError(t, err, "") {
t.FailNow()
}
expectString(t, hdr.Metadata.Get("schwift-test1"), "")
diff --git a/container.go b/container.go
new file mode 100644
index 0000000..9fcfe2b
--- /dev/null
+++ b/container.go
@@ -0,0 +1,172 @@
+/******************************************************************************
+*
+* Copyright 2018 Stefan Majewsky <majewsky@gmx.net>
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*
+******************************************************************************/
+
+package schwift
+
+import (
+ "net/http"
+)
+
+//Container represents a Swift container.
+type Container struct {
+ a *Account
+ name string
+ //cache
+ headers *ContainerHeaders
+}
+
+//Container returns a handle to the container with the given name within this
+//account. This function does not issue any HTTP requests, and therefore cannot
+//ensure that the container exists. Use the Exists() function to check for the
+//container's existence, or chain this function with the EnsureExists()
+//function like so:
+//
+// container, err := account.Container("documents").EnsureExists()
+func (a *Account) Container(name string) *Container {
+ return &Container{a: a, name: name}
+}
+
+//Account returns a handle to the account this container is stored in.
+func (c *Container) Account() *Account {
+ return c.a
+}
+
+//Name returns the container name.
+func (c *Container) Name() string {
+ return c.name
+}
+
+//Exists checks if this container exists, potentially by issuing a HEAD request
+//if no Headers() have been cached yet.
+func (c *Container) Exists() (bool, error) {
+ _, err := c.Headers()
+ if Is(err, http.StatusNotFound) {
+ return false, nil
+ } else if err != nil {
+ return false, err
+ }
+ return true, nil
+}
+
+//Headers returns the ContainerHeaders for this account. If the ContainerHeaders
+//has not been cached yet, a HEAD request is issued on the account.
+func (c *Container) Headers() (ContainerHeaders, error) {
+ if c.headers != nil {
+ return *c.headers, nil
+ }
+
+ resp, err := Request{
+ Method: "HEAD",
+ ContainerName: c.name,
+ ExpectStatusCodes: []int{204},
+ }.Do(c.a.client)
+ if err != nil {
+ return ContainerHeaders{}, err
+ }
+
+ var headers ContainerHeaders
+ err = parseHeaders(resp.Header, &headers)
+ if err != nil {
+ return ContainerHeaders{}, err
+ }
+ c.headers = &headers
+ return *c.headers, nil
+}
+
+//Update updates the container using a POST request. To set arbitrary request
+//headers (and to add URL parameters), pass a non-nil *RequestOptions.
+//
+//If you are not sure whether the container exists, use Create() instead.
+//
+//A successful POST request implies Invalidate() since it may change metadata.
+func (c *Container) Update(headers ContainerHeaders, opts *RequestOptions) error {
+ _, err := Request{
+ Method: "POST",
+ ContainerName: c.name,
+ Options: compileHeaders(&headers, opts),
+ ExpectStatusCodes: []int{204},
+ }.Do(c.a.client)
+ if err == nil {
+ c.Invalidate()
+ }
+ return err
+}
+
+//Create creates the container using a PUT request. To set arbitrary request
+//headers (and to add URL parameters), pass a non-nil *RequestOptions.
+//
+//This function can be used regardless of whether the container exists or not.
+//
+//A successful PUT request implies Invalidate() since it may change metadata.
+func (c *Container) Create(headers ContainerHeaders, opts *RequestOptions) error {
+ _, err := Request{
+ Method: "PUT",
+ ContainerName: c.name,
+ Options: compileHeaders(&headers, opts),
+ ExpectStatusCodes: []int{201, 202},
+ }.Do(c.a.client)
+ if err == nil {
+ c.Invalidate()
+ }
+ return err
+}
+
+//Delete deletes the container using a DELETE request. To set arbitrary request
+//headers (and to add URL parameters), pass a non-nil *RequestOptions.
+//
+//This operation fails with http.StatusConflict if the container is not empty.
+//
+//This operation fails with http.StatusNotFound if the container does not exist.
+//
+//A successful DELETE request implies Invalidate().
+func (c *Container) Delete(headers ContainerHeaders, opts *RequestOptions) error {
+ _, err := Request{
+ Method: "DELETE",
+ ContainerName: c.name,
+ Options: compileHeaders(&headers, opts),
+ ExpectStatusCodes: []int{204},
+ }.Do(c.a.client)
+ if err == nil {
+ c.Invalidate()
+ }
+ return err
+}
+
+//Invalidate clears the internal cache of this Container instance. The next call
+//to Headers() on this instance will issue a HEAD request on the account.
+func (c *Container) Invalidate() {
+ c.headers = nil
+}
+
+//EnsureExists issues a PUT request on this container.
+//If the container does not exist yet, it will be created by this call.
+//If the container exists already, this call does not change it.
+//This function returns the same container again, because its intended use is
+//with freshly constructed Container instances like so:
+//
+// container, err := account.Container("documents").EnsureExists()
+func (c *Container) EnsureExists() (*Container, error) {
+ _, err := Request{
+ Method: "PUT",
+ ContainerName: c.name,
+ ExpectStatusCodes: []int{201, 202},
+ }.Do(c.a.client)
+ return c, err
+}
+
+// TODO object listing
diff --git a/container_test.go b/container_test.go
new file mode 100644
index 0000000..7c67ed5
--- /dev/null
+++ b/container_test.go
@@ -0,0 +1,57 @@
+/******************************************************************************
+*
+* Copyright 2018 Stefan Majewsky <majewsky@gmx.net>
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*
+******************************************************************************/
+
+package schwift
+
+import (
+ "crypto/rand"
+ "encoding/hex"
+ "net/http"
+ "testing"
+)
+
+func TestContainerExistence(t *testing.T) {
+ testWithAccount(t, func(a *Account) {
+ c := a.Container(getRandomName())
+
+ exists, err := c.Exists()
+ expectError(t, err, "")
+ expectBool(t, exists, false)
+
+ _, err = c.Headers()
+ expectError(t, err, "expected 204 response, got 404 instead")
+ expectBool(t, Is(err, http.StatusNotFound), true)
+ expectBool(t, Is(err, http.StatusNoContent), false)
+
+ err = c.Create(ContainerHeaders{}, nil)
+ expectError(t, err, "")
+
+ exists, err = c.Exists()
+ expectError(t, err, "")
+ expectBool(t, exists, true)
+ })
+}
+
+func getRandomName() string {
+ var buf [16]byte
+ _, err := rand.Read(buf[:])
+ if err != nil {
+ panic(err.Error())
+ }
+ return hex.EncodeToString(buf[:])
+}
diff --git a/errors.go b/errors.go
index d4b3470..7a7d40d 100644
--- a/errors.go
+++ b/errors.go
@@ -49,11 +49,14 @@ func (e UnexpectedStatusCodeError) Error() string {
for idx, code := range e.ExpectedStatusCodes {
codeStrs[idx] = strconv.Itoa(code)
}
- return fmt.Sprintf("expected %s response, got %d instead: %s",
+ msg := fmt.Sprintf("expected %s response, got %d instead",
strings.Join(codeStrs, "/"),
e.ActualResponse.StatusCode,
- string(e.ResponseBody),
)
+ if len(e.ResponseBody) > 0 {
+ msg += ": " + string(e.ResponseBody)
+ }
+ return msg
}
//Is checks if the given error is an UnexpectedStatusCodeError for that status
diff --git a/headers.go b/headers.go
index 3473ac0..7f8cfd1 100644
--- a/headers.go
+++ b/headers.go
@@ -64,6 +64,17 @@ func (a AccountHeaders) TempURLKey2() StringField {
}
}
+//ContainerHeaders contains the headers for an account. The Raw attribute
+//contains the original set of headers returned from a HEAD or GET request on
+//the account. The other attributes contain the parsed values of common
+//headers, as noted in the tags next to each field. Well-known metadata headers
+//can be accessed in a type-safe way using the methods on this type.
+type ContainerHeaders struct {
+ Metadata Metadata `schwift:"rw,X-Container-Meta-"`
+ Raw http.Header
+ //TODO map well-known headers
+}
+
////////////////////////////////////////////////////////////////////////////////
// field types
diff --git a/headers_test.go b/headers_test.go
index 2d1d804..b54181c 100644
--- a/headers_test.go
+++ b/headers_test.go
@@ -33,13 +33,13 @@ func TestParseAccountHeadersSuccess(t *testing.T) {
"X-Account-Meta-foo": {"bar"},
}, &headers)
- expectError(t, err, nil)
+ expectError(t, err, "")
expectUint64(t, headers.BytesUsed, 1234)
expectUint64(t, headers.ContainerCount, 23)
expectUint64(t, headers.ObjectCount, 42)
value, err := headers.QuotaBytes().Get()
- expectError(t, err, nil)
+ expectError(t, err, "")
expectUint64(t, value, 1048576)
expectString(t, headers.Metadata.Get("foo"), "bar")
@@ -49,6 +49,13 @@ func TestParseAccountHeadersSuccess(t *testing.T) {
//TODO TestParseAccountHeadersError
+func expectBool(t *testing.T, actual bool, expected bool) {
+ t.Helper()
+ if actual != expected {
+ t.Errorf("expected value %#v, got %#v instead\n", expected, actual)
+ }
+}
+
func expectUint64(t *testing.T, actual uint64, expected uint64) {
t.Helper()
if actual != expected {
@@ -63,19 +70,19 @@ func expectString(t *testing.T, actual string, expected string) {
}
}
-func expectError(t *testing.T, actual error, expected *string) (ok bool) {
+func expectError(t *testing.T, actual error, expected string) (ok bool) {
t.Helper()
if actual == nil {
- if expected != nil {
- t.Errorf("expected error %q, got no error\n", *expected)
+ if expected != "" {
+ t.Errorf("expected error %q, got no error\n", expected)
return false
}
} else {
- if expected == nil {
+ if expected == "" {
t.Errorf("expected no error, got %q\n", actual.Error())
return false
- } else if *expected != actual.Error() {
- t.Errorf("expected error %q, got %q instead\n", *expected, actual.Error())
+ } else if expected != actual.Error() {
+ t.Errorf("expected error %q, got %q instead\n", expected, actual.Error())
return false
}
}