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 {
|
||||
if svc.Type == "image" {
|
||||
for _, ep := range svc.Endpoints {
|
||||
if ep.VersionId == "1.0" && ep.Region == config.ImageRegion {
|
||||
url = ep.PublicURL
|
||||
break
|
||||
}
|
||||
url = ep.PublicURL + "/v1"
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,11 +20,10 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.openstack.org/stackforge/golang-client.git/util"
|
||||
"git.openstack.org/stackforge/golang-client.git/openstack"
|
||||
)
|
||||
|
||||
type Auth struct {
|
||||
@ -128,28 +127,18 @@ func AuthTenantNameTokenId(url, tenantName, tokenId string) (Auth, error) {
|
||||
|
||||
func auth(url, jsonStr *string) (Auth, error) {
|
||||
var s []byte = []byte(*jsonStr)
|
||||
resp, err := util.CallAPI("POST", *url, &s,
|
||||
"Accept-Encoding", "gzip,deflate",
|
||||
"Accept", "application/json",
|
||||
"Content-Type", "application/json",
|
||||
"Content-Length", string(len(*jsonStr)))
|
||||
path := fmt.Sprintf(`%s/tokens`, *url)
|
||||
resp, err := session.Post(path, nil, nil, &s)
|
||||
if err != nil {
|
||||
return Auth{}, err
|
||||
}
|
||||
if err = util.CheckHTTPResponseStatusCode(resp); err != nil {
|
||||
return Auth{}, err
|
||||
}
|
||||
var contentType string = strings.ToLower(resp.Header.Get("Content-Type"))
|
||||
|
||||
var contentType string = strings.ToLower(resp.Resp.Header.Get("Content-Type"))
|
||||
if strings.Contains(contentType, "json") != true {
|
||||
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{}
|
||||
if err = json.Unmarshal(body, &auth); err != nil {
|
||||
if err = json.Unmarshal(resp.Body, &auth); err != nil {
|
||||
return Auth{}, err
|
||||
}
|
||||
return auth, nil
|
||||
|
@ -23,10 +23,12 @@ In addition more complex filtering and sort queries can by using the ImageQueryP
|
||||
package image
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"git.openstack.org/stackforge/golang-client.git/openstack"
|
||||
"git.openstack.org/stackforge/golang-client.git/util"
|
||||
)
|
||||
|
||||
@ -147,11 +149,22 @@ func (imageService Service) queryImages(includeDetails bool, imagesResponseConta
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -21,9 +21,9 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.openstack.org/stackforge/golang-client.git/image/v1"
|
||||
"git.openstack.org/stackforge/golang-client.git/testUtil"
|
||||
"git.openstack.org/stackforge/golang-client.git/util"
|
||||
"git.openstack.org/stackforge/golang-client.git/image/v1"
|
||||
"git.openstack.org/stackforge/golang-client.git/testUtil"
|
||||
"git.openstack.org/stackforge/golang-client.git/util"
|
||||
)
|
||||
|
||||
var tokn = "eaaafd18-0fed-4b3a-81b4-663c99ec1cbb"
|
||||
|
@ -20,6 +20,7 @@ import (
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
"git.openstack.org/stackforge/golang-client.git/openstack"
|
||||
"git.openstack.org/stackforge/golang-client.git/util"
|
||||
)
|
||||
|
||||
@ -107,13 +108,13 @@ func ListObjects(limit int64,
|
||||
//obtained token.
|
||||
//url can be regular storage or CDN-enabled storage URL.
|
||||
func PutObject(fContent *[]byte, url, token string, s ...string) (err error) {
|
||||
s = append(s, "X-Auth-Token")
|
||||
s = append(s, token)
|
||||
resp, err := util.CallAPI("PUT", url, fContent, s...)
|
||||
var headers http.Header = http.Header{}
|
||||
headers.Set("X-Auth-Token", token)
|
||||
resp, err := session.Put(url, nil, &headers, fContent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return util.CheckHTTPResponseStatusCode(resp)
|
||||
return util.CheckHTTPResponseStatusCode(resp.Resp)
|
||||
}
|
||||
|
||||
//CopyObject calls the OpenStack copy object API using previously obtained
|
||||
|
221
openstack/session.go
Normal file
221
openstack/session.go
Normal file
@ -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)
|
||||
}
|
60
openstack/session_test.go
Normal file
60
openstack/session_test.go
Normal file
@ -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,
|
||||
// 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
|
||||
|
Loading…
x
Reference in New Issue
Block a user