Add Session as base REST interface
This is the initial implementation of a Session object that handles the REST calls similar to the new Session in python-keystoneclient. It will be expanded to utilize a callback to an appropriate authentication handler to re-authenticate as required. This is intended to replace CallAPI in the util/util package. Change-Id: I585968cc584327427da3429ef7005dd909c8b8b0
This commit is contained in:
parent
124ac5cc92
commit
a279956280
|
@ -44,10 +44,8 @@ func main() {
|
||||||
for _, svc := range auth.Access.ServiceCatalog {
|
for _, svc := range auth.Access.ServiceCatalog {
|
||||||
if svc.Type == "image" {
|
if svc.Type == "image" {
|
||||||
for _, ep := range svc.Endpoints {
|
for _, ep := range svc.Endpoints {
|
||||||
if ep.VersionId == "1.0" && ep.Region == config.ImageRegion {
|
url = ep.PublicURL + "/v1"
|
||||||
url = ep.PublicURL
|
break
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,11 +20,10 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.openstack.org/stackforge/golang-client.git/util"
|
"git.openstack.org/stackforge/golang-client.git/openstack"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Auth struct {
|
type Auth struct {
|
||||||
|
@ -128,28 +127,18 @@ func AuthTenantNameTokenId(url, tenantName, tokenId string) (Auth, error) {
|
||||||
|
|
||||||
func auth(url, jsonStr *string) (Auth, error) {
|
func auth(url, jsonStr *string) (Auth, error) {
|
||||||
var s []byte = []byte(*jsonStr)
|
var s []byte = []byte(*jsonStr)
|
||||||
resp, err := util.CallAPI("POST", *url, &s,
|
path := fmt.Sprintf(`%s/tokens`, *url)
|
||||||
"Accept-Encoding", "gzip,deflate",
|
resp, err := session.Post(path, nil, nil, &s)
|
||||||
"Accept", "application/json",
|
|
||||||
"Content-Type", "application/json",
|
|
||||||
"Content-Length", string(len(*jsonStr)))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Auth{}, err
|
return Auth{}, err
|
||||||
}
|
}
|
||||||
if err = util.CheckHTTPResponseStatusCode(resp); err != nil {
|
|
||||||
return Auth{}, err
|
var contentType string = strings.ToLower(resp.Resp.Header.Get("Content-Type"))
|
||||||
}
|
|
||||||
var contentType string = strings.ToLower(resp.Header.Get("Content-Type"))
|
|
||||||
if strings.Contains(contentType, "json") != true {
|
if strings.Contains(contentType, "json") != true {
|
||||||
return Auth{}, errors.New("err: header Content-Type is not JSON")
|
return Auth{}, errors.New("err: header Content-Type is not JSON")
|
||||||
}
|
}
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
|
||||||
defer resp.Body.Close()
|
|
||||||
if err != nil {
|
|
||||||
return Auth{}, err
|
|
||||||
}
|
|
||||||
var auth = Auth{}
|
var auth = Auth{}
|
||||||
if err = json.Unmarshal(body, &auth); err != nil {
|
if err = json.Unmarshal(resp.Body, &auth); err != nil {
|
||||||
return Auth{}, err
|
return Auth{}, err
|
||||||
}
|
}
|
||||||
return auth, nil
|
return auth, nil
|
||||||
|
|
|
@ -23,10 +23,12 @@ In addition more complex filtering and sort queries can by using the ImageQueryP
|
||||||
package image
|
package image
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
|
"git.openstack.org/stackforge/golang-client.git/openstack"
|
||||||
"git.openstack.org/stackforge/golang-client.git/util"
|
"git.openstack.org/stackforge/golang-client.git/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -147,11 +149,22 @@ func (imageService Service) queryImages(includeDetails bool, imagesResponseConta
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = util.GetJSON(reqURL.String(), imageService.TokenID, imageService.Client, &imagesResponseContainer)
|
var headers http.Header = http.Header{}
|
||||||
|
headers.Set("X-Auth-Token", imageService.TokenID)
|
||||||
|
headers.Set("Accept", "application/json")
|
||||||
|
resp, err := session.Get(reqURL.String(), nil, &headers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = util.CheckHTTPResponseStatusCode(resp.Resp)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = json.Unmarshal(resp.Body, &imagesResponseContainer); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,9 +21,9 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.openstack.org/stackforge/golang-client.git/image/v1"
|
"git.openstack.org/stackforge/golang-client.git/image/v1"
|
||||||
"git.openstack.org/stackforge/golang-client.git/testUtil"
|
"git.openstack.org/stackforge/golang-client.git/testUtil"
|
||||||
"git.openstack.org/stackforge/golang-client.git/util"
|
"git.openstack.org/stackforge/golang-client.git/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
var tokn = "eaaafd18-0fed-4b3a-81b4-663c99ec1cbb"
|
var tokn = "eaaafd18-0fed-4b3a-81b4-663c99ec1cbb"
|
||||||
|
|
|
@ -20,6 +20,7 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"git.openstack.org/stackforge/golang-client.git/openstack"
|
||||||
"git.openstack.org/stackforge/golang-client.git/util"
|
"git.openstack.org/stackforge/golang-client.git/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -107,13 +108,13 @@ func ListObjects(limit int64,
|
||||||
//obtained token.
|
//obtained token.
|
||||||
//url can be regular storage or CDN-enabled storage URL.
|
//url can be regular storage or CDN-enabled storage URL.
|
||||||
func PutObject(fContent *[]byte, url, token string, s ...string) (err error) {
|
func PutObject(fContent *[]byte, url, token string, s ...string) (err error) {
|
||||||
s = append(s, "X-Auth-Token")
|
var headers http.Header = http.Header{}
|
||||||
s = append(s, token)
|
headers.Set("X-Auth-Token", token)
|
||||||
resp, err := util.CallAPI("PUT", url, fContent, s...)
|
resp, err := session.Put(url, nil, &headers, fContent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return util.CheckHTTPResponseStatusCode(resp)
|
return util.CheckHTTPResponseStatusCode(resp.Resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
//CopyObject calls the OpenStack copy object API using previously obtained
|
//CopyObject calls the OpenStack copy object API using previously obtained
|
||||||
|
|
|
@ -0,0 +1,221 @@
|
||||||
|
// session - REST client session
|
||||||
|
// Copyright 2015 Dean Troyer
|
||||||
|
//
|
||||||
|
// 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 session
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/tls"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httputil"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Debug = new(bool)
|
||||||
|
|
||||||
|
type Response struct {
|
||||||
|
Resp *http.Response
|
||||||
|
Body []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type TokenInterface interface {
|
||||||
|
GetTokenId() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Token struct {
|
||||||
|
Expires string
|
||||||
|
Id string
|
||||||
|
Project struct {
|
||||||
|
Id string
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Token) GetTokenId() string {
|
||||||
|
return t.Id
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generic callback to get a token from the auth plugin
|
||||||
|
type AuthFunc func(s *Session, opts interface{}) (TokenInterface, error)
|
||||||
|
|
||||||
|
type Session struct {
|
||||||
|
httpClient *http.Client
|
||||||
|
endpoint string
|
||||||
|
authenticate AuthFunc
|
||||||
|
Token TokenInterface
|
||||||
|
Headers http.Header
|
||||||
|
// ServCat map[string]ServiceEndpoint
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSession(af AuthFunc, endpoint string, tls *tls.Config) (session *Session, err error) {
|
||||||
|
tr := &http.Transport{
|
||||||
|
TLSClientConfig: tls,
|
||||||
|
DisableCompression: true,
|
||||||
|
}
|
||||||
|
session = &Session{
|
||||||
|
// TODO(dtroyer): httpClient needs to be able to be passed in, or set externally
|
||||||
|
httpClient: &http.Client{Transport: tr},
|
||||||
|
endpoint: strings.TrimRight(endpoint, "/"),
|
||||||
|
authenticate: af,
|
||||||
|
Headers: http.Header{},
|
||||||
|
}
|
||||||
|
return session, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Session) NewRequest(method, url string, headers *http.Header, body io.Reader) (req *http.Request, err error) {
|
||||||
|
req, err = http.NewRequest(method, url, body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// add token, get one if needed
|
||||||
|
if s.Token == nil && s.authenticate != nil {
|
||||||
|
var tok TokenInterface
|
||||||
|
tok, err = s.authenticate(s, nil)
|
||||||
|
if err != nil {
|
||||||
|
// (re-)auth failure!!
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
s.Token = tok
|
||||||
|
}
|
||||||
|
if headers != nil {
|
||||||
|
req.Header = *headers
|
||||||
|
}
|
||||||
|
if s.Token != nil {
|
||||||
|
req.Header.Add("X-Auth-Token", s.Token.GetTokenId())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Session) Do(req *http.Request) (*Response, error) {
|
||||||
|
if *Debug {
|
||||||
|
d, _ := httputil.DumpRequestOut(req, true)
|
||||||
|
log.Printf(">>>>>>>>>> REQUEST:\n", string(d))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add session headers
|
||||||
|
for k := range s.Headers {
|
||||||
|
req.Header.Set(k, s.Headers.Get(k))
|
||||||
|
}
|
||||||
|
|
||||||
|
hresp, err := s.httpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if *Debug {
|
||||||
|
dr, _ := httputil.DumpResponse(hresp, true)
|
||||||
|
log.Printf("<<<<<<<<<< RESULT:\n", string(dr))
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := new(Response)
|
||||||
|
resp.Resp = hresp
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform a simple get to an endpoint
|
||||||
|
func (s *Session) Request(
|
||||||
|
method string,
|
||||||
|
url string,
|
||||||
|
params *url.Values,
|
||||||
|
headers *http.Header,
|
||||||
|
body *[]byte,
|
||||||
|
) (resp *Response, err error) {
|
||||||
|
// add params to url here
|
||||||
|
if params != nil {
|
||||||
|
url = url + "?" + params.Encode()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the body if one is present
|
||||||
|
var buf io.Reader
|
||||||
|
if body != nil {
|
||||||
|
buf = bytes.NewReader(*body)
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := s.NewRequest(method, url, headers, buf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
resp, err = s.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// do we need to parse this in this func? yes...
|
||||||
|
defer resp.Resp.Body.Close()
|
||||||
|
|
||||||
|
resp.Body, err = ioutil.ReadAll(resp.Resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Session) Get(
|
||||||
|
url string,
|
||||||
|
params *url.Values,
|
||||||
|
headers *http.Header) (resp *Response, err error) {
|
||||||
|
return s.Request("GET", url, params, headers, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Session) Post(
|
||||||
|
url string,
|
||||||
|
params *url.Values,
|
||||||
|
headers *http.Header,
|
||||||
|
body *[]byte) (resp *Response, err error) {
|
||||||
|
return s.Request("POST", url, params, headers, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Session) Put(
|
||||||
|
url string,
|
||||||
|
params *url.Values,
|
||||||
|
headers *http.Header,
|
||||||
|
body *[]byte) (resp *Response, err error) {
|
||||||
|
return s.Request("PUT", url, params, headers, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get sends a GET request.
|
||||||
|
func Get(
|
||||||
|
url string,
|
||||||
|
params *url.Values,
|
||||||
|
headers *http.Header) (resp *Response, err error) {
|
||||||
|
s, _ := NewSession(nil, "", nil)
|
||||||
|
return s.Get(url, params, headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Post sends a POST request.
|
||||||
|
func Post(
|
||||||
|
url string,
|
||||||
|
params *url.Values,
|
||||||
|
headers *http.Header,
|
||||||
|
body *[]byte) (resp *Response, err error) {
|
||||||
|
s, _ := NewSession(nil, "", nil)
|
||||||
|
return s.Post(url, params, headers, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put sends a PUT request.
|
||||||
|
func Put(
|
||||||
|
url string,
|
||||||
|
params *url.Values,
|
||||||
|
headers *http.Header,
|
||||||
|
body *[]byte) (resp *Response, err error) {
|
||||||
|
s, _ := NewSession(nil, "", nil)
|
||||||
|
return s.Put(url, params, headers, body)
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
// session_test - REST client session tests
|
||||||
|
// Copyright 2015 Dean Troyer
|
||||||
|
//
|
||||||
|
// 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 session_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.openstack.org/stackforge/golang-client.git/openstack"
|
||||||
|
"git.openstack.org/stackforge/golang-client.git/testUtil"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TestStruct struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSessionGet(t *testing.T) {
|
||||||
|
tokn := "eaaafd18-0fed-4b3a-81b4-663c99ec1cbb"
|
||||||
|
var apiServer = testUtil.CreateGetJsonTestServer(
|
||||||
|
t,
|
||||||
|
tokn,
|
||||||
|
`{"id":"id1","name":"Chris"}`,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
expected := TestStruct{ID: "id1", Name: "Chris"}
|
||||||
|
actual := TestStruct{}
|
||||||
|
|
||||||
|
s, _ := session.NewSession(nil, "", nil)
|
||||||
|
var headers http.Header = http.Header{}
|
||||||
|
headers.Set("X-Auth-Token", tokn)
|
||||||
|
headers.Set("Accept", "application/json")
|
||||||
|
headers.Set("Etag", "md5hash-blahblah")
|
||||||
|
resp, err := s.Get(apiServer.URL, nil, &headers)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
testUtil.IsNil(t, err)
|
||||||
|
|
||||||
|
if err = json.Unmarshal(resp.Body, &actual); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
testUtil.Equals(t, expected, actual)
|
||||||
|
}
|
|
@ -57,6 +57,30 @@ func IsNil(tb testing.TB, act interface{}) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateGetJSONTestServer creates a httptest.Server that can be used to test
|
||||||
|
// JSON Get requests. Takes a token, JSON payload, and a verification function
|
||||||
|
// to do additional validation
|
||||||
|
func CreateGetJsonTestServer(
|
||||||
|
t *testing.T,
|
||||||
|
expectedAuthToken string,
|
||||||
|
jsonResponsePayload string,
|
||||||
|
verifyRequest func(*http.Request)) *httptest.Server {
|
||||||
|
return httptest.NewServer(http.HandlerFunc(
|
||||||
|
func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
headerValuesEqual(t, r, "X-Auth-Token", expectedAuthToken)
|
||||||
|
headerValuesEqual(t, r, "Accept", "application/json")
|
||||||
|
// verifyRequest(r)
|
||||||
|
if r.Method == "GET" {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.Write([]byte(jsonResponsePayload))
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Error(errors.New("Failed: r.Method == GET"))
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
// CreateGetJSONTestRequestServer creates a httptest.Server that can be used to test GetJson requests. Just specify the token,
|
// CreateGetJSONTestRequestServer creates a httptest.Server that can be used to test GetJson requests. Just specify the token,
|
||||||
// json payload that is to be read by the response, and a verification func that can be used
|
// json payload that is to be read by the response, and a verification func that can be used
|
||||||
// to do additional validation of the request that is built
|
// to do additional validation of the request that is built
|
||||||
|
|
Loading…
Reference in New Issue