Merge branch 'master' into b-data-diff-lag

This commit is contained in:
James Nugent 2016-05-29 12:01:01 -07:00
commit 8b252cfd2b
26 changed files with 849 additions and 27 deletions

View File

@ -11,46 +11,48 @@ FEATURES:
* **New Command:** `terraform state` to provide access to a variety of state manipulation functions [GH-5811] * **New Command:** `terraform state` to provide access to a variety of state manipulation functions [GH-5811]
* **New Provider:** `grafana` [GH-6206] * **New Provider:** `grafana` [GH-6206]
* **New Resource:** `aws_rds_cluster_parameter_group` [GH-5269] * **New Provider:** `random` - allows generation of random values without constantly generating diffs [GH6672]
* **New Resource:** `openstack_blockstorage_volume_v2` [GH-6693]
* **New Resource:** `vsphere_virtual_disk` [GH-6273]
* **New Resource:** `aws_iam_group_policy_attachment` [GH-6858] * **New Resource:** `aws_iam_group_policy_attachment` [GH-6858]
* **New Resource:** `aws_iam_role_policy_attachment` [GH-6858] * **New Resource:** `aws_iam_role_policy_attachment` [GH-6858]
* **New Resource:** `aws_iam_user_policy_attachment` [GH-6858] * **New Resource:** `aws_iam_user_policy_attachment` [GH-6858]
* **New Resource:** `aws_rds_cluster_parameter_group` [GH-5269]
* **New Resource:** `openstack_blockstorage_volume_v2` [GH-6693]
* **New Resource:** `vsphere_virtual_disk` [GH-6273]
* core: Data Resources are now supported. Values are refreshed, and available during the planning stage [GH-6598] * core: Data Resources are now supported. Values are refreshed, and available during the planning stage [GH-6598]
* core: Lists and maps can now be used as first class types for variables, and may be passed between modules [GH-6322] * core: Lists and maps can now be used as first class types for variables, and may be passed between modules [GH-6322]
* core: The `terraform plan` command no longer persists state. [GH-6811]
* core: Tainted resources now show up in the plan and respect dependency ordering [GH-6600] * core: Tainted resources now show up in the plan and respect dependency ordering [GH-6600]
* core: The `terraform plan` command no longer persists state. [GH-6811]
IMPROVEMENTS: IMPROVEMENTS:
* core: The `jsonencode` interpolation function now supports encoding lists and maps [GH-6749] * core: The `jsonencode` interpolation function now supports encoding lists and maps [GH-6749]
* provider/aws: Add `option_settings` to `aws_db_option_group` [GH-6560] * provider/aws: Add `option_settings` to `aws_db_option_group` [GH-6560]
* provider/aws: Add more explicit support for Skipping Final Snapshot in RDS Cluster [GH-6795]
* provider/aws: Add support for S3 Bucket Acceleration [GH-6628] * provider/aws: Add support for S3 Bucket Acceleration [GH-6628]
* provider/aws: Add support for `kms_key_id` to `aws_db_instance` [GH-6651] * provider/aws: Add support for `kms_key_id` to `aws_db_instance` [GH-6651]
* provider/aws: Support for Redshift Cluster encryption using a KMS key [GH-6712]
* provider/aws: Add more explicit support for Skipping Final Snapshot in RDS Cluster [GH-6795]
* provider/aws: Set default description to "Managed by Terraform" [GH-6104]
* provider/aws: SQS use raw policy string if compact fails [GH-6724]
* provider/aws: Support tags for AWS redshift cluster [GH-5356]
* provider/aws: Add support to `aws_redshift_cluster` for `iam_roles` [GH-6647] * provider/aws: Add support to `aws_redshift_cluster` for `iam_roles` [GH-6647]
* provider/azurerm: Add support for exporting the `azurerm_storage_account` access keys [GH-6742] * provider/aws: SQS use raw policy string if compact fails [GH-6724]
* provider/aws: Set default description to "Managed by Terraform" [GH-6104]
* provider/aws: Support for Redshift Cluster encryption using a KMS key [GH-6712]
* provider/aws: Support tags for AWS redshift cluster [GH-5356]
* provider/azurerm: Add support for EnableIPForwarding to `azurerm_network_interface` [GH-6807] * provider/azurerm: Add support for EnableIPForwarding to `azurerm_network_interface` [GH-6807]
* provider/azurerm: Add support for exporting the `azurerm_storage_account` access keys [GH-6742]
* provider/clc: Add support for hyperscale and bareMetal server types and package installation * provider/clc: Add support for hyperscale and bareMetal server types and package installation
* provider/clc: Fix optional server password [GH-6414] * provider/clc: Fix optional server password [GH-6414]
* provider/cloudstack: Enable swapping of ACLs without having to rebuild the network tier [GH-6741]
* provider/cloudstack: Add support for affinity groups to `cloudstack_instance` [GH-6898] * provider/cloudstack: Add support for affinity groups to `cloudstack_instance` [GH-6898]
* provider/cloudstack: Enable swapping of ACLs without having to rebuild the network tier [GH-6741]
* provider/datadog: Add support for 'require full window' and 'locked' [GH-6738] * provider/datadog: Add support for 'require full window' and 'locked' [GH-6738]
* provider/fastly: Add support for Cache Settings [GH-6781]
* provider/fastly: Add support for Service Request Settings on `fastly_service_v1` resources [GH-6622] * provider/fastly: Add support for Service Request Settings on `fastly_service_v1` resources [GH-6622]
* provider/fastly: Add support for custom VCL configuration [GH-6662] * provider/fastly: Add support for custom VCL configuration [GH-6662]
* provider/fastly: Add support for Cache Settings [GH-6781]
* provider/google: Support optional uuid naming for Instance Template [GH-6604] * provider/google: Support optional uuid naming for Instance Template [GH-6604]
* provider/openstack: Increase timeouts for image resize, subnets, and routers [GH-6764]
* provider/openstack: Add support for client certificate authentication [GH-6279] * provider/openstack: Add support for client certificate authentication [GH-6279]
* provider/openstack: Enable DHCP By Default [GH-6838]
* provider/openstack: Allow Neutron-based Floating IP to target a specific tenant [GH-6454] * provider/openstack: Allow Neutron-based Floating IP to target a specific tenant [GH-6454]
* provider/vsphere: Fix bug with `vsphere_virtual_machine` wait for ip [GH-6377] * provider/openstack: Enable DHCP By Default [GH-6838]
* provider/openstack: Implement fixed_ip on Neutron floating ip allocations [GH-6837]
* provider/openstack: Increase timeouts for image resize, subnets, and routers [GH-6764]
* provider/vsphere: Add support for `controller_type` to `vsphere_virtual_machine` [GH-6785] * provider/vsphere: Add support for `controller_type` to `vsphere_virtual_machine` [GH-6785]
* provider/vsphere: Fix bug with `vsphere_virtual_machine` wait for ip [GH-6377]
* provider/vsphere: Virtual machine update disk [GH-6619] * provider/vsphere: Virtual machine update disk [GH-6619]
BUG FIXES: BUG FIXES:
@ -74,6 +76,7 @@ BUG FIXES:
* provider/google: Fix a bug causing an error attempting to delete an already-deleted `google_compute_disk` [GH-6689] * provider/google: Fix a bug causing an error attempting to delete an already-deleted `google_compute_disk` [GH-6689]
* provider/openstack: Reassociate Floating IP on network changes [GH-6579] * provider/openstack: Reassociate Floating IP on network changes [GH-6579]
* provider/openstack: Ensure CIDRs Are Lower Case [GH-6864] * provider/openstack: Ensure CIDRs Are Lower Case [GH-6864]
* provider/openstack: Rebuild Instances On Network Changes [GH-6844]
* provider/vsphere: `gateway` and `ipv6_gateway` are now read from `vsphere_virtual_machine` resources [GH-6522] * provider/vsphere: `gateway` and `ipv6_gateway` are now read from `vsphere_virtual_machine` resources [GH-6522]
* provider/vsphere: `ipv*_gateway` parameters won't force a new `vsphere_virtual_machine` [GH-6635] * provider/vsphere: `ipv*_gateway` parameters won't force a new `vsphere_virtual_machine` [GH-6635]

