Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

client: public API #261

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 19 additions & 5 deletions client/changes.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,10 @@ type changeAndData struct {
// Change fetches information about a Change given its ID.
func (client *Client) Change(id string) (*Change, error) {
var chgd changeAndData
_, err := client.doSync("GET", "/v1/changes/"+id, nil, nil, nil, &chgd)
if err != nil {
if _, err := client.DoSync(&RequestInfo{
Method: "GET",
Path: "/v1/changes/" + id,
}, &chgd); err != nil {
return nil, err
}

Expand All @@ -111,7 +113,11 @@ func (client *Client) Abort(id string) (*Change, error) {
}

var chg Change
if _, err := client.doSync("POST", "/v1/changes/"+id, nil, nil, &body, &chg); err != nil {
if _, err := client.DoSync(&RequestInfo{
Method: "POST",
Path: "/v1/changes/" + id,
Body: &body,
}, &chg); err != nil {
return nil, err
}

Expand Down Expand Up @@ -158,7 +164,11 @@ func (client *Client) Changes(opts *ChangesOptions) ([]*Change, error) {
}

var chgds []changeAndData
_, err := client.doSync("GET", "/v1/changes", query, nil, nil, &chgds)
_, err := client.DoSync(&RequestInfo{
Method: "GET",
Path: "/v1/changes",
Query: query,
}, &chgds)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -190,7 +200,11 @@ func (client *Client) WaitChange(id string, opts *WaitChangeOptions) (*Change, e
query.Set("timeout", opts.Timeout.String())
}

_, err := client.doSync("GET", "/v1/changes/"+id+"/wait", query, nil, nil, &chgd)
_, err := client.DoSync(&RequestInfo{
Method: "GET",
Path: "/v1/changes/" + id + "/wait",
Query: query,
}, &chgd)
if err != nil {
return nil, err
}
Expand Down
6 changes: 5 additions & 1 deletion client/checks.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,11 @@ func (client *Client) Checks(opts *ChecksOptions) ([]*CheckInfo, error) {
query["names"] = opts.Names
}
var checks []*CheckInfo
_, err := client.doSync("GET", "/v1/checks", query, nil, nil, &checks)
_, err := client.DoSync(&RequestInfo{
Method: "GET",
Path: "/v1/checks",
Query: query,
}, &checks)
if err != nil {
return nil, err
}
Expand Down
74 changes: 57 additions & 17 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,17 @@ type doer interface {
Do(*http.Request) (*http.Response, error)
}

// ClientGetter implementations must provide a way to convert themselves to
// Pebble client instances.
type ClientGetter interface {
anpep marked this conversation as resolved.
Show resolved Hide resolved
// Client returns a Pebble client instance.
Client() *Client
}

type ClientSetter interface {
SetClient(ClientGetter)
}

// Config allows the user to customize client behavior.
type Config struct {
// BaseURL contains the base URL where the Pebble daemon is expected to be.
Expand Down Expand Up @@ -118,6 +129,10 @@ type Client struct {
getWebsocket getWebsocketFunc
}

func (c *Client) Client() *Client {
return c
}

type getWebsocketFunc func(url string) (clientWebsocket, error)

type clientWebsocket interface {
Expand Down Expand Up @@ -326,13 +341,27 @@ func decodeInto(reader io.Reader, v interface{}) error {
return nil
}

// doSync performs a request to the given path using the specified HTTP method.
// RequestInfo holds the information to perform a request to the daemon.
type RequestInfo struct {
Method string
Path string
Query url.Values
Headers map[string]string
Body io.Reader
}

// ResultInfo is empty for now, but this is the mechanism that conveys
// general information that makes sense to requests at a more general
// level, and might be disconnected from the specific request at hand.
type ResultInfo struct{}

// DoSync performs a request to the given path using the specified HTTP method.
// It expects a "sync" response from the API and on success decodes the JSON
// response payload into the given value using the "UseNumber" json decoding
// which produces json.Numbers instead of float64 types for numbers.
func (client *Client) doSync(method, path string, query url.Values, headers map[string]string, body io.Reader, v interface{}) (*ResultInfo, error) {
func (client *Client) DoSync(req *RequestInfo, v interface{}) (*ResultInfo, error) {
var rsp response
if err := client.do(method, path, query, headers, body, &rsp); err != nil {
if err := client.do(req.Method, req.Path, req.Query, req.Headers, req.Body, &rsp); err != nil {
return nil, err
}
if err := rsp.err(client); err != nil {
Expand All @@ -354,22 +383,28 @@ func (client *Client) doSync(method, path string, query url.Values, headers map[
return &rsp.ResultInfo, nil
}

func (client *Client) doAsync(method, path string, query url.Values, headers map[string]string, body io.Reader) (changeID string, err error) {
_, changeID, err = client.doAsyncFull(method, path, query, headers, body)
// DoAsync performs a request to the given path using the specified HTTP method.
// It expects an "async" response from the API and on success returns the
// change ID.
func (client *Client) DoAsync(req *RequestInfo) (changeID string, err error) {
_, changeID, err = client.DoAsyncFull(req)
return
}

func (client *Client) doAsyncFull(method, path string, query url.Values, headers map[string]string, body io.Reader) (result json.RawMessage, changeID string, err error) {
// DoAsync performs a request to the given path using the specified HTTP method.
// It expects an "async" response from the API and on success returns the raw
// JSON response from the daemon alongside the change ID.
func (client *Client) DoAsyncFull(req *RequestInfo) (result json.RawMessage, changeID string, err error) {
var rsp response

if err := client.do(method, path, query, headers, body, &rsp); err != nil {
if err := client.do(req.Method, req.Path, req.Query, req.Headers, req.Body, &rsp); err != nil {
return nil, "", err
}
if err := rsp.err(client); err != nil {
return nil, "", err
}
if rsp.Type != "async" {
return nil, "", fmt.Errorf("expected async response for %q on %q, got %q", method, path, rsp.Type)
return nil, "", fmt.Errorf("expected async response for %q on %q, got %q", req.Method, req.Path, rsp.Type)
}
if rsp.StatusCode != 202 {
return nil, "", fmt.Errorf("operation not accepted")
Expand All @@ -381,11 +416,6 @@ func (client *Client) doAsyncFull(method, path string, query url.Values, headers
return rsp.Result, rsp.Change, nil
}

// ResultInfo is empty for now, but this is the mechanism that conveys
// general information that makes sense to requests at a more general
// level, and might be disconnected from the specific request at hand.
type ResultInfo struct{}

// A response produced by the REST API will usually fit in this
// (exceptions are the icons/ endpoints obvs)
type response struct {
Expand Down Expand Up @@ -476,7 +506,10 @@ type SysInfo struct {
func (client *Client) SysInfo() (*SysInfo, error) {
var sysInfo SysInfo

if _, err := client.doSync("GET", "/v1/system-info", nil, nil, nil, &sysInfo); err != nil {
if _, err := client.DoSync(&RequestInfo{
Method: "GET",
Path: "/v1/system-info",
}, &sysInfo); err != nil {
return nil, fmt.Errorf("cannot obtain system details: %w", err)
}

Expand All @@ -497,8 +530,11 @@ func (client *Client) DebugPost(action string, params interface{}, result interf
if err != nil {
return err
}

_, err = client.doSync("POST", "/v1/debug", nil, nil, bytes.NewReader(body), result)
_, err = client.DoSync(&RequestInfo{
Method: "POST",
Path: "/v1/debug",
Body: bytes.NewReader(body),
}, result)
return err
}

Expand All @@ -508,6 +544,10 @@ func (client *Client) DebugGet(action string, result interface{}, params map[str
for k, v := range params {
urlParams.Set(k, v)
}
_, err := client.doSync("GET", "/v1/debug", urlParams, nil, nil, &result)
_, err := client.DoSync(&RequestInfo{
Method: "GET",
Path: "/v1/debug",
Query: urlParams,
}, &result)
return err
}
7 changes: 6 additions & 1 deletion client/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,12 @@ func (client *Client) Exec(opts *ExecOptions) (*ExecProcess, error) {
headers := map[string]string{
"Content-Type": "application/json",
}
resultBytes, changeID, err := client.doAsyncFull("POST", "/v1/exec", nil, headers, &body)
resultBytes, changeID, err := client.DoAsyncFull(&RequestInfo{
Method: "POST",
Path: "/v1/exec",
Headers: headers,
Body: &body,
})
if err != nil {
return nil, err
}
Expand Down
5 changes: 4 additions & 1 deletion client/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@ func (client *Client) Do(method, path string, query url.Values, body io.Reader,
}

func (client *Client) FakeAsyncRequest() (changeId string, err error) {
changeId, err = client.doAsync("GET", "/v1/async-test", nil, nil, nil)
changeId, err = client.DoAsync(&RequestInfo{
Method: "GET",
Path: "/v1/async-test",
})
if err != nil {
return "", fmt.Errorf("cannot do async test: %v", err)
}
Expand Down
20 changes: 17 additions & 3 deletions client/files.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,11 @@ func (client *Client) ListFiles(opts *ListFilesOptions) ([]*FileInfo, error) {
}

var results []fileInfoResult
_, err := client.doSync("GET", "/v1/files", q, nil, nil, &results)
_, err := client.DoSync(&RequestInfo{
Method: "GET",
Path: "/v1/files",
Query: q,
}, &results)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -280,7 +284,12 @@ func (client *Client) MakeDir(opts *MakeDirOptions) error {
headers := map[string]string{
"Content-Type": "application/json",
}
if _, err := client.doSync("POST", "/v1/files", nil, headers, &body, &result); err != nil {
if _, err := client.DoSync(&RequestInfo{
Method: "POST",
Path: "/v1/files",
Headers: headers,
Body: &body,
}, &result); err != nil {
return err
}

Expand Down Expand Up @@ -347,7 +356,12 @@ func (client *Client) RemovePath(opts *RemovePathOptions) error {
headers := map[string]string{
"Content-Type": "application/json",
}
if _, err := client.doSync("POST", "/v1/files", nil, headers, &body, &result); err != nil {
if _, err := client.DoSync(&RequestInfo{
Method: "POST",
Path: "/v1/files",
Headers: headers,
Body: &body,
}, &result); err != nil {
return err
}

Expand Down
12 changes: 10 additions & 2 deletions client/plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,11 @@ func (client *Client) AddLayer(opts *AddLayerOptions) error {
if err := json.NewEncoder(&body).Encode(&payload); err != nil {
return err
}
_, err := client.doSync("POST", "/v1/layers", nil, nil, &body, nil)
_, err := client.DoSync(&RequestInfo{
Method: "POST",
Path: "/v1/layers",
Body: &body,
}, nil)
return err
}

Expand All @@ -64,7 +68,11 @@ func (client *Client) PlanBytes(_ *PlanOptions) (data []byte, err error) {
"format": []string{"yaml"},
}
var dataStr string
_, err = client.doSync("GET", "/v1/plan", query, nil, nil, &dataStr)
_, err = client.DoSync(&RequestInfo{
Method: "GET",
Path: "/v1/plan",
Query: query,
}, &dataStr)
if err != nil {
return nil, err
}
Expand Down
13 changes: 11 additions & 2 deletions client/services.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,12 @@ func (client *Client) doMultiServiceAction(actionName string, services []string)
headers := map[string]string{
"Content-Type": "application/json",
}
return client.doAsyncFull("POST", "/v1/services", nil, headers, bytes.NewBuffer(data))
return client.DoAsyncFull(&RequestInfo{
Method: "POST",
Path: "/v1/services",
Headers: headers,
Body: bytes.NewBuffer(data),
})
}

type ServicesOptions struct {
Expand Down Expand Up @@ -119,7 +124,11 @@ func (client *Client) Services(opts *ServicesOptions) ([]*ServiceInfo, error) {
"names": []string{strings.Join(opts.Names, ",")},
}
var services []*ServiceInfo
_, err := client.doSync("GET", "/v1/services", query, nil, nil, &services)
_, err := client.DoSync(&RequestInfo{
Method: "GET",
Path: "/v1/services",
Query: query,
}, &services)
if err != nil {
return nil, err
}
Expand Down
6 changes: 5 additions & 1 deletion client/signals.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,11 @@ func (client *Client) SendSignal(opts *SendSignalOptions) error {
if err != nil {
return fmt.Errorf("cannot encode JSON payload: %w", err)
}
_, err = client.doSync("POST", "/v1/signals", nil, nil, &body, nil)
_, err = client.DoSync(&RequestInfo{
Method: "POST",
Path: "/v1/signals",
Body: &body,
}, nil)
return err
}

Expand Down
12 changes: 10 additions & 2 deletions client/warnings.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,11 @@ func (client *Client) Warnings(opts WarningsOptions) ([]*Warning, error) {
if opts.All {
q.Add("select", "all")
}
_, err := client.doSync("GET", "/v1/warnings", q, nil, nil, &jws)
_, err := client.DoSync(&RequestInfo{
Method: "GET",
Path: "/v1/warnings",
Query: q,
}, &jws)

ws := make([]*Warning, len(jws))
for i, jw := range jws {
Expand All @@ -77,6 +81,10 @@ func (client *Client) Okay(t time.Time) error {
if err := json.NewEncoder(&body).Encode(op); err != nil {
return err
}
_, err := client.doSync("POST", "/v1/warnings", nil, nil, &body, nil)
_, err := client.DoSync(&RequestInfo{
Method: "POST",
Path: "/v1/warnings",
Body: &body,
}, nil)
return err
}
Loading
Loading