infer

package
v0.17.0 Latest Latest
Warning

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

Go to latest
Published: Apr 29, 2024 License: Apache-2.0 Imports: 28 Imported by: 60

README

Infer

The infer package provides infrastructure to infer Pulumi component resources, custom resources and functions from go code.

Defining a component resource

Here we will define a component resource that aggregates two custom resources from the random provider. Our component resource will serve a username, derived from either random.RandomId or random.RandomPet. It will also serve a password, derived from random.RandomPassword. We will call the component resource Login.

To encapsulate the idea of a new component resource, we define the resource, its inputs and its outputs:

type Login struct{}
type LoginArgs struct {
  PasswordLength pulumi.IntPtrInput `pulumi:"passwordLength"`
  PetName        bool               `pulumi:"petName"`
}

type LoginState struct {
  pulumi.ResourceState

	 PasswordLength pulumi.IntPtrInput `pulumi:"passwordLength"`
	 PetName        bool               `pulumi:"petName"`
	 // Outputs
	 Username pulumi.StringOutput `pulumi:"username"`
	 Password pulumi.StringOutput `pulumi:"password"`
}

Each field is tagged with pulumi:"name". Pulumi (and the infer package) only acts on fields with this tag. Pulumi names don't need to match up with with field names, but they should be lowerCamelCase. Fields also need to be exported (capitalized) to interact with Pulumi.

Most fields on components are Inputty or Outputty types, which means they are eventual values. We will make a decision based on PetName, so it is simply a bool. This tells Pulumi that PetName needs to be an immediate value so we can make decisions based on it. Specifically, we decide if we should construct the username based on a random.RandomPet or a random.RandomId.

Now that we have defined the type of the component, we need to define how to actually construct the component resource:

func (r *Login) Construct(ctx *pulumi.Context, name, typ string, args LoginArgs, opts pulumi.ResourceOption) (
 *LoginState, error) {
	comp := &LoginState{}
	err := ctx.RegisterComponentResource(typ, name, comp, opts)
	if err != nil {
		return nil, err
	}
	if args.PetName {
		pet, err := random.NewRandomPet(ctx, name+"-pet", &random.RandomPetArgs{}, pulumi.Parent(comp))
		if err != nil {
			return nil, err
		}
		comp.Username = pet.ID().ToStringOutput()
	} else {
		id, err := random.NewRandomId(ctx, name+"-id", &random.RandomIdArgs{
			ByteLength: pulumi.Int(8),
		}, pulumi.Parent(comp))
		if err != nil {
			return nil, err
		}
		comp.Username = id.ID().ToStringOutput()
	}
	var length pulumi.IntInput = pulumi.Int(16)
	if args.PasswordLength != nil {
		length = args.PasswordLength.ToIntPtrOutput().Elem()
	}
	password, err := random.NewRandomPassword(ctx, name+"-password", &random.RandomPasswordArgs{
		Length: length,
	}, pulumi.Parent(comp))
	if err != nil {
		return nil, err
	}
	comp.Password = password.Result

	return comp, nil
}

This works almost exactly like defining a component resource in Pulumi Go normally does. It is not necessary to call ctx.RegisterComponentResourceOutputs.

The last step in defining the component is serving it. Here we define the provider, telling it that it should serve the Login component. We then run that provider in main with RunProvider.

func main() {
	err := p.RunProvider("", "0.1.0", provider())
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error: %s", err.Error())
		os.Exit(1)
  }
}

func provider() p.Provider {
	return infer.Provider(infer.Options{
		Components: []infer.InferredComponent{
			infer.Component[*Login, LoginArgs, *LoginState](),
		},
	})
}

This is all it takes to serve a component provider.

Defining a custom resource

As our example of a custom resource, we will implement a custom resource to represent a file in the local file system. This will take us through most of the functionality that inferred custom resource support, including the full CRUD lifecycle.

Full working code for this example can be found in examples/file/main.go.

