Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

azurerm_service_plan - Support premium_site_elastic_scaling_enabled feature. #24770

Closed
wants to merge 21 commits into from
Closed
Show file tree
Hide file tree
Changes from 18 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ require (
github.com/hashicorp/terraform-registry-address v0.2.2 // indirect
github.com/hashicorp/terraform-svchost v0.1.1 // indirect
github.com/hashicorp/yamux v0.1.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/kr/pretty v0.3.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
Expand Down
6 changes: 5 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -177,8 +177,9 @@ github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gav
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
Expand Down Expand Up @@ -226,6 +227,8 @@ github.com/rickb777/date v1.12.5-0.20200422084442-6300e543c4d9/go.mod h1:L8WrssT
github.com/rickb777/plural v1.2.0/go.mod h1:UdpyWFCGbo3mvK3f/PfZOAOrkjzJlYN/sD46XNWJ+Es=
github.com/rickb777/plural v1.4.1 h1:5MMLcbIaapLFmvDGRT5iPk8877hpTPt8Y9cdSKRw9sU=
github.com/rickb777/plural v1.4.1/go.mod h1:kdmXUpmKBJTS0FtG/TFumd//VBWsNTD7zOw7x4umxNw=
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
Expand Down Expand Up @@ -376,6 +379,7 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
Expand Down
13 changes: 13 additions & 0 deletions internal/services/appservice/helpers/service_plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ var appServicePlanSkus = []string{
"P1mv3", "P2mv3", "P3mv3", "P4mv3", "P5mv3", // Premium V3 memory optimized
}

var premiumSkus = []string{
"P1v2", "P2v2", "P3v2", "P0v3", "P1v3", "P2v3", "P3v3",
}

var freeSkus = []string{
"F1",
}
Expand Down Expand Up @@ -108,6 +112,15 @@ func PlanIsIsolated(input *string) bool {
return false
}

func PlanIsPremium(input string) bool {
for _, v := range premiumSkus {
if strings.EqualFold(input, v) {
return true
}
}
return false
}

func PlanIsAppPlan(input *string) bool {
if input == nil {
return false
Expand Down
60 changes: 52 additions & 8 deletions internal/services/appservice/service_plan_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ type ServicePlanModel struct {
Sku string `tfschema:"sku_name"`
AppServiceEnvironmentId string `tfschema:"app_service_environment_id"`
PerSiteScaling bool `tfschema:"per_site_scaling_enabled"`
ElasticScaling bool `tfschema:"elastic_scale_enabled"`
Reserved bool `tfschema:"reserved"`
WorkerCount int `tfschema:"worker_count"`
MaximumElasticWorkerCount int `tfschema:"maximum_elastic_worker_count"`
Expand Down Expand Up @@ -100,6 +101,12 @@ func (r ServicePlanResource) Arguments() map[string]*pluginsdk.Schema {
Default: false,
},

"elastic_scale_enabled": {
Type: pluginsdk.TypeBool,
Optional: true,
Default: false,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will, unfortunately, be a breaking change for existing EP plan deployments. It has different default values per Plan SKU. It may be possible to handle this in a CustomiseDiff function in a non-breaking way. Sorry I did not spot it sooner.

},

"worker_count": {
Type: pluginsdk.TypeInt,
Optional: true,
Expand Down Expand Up @@ -170,10 +177,11 @@ func (r ServicePlanResource) Create() sdk.ResourceFunc {

appServicePlan := appserviceplans.AppServicePlan{
Properties: &appserviceplans.AppServicePlanProperties{
PerSiteScaling: pointer.To(servicePlan.PerSiteScaling),
Reserved: pointer.To(servicePlan.OSType == OSTypeLinux),
HyperV: pointer.To(servicePlan.OSType == OSTypeWindowsContainer),
ZoneRedundant: pointer.To(servicePlan.ZoneBalancing),
PerSiteScaling: pointer.To(servicePlan.PerSiteScaling),
Reserved: pointer.To(servicePlan.OSType == OSTypeLinux),
HyperV: pointer.To(servicePlan.OSType == OSTypeWindowsContainer),
ZoneRedundant: pointer.To(servicePlan.ZoneBalancing),
ElasticScaleEnabled: pointer.To(servicePlan.ElasticScaling),
},
Sku: &appserviceplans.SkuDescription{
Name: pointer.To(servicePlan.Sku),
Expand All @@ -192,12 +200,21 @@ func (r ServicePlanResource) Create() sdk.ResourceFunc {
}

if servicePlan.MaximumElasticWorkerCount > 0 {
if !isServicePlanSupportScaleOut(servicePlan.Sku) {
return fmt.Errorf("`maximum_elastic_worker_count` can only be specified with Elastic Premium Skus")
if !isServicePlanSupportScaleOut(servicePlan.Sku) && (helpers.PlanIsPremium(servicePlan.Sku) && !servicePlan.ElasticScaling) {
return fmt.Errorf("`maximum_elastic_worker_count` can only be specified with Elastic Premium Skus or Premium V2 and V3 plan with `elastic_scale_enabled` set to true")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this reads more clearly and accurately here:

Suggested change
return fmt.Errorf("`maximum_elastic_worker_count` can only be specified with Elastic Premium Skus or Premium V2 and V3 plan with `elastic_scale_enabled` set to true")
return fmt.Errorf("`maximum_elastic_worker_count` can only be specified with Elastic Premium Plans, or with Premium V2 or Premium V3 plans with `elastic_scale_enabled` set to true")

}
if helpers.PlanIsPremium(servicePlan.Sku) {
appServicePlan.Properties.ElasticScaleEnabled = pointer.To(servicePlan.ElasticScaling)
}
appServicePlan.Properties.MaximumElasticWorkerCount = pointer.To(int64(servicePlan.MaximumElasticWorkerCount))
}

if servicePlan.ElasticScaling && !(helpers.PlanIsPremium(servicePlan.Sku) || isServicePlanSupportScaleOut(servicePlan.Sku)) {
return fmt.Errorf("`elastic_scale_enabled` can only be enabled for Elastic Premium Skus or Premium V2 and V3 Skus")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For consistency:

Suggested change
return fmt.Errorf("`elastic_scale_enabled` can only be enabled for Elastic Premium Skus or Premium V2 and V3 Skus")
return fmt.Errorf("`elastic_scale_enabled` can only be `true` for Elastic Premium, Premium V2 and Premium V3 Plans")

} else if !servicePlan.ElasticScaling && helpers.PlanIsElastic(&servicePlan.Sku) {
return fmt.Errorf("`elastic_scale_enabled` cannot be disabled for elastic plans")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for consistency:

Suggested change
return fmt.Errorf("`elastic_scale_enabled` cannot be disabled for elastic plans")
return fmt.Errorf("`elastic_scale_enabled` cannot be disabled for Elastic Premuim plans")

}

if servicePlan.WorkerCount != 0 {
appServicePlan.Sku.Capacity = pointer.To(int64(servicePlan.WorkerCount))
}
Expand Down Expand Up @@ -271,7 +288,10 @@ func (r ServicePlanResource) Read() sdk.ResourceFunc {
state.ZoneBalancing = utils.NormaliseNilableBool(props.ZoneRedundant)

state.MaximumElasticWorkerCount = int(pointer.From(props.MaximumElasticWorkerCount))

state.ElasticScaling = pointer.From(props.ElasticScaleEnabled)
}

state.Tags = pointer.From(model.Tags)
}

Expand Down Expand Up @@ -345,12 +365,19 @@ func (r ServicePlanResource) Update() sdk.ResourceFunc {
}

if metadata.ResourceData.HasChange("maximum_elastic_worker_count") {
if metadata.ResourceData.HasChange("maximum_elastic_worker_count") && !isServicePlanSupportScaleOut(state.Sku) {
return fmt.Errorf("`maximum_elastic_worker_count` can only be specified with Elastic Premium Skus")
if metadata.ResourceData.HasChange("maximum_elastic_worker_count") && (!isServicePlanSupportScaleOut(state.Sku) || helpers.PlanIsPremium(state.Sku) && !state.ElasticScaling) {
return fmt.Errorf("`maximum_elastic_worker_count` can only be specified with Elastic Premium Skus or Premium v2, v3 with `elastic_scale_enabled` set to true")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as above:

Suggested change
return fmt.Errorf("`maximum_elastic_worker_count` can only be specified with Elastic Premium Skus or Premium v2, v3 with `elastic_scale_enabled` set to true")
return fmt.Errorf("`maximum_elastic_worker_count` can only be specified with Elastic Premium Plans, or with Premium V2 or Premium V3 plans with `elastic_scale_enabled` set to true")```

}
model.Properties.MaximumElasticWorkerCount = pointer.To(int64(state.MaximumElasticWorkerCount))
}

if metadata.ResourceData.HasChange("elastic_scale_enabled") {
if state.ElasticScaling && !(isServicePlanSupportScaleOut(state.Sku) || helpers.PlanIsPremium(state.Sku)) {
return fmt.Errorf("`elastic_scale_enabled` can only be enabled for Elastic Premium Skus or Premium V2 and V3 Skus")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return fmt.Errorf("`elastic_scale_enabled` can only be enabled for Elastic Premium Skus or Premium V2 and V3 Skus")
return fmt.Errorf("`elastic_scale_enabled` can only be `true` for Elastic Premium, Premium V2 and Premium V3 Plans")

}
model.Properties.ElasticScaleEnabled = pointer.To(state.ElasticScaling)
}

if err = client.CreateOrUpdateThenPoll(ctx, *id, model); err != nil {
return fmt.Errorf("updating %s: %+v", id, err)
}
Expand All @@ -376,3 +403,20 @@ func (r ServicePlanResource) StateUpgraders() sdk.StateUpgradeData {
},
}
}

func (s ServicePlanResource) CustomizeDiff() sdk.ResourceFunc {
return sdk.ResourceFunc{
Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error {
rd := metadata.ResourceDiff
sku := rd.Get("sku_name").(string)

if helpers.PlanIsElastic(&sku) {
elasticScalingEnabled := rd.Get("elastic_scale_enabled").(bool)
if rd.HasChange("elastic_scale_enabled") && !elasticScalingEnabled {
return fmt.Errorf("`elastic_scale_enabled` cannot be disabled for elastic plan")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return fmt.Errorf("`elastic_scale_enabled` cannot be disabled for elastic plan")
return fmt.Errorf("`elastic_scale_enabled` cannot be disabled for Elastic Premium Plans")

}
}
return nil
},
}
}
156 changes: 156 additions & 0 deletions internal/services/appservice/service_plan_resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package appservice_test
import (
"context"
"fmt"
"regexp"
"testing"

"github.com/hashicorp/go-azure-helpers/lang/response"
Expand Down Expand Up @@ -149,6 +150,78 @@ func TestAccServicePlan_maxElasticWorkerCount(t *testing.T) {
})
}

func TestAccServicePlan_premiumElasticScaling(t *testing.T) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add a test that updates a non-scaling plan type to scaling with the new property set to true, and then back again? e.g. r.basic -> r.premiumElasticScale -> r.basic to confirm the collective changes work in a single apply?

data := acceptance.BuildTestData(t, "azurerm_service_plan", "test")
r := ServicePlanResource{}

data.ResourceTest(t, r, []acceptance.TestStep{
{
Config: r.complete(data),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
data.ImportStep(),
{
Config: r.premiumElasticScale(data, 5, "P1v3"),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
data.ImportStep(),
{
Config: r.complete(data),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
data.ImportStep(),
})
}

func TestAccServicePlan_premiumElasticScalingErrorUpdate(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_service_plan", "test")
r := ServicePlanResource{}

data.ResourceTest(t, r, []acceptance.TestStep{
{
Config: r.premiumElasticScaleError(data, "S1"),
ExpectError: regexp.MustCompile("`elastic_scale_enabled` can only be enabled for Elastic Premium Skus or Premium V2 and V3 Skus"),
},
{
Config: r.premiumElasticScaleError(data, "P1v3"),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
data.ImportStep(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An expectError test should only perform that one check. The property should be being checked for correct/successful use elsewhere.

Suggested change
{
Config: r.premiumElasticScaleError(data, "P1v3"),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
data.ImportStep(),

})
}

func TestAccServicePlan_premiumElasticScalingError(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_service_plan", "test")
r := ServicePlanResource{}

data.ResourceTest(t, r, []acceptance.TestStep{
{
Config: r.premiumElasticScaleError(data, "S1"),
ExpectError: regexp.MustCompile("`elastic_scale_enabled` can only be enabled for Elastic Premium Skus or Premium V2 and V3 Skus"),
},
})
}

func TestAccServicePlan_premiumElasticScalingErrorElasticPlan(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_service_plan", "test")
r := ServicePlanResource{}

data.ResourceTest(t, r, []acceptance.TestStep{
{
Config: r.premiumElasticScaleErrorElasticPlan(data, "EP1"),
ExpectError: regexp.MustCompile("`elastic_scale_enabled` cannot be disabled for elastic plan"),
},
})
}

func TestAccServicePlan_memoryOptimized(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_service_plan", "test")
r := ServicePlanResource{}
Expand Down Expand Up @@ -362,6 +435,7 @@ resource "azurerm_service_plan" "test" {
sku_name = "EP1"
os_type = "Linux"
maximum_elastic_worker_count = %[3]d
elastic_scale_enabled = true

tags = {
environment = "AccTest"
Expand Down Expand Up @@ -389,6 +463,35 @@ resource "azurerm_service_plan" "test" {
sku_name = "%[3]s"
os_type = "Linux"
maximum_elastic_worker_count = %[4]d
elastic_scale_enabled = true

tags = {
environment = "AccTest"
Foo = "bar"
}
}
`, data.RandomInteger, data.Locations.Primary, sku, count)
}

func (r ServicePlanResource) premiumElasticScale(data acceptance.TestData, count int, sku string) string {
return fmt.Sprintf(`
provider "azurerm" {
features {}
}

resource "azurerm_resource_group" "test" {
name = "acctestRG-appserviceplan-%[1]d"
location = "%s"
}

resource "azurerm_service_plan" "test" {
name = "acctest-SP-%[1]d"
resource_group_name = azurerm_resource_group.test.name
location = azurerm_resource_group.test.location
sku_name = "%[3]s"
os_type = "Linux"
elastic_scale_enabled = true
maximum_elastic_worker_count = %[4]d

tags = {
environment = "AccTest"
Expand All @@ -398,6 +501,59 @@ resource "azurerm_service_plan" "test" {
`, data.RandomInteger, data.Locations.Primary, sku, count)
}

func (r ServicePlanResource) premiumElasticScaleError(data acceptance.TestData, sku string) string {
return fmt.Sprintf(`
provider "azurerm" {
features {}
}

resource "azurerm_resource_group" "test" {
name = "acctestRG-appserviceplan-%[1]d"
location = "%s"
}

resource "azurerm_service_plan" "test" {
name = "acctest-SP-%[1]d"
resource_group_name = azurerm_resource_group.test.name
location = azurerm_resource_group.test.location
sku_name = "%[3]s"
os_type = "Linux"
elastic_scale_enabled = true

tags = {
environment = "AccTest"
Foo = "bar"
}
}
`, data.RandomInteger, data.Locations.Primary, sku)
}

func (r ServicePlanResource) premiumElasticScaleErrorElasticPlan(data acceptance.TestData, sku string) string {
return fmt.Sprintf(`
provider "azurerm" {
features {}
}

resource "azurerm_resource_group" "test" {
name = "xiaxintestRG-appserviceplan-%[1]d"
location = "%s"
}

resource "azurerm_service_plan" "test" {
name = "xiaxintest-SP-%[1]d"
resource_group_name = azurerm_resource_group.test.name
location = azurerm_resource_group.test.location
sku_name = "%[3]s"
os_type = "Linux"

tags = {
environment = "AccTest"
Foo = "bar"
}
}
`, data.RandomInteger, data.Locations.Primary, sku)
}

func (r ServicePlanResource) memoryOptimized(data acceptance.TestData) string {
return fmt.Sprintf(`
provider "azurerm" {
Expand Down
4 changes: 2 additions & 2 deletions vendor/modules.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1214,8 +1214,8 @@ github.com/hashicorp/terraform-svchost
# github.com/hashicorp/yamux v0.1.1
## explicit; go 1.15
github.com/hashicorp/yamux
# github.com/kr/text v0.2.0
## explicit
# github.com/kr/pretty v0.3.0
## explicit; go 1.12
# github.com/magodo/terraform-provider-azurerm-example-gen v0.0.0-20220407025246-3a3ee0ab24a8
## explicit; go 1.16
github.com/magodo/terraform-provider-azurerm-example-gen/examplegen
Expand Down
6 changes: 5 additions & 1 deletion website/docs/r/service_plan.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,13 @@ The following arguments are supported:

* `maximum_elastic_worker_count` - (Optional) The maximum number of workers to use in an Elastic SKU Plan. Cannot be set unless using an Elastic SKU.

* `elastic_scale_enabled` - (Optional) Should Elastic Scale be enabled for the premium site. Defaults to `false`.

~> **NOTE:** This feature is currently in preview and only available for Premium V2 (P1V2, P2V2, P3V2) and Premium V3 (P1V3, P2V3, P3V3) pricing tiers, and supported for all app types: Windows, Linux, and Windows container. It is not supported for deployment slot traffic The feature is enabled by default and cannot be disabled for Elastic plan such as EP1.

* `worker_count` - (Optional) The number of Workers (instances) to be allocated.

* `per_site_scaling_enabled` - (Optional) Should Per Site Scaling be enabled. Defaults to `false`.
* `elastic_scale_enabled` - (Optional) Should Per Site Scaling be enabled. Defaults to `false`.

* `zone_balancing_enabled` - (Optional) Should the Service Plan balance across Availability Zones in the region. Changing this forces a new resource to be created.

Expand Down
Loading