client.go 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. package client
  2. import (
  3. "bytes"
  4. "context"
  5. "encoding/json"
  6. "errors"
  7. "fmt"
  8. "io"
  9. "net"
  10. "net/http"
  11. "os"
  12. "github.com/rootless-containers/rootlesskit/pkg/api"
  13. "github.com/rootless-containers/rootlesskit/pkg/port"
  14. )
  15. type Client interface {
  16. HTTPClient() *http.Client
  17. PortManager() port.Manager
  18. Info(context.Context) (*api.Info, error)
  19. }
  20. // New creates a client.
  21. // socketPath is a path to the UNIX socket, without unix:// prefix.
  22. func New(socketPath string) (Client, error) {
  23. if _, err := os.Stat(socketPath); err != nil {
  24. return nil, err
  25. }
  26. hc := &http.Client{
  27. Transport: &http.Transport{
  28. DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
  29. var d net.Dialer
  30. return d.DialContext(ctx, "unix", socketPath)
  31. },
  32. },
  33. }
  34. return NewWithHTTPClient(hc), nil
  35. }
  36. func NewWithHTTPClient(hc *http.Client) Client {
  37. return &client{
  38. Client: hc,
  39. version: "v1",
  40. dummyHost: "rootlesskit",
  41. }
  42. }
  43. type client struct {
  44. *http.Client
  45. // version is always "v1"
  46. // TODO(AkihiroSuda): negotiate the version
  47. version string
  48. dummyHost string
  49. }
  50. func (c *client) HTTPClient() *http.Client {
  51. return c.Client
  52. }
  53. func (c *client) PortManager() port.Manager {
  54. return &portManager{
  55. client: c,
  56. }
  57. }
  58. func (c *client) Info(ctx context.Context) (*api.Info, error) {
  59. u := fmt.Sprintf("http://%s/%s/info", c.dummyHost, c.version)
  60. req, err := http.NewRequest("GET", u, nil)
  61. if err != nil {
  62. return nil, err
  63. }
  64. req = req.WithContext(ctx)
  65. resp, err := c.HTTPClient().Do(req)
  66. if err != nil {
  67. return nil, err
  68. }
  69. defer resp.Body.Close()
  70. if err := successful(resp); err != nil {
  71. return nil, err
  72. }
  73. var info api.Info
  74. dec := json.NewDecoder(resp.Body)
  75. if err := dec.Decode(&info); err != nil {
  76. return nil, err
  77. }
  78. return &info, nil
  79. }
  80. func readAtMost(r io.Reader, maxBytes int) ([]byte, error) {
  81. lr := &io.LimitedReader{
  82. R: r,
  83. N: int64(maxBytes),
  84. }
  85. b, err := io.ReadAll(lr)
  86. if err != nil {
  87. return b, err
  88. }
  89. if lr.N == 0 {
  90. return b, fmt.Errorf("expected at most %d bytes, got more", maxBytes)
  91. }
  92. return b, nil
  93. }
  94. // HTTPStatusErrorBodyMaxLength specifies the maximum length of HTTPStatusError.Body
  95. const HTTPStatusErrorBodyMaxLength = 64 * 1024
  96. // HTTPStatusError is created from non-2XX HTTP response
  97. type HTTPStatusError struct {
  98. // StatusCode is non-2XX status code
  99. StatusCode int
  100. // Body is at most HTTPStatusErrorBodyMaxLength
  101. Body string
  102. }
  103. // Error implements error.
  104. // If e.Body is a marshalled string of api.ErrorJSON, Error returns ErrorJSON.Message .
  105. // Otherwise Error returns a human-readable string that contains e.StatusCode and e.Body.
  106. func (e *HTTPStatusError) Error() string {
  107. if e.Body != "" && len(e.Body) < HTTPStatusErrorBodyMaxLength {
  108. var ej api.ErrorJSON
  109. if json.Unmarshal([]byte(e.Body), &ej) == nil {
  110. return ej.Message
  111. }
  112. }
  113. return fmt.Sprintf("unexpected HTTP status %s, body=%q", http.StatusText(e.StatusCode), e.Body)
  114. }
  115. func successful(resp *http.Response) error {
  116. if resp == nil {
  117. return errors.New("nil response")
  118. }
  119. if resp.StatusCode/100 != 2 {
  120. b, _ := readAtMost(resp.Body, HTTPStatusErrorBodyMaxLength)
  121. return &HTTPStatusError{
  122. StatusCode: resp.StatusCode,
  123. Body: string(b),
  124. }
  125. }
  126. return nil
  127. }
  128. type portManager struct {
  129. *client
  130. }
  131. func (pm *portManager) AddPort(ctx context.Context, spec port.Spec) (*port.Status, error) {
  132. m, err := json.Marshal(spec)
  133. if err != nil {
  134. return nil, err
  135. }
  136. u := fmt.Sprintf("http://%s/%s/ports", pm.client.dummyHost, pm.client.version)
  137. req, err := http.NewRequest("POST", u, bytes.NewReader(m))
  138. if err != nil {
  139. return nil, err
  140. }
  141. req.Header.Set("Content-Type", "application/json")
  142. req = req.WithContext(ctx)
  143. resp, err := pm.client.HTTPClient().Do(req)
  144. if err != nil {
  145. return nil, err
  146. }
  147. defer resp.Body.Close()
  148. if err := successful(resp); err != nil {
  149. return nil, err
  150. }
  151. dec := json.NewDecoder(resp.Body)
  152. var status port.Status
  153. if err := dec.Decode(&status); err != nil {
  154. return nil, err
  155. }
  156. return &status, nil
  157. }
  158. func (pm *portManager) ListPorts(ctx context.Context) ([]port.Status, error) {
  159. u := fmt.Sprintf("http://%s/%s/ports", pm.client.dummyHost, pm.client.version)
  160. req, err := http.NewRequest("GET", u, nil)
  161. if err != nil {
  162. return nil, err
  163. }
  164. req = req.WithContext(ctx)
  165. resp, err := pm.client.HTTPClient().Do(req)
  166. if err != nil {
  167. return nil, err
  168. }
  169. defer resp.Body.Close()
  170. if err := successful(resp); err != nil {
  171. return nil, err
  172. }
  173. var statuses []port.Status
  174. dec := json.NewDecoder(resp.Body)
  175. if err := dec.Decode(&statuses); err != nil {
  176. return nil, err
  177. }
  178. return statuses, nil
  179. }
  180. func (pm *portManager) RemovePort(ctx context.Context, id int) error {
  181. u := fmt.Sprintf("http://%s/%s/ports/%d", pm.client.dummyHost, pm.client.version, id)
  182. req, err := http.NewRequest("DELETE", u, nil)
  183. if err != nil {
  184. return err
  185. }
  186. req = req.WithContext(ctx)
  187. resp, err := pm.client.HTTPClient().Do(req)
  188. if err != nil {
  189. return err
  190. }
  191. defer resp.Body.Close()
  192. if err := successful(resp); err != nil {
  193. return err
  194. }
  195. return nil
  196. }