Skip to content

Commit

Permalink
azurem_container_app - Add support for sticky_sessions (hashicorp#24757)
Browse files Browse the repository at this point in the history
  • Loading branch information
bPhysicist committed Feb 9, 2024
1 parent e6ca542 commit ac77e9a
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"testing"

"github.com/hashicorp/terraform-provider-azurerm/internal/acceptance"
"github.com/hashicorp/terraform-provider-azurerm/internal/acceptance/check"
)

type ContainerAppDataSource struct{}
Expand All @@ -19,7 +20,9 @@ func TestAccContainerAppDataSource_basic(t *testing.T) {
data.DataSourceTest(t, []acceptance.TestStep{
{
Config: r.basic(data),
Check: acceptance.ComposeTestCheckFunc(),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).Key("ingress.0.sticky_sessions.0").DoesNotExist(),
),
},
})
}
Expand All @@ -31,7 +34,9 @@ func TestAccContainerAppDataSource_complete(t *testing.T) {
data.DataSourceTest(t, []acceptance.TestStep{
{
Config: r.complete(data, "rev1"),
Check: acceptance.ComposeTestCheckFunc(),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).Key("ingress.0.sticky_sessions.0.affinity").HasValue("sticky"),
),
},
})
}
Expand Down
9 changes: 8 additions & 1 deletion internal/services/containerapps/container_app_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -428,9 +428,16 @@ func (r ContainerAppResource) CustomizeDiff() sdk.ResourceFunc {
if err := metadata.DecodeDiff(&app); err != nil {
return err
}
// Ingress traffic weight validations

if len(app.Ingress) != 0 {
ingress := app.Ingress[0]
// Ingress sticky session validations
if len(ingress.StickySessions) != 0 &&
ingress.StickySessions[0].Affinity == string(containerapps.AffinitySticky) &&
app.RevisionMode != string(containerapps.ActiveRevisionsModeSingle) {
return fmt.Errorf("`sticky` session affinity can only be used in conjunction with `Single` `revision_mode`")
}
// Ingress traffic weight validations
if metadata.ResourceDiff.HasChange("name") {
// Validation for create time
// (Above is a trick to tell whether this is for a new create apply, as the "name" is a force new property)
Expand Down
55 changes: 49 additions & 6 deletions internal/services/containerapps/container_app_resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -494,13 +494,17 @@ func TestAccContainerAppResource_ingressTrafficValidation(t *testing.T) {
Config: r.ingressTrafficValidation(data, r.trafficBlockMoreThanOne()),
ExpectError: regexp.MustCompile(fmt.Sprintf(`at most one %s can be specified during creation`, "`ingress.0.traffic_weight`")),
},
})
}

