func getTimestamp() string {
	return time.Now().UTC().Format(time.RFC3339)
}
getTimestamp returns an ISO-8601 formatted timestamp of the current time in
UTC
package chef
import (
	"bufio"
	"bytes"
	"crypto/rsa"
	"crypto/sha1"
	"crypto/tls"
	"crypto/x509"
	"encoding/base64"
	"encoding/pem"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"math/big"
	"mime"
	"mime/multipart"
	"net/http"
	"net/url"
	"os"
	"path"
	"path/filepath"
	"regexp"
	"strings"
	"time"
)
type Chef struct {
	Host         string
	Url          string
	Port         string
	Version      string
	Key          *rsa.PrivateKey
	UserId       string
	SSLNoVerify  bool
	Organization string
}
func Connect(filename ...string) (*Chef, error) {
	knifeFiles := []string{}
	if len(filename) > 0 {
		for _, v := range filename {
			knifeFiles = append(knifeFiles, v)
		}
	}
	knifeFiles = append(knifeFiles, ".chef/knife.rb")
	homedir := os.Getenv("HOME")
	if homedir != "" {
		knifeFiles = append(knifeFiles, filepath.Join(homedir, ".chef/knife.rb"))
	}
	knifeFiles = append(knifeFiles, "/etc/chef/client.rb")
	knifeFiles = append(knifeFiles, "test/support/knife.rb")
	var knifeFile string
	for _, each := range knifeFiles {
		if _, err := os.Stat(each); err == nil {
			knifeFile = each
			break
		}
	}
	if knifeFile == "" {
		return nil, errors.New("Configuration file not found")
	}
	file, err := os.Open(knifeFile)
	defer file.Close()
	if err != nil {
		return nil, err
	}
	scanner := bufio.NewScanner(file)
	chef := new(Chef)
	for scanner.Scan() {
		split := splitWhitespace(scanner.Text())
		if len(split) == 2 {
			switch split[0] {
			case "node_name":
				chef.UserId = filterQuotes(split[1])
			case "client_key":
				key, err := keyFromFile(filterQuotes(split[1]))
				if err != nil {
					return nil, err
				}
				chef.Key = key
			case "chef_server_url":
				parsedUrl := filterQuotes(split[1])
				chef.Url = parsedUrl
				chefUrl, err := url.Parse(parsedUrl)
				if err != nil {
					return nil, err
				}
				hostPath := strings.Split(chefUrl.Path, "/")
				if len(hostPath) == 3 && hostPath[1] == "organizations" {
					chef.Organization = hostPath[2]
				}
				hostPort := strings.Split(chefUrl.Host, ":")
				if len(hostPort) == 2 {
					chef.Host = hostPort[0]
					chef.Port = hostPort[1]
				} else if len(hostPort) == 1 {
					chef.Host = hostPort[0]
					switch chefUrl.Scheme {
					case "http":
						chef.Port = "80"
					case "https":
						chef.Port = "443"
					default:
						return nil, errors.New("Invalid http scheme")
					}
				} else {
					return nil, errors.New("Invalid host format")
				}
			}
		}
	}
	if chef.Key == nil {
		return nil, errors.New("missing 'client_key' in knife.rb")
	}
	if chef.Version == "" {
		chef.Version = "11.6.0"
	}
	return chef, nil
}
func ConnectBuilder(host, port, version, userid, key string, organization string) (*Chef, error) {
	chef := new(Chef)
	chef.Host = host
	chef.Port = port
	chef.Version = version
	chef.UserId = userid
	chef.Organization = organization
	var url string
	switch chef.Port {
	case "443":
		url = fmt.Sprintf("https://%s", chef.Host)
	case "80":
		url = fmt.Sprintf("http://%s", chef.Host)
	default:
		url = fmt.Sprintf("%s:%s", chef.Host, chef.Port)
	}
	if chef.Organization != "" {
		url += "/organizations/" + chef.Organization
	}
	chef.Url = url
	var rsaKey *rsa.PrivateKey
	var err error
	if strings.Contains(key, "-----BEGIN RSA PRIVATE KEY-----") {
		rsaKey, err = keyFromString([]byte(key))
	} else {
		rsaKey, err = keyFromFile(key)
	}
	if err != nil {
		return nil, err
	}
	chef.Key = rsaKey
	if chef.Version == "" {
		chef.Version = "11.6.0"
	}
	return chef, nil
}
func filterQuotes(s string) string {
	re1 := regexp.MustCompile(`^(\'|\")`)
	re2 := regexp.MustCompile(`(\'|\")$`)
	return re2.ReplaceAllString(re1.ReplaceAllString(s, ``), ``)
}
// Given a string with multiple consecutive spaces, splitWhitespace returns a
// slice of strings which represent the given string split by \s characters with
// all duplicates removed
func splitWhitespace(s string) []string {
	re := regexp.MustCompile(`\s+`)
	return strings.Split(re.ReplaceAllString(s, `\s`), `\s`)
}
// keyFromFile reads an RSA private key given a filepath
func keyFromFile(filename string) (*rsa.PrivateKey, error) {
	content, err := ioutil.ReadFile(filename)
	if err != nil {
		return nil, err
	}
	return keyFromString(content)
}
// keyFromString parses an RSA private key from a string
func keyFromString(key []byte) (*rsa.PrivateKey, error) {
	block, _ := pem.Decode(key)
	if block == nil {
		return nil, fmt.Errorf("block size invalid for '%s'", string(key))
	}
	rsaKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
	if err != nil {
		return nil, err
	}
	return rsaKey, nil
}
// assemble query string from params
func (chef *Chef) buildQueryString(endpoint string, params map[string]string) (string, error) {
	u, err := url.Parse(chef.requestUrl(endpoint))
	if err != nil {
		return "", err
	}
	query := u.Query()
	for k, v := range params {
		query.Set(k, v)
	}
	u.RawQuery = query.Encode()
	if err != nil {
		return "", err
	}
	return u.String(), nil
}
// Get makes an authenticated HTTP request to the Chef server for the supplied
// endpoint
func (chef *Chef) Get(endpoint string) (*http.Response, error) {
	request, _ := http.NewRequest("GET", chef.requestUrl(endpoint), nil)
	return chef.makeRequest(request)
}
// GetWithParams makes an authenticated HTTP request to the Chef server for the
// supplied endpoint and also includes GET query string parameters
func (chef *Chef) GetWithParams(endpoint string, params map[string]string) (*http.Response, error) {
	query, err := chef.buildQueryString(endpoint, params)
	if err != nil {
		return nil, err
	}
	request, err := http.NewRequest("GET", query, nil)
	if err != nil {
		return nil, err
	}
	return chef.makeRequest(request)
}
// Post post to the chef api
func (chef *Chef) Post(endpoint string, contentType string, params map[string]string, body io.Reader) (*http.Response, error) {
	query, err := chef.buildQueryString(endpoint, params)
	if err != nil {
		return nil, err
	}
	request, err := http.NewRequest("POST", query, body)
	request.Header.Set("Content-Type", contentType)
	return chef.makeRequest(request)
}
// Put makes an authenticated PUT request to the Chef server for the supplied
// endpoint
func (chef *Chef) Put(endpoint string, params map[string]string, body io.Reader) (*http.Response, error) {
	//TODO: Finish this
	request, _ := http.NewRequest("PUT", chef.requestUrl(endpoint), body)
	request.Header.Set("Content-Type", "application/json")
	return chef.makeRequest(request)
}
// Delete makes an authenticated DELETE request to the Chef server for the
// supplied endpoint
func (chef *Chef) Delete(endpoint string, params map[string]string) (*http.Response, error) {
	//TODO: Finish this
	request, _ := http.NewRequest("DELETE", chef.requestUrl(endpoint), nil)
	return chef.makeRequest(request)
}
//
// requestUrl generate the requestUrl from supplied endpoint and params
//
func (chef *Chef) requestUrl(endpoint string) string {
	return fmt.Sprintf("%s/%s", chef.Url, endpoint)
}
// makeRequest
// Take a request object, Setup Auth headers and Send it to the server
func (chef *Chef) makeRequest(request *http.Request) (*http.Response, error) {
	chef.apiRequestHeaders(request)
	var client *http.Client
	if chef.SSLNoVerify {
		tr := &http.Transport{
			Proxy:           http.ProxyFromEnvironment,
			TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
		}
		client = &http.Client{Transport: tr}
	} else {
		client = &http.Client{}
	}
	return client.Do(request)
}
// pulled from goiardi
// encode a string suitable for auth header
func hashStr(toHash string) string {
	h := sha1.New()
	io.WriteString(h, toHash)
	hashed := base64.StdEncoding.EncodeToString(h.Sum(nil))
	return hashed
}
// also from goiardi calc and encodebody data
func calcBodyHash(r *http.Request) (string, error) {
	var bodyStr string
	var err error
	if r.Body == nil {
		bodyStr = ""
	} else {
		save := r.Body
		save, r.Body, err = drainBody(r.Body)
		if err != nil {
			return "", err
		}
		mediaType, params, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
		if err != nil {
			return "", err
		}
		if strings.HasPrefix(mediaType, "multipart/form-data") {
			bodyStr, err = readFileFromRequest(r, params["boundary"])
			if err != nil {
				return "", err
			}
		} else {
			buf := new(bytes.Buffer)
			buf.ReadFrom(r.Body)
			bodyStr = buf.String()
		}
		r.Body = save
	}
	chkHash := hashStr(bodyStr)
	return chkHash, err
}
// read a file from a multipart/form-data request body
// and return it as a string needed to caculate the body hash
func readFileFromRequest(r *http.Request, boundary string) (string, error) {
	mr := multipart.NewReader(r.Body, boundary)
	form, err := mr.ReadForm(1048576)
	if err != nil {
		return "", err
	}
	// we can safely assume only one tarball is added to the
	// multipart as this is how the Chef API is designed
	fp := form.File["tarball"][0]
	file, err := fp.Open()
	if err != nil {
		return "", err
	}
	buf, err := ioutil.ReadAll(file)
	if err != nil {
		return "", err
	}
	return string(buf), nil
}
func drainBody(b io.ReadCloser) (r1, r2 io.ReadCloser, err error) {
	var buf bytes.Buffer
	if _, err = buf.ReadFrom(b); err != nil {
		return nil, nil, err
	}
	if err = b.Close(); err != nil {
		return nil, nil, err
	}
	return ioutil.NopCloser(&buf), ioutil.NopCloser(bytes.NewBuffer(buf.Bytes())), nil
}
func base64BlockEncode(content []byte) []string {
	resultString := base64.StdEncoding.EncodeToString(content)
	var resultSlice []string
	index := 0
	for i := 0; i < len(resultString)/60; i++ {
		resultSlice = append(resultSlice, resultString[index:index+60])
		index += 60
	}
	if len(resultString)%60 != 0 {
		resultSlice = append(resultSlice, resultString[index:])
	}
	return resultSlice
}