This commit is contained in:
341
local/go-ucs/api/client.go
Executable file
341
local/go-ucs/api/client.go
Executable file
@@ -0,0 +1,341 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/dnaeon/go-ucs/mo"
|
||||
"golang.org/x/time/rate"
|
||||
)
|
||||
|
||||
// RateLimit limits the number of requests per second that a Client
|
||||
// can send to a remote Cisco UCS API endpoint using a rate.Limiter
|
||||
// token bucket configured with the provided requests per seconds and
|
||||
// burst. A request will wait for up to the given wait time.
|
||||
type RateLimit struct {
|
||||
// RequestsPerSecond defines the allowed number of requests per second.
|
||||
RequestsPerSecond float64
|
||||
|
||||
// Burst is the maximum burst size.
|
||||
Burst int
|
||||
|
||||
// Wait defines the maximum time a request will wait for a token to be consumed.
|
||||
Wait time.Duration
|
||||
}
|
||||
|
||||
// Config type contains the setting used by the Client.
|
||||
type Config struct {
|
||||
// HttpClient is the HTTP client to use for sending requests.
|
||||
// If nil then we use http.DefaultClient for all requests.
|
||||
HttpClient *http.Client
|
||||
|
||||
// Endpoint is the base URL to the remote Cisco UCS Manager endpoint.
|
||||
Endpoint string
|
||||
|
||||
// Username to use when authenticating to the remote endpoint.
|
||||
Username string
|
||||
|
||||
// Password to use when authenticating to the remote endpoint.
|
||||
Password string
|
||||
|
||||
// RateLimit is used for limiting the number of requests per second
|
||||
// against the remote Cisco UCS API endpoint using a token bucket.
|
||||
RateLimit *RateLimit
|
||||
}
|
||||
|
||||
// Client is used for interfacing with the remote Cisco UCS API endpoint.
|
||||
type Client struct {
|
||||
config *Config
|
||||
apiUrl *url.URL
|
||||
limiter *rate.Limiter
|
||||
|
||||
// Cookie is the authentication cookie currently in use.
|
||||
// It's value is set by the AaaLogin and AaaRefresh methods.
|
||||
Cookie string
|
||||
}
|
||||
|
||||
// NewClient creates a new API client from the given config.
|
||||
func NewClient(config Config) (*Client, error) {
|
||||
if config.HttpClient == nil {
|
||||
config.HttpClient = http.DefaultClient
|
||||
}
|
||||
|
||||
baseUrl, err := url.Parse(config.Endpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
apiUrl, err := url.Parse(apiEndpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var limiter *rate.Limiter
|
||||
if config.RateLimit != nil {
|
||||
rps := rate.Limit(config.RateLimit.RequestsPerSecond)
|
||||
limiter = rate.NewLimiter(rps, config.RateLimit.Burst)
|
||||
}
|
||||
|
||||
client := &Client{
|
||||
config: &config,
|
||||
apiUrl: baseUrl.ResolveReference(apiUrl),
|
||||
limiter: limiter,
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
// Hostname returns the host portion of the remote UCS API endpoint without any port number.
|
||||
func (c *Client) Hostname() string {
|
||||
return c.apiUrl.Host
|
||||
}
|
||||
|
||||
// AaaLogin performs the initial authentication to the remote Cisco UCS API endpoint.
|
||||
func (c *Client) AaaLogin(ctx context.Context) (*AaaLoginResponse, error) {
|
||||
req := AaaLoginRequest{
|
||||
InName: c.config.Username,
|
||||
InPassword: c.config.Password,
|
||||
}
|
||||
|
||||
var resp AaaLoginResponse
|
||||
if err := c.Request(ctx, req, &resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.IsError() {
|
||||
return nil, resp.ToError()
|
||||
}
|
||||
|
||||
// Set authentication cookie for future re-use when needed.
|
||||
c.Cookie = resp.OutCookie
|
||||
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
// AaaRefresh refreshes the current session by requesting a new authentication cookie.
|
||||
func (c *Client) AaaRefresh(ctx context.Context) (*AaaRefreshResponse, error) {
|
||||
req := AaaRefreshRequest{
|
||||
InName: c.config.Username,
|
||||
InPassword: c.config.Password,
|
||||
InCookie: c.Cookie,
|
||||
}
|
||||
|
||||
var resp AaaRefreshResponse
|
||||
if err := c.Request(ctx, req, &resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.IsError() {
|
||||
return nil, resp.ToError()
|
||||
}
|
||||
|
||||
// Set new authentication cookie
|
||||
c.Cookie = resp.OutCookie
|
||||
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
// AaaKeepAlive sends a request to keep the current session active using the same cookie.
|
||||
func (c *Client) AaaKeepAlive(ctx context.Context) (*AaaKeepAliveResponse, error) {
|
||||
req := AaaKeepAliveRequest{
|
||||
Cookie: c.Cookie,
|
||||
}
|
||||
|
||||
var resp AaaKeepAliveResponse
|
||||
if err := c.Request(ctx, req, &resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.IsError() {
|
||||
return nil, resp.ToError()
|
||||
}
|
||||
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
// AaaLogout invalidates the current client session.
|
||||
func (c *Client) AaaLogout(ctx context.Context) (*AaaLogoutResponse, error) {
|
||||
req := AaaLogoutRequest{
|
||||
InCookie: c.Cookie,
|
||||
}
|
||||
|
||||
var resp AaaLogoutResponse
|
||||
if err := c.Request(ctx, req, &resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.IsError() {
|
||||
return nil, resp.ToError()
|
||||
}
|
||||
|
||||
c.Cookie = ""
|
||||
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
// doRequest sends a request to the remote Cisco UCS API endpoint.
|
||||
func (c *Client) doRequest(ctx context.Context, in, out interface{}) error {
|
||||
data, err := xmlMarshalWithSelfClosingTags(in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println("doRequest sending following XML request:")
|
||||
fmt.Println(string(data[:]))
|
||||
|
||||
r, err := http.NewRequest("POST", c.apiUrl.String(), bytes.NewBuffer(data))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req := r.WithContext(ctx)
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
resp, err := c.config.HttpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println("doRequest received following response:")
|
||||
fmt.Println(string(body[:]))
|
||||
|
||||
return xml.Unmarshal(body, &out)
|
||||
}
|
||||
|
||||
// Request sends a POST request to the remote Cisco UCS API endpoint.
|
||||
func (c *Client) Request(ctx context.Context, in, out interface{}) error {
|
||||
// Rate limit requests if we are using a limiter
|
||||
if c.limiter != nil {
|
||||
ctxWithTimeout, cancel := context.WithTimeout(ctx, c.config.RateLimit.Wait)
|
||||
defer cancel()
|
||||
if err := c.limiter.Wait(ctxWithTimeout); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return c.doRequest(ctx, in, out)
|
||||
}
|
||||
|
||||
// RequestNow sends a POST request to the remote Cisco UCS API endpoint immediately.
|
||||
// This bypasses any rate limiter configuration that may be used and is
|
||||
// meant to be used for priority requests, e.g. refreshing a token, logging out, etc.
|
||||
func (c *Client) RequestNow(ctx context.Context, in, out interface{}) error {
|
||||
return c.doRequest(ctx, in, out)
|
||||
}
|
||||
|
||||
// ConfigResolveDn retrieves a single managed object for a specified DN.
|
||||
func (c *Client) ConfigResolveDn(ctx context.Context, in ConfigResolveDnRequest, out mo.Any) error {
|
||||
var resp ConfigResolveDnResponse
|
||||
if err := c.Request(ctx, in, &resp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.IsError() {
|
||||
return resp.ToError()
|
||||
}
|
||||
|
||||
// The requested managed object is contained within the inner XML document,
|
||||
// which we need to unmarshal first into the given concrete type.
|
||||
return xml.Unmarshal(resp.OutConfig.Inner, &out)
|
||||
|
||||
}
|
||||
|
||||
// ConfigResolveDns retrieves managed objects for a specified list of DNs.
|
||||
func (c *Client) ConfigResolveDns(ctx context.Context, in ConfigResolveDnsRequest, out mo.Any) (*ConfigResolveDnsResponse, error) {
|
||||
var resp ConfigResolveDnsResponse
|
||||
if err := c.Request(ctx, in, &resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.IsError() {
|
||||
return nil, resp.ToError()
|
||||
}
|
||||
|
||||
inner, err := xml.Marshal(resp.OutConfigs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := xml.Unmarshal(inner, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
// ConfigResolveClass retrieves managed objects of the specified class.
|
||||
func (c *Client) ConfigResolveClass(ctx context.Context, in ConfigResolveClassRequest, out mo.Any) error {
|
||||
var resp ConfigResolveClassResponse
|
||||
if err := c.Request(ctx, in, &resp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.IsError() {
|
||||
return resp.ToError()
|
||||
}
|
||||
|
||||
inner, err := xml.Marshal(resp.OutConfigs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// The requested managed objects are contained within the inner XML document,
|
||||
// which we need to unmarshal first into the given concrete type.
|
||||
return xml.Unmarshal(inner, &out)
|
||||
}
|
||||
|
||||
// ConfigResolveClasses retrieves managed objects from the specified list of classes.
|
||||
func (c *Client) ConfigResolveClasses(ctx context.Context, in ConfigResolveClassesRequest, out mo.Any) error {
|
||||
var resp ConfigResolveClassesResponse
|
||||
if err := c.Request(ctx, in, &resp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.IsError() {
|
||||
return resp.ToError()
|
||||
}
|
||||
|
||||
inner, err := xml.Marshal(resp.OutConfigs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// The requested managed objects are contained within the inner XML document,
|
||||
// which we need to unmarshal first into the given concrete type.
|
||||
return xml.Unmarshal(inner, &out)
|
||||
}
|
||||
|
||||
// ConfigResolveChildren retrieves children of managed objects under a specified DN.
|
||||
func (c *Client) ConfigResolveChildren(ctx context.Context, in ConfigResolveChildrenRequest, out mo.Any) error {
|
||||
var resp ConfigResolveChildrenResponse
|
||||
|
||||
if err := c.Request(ctx, in, &resp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.IsError() {
|
||||
return resp.ToError()
|
||||
}
|
||||
|
||||
inner, err := xml.Marshal(resp.OutConfigs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// The requested managed objects are contained within the inner XML document,
|
||||
// which we need to unmarshal first into the given concrete type.
|
||||
return xml.Unmarshal(inner, &out)
|
||||
}
|
Reference in New Issue
Block a user