core: name_prefix names now start with a timestamp
This means that two resources created by the same rule will get names which sort in the order they are created. The rest of the ID is still random base32 characters; we no longer set the bit values that denote UUIDv4. The length of the ID returned by PrefixedUniqueId is not changed by this commit; that way we don't break any resources where the underlying resource has a name length limit. Fixes #8143.
This commit is contained in:
parent
e6d1e77a9a
commit
4ad825fe08
|
@ -5,6 +5,7 @@ import (
|
||||||
"encoding/base32"
|
"encoding/base32"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
const UniqueIdPrefix = `terraform-`
|
const UniqueIdPrefix = `terraform-`
|
||||||
|
@ -16,31 +17,37 @@ func UniqueId() string {
|
||||||
|
|
||||||
// Helper for a resource to generate a unique identifier w/ given prefix
|
// Helper for a resource to generate a unique identifier w/ given prefix
|
||||||
//
|
//
|
||||||
// This uses a simple RFC 4122 v4 UUID with some basic cosmetic filters
|
// After the prefix, the ID consists of a timestamp and 12 random base32
|
||||||
// applied (base32, remove padding, downcase) to make visually distinguishing
|
// characters. The timestamp means that multiple IDs created with the same
|
||||||
// identifiers easier.
|
// prefix will sort in the order of their creation.
|
||||||
func PrefixedUniqueId(prefix string) string {
|
func PrefixedUniqueId(prefix string) string {
|
||||||
return fmt.Sprintf("%s%s", prefix,
|
// Be precise to the level nanoseconds, but remove the dot before the
|
||||||
strings.ToLower(
|
// nanosecond. We assume that the randomCharacters call takes at least a
|
||||||
|
// nanosecond, so that multiple calls to this function from the same goroutine
|
||||||
|
// will have distinct ordered timestamps.
|
||||||
|
timestamp := strings.Replace(
|
||||||
|
time.Now().UTC().Format("20060102150405.000000000"),
|
||||||
|
".",
|
||||||
|
"", 1)
|
||||||
|
// This uses 3 characters, so that the length of the unique ID is the same as
|
||||||
|
// it was before we added the timestamp prefix, which happened to be 23
|
||||||
|
// characters.
|
||||||
|
return fmt.Sprintf("%s%s%s", prefix, timestamp, randomCharacters(3))
|
||||||
|
}
|
||||||
|
|
||||||
|
func randomCharacters(n int) string {
|
||||||
|
// Base32 has 5 bits of information per character.
|
||||||
|
b := randomBytes(n * 8 / 5)
|
||||||
|
chars := strings.ToLower(
|
||||||
strings.Replace(
|
strings.Replace(
|
||||||
base32.StdEncoding.EncodeToString(uuidV4()),
|
base32.StdEncoding.EncodeToString(b),
|
||||||
"=", "", -1)))
|
"=", "", -1))
|
||||||
|
// Trim extra characters.
|
||||||
|
return chars[:n]
|
||||||
}
|
}
|
||||||
|
|
||||||
func uuidV4() []byte {
|
func randomBytes(n int) []byte {
|
||||||
var uuid [16]byte
|
b := make([]byte, n)
|
||||||
|
rand.Read(b)
|
||||||
// Set all the other bits to randomly (or pseudo-randomly) chosen
|
return b
|
||||||
// values.
|
|
||||||
rand.Read(uuid[:])
|
|
||||||
|
|
||||||
// Set the two most significant bits (bits 6 and 7) of the
|
|
||||||
// clock_seq_hi_and_reserved to zero and one, respectively.
|
|
||||||
uuid[8] = (uuid[8] | 0x80) & 0x8f
|
|
||||||
|
|
||||||
// Set the four most significant bits (bits 12 through 15) of the
|
|
||||||
// time_hi_and_version field to the 4-bit version number from Section 4.1.3.
|
|
||||||
uuid[6] = (uuid[6] | 0x40) & 0x4f
|
|
||||||
|
|
||||||
return uuid[:]
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,18 @@
|
||||||
package resource
|
package resource
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var allDigits = regexp.MustCompile(`^\d+$`)
|
||||||
|
var allBase32 = regexp.MustCompile(`^[a-z234567]+$`)
|
||||||
|
|
||||||
func TestUniqueId(t *testing.T) {
|
func TestUniqueId(t *testing.T) {
|
||||||
iterations := 10000
|
iterations := 10000
|
||||||
ids := make(map[string]struct{})
|
ids := make(map[string]struct{})
|
||||||
var id string
|
var id, lastId string
|
||||||
for i := 0; i < iterations; i++ {
|
for i := 0; i < iterations; i++ {
|
||||||
id = UniqueId()
|
id = UniqueId()
|
||||||
|
|
||||||
|
@ -20,6 +24,28 @@ func TestUniqueId(t *testing.T) {
|
||||||
t.Fatalf("Unique ID didn't have terraform- prefix! %s", id)
|
t.Fatalf("Unique ID didn't have terraform- prefix! %s", id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rest := strings.TrimPrefix(id, "terraform-")
|
||||||
|
|
||||||
|
if len(rest) != 26 {
|
||||||
|
t.Fatalf("Post-prefix part has wrong length! %s", rest)
|
||||||
|
}
|
||||||
|
|
||||||
|
timestamp := rest[:23]
|
||||||
|
random := rest[23:]
|
||||||
|
|
||||||
|
if !allDigits.MatchString(timestamp) {
|
||||||
|
t.Fatalf("Timestamp not all digits! %s", timestamp)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !allBase32.MatchString(random) {
|
||||||
|
t.Fatalf("Random part not all base32! %s", random)
|
||||||
|
}
|
||||||
|
|
||||||
|
if lastId != "" && lastId >= id {
|
||||||
|
t.Fatalf("IDs not ordered! %s vs %s", lastId, id)
|
||||||
|
}
|
||||||
|
|
||||||
ids[id] = struct{}{}
|
ids[id] = struct{}{}
|
||||||
|
lastId = id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue