package client
import (
)
const (
basic = "basic"
eTag = "ETag"
ifMatch = "If-Match"
)
var (
jsonCheck = regexp.MustCompile(`(?i:(?:application|text)/(?:vnd\.[^;]+\+)?json)`)
xmlCheck = regexp.MustCompile(`(?i:(?:application|text)/xml)`)
uriCheck = regexp.MustCompile(`/(?P<namespace>[-\w]+)/v\d+\.\d+(\.[a|b]\d+)?/(?P<suffix>.*)`)
retryStatusList = []int{408, 503, 504}
userAgent = "Nutanix-files/v4.0.1-alpha.2"
)
type ApiClient struct {
Host string `json:"host,omitempty"`
Port int `json:"port,omitempty"`
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
Debug bool `json:"debug,omitempty"`
VerifySSL bool
MaxRetryAttempts int `json:"maxRetryAttempts,omitempty"`
Timeout time.Duration `json:"timeout,omitempty"`
RetryInterval time.Duration `json:"retryInterval,omitempty"`
LoggerFile string `json:"loggerFile,omitempty"`
defaultHeaders map[string]string
retryClient *retryablehttp.Client
httpClient *http.Client
authentication map[string]interface{}
cookie string
refreshCookie bool
previousAuth string
basicAuth *BasicAuth
}
func () *ApiClient {
:= new(BasicAuth)
:= make(map[string]interface{})
["basicAuthScheme"] =
:= &ApiClient{
Host: "localhost",
Port: 9440,
Debug: false,
VerifySSL: true,
MaxRetryAttempts: 5,
Timeout: 30 * time.Second,
RetryInterval: 3 * time.Second,
defaultHeaders: make(map[string]string),
refreshCookie: true,
basicAuth: ,
authentication: ,
}
.setupClient()
return
}
func ( *ApiClient) ( string, string) {
if == "Authorization" {
.cookie = ""
}
.defaultHeaders[] =
}
func ( *ApiClient) ( *string, string, interface{},
url.Values, map[string]string, url.Values,
[]string, []string, []string) ([]byte, error) {
:= "https://" + .Host + ":" + strconv.Itoa(.Port) + *
if ["Authorization"] != "" {
.previousAuth = ["Authorization"]
}
if .defaultHeaders["Authorization"] != "" {
.previousAuth = .defaultHeaders["Authorization"]
}
:= .selectHeaderContentType()
if != "" {
["Content-Type"] =
}
:= .selectHeaderAccept()
if != "" {
["Accept"] =
}
, := ["NTNX-Request-Id"]
, := .defaultHeaders["NTNX-Request-Id"]
if ! && ! {
["NTNX-Request-Id"] = uuid.New().String()
}
:= reflect.ValueOf()
if .IsValid() && !.IsNil() {
addEtagReferenceToHeader(, )
}
, := .prepareRequest(, , , , , , )
if != nil {
return nil,
}
if .Debug {
, := httputil.DumpRequestOut(, true)
if != nil {
return nil,
}
log.Printf("\n%s\n", string())
}
.setupClient()
, := .httpClient.Do()
if != nil && .StatusCode == 401 {
.refreshCookie = true
if len(.previousAuth) > 0 {
.Header["Authorization"] = []string{.previousAuth}
}
delete(.Header, "Cookie")
, = .httpClient.Do()
}
if != nil {
return nil,
}
if .Debug {
, := httputil.DumpResponse(, true)
if != nil {
return nil,
}
log.Printf("\n%s\n", string())
}
if nil == {
return nil, ReportError("response is nil!")
}
.updateCookies()
if .StatusCode == 204 {
return nil, nil
}
, := ioutil.ReadAll(.Body)
if != nil {
return nil,
}
.Body.Close()
.Body = ioutil.NopCloser(bytes.NewBuffer())
if !(200 <= .StatusCode && .StatusCode <= 209) {
return nil, GenericOpenAPIError {
Body: ,
Status: .Status,
}
} else {
:= addEtagReferenceToResponse(.Header, )
return , nil
}
}
func ( *ApiClient) () map[string]interface{} {
return .authentication
}
func ( *ApiClient) ( string) interface{} {
return .authentication[]
}
func ( *ApiClient) ( string) {
.Username =
.basicAuth.UserName =
}
func ( *ApiClient) ( string) {
.Password =
.basicAuth.Password =
}
func ( *ApiClient) ( string) error {
for , := range .authentication {
if , := .(map[string]interface{}); {
if , := ["apiKey"].(*APIKey); {
.Key =
return nil
}
}
}
return ReportError("no API key authentication configured!")
}
func ( *ApiClient) ( string) error {
for , := range .authentication {
if , := .(map[string]interface{}); {
if , := ["apiKey"].(*APIKey); {
.Prefix =
return nil
}
}
}
return ReportError("no API key authentication configured!")
}
func ( *ApiClient) ( string) error {
for , := range .authentication {
if , := .(*OAuth); {
.AccessToken =
return nil
}
}
return ReportError("no OAuth2 authentication configured!")
}
func ( *ApiClient) ( int) {
.MaxRetryAttempts =
}
func ( *ApiClient) ( int) {
:= time.Duration() * time.Millisecond
setTimeout(, )
}
func ( time.Duration, *ApiClient) {
.Timeout =
if <= 0 {
log.Printf("Timeout cannot be 0 or less. Setting 30 second as default timeout.")
.Timeout = 30 * time.Second
} else if > (30 * time.Minute) {
log.Printf("Timeout cannot be greater than 30 minutes. Setting 30 minutes as default timeout.")
.Timeout = 30 * time.Minute
}
.httpClient.Timeout = .Timeout
}
func ( *ApiClient) ( bool) {
.VerifySSL =
}
func ( *ApiClient) ( int) {
.RetryInterval = time.Duration() * time.Millisecond
}
func ( *ApiClient) () {
var = false
:= reflect.ValueOf(.retryClient)
if !.IsValid() || .IsNil() {
.retryClient = retryablehttp.NewClient()
= true
}
var = .retryClient.HTTPClient.Transport.(*http.Transport)
if || .TLSClientConfig == nil || .TLSClientConfig.InsecureSkipVerify != !.VerifySSL {
:= &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: !.VerifySSL},
}
.retryClient.HTTPClient.Transport =
= true
}
if .retryClient.RetryMax != .MaxRetryAttempts ||
.retryClient.RetryWaitMax != .RetryInterval ||
.retryClient.CheckRetry == nil {
= true
}
.retryClient.RetryMax = .MaxRetryAttempts
.retryClient.RetryWaitMax = .RetryInterval
.retryClient.CheckRetry = retryPolicy
if .LoggerFile != "" {
:= logrus.New()
:= LeveledLogrus{}
.setLoggerFilePath(.LoggerFile)
if .Debug {
.SetLevel(logrus.DebugLevel)
} else {
.SetLevel(logrus.WarnLevel)
}
.retryClient.Logger = retryablehttp.LeveledLogger(&)
} else {
.retryClient.Logger = nil
}
if {
.httpClient = .retryClient.StandardClient()
setTimeout(.Timeout, )
}
}
func ( *ApiClient) ( []string) string {
if len() == 0 {
return ""
}
if contains(, "application/json") {
return "application/json"
}
return [0]
}
func ( *ApiClient) ( []string) string {
if len() == 0 {
return ""
}
if contains(, "application/json") {
return "application/json"
}
return strings.Join(, ",")
}
func ( *ApiClient) (
string, string,
interface{},
map[string]string,
url.Values,
url.Values,
[]string) ( *http.Request, error) {
var *bytes.Buffer
:= reflect.ValueOf()
if .IsValid() && !.IsNil() {
:= ["Content-Type"]
if == "" {
= detectContentType()
["Content-Type"] =
}
, = setBody(, )
if != nil {
return nil,
}
}
if strings.HasPrefix(["Content-Type"], "application/x-www-form-urlencoded") && len() > 0 {
if != nil {
return nil, errors.New("Cannot specify postBody and x-www-form-urlencoded form at the same time.")
}
= &bytes.Buffer{}
.WriteString(.Encode())
["Content-Length"] = fmt.Sprintf("%d", .Len())
}
, := url.Parse()
if != nil {
return nil,
}
:= .Query()
for , := range {
for , := range {
.Add(, )
}
}
.RawQuery = .Encode()
if != nil {
, = http.NewRequest(, .String(), )
} else {
, = http.NewRequest(, .String(), nil)
}
if != nil {
return nil,
}
if len() > 0 {
:= http.Header{}
for , := range {
.Set(, )
}
.Header =
}
.Header.Add("User-Agent", userAgent)
.SetUserName(.Username)
.SetPassword(.Password)
for , := range {
if , := .authentication[].(*BasicAuth); {
if .UserName != "" && .Password != "" {
.SetBasicAuth(.UserName, .Password)
}
} else if , := .authentication[].(map[string]interface{}); {
var string
if , := ["apiKey"].(*APIKey); && .Prefix != "" {
= .Prefix + " " + .Key
} else {
= .Key
}
if ["in"] == "header" {
.Header.Add(["name"].(string), )
}
if ["in"] == "query" {
:= .URL.Query()
.Add(["name"].(string), )
.URL.RawQuery = .Encode()
}
} else if , := .authentication[].(*OAuth); {
.Header.Add("Authorization", "Bearer " + .AccessToken)
} else {
return nil, ReportError("unknown authentication type: %s", )
}
}
for , := range .defaultHeaders {
.Header.Add(, )
}
if len(.cookie) > 0 {
.Header.Add("Cookie", .cookie)
delete(.Header, "Authorization")
}
return , nil
}
func ( context.Context, *http.Response, error) (bool, error) {
if != nil {
return false,
}
for , := range retryStatusList {
if .StatusCode == {
return true, nil
}
}
return false, nil
}
func ( []string, string) bool {
for , := range {
if strings.ToLower() == strings.ToLower() {
return true
}
}
return false
}
func ( interface{}) string {
:= "text/plain; charset=utf-8"
:= reflect.TypeOf().Kind()
switch {
case reflect.Struct, reflect.Map, reflect.Ptr:
= "application/json; charset=utf-8"
case reflect.String:
= "text/plain; charset=utf-8"
default:
if , := .([]byte); {
= http.DetectContentType()
} else if == reflect.Slice {
= "application/json; charset=utf-8"
}
}
return
}
func ( interface{}, map[string]string) {
if reflect.ValueOf().Elem().Kind() == reflect.Struct {
if := reflect.ValueOf().Elem().FieldByName("Reserved_"); .IsValid() {
:= .Interface().(map[string]interface{})
if , := [eTag].(string); {
[ifMatch] =
}
}
}
}
func ( *ApiClient) ( interface{}) string {
var reflect.Value
if reflect.TypeOf().Kind() == reflect.Struct {
= reflect.ValueOf().FieldByName("Reserved_")
} else if reflect.TypeOf().Kind() == reflect.Interface || reflect.TypeOf().Kind() == reflect.Ptr {
= reflect.ValueOf().Elem().FieldByName("Reserved_")
} else {
log.Printf("\nUnrecognized input type %s for %s to retrieve etag!\n", reflect.TypeOf().Kind(), )
return ""
}
if .IsValid() {
:= strings.ToLower(eTag)
:= .Interface().(map[string]interface{})
for , := range {
if strings.ToLower() == {
return .(string)
}
}
}
return ""
}
func ( http.Header, []byte) []byte {
if := .Get(eTag); != "" {
:= map[string]interface{}{}
json.Unmarshal(, &)
if , := ["$reserved"].(map[string]interface{}); {
[eTag] =
if , := ["data"].(map[string]interface{}); {
if , := ["$reserved"].(map[string]interface{}); {
[eTag] =
, := json.Marshal()
return
}
} else if , := ["data"].([]interface{}); {
for , := range {
if , := .(map[string]interface{}); {
if , := ["$reserved"].(map[string]interface{}); {
[eTag] =
}
}
}
, := json.Marshal()
return
}
}
}
return
}
func ( interface{}, string) ( *bytes.Buffer, error) {
if nil == {
= &bytes.Buffer{}
}
if , := .(io.Reader); {
_, = .ReadFrom()
} else if , := .(**os.File); {
_, = .ReadFrom(*)
} else if , := .([]byte); {
_, = .Write()
} else if , := .(string); {
_, = .WriteString()
} else if , := .(*string); {
_, = .WriteString(*)
} else if jsonCheck.MatchString() {
= json.NewEncoder().Encode()
} else if xmlCheck.MatchString() {
= xml.NewEncoder().Encode()
}
if != nil {
return nil,
}
if .Len() == 0 {
= fmt.Errorf("Invalid body type %s\n", )
return nil,
}
return , nil
}
func ( *ApiClient) ( *http.Response) {
if .refreshCookie {
:= .Header["Set-Cookie"]
if len() > 0 {
:= ""
for , := range {
:= strings.SplitN(, ";", 2)[0]
if strings.Contains(, "=") {
= strings.TrimSpace()
} else {
= ""
}
if != "" {
+= + ";"
}
}
if != "" {
= strings.TrimSuffix(, ";")
}
.cookie =
.refreshCookie = false
}
}
}
type BasicAuth struct {
UserName string `json:"userName,omitempty"`
Password string `json:"password,omitempty"`
}
type APIKey struct {
Key string
Prefix string
}
type OAuth struct {
AccessToken string
}
type GenericOpenAPIError struct {
Body []byte
Model interface{}
Status string
}
func ( GenericOpenAPIError) () string {
return string(.Body)
}
func ( GenericOpenAPIError) () interface{} {
:= json.Unmarshal(.Body, .Model)
if != nil {
return nil
}
return .Model
}
func ( interface{}, string) string {
var string
switch {
case "pipes":
= "|"
case "ssv":
= " "
case "tsv":
= "\t"
case "csv":
= ","
}
if reflect.TypeOf().Kind() == reflect.Slice {
return strings.Trim(strings.Replace(fmt.Sprint(), " ", , -1), "[]")
} else if , := .(time.Time); {
return .Format(time.RFC3339)
}
return fmt.Sprintf("%v", )
}
func ( interface{}) (string, error) {
, := json.Marshal()
if != nil {
return "",
}
return string(),
}
func ( string, ...interface{}) error {
return fmt.Errorf(, ...)
}
type LeveledLogrus struct {
*logrus.Logger
}
func ( *LeveledLogrus) ( string, ...interface{}) {
.WithFields(fields()).Error()
}
func ( *LeveledLogrus) ( string, ...interface{}) {
.WithFields(fields()).Info()
}
func ( *LeveledLogrus) ( string, ...interface{}) {
.WithFields(fields()).Debug()
}
func ( *LeveledLogrus) ( string, ...interface{}) {
.WithFields(fields()).Warn()
}
func ( []interface{}) map[string]interface{} {
:= make(map[string]interface{})
for := 0; < len()-1; += 2 {
[[].(string)] = [+1]
}
return
}
func ( *LeveledLogrus) ( string) error {
, := os.OpenFile(, os.O_APPEND | os.O_CREATE | os.O_RDWR, 0666)
if != nil {
.Error("Error opening log file", "error", )
return
}
.SetOutput()
.SetReportCaller(true)
return nil
}