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_signalr_service: Expose upstream_endpoint #10459

Merged
merged 15 commits into from
Mar 1, 2021
Merged
2 changes: 1 addition & 1 deletion GNUmakefile
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ testacc: fmtcheck
TF_ACC=1 go test $(TEST) -v $(TESTARGS) -timeout $(TESTTIMEOUT) -ldflags="-X=github.com/terraform-providers/terraform-provider-azurerm/version.ProviderVersion=acc"

acctests: fmtcheck
TF_ACC=1 go test -v ./azurerm/internal/services/$(SERVICE)/tests/ $(TESTARGS) -timeout $(TESTTIMEOUT) -ldflags="-X=github.com/terraform-providers/terraform-provider-azurerm/version.ProviderVersion=acc"
TF_ACC=1 go test -v ./azurerm/internal/services/$(SERVICE) $(TESTARGS) -timeout $(TESTTIMEOUT) -ldflags="-X=github.com/terraform-providers/terraform-provider-azurerm/version.ProviderVersion=acc"
tombuildsstuff marked this conversation as resolved.
Show resolved Hide resolved

debugacc: fmtcheck
TF_ACC=1 dlv test $(TEST) --headless --listen=:2345 --api-version=2 -- -test.v $(TESTARGS)
Expand Down
2 changes: 1 addition & 1 deletion azurerm/internal/services/signalr/client/client.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package client

import (
"github.com/Azure/azure-sdk-for-go/services/signalr/mgmt/2018-10-01/signalr"
"github.com/Azure/azure-sdk-for-go/services/signalr/mgmt/2020-05-01/signalr"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/common"
)

Expand Down
162 changes: 149 additions & 13 deletions azurerm/internal/services/signalr/signalr_service_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,18 @@ package signalr
import (
"fmt"
"log"
"strings"
"time"

"github.com/Azure/azure-sdk-for-go/services/signalr/mgmt/2018-10-01/signalr"
"github.com/Azure/azure-sdk-for-go/services/signalr/mgmt/2020-05-01/signalr"
"github.com/hashicorp/go-azure-helpers/response"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/helper/validation"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/signalr/parse"
signalrValidate "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/signalr/validate"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tags"
azSchema "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/schema"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts"
Expand Down Expand Up @@ -100,6 +102,47 @@ func resourceArmSignalRService() *schema.Resource {
},
},

"upstream_endpoint": {
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"category_pattern": {
Type: schema.TypeList,
Required: true,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validation.StringIsNotEmpty,
},
},
katbyte marked this conversation as resolved.
Show resolved Hide resolved

"event_pattern": {
Type: schema.TypeList,
Required: true,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validation.StringIsNotEmpty,
},
},
katbyte marked this conversation as resolved.
Show resolved Hide resolved

"hub_pattern": {
Type: schema.TypeList,
Required: true,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validation.StringIsNotEmpty,
},
},
katbyte marked this conversation as resolved.
Show resolved Hide resolved

"url_template": {
Type: schema.TypeString,
Required: true,
ValidateFunc: signalrValidate.UrlTemplate,
},
katbyte marked this conversation as resolved.
Show resolved Hide resolved
},
},
},

