Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for state migrations in infer #215

Merged
merged 2 commits into from
Apr 13, 2024

Conversation

iwahbe
Copy link
Member

@iwahbe iwahbe commented Apr 8, 2024

Adds support for state migrations in the infer provider. Resources opt into state migrations when they implement the infer.CustomStateMigrations[O] interface:

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 p.Context) []StateMigrationFunc[O]
}

// 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].
type StateMigrationFunc[New any] interface {} // Sealed

// 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 p.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(p.Context) []infer.StateMigrationFunc[MigrateStateV2] {
//		return []infer.StateMigrationFunc[MigrateStateV2]{
//			infer.StateMigration(migrateFromV1),
//		}
//	}
func StateMigration[Old, New any, F func(p.Context, Old) (MigrationResult[New], error)](f F) StateMigrationFunc[New] { ... }


// MigrationResult represents the result of a migration.
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
}

This allows each resource to define a list of (possibly typed) migrations from old to new state. This design makes the common case of migrating from StateV1 to StateV2 fully typed, but permits an untyped to typed escape hatch.

Fixes #193

@iwahbe iwahbe requested review from blampe and a team April 8, 2024 15:09
@iwahbe iwahbe self-assigned this Apr 8, 2024
@iwahbe iwahbe force-pushed the iwahbe/193/infer-state-migration branch from f68a213 to ed5bb88 Compare April 8, 2024 15:17
@iwahbe iwahbe changed the title [Infer] Add support for state migrations in infer Add support for state migrations in infer Apr 8, 2024
@iwahbe
Copy link
Member Author

iwahbe commented Apr 8, 2024

This is required for pulumi/pulumi-pulumiservice#84, since pulumi-pulumiservice already maintains it's own state migrations.

@iwahbe iwahbe force-pushed the iwahbe/193/infer-state-migration branch from ed5bb88 to e203a6a Compare April 8, 2024 15:24
Copy link
Contributor

@blampe blampe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, let's see how it goes!

Comment on lines +1074 to +1079
// That didn't work, so maybe we can get by decoding without state migration but by tolerating
// missing fields.
stateEncoder, err = ende.DecodeTolerateMissing(req.Properties, &state)
if err != nil {
return p.ReadResponse{}, err
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good fix 👍 I think I ran into this a few times

}, nil
}

func migrateFromV0(ctx p.Context, v0 MigrateStateV0) (infer.MigrationResult[MigrateStateV2], error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests are really helpful to see how this is used in practice.

@iwahbe iwahbe merged commit 80e4b89 into main Apr 13, 2024
6 checks passed
@iwahbe iwahbe deleted the iwahbe/193/infer-state-migration branch April 13, 2024 20:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

UpgradeResourceState
2 participants