Repository: digitalocean/doctl
Pull request #1325 (2022-12-12 17:58:46)

Add firewall support for Loadbalancers


Patch:
diff --git a/args.go b/args.go
index 5ddabada..dece1778 100644
--- a/args.go
+++ b/args.go
@@ -301,6 +301,10 @@ const (
 	ArgForwardingRules = "forwarding-rules"
 	// ArgHTTPIdleTimeoutSeconds is the http idle time out configuration for the load balancer
 	ArgHTTPIdleTimeoutSeconds = "http-idle-timeout-seconds"
+	// ArgAllowList is the list of firewall rules for ALLOWING traffic to the loadbalancer
+	ArgAllowList = "allow-list"
+	// ArgDenyList is a list of firewall rules for DENYING traffic to the loadbalancer
+	ArgDenyList = "deny-list"
 
 	// ArgFirewallName is a name of the firewall.
 	ArgFirewallName = "name"
diff --git a/commands/load_balancers.go b/commands/load_balancers.go
index dce946d2..a1b279ca 100644
--- a/commands/load_balancers.go
+++ b/commands/load_balancers.go
@@ -86,6 +86,10 @@ With the load-balancer command, you can list, create, or delete load balancers,
 	AddBoolFlag(cmdRecordCreate, doctl.ArgCommandWait, "", false, "Boolean that specifies whether to wait for a load balancer to complete before returning control to the terminal")
 	AddStringFlag(cmdRecordCreate, doctl.ArgProjectID, "", "", "Indicates which project to associate the Load Balancer with. If not specified, the Load Balancer will be placed in your default project.")
 	AddIntFlag(cmdRecordCreate, doctl.ArgHTTPIdleTimeoutSeconds, "", 0, "HTTP idle timeout that configures the idle timeout for http connections on the load balancer")
+	AddStringSliceFlag(cmdRecordCreate, doctl.ArgAllowList, "", []string{},
+		"A comma-separated list of ALLOW rules for the load balancer, e.g.: `ip:1.2.3.4,cidr:1.2.0.0/16`")
+	AddStringSliceFlag(cmdRecordCreate, doctl.ArgDenyList, "", []string{},
+		"A comma-separated list of DENY rules for the load balancer, e.g.: `ip:1.2.3.4,cidr:1.2.0.0/16`")
 
 	cmdRecordUpdate := CmdBuilder(cmd, RunLoadBalancerUpdate, "update <id>",
 		"Update a load balancer's configuration", `Use this command to update the configuration of a specified load balancer.`, Writer, aliasOpt("u"))
@@ -118,6 +122,10 @@ With the load-balancer command, you can list, create, or delete load balancers,
 		"disable automatic DNS record creation for Let's Encrypt certificates that are added to the load balancer")
 	AddStringFlag(cmdRecordUpdate, doctl.ArgProjectID, "", "",
 		"Indicates which project to associate the Load Balancer with. If not specified, the Load Balancer will be placed in your default project.")
+	AddStringSliceFlag(cmdRecordUpdate, doctl.ArgAllowList, "", []string{},
+		"A comma-separated list of ALLOW rules for the load balancer, e.g.: `ip:1.2.3.4,cidr:1.2.0.0/16`")
+	AddStringSliceFlag(cmdRecordUpdate, doctl.ArgDenyList, "", []string{},
+		"A comma-separated list of DENY rules for the load balancer, e.g.: `ip:1.2.3.4,cidr:1.2.0.0/16`")
 
 	CmdBuilder(cmd, RunLoadBalancerList, "list", "List load balancers", "Use this command to get a list of the load balancers on your account, including the following information for each:\n\n"+lbDetail, Writer,
 		aliasOpt("ls"), displayerType(&displayers.LoadBalancer{}))
@@ -540,6 +548,23 @@ func buildRequestFromArgs(c *CmdConfig, r *godo.LoadBalancerRequest) error {
 		r.HTTPIdleTimeoutSeconds = &t
 	}
 
+	allowRules, err := c.Doit.GetStringSlice(c.NS, doctl.ArgAllowList)
+	if err != nil {
+		return err
+	}
+
+	denyRules, err := c.Doit.GetStringSlice(c.NS, doctl.ArgDenyList)
+	if err != nil {
+		return err
+	}
+
+	if len(allowRules) > 0 || len(denyRules) > 0 {
+		firewall := new(godo.LBFirewall)
+		firewall.Allow = allowRules
+		firewall.Deny = denyRules
+		r.Firewall = firewall
+	}
+
 	return nil
 }
 
