hostname.go 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. package cfapi
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "io"
  6. "net/http"
  7. "path"
  8. "github.com/google/uuid"
  9. "github.com/pkg/errors"
  10. )
  11. type Change = string
  12. const (
  13. ChangeNew = "new"
  14. ChangeUpdated = "updated"
  15. ChangeUnchanged = "unchanged"
  16. )
  17. // HostnameRoute represents a record type that can route to a tunnel
  18. type HostnameRoute interface {
  19. json.Marshaler
  20. RecordType() string
  21. UnmarshalResult(body io.Reader) (HostnameRouteResult, error)
  22. String() string
  23. }
  24. type HostnameRouteResult interface {
  25. // SuccessSummary explains what will route to this tunnel when it's provisioned successfully
  26. SuccessSummary() string
  27. }
  28. type DNSRoute struct {
  29. userHostname string
  30. overwriteExisting bool
  31. }
  32. type DNSRouteResult struct {
  33. route *DNSRoute
  34. CName Change `json:"cname"`
  35. Name string `json:"name"`
  36. }
  37. func NewDNSRoute(userHostname string, overwriteExisting bool) HostnameRoute {
  38. return &DNSRoute{
  39. userHostname: userHostname,
  40. overwriteExisting: overwriteExisting,
  41. }
  42. }
  43. func (dr *DNSRoute) MarshalJSON() ([]byte, error) {
  44. s := struct {
  45. Type string `json:"type"`
  46. UserHostname string `json:"user_hostname"`
  47. OverwriteExisting bool `json:"overwrite_existing"`
  48. }{
  49. Type: dr.RecordType(),
  50. UserHostname: dr.userHostname,
  51. OverwriteExisting: dr.overwriteExisting,
  52. }
  53. return json.Marshal(&s)
  54. }
  55. func (dr *DNSRoute) UnmarshalResult(body io.Reader) (HostnameRouteResult, error) {
  56. var result DNSRouteResult
  57. err := parseResponse(body, &result)
  58. result.route = dr
  59. return &result, err
  60. }
  61. func (dr *DNSRoute) RecordType() string {
  62. return "dns"
  63. }
  64. func (dr *DNSRoute) String() string {
  65. return fmt.Sprintf("%s %s", dr.RecordType(), dr.userHostname)
  66. }
  67. func (res *DNSRouteResult) SuccessSummary() string {
  68. var msgFmt string
  69. switch res.CName {
  70. case ChangeNew:
  71. msgFmt = "Added CNAME %s which will route to this tunnel"
  72. case ChangeUpdated: // this is not currently returned by tunnelsore
  73. msgFmt = "%s updated to route to your tunnel"
  74. case ChangeUnchanged:
  75. msgFmt = "%s is already configured to route to your tunnel"
  76. }
  77. return fmt.Sprintf(msgFmt, res.hostname())
  78. }
  79. // hostname yields the resulting name for the DNS route; if that is not available from Cloudflare API, then the
  80. // requested name is returned instead (should not be the common path, it is just a fall-back).
  81. func (res *DNSRouteResult) hostname() string {
  82. if res.Name != "" {
  83. return res.Name
  84. }
  85. return res.route.userHostname
  86. }
  87. type LBRoute struct {
  88. lbName string
  89. lbPool string
  90. }
  91. type LBRouteResult struct {
  92. route *LBRoute
  93. LoadBalancer Change `json:"load_balancer"`
  94. Pool Change `json:"pool"`
  95. }
  96. func NewLBRoute(lbName, lbPool string) HostnameRoute {
  97. return &LBRoute{
  98. lbName: lbName,
  99. lbPool: lbPool,
  100. }
  101. }
  102. func (lr *LBRoute) MarshalJSON() ([]byte, error) {
  103. s := struct {
  104. Type string `json:"type"`
  105. LBName string `json:"lb_name"`
  106. LBPool string `json:"lb_pool"`
  107. }{
  108. Type: lr.RecordType(),
  109. LBName: lr.lbName,
  110. LBPool: lr.lbPool,
  111. }
  112. return json.Marshal(&s)
  113. }
  114. func (lr *LBRoute) RecordType() string {
  115. return "lb"
  116. }
  117. func (lb *LBRoute) String() string {
  118. return fmt.Sprintf("%s %s %s", lb.RecordType(), lb.lbName, lb.lbPool)
  119. }
  120. func (lr *LBRoute) UnmarshalResult(body io.Reader) (HostnameRouteResult, error) {
  121. var result LBRouteResult
  122. err := parseResponse(body, &result)
  123. result.route = lr
  124. return &result, err
  125. }
  126. func (res *LBRouteResult) SuccessSummary() string {
  127. var msg string
  128. switch res.LoadBalancer + "," + res.Pool {
  129. case "new,new":
  130. msg = "Created load balancer %s and added a new pool %s with this tunnel as an origin"
  131. case "new,updated":
  132. msg = "Created load balancer %s with an existing pool %s which was updated to use this tunnel as an origin"
  133. case "new,unchanged":
  134. msg = "Created load balancer %s with an existing pool %s which already has this tunnel as an origin"
  135. case "updated,new":
  136. msg = "Added new pool %[2]s with this tunnel as an origin to load balancer %[1]s"
  137. case "updated,updated":
  138. msg = "Updated pool %[2]s to use this tunnel as an origin and added it to load balancer %[1]s"
  139. case "updated,unchanged":
  140. msg = "Added pool %[2]s, which already has this tunnel as an origin, to load balancer %[1]s"
  141. case "unchanged,updated":
  142. msg = "Added this tunnel as an origin in pool %[2]s which is already used by load balancer %[1]s"
  143. case "unchanged,unchanged":
  144. msg = "Load balancer %s already uses pool %s which has this tunnel as an origin"
  145. case "unchanged,new":
  146. // this state is not possible
  147. fallthrough
  148. default:
  149. msg = "Something went wrong: failed to modify load balancer %s with pool %s; please check traffic manager configuration in the dashboard"
  150. }
  151. return fmt.Sprintf(msg, res.route.lbName, res.route.lbPool)
  152. }
  153. func (r *RESTClient) RouteTunnel(tunnelID uuid.UUID, route HostnameRoute) (HostnameRouteResult, error) {
  154. endpoint := r.baseEndpoints.zoneLevel
  155. endpoint.Path = path.Join(endpoint.Path, fmt.Sprintf("%v/routes", tunnelID))
  156. resp, err := r.sendRequest("PUT", endpoint, route)
  157. if err != nil {
  158. return nil, errors.Wrap(err, "REST request failed")
  159. }
  160. defer resp.Body.Close()
  161. if resp.StatusCode == http.StatusOK {
  162. return route.UnmarshalResult(resp.Body)
  163. }
  164. return nil, r.statusCodeToError("add route", resp)
  165. }