We first declare the defining struct, its arguments and its state.

type File struct{}

type FileArgs struct {
	Path    string `pulumi:"path,optional"`
	Force   bool   `pulumi:"force,optional"`
	Content string `pulumi:"content"`
}

type FileState struct {
	Path    string `pulumi:"path"`
	Force   bool   `pulumi:"force"`
	Content string `pulumi:"content"`
}

To add descriptions to the new resource, we implement the Annotated interface for File, FileArgs and FileState. This will add descriptions to the resource, its input fields and its output fields.

func (f *File) Annotate(a infer.Annotator) {
	a.Describe(&f, "A file projected into a pulumi resource")
}


func (f *FileArgs) Annotate(a infer.Annotator) {
	a.Describe(&f.Content, "The content of the file.")
	a.Describe(&f.Force, "If an already existing file should be deleted if it exists.")
	a.Describe(&f.Path, "The path of the file. This defaults to the name of the pulumi resource.")
}

func (f *FileState) Annotate(a infer.Annotator) {
	a.Describe(&f.Content, "The content of the file.")
	a.Describe(&f.Force, "If an already existing file should be deleted if it exists.")
	a.Describe(&f.Path, "The path of the file.")
}

The only mandatory method for a CustomResource is Create.

func (*File) Create(ctx context.Context, name string, input FileArgs, preview bool) (
 id string, output FileState, err error) {
	if !input.Force {
		_, err := os.Stat(input.Path)
		if !os.IsNotExist(err) {
			return "", FileState{}, fmt.Errorf("file already exists; pass force=true to override")
		}
	}

	if preview { // Don't do the actual creating if in preview
		return input.Path, FileState{}, nil
	}

	f, err := os.Create(input.Path)
	if err != nil {
		return "", FileState{}, err
	}
	defer f.Close()
	n, err := f.WriteString(input.Content)
	if err != nil {
		return "", FileState{}, err
	}
	if n != len(input.Content) {
		return "", FileState{}, fmt.Errorf("only wrote %d/%d bytes", n, len(input.Content))
	}
	return input.Path, FileState{
		Path:    input.Path,
		Force:   input.Force,
		Content: input.Content,
	}, nil
}

We would like the file to be deleted when the custom resource is deleted. We can do that by implementing the Delete method:

func (*File) Delete(ctx context.Context, id string, props FileState) error {
	err := os.Remove(props.Path)
	if os.IsNotExist(err) {
		ctx.Logf(diag.Warning, "file %q already deleted", props.Path)
		err = nil
	}
	return err
}

Note that we can issue diagnostics to the user via the passed on Context. The diagnostic messages are tied to the resource that the method is called on, and pulumi will group them nicely:

Diagnostics:
  fs:index:File (managedFile):
    warning: file "managedFile" already deleted

The next method to implement is Check. We say in the description of FileArgs.Path that it defaults to the name of the resource, but that isn't implement in Create. Instead, we automatically fill the FileArgs.Path field from name if it isn't present in our check implementation.

func (*File) Check(ctx context.Context, name string, oldInputs, newInputs resource.PropertyMap) (
 FileArgs, []p.CheckFailure, error) {
	if _, ok := newInputs["path"]; !ok {
		newInputs["path"] = resource.NewStringProperty(name)
	}
	return infer.DefaultCheck[FileArgs](newInputs)
}

We still want to make sure our inputs are valid, so we make the adjustment for giving "path" a default value and the call into DefaultCheck, which ensures that all fields are valid given the constraints of their types.

We want to allow our users to change the content of the file they are managing. To allow updates, we need to implement the Update method:

func (*File) Update(ctx context.Context, id string, olds FileState, news FileArgs, preview bool) (FileState, error) {
	if !preview && olds.Content != news.Content {
		f, err := os.Create(olds.Path)
		if err != nil {
			return FileState{}, err
		}
		defer f.Close()
		n, err := f.WriteString(news.Content)
		if err != nil {
			return FileState{}, err
		}
		if n != len(news.Content) {
			return FileState{}, fmt.Errorf("only wrote %d/%d bytes", n, len(news.Content))
		}
	}

	return FileState{
		Path:    news.Path,
		Force:   news.Force,
		Content: news.Content,
	}, nil

}

