blob: 2def7f6c8fa9187124aef55769198147f8a8b644 [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package cmd
import (
"bytes"
"crypto/hmac"
"crypto/sha1"
"crypto/tls"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"github.com/briandowns/spinner"
"io/ioutil"
"net/http"
"net/http/cookiejar"
"net/url"
"runtime"
"sort"
"strings"
"time"
)
var cursor = []string{"\r⣷ 😸", "\r⣯ 😹", "\r⣟ 😺", "\r⡿ 😻", "\r⢿ 😼", "\r⣻ 😽", "\r⣽ 😾", "\r⣾ 😻"}
func init() {
if runtime.GOOS == "windows" {
cursor = []string{"|", "/", "-", "\\"}
}
}
func encodeRequestParams(params url.Values) string {
if params == nil {
return ""
}
keys := make([]string, 0, len(params))
for key := range params {
keys = append(keys, key)
}
sort.Strings(keys)
var buf bytes.Buffer
for _, key := range keys {
value := params.Get(key)
if buf.Len() > 0 {
buf.WriteByte('&')
}
buf.WriteString(key)
buf.WriteString("=")
buf.WriteString(url.QueryEscape(value))
}
return buf.String()
}
// Login logs in a user based on provided request and returns http client and session key
func Login(r *Request) (*http.Client, string, error) {
params := make(url.Values)
params.Add("command", "login")
params.Add("username", r.Config.ActiveProfile.Username)
params.Add("password", r.Config.ActiveProfile.Password)
params.Add("domain", r.Config.ActiveProfile.Domain)
params.Add("response", "json")
jar, _ := cookiejar.New(nil)
client := &http.Client{
Jar: jar,
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: !r.Config.ActiveProfile.VerifyCert},
},
}
sessionKey := ""
resp, err := client.PostForm(r.Config.ActiveProfile.URL, params)
if err != nil {
return client, sessionKey, errors.New("failed to connect to management server, please check the URL: " + r.Config.ActiveProfile.URL)
}
if resp.StatusCode != http.StatusOK {
e := errors.New("failed to log in, please check the credentials")
if err != nil {
e = errors.New("failed to log in due to " + err.Error())
}
return client, sessionKey, e
}
for _, cookie := range resp.Cookies() {
if cookie.Name == "sessionkey" {
sessionKey = cookie.Value
break
}
}
return client, sessionKey, nil
}
func getResponseData(data map[string]interface{}) map[string]interface{} {
for k := range data {
if strings.HasSuffix(k, "response") {
return data[k].(map[string]interface{})
}
}
return nil
}
func pollAsyncJob(r *Request, jobId string) (map[string]interface{}, error) {
for timeout := float64(r.Config.Core.Timeout); timeout > 0.0; {
startTime := time.Now()
s := spinner.New(cursor, 200*time.Millisecond)
s.Color("blue", "bold")
s.Suffix = " polling for async API job result"
s.Start()
queryResult, queryError := NewAPIRequest(r, "queryAsyncJobResult", []string{"jobid=" + jobId}, false)
diff := time.Duration(1*time.Second).Nanoseconds() - time.Now().Sub(startTime).Nanoseconds()
if diff > 0 {
time.Sleep(time.Duration(diff) * time.Nanosecond)
}
s.Stop()
timeout = timeout - time.Now().Sub(startTime).Seconds()
if queryError != nil {
return queryResult, queryError
}
jobStatus := queryResult["jobstatus"].(float64)
if jobStatus == 0 {
continue
}
if jobStatus == 1 {
return queryResult["jobresult"].(map[string]interface{}), nil
}
if jobStatus == 2 {
return queryResult, errors.New("async API failed for job " + jobId)
}
}
return nil, errors.New("async API job query timed out")
}
// NewAPIRequest makes an API request to configured management server
func NewAPIRequest(r *Request, api string, args []string, isAsync bool) (map[string]interface{}, error) {
params := make(url.Values)
params.Add("command", api)
for _, arg := range args {
parts := strings.Split(arg, "=")
if len(parts) == 2 {
params.Add(parts[0], parts[1])
}
}
params.Add("response", "json")
var client *http.Client
var encodedParams string
var err error
if len(r.Config.ActiveProfile.APIKey) > 0 && len(r.Config.ActiveProfile.SecretKey) > 0 {
apiKey := r.Config.ActiveProfile.APIKey
secretKey := r.Config.ActiveProfile.SecretKey
if len(apiKey) > 0 {
params.Add("apiKey", apiKey)
}
client = &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: !r.Config.ActiveProfile.VerifyCert},
},
}
encodedParams = encodeRequestParams(params)
mac := hmac.New(sha1.New, []byte(secretKey))
mac.Write([]byte(strings.Replace(strings.ToLower(encodedParams), "+", "%20", -1)))
signature := base64.StdEncoding.EncodeToString(mac.Sum(nil))
encodedParams = encodedParams + fmt.Sprintf("&signature=%s", url.QueryEscape(signature))
} else if len(r.Config.ActiveProfile.Username) > 0 && len(r.Config.ActiveProfile.Password) > 0 {
var sessionKey string
client, sessionKey, err = Login(r)
if err != nil {
return nil, err
}
params.Add("sessionkey", sessionKey)
encodedParams = encodeRequestParams(params)
} else {
fmt.Println("Please provide either apikey/secretkey or username/password to make an API call")
return nil, errors.New("failed to authenticate to make API call")
}
client.Timeout = time.Duration(time.Duration(r.Config.Core.Timeout) * time.Second)
response, err := client.Get(fmt.Sprintf("%s?%s", r.Config.ActiveProfile.URL, encodedParams))
if err != nil {
return nil, err
}
body, _ := ioutil.ReadAll(response.Body)
var data map[string]interface{}
_ = json.Unmarshal([]byte(body), &data)
if isAsync && r.Config.Core.AsyncBlock {
if jobResponse := getResponseData(data); jobResponse != nil && jobResponse["jobid"] != nil {
jobId := jobResponse["jobid"].(string)
return pollAsyncJob(r, jobId)
}
}
if apiResponse := getResponseData(data); apiResponse != nil {
return apiResponse, nil
}
return nil, errors.New("failed to decode response")
}