diff --git a/builtin/providers/random/provider.go b/builtin/providers/random/provider.go index 271482381..309c50d6c 100644 --- a/builtin/providers/random/provider.go +++ b/builtin/providers/random/provider.go @@ -12,7 +12,7 @@ func Provider() terraform.ResourceProvider { ResourcesMap: map[string]*schema.Resource{ "random_id": resourceId(), - //"random_shuffle": resourceShuffle(), + "random_shuffle": resourceShuffle(), }, } } diff --git a/builtin/providers/random/resource_shuffle.go b/builtin/providers/random/resource_shuffle.go new file mode 100644 index 000000000..31108aeee --- /dev/null +++ b/builtin/providers/random/resource_shuffle.go @@ -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 +} diff --git a/builtin/providers/random/resource_shuffle_test.go b/builtin/providers/random/resource_shuffle_test.go new file mode 100644 index 000000000..5770e4105 --- /dev/null +++ b/builtin/providers/random/resource_shuffle_test.go @@ -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 +} +` diff --git a/builtin/providers/random/seed.go b/builtin/providers/random/seed.go new file mode 100644 index 000000000..7d16322fd --- /dev/null +++ b/builtin/providers/random/seed.go @@ -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) +}