The above code is pretty straightforward. Note that we don't handle when FileArgs.Path changes, since thats not really an update to an existing file. Its more of a replace operation. To tell pulumi that changes in FileArgs.Content and FileArgs.Force can be handled by updates, but that changes to FileArgs.Path require a replace, we need to override how diff works:

func (*File) Diff(ctx context.Context, id string, olds FileState, news FileArgs) (p.DiffResponse, error) {
	diff := map[string]p.PropertyDiff{}
	if news.Content != olds.Content {
		diff["content"] = p.PropertyDiff{Kind: p.Update}
	}
	if news.Force != olds.Force {
		diff["force"] = p.PropertyDiff{Kind: p.Update}
	}
	if news.Path != olds.Path {
		diff["path"] = p.PropertyDiff{Kind: p.UpdateReplace}
	}
	return p.DiffResponse{
		DeleteBeforeReplace: true,
		HasChanges:          len(diff) > 0,
		DetailedDiff:        diff,
	}, nil
}

We check for each field, and if there is a change, we record it. Changes in news.Content and news.Force result in an Update, but changes in news.Path result in an UpdateReplace. Since the id (file path) is globally unique, we also tell Pulumi that it needs to perform deletes before the associated create.

Last but not least, we want to be able to read state from the file system as-is. Unsurprisingly, we do this by implementing yet another method:

func (*File) Read(ctx context.Context, id string, inputs FileArgs, state FileState) (
 string, FileArgs, FileState, error) {
	path := id
	byteContent, err := ioutil.ReadFile(path)
	if err != nil {
		return "", FileArgs{}, FileState{}, err
	}
	content := string(byteContent)
	return path, FileArgs{
			Path:    path,
			Force:   inputs.Force && state.Force,
			Content: content,
		}, FileState{
			Path:    path,
			Force:   inputs.Force && state.Force,
			Content: content,
		}, nil
}

Here we get a partial view of the id, inputs and state and need to figure out the rest. We return the correct id, args and state.

This is an example of a fully functional custom resource, with full participation in the Pulumi lifecycle.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func DefaultCheck

func DefaultCheck[I any](inputs resource.PropertyMap) (I, []p.CheckFailure, error)

Ensure that `inputs` can deserialize cleanly into `I`.

func GetConfig

func GetConfig[T any](ctx context.Context) T

Retrieve the configuration of this provider.

Note: GetConfig will panic if the type of T does not match the type of the config or if the provider has not supplied a config.

func Provider

func Provider(opts Options) p.Provider

Provider creates a new inferred provider from `opts`.

To customize the resulting provider, including setting resources, functions, config options and other schema metadata, look at the Options struct.

func ProviderErrorf added in v0.16.0

func ProviderErrorf(msg string, a ...any) error

Create a new ProviderErrorf.

func Wrap added in v0.8.0

func Wrap(provider p.Provider, opts Options) p.Provider

Wrap wraps a compatible underlying provider in an inferred provider (as described by options).

The resulting provider will respond to resources and functions that are described in `opts`, delegating unknown calls to the underlying provider.

Types

type Annotated

type Annotated interface {
	Annotate(Annotator)
}

Annotated is used to describe the fields of an object or a resource. Annotated can be implemented by `CustomResource`s, the input and output types for all resources and invokes, as well as other structs used the above.

type Annotator