View File

@ -0,0 +1,15 @@
package main
import (
"github.com/hashicorp/terraform/builtin/providers/random"
"github.com/hashicorp/terraform/plugin"
"github.com/hashicorp/terraform/terraform"
)
func main() {
plugin.Serve(&plugin.ServeOpts{
ProviderFunc: func() terraform.ResourceProvider {
return random.Provider()
},
})
}

View File

@ -0,0 +1 @@
package main

View File

@ -116,10 +116,12 @@ wget http://download.cirros-cloud.net/0.3.4/cirros-0.3.4-x86_64-disk.img
glance image-create --name CirrOS --disk-format qcow2 --container-format bare < cirros-0.3.4-x86_64-disk.img glance image-create --name CirrOS --disk-format qcow2 --container-format bare < cirros-0.3.4-x86_64-disk.img
nova flavor-create m1.tform 99 512 5 1 --ephemeral 10 nova flavor-create m1.tform 99 512 5 1 --ephemeral 10
_NETWORK_ID=$(nova net-list | grep private | awk -F\| '{print $2}' | tr -d ' ') _NETWORK_ID=$(nova net-list | grep private | awk -F\| '{print $2}' | tr -d ' ')
_EXTGW_ID=$(nova net-list | grep public | awk -F\| '{print $2}' | tr -d ' ')
_IMAGE_ID=$(nova image-list | grep CirrOS | awk -F\| '{print $2}' | tr -d ' ' | head -1) _IMAGE_ID=$(nova image-list | grep CirrOS | awk -F\| '{print $2}' | tr -d ' ' | head -1)
echo export OS_IMAGE_NAME="cirros-0.3.4-x86_64-uec" >> openrc echo export OS_IMAGE_NAME="cirros-0.3.4-x86_64-uec" >> openrc
echo export OS_IMAGE_ID="$_IMAGE_ID" >> openrc echo export OS_IMAGE_ID="$_IMAGE_ID" >> openrc
echo export OS_NETWORK_ID=$_NETWORK_ID >> openrc echo export OS_NETWORK_ID=$_NETWORK_ID >> openrc
echo export OS_EXTGW_ID=$_EXTGW_ID >> openrc
echo export OS_POOL_NAME="public" >> openrc echo export OS_POOL_NAME="public" >> openrc
echo export OS_FLAVOR_ID=99 >> openrc echo export OS_FLAVOR_ID=99 >> openrc
source openrc demo source openrc demo

