diff --git a/CHANGELOG.md b/CHANGELOG.md index 32ceb7cd8..a82eaeecb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,12 @@ * **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_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) diff --git a/azurestack/config.go b/azurestack/config.go index e47fb306f..d1aac421b 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 @@ -71,6 +72,7 @@ type ArmClient struct { routeTablesClient network.RouteTablesClient resourceGroupsClient resources.GroupsClient + deploymentsClient resources.DeploymentsClient } func (c *ArmClient) configureClient(client *autorest.Client, auth autorest.Authorizer) { @@ -226,6 +228,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 @@ -268,6 +274,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/data_source_public_ip.go b/azurestack/data_source_public_ip.go new file mode 100644 index 000000000..eb0da1866 --- /dev/null +++ b/azurestack/data_source_public_ip.go @@ -0,0 +1,87 @@ +package azurestack + +import ( + "fmt" + + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" + "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, + ValidateFunc: validation.NoZeroValues, + }, + + "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 props := resp.PublicIPAddressPropertiesFormat; props != nil { + if dnsSettings := props.DNSSettings; dnsSettings != nil { + if v := dnsSettings.Fqdn; v != nil && *v != "" { + d.Set("fqdn", v) + } + + if v := dnsSettings.DomainNameLabel; v != nil && *v != "" { + d.Set("domain_name_label", v) + } + } + + if v := props.IPAddress; v != nil && *v != "" { + d.Set("ip_address", v) + } + + if v := props.IdleTimeoutInMinutes; v != 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/data_source_subnet.go b/azurestack/data_source_subnet.go new file mode 100644 index 000000000..14a05d95f --- /dev/null +++ b/azurestack/data_source_subnet.go @@ -0,0 +1,96 @@ +package azurestack + +import ( + "fmt" + + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" + "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, + ValidateFunc: validation.NoZeroValues, + }, + + "virtual_network_name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.NoZeroValues, + }, + + "resource_group_name": resourceGroupNameForDataSourceSchema(), + + "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 { + 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) + 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", "") + } + + if err := d.Set("ip_configurations", flattenSubnetIPConfigurations(props.IPConfigurations)); 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/data_source_virtual_network_gateway.go b/azurestack/data_source_virtual_network_gateway.go new file mode 100644 index 000000000..442c1f438 --- /dev/null +++ b/azurestack/data_source_virtual_network_gateway.go @@ -0,0 +1,391 @@ +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/hashicorp/terraform/helper/validation" + "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, + ValidateFunc: validation.NoZeroValues, + }, + + "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, + }, + + // Not yet supported on service 2017-03-09 + // "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) + + // ActiveActive not yet supported on 2017-03-09 service + //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 not yet supported on 2017-03-09 service + // 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 + + // 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 + // } + + 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 73cf1d119..476956779 100644 --- a/azurestack/provider.go +++ b/azurestack/provider.go @@ -65,13 +65,16 @@ 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_route_table": dataSourceArmRouteTable(), + "azurestack_client_config": dataSourceArmClientConfig(), + "azurestack_network_interface": dataSourceArmNetworkInterface(), + "azurestack_network_security_group": dataSourceArmNetworkSecurityGroup(), + "azurestack_public_ip": dataSourceArmPublicIP(), + "azurestack_resource_group": dataSourceArmResourceGroup(), + "azurestack_storage_account": dataSourceArmStorageAccount(), + "azurestack_virtual_network": dataSourceArmVirtualNetwork(), + "azurestack_route_table": dataSourceArmRouteTable(), + "azurestack_subnet": dataSourceArmSubnet(), + "azurestack_virtual_network_gateway": dataSourceArmVirtualNetworkGateway(), }, ResourcesMap: map[string]*schema.Resource{ @@ -96,7 +99,9 @@ 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_network_gateway": resourceArmVirtualNetworkGateway(), "azurestack_virtual_machine": resourceArmVirtualMachine(), "azurestack_virtual_machine_extension": resourceArmVirtualMachineExtensions(), "azurestack_virtual_machine_scale_set": resourceArmVirtualMachineScaleSet(), diff --git a/azurestack/resource_arm_template_deployment.go b/azurestack/resource_arm_template_deployment.go new file mode 100644 index 000000000..7d6d73132 --- /dev/null +++ b/azurestack/resource_arm_template_deployment.go @@ -0,0 +1,293 @@ +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, + ValidateFunc: validation.NoZeroValues, + }, + + "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"}, + ValidateFunc: validation.NoZeroValues, + }, + + "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 Template Deployment %q (Resource Group %q): %+v", name, resourceGroup, 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 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) + } + + 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"] + + 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 _, 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) +} + +// 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..22962fecc --- /dev/null +++ b/azurestack/resource_arm_template_deployment_test.go @@ -0,0 +1,712 @@ +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, + }, + }, + }) +} + +func TestAccAzureStackTemplateDeployment_withParams(t *testing.T) { + + 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"), + ), + }, + }, + }) +} + +func TestAccAzureStackTemplateDeployment_withParamsBody(t *testing.T) { + + 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"), + ), + }, + }, + }) + +} + +func TestAccAzureStackTemplateDeployment_withOutputs(t *testing.T) { + + 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_LRS"), + resource.TestCheckOutput("tfFalseOutput", "0"), + resource.TestCheckOutput("tfTrueOutput", "1"), + resource.TestCheckResourceAttr("azurestack_template_deployment.test", "outputs.stringOutput", "Standard_LRS"), + ), + }, + }, + }) +} + +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 = < 0 { + return false, es[0] + } + + return true, 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..1c083f445 --- /dev/null +++ b/azurestack/resource_arm_virtual_network_gateway_test.go @@ -0,0 +1,561 @@ +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"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +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"), + ), + }, + }, + }) +} + +//VpnGw1 sku is not supported yet. +func TestAccAzureStackVirtualNetworkGateway_vpnGw1(t *testing.T) { + t.Skip() + 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"), + ), + }, + }, + }) +} + +//VpnGw1 sku an activeActive are not supported yet. +func TestAccAzureStackVirtualNetworkGateway_activeActive(t *testing.T) { + + t.Skip() + + 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"), + ), + }, + }, + }) +} + +//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") + + 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"), + ), + }, + }, + }) +} + +//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") + + 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.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"), + ), + }, + }, + }) +} + +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 = "Basic" + #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, 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" + } +} +`, 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) +} diff --git a/azurestack/test_utils.go b/azurestack/test_utils.go new file mode 100644 index 000000000..011126952 --- /dev/null +++ b/azurestack/test_utils.go @@ -0,0 +1,26 @@ +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, hasName := rs.Primary.Attributes["name"] + armResourceGroup, hasResourceGroup := rs.Primary.Attributes["resource_group_name"] + if !hasResourceGroup { + 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 +} 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/website/azurestack.erb b/website/azurestack.erb index e08a97ccb..36c6a7b6f 100644 --- a/website/azurestack.erb +++ b/website/azurestack.erb @@ -40,11 +40,12 @@ > azurestack_storage_account - - > azurestack_virtual_network + > + azurestack_virtual_network_gateway + @@ -134,6 +135,10 @@ > azurestack_virtual_network + + > + azurestack_virtual_network_gateway + @@ -187,6 +192,15 @@ + > + Template Resources + + + <% end %> diff --git a/website/docs/d/public_ip.html.markdown b/website/docs/d/public_ip.html.markdown new file mode 100644 index 000000000..2bda45ced --- /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. 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. 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..b86793c5d --- /dev/null +++ b/website/docs/d/virtual_network_gateway.html.markdown @@ -0,0 +1,108 @@ +--- +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. + +* `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. + +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. diff --git a/website/docs/r/template_deployment.html.markdown b/website/docs/r/template_deployment.html.markdown new file mode 100644 index 000000000..09974fff0 --- /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 + +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. +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 ARM 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). 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..815377f9b --- /dev/null +++ b/website/docs/r/virtual_network_gateway.html.markdown @@ -0,0 +1,299 @@ +--- +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" + + 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 +```