k3s/vendor/github.com/rootless-containers/rootlesskit/pkg/api/client/client.go

152 lines
3.3 KiB
Go

package client
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"os"
"github.com/pkg/errors"
"golang.org/x/net/context/ctxhttp"
"github.com/rootless-containers/rootlesskit/pkg/port"
)
type Client interface {
HTTPClient() *http.Client
PortManager() port.Manager
}
// New creates a client.
// socketPath is a path to the UNIX socket, without unix:// prefix.
func New(socketPath string) (Client, error) {
if _, err := os.Stat(socketPath); err != nil {
return nil, err
}
hc := &http.Client{
Transport: &http.Transport{
DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
var d net.Dialer
return d.DialContext(ctx, "unix", socketPath)
},
},
}
return NewWithHTTPClient(hc), nil
}
func NewWithHTTPClient(hc *http.Client) Client {
return &client{
Client: hc,
version: "v1",
dummyHost: "rootlesskit",
}
}
type client struct {
*http.Client
// version is always "v1"
// TODO(AkihiroSuda): negotiate the version
version string
dummyHost string
}
func (c *client) HTTPClient() *http.Client {
return c.Client
}
func (c *client) PortManager() port.Manager {
return &portManager{
client: c,
}
}
func readAtMost(r io.Reader, maxBytes int) ([]byte, error) {
lr := &io.LimitedReader{
R: r,
N: int64(maxBytes),
}
b, err := ioutil.ReadAll(lr)
if err != nil {
return b, err
}
if lr.N == 0 {
return b, errors.Errorf("expected at most %d bytes, got more", maxBytes)
}
return b, nil
}
func successful(resp *http.Response) error {
if resp == nil {
return errors.New("nil response")
}
if resp.StatusCode/100 != 2 {
b, _ := readAtMost(resp.Body, 64*1024)
return errors.Errorf("unexpected HTTP status %s, body=%q", resp.Status, string(b))
}
return nil
}
type portManager struct {
*client
}
func (pm *portManager) AddPort(ctx context.Context, spec port.Spec) (*port.Status, error) {
m, err := json.Marshal(spec)
if err != nil {
return nil, err
}
u := fmt.Sprintf("http://%s/%s/ports", pm.client.dummyHost, pm.client.version)
resp, err := ctxhttp.Post(ctx, pm.client.HTTPClient(), u, "application/json", bytes.NewReader(m))
if err != nil {
return nil, err
}
defer resp.Body.Close()
if err := successful(resp); err != nil {
return nil, err
}
dec := json.NewDecoder(resp.Body)
var status port.Status
if err := dec.Decode(&status); err != nil {
return nil, err
}
return &status, nil
}
func (pm *portManager) ListPorts(ctx context.Context) ([]port.Status, error) {
u := fmt.Sprintf("http://%s/%s/ports", pm.client.dummyHost, pm.client.version)
resp, err := ctxhttp.Get(ctx, pm.client.HTTPClient(), u)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if err := successful(resp); err != nil {
return nil, err
}
var statuses []port.Status
dec := json.NewDecoder(resp.Body)
if err := dec.Decode(&statuses); err != nil {
return nil, err
}
return statuses, nil
}
func (pm *portManager) RemovePort(ctx context.Context, id int) error {
u := fmt.Sprintf("http://%s/%s/ports/%d", pm.client.dummyHost, pm.client.version, id)
req, err := http.NewRequest("DELETE", u, nil)
if err != nil {
return err
}
resp, err := ctxhttp.Do(ctx, pm.client.HTTPClient(), req)
if err != nil {
return err
}
defer resp.Body.Close()
if err := successful(resp); err != nil {
return err
}
return nil
}