Skip to content

Commit

Permalink
azurerm_container_apps - Add scale rules
Browse files Browse the repository at this point in the history
  • Loading branch information
jsok committed Apr 4, 2023
1 parent 57d8ac7 commit 2e26b8c
Show file tree
Hide file tree
Showing 5 changed files with 315 additions and 19 deletions.
12 changes: 10 additions & 2 deletions internal/services/containerapps/container_app_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,11 @@ func (r ContainerAppResource) Create() sdk.ResourceFunc {
return fmt.Errorf("invalid registry config for %s: %+v", id, err)
}

template, err := helpers.ExpandContainerAppTemplate(app.Template, metadata)
if err != nil {
return fmt.Errorf("invalid template config for %s: %+v", id, err)
}

containerApp := containerapps.ContainerApp{
Location: location.Normalize(env.Model.Location),
Properties: &containerapps.ContainerAppProperties{
Expand All @@ -192,7 +197,7 @@ func (r ContainerAppResource) Create() sdk.ResourceFunc {
Registries: registries,
},
ManagedEnvironmentId: pointer.To(app.ManagedEnvironmentId),
Template: helpers.ExpandContainerAppTemplate(app.Template, metadata),
Template: template,
},
Tags: tags.Expand(app.Tags),
}
Expand Down Expand Up @@ -392,7 +397,10 @@ func (r ContainerAppResource) Update() sdk.ResourceFunc {
model.Tags = tags.Expand(state.Tags)
}

model.Properties.Template = helpers.ExpandContainerAppTemplate(state.Template, metadata)
model.Properties.Template, err = helpers.ExpandContainerAppTemplate(state.Template, metadata)
if err != nil {
return fmt.Errorf("invalid template config for %s: %+v", id, err)
}

