Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package opentofu for openSUSE:Factory checked in at 2026-10-02 23:04:30 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/opentofu (Old) and /work/SRC/openSUSE:Factory/.opentofu.new.1631729 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "opentofu" Fri Oct 2 23:04:30 2026 rev:56 rq:1382000 version:1.13.1 Changes: -------- --- /work/SRC/openSUSE:Factory/opentofu/opentofu.changes 2026-10-01 16:49:28.932166299 +0200 +++ /work/SRC/openSUSE:Factory/.opentofu.new.1631729/opentofu.changes 2026-10-02 23:05:42.726239531 +0200 @@ -1,0 +2,11 @@ +Fri Oct 02 05:12:51 UTC 2026 - Johannes Kastl <[email protected]> + +- Update to version 1.13.1: + * BUG FIXES: + - tofu show -json <planfile> now works again when ephemeral + resources are present in the configuration. (4623). + - Ephemeral output values are now always re-evaluated during + the apply phase, instead of sometimes using stale values from + the planning phase. (#4582) + +------------------------------------------------------------------- Old: ---- opentofu-1.13.0.obscpio New: ---- opentofu-1.13.1.obscpio ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ opentofu.spec ++++++ --- /var/tmp/diff_new_pack.Sk3QrV/_old 2026-10-02 23:05:45.036336097 +0200 +++ /var/tmp/diff_new_pack.Sk3QrV/_new 2026-10-02 23:05:45.038336181 +0200 @@ -19,7 +19,7 @@ %define executable_name tofu Name: opentofu -Version: 1.13.0 +Version: 1.13.1 Release: 0 Summary: Declaratively manage your cloud infrastructure License: MPL-2.0 ++++++ _service ++++++ --- /var/tmp/diff_new_pack.Sk3QrV/_old 2026-10-02 23:05:45.242344709 +0200 +++ /var/tmp/diff_new_pack.Sk3QrV/_new 2026-10-02 23:05:45.262345545 +0200 @@ -3,7 +3,7 @@ <param name="url">https://github.com/opentofu/opentofu.git</param> <param name="scm">git</param> <param name="exclude">.git</param> - <param name="revision">refs/tags/v1.13.0</param> + <param name="revision">refs/tags/v1.13.1</param> <param name="versionformat">@PARENT_TAG@</param> <param name="versionrewrite-pattern">v(.*)</param> <param name="changesgenerate">enable</param> ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.Sk3QrV/_old 2026-10-02 23:05:45.371350101 +0200 +++ /var/tmp/diff_new_pack.Sk3QrV/_new 2026-10-02 23:05:45.390350896 +0200 @@ -3,6 +3,6 @@ <param name="url">https://github.com/opentofu/opentofu/</param> <param name="changesrevision">bdcbf091aab886499ed7718d7f558b428fffce39</param></service><service name="tar_scm"> <param name="url">https://github.com/opentofu/opentofu.git</param> - <param name="changesrevision">2b6193043d500dcfef1f3b4f4819b938b667099f</param></service></servicedata> + <param name="changesrevision">233700b795b6d2372f1c56ed57a4abaec9d36475</param></service></servicedata> (No newline at EOF) ++++++ opentofu-1.13.0.obscpio -> opentofu-1.13.1.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/opentofu-1.13.0/CHANGELOG.md new/opentofu-1.13.1/CHANGELOG.md --- old/opentofu-1.13.0/CHANGELOG.md 2026-09-30 14:59:06.000000000 +0200 +++ new/opentofu-1.13.1/CHANGELOG.md 2026-10-01 18:03:40.000000000 +0200 @@ -1,5 +1,12 @@ The v1.13.x release series is supported until **August 1 2027**. +## 1.13.1 + +BUG FIXES: + +- `tofu show -json <planfile>` now works again when ephemeral resources are present in the configuration. ([4623](https://github.com/opentofu/opentofu/pull/4623)). +- Ephemeral output values are now always re-evaluated during the apply phase, instead of sometimes using stale values from the planning phase. ([#4582](https://github.com/opentofu/opentofu/issues/4582)) + ## 1.13.0 UPGRADE NOTES: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/opentofu-1.13.0/internal/command/e2etest/plan_test.go new/opentofu-1.13.1/internal/command/e2etest/plan_test.go --- old/opentofu-1.13.0/internal/command/e2etest/plan_test.go 2026-09-30 14:59:06.000000000 +0200 +++ new/opentofu-1.13.1/internal/command/e2etest/plan_test.go 2026-10-01 18:03:40.000000000 +0200 @@ -7,11 +7,14 @@ import ( "path/filepath" + "slices" "strings" "testing" "github.com/google/go-cmp/cmp" + "github.com/opentofu/opentofu/internal/addrs" "github.com/opentofu/opentofu/internal/e2e" + "github.com/opentofu/opentofu/internal/plans" ) // The tests in this file are for the following sequence: @@ -165,3 +168,86 @@ } }) } + +// TestPlanShowWithEmbeddedSchema is a regression test for [the bug](https://github.com/opentofu/opentofu/issues/4622) where +// a configuration containing ephemeral is shown correctly in json format with the embedded schema in the plan. +func TestPlanShowWithEmbeddedSchema(t *testing.T) { + t.Parallel() + + workdir := "testdata/plan-with-embedded-ephemeral-config" + buildSimpleProvider(t, "6", workdir, "simple") + tf := e2e.NewBinary(t, tofuBin, workdir) + + { // INIT + _, stderr, err := tf.Run("init", "-plugin-dir=cache") + if err != nil { + t.Fatalf("unexpected init error: %s\nstderr:\n%s", err, stderr) + } + } + + { // PLAN + stdout, stderr, err := tf.Run("plan", "-out=tfplan") + if err != nil { + t.Fatalf("unexpected plan error: %s\nstderr:\n%s", err, stderr) + } + expectedChangesOutput := `` + + checker := outputEntriesChecker{ + outputCheckContains{[]string{"ephemeral.simple_resource.test_ephemeral: Opening..."}, true}, + outputCheckContains{[]string{"ephemeral.simple_resource.test_ephemeral: Open complete after"}, true}, + outputCheckContains{[]string{"ephemeral.simple_resource.test_ephemeral: Closing..."}, true}, + outputCheckContains{[]string{"ephemeral.simple_resource.test_ephemeral: Close complete after"}, true}, + outputCheckContains{[]string{`+ resource "simple_resource" "test_res"`}, true}, + } + out := stripAnsi(stdout) + + if !strings.Contains(out, expectedChangesOutput) { + t.Errorf("wrong plan output:\nstdout:%s\nstderr:%s", stdout, stderr) + t.Log(cmp.Diff(out, expectedChangesOutput)) + } + checker.check(t, "plan", out) + + // assert plan file content + plan, err := tf.Plan("tfplan") + if err != nil { + t.Fatalf("failed to read the plan file: %s", err) + } + idx := slices.IndexFunc(plan.Changes.Resources, func(src *plans.ResourceInstanceChangeSrc) bool { + return src.Addr.Resource.Resource.Mode == addrs.EphemeralResourceMode + }) + if idx >= 0 { + t.Fatalf("ephemeral resource found in the plan file. expected to have no ephemeral resource") + } + } + { // SHOW -json + stdout, stderr, err := tf.Run("show", "-json", "tfplan") + if err != nil { + t.Fatalf("unexpected plan error: %s\nstderr:\n%s", err, stderr) + } + // a better way to do this would be to enhance `jsonconfig` package with capabilities to unmarshal the generated output and do better checks on the structured data. + // But to add such a complex logic strictly for this particular test does not worth right now. If we'll find more use cases where that would be useful + // we can create that later and replace this assertion. + checker := outputEntriesChecker{ + outputCheckNumberOfOccurrences{ + token: `{"address":"ephemeral.simple_resource.test_ephemeral","mode":"ephemeral","type":"simple_resource","name":"test_ephemeral","provider_config_key":"simple"`, + wantedOccurrences: 1, + }, + } + out := stripAnsi(stdout) + checker.check(t, "show -json", out) + } + { // SHOW (human) + stdout, stderr, err := tf.Run("show", "tfplan") + if err != nil { + t.Fatalf("unexpected plan error: %s\nstderr:\n%s", err, stderr) + } + checker := outputEntriesChecker{ + outputCheckNumberOfOccurrences{ + token: `+ resource "simple_resource" "test_res"`, + wantedOccurrences: 1, + }, + } + out := stripAnsi(stdout) + checker.check(t, "show", out) + } +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/opentofu-1.13.0/internal/command/e2etest/primary_test.go new/opentofu-1.13.1/internal/command/e2etest/primary_test.go --- old/opentofu-1.13.0/internal/command/e2etest/primary_test.go 2026-09-30 14:59:06.000000000 +0200 +++ new/opentofu-1.13.1/internal/command/e2etest/primary_test.go 2026-10-01 18:03:40.000000000 +0200 @@ -796,6 +796,23 @@ } } +type outputCheckNumberOfOccurrences struct { + token string + wantedOccurrences int +} + +func (oe outputCheckNumberOfOccurrences) check(t *testing.T, hint, in string) { + var found int + for line := range strings.SplitSeq(in, "\n") { + if strings.Contains(line, oe.token) { + found++ + } + } + if oe.wantedOccurrences != found { + t.Errorf("[%s] different number of occurrences %q. Wanted %d but got %d\nout:%s", hint, oe.token, oe.wantedOccurrences, found, in) + } +} + type outputCheckContains struct { variants []string strict bool diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/opentofu-1.13.0/internal/command/e2etest/testdata/plan-with-embedded-ephemeral-config/main.tf new/opentofu-1.13.1/internal/command/e2etest/testdata/plan-with-embedded-ephemeral-config/main.tf --- old/opentofu-1.13.0/internal/command/e2etest/testdata/plan-with-embedded-ephemeral-config/main.tf 1970-01-01 01:00:00.000000000 +0100 +++ new/opentofu-1.13.1/internal/command/e2etest/testdata/plan-with-embedded-ephemeral-config/main.tf 2026-10-01 18:03:40.000000000 +0200 @@ -0,0 +1,17 @@ +// the provider-plugin tests uses the -plugin-cache flag so terraform pulls the +// test binaries instead of reaching out to the registry. +terraform { + required_providers { + simple = { + source = "registry.opentofu.org/hashicorp/simple" + } + } +} + +resource "simple_resource" "test_res" { + value = "test value" +} + +ephemeral "simple_resource" "test_ephemeral" { + value = "ephemeral value" +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/opentofu-1.13.0/internal/plans/internal/planproto/planfile.pb.go new/opentofu-1.13.1/internal/plans/internal/planproto/planfile.pb.go --- old/opentofu-1.13.0/internal/plans/internal/planproto/planfile.pb.go 2026-09-30 14:59:06.000000000 +0200 +++ new/opentofu-1.13.1/internal/plans/internal/planproto/planfile.pb.go 2026-10-01 18:03:40.000000000 +0200 @@ -1719,12 +1719,13 @@ // ProviderSchema is a trimmed-down subset of a single provider's schema, // containing only the parts needed to render a particular saved plan. type ProviderSchema struct { - state protoimpl.MessageState `protogen:"open.v1"` - ProviderConfig *Schema `protobuf:"bytes,1,opt,name=provider_config,json=providerConfig,proto3" json:"provider_config,omitempty"` - ManagedResourceTypes map[string]*ResourceSchema `protobuf:"bytes,2,rep,name=managed_resource_types,json=managedResourceTypes,proto3" json:"managed_resource_types,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` - DataSources map[string]*ResourceSchema `protobuf:"bytes,3,rep,name=data_sources,json=dataSources,proto3" json:"data_sources,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + ProviderConfig *Schema `protobuf:"bytes,1,opt,name=provider_config,json=providerConfig,proto3" json:"provider_config,omitempty"` + ManagedResourceTypes map[string]*ResourceSchema `protobuf:"bytes,2,rep,name=managed_resource_types,json=managedResourceTypes,proto3" json:"managed_resource_types,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + DataSources map[string]*ResourceSchema `protobuf:"bytes,3,rep,name=data_sources,json=dataSources,proto3" json:"data_sources,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + EphemeralResourceTypes map[string]*ResourceSchema `protobuf:"bytes,4,rep,name=ephemeral_resource_types,json=ephemeralResourceTypes,proto3" json:"ephemeral_resource_types,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *ProviderSchema) Reset() { @@ -1778,6 +1779,13 @@ return nil } +func (x *ProviderSchema) GetEphemeralResourceTypes() map[string]*ResourceSchema { + if x != nil { + return x.EphemeralResourceTypes + } + return nil +} + // Schemas is the root message for the "tfschemas" entry in the plan file // archive: a map from provider source address (e.g. // "registry.opentofu.org/hashicorp/aws") to the trimmed schema for that @@ -2156,16 +2164,20 @@ "\x04body\x18\x02 \x01(\v2\x14.tfplan.SchemaObjectR\x04body\"\x81\x01\n" + "\x0eResourceSchema\x12&\n" + "\x06schema\x18\x01 \x01(\v2\x0e.tfplan.SchemaR\x06schema\x12G\n" + - "\x0fidentity_schema\x18\x02 \x01(\v2\x1e.tfplan.ResourceIdentitySchemaR\x0eidentitySchema\"\xb6\x03\n" + + "\x0fidentity_schema\x18\x02 \x01(\v2\x1e.tfplan.ResourceIdentitySchemaR\x0eidentitySchema\"\x87\x05\n" + "\x0eProviderSchema\x127\n" + "\x0fprovider_config\x18\x01 \x01(\v2\x0e.tfplan.SchemaR\x0eproviderConfig\x12f\n" + "\x16managed_resource_types\x18\x02 \x03(\v20.tfplan.ProviderSchema.ManagedResourceTypesEntryR\x14managedResourceTypes\x12J\n" + - "\fdata_sources\x18\x03 \x03(\v2'.tfplan.ProviderSchema.DataSourcesEntryR\vdataSources\x1a_\n" + + "\fdata_sources\x18\x03 \x03(\v2'.tfplan.ProviderSchema.DataSourcesEntryR\vdataSources\x12l\n" + + "\x18ephemeral_resource_types\x18\x04 \x03(\v22.tfplan.ProviderSchema.EphemeralResourceTypesEntryR\x16ephemeralResourceTypes\x1a_\n" + "\x19ManagedResourceTypesEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12,\n" + "\x05value\x18\x02 \x01(\v2\x16.tfplan.ResourceSchemaR\x05value:\x028\x01\x1aV\n" + "\x10DataSourcesEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12,\n" + + "\x05value\x18\x02 \x01(\v2\x16.tfplan.ResourceSchemaR\x05value:\x028\x01\x1aa\n" + + "\x1bEphemeralResourceTypesEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12,\n" + "\x05value\x18\x02 \x01(\v2\x16.tfplan.ResourceSchemaR\x05value:\x028\x01\"\x9d\x01\n" + "\aSchemas\x12<\n" + "\tproviders\x18\x01 \x03(\v2\x1e.tfplan.Schemas.ProvidersEntryR\tproviders\x1aT\n" + @@ -2232,7 +2244,7 @@ } var file_planfile_proto_enumTypes = make([]protoimpl.EnumInfo, 6) -var file_planfile_proto_msgTypes = make([]protoimpl.MessageInfo, 25) +var file_planfile_proto_msgTypes = make([]protoimpl.MessageInfo, 26) var file_planfile_proto_goTypes = []any{ (Mode)(0), // 0: tfplan.Mode (Action)(0), // 1: tfplan.Action @@ -2264,7 +2276,8 @@ (*Path_Step)(nil), // 27: tfplan.Path.Step nil, // 28: tfplan.ProviderSchema.ManagedResourceTypesEntry nil, // 29: tfplan.ProviderSchema.DataSourcesEntry - nil, // 30: tfplan.Schemas.ProvidersEntry + nil, // 30: tfplan.ProviderSchema.EphemeralResourceTypesEntry + nil, // 31: tfplan.Schemas.ProvidersEntry } var file_planfile_proto_depIdxs = []int32{ 0, // 0: tfplan.Plan.ui_mode:type_name -> tfplan.Mode @@ -2306,19 +2319,21 @@ 19, // 36: tfplan.ProviderSchema.provider_config:type_name -> tfplan.Schema 28, // 37: tfplan.ProviderSchema.managed_resource_types:type_name -> tfplan.ProviderSchema.ManagedResourceTypesEntry 29, // 38: tfplan.ProviderSchema.data_sources:type_name -> tfplan.ProviderSchema.DataSourcesEntry - 30, // 39: tfplan.Schemas.providers:type_name -> tfplan.Schemas.ProvidersEntry - 12, // 40: tfplan.Plan.VariablesEntry.value:type_name -> tfplan.DynamicValue - 13, // 41: tfplan.Plan.resource_attr.attr:type_name -> tfplan.Path - 4, // 42: tfplan.CheckResults.ObjectResult.status:type_name -> tfplan.CheckResults.Status - 12, // 43: tfplan.Path.Step.element_key:type_name -> tfplan.DynamicValue - 21, // 44: tfplan.ProviderSchema.ManagedResourceTypesEntry.value:type_name -> tfplan.ResourceSchema - 21, // 45: tfplan.ProviderSchema.DataSourcesEntry.value:type_name -> tfplan.ResourceSchema - 22, // 46: tfplan.Schemas.ProvidersEntry.value:type_name -> tfplan.ProviderSchema - 47, // [47:47] is the sub-list for method output_type - 47, // [47:47] is the sub-list for method input_type - 47, // [47:47] is the sub-list for extension type_name - 47, // [47:47] is the sub-list for extension extendee - 0, // [0:47] is the sub-list for field type_name + 30, // 39: tfplan.ProviderSchema.ephemeral_resource_types:type_name -> tfplan.ProviderSchema.EphemeralResourceTypesEntry + 31, // 40: tfplan.Schemas.providers:type_name -> tfplan.Schemas.ProvidersEntry + 12, // 41: tfplan.Plan.VariablesEntry.value:type_name -> tfplan.DynamicValue + 13, // 42: tfplan.Plan.resource_attr.attr:type_name -> tfplan.Path + 4, // 43: tfplan.CheckResults.ObjectResult.status:type_name -> tfplan.CheckResults.Status + 12, // 44: tfplan.Path.Step.element_key:type_name -> tfplan.DynamicValue + 21, // 45: tfplan.ProviderSchema.ManagedResourceTypesEntry.value:type_name -> tfplan.ResourceSchema + 21, // 46: tfplan.ProviderSchema.DataSourcesEntry.value:type_name -> tfplan.ResourceSchema + 21, // 47: tfplan.ProviderSchema.EphemeralResourceTypesEntry.value:type_name -> tfplan.ResourceSchema + 22, // 48: tfplan.Schemas.ProvidersEntry.value:type_name -> tfplan.ProviderSchema + 49, // [49:49] is the sub-list for method output_type + 49, // [49:49] is the sub-list for method input_type + 49, // [49:49] is the sub-list for extension type_name + 49, // [49:49] is the sub-list for extension extendee + 0, // [0:49] is the sub-list for field type_name } func init() { file_planfile_proto_init() } @@ -2336,7 +2351,7 @@ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_planfile_proto_rawDesc), len(file_planfile_proto_rawDesc)), NumEnums: 6, - NumMessages: 25, + NumMessages: 26, NumExtensions: 0, NumServices: 0, }, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/opentofu-1.13.0/internal/plans/internal/planproto/planfile.proto new/opentofu-1.13.1/internal/plans/internal/planproto/planfile.proto --- old/opentofu-1.13.0/internal/plans/internal/planproto/planfile.proto 2026-09-30 14:59:06.000000000 +0200 +++ new/opentofu-1.13.1/internal/plans/internal/planproto/planfile.proto 2026-10-01 18:03:40.000000000 +0200 @@ -452,6 +452,7 @@ Schema provider_config = 1; map<string, ResourceSchema> managed_resource_types = 2; map<string, ResourceSchema> data_sources = 3; + map<string, ResourceSchema> ephemeral_resource_types = 4; } // Schemas is the root message for the "tfschemas" entry in the plan file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/opentofu-1.13.0/internal/plans/planfile/schema_convert.go new/opentofu-1.13.1/internal/plans/planfile/schema_convert.go --- old/opentofu-1.13.0/internal/plans/planfile/schema_convert.go 2026-09-30 14:59:06.000000000 +0200 +++ new/opentofu-1.13.1/internal/plans/planfile/schema_convert.go 2026-10-01 18:03:40.000000000 +0200 @@ -236,9 +236,10 @@ // into its protobuf representation. func providerSchemaToProto(s providers.ProviderSchema) *planproto.ProviderSchema { ps := &planproto.ProviderSchema{ - ProviderConfig: schemaToProto(s.Provider), - ManagedResourceTypes: make(map[string]*planproto.ResourceSchema, len(s.ResourceTypes)), - DataSources: make(map[string]*planproto.ResourceSchema, len(s.DataSources)), + ProviderConfig: schemaToProto(s.Provider), + ManagedResourceTypes: make(map[string]*planproto.ResourceSchema, len(s.ResourceTypes)), + DataSources: make(map[string]*planproto.ResourceSchema, len(s.DataSources)), + EphemeralResourceTypes: make(map[string]*planproto.ResourceSchema, len(s.EphemeralResources)), } for name, schema := range s.ResourceTypes { ps.ManagedResourceTypes[name] = resourceSchemaToProto(schema) @@ -246,14 +247,18 @@ for name, schema := range s.DataSources { ps.DataSources[name] = resourceSchemaToProto(schema) } + for name, schema := range s.EphemeralResources { + ps.EphemeralResourceTypes[name] = resourceSchemaToProto(schema) + } return ps } func providerSchemaFromProto(ps *planproto.ProviderSchema) providers.ProviderSchema { s := providers.ProviderSchema{ - Provider: schemaFromProto(ps.ProviderConfig), - ResourceTypes: make(map[string]providers.Schema, len(ps.ManagedResourceTypes)), - DataSources: make(map[string]providers.Schema, len(ps.DataSources)), + Provider: schemaFromProto(ps.ProviderConfig), + ResourceTypes: make(map[string]providers.Schema, len(ps.ManagedResourceTypes)), + DataSources: make(map[string]providers.Schema, len(ps.DataSources)), + EphemeralResources: make(map[string]providers.Schema, len(ps.EphemeralResourceTypes)), } for name, rs := range ps.ManagedResourceTypes { s.ResourceTypes[name] = resourceSchemaFromProto(rs) @@ -261,5 +266,8 @@ for name, rs := range ps.DataSources { s.DataSources[name] = resourceSchemaFromProto(rs) } + for name, rs := range ps.EphemeralResourceTypes { + s.EphemeralResources[name] = resourceSchemaFromProto(rs) + } return s } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/opentofu-1.13.0/internal/plans/planfile/schema_trim.go new/opentofu-1.13.1/internal/plans/planfile/schema_trim.go --- old/opentofu-1.13.0/internal/plans/planfile/schema_trim.go 2026-09-30 14:59:06.000000000 +0200 +++ new/opentofu-1.13.1/internal/plans/planfile/schema_trim.go 2026-10-01 18:03:40.000000000 +0200 @@ -21,14 +21,16 @@ // plan (its configuration, prior state, or proposed changes). // We dont need ephemeral as they dont get stored in the config type referencedTypes struct { - managed map[string]struct{} - data map[string]struct{} + managed map[string]struct{} + data map[string]struct{} + ephemeral map[string]struct{} } func newReferencedTypes() *referencedTypes { return &referencedTypes{ - managed: make(map[string]struct{}), - data: make(map[string]struct{}), + managed: make(map[string]struct{}), + data: make(map[string]struct{}), + ephemeral: make(map[string]struct{}), } } @@ -38,6 +40,8 @@ rt.managed[typeName] = struct{}{} case addrs.DataResourceMode: rt.data[typeName] = struct{}{} + case addrs.EphemeralResourceMode: + rt.ephemeral[typeName] = struct{}{} default: } } @@ -71,6 +75,9 @@ for _, r := range c.Module.DataResources { get(r.Provider).addType(r.Mode, r.Type) } + for _, r := range c.Module.EphemeralResources { + get(r.Provider).addType(r.Mode, r.Type) + } } } @@ -138,9 +145,10 @@ } trimmed := providers.ProviderSchema{ - Provider: full.Provider, - ResourceTypes: make(map[string]providers.Schema, len(rt.managed)), - DataSources: make(map[string]providers.Schema, len(rt.data)), + Provider: full.Provider, + ResourceTypes: make(map[string]providers.Schema, len(rt.managed)), + DataSources: make(map[string]providers.Schema, len(rt.data)), + EphemeralResources: make(map[string]providers.Schema, len(rt.ephemeral)), } for typeName := range rt.managed { @@ -155,6 +163,12 @@ missing = append(missing, fmt.Sprintf("data source %q of provider %s", typeName, provider)) } } + for typeName := range rt.ephemeral { + trimmed.EphemeralResources[typeName], ok = full.EphemeralResources[typeName] + if !ok { + missing = append(missing, fmt.Sprintf("ephemeral resource type %q of provider %s", typeName, provider)) + } + } result[provider] = trimmed } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/opentofu-1.13.0/internal/plans/planfile/testdata/test-config/root.tf new/opentofu-1.13.1/internal/plans/planfile/testdata/test-config/root.tf --- old/opentofu-1.13.0/internal/plans/planfile/testdata/test-config/root.tf 2026-09-30 14:59:06.000000000 +0200 +++ new/opentofu-1.13.1/internal/plans/planfile/testdata/test-config/root.tf 2026-10-01 18:03:40.000000000 +0200 @@ -8,3 +8,7 @@ source = "example.com/foo/bar_b/baz" version = ">= 1.0.0" } + +resource "test_resource" "res_test" {} +data "test_data" "data_test" {} +ephemeral "test_ephemeral" "ephemeral_test" {} \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/opentofu-1.13.0/internal/tofu/context_apply2_test.go new/opentofu-1.13.1/internal/tofu/context_apply2_test.go --- old/opentofu-1.13.0/internal/tofu/context_apply2_test.go 2026-09-30 14:59:06.000000000 +0200 +++ new/opentofu-1.13.1/internal/tofu/context_apply2_test.go 2026-10-01 18:03:40.000000000 +0200 @@ -7353,3 +7353,109 @@ } } + +// TestContext2Apply_ephemeralOutputCrossModuleWriteOnly checks that a value produced +// by an ephemeral resource and shared via an `ephemeral = true` module output is +// exporting identically at two different write-only attribute consumers: one in the +// same module as the ephemeral resource, and one in a different module reached only +// through the output. +func TestContext2Apply_ephemeralOutputCrossModuleWriteOnly(t *testing.T) { + SkipExperimental(t, ExperimentalFlagUnknown) + + m := testModuleInline(t, map[string]string{ + "child/main.tf": ` + ephemeral "test_ephemeral_resource" "generate" { + input = "seed" + } + + resource "test_instance" "child" { + vpc_id = "child" + value_wo = ephemeral.test_ephemeral_resource.generate.secret + } + + output "secret" { + ephemeral = true + value = ephemeral.test_ephemeral_resource.generate.secret + } + `, + "main.tf": ` + module "child" { + source = "./child" + } + + resource "test_instance" "root" { + vpc_id = "root" + value_wo = module.child.secret + } + `, + }) + + provider := testProvider("test") + + openCount := 0 + provider.OpenEphemeralResourceFn = func(req providers.OpenEphemeralResourceRequest) providers.OpenEphemeralResourceResponse { + openCount++ + return providers.OpenEphemeralResourceResponse{ + Result: cty.ObjectVal(map[string]cty.Value{ + "id": cty.StringVal("id"), + "secret": cty.StringVal(fmt.Sprintf("generated-%d", openCount)), + "input": req.Config.GetAttr("input"), + }), + } + } + + // Write-only attributes must never appear in planned state, so null it out here + provider.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { + planned := req.ProposedNewState.AsValueMap() + if _, ok := planned["value_wo"]; ok { + planned["value_wo"] = cty.NullVal(cty.String) + } + if planned["id"].IsNull() { + planned["id"] = cty.UnknownVal(cty.String) + } + return providers.PlanResourceChangeResponse{ + PlannedState: cty.ObjectVal(planned), + } + } + + // Capture the write-only value actually sent to ApplyResourceChange for each of the two resources + applied := map[string]string{} + provider.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { + newState := req.PlannedState.AsValueMap() + vpcID := newState["vpc_id"].AsString() + newState["id"] = cty.StringVal("id-" + vpcID) + + wo := req.Config.GetAttr("value_wo") + if !wo.IsNull() { + applied[vpcID] = wo.AsString() + } + + return providers.ApplyResourceChangeResponse{ + NewState: cty.ObjectVal(newState), + } + } + + ctx := testContext2(t, &ContextOpts{ + Plugins: plugins.NewLibrary(map[addrs.Provider]providers.Factory{ + addrs.NewDefaultProvider("test"): testProviderFuncFixed(provider), + }, nil), + }) + + plan, diags := ctx.Plan(context.Background(), m, states.NewState(), &PlanOpts{ + Mode: plans.NormalMode, + }) + assertNoErrors(t, diags) + + _, diags = ctx.Apply(context.Background(), plan, m, nil) + assertNoErrors(t, diags) + + if applied["child"] == "" || applied["root"] == "" { + t.Fatalf("did not capture write-only values for both resources: %#v", applied) + } + if applied["child"] != applied["root"] { + t.Errorf( + "write-only values diverged across the module boundary: child=%q root=%q (ephemeral resource opened %d times)", + applied["child"], applied["root"], openCount, + ) + } +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/opentofu-1.13.0/internal/tofu/node_output.go new/opentofu-1.13.1/internal/tofu/node_output.go --- old/opentofu-1.13.0/internal/tofu/node_output.go 2026-09-30 14:59:06.000000000 +0200 +++ new/opentofu-1.13.1/internal/tofu/node_output.go 2026-10-01 18:03:40.000000000 +0200 @@ -344,8 +344,12 @@ } // If there was no change recorded, or the recorded change was not wholly - // known, then we need to re-evaluate the output - if !changeRecorded || !val.IsWhollyKnown() { + // known, then we need to re-evaluate the output. + // + // Ephemeral outputs must also always be re-evaluated, so their value stays + // consistent with other consumers of the same ephemeral resource in this apply, + // instead of exporting a stale plan-time value across the module boundary. + if !changeRecorded || !val.IsWhollyKnown() || n.Config.Ephemeral { switch { // If the module is not being overridden, we proceed normally case !n.Config.IsOverridden: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/opentofu-1.13.0/version/VERSION new/opentofu-1.13.1/version/VERSION --- old/opentofu-1.13.0/version/VERSION 2026-09-30 14:59:06.000000000 +0200 +++ new/opentofu-1.13.1/version/VERSION 2026-10-01 18:03:40.000000000 +0200 @@ -1 +1 @@ -1.13.0 +1.13.1 ++++++ opentofu.obsinfo ++++++ --- /var/tmp/diff_new_pack.Sk3QrV/_old 2026-10-02 23:05:51.927624166 +0200 +++ /var/tmp/diff_new_pack.Sk3QrV/_new 2026-10-02 23:05:51.937624584 +0200 @@ -1,5 +1,5 @@ name: opentofu -version: 1.13.0 -mtime: 1790773146 -commit: 2b6193043d500dcfef1f3b4f4819b938b667099f +version: 1.13.1 +mtime: 1790870620 +commit: 233700b795b6d2372f1c56ed57a4abaec9d36475 ++++++ vendor.tar.gz ++++++ /work/SRC/openSUSE:Factory/opentofu/vendor.tar.gz /work/SRC/openSUSE:Factory/.opentofu.new.1631729/vendor.tar.gz differ: char 134, line 3
