Blame internal/client/client.go

Packit Service 4d2de5
// Package client contains functions for communicating with the API server
Packit Service 4d2de5
// Copyright (C) 2020 by Red Hat, Inc.
Packit Service 4d2de5
package client
Packit Service 4d2de5
Packit Service 4d2de5
import (
Packit Service 4d2de5
	"bytes"
Packit Service 4d2de5
	"encoding/json"
Packit Service 4d2de5
	"errors"
Packit Service 4d2de5
	"fmt"
Packit Service 4d2de5
	"io"
Packit Service 4d2de5
	"io/ioutil"
Packit Service 4d2de5
	"net/http"
Packit Service 4d2de5
)
Packit Service 4d2de5
Packit Service 4d2de5
// Request handles sending the request, handling errors, returning the response
Packit Service 4d2de5
// socket is the path to a Unix Domain socket
Packit Service 4d2de5
// path is the full URL path, including query strings
Packit Service 4d2de5
// body is the data to send with POST
Packit Service 4d2de5
// headers is a map of header:value to add to the request
Packit Service 4d2de5
//
Packit Service 4d2de5
// If it is successful a http.Response will be returned. If there is an error, the response will be
Packit Service 4d2de5
// nil and error will be returned.
Packit Service 4d2de5
func Request(socket *http.Client, method, path, body string, headers map[string]string) (*http.Response, error) {
Packit Service 4d2de5
	req, err := http.NewRequest(method, "http://localhost"+path, bytes.NewReader([]byte(body)))
Packit Service 4d2de5
	if err != nil {
Packit Service 4d2de5
		return nil, err
Packit Service 4d2de5
	}
Packit Service 4d2de5
Packit Service 4d2de5
	for h, v := range headers {
Packit Service 4d2de5
		req.Header.Set(h, v)
Packit Service 4d2de5
	}
Packit Service 4d2de5
Packit Service 4d2de5
	resp, err := socket.Do(req)
Packit Service 4d2de5
	if err != nil {
Packit Service 4d2de5
		return nil, err
Packit Service 4d2de5
	}
Packit Service 4d2de5
Packit Service 4d2de5
	return resp, nil
Packit Service 4d2de5
}
Packit Service 4d2de5
Packit Service 4d2de5
// APIErrorMsg is an individual API error with an ID and a message string
Packit Service 4d2de5
type APIErrorMsg struct {
Packit Service 4d2de5
	ID  string `json:"id"`
Packit Service 4d2de5
	Msg string `json:"msg"`
Packit Service 4d2de5
}
Packit Service 4d2de5
Packit Service 4d2de5
// String returns the error id and message as a string
Packit Service 4d2de5
func (r *APIErrorMsg) String() string {
Packit Service 4d2de5
	return fmt.Sprintf("%s: %s", r.ID, r.Msg)
Packit Service 4d2de5
}
Packit Service 4d2de5
Packit Service 4d2de5
// APIResponse is returned by some requests to indicate success or failure.
Packit Service 4d2de5
// It is always returned when the status code is 400, indicating some kind of error with the request.
Packit Service 4d2de5
// If Status is true the Errors list will not be included or will be empty.
Packit Service 4d2de5
// When Status is false it will include at least one APIErrorMsg with details about the error.
Packit Service 4d2de5
type APIResponse struct {
Packit Service 4d2de5
	Status bool          `json:"status"`
Packit Service 4d2de5
	Errors []APIErrorMsg `json:"errors,omitempty"`
Packit Service 4d2de5
}
Packit Service 4d2de5
Packit Service 4d2de5
// String returns the description of the first error, if there is one
Packit Service 4d2de5
func (r *APIResponse) String() string {
Packit Service 4d2de5
	if len(r.Errors) == 0 {
Packit Service 4d2de5
		return ""
Packit Service 4d2de5
	}
Packit Service 4d2de5
	return r.Errors[0].String()
Packit Service 4d2de5
}
Packit Service 4d2de5
Packit Service 4d2de5
// AllErrors returns a list of error description strings
Packit Service 4d2de5
func (r *APIResponse) AllErrors() (all []string) {
Packit Service 4d2de5
	for i := range r.Errors {
Packit Service 4d2de5
		all = append(all, r.Errors[i].String())
Packit Service 4d2de5
	}
Packit Service 4d2de5
	return all
Packit Service 4d2de5
}
Packit Service 4d2de5
Packit Service 4d2de5
// NewAPIResponse converts the response body to a status response
Packit Service 4d2de5
func NewAPIResponse(body []byte) (*APIResponse, error) {
Packit Service 4d2de5
	var status APIResponse
Packit Service 4d2de5
	err := json.Unmarshal(body, &status)
Packit Service 4d2de5
	if err != nil {
Packit Service 4d2de5
		return nil, err
Packit Service 4d2de5
	}
Packit Service 4d2de5
	return &status, nil
Packit Service 4d2de5
}
Packit Service 4d2de5
Packit Service 4d2de5
// apiError converts an API error 400 JSON to a status response
Packit Service 4d2de5
//
Packit Service 4d2de5
// The response body should alway be of the form:
Packit Service 4d2de5
//     {"status": false, "errors": [{"id": ERROR_ID, "msg": ERROR_MESSAGE}, ...]}
Packit Service 4d2de5
func apiError(resp *http.Response) (*APIResponse, error) {
Packit Service 4d2de5
	defer resp.Body.Close()
Packit Service 4d2de5
Packit Service 4d2de5
	body, err := ioutil.ReadAll(resp.Body)
Packit Service 4d2de5
	if err != nil {
Packit Service 4d2de5
		return nil, err
Packit Service 4d2de5
	}
Packit Service 4d2de5
	return NewAPIResponse(body)
Packit Service 4d2de5
}
Packit Service 4d2de5
Packit Service 4d2de5
// GetRawBody returns the resp.Body io.ReadCloser to the caller
Packit Service 4d2de5
// NOTE: The caller is responsible for closing the Body when finished
Packit Service 4d2de5
func GetRawBody(socket *http.Client, method, path string) (io.ReadCloser, *APIResponse, error) {
Packit Service 4d2de5
	resp, err := Request(socket, method, path, "", map[string]string{})
Packit Service 4d2de5
	if err != nil {
Packit Service 4d2de5
		return nil, nil, err
Packit Service 4d2de5
	}
Packit Service 4d2de5
Packit Service 4d2de5
	// Convert the API's JSON error response to an error type and return it
Packit Service 4d2de5
	// lorax-composer (wrongly) returns 404 for some of its json responses
Packit Service 4d2de5
	if resp.StatusCode == 400 || resp.StatusCode == 404 {
Packit Service 4d2de5
		apiResponse, err := apiError(resp)
Packit Service 4d2de5
		return nil, apiResponse, err
Packit Service 4d2de5
	}
Packit Service 4d2de5
	return resp.Body, nil, nil
Packit Service 4d2de5
}
Packit Service 4d2de5
Packit Service 4d2de5
// GetRaw returns raw data from a GET request
Packit Service 4d2de5
// Errors from the API are returned as an APIResponse, client errors are returned as error
Packit Service 4d2de5
func GetRaw(socket *http.Client, method, path string) ([]byte, *APIResponse, error) {
Packit Service 4d2de5
	body, resp, err := GetRawBody(socket, method, path)
Packit Service 4d2de5
	if err != nil || resp != nil {
Packit Service 4d2de5
		return nil, resp, err
Packit Service 4d2de5
	}
Packit Service 4d2de5
	defer body.Close()
Packit Service 4d2de5
Packit Service 4d2de5
	bodyBytes, err := ioutil.ReadAll(body)
Packit Service 4d2de5
	if err != nil {
Packit Service 4d2de5
		return nil, nil, err
Packit Service 4d2de5
	}
Packit Service 4d2de5
Packit Service 4d2de5
	return bodyBytes, nil, nil
Packit Service 4d2de5
}
Packit Service 4d2de5
Packit Service 4d2de5
// GetJSONAll returns all JSON results from a GET request using offset/limit
Packit Service 4d2de5
// This function makes 2 requests, the first with limit=0 to get the total number of results,
Packit Service 4d2de5
// and then with limit=TOTAL to fetch all of the results.
Packit Service 4d2de5
// The path passed to GetJSONAll should not include the limit or offset query parameters
Packit Service 4d2de5
// Errors from the API are returned as an APIResponse, client errors are returned as error
Packit Service 4d2de5
func GetJSONAll(socket *http.Client, path string) ([]byte, *APIResponse, error) {
Packit Service 4d2de5
	body, api, err := GetRaw(socket, "GET", path+"?limit=0")
Packit Service 4d2de5
	if api != nil || err != nil {
Packit Service 4d2de5
		return nil, api, err
Packit Service 4d2de5
	}
Packit Service 4d2de5
Packit Service 4d2de5
	// We just want the total
Packit Service 4d2de5
	var j interface{}
Packit Service 4d2de5
	err = json.Unmarshal(body, &j)
Packit Service 4d2de5
	if err != nil {
Packit Service 4d2de5
		return nil, nil, err
Packit Service 4d2de5
	}
Packit Service 4d2de5
	m := j.(map[string]interface{})
Packit Service 4d2de5
	var v interface{}
Packit Service 4d2de5
	var ok bool
Packit Service 4d2de5
	if v, ok = m["total"]; !ok {
Packit Service 4d2de5
		return nil, nil, errors.New("Response is missing the total value")
Packit Service 4d2de5
	}
Packit Service 4d2de5
Packit Service 4d2de5
	switch total := v.(type) {
Packit Service 4d2de5
	case float64:
Packit Service 4d2de5
		allResults := fmt.Sprintf("%s?limit=%v", path, total)
Packit Service 4d2de5
		return GetRaw(socket, "GET", allResults)
Packit Service 4d2de5
	}
Packit Service 4d2de5
	return nil, nil, errors.New("Response 'total' is not a float64")
Packit Service 4d2de5
}
Packit Service 4d2de5
Packit Service 4d2de5
// PostRaw sends a POST with raw data and returns the raw response body
Packit Service 4d2de5
// Errors from the API are returned as an APIResponse, client errors are returned as error
Packit Service 4d2de5
func PostRaw(socket *http.Client, path, body string, headers map[string]string) ([]byte, *APIResponse, error) {
Packit Service 4d2de5
	resp, err := Request(socket, "POST", path, body, headers)
Packit Service 4d2de5
	if err != nil {
Packit Service 4d2de5
		return nil, nil, err
Packit Service 4d2de5
	}
Packit Service 4d2de5
Packit Service 4d2de5
	// Convert the API's JSON error response to an APIResponse
Packit Service 4d2de5
	if resp.StatusCode == 400 {
Packit Service 4d2de5
		apiResponse, err := apiError(resp)
Packit Service 4d2de5
		return nil, apiResponse, err
Packit Service 4d2de5
	}
Packit Service 4d2de5
	defer resp.Body.Close()
Packit Service 4d2de5
Packit Service 4d2de5
	responseBody, err := ioutil.ReadAll(resp.Body)
Packit Service 4d2de5
	if err != nil {
Packit Service 4d2de5
		return nil, nil, err
Packit Service 4d2de5
	}
Packit Service 4d2de5
Packit Service 4d2de5
	return responseBody, nil, nil
Packit Service 4d2de5
}
Packit Service 4d2de5
Packit Service 4d2de5
// PostTOML sends a POST with TOML data and the Content-Type header set to "text/x-toml"
Packit Service 4d2de5
// Errors from the API are returned as an APIResponse, client errors are returned as error
Packit Service 4d2de5
func PostTOML(socket *http.Client, path, body string) ([]byte, *APIResponse, error) {
Packit Service 4d2de5
	headers := map[string]string{"Content-Type": "text/x-toml"}
Packit Service 4d2de5
	return PostRaw(socket, path, body, headers)
Packit Service 4d2de5
}
Packit Service 4d2de5
Packit Service 4d2de5
// PostJSON sends a POST with JSON data and the Content-Type header set to "application/json"
Packit Service 4d2de5
// Errors from the API are returned as an APIResponse, client errors are returned as error
Packit Service 4d2de5
func PostJSON(socket *http.Client, path, body string) ([]byte, *APIResponse, error) {
Packit Service 4d2de5
	headers := map[string]string{"Content-Type": "application/json"}
Packit Service 4d2de5
	return PostRaw(socket, path, body, headers)
Packit Service 4d2de5
}
Packit Service 4d2de5
Packit Service 4d2de5
// DeleteRaw sends a DELETE request
Packit Service 4d2de5
// Errors from the API are returned as an APIResponse, client errors are returned as error
Packit Service 4d2de5
func DeleteRaw(socket *http.Client, path string) ([]byte, *APIResponse, error) {
Packit Service 4d2de5
	resp, err := Request(socket, "DELETE", path, "", nil)
Packit Service 4d2de5
	if err != nil {
Packit Service 4d2de5
		return nil, nil, err
Packit Service 4d2de5
	}
Packit Service 4d2de5
Packit Service 4d2de5
	// Convert the API's JSON error response to an APIResponse
Packit Service 4d2de5
	if resp.StatusCode == 400 {
Packit Service 4d2de5
		apiResponse, err := apiError(resp)
Packit Service 4d2de5
		return nil, apiResponse, err
Packit Service 4d2de5
	}
Packit Service 4d2de5
	defer resp.Body.Close()
Packit Service 4d2de5
Packit Service 4d2de5
	responseBody, err := ioutil.ReadAll(resp.Body)
Packit Service 4d2de5
	if err != nil {
Packit Service 4d2de5
		return nil, nil, err
Packit Service 4d2de5
	}
Packit Service 4d2de5
Packit Service 4d2de5
	return responseBody, nil, nil
Packit Service 4d2de5
}