Migration of null resource to terraform data (#2481)

Signed-off-by: Ilia Gogotchuri <ilia.gogotchuri0@gmail.com>
This commit is contained in:
Ilia Gogotchuri 2025-02-06 17:45:00 +04:00 committed by GitHub
parent 7ad0af1f4c
commit ec4e0cf0e2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 113 additions and 2 deletions

View File

@ -17,6 +17,7 @@ ENHANCEMENTS:
* State encryption now supports using external programs as key providers. Additionally, the PBKDF2 key provider now supports chaining via the `chain` parameter. ([#2023](https://github.com/opentofu/opentofu/pull/2023))
* The `element` function now accepts negative indices, which extends the existing "wrapping" model into the negative direction. In particular, choosing element `-1` selects the final element in the sequence. ([#2371](https://github.com/opentofu/opentofu/pull/2371))
* `moved` now supports moving between different types ([#2370](https://github.com/opentofu/opentofu/pull/2370))
* `moved` block can now be used to migrate from the `null_resource` to the `terraform_data` resource. ([#2481](https://github.com/opentofu/opentofu/pull/2481))
BUG FIXES:

View File

@ -169,11 +169,14 @@ func (p *Provider) ImportResourceState(req providers.ImportResourceStateRequest)
panic("unimplemented - terraform_remote_state has no resources")
}
// MoveResourceState is called when the state loader encounters an instance state
// that has been moved to a new type, and the state should be updated to reflect the change.
// This is used to move the old state to the new schema.
func (p *Provider) MoveResourceState(r providers.MoveResourceStateRequest) (resp providers.MoveResourceStateResponse) {
panic("unimplmented")
return moveDataStoreResourceState(r)
}
// ValidateResourceConfig is used to to validate the resource configuration values.
// ValidateResourceConfig is used to validate the resource configuration values.
func (p *Provider) ValidateResourceConfig(req providers.ValidateResourceConfigRequest) providers.ValidateResourceConfigResponse {
return validateDataStoreResourceConfig(req)
}

View File

@ -56,6 +56,54 @@ func upgradeDataStoreResourceState(req providers.UpgradeResourceStateRequest) (r
return resp
}
// nullResourceSchema returns a schema for a null_resource with relevant attributes for type migration.
func nullResourceSchema() providers.Schema {
return providers.Schema{
Block: &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"triggers": {Type: cty.Map(cty.String), Optional: true},
"id": {Type: cty.String, Computed: true},
},
},
}
}
func moveDataStoreResourceState(req providers.MoveResourceStateRequest) providers.MoveResourceStateResponse {
var resp providers.MoveResourceStateResponse
if req.SourceTypeName != "null_resource" || req.TargetTypeName != "terraform_data" {
resp.Diagnostics = resp.Diagnostics.Append(
fmt.Errorf("unsupported move: %s -> %s; only move from null_resource to terraform_data is supported",
req.SourceTypeName, req.TargetTypeName))
return resp
}
nullTy := nullResourceSchema().Block.ImpliedType()
oldState, err := ctyjson.Unmarshal(req.SourceStateJSON, nullTy)
if err != nil {
resp.Diagnostics = resp.Diagnostics.Append(err)
return resp
}
oldStateMap := oldState.AsValueMap()
newStateMap := map[string]cty.Value{}
if trigger, ok := oldStateMap["triggers"]; ok && !trigger.IsNull() {
newStateMap["triggers_replace"] = cty.ObjectVal(trigger.AsValueMap())
}
if id, ok := oldStateMap["id"]; ok && !id.IsNull() {
newStateMap["id"] = id
}
currentSchema := dataStoreResourceSchema()
newState, err := currentSchema.Block.CoerceValue(cty.ObjectVal(newStateMap))
if err != nil {
resp.Diagnostics = resp.Diagnostics.Append(err)
return resp
}
resp.TargetState = newState
resp.TargetPrivate = req.SourcePrivate
return resp
}
func readDataStoreResourceState(req providers.ReadResourceRequest) (resp providers.ReadResourceResponse) {
resp.NewState = req.PriorState
return resp

View File

@ -6,6 +6,7 @@
package tf
import (
"bytes"
"strings"
"testing"
@ -82,6 +83,57 @@ func TestManagedDataUpgradeState(t *testing.T) {
}
}
func TestManagedDataMovedState(t *testing.T) {
nullSchema := nullResourceSchema()
nullTy := nullSchema.Block.ImpliedType()
state := cty.ObjectVal(map[string]cty.Value{
"triggers": cty.MapVal(map[string]cty.Value{
"examplekey": cty.StringVal("value"),
}),
"id": cty.StringVal("not-quite-unique"),
})
jsState, err := ctyjson.Marshal(state, nullTy)
if err != nil {
t.Fatal(err)
}
// empty request should fail
req := providers.MoveResourceStateRequest{}
resp := moveDataStoreResourceState(req)
if !resp.Diagnostics.HasErrors() {
t.Fatalf("expected error, got %#v", resp)
}
// valid request
req = providers.MoveResourceStateRequest{
TargetTypeName: "terraform_data",
SourceTypeName: "null_resource",
SourcePrivate: []byte("PRIVATE"),
SourceStateJSON: jsState,
}
resp = moveDataStoreResourceState(req)
expectedState := cty.ObjectVal(map[string]cty.Value{
"triggers_replace": cty.ObjectVal(map[string]cty.Value{
"examplekey": cty.StringVal("value"),
}),
"id": cty.StringVal("not-quite-unique"),
"input": cty.NullVal(cty.DynamicPseudoType),
"output": cty.NullVal(cty.DynamicPseudoType),
})
if !resp.TargetState.RawEquals(expectedState) {
t.Errorf("prior state was:\n%#v\nmoved state is:\n%#v\n", expectedState, resp.TargetState)
}
if !bytes.Equal(resp.TargetPrivate, req.SourcePrivate) {
t.Error("expected private data to be copied")
}
}
func TestManagedDataRead(t *testing.T) {
req := providers.ReadResourceRequest{
TypeName: "terraform_data",

View File

@ -11,6 +11,7 @@ This page will run you through the most important changes in OpenTofu 1.10:
- [New features](#new-features)
- [New built-in functions](#new-built-in-functions)
- [Moved for different resource types](#moved-for-different-resource-types)
- [Improvements to existing features](#improvements-to-existing-features)
- [External programs as encryption key providers (experimental)](#external-programs-as-encryption-key-providers-experimental)
- [Smaller improvements](#smaller-improvements)
@ -28,6 +29,11 @@ New builtin provider functions added ([#2306](https://github.com/opentofu/opento
- `provider::terraform::encode_tfvars` - Encode an object into a string with the same format as a TFVars file.
- `provider::terraform::encode_expr` - Encode an arbitrary expression into a string with valid OpenTofu syntax.
### Moved for different resource types
- `moved` block can now be used to migrate between different types of resources ([docs](../language/modules/develop/refactoring.mdx#changing-a-resource-type)).
- Builtin provider now supports migration from `null_resource` to `terraform_data` resource.
## Improvements to existing features
### External programs as encryption key providers (experimental)

View File

@ -63,6 +63,7 @@ resource "terraform_data" "bootstrap" {
}
```
`moved` block can be used to migrate from the `null_resource` to the `terraform_data` resource. [Migration guide](https://search.opentofu.org/provider/hashicorp/null/latest/docs/guides/terraform-migration)
## Argument Reference