aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--account.go15
-rw-r--r--account_test.go52
-rw-r--r--headers.go2
-rw-r--r--headers_test.go7
-rw-r--r--request.go35
-rw-r--r--shared_test.go74
6 files changed, 165 insertions, 20 deletions
diff --git a/account.go b/account.go
index a2bb6a7..af7698b 100644
--- a/account.go
+++ b/account.go
@@ -96,7 +96,7 @@ func (a *Account) Headers() (AccountHeaders, error) {
resp, err := Request{
Method: "HEAD",
- ExpectStatusCodes: []int{200},
+ ExpectStatusCodes: []int{204},
}.Do(a.client)
if err != nil {
return AccountHeaders{}, err
@@ -107,6 +107,7 @@ func (a *Account) Headers() (AccountHeaders, error) {
if err != nil {
return AccountHeaders{}, err
}
+ a.headers = &headers
return *a.headers, nil
}
@@ -116,13 +117,19 @@ func (a *Account) Invalidate() {
a.headers = nil
}
-//Post creates or updates the account using a POST request.
-func (a *Account) Post(headers AccountHeaders) error {
+//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.
+//
+//A successful POST request implies Invalidate() since it changes account metadata.
+func (a *Account) Post(headers AccountHeaders, opts *RequestOptions) error {
_, err := Request{
Method: "POST",
- AdditionalHeaders: compileHeaders(headers),
+ Options: compileHeaders(headers, opts),
ExpectStatusCodes: []int{204},
}.Do(a.client)
+ if err == nil {
+ a.Invalidate()
+ }
return err
}
diff --git a/account_test.go b/account_test.go
new file mode 100644
index 0000000..83567f9
--- /dev/null
+++ b/account_test.go
@@ -0,0 +1,52 @@
+/******************************************************************************
+*
+* 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 "testing"
+
+func TestAccountBasic(t *testing.T) {
+ testWithAccount(t, func(a *Account) {
+ hdr, err := a.Headers()
+ if !expectError(t, err, nil) {
+ t.FailNow()
+ }
+ //There are not a lot of things we can test here (besides testing that
+ //Headers() does not fail, i.e. everything parses correctly), but
+ //Content-Type is going to be text/plain because GET on an account lists
+ //the container names as plain text.
+ expectString(t, hdr.Raw.Get("Content-Type"), "text/plain; charset=utf-8")
+ })
+}
+
+func TestAccountMetadata(t *testing.T) {
+ testWithAccount(t, func(a *Account) {
+ err := a.Post(AccountHeaders{
+ Metadata: map[string]string{"schwift-test": "first"},
+ }, nil)
+ if !expectError(t, err, nil) {
+ t.FailNow()
+ }
+
+ hdr, err := a.Headers()
+ if !expectError(t, err, nil) {
+ t.FailNow()
+ }
+ expectString(t, hdr.Metadata["schwift-test"], "first")
+ })
+}
diff --git a/headers.go b/headers.go
index 0d9f0e0..2153d9d 100644
--- a/headers.go
+++ b/headers.go
@@ -189,7 +189,7 @@ func parseHeaders(hdr http.Header, target interface{}) error {
})
}
-func compileHeaders(headers interface{}) map[string]string {
+func compileHeaders(headers interface{}, opts *RequestOptions) RequestOptions {
panic("TODO")
}
diff --git a/headers_test.go b/headers_test.go
index 6859690..f00b166 100644
--- a/headers_test.go
+++ b/headers_test.go
@@ -65,17 +65,22 @@ func expectString(t *testing.T, actual string, expected string) {
}
}
-func expectError(t *testing.T, actual error, expected *string) {
+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)
+ return false
}
} else {
if expected == nil {
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())
+ return false
}
}
+
+ return true
}
diff --git a/request.go b/request.go
index 5a2d4e2..fb4b596 100644
--- a/request.go
+++ b/request.go
@@ -22,7 +22,7 @@ import (
"io"
"io/ioutil"
"net/http"
- urlmodule "net/url"
+ "net/url"
"strings"
"github.com/gophercloud/gophercloud"
@@ -44,23 +44,29 @@ func init() {
//Request contains the parameters that can be set in a request to the Swift API.
type Request struct {
- Method string //"GET", "HEAD", "PUT", "POST" or "DELETE"
- ContainerName string //empty for requests on accounts
- ObjectName string //empty for requests on accounts/containers
- AdditionalHeaders map[string]string
+ Method string //"GET", "HEAD", "PUT", "POST" or "DELETE"
+ ContainerName string //empty for requests on accounts
+ ObjectName string //empty for requests on accounts/containers
+ Options RequestOptions
//ExpectStatusCodes can be left empty to disable this check, otherwise
//schwift.UnexpectedStatusCodeError may be returned.
ExpectStatusCodes []int
}
+//RequestOptions contains additional headers and values for request.
+type RequestOptions struct {
+ Headers map[string]string
+ Values url.Values
+}
+
//URL returns the full URL for this request.
-func (r Request) URL(client *gophercloud.ServiceClient) (string, error) {
- url, err := urlmodule.Parse(client.Endpoint)
+func (r Request) URL(client *gophercloud.ServiceClient, values url.Values) (string, error) {
+ uri, err := url.Parse(client.Endpoint)
if err != nil {
return "", err
}
- if !strings.HasSuffix(url.Path, "/") {
- url.Path += "/"
+ if !strings.HasSuffix(uri.Path, "/") {
+ uri.Path += "/"
}
if r.ContainerName == "" {
@@ -71,10 +77,11 @@ func (r Request) URL(client *gophercloud.ServiceClient) (string, error) {
if strings.Contains(r.ContainerName, "/") {
return "", ErrMalformedContainerName
}
- url.Path += r.ContainerName + "/" + r.ObjectName
+ uri.Path += r.ContainerName + "/" + r.ObjectName
}
- return url.String(), nil
+ uri.RawQuery = values.Encode()
+ return uri.String(), nil
}
//Do executes this request on the given service client.
@@ -84,7 +91,7 @@ func (r Request) Do(client *gophercloud.ServiceClient) (*http.Response, error) {
func (r Request) do(client *gophercloud.ServiceClient, afterReauth bool) (*http.Response, error) {
//build URL
- url, err := r.URL(client)
+ uri, err := r.URL(client, r.Options.Values)
if err != nil {
return nil, err
}
@@ -97,11 +104,11 @@ func (r Request) do(client *gophercloud.ServiceClient, afterReauth bool) (*http.
"Accept": "",
"Content-Type": "",
}
- for key, value := range r.AdditionalHeaders {
+ for key, value := range r.Options.Headers {
opts.MoreHeaders[key] = value
}
- resp, err := client.ProviderClient.Request(r.Method, url, opts)
+ resp, err := client.ProviderClient.Request(r.Method, uri, opts)
if err != nil {
if resp.StatusCode == 204 {
return resp, drainResponseBody(resp)
diff --git a/shared_test.go b/shared_test.go
new file mode 100644
index 0000000..bc67320
--- /dev/null
+++ b/shared_test.go
@@ -0,0 +1,74 @@
+/******************************************************************************
+*
+* 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 (
+ "os"
+ "testing"
+
+ "github.com/gophercloud/gophercloud"
+ "github.com/gophercloud/gophercloud/openstack"
+ "github.com/gophercloud/gophercloud/openstack/objectstorage/v1/swauth"
+)
+
+func testWithAccount(t *testing.T, testCode func(a *Account)) {
+ stAuth := os.Getenv("ST_AUTH")
+ stUser := os.Getenv("ST_USER")
+ stKey := os.Getenv("ST_KEY")
+ var client *gophercloud.ServiceClient
+
+ if stAuth == "" && stUser == "" && stKey == "" {
+ //option 1: Keystone authentication
+ authOptions, err := openstack.AuthOptionsFromEnv()
+ if err != nil {
+ t.Error("missing Swift credentials (need either ST_AUTH, ST_USER, ST_KEY or OS_* variables)")
+ t.Error("openstack.AuthOptionsFromEnv returned: " + err.Error())
+ return
+ }
+ provider, err := openstack.AuthenticatedClient(authOptions)
+ if err != nil {
+ t.Errorf("openstack.AuthenticatedClient returned: " + err.Error())
+ return
+ }
+ client, err = openstack.NewObjectStorageV1(provider, gophercloud.EndpointOpts{})
+ if err != nil {
+ t.Errorf("openstack.NewObjectStorageV1 returned: " + err.Error())
+ return
+ }
+ } else {
+ //option 2: Swift authentication v1
+ provider, err := openstack.NewClient(stAuth)
+ if err != nil {
+ t.Errorf("openstack.NewClient returned: " + err.Error())
+ return
+ }
+ client, err = swauth.NewObjectStorageV1(provider, swauth.AuthOpts{User: stUser, Key: stKey})
+ if err != nil {
+ t.Errorf("swauth.NewObjectStorageV1 returned: " + err.Error())
+ return
+ }
+ }
+
+ account, err := AccountFromClient(client)
+ if err != nil {
+ t.Errorf("schwift.AccountFromClient returned: " + err.Error())
+ return
+ }
+ testCode(account)
+}