-
Notifications
You must be signed in to change notification settings - Fork 4.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
New Resource: Azure Front Door (#3933)
* WIP: Initial checkin * Updates to Frontdoor * PIP * PIP * Finalized Schema * WIP Current state * WIP adding validation * WIP Finished validation code * WIP Expand RoutingRules working * WIP All expand func implemented * WIP: Functional CreateUpdate done * Cast values and start on flatten functions * WIP: Last fix routing rules * WIP: Fully working Frontdoor * WIP: Wasnt exactly done... fixed destroy * gofmt code * WIP: Added first test * Fixed casing issue when interacting with portal * Fix bug in fronend endpoint enum * Bug fix for Frontend Endpoint flatten * Add data source * Slight update to documentation * Added Frontend client code * Extending frontend endpoints schema * schema and exposed enable and disable for frontend * Enabled custom HTTPS domain setting * [WIP] Last code changes * Tweeking defaults and adding validation rules * [WIP] Update test case * Updated documentation and exposed cname * fixed flatten for Cert Source AzureKeyVault * Update to latest * Few changes per PR review * Fixed lint errors * Missed one client declaration * Fix test client * Removed data source fix lint errs * update CheckDestroy func * Update website/docs/r/front_door.html.markdown Co-Authored-By: kt <kt@katbyte.me> * Update azurerm/resource_arm_front_door.go Co-Authored-By: kt <kt@katbyte.me> * Update azurerm/resource_arm_front_door.go Co-Authored-By: kt <kt@katbyte.me> * Update test and docs * Added test case * Update test per PR * Address PR issues * Update website/docs/r/front_door.html.markdown Co-Authored-By: kt <kt@katbyte.me> * Added link to internal bug tracking casing issue * gofmt * Fixes per PR comments * Moving package(s) and refactor * gofmt
- Loading branch information
Showing
21 changed files
with
10,288 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package frontdoor | ||
|
||
import ( | ||
"github.com/Azure/azure-sdk-for-go/services/preview/frontdoor/mgmt/2019-04-01/frontdoor" | ||
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/common" | ||
) | ||
|
||
type Client struct { | ||
FrontDoorsClient *frontdoor.FrontDoorsClient | ||
FrontDoorsFrontendClient *frontdoor.FrontendEndpointsClient | ||
} | ||
|
||
func BuildClient(o *common.ClientOptions) *Client { | ||
frontDoorsClient := frontdoor.NewFrontDoorsClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) | ||
o.ConfigureClient(&frontDoorsClient.Client, o.ResourceManagerAuthorizer) | ||
|
||
frontDoorsFrontendClient := frontdoor.NewFrontendEndpointsClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) | ||
o.ConfigureClient(&frontDoorsFrontendClient.Client, o.ResourceManagerAuthorizer) | ||
|
||
return &Client{ | ||
FrontDoorsClient: &frontDoorsClient, | ||
FrontDoorsFrontendClient: &frontDoorsFrontendClient, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,180 @@ | ||
package frontdoor | ||
|
||
import ( | ||
"fmt" | ||
"strings" | ||
|
||
"github.com/Azure/azure-sdk-for-go/services/preview/frontdoor/mgmt/2019-04-01/frontdoor" | ||
) | ||
|
||
func VerifyBackendPoolExists(backendPoolName string, backendPools []interface{}) error { | ||
if backendPoolName == "" { | ||
return fmt.Errorf(`"backend_pool_name" cannot be empty`) | ||
} | ||
|
||
for _, bps := range backendPools { | ||
backendPool := bps.(map[string]interface{}) | ||
if backendPool["name"].(string) == backendPoolName { | ||
return nil | ||
} | ||
} | ||
|
||
return fmt.Errorf(`unable to locate "backend_pool_name":%q in configuration file`, backendPoolName) | ||
} | ||
|
||
func AzureKeyVaultCertificateHasValues(customHttpsConfiguration map[string]interface{}, MatchAllKeys bool) bool { | ||
certificateSecretName := customHttpsConfiguration["azure_key_vault_certificate_secret_name"] | ||
certificateSecretVersion := customHttpsConfiguration["azure_key_vault_certificate_secret_version"] | ||
certificateVaultId := customHttpsConfiguration["azure_key_vault_certificate_vault_id"] | ||
|
||
if MatchAllKeys { | ||
if strings.TrimSpace(certificateSecretName.(string)) != "" && strings.TrimSpace(certificateSecretVersion.(string)) != "" && strings.TrimSpace(certificateVaultId.(string)) != "" { | ||
return true | ||
} | ||
} else { | ||
if strings.TrimSpace(certificateSecretName.(string)) != "" || strings.TrimSpace(certificateSecretVersion.(string)) != "" || strings.TrimSpace(certificateVaultId.(string)) != "" { | ||
return true | ||
} | ||
} | ||
|
||
return false | ||
} | ||
|
||
func IsFrontDoorFrontendEndpointConfigurable(currentState frontdoor.CustomHTTPSProvisioningState, customHttpsProvisioningEnabled bool, frontendEndpointName string, resourceGroup string) error { | ||
action := "disable" | ||
if customHttpsProvisioningEnabled { | ||
action = "enable" | ||
} | ||
|
||
switch currentState { | ||
case frontdoor.CustomHTTPSProvisioningStateDisabling, frontdoor.CustomHTTPSProvisioningStateEnabling, frontdoor.CustomHTTPSProvisioningStateFailed: | ||
return fmt.Errorf("Unable to %s the Front Door Frontend Endpoint %q (Resource Group %q) Custom Domain HTTPS state because the Frontend Endpoint is currently in the %q state", action, frontendEndpointName, resourceGroup, currentState) | ||
default: | ||
return nil | ||
} | ||
} | ||
|
||
func NormalizeCustomHTTPSProvisioningStateToBool(provisioningState frontdoor.CustomHTTPSProvisioningState) bool { | ||
isEnabled := false | ||
if provisioningState == frontdoor.CustomHTTPSProvisioningStateEnabled || provisioningState == frontdoor.CustomHTTPSProvisioningStateEnabling { | ||
isEnabled = true | ||
} | ||
|
||
return isEnabled | ||
} | ||
|
||
func GetFrontDoorBasicRouteConfigurationType(i interface{}) string { | ||
_, ok := i.(frontdoor.ForwardingConfiguration) | ||
if !ok { | ||
_, ok := i.(frontdoor.RedirectConfiguration) | ||
if !ok { | ||
return "" | ||
} | ||
return "RedirectConfiguration" | ||
} else { | ||
return "ForwardingConfiguration" | ||
} | ||
} | ||
func VerifyRoutingRuleFrontendEndpoints(routingRuleFrontends []interface{}, configFrontendEndpoints []interface{}) error { | ||
for _, routingRuleFrontend := range routingRuleFrontends { | ||
// Get the name of the frontend defined in the routing rule | ||
routingRulefrontendName := routingRuleFrontend.(string) | ||
found := false | ||
|
||
// Loop over all of the defined frontend endpoints in the config | ||
// seeing if we find the routing rule frontend in the list | ||
for _, configFrontendEndpoint := range configFrontendEndpoints { | ||
configFrontend := configFrontendEndpoint.(map[string]interface{}) | ||
configFrontendName := configFrontend["name"] | ||
if routingRulefrontendName == configFrontendName { | ||
found = true | ||
break | ||
} | ||
} | ||
|
||
if !found { | ||
return fmt.Errorf(`"frontend_endpoints":%q was not found in the configuration file. verify you have the "frontend_endpoint":%q defined in the configuration file`, routingRulefrontendName, routingRulefrontendName) | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func VerifyLoadBalancingAndHealthProbeSettings(backendPools []interface{}, loadBalancingSettings []interface{}, healthProbeSettings []interface{}) error { | ||
for _, bps := range backendPools { | ||
backendPool := bps.(map[string]interface{}) | ||
backendPoolName := backendPool["name"] | ||
backendPoolLoadBalancingName := backendPool["load_balancing_name"] | ||
backendPoolHealthProbeName := backendPool["health_probe_name"] | ||
found := false | ||
|
||
// Verify backend pool load balancing settings name exists | ||
if len(loadBalancingSettings) > 0 { | ||
for _, lbs := range loadBalancingSettings { | ||
loadBalancing := lbs.(map[string]interface{}) | ||
loadBalancingName := loadBalancing["name"] | ||
|
||
if loadBalancingName == backendPoolLoadBalancingName { | ||
found = true | ||
break | ||
} | ||
} | ||
|
||
if !found { | ||
return fmt.Errorf(`"backend_pool":%q "load_balancing_name":%q was not found in the configuration file. verify you have the "backend_pool_load_balancing":%q defined in the configuration file`, backendPoolName, backendPoolLoadBalancingName, backendPoolLoadBalancingName) | ||
} | ||
} | ||
|
||
found = false | ||
|
||
// Verify health probe settings name exists | ||
if len(healthProbeSettings) > 0 { | ||
for _, hps := range healthProbeSettings { | ||
healthProbe := hps.(map[string]interface{}) | ||
healthProbeName := healthProbe["name"] | ||
|
||
if healthProbeName == backendPoolHealthProbeName { | ||
found = true | ||
break | ||
} | ||
} | ||
|
||
if !found { | ||
return fmt.Errorf(`"backend_pool":%q "health_probe_name":%q was not found in the configuration file. verify you have the "backend_pool_health_probe":%q defined in the configuration file`, backendPoolName, backendPoolHealthProbeName, backendPoolHealthProbeName) | ||
} | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func VerifyCustomHttpsConfiguration(configFrontendEndpoints []interface{}) error { | ||
for _, configFrontendEndpoint := range configFrontendEndpoints { | ||
if configFrontend := configFrontendEndpoint.(map[string]interface{}); len(configFrontend) > 0 { | ||
FrontendName := configFrontend["name"] | ||
customHttpsEnabled := configFrontend["custom_https_provisioning_enabled"].(bool) | ||
|
||
if chc := configFrontend["custom_https_configuration"].([]interface{}); len(chc) > 0 { | ||
if !customHttpsEnabled { | ||
return fmt.Errorf(`"frontend_endpoint":%q "custom_https_configuration" is invalid because "custom_https_provisioning_enabled" is set to "false". please remove the "custom_https_configuration" block from the configuration file`, FrontendName) | ||
} | ||
|
||
customHttpsConfiguration := chc[0].(map[string]interface{}) | ||
certificateSource := customHttpsConfiguration["certificate_source"] | ||
if certificateSource == string(frontdoor.CertificateSourceAzureKeyVault) { | ||
if !AzureKeyVaultCertificateHasValues(customHttpsConfiguration, true) { | ||
return fmt.Errorf(`"frontend_endpoint":%q "custom_https_configuration" is invalid, all of the following keys must have values in the "custom_https_configuration" block: "azure_key_vault_certificate_secret_name", "azure_key_vault_certificate_secret_version", and "azure_key_vault_certificate_vault_id"`, FrontendName) | ||
} | ||
} else { | ||
if AzureKeyVaultCertificateHasValues(customHttpsConfiguration, false) { | ||
return fmt.Errorf(`"frontend_endpoint":%q "custom_https_configuration" is invalid, all of the following keys must be removed from the "custom_https_configuration" block: "azure_key_vault_certificate_secret_name", "azure_key_vault_certificate_secret_version", and "azure_key_vault_certificate_vault_id"`, FrontendName) | ||
} | ||
} | ||
} else if customHttpsEnabled { | ||
return fmt.Errorf(`"frontend_endpoint":%q configuration is invalid because "custom_https_provisioning_enabled" is set to "true" and the "custom_https_configuration" block is undefined. please add the "custom_https_configuration" block to the configuration file`, FrontendName) | ||
} | ||
} | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
package frontdoor | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/hashicorp/terraform/helper/schema" | ||
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/validate" | ||
) | ||
|
||
func ValidateFrontDoorName(i interface{}, k string) (_ []string, errors []error) { | ||
if m, regexErrs := validate.RegExHelper(i, k, `(^[\da-zA-Z])([-\da-zA-Z]{3,61})([\da-zA-Z]$)`); !m { | ||
errors = append(regexErrs, fmt.Errorf(`%q must be between 5 and 63 characters in length and begin with a letter or number, end with a letter or number and may contain only letters, numbers or hyphens.`, k)) | ||
} | ||
|
||
return nil, errors | ||
} | ||
|
||
func ValidateBackendPoolRoutingRuleName(i interface{}, k string) (_ []string, errors []error) { | ||
if m, regexErrs := validate.RegExHelper(i, k, `(^[\da-zA-Z])([-\da-zA-Z]{1,88})([\da-zA-Z]$)`); !m { | ||
errors = append(regexErrs, fmt.Errorf(`%q must be between 1 and 90 characters in length and begin with a letter or number, end with a letter or number and may contain only letters, numbers or hyphens.`, k)) | ||
} | ||
|
||
return nil, errors | ||
} | ||
|
||
func ValidateFrontdoorSettings(d *schema.ResourceDiff) error { | ||
routingRules := d.Get("routing_rule").([]interface{}) | ||
configFrontendEndpoints := d.Get("frontend_endpoint").([]interface{}) | ||
backendPools := d.Get("backend_pool").([]interface{}) | ||
loadBalancingSettings := d.Get("backend_pool_load_balancing").([]interface{}) | ||
healthProbeSettings := d.Get("backend_pool_health_probe").([]interface{}) | ||
|
||
if len(configFrontendEndpoints) == 0 { | ||
return fmt.Errorf(`"frontend_endpoint": must have at least one "frontend_endpoint" defined, found 0`) | ||
} | ||
|
||
// Loop over all of the Routing Rules and validate that only one type of configuration is defined per Routing Rule | ||
for _, rr := range routingRules { | ||
routingRule := rr.(map[string]interface{}) | ||
routingRuleName := routingRule["name"] | ||
redirectConfig := routingRule["redirect_configuration"].([]interface{}) | ||
forwardConfig := routingRule["forwarding_configuration"].([]interface{}) | ||
|
||
// Check 0. validate that at least one routing configuration exists per routing rule | ||
if len(redirectConfig) == 0 && len(forwardConfig) == 0 { | ||
return fmt.Errorf(`"routing_rule":%q is invalid. you must have either a "redirect_configuration" or a "forwarding_configuration" defined for the "routing_rule":%q `, routingRuleName, routingRuleName) | ||
} | ||
|
||
// Check 1. validate that only one configuration type is defined per routing rule | ||
if len(redirectConfig) == 1 && len(forwardConfig) == 1 { | ||
return fmt.Errorf(`"routing_rule":%q is invalid. "redirect_configuration" conflicts with "forwarding_configuration". You can only have one configuration type per each routing rule`, routingRuleName) | ||
} | ||
|
||
// Check 2. routing rule is a forwarding_configuration type make sure the backend_pool_name exists in the configuration file | ||
if len(forwardConfig) > 0 { | ||
fc := forwardConfig[0].(map[string]interface{}) | ||
if err := VerifyBackendPoolExists(fc["backend_pool_name"].(string), backendPools); err != nil { | ||
return fmt.Errorf(`"routing_rule":%q is invalid. %+v`, routingRuleName, err) | ||
} | ||
} | ||
|
||
// Check 3. validate that each routing rule frontend_endpoints are actually defined in the resource schema | ||
if routingRuleFrontends := routingRule["frontend_endpoints"].([]interface{}); len(routingRuleFrontends) > 0 { | ||
if err := VerifyRoutingRuleFrontendEndpoints(routingRuleFrontends, configFrontendEndpoints); err != nil { | ||
return fmt.Errorf(`"routing_rule":%q %+v`, routingRuleName, err) | ||
} | ||
} else { | ||
return fmt.Errorf(`"routing_rule": %q must have at least one "frontend_endpoints" defined`, routingRuleName) | ||
} | ||
} | ||
|
||
// Verify backend pool load balancing settings and health probe settings are defined in the resource schema | ||
if err := VerifyLoadBalancingAndHealthProbeSettings(backendPools, loadBalancingSettings, healthProbeSettings); err != nil { | ||
return fmt.Errorf(`%+v`, err) | ||
} | ||
|
||
// Verify frontend endpoints custom https configuration is valid if defined | ||
if err := VerifyCustomHttpsConfiguration(configFrontendEndpoints); err != nil { | ||
return fmt.Errorf(`%+v`, err) | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.