diff options
| -rw-r--r-- | account.go | 15 | ||||
| -rw-r--r-- | account_test.go | 52 | ||||
| -rw-r--r-- | headers.go | 2 | ||||
| -rw-r--r-- | headers_test.go | 7 | ||||
| -rw-r--r-- | request.go | 35 | ||||
| -rw-r--r-- | shared_test.go | 74 |
6 files changed, 165 insertions, 20 deletions
@@ -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") + }) +} @@ -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 } @@ -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) +} |
