All checks were successful
continuous-integration/drone/push Build is passing
342 lines
8.6 KiB
Go
Executable File
342 lines
8.6 KiB
Go
Executable File
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)
|
|
}
|