"cors": {
Type: schema.TypeList,
Optional: true,
Expand Down Expand Up @@ -189,20 +232,29 @@ func resourceArmSignalRServiceCreate(d *schema.ResourceData, meta interface{}) e
featureFlags := d.Get("features").(*schema.Set).List()
cors := d.Get("cors").([]interface{})
expandedTags := tags.Expand(t)
upstreamSettings := d.Get("upstream_endpoint").(*schema.Set).List()

expandedFeatures := expandSignalRFeatures(featureFlags)

properties := &signalr.CreateOrUpdateProperties{
// Upstream configurations are only allowed when the SignalR service is in `Serverless` mode
if len(upstreamSettings) > 0 && !signalRIsInServerlessMode(expandedFeatures) {
return fmt.Errorf("Upstream configurations are only allowed when the SignalR Service is in `Serverless` mode")
}

properties := &signalr.Properties{
Cors: expandSignalRCors(cors),
Features: expandSignalRFeatures(featureFlags),
Features: expandedFeatures,
Upstream: expandUpstreamSettings(upstreamSettings),
}

parameters := &signalr.CreateParameters{
resourceType := &signalr.ResourceType{
Location: utils.String(location),
Sku: expandSignalRServiceSku(sku),
Tags: expandedTags,
Properties: properties,
}

future, err := client.CreateOrUpdate(ctx, resourceGroup, name, parameters)
future, err := client.CreateOrUpdate(ctx, resourceGroup, name, resourceType)
if err != nil {
return fmt.Errorf("Error creating or updating SignalR %q (Resource Group %q): %+v", name, resourceGroup, err)
}
Expand Down Expand Up @@ -271,6 +323,10 @@ func resourceArmSignalRServiceRead(d *schema.ResourceData, meta interface{}) err
if err := d.Set("cors", flattenSignalRCors(properties.Cors)); err != nil {
return fmt.Errorf("Error setting `cors`: %+v", err)
}

if err := d.Set("upstream_endpoint", flattenUpstreamSettings(properties.Upstream)); err != nil {
return fmt.Errorf("Error setting `upstream_endpoint`: %+v", err)
}
}

d.Set("primary_access_key", keys.PrimaryKey)
Expand All @@ -291,33 +347,38 @@ func resourceArmSignalRServiceUpdate(d *schema.ResourceData, meta interface{}) e
return err
}

parameters := &signalr.UpdateParameters{}
resourceType := &signalr.ResourceType{}

if d.HasChanges("cors", "features") {
parameters.Properties = &signalr.CreateOrUpdateProperties{}
if d.HasChanges("cors", "features", "upstream_endpoint") {
resourceType.Properties = &signalr.Properties{}

if d.HasChange("cors") {
corsRaw := d.Get("cors").([]interface{})
parameters.Properties.Cors = expandSignalRCors(corsRaw)
resourceType.Properties.Cors = expandSignalRCors(corsRaw)
}

if d.HasChange("features") {
featuresRaw := d.Get("features").(*schema.Set).List()
parameters.Properties.Features = expandSignalRFeatures(featuresRaw)
resourceType.Properties.Features = expandSignalRFeatures(featuresRaw)
}

if d.HasChange("upstream_endpoint") {
featuresRaw := d.Get("upstream_endpoint").(*schema.Set).List()
resourceType.Properties.Upstream = expandUpstreamSettings(featuresRaw)
}
}

if d.HasChange("sku") {
sku := d.Get("sku").([]interface{})
parameters.Sku = expandSignalRServiceSku(sku)
resourceType.Sku = expandSignalRServiceSku(sku)
}

if d.HasChange("tags") {
tagsRaw := d.Get("tags").(map[string]interface{})
parameters.Tags = tags.Expand(tagsRaw)
resourceType.Tags = tags.Expand(tagsRaw)
}

future, err := client.Update(ctx, id.ResourceGroup, id.SignalRName, parameters)
future, err := client.Update(ctx, id.ResourceGroup, id.SignalRName, resourceType)
if err != nil {
return fmt.Errorf("updating SignalR Service %q (Resource Group %q): %+v", id.SignalRName, id.ResourceGroup, err)
}
Expand Down Expand Up @@ -354,6 +415,20 @@ func resourceArmSignalRServiceDelete(d *schema.ResourceData, meta interface{}) e
return nil
}

func signalRIsInServerlessMode(features *[]signalr.Feature) bool {
if features == nil {
return false
}

for _, feature := range *features {
if feature.Flag == signalr.ServiceMode && feature.Value != nil {
return *feature.Value == "Serverless"
}
}

return false
}

func expandSignalRFeatures(input []interface{}) *[]signalr.Feature {
features := make([]signalr.Feature, 0)
for _, featureValue := range input {
Expand Down Expand Up @@ -390,6 +465,67 @@ func flattenSignalRFeatures(features *[]signalr.Feature) []interface{} {
return result
}

func expandUpstreamSettings(input []interface{}) *signalr.ServerlessUpstreamSettings {
upstreamTemplates := make([]signalr.UpstreamTemplate, 0)

for _, upstreamSetting := range input {
setting := upstreamSetting.(map[string]interface{})

upstreamTemplate := signalr.UpstreamTemplate{
HubPattern: utils.String(strings.Join(*utils.ExpandStringSlice(setting["hub_pattern"].([]interface{})), ",")),
EventPattern: utils.String(strings.Join(*utils.ExpandStringSlice(setting["event_pattern"].([]interface{})), ",")),
CategoryPattern: utils.String(strings.Join(*utils.ExpandStringSlice(setting["category_pattern"].([]interface{})), ",")),
URLTemplate: utils.String(setting["url_template"].(string)),
}

upstreamTemplates = append(upstreamTemplates, upstreamTemplate)
}

return &signalr.ServerlessUpstreamSettings{
Templates: &upstreamTemplates,
}
}

func flattenUpstreamSettings(upstreamSettings *signalr.ServerlessUpstreamSettings) []interface{} {
result := make([]interface{}, 0)
if upstreamSettings == nil || upstreamSettings.Templates == nil {
return result
}

for _, settings := range *upstreamSettings.Templates {
categoryPattern := make([]interface{}, 0)
if settings.CategoryPattern != nil {
categoryPatterns := strings.Split(*settings.CategoryPattern, ",")
categoryPattern = utils.FlattenStringSlice(&categoryPatterns)
}

eventPattern := make([]interface{}, 0)
if settings.EventPattern != nil {
eventPatterns := strings.Split(*settings.EventPattern, ",")
eventPattern = utils.FlattenStringSlice(&eventPatterns)
}

hubPattern := make([]interface{}, 0)
if settings.HubPattern != nil {
hubPatterns := strings.Split(*settings.HubPattern, ",")
hubPattern = utils.FlattenStringSlice(&hubPatterns)
}

urlTemplate := ""
if settings.URLTemplate != nil {
urlTemplate = *settings.URLTemplate
}

result = append(result, map[string]interface{}{
"url_template": urlTemplate,
"hub_pattern": hubPattern,
"event_pattern": eventPattern,
"category_pattern": categoryPattern,
})
}
return result
}

func expandSignalRCors(input []interface{}) *signalr.CorsSettings {
corsSettings := signalr.CorsSettings{}

Expand Down
83 changes: 83 additions & 0 deletions azurerm/internal/services/signalr/signalr_service_resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,22 @@ func TestAccSignalRService_cors(t *testing.T) {
})
}

func TestAccSignalRService_upstreamSetting(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_signalr_service", "test")
r := SignalRServiceResource{}

data.ResourceTest(t, r, []resource.TestStep{
{
Config: r.withUpstreamEndpoints(data),
Check: resource.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
check.That(data.ResourceName).Key("upstream_endpoint.#").HasValue("4"),
),
},
data.ImportStep(),
})
}

func (r SignalRServiceResource) Exists(ctx context.Context, client *clients.Client, state *terraform.InstanceState) (*bool, error) {
id, err := parse.ServiceID(state.ID)
if err != nil {
Expand Down Expand Up @@ -481,3 +497,70 @@ resource "azurerm_signalr_service" "test" {
}
`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, serviceMode)
}

func (r SignalRServiceResource) withUpstreamEndpoints(data acceptance.TestData) string {
return fmt.Sprintf(`
provider "azurerm" {
features {}
}

resource "azurerm_resource_group" "test" {
name = "acctestRG-%d"
location = "%s"
}

resource "azurerm_signalr_service" "test" {
name = "acctestSignalR-%d"
location = azurerm_resource_group.test.location
resource_group_name = azurerm_resource_group.test.name

sku {
name = "Free_F1"
capacity = 1
}

features {
flag = "ServiceMode"
value = "Serverless"
}

features {
flag = "EnableConnectivityLogs"
value = "False"
}

features {
flag = "EnableMessagingLogs"
value = "False"
}

upstream_endpoint {
category_pattern = ["*"]
event_pattern = ["*"]
hub_pattern = ["*"]
url_template = "http://foo.com/{hub}/api/{category}/{event}"
}

upstream_endpoint {
category_pattern = ["connections", "messages"]
event_pattern = ["*"]
hub_pattern = ["hub1"]
url_template = "http://foo.com"
}

upstream_endpoint {
category_pattern = ["*"]
event_pattern = ["connect", "disconnect"]
hub_pattern = ["hub1", "hub2"]
url_template = "http://foo3.com"
}

upstream_endpoint {
category_pattern = ["connections"]
event_pattern = ["disconnect"]
hub_pattern = ["*"]
url_template = "http://foo4.com"
}
}
`, data.RandomInteger, data.Locations.Primary, data.RandomInteger)
}
17 changes: 17 additions & 0 deletions azurerm/internal/services/signalr/validate/upstream.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package validate

import (
"fmt"
"regexp"
)

func UrlTemplate(v interface{}, k string) (warnings []string, errors []error) {
upstreamURL := v.(string)

if !regexp.MustCompile(`^https?://[^\s]+$`).MatchString(upstreamURL) {
errors = append(errors, fmt.Errorf(
"%q must start with http:// or https:// and must not contain whitespaces: %q", k, upstreamURL))
}

return warnings, errors
}
Loading