//The api client for files's golang SDK
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" ) /** Generic API client for Swagger client library builds. Swagger generic API client. This client handles the client- server communication, and is invariant across implementations. Specifics of the methods and models for each application are generated from the Swagger templates. Parameters :- Host (string) : Host IPV4, IPV6 or FQDN for all http request made by this client (default : localhost) Port (string) : Port for the host to connect to make all http request (default : 9440) Username (string) : Username to connect to a cluster Password (string) : Password to connect to a cluster Debug (bool) : flag to enable debug logging (default : empty) VerifySSL (bool) : Verify SSL certificate of cluster (default: true) MaxRetryAttempts (int) : Maximum number of retry attempts to be made at a time (default: 5) Timeout (time.Duration) : Global timeout for all operations RetryInterval (time.Duration) : Interval between successive retry attempts (default: 3 sec) LoggerFile (string) : Log file to write activity logs */ 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 } /** Returns a newly generated ApiClient instance populated with default values */ 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 } /*AddDefaultHeader Adds a default header to current api client instance for all the HTTP calls. */ func ( *ApiClient) ( string, string) { if == "Authorization" { .cookie = "" } .defaultHeaders[] = } // Makes the HTTP request with given options and returns response body as byte array. 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"] } // set Content-Type header := .selectHeaderContentType() if != "" { ["Content-Type"] = } // set Accept header := .selectHeaderAccept() if != "" { ["Accept"] = } // set NTNX-Request-Id header , := ["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() // Retry one more time without the cookie but with basic auth header 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 } } // Get all authentications (key: authentication name, value: authentication) func ( *ApiClient) () map[string]interface{} { return .authentication } // Get authentication for the given auth name (eg : basic, oauth, bearer, apiKey) func ( *ApiClient) ( string) interface{} { return .authentication[] } // Helper method to set username for the first HTTP basic authentication. func ( *ApiClient) ( string) { .Username = .basicAuth.UserName = } // Helper method to set password for the first HTTP basic authentication func ( *ApiClient) ( string) { .Password = .basicAuth.Password = } // Helper method to set API key value for the first API key authentication func ( *ApiClient) ( string) error { for , := range .authentication { if , := .(map[string]interface{}); { if , := ["apiKey"].(*APIKey); { .Key = return nil } } } return ReportError("no API key authentication configured!") } // Helper method to set API key prefix for the first API key authentication func ( *ApiClient) ( string) error { for , := range .authentication { if , := .(map[string]interface{}); { if , := ["apiKey"].(*APIKey); { .Prefix = return nil } } } return ReportError("no API key authentication configured!") } // Helper method to set access token for the first OAuth2 authentication. func ( *ApiClient) ( string) error { for , := range .authentication { if , := .(*OAuth); { .AccessToken = return nil } } return ReportError("no OAuth2 authentication configured!") } /** Helper method to set maximum retry attempts. After the initial instantiation of ApiClient, maximum retry attempts must be modified only via this method */ func ( *ApiClient) ( int) { .MaxRetryAttempts = } /** Helper method to set httpclient timeout. After the initial instantiation of ApiClient, timeout must be modified only via this method */ 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 } /** Helper method to enable/disable SSL verification. By default, SSL verification is enabled. Please note that disabling SSL verification is not recommended and should only be done for test purposes. */ func ( *ApiClient) ( bool) { .VerifySSL = } /** Helper method to set retry back off period. After the initial instantiation of ApiClient, back off period must be modified only via this method */ 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, ) } } // SelectHeaderContentType select a content type from the available list. func ( *ApiClient) ( []string) string { if len() == 0 { return "" } if contains(, "application/json") { return "application/json" } return [0] // use the first content type specified in 'consumes' } // selectHeaderAccept join all accept types and return func ( *ApiClient) ( []string) string { if len() == 0 { return "" } if contains(, "application/json") { return "application/json" } return strings.Join(, ",") } // prepareRequest build the request func ( *ApiClient) ( string, string, interface{}, map[string]string, url.Values, url.Values, []string) ( *http.Request, error) { var *bytes.Buffer // Detect postBody type and post. := 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()) // Set Content-Length ["Content-Length"] = fmt.Sprintf("%d", .Len()) } // Setup path and query parameters , := url.Parse() if != nil { return nil, } // Adding Query Param := .Query() for , := range { for , := range { .Add(, ) } } // Encode the parameters. .RawQuery = .Encode() // Generate a new request if != nil { , = http.NewRequest(, .String(), ) } else { , = http.NewRequest(, .String(), nil) } if != nil { return nil, } // add header parameters, if any if len() > 0 { := http.Header{} for , := range { .Set(, ) } .Header = } // Add the user agent to the request. .Header.Add("User-Agent", userAgent) // Authentication .SetUserName(.Username) .SetPassword(.Password) for , := range { // Basic HTTP authentication if , := .authentication[].(*BasicAuth); { if .UserName != "" && .Password != "" { .SetBasicAuth(.UserName, .Password) } // API Key authentication } 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() } // OAuth or Bearer authentication } else if , := .authentication[].(*OAuth); { .Header.Add("Authorization", "Bearer " + .AccessToken) } else { return nil, ReportError("unknown authentication type: %s", ) } } for , := range .defaultHeaders { .Header.Add(, ) } // Add the cookie to the request. if len(.cookie) > 0 { .Header.Add("Cookie", .cookie) delete(.Header, "Authorization") } return , nil } // RetryPolicy provides a callback for Client.CheckRetry, specifies retry on // error codes mentioned in RetryStatusList func ( context.Context, *http.Response, error) (bool, error) { if != nil { return false, } for , := range retryStatusList { if .StatusCode == { return true, nil } } return false, nil } // contains is a case insensitive match, finding needle in a haystack func ( []string, string) bool { for , := range { if strings.ToLower() == strings.ToLower() { return true } } return false } // detectContentType method is used to figure out `Request.Body` content type for request header 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 } // addEtagReferenceToHeader method is used to read ETag and add it to If-Match header 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] = } } } } /*GetEtag Get ETag from an object if exists, otherwise returns empty string. The ETag is usually provided in the response of the GET API calls, which can further be used in other HTTP operations. */ 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 "" } // addEtagReferenceToResponse method is used to read ETag and add it to response 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 } // Set request body from an interface{} 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 } // Set Cookie information to reuse in subsequent requests for a valid response 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 != "" { += + ";" } } // Remove trailing ";" if != "" { = strings.TrimSuffix(, ";") } .cookie = .refreshCookie = false } } } // BasicAuth provides basic http authentication to a request passed via context using ContextBasicAuth type BasicAuth struct { UserName string `json:"userName,omitempty"` Password string `json:"password,omitempty"` } // APIKey provides API key based authentication to a request passed via context using ContextAPIKey type APIKey struct { Key string Prefix string } // OAuth provides OAuth authentication type OAuth struct { AccessToken string } // GenericOpenAPIError Provides access to the body (error), status and model on returned errors. type GenericOpenAPIError struct { Body []byte Model interface{} Status string } // Error returns non-empty string if there was an error. func ( GenericOpenAPIError) () string { return string(.Body) } // Error returns deserialized response body if compatible with GenericOpenAPIError.Model func ( GenericOpenAPIError) () interface{} { := json.Unmarshal(.Body, .Model) if != nil { return nil } return .Model } // parameterToString convert interface{} parameters to string, using a delimiter if format is provided. 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", ) } // helper for converting interface{} parameters to json strings func ( interface{}) (string, error) { , := json.Marshal() if != nil { return "", } return string(), } // Prevent trying to import "fmt" 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 }