initial commit

This commit is contained in:
ston1th 2020-06-01 12:18:20 +02:00
commit 501d579a90
35 changed files with 11357 additions and 0 deletions

226
client.go Normal file
View file

@ -0,0 +1,226 @@
// Copyright (C) 2020 Marius Schellenberger
package pve
import (
"bytes"
"context"
"crypto/tls"
"errors"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"net/http/httputil"
"strings"
"sync"
"time"
)
/*
type BackoffFunc func(retries int) time.Duration
func ConstantBackoff(d time.Duration) BackoffFunc {
return func(_ int) time.Duration {
return d
}
}
func ExponentialBackoff(b float64, d time.Duration) BackoffFunc {
return func(retries int) time.Duration {
return time.Duration(math.Pow(b, float64(retries))) * d
}
}
*/
type Client struct {
sync.Mutex
endpoint string
username string
password string
session session
//pollInterval time.Duration
//backoffFunc BackoffFunc
httpClient *http.Client
debugWriter io.Writer
insecure bool
Server ServerClient
Task TaskClient
Pool PoolClient
Node NodeClient
}
type session struct {
Username string `json:"username"`
Ticket string `json:"ticket"`
CSRFPreventionToken string `json:"CSRFPreventionToken"`
Time time.Time `json:"-"`
}
type ClientOption func(*Client)
func WithEndpoint(endpoint string) ClientOption {
return func(client *Client) {
client.endpoint = strings.TrimRight(endpoint, "/")
}
}
/*
func WithPollInterval(pollInterval time.Duration) ClientOption {
return func(client *Client) {
client.pollInterval = pollInterval
}
}
func WithBackoffFunc(f BackoffFunc) ClientOption {
return func(client *Client) {
client.backoffFunc = f
}
}
*/
func WithDebugWriter(debugWriter io.Writer) ClientOption {
return func(client *Client) {
client.debugWriter = debugWriter
}
}
func WithHTTPClient(httpClient *http.Client) ClientOption {
return func(client *Client) {
client.httpClient = httpClient
}
}
func WithInsecureClient() ClientOption {
return func(client *Client) {
client.insecure = true
}
}
func WithCredentials(username, password string) ClientOption {
return func(client *Client) {
client.username = username
client.password = password
}
}
func NewClient(options ...ClientOption) *Client {
client := &Client{}
/*
endpoint: Endpoint,
backoffFunc: ExponentialBackoff(2, 500*time.Millisecond),
pollInterval: 500 * time.Millisecond,
}*/
for _, option := range options {
option(client)
}
if client.httpClient == nil {
client.httpClient = &http.Client{Transport: &http.Transport{
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
}).DialContext,
TLSClientConfig: &tls.Config{
InsecureSkipVerify: client.insecure,
},
}}
}
client.Server = ServerClient{client: client}
client.Task = TaskClient{client: client}
client.Pool = PoolClient{client: client}
client.Node = NodeClient{client: client}
return client
}
func (c *Client) Auth() (err error) {
if !time.Now().After(c.session.Time) {
return
}
c.Lock()
defer c.Unlock()
if !time.Now().After(c.session.Time) {
return
}
u := c.endpoint + "/access/ticket"
body := httpbody{"username": c.username, "password": c.password}
r, err := http.NewRequest("POST", u, body.Reader())
if err != nil {
return
}
resp, err := c.httpClient.Do(r)
if err != nil {
return
}
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("api authentication failed: %w", errors.New(resp.Status))
}
err = unwrapData(resp, &c.session)
if err != nil {
return
}
c.session.Time = time.Now().Add(time.Hour)
return
}
func (c *Client) NewRequest(ctx context.Context, method, path string, body io.Reader) (*http.Request, error) {
err := c.Auth()
if err != nil {
return nil, err
}
url := c.endpoint + path
req, err := http.NewRequest(method, url, body)
if err != nil {
return nil, err
}
req.AddCookie(&http.Cookie{Name: "PVEAuthCookie", Value: c.session.Ticket})
if method != "GET" {
req.Header.Add("CSRFPreventionToken", c.session.CSRFPreventionToken)
}
req = req.WithContext(ctx)
return req, nil
}
var HTTPErr = errors.New("http error")
func (c *Client) Do(r *http.Request, v interface{}) (resp *http.Response, err error) {
resp, err = c.httpClient.Do(r)
if err != nil {
return
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
resp.Body.Close()
return
}
resp.Body.Close()
resp.Body = ioutil.NopCloser(bytes.NewReader(body))
if c.debugWriter != nil {
dumpReq, err := httputil.DumpRequest(r, true)
if err != nil {
return resp, err
}
fmt.Fprintf(c.debugWriter, "--- Request:\n%s\n\n", dumpReq)
dumpResp, err := httputil.DumpResponse(resp, true)
if err != nil {
return resp, err
}
fmt.Fprintf(c.debugWriter, "--- Response:\n%s\n\n", dumpResp)
}
err = unwrapData(resp, v)
if resp.StatusCode >= 400 && resp.StatusCode <= 599 {
if err != nil {
err = fmt.Errorf("%w: %s: %s", HTTPErr, errors.New(resp.Status), err.Error())
return
}
err = fmt.Errorf("%w: %s", HTTPErr, errors.New(resp.Status))
}
return
}