pulumitest

package
v0.0.15 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 26, 2024 License: Apache-2.0 Imports: 31 Imported by: 3

README

Pulumi Testing Library

Pulumi Test is a thin(ish) wrapper over the Automation API, making it easier to use within test scenarios.

The Automation API is just a thin wrapper around the calling the Pulumi CLI (pulumi ...). Some additional commands which are not yet supported by the Automation API are also added for convenience.

Getting Started

The starting point to testing a program is to create a new PulumiTest pointing at some existing test.

func TestPulumiProgram(t *testing.T) {
  test := NewPulumiTest(t, filepath.Join("path", "to", "program"))
  //...
}

By default your program is copied to a temporary directory before running to avoid cluttering your working directory with temporary or ephemeral files. To disable this behaviour, use opttest.TestInPlace(). You can also do a copy of a test manually by calling CopyToTempDir():

source := NewPulumiTest(t, opttest.TestInPlace())
copy := source.CopyToTempDir()

The source variable is still pointing to the original path, but the copy is pointing to a new PulumiTest in temporary directory which will automatically get removed at the end of the test.

The default behaviour is also to install dependencies and create a new stack called "test".

  • The default stack name can be customised by using opttest.StackName("my-stack").
  • To prevent the automatic install of dependencies use opttest.SkipInstall().
  • To prevent automatically creating a stack use opttest.SkipStackCreate()

The following program is equivalent to the default test setup:

test := NewPulumiTest(t, opttest.SkipInstall(), opttest.SkipStackCreate())
test.Install() // Runs `pulumi install` to restore all dependencies
test.NewStack("test") // Creates a new stack and returns it.

The Install and NewStack steps can also be done together by calling InstallStack():

test.InstallStack("test")

The created stack is returned but is also set as the current stack on the PulumiTest object. All methods such as source.Preview() or source.Up() will use this current stack.

[!NOTE] The new stack will be automatically destroyed and removed at the end of the test.

Default Settings

PULUMI_BACKEND_URL is set to a temporary directory. This improves test performance and doesn't rely on the user already being authenticated to a specific backend account. This also isolates stacks so the same stack name can be re-used for several tests at once without risking conflicts and avoiding stack name randomisation which breaks importing & exporting between test runs. This can be overridden by the option opttest.UseAmbientBackend() or by setting PULUMI_BACKEND_URL yourself in the stack initialization options.

PULUMI_CONFIG_PASSPHRASE is set by default to "correct horse battery staple" (an arbitrary phrase) so that encrypted values are not tied to an external secret store that the user might not have access to. This can be overridden by setting PULUMI_CONFIG_PASSPHRASE in the stack initialization options.

Configuring Providers

Pulumi discovers plugins the same as when running Pulumi commands directly.

In a test scenario, we often want to ensure a specific implementation of a provider is used during testing. The most reliable way is to configure use plugin attachment PULUMI_DEBUG_PROVIDERS=NAME:PORT. This prevents the Pulumi engine from searching for and starting the provider with the given name. Instead, it will connect to the already-running provider on the specified port. If the provider is not reachable on the given port, Pulumi will throw an error.

These can be specified via the Attach* options when constructing the test:

