From 71545ffaf81fecca0ed741ce66674b4fc4816db8 Mon Sep 17 00:00:00 2001 From: Antonio Cabrera Date: Wed, 22 Aug 2018 10:49:15 -0500 Subject: [PATCH 01/40] Add virtual network gateway resource --- azurestack/config.go | 5 + azurestack/provider.go | 1 + .../resource_arm_virtual_network_gateway.go | 775 ++++++++++++++++++ ...source_arm_virtual_network_gateway_test.go | 539 ++++++++++++ 4 files changed, 1320 insertions(+) create mode 100644 azurestack/resource_arm_virtual_network_gateway.go create mode 100644 azurestack/resource_arm_virtual_network_gateway_test.go diff --git a/azurestack/config.go b/azurestack/config.go index 649e7de08..02bd96e2a 100644 --- a/azurestack/config.go +++ b/azurestack/config.go @@ -52,6 +52,7 @@ type ArmClient struct { ifaceClient network.InterfacesClient localNetConnClient network.LocalNetworkGatewaysClient secRuleClient network.SecurityRulesClient + vnetGatewayClient network.VirtualNetworkGatewaysClient // Resources providersClient resources.ProvidersClient @@ -219,6 +220,10 @@ func (c *ArmClient) registerNetworkingClients(endpoint, subscriptionId string, a c.configureClient(&interfacesClient.Client, auth) c.ifaceClient = interfacesClient + gatewaysClient := network.NewVirtualNetworkGatewaysClientWithBaseURI(endpoint, subscriptionId) + c.configureClient(&gatewaysClient.Client, auth) + c.vnetGatewayClient = gatewaysClient + localNetworkGatewaysClient := network.NewLocalNetworkGatewaysClientWithBaseURI(endpoint, subscriptionId) c.configureClient(&localNetworkGatewaysClient.Client, auth) c.localNetConnClient = localNetworkGatewaysClient diff --git a/azurestack/provider.go b/azurestack/provider.go index 35a33075e..4e030a5d6 100644 --- a/azurestack/provider.go +++ b/azurestack/provider.go @@ -94,6 +94,7 @@ func Provider() terraform.ResourceProvider { "azurestack_storage_container": resourceArmStorageContainer(), "azurestack_subnet": resourceArmSubnet(), "azurestack_virtual_network": resourceArmVirtualNetwork(), + "azurestack_virtual_network_gateway": resourceArmVirtualNetworkGateway(), "azurestack_virtual_machine": resourceArmVirtualMachine(), "azurestack_virtual_machine_extension": resourceArmVirtualMachineExtensions(), }, diff --git a/azurestack/resource_arm_virtual_network_gateway.go b/azurestack/resource_arm_virtual_network_gateway.go new file mode 100644 index 000000000..aec2753eb --- /dev/null +++ b/azurestack/resource_arm_virtual_network_gateway.go @@ -0,0 +1,775 @@ +package azurestack + +import ( + "bytes" + "fmt" + "log" + "strings" + + "github.com/Azure/azure-sdk-for-go/profiles/2017-03-09/network/mgmt/network" + "github.com/hashicorp/terraform/helper/hashcode" + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" + "github.com/terraform-providers/terraform-provider-azurestack/azurestack/helpers/validate" + "github.com/terraform-providers/terraform-provider-azurestack/azurestack/utils" +) + +func resourceArmVirtualNetworkGateway() *schema.Resource { + return &schema.Resource{ + Create: resourceArmVirtualNetworkGatewayCreateUpdate, + Read: resourceArmVirtualNetworkGatewayRead, + Update: resourceArmVirtualNetworkGatewayCreateUpdate, + Delete: resourceArmVirtualNetworkGatewayDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + CustomizeDiff: resourceArmVirtualNetworkGatewayCustomizeDiff, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "resource_group_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "location": locationSchema(), + + "type": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{ + string(network.VirtualNetworkGatewayTypeExpressRoute), + string(network.VirtualNetworkGatewayTypeVpn), + }, true), + DiffSuppressFunc: ignoreCaseDiffSuppressFunc, + }, + + "vpn_type": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Default: string(network.RouteBased), + ValidateFunc: validation.StringInSlice([]string{ + string(network.RouteBased), + string(network.PolicyBased), + }, true), + DiffSuppressFunc: ignoreCaseDiffSuppressFunc, + }, + + "enable_bgp": { + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + + "active_active": { + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + + "sku": { + Type: schema.TypeString, + Required: true, + DiffSuppressFunc: ignoreCaseDiffSuppressFunc, + ValidateFunc: validation.StringInSlice([]string{ + string(network.VirtualNetworkGatewaySkuTierBasic), + string(network.VirtualNetworkGatewaySkuTierStandard), + string(network.VirtualNetworkGatewaySkuTierHighPerformance), + string("VirtualNetworkGatewaySkuTierUltraPerformance"), + string("VirtualNetworkGatewaySkuNameVpnGw1"), + string("VirtualNetworkGatewaySkuNameVpnGw2"), + string("VirtualNetworkGatewaySkuNameVpnGw3"), + }, true), + }, + + "ip_configuration": { + Type: schema.TypeList, + Required: true, + MaxItems: 2, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Optional: true, + // Azure Management API requires a name but does not generate a name if the field is missing + // The name "vnetGatewayConfig" is used when creating a virtual network gateway via the + // Azure portal. + Default: "vnetGatewayConfig", + }, + "private_ip_address_allocation": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{ + string(network.Static), + string(network.Dynamic), + }, false), + Default: string(network.Dynamic), + }, + "subnet_id": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validateArmVirtualNetworkGatewaySubnetId, + DiffSuppressFunc: ignoreCaseDiffSuppressFunc, + }, + "public_ip_address_id": { + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + + "vpn_client_configuration": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "address_space": { + Type: schema.TypeList, + Required: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "root_certificate": { + Type: schema.TypeSet, + Optional: true, + + ConflictsWith: []string{ + "vpn_client_configuration.0.radius_server_address", + "vpn_client_configuration.0.radius_server_secret", + }, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + "public_cert_data": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + Set: hashVirtualNetworkGatewayRootCert, + }, + "revoked_certificate": { + Type: schema.TypeSet, + Optional: true, + ConflictsWith: []string{ + "vpn_client_configuration.0.radius_server_address", + "vpn_client_configuration.0.radius_server_secret", + }, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + "thumbprint": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + Set: hashVirtualNetworkGatewayRevokedCert, + }, + "radius_server_address": { + Type: schema.TypeString, + Optional: true, + ConflictsWith: []string{ + "vpn_client_configuration.0.root_certificate", + "vpn_client_configuration.0.revoked_certificate", + }, + ValidateFunc: validate.IPv4Address, + }, + "radius_server_secret": { + Type: schema.TypeString, + Optional: true, + ConflictsWith: []string{ + "vpn_client_configuration.0.root_certificate", + "vpn_client_configuration.0.revoked_certificate", + }, + }, + "vpn_client_protocols": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringInSlice([]string{ + string("IkeV2"), + string("SSTP"), + }, true), + }, + }, + }, + }, + }, + + "bgp_settings": { + Type: schema.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "asn": { + Type: schema.TypeInt, + Optional: true, + }, + "peering_address": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + "peer_weight": { + Type: schema.TypeInt, + Optional: true, + }, + }, + }, + }, + + "default_local_network_gateway_id": { + Type: schema.TypeString, + Optional: true, + }, + + "tags": tagsSchema(), + }, + } +} + +func resourceArmVirtualNetworkGatewayCreateUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).vnetGatewayClient + ctx := meta.(*ArmClient).StopContext + + log.Printf("[INFO] preparing arguments for AzureRM Virtual Network Gateway creation.") + + name := d.Get("name").(string) + location := azureStackNormalizeLocation(d.Get("location").(string)) + resGroup := d.Get("resource_group_name").(string) + tags := d.Get("tags").(map[string]interface{}) + + properties, err := getArmVirtualNetworkGatewayProperties(d) + if err != nil { + return err + } + + gateway := network.VirtualNetworkGateway{ + Name: &name, + Location: &location, + Tags: *expandTags(tags), + VirtualNetworkGatewayPropertiesFormat: properties, + } + + future, err := client.CreateOrUpdate(ctx, resGroup, name, gateway) + if err != nil { + return fmt.Errorf("Error Creating/Updating AzureRM Virtual Network Gateway %q (Resource Group %q): %+v", name, resGroup, err) + } + + err = future.WaitForCompletionRef(ctx, client.Client) + if err != nil { + return fmt.Errorf("Error waiting for completion of AzureRM Virtual Network Gateway %q (Resource Group %q): %+v", name, resGroup, err) + } + + read, err := client.Get(ctx, resGroup, name) + if err != nil { + return err + } + if read.ID == nil { + return fmt.Errorf("Cannot read AzureRM Virtual Network Gateway %s (resource group %s) ID", name, resGroup) + } + + d.SetId(*read.ID) + + return resourceArmVirtualNetworkGatewayRead(d, meta) +} + +func resourceArmVirtualNetworkGatewayRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).vnetGatewayClient + ctx := meta.(*ArmClient).StopContext + + resGroup, name, err := resourceGroupAndVirtualNetworkGatewayFromId(d.Id()) + if err != nil { + return err + } + + resp, err := client.Get(ctx, resGroup, name) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + d.SetId("") + return nil + } + return fmt.Errorf("Error making Read request on AzureRM Virtual Network Gateway %q (Resource Group %q): %+v", name, resGroup, err) + } + + d.Set("name", resp.Name) + d.Set("resource_group_name", resGroup) + if location := resp.Location; location != nil { + d.Set("location", azureStackNormalizeLocation(*location)) + } + + if gw := resp.VirtualNetworkGatewayPropertiesFormat; gw != nil { + d.Set("type", string(gw.GatewayType)) + d.Set("enable_bgp", gw.EnableBgp) + // d.Set("active_active", gw.ActiveActive) + + if string(gw.VpnType) != "" { + d.Set("vpn_type", string(gw.VpnType)) + } + + if gw.GatewayDefaultSite != nil { + d.Set("default_local_network_gateway_id", gw.GatewayDefaultSite.ID) + } + + if gw.Sku != nil { + d.Set("sku", string(gw.Sku.Name)) + } + + if err := d.Set("ip_configuration", flattenArmVirtualNetworkGatewayIPConfigurations(gw.IPConfigurations)); err != nil { + return fmt.Errorf("Error setting `ip_configuration`: %+v", err) + } + + vpnConfigFlat := flattenArmVirtualNetworkGatewayVpnClientConfig(gw.VpnClientConfiguration) + if err := d.Set("vpn_client_configuration", vpnConfigFlat); err != nil { + return fmt.Errorf("Error setting `vpn_client_configuration`: %+v", err) + } + + bgpSettingsFlat := flattenArmVirtualNetworkGatewayBgpSettings(gw.BgpSettings) + if err := d.Set("bgp_settings", bgpSettingsFlat); err != nil { + return fmt.Errorf("Error setting `bgp_settings`: %+v", err) + } + + } + + flattenAndSetTags(d, &resp.Tags) + + return nil +} + +func resourceArmVirtualNetworkGatewayDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).vnetGatewayClient + ctx := meta.(*ArmClient).StopContext + + resGroup, name, err := resourceGroupAndVirtualNetworkGatewayFromId(d.Id()) + if err != nil { + return err + } + + future, err := client.Delete(ctx, resGroup, name) + if err != nil { + return fmt.Errorf("Error deleting Virtual Network Gateway %q (Resource Group %q): %+v", name, resGroup, err) + } + + err = future.WaitForCompletionRef(ctx, client.Client) + if err != nil { + return fmt.Errorf("Error waiting for deletion of Virtual Network Gateway %q (Resource Group %q): %+v", name, resGroup, err) + } + + return nil +} + +func getArmVirtualNetworkGatewayProperties(d *schema.ResourceData) (*network.VirtualNetworkGatewayPropertiesFormat, error) { + gatewayType := network.VirtualNetworkGatewayType(d.Get("type").(string)) + vpnType := network.VpnType(d.Get("vpn_type").(string)) + enableBgp := d.Get("enable_bgp").(bool) + // activeActive := d.Get("active_active").(bool) + + props := &network.VirtualNetworkGatewayPropertiesFormat{ + GatewayType: gatewayType, + VpnType: vpnType, + EnableBgp: &enableBgp, + // ActiveActive: &activeActive, + Sku: expandArmVirtualNetworkGatewaySku(d), + IPConfigurations: expandArmVirtualNetworkGatewayIPConfigurations(d), + } + + if gatewayDefaultSiteID := d.Get("default_local_network_gateway_id").(string); gatewayDefaultSiteID != "" { + props.GatewayDefaultSite = &network.SubResource{ + ID: &gatewayDefaultSiteID, + } + } + + if _, ok := d.GetOk("vpn_client_configuration"); ok { + props.VpnClientConfiguration = expandArmVirtualNetworkGatewayVpnClientConfig(d) + } + + if _, ok := d.GetOk("bgp_settings"); ok { + props.BgpSettings = expandArmVirtualNetworkGatewayBgpSettings(d) + } + + // Sku validation for policy-based VPN gateways + if props.GatewayType == network.VirtualNetworkGatewayTypeVpn && props.VpnType == network.PolicyBased { + ok, err := evaluateSchemaValidateFunc(string(props.Sku.Name), "sku", validateArmVirtualNetworkGatewayPolicyBasedVpnSku()) + + if !ok { + return nil, err + } + } + + // Sku validation for route-based VPN gateways + if props.GatewayType == network.VirtualNetworkGatewayTypeVpn && props.VpnType == network.RouteBased { + ok, err := evaluateSchemaValidateFunc(string(props.Sku.Name), "sku", validateArmVirtualNetworkGatewayRouteBasedVpnSku()) + + if !ok { + return nil, err + } + } + + // Sku validation for ExpressRoute gateways + if props.GatewayType == network.VirtualNetworkGatewayTypeExpressRoute { + ok, err := evaluateSchemaValidateFunc(string(props.Sku.Name), "sku", validateArmVirtualNetworkGatewayExpressRouteSku()) + + if !ok { + return nil, err + } + } + + return props, nil +} + +func expandArmVirtualNetworkGatewayBgpSettings(d *schema.ResourceData) *network.BgpSettings { + bgpSets := d.Get("bgp_settings").([]interface{}) + bgp := bgpSets[0].(map[string]interface{}) + + asn := int64(bgp["asn"].(int)) + peeringAddress := bgp["peering_address"].(string) + peerWeight := int32(bgp["peer_weight"].(int)) + + return &network.BgpSettings{ + Asn: &asn, + BgpPeeringAddress: &peeringAddress, + PeerWeight: &peerWeight, + } +} + +func expandArmVirtualNetworkGatewayIPConfigurations(d *schema.ResourceData) *[]network.VirtualNetworkGatewayIPConfiguration { + configs := d.Get("ip_configuration").([]interface{}) + ipConfigs := make([]network.VirtualNetworkGatewayIPConfiguration, 0, len(configs)) + + for _, c := range configs { + conf := c.(map[string]interface{}) + + name := conf["name"].(string) + privateIPAllocMethod := network.IPAllocationMethod(conf["private_ip_address_allocation"].(string)) + + props := &network.VirtualNetworkGatewayIPConfigurationPropertiesFormat{ + PrivateIPAllocationMethod: privateIPAllocMethod, + } + + if subnetID := conf["subnet_id"].(string); subnetID != "" { + props.Subnet = &network.SubResource{ + ID: &subnetID, + } + } + + if publicIP := conf["public_ip_address_id"].(string); publicIP != "" { + props.PublicIPAddress = &network.SubResource{ + ID: &publicIP, + } + } + + ipConfig := network.VirtualNetworkGatewayIPConfiguration{ + Name: &name, + VirtualNetworkGatewayIPConfigurationPropertiesFormat: props, + } + + ipConfigs = append(ipConfigs, ipConfig) + } + + return &ipConfigs +} + +func expandArmVirtualNetworkGatewayVpnClientConfig(d *schema.ResourceData) *network.VpnClientConfiguration { + configSets := d.Get("vpn_client_configuration").([]interface{}) + conf := configSets[0].(map[string]interface{}) + + confAddresses := conf["address_space"].([]interface{}) + addresses := make([]string, 0, len(confAddresses)) + for _, addr := range confAddresses { + addresses = append(addresses, addr.(string)) + } + + var rootCerts []network.VpnClientRootCertificate + for _, rootCertSet := range conf["root_certificate"].(*schema.Set).List() { + rootCert := rootCertSet.(map[string]interface{}) + name := rootCert["name"].(string) + publicCertData := rootCert["public_cert_data"].(string) + r := network.VpnClientRootCertificate{ + Name: &name, + VpnClientRootCertificatePropertiesFormat: &network.VpnClientRootCertificatePropertiesFormat{ + PublicCertData: &publicCertData, + }, + } + rootCerts = append(rootCerts, r) + } + + var revokedCerts []network.VpnClientRevokedCertificate + for _, revokedCertSet := range conf["revoked_certificate"].(*schema.Set).List() { + revokedCert := revokedCertSet.(map[string]interface{}) + name := revokedCert["name"].(string) + thumbprint := revokedCert["thumbprint"].(string) + r := network.VpnClientRevokedCertificate{ + Name: &name, + VpnClientRevokedCertificatePropertiesFormat: &network.VpnClientRevokedCertificatePropertiesFormat{ + Thumbprint: &thumbprint, + }, + } + revokedCerts = append(revokedCerts, r) + } + + // var vpnClientProtocols []network.VpnClientProtocol + // for _, vpnClientProtocol := range conf["vpn_client_protocols"].(*schema.Set).List() { + // p := network.VpnClientProtocol(vpnClientProtocol.(string)) + // vpnClientProtocols = append(vpnClientProtocols, p) + // } + + // confRadiusServerAddress := conf["radius_server_address"].(string) + // confRadiusServerSecret := conf["radius_server_secret"].(string) + + return &network.VpnClientConfiguration{ + VpnClientAddressPool: &network.AddressSpace{ + AddressPrefixes: &addresses, + }, + VpnClientRootCertificates: &rootCerts, + VpnClientRevokedCertificates: &revokedCerts, + // VpnClientProtocols: &vpnClientProtocols, + // RadiusServerAddress: &confRadiusServerAddress, + // RadiusServerSecret: &confRadiusServerSecret, + } +} + +func expandArmVirtualNetworkGatewaySku(d *schema.ResourceData) *network.VirtualNetworkGatewaySku { + sku := d.Get("sku").(string) + + return &network.VirtualNetworkGatewaySku{ + Name: network.VirtualNetworkGatewaySkuName(sku), + Tier: network.VirtualNetworkGatewaySkuTier(sku), + } +} + +func flattenArmVirtualNetworkGatewayBgpSettings(settings *network.BgpSettings) []interface{} { + output := make([]interface{}, 1) + + if settings != nil { + flat := make(map[string]interface{}) + + if asn := settings.Asn; asn != nil { + flat["asn"] = int(*asn) + } + if address := settings.BgpPeeringAddress; address != nil { + flat["peering_address"] = *address + } + if weight := settings.PeerWeight; weight != nil { + flat["peer_weight"] = int(*weight) + } + + output[0] = flat + } + + return output +} + +func flattenArmVirtualNetworkGatewayIPConfigurations(ipConfigs *[]network.VirtualNetworkGatewayIPConfiguration) []interface{} { + flat := make([]interface{}, 0) + + if ipConfigs != nil { + for _, cfg := range *ipConfigs { + props := cfg.VirtualNetworkGatewayIPConfigurationPropertiesFormat + v := make(map[string]interface{}) + + if name := cfg.Name; name != nil { + v["name"] = *name + } + v["private_ip_address_allocation"] = string(props.PrivateIPAllocationMethod) + + if subnet := props.Subnet; subnet != nil { + if id := subnet.ID; id != nil { + v["subnet_id"] = *id + } + } + + if pip := props.PublicIPAddress; pip != nil { + if id := pip.ID; id != nil { + v["public_ip_address_id"] = *id + } + } + + flat = append(flat, v) + } + } + + return flat +} + +func flattenArmVirtualNetworkGatewayVpnClientConfig(cfg *network.VpnClientConfiguration) []interface{} { + if cfg == nil { + return []interface{}{} + } + + flat := make(map[string]interface{}) + + addressSpace := make([]interface{}, 0) + if pool := cfg.VpnClientAddressPool; pool != nil { + if prefixes := pool.AddressPrefixes; prefixes != nil { + for _, addr := range *prefixes { + addressSpace = append(addressSpace, addr) + } + } + } + flat["address_space"] = addressSpace + + rootCerts := make([]interface{}, 0) + if certs := cfg.VpnClientRootCertificates; certs != nil { + for _, cert := range *certs { + v := map[string]interface{}{ + "name": *cert.Name, + "public_cert_data": *cert.VpnClientRootCertificatePropertiesFormat.PublicCertData, + } + rootCerts = append(rootCerts, v) + } + } + flat["root_certificate"] = schema.NewSet(hashVirtualNetworkGatewayRootCert, rootCerts) + + revokedCerts := make([]interface{}, 0) + if certs := cfg.VpnClientRevokedCertificates; certs != nil { + for _, cert := range *certs { + v := map[string]interface{}{ + "name": *cert.Name, + "thumbprint": *cert.VpnClientRevokedCertificatePropertiesFormat.Thumbprint, + } + revokedCerts = append(revokedCerts, v) + } + } + flat["revoked_certificate"] = schema.NewSet(hashVirtualNetworkGatewayRevokedCert, revokedCerts) + + // vpnClientProtocols := &schema.Set{F: schema.HashString} + // if vpnProtocols := cfg.VpnClientProtocols; vpnProtocols != nil { + // for _, protocol := range *vpnProtocols { + // vpnClientProtocols.Add(string(protocol)) + // } + // } + // flat["vpn_client_protocols"] = vpnClientProtocols + + // if v := cfg.RadiusServerAddress; v != nil { + // flat["radius_server_address"] = *v + // } + + // if v := cfg.RadiusServerSecret; v != nil { + // flat["radius_server_secret"] = *v + // } + + return []interface{}{flat} +} + +func hashVirtualNetworkGatewayRootCert(v interface{}) int { + var buf bytes.Buffer + m := v.(map[string]interface{}) + + buf.WriteString(fmt.Sprintf("%s-", m["name"].(string))) + buf.WriteString(fmt.Sprintf("%s-", m["public_cert_data"].(string))) + + return hashcode.String(buf.String()) +} + +func hashVirtualNetworkGatewayRevokedCert(v interface{}) int { + var buf bytes.Buffer + m := v.(map[string]interface{}) + + buf.WriteString(fmt.Sprintf("%s-", m["name"].(string))) + buf.WriteString(fmt.Sprintf("%s-", m["thumbprint"].(string))) + + return hashcode.String(buf.String()) +} + +func resourceGroupAndVirtualNetworkGatewayFromId(virtualNetworkGatewayId string) (string, string, error) { + id, err := parseAzureResourceID(virtualNetworkGatewayId) + if err != nil { + return "", "", err + } + name := id.Path["virtualNetworkGateways"] + resGroup := id.ResourceGroup + + return resGroup, name, nil +} + +func validateArmVirtualNetworkGatewaySubnetId(i interface{}, k string) (s []string, es []error) { + value, ok := i.(string) + if !ok { + es = append(es, fmt.Errorf("expected type of %s to be string", k)) + return + } + + id, err := parseAzureResourceID(value) + if err != nil { + es = append(es, fmt.Errorf("expected %s to be an Azure resource id", k)) + return + } + + subnet, ok := id.Path["subnets"] + if !ok { + es = append(es, fmt.Errorf("expected %s to reference a subnet resource", k)) + return + } + + if strings.ToLower(subnet) != "gatewaysubnet" { + es = append(es, fmt.Errorf("expected %s to reference a gateway subnet with name GatewaySubnet", k)) + } + + return +} + +func validateArmVirtualNetworkGatewayPolicyBasedVpnSku() schema.SchemaValidateFunc { + return validation.StringInSlice([]string{ + string(network.VirtualNetworkGatewaySkuTierBasic), + }, true) +} + +func validateArmVirtualNetworkGatewayRouteBasedVpnSku() schema.SchemaValidateFunc { + return validation.StringInSlice([]string{ + string(network.VirtualNetworkGatewaySkuTierBasic), + string(network.VirtualNetworkGatewaySkuTierStandard), + string(network.VirtualNetworkGatewaySkuTierHighPerformance), + string("VirtualNetworkGatewaySkuNameVpnGw1"), + string("VirtualNetworkGatewaySkuNameVpnGw2"), + string("VirtualNetworkGatewaySkuNameVpnGw3"), + }, true) +} + +func validateArmVirtualNetworkGatewayExpressRouteSku() schema.SchemaValidateFunc { + return validation.StringInSlice([]string{ + string(network.VirtualNetworkGatewaySkuTierStandard), + string(network.VirtualNetworkGatewaySkuTierHighPerformance), + string("VirtualNetworkGatewaySkuTierUltraPerformance"), + }, true) +} + +func resourceArmVirtualNetworkGatewayCustomizeDiff(diff *schema.ResourceDiff, v interface{}) error { + + if vpnClient, ok := diff.GetOk("vpn_client_configuration"); ok { + if vpnClientConfig, ok := vpnClient.([]interface{})[0].(map[string]interface{}); ok { + hasRadiusAddress := vpnClientConfig["radius_server_address"] != "" + hasRadiusSecret := vpnClientConfig["radius_server_secret"] != "" + + if hasRadiusAddress && !hasRadiusSecret { + return fmt.Errorf("if radius_server_address is set radius_server_secret must also be set") + } + if !hasRadiusAddress && hasRadiusSecret { + return fmt.Errorf("if radius_server_secret is set radius_server_address must also be set") + } + } + } + return nil +} diff --git a/azurestack/resource_arm_virtual_network_gateway_test.go b/azurestack/resource_arm_virtual_network_gateway_test.go new file mode 100644 index 000000000..10414ea71 --- /dev/null +++ b/azurestack/resource_arm_virtual_network_gateway_test.go @@ -0,0 +1,539 @@ +package azurestack + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "github.com/terraform-providers/terraform-provider-azurestack/azurestack/utils" +) + +func TestAccAzureStackVirtualNetworkGateway_basic(t *testing.T) { + ri := acctest.RandInt() + resourceName := "azurestack_virtual_network_gateway.test" + config := testAccAzureStackVirtualNetworkGateway_basic(ri, testLocation()) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureStackVirtualNetworkGatewayDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureStackVirtualNetworkGatewayExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "sku", "Basic"), + ), + }, + }, + }) +} + +func TestAccAzureStackVirtualNetworkGateway_lowerCaseSubnetName(t *testing.T) { + ri := acctest.RandInt() + resourceName := "azurestack_virtual_network_gateway.test" + config := testAccAzureStackVirtualNetworkGateway_lowerCaseSubnetName(ri, testLocation()) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureStackVirtualNetworkGatewayDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureStackVirtualNetworkGatewayExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "sku", "Basic"), + ), + }, + }, + }) +} + +func TestAccAzureStackVirtualNetworkGateway_vpnGw1(t *testing.T) { + ri := acctest.RandInt() + config := testAccAzureStackVirtualNetworkGateway_vpnGw1(ri, testLocation()) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureStackVirtualNetworkGatewayDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureStackVirtualNetworkGatewayExists("azurestack_virtual_network_gateway.test"), + ), + }, + }, + }) +} + +func TestAccAzureStackVirtualNetworkGateway_activeActive(t *testing.T) { + ri := acctest.RandInt() + config := testAccAzureStackVirtualNetworkGateway_activeActive(ri, testLocation()) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureStackVirtualNetworkGatewayDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureStackVirtualNetworkGatewayExists("azurestack_virtual_network_gateway.test"), + ), + }, + }, + }) +} + +func TestAccAzureStackVirtualNetworkGateway_standard(t *testing.T) { + resourceName := "azurestack_virtual_network_gateway.test" + ri := acctest.RandInt() + config := testAccAzureStackVirtualNetworkGateway_sku(ri, testLocation(), "Standard") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureStackVirtualNetworkGatewayDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureStackVirtualNetworkGatewayExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "sku", "Standard"), + ), + }, + }, + }) +} + +func TestAccAzureStackVirtualNetworkGateway_vpnGw2(t *testing.T) { + resourceName := "azurestack_virtual_network_gateway.test" + ri := acctest.RandInt() + config := testAccAzureStackVirtualNetworkGateway_sku(ri, testLocation(), "VpnGw2") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureStackVirtualNetworkGatewayDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureStackVirtualNetworkGatewayExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "sku", "VpnGw2"), + ), + }, + }, + }) +} + +func TestAccAzureStackVirtualNetworkGateway_vpnGw3(t *testing.T) { + resourceName := "azurestack_virtual_network_gateway.test" + ri := acctest.RandInt() + config := testAccAzureStackVirtualNetworkGateway_sku(ri, testLocation(), "VpnGw3") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureStackVirtualNetworkGatewayDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureStackVirtualNetworkGatewayExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "sku", "VpnGw3"), + ), + }, + }, + }) +} + +func TestAccAzureStackVirtualNetworkGateway_vpnClientConfig(t *testing.T) { + ri := acctest.RandInt() + resourceName := "azurestack_virtual_network_gateway.test" + config := testAccAzureStackVirtualNetworkGateway_vpnClientConfig(ri, testLocation()) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureStackVirtualNetworkGatewayDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureStackVirtualNetworkGatewayExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "vpn_client_configuration.0.radius_server_address", "1.2.3.4"), + resource.TestCheckResourceAttr(resourceName, "vpn_client_configuration.0.vpn_client_protocols.#", "2"), + ), + }, + }, + }) +} + +func testCheckAzureStackVirtualNetworkGatewayExists(name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + name, resourceGroup, err := getArmResourceNameAndGroup(s, name) + if err != nil { + return err + } + + client := testAccProvider.Meta().(*ArmClient).vnetGatewayClient + ctx := testAccProvider.Meta().(*ArmClient).StopContext + + resp, err := client.Get(ctx, resourceGroup, name) + if err != nil { + return fmt.Errorf("Bad: Get on vnetGatewayClient: %+v", err) + } + + if utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Bad: Virtual Network Gateway %q (resource group: %q) does not exist", name, resourceGroup) + } + + return nil + } +} + +func testCheckAzureStackVirtualNetworkGatewayDestroy(s *terraform.State) error { + client := testAccProvider.Meta().(*ArmClient).vnetGatewayClient + ctx := testAccProvider.Meta().(*ArmClient).StopContext + + for _, rs := range s.RootModule().Resources { + if rs.Type != "azurestack_virtual_network_gateway" { + continue + } + + name := rs.Primary.Attributes["name"] + resourceGroup := rs.Primary.Attributes["resource_group_name"] + + resp, err := client.Get(ctx, resourceGroup, name) + + if err != nil { + return nil + } + + if utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Virtual Network Gateway still exists:\n%#v", resp.VirtualNetworkGatewayPropertiesFormat) + } + } + + return nil +} + +func testAccAzureStackVirtualNetworkGateway_basic(rInt int, location string) string { + return fmt.Sprintf(` +resource "azurestack_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurestack_virtual_network" "test" { + name = "acctestvn-%d" + location = "${azurestack_resource_group.test.location}" + resource_group_name = "${azurestack_resource_group.test.name}" + address_space = ["10.0.0.0/16"] +} + +resource "azurestack_subnet" "test" { + name = "GatewaySubnet" + resource_group_name = "${azurestack_resource_group.test.name}" + virtual_network_name = "${azurestack_virtual_network.test.name}" + address_prefix = "10.0.1.0/24" +} + +resource "azurestack_public_ip" "test" { + name = "acctestpip-%d" + location = "${azurestack_resource_group.test.location}" + resource_group_name = "${azurestack_resource_group.test.name}" + public_ip_address_allocation = "Dynamic" +} + +resource "azurestack_virtual_network_gateway" "test" { + name = "acctestvng-%d" + location = "${azurestack_resource_group.test.location}" + resource_group_name = "${azurestack_resource_group.test.name}" + + type = "Vpn" + vpn_type = "RouteBased" + sku = "Basic" + + ip_configuration { + public_ip_address_id = "${azurestack_public_ip.test.id}" + private_ip_address_allocation = "Dynamic" + subnet_id = "${azurestack_subnet.test.id}" + } +} +`, rInt, location, rInt, rInt, rInt) +} + +func testAccAzureStackVirtualNetworkGateway_lowerCaseSubnetName(rInt int, location string) string { + return fmt.Sprintf(` +resource "azurestack_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurestack_virtual_network" "test" { + name = "acctestvn-%d" + location = "${azurestack_resource_group.test.location}" + resource_group_name = "${azurestack_resource_group.test.name}" + address_space = ["10.0.0.0/16"] +} + +resource "azurestack_subnet" "test" { + name = "gatewaySubnet" + resource_group_name = "${azurestack_resource_group.test.name}" + virtual_network_name = "${azurestack_virtual_network.test.name}" + address_prefix = "10.0.1.0/24" +} + +resource "azurestack_public_ip" "test" { + name = "acctestpip-%d" + location = "${azurestack_resource_group.test.location}" + resource_group_name = "${azurestack_resource_group.test.name}" + public_ip_address_allocation = "Dynamic" +} + +resource "azurestack_virtual_network_gateway" "test" { + name = "acctestvng-%d" + location = "${azurestack_resource_group.test.location}" + resource_group_name = "${azurestack_resource_group.test.name}" + + type = "Vpn" + vpn_type = "RouteBased" + sku = "Basic" + + ip_configuration { + public_ip_address_id = "${azurestack_public_ip.test.id}" + private_ip_address_allocation = "Dynamic" + subnet_id = "${azurestack_subnet.test.id}" + } +} +`, rInt, location, rInt, rInt, rInt) +} + +func testAccAzureStackVirtualNetworkGateway_vpnGw1(rInt int, location string) string { + return fmt.Sprintf(` +resource "azurestack_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurestack_virtual_network" "test" { + name = "acctestvn-%d" + location = "${azurestack_resource_group.test.location}" + resource_group_name = "${azurestack_resource_group.test.name}" + address_space = ["10.0.0.0/16"] +} + +resource "azurestack_subnet" "test" { + name = "GatewaySubnet" + resource_group_name = "${azurestack_resource_group.test.name}" + virtual_network_name = "${azurestack_virtual_network.test.name}" + address_prefix = "10.0.1.0/24" +} + +resource "azurestack_public_ip" "test" { + name = "acctestpip-%d" + location = "${azurestack_resource_group.test.location}" + resource_group_name = "${azurestack_resource_group.test.name}" + public_ip_address_allocation = "Dynamic" +} + +resource "azurestack_virtual_network_gateway" "test" { + name = "acctestvng-%d" + location = "${azurestack_resource_group.test.location}" + resource_group_name = "${azurestack_resource_group.test.name}" + + type = "Vpn" + vpn_type = "RouteBased" + sku = "VpnGw1" + + ip_configuration { + public_ip_address_id = "${azurestack_public_ip.test.id}" + private_ip_address_allocation = "Dynamic" + subnet_id = "${azurestack_subnet.test.id}" + } +} +`, rInt, location, rInt, rInt, rInt) +} + +func testAccAzureStackVirtualNetworkGateway_activeActive(rInt int, location string) string { + + return fmt.Sprintf(` +resource "azurestack_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurestack_virtual_network" "test" { + name = "acctestvn-%d" + location = "${azurestack_resource_group.test.location}" + resource_group_name = "${azurestack_resource_group.test.name}" + address_space = ["10.0.0.0/16"] +} + +resource "azurestack_subnet" "test" { + name = "GatewaySubnet" + resource_group_name = "${azurestack_resource_group.test.name}" + virtual_network_name = "${azurestack_virtual_network.test.name}" + address_prefix = "10.0.1.0/24" +} + + +resource "azurestack_public_ip" "first" { + name = "acctestpip1-%d" + location = "${azurestack_resource_group.test.location}" + resource_group_name = "${azurestack_resource_group.test.name}" + public_ip_address_allocation = "Dynamic" +} + +resource "azurestack_public_ip" "second" { + name = "acctestpip2-%d" + + location = "${azurestack_resource_group.test.location}" + resource_group_name = "${azurestack_resource_group.test.name}" + public_ip_address_allocation = "Dynamic" +} + +resource "azurestack_virtual_network_gateway" "test" { + depends_on = ["azurestack_public_ip.first", "azurestack_public_ip.second"] + name = "acctestvng-%d" + location = "${azurestack_resource_group.test.location}" + resource_group_name = "${azurestack_resource_group.test.name}" + + type = "Vpn" + vpn_type = "RouteBased" + sku = "VpnGw1" + + active_active = true + enable_bgp = true + + ip_configuration { + name = "gw-ip1" + public_ip_address_id = "${azurestack_public_ip.first.id}" + + private_ip_address_allocation = "Dynamic" + subnet_id = "${azurestack_subnet.test.id}" + } + + + ip_configuration { + name = "gw-ip2" + public_ip_address_id = "${azurestack_public_ip.second.id}" + private_ip_address_allocation = "Dynamic" + subnet_id = "${azurestack_subnet.test.id}" + } + + bgp_settings { + asn = "65010" + } +} +`, rInt, location, rInt, rInt, rInt, rInt) + +} + +func testAccAzureStackVirtualNetworkGateway_vpnClientConfig(rInt int, location string) string { + return fmt.Sprintf(` +resource "azurestack_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurestack_virtual_network" "test" { + name = "acctestvn-%d" + location = "${azurestack_resource_group.test.location}" + resource_group_name = "${azurestack_resource_group.test.name}" + address_space = ["10.0.0.0/16"] +} + +resource "azurestack_subnet" "test" { + name = "GatewaySubnet" + resource_group_name = "${azurestack_resource_group.test.name}" + virtual_network_name = "${azurestack_virtual_network.test.name}" + address_prefix = "10.0.1.0/24" +} + +resource "azurestack_public_ip" "test" { + name = "acctestpip-%d" + location = "${azurestack_resource_group.test.location}" + resource_group_name = "${azurestack_resource_group.test.name}" + public_ip_address_allocation = "Dynamic" +} + +resource "azurestack_virtual_network_gateway" "test" { + depends_on = ["azurestack_public_ip.test"] + name = "acctestvng-%d" + location = "${azurestack_resource_group.test.location}" + resource_group_name = "${azurestack_resource_group.test.name}" + + type = "Vpn" + vpn_type = "RouteBased" + sku = "VpnGw1" + + ip_configuration { + public_ip_address_id = "${azurestack_public_ip.test.id}" + private_ip_address_allocation = "Dynamic" + subnet_id = "${azurestack_subnet.test.id}" + } + + vpn_client_configuration { + address_space = ["10.2.0.0/24"] + vpn_client_protocols = ["SSTP", "IkeV2"] + + radius_server_address = "1.2.3.4" + radius_server_secret = "1234" + } +} +`, rInt, location, rInt, rInt, rInt) +} + +func testAccAzureStackVirtualNetworkGateway_sku(rInt int, location string, sku string) string { + return fmt.Sprintf(` +resource "azurestack_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurestack_virtual_network" "test" { + name = "acctestvn-%d" + location = "${azurestack_resource_group.test.location}" + resource_group_name = "${azurestack_resource_group.test.name}" + address_space = ["10.0.0.0/16"] +} + +resource "azurestack_subnet" "test" { + name = "GatewaySubnet" + resource_group_name = "${azurestack_resource_group.test.name}" + virtual_network_name = "${azurestack_virtual_network.test.name}" + address_prefix = "10.0.1.0/24" +} + +resource "azurestack_public_ip" "test" { + name = "acctestpip-%d" + location = "${azurestack_resource_group.test.location}" + resource_group_name = "${azurestack_resource_group.test.name}" + public_ip_address_allocation = "Dynamic" +} + +resource "azurestack_virtual_network_gateway" "test" { + name = "acctestvng-%d" + location = "${azurestack_resource_group.test.location}" + resource_group_name = "${azurestack_resource_group.test.name}" + + type = "Vpn" + vpn_type = "RouteBased" + sku = "%s" + + ip_configuration { + public_ip_address_id = "${azurestack_public_ip.test.id}" + private_ip_address_allocation = "Dynamic" + subnet_id = "${azurestack_subnet.test.id}" + } +} +`, rInt, location, rInt, rInt, rInt, sku) +} From c2d738d6e804f3f29dbb99b681f838b176673946 Mon Sep 17 00:00:00 2001 From: Antonio Cabrera Date: Wed, 22 Aug 2018 10:49:40 -0500 Subject: [PATCH 02/40] Add some validators and utilities backported from AzureRM --- azurestack/test_utils.go | 22 +++ azurestack/validators.go | 248 ++++++++++++++++++++++++++++++ azurestack/validators_test.go | 277 ++++++++++++++++++++++++++++++++++ 3 files changed, 547 insertions(+) create mode 100644 azurestack/test_utils.go create mode 100644 azurestack/validators.go create mode 100644 azurestack/validators_test.go diff --git a/azurestack/test_utils.go b/azurestack/test_utils.go new file mode 100644 index 000000000..ed75426d0 --- /dev/null +++ b/azurestack/test_utils.go @@ -0,0 +1,22 @@ +package azurestack + +import ( + "fmt" + + "github.com/hashicorp/terraform/terraform" +) + +func getArmResourceNameAndGroup(s *terraform.State, name string) (string, string, error) { + rs, ok := s.RootModule().Resources[name] + if !ok { + return "", "", fmt.Errorf("Not found: %s", name) + } + + armName := rs.Primary.Attributes["name"] + armResourceGroup, hasResourceGroup := rs.Primary.Attributes["resource_group_name"] + if !hasResourceGroup { + return "", "", fmt.Errorf("Bad: no resource group found in state for virtual network gateway: %s", name) + } + + return armName, armResourceGroup, nil +} diff --git a/azurestack/validators.go b/azurestack/validators.go new file mode 100644 index 000000000..bff6a5e8a --- /dev/null +++ b/azurestack/validators.go @@ -0,0 +1,248 @@ +package azurestack + +import ( + "fmt" + "math" + "regexp" + "strings" + "time" + + "github.com/Azure/go-autorest/autorest/date" + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" + "github.com/satori/uuid" +) + +func validateRFC3339Date(v interface{}, k string) (ws []string, errors []error) { + dateString := v.(string) + + if _, err := date.ParseTime(time.RFC3339, dateString); err != nil { + errors = append(errors, fmt.Errorf("%q is an invalid RFC3339 date: %+v", k, err)) + } + + return +} + +// validateIntInSlice returns a SchemaValidateFunc which tests if the provided value +// is of type int and matches the value of an element in the valid slice +func validateIntInSlice(valid []int) schema.SchemaValidateFunc { + return func(i interface{}, k string) (s []string, es []error) { + v, ok := i.(int) + if !ok { + es = append(es, fmt.Errorf("expected type of %s to be int", k)) + return + } + + for _, str := range valid { + if v == str { + return + } + } + + es = append(es, fmt.Errorf("expected %q to be one of %v, got %v", k, valid, v)) + return + } +} + +func validateUUID(v interface{}, k string) (ws []string, errors []error) { + if _, err := uuid.FromString(v.(string)); err != nil { + errors = append(errors, fmt.Errorf("%q is an invalid UUUID: %s", k, err)) + } + return +} + +func evaluateSchemaValidateFunc(i interface{}, k string, validateFunc schema.SchemaValidateFunc) (bool, error) { + _, es := validateFunc(i, k) + + if len(es) > 0 { + return false, es[0] + } + + return true, nil +} + +func validateIso8601Duration() schema.SchemaValidateFunc { + return func(i interface{}, k string) (s []string, es []error) { + v, ok := i.(string) + if !ok { + es = append(es, fmt.Errorf("expected type of %s to be string", k)) + return + } + + matched, _ := regexp.MatchString(`^P([0-9]+Y)?([0-9]+M)?([0-9]+W)?([0-9]+D)?(T([0-9]+H)?([0-9]+M)?([0-9]+(\.?[0-9]+)?S)?)?$`, v) + + if !matched { + es = append(es, fmt.Errorf("expected %s to be in ISO 8601 duration format, got %s", k, v)) + } + return + } +} + +func validateAzureVirtualMachineTimeZone() schema.SchemaValidateFunc { + // Candidates are listed here: http://jackstromberg.com/2017/01/list-of-time-zones-consumed-by-azure/ + candidates := []string{ + "", + "Afghanistan Standard Time", + "Alaskan Standard Time", + "Arab Standard Time", + "Arabian Standard Time", + "Arabic Standard Time", + "Argentina Standard Time", + "Atlantic Standard Time", + "AUS Central Standard Time", + "AUS Eastern Standard Time", + "Azerbaijan Standard Time", + "Azores Standard Time", + "Bahia Standard Time", + "Bangladesh Standard Time", + "Belarus Standard Time", + "Canada Central Standard Time", + "Cape Verde Standard Time", + "Caucasus Standard Time", + "Cen. Australia Standard Time", + "Central America Standard Time", + "Central Asia Standard Time", + "Central Brazilian Standard Time", + "Central Europe Standard Time", + "Central European Standard Time", + "Central Pacific Standard Time", + "Central Standard Time (Mexico)", + "Central Standard Time", + "China Standard Time", + "Dateline Standard Time", + "E. Africa Standard Time", + "E. Australia Standard Time", + "E. Europe Standard Time", + "E. South America Standard Time", + "Eastern Standard Time (Mexico)", + "Eastern Standard Time", + "Egypt Standard Time", + "Ekaterinburg Standard Time", + "Fiji Standard Time", + "FLE Standard Time", + "Georgian Standard Time", + "GMT Standard Time", + "Greenland Standard Time", + "Greenwich Standard Time", + "GTB Standard Time", + "Hawaiian Standard Time", + "India Standard Time", + "Iran Standard Time", + "Israel Standard Time", + "Jordan Standard Time", + "Kaliningrad Standard Time", + "Korea Standard Time", + "Libya Standard Time", + "Line Islands Standard Time", + "Magadan Standard Time", + "Mauritius Standard Time", + "Middle East Standard Time", + "Montevideo Standard Time", + "Morocco Standard Time", + "Mountain Standard Time (Mexico)", + "Mountain Standard Time", + "Myanmar Standard Time", + "N. Central Asia Standard Time", + "Namibia Standard Time", + "Nepal Standard Time", + "New Zealand Standard Time", + "Newfoundland Standard Time", + "North Asia East Standard Time", + "North Asia Standard Time", + "Pacific SA Standard Time", + "Pacific Standard Time (Mexico)", + "Pacific Standard Time", + "Pakistan Standard Time", + "Paraguay Standard Time", + "Romance Standard Time", + "Russia Time Zone 10", + "Russia Time Zone 11", + "Russia Time Zone 3", + "Russian Standard Time", + "SA Eastern Standard Time", + "SA Pacific Standard Time", + "SA Western Standard Time", + "Samoa Standard Time", + "SE Asia Standard Time", + "Singapore Standard Time", + "South Africa Standard Time", + "Sri Lanka Standard Time", + "Syria Standard Time", + "Taipei Standard Time", + "Tasmania Standard Time", + "Tokyo Standard Time", + "Tonga Standard Time", + "Turkey Standard Time", + "Ulaanbaatar Standard Time", + "US Eastern Standard Time", + "US Mountain Standard Time", + "UTC", + "UTC+12", + "UTC-02", + "UTC-11", + "Venezuela Standard Time", + "Vladivostok Standard Time", + "W. Australia Standard Time", + "W. Central Africa Standard Time", + "W. Europe Standard Time", + "West Asia Standard Time", + "West Pacific Standard Time", + "Yakutsk Standard Time", + } + return validation.StringInSlice(candidates, true) +} + +// intBetweenDivisibleBy returns a SchemaValidateFunc which tests if the provided value +// is of type int and is between min and max (inclusive) and is divisible by a given number +func validateIntBetweenDivisibleBy(min, max, divisor int) schema.SchemaValidateFunc { + return func(i interface{}, k string) (s []string, es []error) { + v, ok := i.(int) + if !ok { + es = append(es, fmt.Errorf("expected type of %s to be int", k)) + return + } + + if v < min || v > max { + es = append(es, fmt.Errorf("expected %s to be in the range (%d - %d), got %d", k, min, max, v)) + return + } + + if math.Mod(float64(v), float64(divisor)) != 0 { + es = append(es, fmt.Errorf("expected %s to be divisible by %d", k, divisor)) + return + } + + return + } +} + +func validateCollation() schema.SchemaValidateFunc { + return func(i interface{}, k string) (s []string, es []error) { + v, ok := i.(string) + if !ok { + es = append(es, fmt.Errorf("expected type of %s to be string", k)) + return + } + + matched, _ := regexp.MatchString(`^[A-Za-z0-9_. ]+$`, v) + + if !matched { + es = append(es, fmt.Errorf("%s contains invalid characters, only underscores are supported, got %s", k, v)) + return + } + + return + } +} + +func validateFilePath() schema.SchemaValidateFunc { + return func(v interface{}, k string) (ws []string, es []error) { + val := v.(string) + + if !strings.HasPrefix(val, "/") { + es = append(es, fmt.Errorf("%q must start with `/`", k)) + } + + return + } +} diff --git a/azurestack/validators_test.go b/azurestack/validators_test.go new file mode 100644 index 000000000..2a09fc6a5 --- /dev/null +++ b/azurestack/validators_test.go @@ -0,0 +1,277 @@ +package azurestack + +import ( + "strconv" + "testing" +) + +func TestValidateRFC3339Date(t *testing.T) { + cases := []struct { + Value string + ErrCount int + }{ + { + Value: "", + ErrCount: 1, + }, + { + Value: "Random", + ErrCount: 1, + }, + { + Value: "2017-01-01", + ErrCount: 1, + }, + { + Value: "2017-01-01T01:23:45", + ErrCount: 1, + }, + { + Value: "2017-01-01T01:23:45+00:00", + ErrCount: 0, + }, + { + Value: "2017-01-01T01:23:45Z", + ErrCount: 0, + }, + } + + for _, tc := range cases { + _, errors := validateRFC3339Date(tc.Value, "example") + + if len(errors) != tc.ErrCount { + t.Fatalf("Expected validateRFC3339Date to trigger '%d' errors for '%s' - got '%d'", tc.ErrCount, tc.Value, len(errors)) + } + } +} + +func TestValidateIntInSlice(t *testing.T) { + + cases := []struct { + Input []int + Value int + Errors int + }{ + { + Input: []int{}, + Value: 0, + Errors: 1, + }, + { + Input: []int{1}, + Value: 1, + Errors: 0, + }, + { + Input: []int{1, 2, 3, 4, 5}, + Value: 3, + Errors: 0, + }, + { + Input: []int{1, 3, 5}, + Value: 3, + Errors: 0, + }, + { + Input: []int{1, 3, 5}, + Value: 4, + Errors: 1, + }, + } + + for _, tc := range cases { + _, errors := validateIntInSlice(tc.Input)(tc.Value, "azurerm_postgresql_database") + + if len(errors) != tc.Errors { + t.Fatalf("Expected the validateIntInSlice trigger a validation error for input: %+v looking for %+v", tc.Input, tc.Value) + } + } + +} + +func TestValidateIso8601Duration(t *testing.T) { + cases := []struct { + Value string + Errors int + }{ + { + // Date components only + Value: "P1Y2M3D", + Errors: 0, + }, + { + // Time components only + Value: "PT7H42M3S", + Errors: 0, + }, + { + // Date and time components + Value: "P1Y2M3DT7H42M3S", + Errors: 0, + }, + { + // Invalid prefix + Value: "1Y2M3DT7H42M3S", + Errors: 1, + }, + { + // Wrong order of components, i.e. invalid format + Value: "PT7H42M3S1Y2M3D", + Errors: 1, + }, + } + + for _, tc := range cases { + _, errors := validateIso8601Duration()(tc.Value, "example") + + if len(errors) != tc.Errors { + t.Fatalf("Expected validateIso8601Duration to trigger '%d' errors for '%s' - got '%d'", tc.Errors, tc.Value, len(errors)) + } + } +} + +func TestValidateIntBetweenDivisibleBy(t *testing.T) { + cases := []struct { + Min int + Max int + Div int + Value interface{} + Errors int + }{ + { + Min: 1025, + Max: 2048, + Div: 1024, + Value: 1024, + Errors: 1, + }, + { + Min: 1025, + Max: 2048, + Div: 3, + Value: 1024, + Errors: 1, + }, + { + Min: 1024, + Max: 2048, + Div: 1024, + Value: 3072, + Errors: 1, + }, + { + Min: 1024, + Max: 2048, + Div: 1024, + Value: 2049, + Errors: 1, + }, + { + Min: 1024, + Max: 2048, + Div: 1024, + Value: 1024, + Errors: 0, + }, + } + + for _, tc := range cases { + _, errors := validateIntBetweenDivisibleBy(tc.Min, tc.Max, tc.Div)(tc.Value, strconv.Itoa(tc.Value.(int))) + if len(errors) != tc.Errors { + t.Fatalf("Expected intBetweenDivisibleBy to trigger '%d' errors for '%s' - got '%d'", tc.Errors, tc.Value, len(errors)) + } + } +} + +func TestValidateCollation(t *testing.T) { + cases := []struct { + Value string + Errors int + }{ + { + Value: "en-US", + Errors: 1, + }, + { + Value: "en_US", + Errors: 0, + }, + { + Value: "en US", + Errors: 0, + }, + { + Value: "English_United States.1252", + Errors: 0, + }, + } + + for _, tc := range cases { + _, errors := validateCollation()(tc.Value, "collation") + if len(errors) != tc.Errors { + t.Fatalf("Expected validateCollation to trigger '%d' errors for '%s' - got '%d'", tc.Errors, tc.Value, len(errors)) + } + } +} + +func TestValidateAzureVirtualMachineTimeZone(t *testing.T) { + cases := []struct { + Value string + Errors int + }{ + { + Value: "", + Errors: 0, + }, + { + Value: "UTC", + Errors: 0, + }, + { + Value: "China Standard Time", + Errors: 0, + }, + { + // Valid UTC time zone + Value: "utc-11", + Errors: 0, + }, + { + // Invalid UTC time zone + Value: "UTC-30", + Errors: 1, + }, + } + + for _, tc := range cases { + _, errors := validateAzureVirtualMachineTimeZone()(tc.Value, "unittest") + + if len(errors) != tc.Errors { + t.Fatalf("Expected validateAzureVMTimeZone to trigger '%d' errors for '%s' - got '%d'", tc.Errors, tc.Value, len(errors)) + } + } +} + +func TestValidateAzureDataLakeStoreRemoteFilePath(t *testing.T) { + cases := []struct { + Value string + Errors int + }{ + { + Value: "bad", + Errors: 1, + }, + { + Value: "/good/file/path", + Errors: 0, + }, + } + + for _, tc := range cases { + _, errors := validateFilePath()(tc.Value, "unittest") + + if len(errors) != tc.Errors { + t.Fatalf("Expected validateFilePath to trigger '%d' errors for '%s' - got '%d'", tc.Errors, tc.Value, len(errors)) + } + } +} From a9d8452078b3fe6d5549756b6e5aac7c4bce4580 Mon Sep 17 00:00:00 2001 From: Marin Salinas Date: Wed, 22 Aug 2018 17:20:30 -0500 Subject: [PATCH 03/40] Add vendor library missing --- vendor/github.com/satori/uuid/LICENSE | 20 ++ vendor/github.com/satori/uuid/README.md | 74 ++++++ vendor/github.com/satori/uuid/codec.go | 206 ++++++++++++++++ vendor/github.com/satori/uuid/generator.go | 265 +++++++++++++++++++++ vendor/github.com/satori/uuid/sql.go | 78 ++++++ vendor/github.com/satori/uuid/uuid.go | 161 +++++++++++++ vendor/vendor.json | 6 + 7 files changed, 810 insertions(+) create mode 100644 vendor/github.com/satori/uuid/LICENSE create mode 100644 vendor/github.com/satori/uuid/README.md create mode 100644 vendor/github.com/satori/uuid/codec.go create mode 100644 vendor/github.com/satori/uuid/generator.go create mode 100644 vendor/github.com/satori/uuid/sql.go create mode 100644 vendor/github.com/satori/uuid/uuid.go diff --git a/vendor/github.com/satori/uuid/LICENSE b/vendor/github.com/satori/uuid/LICENSE new file mode 100644 index 000000000..926d54987 --- /dev/null +++ b/vendor/github.com/satori/uuid/LICENSE @@ -0,0 +1,20 @@ +Copyright (C) 2013-2018 by Maxim Bublis + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/satori/uuid/README.md b/vendor/github.com/satori/uuid/README.md new file mode 100644 index 000000000..770284969 --- /dev/null +++ b/vendor/github.com/satori/uuid/README.md @@ -0,0 +1,74 @@ +# UUID package for Go language + +[![Build Status](https://travis-ci.org/satori/go.uuid.svg?branch=master)](https://travis-ci.org/satori/go.uuid) +[![Coverage Status](https://coveralls.io/repos/github/satori/go.uuid/badge.svg?branch=master)](https://coveralls.io/github/satori/go.uuid) +[![GoDoc](http://godoc.org/github.com/satori/go.uuid?status.svg)](http://godoc.org/github.com/satori/go.uuid) + +This package provides pure Go implementation of Universally Unique Identifier (UUID). Supported both creation and parsing of UUIDs. + +With 100% test coverage and benchmarks out of box. + +Supported versions: +* Version 1, based on timestamp and MAC address (RFC 4122) +* Version 2, based on timestamp, MAC address and POSIX UID/GID (DCE 1.1) +* Version 3, based on MD5 hashing (RFC 4122) +* Version 4, based on random numbers (RFC 4122) +* Version 5, based on SHA-1 hashing (RFC 4122) + +## Installation + +Use the `go` command: + + $ go get github.com/satori/go.uuid + +## Requirements + +UUID package requires Go >= 1.2. + +## Example + +```go +package main + +import ( + "fmt" + "github.com/satori/go.uuid" +) + +func main() { + // Creating UUID Version 4 + // panic on error + u1 := uuid.Must(uuid.NewV4()) + fmt.Printf("UUIDv4: %s\n", u1) + + // or error handling + u2, err := uuid.NewV4() + if err != nil { + fmt.Printf("Something went wrong: %s", err) + return + } + fmt.Printf("UUIDv4: %s\n", u2) + + // Parsing UUID from string input + u2, err := uuid.FromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8") + if err != nil { + fmt.Printf("Something went wrong: %s", err) + } + fmt.Printf("Successfully parsed: %s", u2) +} +``` + +## Documentation + +[Documentation](http://godoc.org/github.com/satori/go.uuid) is hosted at GoDoc project. + +## Links +* [RFC 4122](http://tools.ietf.org/html/rfc4122) +* [DCE 1.1: Authentication and Security Services](http://pubs.opengroup.org/onlinepubs/9696989899/chap5.htm#tagcjh_08_02_01_01) + +## Copyright + +Copyright (C) 2013-2018 by Maxim Bublis . + +UUID package released under MIT License. +See [LICENSE](https://github.com/satori/go.uuid/blob/master/LICENSE) for details. diff --git a/vendor/github.com/satori/uuid/codec.go b/vendor/github.com/satori/uuid/codec.go new file mode 100644 index 000000000..656892c53 --- /dev/null +++ b/vendor/github.com/satori/uuid/codec.go @@ -0,0 +1,206 @@ +// Copyright (C) 2013-2018 by Maxim Bublis +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +package uuid + +import ( + "bytes" + "encoding/hex" + "fmt" +) + +// FromBytes returns UUID converted from raw byte slice input. +// It will return error if the slice isn't 16 bytes long. +func FromBytes(input []byte) (u UUID, err error) { + err = u.UnmarshalBinary(input) + return +} + +// FromBytesOrNil returns UUID converted from raw byte slice input. +// Same behavior as FromBytes, but returns a Nil UUID on error. +func FromBytesOrNil(input []byte) UUID { + uuid, err := FromBytes(input) + if err != nil { + return Nil + } + return uuid +} + +// FromString returns UUID parsed from string input. +// Input is expected in a form accepted by UnmarshalText. +func FromString(input string) (u UUID, err error) { + err = u.UnmarshalText([]byte(input)) + return +} + +// FromStringOrNil returns UUID parsed from string input. +// Same behavior as FromString, but returns a Nil UUID on error. +func FromStringOrNil(input string) UUID { + uuid, err := FromString(input) + if err != nil { + return Nil + } + return uuid +} + +// MarshalText implements the encoding.TextMarshaler interface. +// The encoding is the same as returned by String. +func (u UUID) MarshalText() (text []byte, err error) { + text = []byte(u.String()) + return +} + +// UnmarshalText implements the encoding.TextUnmarshaler interface. +// Following formats are supported: +// "6ba7b810-9dad-11d1-80b4-00c04fd430c8", +// "{6ba7b810-9dad-11d1-80b4-00c04fd430c8}", +// "urn:uuid:6ba7b810-9dad-11d1-80b4-00c04fd430c8" +// "6ba7b8109dad11d180b400c04fd430c8" +// ABNF for supported UUID text representation follows: +// uuid := canonical | hashlike | braced | urn +// plain := canonical | hashlike +// canonical := 4hexoct '-' 2hexoct '-' 2hexoct '-' 6hexoct +// hashlike := 12hexoct +// braced := '{' plain '}' +// urn := URN ':' UUID-NID ':' plain +// URN := 'urn' +// UUID-NID := 'uuid' +// 12hexoct := 6hexoct 6hexoct +// 6hexoct := 4hexoct 2hexoct +// 4hexoct := 2hexoct 2hexoct +// 2hexoct := hexoct hexoct +// hexoct := hexdig hexdig +// hexdig := '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | +// 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | +// 'A' | 'B' | 'C' | 'D' | 'E' | 'F' +func (u *UUID) UnmarshalText(text []byte) (err error) { + switch len(text) { + case 32: + return u.decodeHashLike(text) + case 36: + return u.decodeCanonical(text) + case 38: + return u.decodeBraced(text) + case 41: + fallthrough + case 45: + return u.decodeURN(text) + default: + return fmt.Errorf("uuid: incorrect UUID length: %s", text) + } +} + +// decodeCanonical decodes UUID string in format +// "6ba7b810-9dad-11d1-80b4-00c04fd430c8". +func (u *UUID) decodeCanonical(t []byte) (err error) { + if t[8] != '-' || t[13] != '-' || t[18] != '-' || t[23] != '-' { + return fmt.Errorf("uuid: incorrect UUID format %s", t) + } + + src := t[:] + dst := u[:] + + for i, byteGroup := range byteGroups { + if i > 0 { + src = src[1:] // skip dash + } + _, err = hex.Decode(dst[:byteGroup/2], src[:byteGroup]) + if err != nil { + return + } + src = src[byteGroup:] + dst = dst[byteGroup/2:] + } + + return +} + +// decodeHashLike decodes UUID string in format +// "6ba7b8109dad11d180b400c04fd430c8". +func (u *UUID) decodeHashLike(t []byte) (err error) { + src := t[:] + dst := u[:] + + if _, err = hex.Decode(dst, src); err != nil { + return err + } + return +} + +// decodeBraced decodes UUID string in format +// "{6ba7b810-9dad-11d1-80b4-00c04fd430c8}" or in format +// "{6ba7b8109dad11d180b400c04fd430c8}". +func (u *UUID) decodeBraced(t []byte) (err error) { + l := len(t) + + if t[0] != '{' || t[l-1] != '}' { + return fmt.Errorf("uuid: incorrect UUID format %s", t) + } + + return u.decodePlain(t[1 : l-1]) +} + +// decodeURN decodes UUID string in format +// "urn:uuid:6ba7b810-9dad-11d1-80b4-00c04fd430c8" or in format +// "urn:uuid:6ba7b8109dad11d180b400c04fd430c8". +func (u *UUID) decodeURN(t []byte) (err error) { + total := len(t) + + urn_uuid_prefix := t[:9] + + if !bytes.Equal(urn_uuid_prefix, urnPrefix) { + return fmt.Errorf("uuid: incorrect UUID format: %s", t) + } + + return u.decodePlain(t[9:total]) +} + +// decodePlain decodes UUID string in canonical format +// "6ba7b810-9dad-11d1-80b4-00c04fd430c8" or in hash-like format +// "6ba7b8109dad11d180b400c04fd430c8". +func (u *UUID) decodePlain(t []byte) (err error) { + switch len(t) { + case 32: + return u.decodeHashLike(t) + case 36: + return u.decodeCanonical(t) + default: + return fmt.Errorf("uuid: incorrrect UUID length: %s", t) + } +} + +// MarshalBinary implements the encoding.BinaryMarshaler interface. +func (u UUID) MarshalBinary() (data []byte, err error) { + data = u.Bytes() + return +} + +// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface. +// It will return error if the slice isn't 16 bytes long. +func (u *UUID) UnmarshalBinary(data []byte) (err error) { + if len(data) != Size { + err = fmt.Errorf("uuid: UUID must be exactly 16 bytes long, got %d bytes", len(data)) + return + } + copy(u[:], data) + + return +} diff --git a/vendor/github.com/satori/uuid/generator.go b/vendor/github.com/satori/uuid/generator.go new file mode 100644 index 000000000..499dc35fb --- /dev/null +++ b/vendor/github.com/satori/uuid/generator.go @@ -0,0 +1,265 @@ +// Copyright (C) 2013-2018 by Maxim Bublis +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +package uuid + +import ( + "crypto/md5" + "crypto/rand" + "crypto/sha1" + "encoding/binary" + "fmt" + "hash" + "io" + "net" + "os" + "sync" + "time" +) + +// Difference in 100-nanosecond intervals between +// UUID epoch (October 15, 1582) and Unix epoch (January 1, 1970). +const epochStart = 122192928000000000 + +type epochFunc func() time.Time +type hwAddrFunc func() (net.HardwareAddr, error) + +var ( + global = newRFC4122Generator() + + posixUID = uint32(os.Getuid()) + posixGID = uint32(os.Getgid()) +) + +// NewV1 returns UUID based on current timestamp and MAC address. +func NewV1() (UUID, error) { + return global.NewV1() +} + +// NewV2 returns DCE Security UUID based on POSIX UID/GID. +func NewV2(domain byte) (UUID, error) { + return global.NewV2(domain) +} + +// NewV3 returns UUID based on MD5 hash of namespace UUID and name. +func NewV3(ns UUID, name string) UUID { + return global.NewV3(ns, name) +} + +// NewV4 returns random generated UUID. +func NewV4() (UUID, error) { + return global.NewV4() +} + +// NewV5 returns UUID based on SHA-1 hash of namespace UUID and name. +func NewV5(ns UUID, name string) UUID { + return global.NewV5(ns, name) +} + +// Generator provides interface for generating UUIDs. +type Generator interface { + NewV1() (UUID, error) + NewV2(domain byte) (UUID, error) + NewV3(ns UUID, name string) UUID + NewV4() (UUID, error) + NewV5(ns UUID, name string) UUID +} + +// Default generator implementation. +type rfc4122Generator struct { + clockSequenceOnce sync.Once + hardwareAddrOnce sync.Once + storageMutex sync.Mutex + + rand io.Reader + + epochFunc epochFunc + hwAddrFunc hwAddrFunc + lastTime uint64 + clockSequence uint16 + hardwareAddr [6]byte +} + +func newRFC4122Generator() Generator { + return &rfc4122Generator{ + epochFunc: time.Now, + hwAddrFunc: defaultHWAddrFunc, + rand: rand.Reader, + } +} + +// NewV1 returns UUID based on current timestamp and MAC address. +func (g *rfc4122Generator) NewV1() (UUID, error) { + u := UUID{} + + timeNow, clockSeq, err := g.getClockSequence() + if err != nil { + return Nil, err + } + binary.BigEndian.PutUint32(u[0:], uint32(timeNow)) + binary.BigEndian.PutUint16(u[4:], uint16(timeNow>>32)) + binary.BigEndian.PutUint16(u[6:], uint16(timeNow>>48)) + binary.BigEndian.PutUint16(u[8:], clockSeq) + + hardwareAddr, err := g.getHardwareAddr() + if err != nil { + return Nil, err + } + copy(u[10:], hardwareAddr) + + u.SetVersion(V1) + u.SetVariant(VariantRFC4122) + + return u, nil +} + +// NewV2 returns DCE Security UUID based on POSIX UID/GID. +func (g *rfc4122Generator) NewV2(domain byte) (UUID, error) { + u, err := g.NewV1() + if err != nil { + return Nil, err + } + + switch domain { + case DomainPerson: + binary.BigEndian.PutUint32(u[:], posixUID) + case DomainGroup: + binary.BigEndian.PutUint32(u[:], posixGID) + } + + u[9] = domain + + u.SetVersion(V2) + u.SetVariant(VariantRFC4122) + + return u, nil +} + +// NewV3 returns UUID based on MD5 hash of namespace UUID and name. +func (g *rfc4122Generator) NewV3(ns UUID, name string) UUID { + u := newFromHash(md5.New(), ns, name) + u.SetVersion(V3) + u.SetVariant(VariantRFC4122) + + return u +} + +// NewV4 returns random generated UUID. +func (g *rfc4122Generator) NewV4() (UUID, error) { + u := UUID{} + if _, err := g.rand.Read(u[:]); err != nil { + return Nil, err + } + u.SetVersion(V4) + u.SetVariant(VariantRFC4122) + + return u, nil +} + +// NewV5 returns UUID based on SHA-1 hash of namespace UUID and name. +func (g *rfc4122Generator) NewV5(ns UUID, name string) UUID { + u := newFromHash(sha1.New(), ns, name) + u.SetVersion(V5) + u.SetVariant(VariantRFC4122) + + return u +} + +// Returns epoch and clock sequence. +func (g *rfc4122Generator) getClockSequence() (uint64, uint16, error) { + var err error + g.clockSequenceOnce.Do(func() { + buf := make([]byte, 2) + if _, err = g.rand.Read(buf); err != nil { + return + } + g.clockSequence = binary.BigEndian.Uint16(buf) + }) + if err != nil { + return 0, 0, err + } + + g.storageMutex.Lock() + defer g.storageMutex.Unlock() + + timeNow := g.getEpoch() + // Clock didn't change since last UUID generation. + // Should increase clock sequence. + if timeNow <= g.lastTime { + g.clockSequence++ + } + g.lastTime = timeNow + + return timeNow, g.clockSequence, nil +} + +// Returns hardware address. +func (g *rfc4122Generator) getHardwareAddr() ([]byte, error) { + var err error + g.hardwareAddrOnce.Do(func() { + if hwAddr, err := g.hwAddrFunc(); err == nil { + copy(g.hardwareAddr[:], hwAddr) + return + } + + // Initialize hardwareAddr randomly in case + // of real network interfaces absence. + if _, err = g.rand.Read(g.hardwareAddr[:]); err != nil { + return + } + // Set multicast bit as recommended by RFC 4122 + g.hardwareAddr[0] |= 0x01 + }) + if err != nil { + return []byte{}, err + } + return g.hardwareAddr[:], nil +} + +// Returns difference in 100-nanosecond intervals between +// UUID epoch (October 15, 1582) and current time. +func (g *rfc4122Generator) getEpoch() uint64 { + return epochStart + uint64(g.epochFunc().UnixNano()/100) +} + +// Returns UUID based on hashing of namespace UUID and name. +func newFromHash(h hash.Hash, ns UUID, name string) UUID { + u := UUID{} + h.Write(ns[:]) + h.Write([]byte(name)) + copy(u[:], h.Sum(nil)) + + return u +} + +// Returns hardware address. +func defaultHWAddrFunc() (net.HardwareAddr, error) { + ifaces, err := net.Interfaces() + if err != nil { + return []byte{}, err + } + for _, iface := range ifaces { + if len(iface.HardwareAddr) >= 6 { + return iface.HardwareAddr, nil + } + } + return []byte{}, fmt.Errorf("uuid: no HW address found") +} diff --git a/vendor/github.com/satori/uuid/sql.go b/vendor/github.com/satori/uuid/sql.go new file mode 100644 index 000000000..56759d390 --- /dev/null +++ b/vendor/github.com/satori/uuid/sql.go @@ -0,0 +1,78 @@ +// Copyright (C) 2013-2018 by Maxim Bublis +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +package uuid + +import ( + "database/sql/driver" + "fmt" +) + +// Value implements the driver.Valuer interface. +func (u UUID) Value() (driver.Value, error) { + return u.String(), nil +} + +// Scan implements the sql.Scanner interface. +// A 16-byte slice is handled by UnmarshalBinary, while +// a longer byte slice or a string is handled by UnmarshalText. +func (u *UUID) Scan(src interface{}) error { + switch src := src.(type) { + case []byte: + if len(src) == Size { + return u.UnmarshalBinary(src) + } + return u.UnmarshalText(src) + + case string: + return u.UnmarshalText([]byte(src)) + } + + return fmt.Errorf("uuid: cannot convert %T to UUID", src) +} + +// NullUUID can be used with the standard sql package to represent a +// UUID value that can be NULL in the database +type NullUUID struct { + UUID UUID + Valid bool +} + +// Value implements the driver.Valuer interface. +func (u NullUUID) Value() (driver.Value, error) { + if !u.Valid { + return nil, nil + } + // Delegate to UUID Value function + return u.UUID.Value() +} + +// Scan implements the sql.Scanner interface. +func (u *NullUUID) Scan(src interface{}) error { + if src == nil { + u.UUID, u.Valid = Nil, false + return nil + } + + // Delegate to UUID Scan function + u.Valid = true + return u.UUID.Scan(src) +} diff --git a/vendor/github.com/satori/uuid/uuid.go b/vendor/github.com/satori/uuid/uuid.go new file mode 100644 index 000000000..a2b8e2ca2 --- /dev/null +++ b/vendor/github.com/satori/uuid/uuid.go @@ -0,0 +1,161 @@ +// Copyright (C) 2013-2018 by Maxim Bublis +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +// Package uuid provides implementation of Universally Unique Identifier (UUID). +// Supported versions are 1, 3, 4 and 5 (as specified in RFC 4122) and +// version 2 (as specified in DCE 1.1). +package uuid + +import ( + "bytes" + "encoding/hex" +) + +// Size of a UUID in bytes. +const Size = 16 + +// UUID representation compliant with specification +// described in RFC 4122. +type UUID [Size]byte + +// UUID versions +const ( + _ byte = iota + V1 + V2 + V3 + V4 + V5 +) + +// UUID layout variants. +const ( + VariantNCS byte = iota + VariantRFC4122 + VariantMicrosoft + VariantFuture +) + +// UUID DCE domains. +const ( + DomainPerson = iota + DomainGroup + DomainOrg +) + +// String parse helpers. +var ( + urnPrefix = []byte("urn:uuid:") + byteGroups = []int{8, 4, 4, 4, 12} +) + +// Nil is special form of UUID that is specified to have all +// 128 bits set to zero. +var Nil = UUID{} + +// Predefined namespace UUIDs. +var ( + NamespaceDNS = Must(FromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8")) + NamespaceURL = Must(FromString("6ba7b811-9dad-11d1-80b4-00c04fd430c8")) + NamespaceOID = Must(FromString("6ba7b812-9dad-11d1-80b4-00c04fd430c8")) + NamespaceX500 = Must(FromString("6ba7b814-9dad-11d1-80b4-00c04fd430c8")) +) + +// Equal returns true if u1 and u2 equals, otherwise returns false. +func Equal(u1 UUID, u2 UUID) bool { + return bytes.Equal(u1[:], u2[:]) +} + +// Version returns algorithm version used to generate UUID. +func (u UUID) Version() byte { + return u[6] >> 4 +} + +// Variant returns UUID layout variant. +func (u UUID) Variant() byte { + switch { + case (u[8] >> 7) == 0x00: + return VariantNCS + case (u[8] >> 6) == 0x02: + return VariantRFC4122 + case (u[8] >> 5) == 0x06: + return VariantMicrosoft + case (u[8] >> 5) == 0x07: + fallthrough + default: + return VariantFuture + } +} + +// Bytes returns bytes slice representation of UUID. +func (u UUID) Bytes() []byte { + return u[:] +} + +// Returns canonical string representation of UUID: +// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx. +func (u UUID) String() string { + buf := make([]byte, 36) + + hex.Encode(buf[0:8], u[0:4]) + buf[8] = '-' + hex.Encode(buf[9:13], u[4:6]) + buf[13] = '-' + hex.Encode(buf[14:18], u[6:8]) + buf[18] = '-' + hex.Encode(buf[19:23], u[8:10]) + buf[23] = '-' + hex.Encode(buf[24:], u[10:]) + + return string(buf) +} + +// SetVersion sets version bits. +func (u *UUID) SetVersion(v byte) { + u[6] = (u[6] & 0x0f) | (v << 4) +} + +// SetVariant sets variant bits. +func (u *UUID) SetVariant(v byte) { + switch v { + case VariantNCS: + u[8] = (u[8]&(0xff>>1) | (0x00 << 7)) + case VariantRFC4122: + u[8] = (u[8]&(0xff>>2) | (0x02 << 6)) + case VariantMicrosoft: + u[8] = (u[8]&(0xff>>3) | (0x06 << 5)) + case VariantFuture: + fallthrough + default: + u[8] = (u[8]&(0xff>>3) | (0x07 << 5)) + } +} + +// Must is a helper that wraps a call to a function returning (UUID, error) +// and panics if the error is non-nil. It is intended for use in variable +// initializations such as +// var packageUUID = uuid.Must(uuid.FromString("123e4567-e89b-12d3-a456-426655440000")); +func Must(u UUID, err error) UUID { + if err != nil { + panic(err) + } + return u +} diff --git a/vendor/vendor.json b/vendor/vendor.json index 77d4b1b3b..4fac1660e 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -938,6 +938,12 @@ "revision": "b061729afc07e77a8aa4fad0a2fd840958f1942a", "revisionTime": "2016-09-27T10:08:44Z" }, + { + "checksumSHA1": "DZkYNX2w0P8kfM5fZcqltuA2x5Q=", + "path": "github.com/satori/uuid", + "revision": "36e9d2ebbde5e3f13ab2e25625fd453271d6522e", + "revisionTime": "2018-01-03T17:44:51Z" + }, { "checksumSHA1": "qgMa75aMGbkFY0jIqqqgVnCUoNA=", "origin": "github.com/hashicorp/terraform/vendor/github.com/ulikunitz/xz", From 7bdb3462aea296183a930737543f75e78b8a0594 Mon Sep 17 00:00:00 2001 From: Marin Salinas Date: Wed, 22 Aug 2018 17:23:18 -0500 Subject: [PATCH 04/40] Fix acceptance test --- .../resource_arm_virtual_network_gateway.go | 12 +++-------- ...source_arm_virtual_network_gateway_test.go | 20 +++++++++++++------ 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/azurestack/resource_arm_virtual_network_gateway.go b/azurestack/resource_arm_virtual_network_gateway.go index aec2753eb..b7ef97d18 100644 --- a/azurestack/resource_arm_virtual_network_gateway.go +++ b/azurestack/resource_arm_virtual_network_gateway.go @@ -84,10 +84,6 @@ func resourceArmVirtualNetworkGateway() *schema.Resource { string(network.VirtualNetworkGatewaySkuTierBasic), string(network.VirtualNetworkGatewaySkuTierStandard), string(network.VirtualNetworkGatewaySkuTierHighPerformance), - string("VirtualNetworkGatewaySkuTierUltraPerformance"), - string("VirtualNetworkGatewaySkuNameVpnGw1"), - string("VirtualNetworkGatewaySkuNameVpnGw2"), - string("VirtualNetworkGatewaySkuNameVpnGw3"), }, true), }, @@ -131,6 +127,7 @@ func resourceArmVirtualNetworkGateway() *schema.Resource { "vpn_client_configuration": { Type: schema.TypeList, Optional: true, + Computed: true, MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -546,8 +543,8 @@ func expandArmVirtualNetworkGatewayVpnClientConfig(d *schema.ResourceData) *netw VpnClientRootCertificates: &rootCerts, VpnClientRevokedCertificates: &revokedCerts, // VpnClientProtocols: &vpnClientProtocols, - // RadiusServerAddress: &confRadiusServerAddress, - // RadiusServerSecret: &confRadiusServerSecret, + //RadiusServerAddress: &confRadiusServerAddress, + //RadiusServerSecret: &confRadiusServerSecret, } } @@ -742,9 +739,6 @@ func validateArmVirtualNetworkGatewayRouteBasedVpnSku() schema.SchemaValidateFun string(network.VirtualNetworkGatewaySkuTierBasic), string(network.VirtualNetworkGatewaySkuTierStandard), string(network.VirtualNetworkGatewaySkuTierHighPerformance), - string("VirtualNetworkGatewaySkuNameVpnGw1"), - string("VirtualNetworkGatewaySkuNameVpnGw2"), - string("VirtualNetworkGatewaySkuNameVpnGw3"), }, true) } diff --git a/azurestack/resource_arm_virtual_network_gateway_test.go b/azurestack/resource_arm_virtual_network_gateway_test.go index 10414ea71..c63116380 100644 --- a/azurestack/resource_arm_virtual_network_gateway_test.go +++ b/azurestack/resource_arm_virtual_network_gateway_test.go @@ -52,7 +52,9 @@ func TestAccAzureStackVirtualNetworkGateway_lowerCaseSubnetName(t *testing.T) { }) } +//VpnGw1 sku is not supported yet. func TestAccAzureStackVirtualNetworkGateway_vpnGw1(t *testing.T) { + t.Skip() ri := acctest.RandInt() config := testAccAzureStackVirtualNetworkGateway_vpnGw1(ri, testLocation()) @@ -71,6 +73,7 @@ func TestAccAzureStackVirtualNetworkGateway_vpnGw1(t *testing.T) { }) } +//VpnGw1 sku an activeActive are not supported yet. func TestAccAzureStackVirtualNetworkGateway_activeActive(t *testing.T) { ri := acctest.RandInt() config := testAccAzureStackVirtualNetworkGateway_activeActive(ri, testLocation()) @@ -111,6 +114,7 @@ func TestAccAzureStackVirtualNetworkGateway_standard(t *testing.T) { }) } +//VpnGw2 sku is not supported yet. func TestAccAzureStackVirtualNetworkGateway_vpnGw2(t *testing.T) { resourceName := "azurestack_virtual_network_gateway.test" ri := acctest.RandInt() @@ -132,6 +136,7 @@ func TestAccAzureStackVirtualNetworkGateway_vpnGw2(t *testing.T) { }) } +//VpnGw3 sku is not supported yet. func TestAccAzureStackVirtualNetworkGateway_vpnGw3(t *testing.T) { resourceName := "azurestack_virtual_network_gateway.test" ri := acctest.RandInt() @@ -167,8 +172,9 @@ func TestAccAzureStackVirtualNetworkGateway_vpnClientConfig(t *testing.T) { Config: config, Check: resource.ComposeTestCheckFunc( testCheckAzureStackVirtualNetworkGatewayExists(resourceName), - resource.TestCheckResourceAttr(resourceName, "vpn_client_configuration.0.radius_server_address", "1.2.3.4"), - resource.TestCheckResourceAttr(resourceName, "vpn_client_configuration.0.vpn_client_protocols.#", "2"), + resource.TestCheckResourceAttr(resourceName, "vpn_client_configuration.0.address_space.0", "10.2.0.0/24"), + //resource.TestCheckResourceAttr(resourceName, "vpn_client_configuration.0.radius_server_address", "1.2.3.4"), + //resource.TestCheckResourceAttr(resourceName, "vpn_client_configuration.0.vpn_client_protocols.#", "2"), ), }, }, @@ -473,7 +479,8 @@ resource "azurestack_virtual_network_gateway" "test" { type = "Vpn" vpn_type = "RouteBased" - sku = "VpnGw1" + sku = "Basic" + #sku = "VpnGw1" ip_configuration { public_ip_address_id = "${azurestack_public_ip.test.id}" @@ -483,10 +490,11 @@ resource "azurestack_virtual_network_gateway" "test" { vpn_client_configuration { address_space = ["10.2.0.0/24"] - vpn_client_protocols = ["SSTP", "IkeV2"] + #vpn_client_protocols, radius_server_address and radius_server_certificate are not supported yet. + #vpn_client_protocols = ["SSTP", "IkeV2"] - radius_server_address = "1.2.3.4" - radius_server_secret = "1234" + #radius_server_address = "1.2.3.4" + # radius_server_secret = "1234" } } `, rInt, location, rInt, rInt, rInt) From bc79034a9747b2a8e35f80d497c6c69f09b90910 Mon Sep 17 00:00:00 2001 From: Marin Salinas Date: Wed, 22 Aug 2018 19:20:34 -0500 Subject: [PATCH 05/40] Add virtual_network_gateway data source --- .../data_source_virtual_network_gateway.go | 369 ++++++++++++++++++ ...ata_source_virtual_network_gateway_test.go | 79 ++++ azurestack/provider.go | 13 +- 3 files changed, 455 insertions(+), 6 deletions(-) create mode 100644 azurestack/data_source_virtual_network_gateway.go create mode 100644 azurestack/data_source_virtual_network_gateway_test.go diff --git a/azurestack/data_source_virtual_network_gateway.go b/azurestack/data_source_virtual_network_gateway.go new file mode 100644 index 000000000..ac816ccf0 --- /dev/null +++ b/azurestack/data_source_virtual_network_gateway.go @@ -0,0 +1,369 @@ +package azurestack + +import ( + "fmt" + + "bytes" + + "github.com/Azure/azure-sdk-for-go/profiles/2017-03-09/network/mgmt/network" + "github.com/hashicorp/terraform/helper/hashcode" + "github.com/hashicorp/terraform/helper/schema" + "github.com/terraform-providers/terraform-provider-azurestack/azurestack/utils" +) + +func dataSourceArmVirtualNetworkGateway() *schema.Resource { + return &schema.Resource{ + Read: dataSourceArmVirtualNetworkGatewayRead, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + + "resource_group_name": resourceGroupNameForDataSourceSchema(), + + "location": locationForDataSourceSchema(), + + "type": { + Type: schema.TypeString, + Computed: true, + }, + + "vpn_type": { + Type: schema.TypeString, + Computed: true, + }, + + "enable_bgp": { + Type: schema.TypeBool, + Computed: true, + }, + + "active_active": { + Type: schema.TypeBool, + Computed: true, + }, + + "sku": { + Type: schema.TypeString, + Computed: true, + }, + + "ip_configuration": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Computed: true, + }, + "private_ip_address_allocation": { + Type: schema.TypeString, + Computed: true, + }, + "subnet_id": { + Type: schema.TypeString, + Computed: true, + }, + "public_ip_address_id": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + + "vpn_client_configuration": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "address_space": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "root_certificate": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Computed: true, + }, + "public_cert_data": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + Set: hashVirtualNetworkGatewayDataSourceRootCert, + }, + "revoked_certificate": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Computed: true, + }, + "thumbprint": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + Set: hashVirtualNetworkGatewayDataSourceRevokedCert, + }, + "radius_server_address": { + Type: schema.TypeString, + Computed: true, + }, + "radius_server_secret": { + Type: schema.TypeString, + Computed: true, + }, + "vpn_client_protocols": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + }, + }, + + "bgp_settings": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "asn": { + Type: schema.TypeInt, + Computed: true, + }, + "peering_address": { + Type: schema.TypeString, + Computed: true, + }, + "peer_weight": { + Type: schema.TypeInt, + Computed: true, + }, + }, + }, + }, + + "default_local_network_gateway_id": { + Type: schema.TypeString, + Computed: true, + }, + + "tags": tagsForDataSourceSchema(), + }, + } +} + +func dataSourceArmVirtualNetworkGatewayRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).vnetGatewayClient + ctx := meta.(*ArmClient).StopContext + + name := d.Get("name").(string) + resGroup := d.Get("resource_group_name").(string) + + resp, err := client.Get(ctx, resGroup, name) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Virtual Network Gateway %q (Resource Group %q) was not found", name, resGroup) + } + + return fmt.Errorf("Error making Read request on AzureRM Virtual Network Gateway %q (Resource Group %q): %+v", name, resGroup, err) + } + + d.SetId(*resp.ID) + + d.Set("name", resp.Name) + d.Set("resource_group_name", resGroup) + if location := resp.Location; location != nil { + d.Set("location", azureStackNormalizeLocation(*location)) + } + + if resp.VirtualNetworkGatewayPropertiesFormat != nil { + gw := *resp.VirtualNetworkGatewayPropertiesFormat + + d.Set("type", string(gw.GatewayType)) + d.Set("enable_bgp", gw.EnableBgp) + //d.Set("active_active", gw.ActiveActive) + + if string(gw.VpnType) != "" { + d.Set("vpn_type", string(gw.VpnType)) + } + + if gw.GatewayDefaultSite != nil { + d.Set("default_local_network_gateway_id", gw.GatewayDefaultSite.ID) + } + + if gw.Sku != nil { + d.Set("sku", string(gw.Sku.Name)) + } + + if err := d.Set("ip_configuration", flattenArmVirtualNetworkGatewayDataSourceIPConfigurations(gw.IPConfigurations)); err != nil { + return fmt.Errorf("Error setting `ip_configuration`: %+v", err) + } + + vpnConfigFlat := flattenArmVirtualNetworkGatewayDataSourceVpnClientConfig(gw.VpnClientConfiguration) + if err := d.Set("vpn_client_configuration", vpnConfigFlat); err != nil { + return fmt.Errorf("Error setting `vpn_client_configuration`: %+v", err) + } + + bgpSettingsFlat := flattenArmVirtualNetworkGatewayDataSourceBgpSettings(gw.BgpSettings) + if err := d.Set("bgp_settings", bgpSettingsFlat); err != nil { + return fmt.Errorf("Error setting `bgp_settings`: %+v", err) + } + } + + flattenAndSetTags(d, &resp.Tags) + + return nil +} + +func flattenArmVirtualNetworkGatewayDataSourceIPConfigurations(ipConfigs *[]network.VirtualNetworkGatewayIPConfiguration) []interface{} { + flat := make([]interface{}, 0) + + if ipConfigs != nil { + for _, cfg := range *ipConfigs { + props := cfg.VirtualNetworkGatewayIPConfigurationPropertiesFormat + v := make(map[string]interface{}) + + if name := cfg.Name; name != nil { + v["name"] = *name + } + v["private_ip_address_allocation"] = string(props.PrivateIPAllocationMethod) + + if subnet := props.Subnet; subnet != nil { + if id := subnet.ID; id != nil { + v["subnet_id"] = *id + } + } + + if pip := props.PublicIPAddress; pip != nil { + if id := pip.ID; id != nil { + v["public_ip_address_id"] = *id + } + } + + flat = append(flat, v) + } + } + + return flat +} + +func flattenArmVirtualNetworkGatewayDataSourceVpnClientConfig(cfg *network.VpnClientConfiguration) []interface{} { + if cfg == nil { + return []interface{}{} + } + + flat := make(map[string]interface{}) + + addressSpace := make([]interface{}, 0) + if pool := cfg.VpnClientAddressPool; pool != nil { + if prefixes := pool.AddressPrefixes; prefixes != nil { + for _, addr := range *prefixes { + addressSpace = append(addressSpace, addr) + } + } + } + flat["address_space"] = addressSpace + + rootCerts := make([]interface{}, 0) + if certs := cfg.VpnClientRootCertificates; certs != nil { + for _, cert := range *certs { + v := map[string]interface{}{ + "name": *cert.Name, + "public_cert_data": *cert.VpnClientRootCertificatePropertiesFormat.PublicCertData, + } + rootCerts = append(rootCerts, v) + } + } + flat["root_certificate"] = schema.NewSet(hashVirtualNetworkGatewayDataSourceRootCert, rootCerts) + + revokedCerts := make([]interface{}, 0) + if certs := cfg.VpnClientRevokedCertificates; certs != nil { + for _, cert := range *certs { + v := map[string]interface{}{ + "name": *cert.Name, + "thumbprint": *cert.VpnClientRevokedCertificatePropertiesFormat.Thumbprint, + } + revokedCerts = append(revokedCerts, v) + } + } + flat["revoked_certificate"] = schema.NewSet(hashVirtualNetworkGatewayDataSourceRevokedCert, revokedCerts) + + // vpnClientProtocols := &schema.Set{F: schema.HashString} + // if vpnProtocols := cfg.VpnClientProtocols; vpnProtocols != nil { + // for _, protocol := range *vpnProtocols { + // vpnClientProtocols.Add(string(protocol)) + // } + // } + // flat["vpn_client_protocols"] = vpnClientProtocols + + // if v := cfg.RadiusServerAddress; v != nil { + // flat["radius_server_address"] = *v + // } + + // if v := cfg.RadiusServerSecret; v != nil { + // flat["radius_server_secret"] = *v + // } + + return []interface{}{flat} +} + +func flattenArmVirtualNetworkGatewayDataSourceBgpSettings(settings *network.BgpSettings) []interface{} { + output := make([]interface{}, 0) + + if settings != nil { + flat := make(map[string]interface{}) + + if asn := settings.Asn; asn != nil { + flat["asn"] = int(*asn) + } + if address := settings.BgpPeeringAddress; address != nil { + flat["peering_address"] = *address + } + if weight := settings.PeerWeight; weight != nil { + flat["peer_weight"] = int(*weight) + } + + output = append(output, flat) + } + + return output +} + +func hashVirtualNetworkGatewayDataSourceRootCert(v interface{}) int { + var buf bytes.Buffer + m := v.(map[string]interface{}) + + buf.WriteString(fmt.Sprintf("%s-", m["name"].(string))) + buf.WriteString(fmt.Sprintf("%s-", m["public_cert_data"].(string))) + + return hashcode.String(buf.String()) +} + +func hashVirtualNetworkGatewayDataSourceRevokedCert(v interface{}) int { + var buf bytes.Buffer + m := v.(map[string]interface{}) + + buf.WriteString(fmt.Sprintf("%s-", m["name"].(string))) + buf.WriteString(fmt.Sprintf("%s-", m["thumbprint"].(string))) + + return hashcode.String(buf.String()) +} diff --git a/azurestack/data_source_virtual_network_gateway_test.go b/azurestack/data_source_virtual_network_gateway_test.go new file mode 100644 index 000000000..dc33f011c --- /dev/null +++ b/azurestack/data_source_virtual_network_gateway_test.go @@ -0,0 +1,79 @@ +package azurestack + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccAzureStackDataSourceVirtualNetworkGateway_basic(t *testing.T) { + ri := acctest.RandInt() + config := testAccAzureStackDataSourceVirtualNetworkGateway_basic(ri, testLocation()) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureStackVirtualNetworkGatewayDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureStackVirtualNetworkGatewayExists("data.azurestack_virtual_network_gateway.test"), + ), + }, + }, + }) +} + +func testAccAzureStackDataSourceVirtualNetworkGateway_basic(rInt int, location string) string { + return fmt.Sprintf(` +resource "azurestack_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurestack_virtual_network" "test" { + name = "acctestvn-%d" + location = "${azurestack_resource_group.test.location}" + resource_group_name = "${azurestack_resource_group.test.name}" + address_space = ["10.0.0.0/16"] +} + +resource "azurestack_subnet" "test" { + name = "GatewaySubnet" + resource_group_name = "${azurestack_resource_group.test.name}" + virtual_network_name = "${azurestack_virtual_network.test.name}" + address_prefix = "10.0.1.0/24" +} + +resource "azurestack_public_ip" "test" { + name = "acctestpip-%d" + location = "${azurestack_resource_group.test.location}" + resource_group_name = "${azurestack_resource_group.test.name}" + public_ip_address_allocation = "Dynamic" +} + +resource "azurestack_virtual_network_gateway" "test" { + name = "acctestvng-%d" + location = "${azurestack_resource_group.test.location}" + resource_group_name = "${azurestack_resource_group.test.name}" + + type = "Vpn" + vpn_type = "RouteBased" + sku = "Basic" + + ip_configuration { + public_ip_address_id = "${azurestack_public_ip.test.id}" + private_ip_address_allocation = "Dynamic" + subnet_id = "${azurestack_subnet.test.id}" + } +} + +data "azurestack_virtual_network_gateway" "test" { + name = "${azurestack_virtual_network_gateway.test.name}" + resource_group_name = "${azurestack_virtual_network_gateway.test.resource_group_name}" +} +`, rInt, location, rInt, rInt, rInt) +} diff --git a/azurestack/provider.go b/azurestack/provider.go index 4e030a5d6..c8c64e8cf 100644 --- a/azurestack/provider.go +++ b/azurestack/provider.go @@ -65,12 +65,13 @@ func Provider() terraform.ResourceProvider { }, DataSourcesMap: map[string]*schema.Resource{ - "azurestack_client_config": dataSourceArmClientConfig(), - "azurestack_network_interface": dataSourceArmNetworkInterface(), - "azurestack_network_security_group": dataSourceArmNetworkSecurityGroup(), - "azurestack_resource_group": dataSourceArmResourceGroup(), - "azurestack_storage_account": dataSourceArmStorageAccount(), - "azurestack_virtual_network": dataSourceArmVirtualNetwork(), + "azurestack_client_config": dataSourceArmClientConfig(), + "azurestack_network_interface": dataSourceArmNetworkInterface(), + "azurestack_network_security_group": dataSourceArmNetworkSecurityGroup(), + "azurestack_resource_group": dataSourceArmResourceGroup(), + "azurestack_storage_account": dataSourceArmStorageAccount(), + "azurestack_virtual_network": dataSourceArmVirtualNetwork(), + "azurestack_virtual_network_gateway": dataSourceArmVirtualNetworkGateway(), }, ResourcesMap: map[string]*schema.Resource{ From 6e72a53495b30e2697d97b67e85e648539c9accb Mon Sep 17 00:00:00 2001 From: Marin Salinas Date: Thu, 23 Aug 2018 10:39:29 -0500 Subject: [PATCH 06/40] Add website documentation for virtual_nework_gateway resource and data_source --- website/azurestack.erb | 9 +- .../d/virtual_network_gateway.html.markdown | 119 +++++++ .../r/virtual_network_gateway.html.markdown | 300 ++++++++++++++++++ 3 files changed, 426 insertions(+), 2 deletions(-) create mode 100644 website/docs/d/virtual_network_gateway.html.markdown create mode 100644 website/docs/r/virtual_network_gateway.html.markdown diff --git a/website/azurestack.erb b/website/azurestack.erb index 8536673d8..ee7cbcb96 100644 --- a/website/azurestack.erb +++ b/website/azurestack.erb @@ -35,11 +35,12 @@ > azurestack_storage_account - - > azurestack_virtual_network + > + azurestack_virtual_network_gateway + @@ -118,6 +119,10 @@ > azurestack_virtual_network + + > + azurestack_virtual_network_gateway + diff --git a/website/docs/d/virtual_network_gateway.html.markdown b/website/docs/d/virtual_network_gateway.html.markdown new file mode 100644 index 000000000..6d7219f06 --- /dev/null +++ b/website/docs/d/virtual_network_gateway.html.markdown @@ -0,0 +1,119 @@ +--- +layout: "azurestack" +page_title: "Azure Stack: azurestack_virtual_network_gateway" +sidebar_current: "docs-azurestack-datasource-virtual-network-x" +description: |- + Get information about the specified Virtual Network Gateway. +--- + +# Data Source: azurestack_virtual_network_gateway + +Use this data source to access the properties of an Azure Virtual Network Gateway. + +## Example Usage + +```hcl +data "azurestack_virtual_network_gateway" "test" { + name = "production" + resource_group_name = "networking" +} + +output "virtual_network_gateway_id" { + value = "${data.azurestack_virtual_network_gateway.test.id}" +} +``` + +## Argument Reference + +* `name` - (Required) Specifies the name of the Virtual Network Gateway. +* `resource_group_name` - (Required) Specifies the name of the resource group the Virtual Network Gateway is located in. + +## Attributes Reference + +* `id` - The ID of the Virtual Network Gateway. + +* `location` - The location/region where the Virtual Network Gateway is located. + +* `type` - The type of the Virtual Network Gateway. + +* `vpn_type` - The routing type of the Virtual Network Gateway. + +* `enable_bgp` - Will BGP (Border Gateway Protocol) will be enabled + for this Virtual Network Gateway. + +* `active_active` - (Optional) Is this an Active-Active Gateway? + +* `default_local_network_gateway_id` - The ID of the local network gateway + through which outbound Internet traffic from the virtual network in which the + gateway is created will be routed (*forced tunneling*). Refer to the + [Azure documentation on forced tunneling](https://docs.microsoft.com/en-us/azure/vpn-gateway/vpn-gateway-forced-tunneling-rm). + +* `sku` - Configuration of the size and capacity of the Virtual Network Gateway. + +* `ip_configuration` - One or two `ip_configuration` blocks documented below. + +* `vpn_client_configuration` - A `vpn_client_configuration` block which is documented below. + +* `tags` - A mapping of tags assigned to the resource. + +The `ip_configuration` block supports: + +* `name` - A user-defined name of the IP configuration. + +* `private_ip_address_allocation` - Defines how the private IP address + of the gateways virtual interface is assigned. + +* `subnet_id` - The ID of the gateway subnet of a virtual network in + which the virtual network gateway will be created. It is mandatory that + the associated subnet is named `GatewaySubnet`. Therefore, each virtual + network can contain at most a single Virtual Network Gateway. + +* `public_ip_address_id` - The ID of the Public IP Address associated + with the Virtual Network Gateway. + +The `vpn_client_configuration` block supports: + +* `address_space` - The address space out of which ip addresses for + vpn clients will be taken. You can provide more than one address space, e.g. + in CIDR notation. + +* `root_certificate` - One or more `root_certificate` blocks which are + defined below. These root certificates are used to sign the client certificate + used by the VPN clients to connect to the gateway. + +* `revoked_certificate` - One or more `revoked_certificate` blocks which + are defined below. + +* `radius_server_address` - (Optional) The address of the Radius server. + This setting is incompatible with the use of `root_certificate` and `revoked_certificate`. + +* `radius_server_secret` - (Optional) The secret used by the Radius server. + This setting is incompatible with the use of `root_certificate` and `revoked_certificate`. + +* `vpn_client_protocols` - (Optional) List of the protocols supported by the vpn client. + The supported values are `SSTP` and `IkeV2`. + +The `bgp_settings` block supports: + +* `asn` - The Autonomous System Number (ASN) to use as part of the BGP. + +* `peering_address` - The BGP peer IP address of the virtual network + gateway. This address is needed to configure the created gateway as a BGP Peer + on the on-premises VPN devices. + +* `peer_weight` - The weight added to routes which have been learned + through BGP peering. + +The `root_certificate` block supports: + +* `name` - The user-defined name of the root certificate. + +* `public_cert_data` - The public certificate of the root certificate + authority. The certificate must be provided in Base-64 encoded X.509 format + (PEM). + +The `root_revoked_certificate` block supports: + +* `name` - The user-defined name of the revoked certificate. + +* `public_cert_data` - The SHA1 thumbprint of the certificate to be revoked. \ No newline at end of file diff --git a/website/docs/r/virtual_network_gateway.html.markdown b/website/docs/r/virtual_network_gateway.html.markdown new file mode 100644 index 000000000..4483db38d --- /dev/null +++ b/website/docs/r/virtual_network_gateway.html.markdown @@ -0,0 +1,300 @@ +--- +layout: "azurestack" +page_title: "Azure Stack: azurestack_virtual_network_gateway_connection" +sidebar_current: "docs-azurestack-resource-network-virtual-network-gateway-connection" +description: |- + Manages a connection in an existing Virtual Network Gateway. +--- + +# azurestack_virtual_network_gateway_connection + +Manages a connection in an existing Virtual Network Gateway. + +## Example Usage + +### Site-to-Site connection + +The following example shows a connection between an Azure virtual network +and an on-premises VPN device and network. + +```hcl +resource "azurestack_resource_group" "test" { + name = "test" + location = "West US" +} + +resource "azurestack_virtual_network" "test" { + name = "test" + location = "${azurestack_resource_group.test.location}" + resource_group_name = "${azurestack_resource_group.test.name}" + address_space = ["10.0.0.0/16"] +} + +resource "azurestack_subnet" "test" { + name = "GatewaySubnet" + resource_group_name = "${azurestack_resource_group.test.name}" + virtual_network_name = "${azurestack_virtual_network.test.name}" + address_prefix = "10.0.1.0/24" +} + +resource "azurestack_local_network_gateway" "onpremise" { + name = "onpremise" + location = "${azurestack_resource_group.test.location}" + resource_group_name = "${azurestack_resource_group.test.name}" + gateway_address = "168.62.225.23" + address_space = ["10.1.1.0/24"] +} + +resource "azurestack_public_ip" "test" { + name = "test" + location = "${azurestack_resource_group.test.location}" + resource_group_name = "${azurestack_resource_group.test.name}" + public_ip_address_allocation = "Dynamic" +} + +resource "azurestack_virtual_network_gateway" "test" { + name = "test" + location = "${azurestack_resource_group.test.location}" + resource_group_name = "${azurestack_resource_group.test.name}" + + type = "Vpn" + vpn_type = "RouteBased" + + active_active = false + enable_bgp = false + sku = "Basic" + + ip_configuration { + public_ip_address_id = "${azurestack_public_ip.test.id}" + private_ip_address_allocation = "Dynamic" + subnet_id = "${azurestack_subnet.test.id}" + } +} + +resource "azurestack_virtual_network_gateway_connection" "onpremise" { + name = "onpremise" + location = "${azurestack_resource_group.test.location}" + resource_group_name = "${azurestack_resource_group.test.name}" + + type = "IPsec" + virtual_network_gateway_id = "${azurestack_virtual_network_gateway.test.id}" + local_network_gateway_id = "${azurestack_local_network_gateway.onpremise.id}" + + shared_key = "4-v3ry-53cr37-1p53c-5h4r3d-k3y" +} +``` + +### VNet-to-VNet connection + +The following example shows a connection between two Azure virtual network +in different locations/regions. + +```hcl +resource "azurestack_resource_group" "us" { + name = "us" + location = "East US" +} + +resource "azurestack_virtual_network" "us" { + name = "us" + location = "${azurestack_resource_group.us.location}" + resource_group_name = "${azurestack_resource_group.us.name}" + address_space = ["10.0.0.0/16"] +} + +resource "azurestack_subnet" "us_gateway" { + name = "GatewaySubnet" + resource_group_name = "${azurestack_resource_group.us.name}" + virtual_network_name = "${azurestack_virtual_network.us.name}" + address_prefix = "10.0.1.0/24" +} + +resource "azurestack_public_ip" "us" { + name = "us" + location = "${azurestack_resource_group.us.location}" + resource_group_name = "${azurestack_resource_group.us.name}" + public_ip_address_allocation = "Dynamic" +} + +resource "azurestack_virtual_network_gateway" "us" { + name = "us-gateway" + location = "${azurestack_resource_group.us.location}" + resource_group_name = "${azurestack_resource_group.us.name}" + + type = "Vpn" + vpn_type = "RouteBased" + sku = "Basic" + + ip_configuration { + public_ip_address_id = "${azurestack_public_ip.us.id}" + private_ip_address_allocation = "Dynamic" + subnet_id = "${azurestack_subnet.us_gateway.id}" + } +} + +resource "azurestack_resource_group" "europe" { + name = "europe" + location = "West Europe" +} + +resource "azurestack_virtual_network" "europe" { + name = "europe" + location = "${azurestack_resource_group.europe.location}" + resource_group_name = "${azurestack_resource_group.europe.name}" + address_space = ["10.1.0.0/16"] +} + +resource "azurestack_subnet" "europe_gateway" { + name = "GatewaySubnet" + resource_group_name = "${azurestack_resource_group.europe.name}" + virtual_network_name = "${azurestack_virtual_network.europe.name}" + address_prefix = "10.1.1.0/24" +} + +resource "azurestack_public_ip" "europe" { + name = "europe" + location = "${azurestack_resource_group.europe.location}" + resource_group_name = "${azurestack_resource_group.europe.name}" + public_ip_address_allocation = "Dynamic" +} + +resource "azurestack_virtual_network_gateway" "europe" { + name = "europe-gateway" + location = "${azurestack_resource_group.europe.location}" + resource_group_name = "${azurestack_resource_group.europe.name}" + + type = "Vpn" + vpn_type = "RouteBased" + sku = "Basic" + + ip_configuration { + public_ip_address_id = "${azurestack_public_ip.europe.id}" + private_ip_address_allocation = "Dynamic" + subnet_id = "${azurestack_subnet.europe_gateway.id}" + } +} + +resource "azurestack_virtual_network_gateway_connection" "us_to_europe" { + name = "us-to-europe" + location = "${azurestack_resource_group.us.location}" + resource_group_name = "${azurestack_resource_group.us.name}" + + type = "Vnet2Vnet" + virtual_network_gateway_id = "${azurestack_virtual_network_gateway.us.id}" + peer_virtual_network_gateway_id = "${azurestack_virtual_network_gateway.europe.id}" + + shared_key = "4-v3ry-53cr37-1p53c-5h4r3d-k3y" +} + +resource "azurestack_virtual_network_gateway_connection" "europe_to_us" { + name = "europe-to-us" + location = "${azurestack_resource_group.europe.location}" + resource_group_name = "${azurestack_resource_group.europe.name}" + + type = "Vnet2Vnet" + virtual_network_gateway_id = "${azurestack_virtual_network_gateway.europe.id}" + peer_virtual_network_gateway_id = "${azurestack_virtual_network_gateway.us.id}" + + shared_key = "4-v3ry-53cr37-1p53c-5h4r3d-k3y" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of the connection. Changing the name forces a + new resource to be created. + +* `resource_group_name` - (Required) The name of the resource group in which to + create the connection Changing the name forces a new resource to be created. + +* `location` - (Required) The location/region where the connection is + located. Changing this forces a new resource to be created. + +* `type` - (Required) The type of connection. Valid options are `IPsec` + (Site-to-Site), `ExpressRoute` (ExpressRoute), and `Vnet2Vnet` (VNet-to-VNet). + Each connection type requires different mandatory arguments (refer to the + examples above). Changing the connection type will force a new connection + to be created. + +* `virtual_network_gateway_id` - (Required) The ID of the Virtual Network Gateway + in which the connection will be created. Changing the gateway forces a new + resource to be created. + +* `authorization_key` - (Optional) The authorization key associated with the + Express Route Circuit. This field is required only if the type is an + ExpressRoute connection. + +* `express_route_circuit_id` - (Optional) The ID of the Express Route Circuit + when creating an ExpressRoute connection (i.e. when `type` is `ExpressRoute`). + The Express Route Circuit can be in the same or in a different subscription. + +* `peer_virtual_network_gateway_id` - (Optional) The ID of the peer virtual + network gateway when creating a VNet-to-VNet connection (i.e. when `type` + is `Vnet2Vnet`). The peer Virtual Network Gateway can be in the same or + in a different subscription. + +* `local_network_gateway_id` - (Optional) The ID of the local network gateway + when creating Site-to-Site connection (i.e. when `type` is `IPsec`). + +* `routing_weight` - (Optional) The routing weight. Defaults to `10`. + +* `shared_key` - (Optional) The shared IPSec key. A key must be provided if a + Site-to-Site or VNet-to-VNet connection is created whereas ExpressRoute + connections do not need a shared key. + +* `enable_bgp` - (Optional) If `true`, BGP (Border Gateway Protocol) is enabled + for this connection. Defaults to `false`. + +* `use_policy_based_traffic_selectors` - (Optional) If `true`, policy-based traffic + selectors are enabled for this connection. Enabling policy-based traffic + selectors requires an `ipsec_policy` block. Defaults to `false`. + +* `ipsec_policy` (Optional) A `ipsec_policy` block which is documented below. + Only a single policy can be defined for a connection. For details on + custom policies refer to [the relevant section in the Azure documentation](https://docs.microsoft.com/en-us/azure/vpn-gateway/vpn-gateway-ipsecikepolicy-rm-powershell). + +* `tags` - (Optional) A mapping of tags to assign to the resource. + +The `ipsec_policy` block supports: + +* `dh_group` - (Required) The DH group used in IKE phase 1 for initial SA. Valid + options are `DHGroup1`, `DHGroup14`, `DHGroup2`, `DHGroup2048`, `DHGroup24`, + `ECP256`, `ECP384`, or `None`. + +* `ike_encryption` - (Required) The IKE encryption algorithm. Valid + options are `AES128`, `AES192`, `AES256`, `DES`, or `DES3`. + +* `ike_integrity` - (Required) The IKE integrity algorithm. Valid + options are `MD5`, `SHA1`, `SHA256`, or `SHA384`. + +* `ipsec_encryption` - (Required) The IPSec encryption algorithm. Valid + options are `AES128`, `AES192`, `AES256`, `DES`, `DES3`, `GCMAES128`, `GCMAES192`, `GCMAES256`, or `None`. + +* `ipsec_integrity` - (Required) The IPSec integrity algorithm. Valid + options are `GCMAES128`, `GCMAES192`, `GCMAES256`, `MD5`, `SHA1`, or `SHA256`. + +* `pfs_group` - (Required) The DH group used in IKE phase 2 for new child SA. + Valid options are `ECP256`, `ECP384`, `PFS1`, `PFS2`, `PFS2048`, `PFS24`, + or `None`. + +* `sa_datasize` - (Optional) The IPSec SA payload size in KB. Must be at least + `1024` KB. Defaults to `102400000` KB. + +* `sa_lifetime` - (Optional) The IPSec SA lifetime in seconds. Must be at least + `300` seconds. Defaults to `27000` seconds. + +## Attributes Reference + +The following attributes are exported: + +* `id` - The connection ID. + +## Import + +Virtual Network Gateway Connections can be imported using their `resource id`, e.g. + +``` +terraform import azurestack_virtual_network_gateway_connection.testConnection /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myGroup1/providers/Microsoft.Network/connections/myConnection1 +``` \ No newline at end of file From ed6794484bda4dd97e452a989c6c9376e9064715 Mon Sep 17 00:00:00 2001 From: Antonio Cabrera Date: Thu, 23 Aug 2018 17:16:31 -0500 Subject: [PATCH 07/40] Skip some unsupported tests --- azurestack/resource_arm_virtual_network_gateway_test.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/azurestack/resource_arm_virtual_network_gateway_test.go b/azurestack/resource_arm_virtual_network_gateway_test.go index c63116380..6e8e85fb2 100644 --- a/azurestack/resource_arm_virtual_network_gateway_test.go +++ b/azurestack/resource_arm_virtual_network_gateway_test.go @@ -75,6 +75,9 @@ func TestAccAzureStackVirtualNetworkGateway_vpnGw1(t *testing.T) { //VpnGw1 sku an activeActive are not supported yet. func TestAccAzureStackVirtualNetworkGateway_activeActive(t *testing.T) { + + t.Skip() + ri := acctest.RandInt() config := testAccAzureStackVirtualNetworkGateway_activeActive(ri, testLocation()) @@ -116,6 +119,9 @@ func TestAccAzureStackVirtualNetworkGateway_standard(t *testing.T) { //VpnGw2 sku is not supported yet. func TestAccAzureStackVirtualNetworkGateway_vpnGw2(t *testing.T) { + + t.Skip() + resourceName := "azurestack_virtual_network_gateway.test" ri := acctest.RandInt() config := testAccAzureStackVirtualNetworkGateway_sku(ri, testLocation(), "VpnGw2") @@ -138,6 +144,9 @@ func TestAccAzureStackVirtualNetworkGateway_vpnGw2(t *testing.T) { //VpnGw3 sku is not supported yet. func TestAccAzureStackVirtualNetworkGateway_vpnGw3(t *testing.T) { + + t.Skip() + resourceName := "azurestack_virtual_network_gateway.test" ri := acctest.RandInt() config := testAccAzureStackVirtualNetworkGateway_sku(ri, testLocation(), "VpnGw3") From f16a5ce85409420325d151cb6abfe69fdd688d5a Mon Sep 17 00:00:00 2001 From: Antonio Cabrera Date: Thu, 23 Aug 2018 17:16:49 -0500 Subject: [PATCH 08/40] Remove some unused fields and refactor --- .../resource_arm_virtual_network_gateway.go | 97 ++++++++++--------- 1 file changed, 52 insertions(+), 45 deletions(-) diff --git a/azurestack/resource_arm_virtual_network_gateway.go b/azurestack/resource_arm_virtual_network_gateway.go index b7ef97d18..7df8e233f 100644 --- a/azurestack/resource_arm_virtual_network_gateway.go +++ b/azurestack/resource_arm_virtual_network_gateway.go @@ -10,7 +10,6 @@ import ( "github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" - "github.com/terraform-providers/terraform-provider-azurestack/azurestack/helpers/validate" "github.com/terraform-providers/terraform-provider-azurestack/azurestack/utils" ) @@ -28,15 +27,17 @@ func resourceArmVirtualNetworkGateway() *schema.Resource { Schema: map[string]*schema.Schema{ "name": { - Type: schema.TypeString, - Required: true, - ForceNew: true, + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.NoZeroValues, }, "resource_group_name": { - Type: schema.TypeString, - Required: true, - ForceNew: true, + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.NoZeroValues, }, "location": locationSchema(), @@ -70,11 +71,12 @@ func resourceArmVirtualNetworkGateway() *schema.Resource { Computed: true, }, - "active_active": { - Type: schema.TypeBool, - Optional: true, - Computed: true, - }, + // Not Supported by AzureStack + // "active_active": { + // Type: schema.TypeBool, + // Optional: true, + // Computed: true, + // }, "sku": { Type: schema.TypeString, @@ -142,10 +144,10 @@ func resourceArmVirtualNetworkGateway() *schema.Resource { Type: schema.TypeSet, Optional: true, - ConflictsWith: []string{ - "vpn_client_configuration.0.radius_server_address", - "vpn_client_configuration.0.radius_server_secret", - }, + // ConflictsWith: []string{ + // "vpn_client_configuration.0.radius_server_address", + // "vpn_client_configuration.0.radius_server_secret", + // }, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "name": { @@ -163,10 +165,12 @@ func resourceArmVirtualNetworkGateway() *schema.Resource { "revoked_certificate": { Type: schema.TypeSet, Optional: true, - ConflictsWith: []string{ - "vpn_client_configuration.0.radius_server_address", - "vpn_client_configuration.0.radius_server_secret", - }, + + // Not supported by AzureStack + // ConflictsWith: []string{ + // "vpn_client_configuration.0.radius_server_address", + // "vpn_client_configuration.0.radius_server_secret", + // }, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "name": { @@ -181,23 +185,23 @@ func resourceArmVirtualNetworkGateway() *schema.Resource { }, Set: hashVirtualNetworkGatewayRevokedCert, }, - "radius_server_address": { - Type: schema.TypeString, - Optional: true, - ConflictsWith: []string{ - "vpn_client_configuration.0.root_certificate", - "vpn_client_configuration.0.revoked_certificate", - }, - ValidateFunc: validate.IPv4Address, - }, - "radius_server_secret": { - Type: schema.TypeString, - Optional: true, - ConflictsWith: []string{ - "vpn_client_configuration.0.root_certificate", - "vpn_client_configuration.0.revoked_certificate", - }, - }, + // "radius_server_address": { + // Type: schema.TypeString, + // Optional: true, + // ConflictsWith: []string{ + // "vpn_client_configuration.0.root_certificate", + // "vpn_client_configuration.0.revoked_certificate", + // }, + // ValidateFunc: validate.IPv4Address, + // }, + // "radius_server_secret": { + // Type: schema.TypeString, + // Optional: true, + // ConflictsWith: []string{ + // "vpn_client_configuration.0.root_certificate", + // "vpn_client_configuration.0.revoked_certificate", + // }, + // }, "vpn_client_protocols": { Type: schema.TypeSet, Optional: true, @@ -276,8 +280,7 @@ func resourceArmVirtualNetworkGatewayCreateUpdate(d *schema.ResourceData, meta i return fmt.Errorf("Error Creating/Updating AzureRM Virtual Network Gateway %q (Resource Group %q): %+v", name, resGroup, err) } - err = future.WaitForCompletionRef(ctx, client.Client) - if err != nil { + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { return fmt.Errorf("Error waiting for completion of AzureRM Virtual Network Gateway %q (Resource Group %q): %+v", name, resGroup, err) } @@ -321,10 +324,11 @@ func resourceArmVirtualNetworkGatewayRead(d *schema.ResourceData, meta interface if gw := resp.VirtualNetworkGatewayPropertiesFormat; gw != nil { d.Set("type", string(gw.GatewayType)) d.Set("enable_bgp", gw.EnableBgp) + // ActiveActive is not supported by AzureStack // d.Set("active_active", gw.ActiveActive) - if string(gw.VpnType) != "" { - d.Set("vpn_type", string(gw.VpnType)) + if vpnType := string(gw.VpnType); vpnType != "" { + d.Set("vpn_type", vpnType) } if gw.GatewayDefaultSite != nil { @@ -370,8 +374,7 @@ func resourceArmVirtualNetworkGatewayDelete(d *schema.ResourceData, meta interfa return fmt.Errorf("Error deleting Virtual Network Gateway %q (Resource Group %q): %+v", name, resGroup, err) } - err = future.WaitForCompletionRef(ctx, client.Client) - if err != nil { + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { return fmt.Errorf("Error waiting for deletion of Virtual Network Gateway %q (Resource Group %q): %+v", name, resGroup, err) } @@ -382,13 +385,18 @@ func getArmVirtualNetworkGatewayProperties(d *schema.ResourceData) (*network.Vir gatewayType := network.VirtualNetworkGatewayType(d.Get("type").(string)) vpnType := network.VpnType(d.Get("vpn_type").(string)) enableBgp := d.Get("enable_bgp").(bool) + + // ActiveActive is not supported by AzureStack // activeActive := d.Get("active_active").(bool) props := &network.VirtualNetworkGatewayPropertiesFormat{ GatewayType: gatewayType, VpnType: vpnType, EnableBgp: &enableBgp, + + // ActiveActive is not supported by AzureStack // ActiveActive: &activeActive, + Sku: expandArmVirtualNetworkGatewaySku(d), IPConfigurations: expandArmVirtualNetworkGatewayIPConfigurations(d), } @@ -441,12 +449,11 @@ func expandArmVirtualNetworkGatewayBgpSettings(d *schema.ResourceData) *network. bgpSets := d.Get("bgp_settings").([]interface{}) bgp := bgpSets[0].(map[string]interface{}) - asn := int64(bgp["asn"].(int)) peeringAddress := bgp["peering_address"].(string) peerWeight := int32(bgp["peer_weight"].(int)) return &network.BgpSettings{ - Asn: &asn, + Asn: utils.Int64(int64(bgp["asn"].(int))), BgpPeeringAddress: &peeringAddress, PeerWeight: &peerWeight, } From f5493f921ef3c54d2184b5782df633ea40666746 Mon Sep 17 00:00:00 2001 From: Antonio Cabrera Date: Fri, 24 Aug 2018 14:16:41 -0500 Subject: [PATCH 09/40] Add azurestack template deployment resource --- azurestack/config.go | 5 + azurestack/provider.go | 1 + .../resource_arm_template_deployment.go | 299 +++++++ .../resource_arm_template_deployment_test.go | 758 ++++++++++++++++++ 4 files changed, 1063 insertions(+) create mode 100644 azurestack/resource_arm_template_deployment.go create mode 100644 azurestack/resource_arm_template_deployment_test.go diff --git a/azurestack/config.go b/azurestack/config.go index 2b5dc64e9..b54c136f5 100644 --- a/azurestack/config.go +++ b/azurestack/config.go @@ -70,6 +70,7 @@ type ArmClient struct { routeTablesClient network.RouteTablesClient resourceGroupsClient resources.GroupsClient + deploymentsClient resources.DeploymentsClient } func (c *ArmClient) configureClient(client *autorest.Client, auth autorest.Authorizer) { @@ -263,6 +264,10 @@ func (c *ArmClient) registerResourcesClients(endpoint, subscriptionId string, au c.configureClient(&resourcesClient.Client, auth) c.resourcesClient = resourcesClient + deploymentsClient := resources.NewDeploymentsClientWithBaseURI(endpoint, subscriptionId) + c.configureClient(&deploymentsClient.Client, auth) + c.deploymentsClient = deploymentsClient + resourceGroupsClient := resources.NewGroupsClientWithBaseURI(endpoint, subscriptionId) c.configureClient(&resourceGroupsClient.Client, auth) c.resourceGroupsClient = resourceGroupsClient diff --git a/azurestack/provider.go b/azurestack/provider.go index eb6163cb8..09faa56be 100644 --- a/azurestack/provider.go +++ b/azurestack/provider.go @@ -96,6 +96,7 @@ func Provider() terraform.ResourceProvider { "azurestack_storage_blob": resourceArmStorageBlob(), "azurestack_storage_container": resourceArmStorageContainer(), "azurestack_subnet": resourceArmSubnet(), + "azurestack_template_deployment": resourceArmTemplateDeployment(), "azurestack_virtual_network": resourceArmVirtualNetwork(), "azurestack_virtual_machine": resourceArmVirtualMachine(), "azurestack_virtual_machine_extension": resourceArmVirtualMachineExtensions(), diff --git a/azurestack/resource_arm_template_deployment.go b/azurestack/resource_arm_template_deployment.go new file mode 100644 index 000000000..8228ec00a --- /dev/null +++ b/azurestack/resource_arm_template_deployment.go @@ -0,0 +1,299 @@ +package azurestack + +import ( + "context" + "encoding/json" + "fmt" + "log" + "strconv" + "strings" + "time" + + "github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2016-02-01/resources" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" + "github.com/terraform-providers/terraform-provider-azurestack/azurestack/utils" +) + +func resourceArmTemplateDeployment() *schema.Resource { + return &schema.Resource{ + Create: resourceArmTemplateDeploymentCreate, + Read: resourceArmTemplateDeploymentRead, + Update: resourceArmTemplateDeploymentCreate, + Delete: resourceArmTemplateDeploymentDelete, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "resource_group_name": resourceGroupNameSchema(), + + "template_body": { + Type: schema.TypeString, + Optional: true, + Computed: true, + StateFunc: normalizeJson, + }, + + "parameters": { + Type: schema.TypeMap, + Optional: true, + ConflictsWith: []string{"parameters_body"}, + }, + + "parameters_body": { + Type: schema.TypeString, + Optional: true, + StateFunc: normalizeJson, + ConflictsWith: []string{"parameters"}, + }, + + "deployment_mode": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + string(resources.Complete), + string(resources.Incremental), + }, true), + DiffSuppressFunc: ignoreCaseDiffSuppressFunc, + }, + + "outputs": { + Type: schema.TypeMap, + Computed: true, + }, + }, + } +} + +func resourceArmTemplateDeploymentCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient) + deployClient := client.deploymentsClient + ctx := client.StopContext + + name := d.Get("name").(string) + resourceGroup := d.Get("resource_group_name").(string) + deploymentMode := d.Get("deployment_mode").(string) + + log.Printf("[INFO] preparing arguments for AzureRM Template Deployment creation.") + properties := resources.DeploymentProperties{ + Mode: resources.DeploymentMode(deploymentMode), + } + + if v, ok := d.GetOk("parameters"); ok { + params := v.(map[string]interface{}) + + newParams := make(map[string]interface{}, len(params)) + for key, val := range params { + newParams[key] = struct { + Value interface{} + }{ + Value: val, + } + } + + properties.Parameters = &newParams + } + + if v, ok := d.GetOk("parameters_body"); ok { + params, err := expandParametersBody(v.(string)) + if err != nil { + return err + } + + properties.Parameters = ¶ms + } + + if v, ok := d.GetOk("template_body"); ok { + template, err := expandTemplateBody(v.(string)) + if err != nil { + return err + } + + properties.Template = &template + } + + deployment := resources.Deployment{ + Properties: &properties, + } + + future, err := deployClient.CreateOrUpdate(ctx, resourceGroup, name, deployment) + if err != nil { + return fmt.Errorf("Error creating deployment: %+v", err) + } + + err = future.WaitForCompletionRef(ctx, deployClient.Client) + if err != nil { + return fmt.Errorf("Error creating deployment: %+v", err) + } + + read, err := deployClient.Get(ctx, resourceGroup, name) + if err != nil { + return err + } + if read.ID == nil { + return fmt.Errorf("Cannot read Template Deployment %s (resource group %s) ID", name, resourceGroup) + } + + d.SetId(*read.ID) + + return resourceArmTemplateDeploymentRead(d, meta) +} + +func resourceArmTemplateDeploymentRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient) + deployClient := client.deploymentsClient + ctx := client.StopContext + + id, err := parseAzureResourceID(d.Id()) + if err != nil { + return err + } + resourceGroup := id.ResourceGroup + name := id.Path["deployments"] + if name == "" { + name = id.Path["Deployments"] + } + + resp, err := deployClient.Get(ctx, resourceGroup, name) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + d.SetId("") + return nil + } + return fmt.Errorf("Error making Read request on Azure RM Template Deployment %q (Resource Group %q): %+v", name, resourceGroup, err) + } + + outputs := make(map[string]string, 0) + if outs := resp.Properties.Outputs; outs != nil { + outsVal := outs.(map[string]interface{}) + if len(outsVal) > 0 { + for key, output := range outsVal { + log.Printf("[DEBUG] Processing deployment output %s", key) + outputMap := output.(map[string]interface{}) + outputValue, ok := outputMap["value"] + if !ok { + log.Printf("[DEBUG] No value - skipping") + continue + } + outputType, ok := outputMap["type"] + if !ok { + log.Printf("[DEBUG] No type - skipping") + continue + } + + var outputValueString string + switch strings.ToLower(outputType.(string)) { + case "bool": + outputValueString = strconv.FormatBool(outputValue.(bool)) + + case "string": + outputValueString = outputValue.(string) + + case "int": + outputValueString = fmt.Sprint(outputValue) + + default: + log.Printf("[WARN] Ignoring output %s: Outputs of type %s are not currently supported in azurerm_template_deployment.", + key, outputType) + continue + } + outputs[key] = outputValueString + } + } + } + + return d.Set("outputs", outputs) +} + +func resourceArmTemplateDeploymentDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient) + deployClient := client.deploymentsClient + ctx := client.StopContext + + id, err := parseAzureResourceID(d.Id()) + if err != nil { + return err + } + resourceGroup := id.ResourceGroup + name := id.Path["deployments"] + if name == "" { + name = id.Path["Deployments"] + } + + _, err = deployClient.Delete(ctx, resourceGroup, name) + if err != nil { + return err + } + + return waitForTemplateDeploymentToBeDeleted(ctx, deployClient, resourceGroup, name) +} + +// TODO: move this out into the new `helpers` structure +func expandParametersBody(body string) (map[string]interface{}, error) { + var parametersBody map[string]interface{} + err := json.Unmarshal([]byte(body), ¶metersBody) + if err != nil { + return nil, fmt.Errorf("Error Expanding the parameters_body for Azure RM Template Deployment") + } + return parametersBody, nil +} + +func expandTemplateBody(template string) (map[string]interface{}, error) { + var templateBody map[string]interface{} + err := json.Unmarshal([]byte(template), &templateBody) + if err != nil { + return nil, fmt.Errorf("Error Expanding the template_body for Azure RM Template Deployment") + } + return templateBody, nil +} + +func normalizeJson(jsonString interface{}) string { + if jsonString == nil || jsonString == "" { + return "" + } + var j interface{} + err := json.Unmarshal([]byte(jsonString.(string)), &j) + if err != nil { + return fmt.Sprintf("Error parsing JSON: %+v", err) + } + b, _ := json.Marshal(j) + return string(b[:]) +} + +func waitForTemplateDeploymentToBeDeleted(ctx context.Context, client resources.DeploymentsClient, resourceGroup, name string) error { + // we can't use the Waiter here since the API returns a 200 once it's deleted which is considered a polling status code.. + log.Printf("[DEBUG] Waiting for Template Deployment (%q in Resource Group %q) to be deleted", name, resourceGroup) + stateConf := &resource.StateChangeConf{ + Pending: []string{"200"}, + Target: []string{"404"}, + Refresh: templateDeploymentStateStatusCodeRefreshFunc(ctx, client, resourceGroup, name), + Timeout: 40 * time.Minute, + } + if _, err := stateConf.WaitForState(); err != nil { + return fmt.Errorf("Error waiting for Template Deployment (%q in Resource Group %q) to be deleted: %+v", name, resourceGroup, err) + } + + return nil +} + +func templateDeploymentStateStatusCodeRefreshFunc(ctx context.Context, client resources.DeploymentsClient, resourceGroup, name string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + res, err := client.Get(ctx, resourceGroup, name) + + log.Printf("Retrieving Template Deployment %q (Resource Group %q) returned Status %d", resourceGroup, name, res.StatusCode) + + if err != nil { + if utils.ResponseWasNotFound(res.Response) { + return res, strconv.Itoa(res.StatusCode), nil + } + return nil, "", fmt.Errorf("Error polling for the status of the Template Deployment %q (RG: %q): %+v", name, resourceGroup, err) + } + + return res, strconv.Itoa(res.StatusCode), nil + } +} diff --git a/azurestack/resource_arm_template_deployment_test.go b/azurestack/resource_arm_template_deployment_test.go new file mode 100644 index 000000000..73481bc16 --- /dev/null +++ b/azurestack/resource_arm_template_deployment_test.go @@ -0,0 +1,758 @@ +package azurestack + +import ( + "fmt" + "net/http" + "regexp" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccAzureStackTemplateDeployment_basic(t *testing.T) { + ri := acctest.RandInt() + config := testAccAzureStackTemplateDeployment_basicMultiple(ri, testLocation()) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureStackTemplateDeploymentDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureStackTemplateDeploymentExists("azurestack_template_deployment.test"), + ), + }, + }, + }) +} + +func TestAccAzureStackTemplateDeployment_disappears(t *testing.T) { + ri := acctest.RandInt() + config := testAccAzureStackTemplateDeployment_basicSingle(ri, testLocation()) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureStackTemplateDeploymentDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureStackTemplateDeploymentExists("azurestack_template_deployment.test"), + testCheckAzureStackTemplateDeploymentDisappears("azurestack_template_deployment.test"), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +// Storage Account type is not supported +func TestAccAzureStackTemplateDeployment_withParams(t *testing.T) { + + t.Skip() + + ri := acctest.RandInt() + config := testAccAzureStackTemplateDeployment_withParams(ri, testLocation()) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureStackTemplateDeploymentDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureStackTemplateDeploymentExists("azurestack_template_deployment.test"), + resource.TestCheckResourceAttr("azurestack_template_deployment.test", "outputs.testOutput", "Output Value"), + ), + }, + }, + }) +} + +// Provider doesn't support resource: azurestack_key_vault_secret +func TestAccAzureStackTemplateDeployment_withParamsBody(t *testing.T) { + + t.Skip() + + ri := acctest.RandInt() + config := testaccAzureStackTemplateDeployment_withParamsBody(ri, testLocation()) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureStackTemplateDeploymentDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureStackTemplateDeploymentExists("azurestack_template_deployment.test"), + resource.TestCheckResourceAttr("azurestack_template_deployment.test", "outputs.testOutput", "Output Value"), + ), + }, + }, + }) + +} + + +// Storage account type is not supported +func TestAccAzureStackTemplateDeployment_withOutputs(t *testing.T) { + + t.Skip() + + ri := acctest.RandInt() + config := testAccAzureStackTemplateDeployment_withOutputs(ri, testLocation()) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureStackTemplateDeploymentDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureStackTemplateDeploymentExists("azurestack_template_deployment.test"), + resource.TestCheckOutput("tfIntOutput", "-123"), + resource.TestCheckOutput("tfStringOutput", "Standard_GRS"), + + // these values *should* be 'true' and 'false' but, + // due to a bug in the way terraform represents bools at various times these are for now 0 and 1 + // see https://github.com/hashicorp/terraform/issues/13512#issuecomment-295389523 + // at a later date these may return the expected 'true' / 'false' and should be changed back + resource.TestCheckOutput("tfFalseOutput", "0"), + resource.TestCheckOutput("tfTrueOutput", "1"), + resource.TestCheckResourceAttr("azurestack_template_deployment.test", "outputs.stringOutput", "Standard_GRS"), + ), + }, + }, + }) +} + +func TestAccAzureStackTemplateDeployment_withError(t *testing.T) { + ri := acctest.RandInt() + config := testAccAzureStackTemplateDeployment_withError(ri, testLocation()) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureStackTemplateDeploymentDestroy, + Steps: []resource.TestStep{ + { + Config: config, + ExpectError: regexp.MustCompile("Code=\"DeploymentFailed\""), + }, + }, + }) +} + +func testCheckAzureStackTemplateDeploymentExists(name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + // Ensure we have enough information in state to look up in API + rs, ok := s.RootModule().Resources[name] + if !ok { + return fmt.Errorf("Not found: %s", name) + } + + name := rs.Primary.Attributes["name"] + resourceGroup, hasResourceGroup := rs.Primary.Attributes["resource_group_name"] + if !hasResourceGroup { + return fmt.Errorf("Bad: no resource group found in state for template deployment: %s", name) + } + + client := testAccProvider.Meta().(*ArmClient).deploymentsClient + ctx := testAccProvider.Meta().(*ArmClient).StopContext + + resp, err := client.Get(ctx, resourceGroup, name) + if err != nil { + return fmt.Errorf("Bad: Get on deploymentsClient: %s", err) + } + + if resp.StatusCode == http.StatusNotFound { + return fmt.Errorf("Bad: TemplateDeployment %q (resource group: %q) does not exist", name, resourceGroup) + } + + return nil + } +} + +func testCheckAzureStackTemplateDeploymentDisappears(name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + // Ensure we have enough information in state to look up in API + rs, ok := s.RootModule().Resources[name] + if !ok { + return fmt.Errorf("Not found: %s", name) + } + + deploymentName := rs.Primary.Attributes["name"] + resourceGroup, hasResourceGroup := rs.Primary.Attributes["resource_group_name"] + if !hasResourceGroup { + return fmt.Errorf("Bad: no resource group found in state for template deployment: %s", name) + } + + client := testAccProvider.Meta().(*ArmClient).deploymentsClient + ctx := testAccProvider.Meta().(*ArmClient).StopContext + + _, err := client.Delete(ctx, resourceGroup, deploymentName) + if err != nil { + return fmt.Errorf("Failed deleting Deployment %q (Resource Group %q): %+v", deploymentName, resourceGroup, err) + } + + return waitForTemplateDeploymentToBeDeleted(ctx, client, resourceGroup, deploymentName) + } +} + +func testCheckAzureStackTemplateDeploymentDestroy(s *terraform.State) error { + client := testAccProvider.Meta().(*ArmClient).deploymentsClient + ctx := testAccProvider.Meta().(*ArmClient).StopContext + + for _, rs := range s.RootModule().Resources { + if rs.Type != "azurestack_template_deployment" { + continue + } + + name := rs.Primary.Attributes["name"] + resourceGroup := rs.Primary.Attributes["resource_group_name"] + + resp, err := client.Get(ctx, resourceGroup, name) + + if err != nil { + return nil + } + + if resp.StatusCode != http.StatusNotFound { + return fmt.Errorf("Template Deployment still exists:\n%#v", resp.Properties) + } + } + + return nil +} + +func testAccAzureStackTemplateDeployment_basicSingle(rInt int, location string) string { + return fmt.Sprintf(` +resource "azurestack_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" + } + + resource "azurestack_template_deployment" "test" { + name = "acctesttemplate-%d" + resource_group_name = "${azurestack_resource_group.test.name}" + template_body = < Date: Fri, 24 Aug 2018 14:17:13 -0500 Subject: [PATCH 10/40] Add azurestack template deployment documentation --- website/azurestack.erb | 9 ++ .../docs/r/template_deployment.html.markdown | 133 ++++++++++++++++++ 2 files changed, 142 insertions(+) create mode 100644 website/docs/r/template_deployment.html.markdown diff --git a/website/azurestack.erb b/website/azurestack.erb index 731b6027d..9fa7b900e 100644 --- a/website/azurestack.erb +++ b/website/azurestack.erb @@ -184,6 +184,15 @@ + > + Template Resources + + + <% end %> diff --git a/website/docs/r/template_deployment.html.markdown b/website/docs/r/template_deployment.html.markdown new file mode 100644 index 000000000..a17bef0d9 --- /dev/null +++ b/website/docs/r/template_deployment.html.markdown @@ -0,0 +1,133 @@ +--- +layout: "azurestack" +page_title: "Azure Resource Manager: azurestack_template_deployment" +sidebar_current: "docs-azurestack-resource-template-deployment" +description: |- + Manages a template deployment of resources. +--- + +# azurestack_template_deployment + +Create a template deployment of resources + +~> **Note on AzureStack Template Deployments:** Due to the way the underlying Azure API is designed, Terraform can only manage the deployment of the AzureStack Template - and not any resources which are created by it. +This means that when deleting the `azurestack_template_deployment` resource, Terraform will only remove the reference to the deployment, whilst leaving any resources created by that AzureStack Template Deployment. +One workaround for this is to use a unique Resource Group for each AzureStack Template Deployment, which means deleting the Resource Group would contain any resources created within it - however this isn't ideal. [More information](https://docs.microsoft.com/en-us/rest/api/resources/deployments#Deployments_Delete). + +## Example Usage + +~> **Note:** This example uses [Storage Accounts](storage_account.html) and [Public IP's](public_ip.html) which are natively supported by Terraform - we'd highly recommend using the Native Resources where possible instead rather than an AzureStack Template, for the reasons outlined above. + +```hcl +resource "azurestack_resource_group" "test" { + name = "acctestRG-01" + location = "West US" +} + +resource "azurestack_template_deployment" "test" { + name = "acctesttemplate-01" + resource_group_name = "${azurestack_resource_group.test.name}" + + template_body = < **Note:** There's an [`file` interpolation function available](https://www.terraform.io/docs/configuration/interpolation.html#file-path-) which allows you to read this from an external file, which helps makes this more resource more readable. + +* `parameters` - (Optional) Specifies the name and value pairs that define the deployment parameters for the template. + +* `parameters_body` - (Optional) Specifies a valid Azure JSON parameters file that define the deployment parameters. It can contain KeyVault references + +~> **Note:** There's an [`file` interpolation function available](https://www.terraform.io/docs/configuration/interpolation.html#file-path-) which allows you to read this from an external file, which helps makes this more resource more readable. + +## Attributes Reference + +The following attributes are exported: + +* `id` - The Template Deployment ID. + +* `outputs` - A map of supported scalar output types returned from the deployment (currently, Azure Template Deployment outputs of type String, Int and Bool are supported, and are converted to strings - others will be ignored) and can be accessed using `.outputs["name"]`. + +## Note + +Terraform does not know about the individual resources created by Azure using a deployment template and therefore cannot delete these resources during a destroy. Destroying a template deployment removes the associated deployment operations, but will not delete the Azure resources created by the deployment. In order to delete these resources, the containing resource group must also be destroyed. [More information](https://docs.microsoft.com/en-us/rest/api/resources/deployments#Deployments_Delete). From c2de544ff598bf309216c5335544b76c1e015b46 Mon Sep 17 00:00:00 2001 From: Antonio Cabrera Date: Fri, 24 Aug 2018 15:17:15 -0500 Subject: [PATCH 11/40] Add validation to some fields --- azurestack/resource_arm_template_deployment.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/azurestack/resource_arm_template_deployment.go b/azurestack/resource_arm_template_deployment.go index 8228ec00a..84dc55a51 100644 --- a/azurestack/resource_arm_template_deployment.go +++ b/azurestack/resource_arm_template_deployment.go @@ -25,9 +25,10 @@ func resourceArmTemplateDeployment() *schema.Resource { Schema: map[string]*schema.Schema{ "name": { - Type: schema.TypeString, - Required: true, - ForceNew: true, + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.NoZeroValues, }, "resource_group_name": resourceGroupNameSchema(), @@ -50,6 +51,7 @@ func resourceArmTemplateDeployment() *schema.Resource { Optional: true, StateFunc: normalizeJson, ConflictsWith: []string{"parameters"}, + ValidateFunc: validation.NoZeroValues, }, "deployment_mode": { From a0838be764f3af3bd3bfe09fa5377384f47dc2d6 Mon Sep 17 00:00:00 2001 From: Antonio Cabrera Date: Mon, 27 Aug 2018 12:52:18 -0500 Subject: [PATCH 12/40] Run go fmt on test file --- azurestack/resource_arm_template_deployment_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/azurestack/resource_arm_template_deployment_test.go b/azurestack/resource_arm_template_deployment_test.go index 73481bc16..92d23f026 100644 --- a/azurestack/resource_arm_template_deployment_test.go +++ b/azurestack/resource_arm_template_deployment_test.go @@ -96,7 +96,6 @@ func TestAccAzureStackTemplateDeployment_withParamsBody(t *testing.T) { } - // Storage account type is not supported func TestAccAzureStackTemplateDeployment_withOutputs(t *testing.T) { From 61712abb9fda0e4ae3c570022f01f8fe7032b725 Mon Sep 17 00:00:00 2001 From: mbjorgan Date: Mon, 27 Aug 2018 21:00:42 +0200 Subject: [PATCH 13/40] Add subnet data source --- azurestack/data_source_subnet.go | 102 +++++++++++++ azurestack/data_source_subnet_test.go | 198 ++++++++++++++++++++++++++ azurestack/provider.go | 1 + 3 files changed, 301 insertions(+) create mode 100644 azurestack/data_source_subnet.go create mode 100644 azurestack/data_source_subnet_test.go diff --git a/azurestack/data_source_subnet.go b/azurestack/data_source_subnet.go new file mode 100644 index 000000000..c064c134a --- /dev/null +++ b/azurestack/data_source_subnet.go @@ -0,0 +1,102 @@ +package azurestack + +import ( + "fmt" + + "github.com/hashicorp/terraform/helper/schema" + "github.com/terraform-providers/terraform-provider-azurestack/azurestack/utils" +) + +func dataSourceArmSubnet() *schema.Resource { + return &schema.Resource{ + Read: dataSourceArmSubnetRead, + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + + "virtual_network_name": { + Type: schema.TypeString, + Required: true, + }, + + "resource_group_name": { + Type: schema.TypeString, + Required: true, + }, + + "address_prefix": { + Type: schema.TypeString, + Computed: true, + }, + + "network_security_group_id": { + Type: schema.TypeString, + Computed: true, + }, + + "route_table_id": { + Type: schema.TypeString, + Computed: true, + }, + + "ip_configurations": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, + }, + }, + } +} + +func dataSourceArmSubnetRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).subnetClient + ctx := meta.(*ArmClient).StopContext + + name := d.Get("name").(string) + virtualNetworkName := d.Get("virtual_network_name").(string) + resourceGroup := d.Get("resource_group_name").(string) + + resp, err := client.Get(ctx, resourceGroup, virtualNetworkName, name, "") + if err != nil { + return fmt.Errorf("Error reading Subnet: %+v", err) + } + + d.SetId(*resp.ID) + + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error: Subnet %q (Virtual Network %q / Resource Group %q) was not found", name, resourceGroup, virtualNetworkName) + } + return fmt.Errorf("Error making Read request on Azure Subnet %q: %+v", name, err) + } + + d.Set("name", name) + d.Set("resource_group_name", resourceGroup) + d.Set("virtual_network_name", virtualNetworkName) + + if props := resp.SubnetPropertiesFormat; props != nil { + d.Set("address_prefix", props.AddressPrefix) + + if props.NetworkSecurityGroup != nil { + d.Set("network_security_group_id", props.NetworkSecurityGroup.ID) + } else { + d.Set("network_security_group_id", "") + } + + if props.RouteTable != nil { + d.Set("route_table_id", props.RouteTable.ID) + } else { + d.Set("route_table_id", "") + } + + ips := flattenSubnetIPConfigurations(props.IPConfigurations) + if err := d.Set("ip_configurations", ips); err != nil { + return err + } + } + + return nil +} diff --git a/azurestack/data_source_subnet_test.go b/azurestack/data_source_subnet_test.go new file mode 100644 index 000000000..13648ab85 --- /dev/null +++ b/azurestack/data_source_subnet_test.go @@ -0,0 +1,198 @@ +package azurestack + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccDataSourceArmSubnet_basic(t *testing.T) { + resourceName := "data.azurestack_subnet.test" + ri := acctest.RandInt() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceArmSubnet_basic(ri, testLocation()), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(resourceName, "name"), + resource.TestCheckResourceAttrSet(resourceName, "resource_group_name"), + resource.TestCheckResourceAttrSet(resourceName, "virtual_network_name"), + resource.TestCheckResourceAttrSet(resourceName, "address_prefix"), + resource.TestCheckResourceAttr(resourceName, "network_security_group_id", ""), + resource.TestCheckResourceAttr(resourceName, "route_table_id", ""), + ), + }, + }, + }) +} + +func TestAccDataSourceArmSubnet_networkSecurityGroup(t *testing.T) { + dataSourceName := "data.azurestack_subnet.test" + ri := acctest.RandInt() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceArmSubnet_networkSecurityGroup(ri, testLocation()), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(dataSourceName, "name"), + resource.TestCheckResourceAttrSet(dataSourceName, "resource_group_name"), + resource.TestCheckResourceAttrSet(dataSourceName, "virtual_network_name"), + resource.TestCheckResourceAttrSet(dataSourceName, "address_prefix"), + resource.TestCheckResourceAttrSet(dataSourceName, "network_security_group_id"), + resource.TestCheckResourceAttr(dataSourceName, "route_table_id", ""), + ), + }, + }, + }) +} + +func TestAccDataSourceArmSubnet_routeTable(t *testing.T) { + dataSourceName := "data.azurestack_subnet.test" + ri := acctest.RandInt() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceArmSubnet_routeTable(ri, testLocation()), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(dataSourceName, "name"), + resource.TestCheckResourceAttrSet(dataSourceName, "resource_group_name"), + resource.TestCheckResourceAttrSet(dataSourceName, "virtual_network_name"), + resource.TestCheckResourceAttrSet(dataSourceName, "address_prefix"), + resource.TestCheckResourceAttr(dataSourceName, "network_security_group_id", ""), + resource.TestCheckResourceAttrSet(dataSourceName, "route_table_id"), + ), + }, + }, + }) +} + +func testAccDataSourceArmSubnet_basic(rInt int, location string) string { + return fmt.Sprintf(` +resource "azurestack_resource_group" "test" { + name = "acctest%d-rg" + location = "%s" +} + +resource "azurestack_virtual_network" "test" { + name = "acctest%d-vn" + address_space = ["10.0.0.0/16"] + location = "${azurestack_resource_group.test.location}" + resource_group_name = "${azurestack_resource_group.test.name}" +} + +resource "azurestack_subnet" "test" { + name = "acctest%d-private" + resource_group_name = "${azurestack_resource_group.test.name}" + virtual_network_name = "${azurestack_virtual_network.test.name}" + address_prefix = "10.0.0.0/24" +} + +data "azurestack_subnet" "test" { + name = "${azurestack_subnet.test.name}" + resource_group_name = "${azurestack_resource_group.test.name}" + virtual_network_name = "${azurestack_virtual_network.test.name}" +} +`, rInt, location, rInt, rInt) +} + +func testAccDataSourceArmSubnet_networkSecurityGroup(rInt int, location string) string { + return fmt.Sprintf(` +resource "azurestack_resource_group" "test" { + name = "acctest%d-rg" + location = "%s" +} + +resource "azurestack_network_security_group" "test" { + name = "acctestnsg%d" + location = "${azurestack_resource_group.test.location}" + resource_group_name = "${azurestack_resource_group.test.name}" + + security_rule { + name = "test123" + priority = 100 + direction = "Inbound" + access = "Allow" + protocol = "Tcp" + source_port_range = "*" + destination_port_range = "*" + source_address_prefix = "*" + destination_address_prefix = "*" + } +} + +resource "azurestack_virtual_network" "test" { + name = "acctest%d-vn" + address_space = ["10.0.0.0/16"] + location = "${azurestack_resource_group.test.location}" + resource_group_name = "${azurestack_resource_group.test.name}" +} + +resource "azurestack_subnet" "test" { + name = "acctest%d-private" + resource_group_name = "${azurestack_resource_group.test.name}" + virtual_network_name = "${azurestack_virtual_network.test.name}" + address_prefix = "10.0.0.0/24" + network_security_group_id = "${azurestack_network_security_group.test.id}" +} + +data "azurestack_subnet" "test" { + name = "${azurestack_subnet.test.name}" + resource_group_name = "${azurestack_resource_group.test.name}" + virtual_network_name = "${azurestack_virtual_network.test.name}" +} +`, rInt, location, rInt, rInt, rInt) +} + +func testAccDataSourceArmSubnet_routeTable(rInt int, location string) string { + return fmt.Sprintf(` +resource "azurestack_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurestack_route_table" "test" { + name = "acctest-%d" + location = "${azurestack_resource_group.test.location}" + resource_group_name = "${azurestack_resource_group.test.name}" + + route { + name = "acctest-%d" + address_prefix = "10.100.0.0/14" + next_hop_type = "VirtualAppliance" + next_hop_in_ip_address = "10.10.1.1" + } +} + +resource "azurestack_virtual_network" "test" { + name = "acctestvirtnet%d" + address_space = ["10.0.0.0/16"] + location = "${azurestack_resource_group.test.location}" + resource_group_name = "${azurestack_resource_group.test.name}" +} + +resource "azurestack_subnet" "test" { + name = "acctestsubnet%d" + resource_group_name = "${azurestack_resource_group.test.name}" + virtual_network_name = "${azurestack_virtual_network.test.name}" + address_prefix = "10.0.2.0/24" + route_table_id = "${azurestack_route_table.test.id}" +} + +data "azurestack_subnet" "test" { + name = "${azurestack_subnet.test.name}" + resource_group_name = "${azurestack_resource_group.test.name}" + virtual_network_name = "${azurestack_virtual_network.test.name}" +} +`, rInt, location, rInt, rInt, rInt, rInt) +} diff --git a/azurestack/provider.go b/azurestack/provider.go index eb6163cb8..6c0ff6fe1 100644 --- a/azurestack/provider.go +++ b/azurestack/provider.go @@ -72,6 +72,7 @@ func Provider() terraform.ResourceProvider { "azurestack_storage_account": dataSourceArmStorageAccount(), "azurestack_virtual_network": dataSourceArmVirtualNetwork(), "azurestack_route_table": dataSourceArmRouteTable(), + "azurestack_subnet": dataSourceArmSubnet(), }, ResourcesMap: map[string]*schema.Resource{ From e7ccfea5198ec345f887cf2b3110e38ed2940973 Mon Sep 17 00:00:00 2001 From: mbjorgan Date: Mon, 27 Aug 2018 21:10:33 +0200 Subject: [PATCH 14/40] Add documentation for subnet data source --- website/docs/d/subnet.html.markdown | 39 +++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 website/docs/d/subnet.html.markdown diff --git a/website/docs/d/subnet.html.markdown b/website/docs/d/subnet.html.markdown new file mode 100644 index 000000000..9d9454d1f --- /dev/null +++ b/website/docs/d/subnet.html.markdown @@ -0,0 +1,39 @@ +--- +layout: "azurestack" +page_title: "Azure Resource Manager: azurestack_subnet" +sidebar_current: "docs-azurestack-datasource-subnet" +description: |- + Get information about the specified Subnet located within a Virtual Network. +--- + +# Data Source: azurestack_subnet + +Use this data source to access the properties of an Azure Subnet located within a Virtual Network. + +## Example Usage + +```hcl +data "azurestack_subnet" "test" { + name = "backend" + virtual_network_name = "production" + resource_group_name = "networking" +} + +output "subnet_id" { + value = "${data.azurestack_subnet.test.id}" +} +``` + +## Argument Reference + +* `name` - (Required) Specifies the name of the Subnet. +* `virtual_network_name` - (Required) Specifies the name of the Virtual Network this Subnet is located within. +* `resource_group_name` - (Required) Specifies the name of the resource group the Virtual Network is located in. + +## Attributes Reference + +* `id` - The ID of the Subnet. +* `address_prefix` - The address prefix used for the subnet. +* `network_security_group_id` - The ID of the Network Security Group associated with the subnet. +* `route_table_id` - The ID of the Route Table associated with this subnet. +* `ip_configurations` - The collection of IP Configurations with IPs within this subnet. From 75b72930b0ce1206b3ccab4d568aa2a33cfe94b0 Mon Sep 17 00:00:00 2001 From: mbjorgan Date: Mon, 27 Aug 2018 23:07:56 +0200 Subject: [PATCH 15/40] Add public IP data source --- azurestack/data_source_public_ip.go | 84 ++++++++++++++++++++++++ azurestack/data_source_public_ip_test.go | 67 +++++++++++++++++++ azurestack/provider.go | 1 + 3 files changed, 152 insertions(+) create mode 100644 azurestack/data_source_public_ip.go create mode 100644 azurestack/data_source_public_ip_test.go diff --git a/azurestack/data_source_public_ip.go b/azurestack/data_source_public_ip.go new file mode 100644 index 000000000..7aed537fe --- /dev/null +++ b/azurestack/data_source_public_ip.go @@ -0,0 +1,84 @@ +package azurestack + +import ( + "fmt" + + "github.com/hashicorp/terraform/helper/schema" + "github.com/terraform-providers/terraform-provider-azurestack/azurestack/utils" +) + +func dataSourceArmPublicIP() *schema.Resource { + return &schema.Resource{ + Read: dataSourceArmPublicIPRead, + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + + "resource_group_name": resourceGroupNameForDataSourceSchema(), + + "domain_name_label": { + Type: schema.TypeString, + Computed: true, + }, + + "idle_timeout_in_minutes": { + Type: schema.TypeInt, + Computed: true, + }, + + "fqdn": { + Type: schema.TypeString, + Computed: true, + }, + + "ip_address": { + Type: schema.TypeString, + Computed: true, + }, + + "tags": tagsSchema(), + }, + } +} + +func dataSourceArmPublicIPRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).publicIPClient + ctx := meta.(*ArmClient).StopContext + + resGroup := d.Get("resource_group_name").(string) + name := d.Get("name").(string) + + resp, err := client.Get(ctx, resGroup, name, "") + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error: Public IP %q (Resource Group %q) was not found", name, resGroup) + } + return fmt.Errorf("Error making Read request on Azure public ip %s: %s", name, err) + } + + d.SetId(*resp.ID) + + if resp.PublicIPAddressPropertiesFormat.DNSSettings != nil { + + if resp.PublicIPAddressPropertiesFormat.DNSSettings.Fqdn != nil && *resp.PublicIPAddressPropertiesFormat.DNSSettings.Fqdn != "" { + d.Set("fqdn", resp.PublicIPAddressPropertiesFormat.DNSSettings.Fqdn) + } + + if resp.PublicIPAddressPropertiesFormat.DNSSettings.DomainNameLabel != nil && *resp.PublicIPAddressPropertiesFormat.DNSSettings.DomainNameLabel != "" { + d.Set("domain_name_label", resp.PublicIPAddressPropertiesFormat.DNSSettings.DomainNameLabel) + } + } + + if resp.PublicIPAddressPropertiesFormat.IPAddress != nil && *resp.PublicIPAddressPropertiesFormat.IPAddress != "" { + d.Set("ip_address", resp.PublicIPAddressPropertiesFormat.IPAddress) + } + + if resp.PublicIPAddressPropertiesFormat.IdleTimeoutInMinutes != nil { + d.Set("idle_timeout_in_minutes", *resp.PublicIPAddressPropertiesFormat.IdleTimeoutInMinutes) + } + + flattenAndSetTags(d, &resp.Tags) + return nil +} diff --git a/azurestack/data_source_public_ip_test.go b/azurestack/data_source_public_ip_test.go new file mode 100644 index 000000000..fef1d70ac --- /dev/null +++ b/azurestack/data_source_public_ip_test.go @@ -0,0 +1,67 @@ +package azurestack + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccDataSourceAzureRMPublicIP_basic(t *testing.T) { + dataSourceName := "data.azurestack_public_ip.test" + ri := acctest.RandInt() + + name := fmt.Sprintf("acctestpublicip-%d", ri) + resourceGroupName := fmt.Sprintf("acctestRG-%d", ri) + + config := testAccDataSourceAzureRMPublicIPBasic(name, resourceGroupName, ri, testLocation()) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureStackPublicIpDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(dataSourceName, "name", name), + resource.TestCheckResourceAttr(dataSourceName, "resource_group_name", resourceGroupName), + resource.TestCheckResourceAttr(dataSourceName, "domain_name_label", fmt.Sprintf("acctest-%d", ri)), + resource.TestCheckResourceAttr(dataSourceName, "idle_timeout_in_minutes", "30"), + resource.TestCheckResourceAttrSet(dataSourceName, "fqdn"), + resource.TestCheckResourceAttrSet(dataSourceName, "ip_address"), + resource.TestCheckResourceAttr(dataSourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(dataSourceName, "tags.environment", "test"), + ), + }, + }, + }) +} + +func testAccDataSourceAzureRMPublicIPBasic(name string, resourceGroupName string, rInt int, location string) string { + return fmt.Sprintf(` +resource "azurestack_resource_group" "test" { + name = "%s" + location = "%s" +} + +resource "azurestack_public_ip" "test" { + name = "%s" + location = "${azurestack_resource_group.test.location}" + resource_group_name = "${azurestack_resource_group.test.name}" + public_ip_address_allocation = "static" + domain_name_label = "acctest-%d" + idle_timeout_in_minutes = 30 + + tags { + environment = "test" + } +} + +data "azurestack_public_ip" "test" { + name = "${azurestack_public_ip.test.name}" + resource_group_name = "${azurestack_resource_group.test.name}" +} +`, resourceGroupName, location, name, rInt) +} diff --git a/azurestack/provider.go b/azurestack/provider.go index 6c0ff6fe1..3d537b3f4 100644 --- a/azurestack/provider.go +++ b/azurestack/provider.go @@ -73,6 +73,7 @@ func Provider() terraform.ResourceProvider { "azurestack_virtual_network": dataSourceArmVirtualNetwork(), "azurestack_route_table": dataSourceArmRouteTable(), "azurestack_subnet": dataSourceArmSubnet(), + "azurestack_public_ip": dataSourceArmPublicIP(), }, ResourcesMap: map[string]*schema.Resource{ From b3cd31da4a2688d5392d240ad746201c68b81587 Mon Sep 17 00:00:00 2001 From: mbjorgan Date: Mon, 27 Aug 2018 23:08:47 +0200 Subject: [PATCH 16/40] Adding documentation for the public ip data source --- website/docs/d/public_ip.html.markdown | 110 +++++++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 website/docs/d/public_ip.html.markdown diff --git a/website/docs/d/public_ip.html.markdown b/website/docs/d/public_ip.html.markdown new file mode 100644 index 000000000..4014b6098 --- /dev/null +++ b/website/docs/d/public_ip.html.markdown @@ -0,0 +1,110 @@ +--- +layout: "azurestack" +page_title: "Azure Resource Manager: azurestack_public_ip" +sidebar_current: "docs-azurestack-datasource-public-ip-x" +description: |- + Retrieves information about the specified public IP address. + +--- + +# Data Source: azurestack_public_ip + +Use this data source to access the properties of an existing Azure Public IP Address. + +## Example Usage (reference an existing) + +```hcl +data "azurestack_public_ip" "test" { + name = "name_of_public_ip" + resource_group_name = "name_of_resource_group" +} + +output "domain_name_label" { + value = "${data.azurestack_public_ip.test.domain_name_label}" +} + +output "public_ip_address" { + value = "${data.azurestack_public_ip.test.ip_address}" +} +``` + +## Example Usage (Retrieve the Dynamic Public IP of a new VM) + +```hcl +resource "azurestack_resource_group" "test" { + name = "test-resources" + location = "West US 2" +} + +resource "azurestack_virtual_network" "test" { + name = "test-network" + address_space = ["10.0.0.0/16"] + location = "${azurestack_resource_group.test.location}" + resource_group_name = "${azurestack_resource_group.test.name}" +} + +resource "azurestack_subnet" "test" { + name = "acctsub" + resource_group_name = "${azurestack_resource_group.test.name}" + virtual_network_name = "${azurestack_virtual_network.test.name}" + address_prefix = "10.0.2.0/24" +} + +resource "azurestack_public_ip" "test" { + name = "test-pip" + location = "${azurestack_resource_group.test.location}" + resource_group_name = "${azurestack_resource_group.test.name}" + public_ip_address_allocation = "Dynamic" + idle_timeout_in_minutes = 30 + + tags { + environment = "test" + } +} + +resource "azurestack_network_interface" "test" { + name = "test-nic" + location = "${azurestack_resource_group.test.location}" + resource_group_name = "${azurestack_resource_group.test.name}" + + ip_configuration { + name = "testconfiguration1" + subnet_id = "${azurestack_subnet.test.id}" + private_ip_address_allocation = "static" + private_ip_address = "10.0.2.5" + public_ip_address_id = "${azurestack_public_ip.test.id}" + } +} + +resource "azurestack_virtual_machine" "test" { + name = "test-vm" + location = "${azurestack_resource_group.test.location}" + resource_group_name = "${azurestack_resource_group.test.name}" + network_interface_ids = ["${azurestack_network_interface.test.id}"] + + # ... +} + +data "azurestack_public_ip" "test" { + name = "${azurestack_public_ip.test.name}" + resource_group_name = "${azurestack_virtual_machine.test.resource_group_name}" +} + +output "public_ip_address" { + value = "${data.azurestack_public_ip.test.ip_address}" +} +``` + +## Argument Reference + +* `name` - (Required) Specifies the name of the public IP address. +* `resource_group_name` - (Required) Specifies the name of the resource group. + + +## Attributes Reference + +* `domain_name_label` - The label for the Domain Name. +* `idle_timeout_in_minutes` - Specifies the timeout for the TCP idle connection. +* `fqdn` - Fully qualified domain name of the A DNS record associated with the public IP. This is the concatenation of the domainNameLabel and the regionalized DNS zone. +* `ip_address` - The IP address value that was allocated. +* `tags` - A mapping of tags to assigned to the resource. From 9b57ec880ae2e3f16af9a4455af2c8ff0852397a Mon Sep 17 00:00:00 2001 From: Marin Salinas Date: Tue, 28 Aug 2018 16:18:24 -0500 Subject: [PATCH 17/40] Add import test step on virtual network gateway resource --- azurestack/resource_arm_virtual_network_gateway_test.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/azurestack/resource_arm_virtual_network_gateway_test.go b/azurestack/resource_arm_virtual_network_gateway_test.go index 6e8e85fb2..1c083f445 100644 --- a/azurestack/resource_arm_virtual_network_gateway_test.go +++ b/azurestack/resource_arm_virtual_network_gateway_test.go @@ -27,6 +27,11 @@ func TestAccAzureStackVirtualNetworkGateway_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "sku", "Basic"), ), }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, }, }) } From 5e07e4c5e096b4caa861bfd77e1a71d298879b5a Mon Sep 17 00:00:00 2001 From: Antonio Cabrera Date: Wed, 29 Aug 2018 16:10:36 -0500 Subject: [PATCH 18/40] Normalize this to the lower-case deployments --- azurestack/resource_arm_template_deployment.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/azurestack/resource_arm_template_deployment.go b/azurestack/resource_arm_template_deployment.go index 84dc55a51..2c461232f 100644 --- a/azurestack/resource_arm_template_deployment.go +++ b/azurestack/resource_arm_template_deployment.go @@ -157,9 +157,6 @@ func resourceArmTemplateDeploymentRead(d *schema.ResourceData, meta interface{}) } resourceGroup := id.ResourceGroup name := id.Path["deployments"] - if name == "" { - name = id.Path["Deployments"] - } resp, err := deployClient.Get(ctx, resourceGroup, name) if err != nil { @@ -223,9 +220,6 @@ func resourceArmTemplateDeploymentDelete(d *schema.ResourceData, meta interface{ } resourceGroup := id.ResourceGroup name := id.Path["deployments"] - if name == "" { - name = id.Path["Deployments"] - } _, err = deployClient.Delete(ctx, resourceGroup, name) if err != nil { From 750d153ba3f4cbb89e94ec7b419e06e0a027fcb6 Mon Sep 17 00:00:00 2001 From: Antonio Cabrera Date: Wed, 29 Aug 2018 16:25:09 -0500 Subject: [PATCH 19/40] Wrap error messages --- azurestack/resource_arm_template_deployment.go | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/azurestack/resource_arm_template_deployment.go b/azurestack/resource_arm_template_deployment.go index 2c461232f..c90dace88 100644 --- a/azurestack/resource_arm_template_deployment.go +++ b/azurestack/resource_arm_template_deployment.go @@ -128,14 +128,13 @@ func resourceArmTemplateDeploymentCreate(d *schema.ResourceData, meta interface{ return fmt.Errorf("Error creating deployment: %+v", err) } - err = future.WaitForCompletionRef(ctx, deployClient.Client) - if err != nil { - return fmt.Errorf("Error creating deployment: %+v", err) + if err = future.WaitForCompletionRef(ctx, deployClient.Client); err != nil { + return fmt.Errorf("Error creating Template Deployment %q (Resource Group %q): %+v", name, resourceGroup, err) } read, err := deployClient.Get(ctx, resourceGroup, name) if err != nil { - return err + return fmt.Errorf("Error retrieving Template Deployment %q (Resource Group %q): %+v", name, resourceGroup, err) } if read.ID == nil { return fmt.Errorf("Cannot read Template Deployment %s (resource group %s) ID", name, resourceGroup) @@ -221,9 +220,8 @@ func resourceArmTemplateDeploymentDelete(d *schema.ResourceData, meta interface{ resourceGroup := id.ResourceGroup name := id.Path["deployments"] - _, err = deployClient.Delete(ctx, resourceGroup, name) - if err != nil { - return err + if _, err = deployClient.Delete(ctx, resourceGroup, name); err != nil { + return fmt.Errorf("Error deleting Template Deployment %q (Resource Group %q): %+v", name, resourceGroup, err) } return waitForTemplateDeploymentToBeDeleted(ctx, deployClient, resourceGroup, name) From 57bcc9db84e9caa43476637f9270434ce70e5137 Mon Sep 17 00:00:00 2001 From: Antonio Cabrera Date: Wed, 29 Aug 2018 16:26:56 -0500 Subject: [PATCH 20/40] Remove comment on test for template deployment --- azurestack/resource_arm_template_deployment_test.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/azurestack/resource_arm_template_deployment_test.go b/azurestack/resource_arm_template_deployment_test.go index 92d23f026..3d15a7411 100644 --- a/azurestack/resource_arm_template_deployment_test.go +++ b/azurestack/resource_arm_template_deployment_test.go @@ -114,11 +114,6 @@ func TestAccAzureStackTemplateDeployment_withOutputs(t *testing.T) { testCheckAzureStackTemplateDeploymentExists("azurestack_template_deployment.test"), resource.TestCheckOutput("tfIntOutput", "-123"), resource.TestCheckOutput("tfStringOutput", "Standard_GRS"), - - // these values *should* be 'true' and 'false' but, - // due to a bug in the way terraform represents bools at various times these are for now 0 and 1 - // see https://github.com/hashicorp/terraform/issues/13512#issuecomment-295389523 - // at a later date these may return the expected 'true' / 'false' and should be changed back resource.TestCheckOutput("tfFalseOutput", "0"), resource.TestCheckOutput("tfTrueOutput", "1"), resource.TestCheckResourceAttr("azurestack_template_deployment.test", "outputs.stringOutput", "Standard_GRS"), From bb80ddc48fb1ab2184e5c39d6443e2559a568e18 Mon Sep 17 00:00:00 2001 From: Antonio Cabrera Date: Wed, 29 Aug 2018 16:29:41 -0500 Subject: [PATCH 21/40] AzureStack Template -> ARM Template --- website/docs/r/template_deployment.html.markdown | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/website/docs/r/template_deployment.html.markdown b/website/docs/r/template_deployment.html.markdown index a17bef0d9..39545ec64 100644 --- a/website/docs/r/template_deployment.html.markdown +++ b/website/docs/r/template_deployment.html.markdown @@ -10,13 +10,13 @@ description: |- Create a template deployment of resources -~> **Note on AzureStack Template Deployments:** Due to the way the underlying Azure API is designed, Terraform can only manage the deployment of the AzureStack Template - and not any resources which are created by it. -This means that when deleting the `azurestack_template_deployment` resource, Terraform will only remove the reference to the deployment, whilst leaving any resources created by that AzureStack Template Deployment. -One workaround for this is to use a unique Resource Group for each AzureStack Template Deployment, which means deleting the Resource Group would contain any resources created within it - however this isn't ideal. [More information](https://docs.microsoft.com/en-us/rest/api/resources/deployments#Deployments_Delete). +~> **Note on ARM Template Deployments:** Due to the way the underlying Azure API is designed, Terraform can only manage the deployment of the ARM Template - and not any resources which are created by it. +This means that when deleting the `azurestack_template_deployment` resource, Terraform will only remove the reference to the deployment, whilst leaving any resources created by that ARM Template Deployment. +One workaround for this is to use a unique Resource Group for each ARM Template Deployment, which means deleting the Resource Group would contain any resources created within it - however this isn't ideal. [More information](https://docs.microsoft.com/en-us/rest/api/resources/deployments#Deployments_Delete). ## Example Usage -~> **Note:** This example uses [Storage Accounts](storage_account.html) and [Public IP's](public_ip.html) which are natively supported by Terraform - we'd highly recommend using the Native Resources where possible instead rather than an AzureStack Template, for the reasons outlined above. +~> **Note:** This example uses [Storage Accounts](storage_account.html) and [Public IP's](public_ip.html) which are natively supported by Terraform - we'd highly recommend using the Native Resources where possible instead rather than an ARM Template, for the reasons outlined above. ```hcl resource "azurestack_resource_group" "test" { From 8c0bde6ac9f7c2130eaac0e60ec3df9483417de2 Mon Sep 17 00:00:00 2001 From: Antonio Cabrera Date: Wed, 29 Aug 2018 16:31:20 -0500 Subject: [PATCH 22/40] Wrap one last error --- azurestack/resource_arm_template_deployment.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azurestack/resource_arm_template_deployment.go b/azurestack/resource_arm_template_deployment.go index c90dace88..7d6d73132 100644 --- a/azurestack/resource_arm_template_deployment.go +++ b/azurestack/resource_arm_template_deployment.go @@ -125,7 +125,7 @@ func resourceArmTemplateDeploymentCreate(d *schema.ResourceData, meta interface{ future, err := deployClient.CreateOrUpdate(ctx, resourceGroup, name, deployment) if err != nil { - return fmt.Errorf("Error creating deployment: %+v", err) + return fmt.Errorf("Error creating Template Deployment %q (Resource Group %q): %+v", name, resourceGroup, err) } if err = future.WaitForCompletionRef(ctx, deployClient.Client); err != nil { From 1b803789fbe57f380fd97b10fae9abeb112b8181 Mon Sep 17 00:00:00 2001 From: Antonio Cabrera Date: Fri, 31 Aug 2018 13:58:56 -0500 Subject: [PATCH 23/40] Enable skipped tests - Change storage account types to supported types - Change the expected values on tests to be consisting with the new storage account types - Remove for now the unsupported resources and adapt the template to use supported resources --- .../resource_arm_template_deployment_test.go | 51 +++---------------- 1 file changed, 7 insertions(+), 44 deletions(-) diff --git a/azurestack/resource_arm_template_deployment_test.go b/azurestack/resource_arm_template_deployment_test.go index 3d15a7411..58c2162e5 100644 --- a/azurestack/resource_arm_template_deployment_test.go +++ b/azurestack/resource_arm_template_deployment_test.go @@ -52,8 +52,6 @@ func TestAccAzureStackTemplateDeployment_disappears(t *testing.T) { // Storage Account type is not supported func TestAccAzureStackTemplateDeployment_withParams(t *testing.T) { - t.Skip() - ri := acctest.RandInt() config := testAccAzureStackTemplateDeployment_withParams(ri, testLocation()) resource.Test(t, resource.TestCase{ @@ -75,8 +73,6 @@ func TestAccAzureStackTemplateDeployment_withParams(t *testing.T) { // Provider doesn't support resource: azurestack_key_vault_secret func TestAccAzureStackTemplateDeployment_withParamsBody(t *testing.T) { - t.Skip() - ri := acctest.RandInt() config := testaccAzureStackTemplateDeployment_withParamsBody(ri, testLocation()) resource.Test(t, resource.TestCase{ @@ -99,8 +95,6 @@ func TestAccAzureStackTemplateDeployment_withParamsBody(t *testing.T) { // Storage account type is not supported func TestAccAzureStackTemplateDeployment_withOutputs(t *testing.T) { - t.Skip() - ri := acctest.RandInt() config := testAccAzureStackTemplateDeployment_withOutputs(ri, testLocation()) resource.Test(t, resource.TestCase{ @@ -113,10 +107,10 @@ func TestAccAzureStackTemplateDeployment_withOutputs(t *testing.T) { Check: resource.ComposeTestCheckFunc( testCheckAzureStackTemplateDeploymentExists("azurestack_template_deployment.test"), resource.TestCheckOutput("tfIntOutput", "-123"), - resource.TestCheckOutput("tfStringOutput", "Standard_GRS"), + resource.TestCheckOutput("tfStringOutput", "Standard_LRS"), resource.TestCheckOutput("tfFalseOutput", "0"), resource.TestCheckOutput("tfTrueOutput", "1"), - resource.TestCheckResourceAttr("azurestack_template_deployment.test", "outputs.stringOutput", "Standard_GRS"), + resource.TestCheckResourceAttr("azurestack_template_deployment.test", "outputs.stringOutput", "Standard_LRS"), ), }, }, @@ -348,44 +342,14 @@ resource "azurestack_storage_container" "using-outputs" { data "azurestack_client_config" "current" {} -resource "azurestack_key_vault" "test-kv" { - location = "%s" - name = "vault%d" - resource_group_name = "${azurestack_resource_group.test.name}" - "sku" { - name = "standard" - } - tenant_id = "${data.azurestack_client_config.current.tenant_id}" - enabled_for_template_deployment = true - - access_policy { - key_permissions = [] - object_id = "${data.azurestack_client_config.current.service_principal_object_id}" - secret_permissions = [ - "get","list","set","purge"] - tenant_id = "${data.azurestack_client_config.current.tenant_id}" - } -} - -resource "azurestack_key_vault_secret" "test-secret" { - name = "acctestsecret-%d" - value = "terraform-test-%d" - vault_uri = "${azurestack_key_vault.test-kv.vault_uri}" -} - locals { "templated-file" = < Date: Thu, 6 Sep 2018 20:05:18 -0700 Subject: [PATCH 24/40] Update template_deployment.html.markdown --- website/docs/r/template_deployment.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/r/template_deployment.html.markdown b/website/docs/r/template_deployment.html.markdown index 39545ec64..09974fff0 100644 --- a/website/docs/r/template_deployment.html.markdown +++ b/website/docs/r/template_deployment.html.markdown @@ -8,7 +8,7 @@ description: |- # azurestack_template_deployment -Create a template deployment of resources +Manages a template deployment of resources ~> **Note on ARM Template Deployments:** Due to the way the underlying Azure API is designed, Terraform can only manage the deployment of the ARM Template - and not any resources which are created by it. This means that when deleting the `azurestack_template_deployment` resource, Terraform will only remove the reference to the deployment, whilst leaving any resources created by that ARM Template Deployment. From 6248f15859218b2e3e8523e0704454370f5ec807 Mon Sep 17 00:00:00 2001 From: kt Date: Thu, 6 Sep 2018 20:05:48 -0700 Subject: [PATCH 25/40] Update resource_arm_template_deployment_test.go --- azurestack/resource_arm_template_deployment_test.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/azurestack/resource_arm_template_deployment_test.go b/azurestack/resource_arm_template_deployment_test.go index 58c2162e5..22962fecc 100644 --- a/azurestack/resource_arm_template_deployment_test.go +++ b/azurestack/resource_arm_template_deployment_test.go @@ -49,7 +49,6 @@ func TestAccAzureStackTemplateDeployment_disappears(t *testing.T) { }) } -// Storage Account type is not supported func TestAccAzureStackTemplateDeployment_withParams(t *testing.T) { ri := acctest.RandInt() @@ -70,7 +69,6 @@ func TestAccAzureStackTemplateDeployment_withParams(t *testing.T) { }) } -// Provider doesn't support resource: azurestack_key_vault_secret func TestAccAzureStackTemplateDeployment_withParamsBody(t *testing.T) { ri := acctest.RandInt() @@ -92,7 +90,6 @@ func TestAccAzureStackTemplateDeployment_withParamsBody(t *testing.T) { } -// Storage account type is not supported func TestAccAzureStackTemplateDeployment_withOutputs(t *testing.T) { ri := acctest.RandInt() From 0bf55b2f8ff11a068ea65f812b40819005337d44 Mon Sep 17 00:00:00 2001 From: kt Date: Thu, 6 Sep 2018 20:12:15 -0700 Subject: [PATCH 26/40] Update CHANGELOG.md to include #33 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 32ceb7cd8..bdf91df17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ * **New Resource:** `azurestack_route_table` [GH-26] * **New Resource:** `azurestack_route` [GH-27] +* **New Resource:** `azurestack_template_deployment` [GH-33] * **New Data Source:** `azurestack_route_table` [GH-26] ## 0.3.0 (August 13, 2018) From 5f4608322f95607f62db40bdf3e9cc2f15c971a3 Mon Sep 17 00:00:00 2001 From: mbjorgan Date: Fri, 7 Sep 2018 13:36:45 +0200 Subject: [PATCH 27/40] Cleanup after review --- azurestack/data_source_public_ip.go | 31 ++++++++++++++++------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/azurestack/data_source_public_ip.go b/azurestack/data_source_public_ip.go index 7aed537fe..eb0da1866 100644 --- a/azurestack/data_source_public_ip.go +++ b/azurestack/data_source_public_ip.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" "github.com/terraform-providers/terraform-provider-azurestack/azurestack/utils" ) @@ -12,8 +13,9 @@ func dataSourceArmPublicIP() *schema.Resource { Read: dataSourceArmPublicIPRead, Schema: map[string]*schema.Schema{ "name": { - Type: schema.TypeString, - Required: true, + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.NoZeroValues, }, "resource_group_name": resourceGroupNameForDataSourceSchema(), @@ -60,23 +62,24 @@ func dataSourceArmPublicIPRead(d *schema.ResourceData, meta interface{}) error { d.SetId(*resp.ID) - if resp.PublicIPAddressPropertiesFormat.DNSSettings != nil { + if props := resp.PublicIPAddressPropertiesFormat; props != nil { + if dnsSettings := props.DNSSettings; dnsSettings != nil { + if v := dnsSettings.Fqdn; v != nil && *v != "" { + d.Set("fqdn", v) + } - if resp.PublicIPAddressPropertiesFormat.DNSSettings.Fqdn != nil && *resp.PublicIPAddressPropertiesFormat.DNSSettings.Fqdn != "" { - d.Set("fqdn", resp.PublicIPAddressPropertiesFormat.DNSSettings.Fqdn) + if v := dnsSettings.DomainNameLabel; v != nil && *v != "" { + d.Set("domain_name_label", v) + } } - if resp.PublicIPAddressPropertiesFormat.DNSSettings.DomainNameLabel != nil && *resp.PublicIPAddressPropertiesFormat.DNSSettings.DomainNameLabel != "" { - d.Set("domain_name_label", resp.PublicIPAddressPropertiesFormat.DNSSettings.DomainNameLabel) + if v := props.IPAddress; v != nil && *v != "" { + d.Set("ip_address", v) } - } - if resp.PublicIPAddressPropertiesFormat.IPAddress != nil && *resp.PublicIPAddressPropertiesFormat.IPAddress != "" { - d.Set("ip_address", resp.PublicIPAddressPropertiesFormat.IPAddress) - } - - if resp.PublicIPAddressPropertiesFormat.IdleTimeoutInMinutes != nil { - d.Set("idle_timeout_in_minutes", *resp.PublicIPAddressPropertiesFormat.IdleTimeoutInMinutes) + if v := props.IdleTimeoutInMinutes; v != nil { + d.Set("idle_timeout_in_minutes", *resp.PublicIPAddressPropertiesFormat.IdleTimeoutInMinutes) + } } flattenAndSetTags(d, &resp.Tags) From fab16e0946afb1310b38299845c113b073a077a5 Mon Sep 17 00:00:00 2001 From: mbjorgan Date: Fri, 7 Sep 2018 13:37:06 +0200 Subject: [PATCH 28/40] Cleanup after review --- azurestack/data_source_subnet.go | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/azurestack/data_source_subnet.go b/azurestack/data_source_subnet.go index c064c134a..86d27ef02 100644 --- a/azurestack/data_source_subnet.go +++ b/azurestack/data_source_subnet.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" "github.com/terraform-providers/terraform-provider-azurestack/azurestack/utils" ) @@ -12,19 +13,18 @@ func dataSourceArmSubnet() *schema.Resource { Read: dataSourceArmSubnetRead, Schema: map[string]*schema.Schema{ "name": { - Type: schema.TypeString, - Required: true, + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.NoZeroValues, }, "virtual_network_name": { - Type: schema.TypeString, - Required: true, + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.NoZeroValues, }, - "resource_group_name": { - Type: schema.TypeString, - Required: true, - }, + "resource_group_name": resourceGroupNameForDataSourceSchema(), "address_prefix": { Type: schema.TypeString, @@ -92,8 +92,7 @@ func dataSourceArmSubnetRead(d *schema.ResourceData, meta interface{}) error { d.Set("route_table_id", "") } - ips := flattenSubnetIPConfigurations(props.IPConfigurations) - if err := d.Set("ip_configurations", ips); err != nil { + if err := d.Set("ip_configurations", flattenSubnetIPConfigurations(props.IPConfigurations)); err != nil { return err } } From 34a03ab1c1ad43f3b8d52db9203ed4ded4047028 Mon Sep 17 00:00:00 2001 From: mbjorgan Date: Fri, 7 Sep 2018 13:37:34 +0200 Subject: [PATCH 29/40] Format example code --- website/docs/d/public_ip.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/d/public_ip.html.markdown b/website/docs/d/public_ip.html.markdown index 4014b6098..2bda45ced 100644 --- a/website/docs/d/public_ip.html.markdown +++ b/website/docs/d/public_ip.html.markdown @@ -15,7 +15,7 @@ Use this data source to access the properties of an existing Azure Public IP Add ```hcl data "azurestack_public_ip" "test" { - name = "name_of_public_ip" + name = "name_of_public_ip" resource_group_name = "name_of_resource_group" } From 989d45bf988214038c2fd43181b77a33f89e0e34 Mon Sep 17 00:00:00 2001 From: Antonio Cabrera Date: Fri, 7 Sep 2018 12:02:46 -0500 Subject: [PATCH 30/40] Delete unused funcions --- azurestack/validators.go | 248 ------------------------------ azurestack/validators_test.go | 277 ---------------------------------- 2 files changed, 525 deletions(-) delete mode 100644 azurestack/validators.go delete mode 100644 azurestack/validators_test.go diff --git a/azurestack/validators.go b/azurestack/validators.go deleted file mode 100644 index bff6a5e8a..000000000 --- a/azurestack/validators.go +++ /dev/null @@ -1,248 +0,0 @@ -package azurestack - -import ( - "fmt" - "math" - "regexp" - "strings" - "time" - - "github.com/Azure/go-autorest/autorest/date" - "github.com/hashicorp/terraform/helper/schema" - "github.com/hashicorp/terraform/helper/validation" - "github.com/satori/uuid" -) - -func validateRFC3339Date(v interface{}, k string) (ws []string, errors []error) { - dateString := v.(string) - - if _, err := date.ParseTime(time.RFC3339, dateString); err != nil { - errors = append(errors, fmt.Errorf("%q is an invalid RFC3339 date: %+v", k, err)) - } - - return -} - -// validateIntInSlice returns a SchemaValidateFunc which tests if the provided value -// is of type int and matches the value of an element in the valid slice -func validateIntInSlice(valid []int) schema.SchemaValidateFunc { - return func(i interface{}, k string) (s []string, es []error) { - v, ok := i.(int) - if !ok { - es = append(es, fmt.Errorf("expected type of %s to be int", k)) - return - } - - for _, str := range valid { - if v == str { - return - } - } - - es = append(es, fmt.Errorf("expected %q to be one of %v, got %v", k, valid, v)) - return - } -} - -func validateUUID(v interface{}, k string) (ws []string, errors []error) { - if _, err := uuid.FromString(v.(string)); err != nil { - errors = append(errors, fmt.Errorf("%q is an invalid UUUID: %s", k, err)) - } - return -} - -func evaluateSchemaValidateFunc(i interface{}, k string, validateFunc schema.SchemaValidateFunc) (bool, error) { - _, es := validateFunc(i, k) - - if len(es) > 0 { - return false, es[0] - } - - return true, nil -} - -func validateIso8601Duration() schema.SchemaValidateFunc { - return func(i interface{}, k string) (s []string, es []error) { - v, ok := i.(string) - if !ok { - es = append(es, fmt.Errorf("expected type of %s to be string", k)) - return - } - - matched, _ := regexp.MatchString(`^P([0-9]+Y)?([0-9]+M)?([0-9]+W)?([0-9]+D)?(T([0-9]+H)?([0-9]+M)?([0-9]+(\.?[0-9]+)?S)?)?$`, v) - - if !matched { - es = append(es, fmt.Errorf("expected %s to be in ISO 8601 duration format, got %s", k, v)) - } - return - } -} - -func validateAzureVirtualMachineTimeZone() schema.SchemaValidateFunc { - // Candidates are listed here: http://jackstromberg.com/2017/01/list-of-time-zones-consumed-by-azure/ - candidates := []string{ - "", - "Afghanistan Standard Time", - "Alaskan Standard Time", - "Arab Standard Time", - "Arabian Standard Time", - "Arabic Standard Time", - "Argentina Standard Time", - "Atlantic Standard Time", - "AUS Central Standard Time", - "AUS Eastern Standard Time", - "Azerbaijan Standard Time", - "Azores Standard Time", - "Bahia Standard Time", - "Bangladesh Standard Time", - "Belarus Standard Time", - "Canada Central Standard Time", - "Cape Verde Standard Time", - "Caucasus Standard Time", - "Cen. Australia Standard Time", - "Central America Standard Time", - "Central Asia Standard Time", - "Central Brazilian Standard Time", - "Central Europe Standard Time", - "Central European Standard Time", - "Central Pacific Standard Time", - "Central Standard Time (Mexico)", - "Central Standard Time", - "China Standard Time", - "Dateline Standard Time", - "E. Africa Standard Time", - "E. Australia Standard Time", - "E. Europe Standard Time", - "E. South America Standard Time", - "Eastern Standard Time (Mexico)", - "Eastern Standard Time", - "Egypt Standard Time", - "Ekaterinburg Standard Time", - "Fiji Standard Time", - "FLE Standard Time", - "Georgian Standard Time", - "GMT Standard Time", - "Greenland Standard Time", - "Greenwich Standard Time", - "GTB Standard Time", - "Hawaiian Standard Time", - "India Standard Time", - "Iran Standard Time", - "Israel Standard Time", - "Jordan Standard Time", - "Kaliningrad Standard Time", - "Korea Standard Time", - "Libya Standard Time", - "Line Islands Standard Time", - "Magadan Standard Time", - "Mauritius Standard Time", - "Middle East Standard Time", - "Montevideo Standard Time", - "Morocco Standard Time", - "Mountain Standard Time (Mexico)", - "Mountain Standard Time", - "Myanmar Standard Time", - "N. Central Asia Standard Time", - "Namibia Standard Time", - "Nepal Standard Time", - "New Zealand Standard Time", - "Newfoundland Standard Time", - "North Asia East Standard Time", - "North Asia Standard Time", - "Pacific SA Standard Time", - "Pacific Standard Time (Mexico)", - "Pacific Standard Time", - "Pakistan Standard Time", - "Paraguay Standard Time", - "Romance Standard Time", - "Russia Time Zone 10", - "Russia Time Zone 11", - "Russia Time Zone 3", - "Russian Standard Time", - "SA Eastern Standard Time", - "SA Pacific Standard Time", - "SA Western Standard Time", - "Samoa Standard Time", - "SE Asia Standard Time", - "Singapore Standard Time", - "South Africa Standard Time", - "Sri Lanka Standard Time", - "Syria Standard Time", - "Taipei Standard Time", - "Tasmania Standard Time", - "Tokyo Standard Time", - "Tonga Standard Time", - "Turkey Standard Time", - "Ulaanbaatar Standard Time", - "US Eastern Standard Time", - "US Mountain Standard Time", - "UTC", - "UTC+12", - "UTC-02", - "UTC-11", - "Venezuela Standard Time", - "Vladivostok Standard Time", - "W. Australia Standard Time", - "W. Central Africa Standard Time", - "W. Europe Standard Time", - "West Asia Standard Time", - "West Pacific Standard Time", - "Yakutsk Standard Time", - } - return validation.StringInSlice(candidates, true) -} - -// intBetweenDivisibleBy returns a SchemaValidateFunc which tests if the provided value -// is of type int and is between min and max (inclusive) and is divisible by a given number -func validateIntBetweenDivisibleBy(min, max, divisor int) schema.SchemaValidateFunc { - return func(i interface{}, k string) (s []string, es []error) { - v, ok := i.(int) - if !ok { - es = append(es, fmt.Errorf("expected type of %s to be int", k)) - return - } - - if v < min || v > max { - es = append(es, fmt.Errorf("expected %s to be in the range (%d - %d), got %d", k, min, max, v)) - return - } - - if math.Mod(float64(v), float64(divisor)) != 0 { - es = append(es, fmt.Errorf("expected %s to be divisible by %d", k, divisor)) - return - } - - return - } -} - -func validateCollation() schema.SchemaValidateFunc { - return func(i interface{}, k string) (s []string, es []error) { - v, ok := i.(string) - if !ok { - es = append(es, fmt.Errorf("expected type of %s to be string", k)) - return - } - - matched, _ := regexp.MatchString(`^[A-Za-z0-9_. ]+$`, v) - - if !matched { - es = append(es, fmt.Errorf("%s contains invalid characters, only underscores are supported, got %s", k, v)) - return - } - - return - } -} - -func validateFilePath() schema.SchemaValidateFunc { - return func(v interface{}, k string) (ws []string, es []error) { - val := v.(string) - - if !strings.HasPrefix(val, "/") { - es = append(es, fmt.Errorf("%q must start with `/`", k)) - } - - return - } -} diff --git a/azurestack/validators_test.go b/azurestack/validators_test.go deleted file mode 100644 index 2a09fc6a5..000000000 --- a/azurestack/validators_test.go +++ /dev/null @@ -1,277 +0,0 @@ -package azurestack - -import ( - "strconv" - "testing" -) - -func TestValidateRFC3339Date(t *testing.T) { - cases := []struct { - Value string - ErrCount int - }{ - { - Value: "", - ErrCount: 1, - }, - { - Value: "Random", - ErrCount: 1, - }, - { - Value: "2017-01-01", - ErrCount: 1, - }, - { - Value: "2017-01-01T01:23:45", - ErrCount: 1, - }, - { - Value: "2017-01-01T01:23:45+00:00", - ErrCount: 0, - }, - { - Value: "2017-01-01T01:23:45Z", - ErrCount: 0, - }, - } - - for _, tc := range cases { - _, errors := validateRFC3339Date(tc.Value, "example") - - if len(errors) != tc.ErrCount { - t.Fatalf("Expected validateRFC3339Date to trigger '%d' errors for '%s' - got '%d'", tc.ErrCount, tc.Value, len(errors)) - } - } -} - -func TestValidateIntInSlice(t *testing.T) { - - cases := []struct { - Input []int - Value int - Errors int - }{ - { - Input: []int{}, - Value: 0, - Errors: 1, - }, - { - Input: []int{1}, - Value: 1, - Errors: 0, - }, - { - Input: []int{1, 2, 3, 4, 5}, - Value: 3, - Errors: 0, - }, - { - Input: []int{1, 3, 5}, - Value: 3, - Errors: 0, - }, - { - Input: []int{1, 3, 5}, - Value: 4, - Errors: 1, - }, - } - - for _, tc := range cases { - _, errors := validateIntInSlice(tc.Input)(tc.Value, "azurerm_postgresql_database") - - if len(errors) != tc.Errors { - t.Fatalf("Expected the validateIntInSlice trigger a validation error for input: %+v looking for %+v", tc.Input, tc.Value) - } - } - -} - -func TestValidateIso8601Duration(t *testing.T) { - cases := []struct { - Value string - Errors int - }{ - { - // Date components only - Value: "P1Y2M3D", - Errors: 0, - }, - { - // Time components only - Value: "PT7H42M3S", - Errors: 0, - }, - { - // Date and time components - Value: "P1Y2M3DT7H42M3S", - Errors: 0, - }, - { - // Invalid prefix - Value: "1Y2M3DT7H42M3S", - Errors: 1, - }, - { - // Wrong order of components, i.e. invalid format - Value: "PT7H42M3S1Y2M3D", - Errors: 1, - }, - } - - for _, tc := range cases { - _, errors := validateIso8601Duration()(tc.Value, "example") - - if len(errors) != tc.Errors { - t.Fatalf("Expected validateIso8601Duration to trigger '%d' errors for '%s' - got '%d'", tc.Errors, tc.Value, len(errors)) - } - } -} - -func TestValidateIntBetweenDivisibleBy(t *testing.T) { - cases := []struct { - Min int - Max int - Div int - Value interface{} - Errors int - }{ - { - Min: 1025, - Max: 2048, - Div: 1024, - Value: 1024, - Errors: 1, - }, - { - Min: 1025, - Max: 2048, - Div: 3, - Value: 1024, - Errors: 1, - }, - { - Min: 1024, - Max: 2048, - Div: 1024, - Value: 3072, - Errors: 1, - }, - { - Min: 1024, - Max: 2048, - Div: 1024, - Value: 2049, - Errors: 1, - }, - { - Min: 1024, - Max: 2048, - Div: 1024, - Value: 1024, - Errors: 0, - }, - } - - for _, tc := range cases { - _, errors := validateIntBetweenDivisibleBy(tc.Min, tc.Max, tc.Div)(tc.Value, strconv.Itoa(tc.Value.(int))) - if len(errors) != tc.Errors { - t.Fatalf("Expected intBetweenDivisibleBy to trigger '%d' errors for '%s' - got '%d'", tc.Errors, tc.Value, len(errors)) - } - } -} - -func TestValidateCollation(t *testing.T) { - cases := []struct { - Value string - Errors int - }{ - { - Value: "en-US", - Errors: 1, - }, - { - Value: "en_US", - Errors: 0, - }, - { - Value: "en US", - Errors: 0, - }, - { - Value: "English_United States.1252", - Errors: 0, - }, - } - - for _, tc := range cases { - _, errors := validateCollation()(tc.Value, "collation") - if len(errors) != tc.Errors { - t.Fatalf("Expected validateCollation to trigger '%d' errors for '%s' - got '%d'", tc.Errors, tc.Value, len(errors)) - } - } -} - -func TestValidateAzureVirtualMachineTimeZone(t *testing.T) { - cases := []struct { - Value string - Errors int - }{ - { - Value: "", - Errors: 0, - }, - { - Value: "UTC", - Errors: 0, - }, - { - Value: "China Standard Time", - Errors: 0, - }, - { - // Valid UTC time zone - Value: "utc-11", - Errors: 0, - }, - { - // Invalid UTC time zone - Value: "UTC-30", - Errors: 1, - }, - } - - for _, tc := range cases { - _, errors := validateAzureVirtualMachineTimeZone()(tc.Value, "unittest") - - if len(errors) != tc.Errors { - t.Fatalf("Expected validateAzureVMTimeZone to trigger '%d' errors for '%s' - got '%d'", tc.Errors, tc.Value, len(errors)) - } - } -} - -func TestValidateAzureDataLakeStoreRemoteFilePath(t *testing.T) { - cases := []struct { - Value string - Errors int - }{ - { - Value: "bad", - Errors: 1, - }, - { - Value: "/good/file/path", - Errors: 0, - }, - } - - for _, tc := range cases { - _, errors := validateFilePath()(tc.Value, "unittest") - - if len(errors) != tc.Errors { - t.Fatalf("Expected validateFilePath to trigger '%d' errors for '%s' - got '%d'", tc.Errors, tc.Value, len(errors)) - } - } -} From 7b6bcd66917cfa57ef90612d38090240de4fa8f0 Mon Sep 17 00:00:00 2001 From: Antonio Cabrera Date: Fri, 7 Sep 2018 12:04:12 -0500 Subject: [PATCH 31/40] Validate resource name and return a general error message --- azurestack/test_utils.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/azurestack/test_utils.go b/azurestack/test_utils.go index ed75426d0..011126952 100644 --- a/azurestack/test_utils.go +++ b/azurestack/test_utils.go @@ -12,10 +12,14 @@ func getArmResourceNameAndGroup(s *terraform.State, name string) (string, string return "", "", fmt.Errorf("Not found: %s", name) } - armName := rs.Primary.Attributes["name"] + armName, hasName := rs.Primary.Attributes["name"] armResourceGroup, hasResourceGroup := rs.Primary.Attributes["resource_group_name"] if !hasResourceGroup { - return "", "", fmt.Errorf("Bad: no resource group found in state for virtual network gateway: %s", name) + return "", "", fmt.Errorf("Error: no resource group found in state for resource: %s", name) + } + + if !hasName { + return "", "", fmt.Errorf("Error: no name found in state for resource: %s", name) } return armName, armResourceGroup, nil From 31cb153ff799a3cce60726eb71273cf8afe0b3e6 Mon Sep 17 00:00:00 2001 From: Antonio Cabrera Date: Fri, 7 Sep 2018 12:06:16 -0500 Subject: [PATCH 32/40] Style changes and add comments on unused code --- .../data_source_virtual_network_gateway.go | 34 +++++++++++++++---- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/azurestack/data_source_virtual_network_gateway.go b/azurestack/data_source_virtual_network_gateway.go index ac816ccf0..442c1f438 100644 --- a/azurestack/data_source_virtual_network_gateway.go +++ b/azurestack/data_source_virtual_network_gateway.go @@ -8,6 +8,7 @@ import ( "github.com/Azure/azure-sdk-for-go/profiles/2017-03-09/network/mgmt/network" "github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" "github.com/terraform-providers/terraform-provider-azurestack/azurestack/utils" ) @@ -17,8 +18,9 @@ func dataSourceArmVirtualNetworkGateway() *schema.Resource { Schema: map[string]*schema.Schema{ "name": { - Type: schema.TypeString, - Required: true, + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.NoZeroValues, }, "resource_group_name": resourceGroupNameForDataSourceSchema(), @@ -40,10 +42,11 @@ func dataSourceArmVirtualNetworkGateway() *schema.Resource { Computed: true, }, - "active_active": { - Type: schema.TypeBool, - Computed: true, - }, + // Not yet supported on service 2017-03-09 + // "active_active": { + // Type: schema.TypeBool, + // Computed: true, + // }, "sku": { Type: schema.TypeString, @@ -59,14 +62,17 @@ func dataSourceArmVirtualNetworkGateway() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "private_ip_address_allocation": { Type: schema.TypeString, Computed: true, }, + "subnet_id": { Type: schema.TypeString, Computed: true, }, + "public_ip_address_id": { Type: schema.TypeString, Computed: true, @@ -87,6 +93,7 @@ func dataSourceArmVirtualNetworkGateway() *schema.Resource { Type: schema.TypeString, }, }, + "root_certificate": { Type: schema.TypeSet, Computed: true, @@ -96,6 +103,7 @@ func dataSourceArmVirtualNetworkGateway() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "public_cert_data": { Type: schema.TypeString, Computed: true, @@ -104,6 +112,7 @@ func dataSourceArmVirtualNetworkGateway() *schema.Resource { }, Set: hashVirtualNetworkGatewayDataSourceRootCert, }, + "revoked_certificate": { Type: schema.TypeSet, Computed: true, @@ -113,6 +122,7 @@ func dataSourceArmVirtualNetworkGateway() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "thumbprint": { Type: schema.TypeString, Computed: true, @@ -121,14 +131,17 @@ func dataSourceArmVirtualNetworkGateway() *schema.Resource { }, Set: hashVirtualNetworkGatewayDataSourceRevokedCert, }, + "radius_server_address": { Type: schema.TypeString, Computed: true, }, + "radius_server_secret": { Type: schema.TypeString, Computed: true, }, + "vpn_client_protocols": { Type: schema.TypeSet, Computed: true, @@ -149,10 +162,12 @@ func dataSourceArmVirtualNetworkGateway() *schema.Resource { Type: schema.TypeInt, Computed: true, }, + "peering_address": { Type: schema.TypeString, Computed: true, }, + "peer_weight": { Type: schema.TypeInt, Computed: true, @@ -200,6 +215,8 @@ func dataSourceArmVirtualNetworkGatewayRead(d *schema.ResourceData, meta interfa d.Set("type", string(gw.GatewayType)) d.Set("enable_bgp", gw.EnableBgp) + + // ActiveActive not yet supported on 2017-03-09 service //d.Set("active_active", gw.ActiveActive) if string(gw.VpnType) != "" { @@ -307,6 +324,7 @@ func flattenArmVirtualNetworkGatewayDataSourceVpnClientConfig(cfg *network.VpnCl } flat["revoked_certificate"] = schema.NewSet(hashVirtualNetworkGatewayDataSourceRevokedCert, revokedCerts) + // VpnClientProtocols not yet supported on 2017-03-09 service // vpnClientProtocols := &schema.Set{F: schema.HashString} // if vpnProtocols := cfg.VpnClientProtocols; vpnProtocols != nil { // for _, protocol := range *vpnProtocols { @@ -315,10 +333,14 @@ func flattenArmVirtualNetworkGatewayDataSourceVpnClientConfig(cfg *network.VpnCl // } // flat["vpn_client_protocols"] = vpnClientProtocols + // RadiusServerAddress not yet supported on 2017-03-09 service + // VpnClientProtocols not yet supported on 2017-03-09 service // if v := cfg.RadiusServerAddress; v != nil { // flat["radius_server_address"] = *v // } + // RadiusServerSecret not yet supported on 2017-03-09 service + // VpnClientProtocols not yet supported on 2017-03-09 service // if v := cfg.RadiusServerSecret; v != nil { // flat["radius_server_secret"] = *v // } From ac87f7e44dcf6604efc20051d7d237866d8eb9ae Mon Sep 17 00:00:00 2001 From: Antonio Cabrera Date: Fri, 7 Sep 2018 12:27:04 -0500 Subject: [PATCH 33/40] Add funciton only used on this file from the validators --- azurestack/resource_arm_virtual_network_gateway.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/azurestack/resource_arm_virtual_network_gateway.go b/azurestack/resource_arm_virtual_network_gateway.go index 7df8e233f..c0ecf84ac 100644 --- a/azurestack/resource_arm_virtual_network_gateway.go +++ b/azurestack/resource_arm_virtual_network_gateway.go @@ -774,3 +774,13 @@ func resourceArmVirtualNetworkGatewayCustomizeDiff(diff *schema.ResourceDiff, v } return nil } + +func evaluateSchemaValidateFunc(i interface{}, k string, validateFunc schema.SchemaValidateFunc) (bool, error) { + _, es := validateFunc(i, k) + + if len(es) > 0 { + return false, es[0] + } + + return true, nil +} From 482afa8cc1c452d750707d214cb8e183260ae7c0 Mon Sep 17 00:00:00 2001 From: Antonio Cabrera Date: Fri, 7 Sep 2018 12:27:48 -0500 Subject: [PATCH 34/40] Format the terraform expamples and remove some unsupported fields --- .../d/virtual_network_gateway.html.markdown | 13 +- .../r/virtual_network_gateway.html.markdown | 139 +++++++++--------- 2 files changed, 70 insertions(+), 82 deletions(-) diff --git a/website/docs/d/virtual_network_gateway.html.markdown b/website/docs/d/virtual_network_gateway.html.markdown index 6d7219f06..b86793c5d 100644 --- a/website/docs/d/virtual_network_gateway.html.markdown +++ b/website/docs/d/virtual_network_gateway.html.markdown @@ -41,8 +41,6 @@ output "virtual_network_gateway_id" { * `enable_bgp` - Will BGP (Border Gateway Protocol) will be enabled for this Virtual Network Gateway. -* `active_active` - (Optional) Is this an Active-Active Gateway? - * `default_local_network_gateway_id` - The ID of the local network gateway through which outbound Internet traffic from the virtual network in which the gateway is created will be routed (*forced tunneling*). Refer to the @@ -84,15 +82,6 @@ The `vpn_client_configuration` block supports: * `revoked_certificate` - One or more `revoked_certificate` blocks which are defined below. -* `radius_server_address` - (Optional) The address of the Radius server. - This setting is incompatible with the use of `root_certificate` and `revoked_certificate`. - -* `radius_server_secret` - (Optional) The secret used by the Radius server. - This setting is incompatible with the use of `root_certificate` and `revoked_certificate`. - -* `vpn_client_protocols` - (Optional) List of the protocols supported by the vpn client. - The supported values are `SSTP` and `IkeV2`. - The `bgp_settings` block supports: * `asn` - The Autonomous System Number (ASN) to use as part of the BGP. @@ -116,4 +105,4 @@ The `root_revoked_certificate` block supports: * `name` - The user-defined name of the revoked certificate. -* `public_cert_data` - The SHA1 thumbprint of the certificate to be revoked. \ No newline at end of file +* `public_cert_data` - The SHA1 thumbprint of the certificate to be revoked. diff --git a/website/docs/r/virtual_network_gateway.html.markdown b/website/docs/r/virtual_network_gateway.html.markdown index 4483db38d..815377f9b 100644 --- a/website/docs/r/virtual_network_gateway.html.markdown +++ b/website/docs/r/virtual_network_gateway.html.markdown @@ -19,68 +19,67 @@ and an on-premises VPN device and network. ```hcl resource "azurestack_resource_group" "test" { - name = "test" + name = "test" location = "West US" } resource "azurestack_virtual_network" "test" { - name = "test" - location = "${azurestack_resource_group.test.location}" + name = "test" + location = "${azurestack_resource_group.test.location}" resource_group_name = "${azurestack_resource_group.test.name}" - address_space = ["10.0.0.0/16"] + address_space = ["10.0.0.0/16"] } resource "azurestack_subnet" "test" { - name = "GatewaySubnet" - resource_group_name = "${azurestack_resource_group.test.name}" + name = "GatewaySubnet" + resource_group_name = "${azurestack_resource_group.test.name}" virtual_network_name = "${azurestack_virtual_network.test.name}" - address_prefix = "10.0.1.0/24" + address_prefix = "10.0.1.0/24" } resource "azurestack_local_network_gateway" "onpremise" { - name = "onpremise" - location = "${azurestack_resource_group.test.location}" + name = "onpremise" + location = "${azurestack_resource_group.test.location}" resource_group_name = "${azurestack_resource_group.test.name}" - gateway_address = "168.62.225.23" - address_space = ["10.1.1.0/24"] + gateway_address = "168.62.225.23" + address_space = ["10.1.1.0/24"] } resource "azurestack_public_ip" "test" { - name = "test" - location = "${azurestack_resource_group.test.location}" - resource_group_name = "${azurestack_resource_group.test.name}" + name = "test" + location = "${azurestack_resource_group.test.location}" + resource_group_name = "${azurestack_resource_group.test.name}" public_ip_address_allocation = "Dynamic" } resource "azurestack_virtual_network_gateway" "test" { - name = "test" - location = "${azurestack_resource_group.test.location}" + name = "test" + location = "${azurestack_resource_group.test.location}" resource_group_name = "${azurestack_resource_group.test.name}" - type = "Vpn" - vpn_type = "RouteBased" + type = "Vpn" + vpn_type = "RouteBased" - active_active = false - enable_bgp = false - sku = "Basic" + enable_bgp = false + sku = "Basic" ip_configuration { - public_ip_address_id = "${azurestack_public_ip.test.id}" + public_ip_address_id = "${azurestack_public_ip.test.id}" private_ip_address_allocation = "Dynamic" - subnet_id = "${azurestack_subnet.test.id}" + subnet_id = "${azurestack_subnet.test.id}" } } resource "azurestack_virtual_network_gateway_connection" "onpremise" { - name = "onpremise" - location = "${azurestack_resource_group.test.location}" + name = "onpremise" + location = "${azurestack_resource_group.test.location}" resource_group_name = "${azurestack_resource_group.test.name}" type = "IPsec" virtual_network_gateway_id = "${azurestack_virtual_network_gateway.test.id}" - local_network_gateway_id = "${azurestack_local_network_gateway.onpremise.id}" + local_network_gateway_id = "${azurestack_local_network_gateway.onpremise.id}" - shared_key = "4-v3ry-53cr37-1p53c-5h4r3d-k3y" + shared_key = "4-v3ry-53cr37-1p53c-5h4r3d-k3y" } ``` @@ -91,108 +90,108 @@ in different locations/regions. ```hcl resource "azurestack_resource_group" "us" { - name = "us" + name = "us" location = "East US" } resource "azurestack_virtual_network" "us" { - name = "us" - location = "${azurestack_resource_group.us.location}" + name = "us" + location = "${azurestack_resource_group.us.location}" resource_group_name = "${azurestack_resource_group.us.name}" - address_space = ["10.0.0.0/16"] + address_space = ["10.0.0.0/16"] } resource "azurestack_subnet" "us_gateway" { - name = "GatewaySubnet" - resource_group_name = "${azurestack_resource_group.us.name}" + name = "GatewaySubnet" + resource_group_name = "${azurestack_resource_group.us.name}" virtual_network_name = "${azurestack_virtual_network.us.name}" - address_prefix = "10.0.1.0/24" + address_prefix = "10.0.1.0/24" } resource "azurestack_public_ip" "us" { - name = "us" - location = "${azurestack_resource_group.us.location}" - resource_group_name = "${azurestack_resource_group.us.name}" + name = "us" + location = "${azurestack_resource_group.us.location}" + resource_group_name = "${azurestack_resource_group.us.name}" public_ip_address_allocation = "Dynamic" } resource "azurestack_virtual_network_gateway" "us" { - name = "us-gateway" - location = "${azurestack_resource_group.us.location}" + name = "us-gateway" + location = "${azurestack_resource_group.us.location}" resource_group_name = "${azurestack_resource_group.us.name}" - type = "Vpn" - vpn_type = "RouteBased" - sku = "Basic" + type = "Vpn" + vpn_type = "RouteBased" + sku = "Basic" ip_configuration { - public_ip_address_id = "${azurestack_public_ip.us.id}" + public_ip_address_id = "${azurestack_public_ip.us.id}" private_ip_address_allocation = "Dynamic" - subnet_id = "${azurestack_subnet.us_gateway.id}" + subnet_id = "${azurestack_subnet.us_gateway.id}" } } resource "azurestack_resource_group" "europe" { - name = "europe" + name = "europe" location = "West Europe" } resource "azurestack_virtual_network" "europe" { - name = "europe" - location = "${azurestack_resource_group.europe.location}" + name = "europe" + location = "${azurestack_resource_group.europe.location}" resource_group_name = "${azurestack_resource_group.europe.name}" - address_space = ["10.1.0.0/16"] + address_space = ["10.1.0.0/16"] } resource "azurestack_subnet" "europe_gateway" { - name = "GatewaySubnet" - resource_group_name = "${azurestack_resource_group.europe.name}" + name = "GatewaySubnet" + resource_group_name = "${azurestack_resource_group.europe.name}" virtual_network_name = "${azurestack_virtual_network.europe.name}" - address_prefix = "10.1.1.0/24" + address_prefix = "10.1.1.0/24" } resource "azurestack_public_ip" "europe" { - name = "europe" - location = "${azurestack_resource_group.europe.location}" - resource_group_name = "${azurestack_resource_group.europe.name}" + name = "europe" + location = "${azurestack_resource_group.europe.location}" + resource_group_name = "${azurestack_resource_group.europe.name}" public_ip_address_allocation = "Dynamic" } resource "azurestack_virtual_network_gateway" "europe" { - name = "europe-gateway" - location = "${azurestack_resource_group.europe.location}" + name = "europe-gateway" + location = "${azurestack_resource_group.europe.location}" resource_group_name = "${azurestack_resource_group.europe.name}" - type = "Vpn" - vpn_type = "RouteBased" - sku = "Basic" + type = "Vpn" + vpn_type = "RouteBased" + sku = "Basic" ip_configuration { - public_ip_address_id = "${azurestack_public_ip.europe.id}" + public_ip_address_id = "${azurestack_public_ip.europe.id}" private_ip_address_allocation = "Dynamic" - subnet_id = "${azurestack_subnet.europe_gateway.id}" + subnet_id = "${azurestack_subnet.europe_gateway.id}" } } resource "azurestack_virtual_network_gateway_connection" "us_to_europe" { - name = "us-to-europe" - location = "${azurestack_resource_group.us.location}" + name = "us-to-europe" + location = "${azurestack_resource_group.us.location}" resource_group_name = "${azurestack_resource_group.us.name}" - type = "Vnet2Vnet" - virtual_network_gateway_id = "${azurestack_virtual_network_gateway.us.id}" + type = "Vnet2Vnet" + virtual_network_gateway_id = "${azurestack_virtual_network_gateway.us.id}" peer_virtual_network_gateway_id = "${azurestack_virtual_network_gateway.europe.id}" shared_key = "4-v3ry-53cr37-1p53c-5h4r3d-k3y" } resource "azurestack_virtual_network_gateway_connection" "europe_to_us" { - name = "europe-to-us" - location = "${azurestack_resource_group.europe.location}" + name = "europe-to-us" + location = "${azurestack_resource_group.europe.location}" resource_group_name = "${azurestack_resource_group.europe.name}" - type = "Vnet2Vnet" - virtual_network_gateway_id = "${azurestack_virtual_network_gateway.europe.id}" + type = "Vnet2Vnet" + virtual_network_gateway_id = "${azurestack_virtual_network_gateway.europe.id}" peer_virtual_network_gateway_id = "${azurestack_virtual_network_gateway.us.id}" shared_key = "4-v3ry-53cr37-1p53c-5h4r3d-k3y" @@ -297,4 +296,4 @@ Virtual Network Gateway Connections can be imported using their `resource id`, e ``` terraform import azurestack_virtual_network_gateway_connection.testConnection /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myGroup1/providers/Microsoft.Network/connections/myConnection1 -``` \ No newline at end of file +``` From d8bac59d61eaf989f69d0d5f8acb4f950e01e429 Mon Sep 17 00:00:00 2001 From: Antonio Cabrera Date: Fri, 7 Sep 2018 12:51:03 -0500 Subject: [PATCH 35/40] Add better comments --- .../resource_arm_virtual_network_gateway.go | 37 ++++++++++++++----- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/azurestack/resource_arm_virtual_network_gateway.go b/azurestack/resource_arm_virtual_network_gateway.go index c0ecf84ac..360ab40da 100644 --- a/azurestack/resource_arm_virtual_network_gateway.go +++ b/azurestack/resource_arm_virtual_network_gateway.go @@ -71,7 +71,7 @@ func resourceArmVirtualNetworkGateway() *schema.Resource { Computed: true, }, - // Not Supported by AzureStack + // ActiveActive not yet supported on 2017-03-09 service // "active_active": { // Type: schema.TypeBool, // Optional: true, @@ -144,6 +144,8 @@ func resourceArmVirtualNetworkGateway() *schema.Resource { Type: schema.TypeSet, Optional: true, + // Both radius_server_address and radius_server_secret are not yet supported on 2017-03-09 service + // and cause an error if left uncommented // ConflictsWith: []string{ // "vpn_client_configuration.0.radius_server_address", // "vpn_client_configuration.0.radius_server_secret", @@ -166,7 +168,8 @@ func resourceArmVirtualNetworkGateway() *schema.Resource { Type: schema.TypeSet, Optional: true, - // Not supported by AzureStack + // Both radius_server_address and radius_server_secret are not yet supported on 2017-03-09 service + // and cause an error if left uncommented // ConflictsWith: []string{ // "vpn_client_configuration.0.radius_server_address", // "vpn_client_configuration.0.radius_server_secret", @@ -185,6 +188,8 @@ func resourceArmVirtualNetworkGateway() *schema.Resource { }, Set: hashVirtualNetworkGatewayRevokedCert, }, + + // RadiusServerAddress not yet supported on 2017-03-09 service // "radius_server_address": { // Type: schema.TypeString, // Optional: true, @@ -194,6 +199,8 @@ func resourceArmVirtualNetworkGateway() *schema.Resource { // }, // ValidateFunc: validate.IPv4Address, // }, + + // RadiusServerSecret not yet supported on 2017-03-09 service // "radius_server_secret": { // Type: schema.TypeString, // Optional: true, @@ -208,8 +215,11 @@ func resourceArmVirtualNetworkGateway() *schema.Resource { Elem: &schema.Schema{ Type: schema.TypeString, ValidateFunc: validation.StringInSlice([]string{ - string("IkeV2"), - string("SSTP"), + + // Enums are not defined on 2017-03-09 service, using simple + // strings + "IkeV2", + "SSTP", }, true), }, }, @@ -324,7 +334,8 @@ func resourceArmVirtualNetworkGatewayRead(d *schema.ResourceData, meta interface if gw := resp.VirtualNetworkGatewayPropertiesFormat; gw != nil { d.Set("type", string(gw.GatewayType)) d.Set("enable_bgp", gw.EnableBgp) - // ActiveActive is not supported by AzureStack + + // ActiveActive not yet supported on 2017-03-09 service // d.Set("active_active", gw.ActiveActive) if vpnType := string(gw.VpnType); vpnType != "" { @@ -386,7 +397,7 @@ func getArmVirtualNetworkGatewayProperties(d *schema.ResourceData) (*network.Vir vpnType := network.VpnType(d.Get("vpn_type").(string)) enableBgp := d.Get("enable_bgp").(bool) - // ActiveActive is not supported by AzureStack + // ActiveActive not yet supported on 2017-03-09 service // activeActive := d.Get("active_active").(bool) props := &network.VirtualNetworkGatewayPropertiesFormat{ @@ -394,7 +405,7 @@ func getArmVirtualNetworkGatewayProperties(d *schema.ResourceData) (*network.Vir VpnType: vpnType, EnableBgp: &enableBgp, - // ActiveActive is not supported by AzureStack + // ActiveActive not yet supported on 2017-03-09 service // ActiveActive: &activeActive, Sku: expandArmVirtualNetworkGatewaySku(d), @@ -534,12 +545,15 @@ func expandArmVirtualNetworkGatewayVpnClientConfig(d *schema.ResourceData) *netw revokedCerts = append(revokedCerts, r) } + // VpnClientProtocols not yet supported on 2017-03-09 service // var vpnClientProtocols []network.VpnClientProtocol // for _, vpnClientProtocol := range conf["vpn_client_protocols"].(*schema.Set).List() { // p := network.VpnClientProtocol(vpnClientProtocol.(string)) // vpnClientProtocols = append(vpnClientProtocols, p) // } + // RadiusServerAddress and RadiusServerSecret not yet supported for 2017-03-09 + // service // confRadiusServerAddress := conf["radius_server_address"].(string) // confRadiusServerSecret := conf["radius_server_secret"].(string) @@ -549,9 +563,12 @@ func expandArmVirtualNetworkGatewayVpnClientConfig(d *schema.ResourceData) *netw }, VpnClientRootCertificates: &rootCerts, VpnClientRevokedCertificates: &revokedCerts, + + // RadiusServerAddress, RadiusServerSecret and VpnClientProtocols + // not yet supported for 2017-03-09 service // VpnClientProtocols: &vpnClientProtocols, - //RadiusServerAddress: &confRadiusServerAddress, - //RadiusServerSecret: &confRadiusServerSecret, + // RadiusServerAddress: &confRadiusServerAddress, + // RadiusServerSecret: &confRadiusServerSecret, } } @@ -659,6 +676,8 @@ func flattenArmVirtualNetworkGatewayVpnClientConfig(cfg *network.VpnClientConfig } flat["revoked_certificate"] = schema.NewSet(hashVirtualNetworkGatewayRevokedCert, revokedCerts) + // RadiusServerAddress, RadiusServerSecret and VpnClientProtocols + // not yet supported for 2017-03-09 service // vpnClientProtocols := &schema.Set{F: schema.HashString} // if vpnProtocols := cfg.VpnClientProtocols; vpnProtocols != nil { // for _, protocol := range *vpnProtocols { From ace1b6964a9aa95618914aac1c6b83d760858683 Mon Sep 17 00:00:00 2001 From: Antonio Cabrera Date: Fri, 7 Sep 2018 15:17:46 -0500 Subject: [PATCH 36/40] Add validations and refactor --- .../resource_arm_virtual_network_gateway.go | 48 ++++++++++--------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/azurestack/resource_arm_virtual_network_gateway.go b/azurestack/resource_arm_virtual_network_gateway.go index 360ab40da..00d315dcb 100644 --- a/azurestack/resource_arm_virtual_network_gateway.go +++ b/azurestack/resource_arm_virtual_network_gateway.go @@ -10,6 +10,8 @@ import ( "github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/suppress" + "github.com/terraform-providers/terraform-provider-azurestack/azurestack/helpers/azure" "github.com/terraform-providers/terraform-provider-azurestack/azurestack/utils" ) @@ -33,12 +35,7 @@ func resourceArmVirtualNetworkGateway() *schema.Resource { ValidateFunc: validation.NoZeroValues, }, - "resource_group_name": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - ValidateFunc: validation.NoZeroValues, - }, + "resource_group_name": resourceGroupNameSchema(), "location": locationSchema(), @@ -50,7 +47,7 @@ func resourceArmVirtualNetworkGateway() *schema.Resource { string(network.VirtualNetworkGatewayTypeExpressRoute), string(network.VirtualNetworkGatewayTypeVpn), }, true), - DiffSuppressFunc: ignoreCaseDiffSuppressFunc, + DiffSuppressFunc: suppress.CaseDifference, }, "vpn_type": { @@ -62,7 +59,7 @@ func resourceArmVirtualNetworkGateway() *schema.Resource { string(network.RouteBased), string(network.PolicyBased), }, true), - DiffSuppressFunc: ignoreCaseDiffSuppressFunc, + DiffSuppressFunc: suppress.CaseDifference, }, "enable_bgp": { @@ -81,7 +78,7 @@ func resourceArmVirtualNetworkGateway() *schema.Resource { "sku": { Type: schema.TypeString, Required: true, - DiffSuppressFunc: ignoreCaseDiffSuppressFunc, + DiffSuppressFunc: suppress.CaseDifference, ValidateFunc: validation.StringInSlice([]string{ string(network.VirtualNetworkGatewaySkuTierBasic), string(network.VirtualNetworkGatewaySkuTierStandard), @@ -103,6 +100,7 @@ func resourceArmVirtualNetworkGateway() *schema.Resource { // Azure portal. Default: "vnetGatewayConfig", }, + "private_ip_address_allocation": { Type: schema.TypeString, Optional: true, @@ -112,15 +110,18 @@ func resourceArmVirtualNetworkGateway() *schema.Resource { }, false), Default: string(network.Dynamic), }, + "subnet_id": { Type: schema.TypeString, Required: true, ValidateFunc: validateArmVirtualNetworkGatewaySubnetId, - DiffSuppressFunc: ignoreCaseDiffSuppressFunc, + DiffSuppressFunc: suppress.CaseDifference, }, + "public_ip_address_id": { - Type: schema.TypeString, - Optional: true, + Type: schema.TypeString, + Optional: true, + ValidateFunc: azure.ValidateResourceId, }, }, }, @@ -140,6 +141,7 @@ func resourceArmVirtualNetworkGateway() *schema.Resource { Type: schema.TypeString, }, }, + "root_certificate": { Type: schema.TypeSet, Optional: true, @@ -156,6 +158,7 @@ func resourceArmVirtualNetworkGateway() *schema.Resource { Type: schema.TypeString, Required: true, }, + "public_cert_data": { Type: schema.TypeString, Required: true, @@ -164,6 +167,7 @@ func resourceArmVirtualNetworkGateway() *schema.Resource { }, Set: hashVirtualNetworkGatewayRootCert, }, + "revoked_certificate": { Type: schema.TypeSet, Optional: true, @@ -180,6 +184,7 @@ func resourceArmVirtualNetworkGateway() *schema.Resource { Type: schema.TypeString, Required: true, }, + "thumbprint": { Type: schema.TypeString, Required: true, @@ -238,12 +243,14 @@ func resourceArmVirtualNetworkGateway() *schema.Resource { Type: schema.TypeInt, Optional: true, }, + "peering_address": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, }, + "peer_weight": { Type: schema.TypeInt, Optional: true, @@ -253,8 +260,9 @@ func resourceArmVirtualNetworkGateway() *schema.Resource { }, "default_local_network_gateway_id": { - Type: schema.TypeString, - Optional: true, + Type: schema.TypeString, + Optional: true, + ValidateFunc: azure.ValidateResourceId, }, "tags": tagsSchema(), @@ -428,27 +436,21 @@ func getArmVirtualNetworkGatewayProperties(d *schema.ResourceData) (*network.Vir // Sku validation for policy-based VPN gateways if props.GatewayType == network.VirtualNetworkGatewayTypeVpn && props.VpnType == network.PolicyBased { - ok, err := evaluateSchemaValidateFunc(string(props.Sku.Name), "sku", validateArmVirtualNetworkGatewayPolicyBasedVpnSku()) - - if !ok { + if ok, err := evaluateSchemaValidateFunc(string(props.Sku.Name), "sku", validateArmVirtualNetworkGatewayPolicyBasedVpnSku()); !ok { return nil, err } } // Sku validation for route-based VPN gateways if props.GatewayType == network.VirtualNetworkGatewayTypeVpn && props.VpnType == network.RouteBased { - ok, err := evaluateSchemaValidateFunc(string(props.Sku.Name), "sku", validateArmVirtualNetworkGatewayRouteBasedVpnSku()) - - if !ok { + if ok, err := evaluateSchemaValidateFunc(string(props.Sku.Name), "sku", validateArmVirtualNetworkGatewayRouteBasedVpnSku()); !ok { return nil, err } } // Sku validation for ExpressRoute gateways if props.GatewayType == network.VirtualNetworkGatewayTypeExpressRoute { - ok, err := evaluateSchemaValidateFunc(string(props.Sku.Name), "sku", validateArmVirtualNetworkGatewayExpressRouteSku()) - - if !ok { + if ok, err := evaluateSchemaValidateFunc(string(props.Sku.Name), "sku", validateArmVirtualNetworkGatewayExpressRouteSku()); !ok { return nil, err } } From f42216a6a1fb3246dc52f0cb86f727361a266133 Mon Sep 17 00:00:00 2001 From: kt Date: Fri, 7 Sep 2018 14:01:44 -0700 Subject: [PATCH 37/40] Update CHANGELOG.md to include #31 --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bdf91df17..5075139ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,9 @@ * **New Resource:** `azurestack_route_table` [GH-26] * **New Resource:** `azurestack_route` [GH-27] * **New Resource:** `azurestack_template_deployment` [GH-33] +* **New Resource:** `azurestack_virtual_network_gateway` [GH-31] * **New Data Source:** `azurestack_route_table` [GH-26] +* **New Data Source:** `azurestack_virtual_network_gateway` [GH-31] ## 0.3.0 (August 13, 2018) From bad5270700ce9d7537873191fb5ecb0938ced3a9 Mon Sep 17 00:00:00 2001 From: kt Date: Sat, 8 Sep 2018 10:41:20 -0700 Subject: [PATCH 38/40] Update data_source_subnet.go --- azurestack/data_source_subnet.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/azurestack/data_source_subnet.go b/azurestack/data_source_subnet.go index 86d27ef02..14a05d95f 100644 --- a/azurestack/data_source_subnet.go +++ b/azurestack/data_source_subnet.go @@ -60,18 +60,13 @@ func dataSourceArmSubnetRead(d *schema.ResourceData, meta interface{}) error { resourceGroup := d.Get("resource_group_name").(string) resp, err := client.Get(ctx, resourceGroup, virtualNetworkName, name, "") - if err != nil { - return fmt.Errorf("Error reading Subnet: %+v", err) - } - - d.SetId(*resp.ID) - if err != nil { if utils.ResponseWasNotFound(resp.Response) { return fmt.Errorf("Error: Subnet %q (Virtual Network %q / Resource Group %q) was not found", name, resourceGroup, virtualNetworkName) } return fmt.Errorf("Error making Read request on Azure Subnet %q: %+v", name, err) } + d.SetId(*resp.ID) d.Set("name", name) d.Set("resource_group_name", resourceGroup) From ee036442ec5c2bc5ced335afc7f546a982b81028 Mon Sep 17 00:00:00 2001 From: kt Date: Sat, 8 Sep 2018 10:44:54 -0700 Subject: [PATCH 39/40] Update provider.go --- azurestack/provider.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azurestack/provider.go b/azurestack/provider.go index 7c566b034..8220c49e4 100644 --- a/azurestack/provider.go +++ b/azurestack/provider.go @@ -68,7 +68,7 @@ func Provider() terraform.ResourceProvider { "azurestack_client_config": dataSourceArmClientConfig(), "azurestack_network_interface": dataSourceArmNetworkInterface(), "azurestack_network_security_group": dataSourceArmNetworkSecurityGroup(), - "azurestack_public_ip": dataSourceArmPublicIP(), + "azurestack_public_ip": dataSourceArmPublicIP(), "azurestack_resource_group": dataSourceArmResourceGroup(), "azurestack_storage_account": dataSourceArmStorageAccount(), "azurestack_virtual_network": dataSourceArmVirtualNetwork(), From 8311774cffdd7a4ba03dfac79d708f064a699bcf Mon Sep 17 00:00:00 2001 From: kt Date: Sat, 8 Sep 2018 10:51:10 -0700 Subject: [PATCH 40/40] Update CHANGELOG.md to include #34 --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5075139ef..a82eaeecb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,9 @@ * **New Resource:** `azurestack_route` [GH-27] * **New Resource:** `azurestack_template_deployment` [GH-33] * **New Resource:** `azurestack_virtual_network_gateway` [GH-31] +* **New Data Source:** `azurestack_public_ip` [GH-34] * **New Data Source:** `azurestack_route_table` [GH-26] +* **New Data Source:** `azurestack_subnet` [GH-34] * **New Data Source:** `azurestack_virtual_network_gateway` [GH-31] ## 0.3.0 (August 13, 2018)