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:
		@@ -44,13 +44,11 @@ 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
 | 
			
		||||
				url = ep.PublicURL + "/v1"
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if url == "" {
 | 
			
		||||
		panic("v1 image service url not found during authentication")
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user