// Start a provider yourself
NewPulumiTest(t, "path", opttest.AttachProvider("gcp", func(ctx context.Context) (int, error) {
  return port, nil // TODO: Actually start a provider.
})
// Start a server for testing from a pulumirpc.ResourceProviderServer implementation
NewPulumiTest(t, "path", opttest.AttachProviderServer("gcp", func() (pulumirpc.ResourceProviderServer, error) {
  return makeProvider()
})
// Specify a local path where the binary lives to be started and attached.
NewPulumiTest(t, "path", opttest.AttachProviderBinary("gcp", filepath.Join("..", "bin"))
// Use Pulumi to download a specific published version, then start and attach it.
NewPulumiTest(t, "path", opttest.AttachDownloadedPlugin("gcp", "6.61.0")

For providers which don't support attaching, we can configure the path to the binary of a specific provider in the plugins.providers property in the project settings (Pulumi.yaml) by using the LocalProviderPath() option:

NewPulumiTest(t, "path", opttest.LocalProviderPath("gcp", filepath.Join("..", "bin"))

Pulumi Operations

Preview, Up, Refresh and Destroy can be run directly from the test context:

test.Preview()
test.Up()
test.Refresh()
test.Destroy()

[!NOTE] Stacks created with InstallStack or NewStack will be automatically destroyed and removed at the end of the test.

Using Local SDKs

When running tests via SDKs that haven't yet been published, we need to configure the program under test to use our local build of the SDK instead of installing a version from their package registry.

For Node.js, we support using a locally linked version of the NPM package. Before running your test, you must run yarn link in the directory of the built Node.js SDK (normally in sdk/nodejs/bin).

Once the local link is configured, you can configure your test to use the linked package by using the YarnLink test option:

NewPulumiTest(t, "test_dir", opttest.YarnLink("@pulumi/azure-native"))
Go - Module Replacement

In Go, we support adding a replacement to the go.mod of the program under test. This is implemented by calling go mod edit -replace with the user-specified replacement.

The replacement can be specified using the GoModReplacement test option:

NewPulumiTest(t, "test_dir",
  opttest.GoModReplacement("github.com/pulumi/pulumi-my-provider/sdk/v3", "..", "sdk"))

Additional Operations

Update Source

Update source files for a subsequent step in the test:

test.UpdateSource("folder_with_updates")
Set Config

Set a variable in the stack's config:

test.SetConfig("gcp:project", "pulumi-development")

Asserts

The assertup and assertpreview modules contain a selection of functions for asserting on the results of the automation API:

assertup.HasNoDeletes(t, upResult)
assertup.HasNoChanges(t, upResult)
assertpreview.HasNoChanges(t, previewResult)
assertpreview.HasNoDeletes(t, previewResult)

Example

Here's a complete example as a test might look for the gcp provider with a local pre-built binary.

func TestExample(t *testing.T) {
  // Copy test_dir to temp directory, install deps and create "my-stack"
  test := NewPulumiTest(t, "test_dir", opttest.AttachProviderBinary("gcp", "../bin"))
  test.InstallStack("my-stack")

  // Configure the test environment & project
  test.SetConfig("gcp:project", "pulumi-development")

  // Preview, Deploy, Refresh, 
  preview := test.Preview()
  t.Log(preview.StdOut)

  deploy := test.Up()
  t.Log(deploy.StdOut)
  assertpreview.HasNoChanges(t, test.Preview())

  // Export import
  test.ImportStack(test.ExportStack())
  assertpreview.HasNoChanges(t, test.Preview())

  test.UpdateSource(filepath.Join("testdata", "step2"))
  update := test.Up()
  t.Log(update.StdOut)
}

Comparative ProgramTest example:

func TestExample(t *testing.T) {
  test := integration.ProgramTestOptions{
    Dir: testDir(t, "test_dir"),
    Dependencies: []string{filepath.Join("..", "sdk", "python", "bin")},
    ExpectRefreshChanges: true,
    Config: map[string]string{
      "gcp:project": "pulumi-development",
    },
    LocalProviders: []integration.LocalDependency{
      {
        Package: "gcp",
        Path:    "../bin",
      },
    },
    EditDirs: []integration.EditDir{
      {
        Dir:      testDir(t, "test_dir", "step2"),
        Additive: true,
      },
    },
  }

  integration.ProgramTest(t, &test)
}

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type ConvertResult

type ConvertResult struct {
	// PulumiTest instance for the converted program.
	PulumiTest *PulumiTest
	// Combined output of the `pulumi convert` command.
	Output string
}

type PT added in v0.0.12

type PT interface {
	Name() string
	TempDir() string
	Log(...any)
	Fail()
	FailNow()
	Cleanup(func())
	Helper()
	Deadline() (time.Time, bool)
}

A subset of *testing.T functionality used by pulumitest.

type PulumiTest

type PulumiTest struct {
	// contains filtered or unexported fields
}

func NewPulumiTest

func NewPulumiTest(t PT, source string, opts ...opttest.Option) *PulumiTest

NewPulumiTest creates a new PulumiTest instance. By default it will: 1. Copy the source to a temporary directory. 2. Install dependencies. 3. Create a new stack called "test" with state stored to a local temporary directory and a fixed passphrase for encryption.

func (*PulumiTest) ClearGrpcLog added in v0.0.11

func (pt *PulumiTest) ClearGrpcLog()

ClearGrpcLog clears the gRPC log for the current stack based on the PULUMI_DEBUG_GRPC env var.

func (*PulumiTest) Context

func (a *PulumiTest) Context() context.Context

Context returns the current context.Context instance used for automation API calls.

func (*PulumiTest) Convert

func (a *PulumiTest) Convert(language string, opts ...opttest.Option) ConvertResult

Convert a program to a given language. It returns a new PulumiTest instance for the converted program which will be outputted into a temporary directory.

func (*PulumiTest) CopyTo added in v0.0.6

func (a *PulumiTest) CopyTo(dir string, opts ...opttest.Option) *PulumiTest

CopyTo copies the program to the specified directory. It returns a new PulumiTest instance for the copied program.

func (*PulumiTest) CopyToTempDir

func (a *PulumiTest) CopyToTempDir(opts ...opttest.Option) *PulumiTest

CopyToTempDir copies the program to a temporary directory. It returns a new PulumiTest instance for the copied program. This is used to avoid temporary files being written to the source directory.

func (*PulumiTest) CurrentStack

func (a *PulumiTest) CurrentStack() *auto.Stack

CurrentStack returns the last stack that was created or nil if no stack has been created yet.

func (*PulumiTest) Destroy

func (a *PulumiTest) Destroy(opts ...optdestroy.Option) auto.DestroyResult

Up deploys the current stack.

func (*PulumiTest) ExportStack

func (a *PulumiTest) ExportStack() apitype.UntypedDeployment

ExportStack exports the current stack state.

func (*PulumiTest) GrpcLog added in v0.0.6

func (pt *PulumiTest) GrpcLog() *grpclog.GrpcLog

GrpcLog reads the gRPC log for the current stack based on the PULUMI_DEBUG_GRPC env var.

func (*PulumiTest) Import added in v0.0.14

func (a *PulumiTest) Import(
	resourceType, resourceName, resourceID string, providerUrn string, args ...string,
) cmdOutput

func (*PulumiTest) ImportStack

func (a *PulumiTest) ImportStack(source apitype.UntypedDeployment)

ImportStack imports the given stack state into the test's current stack.

func (*PulumiTest) Install

func (a *PulumiTest) Install() string

Install installs packages and plugins for a given directory by running `pulumi install`.

func (*PulumiTest) InstallStack

func (pt *PulumiTest) InstallStack(stackName string, opts ...optnewstack.NewStackOpt) *PulumiTest

InstallStack installs packages, and creates a new stack.

func (*PulumiTest) NewStack

func (pt *PulumiTest) NewStack(stackName string, opts ...optnewstack.NewStackOpt) *auto.Stack

NewStack creates a new stack, ensure it's cleaned up after the test is done. If no stack name is provided, a random one will be generated.

func (*PulumiTest) Preview

func (a *PulumiTest) Preview(opts ...optpreview.Option) auto.PreviewResult

Preview previews an update to the current stack.

func (*PulumiTest) Refresh

func (a *PulumiTest) Refresh(opts ...optrefresh.Option) auto.RefreshResult

Refresh refreshes the current stack.

func (*PulumiTest) Run added in v0.0.6

func (pulumiTest *PulumiTest) Run(execute func(test *PulumiTest), opts ...optrun.Option) *PulumiTest

Run will run the `execute` function in an isolated temp directory and with additional test options, then import the resulting stack state into the original test. WithCache can be used to skip executing the run and return the cached stack state if available, or to cache the stack state after executing the run. Options will be inherited from the original test, but can be added to with `optrun.WithOpts` or reset with `opttest.Defaults()`.

func (*PulumiTest) SetConfig

func (a *PulumiTest) SetConfig(key, value string)

func (*PulumiTest) Source

func (a *PulumiTest) Source() string

Source returns the current source directory.

func (*PulumiTest) Up

func (a *PulumiTest) Up(opts ...optup.Option) auto.UpResult

Up deploys the current stack.

func (*PulumiTest) UpdateSource

func (a *PulumiTest) UpdateSource(pathElems ...string)

Copy files from a source directory to the current program directory.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL