cloudflare/cloudflared

Public

mirrored fromhttps://github.com/cloudflare/cloudflaredAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
cc1c6d9abc865533e1ebb5a6a387fd3973ff7bc6

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

cfapi/hostname.go

192lines · modecode

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