diff --git a/commands/monitoring.go b/commands/monitoring.go
index 4815ec9e..5564eb9e 100644
--- a/commands/monitoring.go
+++ b/commands/monitoring.go
@@ -325,29 +325,31 @@ func getComparator(compareStr string) (godo.AlertPolicyComp, error) {
 
 func validateAlertPolicyType(t string) error {
 	validAlertPolicyTypes := map[string]struct{}{
-		godo.DropletCPUUtilizationPercent:                  {},
-		godo.DropletMemoryUtilizationPercent:               {},
-		godo.DropletDiskUtilizationPercent:                 {},
-		godo.DropletDiskReadRate:                           {},
-		godo.DropletDiskWriteRate:                          {},
-		godo.DropletOneMinuteLoadAverage:                   {},
-		godo.DropletFiveMinuteLoadAverage:                  {},
-		godo.DropletFifteenMinuteLoadAverage:               {},
-		godo.DropletPublicOutboundBandwidthRate:            {},
-		godo.DbaasFifteenMinuteLoadAverage:                 {},
-		godo.DbaasMemoryUtilizationPercent:                 {},
-		godo.DbaasDiskUtilizationPercent:                   {},
-		godo.DbaasCPUUtilizationPercent:                    {},
-		godo.LoadBalancerCPUUtilizationPercent:             {},
-		godo.LoadBalancerDropletHealth:                     {},
-		godo.LoadBalancerTLSUtilizationPercent:             {},
-		godo.LoadBalancerConnectionUtilizationPercent:      {},
-		godo.LoadBalancerIncreaseInHTTPErrorRatePercentage: {},
-		godo.LoadBalancerIncreaseInHTTPErrorRateCount:      {},
-		godo.LoadBalancerHighHttpResponseTime:              {},
-		godo.LoadBalancerHighHttpResponseTime50P:           {},
-		godo.LoadBalancerHighHttpResponseTime95P:           {},
-		godo.LoadBalancerHighHttpResponseTime99P:           {},
+		godo.DropletCPUUtilizationPercent:                     {},
+		godo.DropletMemoryUtilizationPercent:                  {},
+		godo.DropletDiskUtilizationPercent:                    {},
+		godo.DropletDiskReadRate:                              {},
+		godo.DropletDiskWriteRate:                             {},
+		godo.DropletOneMinuteLoadAverage:                      {},
+		godo.DropletFiveMinuteLoadAverage:                     {},
+		godo.DropletFifteenMinuteLoadAverage:                  {},
+		godo.DropletPublicOutboundBandwidthRate:               {},
+		godo.DbaasFifteenMinuteLoadAverage:                    {},
+		godo.DbaasMemoryUtilizationPercent:                    {},
+		godo.DbaasDiskUtilizationPercent:                      {},
+		godo.DbaasCPUUtilizationPercent:                       {},
+		godo.LoadBalancerCPUUtilizationPercent:                {},
+		godo.LoadBalancerDropletHealth:                        {},
+		godo.LoadBalancerTLSUtilizationPercent:                {},
+		godo.LoadBalancerConnectionUtilizationPercent:         {},
+		godo.LoadBalancerIncreaseInHTTPErrorRateCount4xx:      {},
+		godo.LoadBalancerIncreaseInHTTPErrorRateCount5xx:      {},
+		godo.LoadBalancerIncreaseInHTTPErrorRatePercentage4xx: {},
+		godo.LoadBalancerIncreaseInHTTPErrorRatePercentage5xx: {},
+		godo.LoadBalancerHighHttpResponseTime:                 {},
+		godo.LoadBalancerHighHttpResponseTime50P:              {},
+		godo.LoadBalancerHighHttpResponseTime95P:              {},
+		godo.LoadBalancerHighHttpResponseTime99P:              {},
 	}
 
 	_, ok := validAlertPolicyTypes[t]
diff --git a/go.mod b/go.mod
index d1fafd51..39669bc9 100644
--- a/go.mod
+++ b/go.mod
@@ -7,7 +7,7 @@ require (
 	github.com/blang/semver v3.5.1+incompatible
 	github.com/containerd/continuity v0.3.0 // indirect
 	github.com/creack/pty v1.1.11
-	github.com/digitalocean/godo v1.91.1
+	github.com/digitalocean/godo v1.92.0
 	github.com/docker/cli v20.10.17+incompatible
 	github.com/docker/docker v20.10.17+incompatible
 	github.com/docker/docker-credential-helpers v0.6.4 // indirect
@@ -33,9 +33,9 @@ require (
 	github.com/spf13/viper v1.11.0
 	github.com/stretchr/testify v1.7.2
 	golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4
-	golang.org/x/net v0.2.0 // indirect
-	golang.org/x/oauth2 v0.2.0
-	golang.org/x/sys v0.2.0 // indirect
+	golang.org/x/net v0.4.0 // indirect
+	golang.org/x/oauth2 v0.3.0
+	golang.org/x/sys v0.3.0 // indirect
 	google.golang.org/protobuf v1.28.1 // indirect
 	gopkg.in/yaml.v2 v2.4.0
 	k8s.io/api v0.22.5
@@ -116,9 +116,9 @@ require (
 	github.com/subosito/gotenv v1.2.0 // indirect
 	go.opencensus.io v0.23.0 // indirect
 	golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
-	golang.org/x/term v0.2.0 // indirect
-	golang.org/x/text v0.4.0 // indirect
-	golang.org/x/time v0.2.0 // indirect
+	golang.org/x/term v0.3.0 // indirect
+	golang.org/x/text v0.5.0 // indirect
+	golang.org/x/time v0.3.0 // indirect
 	golang.org/x/tools v0.1.12 // indirect
 	google.golang.org/appengine v1.6.7 // indirect
 	gopkg.in/inf.v0 v0.9.1 // indirect
diff --git a/go.sum b/go.sum
index 8be25d53..194007f6 100644
--- a/go.sum
+++ b/go.sum
@@ -280,8 +280,8 @@ github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8l
 github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
 github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
 github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
-github.com/digitalocean/godo v1.91.1 h1:1o30VOCu1aC6488qBd0SkQiBeAZ35RSTvLwCA1pQMhc=
-github.com/digitalocean/godo v1.91.1/go.mod h1:NRpFznZFvhHjBoqZAaOD3khVzsJ3EibzKqFL4R60dmA=
+github.com/digitalocean/godo v1.92.0 h1:eK9DgdLcjozZbywjh/9k2NF++ttbcidASOoCGRu48yQ=
+github.com/digitalocean/godo v1.92.0/go.mod h1:NRpFznZFvhHjBoqZAaOD3khVzsJ3EibzKqFL4R60dmA=
 github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
 github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
 github.com/docker/cli v20.10.17+incompatible h1:eO2KS7ZFeov5UJeaDmIs1NFEDRf32PaqRpvoEkKBy5M=
@@ -978,8 +978,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
 golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
 golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU=
-golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
+golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU=
+golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -989,8 +989,8 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ
 golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.2.0 h1:GtQkldQ9m7yvzCL1V+LrYow3Khe0eJH0w7RbX/VbaIU=
-golang.org/x/oauth2 v0.2.0/go.mod h1:Cwn6afJ8jrQwYMxQDTpISoXmXW9I6qF6vDeuuoX3Ibs=
+golang.org/x/oauth2 v0.3.0 h1:6l90koy8/LaBLmLu8jpHeHexzMwEita0zFfYlggy2F8=
+golang.org/x/oauth2 v0.3.0/go.mod h1:rQrIauxkUhJ6CuwEXwymO2/eh4xz2ZWF1nBkcxS+tGk=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -1093,14 +1093,14 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
-golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
+golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
-golang.org/x/term v0.2.0 h1:z85xZCsEl7bi/KwbNADeBYoOP0++7W1ipu+aGnpwzRM=
-golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
+golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI=
+golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -1108,8 +1108,8 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
-golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
+golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -1117,8 +1117,8 @@ golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxb
 golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.2.0 h1:52I/1L54xyEQAYdtcSuxtiT84KGYTBGXwayxmIpNJhE=
-golang.org/x/time v0.2.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
+golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
diff --git a/vendor/github.com/digitalocean/godo/CHANGELOG.md b/vendor/github.com/digitalocean/godo/CHANGELOG.md
index 803ba787..04da396c 100644
--- a/vendor/github.com/digitalocean/godo/CHANGELOG.md
+++ b/vendor/github.com/digitalocean/godo/CHANGELOG.md
@@ -1,5 +1,13 @@
 # Change Log
 
+## [v1.92.0] - 2022-12-08
+
+- #589 - @wez470 - load-balancers: Minor doc fixup
+- #585 - @StephenVarela - Add firewall support for load balancers
+- #587 - @StephenVarela - Support new http alerts for load balancers
+- #586 - @andrewsomething - godo.go: Sort service lists.
+- #583 - @ddebarros - Adds support for functions trigger API
+
 ## [v1.91.1] - 2022-11-23
 
 - #582 - @StephenVarela - Load Balancers: Support new endpoints for http alerts
diff --git a/vendor/github.com/digitalocean/godo/functions.go b/vendor/github.com/digitalocean/godo/functions.go
index b3829367..61c80778 100644
--- a/vendor/github.com/digitalocean/godo/functions.go
+++ b/vendor/github.com/digitalocean/godo/functions.go
@@ -8,8 +8,9 @@ import (
 )
 
 const (
-	functionsBasePath      = "/v2/functions/namespaces"
-	functionsNamespacePath = functionsBasePath + "/%s"
+	functionsBasePath        = "/v2/functions/namespaces"
+	functionsNamespacePath   = functionsBasePath + "/%s"
+	functionsTriggerBasePath = functionsNamespacePath + "/triggers"
 )
 
 type FunctionsService interface {
@@ -17,6 +18,12 @@ type FunctionsService interface {
 	GetNamespace(context.Context, string) (*FunctionsNamespace, *Response, error)
 	CreateNamespace(context.Context, *FunctionsNamespaceCreateRequest) (*FunctionsNamespace, *Response, error)
 	DeleteNamespace(context.Context, string) (*Response, error)
+
+	ListTriggers(context.Context, string) ([]FunctionsTrigger, *Response, error)
+	GetTrigger(context.Context, string, string) (*FunctionsTrigger, *Response, error)
+	CreateTrigger(context.Context, string, *FunctionsTriggerCreateRequest) (*FunctionsTrigger, *Response, error)
+	UpdateTrigger(context.Context, string, string, *FunctionsTriggerUpdateRequest) (*FunctionsTrigger, *Response, error)
+	DeleteTrigger(context.Context, string, string) (*Response, error)
 }
 
 type FunctionsServiceOp struct {
@@ -49,6 +56,49 @@ type FunctionsNamespaceCreateRequest struct {
 	Region string `json:"region"`
 }
 
+type triggersRoot struct {
+	Triggers []FunctionsTrigger `json:"triggers,omitempty"`
+}
+
+type triggerRoot struct {
+	Trigger *FunctionsTrigger `json:"trigger,omitempty"`
+}
+
+type FunctionsTrigger struct {
+	Namespace        string                   `json:"namespace,omitempty"`
+	Function         string                   `json:"function,omitempty"`
+	Type             string                   `json:"type,omitempty"`
+	Name             string                   `json:"name,omitempty"`
+	IsEnabled        bool                     `json:"is_enabled"`
+	CreatedAt        time.Time                `json:"created_at,omitempty"`
+	UpdatedAt        time.Time                `json:"updated_at,omitempty"`
+	ScheduledDetails *TriggerScheduledDetails `json:"scheduled_details,omitempty"`
+	ScheduledRuns    *TriggerScheduledRuns    `json:"scheduled_runs,omitempty"`
+}
+
+type TriggerScheduledDetails struct {
+	Cron string                 `json:"cron,omitempty"`
+	Body map[string]interface{} `json:"body,omitempty"`
+}
+
+type TriggerScheduledRuns struct {
+	LastRunAt time.Time `json:"last_run_at,omitempty"`
+	NextRunAt time.Time `json:"next_run_at,omitempty"`
+}
+
+type FunctionsTriggerCreateRequest struct {
+	Name             string                   `json:"name"`
+	Type             string                   `json:"type"`
+	Function         string                   `json:"function"`
+	IsEnabled        bool                     `json:"is_enabled"`
+	ScheduledDetails *TriggerScheduledDetails `json:"scheduled_details,omitempty"`
+}
+
+type FunctionsTriggerUpdateRequest struct {
+	IsEnabled        *bool                    `json:"is_enabled,omitempty"`
+	ScheduledDetails *TriggerScheduledDetails `json:"scheduled_details,omitempty"`
+}
+
 // Gets a list of namespaces
 func (s *FunctionsServiceOp) ListNamespaces(ctx context.Context) ([]FunctionsNamespace, *Response, error) {
 	req, err := s.client.NewRequest(ctx, http.MethodGet, functionsBasePath, nil)
@@ -108,3 +158,79 @@ func (s *FunctionsServiceOp) DeleteNamespace(ctx context.Context, namespace stri
 	}
 	return resp, nil
 }
+
+// ListTriggers gets a list of triggers
+func (s *FunctionsServiceOp) ListTriggers(ctx context.Context, namespace string) ([]FunctionsTrigger, *Response, error) {
+	path := fmt.Sprintf(functionsTriggerBasePath, namespace)
+	req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
+	if err != nil {
+		return nil, nil, err
+	}
+	root := new(triggersRoot)
+	resp, err := s.client.Do(ctx, req, root)
+	if err != nil {
+		return nil, resp, err
+	}
+	return root.Triggers, resp, nil
+}
+
+// GetTrigger gets a single trigger
+func (s *FunctionsServiceOp) GetTrigger(ctx context.Context, namespace string, trigger string) (*FunctionsTrigger, *Response, error) {
+	path := fmt.Sprintf(functionsTriggerBasePath+"/%s", namespace, trigger)
+	req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
+	if err != nil {
+		return nil, nil, err
+	}
+	root := new(triggerRoot)
+	resp, err := s.client.Do(ctx, req, root)
+	if err != nil {
+		return nil, resp, err
+	}
+	return root.Trigger, resp, nil
+}
+
+// CreateTrigger creates a trigger
+func (s *FunctionsServiceOp) CreateTrigger(ctx context.Context, namespace string, opts *FunctionsTriggerCreateRequest) (*FunctionsTrigger, *Response, error) {
+	path := fmt.Sprintf(functionsTriggerBasePath, namespace)
+	req, err := s.client.NewRequest(ctx, http.MethodPost, path, opts)
+	if err != nil {
+		return nil, nil, err
+	}
+	root := new(triggerRoot)
+	resp, err := s.client.Do(ctx, req, root)
+	if err != nil {
+		return nil, resp, err
+	}
+	return root.Trigger, resp, nil
+}
+
+// UpdateTrigger updates a trigger
+func (s *FunctionsServiceOp) UpdateTrigger(ctx context.Context, namespace string, trigger string, opts *FunctionsTriggerUpdateRequest) (*FunctionsTrigger, *Response, error) {
+	path := fmt.Sprintf(functionsTriggerBasePath+"/%s", namespace, trigger)
+	req, err := s.client.NewRequest(ctx, http.MethodPut, path, opts)
+
+	if err != nil {
+		return nil, nil, err
+	}
+	root := new(triggerRoot)
+	resp, err := s.client.Do(ctx, req, root)
+	if err != nil {
+		return nil, resp, err
+	}
+	return root.Trigger, resp, nil
+}
+
+// DeleteTrigger deletes a trigger
+func (s *FunctionsServiceOp) DeleteTrigger(ctx context.Context, namespace string, trigger string) (*Response, error) {
+	path := fmt.Sprintf(functionsTriggerBasePath+"/%s", namespace, trigger)
+	req, err := s.client.NewRequest(ctx, http.MethodDelete, path, nil)
+
+	if err != nil {
+		return nil, err
+	}
+	resp, err := s.client.Do(ctx, req, nil)
+	if err != nil {
+		return resp, err
+	}
+	return resp, nil
+}
diff --git a/vendor/github.com/digitalocean/godo/godo.go b/vendor/github.com/digitalocean/godo/godo.go
index 7c15e92f..354a1d51 100644
--- a/vendor/github.com/digitalocean/godo/godo.go
+++ b/vendor/github.com/digitalocean/godo/godo.go
@@ -21,7 +21,7 @@ import (
 )
 
 const (
-	libraryVersion = "1.91.1"
+	libraryVersion = "1.92.0"
 	defaultBaseURL = "https://api.digitalocean.com/"
 	userAgent      = "godo/" + libraryVersion
 	mediaType      = "application/json"
@@ -54,34 +54,34 @@ type Client struct {
 	Balance           BalanceService
 	BillingHistory    BillingHistoryService
 	CDNs              CDNService
+	Certificates      CertificatesService
+	Databases         DatabasesService
 	Domains           DomainsService
 	Droplets          DropletsService
 	DropletActions    DropletActionsService
+	Firewalls         FirewallsService
+	FloatingIPs       FloatingIPsService
+	FloatingIPActions FloatingIPActionsService
+	Functions         FunctionsService
 	Images            ImagesService
 	ImageActions      ImageActionsService
 	Invoices          InvoicesService
 	Keys              KeysService
+	Kubernetes        KubernetesService
+	LoadBalancers     LoadBalancersService
+	Monitoring        MonitoringService
+	OneClick          OneClickService
+	Projects          ProjectsService
 	Regions           RegionsService
-	Sizes             SizesService
-	FloatingIPs       FloatingIPsService
-	FloatingIPActions FloatingIPActionsService
+	Registry          RegistryService
 	ReservedIPs       ReservedIPsService
 	ReservedIPActions ReservedIPActionsService
+	Sizes             SizesService
 	Snapshots         SnapshotsService
 	Storage           StorageService
 	StorageActions    StorageActionsService
 	Tags              TagsService
-	LoadBalancers     LoadBalancersService
-	Certificates      CertificatesService
-	Firewalls         FirewallsService
-	Projects          ProjectsService
-	Kubernetes        KubernetesService
-	Registry          RegistryService
-	Databases         DatabasesService
 	VPCs              VPCsService
-	OneClick          OneClickService
-	Monitoring        MonitoringService
-	Functions         FunctionsService
 
 	// Optional function called after every successful request made to the DO APIs
 	onRequestCompleted RequestCompletionCallback
@@ -216,6 +216,7 @@ func NewClient(httpClient *http.Client) *Client {
 	baseURL, _ := url.Parse(defaultBaseURL)
 
 	c := &Client{client: httpClient, BaseURL: baseURL, UserAgent: userAgent}
+
 	c.Account = &AccountServiceOp{client: c}
 	c.Actions = &ActionsServiceOp{client: c}
 	c.Apps = &AppsServiceOp{client: c}
@@ -223,33 +224,34 @@ func NewClient(httpClient *http.Client) *Client {
 	c.BillingHistory = &BillingHistoryServiceOp{client: c}
 	c.CDNs = &CDNServiceOp{client: c}
 	c.Certificates = &CertificatesServiceOp{client: c}
+	c.Databases = &DatabasesServiceOp{client: c}
 	c.Domains = &DomainsServiceOp{client: c}
 	c.Droplets = &DropletsServiceOp{client: c}
 	c.DropletActions = &DropletActionsServiceOp{client: c}
 	c.Firewalls = &FirewallsServiceOp{client: c}
 	c.FloatingIPs = &FloatingIPsServiceOp{client: c}
 	c.FloatingIPActions = &FloatingIPActionsServiceOp{client: c}
-	c.ReservedIPs = &ReservedIPsServiceOp{client: c}
-	c.ReservedIPActions = &ReservedIPActionsServiceOp{client: c}
+	c.Functions = &FunctionsServiceOp{client: c}
 	c.Images = &ImagesServiceOp{client: c}
 	c.ImageActions = &ImageActionsServiceOp{client: c}
 	c.Invoices = &InvoicesServiceOp{client: c}
 	c.Keys = &KeysServiceOp{client: c}
+	c.Kubernetes = &KubernetesServiceOp{client: c}
 	c.LoadBalancers = &LoadBalancersServiceOp{client: c}
+	c.Monitoring = &MonitoringServiceOp{client: c}
+	c.OneClick = &OneClickServiceOp{client: c}
 	c.Projects = &ProjectsServiceOp{client: c}
 	c.Regions = &RegionsServiceOp{client: c}
+	c.Registry = &RegistryServiceOp{client: c}
+	c.ReservedIPs = &ReservedIPsServiceOp{client: c}
+	c.ReservedIPActions = &ReservedIPActionsServiceOp{client: c}
 	c.Sizes = &SizesServiceOp{client: c}
 	c.Snapshots = &SnapshotsServiceOp{client: c}
 	c.Storage = &StorageServiceOp{client: c}
 	c.StorageActions = &StorageActionsServiceOp{client: c}
 	c.Tags = &TagsServiceOp{client: c}
-	c.Kubernetes = &KubernetesServiceOp{client: c}
-	c.Registry = &RegistryServiceOp{client: c}
-	c.Databases = &DatabasesServiceOp{client: c}
 	c.VPCs = &VPCsServiceOp{client: c}
-	c.OneClick = &OneClickServiceOp{client: c}
-	c.Monitoring = &MonitoringServiceOp{client: c}
-	c.Functions = &FunctionsServiceOp{client: c}
+
 	c.headers = make(map[string]string)
 
 	return c
diff --git a/vendor/github.com/digitalocean/godo/keys.go b/vendor/github.com/digitalocean/godo/keys.go
index d50946c2..cd0bd29d 100644
--- a/vendor/github.com/digitalocean/godo/keys.go
+++ b/vendor/github.com/digitalocean/godo/keys.go
@@ -8,7 +8,7 @@ import (
 
 const keysBasePath = "v2/account/keys"
 
-// KeysService is an interface for interfacing with the keys
+// KeysService is an interface for interfacing with the SSH keys
 // endpoints of the DigitalOcean API
 // See: https://docs.digitalocean.com/reference/api/api-reference/#tag/SSH-Keys
 type KeysService interface {
@@ -22,7 +22,7 @@ type KeysService interface {
 	DeleteByFingerprint(context.Context, string) (*Response, error)
 }
 
-// KeysServiceOp handles communication with key related method of the
+// KeysServiceOp handles communication with SSH key related method of the
 // DigitalOcean API.
 type KeysServiceOp struct {
 	client *Client
@@ -38,7 +38,7 @@ type Key struct {
 	PublicKey   string `json:"public_key,omitempty"`
 }
 
-// KeyUpdateRequest represents a request to update a DigitalOcean key.
+// KeyUpdateRequest represents a request to update an SSH key stored in a DigitalOcean account.
 type KeyUpdateRequest struct {
 	Name string `json:"name"`
 }
@@ -57,13 +57,13 @@ func (s Key) String() string {
 	return Stringify(s)
 }
 
-// KeyCreateRequest represents a request to create a new key.
+// KeyCreateRequest represents a request to create a new SSH key.
 type KeyCreateRequest struct {
 	Name      string `json:"name"`
 	PublicKey string `json:"public_key"`
 }
 
-// List all keys
+// List all SSH keys
 func (s *KeysServiceOp) List(ctx context.Context, opt *ListOptions) ([]Key, *Response, error) {
 	path := keysBasePath
 	path, err := addOptions(path, opt)
@@ -107,7 +107,7 @@ func (s *KeysServiceOp) get(ctx context.Context, path string) (*Key, *Response,
 	return root.SSHKey, resp, err
 }
 
-// GetByID gets a Key by id
+// GetByID gets an SSH key by its ID
 func (s *KeysServiceOp) GetByID(ctx context.Context, keyID int) (*Key, *Response, error) {
 	if keyID < 1 {
 		return nil, nil, NewArgError("keyID", "cannot be less than 1")
@@ -117,7 +117,7 @@ func (s *KeysServiceOp) GetByID(ctx context.Context, keyID int) (*Key, *Response
 	return s.get(ctx, path)
 }
 
-// GetByFingerprint gets a Key by fingerprint
+// GetByFingerprint gets an SSH key by its fingerprint
 func (s *KeysServiceOp) GetByFingerprint(ctx context.Context, fingerprint string) (*Key, *Response, error) {
 	if len(fingerprint) < 1 {
 		return nil, nil, NewArgError("fingerprint", "cannot not be empty")
@@ -127,7 +127,7 @@ func (s *KeysServiceOp) GetByFingerprint(ctx context.Context, fingerprint string
 	return s.get(ctx, path)
 }
 
-// Create a key using a KeyCreateRequest
+// Create an SSH key using a KeyCreateRequest
 func (s *KeysServiceOp) Create(ctx context.Context, createRequest *KeyCreateRequest) (*Key, *Response, error) {
 	if createRequest == nil {
 		return nil, nil, NewArgError("createRequest", "cannot be nil")
@@ -147,7 +147,7 @@ func (s *KeysServiceOp) Create(ctx context.Context, createRequest *KeyCreateRequ
 	return root.SSHKey, resp, err
 }
 
-// UpdateByID updates a key name by ID.
+// UpdateByID updates an SSH key name by ID.
 func (s *KeysServiceOp) UpdateByID(ctx context.Context, keyID int, updateRequest *KeyUpdateRequest) (*Key, *Response, error) {
 	if keyID < 1 {
 		return nil, nil, NewArgError("keyID", "cannot be less than 1")
@@ -172,7 +172,7 @@ func (s *KeysServiceOp) UpdateByID(ctx context.Context, keyID int, updateRequest
 	return root.SSHKey, resp, err
 }
 
-// UpdateByFingerprint updates a key name by fingerprint.
+// UpdateByFingerprint updates an SSH key name by fingerprint.
 func (s *KeysServiceOp) UpdateByFingerprint(ctx context.Context, fingerprint string, updateRequest *KeyUpdateRequest) (*Key, *Response, error) {
 	if len(fingerprint) < 1 {
 		return nil, nil, NewArgError("fingerprint", "cannot be empty")
@@ -197,7 +197,7 @@ func (s *KeysServiceOp) UpdateByFingerprint(ctx context.Context, fingerprint str
 	return root.SSHKey, resp, err
 }
 
-// Delete key using a path
+// Delete an SSH key using a path
 func (s *KeysServiceOp) delete(ctx context.Context, path string) (*Response, error) {
 	req, err := s.client.NewRequest(ctx, http.MethodDelete, path, nil)
 	if err != nil {
@@ -209,7 +209,7 @@ func (s *KeysServiceOp) delete(ctx context.Context, path string) (*Response, err
 	return resp, err
 }
 
-// DeleteByID deletes a key by its id
+// DeleteByID deletes an SSH key by its id
 func (s *KeysServiceOp) DeleteByID(ctx context.Context, keyID int) (*Response, error) {
 	if keyID < 1 {
 		return nil, NewArgError("keyID", "cannot be less than 1")
@@ -219,7 +219,7 @@ func (s *KeysServiceOp) DeleteByID(ctx context.Context, keyID int) (*Response, e
 	return s.delete(ctx, path)
 }
 
-// DeleteByFingerprint deletes a key by its fingerprint
+// DeleteByFingerprint deletes an SSH key by its fingerprint
 func (s *KeysServiceOp) DeleteByFingerprint(ctx context.Context, fingerprint string) (*Response, error) {
 	if len(fingerprint) < 1 {
 		return nil, NewArgError("fingerprint", "cannot be empty")
diff --git a/vendor/github.com/digitalocean/godo/load_balancers.go b/vendor/github.com/digitalocean/godo/load_balancers.go
index 1466d523..6a9a70ef 100644
--- a/vendor/github.com/digitalocean/godo/load_balancers.go
+++ b/vendor/github.com/digitalocean/godo/load_balancers.go
@@ -53,6 +53,7 @@ type LoadBalancer struct {
 	ValidateOnly                 bool             `json:"validate_only,omitempty"`
 	ProjectID                    string           `json:"project_id,omitempty"`
 	HTTPIdleTimeoutSeconds       *uint64          `json:"http_idle_timeout_seconds,omitempty"`
+	Firewall                     *LBFirewall      `json:"firewall,omitempty"`
 }
 
 // String creates a human-readable description of a LoadBalancer.
@@ -103,6 +104,10 @@ func (l LoadBalancer) AsRequest() *LoadBalancerRequest {
 		r.Region = l.Region.Slug
 	}
 
+	if l.Firewall != nil {
+		r.Firewall = l.Firewall.deepCopy()
+	}
+
 	return &r
 }
 
@@ -149,6 +154,33 @@ func (s StickySessions) String() string {
 	return Stringify(s)
 }
 
+// LBFirewall holds the allow and deny rules for a loadbalancer's firewall.
+// Currently, allow and deny rules support cidrs and ips.
+// Please use the helper methods (IPSourceFirewall/CIDRSourceFirewall) to format the allow/deny rules.
+type LBFirewall struct {
+	Allow []string `json:"allow,omitempty"`
+	Deny  []string `json:"deny,omitempty"`
+}
+
+func (lbf *LBFirewall) deepCopy() *LBFirewall {
+	return &LBFirewall{
+		Allow: append([]string(nil), lbf.Allow...),
+		Deny:  append([]string(nil), lbf.Deny...),
+	}
+}
+
+// IPSourceFirewall takes an IP (string) and returns a formatted ip source firewall rule
+func IPSourceFirewall(ip string) string { return fmt.Sprintf("ip:%s", ip) }
+
+// CIDRSourceFirewall takes a CIDR notation IP address and prefix length string
+// like "192.0.2.0/24" and returns a formatted cidr source firewall rule
+func CIDRSourceFirewall(cidr string) string { return fmt.Sprintf("cidr:%s", cidr) }
+
+// String creates a human-readable description of an LBFirewall instance.
+func (f LBFirewall) String() string {
+	return Stringify(f)
+}
+
 // LoadBalancerRequest represents the configuration to be applied to an existing or a new load balancer.
 type LoadBalancerRequest struct {
 	Name      string `json:"name,omitempty"`
@@ -172,6 +204,7 @@ type LoadBalancerRequest struct {
 	ValidateOnly                 bool             `json:"validate_only,omitempty"`
 	ProjectID                    string           `json:"project_id,omitempty"`
 	HTTPIdleTimeoutSeconds       *uint64          `json:"http_idle_timeout_seconds,omitempty"`
+	Firewall                     *LBFirewall      `json:"firewall,omitempty"`
 }
 
 // String creates a human-readable description of a LoadBalancerRequest.
diff --git a/vendor/github.com/digitalocean/godo/monitoring.go b/vendor/github.com/digitalocean/godo/monitoring.go
index 5934c875..937bb8d9 100644
--- a/vendor/github.com/digitalocean/godo/monitoring.go
+++ b/vendor/github.com/digitalocean/godo/monitoring.go
@@ -27,16 +27,18 @@ const (
 	DropletFiveMinuteLoadAverage        = "v1/insights/droplet/load_5"
 	DropletFifteenMinuteLoadAverage     = "v1/insights/droplet/load_15"
 
-	LoadBalancerCPUUtilizationPercent             = "v1/insights/lbaas/avg_cpu_utilization_percent"
-	LoadBalancerConnectionUtilizationPercent      = "v1/insights/lbaas/connection_utilization_percent"
-	LoadBalancerDropletHealth                     = "v1/insights/lbaas/droplet_health"
-	LoadBalancerTLSUtilizationPercent             = "v1/insights/lbaas/tls_connections_per_second_utilization_percent"
-	LoadBalancerIncreaseInHTTPErrorRatePercentage = "v1/insights/lbaas/increase_in_http_error_rate_percentage"
-	LoadBalancerIncreaseInHTTPErrorRateCount      = "v1/insights/lbaas/increase_in_http_error_rate_count"
-	LoadBalancerHighHttpResponseTime              = "v1/insights/lbaas/high_http_request_response_time"
-	LoadBalancerHighHttpResponseTime50P           = "v1/insights/lbaas/high_http_request_response_time_50p"
-	LoadBalancerHighHttpResponseTime95P           = "v1/insights/lbaas/high_http_request_response_time_95p"
-	LoadBalancerHighHttpResponseTime99P           = "v1/insights/lbaas/high_http_request_response_time_99p"
+	LoadBalancerCPUUtilizationPercent                = "v1/insights/lbaas/avg_cpu_utilization_percent"
+	LoadBalancerConnectionUtilizationPercent         = "v1/insights/lbaas/connection_utilization_percent"
+	LoadBalancerDropletHealth                        = "v1/insights/lbaas/droplet_health"
+	LoadBalancerTLSUtilizationPercent                = "v1/insights/lbaas/tls_connections_per_second_utilization_percent"
+	LoadBalancerIncreaseInHTTPErrorRatePercentage5xx = "v1/insights/lbaas/increase_in_http_error_rate_percentage_5xx"
+	LoadBalancerIncreaseInHTTPErrorRatePercentage4xx = "v1/insights/lbaas/increase_in_http_error_rate_percentage_4xx"
+	LoadBalancerIncreaseInHTTPErrorRateCount5xx      = "v1/insights/lbaas/increase_in_http_error_rate_count_5xx"
+	LoadBalancerIncreaseInHTTPErrorRateCount4xx      = "v1/insights/lbaas/increase_in_http_error_rate_count_4xx"
+	LoadBalancerHighHttpResponseTime                 = "v1/insights/lbaas/high_http_request_response_time"
+	LoadBalancerHighHttpResponseTime50P              = "v1/insights/lbaas/high_http_request_response_time_50p"
+	LoadBalancerHighHttpResponseTime95P              = "v1/insights/lbaas/high_http_request_response_time_95p"
+	LoadBalancerHighHttpResponseTime99P              = "v1/insights/lbaas/high_http_request_response_time_99p"
 
 	DbaasFifteenMinuteLoadAverage = "v1/dbaas/alerts/load_15_alerts"
 	DbaasMemoryUtilizationPercent = "v1/dbaas/alerts/memory_utilization_alerts"
diff --git a/vendor/golang.org/x/net/http2/hpack/encode.go b/vendor/golang.org/x/net/http2/hpack/encode.go
index 6886dc16..46219da2 100644
--- a/vendor/golang.org/x/net/http2/hpack/encode.go
+++ b/vendor/golang.org/x/net/http2/hpack/encode.go
@@ -116,6 +116,11 @@ func (e *Encoder) SetMaxDynamicTableSize(v uint32) {
 	e.dynTab.setMaxSize(v)
 }
 
+// MaxDynamicTableSize returns the current dynamic header table size.
+func (e *Encoder) MaxDynamicTableSize() (v uint32) {
+	return e.dynTab.maxSize
+}
+
 // SetMaxDynamicTableSizeLimit changes the maximum value that can be
 // specified in SetMaxDynamicTableSize to v. By default, it is set to
 // 4096, which is the same size of the default dynamic header table
diff --git a/vendor/golang.org/x/net/http2/server.go b/vendor/golang.org/x/net/http2/server.go
index d8a17aa9..4eb7617f 100644
--- a/vendor/golang.org/x/net/http2/server.go
+++ b/vendor/golang.org/x/net/http2/server.go
@@ -98,6 +98,19 @@ type Server struct {
 	// the HTTP/2 spec's recommendations.
 	MaxConcurrentStreams uint32
 
+	// MaxDecoderHeaderTableSize optionally specifies the http2
+	// SETTINGS_HEADER_TABLE_SIZE to send in the initial settings frame. It
+	// informs the remote endpoint of the maximum size of the header compression
+	// table used to decode header blocks, in octets. If zero, the default value
+	// of 4096 is used.
+	MaxDecoderHeaderTableSize uint32
+
+	// MaxEncoderHeaderTableSize optionally specifies an upper limit for the
+	// header compression table used for encoding request headers. Received
+	// SETTINGS_HEADER_TABLE_SIZE settings are capped at this limit. If zero,
+	// the default value of 4096 is used.
+	MaxEncoderHeaderTableSize uint32
+
 	// MaxReadFrameSize optionally specifies the largest frame
 	// this server is willing to read. A valid value is between
 	// 16k and 16M, inclusive. If zero or otherwise invalid, a
@@ -170,6 +183,20 @@ func (s *Server) maxConcurrentStreams() uint32 {
 	return defaultMaxStreams
 }
 
+func (s *Server) maxDecoderHeaderTableSize() uint32 {
+	if v := s.MaxDecoderHeaderTableSize; v > 0 {
+		return v
+	}
+	return initialHeaderTableSize
+}
+
+func (s *Server) maxEncoderHeaderTableSize() uint32 {
+	if v := s.MaxEncoderHeaderTableSize; v > 0 {
+		return v
+	}
+	return initialHeaderTableSize
+}
+
 // maxQueuedControlFrames is the maximum number of control frames like
 // SETTINGS, PING and RST_STREAM that will be queued for writing before
 // the connection is closed to prevent memory exhaustion attacks.
@@ -394,7 +421,6 @@ func (s *Server) ServeConn(c net.Conn, opts *ServeConnOpts) {
 		advMaxStreams:               s.maxConcurrentStreams(),
 		initialStreamSendWindowSize: initialWindowSize,
 		maxFrameSize:                initialMaxFrameSize,
-		headerTableSize:             initialHeaderTableSize,
 		serveG:                      newGoroutineLock(),
 		pushEnabled:                 true,
 		sawClientPreface:            opts.SawClientPreface,
@@ -424,12 +450,13 @@ func (s *Server) ServeConn(c net.Conn, opts *ServeConnOpts) {
 	sc.flow.add(initialWindowSize)
 	sc.inflow.add(initialWindowSize)
 	sc.hpackEncoder = hpack.NewEncoder(&sc.headerWriteBuf)
+	sc.hpackEncoder.SetMaxDynamicTableSizeLimit(s.maxEncoderHeaderTableSize())
 
 	fr := NewFramer(sc.bw, c)
 	if s.CountError != nil {
 		fr.countError = s.CountError
 	}
-	fr.ReadMetaHeaders = hpack.NewDecoder(initialHeaderTableSize, nil)
+	fr.ReadMetaHeaders = hpack.NewDecoder(s.maxDecoderHeaderTableSize(), nil)
 	fr.MaxHeaderListSize = sc.maxHeaderListSize()
 	fr.SetMaxReadFrameSize(s.maxReadFrameSize())
 	sc.framer = fr
@@ -559,9 +586,9 @@ type serverConn struct {
 	streams                     map[uint32]*stream
 	initialStreamSendWindowSize int32
 	maxFrameSize                int32
-	headerTableSize             uint32
 	peerMaxHeaderListSize       uint32            // zero means unknown (default)
 	canonHeader                 map[string]string // http2-lower-case -> Go-Canonical-Case
+	canonHeaderKeysSize         int               // canonHeader keys size in bytes
 	writingFrame                bool              // started writing a frame (on serve goroutine or separate)
 	writingFrameAsync           bool              // started a frame on its own goroutine but haven't heard back on wroteFrameCh
 	needsFrameFlush             bool              // last frame write wasn't a flush
@@ -740,6 +767,13 @@ func (sc *serverConn) condlogf(err error, format string, args ...interface{}) {
 	}
 }
 
+// maxCachedCanonicalHeadersKeysSize is an arbitrarily-chosen limit on the size
+// of the entries in the canonHeader cache.
+// This should be larger than the size of unique, uncommon header keys likely to
+// be sent by the peer, while not so high as to permit unreasonable memory usage
+// if the peer sends an unbounded number of unique header keys.
+const maxCachedCanonicalHeadersKeysSize = 2048
+
 func (sc *serverConn) canonicalHeader(v string) string {
 	sc.serveG.check()
 	buildCommonHeaderMapsOnce()
@@ -755,14 +789,10 @@ func (sc *serverConn) canonicalHeader(v string) string {
 		sc.canonHeader = make(map[string]string)
 	}
 	cv = http.CanonicalHeaderKey(v)
-	// maxCachedCanonicalHeaders is an arbitrarily-chosen limit on the number of
-	// entries in the canonHeader cache. This should be larger than the number
-	// of unique, uncommon header keys likely to be sent by the peer, while not
-	// so high as to permit unreasonable memory usage if the peer sends an unbounded
-	// number of unique header keys.
-	const maxCachedCanonicalHeaders = 32
-	if len(sc.canonHeader) < maxCachedCanonicalHeaders {
+	size := 100 + len(v)*2 // 100 bytes of map overhead + key + value
+	if sc.canonHeaderKeysSize+size <= maxCachedCanonicalHeadersKeysSize {
 		sc.canonHeader[v] = cv
+		sc.canonHeaderKeysSize += size
 	}
 	return cv
 }
@@ -864,6 +894,7 @@ func (sc *serverConn) serve() {
 			{SettingMaxFrameSize, sc.srv.maxReadFrameSize()},
 			{SettingMaxConcurrentStreams, sc.advMaxStreams},
 			{SettingMaxHeaderListSize, sc.maxHeaderListSize()},
+			{SettingHeaderTableSize, sc.srv.maxDecoderHeaderTableSize()},
 			{SettingInitialWindowSize, uint32(sc.srv.initialStreamRecvWindowSize())},
 		},
 	})
@@ -1661,7 +1692,6 @@ func (sc *serverConn) processSetting(s Setting) error {
 	}
 	switch s.ID {
 	case SettingHeaderTableSize:
-		sc.headerTableSize = s.Val
 		sc.hpackEncoder.SetMaxDynamicTableSize(s.Val)
 	case SettingEnablePush:
 		sc.pushEnabled = s.Val != 0
diff --git a/vendor/golang.org/x/net/http2/transport.go b/vendor/golang.org/x/net/http2/transport.go
index 46dda4dc..30f706e6 100644
--- a/vendor/golang.org/x/net/http2/transport.go
+++ b/vendor/golang.org/x/net/http2/transport.go
@@ -118,6 +118,28 @@ type Transport struct {
 	// to mean no limit.
 	MaxHeaderListSize uint32
 
+	// MaxReadFrameSize is the http2 SETTINGS_MAX_FRAME_SIZE to send in the
+	// initial settings frame. It is the size in bytes of the largest frame
+	// payload that the sender is willing to receive. If 0, no setting is
+	// sent, and the value is provided by the peer, which should be 16384
+	// according to the spec:
+	// https://datatracker.ietf.org/doc/html/rfc7540#section-6.5.2.
+	// Values are bounded in the range 16k to 16M.
+	MaxReadFrameSize uint32
+
+	// MaxDecoderHeaderTableSize optionally specifies the http2
+	// SETTINGS_HEADER_TABLE_SIZE to send in the initial settings frame. It
+	// informs the remote endpoint of the maximum size of the header compression
+	// table used to decode header blocks, in octets. If zero, the default value
+	// of 4096 is used.
+	MaxDecoderHeaderTableSize uint32
+
+	// MaxEncoderHeaderTableSize optionally specifies an upper limit for the
+	// header compression table used for encoding request headers. Received
+	// SETTINGS_HEADER_TABLE_SIZE settings are capped at this limit. If zero,
+	// the default value of 4096 is used.
+	MaxEncoderHeaderTableSize uint32
+
 	// StrictMaxConcurrentStreams controls whether the server's
 	// SETTINGS_MAX_CONCURRENT_STREAMS should be respected
 	// globally. If false, new TCP connections are created to the
@@ -171,6 +193,19 @@ func (t *Transport) maxHeaderListSize() uint32 {
 	return t.MaxHeaderListSize
 }
 
+func (t *Transport) maxFrameReadSize() uint32 {
+	if t.MaxReadFrameSize == 0 {
+		return 0 // use the default provided by the peer
+	}
+	if t.MaxReadFrameSize < minMaxFrameSize {
+		return minMaxFrameSize
+	}
+	if t.MaxReadFrameSize > maxFrameSize {
+		return maxFrameSize
+	}
+	return t.MaxReadFrameSize
+}
+
 func (t *Transport) disableCompression() bool {
 	return t.DisableCompression || (t.t1 != nil && t.t1.DisableCompression)
 }
@@ -293,10 +328,11 @@ type ClientConn struct {
 	lastActive      time.Time
 	lastIdle        time.Time // time last idle
 	// Settings from peer: (also guarded by wmu)
-	maxFrameSize          uint32
-	maxConcurrentStreams  uint32
-	peerMaxHeaderListSize uint64
-	initialWindowSize     uint32
+	maxFrameSize           uint32
+	maxConcurrentStreams   uint32
+	peerMaxHeaderListSize  uint64
+	peerMaxHeaderTableSize uint32
+	initialWindowSize      uint32
 
 	// reqHeaderMu is a 1-element semaphore channel controlling access to sending new requests.
 	// Write to reqHeaderMu to lock it, read from it to unlock.
@@ -681,6 +717,20 @@ func (t *Transport) expectContinueTimeout() time.Duration {
 	return t.t1.ExpectContinueTimeout
 }
 
+func (t *Transport) maxDecoderHeaderTableSize() uint32 {
+	if v := t.MaxDecoderHeaderTableSize; v > 0 {
+		return v
+	}
+	return initialHeaderTableSize
+}
+
+func (t *Transport) maxEncoderHeaderTableSize() uint32 {
+	if v := t.MaxEncoderHeaderTableSize; v > 0 {
+		return v
+	}
+	return initialHeaderTableSize
+}
+
 func (t *Transport) NewClientConn(c net.Conn) (*ClientConn, error) {
 	return t.newClientConn(c, t.disableKeepAlives())
 }
@@ -721,15 +771,19 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, erro
 	})
 	cc.br = bufio.NewReader(c)
 	cc.fr = NewFramer(cc.bw, cc.br)
+	if t.maxFrameReadSize() != 0 {
+		cc.fr.SetMaxReadFrameSize(t.maxFrameReadSize())
+	}
 	if t.CountError != nil {
 		cc.fr.countError = t.CountError
 	}
-	cc.fr.ReadMetaHeaders = hpack.NewDecoder(initialHeaderTableSize, nil)
+	maxHeaderTableSize := t.maxDecoderHeaderTableSize()
+	cc.fr.ReadMetaHeaders = hpack.NewDecoder(maxHeaderTableSize, nil)
 	cc.fr.MaxHeaderListSize = t.maxHeaderListSize()
 
-	// TODO: SetMaxDynamicTableSize, SetMaxDynamicTableSizeLimit on
-	// henc in response to SETTINGS frames?
 	cc.henc = hpack.NewEncoder(&cc.hbuf)
+	cc.henc.SetMaxDynamicTableSizeLimit(t.maxEncoderHeaderTableSize())
+	cc.peerMaxHeaderTableSize = initialHeaderTableSize
 
 	if t.AllowHTTP {
 		cc.nextStreamID = 3
@@ -744,9 +798,15 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, erro
 		{ID: SettingEnablePush, Val: 0},
 		{ID: SettingInitialWindowSize, Val: transportDefaultStreamFlow},
 	}
+	if max := t.maxFrameReadSize(); max != 0 {
+		initialSettings = append(initialSettings, Setting{ID: SettingMaxFrameSize, Val: max})
+	}
 	if max := t.maxHeaderListSize(); max != 0 {
 		initialSettings = append(initialSettings, Setting{ID: SettingMaxHeaderListSize, Val: max})
 	}
+	if maxHeaderTableSize != initialHeaderTableSize {
+		initialSettings = append(initialSettings, Setting{ID: SettingHeaderTableSize, Val: maxHeaderTableSize})
+	}
 
 	cc.bw.Write(clientPreface)
 	cc.fr.WriteSettings(initialSettings...)
@@ -2773,8 +2833,10 @@ func (rl *clientConnReadLoop) processSettingsNoWrite(f *SettingsFrame) error {
 			cc.cond.Broadcast()
 
 			cc.initialWindowSize = s.Val
+		case SettingHeaderTableSize:
+			cc.henc.SetMaxDynamicTableSize(s.Val)
+			cc.peerMaxHeaderTableSize = s.Val
 		default:
-			// TODO(bradfitz): handle more settings? SETTINGS_HEADER_TABLE_SIZE probably.
 			cc.vlogf("Unhandled Setting: %v", s)
 		}
 		return nil
diff --git a/vendor/golang.org/x/sys/execabs/execabs_go119.go b/vendor/golang.org/x/sys/execabs/execabs_go119.go
index 1e7a9ada..46c5b525 100644
--- a/vendor/golang.org/x/sys/execabs/execabs_go119.go
+++ b/vendor/golang.org/x/sys/execabs/execabs_go119.go
@@ -7,9 +7,11 @@
 
 package execabs
 
-import "strings"
+import (
+	"errors"
+	"os/exec"
+)
 
 func isGo119ErrDot(err error) bool {
-	// TODO: return errors.Is(err, exec.ErrDot)
-	return strings.Contains(err.Error(), "current directory")
+	return errors.Is(err, exec.ErrDot)
 }
diff --git a/vendor/golang.org/x/sys/windows/syscall_windows.go b/vendor/golang.org/x/sys/windows/syscall_windows.go
index 7a6ba43a..a49853e9 100644
--- a/vendor/golang.org/x/sys/windows/syscall_windows.go
+++ b/vendor/golang.org/x/sys/windows/syscall_windows.go
@@ -367,6 +367,7 @@ func NewCallbackCDecl(fn interface{}) uintptr {
 //sys	IsWindowUnicode(hwnd HWND) (isUnicode bool) = user32.IsWindowUnicode
 //sys	IsWindowVisible(hwnd HWND) (isVisible bool) = user32.IsWindowVisible
 //sys	GetGUIThreadInfo(thread uint32, info *GUIThreadInfo) (err error) = user32.GetGUIThreadInfo
+//sys	GetLargePageMinimum() (size uintptr)
 
 // Volume Management Functions
 //sys	DefineDosDevice(flags uint32, deviceName *uint16, targetPath *uint16) (err error) = DefineDosDeviceW
diff --git a/vendor/golang.org/x/sys/windows/zsyscall_windows.go b/vendor/golang.org/x/sys/windows/zsyscall_windows.go
index 96ba8559..ac60052e 100644
--- a/vendor/golang.org/x/sys/windows/zsyscall_windows.go
+++ b/vendor/golang.org/x/sys/windows/zsyscall_windows.go
@@ -252,6 +252,7 @@ var (
 	procGetFileType                                          = modkernel32.NewProc("GetFileType")
 	procGetFinalPathNameByHandleW                            = modkernel32.NewProc("GetFinalPathNameByHandleW")
 	procGetFullPathNameW                                     = modkernel32.NewProc("GetFullPathNameW")
+	procGetLargePageMinimum                                  = modkernel32.NewProc("GetLargePageMinimum")
 	procGetLastError                                         = modkernel32.NewProc("GetLastError")
 	procGetLogicalDriveStringsW                              = modkernel32.NewProc("GetLogicalDriveStringsW")
 	procGetLogicalDrives                                     = modkernel32.NewProc("GetLogicalDrives")
@@ -2180,6 +2181,12 @@ func GetFullPathName(path *uint16, buflen uint32, buf *uint16, fname **uint16) (
 	return
 }
 
+func GetLargePageMinimum() (size uintptr) {
+	r0, _, _ := syscall.Syscall(procGetLargePageMinimum.Addr(), 0, 0, 0, 0)
+	size = uintptr(r0)
+	return
+}
+
 func GetLastError() (lasterr error) {
 	r0, _, _ := syscall.Syscall(procGetLastError.Addr(), 0, 0, 0, 0)
 	if r0 != 0 {
diff --git a/vendor/golang.org/x/text/unicode/bidi/trieval.go b/vendor/golang.org/x/text/unicode/bidi/trieval.go
index 4c459c4b..6a796e22 100644
--- a/vendor/golang.org/x/text/unicode/bidi/trieval.go
+++ b/vendor/golang.org/x/text/unicode/bidi/trieval.go
@@ -37,18 +37,6 @@ const (
 	unknownClass = ^Class(0)
 )
 
-var controlToClass = map[rune]Class{
-	0x202D: LRO, // LeftToRightOverride,
-	0x202E: RLO, // RightToLeftOverride,
-	0x202A: LRE, // LeftToRightEmbedding,
-	0x202B: RLE, // RightToLeftEmbedding,
-	0x202C: PDF, // PopDirectionalFormat,
-	0x2066: LRI, // LeftToRightIsolate,
-	0x2067: RLI, // RightToLeftIsolate,
-	0x2068: FSI, // FirstStrongIsolate,
-	0x2069: PDI, // PopDirectionalIsolate,
-}
-
 // A trie entry has the following bits:
 // 7..5  XOR mask for brackets
 // 4     1: Bracket open, 0: Bracket close
diff --git a/vendor/golang.org/x/time/rate/rate.go b/vendor/golang.org/x/time/rate/rate.go
index 8f7c29f1..f0e0cf3c 100644
--- a/vendor/golang.org/x/time/rate/rate.go
+++ b/vendor/golang.org/x/time/rate/rate.go
@@ -83,7 +83,7 @@ func (lim *Limiter) Burst() int {
 // TokensAt returns the number of tokens available at time t.
 func (lim *Limiter) TokensAt(t time.Time) float64 {
 	lim.mu.Lock()
-	_, _, tokens := lim.advance(t) // does not mutute lim
+	_, tokens := lim.advance(t) // does not mutate lim
 	lim.mu.Unlock()
 	return tokens
 }
@@ -183,7 +183,7 @@ func (r *Reservation) CancelAt(t time.Time) {
 		return
 	}
 	// advance time to now
-	t, _, tokens := r.lim.advance(t)
+	t, tokens := r.lim.advance(t)
 	// calculate new number of tokens
 	tokens += restoreTokens
 	if burst := float64(r.lim.burst); tokens > burst {
@@ -304,7 +304,7 @@ func (lim *Limiter) SetLimitAt(t time.Time, newLimit Limit) {
 	lim.mu.Lock()
 	defer lim.mu.Unlock()
 
-	t, _, tokens := lim.advance(t)
+	t, tokens := lim.advance(t)
 
 	lim.last = t
 	lim.tokens = tokens
@@ -321,7 +321,7 @@ func (lim *Limiter) SetBurstAt(t time.Time, newBurst int) {
 	lim.mu.Lock()
 	defer lim.mu.Unlock()
 
-	t, _, tokens := lim.advance(t)
+	t, tokens := lim.advance(t)
 
 	lim.last = t
 	lim.tokens = tokens
@@ -356,7 +356,7 @@ func (lim *Limiter) reserveN(t time.Time, n int, maxFutureReserve time.Duration)
 		}
 	}
 
-	t, last, tokens := lim.advance(t)
+	t, tokens := lim.advance(t)
 
 	// Calculate the remaining number of tokens resulting from the request.
 	tokens -= float64(n)
@@ -379,15 +379,11 @@ func (lim *Limiter) reserveN(t time.Time, n int, maxFutureReserve time.Duration)
 	if ok {
 		r.tokens = n
 		r.timeToAct = t.Add(waitDuration)
-	}
 
-	// Update state
-	if ok {
+		// Update state
 		lim.last = t
 		lim.tokens = tokens
 		lim.lastEvent = r.timeToAct
-	} else {
-		lim.last = last
 	}
 
 	return r
@@ -396,7 +392,7 @@ func (lim *Limiter) reserveN(t time.Time, n int, maxFutureReserve time.Duration)
 // advance calculates and returns an updated state for lim resulting from the passage of time.
 // lim is not changed.
 // advance requires that lim.mu is held.
-func (lim *Limiter) advance(t time.Time) (newT time.Time, newLast time.Time, newTokens float64) {
+func (lim *Limiter) advance(t time.Time) (newT time.Time, newTokens float64) {
 	last := lim.last
 	if t.Before(last) {
 		last = t
@@ -409,7 +405,7 @@ func (lim *Limiter) advance(t time.Time) (newT time.Time, newLast time.Time, new
 	if burst := float64(lim.burst); tokens > burst {
 		tokens = burst
 	}
-	return t, last, tokens
+	return t, tokens
 }
 
 // durationFromTokens is a unit conversion function from the number of tokens to the duration
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 42dc0dba..ad879520 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -85,7 +85,7 @@ github.com/creack/pty
 # github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
 ## explicit
 github.com/davecgh/go-spew/spew
-# github.com/digitalocean/godo v1.91.1
+# github.com/digitalocean/godo v1.92.0
 ## explicit; go 1.18
 github.com/digitalocean/godo
 github.com/digitalocean/godo/metrics
@@ -430,7 +430,7 @@ golang.org/x/mod/internal/lazyregexp
 golang.org/x/mod/modfile
 golang.org/x/mod/module
 golang.org/x/mod/semver
-# golang.org/x/net v0.2.0
+# golang.org/x/net v0.4.0
 ## explicit; go 1.17
 golang.org/x/net/context
 golang.org/x/net/context/ctxhttp
@@ -440,11 +440,11 @@ golang.org/x/net/http2/hpack
 golang.org/x/net/idna
 golang.org/x/net/internal/socks
 golang.org/x/net/proxy
-# golang.org/x/oauth2 v0.2.0
+# golang.org/x/oauth2 v0.3.0
 ## explicit; go 1.17
 golang.org/x/oauth2
 golang.org/x/oauth2/internal
-# golang.org/x/sys v0.2.0
+# golang.org/x/sys v0.3.0
 ## explicit; go 1.17
 golang.org/x/sys/cpu
 golang.org/x/sys/execabs
@@ -452,16 +452,16 @@ golang.org/x/sys/internal/unsafeheader
 golang.org/x/sys/plan9
 golang.org/x/sys/unix
 golang.org/x/sys/windows
-# golang.org/x/term v0.2.0
+# golang.org/x/term v0.3.0
 ## explicit; go 1.17
 golang.org/x/term
-# golang.org/x/text v0.4.0
+# golang.org/x/text v0.5.0
 ## explicit; go 1.17
 golang.org/x/text/secure/bidirule
 golang.org/x/text/transform
 golang.org/x/text/unicode/bidi
 golang.org/x/text/unicode/norm
-# golang.org/x/time v0.2.0
+# golang.org/x/time v0.3.0
 ## explicit
 golang.org/x/time/rate
 # golang.org/x/tools v0.1.12


Test patch:
diff --git a/commands/load_balancers_test.go b/commands/load_balancers_test.go
index 5666648f..f0f77c7d 100644
--- a/commands/load_balancers_test.go
+++ b/commands/load_balancers_test.go
@@ -111,6 +111,10 @@ func TestLoadBalancerCreate(t *testing.T) {
 			VPCUUID:                vpcUUID,
 			ProjectID:              "project-id-uuid",
 			HTTPIdleTimeoutSeconds: &timeout,
+			Firewall: &godo.LBFirewall{
+				Deny:  []string{"cidr:1.2.0.0/16"},
+				Allow: []string{"ip:1.2.3.4", "ip:1.2.3.5"},
+			},
 		}
 		disableLetsEncryptDNSRecords := true
 		r.DisableLetsEncryptDNSRecords = &disableLetsEncryptDNSRecords
@@ -127,6 +131,8 @@ func TestLoadBalancerCreate(t *testing.T) {
 		config.Doit.Set(config.NS, doctl.ArgDisableLetsEncryptDNSRecords, true)
 		config.Doit.Set(config.NS, doctl.ArgProjectID, "project-id-uuid")
 		config.Doit.Set(config.NS, doctl.ArgHTTPIdleTimeoutSeconds, 120)
+		config.Doit.Set(config.NS, doctl.ArgDenyList, []string{"cidr:1.2.0.0/16"})
+		config.Doit.Set(config.NS, doctl.ArgAllowList, []string{"ip:1.2.3.4", "ip:1.2.3.5"})
 
 		err := RunLoadBalancerCreate(config)
 		assert.NoError(t, err)
@@ -165,6 +171,10 @@ func TestLoadBalancerUpdate(t *testing.T) {
 			},
 			ProjectID:              "project-id-uuid",
 			HTTPIdleTimeoutSeconds: &timeout,
+			Firewall: &godo.LBFirewall{
+				Deny:  []string{"cidr:1.2.0.0/16"},
+				Allow: []string{"ip:1.2.3.4", "ip:1.2.3.5"},
+			},
 		}
 		disableLetsEncryptDNSRecords := true
 		r.DisableLetsEncryptDNSRecords = &disableLetsEncryptDNSRecords
@@ -182,6 +192,8 @@ func TestLoadBalancerUpdate(t *testing.T) {
 		config.Doit.Set(config.NS, doctl.ArgDisableLetsEncryptDNSRecords, true)
 		config.Doit.Set(config.NS, doctl.ArgProjectID, "project-id-uuid")
 		config.Doit.Set(config.NS, doctl.ArgHTTPIdleTimeoutSeconds, 120)
+		config.Doit.Set(config.NS, doctl.ArgDenyList, []string{"cidr:1.2.0.0/16"})
+		config.Doit.Set(config.NS, doctl.ArgAllowList, []string{"ip:1.2.3.4", "ip:1.2.3.5"})
 
 		err := RunLoadBalancerUpdate(config)
 		assert.NoError(t, err)