View File

@ -67,4 +67,9 @@ func testAccPreCheck(t *testing.T) {
if v == "" { if v == "" {
t.Fatal("OS_NETWORK_ID must be set for acceptance tests") t.Fatal("OS_NETWORK_ID must be set for acceptance tests")
} }
v = os.Getenv("OS_EXTGW_ID")
if v == "" {
t.Fatal("OS_EXTGW_ID must be set for acceptance tests")
}
} }

View File

@ -114,26 +114,31 @@ func resourceComputeInstanceV2() *schema.Resource {
"uuid": &schema.Schema{ "uuid": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
ForceNew: true,
Computed: true, Computed: true,
}, },
"name": &schema.Schema{ "name": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
ForceNew: true,
Computed: true, Computed: true,
}, },
"port": &schema.Schema{ "port": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
ForceNew: true,
Computed: true, Computed: true,
}, },
"fixed_ip_v4": &schema.Schema{ "fixed_ip_v4": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
ForceNew: true,
Computed: true, Computed: true,
}, },
"fixed_ip_v6": &schema.Schema{ "fixed_ip_v6": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
ForceNew: true,
Computed: true, Computed: true,
}, },
"floating_ip": &schema.Schema{ "floating_ip": &schema.Schema{

View File

@ -557,6 +557,53 @@ func TestAccComputeV2Instance_accessIPv4(t *testing.T) {
}) })
} }
func TestAccComputeV2Instance_ChangeFixedIP(t *testing.T) {
var instance1_1 servers.Server
var instance1_2 servers.Server
var testAccComputeV2Instance_ChangeFixedIP_1 = fmt.Sprintf(`
resource "openstack_compute_instance_v2" "instance_1" {
name = "instance_1"
security_groups = ["default"]
network {
uuid = "%s"
fixed_ip_v4 = "10.0.0.24"
}
}`,
os.Getenv("OS_NETWORK_ID"))
var testAccComputeV2Instance_ChangeFixedIP_2 = fmt.Sprintf(`
resource "openstack_compute_instance_v2" "instance_1" {
name = "instance_1"
security_groups = ["default"]
network {
uuid = "%s"
fixed_ip_v4 = "10.0.0.25"
}
}`,
os.Getenv("OS_NETWORK_ID"))
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckComputeV2InstanceDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccComputeV2Instance_ChangeFixedIP_1,
Check: resource.ComposeTestCheckFunc(
testAccCheckComputeV2InstanceExists(t, "openstack_compute_instance_v2.instance_1", &instance1_1),
),
},
resource.TestStep{
Config: testAccComputeV2Instance_ChangeFixedIP_2,
Check: resource.ComposeTestCheckFunc(
testAccCheckComputeV2InstanceExists(t, "openstack_compute_instance_v2.instance_1", &instance1_2),
testAccCheckComputeV2InstanceInstanceIDsDoNotMatch(&instance1_1, &instance1_2),
),
},
},
})
}
func testAccCheckComputeV2InstanceDestroy(s *terraform.State) error { func testAccCheckComputeV2InstanceDestroy(s *terraform.State) error {
config := testAccProvider.Meta().(*Config) config := testAccProvider.Meta().(*Config)
computeClient, err := config.computeV2Client(OS_REGION_NAME) computeClient, err := config.computeV2Client(OS_REGION_NAME)
@ -726,6 +773,15 @@ func testAccCheckComputeV2InstanceFloatingIPAttach(
} }
return fmt.Errorf("Floating IP %s was not attached to instance %s", fip.ID, instance.ID) return fmt.Errorf("Floating IP %s was not attached to instance %s", fip.ID, instance.ID)
}
}
func testAccCheckComputeV2InstanceInstanceIDsDoNotMatch(
instance1, instance2 *servers.Server) resource.TestCheckFunc {
return func(s *terraform.State) error {
if instance1.ID == instance2.ID {
return fmt.Errorf("Instance was not recreated.")
}
return nil
} }
} }

View File

