mirror of https://github.com/pulumi/pulumi.git
168 lines
5.0 KiB
Go
168 lines
5.0 KiB
Go
// Copyright 2024, Pulumi Corporation.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package fuzzing
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/pulumi/pulumi/pkg/v3/engine"
|
|
lt "github.com/pulumi/pulumi/pkg/v3/engine/lifecycletest/framework"
|
|
"github.com/pulumi/pulumi/pkg/v3/resource/deploy"
|
|
"github.com/pulumi/pulumi/pkg/v3/resource/deploy/deploytest"
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
|
|
"pgregory.net/rapid"
|
|
)
|
|
|
|
// A PlanSpec specifies a lifecycle test operation that executes some program against an initial snapshot using a
|
|
// configured set of providers.
|
|
type PlanSpec struct {
|
|
// The operation that will be executed (e.g. update, refresh, destroy).
|
|
Operation OperationSpec
|
|
// The set of target URNs that will be passed to the operation, if any.
|
|
TargetURNs []resource.URN
|
|
}
|
|
|
|
// The type of operations that may be executed as part of a PlanSpec.
|
|
type OperationSpec string
|
|
|
|
const (
|
|
// An update operation.
|
|
PlanOperationUpdate OperationSpec = "plan.update"
|
|
// A refresh operation.
|
|
PlanOperationRefresh OperationSpec = "plan.refresh"
|
|
// A destroy operation.
|
|
PlanOperationDestroy OperationSpec = "plan.destroy"
|
|
)
|
|
|
|
// Returns a set of test options and a test operation that can be used to execute this PlanSpec as part of a lifecycle
|
|
// test.
|
|
func (ps *PlanSpec) Executors(t lt.TB, hostF deploytest.PluginHostFactory) (lt.TestUpdateOptions, lt.TestOp) {
|
|
opts := lt.TestUpdateOptions{
|
|
T: t,
|
|
HostF: hostF,
|
|
}
|
|
|
|
if len(ps.TargetURNs) > 0 {
|
|
opts.UpdateOptions = engine.UpdateOptions{
|
|
Targets: deploy.NewUrnTargetsFromUrns(ps.TargetURNs),
|
|
}
|
|
}
|
|
|
|
var op lt.TestOp
|
|
switch ps.Operation {
|
|
case PlanOperationUpdate:
|
|
op = lt.TestOp(engine.Update)
|
|
case PlanOperationRefresh:
|
|
op = lt.TestOp(engine.Refresh)
|
|
case PlanOperationDestroy:
|
|
op = lt.TestOp(engine.Destroy)
|
|
}
|
|
|
|
return opts, op
|
|
}
|
|
|
|
// Implements PrettySpec.Pretty. Returns a human-readable string representation of this PlanSpec, suitable for use in
|
|
// debugging output and error messages.
|
|
func (ps *PlanSpec) Pretty(indent string) string {
|
|
rendered := fmt.Sprintf("%sPlan %p", indent, ps)
|
|
rendered += fmt.Sprintf("\n%s Operation: %s", indent, ps.Operation)
|
|
if len(ps.TargetURNs) > 0 {
|
|
rendered += fmt.Sprintf("\n%s Targets:", indent)
|
|
for _, urn := range ps.TargetURNs {
|
|
rendered += fmt.Sprintf("\n%s %s", indent, Colored(urn))
|
|
}
|
|
} else {
|
|
rendered += fmt.Sprintf("\n%s No targets", indent)
|
|
}
|
|
|
|
return rendered
|
|
}
|
|
|
|
// A set of options for configuring the generation of a PlanSpec.
|
|
type PlanSpecOptions struct {
|
|
// A generator for operations that might be planned.
|
|
Operation *rapid.Generator[OperationSpec]
|
|
|
|
// A source set of targets that should be used literally, skipping the target generation process.
|
|
SourceTargets []resource.URN
|
|
|
|
// A generator for the maximum number of resources to target in a plan.
|
|
TargetCount *rapid.Generator[int]
|
|
}
|
|
|
|
// Returns a copy of the given PlanSpecOptions with the given overrides applied.
|
|
func (pso PlanSpecOptions) With(overrides PlanSpecOptions) PlanSpecOptions {
|
|
if overrides.Operation != nil {
|
|
pso.Operation = overrides.Operation
|
|
}
|
|
if overrides.SourceTargets != nil {
|
|
pso.SourceTargets = overrides.SourceTargets
|
|
}
|
|
if overrides.TargetCount != nil {
|
|
pso.TargetCount = overrides.TargetCount
|
|
}
|
|
|
|
return pso
|
|
}
|
|
|
|
// A default set of PlanSpecOptions. By default, a PlanSpec will have a random operation and between 0 and 5 target
|
|
// URNs.
|
|
var defaultPlanSpecOptions = PlanSpecOptions{
|
|
Operation: rapid.SampledFrom(operationSpecs),
|
|
SourceTargets: nil,
|
|
TargetCount: rapid.IntRange(0, 5),
|
|
}
|
|
|
|
var operationSpecs = []OperationSpec{
|
|
PlanOperationUpdate,
|
|
PlanOperationRefresh,
|
|
PlanOperationDestroy,
|
|
}
|
|
|
|
// Given a SnapshotSpec and a set of options, returns a rapid.Generator that will produce PlanSpecs that can be executed
|
|
// against the specified snapshot.
|
|
func GeneratedPlanSpec(ss *SnapshotSpec, pso PlanSpecOptions) *rapid.Generator[*PlanSpec] {
|
|
pso = defaultPlanSpecOptions.With(pso)
|
|
|
|
return rapid.Custom(func(t *rapid.T) *PlanSpec {
|
|
op := pso.Operation.Draw(t, "PlanSpec.Operation")
|
|
|
|
var targetURNs []resource.URN
|
|
if len(pso.SourceTargets) > 0 {
|
|
targetURNs = pso.SourceTargets
|
|
} else {
|
|
seen := map[resource.URN]bool{}
|
|
|
|
targetCount := pso.TargetCount.Draw(t, "PlanSpec.TargetCount")
|
|
for i := 0; i < targetCount; i++ {
|
|
candidate := rapid.SampledFrom(ss.Resources).Draw(t, fmt.Sprintf("PlanSpec.TargetResource[%d]", i))
|
|
if seen[candidate.URN()] {
|
|
continue
|
|
}
|
|
|
|
seen[candidate.URN()] = true
|
|
targetURNs = append(targetURNs, candidate.URN())
|
|
}
|
|
}
|
|
|
|
ps := &PlanSpec{
|
|
Operation: op,
|
|
TargetURNs: targetURNs,
|
|
}
|
|
|
|
return ps
|
|
})
|
|
}
|