type Annotator interface {
	// Annotate a struct field with a text description.
	Describe(i any, description string)

	// Annotate a struct field with a default value. The default value must be a primitive
	// type in the pulumi type system.
	SetDefault(i any, defaultValue any, env ...string)

	// Set the token of the annotated type.
	//
	// module and name should be valid Pulumi token segments. The package name will be
	// inferred from the provider.
	//
	// For example:
	//
	//	a.SetToken("mymodule", "MyResource")
	//
	// On a provider created with the name "mypkg" will have the token:
	//
	//	mypkg:mymodule:MyResource
	//
	SetToken(module, name string)
}

The methods of Annotator must be called on pointers to fields of their receivers, or on their receiver itself.

func (*s Struct) Annotated(a Annotator) {
	a.Describe(&s, "A struct")            // Legal
	a.Describe(&s.field1, "A field")      // Legal
	a.Describe(s.field2, "A field")       // Not legal, since the pointer is missing.
	otherS := &Struct{}
	a.Describe(&otherS.field1, "A field") // Not legal, since describe is not called on its receiver.
}

type ComponentResource

type ComponentResource[I any, O pulumi.ComponentResource] interface {
	// Construct a component resource
	//
	// ctx.RegisterResource needs to be called, but ctx.RegisterOutputs does not need to
	// be called.
	Construct(ctx *pulumi.Context, name, typ string, inputs I, opts pulumi.ResourceOption) (O, error)
}

A component resource.

type Crawler

type Crawler func(
	t reflect.Type, isReference bool,
	fieldInfo *introspect.FieldTag,
	parent, field string,
) (drill bool, err error)

type CustomCheck

type CustomCheck[I any] interface {
	// Maybe oldInputs can be of type I
	Check(ctx context.Context, name string, oldInputs resource.PropertyMap, newInputs resource.PropertyMap) (
		I, []p.CheckFailure, error)
}

A resource that understands how to check its inputs.

By default, infer handles checks by ensuring that a inputs de-serialize correctly. This is where you can extend that behavior. The returned input is given to subsequent calls to `Create` and `Update`.

Example: TODO - Maybe a resource that has a regex. We could reject invalid regex before the up actually happens.

type CustomConfigure added in v0.10.1

type CustomConfigure interface {
	// Configure the provider.
	//
	// This method will only be called once per provider process.
	//
	// By the time Configure is called, the receiver will be fully hydrated.
	//
	// Changes to the receiver will not be saved in state. For normalizing inputs see
	// [CustomCheck].
	Configure(ctx context.Context) error
}

A provider that requires custom configuration before running.

This interface should be implemented by reference to allow setting private fields on its receiver.

type CustomCreate added in v0.16.0

type CustomCreate[I, O any] interface {
	Create(ctx context.Context, name string, inputs I, preview bool) (id string, output O, err error)
}

type CustomDelete

type CustomDelete[O any] interface {
	// Delete is called before a resource is removed from pulumi state.
	Delete(ctx context.Context, id string, props O) error
}

A resource that knows how to delete itself.

If a resource does not implement Delete, no code will be run on resource deletion.

type CustomDiff

type CustomDiff[I, O any] interface {
	// Maybe oldInputs can be of type I
	Diff(ctx context.Context, id string, olds O, news I) (p.DiffResponse, error)
}

A resource that understands how to diff itself given a new set of inputs.

By default, infer handles diffs by structural equality among inputs. If CustomUpdate is implemented, changes will result in updates. Otherwise changes will result in replaces.

Example: TODO - Indicate replacements for certain changes but not others.

type CustomRead

type CustomRead[I, O any] interface {
	// Read accepts a resource id, and a best guess of the input and output state. It returns
	// a normalized version of each, assuming it can be recovered.
	Read(ctx context.Context, id string, inputs I, state O) (
		canonicalID string, normalizedInputs I, normalizedState O, err error)
}

A resource that can recover its state from the provider.

If CustomRead is not implemented, it will default to checking that the inputs and state fit into I and O respectively. If they do, then the values will be returned as is. Otherwise an error will be returned.

Example: TODO - Probably something to do with the file system.

type CustomResource