@ -49,6 +49,11 @@ func resourceNetworkingFloatingIPV2() *schema.Resource {
Computed: true, Computed: true,
ForceNew: true, ForceNew: true,
}, },
"fixed_ip": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
}, },
} }
} }
@ -71,6 +76,7 @@ func resourceNetworkFloatingIPV2Create(d *schema.ResourceData, meta interface{})
FloatingNetworkID: poolID, FloatingNetworkID: poolID,
PortID: d.Get("port_id").(string), PortID: d.Get("port_id").(string),
TenantID: d.Get("tenant_id").(string), TenantID: d.Get("tenant_id").(string),
FixedIP: d.Get("fixed_ip").(string),
} }
log.Printf("[DEBUG] Create Options: %#v", createOpts) log.Printf("[DEBUG] Create Options: %#v", createOpts)
floatingIP, err := floatingips.Create(networkingClient, createOpts).Extract() floatingIP, err := floatingips.Create(networkingClient, createOpts).Extract()
@ -109,6 +115,7 @@ func resourceNetworkFloatingIPV2Read(d *schema.ResourceData, meta interface{}) e
d.Set("address", floatingIP.FloatingIP) d.Set("address", floatingIP.FloatingIP)
d.Set("port_id", floatingIP.PortID) d.Set("port_id", floatingIP.PortID)
d.Set("fixed_ip", floatingIP.FixedIP)
poolName, err := getNetworkName(d, meta, floatingIP.FloatingNetworkID) poolName, err := getNetworkName(d, meta, floatingIP.FloatingNetworkID)
if err != nil { if err != nil {
return fmt.Errorf("Error retrieving floating IP pool name: %s", err) return fmt.Errorf("Error retrieving floating IP pool name: %s", err)

View File

@ -65,6 +65,67 @@ func TestAccNetworkingV2FloatingIP_attach(t *testing.T) {
}) })
} }
func TestAccNetworkingV2FloatingIP_fixedip_bind(t *testing.T) {
var fip floatingips.FloatingIP
var testAccNetworkingV2FloatingIP_fixedip_bind = fmt.Sprintf(`
resource "openstack_networking_network_v2" "network_1" {
name = "network_1"
admin_state_up = "true"
}
resource "openstack_networking_subnet_v2" "subnet_1" {
name = "subnet_1"
network_id = "${openstack_networking_network_v2.network_1.id}"
cidr = "192.168.199.0/24"
ip_version = 4
}
resource "openstack_networking_router_interface_v2" "router_interface_1" {
router_id = "${openstack_networking_router_v2.router_1.id}"
subnet_id = "${openstack_networking_subnet_v2.subnet_1.id}"
}
resource "openstack_networking_router_v2" "router_1" {
name = "router_1"
external_gateway = "%s"
}
resource "openstack_networking_port_v2" "port_1" {
network_id = "${openstack_networking_subnet_v2.subnet_1.network_id}"
admin_state_up = "true"
fixed_ip {
subnet_id = "${openstack_networking_subnet_v2.subnet_1.id}"
ip_address = "192.168.199.10"
}
fixed_ip {
subnet_id = "${openstack_networking_subnet_v2.subnet_1.id}"
ip_address = "192.168.199.20"
}
}
resource "openstack_networking_floatingip_v2" "ip_1" {
pool = "%s"
port_id = "${openstack_networking_port_v2.port_1.id}"
fixed_ip = "${openstack_networking_port_v2.port_1.fixed_ip.1.ip_address}"
}`,
os.Getenv("OS_EXTGW_ID"), os.Getenv("OS_POOL_NAME"))
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckNetworkingV2FloatingIPDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccNetworkingV2FloatingIP_fixedip_bind,
Check: resource.ComposeTestCheckFunc(
testAccCheckNetworkingV2FloatingIPExists(t, "openstack_networking_floatingip_v2.ip_1", &fip),
testAccCheckNetworkingV2FloatingIPBoundToCorrectIP(t, &fip, "192.168.199.20"),
),
},
},
})
}
func testAccCheckNetworkingV2FloatingIPDestroy(s *terraform.State) error { func testAccCheckNetworkingV2FloatingIPDestroy(s *terraform.State) error {
config := testAccProvider.Meta().(*Config) config := testAccProvider.Meta().(*Config)
networkClient, err := config.networkingV2Client(OS_REGION_NAME) networkClient, err := config.networkingV2Client(OS_REGION_NAME)
@ -118,6 +179,16 @@ func testAccCheckNetworkingV2FloatingIPExists(t *testing.T, n string, kp *floati
} }
} }
func testAccCheckNetworkingV2FloatingIPBoundToCorrectIP(t *testing.T, fip *floatingips.FloatingIP, fixed_ip string) resource.TestCheckFunc {
return func(s *terraform.State) error {
if fip.FixedIP != fixed_ip {
return fmt.Errorf("Floating ip associated with wrong fixed ip")
}
return nil
}
}
func testAccCheckNetworkingV2InstanceFloatingIPAttach( func testAccCheckNetworkingV2InstanceFloatingIPAttach(
instance *servers.Server, fip *floatingips.FloatingIP) resource.TestCheckFunc { instance *servers.Server, fip *floatingips.FloatingIP) resource.TestCheckFunc {

View File

@ -0,0 +1,31 @@
package random
import (
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/terraform"
)
// Provider returns a terraform.ResourceProvider.
func Provider() terraform.ResourceProvider {
return &schema.Provider{
Schema: map[string]*schema.Schema{},
ResourcesMap: map[string]*schema.Resource{
"random_id": resourceId(),
"random_shuffle": resourceShuffle(),
},
}
}
// stubRead is a do-nothing Read implementation used for our resources,
// which don't actually need to do anything on read.
func stubRead(d *schema.ResourceData, meta interface{}) error {
return nil
}
// stubDelete is a do-nothing Dete implementation used for our resources,
// which don't actually need to do anything unusual on delete.
func stubDelete(d *schema.ResourceData, meta interface{}) error {
d.SetId("")
return nil
}

View File

@ -0,0 +1,31 @@
package random
import (
"testing"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/terraform"
)
var testAccProviders map[string]terraform.ResourceProvider
var testAccProvider *schema.Provider
func init() {
testAccProvider = Provider().(*schema.Provider)
testAccProviders = map[string]terraform.ResourceProvider{
"random": testAccProvider,
}
}
func TestProvider(t *testing.T) {
if err := Provider().(*schema.Provider).InternalValidate(); err != nil {
t.Fatalf("err: %s", err)
}
}
func TestProvider_impl(t *testing.T) {
var _ terraform.ResourceProvider = Provider()
}
func testAccPreCheck(t *testing.T) {
}

View File

@ -0,0 +1,76 @@
package random
import (
"crypto/rand"
"encoding/base64"
"encoding/hex"
"fmt"
"math/big"
"github.com/hashicorp/terraform/helper/schema"
)
func resourceId() *schema.Resource {
return &schema.Resource{
Create: CreateID,
Read: stubRead,
Delete: stubDelete,
Schema: map[string]*schema.Schema{
"keepers": &schema.Schema{
Type: schema.TypeMap,
Optional: true,
ForceNew: true,
},
"byte_length": &schema.Schema{
Type: schema.TypeInt,
Required: true,
ForceNew: true,
},
"b64": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"hex": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"dec": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
},
}
}
func CreateID(d *schema.ResourceData, meta interface{}) error {
byteLength := d.Get("byte_length").(int)
bytes := make([]byte, byteLength)
n, err := rand.Reader.Read(bytes)
if n != byteLength {
return fmt.Errorf("generated insufficient random bytes")
}
if err != nil {
return fmt.Errorf("error generating random bytes: %s", err)
}
b64Str := base64.RawURLEncoding.EncodeToString(bytes)
hexStr := hex.EncodeToString(bytes)
int := big.Int{}
int.SetBytes(bytes)
decStr := int.String()
d.SetId(b64Str)
d.Set("b64", b64Str)
d.Set("hex", hexStr)
d.Set("dec", decStr)
return nil
}

View File

@ -0,0 +1,58 @@
package random
import (
"fmt"
"testing"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
func TestAccResourceID(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccResourceIDConfig,
Check: resource.ComposeTestCheckFunc(
testAccResourceIDCheck("random_id.foo"),
),
},
},
})
}
func testAccResourceIDCheck(id string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[id]
if !ok {
return fmt.Errorf("Not found: %s", id)
}
if rs.Primary.ID == "" {
return fmt.Errorf("No ID is set")
}
b64Str := rs.Primary.Attributes["b64"]
hexStr := rs.Primary.Attributes["hex"]
decStr := rs.Primary.Attributes["dec"]
if got, want := len(b64Str), 6; got != want {
return fmt.Errorf("base64 string length is %d; want %d", got, want)
}
if got, want := len(hexStr), 8; got != want {
return fmt.Errorf("hex string length is %d; want %d", got, want)
}
if len(decStr) < 1 {
return fmt.Errorf("decimal string is empty; want at least one digit")
}
return nil
}
}
const testAccResourceIDConfig = `
resource "random_id" "foo" {
byte_length = 4
}
`

