From 1b1c2bc1d1efdb5543d64b96a6b6c54756503eb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Carlos=20Mej=C3=ADas=20Rodr=C3=ADguez?= Date: Thu, 1 Aug 2019 22:48:38 -0400 Subject: [PATCH] Encapsulate network communication into a Client struct --- common/client.go | 184 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 184 insertions(+) create mode 100644 common/client.go diff --git a/common/client.go b/common/client.go new file mode 100644 index 0000000..8734bd3 --- /dev/null +++ b/common/client.go @@ -0,0 +1,184 @@ +package common + +import ( + "bytes" + "crypto/tls" + "encoding/json" + "errors" + "fmt" + "io" + "io/ioutil" + "net/http" + "net/url" + "time" + + "github.com/spf13/viper" +) + +var client *PortainerClient + +type PortainerClient struct { + http.Client + url *url.URL + token string +} + +func checkResponseForErrors(resp *http.Response) error { + if 300 <= resp.StatusCode { + // Guess it's a GenericError + respBody := GenericError{} + err := json.NewDecoder(resp.Body).Decode(&respBody) + if err != nil { + // It's not a GenericError + bodyBytes, err := ioutil.ReadAll(resp.Body) + defer resp.Body.Close() + if err != nil { + return err + } + resp.Body = ioutil.NopCloser(bytes.NewReader(bodyBytes)) + return errors.New(string(bodyBytes)) + } + return &respBody + } + return nil +} + +func (n *PortainerClient) do(uri, method string, request io.Reader, requestType string, headers http.Header) (resp *http.Response, err error) { + requestUrl, err := n.url.Parse(uri) + if err != nil { + return + } + + req, err := http.NewRequest(method, requestUrl.String(), request) + if err != nil { + return + } + + if headers != nil { + req.Header = headers + } + + if request != nil { + req.Header.Set("Content-Type", requestType) + } + + if n.token != "" { + req.Header.Set("Authorization", "Bearer "+n.token) + } + + PrintDebugRequest("Request", req) + + resp, err = n.Do(req) + if err != nil { + return + } + + err = checkResponseForErrors(resp) + if err != nil { + return + } + + PrintDebugResponse("Response", resp) + + return +} + +func (n *PortainerClient) DoJSON(uri, method string, request interface{}, response interface{}) error { + var body io.Reader + + if request != nil { + reqBodyBytes, err := json.Marshal(request) + if err != nil { + return err + } + body = bytes.NewReader(reqBodyBytes) + } + + resp, err := n.do(uri, method, body, "application/json", nil) + if err != nil { + return err + } + + if response != nil { + d := json.NewDecoder(resp.Body) + err := d.Decode(response) + if err != nil { + return err + } + } + + return nil +} + +func (n *PortainerClient) Authenticate(user, password string) (token string, err error) { + PrintVerbose("Getting auth token...") + + reqBody := AuthenticateUserRequest{ + Username: viper.GetString("user"), + Password: viper.GetString("password"), + } + + respBody := AuthenticateUserResponse{} + + err = n.DoJSON("auth", http.MethodPost, &reqBody, &respBody) + if err != nil { + return + } + + token = respBody.Jwt + + return +} + +type clientConfig struct { + Url string + User string + Password string + Token string + Insecure bool + Timeout time.Duration +} + +func newClient(config clientConfig) (c *PortainerClient, err error) { + apiUrl, err := url.Parse(config.Url + "/api/") + if err != nil { + return + } + + c = &PortainerClient{ + url: apiUrl, + } + + c.Timeout = config.Timeout + + c.Transport = &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: config.Insecure, + }, + } + + if config.Token != "" { + c.token = config.Token + } else { + c.token, err = c.Authenticate(config.User, config.Password) + if err != nil { + return nil, err + } + PrintDebug(fmt.Sprintf("Auth token: %s", c.token)) + } + + return +} + +func GetClient() (c *PortainerClient, err error) { + if client == nil { + client, err = newClient(clientConfig{ + Url: viper.GetString("url"), + User: viper.GetString("user"), + Password: viper.GetString("password"), + Token: viper.GetString("auth-token"), + }) + } + c = client + return +}