tunnel.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. package cfapi
  2. import (
  3. "fmt"
  4. "io"
  5. "net"
  6. "net/http"
  7. "net/url"
  8. "path"
  9. "time"
  10. "github.com/google/uuid"
  11. "github.com/pkg/errors"
  12. )
  13. var ErrTunnelNameConflict = errors.New("tunnel with name already exists")
  14. type Tunnel struct {
  15. ID uuid.UUID `json:"id"`
  16. Name string `json:"name"`
  17. CreatedAt time.Time `json:"created_at"`
  18. DeletedAt time.Time `json:"deleted_at"`
  19. Connections []Connection `json:"connections"`
  20. }
  21. type TunnelWithToken struct {
  22. Tunnel
  23. Token string `json:"token"`
  24. }
  25. type Connection struct {
  26. ColoName string `json:"colo_name"`
  27. ID uuid.UUID `json:"id"`
  28. IsPendingReconnect bool `json:"is_pending_reconnect"`
  29. OriginIP net.IP `json:"origin_ip"`
  30. OpenedAt time.Time `json:"opened_at"`
  31. }
  32. type ActiveClient struct {
  33. ID uuid.UUID `json:"id"`
  34. Features []string `json:"features"`
  35. Version string `json:"version"`
  36. Arch string `json:"arch"`
  37. RunAt time.Time `json:"run_at"`
  38. Connections []Connection `json:"conns"`
  39. }
  40. type newTunnel struct {
  41. Name string `json:"name"`
  42. TunnelSecret []byte `json:"tunnel_secret"`
  43. }
  44. type managementRequest struct {
  45. Resources []string `json:"resources"`
  46. }
  47. type CleanupParams struct {
  48. queryParams url.Values
  49. }
  50. func NewCleanupParams() *CleanupParams {
  51. return &CleanupParams{
  52. queryParams: url.Values{},
  53. }
  54. }
  55. func (cp *CleanupParams) ForClient(clientID uuid.UUID) {
  56. cp.queryParams.Set("client_id", clientID.String())
  57. }
  58. func (cp CleanupParams) encode() string {
  59. return cp.queryParams.Encode()
  60. }
  61. func (r *RESTClient) CreateTunnel(name string, tunnelSecret []byte) (*TunnelWithToken, error) {
  62. if name == "" {
  63. return nil, errors.New("tunnel name required")
  64. }
  65. if _, err := uuid.Parse(name); err == nil {
  66. return nil, errors.New("you cannot use UUIDs as tunnel names")
  67. }
  68. body := &newTunnel{
  69. Name: name,
  70. TunnelSecret: tunnelSecret,
  71. }
  72. resp, err := r.sendRequest("POST", r.baseEndpoints.accountLevel, body)
  73. if err != nil {
  74. return nil, errors.Wrap(err, "REST request failed")
  75. }
  76. defer resp.Body.Close()
  77. switch resp.StatusCode {
  78. case http.StatusOK:
  79. var tunnel TunnelWithToken
  80. if serdeErr := parseResponse(resp.Body, &tunnel); serdeErr != nil {
  81. return nil, serdeErr
  82. }
  83. return &tunnel, nil
  84. case http.StatusConflict:
  85. return nil, ErrTunnelNameConflict
  86. }
  87. return nil, r.statusCodeToError("create tunnel", resp)
  88. }
  89. func (r *RESTClient) GetTunnel(tunnelID uuid.UUID) (*Tunnel, error) {
  90. endpoint := r.baseEndpoints.accountLevel
  91. endpoint.Path = path.Join(endpoint.Path, fmt.Sprintf("%v", tunnelID))
  92. resp, err := r.sendRequest("GET", endpoint, nil)
  93. if err != nil {
  94. return nil, errors.Wrap(err, "REST request failed")
  95. }
  96. defer resp.Body.Close()
  97. if resp.StatusCode == http.StatusOK {
  98. return unmarshalTunnel(resp.Body)
  99. }
  100. return nil, r.statusCodeToError("get tunnel", resp)
  101. }
  102. func (r *RESTClient) GetTunnelToken(tunnelID uuid.UUID) (token string, err error) {
  103. endpoint := r.baseEndpoints.accountLevel
  104. endpoint.Path = path.Join(endpoint.Path, fmt.Sprintf("%v/token", tunnelID))
  105. resp, err := r.sendRequest("GET", endpoint, nil)
  106. if err != nil {
  107. return "", errors.Wrap(err, "REST request failed")
  108. }
  109. defer resp.Body.Close()
  110. if resp.StatusCode == http.StatusOK {
  111. err = parseResponse(resp.Body, &token)
  112. return token, err
  113. }
  114. return "", r.statusCodeToError("get tunnel token", resp)
  115. }
  116. func (r *RESTClient) GetManagementToken(tunnelID uuid.UUID) (token string, err error) {
  117. endpoint := r.baseEndpoints.accountLevel
  118. endpoint.Path = path.Join(endpoint.Path, fmt.Sprintf("%v/management", tunnelID))
  119. body := &managementRequest{
  120. Resources: []string{"logs"},
  121. }
  122. resp, err := r.sendRequest("POST", endpoint, body)
  123. if err != nil {
  124. return "", errors.Wrap(err, "REST request failed")
  125. }
  126. defer resp.Body.Close()
  127. if resp.StatusCode == http.StatusOK {
  128. err = parseResponse(resp.Body, &token)
  129. return token, err
  130. }
  131. return "", r.statusCodeToError("get tunnel token", resp)
  132. }
  133. func (r *RESTClient) DeleteTunnel(tunnelID uuid.UUID, cascade bool) error {
  134. endpoint := r.baseEndpoints.accountLevel
  135. endpoint.Path = path.Join(endpoint.Path, fmt.Sprintf("%v", tunnelID))
  136. // Cascade will delete all tunnel dependencies (connections, routes, etc.) that
  137. // are linked to the deleted tunnel.
  138. if cascade {
  139. endpoint.RawQuery = "cascade=true"
  140. }
  141. resp, err := r.sendRequest("DELETE", endpoint, nil)
  142. if err != nil {
  143. return errors.Wrap(err, "REST request failed")
  144. }
  145. defer resp.Body.Close()
  146. return r.statusCodeToError("delete tunnel", resp)
  147. }
  148. func (r *RESTClient) ListTunnels(filter *TunnelFilter) ([]*Tunnel, error) {
  149. fetchFn := func(page int) (*http.Response, error) {
  150. endpoint := r.baseEndpoints.accountLevel
  151. filter.Page(page)
  152. endpoint.RawQuery = filter.encode()
  153. rsp, err := r.sendRequest("GET", endpoint, nil)
  154. if err != nil {
  155. return nil, errors.Wrap(err, "REST request failed")
  156. }
  157. if rsp.StatusCode != http.StatusOK {
  158. rsp.Body.Close()
  159. return nil, r.statusCodeToError("list tunnels", rsp)
  160. }
  161. return rsp, nil
  162. }
  163. return fetchExhaustively[Tunnel](fetchFn)
  164. }
  165. func (r *RESTClient) ListActiveClients(tunnelID uuid.UUID) ([]*ActiveClient, error) {
  166. endpoint := r.baseEndpoints.accountLevel
  167. endpoint.Path = path.Join(endpoint.Path, fmt.Sprintf("%v/connections", tunnelID))
  168. resp, err := r.sendRequest("GET", endpoint, nil)
  169. if err != nil {
  170. return nil, errors.Wrap(err, "REST request failed")
  171. }
  172. defer resp.Body.Close()
  173. if resp.StatusCode == http.StatusOK {
  174. return parseConnectionsDetails(resp.Body)
  175. }
  176. return nil, r.statusCodeToError("list connection details", resp)
  177. }
  178. func parseConnectionsDetails(reader io.Reader) ([]*ActiveClient, error) {
  179. var clients []*ActiveClient
  180. err := parseResponse(reader, &clients)
  181. return clients, err
  182. }
  183. func (r *RESTClient) CleanupConnections(tunnelID uuid.UUID, params *CleanupParams) error {
  184. endpoint := r.baseEndpoints.accountLevel
  185. endpoint.RawQuery = params.encode()
  186. endpoint.Path = path.Join(endpoint.Path, fmt.Sprintf("%v/connections", tunnelID))
  187. resp, err := r.sendRequest("DELETE", endpoint, nil)
  188. if err != nil {
  189. return errors.Wrap(err, "REST request failed")
  190. }
  191. defer resp.Body.Close()
  192. return r.statusCodeToError("cleanup connections", resp)
  193. }
  194. func unmarshalTunnel(reader io.Reader) (*Tunnel, error) {
  195. var tunnel Tunnel
  196. err := parseResponse(reader, &tunnel)
  197. return &tunnel, err
  198. }