View File

@ -0,0 +1,82 @@
package random
import (
"github.com/hashicorp/terraform/helper/schema"
)
func resourceShuffle() *schema.Resource {
return &schema.Resource{
Create: CreateShuffle,
Read: stubRead,
Delete: stubDelete,
Schema: map[string]*schema.Schema{
"keepers": &schema.Schema{
Type: schema.TypeMap,
Optional: true,
ForceNew: true,
},
"seed": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
"input": &schema.Schema{
Type: schema.TypeList,
Required: true,
ForceNew: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
"result": &schema.Schema{
Type: schema.TypeList,
Computed: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
"result_count": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
ForceNew: true,
},
},
}
}
func CreateShuffle(d *schema.ResourceData, meta interface{}) error {
input := d.Get("input").([]interface{})
seed := d.Get("seed").(string)
resultCount := d.Get("result_count").(int)
if resultCount == 0 {
resultCount = len(input)
}
result := make([]interface{}, 0, resultCount)
rand := NewRand(seed)
// Keep producing permutations until we fill our result
Batches:
for {
perm := rand.Perm(len(input))
for _, i := range perm {
result = append(result, input[i])
if len(result) >= resultCount {
break Batches
}
}
}
d.SetId("-")
d.Set("result", result)
return nil
}

View File

@ -0,0 +1,91 @@
package random
import (
"fmt"
"strconv"
"testing"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
func TestAccResourceShuffle(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccResourceShuffleConfig,
Check: resource.ComposeTestCheckFunc(
// These results are current as of Go 1.6. The Go
// "rand" package does not guarantee that the random
// number generator will generate the same results
// forever, but the maintainers endeavor not to change
// it gratuitously.
// These tests allow us to detect such changes and
// document them when they arise, but the docs for this
// resource specifically warn that results are not
// guaranteed consistent across Terraform releases.
testAccResourceShuffleCheck(
"random_shuffle.default_length",
[]string{"a", "c", "b", "e", "d"},
),
testAccResourceShuffleCheck(
"random_shuffle.shorter_length",
[]string{"a", "c", "b"},
),
testAccResourceShuffleCheck(
"random_shuffle.longer_length",
[]string{"a", "c", "b", "e", "d", "a", "e", "d", "c", "b", "a", "b"},
),
),
},
},
})
}
func testAccResourceShuffleCheck(id string, wants []string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[id]
if !ok {
return fmt.Errorf("Not found: %s", id)
}
if rs.Primary.ID == "" {
return fmt.Errorf("No ID is set")
}
attrs := rs.Primary.Attributes
gotLen := attrs["result.#"]
wantLen := strconv.Itoa(len(wants))
if gotLen != wantLen {
return fmt.Errorf("got %s result items; want %s", gotLen, wantLen)
}
for i, want := range wants {
key := fmt.Sprintf("result.%d", i)
if got := attrs[key]; got != want {
return fmt.Errorf("index %d is %q; want %q", i, got, want)
}
}
return nil
}
}
const testAccResourceShuffleConfig = `
resource "random_shuffle" "default_length" {
input = ["a", "b", "c", "d", "e"]
seed = "-"
}
resource "random_shuffle" "shorter_length" {
input = ["a", "b", "c", "d", "e"]
seed = "-"
result_count = 3
}
resource "random_shuffle" "longer_length" {
input = ["a", "b", "c", "d", "e"]
seed = "-"
result_count = 12
}
`

