1package godo 2 3import ( 4 "context" 5 "fmt" 6 "net/http" 7) 8 9const loadBalancersBasePath = "/v2/load_balancers" 10const forwardingRulesPath = "forwarding_rules" 11 12const dropletsPath = "droplets" 13 14// LoadBalancersService is an interface for managing load balancers with the DigitalOcean API. 15// See: https://developers.digitalocean.com/documentation/v2#load-balancers 16type LoadBalancersService interface { 17 Get(context.Context, string) (*LoadBalancer, *Response, error) 18 List(context.Context, *ListOptions) ([]LoadBalancer, *Response, error) 19 Create(context.Context, *LoadBalancerRequest) (*LoadBalancer, *Response, error) 20 Update(ctx context.Context, lbID string, lbr *LoadBalancerRequest) (*LoadBalancer, *Response, error) 21 Delete(ctx context.Context, lbID string) (*Response, error) 22 AddDroplets(ctx context.Context, lbID string, dropletIDs ...int) (*Response, error) 23 RemoveDroplets(ctx context.Context, lbID string, dropletIDs ...int) (*Response, error) 24 AddForwardingRules(ctx context.Context, lbID string, rules ...ForwardingRule) (*Response, error) 25 RemoveForwardingRules(ctx context.Context, lbID string, rules ...ForwardingRule) (*Response, error) 26} 27 28// LoadBalancer represents a DigitalOcean load balancer configuration. 29// Tags can only be provided upon the creation of a Load Balancer. 30type LoadBalancer struct { 31 ID string `json:"id,omitempty"` 32 Name string `json:"name,omitempty"` 33 IP string `json:"ip,omitempty"` 34 Algorithm string `json:"algorithm,omitempty"` 35 Status string `json:"status,omitempty"` 36 Created string `json:"created_at,omitempty"` 37 ForwardingRules []ForwardingRule `json:"forwarding_rules,omitempty"` 38 HealthCheck *HealthCheck `json:"health_check,omitempty"` 39 StickySessions *StickySessions `json:"sticky_sessions,omitempty"` 40 Region *Region `json:"region,omitempty"` 41 DropletIDs []int `json:"droplet_ids,omitempty"` 42 Tag string `json:"tag,omitempty"` 43 Tags []string `json:"tags,omitempty"` 44 RedirectHttpToHttps bool `json:"redirect_http_to_https,omitempty"` 45} 46 47// String creates a human-readable description of a LoadBalancer. 48func (l LoadBalancer) String() string { 49 return Stringify(l) 50} 51 52func (l LoadBalancer) URN() string { 53 return ToURN("LoadBalancer", l.ID) 54} 55 56// AsRequest creates a LoadBalancerRequest that can be submitted to Update with the current values of the LoadBalancer. 57// Modifying the returned LoadBalancerRequest will not modify the original LoadBalancer. 58func (l LoadBalancer) AsRequest() *LoadBalancerRequest { 59 r := LoadBalancerRequest{ 60 Name: l.Name, 61 Algorithm: l.Algorithm, 62 ForwardingRules: append([]ForwardingRule(nil), l.ForwardingRules...), 63 DropletIDs: append([]int(nil), l.DropletIDs...), 64 Tag: l.Tag, 65 RedirectHttpToHttps: l.RedirectHttpToHttps, 66 HealthCheck: l.HealthCheck, 67 } 68 69 if l.HealthCheck != nil { 70 r.HealthCheck = &HealthCheck{} 71 *r.HealthCheck = *l.HealthCheck 72 } 73 if l.StickySessions != nil { 74 r.StickySessions = &StickySessions{} 75 *r.StickySessions = *l.StickySessions 76 } 77 if l.Region != nil { 78 r.Region = l.Region.Slug 79 } 80 return &r 81} 82 83// ForwardingRule represents load balancer forwarding rules. 84type ForwardingRule struct { 85 EntryProtocol string `json:"entry_protocol,omitempty"` 86 EntryPort int `json:"entry_port,omitempty"` 87 TargetProtocol string `json:"target_protocol,omitempty"` 88 TargetPort int `json:"target_port,omitempty"` 89 CertificateID string `json:"certificate_id,omitempty"` 90 TlsPassthrough bool `json:"tls_passthrough,omitempty"` 91} 92 93// String creates a human-readable description of a ForwardingRule. 94func (f ForwardingRule) String() string { 95 return Stringify(f) 96} 97 98// HealthCheck represents optional load balancer health check rules. 99type HealthCheck struct { 100 Protocol string `json:"protocol,omitempty"` 101 Port int `json:"port,omitempty"` 102 Path string `json:"path,omitempty"` 103 CheckIntervalSeconds int `json:"check_interval_seconds,omitempty"` 104 ResponseTimeoutSeconds int `json:"response_timeout_seconds,omitempty"` 105 HealthyThreshold int `json:"healthy_threshold,omitempty"` 106 UnhealthyThreshold int `json:"unhealthy_threshold,omitempty"` 107} 108 109// String creates a human-readable description of a HealthCheck. 110func (h HealthCheck) String() string { 111 return Stringify(h) 112} 113 114// StickySessions represents optional load balancer session affinity rules. 115type StickySessions struct { 116 Type string `json:"type,omitempty"` 117 CookieName string `json:"cookie_name,omitempty"` 118 CookieTtlSeconds int `json:"cookie_ttl_seconds,omitempty"` 119} 120 121// String creates a human-readable description of a StickySessions instance. 122func (s StickySessions) String() string { 123 return Stringify(s) 124} 125 126// LoadBalancerRequest represents the configuration to be applied to an existing or a new load balancer. 127type LoadBalancerRequest struct { 128 Name string `json:"name,omitempty"` 129 Algorithm string `json:"algorithm,omitempty"` 130 Region string `json:"region,omitempty"` 131 ForwardingRules []ForwardingRule `json:"forwarding_rules,omitempty"` 132 HealthCheck *HealthCheck `json:"health_check,omitempty"` 133 StickySessions *StickySessions `json:"sticky_sessions,omitempty"` 134 DropletIDs []int `json:"droplet_ids,omitempty"` 135 Tag string `json:"tag,omitempty"` 136 Tags []string `json:"tags,omitempty"` 137 RedirectHttpToHttps bool `json:"redirect_http_to_https,omitempty"` 138} 139 140// String creates a human-readable description of a LoadBalancerRequest. 141func (l LoadBalancerRequest) String() string { 142 return Stringify(l) 143} 144 145type forwardingRulesRequest struct { 146 Rules []ForwardingRule `json:"forwarding_rules,omitempty"` 147} 148 149func (l forwardingRulesRequest) String() string { 150 return Stringify(l) 151} 152 153type dropletIDsRequest struct { 154 IDs []int `json:"droplet_ids,omitempty"` 155} 156 157func (l dropletIDsRequest) String() string { 158 return Stringify(l) 159} 160 161type loadBalancersRoot struct { 162 LoadBalancers []LoadBalancer `json:"load_balancers"` 163 Links *Links `json:"links"` 164} 165 166type loadBalancerRoot struct { 167 LoadBalancer *LoadBalancer `json:"load_balancer"` 168} 169 170// LoadBalancersServiceOp handles communication with load balancer-related methods of the DigitalOcean API. 171type LoadBalancersServiceOp struct { 172 client *Client 173} 174 175var _ LoadBalancersService = &LoadBalancersServiceOp{} 176 177// Get an existing load balancer by its identifier. 178func (l *LoadBalancersServiceOp) Get(ctx context.Context, lbID string) (*LoadBalancer, *Response, error) { 179 path := fmt.Sprintf("%s/%s", loadBalancersBasePath, lbID) 180 181 req, err := l.client.NewRequest(ctx, http.MethodGet, path, nil) 182 if err != nil { 183 return nil, nil, err 184 } 185 186 root := new(loadBalancerRoot) 187 resp, err := l.client.Do(ctx, req, root) 188 if err != nil { 189 return nil, resp, err 190 } 191 192 return root.LoadBalancer, resp, err 193} 194 195// List load balancers, with optional pagination. 196func (l *LoadBalancersServiceOp) List(ctx context.Context, opt *ListOptions) ([]LoadBalancer, *Response, error) { 197 path, err := addOptions(loadBalancersBasePath, opt) 198 if err != nil { 199 return nil, nil, err 200 } 201 202 req, err := l.client.NewRequest(ctx, http.MethodGet, path, nil) 203 if err != nil { 204 return nil, nil, err 205 } 206 207 root := new(loadBalancersRoot) 208 resp, err := l.client.Do(ctx, req, root) 209 if err != nil { 210 return nil, resp, err 211 } 212 if l := root.Links; l != nil { 213 resp.Links = l 214 } 215 216 return root.LoadBalancers, resp, err 217} 218 219// Create a new load balancer with a given configuration. 220func (l *LoadBalancersServiceOp) Create(ctx context.Context, lbr *LoadBalancerRequest) (*LoadBalancer, *Response, error) { 221 req, err := l.client.NewRequest(ctx, http.MethodPost, loadBalancersBasePath, lbr) 222 if err != nil { 223 return nil, nil, err 224 } 225 226 root := new(loadBalancerRoot) 227 resp, err := l.client.Do(ctx, req, root) 228 if err != nil { 229 return nil, resp, err 230 } 231 232 return root.LoadBalancer, resp, err 233} 234 235// Update an existing load balancer with new configuration. 236func (l *LoadBalancersServiceOp) Update(ctx context.Context, lbID string, lbr *LoadBalancerRequest) (*LoadBalancer, *Response, error) { 237 path := fmt.Sprintf("%s/%s", loadBalancersBasePath, lbID) 238 239 req, err := l.client.NewRequest(ctx, "PUT", path, lbr) 240 if err != nil { 241 return nil, nil, err 242 } 243 244 root := new(loadBalancerRoot) 245 resp, err := l.client.Do(ctx, req, root) 246 if err != nil { 247 return nil, resp, err 248 } 249 250 return root.LoadBalancer, resp, err 251} 252 253// Delete a load balancer by its identifier. 254func (l *LoadBalancersServiceOp) Delete(ctx context.Context, ldID string) (*Response, error) { 255 path := fmt.Sprintf("%s/%s", loadBalancersBasePath, ldID) 256 257 req, err := l.client.NewRequest(ctx, http.MethodDelete, path, nil) 258 if err != nil { 259 return nil, err 260 } 261 262 return l.client.Do(ctx, req, nil) 263} 264 265// AddDroplets adds droplets to a load balancer. 266func (l *LoadBalancersServiceOp) AddDroplets(ctx context.Context, lbID string, dropletIDs ...int) (*Response, error) { 267 path := fmt.Sprintf("%s/%s/%s", loadBalancersBasePath, lbID, dropletsPath) 268 269 req, err := l.client.NewRequest(ctx, http.MethodPost, path, &dropletIDsRequest{IDs: dropletIDs}) 270 if err != nil { 271 return nil, err 272 } 273 274 return l.client.Do(ctx, req, nil) 275} 276 277// RemoveDroplets removes droplets from a load balancer. 278func (l *LoadBalancersServiceOp) RemoveDroplets(ctx context.Context, lbID string, dropletIDs ...int) (*Response, error) { 279 path := fmt.Sprintf("%s/%s/%s", loadBalancersBasePath, lbID, dropletsPath) 280 281 req, err := l.client.NewRequest(ctx, http.MethodDelete, path, &dropletIDsRequest{IDs: dropletIDs}) 282 if err != nil { 283 return nil, err 284 } 285 286 return l.client.Do(ctx, req, nil) 287} 288 289// AddForwardingRules adds forwarding rules to a load balancer. 290func (l *LoadBalancersServiceOp) AddForwardingRules(ctx context.Context, lbID string, rules ...ForwardingRule) (*Response, error) { 291 path := fmt.Sprintf("%s/%s/%s", loadBalancersBasePath, lbID, forwardingRulesPath) 292 293 req, err := l.client.NewRequest(ctx, http.MethodPost, path, &forwardingRulesRequest{Rules: rules}) 294 if err != nil { 295 return nil, err 296 } 297 298 return l.client.Do(ctx, req, nil) 299} 300 301// RemoveForwardingRules removes forwarding rules from a load balancer. 302func (l *LoadBalancersServiceOp) RemoveForwardingRules(ctx context.Context, lbID string, rules ...ForwardingRule) (*Response, error) { 303 path := fmt.Sprintf("%s/%s/%s", loadBalancersBasePath, lbID, forwardingRulesPath) 304 305 req, err := l.client.NewRequest(ctx, http.MethodDelete, path, &forwardingRulesRequest{Rules: rules}) 306 if err != nil { 307 return nil, err 308 } 309 310 return l.client.Do(ctx, req, nil) 311} 312