packer-cn/vendor/github.com/Telmate/proxmox-api-go/proxmox/session.go

340 lines
7.6 KiB
Go
Raw Normal View History

package proxmox
// inspired by https://github.com/openstack/golang-client/blob/master/openstack/session.go
import (
"bytes"
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"net/http/httputil"
"net/url"
)
var Debug = new(bool)
type Response struct {
Resp *http.Response
Body []byte
}
type Session struct {
httpClient *http.Client
ApiUrl string
AuthTicket string
CsrfToken string
Headers http.Header
}
func NewSession(apiUrl string, hclient *http.Client, tls *tls.Config) (session *Session, err error) {
if hclient == nil {
// Only build a transport if we're also building the client
tr := &http.Transport{
TLSClientConfig: tls,
DisableCompression: true,
}
hclient = &http.Client{Transport: tr}
}
session = &Session{
httpClient: hclient,
ApiUrl: apiUrl,
AuthTicket: "",
CsrfToken: "",
Headers: http.Header{},
}
return session, nil
}
func ParamsToBody(params map[string]interface{}) (body []byte) {
vals := url.Values{}
for k, intrV := range params {
var v string
switch intrV.(type) {
// Convert true/false bool to 1/0 string where Proxmox API can understand it.
case bool:
if intrV.(bool) {
v = "1"
} else {
v = "0"
}
default:
v = fmt.Sprintf("%v", intrV)
}
2020-07-15 17:20:09 -04:00
if v != "" {
vals.Set(k, v)
}
}
body = bytes.NewBufferString(vals.Encode()).Bytes()
return
}
func decodeResponse(resp *http.Response, v interface{}) error {
if resp.Body == nil {
return nil
}
rbody, err := ioutil.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("error reading response body: %s", err)
}
if err = json.Unmarshal(rbody, &v); err != nil {
return err
}
return nil
}
func ResponseJSON(resp *http.Response) (jbody map[string]interface{}, err error) {
err = decodeResponse(resp, &jbody)
return jbody, err
}
func TypedResponse(resp *http.Response, v interface{}) error {
var intermediate struct {
Data struct {
Result json.RawMessage `json:"result"`
} `json:"data"`
}
err := decodeResponse(resp, &intermediate)
if err != nil {
return fmt.Errorf("error reading response envelope: %v", err)
}
if err = json.Unmarshal(intermediate.Data.Result, v); err != nil {
return fmt.Errorf("error unmarshalling result %v", err)
}
return nil
}
2019-09-07 15:07:09 -04:00
func (s *Session) Login(username string, password string, otp string) (err error) {
reqUser := map[string]interface{}{"username": username, "password": password}
if otp != "" {
reqUser["otp"] = otp
}
reqbody := ParamsToBody(reqUser)
olddebug := *Debug
*Debug = false // don't share passwords in debug log
resp, err := s.Post("/access/ticket", nil, nil, &reqbody)
*Debug = olddebug
if err != nil {
return err
}
if resp == nil {
return errors.New("Login error reading response")
}
dr, _ := httputil.DumpResponse(resp, true)
jbody, err := ResponseJSON(resp)
if err != nil {
return err
}
if jbody == nil || jbody["data"] == nil {
return fmt.Errorf("Invalid login response:\n-----\n%s\n-----", dr)
}
dat := jbody["data"].(map[string]interface{})
2019-09-07 15:07:09 -04:00
//Check if the 2FA was required
if dat["NeedTFA"] == 1.0 {
return errors.New("Missing TFA code")
}
s.AuthTicket = dat["ticket"].(string)
s.CsrfToken = dat["CSRFPreventionToken"].(string)
return 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
}
if headers != nil {
req.Header = *headers
}
if s.AuthTicket != "" {
req.Header.Add("Cookie", "PVEAuthCookie="+s.AuthTicket)
req.Header.Add("CSRFPreventionToken", s.CsrfToken)
}
return
}
func (s *Session) Do(req *http.Request) (*http.Response, error) {
// Add session headers
for k := range s.Headers {
req.Header.Set(k, s.Headers.Get(k))
}
if *Debug {
d, _ := httputil.DumpRequestOut(req, true)
2019-10-27 08:10:35 -04:00
log.Printf(">>>>>>>>>> REQUEST:\n%v", string(d))
}
resp, err := s.httpClient.Do(req)
2020-02-26 02:47:44 -05:00
if err != nil {
return nil, err
}
2020-02-26 02:47:44 -05:00
// The response body reader needs to be closed, but lots of places call
// session.Do, and they might not be able to reliably close it themselves.
// Therefore, read the body out, close the original, then replace it with
// a NopCloser over the bytes, which does not need to be closed downsteam.
respBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
2020-02-26 02:47:44 -05:00
resp.Body.Close()
resp.Body = ioutil.NopCloser(bytes.NewReader(respBody))
if *Debug {
dr, _ := httputil.DumpResponse(resp, true)
2019-10-27 08:10:35 -04:00
log.Printf("<<<<<<<<<< RESULT:\n%v", string(dr))
}
if resp.StatusCode < 200 || resp.StatusCode > 299 {
return resp, errors.New(resp.Status)
}
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 *http.Response, err error) {
// add params to url here
url = s.ApiUrl + url
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("Accept", "application/json")
return s.Do(req)
}
// Perform a simple get to an endpoint and unmarshall returned JSON
func (s *Session) RequestJSON(
method string,
url string,
params *url.Values,
headers *http.Header,
body interface{},
responseContainer interface{},
) (resp *http.Response, err error) {
var bodyjson []byte
if body != nil {
bodyjson, err = json.Marshal(body)
if err != nil {
return nil, err
}
}
// if headers == nil {
// headers = &http.Header{}
// headers.Add("Content-Type", "application/json")
// }
resp, err = s.Request(method, url, params, headers, &bodyjson)
if err != nil {
return resp, err
}
// err = util.CheckHTTPResponseStatusCode(resp)
// if err != nil {
// return nil, err
// }
rbody, err := ioutil.ReadAll(resp.Body)
if err != nil {
return resp, errors.New("error reading response body")
}
if err = json.Unmarshal(rbody, &responseContainer); err != nil {
return resp, err
}
return resp, nil
}
func (s *Session) Delete(
url string,
params *url.Values,
headers *http.Header,
) (resp *http.Response, err error) {
return s.Request("DELETE", url, params, headers, nil)
}
func (s *Session) Get(
url string,
params *url.Values,
headers *http.Header,
) (resp *http.Response, err error) {
return s.Request("GET", url, params, headers, nil)
}
func (s *Session) GetJSON(
url string,
params *url.Values,
headers *http.Header,
responseContainer interface{},
) (resp *http.Response, err error) {
return s.RequestJSON("GET", url, params, headers, nil, responseContainer)
}
func (s *Session) Head(
url string,
params *url.Values,
headers *http.Header,
) (resp *http.Response, err error) {
return s.Request("HEAD", url, params, headers, nil)
}
func (s *Session) Post(
url string,
params *url.Values,
headers *http.Header,
body *[]byte,
) (resp *http.Response, err error) {
if headers == nil {
headers = &http.Header{}
headers.Add("Content-Type", "application/x-www-form-urlencoded")
}
return s.Request("POST", url, params, headers, body)
}
func (s *Session) PostJSON(
url string,
params *url.Values,
headers *http.Header,
body interface{},
responseContainer interface{},
) (resp *http.Response, err error) {
return s.RequestJSON("POST", url, params, headers, body, responseContainer)
}
func (s *Session) Put(
url string,
params *url.Values,
headers *http.Header,
body *[]byte,
) (resp *http.Response, err error) {
if headers == nil {
headers = &http.Header{}
headers.Add("Content-Type", "application/x-www-form-urlencoded")
}
return s.Request("PUT", url, params, headers, body)
}