provider/random: random_shuffle resource

random_shuffle takes a list of strings and returns a new list with the
same items in a random permutation.

Optionally allows the result list to be a different length than the
input list. A shorter result than input results in some items being
excluded. A longer result than input results in some items being
repeated, but never more often than the number of input items.
This commit is contained in:
Martin Atkins 2016-05-14 16:48:45 -07:00
parent b1de477691
commit eec6c88fd2
4 changed files with 198 additions and 1 deletions

View File

@ -12,7 +12,7 @@ func Provider() terraform.ResourceProvider {
ResourcesMap: map[string]*schema.Resource{
"random_id": resourceId(),
//"random_shuffle": resourceShuffle(),
"random_shuffle": resourceShuffle(),
},
}
}

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)
}