Merge pull request #6672 from apparentlymart/random-provider
Logical Resources for Random Values
This commit is contained in:
commit
5a0f6565d3
|
@ -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()
|
||||
},
|
||||
})
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
package main
|
|
@ -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
|
||||
}
|
|
@ -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) {
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
`
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
`
|
|
@ -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)
|
||||
}
|
|
@ -36,6 +36,7 @@ import (
|
|||
packetprovider "github.com/hashicorp/terraform/builtin/providers/packet"
|
||||
postgresqlprovider "github.com/hashicorp/terraform/builtin/providers/postgresql"
|
||||
powerdnsprovider "github.com/hashicorp/terraform/builtin/providers/powerdns"
|
||||
randomprovider "github.com/hashicorp/terraform/builtin/providers/random"
|
||||
rundeckprovider "github.com/hashicorp/terraform/builtin/providers/rundeck"
|
||||
softlayerprovider "github.com/hashicorp/terraform/builtin/providers/softlayer"
|
||||
statuscakeprovider "github.com/hashicorp/terraform/builtin/providers/statuscake"
|
||||
|
@ -87,6 +88,7 @@ var InternalProviders = map[string]plugin.ProviderFunc{
|
|||
"packet": packetprovider.Provider,
|
||||
"postgresql": postgresqlprovider.Provider,
|
||||
"powerdns": powerdnsprovider.Provider,
|
||||
"random": randomprovider.Provider,
|
||||
"rundeck": rundeckprovider.Provider,
|
||||
"softlayer": softlayerprovider.Provider,
|
||||
"statuscake": statuscakeprovider.Provider,
|
||||
|
|
|
@ -36,6 +36,7 @@ body.layout-openstack,
|
|||
body.layout-packet,
|
||||
body.layout-postgresql,
|
||||
body.layout-powerdns,
|
||||
body.layout-random,
|
||||
body.layout-rundeck,
|
||||
body.layout-statuscake,
|
||||
body.layout-softlayer,
|
||||
|
|
|
@ -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.
|
|
@ -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.
|
|
@ -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`.
|
||||
|
|
@ -276,6 +276,10 @@
|
|||
|
||||
<li<%= sidebar_current("docs-providers-powerdns") %>>
|
||||
<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<%= sidebar_current("docs-providers-rundeck") %>>
|
||||
|
|
|
@ -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">« 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 %>
|
Loading…
Reference in New Issue