Skip to content

Commit 579fa97

Browse files
committed
Merge branch 'loadbalancer-healthchecks' into loadbalancer
2 parents 6dc525c + 38cf9ba commit 579fa97

File tree

9 files changed

+485
-37
lines changed

9 files changed

+485
-37
lines changed

cmd/networkLoadBalancer.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ var networkLoadBalancerCmd = &cobra.Command{
2626
Short: "Load Balancer specific operations",
2727
Long: `Load Balancer specific operations.
2828
29-
A Load Balancer helps you distribute load across multiple servers.
29+
A Load Balancer allows you to distribute traffic to multiple endpoints.
3030
3131
For a full list of capabilities, please refer to the "Available Commands" section.`,
3232
Run: func(cmd *cobra.Command, args []string) {

cmd/networkLoadBalancerCreate.go

Lines changed: 66 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,29 @@ import (
2222

2323
"github.com/spf13/cast"
2424
"github.com/spf13/cobra"
25+
"gopkg.in/yaml.v2"
2526

27+
"github.com/liquidweb/liquidweb-cli/instance"
2628
"github.com/liquidweb/liquidweb-cli/types/api"
29+
"github.com/liquidweb/liquidweb-cli/types/cmd"
2730
"github.com/liquidweb/liquidweb-cli/validate"
2831
)
2932

3033
var networkLoadBalancerCreateNodesCmd []string
3134
var networkLoadBalancerCreateServicesCmd []string
35+
var healthChecksMapCreate map[string]string
3236

3337
var networkLoadBalancerCreateCmd = &cobra.Command{
3438
Use: "create",
3539
Short: "Create a Load Balancer",
36-
Long: `Create a Load Balancer`,
40+
Long: fmt.Sprintf(`Create a Load Balancer
41+
42+
A Load Balancer allows you to distribute traffic to multiple endpoints.
43+
44+
%s
45+
46+
%s
47+
`, networkLoadBalancerServicesHealthChecksHelp, networkLoadBalancerServicesHealthCheckFileHelp),
3748
Run: func(cmd *cobra.Command, args []string) {
3849
nameFlag, _ := cmd.Flags().GetString("name")
3950
strategyFlag, _ := cmd.Flags().GetString("strategy")
@@ -45,6 +56,7 @@ var networkLoadBalancerCreateCmd = &cobra.Command{
4556
enableSslIncludesFlag, _ := cmd.Flags().GetBool("enable-ssl-includes")
4657
disableSslIncludesFlag, _ := cmd.Flags().GetBool("disable-ssl-includes")
4758
regionFlag, _ := cmd.Flags().GetInt("region")
59+
healthCheckFileFlag, _ := cmd.Flags().GetString("health-check-file")
4860

4961
if enableSslTerminationFlag && disableSslTerminationFlag {
5062
lwCliInst.Die(fmt.Errorf("can't both enable and disable ssl termination"))
@@ -62,6 +74,9 @@ var networkLoadBalancerCreateCmd = &cobra.Command{
6274
lwCliInst.Die(fmt.Errorf("when using --ssl-certificate or --ssl-private-key --enable-ssl-termination must be passed"))
6375
}
6476
}
77+
if len(healthChecksMapCreate) > 0 && healthCheckFileFlag != "" {
78+
lwCliInst.Die(fmt.Errorf("cannot pass conflicting flags --health-check and --health-check-file"))
79+
}
6580

6681
validateFields := map[interface{}]interface{}{
6782
strategyFlag: "LoadBalancerStrategy",
@@ -141,7 +156,42 @@ var networkLoadBalancerCreateCmd = &cobra.Command{
141156
lwCliInst.Die(fmt.Errorf("--services must have source/destination port pairs (see 'help network load-balancer create')"))
142157
}
143158
// slice of maps with keys src_port, dest_port, with a value of its network port number.
144-
var servicesToBalance []map[string]int
159+
var servicesToBalance []map[string]interface{}
160+
// a service is permitted to have one health check
161+
162+
var healthChecks map[string]map[string]interface{}
163+
164+
// health check, command line flags.
165+
if len(healthChecksMapCreate) > 0 {
166+
healthChecksFromCmdLine, err := cmdTypes.LoadBalancerHealthCheckCmdLine{HealthCheck: healthChecksMapCreate}.Transform()
167+
if err != nil {
168+
lwCliInst.Die(err)
169+
}
170+
healthChecks = healthChecksFromCmdLine
171+
} else if healthCheckFileFlag != "" {
172+
// health check, yaml file
173+
contents, err := ioutil.ReadFile(healthCheckFileFlag)
174+
if err != nil {
175+
lwCliInst.Die(fmt.Errorf("error reading given --health-check-file [%s]: %s", healthCheckFileFlag, err))
176+
}
177+
if err = yaml.Unmarshal(contents, &healthChecks); err != nil {
178+
lwCliInst.Die(fmt.Errorf("error yaml decoding [%s] (see help for an example of the file); %s", healthCheckFileFlag, err))
179+
}
180+
}
181+
182+
// validate
183+
for _, healthCheck := range healthChecks {
184+
var obj apiTypes.NetworkLoadBalancerDetailsServiceHealthCheck
185+
186+
if err := instance.CastFieldTypes(healthCheck, &obj); err != nil {
187+
lwCliInst.Die(fmt.Errorf(
188+
"failed casting --health-check-file [%s] to expected structure (see help for an example of the file): %s",
189+
healthCheckFileFlag, err))
190+
}
191+
if err := obj.Validate(); err != nil {
192+
lwCliInst.Die(err)
193+
}
194+
}
145195

146196
for _, pair := range networkLoadBalancerCreateServicesCmd {
147197
err := validate.Validate(map[interface{}]interface{}{pair: "NetworkPortPair"})
@@ -153,10 +203,17 @@ var networkLoadBalancerCreateCmd = &cobra.Command{
153203
srcPort := cast.ToInt(splitPair[0])
154204
destPort := cast.ToInt(splitPair[1])
155205

156-
servicesToBalance = append(servicesToBalance, map[string]int{
206+
serviceToBalance := map[string]interface{}{
157207
"src_port": srcPort,
158208
"dest_port": destPort,
159-
})
209+
}
210+
211+
// if a health check exists for this service set it
212+
if _, exists := healthChecks[splitPair[0]]; exists {
213+
serviceToBalance["health_check"] = healthChecks[splitPair[0]]
214+
}
215+
216+
servicesToBalance = append(servicesToBalance, serviceToBalance)
160217
}
161218

162219
apiArgs["services"] = servicesToBalance
@@ -196,6 +253,11 @@ func init() {
196253
networkLoadBalancerCreateCmd.Flags().StringSliceVar(&networkLoadBalancerCreateServicesCmd, "services",
197254
[]string{}, "source/destination port pairs (such as 80:80) separated by ',' to balance via the Load Balancer")
198255

256+
networkLoadBalancerCreateCmd.Flags().StringToStringVar(&healthChecksMapCreate, "health-check", nil,
257+
"Health check defintions for the service matching src_port. Should not be combined with --health-check.")
258+
networkLoadBalancerCreateCmd.Flags().String("health-check-file", "",
259+
"A file containing valid yaml describing the LoadBalancer health checks to add for the service(s). Should not be combined with --health-check.")
260+
199261
networkLoadBalancerCreateCmd.MarkFlagRequired("name")
200262
networkLoadBalancerCreateCmd.MarkFlagRequired("services")
201263
networkLoadBalancerCreateCmd.MarkFlagRequired("strategy")

cmd/networkLoadBalancerUpdate.go

Lines changed: 120 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,82 @@ import (
2222

2323
"github.com/spf13/cast"
2424
"github.com/spf13/cobra"
25+
"gopkg.in/yaml.v2"
2526

27+
"github.com/liquidweb/liquidweb-cli/instance"
2628
"github.com/liquidweb/liquidweb-cli/types/api"
29+
"github.com/liquidweb/liquidweb-cli/types/cmd"
2730
"github.com/liquidweb/liquidweb-cli/validate"
2831
)
2932

3033
var networkLoadBalancerUpdateNodesCmd []string
3134
var networkLoadBalancerUpdateServicesCmd []string
35+
var healthChecksMapUpdate map[string]string
36+
var networkLoadBalancerServicesHealthChecksHelp = `--services flag values are ',' delimited. Each value should be in format:
37+
38+
'sourcePort:destinationPort',
39+
40+
such as '80:80,443:443'.
41+
42+
--health-check flag values represent custom health check paramaters for a service on a Load Balancer. Valid health check parameters:
43+
44+
failure_threshold -> int // permissible failures before node is taken out of services pool (default 3)
45+
http_body_match -> string // when protocol is http, the string to look for in the http body to determine if health is ok (default unset)
46+
http_path -> string // when protocol is http, the http path to hit when performing a health check (default /)
47+
http_response_codes -> string // when protocol is http, http response codes to consider "success" when performing a health check (default 200-206:300-304)
48+
http_use_tls -> "bool" // when protocol is http, uses https when "true" for health check (default false)
49+
interval -> int // time duration between health checks (default 30)
50+
protocol -> string // *Required (valid values: tcp, http)
51+
timeout -> int // timeout value for the health check probe (default 5)
52+
53+
For example, to set these values for the service with source port 443, the flag could look like this:
54+
55+
--health-check 443_failure_threshold=12,443_http_body_match=hello,443_http_path=/status,443_http_response_codes=200:201:202,443_http_use_tls=true,443_interval=10,443_protocol=http,443_timeout=99
56+
57+
Notice the leading '443_' before the parameter name. To create a health check for service 80 as well, follow the same pattern, but
58+
replacing '443_' with '80_'.`
59+
var networkLoadBalancerServicesHealthCheckFileHelp = `--health-check-file value should be the path to a yaml file containing the health check(s) to apply for each service(s). Here is
60+
an example of how that file should look:
61+
62+
443:
63+
protocol: http
64+
timeout: 5
65+
interval: 10
66+
http_use_tls: true
67+
http_response_codes: 200-202,404
68+
http_path: /status-443
69+
http_body_match:
70+
failure_threshold: 3
71+
80:
72+
protocol: http
73+
timeout: 10
74+
interval: 20
75+
http_use_tls: false
76+
http_response_codes: 200-202,404
77+
http_path: /status-80
78+
http_body_match:
79+
failure_threshold: 3
80+
81+
It is an error to provide both --health-check and --health-check-file flags.`
3282

3383
var networkLoadBalancerUpdateCmd = &cobra.Command{
3484
Use: "update",
3585
Short: "Change configuration of an existing Load Balancer",
36-
Long: `Change configuration of an existing Load Balancer`,
86+
Long: fmt.Sprintf(`Change configuration of an existing Load Balancer
87+
88+
A Load Balancer allows you to distribute traffic to multiple endpoints.
89+
90+
%s
91+
92+
%s
93+
94+
To remove a health check from a service, simply call update for the service(s) omitting their --health-check entries. For example,
95+
this would remove any set health checks for services 443:443,80:80 (as well as remove any other services entirely):
96+
97+
network load-balancer update --uniq_id ABC123 --services 443:443,80:80
98+
99+
Similarly to remove a health check when using --health-check-file, simply remove the health check from the file.
100+
`, networkLoadBalancerServicesHealthChecksHelp, networkLoadBalancerServicesHealthCheckFileHelp),
37101
Run: func(cmd *cobra.Command, args []string) {
38102
uniqIdFlag, _ := cmd.Flags().GetString("uniq_id")
39103
nameFlag, _ := cmd.Flags().GetString("name")
@@ -45,6 +109,7 @@ var networkLoadBalancerUpdateCmd = &cobra.Command{
45109
sslIntermediateCertFlag, _ := cmd.Flags().GetString("ssl-intermediate-certificate")
46110
enableSslIncludesFlag, _ := cmd.Flags().GetBool("enable-ssl-includes")
47111
disableSslIncludesFlag, _ := cmd.Flags().GetBool("disable-ssl-includes")
112+
healthCheckFileFlag, _ := cmd.Flags().GetString("health-check-file")
48113

49114
if enableSslTerminationFlag && disableSslTerminationFlag {
50115
lwCliInst.Die(fmt.Errorf("can't both enable and disable ssl termination"))
@@ -62,6 +127,9 @@ var networkLoadBalancerUpdateCmd = &cobra.Command{
62127
lwCliInst.Die(fmt.Errorf("when using --ssl-certificate or --ssl-private-key --enable-ssl-termination must be passed"))
63128
}
64129
}
130+
if len(healthChecksMapUpdate) > 0 && healthCheckFileFlag != "" {
131+
lwCliInst.Die(fmt.Errorf("cannot pass conflicting flags --health-check and --health-check-file"))
132+
}
65133

66134
validateFields := map[interface{}]interface{}{
67135
uniqIdFlag: "UniqId",
@@ -142,9 +210,43 @@ var networkLoadBalancerUpdateCmd = &cobra.Command{
142210

143211
// services
144212
if len(networkLoadBalancerUpdateServicesCmd) > 0 {
145-
// slice of maps with keys src_port, dest_port, with a value of its network port number.
146-
var servicesToBalance []map[string]int
213+
var servicesToBalance []map[string]interface{}
214+
// a service is permitted to have one health check.
215+
216+
var healthChecks map[string]map[string]interface{}
217+
218+
// health check, command line flags.
219+
if len(healthChecksMapUpdate) > 0 {
220+
healthChecksFromCmdLine, err := cmdTypes.LoadBalancerHealthCheckCmdLine{HealthCheck: healthChecksMapUpdate}.Transform()
221+
if err != nil {
222+
lwCliInst.Die(err)
223+
}
224+
healthChecks = healthChecksFromCmdLine
225+
} else if healthCheckFileFlag != "" {
226+
// health check, yaml file
227+
contents, err := ioutil.ReadFile(healthCheckFileFlag)
228+
if err != nil {
229+
lwCliInst.Die(fmt.Errorf("error reading given --health-check-file [%s]: %s", healthCheckFileFlag, err))
230+
}
231+
if err = yaml.Unmarshal(contents, &healthChecks); err != nil {
232+
lwCliInst.Die(fmt.Errorf("error yaml decoding [%s] (see help for an example of the file); %s", healthCheckFileFlag, err))
233+
}
234+
}
235+
236+
// validate
237+
for _, healthCheck := range healthChecks {
238+
var obj apiTypes.NetworkLoadBalancerDetailsServiceHealthCheck
239+
if err := instance.CastFieldTypes(healthCheck, &obj); err != nil {
240+
lwCliInst.Die(fmt.Errorf(
241+
"failed casting --health-check-file [%s] to expected structure (see help for an example of the file): %s",
242+
healthCheckFileFlag, err))
243+
}
244+
if err := obj.Validate(); err != nil {
245+
lwCliInst.Die(err)
246+
}
247+
}
147248

249+
// build services api argument
148250
for _, pair := range networkLoadBalancerUpdateServicesCmd {
149251
err := validate.Validate(map[interface{}]interface{}{pair: "NetworkPortPair"})
150252
if err != nil {
@@ -155,12 +257,18 @@ var networkLoadBalancerUpdateCmd = &cobra.Command{
155257
srcPort := cast.ToInt(splitPair[0])
156258
destPort := cast.ToInt(splitPair[1])
157259

158-
servicesToBalance = append(servicesToBalance, map[string]int{
260+
serviceToBalance := map[string]interface{}{
159261
"src_port": srcPort,
160262
"dest_port": destPort,
161-
})
162-
}
263+
}
264+
265+
// if a health check exists for this service set it
266+
if _, exists := healthChecks[splitPair[0]]; exists {
267+
serviceToBalance["health_check"] = healthChecks[splitPair[0]]
268+
}
163269

270+
servicesToBalance = append(servicesToBalance, serviceToBalance)
271+
}
164272
apiArgs["services"] = servicesToBalance
165273
}
166274

@@ -203,5 +311,11 @@ func init() {
203311
networkLoadBalancerUpdateCmd.Flags().StringSliceVar(&networkLoadBalancerUpdateServicesCmd, "services",
204312
[]string{}, "source/destination port pairs (such as 80:80) separated by ',' to balance via the Load Balancer")
205313

314+
networkLoadBalancerUpdateCmd.Flags().StringToStringVar(&healthChecksMapUpdate, "health-check", nil,
315+
"Health check defintions for the service matching source port. Should not be combined with --health-check.")
316+
317+
networkLoadBalancerUpdateCmd.Flags().String("health-check-file", "",
318+
"A file containing valid yaml describing the LoadBalancer health checks to add for the service(s). Should not be combined with --health-check.")
319+
206320
networkLoadBalancerUpdateCmd.MarkFlagRequired("uniq_id")
207321
}

go.mod

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ require (
1212
github.com/spf13/cobra v0.0.5
1313
github.com/spf13/jwalterweatherman v1.1.0 // indirect
1414
github.com/spf13/pflag v1.0.5 // indirect
15-
github.com/spf13/viper v1.6.1
16-
golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d
17-
golang.org/x/sys v0.0.0-20200116001909-b77594299b42 // indirect
15+
github.com/spf13/viper v1.6.2
16+
golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d
17+
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9 // indirect
1818
golang.org/x/text v0.3.2 // indirect
1919
gopkg.in/ini.v1 v1.51.1 // indirect
20-
gopkg.in/yaml.v2 v2.2.7 // indirect
20+
gopkg.in/yaml.v2 v2.2.8
2121
)

go.sum

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,8 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An
116116
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
117117
github.com/spf13/viper v1.6.1 h1:VPZzIkznI1YhVMRi6vNFLHSwhnhReBfgTxIPccpfdZk=
118118
github.com/spf13/viper v1.6.1/go.mod h1:t3iDnF5Jlj76alVNuyFBk5oUMCvsrkbvZK0WQdfDi5k=
119+
github.com/spf13/viper v1.6.2 h1:7aKfF+e8/k68gda3LOjo5RxiUqddoFxVq4BKBPrxk5E=
120+
github.com/spf13/viper v1.6.2/go.mod h1:t3iDnF5Jlj76alVNuyFBk5oUMCvsrkbvZK0WQdfDi5k=
119121
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
120122
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
121123
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
@@ -135,6 +137,8 @@ golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnf
135137
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
136138
golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d h1:2+ZP7EfsZV7Vvmx3TIqSlSzATMkTAKqM14YGFPoSKjI=
137139
golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
140+
golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d h1:9FCpayM9Egr1baVnV1SX0H87m+XB0B8S0hAMi99X/3U=
141+
golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
138142
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
139143
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
140144
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -157,6 +161,8 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5
157161
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
158162
golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg=
159163
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
164+
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9 h1:1/DFK4b7JH8DmkqhUk48onnSfrPzImPoVxuomtbT2nk=
165+
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
160166
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
161167
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
162168
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
@@ -187,4 +193,6 @@ gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
187193
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
188194
gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
189195
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
196+
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
197+
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
190198
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

0 commit comments

Comments
 (0)