View File

@ -0,0 +1,24 @@
package random
import (
"hash/crc64"
"math/rand"
"time"
)
// NewRand returns a seeded random number generator, using a seed derived
// from the provided string.
//
// If the seed string is empty, the current time is used as a seed.
func NewRand(seed string) *rand.Rand {
var seedInt int64
if seed != "" {
crcTable := crc64.MakeTable(crc64.ISO)
seedInt = int64(crc64.Checksum([]byte(seed), crcTable))
} else {
seedInt = time.Now().Unix()
}
randSource := rand.NewSource(seedInt)
return rand.New(randSource)
}

View File

@ -36,6 +36,7 @@ import (
packetprovider "github.com/hashicorp/terraform/builtin/providers/packet" packetprovider "github.com/hashicorp/terraform/builtin/providers/packet"
postgresqlprovider "github.com/hashicorp/terraform/builtin/providers/postgresql" postgresqlprovider "github.com/hashicorp/terraform/builtin/providers/postgresql"
powerdnsprovider "github.com/hashicorp/terraform/builtin/providers/powerdns" powerdnsprovider "github.com/hashicorp/terraform/builtin/providers/powerdns"
randomprovider "github.com/hashicorp/terraform/builtin/providers/random"
rundeckprovider "github.com/hashicorp/terraform/builtin/providers/rundeck" rundeckprovider "github.com/hashicorp/terraform/builtin/providers/rundeck"
softlayerprovider "github.com/hashicorp/terraform/builtin/providers/softlayer" softlayerprovider "github.com/hashicorp/terraform/builtin/providers/softlayer"
statuscakeprovider "github.com/hashicorp/terraform/builtin/providers/statuscake" statuscakeprovider "github.com/hashicorp/terraform/builtin/providers/statuscake"
@ -87,6 +88,7 @@ var InternalProviders = map[string]plugin.ProviderFunc{
"packet": packetprovider.Provider, "packet": packetprovider.Provider,
"postgresql": postgresqlprovider.Provider, "postgresql": postgresqlprovider.Provider,
"powerdns": powerdnsprovider.Provider, "powerdns": powerdnsprovider.Provider,
"random": randomprovider.Provider,
"rundeck": rundeckprovider.Provider, "rundeck": rundeckprovider.Provider,
"softlayer": softlayerprovider.Provider, "softlayer": softlayerprovider.Provider,
"statuscake": statuscakeprovider.Provider, "statuscake": statuscakeprovider.Provider,

View File

@ -897,7 +897,23 @@ func (n *graphNodeExpandedResourceDestroy) EvalTree() EvalNode {
&EvalRequireState{ &EvalRequireState{
State: &state, State: &state,
}, },
&EvalApply{ // Make sure we handle data sources properly.
&EvalIf{
If: func(ctx EvalContext) (bool, error) {
if n.Resource.Mode == config.DataResourceMode {
return true, nil
}
return false, nil
},
Then: &EvalReadDataApply{
Info: info,
Diff: &diffApply,
Provider: &provider,
Output: &state,
},
Else: &EvalApply{
Info: info, Info: info,
State: &state, State: &state,
Diff: &diffApply, Diff: &diffApply,
@ -905,6 +921,7 @@ func (n *graphNodeExpandedResourceDestroy) EvalTree() EvalNode {
Output: &state, Output: &state,
Error: &err, Error: &err,
}, },
},
&EvalWriteState{ &EvalWriteState{
Name: n.stateId(), Name: n.stateId(),
ResourceType: n.Resource.Type, ResourceType: n.Resource.Type,

View File

@ -36,6 +36,7 @@ body.layout-openstack,
body.layout-packet, body.layout-packet,
body.layout-postgresql, body.layout-postgresql,
body.layout-powerdns, body.layout-powerdns,
body.layout-random,
body.layout-rundeck, body.layout-rundeck,
body.layout-statuscake, body.layout-statuscake,
body.layout-softlayer, body.layout-softlayer,

View File

@ -267,16 +267,19 @@ The following arguments are supported:
The `network` block supports: The `network` block supports:
* `uuid` - (Required unless `port` or `name` is provided) The network UUID to * `uuid` - (Required unless `port` or `name` is provided) The network UUID to
attach to the server. attach to the server. Changing this creates a new server.
* `name` - (Required unless `uuid` or `port` is provided) The human-readable * `name` - (Required unless `uuid` or `port` is provided) The human-readable
name of the network. name of the network. Changing this creates a new server.
* `port` - (Required unless `uuid` or `name` is provided) The port UUID of a * `port` - (Required unless `uuid` or `name` is provided) The port UUID of a
network to attach to the server. network to attach to the server. Changing this creates a new server.
* `fixed_ip_v4` - (Optional) Specifies a fixed IPv4 address to be used on this * `fixed_ip_v4` - (Optional) Specifies a fixed IPv4 address to be used on this
network. network. Changing this creates a new server.
* `fixed_ip_v6` - (Optional) Specifies a fixed IPv6 address to be used on this
network. Changing this creates a new server.
* `floating_ip` - (Optional) Specifies a floating IP address to be associated * `floating_ip` - (Optional) Specifies a floating IP address to be associated
with this network. Cannot be combined with a top-level floating IP. See with this network. Cannot be combined with a top-level floating IP. See
@ -301,6 +304,9 @@ The `block_device` block supports:
* `destination_type` - (Optional) The type that gets created. Possible values * `destination_type` - (Optional) The type that gets created. Possible values
are "volume" and "local". are "volume" and "local".
* `delete_on_termination` - (Optional) Delete the volume / block device upon
termination of the instance. Defaults to false.
The `volume` block supports: The `volume` block supports:
* `volume_id` - (Required) The UUID of the volume to attach. * `volume_id` - (Required) The UUID of the volume to attach.

View File

@ -43,6 +43,9 @@ The following arguments are supported:
belongs to the same tenant. Changing this creates a new floating IP (which belongs to the same tenant. Changing this creates a new floating IP (which
may or may not have a different address) may or may not have a different address)
* `fixed_ip` - Fixed IP of the port to associate with this floating IP. Required if
the port has multiple fixed IPs.
## Attributes Reference ## Attributes Reference
The following attributes are exported: The following attributes are exported:
@ -52,3 +55,4 @@ The following attributes are exported:
* `address` - The actual floating IP address itself. * `address` - The actual floating IP address itself.
* `port_id` - ID of associated port. * `port_id` - ID of associated port.
* `tenant_id` - the ID of the tenant in which to create the floating IP. * `tenant_id` - the ID of the tenant in which to create the floating IP.
* `fixed_ip` - The fixed IP which the floating IP maps to.

View File

@ -0,0 +1,73 @@
---
layout: "random"
page_title: "Provider: Random"
sidebar_current: "docs-random-index"
description: |-
The Random provider is used to generate randomness.
---
# Random Provider
The "random" provider allows the use of randomness within Terraform
configurations. This is a *logical provider*, which means that it works
entirely within Terraform's logic, and doesn't interact with any other
services.
Unconstrained randomness within a Terraform configuration would not be very
useful, since Terraform's goal is to converge on a fixed configuration by
applying a diff. Because of this, the "random" provider provides an idea of
*managed randomness*: it provides resources that generate random values during
their creation and then hold those values steady until the inputs are changed.
Even with these resources, it is advisable to keep the use of randomness within
Terraform configuration to a minimum, and retain it for special cases only;
Terraform works best when the configuration is well-defined, since its behavior
can then be more readily predicted.
Unless otherwise stated within the documentation of a specific resource, this
provider's results are **not** sufficiently random for cryptographic use.
For more information on the specific resources available, see the links in the
navigation bar. Read on for information on the general patterns that apply
to this provider's resources.
## Resource "Keepers"
As noted above, the random resources generate randomness only when they are
created; the results produced are stored in the Terraform state and re-used
until the inputs change, prompting the resource to be recreated.
The resources all provide a map argument called `keepers` that can be populated
with arbitrary key/value pairs that should be selected such that they remain
the same until new random values are desired.
For example:
```
resource "random_id" "server" {
keepers = {
# Generate a new id each time we switch to a new AMI id
ami_id = "${var.ami_id}"
}
byte_length = 8
}
resource "aws_instance" "server" {
tags = {
Name = "web-server ${random_id.server.hex}"
}
# Read the AMI id "through" the random_id resource to ensure that
# both will change together.
ami = "${random_id.server.keepers.ami_id}"
# ... (other aws_instance arguments) ...
}
```
Resource "keepers" are optional. The other arguments to each resource must
*also* remain constant in order to retain a random result.
To force a random result to be replaced, the `taint` command can be used to
produce a new result on the next run.

View File

@ -0,0 +1,69 @@
---
layout: "random"
page_title: "Random: random_id"
sidebar_current: "docs-random-resource-id"
description: |-
Generates a random identifier.
---
# random\_id
The resource `random_id` generates random numbers that are intended to be
used as unique identifiers for other resources.
Unlike other resources in the "random" provider, this resource *does* use a
cryptographic random number generator in order to minimize the chance of
collisions, making the results of this resource when a 32-byte identifier
is requested of equivalent uniqueness to a type-4 UUID.
This resource can be used in conjunction with resources that have,
the `create_before_destroy` lifecycle flag set, to avoid conflicts with
unique names during the brief period where both the old and new resources
exist concurrently.
## Example Usage
The following example shows how to generate a unique name for an AWS EC2
instance that changes each time a new AMI id is selected.
```
resource "random_id" "server" {
keepers = {
# Generate a new id each time we switch to a new AMI id
ami_id = "${var.ami_id}"
}
byte_length = 8
}
resource "aws_instance" "server" {
tags = {
Name = "web-server ${random_id.server.hex}"
}
# Read the AMI id "through" the random_id resource to ensure that
# both will change together.
ami = "${random_id.server.keepers.ami_id}"
# ... (other aws_instance arguments) ...
}
```
## Argument Reference
The following arguments are supported:
* `byte_length` - (Required) The number of random bytes to produce. The
minimum value is 1, which produces eight bits of randomness.
* `keepers` - (Optional) Arbitrary map of values that, when changed, will
trigger a new id to be generated. See
[the main provider documentation](../index.html) for more information.
## Attributes Reference
The following attributes are exported:
* `b64` - The generated id presented in base64, using the URL-friendly character set: case-sensitive letters, digits and the characters `_` and `-`.
* `hex` - The generated id presented in padded hexadecimal digits. This result will always be twice as long as the requested byte length.
* `decimal` - The generated id presented in non-padded decimal digits.

View File

@ -0,0 +1,59 @@
---
layout: "random"
page_title: "Random: random_shuffle"
sidebar_current: "docs-random-resource-shuffle"
description: |-
Produces a random permutation of a given list.
---
# random\_shuffle
The resource `random_shuffle` generates a random permutation of a list
of strings given as an argument.
## Example Usage
```
resource "random_shuffle" "az" {
input = ["us-west-1a", "us-west-1c", "us-west-1d", "us-west-1e"]
result_count = 2
}
resource "aws_elb" "example" {
# Place the ELB in any two of the given availability zones, selected
# at random.
availability_zones = ["${random_shuffle.az.result}"]
# ... and other aws_elb arguments ...
}
```
## Argument Reference
The following arguments are supported:
* `input` - (Required) The list of strings to shuffle.
* `result_count` - (Optional) The number of results to return. Defaults to
the number of items in the `input` list. If fewer items are requested,
some elements will be excluded from the result. If more items are requested,
items will be repeated in the result but not more frequently than the number
of items in the input list.
* `keepers` - (Optional) Arbitrary map of values that, when changed, will
trigger a new id to be generated. See
[the main provider documentation](../index.html) for more information.
* `seed` - (Optional) Arbitrary string with which to seed the random number
generator, in order to produce less-volatile permutations of the list.
**Important:** Even with an identical seed, it is not guaranteed that the
same permutation will be produced across different versions of Terraform.
This argument causes the result to be *less volatile*, but not fixed for
all time.
## Attributes Reference
The following attributes are exported:
* `result` - Random permutation of the list of strings given in `input`.

View File

@ -276,6 +276,10 @@
<li<%= sidebar_current("docs-providers-powerdns") %>> <li<%= sidebar_current("docs-providers-powerdns") %>>
<a href="/docs/providers/powerdns/index.html">PowerDNS</a> <a href="/docs/providers/powerdns/index.html">PowerDNS</a>
</li>
<li<%= sidebar_current("docs-providers-random") %>>
<a href="/docs/providers/random/index.html">Random</a>
</li> </li>
<li<%= sidebar_current("docs-providers-rundeck") %>> <li<%= sidebar_current("docs-providers-rundeck") %>>

View File

@ -0,0 +1,29 @@
<% wrap_layout :inner do %>
<% content_for :sidebar do %>
<div class="docs-sidebar hidden-print affix-top" role="complementary">
<ul class="nav docs-sidenav">
<li<%= sidebar_current("docs-home") %>>
<a href="/docs/providers/index.html">&laquo; Documentation Home</a>
</li>
<li<%= sidebar_current("docs-random-index") %>>
<a href="/docs/providers/random/index.html">Random Provider</a>
</li>
<li<%= sidebar_current(/^docs-random-resource/) %>>
<a href="#">Resources</a>
<ul class="nav nav-visible">
<li<%= sidebar_current("docs-random-resource-id") %>>
<a href="/docs/providers/random/r/id.html">random_id</a>
</li>
<li<%= sidebar_current("docs-random-resource-shuffle") %>>
<a href="/docs/providers/random/r/shuffle.html">random_shuffle</a>
</li>
</ul>
</li>
</ul>
</div>
<% end %>
<%= yield %>
<% end %>