func TestAccContainerAppResource_ingressStickySessionsValidation(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_container_app", "test")
r := ContainerAppResource{}

data.ResourceTest(t, r, []acceptance.TestStep{
{
Config: r.ingressTrafficValidation(data, r.trafficBlockLatestRevisionNotSet()),
ExpectError: regexp.MustCompile(fmt.Sprintf(`%s must be set to true during creation`, "`ingress.0.traffic_weight.0.latest_revision`")),
},
{
Config: r.ingressTrafficValidation(data, r.trafficBlockRevisionSuffixSet()),
ExpectError: regexp.MustCompile(fmt.Sprintf(`%s must not be set during creation`, "`ingress.0.traffic_weight.0.revision_suffix`")),
Config: r.ingressStickySessionsValidation(data, "sticky"),
ExpectError: regexp.MustCompile("`sticky` session affinity can only be used in conjunction with `Single` `revision_mode`"),
},
})
}
Expand Down Expand Up @@ -782,6 +786,9 @@ resource "azurerm_container_app" "test" {
ingress {
allow_insecure_connections = true
external_enabled = true
sticky_sessions {
affinity = "sticky"
}
target_port = 5000
transport = "http"
traffic_weight {
Expand Down Expand Up @@ -2114,6 +2121,42 @@ resource "azurerm_container_app" "test" {
`, r.template(data), data.RandomInteger, trafficBlock)
}

func (r ContainerAppResource) ingressStickySessionsValidation(data acceptance.TestData, affinity string) string {
return fmt.Sprintf(`
%s
resource "azurerm_container_app" "test" {
name = "acctest-capp-%[2]d"
resource_group_name = azurerm_resource_group.test.name
container_app_environment_id = azurerm_container_app_environment.test.id
revision_mode = "Multiple"
template {
container {
name = "acctest-cont-%[2]d"
image = "jackofallops/azure-containerapps-python-acctest:v0.0.1"
cpu = 0.25
memory = "0.5Gi"
}
}
ingress {
allow_insecure_connections = true
external_enabled = true
sticky_sessions {
affinity = "%s"
}
target_port = 5000
transport = "http"
traffic_weight {
latest_revision = true
percentage = 100
}
}
}
`, r.template(data), data.RandomInteger, affinity)
}

func (r ContainerAppResource) secretBasic(data acceptance.TestData) string {
return fmt.Sprintf(`
%s
Expand Down
73 changes: 73 additions & 0 deletions internal/services/containerapps/helpers/container_apps.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ type Ingress struct {
CustomDomains []CustomDomain `tfschema:"custom_domain"`
IsExternal bool `tfschema:"external_enabled"`
FQDN string `tfschema:"fqdn"`
StickySessions []StickySessions `tfschema:"sticky_sessions"`
TargetPort int `tfschema:"target_port"`
ExposedPort int `tfschema:"exposed_port"`
TrafficWeights []TrafficWeight `tfschema:"traffic_weight"`
Expand Down Expand Up @@ -189,6 +190,8 @@ func ContainerAppIngressSchema() *pluginsdk.Schema {

"ip_security_restriction": ContainerAppIngressIpSecurityRestriction(),

"sticky_sessions": ContainerAppIngressStickySessions(),

"target_port": {
Type: pluginsdk.TypeInt,
Required: true,
Expand Down Expand Up @@ -245,6 +248,8 @@ func ContainerAppIngressSchemaComputed() *pluginsdk.Schema {

"ip_security_restriction": ContainerAppIngressIpSecurityRestrictionComputed(),

"sticky_sessions": ContainerAppIngressStickySessionsComputed(),

"target_port": {
Type: pluginsdk.TypeInt,
Computed: true,
Expand Down Expand Up @@ -280,6 +285,7 @@ func ExpandContainerAppIngress(input []Ingress, appName string) *containerapps.I
CustomDomains: expandContainerAppIngressCustomDomain(ingress.CustomDomains),
External: pointer.To(ingress.IsExternal),
Fqdn: pointer.To(ingress.FQDN),
StickySessions: expandContainerAppIngressStickySessions(ingress.StickySessions),
TargetPort: pointer.To(int64(ingress.TargetPort)),
ExposedPort: pointer.To(int64(ingress.ExposedPort)),
Traffic: expandContainerAppIngressTraffic(ingress.TrafficWeights, appName),
Expand All @@ -302,6 +308,7 @@ func FlattenContainerAppIngress(input *containerapps.Ingress, appName string) []
CustomDomains: flattenContainerAppIngressCustomDomain(ingress.CustomDomains),
IsExternal: pointer.From(ingress.External),
FQDN: pointer.From(ingress.Fqdn),
StickySessions: flattenContainerAppIngressStickySessions(ingress.StickySessions),
TargetPort: int(pointer.From(ingress.TargetPort)),
ExposedPort: int(pointer.From(ingress.ExposedPort)),
TrafficWeights: flattenContainerAppIngressTraffic(ingress.Traffic, appName),
Expand Down Expand Up @@ -424,6 +431,19 @@ func flattenContainerAppIngressCustomDomain(input *[]containerapps.CustomDomain)
return result
}

func flattenContainerAppIngressStickySessions(input *containerapps.IngressStickySessions) []StickySessions {
if input == nil {
return []StickySessions{}
}

stickSessions := *input
result := StickySessions{
Affinity: string(*stickSessions.Affinity),
}

return []StickySessions{result}
}

func flattenContainerAppIngressIpSecurityRestrictions(input *[]containerapps.IPSecurityRestrictionRule) []IpSecurityRestriction {
if input == nil {
return []IpSecurityRestriction{}
Expand All @@ -444,6 +464,10 @@ func flattenContainerAppIngressIpSecurityRestrictions(input *[]containerapps.IPS
return result
}

type StickySessions struct {
Affinity string `tfschema:"affinity"`
}

type TrafficWeight struct {
Label string `tfschema:"label"`
LatestRevision bool `tfschema:"latest_revision"`
Expand Down Expand Up @@ -529,6 +553,43 @@ func ContainerAppIngressIpSecurityRestrictionComputed() *pluginsdk.Schema {
}
}

func ContainerAppIngressStickySessions() *pluginsdk.Schema {
return &pluginsdk.Schema{
Type: pluginsdk.TypeList,
Optional: true,
MaxItems: 1,
Elem: &pluginsdk.Resource{
Schema: map[string]*pluginsdk.Schema{
"affinity": {
Type: pluginsdk.TypeString,
ValidateFunc: validation.StringInSlice([]string{
string(containerapps.AffinityNone),
string(containerapps.AffinitySticky),
}, false),
Required: true,
Description: "Sticky sessions affinity. Possible values are `none`, and `sticky`.",
},
},
},
}
}

func ContainerAppIngressStickySessionsComputed() *pluginsdk.Schema {
return &pluginsdk.Schema{
Type: pluginsdk.TypeList,
Computed: true,
Elem: &pluginsdk.Resource{
Schema: map[string]*pluginsdk.Schema{
"affinity": {
Type: pluginsdk.TypeString,
Computed: true,
Description: "Sticky sessions affinity. Possible values are `none`, and `sticky`.",
},
},
},
}
}

func ContainerAppIngressTrafficWeight() *pluginsdk.Schema {
return &pluginsdk.Schema{
Type: pluginsdk.TypeList,
Expand Down Expand Up @@ -601,6 +662,18 @@ func ContainerAppIngressTrafficWeightComputed() *pluginsdk.Schema {
}
}

func expandContainerAppIngressStickySessions(input []StickySessions) *containerapps.IngressStickySessions {
if len(input) == 0 {
return nil
}

stickySessions := input[0]

return &containerapps.IngressStickySessions{
Affinity: pointer.To(containerapps.Affinity(stickySessions.Affinity)),
}
}

func expandContainerAppIngressTraffic(input []TrafficWeight, appName string) *[]containerapps.TrafficWeight {
if len(input) == 0 {
return nil
Expand Down
8 changes: 8 additions & 0 deletions website/docs/d/container_app.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,8 @@ An `ingress` block supports the following:

* `ip_security_restriction` - One or more `ip_security_restriction` blocks for IP-filtering rules as defined below.

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

* `target_port` - The target port on the container for the Ingress traffic.

* `traffic_weight` - A `traffic_weight` block as detailed below.
Expand Down Expand Up @@ -297,6 +299,12 @@ A `ip_security_restriction` block exports the following:

---

A `sticky_sessions` block exports the following:

* `affinity` - Sticky sessions affinity. Possible values are `none`, and `sticky`.

---

A `traffic_weight` block supports the following:

* `label` - The label to apply to the revision as a name prefix for routing traffic.
Expand Down
8 changes: 8 additions & 0 deletions website/docs/r/container_app.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,8 @@ An `ingress` block supports the following:

* `ip_security_restriction` - (Optional) One or more `ip_security_restriction` blocks for IP-filtering rules as defined below.

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

* `target_port` - (Required) The target port on the container for the Ingress traffic.

* `exposed_port` - (Optional) The exposed port on the container for the Ingress traffic.
Expand Down Expand Up @@ -409,6 +411,12 @@ A `ip_security_restriction` block supports the following:

---

A `sticky_sessions` block supports the following:

* `affinity` - Sticky sessions affinity. Possible values are `none`, and `sticky`. `sticky` can only be used in conjunction with a `Single` `revision_mode`.

---

A `traffic_weight` block supports the following:

~> **Note:** This block only applies when `revision_mode` is set to `Multiple`.
Expand Down

0 comments on commit ac77e9a

Please sign in to comment.