mirror of https://github.com/pulumi/pulumi.git
695 lines
26 KiB
Go
695 lines
26 KiB
Go
//nolint:lll
|
|
package testing
|
|
|
|
import (
|
|
"math/rand"
|
|
"sort"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
"pgregory.net/rapid"
|
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/tokens"
|
|
)
|
|
|
|
// A StackContext provides context for generating URNs and references to resources.
|
|
type StackContext struct {
|
|
projectName string
|
|
stackName string
|
|
resources []*resource.State
|
|
}
|
|
|
|
// NewStackContext creates a new stack context with the given project name, stack name, and list of resources.
|
|
func NewStackContext(projectName, stackName string, resources ...*resource.State) *StackContext {
|
|
ctx := &StackContext{projectName: projectName, stackName: stackName}
|
|
|
|
if len(resources) == 0 {
|
|
stack := &resource.State{
|
|
Type: "pulumi:pulumi:Stack",
|
|
URN: resource.NewURN(tokens.QName(stackName), tokens.PackageName(projectName), "", "pulumi:pulumi:Stack", tokens.QName(stackName)),
|
|
}
|
|
resources = []*resource.State{stack}
|
|
}
|
|
|
|
return ctx.Append(resources...)
|
|
}
|
|
|
|
// ProjectName returns the context's project name.
|
|
func (ctx *StackContext) ProjectName() string {
|
|
return ctx.projectName
|
|
}
|
|
|
|
// StackName returns the context's stack name.
|
|
func (ctx *StackContext) StackName() string {
|
|
return ctx.stackName
|
|
}
|
|
|
|
// Resources returns the context's resources.
|
|
func (ctx *StackContext) Resources() []*resource.State {
|
|
return ctx.resources
|
|
}
|
|
|
|
// Append creates a new context that contains the current context's resources and the given list of resources.
|
|
func (ctx *StackContext) Append(r ...*resource.State) *StackContext {
|
|
rs := make([]*resource.State, len(ctx.resources)+len(r))
|
|
copy(rs, ctx.resources)
|
|
copy(rs[len(ctx.resources):], r)
|
|
return &StackContext{ctx.projectName, ctx.stackName, rs}
|
|
}
|
|
|
|
// URNGenerator generates URNs that are valid within the context (i.e. the project name and stack name portions of the
|
|
// generated URNs will always be taken from the context).
|
|
func (ctx *StackContext) URNGenerator() *rapid.Generator {
|
|
return urnGenerator(ctx, "", "")
|
|
}
|
|
|
|
// URNSampler samples URNs from the stack's resources.
|
|
func (ctx *StackContext) URNSampler() *rapid.Generator {
|
|
return rapid.Custom(func(t *rapid.T) resource.URN {
|
|
return rapid.SampledFrom(ctx.Resources()).Draw(t, "referenced resource").(*resource.State).URN
|
|
})
|
|
}
|
|
|
|
// ResourceReferenceGenerator generates resource.ResourceReference values. The referenced resource is
|
|
func (ctx *StackContext) ResourceReferenceGenerator() *rapid.Generator {
|
|
if len(ctx.Resources()) == 0 {
|
|
panic("cannot generate resource references: stack context has no resources")
|
|
}
|
|
return resourceReferenceGenerator(ctx)
|
|
}
|
|
|
|
// ResourceReferencePropertyGenerator generates resource reference resource.PropertyValues.
|
|
func (ctx *StackContext) ResourceReferencePropertyGenerator() *rapid.Generator {
|
|
return resourceReferencePropertyGenerator(ctx)
|
|
}
|
|
|
|
// ArrayPropertyGenerator generates array resource.PropertyValues. The maxDepth parameter controls the maximum
|
|
// depth of the elements of the array.
|
|
func (ctx *StackContext) ArrayPropertyGenerator(maxDepth int) *rapid.Generator {
|
|
return arrayPropertyGenerator(ctx, maxDepth)
|
|
}
|
|
|
|
// PropertyMapGenerator generates resource.PropertyMap values. The maxDepth parameter controls the maximum
|
|
// depth of the elements of the map.
|
|
func (ctx *StackContext) PropertyMapGenerator(maxDepth int) *rapid.Generator {
|
|
return propertyMapGenerator(ctx, maxDepth)
|
|
}
|
|
|
|
// ObjectPropertyGenerator generates object resource.PropertyValues. The maxDepth parameter controls the maximum
|
|
// depth of the elements of the object.
|
|
func (ctx *StackContext) ObjectPropertyGenerator(maxDepth int) *rapid.Generator {
|
|
return objectPropertyGenerator(ctx, maxDepth)
|
|
}
|
|
|
|
// OutputPropertyGenerator generates output resource.PropertyValues. The maxDepth parameter controls the maximum
|
|
// depth of the resolved value of the output, if any. The output's dependencies will only refer to resources in
|
|
// the context.
|
|
func (ctx *StackContext) OutputPropertyGenerator(maxDepth int) *rapid.Generator {
|
|
return outputPropertyGenerator(ctx, maxDepth)
|
|
}
|
|
|
|
// SecretPropertyGenerator generates secret resource.PropertyValues. The maxDepth parameter controls the maximum
|
|
// depth of the plaintext value of the secret, if any.
|
|
func (ctx *StackContext) SecretPropertyGenerator(maxDepth int) *rapid.Generator {
|
|
return secretPropertyGenerator(ctx, maxDepth)
|
|
}
|
|
|
|
// PropertyValueGenerator generates arbitrary resource.PropertyValues. The maxDepth parameter controls the maximum
|
|
// number of times the generator may recur.
|
|
func (ctx *StackContext) PropertyValueGenerator(maxDepth int) *rapid.Generator {
|
|
return propertyValueGenerator(ctx, maxDepth)
|
|
}
|
|
|
|
// ResourceStateGenerator generates arbitrary *resource.State values. The maxDepth parameter controls the maximum
|
|
// number of times the generator may recur when generating input and output properties.
|
|
//
|
|
// This generator can be used iteratively in order to generate a list of resources that forms a valid topological sort
|
|
// of a dependency graph:
|
|
//
|
|
// context := NewStackContext("test", "test")
|
|
//
|
|
// // seed the context with a provider
|
|
// context = context.Append(context.ResourceStateGenerator(true, 6).Draw(t, "provider").(*resource.State))
|
|
//
|
|
// // then generate some resources!
|
|
// count := rapid.IntRange(1, 16).Draw(t, "resource count").(int)
|
|
// for i := 0; i < count; i++ {
|
|
// isProvider := rapid.Bool().Draw(t, "is provider").(bool)
|
|
// context = context.Append(context.ResourceStateGenerator(isProvider, 6).Draw(t, fmt.Sprintf("resource %v", i)).(*resource.State))
|
|
// }
|
|
//
|
|
func (ctx *StackContext) ResourceStateGenerator(isProvider bool, maxDepth int) *rapid.Generator {
|
|
return resourceStateGenerator(ctx, isProvider, maxDepth)
|
|
}
|
|
|
|
// TypeGenerator generates legal tokens.Type values.
|
|
func TypeGenerator() *rapid.Generator {
|
|
return typeGenerator("")
|
|
}
|
|
|
|
func typeGenerator(pkg tokens.Package) *rapid.Generator {
|
|
if pkg == "" {
|
|
return rapid.Custom(func(t *rapid.T) tokens.Type {
|
|
return tokens.Type(rapid.StringMatching(`^[a-zA-Z][-a-zA-Z0-9_]*:([^0-9][a-zA-Z0-9._/]*)?:[^0-9][a-zA-Z0-9._/]*$`).Draw(t, "type token").(string))
|
|
})
|
|
}
|
|
|
|
return rapid.Custom(func(t *rapid.T) tokens.Type {
|
|
return tokens.Type(string(pkg) + ":" + rapid.StringMatching(`^([^0-9][a-zA-Z0-9._/]*)?:[^0-9][a-zA-Z0-9._/]*$`).Draw(t, "type token").(string))
|
|
})
|
|
}
|
|
|
|
// URNGenerator generates legal resource.URN values.
|
|
func URNGenerator() *rapid.Generator {
|
|
return urnGenerator(nil, "", "")
|
|
}
|
|
|
|
func urnGenerator(ctx *StackContext, parentType, resourceType tokens.Type) *rapid.Generator {
|
|
var stackNameGenerator, projectNameGenerator *rapid.Generator
|
|
if ctx == nil {
|
|
stackNameGenerator = rapid.StringMatching(`^[^:]((:[^:])[^:]*)*$`)
|
|
projectNameGenerator = rapid.StringMatching(`^[^:]((:[^:])[^:]*)*$`)
|
|
} else {
|
|
stackNameGenerator = rapid.Just(ctx.StackName())
|
|
projectNameGenerator = rapid.Just(ctx.ProjectName())
|
|
}
|
|
|
|
return rapid.Custom(func(t *rapid.T) resource.URN {
|
|
stackName := tokens.QName(stackNameGenerator.Draw(t, "stack name").(string))
|
|
projectName := tokens.PackageName(projectNameGenerator.Draw(t, "project name").(string))
|
|
if parentType == "" {
|
|
parentType = TypeGenerator().Draw(t, "parent type").(tokens.Type)
|
|
}
|
|
if resourceType == "" {
|
|
resourceType = TypeGenerator().Draw(t, "resource type").(tokens.Type)
|
|
}
|
|
resourceName := tokens.QName(rapid.StringMatching(`^[^:]((:[^:])[^:]*)*$`).Draw(t, "resource name").(string))
|
|
return resource.NewURN(stackName, projectName, parentType, resourceType, resourceName)
|
|
})
|
|
}
|
|
|
|
// IDGenerator generates legal resource.ID values.
|
|
func IDGenerator() *rapid.Generator {
|
|
return rapid.Custom(func(t *rapid.T) resource.ID {
|
|
return resource.ID(rapid.StringMatching(`..*`).Draw(t, "ids").(string))
|
|
})
|
|
}
|
|
|
|
// SemverStringGenerator generates legal semver strings.
|
|
func SemverStringGenerator() *rapid.Generator {
|
|
return rapid.StringMatching(`^v?(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>0|[1-9]\d*)(?:-(?P<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`)
|
|
}
|
|
|
|
// UnknownPropertyGenerator generates the unknown resource.PropertyValue.
|
|
func UnknownPropertyGenerator() *rapid.Generator {
|
|
return rapid.Custom(func(t *rapid.T) resource.PropertyValue {
|
|
return rapid.Just(resource.MakeComputed(resource.NewStringProperty(""))).Draw(t, "unknowns").(resource.PropertyValue)
|
|
})
|
|
}
|
|
|
|
// NullPropertyGenerator generates the null resource.PropertyValue.
|
|
func NullPropertyGenerator() *rapid.Generator {
|
|
return rapid.Custom(func(t *rapid.T) resource.PropertyValue {
|
|
return rapid.Just(resource.NewNullProperty()).Draw(t, "nulls").(resource.PropertyValue)
|
|
})
|
|
}
|
|
|
|
// BoolPropertyGenerator generates boolean resource.PropertyValues.
|
|
func BoolPropertyGenerator() *rapid.Generator {
|
|
return rapid.Custom(func(t *rapid.T) resource.PropertyValue {
|
|
return resource.NewBoolProperty(rapid.Bool().Draw(t, "booleans").(bool))
|
|
})
|
|
}
|
|
|
|
// NumberPropertyGenerator generates numeric resource.PropertyValues.
|
|
func NumberPropertyGenerator() *rapid.Generator {
|
|
return rapid.Custom(func(t *rapid.T) resource.PropertyValue {
|
|
return resource.NewNumberProperty(rapid.Float64().Draw(t, "numbers").(float64))
|
|
})
|
|
}
|
|
|
|
// StringPropertyGenerator generates string resource.PropertyValues.
|
|
func StringPropertyGenerator() *rapid.Generator {
|
|
return rapid.Custom(func(t *rapid.T) resource.PropertyValue {
|
|
return resource.NewStringProperty(rapid.String().Draw(t, "strings").(string))
|
|
})
|
|
}
|
|
|
|
// TextAssetGenerator generates textual *resource.Asset values.
|
|
func TextAssetGenerator() *rapid.Generator {
|
|
return rapid.Custom(func(t *rapid.T) *resource.Asset {
|
|
asset, err := resource.NewTextAsset(rapid.String().Draw(t, "text asset contents").(string))
|
|
require.NoError(t, err)
|
|
return asset
|
|
})
|
|
}
|
|
|
|
// AssetGenerator generates *resource.Asset values.
|
|
func AssetGenerator() *rapid.Generator {
|
|
return TextAssetGenerator()
|
|
}
|
|
|
|
// AssetPropertyGenerator generates asset resource.PropertyValues.
|
|
func AssetPropertyGenerator() *rapid.Generator {
|
|
return rapid.Custom(func(t *rapid.T) resource.PropertyValue {
|
|
return resource.NewAssetProperty(AssetGenerator().Draw(t, "assets").(*resource.Asset))
|
|
})
|
|
}
|
|
|
|
// LiteralArchiveGenerator generates *resource.Archive values with literal archive contents.
|
|
func LiteralArchiveGenerator(maxDepth int) *rapid.Generator {
|
|
return rapid.Custom(func(t *rapid.T) *resource.Archive {
|
|
var contentsGenerator *rapid.Generator
|
|
if maxDepth > 0 {
|
|
contentsGenerator = rapid.MapOfN(rapid.StringMatching(`^(/[^[:cntrl:]/]+)*/?[^[:cntrl:]/]+$`), rapid.OneOf(AssetGenerator(), ArchiveGenerator(maxDepth-1)), 0, 16)
|
|
} else {
|
|
contentsGenerator = rapid.Just(map[string]interface{}{})
|
|
}
|
|
archive, err := resource.NewAssetArchive(contentsGenerator.Draw(t, "literal archive contents").(map[string]interface{}))
|
|
require.NoError(t, err)
|
|
return archive
|
|
})
|
|
}
|
|
|
|
// ArchiveGenerator generates *resource.Archive values.
|
|
func ArchiveGenerator(maxDepth int) *rapid.Generator {
|
|
return LiteralArchiveGenerator(maxDepth)
|
|
}
|
|
|
|
// ArchivePropertyGenerator generates archive resource.PropertyValues.
|
|
func ArchivePropertyGenerator(maxDepth int) *rapid.Generator {
|
|
return rapid.Custom(func(t *rapid.T) resource.PropertyValue {
|
|
return resource.NewArchiveProperty(ArchiveGenerator(maxDepth).Draw(t, "archives").(*resource.Archive))
|
|
})
|
|
}
|
|
|
|
// ResourceReferenceGenerator generates resource.ResourceReference values.
|
|
func ResourceReferenceGenerator() *rapid.Generator {
|
|
return resourceReferenceGenerator(nil)
|
|
}
|
|
|
|
func resourceReferenceGenerator(ctx *StackContext) *rapid.Generator {
|
|
var resourceGenerator *rapid.Generator
|
|
if ctx == nil {
|
|
resourceGenerator = rapid.Custom(func(t *rapid.T) *resource.State {
|
|
id := resource.ID("")
|
|
custom := !rapid.Bool().Draw(t, "component").(bool)
|
|
if custom {
|
|
id = IDGenerator().Draw(t, "resource ID").(resource.ID)
|
|
}
|
|
|
|
return &resource.State{
|
|
URN: URNGenerator().Draw(t, "resource URN").(resource.URN),
|
|
Custom: custom,
|
|
ID: id,
|
|
}
|
|
})
|
|
} else {
|
|
resourceGenerator = rapid.SampledFrom(ctx.Resources())
|
|
}
|
|
|
|
return rapid.Custom(func(t *rapid.T) resource.ResourceReference {
|
|
r := resourceGenerator.Draw(t, "referenced resource").(*resource.State)
|
|
|
|
// Only pull the resource's ID if it is a custom resource. Component resources do not have IDs.
|
|
var id resource.PropertyValue
|
|
if r.Custom {
|
|
id = rapid.OneOf(UnknownPropertyGenerator(), rapid.Just(resource.NewStringProperty(string(r.ID)))).Draw(t, "referenced ID").(resource.PropertyValue)
|
|
}
|
|
|
|
return resource.ResourceReference{
|
|
URN: r.URN,
|
|
ID: id,
|
|
PackageVersion: SemverStringGenerator().Draw(t, "package version").(string),
|
|
}
|
|
})
|
|
}
|
|
|
|
// ResourceReferencePropertyGenerator generates resource reference resource.PropertyValues.
|
|
func ResourceReferencePropertyGenerator() *rapid.Generator {
|
|
return resourceReferencePropertyGenerator(nil)
|
|
}
|
|
|
|
func resourceReferencePropertyGenerator(ctx *StackContext) *rapid.Generator {
|
|
return rapid.Custom(func(t *rapid.T) resource.PropertyValue {
|
|
return resource.NewResourceReferenceProperty(resourceReferenceGenerator(ctx).Draw(t, "resource reference").(resource.ResourceReference))
|
|
})
|
|
}
|
|
|
|
// ArrayPropertyGenerator generates array resource.PropertyValues. The maxDepth parameter controls the maximum
|
|
// depth of the elements of the array.
|
|
func ArrayPropertyGenerator(maxDepth int) *rapid.Generator {
|
|
return arrayPropertyGenerator(nil, maxDepth)
|
|
}
|
|
|
|
func arrayPropertyGenerator(ctx *StackContext, maxDepth int) *rapid.Generator {
|
|
return rapid.Custom(func(t *rapid.T) resource.PropertyValue {
|
|
return resource.NewArrayProperty(rapid.SliceOfN(propertyValueGenerator(ctx, maxDepth-1), 0, 32).Draw(t, "array elements").([]resource.PropertyValue))
|
|
})
|
|
}
|
|
|
|
// PropertyKeyGenerator generates legal resource.PropertyKey values.
|
|
func PropertyKeyGenerator() *rapid.Generator {
|
|
return rapid.Custom(func(t *rapid.T) resource.PropertyKey {
|
|
return resource.PropertyKey(rapid.String().Draw(t, "property key").(string))
|
|
})
|
|
}
|
|
|
|
// PropertyMapGenerator generates resource.PropertyMap values. The maxDepth parameter controls the maximum
|
|
// depth of the elements of the map.
|
|
func PropertyMapGenerator(maxDepth int) *rapid.Generator {
|
|
return propertyMapGenerator(nil, maxDepth)
|
|
}
|
|
|
|
func propertyMapGenerator(ctx *StackContext, maxDepth int) *rapid.Generator {
|
|
return rapid.Custom(func(t *rapid.T) resource.PropertyMap {
|
|
return resource.PropertyMap(rapid.MapOfN(PropertyKeyGenerator(), propertyValueGenerator(ctx, maxDepth-1), 0, 32).Draw(t, "property map").(map[resource.PropertyKey]resource.PropertyValue))
|
|
})
|
|
}
|
|
|
|
// ObjectPropertyGenerator generates object resource.PropertyValues. The maxDepth parameter controls the maximum
|
|
// depth of the elements of the object.
|
|
func ObjectPropertyGenerator(maxDepth int) *rapid.Generator {
|
|
return objectPropertyGenerator(nil, maxDepth)
|
|
}
|
|
|
|
func objectPropertyGenerator(ctx *StackContext, maxDepth int) *rapid.Generator {
|
|
return rapid.Custom(func(t *rapid.T) resource.PropertyValue {
|
|
return resource.NewObjectProperty(propertyMapGenerator(ctx, maxDepth).Draw(t, "object contents").(resource.PropertyMap))
|
|
})
|
|
}
|
|
|
|
// OutputPropertyGenerator generates output resource.PropertyValues. The maxDepth parameter controls the maximum
|
|
// depth of the resolved value of the output, if any. If a StackContext, the output's dependencies will only refer to
|
|
// resources in the context.
|
|
func OutputPropertyGenerator(maxDepth int) *rapid.Generator {
|
|
return outputPropertyGenerator(nil, maxDepth)
|
|
}
|
|
|
|
func outputPropertyGenerator(ctx *StackContext, maxDepth int) *rapid.Generator {
|
|
var urnGenerator *rapid.Generator
|
|
var dependenciesUpperBound int
|
|
if ctx == nil {
|
|
urnGenerator, dependenciesUpperBound = URNGenerator(), 32
|
|
} else {
|
|
urnGenerator = ctx.URNSampler()
|
|
dependenciesUpperBound = len(ctx.Resources())
|
|
if dependenciesUpperBound > 32 {
|
|
dependenciesUpperBound = 32
|
|
}
|
|
}
|
|
|
|
return rapid.Custom(func(t *rapid.T) resource.PropertyValue {
|
|
var element resource.PropertyValue
|
|
|
|
known := rapid.Bool().Draw(t, "known").(bool)
|
|
if known {
|
|
element = propertyValueGenerator(ctx, maxDepth-1).Draw(t, "output element").(resource.PropertyValue)
|
|
}
|
|
|
|
return resource.NewOutputProperty(resource.Output{
|
|
Element: element,
|
|
Known: known,
|
|
Secret: rapid.Bool().Draw(t, "secret").(bool),
|
|
Dependencies: rapid.SliceOfN(urnGenerator, 0, dependenciesUpperBound).Draw(t, "dependencies").([]resource.URN),
|
|
})
|
|
})
|
|
}
|
|
|
|
// SecretPropertyGenerator generates secret resource.PropertyValues. The maxDepth parameter controls the maximum
|
|
// depth of the plaintext value of the secret, if any.
|
|
func SecretPropertyGenerator(maxDepth int) *rapid.Generator {
|
|
return secretPropertyGenerator(nil, maxDepth)
|
|
}
|
|
|
|
func secretPropertyGenerator(ctx *StackContext, maxDepth int) *rapid.Generator {
|
|
return rapid.Custom(func(t *rapid.T) resource.PropertyValue {
|
|
return resource.NewSecretProperty(&resource.Secret{
|
|
Element: propertyValueGenerator(ctx, maxDepth-1).Draw(t, "secret element").(resource.PropertyValue),
|
|
})
|
|
})
|
|
}
|
|
|
|
// PropertyValueGenerator generates arbitrary resource.PropertyValues. The maxDepth parameter controls the maximum
|
|
// number of times the generator may recur.
|
|
func PropertyValueGenerator(maxDepth int) *rapid.Generator {
|
|
return propertyValueGenerator(nil, maxDepth)
|
|
}
|
|
|
|
func propertyValueGenerator(ctx *StackContext, maxDepth int) *rapid.Generator {
|
|
choices := []*rapid.Generator{
|
|
UnknownPropertyGenerator(),
|
|
NullPropertyGenerator(),
|
|
BoolPropertyGenerator(),
|
|
NumberPropertyGenerator(),
|
|
StringPropertyGenerator(),
|
|
AssetPropertyGenerator(),
|
|
}
|
|
|
|
if ctx == nil || len(ctx.Resources()) > 0 {
|
|
choices = append(choices, resourceReferencePropertyGenerator(ctx))
|
|
}
|
|
|
|
if maxDepth > 0 {
|
|
choices = append(choices,
|
|
ArchivePropertyGenerator(maxDepth),
|
|
arrayPropertyGenerator(ctx, maxDepth),
|
|
objectPropertyGenerator(ctx, maxDepth),
|
|
outputPropertyGenerator(ctx, maxDepth),
|
|
secretPropertyGenerator(ctx, maxDepth))
|
|
}
|
|
return rapid.OneOf(choices...)
|
|
}
|
|
|
|
// ResourceStateGenerator generates arbitrary *resource.State values. The maxDepth parameter controls the maximum
|
|
// number of times the generator may recur when generating input and output properties.
|
|
func ResourceStateGenerator(maxDepth int) *rapid.Generator {
|
|
return rapid.Custom(func(t *rapid.T) *resource.State {
|
|
return resourceStateGenerator(nil, rapid.Bool().Draw(t, "is provider").(bool), maxDepth).Draw(t, "resource state").(*resource.State)
|
|
})
|
|
}
|
|
|
|
func getValueDependencies(deps map[resource.URN]bool, v resource.PropertyValue) {
|
|
switch {
|
|
case v.IsArray():
|
|
for _, v := range v.ArrayValue() {
|
|
getValueDependencies(deps, v)
|
|
}
|
|
case v.IsObject():
|
|
for _, v := range v.ObjectValue() {
|
|
getValueDependencies(deps, v)
|
|
}
|
|
case v.IsOutput():
|
|
output := v.OutputValue()
|
|
for _, d := range output.Dependencies {
|
|
deps[d] = true
|
|
}
|
|
getValueDependencies(deps, output.Element)
|
|
case v.IsSecret():
|
|
getValueDependencies(deps, v.SecretValue().Element)
|
|
}
|
|
}
|
|
|
|
func sortedDeps(deps map[resource.URN]bool) []resource.URN {
|
|
sorted := make([]resource.URN, 0, len(deps))
|
|
for urn := range deps {
|
|
sorted = append(sorted, urn)
|
|
}
|
|
sort.Slice(sorted, func(i, j int) bool { return sorted[i] < sorted[j] })
|
|
return sorted
|
|
}
|
|
|
|
func unionDeps(dest, src map[resource.URN]bool) {
|
|
for k := range src {
|
|
dest[k] = true
|
|
}
|
|
}
|
|
|
|
func removeOutputDeps(v resource.PropertyValue) {
|
|
switch {
|
|
case v.IsArray():
|
|
for _, v := range v.ArrayValue() {
|
|
removeOutputDeps(v)
|
|
}
|
|
case v.IsObject():
|
|
for _, v := range v.ObjectValue() {
|
|
removeOutputDeps(v)
|
|
}
|
|
case v.IsOutput():
|
|
output := v.OutputValue()
|
|
output.Dependencies = nil
|
|
v.V = output
|
|
case v.IsSecret():
|
|
removeOutputDeps(v.SecretValue().Element)
|
|
}
|
|
}
|
|
|
|
func resourceStateGenerator(ctx *StackContext, isProvider bool, maxDepth int) *rapid.Generator {
|
|
// otherURNGenerator either generates arbitrary URNs (if no StackContext is available) or samples a URN from the
|
|
// context's list of resources.
|
|
var otherURNGenerator *rapid.Generator
|
|
if ctx == nil {
|
|
otherURNGenerator = URNGenerator()
|
|
} else {
|
|
otherURNGenerator = ctx.URNSampler()
|
|
}
|
|
|
|
var dependenciesUpperBound int
|
|
if ctx == nil {
|
|
dependenciesUpperBound = 32
|
|
} else {
|
|
dependenciesUpperBound = len(ctx.Resources())
|
|
if dependenciesUpperBound > 32 {
|
|
dependenciesUpperBound = 32
|
|
}
|
|
}
|
|
|
|
// providerGenerator generates a provider resource for the purposes of determining the generated resource's package
|
|
// and provider reference. If no context is provided, a provider resource is synthesized. Otherwise, it is sampled
|
|
// from the context's resources.
|
|
var providerGenerator *rapid.Generator
|
|
if !isProvider {
|
|
if ctx == nil {
|
|
providerGenerator = rapid.Custom(func(t *rapid.T) *resource.State {
|
|
pkg := rapid.StringMatching(`[^0-9][a-zA-Z0-9._/]*`).Draw(t, "provider package").(string)
|
|
urn := urnGenerator(nil, "", tokens.Type("pulumi:providers:"+pkg)).Draw(t, "provider URN").(resource.URN)
|
|
return &resource.State{
|
|
Type: urn.Type(),
|
|
URN: urn,
|
|
ID: IDGenerator().Draw(t, "provider ID").(resource.ID),
|
|
}
|
|
})
|
|
} else {
|
|
providers := make([]*resource.State, 0, len(ctx.Resources()))
|
|
for _, r := range ctx.Resources() {
|
|
if r.Type.Module() == "pulumi:providers" {
|
|
providers = append(providers, r)
|
|
}
|
|
}
|
|
if len(providers) == 0 {
|
|
providerGenerator = rapid.SampledFrom([]*resource.State{nil})
|
|
} else {
|
|
providerGenerator = rapid.SampledFrom(providers)
|
|
}
|
|
}
|
|
}
|
|
|
|
return rapid.Custom(func(t *rapid.T) *resource.State {
|
|
custom := isProvider || rapid.Bool().Draw(t, "custom").(bool)
|
|
|
|
var providerResource *resource.State
|
|
var typ tokens.Type
|
|
switch {
|
|
case isProvider:
|
|
typ = "pulumi:providers:" + tokens.Type(rapid.StringMatching(`[^0-9:][a-zA-Z0-9._/]*`).Draw(t, "provider package").(string))
|
|
case custom:
|
|
providerResource = providerGenerator.Draw(t, "provider resource").(*resource.State)
|
|
if providerResource == nil {
|
|
panic("stack has no providers")
|
|
}
|
|
typ = typeGenerator(tokens.Package(providerResource.Type.Name())).Draw(t, "type").(tokens.Type)
|
|
default:
|
|
typ = TypeGenerator().Draw(t, "type").(tokens.Type)
|
|
}
|
|
|
|
parent := otherURNGenerator.Draw(t, "parent").(resource.URN)
|
|
|
|
// NOTE: this generator does not ensure that the generated URN is unique within the stack context, if one
|
|
// exists. There is a slim chance that this could generate duplicate resources within a stack, which is not
|
|
// valid.
|
|
urn := urnGenerator(ctx, parent.QualifiedType(), typ).Draw(t, "URN").(resource.URN)
|
|
|
|
del := rapid.Bool().Draw(t, "delete").(bool)
|
|
protect := rapid.Bool().Draw(t, "protect").(bool)
|
|
external := rapid.Bool().Draw(t, "external").(bool)
|
|
pendingReplacement := rapid.Bool().Draw(t, "pending replacement").(bool)
|
|
|
|
id := resource.ID("")
|
|
importID := resource.ID("")
|
|
if custom {
|
|
id = IDGenerator().Draw(t, "id").(resource.ID)
|
|
|
|
// A resource's import ID may be the empty string if the resource was not imported. If the resource was
|
|
// imported, the import ID is non-empty, but may or may not be the same as the actual ID, even if both IDs
|
|
// refer to the same resource.
|
|
if !isProvider {
|
|
importID = rapid.OneOf(rapid.Just(resource.ID("")), rapid.Just(id), IDGenerator()).Draw(t, "import ID").(resource.ID)
|
|
}
|
|
}
|
|
|
|
dependencySet := rapid.MapOfN(otherURNGenerator, rapid.SampledFrom([]bool{true}), 0, dependenciesUpperBound).Draw(t, "dependencies").(map[resource.URN]bool)
|
|
|
|
// Draw inputs and calculate property dependencies from any nested output values therein.
|
|
inputs := propertyMapGenerator(ctx, maxDepth).Draw(t, "inputs").(resource.PropertyMap)
|
|
propertyDependencies := map[resource.PropertyKey][]resource.URN{}
|
|
for k, v := range inputs {
|
|
propertyDependencySet := map[resource.URN]bool{}
|
|
getValueDependencies(propertyDependencySet, v)
|
|
unionDeps(dependencySet, propertyDependencySet)
|
|
if len(propertyDependencySet) > 0 {
|
|
propertyDependencies[k] = sortedDeps(propertyDependencySet)
|
|
}
|
|
}
|
|
|
|
// Draw outputs. If this is a custom resource, remove nested deps and sample secret outputs.
|
|
outputs := propertyMapGenerator(ctx, maxDepth).Draw(t, "outputs").(resource.PropertyMap)
|
|
var additionalSecretOutputs []resource.PropertyKey
|
|
if custom {
|
|
removeOutputDeps(resource.NewObjectProperty(outputs))
|
|
|
|
additionalSecretOutputs = make([]resource.PropertyKey, 0, len(outputs))
|
|
for k := range outputs {
|
|
additionalSecretOutputs = append(additionalSecretOutputs, k)
|
|
}
|
|
|
|
count := rapid.IntRange(0, len(additionalSecretOutputs)).Draw(t, "additionalSecretOutputs").(int)
|
|
additionalSecretOutputs = additionalSecretOutputs[:count]
|
|
|
|
rand.Shuffle(len(additionalSecretOutputs), func(i, j int) {
|
|
additionalSecretOutputs[i], additionalSecretOutputs[j] = additionalSecretOutputs[j], additionalSecretOutputs[i]
|
|
})
|
|
}
|
|
|
|
// If this is a custom resource, generate init errors.
|
|
var initErrors []string
|
|
if custom && !isProvider {
|
|
initErrors = rapid.SliceOfN(rapid.String(), 0, 4).Draw(t, "init errors").([]string)
|
|
}
|
|
|
|
// If this is a non-provider custom resource, generate a provider reference.
|
|
var provider string
|
|
if custom && !isProvider {
|
|
provider = string(providerResource.URN) + "::" + string(providerResource.ID)
|
|
}
|
|
|
|
// If this is a custom resource, generate custom timeouts.
|
|
var timeouts resource.CustomTimeouts
|
|
if custom && rapid.Bool().Draw(t, "custom timeouts").(bool) {
|
|
timeouts.Create = rapid.OneOf(rapid.Just(0.0), rapid.Float64Max(100000)).Draw(t, "create timeout").(float64)
|
|
timeouts.Update = rapid.OneOf(rapid.Just(0.0), rapid.Float64Max(100000)).Draw(t, "update timeout").(float64)
|
|
timeouts.Delete = rapid.OneOf(rapid.Just(0.0), rapid.Float64Max(100000)).Draw(t, "delete timeout").(float64)
|
|
}
|
|
|
|
return &resource.State{
|
|
Type: urn.Type(),
|
|
URN: urn,
|
|
Custom: custom,
|
|
Delete: del,
|
|
ID: id,
|
|
Inputs: inputs,
|
|
Outputs: outputs,
|
|
Parent: parent,
|
|
Protect: protect,
|
|
External: external,
|
|
Dependencies: sortedDeps(dependencySet),
|
|
InitErrors: initErrors,
|
|
Provider: provider,
|
|
PropertyDependencies: propertyDependencies,
|
|
PendingReplacement: pendingReplacement,
|
|
AdditionalSecretOutputs: additionalSecretOutputs,
|
|
CustomTimeouts: timeouts,
|
|
ImportID: importID,
|
|
}
|
|
})
|
|
}
|