if err := client.CreateOrUpdateThenPoll(ctx, *id, *model); err != nil {
return fmt.Errorf("updating %s: %+v", *id, err)
Expand Down
21 changes: 21 additions & 0 deletions internal/services/containerapps/container_app_resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,27 @@ resource "azurerm_container_app" "test" {
min_replicas = 2
max_replicas = 3
scale {
rule {
name = "http-concurrency"
http {
metadata = {
concurrentRequests = "10"
}
}
}
rule {
name = "cpu-utilization"
custom {
type = "cpu"
metadata = {
type = "Utilization"
value = "90"
}
}
}
}
revision_suffix = "%[3]s"
}
Expand Down
215 changes: 198 additions & 17 deletions internal/services/containerapps/helpers/container_apps.go
Original file line number Diff line number Diff line change
Expand Up @@ -698,6 +698,7 @@ type ContainerTemplate struct {
Suffix string `tfschema:"revision_suffix"`
MinReplicas int `tfschema:"min_replicas"`
MaxReplicas int `tfschema:"max_replicas"`
Scale []ContainerScale `tfschema:"scale"`
Volumes []ContainerVolume `tfschema:"volume"`
}

Expand Down Expand Up @@ -726,6 +727,8 @@ func ContainerTemplateSchema() *pluginsdk.Schema {
Description: "The maximum number of replicas for this container.",
},

"scale": ContainerScaleSchema(),

"volume": ContainerVolumeSchema(),

"revision_suffix": {
Expand Down Expand Up @@ -759,6 +762,8 @@ func ContainerTemplateSchemaComputed() *pluginsdk.Schema {
Description: "The maximum number of replicas for this container.",
},

"scale": ContainerScaleSchema(),

"volume": ContainerVolumeSchema(),

"revision_suffix": {
Expand All @@ -771,38 +776,29 @@ func ContainerTemplateSchemaComputed() *pluginsdk.Schema {
}
}

func ExpandContainerAppTemplate(input []ContainerTemplate, metadata sdk.ResourceMetaData) *containerapps.Template {
func ExpandContainerAppTemplate(input []ContainerTemplate, metadata sdk.ResourceMetaData) (*containerapps.Template, error) {
if len(input) != 1 {
return nil
return nil, nil
}

config := input[0]
scale, err := expandContainerScale(config.Scale, config.MaxReplicas, config.MinReplicas)
if err != nil {
return nil, err
}
template := &containerapps.Template{
Containers: expandContainerAppContainers(config.Containers),
Scale: scale,
Volumes: expandContainerAppVolumes(config.Volumes),
}

if config.MaxReplicas != 0 {
if template.Scale == nil {
template.Scale = &containerapps.Scale{}
}
template.Scale.MaxReplicas = pointer.To(int64(config.MaxReplicas))
}

if config.MinReplicas != 0 {
if template.Scale == nil {
template.Scale = &containerapps.Scale{}
}
template.Scale.MinReplicas = pointer.To(int64(config.MinReplicas))
}

if config.Suffix != "" {
if metadata.ResourceData.HasChange("template.0.revision_suffix") {
template.RevisionSuffix = pointer.To(config.Suffix)
}
}

return template
return template, nil
}

func FlattenContainerAppTemplate(input *containerapps.Template) []ContainerTemplate {
Expand All @@ -811,6 +807,7 @@ func FlattenContainerAppTemplate(input *containerapps.Template) []ContainerTempl
}
result := ContainerTemplate{
Containers: flattenContainerAppContainers(input.Containers),
Scale: flattenContainerScale(input.Scale),
Suffix: pointer.From(input.RevisionSuffix),
Volumes: flattenContainerAppVolumes(input.Volumes),
}
Expand Down Expand Up @@ -1058,6 +1055,190 @@ func flattenContainerAppContainers(input *[]containerapps.Container) []Container
return result
}

type ContainerScale struct {
Rules []ContainerScaleRule `tfschema:"rule"`
}

type ContainerScaleRule struct {
Name string `tfschema:"name"`
Custom []ContainerCustomScaleRule `tfschema:"custom"`
HTTP []ContainerHTTPScaleRule `tfschema:"http"`
}

func ContainerScaleSchema() *pluginsdk.Schema {
return &pluginsdk.Schema{
Type: pluginsdk.TypeList,
MaxItems: 1,
Optional: true,
Elem: &pluginsdk.Resource{
Schema: map[string]*pluginsdk.Schema{
"rule": {
Type: pluginsdk.TypeList,
Optional: true,
MinItems: 1,
Elem: &pluginsdk.Resource{
Schema: map[string]*pluginsdk.Schema{
"name": {
Type: pluginsdk.TypeString,
Required: true,
ValidateFunc: validation.StringIsNotEmpty,
Description: "The name of the rule.",
},
"custom": &pluginsdk.Schema{
Type: pluginsdk.TypeList,
Optional: true,
MaxItems: 1,
Elem: &pluginsdk.Resource{
Schema: map[string]*pluginsdk.Schema{
"type": {
Type: pluginsdk.TypeString,
Required: true,
ValidateFunc: validation.StringIsNotEmpty,
Description: "The name of the volume.",
},
"metadata": &pluginsdk.Schema{
Type: pluginsdk.TypeMap,
Required: true,
Elem: &pluginsdk.Schema{
Type: pluginsdk.TypeString,
},
},
},
},
},
"http": &pluginsdk.Schema{
Type: pluginsdk.TypeList,
Optional: true,
MaxItems: 1,
Elem: &pluginsdk.Resource{
Schema: map[string]*pluginsdk.Schema{
"metadata": &pluginsdk.Schema{
Type: pluginsdk.TypeMap,
Optional: true,
Elem: &pluginsdk.Schema{
Type: pluginsdk.TypeString,
},
},
},
},
},
},
},
},
},
},
}
}

func expandContainerScale(scale []ContainerScale, maxReplicas, minReplicas int) (*containerapps.Scale, error) {
result := &containerapps.Scale{}

if maxReplicas != 0 {
result.MaxReplicas = pointer.To(int64(maxReplicas))
}
if minReplicas != 0 {
result.MinReplicas = pointer.To(int64(minReplicas))
}

if scale == nil {
return result, nil
}

rules := make([]containerapps.ScaleRule, 0)
for _, v := range scale {
for _, r := range v.Rules {
rule := containerapps.ScaleRule{}
rule.Name = pointer.To(r.Name)
rule.Custom = expandContainerCustomScaleRule(r.Custom)
rule.HTTP = expandContainerHTTPScaleRule(r.HTTP)
if rule.Custom != nil && rule.HTTP != nil {
return nil, fmt.Errorf("rule %q cannot specify multiple triggers", r.Name)
}
if rule.Custom == nil && rule.HTTP == nil {
return nil, fmt.Errorf("rule %q must specify at least one trigger: custom, http", r.Name)
}
rules = append(rules, rule)
}
}

result.Rules = &rules
return result, nil
}

func flattenContainerScale(input *containerapps.Scale) []ContainerScale {
if input == nil {
return nil
}
result := ContainerScale{}
rules := make([]ContainerScaleRule, 0)
for _, v := range *input.Rules {
rule := ContainerScaleRule{}
rule.Name = pointer.From(v.Name)
rule.Custom = flattenContainerCustomScaleRule(v.Custom)
rule.HTTP = flattenContainerHTTPScaleRule(v.HTTP)
rules = append(rules, rule)
}
result.Rules = rules
return []ContainerScale{result}
}

type ContainerHTTPScaleRule struct {
Metadata map[string]string `tfschema:"metadata"`
}

func expandContainerHTTPScaleRule(input []ContainerHTTPScaleRule) *containerapps.HTTPScaleRule {
if input == nil {
return nil
}
if len(input) != 1 {
return nil
}
rule := input[0]
return &containerapps.HTTPScaleRule{
Metadata: pointer.To(rule.Metadata),
}
}

func flattenContainerHTTPScaleRule(input *containerapps.HTTPScaleRule) []ContainerHTTPScaleRule {
if input == nil {
return nil
}
rule := ContainerHTTPScaleRule{
Metadata: pointer.From(input.Metadata),
}
return []ContainerHTTPScaleRule{rule}
}

type ContainerCustomScaleRule struct {
Type string `tfschema:"type"`
Metadata map[string]string `tfschema:"metadata"`
}

func expandContainerCustomScaleRule(input []ContainerCustomScaleRule) *containerapps.CustomScaleRule {
if input == nil {
return nil
}
if len(input) != 1 {
return nil
}
rule := input[0]
return &containerapps.CustomScaleRule{
Type: pointer.To(rule.Type),
Metadata: pointer.To(rule.Metadata),
}
}

func flattenContainerCustomScaleRule(input *containerapps.CustomScaleRule) []ContainerCustomScaleRule {
if input == nil {
return nil
}
rule := ContainerCustomScaleRule{
Metadata: pointer.From(input.Metadata),
Type: pointer.From(input.Type),
}
return []ContainerCustomScaleRule{rule}
}

type ContainerVolume struct {
Name string `tfschema:"name"`
StorageName string `tfschema:"storage_name"`
Expand Down
32 changes: 32 additions & 0 deletions website/docs/d/container_app.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ A `template` block supports the following:

* `min_replicas` - The minimum number of replicas for this container.

* `scale` - A `scale` block as detailed below.

* `revision_suffix` - The suffix for the revision. This value must be unique for the lifetime of the Resource. If omitted the service will use a hash function to create one.

* `volume` - A `volume` block as detailed below.
Expand Down Expand Up @@ -113,6 +115,36 @@ A `container` block supports the following:

---

A `scale` block supports the following:

* `rule` - One or more `rule` blocks as outlined below.

---

A `rule` block supports the following, `custom` and `http` are mutually exclusive:

* `name` - The name of the rule.

* `custom` - A `custom` block as detailed below.

* `http` - A `http` block as detailed below.

---

A `custom` block supports the following:

* `type` - The type of custom rule.

* `metadata` - Map of metadata values supplied to the custom scaler.

---

A `http` block supports the following:

* `metadata` - Map of metadata values supplied to the custom scaler.

---

A `liveness_probe` block supports the following:

* `failure_count_threshold` - The number of consecutive failures required to consider this probe as failed. Possible values are between `1` and `10`. Defaults to `3`.
Expand Down
Loading

0 comments on commit 2e26b8c

Please sign in to comment.