type CustomResource[I, O any] interface {
	// All custom resources must be able to be created.
	CustomCreate[I, O]
}

A [custom resource](https://www.pulumi.com/docs/concepts/resources/) inferred from code. This is the minimum requirement for defining a new custom resource.

This interface should be implemented by the resource controller, with `I` the resource inputs and `O` the full set of resource fields. It is recommended that `O` is a superset of `I`, but it is not strictly required. The fields of `I` and `O` should consist of non-pulumi types i.e. `string` and `int` instead of `pulumi.StringInput` and `pulumi.IntOutput`.

The behavior of a CustomResource resource can be extended by implementing any of the following interfaces on the resource controller:

- CustomCheck - CustomDiff - CustomUpdate - CustomRead - CustomDelete - CustomStateMigrations - Annotated

Example:

type MyResource struct{}

type MyResourceInputs struct {
	MyString string `pulumi:"myString"`
	OptionalInt *int `pulumi:"myInt,optional"`
}

type MyResourceOutputs struct {
	MyResourceInputs
	Result string `pulumi:"result"`
}

func (*MyResource) Create(
	ctx context.Context, name string, inputs MyResourceInputs, preview bool,
) (string, MyResourceOutputs, error) {
	id := input.MyString + ".id"
	if preview {
		return id, MyResourceOutputs{MyResourceInputs: inputs}, nil
	}

	result := input.MyString
	if inputs.OptionalInt != nil {
		result = fmt.Sprintf("%s.%d", result, *inputs.OptionalInt)
	}

	return id, MyResourceOutputs{inputs, result}, nil
}

type CustomStateMigrations added in v0.16.0

type CustomStateMigrations[O any] interface {
	// StateMigrations is the list of know migrations.
	//
	// Each migration should return a valid State object.
	//
	// The first migration to return a non-nil Result will be used.
	StateMigrations(ctx context.Context) []StateMigrationFunc[O]
}

type CustomUpdate

type CustomUpdate[I, O any] interface {
	Update(ctx context.Context, id string, olds O, news I, preview bool) (O, error)
}

A resource that can adapt to new inputs with a delete and replace.

There is no default behavior for CustomUpdate.

Here the old state (as returned by Create or Update) as well as the new inputs are passed. Update should return the new state of the resource. If preview is true, then the update is part of `pulumi preview` and no changes should be made.

Example: TODO

type Enum

type Enum[T EnumKind] interface {
	// A list of all allowed values for the enum.
	Values() []EnumValue[T]
}

An Enum in the Pulumi type system.

type EnumKind

type EnumKind interface {
	~string | ~float64 | ~bool | ~int
}

The set of allowed enum underlying values.

type EnumValue

type EnumValue[T any] struct {
	Name        string
	Value       T
	Description string
}

An EnumValue represents an allowed value for an Enum.

type ExplicitDependencies

type ExplicitDependencies[I, O any] interface {
	// WireDependencies specifies the dependencies between inputs and outputs.
	WireDependencies(f FieldSelector, args *I, state *O)
}

A custom resource with the dataflow between its arguments (`I`) and outputs (`O`) specified. If a CustomResource implements ExplicitDependencies then WireDependencies will be called for each Create and Update call with `args` and `state` holding the values they will have for that call.

If ExplicitDependencies is not implemented, it is assumed that all outputs depend on all inputs.

type FieldSelector

type FieldSelector interface {
	// Create an input field. The argument to InputField must be a pointer to a field of
	// the associated input type I.
	//
	// For example:
	// “`go
	// func (r *MyResource) WireDependencies(f infer.FieldSelector, args *MyArgs, state *MyState) {
	//   f.InputField(&args.Field)
	// }
	// “`
	InputField(any) InputField
	// Create an output field. The argument to OutputField must be a pointer to a field of
	// the associated output type O.
	//
	// For example:
	// “`go
	// func (r *MyResource) WireDependencies(f infer.FieldSelector, args *MyArgs, state *MyState) {
	//   f.OutputField(&state.Field)
	// }
	// “`
	OutputField(any) OutputField
	// contains filtered or unexported methods
}

An interface to help wire fields together.

type Fn

type Fn[I any, O any] interface {
	// A function is a mapping from `I` to `O`.
	Call(ctx context.Context, input I) (output O, err error)
}

A Function (also called Invoke) inferred from code. `I` is the function input, and `O` is the function output. Both must be structs.

type InferredComponent

type InferredComponent interface {
	t.ComponentResource
	schema.Resource
	// contains filtered or unexported methods
}

A component resource inferred from code.

To create an InferredComponent, call the Component function.

func Component

Define a component resource from go code. Here `R` is the component resource anchor, `I` describes its inputs and `O` its outputs. To add descriptions to `R`, `I` and `O`, see the `Annotated` trait defined in this module.

type InferredConfig

type InferredConfig interface {
	schema.Resource
	// contains filtered or unexported methods
}

func Config

func Config[T any]() InferredConfig

Turn an object into a description for the provider configuration.

`T` has the same properties as an input or output type for a custom resource, and is responsive to the same interfaces.

`T` can implement CustomDiff and CustomCheck and CustomConfigure.

type InferredFunction

type InferredFunction interface {
	t.Invoke
	schema.Function
	// contains filtered or unexported methods
}

A function inferred from code. See Function for creating a InferredFunction.

func Function

func Function[F Fn[I, O], I, O any]() InferredFunction

Infer a function from `F`, which maps `I` to `O`.

type InferredResource

type InferredResource interface {
	t.CustomResource
	schema.Resource
	// contains filtered or unexported methods
}

A resource inferred by the Resource function.

This interface cannot be implemented directly. Instead consult the Resource function.

func Resource

func Resource[R CustomResource[I, O], I, O any]() InferredResource

Create a new InferredResource, where `R` is the resource controller, `I` is the resources inputs and `O` is the resources outputs.

type InputField

type InputField interface {

	// Only wire secretness, not computedness.
	Secret() InputField

	// Only flow computedness, not secretness.
	Computed() InputField
	// contains filtered or unexported methods
}

A field of the input (args).

type MigrationResult added in v0.16.0

type MigrationResult[T any] struct {
	// Result is the result of the migration.
	//
	// If Result is nil, then the migration is considered to have been unnecessary.
	//
	// If Result is non-nil, then the migration is considered to have completed and
	// the new value state value will be *Result.
	Result *T
}

MigrationResult represents the result of a migration.

type Options added in v0.8.0

type Options struct {
	// Metadata describes provider level metadata for the schema.
	//
	// Look at [schema.Metadata] to see the set of configurable options.
	//
	// It does not contain runtime details for the provider.
	schema.Metadata

	// The set of custom resources served by the provider.
	//
	// To create an [InferredResource], use [Resource].
	Resources []InferredResource

	// The set of component resources served by the provider.
	//
	// To create an [InferredComponent], use [Component].
	Components []InferredComponent

	// The set of functions served by the provider.
	//
	// To create an [InferredFunction], use [Function].
	Functions []InferredFunction

	// The config used by the provider, if any.
	//
	// To create an [InferredConfig], use [Config].
	Config InferredConfig

	// ModuleMap provides a mapping between go modules and pulumi modules.
	//
	// For example, given a provider `pkg` with defines resources `foo.Foo`, `foo.Bar`, and
	// `fizz.Buzz` the provider will expose resources at `pkg:foo:Foo`, `pkg:foo:Bar` and
	// `pkg:fizz:Buzz`. Adding
	//
	//	`opts.ModuleMap = map[tokens.ModuleName]tokens.ModuleName{"foo": "bar"}`
	//
	// will instead result in exposing the same resources at `pkg:bar:Foo`, `pkg:bar:Bar` and
	// `pkg:fizz:Buzz`.
	ModuleMap map[tokens.ModuleName]tokens.ModuleName
}

Configure an inferred provider.

type OutputField

type OutputField interface {
	// Specify that a state (output) field is always secret, regardless of its dependencies.
	AlwaysSecret()
	// Specify that a state (output) field is never secret, regardless of its dependencies.
	NeverSecret()
	// Specify that a state (output) field is always known, regardless of dependencies
	// or preview.
	AlwaysKnown()
	// Specify that a state (output) Field uses data from some args (input) Fields.
	DependsOn(dependencies ...InputField)
	// contains filtered or unexported methods
}

A field of the output (state).

type ProviderError added in v0.16.0

type ProviderError struct {
	Inner error
}

An error indicating a bug in the provider implementation.

func (ProviderError) Error added in v0.16.0

func (err ProviderError) Error() string

type ResourceInitFailedError added in v0.16.0

type ResourceInitFailedError struct {
	Reasons []string
}

An error indicating that the resource was created but failed to initialize.

This error is treated specially in Create, Update and Read. If the returner error for a Create, Update or Read returns true for Errors.Is, state is updated to correspond to the accompanying state, the resource will be considered created, and the next call will be Update with the new state.

func (*Team) Create(
	ctx context.Context, name string, input TeamInput, preview bool,
) (id string, output TeamState, err error) {
	team, err := GetConfig(ctx).Client.CreateTeam(ctx,
		input.OrganizationName, input.Name,
		input.TeamType, input.DisplayName,
		input.Description, input.GithubTeamId)
	if err != nil {
		return "", TeamState{}, fmt.Errorf("error creating team '%s': %w", input.Name, err)
	}

	if membersAdded, err := addMembers(team, input.Members); err != nil {
		return TeamState{
			Input: input,
			Members: membersAdded,
		}, infer.ResourceInitFailedError{Reasons: []string{
			fmt.Sprintf("Failed to add members: %s", err),
		}}
	}

	return TeamState{input, input.Members}, nil
}

If the the above example errors with infer.ResourceInitFailedError, the next Update will be called with `state` equal to what was returned alongside infer.ResourceInitFailedError.

func (ResourceInitFailedError) Error added in v0.16.0

func (err ResourceInitFailedError) Error() string

type StateMigrationFunc added in v0.16.0

type StateMigrationFunc[New any] interface {
	// contains filtered or unexported methods
}

StateMigrationFunc represents a stateless mapping from an old state shape to a new state shape. Each StateMigrationFunc is parameterized by the shape of the type it produces, ensuring that all successful migrations end up in a valid state.

To create a StateMigrationFunc, use StateMigration.

func StateMigration added in v0.16.0

func StateMigration[Old, New any, F func(context.Context, Old) (MigrationResult[New], error)](
	f F,
) StateMigrationFunc[New]

StateMigration creates a mapping from an old state shape (type Old) to a new state shape (type New).

If Old = resource.PropertyMap, then the migration is always run.

Example:

type MyResource struct{}

type MyInput struct{}

type MyStateV1 struct {
	SomeInt *int `pulumi:"someInt,optional"`
}

type MyStateV2 struct {
	AString string `pulumi:"aString"`
	AInt    *int   `pulumi:"aInt,optional"`
}

func migrateFromV1(ctx context.Context, v1 StateV1) (infer.MigrationResult[MigrateStateV2], error) {
	return infer.MigrationResult[MigrateStateV2]{
		Result: &MigrateStateV2{
			AString: "default-string", // Add a new required field
			AInt: v1.SomeInt, // Rename an existing field
		},
	}, nil
}

// Associate your migration with the resource it encapsulates.
func (*MyResource) StateMigrations(context.Context) []infer.StateMigrationFunc[MigrateStateV2] {
	return []infer.StateMigrationFunc[MigrateStateV2]{
		infer.StateMigration(migrateFromV1),
	}
}

Directories

Path Synopsis
internal
tests module

Jump to

Keyboard shortcuts

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