This commit is contained in:
326
local/go-ucs/api/api.go
Executable file
326
local/go-ucs/api/api.go
Executable file
@@ -0,0 +1,326 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/dnaeon/go-ucs/version"
|
||||
)
|
||||
|
||||
// The remote API endpoint to which we POST requests.
|
||||
const apiEndpoint = "nuova"
|
||||
|
||||
// The UserAgent that we use for our requests.
|
||||
const userAgent = "go-ucs/" + version.Version
|
||||
|
||||
// BaseResponse contains the base attributes as returned in a response from a
|
||||
// Cisco UCS API endpoint.
|
||||
type BaseResponse struct {
|
||||
Cookie string `xml:"cookie,attr"`
|
||||
Response string `xml:"response,attr"`
|
||||
ErrorCode string `xml:"errorCode,attr,omitempty"`
|
||||
InvocationResult string `xml:"invocationResult,attr,omitempty"`
|
||||
ErrorDescription string `xml:"errorDescr,attr,omitempty"`
|
||||
}
|
||||
|
||||
// IsError returns a boolean indicating whether the response contains errors.
|
||||
func (b *BaseResponse) IsError() bool {
|
||||
return b.ErrorCode != ""
|
||||
}
|
||||
|
||||
// Error implements the error interface.
|
||||
func (b *BaseResponse) Error() string {
|
||||
return fmt.Sprintf("%s: %s (code %s)", b.ErrorDescription, b.InvocationResult, b.ErrorCode)
|
||||
}
|
||||
|
||||
// ToError creates a new error.Error from the error response fields.
|
||||
func (b *BaseResponse) ToError() error {
|
||||
return errors.New(b.Error())
|
||||
}
|
||||
|
||||
// AaaLoginRequest is the type which is sent during initial login
|
||||
// in order to obtain authentication cookie.
|
||||
type AaaLoginRequest struct {
|
||||
XMLName xml.Name `xml:"aaaLogin"`
|
||||
InName string `xml:"inName,attr"`
|
||||
InPassword string `xml:"inPassword,attr"`
|
||||
}
|
||||
|
||||
// AaaLoginResponse represents the response after a successful login to UCS manager.
|
||||
type AaaLoginResponse struct {
|
||||
BaseResponse
|
||||
XMLName xml.Name `xml:"aaaLogin"`
|
||||
OutCookie string `xml:"outCookie,attr,omitempty"`
|
||||
OutRefreshPeriod int `xml:"outRefreshPeriod,attr,omitempty"`
|
||||
OutPriv string `xml:"outPriv,attr,omitempty"`
|
||||
OutDomains string `xml:"outDomains,attr,omitempty"`
|
||||
OutChannel string `xml:"outChannel,attr,omitempty"`
|
||||
OutEvtChannel string `xml:"outEvtChannel,attr,omitempty"`
|
||||
OutName string `xml:"outName,attr,omitempty"`
|
||||
OutVersion string `xml:"outVersion,attr,omitempty"`
|
||||
OutSessionId string `xml:"outSessionId,attr,omitempty"`
|
||||
}
|
||||
|
||||
// AaaRefreshRequest type is used for sending a request to the remote API endpoint for
|
||||
// refreshing a session using the 47-character cookie obtained from a previous refresh
|
||||
// or during the initial authentication as returned in a AaaLoginResponse.
|
||||
type AaaRefreshRequest struct {
|
||||
XMLName xml.Name `xml:"aaaRefresh"`
|
||||
InName string `xml:"inName,attr"`
|
||||
InPassword string `xml:"inPassword,attr"`
|
||||
InCookie string `xml:"inCookie,attr"`
|
||||
}
|
||||
|
||||
// AaaRefreshResponse is the response associated to a AaaRefreshRequest.
|
||||
type AaaRefreshResponse struct {
|
||||
XMLName xml.Name `xml:"aaaRefresh"`
|
||||
AaaLoginResponse
|
||||
}
|
||||
|
||||
// AaaLogoutRequest type is used for sending a request to invalidate an existing
|
||||
// authentication cookie.
|
||||
type AaaLogoutRequest struct {
|
||||
XMLName xml.Name `xml:"aaaLogout"`
|
||||
InCookie string `xml:"inCookie,attr"`
|
||||
}
|
||||
|
||||
// AaaLogoutResponse represents the type that is returned after a call to aaaLogout method.
|
||||
type AaaLogoutResponse struct {
|
||||
BaseResponse
|
||||
XMLName xml.Name `xml:"aaaLogout"`
|
||||
OutStatus string `xml:"outStatus,attr,omitempty"`
|
||||
}
|
||||
|
||||
// AaaKeepAliveRequest type is used for sending a request for keeping a session active
|
||||
// until the default session time expires.
|
||||
type AaaKeepAliveRequest struct {
|
||||
XMLName xml.Name `xml:"aaaKeepAlive"`
|
||||
Cookie string `xml:"cookie,attr"`
|
||||
}
|
||||
|
||||
// AaaKeepAliveResponse is the response type associated with a AaaKeepAliveRequest.
|
||||
type AaaKeepAliveResponse struct {
|
||||
BaseResponse
|
||||
XMLName xml.Name `xml:"aaaKeepAlive"`
|
||||
Cookie string `xml:"cookie,attr"`
|
||||
}
|
||||
|
||||
// ConfigResolveDnRequest type is used for constructing requests that retrieve a
|
||||
// single managed object with the given DN.
|
||||
type ConfigResolveDnRequest struct {
|
||||
XMLName xml.Name `xml:"configResolveDn"`
|
||||
Cookie string `xml:"cookie,attr"`
|
||||
Dn string `xml:"dn,attr"`
|
||||
InHierarchical string `xml:"inHierarchical,attr,omitempty"`
|
||||
}
|
||||
|
||||
// ConfigResolveDnResponse is the type associated with a ConfigResolveDnRequest type.
|
||||
// Specific classes contained within OutConfig should be xml.Unmarshal'ed first.
|
||||
type ConfigResolveDnResponse struct {
|
||||
BaseResponse
|
||||
XMLName xml.Name `xml:"configResolveDn"`
|
||||
Dn string `xml:"dn,attr"`
|
||||
OutConfig InnerXml `xml:"outConfig"`
|
||||
}
|
||||
|
||||
// Dn represents a single managed object DN.
|
||||
type Dn struct {
|
||||
XMLName xml.Name `xml:"dn"`
|
||||
Value string `xml:"value,attr"`
|
||||
}
|
||||
|
||||
// NewDn creates a new DN value.
|
||||
func NewDn(value string) Dn {
|
||||
dn := Dn{
|
||||
Value: value,
|
||||
}
|
||||
|
||||
return dn
|
||||
}
|
||||
|
||||
// ConfigResolveDnsRequest type is used for constructing requests that retrieve
|
||||
// managed objects for a list of given DNs.
|
||||
type ConfigResolveDnsRequest struct {
|
||||
XMLName xml.Name `xml:"configResolveDns"`
|
||||
Cookie string `xml:"cookie,attr"`
|
||||
InHierarchical string `xml:"inHierarchical,attr,omitempty"`
|
||||
InDns []Dn `xml:"inDns>dn"`
|
||||
}
|
||||
|
||||
// ConfigResolveDnsResponse is the response type associated with a ConfigResolveDnsRequest.
|
||||
// The managed objects within OutConfigs field should be xml.Unmarshal'ed.
|
||||
type ConfigResolveDnsResponse struct {
|
||||
BaseResponse
|
||||
XMLName xml.Name `xml:"configResolveDns"`
|
||||
OutUnresolved []Dn `xml:"outUnresolved>dn"`
|
||||
OutConfigs InnerXml `xml:"outConfigs"`
|
||||
}
|
||||
|
||||
// InnerXml represents a generic configuration retrieved by the various query methods.
|
||||
// After a successful result from a query method a client should unmarshal the data
|
||||
// contained within an InnerXml to the specific managed object.
|
||||
type InnerXml struct {
|
||||
XMLName xml.Name
|
||||
Inner []byte `xml:",innerxml"`
|
||||
}
|
||||
|
||||
// ConfigResolveClassRequest type is used for constructing requests that retrieve
|
||||
// managed objects of a given class.
|
||||
type ConfigResolveClassRequest struct {
|
||||
XMLName xml.Name `xml:"configResolveClass"`
|
||||
Cookie string `xml:"cookie,attr"`
|
||||
ClassId string `xml:"classId,attr"`
|
||||
InHierarchical string `xml:"inHierarchical,attr,omitempty"`
|
||||
InFilter FilterAny `xml:"inFilter>any,omitempty"`
|
||||
}
|
||||
|
||||
// ConfigResolveClassResponse is the type associated with a ConfigResolveClassRequest.
|
||||
// Specific classes contained within OutConfigs should be xml.Unmarshal'ed first.
|
||||
type ConfigResolveClassResponse struct {
|
||||
BaseResponse
|
||||
XMLName xml.Name `xml:"configResolveClass"`
|
||||
OutConfigs InnerXml `xml:"outConfigs"`
|
||||
}
|
||||
|
||||
// Id represents an ID of a class.
|
||||
type Id struct {
|
||||
XMLName xml.Name `xml:"Id"`
|
||||
Value string `xml:"value,attr"`
|
||||
}
|
||||
|
||||
// NewId creates a new class id.
|
||||
func NewId(value string) Id {
|
||||
id := Id{
|
||||
Value: value,
|
||||
}
|
||||
|
||||
return id
|
||||
}
|
||||
|
||||
// ConfigResolveClassesRequest type is used for constructing requests that retrieve managed
|
||||
// objects in several classes.
|
||||
type ConfigResolveClassesRequest struct {
|
||||
XMLName xml.Name `xml:"configResolveClasses"`
|
||||
Cookie string `xml:"cookie,attr"`
|
||||
InHierarchical string `xml:"inHierarchical,attr,omitempty"`
|
||||
InIds []Id `xml:"inIds>Id"`
|
||||
}
|
||||
|
||||
// ConfigResolveClassesResponse is the response type associated with a ConfigResolveClassesRequest.
|
||||
type ConfigResolveClassesResponse struct {
|
||||
BaseResponse
|
||||
XMLName xml.Name `xml:"configResolveClasses"`
|
||||
OutConfigs InnerXml `xml:"outConfigs"`
|
||||
}
|
||||
|
||||
// ConfigResolveChildren type is used for constructing requests that retrieve
|
||||
// children of managed objects under a specified DN. A filter can be used to
|
||||
// reduce the number of children being returned.
|
||||
type ConfigResolveChildrenRequest struct {
|
||||
XMLName xml.Name `xml:"configResolveChildren"`
|
||||
Cookie string `xml:"cookie,attr"`
|
||||
ClassId string `xml:"classId,attr"`
|
||||
InDn string `xml:"inDn,attr"`
|
||||
InHierarchical string `xml:"inHierarchical,attr"`
|
||||
InFilter FilterAny `xml:"inFilter>any,omitempty"`
|
||||
}
|
||||
|
||||
// ConfigResolveChildrenResponse is the response type associated with a ConfigResolveChildrenRequest.
|
||||
type ConfigResolveChildrenResponse struct {
|
||||
BaseResponse
|
||||
XMLName xml.Name `xml:"configResolveChildren"`
|
||||
OutConfigs InnerXml `xml:"outConfigs"`
|
||||
}
|
||||
|
||||
// FilterAny represents any valid filter.
|
||||
type FilterAny interface{}
|
||||
|
||||
// FilterProperty represents a Property Filter.
|
||||
type FilterProperty struct {
|
||||
Class string `xml:"class,attr"`
|
||||
Property string `xml:"property,attr"`
|
||||
Value string `xml:"value,attr"`
|
||||
}
|
||||
|
||||
// FilterEq represents an Equality Filter.
|
||||
type FilterEq struct {
|
||||
XMLName xml.Name `xml:"eq"`
|
||||
FilterProperty
|
||||
}
|
||||
|
||||
// FilterNe represents a Not Equal Filter.
|
||||
type FilterNe struct {
|
||||
XMLName xml.Name `xml:"ne"`
|
||||
FilterProperty
|
||||
}
|
||||
|
||||
// FilterGt represents a Greater Than Filter.
|
||||
type FilterGt struct {
|
||||
XMLName xml.Name `xml:"gt"`
|
||||
FilterProperty
|
||||
}
|
||||
|
||||
// FilterGe represents a Greater Than Or Equal To Filter.
|
||||
type FilterGe struct {
|
||||
XMLName xml.Name `xml:"ge"`
|
||||
FilterProperty
|
||||
}
|
||||
|
||||
// FilterLt represents a Less Than Filter.
|
||||
type FilterLt struct {
|
||||
XMLName xml.Name `xml:"lt"`
|
||||
FilterProperty
|
||||
}
|
||||
|
||||
// FilterLe represents a Less Than Or Equal To Filter.
|
||||
type FilterLe struct {
|
||||
XMLName xml.Name `xml:"le"`
|
||||
FilterProperty
|
||||
}
|
||||
|
||||
// FilterWildcard represents a Wildcard Filter.
|
||||
// The wildcard filter uses standard regular expression syntax.
|
||||
type FilterWildcard struct {
|
||||
XMLName xml.Name `xml:"wcard"`
|
||||
FilterProperty
|
||||
}
|
||||
|
||||
// FilterAnyBits represents an Any Bits Filter.
|
||||
type FilterAnyBits struct {
|
||||
XMLName xml.Name `xml:"anybit"`
|
||||
FilterProperty
|
||||
}
|
||||
|
||||
// FilterAllBits represents an All Bits Filter.
|
||||
type FilterAllBits struct {
|
||||
XMLName xml.Name `xml:"allbits"`
|
||||
FilterProperty
|
||||
}
|
||||
|
||||
// FilterAnd represents a composite AND Filter.
|
||||
type FilterAnd struct {
|
||||
XMLName xml.Name `xml:"and"`
|
||||
Filters []FilterAny
|
||||
}
|
||||
|
||||
// FilterOr represents a composite OR Filter.
|
||||
type FilterOr struct {
|
||||
XMLName xml.Name `xml:"or"`
|
||||
Filters []FilterAny
|
||||
}
|
||||
|
||||
// FilterNot represents a NOT Modifier Filter.
|
||||
type FilterNot struct {
|
||||
XMLName xml.Name `xml:"not"`
|
||||
Filters []FilterAny
|
||||
}
|
||||
|
||||
// FilterBetween represents a Between Filter.
|
||||
type FilterBetween struct {
|
||||
XMLName xml.Name `xml:"bw"`
|
||||
Class string `xml:"class,attr"`
|
||||
Property string `xml:"property,attr"`
|
||||
FirstVault string `xml:"firstValue,attr"`
|
||||
SecondValue string `xml:"secondValue,attr"`
|
||||
}
|
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)
|
||||
}
|
2
local/go-ucs/api/doc.go
Executable file
2
local/go-ucs/api/doc.go
Executable file
@@ -0,0 +1,2 @@
|
||||
// Package api provides types and methods for interfacing with a Cisco UCS API endpoint.
|
||||
package api
|
73
local/go-ucs/api/example_aaa_keepalive_test.go
Executable file
73
local/go-ucs/api/example_aaa_keepalive_test.go
Executable file
@@ -0,0 +1,73 @@
|
||||
package api_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/dnaeon/go-ucs/api"
|
||||
)
|
||||
|
||||
func Example_aaaKeepAlive() {
|
||||
// The following example shows how to keep a session alive.
|
||||
|
||||
// Skip SSL certificate verification of remote endpoint.
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
httpClient := &http.Client{Transport: tr}
|
||||
|
||||
// Create a new Cisco UCS API client
|
||||
config := api.Config{
|
||||
Endpoint: "https://ucs01.example.org/",
|
||||
Username: "admin",
|
||||
Password: "password",
|
||||
HttpClient: httpClient,
|
||||
}
|
||||
|
||||
client, err := api.NewClient(config)
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to create API client: %s", err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// Authenticate to the remote API endpoint and obtain authentication cookie
|
||||
log.Printf("Logging in to %s\n", config.Endpoint)
|
||||
if _, err := client.AaaLogin(ctx); err != nil {
|
||||
log.Fatalf("Unable to authenticate: %s", err)
|
||||
}
|
||||
defer client.AaaLogout(ctx)
|
||||
|
||||
log.Printf("Got authentication cookie: %s\n", client.Cookie)
|
||||
|
||||
// Channel on which the shutdown signal is sent
|
||||
quit := make(chan os.Signal, 1)
|
||||
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
||||
|
||||
// Send a KeepAlive request every minute
|
||||
ticker := time.NewTicker(1 * time.Minute)
|
||||
|
||||
L:
|
||||
for {
|
||||
select {
|
||||
case <-quit:
|
||||
log.Println("Keyboard interrupt detected, terminating.")
|
||||
break L
|
||||
case <-ticker.C:
|
||||
log.Println("Sending KeepAlive request ...")
|
||||
resp, err := client.AaaKeepAlive(ctx)
|
||||
if err != nil {
|
||||
log.Printf("Unable to keep session alive: %s\n", err)
|
||||
break L
|
||||
}
|
||||
|
||||
log.Printf("Got response with cookie: %s\n", resp.Cookie)
|
||||
}
|
||||
}
|
||||
}
|
44
local/go-ucs/api/example_aaa_login_test.go
Executable file
44
local/go-ucs/api/example_aaa_login_test.go
Executable file
@@ -0,0 +1,44 @@
|
||||
package api_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/dnaeon/go-ucs/api"
|
||||
)
|
||||
|
||||
func Example_aaaLogin() {
|
||||
// The following example shows how to login and logout from a Cisco UCS API endpoint
|
||||
|
||||
// Skip SSL certificate verification of remote endpoint.
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
httpClient := &http.Client{Transport: tr}
|
||||
|
||||
// Create a new Cisco UCS API client
|
||||
config := api.Config{
|
||||
Endpoint: "https://ucs01.example.org/",
|
||||
Username: "admin",
|
||||
Password: "password",
|
||||
HttpClient: httpClient,
|
||||
}
|
||||
|
||||
client, err := api.NewClient(config)
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to create API client: %s\n", err)
|
||||
}
|
||||
|
||||
// Authenticate to the remote API endpoint and obtain authentication cookie
|
||||
log.Printf("Logging in to %s\n", config.Endpoint)
|
||||
|
||||
ctx := context.Background()
|
||||
if _, err := client.AaaLogin(ctx); err != nil {
|
||||
log.Fatalf("Unable to authenticate: %s", err)
|
||||
}
|
||||
defer client.AaaLogout(ctx)
|
||||
|
||||
log.Printf("Got authentication cookie: %s\n", client.Cookie)
|
||||
}
|
51
local/go-ucs/api/example_aaa_refresh_test.go
Executable file
51
local/go-ucs/api/example_aaa_refresh_test.go
Executable file
@@ -0,0 +1,51 @@
|
||||
package api_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/dnaeon/go-ucs/api"
|
||||
)
|
||||
|
||||
func Example_aaaRefresh() {
|
||||
// The following example shows how to refresh an existing session.
|
||||
|
||||
// Skip SSL certificate verification of remote endpoint.
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
httpClient := &http.Client{Transport: tr}
|
||||
|
||||
// Create a new Cisco UCS API client
|
||||
config := api.Config{
|
||||
Endpoint: "https://ucs01.example.org/",
|
||||
Username: "admin",
|
||||
Password: "password",
|
||||
HttpClient: httpClient,
|
||||
}
|
||||
|
||||
client, err := api.NewClient(config)
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to create API client: %s", err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// Authenticate to the remote API endpoint and obtain authentication cookie
|
||||
log.Printf("Logging in to %s\n", config.Endpoint)
|
||||
if _, err := client.AaaLogin(ctx); err != nil {
|
||||
log.Fatalf("Unable to authenticate: %s", err)
|
||||
}
|
||||
defer client.AaaLogout(ctx)
|
||||
|
||||
log.Printf("Got authentication cookie: %s\n", client.Cookie)
|
||||
|
||||
log.Println("Refreshing session")
|
||||
if _, err := client.AaaRefresh(ctx); err != nil {
|
||||
log.Fatalf("Unable to refresh session: %s\n", err)
|
||||
}
|
||||
|
||||
log.Printf("New authentication cookie is: %s\n", client.Cookie)
|
||||
}
|
96
local/go-ucs/api/example_composite_filter_test.go
Executable file
96
local/go-ucs/api/example_composite_filter_test.go
Executable file
@@ -0,0 +1,96 @@
|
||||
package api_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/xml"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/dnaeon/go-ucs/api"
|
||||
"github.com/dnaeon/go-ucs/mo"
|
||||
)
|
||||
|
||||
func Example_compositeFilter() {
|
||||
// The following example shows how to use a composite AND property filter.
|
||||
|
||||
// Skip SSL certificate verification of remote endpoint.
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
httpClient := &http.Client{Transport: tr}
|
||||
|
||||
// Create a new Cisco UCS API client
|
||||
config := api.Config{
|
||||
Endpoint: "https://ucs01.example.org/",
|
||||
Username: "admin",
|
||||
Password: "password",
|
||||
HttpClient: httpClient,
|
||||
}
|
||||
|
||||
client, err := api.NewClient(config)
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to create API client: %s", err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
log.Printf("Logging in to %s\n", config.Endpoint)
|
||||
if _, err := client.AaaLogin(ctx); err != nil {
|
||||
log.Fatalf("Unable to login: %s\n", err)
|
||||
}
|
||||
defer client.AaaLogout(ctx)
|
||||
|
||||
log.Printf("Got authentication cookie: %s\n", client.Cookie)
|
||||
|
||||
// The type into which we unmarshal the result data
|
||||
type blades struct {
|
||||
XMLName xml.Name
|
||||
Blades []mo.ComputeBlade `xml:"computeBlade"`
|
||||
}
|
||||
|
||||
// Create a composite AND property filter, which will find all blades,
|
||||
// which have total memory greater than or equal to 2048 MB and are in chassis 3.
|
||||
filter := api.FilterAnd{
|
||||
Filters: []api.FilterAny{
|
||||
api.FilterGe{
|
||||
FilterProperty: api.FilterProperty{
|
||||
Class: "computeBlade",
|
||||
Property: "totalMemory",
|
||||
Value: "2048",
|
||||
},
|
||||
},
|
||||
api.FilterEq{
|
||||
FilterProperty: api.FilterProperty{
|
||||
Class: "computeBlade",
|
||||
Property: "chassisId",
|
||||
Value: "3",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
req := api.ConfigResolveClassRequest{
|
||||
Cookie: client.Cookie,
|
||||
InHierarchical: "false",
|
||||
ClassId: "computeBlade",
|
||||
InFilter: filter,
|
||||
}
|
||||
|
||||
var out blades
|
||||
log.Println("Retrieving managed objects with class `computeBlade`")
|
||||
if err := client.ConfigResolveClass(ctx, req, &out); err != nil {
|
||||
log.Fatalf("Unable to retrieve `computeBlade` managed object: %s", err)
|
||||
}
|
||||
|
||||
log.Printf("Retrieved %d compute blades\n", len(out.Blades))
|
||||
|
||||
for _, blade := range out.Blades {
|
||||
log.Printf("%s:\n", blade.Dn)
|
||||
log.Printf("\tNumber of CPUs: %d\n", blade.NumOfCpus)
|
||||
log.Printf("\tTotal Memory: %d\n", blade.TotalMemory)
|
||||
log.Printf("\tModel: %s\n", blade.Model)
|
||||
log.Printf("\tChassis ID: %d\n", blade.ChassisId)
|
||||
log.Printf("\tVendor: %s\n", blade.Vendor)
|
||||
}
|
||||
}
|
73
local/go-ucs/api/example_config_resolve_class_test.go
Executable file
73
local/go-ucs/api/example_config_resolve_class_test.go
Executable file
@@ -0,0 +1,73 @@
|
||||
package api_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/xml"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/dnaeon/go-ucs/api"
|
||||
"github.com/dnaeon/go-ucs/mo"
|
||||
)
|
||||
|
||||
func Example_configResolveClass() {
|
||||
// The following example shows how to retrieve all compute blades.
|
||||
|
||||
// Skip SSL certificate verification of remote endpoint.
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
httpClient := &http.Client{Transport: tr}
|
||||
|
||||
// Create a new Cisco UCS API client
|
||||
config := api.Config{
|
||||
Endpoint: "https://ucs01.example.org/",
|
||||
Username: "admin",
|
||||
Password: "password",
|
||||
HttpClient: httpClient,
|
||||
}
|
||||
|
||||
client, err := api.NewClient(config)
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to create API client: %s", err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
log.Printf("Logging in to %s\n", config.Endpoint)
|
||||
if _, err := client.AaaLogin(ctx); err != nil {
|
||||
log.Fatalf("Unable to login: %s\n", err)
|
||||
}
|
||||
defer client.AaaLogout(ctx)
|
||||
|
||||
log.Printf("Got authentication cookie: %s\n", client.Cookie)
|
||||
|
||||
// The type into which we unmarshal the result data
|
||||
type blades struct {
|
||||
XMLName xml.Name
|
||||
Blades []mo.ComputeBlade `xml:"computeBlade"`
|
||||
}
|
||||
|
||||
req := api.ConfigResolveClassRequest{
|
||||
Cookie: client.Cookie,
|
||||
ClassId: "computeBlade",
|
||||
InHierarchical: "false",
|
||||
}
|
||||
|
||||
var out blades
|
||||
|
||||
log.Println("Retrieving managed objects with class `computeBlade`")
|
||||
if err := client.ConfigResolveClass(ctx, req, &out); err != nil {
|
||||
log.Fatalf("Unable to retrieve `computeBlade` managed object: %s", err)
|
||||
}
|
||||
|
||||
log.Printf("Retrieved %d compute blades\n", len(out.Blades))
|
||||
for _, blade := range out.Blades {
|
||||
log.Printf("%s:\n", blade.Dn)
|
||||
log.Printf("\tNumber of CPUs: %d\n", blade.NumOfCpus)
|
||||
log.Printf("\tTotal Memory: %d\n", blade.TotalMemory)
|
||||
log.Printf("\tModel: %s\n", blade.Model)
|
||||
log.Printf("\tVendor: %s\n", blade.Vendor)
|
||||
}
|
||||
}
|
81
local/go-ucs/api/example_config_resolve_classes_test.go
Executable file
81
local/go-ucs/api/example_config_resolve_classes_test.go
Executable file
@@ -0,0 +1,81 @@
|
||||
package api_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/dnaeon/go-ucs/api"
|
||||
"github.com/dnaeon/go-ucs/mo"
|
||||
)
|
||||
|
||||
func Example_configResolveClasses() {
|
||||
// The following example shows how to retrieve managed objects from different classes.
|
||||
// In the example below we will retrieve the managed objects of `computeBlade` and `computeRackUnit` classes.
|
||||
|
||||
// Skip SSL certificate verification of remote endpoint.
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
httpClient := &http.Client{Transport: tr}
|
||||
|
||||
// Create a new Cisco UCS API client
|
||||
config := api.Config{
|
||||
Endpoint: "https://ucs01.example.org/",
|
||||
Username: "admin",
|
||||
Password: "password",
|
||||
HttpClient: httpClient,
|
||||
}
|
||||
|
||||
client, err := api.NewClient(config)
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to create API client: %s", err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
log.Printf("Logging in to %s\n", config.Endpoint)
|
||||
if _, err := client.AaaLogin(ctx); err != nil {
|
||||
log.Fatalf("Unable to login: %s\n", err)
|
||||
}
|
||||
defer client.AaaLogout(ctx)
|
||||
|
||||
log.Printf("Got authentication cookie: %s\n", client.Cookie)
|
||||
|
||||
req := api.ConfigResolveClassesRequest{
|
||||
Cookie: client.Cookie,
|
||||
InHierarchical: "false",
|
||||
InIds: []api.Id{
|
||||
api.NewId("computeBlade"),
|
||||
api.NewId("computeRackUnit"),
|
||||
},
|
||||
}
|
||||
|
||||
// ComputeItem is a container for all physical compute items.
|
||||
var out mo.ComputeItem
|
||||
|
||||
log.Println("Retrieving managed objects with classes `computeBlade` and `computeRackUnit`")
|
||||
if err := client.ConfigResolveClasses(ctx, req, &out); err != nil {
|
||||
log.Fatalf("Unable to retrieve `computeBlade` and `computeRackUnit` managed object: %s", err)
|
||||
}
|
||||
|
||||
log.Printf("Retrieved %d compute blades\n", len(out.Blades))
|
||||
log.Printf("Retrieved %d compute rack units\n", len(out.RackUnits))
|
||||
|
||||
for _, blade := range out.Blades {
|
||||
log.Printf("%s:\n", blade.Dn)
|
||||
log.Printf("\tNumber of CPUs: %d\n", blade.NumOfCpus)
|
||||
log.Printf("\tTotal Memory: %d\n", blade.TotalMemory)
|
||||
log.Printf("\tModel: %s\n", blade.Model)
|
||||
log.Printf("\tVendor: %s\n", blade.Vendor)
|
||||
}
|
||||
|
||||
for _, blade := range out.RackUnits {
|
||||
log.Printf("%s:\n", blade.Dn)
|
||||
log.Printf("\tNumber of CPUs: %d\n", blade.NumOfCpus)
|
||||
log.Printf("\tTotal Memory: %d\n", blade.TotalMemory)
|
||||
log.Printf("\tModel: %s\n", blade.Model)
|
||||
log.Printf("\tVendor: %s\n", blade.Vendor)
|
||||
}
|
||||
}
|
63
local/go-ucs/api/example_config_resolve_dn_test.go
Executable file
63
local/go-ucs/api/example_config_resolve_dn_test.go
Executable file
@@ -0,0 +1,63 @@
|
||||
package api_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/dnaeon/go-ucs/api"
|
||||
"github.com/dnaeon/go-ucs/mo"
|
||||
)
|
||||
|
||||
func Example_configResolveDn() {
|
||||
// The following example shows how to retrieve a single managed object for a specified DN.
|
||||
|
||||
// Skip SSL certificate verification of remote endpoint.
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
httpClient := &http.Client{Transport: tr}
|
||||
|
||||
// Create a new Cisco UCS API client
|
||||
config := api.Config{
|
||||
Endpoint: "https://ucs01.example.org/",
|
||||
Username: "admin",
|
||||
Password: "password",
|
||||
HttpClient: httpClient,
|
||||
}
|
||||
|
||||
client, err := api.NewClient(config)
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to create API client: %s", err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
log.Printf("Logging in to %s\n", config.Endpoint)
|
||||
if _, err := client.AaaLogin(ctx); err != nil {
|
||||
log.Fatalf("Unable to login: %s\n", err)
|
||||
}
|
||||
defer client.AaaLogout(ctx)
|
||||
|
||||
log.Printf("Got authentication cookie: %s\n", client.Cookie)
|
||||
|
||||
// Retrieve the `sys` DN, which is of type mo.TopSystem
|
||||
log.Println("Retrieving `sys` managed object")
|
||||
req := api.ConfigResolveDnRequest{
|
||||
Cookie: client.Cookie,
|
||||
Dn: "sys",
|
||||
InHierarchical: "false",
|
||||
}
|
||||
|
||||
var sys mo.TopSystem
|
||||
if err := client.ConfigResolveDn(ctx, req, &sys); err != nil {
|
||||
log.Fatalf("Unable to retrieve DN: %s", err)
|
||||
}
|
||||
|
||||
log.Printf("Address: %s\n", sys.Address)
|
||||
log.Printf("Current time: %s\n", sys.CurrentTime)
|
||||
log.Printf("Dn: %s\n", sys.Dn)
|
||||
log.Printf("Mode: %s\n", sys.Mode)
|
||||
log.Printf("Uptime: %s\n", sys.SystemUptime)
|
||||
}
|
80
local/go-ucs/api/example_config_resolve_dns_test.go
Executable file
80
local/go-ucs/api/example_config_resolve_dns_test.go
Executable file
@@ -0,0 +1,80 @@
|
||||
package api_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/xml"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/dnaeon/go-ucs/api"
|
||||
"github.com/dnaeon/go-ucs/mo"
|
||||
)
|
||||
|
||||
func Example_configResolveDns() {
|
||||
// The following example shows how to retrieve a list of managed objects by specifying their DNs.
|
||||
|
||||
// Skip SSL certificate verification of remote endpoint.
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
httpClient := &http.Client{Transport: tr}
|
||||
|
||||
// Create a new Cisco UCS API client
|
||||
config := api.Config{
|
||||
Endpoint: "https://ucs01.example.org/",
|
||||
Username: "admin",
|
||||
Password: "password",
|
||||
HttpClient: httpClient,
|
||||
}
|
||||
|
||||
client, err := api.NewClient(config)
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to create API client: %s", err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
log.Printf("Logging in to %s\n", config.Endpoint)
|
||||
if _, err := client.AaaLogin(ctx); err != nil {
|
||||
log.Fatalf("Unable to login: %s\n", err)
|
||||
}
|
||||
defer client.AaaLogout(ctx)
|
||||
|
||||
log.Printf("Got authentication cookie: %s\n", client.Cookie)
|
||||
|
||||
// A type to contain the instances of the retrieved DNs.
|
||||
type outConfigs struct {
|
||||
XMLName xml.Name
|
||||
Sys mo.TopSystem `xml:"topSystem"`
|
||||
Version mo.VersionApplication `xml:"versionApplication"`
|
||||
}
|
||||
|
||||
var out outConfigs
|
||||
|
||||
// Retrieve the list of DNs
|
||||
log.Println("Retrieving managed objects using configResolveDns query method")
|
||||
req := api.ConfigResolveDnsRequest{
|
||||
Cookie: client.Cookie,
|
||||
InHierarchical: "false",
|
||||
InDns: []api.Dn{
|
||||
api.NewDn("sys"),
|
||||
api.NewDn("sys/version/application"),
|
||||
api.NewDn("no/such/dn"),
|
||||
},
|
||||
}
|
||||
|
||||
resp, err := client.ConfigResolveDns(ctx, req, &out)
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to retrieve DNs: %s", err)
|
||||
}
|
||||
|
||||
log.Printf("%s is at version %s\n", out.Sys.Name, out.Version.Version)
|
||||
|
||||
unresolved := make([]string, 0)
|
||||
for _, dn := range resp.OutUnresolved {
|
||||
unresolved = append(unresolved, dn.Value)
|
||||
}
|
||||
log.Printf("Unresolved DNs: %s", strings.Join(unresolved, ", "))
|
||||
}
|
88
local/go-ucs/api/example_rate_limiter_test.go
Executable file
88
local/go-ucs/api/example_rate_limiter_test.go
Executable file
@@ -0,0 +1,88 @@
|
||||
package api_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"log"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/dnaeon/go-ucs/api"
|
||||
"github.com/dnaeon/go-ucs/mo"
|
||||
)
|
||||
|
||||
func Example_rateLimiter() {
|
||||
// The following example shows how to rate limit requests again the remote
|
||||
// Cisco UCS API endpoint using a token bucket rate limiter.
|
||||
// https://en.wikipedia.org/wiki/Token_bucket
|
||||
|
||||
// Skip SSL certificate verification of remote endpoint.
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
httpClient := &http.Client{Transport: tr}
|
||||
|
||||
// Create a new Cisco UCS API client.
|
||||
// Set maximum allowed requests per second to 1 with a burst size of 1.
|
||||
// A request will wait up to 1 minute for a token.
|
||||
config := api.Config{
|
||||
Endpoint: "https://ucs01.example.org/",
|
||||
Username: "admin",
|
||||
Password: "password",
|
||||
HttpClient: httpClient,
|
||||
RateLimit: &api.RateLimit{
|
||||
RequestsPerSecond: 1.0,
|
||||
Burst: 1,
|
||||
Wait: time.Duration(1 * time.Minute),
|
||||
},
|
||||
}
|
||||
|
||||
client, err := api.NewClient(config)
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to create API client: %s", err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
log.Printf("Logging in to %s\n", config.Endpoint)
|
||||
if _, err := client.AaaLogin(ctx); err != nil {
|
||||
log.Fatalf("Unable to login: %s\n", err)
|
||||
}
|
||||
defer client.AaaLogout(ctx)
|
||||
|
||||
log.Printf("Got authentication cookie: %s\n", client.Cookie)
|
||||
|
||||
// Start a few concurrent requests to the remote API endpoint.
|
||||
// Requests will be executed one at a time, because of how the limiter is configured.
|
||||
var wg sync.WaitGroup
|
||||
for i := 1; i < 10; i++ {
|
||||
wg.Add(1)
|
||||
|
||||
// Our worker function will retrieve the `sys` DN from the remote Cisco UCS API.
|
||||
// We will start a few of these in separate goroutines.
|
||||
worker := func(id int) {
|
||||
defer wg.Done()
|
||||
|
||||
// Retrieve the `sys` DN, which is of type mo.TopSystem
|
||||
log.Printf("Worker #%d: Retrieving `sys` managed object\n", id)
|
||||
req := api.ConfigResolveDnRequest{
|
||||
Cookie: client.Cookie,
|
||||
Dn: "sys",
|
||||
InHierarchical: "false",
|
||||
}
|
||||
|
||||
var sys mo.TopSystem
|
||||
if err := client.ConfigResolveDn(ctx, req, &sys); err != nil {
|
||||
log.Printf("Worker #%d: Unable to retrieve DN: %s\n", id, err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("Worker #%d: successfully retrieved `sys` managed object\n", id)
|
||||
}
|
||||
|
||||
go worker(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
54
local/go-ucs/api/xml.go
Executable file
54
local/go-ucs/api/xml.go
Executable file
@@ -0,0 +1,54 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
// xmlMarshalWithSelfClosingTags post-processes results from xml.Marshal into XML
|
||||
// document where empty XML elements use self-closing tags.
|
||||
//
|
||||
// The XML standard states that self-closing tags are permitted.
|
||||
//
|
||||
// https://www.w3.org/TR/REC-xml/#dt-empty
|
||||
// https://www.w3.org/TR/REC-xml/#d0e2480
|
||||
//
|
||||
// According to the XML standard an empty element with a start and end tag is
|
||||
// semantically the same as an empty element with a self-closing tag.
|
||||
//
|
||||
// Unfortunately not all XML parsers can understand both. Such is the
|
||||
// case with Cisco UCS Manager API, which expects empty elements to use
|
||||
// self-closing tags only.
|
||||
//
|
||||
// As of now XML marshaling in Go always uses start and end tags,
|
||||
// which results in XML elements like the one below.
|
||||
//
|
||||
// <Person name="me"></Person>
|
||||
//
|
||||
// Above XML elements cannot be parsed by the remote Cisco UCS API endpoint,
|
||||
// and such API calls result in parse error returned to the client.
|
||||
//
|
||||
// Currently the Go team is considering implementing XML self-closing tags,
|
||||
// which progress can be tracked in the issue below.
|
||||
//
|
||||
// https://github.com/golang/go/issues/21399
|
||||
//
|
||||
// Until support for XML self-closing tags (if ever) becomes real in Go
|
||||
// we need to ensure compatibility with the Cisco UCS API by doing that ourselves.
|
||||
//
|
||||
// In a separate thread @rsc also suggested a similar approach by using
|
||||
// strings.Replace(), even though such thing is not ideal and should
|
||||
// hopefully one day make into the language as a feature.
|
||||
//
|
||||
// https://groups.google.com/forum/#!topic/golang-nuts/guG6iOCRu08
|
||||
func xmlMarshalWithSelfClosingTags(in interface{}) ([]byte, error) {
|
||||
data, err := xml.Marshal(in)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
re := regexp.MustCompile(`<([^/][\w\s\"\=\-\/]*)>\s*<(\/\w*)>`)
|
||||
newData := re.ReplaceAllString(string(data), "<$1/>")
|
||||
|
||||
return []byte(newData), nil
|
||||
}
|
64
local/go-ucs/api/xml_test.go
Executable file
64
local/go-ucs/api/xml_test.go
Executable file
@@ -0,0 +1,64 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type Person struct {
|
||||
XMLName xml.Name `xml:"person"`
|
||||
Name string `xml:"name,attr,omitempty"`
|
||||
Place *Location `xml:"place,omitempty"`
|
||||
}
|
||||
|
||||
type Location struct {
|
||||
Country string `xml:"country,omitempty"`
|
||||
City string `xml:"city,omitempty"`
|
||||
}
|
||||
|
||||
type PersonEmbedded struct {
|
||||
XMLName xml.Name `xml:"personEmbedded"`
|
||||
Person
|
||||
Location
|
||||
}
|
||||
|
||||
type NilStruct *Person
|
||||
|
||||
func TestXmlMarshalWithSelfClosingTags(t *testing.T) {
|
||||
var tests = []struct {
|
||||
value interface{}
|
||||
expect string
|
||||
}{
|
||||
// Nil values
|
||||
{value: nil, expect: ``},
|
||||
{value: new(NilStruct), expect: ``},
|
||||
|
||||
// Values
|
||||
{value: Person{}, expect: `<person/>`},
|
||||
{value: Person{Name: "John Doe"}, expect: `<person name="John Doe"/>`},
|
||||
{
|
||||
value: Person{Name: "Jane Doe", Place: &Location{Country: "unknown", City: "unknown"}},
|
||||
expect: `<person name="Jane Doe"><place><country>unknown</country><city>unknown</city></place></person>`,
|
||||
},
|
||||
{
|
||||
value: &PersonEmbedded{Person: Person{Name: "John Doe"}, Location: Location{Country: "unknown"}},
|
||||
expect: `<personEmbedded name="John Doe"><country>unknown</country></personEmbedded>`,
|
||||
},
|
||||
|
||||
// Pointers to values
|
||||
{value: &Person{}, expect: `<person/>`},
|
||||
{value: &Person{Name: "John Doe"}, expect: `<person name="John Doe"/>`},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
data, err := xmlMarshalWithSelfClosingTags(test.value)
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot marshal %+v: %s", test.value, err)
|
||||
}
|
||||
|
||||
got := string(data)
|
||||
if got != test.expect {
|
||||
t.Fatalf("Got XML '%s', expect '%s'", got, test.expect)
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user