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-01-27 16:11:15
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/opentofu (Old)
 and      /work/SRC/openSUSE:Factory/.opentofu.new.1928 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "opentofu"

Tue Jan 27 16:11:15 2026 rev:43 rq:1329325 version:1.11.4

Changes:
--------
--- /work/SRC/openSUSE:Factory/opentofu/opentofu.changes        2026-01-14 
16:23:45.494609473 +0100
+++ /work/SRC/openSUSE:Factory/.opentofu.new.1928/opentofu.changes      
2026-01-27 16:11:57.729169153 +0100
@@ -1,0 +2,33 @@
+Thu Jan 22 06:29:13 UTC 2026 - Johannes Kastl 
<[email protected]>
+
+- Update to version 1.11.4:
+  * SECURITY ADVISORIES:
+    - Previous releases in the v1.11 series could potentially take
+      an excessive amount of time processing a maliciously-crafted
+      .zip archive during either provider or module installation
+      during tofu init. (#3689)
+  * BREAKING CHANGES:
+    - Modules containing local provider configurations now also
+      reject the enabled argument, matching existing behavior for
+      count, for_each, and depends_on. (#3680)
+    - This was an oversight in the original design of the enabled
+      feature and was missed during the review process. Although
+      our goal is to not introduce breaking changes in patch
+      releases, in some cases it may be warranted. Anyone who has
+      used the enabled feature in this particular way will have
+      unintentionally introduced a foot-gun into their
+      infrastructure and should remedy it post-haste.
+  * BUG FIXES:
+    - In JSON syntax, the state encryption method configuration now
+      allows specifying keys using both normal expression syntax
+      and using template interpolation syntax. Previously only the
+      template interpolation syntax was allowed, which was
+      inconsistent with other parts of the encryption
+      configuration. (#3654)
+    - Providers are not configured anymore with DeferralAllowed
+      capability of OpenTofu since having that created unwanted
+      behaviour from some providers. (#3676)
+    - Resources containing write-only attributes now are rendered
+      consistently during planning. (#3667)
+
+-------------------------------------------------------------------

Old:
----
  opentofu-1.11.3.obscpio

New:
----
  opentofu-1.11.4.obscpio

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ opentofu.spec ++++++
--- /var/tmp/diff_new_pack.QTVh3R/_old  2026-01-27 16:11:58.877217062 +0100
+++ /var/tmp/diff_new_pack.QTVh3R/_new  2026-01-27 16:11:58.881217229 +0100
@@ -19,7 +19,7 @@
 %define executable_name tofu
 
 Name:           opentofu
-Version:        1.11.3
+Version:        1.11.4
 Release:        0
 Summary:        Declaratively manage your cloud infrastructure
 License:        MPL-2.0
@@ -29,7 +29,7 @@
 Source1:        vendor.tar.gz
 Source99:       opentofu-rpmlintrc
 BuildRequires:  bash-completion
-BuildRequires:  go1.25 >= 1.25.5
+BuildRequires:  go1.25 >= 1.25.6
 BuildRequires:  golang-packaging
 # See: https://github.com/hashicorp/opentofu/issues/22807
 ExcludeArch:    %{ix86} %{arm}

++++++ _service ++++++
--- /var/tmp/diff_new_pack.QTVh3R/_old  2026-01-27 16:11:58.937219566 +0100
+++ /var/tmp/diff_new_pack.QTVh3R/_new  2026-01-27 16:11:58.953220234 +0100
@@ -3,7 +3,7 @@
     <param name="url">https://github.com/opentofu/opentofu/</param>
     <param name="scm">git</param>
     <param name="exclude">.git</param>
-    <param name="revision">v1.11.3</param>
+    <param name="revision">v1.11.4</param>
     <param name="versionformat">@PARENT_TAG@</param>
     <param name="versionrewrite-pattern">v(.*)</param>
     <param name="changesgenerate">enable</param>

++++++ _servicedata ++++++
--- /var/tmp/diff_new_pack.QTVh3R/_old  2026-01-27 16:11:58.981221402 +0100
+++ /var/tmp/diff_new_pack.QTVh3R/_new  2026-01-27 16:11:58.985221569 +0100
@@ -1,6 +1,6 @@
 <servicedata>
 <service name="tar_scm">
                 <param name="url">https://github.com/opentofu/opentofu/</param>
-              <param 
name="changesrevision">f53d9e3a6f6e04a4f5f22b7c32c1ddc1272ba547</param></service></servicedata>
+              <param 
name="changesrevision">2a9b7ac61409f7f22bde65c192c5814eb4b75cdf</param></service></servicedata>
 (No newline at EOF)
 

++++++ opentofu-1.11.3.obscpio -> opentofu-1.11.4.obscpio ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/opentofu-1.11.3/.go-version 
new/opentofu-1.11.4/.go-version
--- old/opentofu-1.11.3/.go-version     2026-01-13 18:01:54.000000000 +0100
+++ new/opentofu-1.11.4/.go-version     2026-01-21 16:57:28.000000000 +0100
@@ -1 +1 @@
-1.25.5
+1.25.6
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/opentofu-1.11.3/CHANGELOG.md 
new/opentofu-1.11.4/CHANGELOG.md
--- old/opentofu-1.11.3/CHANGELOG.md    2026-01-13 18:01:54.000000000 +0100
+++ new/opentofu-1.11.4/CHANGELOG.md    2026-01-21 16:57:28.000000000 +0100
@@ -1,6 +1,24 @@
 The v1.11.x release series is supported until **August 1 2026**.
 
-## 1.11.4 (Unreleased)
+## 1.11.5 (Unreleased)
+
+## 1.11.4
+
+SECURITY ADVISORIES:
+
+* Previous releases in the v1.11 series could potentially take an excessive 
amount of time processing a maliciously-crafted `.zip` archive during either 
provider or module installation during `tofu init`. 
([#3689](https://github.com/opentofu/opentofu/pull/3689))
+
+BREAKING CHANGES:
+
+* Modules containing local provider configurations now also reject the 
`enabled` argument, matching existing behavior for `count`, `for_each`, and 
`depends_on`. ([#3680](https://github.com/opentofu/opentofu/pull/3680))
+
+    This was an oversight in the original design of the enabled feature and 
was missed during the review process. Although our goal is to not introduce 
breaking changes in patch releases, in some cases it may be warranted. Anyone 
who has used the enabled feature in this particular way will have 
unintentionally introduced a foot-gun into their infrastructure and should 
remedy it post-haste.
+
+BUG FIXES:
+
+* In JSON syntax, the state encryption method configuration now allows 
specifying keys using both normal expression syntax and using template 
interpolation syntax. Previously only the template interpolation syntax was 
allowed, which was inconsistent with other parts of the encryption 
configuration. ([#3654](https://github.com/opentofu/opentofu/issues/3654))
+* Providers are not configured anymore with `DeferralAllowed` capability of 
OpenTofu since having that created unwanted behaviour from some providers. 
([#3676](https://github.com/opentofu/opentofu/pull/3676))
+* Resources containing write-only attributes now are rendered consistently 
during planning. ([#3667](https://github.com/opentofu/opentofu/pull/3667))
 
 ## 1.11.3
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/opentofu-1.11.3/go.mod new/opentofu-1.11.4/go.mod
--- old/opentofu-1.11.3/go.mod  2026-01-13 18:01:54.000000000 +0100
+++ new/opentofu-1.11.4/go.mod  2026-01-21 16:57:28.000000000 +0100
@@ -1,6 +1,6 @@
 module github.com/opentofu/opentofu
 
-go 1.25.5
+go 1.25.6
 
 // At the time of adding this configuration, the new Go feature introduced 
here https://github.com/golang/go/issues/67061,
 // was having a good amount of issues linked to, affecting AWS Firewall, GCP 
various services and a lot more.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/opentofu-1.11.3/internal/command/jsonformat/differ/block.go 
new/opentofu-1.11.4/internal/command/jsonformat/differ/block.go
--- old/opentofu-1.11.3/internal/command/jsonformat/differ/block.go     
2026-01-13 18:01:54.000000000 +0100
+++ new/opentofu-1.11.4/internal/command/jsonformat/differ/block.go     
2026-01-21 16:57:28.000000000 +0100
@@ -28,44 +28,40 @@
        blockValue := change.AsMap()
 
        attributes := make(map[string]computed.Diff)
-       for key, attr := range block.Attributes {
-               childValue := blockValue.GetChild(key)
 
-               if !childValue.RelevantAttributes.MatchesPartial() {
-                       // Mark non-relevant attributes as unchanged.
-                       childValue = childValue.AsNoOp()
+       // In the first iteration we generate the diffs for all non write-only 
attributes
+       // and only collect the write-only attributes for a second run.
+       // This is necessary because [block.Attributes] is a map and since the 
order is not
+       // guarantee in a map we cannot reliably include all the write-only 
attributes
+       // in the rendered diff. Therefore, we generate the diffs for all non 
write-only attributes,
+       // which will generate the actual action of the resource, action that 
will decide if write-only
+       // attributes will be included in the rendered output or not.
+       var writeOnlyAttributes []string
+       for key, attr := range block.Attributes {
+               if attr.WriteOnly {
+                       writeOnlyAttributes = append(writeOnlyAttributes, key)
+                       continue
                }
-
-               // Empty strings in blocks should be considered null for legacy 
reasons.
-               // The SDK doesn't support null strings yet, so we work around 
this now.
-               if before, ok := childValue.Before.(string); ok && len(before) 
== 0 {
-                       childValue.Before = nil
-               }
-               if after, ok := childValue.After.(string); ok && len(after) == 
0 {
-                       childValue.After = nil
-               }
-
-               // Always treat changes to blocks as implicit.
-               childValue.BeforeExplicit = false
-               childValue.AfterExplicit = false
-
-               // Because we want to print also the write-only attributes, we 
need to pass in the parent block
-               // action instead of the child one.
-               // This is because the child action will always result in NoOp 
since for write-only attributes, the
-               // values returned will be null.
-               childChange := ComputeDiffForAttribute(childValue, attr, 
current)
-               if childChange.Action == plans.NoOp && childValue.Before == nil 
&& childValue.After == nil {
-                       // This validation is specifically added for `tofu 
show`.
-                       // Since "current" will be NoOp during rendering the 
output for `tofu show`,
-                       // we need this validation to include the write-only 
attributes in the output.
-                       if !attr.WriteOnly {
-                               // Don't record nil values at all in blocks.
-                               continue
-                       }
+               attrChange := blockValue.GetChild(key)
+               childChange := diffChildAttribute(attrChange, attr, current)
+               if childChange == nil {
+                       continue
+               }
+               current = collections.CompareActions(current, 
childChange.Action)
+               attributes[key] = *childChange
+       }
+       // In the second iteration, now that the action of the resource is 
decided, we process only the write-only
+       // attributes. If the [current] action is NoOp, then none of the 
write-only attributes will be included,
+       // otherwise, will include all the write-only attributes.
+       for _, key := range writeOnlyAttributes {
+               attr := block.Attributes[key]
+               attrChange := blockValue.GetChild(key)
+               childChange := diffChildAttribute(attrChange, attr, current)
+               if childChange == nil {
+                       continue
                }
-
-               attributes[key] = childChange
                current = collections.CompareActions(current, 
childChange.Action)
+               attributes[key] = *childChange
        }
 
        blocks := renderers.Blocks{
@@ -138,3 +134,43 @@
 
        return computed.NewDiff(renderers.Block(attributes, blocks), current, 
change.ReplacePaths.Matches())
 }
+
+// diffChildAttribute computes a new [compute.Diff] for the given attribute 
change and its schema.
+// When a resource for which the diff is built contains also write-only 
attributes, we want to process first
+// all the non write-only attributes to get the actual change action on that 
resource, action that will decide
+// if the write-only attributes will or not be included in the rendered output.
+func diffChildAttribute(attrChange structured.Change, attrSchema 
*jsonprovider.Attribute, currentAction plans.Action) *computed.Diff {
+       if !attrChange.RelevantAttributes.MatchesPartial() {
+               // Mark non-relevant attributes as unchanged.
+               attrChange = attrChange.AsNoOp()
+       }
+
+       // Empty strings in blocks should be considered null for legacy reasons.
+       // The SDK doesn't support null strings yet, so we work around this now.
+       if before, ok := attrChange.Before.(string); ok && len(before) == 0 {
+               attrChange.Before = nil
+       }
+       if after, ok := attrChange.After.(string); ok && len(after) == 0 {
+               attrChange.After = nil
+       }
+
+       // Always treat changes to blocks as implicit.
+       attrChange.BeforeExplicit = false
+       attrChange.AfterExplicit = false
+
+       // Because we want to render also the write-only attributes, we need to 
pass in the parent block
+       // action instead of the child one.
+       // This is because the child action will always result in NoOp since 
for write-only attributes, the
+       // values returned will be null.
+       childChange := ComputeDiffForAttribute(attrChange, attrSchema, 
currentAction)
+       if childChange.Action == plans.NoOp && attrChange.Before == nil && 
attrChange.After == nil {
+               // This validation is specifically added for `tofu show`.
+               // Since "current" will be NoOp during rendering the output for 
`tofu show`,
+               // we need this validation to include the write-only attributes 
in the output.
+               if !attrSchema.WriteOnly {
+                       // Don't record nil values at all in blocks.
+                       return nil
+               }
+       }
+       return &childChange
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/opentofu-1.11.3/internal/command/jsonformat/differ/differ_test.go 
new/opentofu-1.11.4/internal/command/jsonformat/differ/differ_test.go
--- old/opentofu-1.11.3/internal/command/jsonformat/differ/differ_test.go       
2026-01-13 18:01:54.000000000 +0100
+++ new/opentofu-1.11.4/internal/command/jsonformat/differ/differ_test.go       
2026-01-21 16:57:28.000000000 +0100
@@ -134,7 +134,7 @@
                                "write_only_attribute": 
renderers.ValidateWriteOnly(plans.Delete, false),
                        }, nil, nil, nil, nil, plans.Delete, false),
                },
-               "create_with_write_only_value": {
+               "create_with_only_write_only_value": {
                        input: structured.Change{
                                Before:          nil,
                                After:           map[string]any{},
@@ -166,6 +166,66 @@
                                "write_only_attribute": 
renderers.ValidateWriteOnly(plans.Create, false),
                        }, nil, nil, nil, nil, plans.Create, false),
                },
+               // Before the fix for 
https://github.com/opentofu/opentofu/issues/3640, this test was failing 
randomnly.
+               // That was because the write-only attributes diffs could have 
been generated with NoOp instead of having
+               // the final action of the resource. After the fix, the 
write-only attributes diffs are generated after
+               // generating the diffs for any non write-only attribute 
meaning that the action generated from regular
+               // attributes will be used for all the write-only attributes.
+               "write_only_not_updated_but_regular_updated": {
+                       input: structured.Change{
+                               Before: map[string]any{
+                                       "regular_attribute": "before value",
+                               },
+                               After: map[string]any{
+                                       "regular_attribute": "after value",
+                               },
+                               BeforeSensitive: false,
+                               AfterSensitive:  false,
+                       },
+                       block: &jsonprovider.Block{
+                               Attributes: map[string]*jsonprovider.Attribute{
+                                       "regular_attribute": {
+                                               AttributeType: unmarshalType(t, 
cty.String),
+                                       },
+                                       "write_only_attribute": {
+                                               AttributeType: unmarshalType(t, 
cty.String),
+                                               WriteOnly:     true,
+                                       },
+                                       "write_only_attribute2": {
+                                               AttributeType: unmarshalType(t, 
cty.String),
+                                               WriteOnly:     true,
+                                       },
+                                       "write_only_attribute3": {
+                                               AttributeType: unmarshalType(t, 
cty.String),
+                                               WriteOnly:     true,
+                                       },
+                                       "write_only_attribute4": {
+                                               AttributeType: unmarshalType(t, 
cty.String),
+                                               WriteOnly:     true,
+                                       },
+                               },
+                               BlockTypes: map[string]*jsonprovider.BlockType{
+                                       "nested_with_write_only": {
+                                               NestingMode: "single",
+                                               Block: &jsonprovider.Block{
+                                                       Attributes: 
map[string]*jsonprovider.Attribute{
+                                                               
"inner_write_only": {
+                                                                       
AttributeType: unmarshalType(t, cty.String),
+                                                                       
WriteOnly:     true,
+                                                               },
+                                                       },
+                                               },
+                                       },
+                               },
+                       },
+                       validate: 
renderers.ValidateBlock(map[string]renderers.ValidateDiffFunction{
+                               "regular_attribute":     
renderers.ValidatePrimitive("before value", "after value", plans.Update, false),
+                               "write_only_attribute":  
renderers.ValidateWriteOnly(plans.Update, false),
+                               "write_only_attribute2": 
renderers.ValidateWriteOnly(plans.Update, false),
+                               "write_only_attribute3": 
renderers.ValidateWriteOnly(plans.Update, false),
+                               "write_only_attribute4": 
renderers.ValidateWriteOnly(plans.Update, false),
+                       }, nil, nil, nil, nil, plans.Update, false),
+               },
                "update_with_write_only_value": {
                        input: structured.Change{
                                Before: map[string]any{
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/opentofu-1.11.3/internal/configs/configload/loader_load_test.go 
new/opentofu-1.11.4/internal/configs/configload/loader_load_test.go
--- old/opentofu-1.11.3/internal/configs/configload/loader_load_test.go 
2026-01-13 18:01:54.000000000 +0100
+++ new/opentofu-1.11.4/internal/configs/configload/loader_load_test.go 
2026-01-21 16:57:28.000000000 +0100
@@ -204,7 +204,7 @@
                if !diags.HasErrors() {
                        t.Fatalf("loading succeeded; want an error")
                }
-               if got, want := diags.Error(), "Module is incompatible with 
count, for_each, and depends_on"; !strings.Contains(got, want) {
+               if got, want := diags.Error(), "Module is incompatible with 
count, for_each, enabled and depends_on"; !strings.Contains(got, want) {
                        t.Errorf("missing expected error\nwant substring: 
%s\ngot: %s", want, got)
                }
        })
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/opentofu-1.11.3/internal/configs/provider_validation.go 
new/opentofu-1.11.4/internal/configs/provider_validation.go
--- old/opentofu-1.11.3/internal/configs/provider_validation.go 2026-01-13 
18:01:54.000000000 +0100
+++ new/opentofu-1.11.4/internal/configs/provider_validation.go 2026-01-21 
16:57:28.000000000 +0100
@@ -292,7 +292,7 @@
        for name, child := range cfg.Children {
                mc := mod.ModuleCalls[name]
                childNoProviderConfigRange := noProviderConfigRange
-               // if the module call has any of count, for_each or depends_on,
+               // if the module call has any of count, for_each, enabled or 
depends_on,
                // providers are prohibited from being configured in this 
module, or
                // any module beneath this module.
                switch {
@@ -300,6 +300,8 @@
                        childNoProviderConfigRange = mc.Count.Range().Ptr()
                case mc.ForEach != nil:
                        childNoProviderConfigRange = mc.ForEach.Range().Ptr()
+               case mc.Enabled != nil:
+                       childNoProviderConfigRange = mc.Enabled.Range().Ptr()
                case mc.DependsOn != nil:
                        if len(mc.DependsOn) > 0 {
                                childNoProviderConfigRange = 
mc.DependsOn[0].SourceRange().Ptr()
@@ -595,9 +597,9 @@
                // updated yet) than of the called module.
                diags = append(diags, &hcl.Diagnostic{
                        Severity: hcl.DiagError,
-                       Summary:  "Module is incompatible with count, for_each, 
and depends_on",
+                       Summary:  "Module is incompatible with count, for_each, 
enabled and depends_on",
                        Detail: fmt.Sprintf(
-                               "The module at %s is a legacy module which 
contains its own local provider configurations, and so calls to it may not use 
the count, for_each, or depends_on arguments.\n\nIf you also control the module 
%q, consider updating this module to instead expect provider configurations to 
be passed by its caller.",
+                               "The module at %s is a legacy module which 
contains its own local provider configurations, and so calls to it may not use 
the count, for_each, enabled or depends_on arguments.\n\nIf you also control 
the module %q, consider updating this module to instead expect provider 
configurations to be passed by its caller.",
                                cfg.Path, cfg.SourceAddr,
                        ),
                        Subject: noProviderConfigRange,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/opentofu-1.11.3/internal/configs/provider_validation_test.go 
new/opentofu-1.11.4/internal/configs/provider_validation_test.go
--- old/opentofu-1.11.3/internal/configs/provider_validation_test.go    
1970-01-01 01:00:00.000000000 +0100
+++ new/opentofu-1.11.4/internal/configs/provider_validation_test.go    
2026-01-21 16:57:28.000000000 +0100
@@ -0,0 +1,141 @@
+// Copyright (c) The OpenTofu Authors
+// SPDX-License-Identifier: MPL-2.0
+// Copyright (c) 2023 HashiCorp, Inc.
+// SPDX-License-Identifier: MPL-2.0
+
+package configs
+
+import (
+       "strings"
+       "testing"
+
+       "github.com/hashicorp/hcl/v2"
+       "github.com/hashicorp/hcl/v2/hclsyntax"
+       "github.com/opentofu/opentofu/internal/addrs"
+)
+
+func TestValidateProviderConfigs_WithMetaArguments(t *testing.T) {
+       tests := []struct {
+               name                   string
+               moduleCall             *ModuleCall
+               childHasProviderConfig bool
+               wantError              bool
+       }{
+               {
+                       name: "count",
+                       moduleCall: &ModuleCall{
+                               Name:  "child",
+                               Count: &hclsyntax.LiteralValueExpr{},
+                       },
+                       childHasProviderConfig: true,
+                       wantError:              true,
+               },
+               {
+                       name: "for_each",
+                       moduleCall: &ModuleCall{
+                               Name:    "child",
+                               ForEach: &hclsyntax.LiteralValueExpr{},
+                       },
+                       childHasProviderConfig: true,
+                       wantError:              true,
+               },
+               {
+                       name: "depends_on",
+                       moduleCall: &ModuleCall{
+                               Name:      "child",
+                               DependsOn: []hcl.Traversal{{}},
+                       },
+                       childHasProviderConfig: true,
+                       wantError:              true,
+               },
+               {
+                       name: "enabled",
+                       moduleCall: &ModuleCall{
+                               Name:    "child",
+                               Enabled: &hclsyntax.LiteralValueExpr{},
+                       },
+                       childHasProviderConfig: true,
+                       wantError:              true,
+               },
+               {
+                       name: "no meta-arguments",
+                       moduleCall: &ModuleCall{
+                               Name: "child",
+                       },
+                       childHasProviderConfig: true,
+                       wantError:              false,
+               },
+               {
+                       name: "count without child provider config",
+                       moduleCall: &ModuleCall{
+                               Name:  "child",
+                               Count: &hclsyntax.LiteralValueExpr{},
+                       },
+                       childHasProviderConfig: false,
+                       wantError:              false,
+               },
+       }
+
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       childModule := &Module{
+                               ProviderConfigs: map[string]*Provider{},
+                       }
+
+                       if tt.childHasProviderConfig {
+                               childModule.ProviderConfigs["aws"] = &Provider{
+                                       Name: "aws",
+                                       Config: &hclsyntax.Body{
+                                               Attributes: 
hclsyntax.Attributes{
+                                                       "region": 
&hclsyntax.Attribute{Name: "region"},
+                                               },
+                                       },
+                               }
+                       }
+
+                       childCfg := &Config{
+                               Path:       addrs.Module{"child"},
+                               Module:     childModule,
+                               SourceAddr: addrs.ModuleSourceLocal("./child"),
+                               Children:   map[string]*Config{},
+                       }
+
+                       parentModule := &Module{
+                               ModuleCalls: map[string]*ModuleCall{
+                                       "child": tt.moduleCall,
+                               },
+                       }
+
+                       parentCfg := &Config{
+                               Path:     addrs.RootModule,
+                               Module:   parentModule,
+                               Children: map[string]*Config{"child": childCfg},
+                       }
+                       parentCfg.Root = parentCfg
+                       childCfg.Root = parentCfg
+                       childCfg.Parent = parentCfg
+
+                       diags := validateProviderConfigs(nil, parentCfg, nil)
+
+                       var foundError bool
+                       for _, diag := range diags {
+                               if diag.Severity == hcl.DiagError &&
+                                       strings.Contains(diag.Summary, "Module 
is incompatible with count, for_each") {
+                                       foundError = true
+                                       if !strings.Contains(diag.Detail, 
"legacy module which contains its own local provider configurations") {
+                                               t.Errorf("expected error detail 
to mention 'legacy module', got: %s", diag.Detail)
+                                       }
+                                       break
+                               }
+                       }
+
+                       if tt.wantError && !foundError {
+                               t.Errorf("expected error, but got none")
+                       }
+
+                       if !tt.wantError && foundError {
+                               t.Errorf("did not expect error, but got %s", 
diags)
+                       }
+               })
+       }
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/opentofu-1.11.3/internal/configs/state_encryption.go 
new/opentofu-1.11.4/internal/configs/state_encryption.go
--- old/opentofu-1.11.3/internal/configs/state_encryption.go    2026-01-13 
18:01:54.000000000 +0100
+++ new/opentofu-1.11.4/internal/configs/state_encryption.go    1970-01-01 
01:00:00.000000000 +0100
@@ -1,16 +0,0 @@
-// Copyright (c) The OpenTofu Authors
-// SPDX-License-Identifier: MPL-2.0
-// Copyright (c) 2023 HashiCorp, Inc.
-// SPDX-License-Identifier: MPL-2.0
-
-package configs
-
-import "github.com/hashicorp/hcl/v2"
-
-type StateEncryption struct {
-       Type   string
-       Config hcl.Body
-
-       TypeRange hcl.Range
-       DeclRange hcl.Range
-}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/opentofu-1.11.3/internal/configs/testdata/config-diagnostics/nested-provider/errors
 
new/opentofu-1.11.4/internal/configs/testdata/config-diagnostics/nested-provider/errors
--- 
old/opentofu-1.11.3/internal/configs/testdata/config-diagnostics/nested-provider/errors
     2026-01-13 18:01:54.000000000 +0100
+++ 
new/opentofu-1.11.4/internal/configs/testdata/config-diagnostics/nested-provider/errors
     2026-01-21 16:57:28.000000000 +0100
@@ -1 +1 @@
-nested-provider/root.tf:2,11-12: Module is incompatible with count, for_each, 
and depends_on; The module at module.child.module.child2 is a legacy module 
which contains its own local provider configurations, and so calls to it may 
not use the count, for_each, or depends_on arguments.
+nested-provider/root.tf:2,11-12: Module is incompatible with count, for_each, 
enabled and depends_on; The module at module.child.module.child2 is a legacy 
module which contains its own local provider configurations, and so calls to it 
may not use the count, for_each, enabled or depends_on arguments.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/opentofu-1.11.3/internal/encryption/keyprovider/output.go 
new/opentofu-1.11.4/internal/encryption/keyprovider/output.go
--- old/opentofu-1.11.3/internal/encryption/keyprovider/output.go       
2026-01-13 18:01:54.000000000 +0100
+++ new/opentofu-1.11.4/internal/encryption/keyprovider/output.go       
2026-01-21 16:57:28.000000000 +0100
@@ -5,7 +5,14 @@
 
 package keyprovider
 
-import "github.com/zclconf/go-cty/cty"
+import (
+       "fmt"
+
+       "github.com/hashicorp/hcl/v2"
+       "github.com/hashicorp/hcl/v2/gohcl"
+       "github.com/hashicorp/hcl/v2/hclsyntax"
+       "github.com/zclconf/go-cty/cty"
+)
 
 // Output is the standardized structure a key provider must return when 
providing a key.
 // It contains two keys because some key providers may prefer include random 
data (e.g. salt)
@@ -15,6 +22,38 @@
        DecryptionKey []byte `hcl:"decryption_key,optional" 
cty:"decryption_key" json:"decryption_key,omitempty" yaml:"decryption_key"`
 }
 
+func DecodeOutput(val cty.Value, subject hcl.Range) (Output, hcl.Diagnostics) {
+       var out Output
+       if !val.CanIterateElements() {
+               return out, hcl.Diagnostics{{
+                       Severity: hcl.DiagError,
+                       Summary:  "Expected key_provider value",
+                       Detail:   fmt.Sprintf("Expected a key_provider 
compatible value, found %s instead", val.Type().FriendlyName()),
+                       Subject:  &subject,
+               }}
+       }
+
+       var diags hcl.Diagnostics
+       mapVal := val.AsValueMap()
+       if attr, ok := mapVal["encryption_key"]; ok {
+               decodeDiags := 
gohcl.DecodeExpression(&hclsyntax.LiteralValueExpr{Val: attr, SrcRange: 
subject}, nil, &out.EncryptionKey)
+               diags = diags.Extend(decodeDiags)
+       } else {
+               return out, hcl.Diagnostics{{
+                       Severity: hcl.DiagError,
+                       Summary:  "Missing encryption_key value",
+                       Detail:   "An encryption_key value is required in the 
key_provider compatible object at this location",
+                       Subject:  &subject,
+               }}
+       }
+
+       if attr, ok := mapVal["decryption_key"]; ok {
+               decodeDiags := 
gohcl.DecodeExpression(&hclsyntax.LiteralValueExpr{Val: attr, SrcRange: 
subject}, nil, &out.DecryptionKey)
+               diags = diags.Extend(decodeDiags)
+       }
+       return out, diags
+}
+
 // Cty turns the Output struct into a CTY value.
 func (o *Output) Cty() cty.Value {
        return cty.ObjectVal(map[string]cty.Value{
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/opentofu-1.11.3/internal/encryption/method/aesgcm/compliance_test.go 
new/opentofu-1.11.4/internal/encryption/method/aesgcm/compliance_test.go
--- old/opentofu-1.11.3/internal/encryption/method/aesgcm/compliance_test.go    
2026-01-13 18:01:54.000000000 +0100
+++ new/opentofu-1.11.4/internal/encryption/method/aesgcm/compliance_test.go    
2026-01-21 16:57:28.000000000 +0100
@@ -180,16 +180,6 @@
                                Validate:   nil,
                        },
                },
-               ConfigStructTestCases: 
map[string]compliancetest.ConfigStructTestCase[*Config, *aesgcm]{
-                       "empty": {
-                               Config: &Config{
-                                       Keys: keyprovider.Output{},
-                                       AAD:  nil,
-                               },
-                               ValidBuild: false,
-                               Validate:   nil,
-                       },
-               },
                EncryptDecryptTestCase: 
compliancetest.EncryptDecryptTestCase[*Config, *aesgcm]{
                        ValidEncryptOnlyConfig: &Config{
                                Keys: keyprovider.Output{
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/opentofu-1.11.3/internal/encryption/method/aesgcm/config.go 
new/opentofu-1.11.4/internal/encryption/method/aesgcm/config.go
--- old/opentofu-1.11.3/internal/encryption/method/aesgcm/config.go     
2026-01-13 18:01:54.000000000 +0100
+++ new/opentofu-1.11.4/internal/encryption/method/aesgcm/config.go     
2026-01-21 16:57:28.000000000 +0100
@@ -22,13 +22,13 @@
 type Config struct {
        // Key is the encryption key for the AES-GCM encryption. It has to be 
16, 24, or 32 bytes long for AES-128, 192, or
        // 256, respectively.
-       Keys keyprovider.Output `hcl:"keys" json:"keys" yaml:"keys"`
+       Keys keyprovider.Output
 
        // AAD is the Additional Authenticated Data that is authenticated, but 
not encrypted. In the Go implementation, this
        // data serves as a canary value against replay attacks. The AAD value 
on decryption must match this setting,
        // otherwise the decryption will fail. (Note: this is Go-specific and 
differs from the NIST SP 800-38D description
        // of the AAD.)
-       AAD []byte `hcl:"aad,optional" json:"aad,omitempty" 
yaml:"aad,omitempty"`
+       AAD []byte
 }
 
 // Build checks the validity of the configuration and returns a ready-to-use 
AES-GCM implementation.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/opentofu-1.11.3/internal/encryption/method/aesgcm/descriptor.go 
new/opentofu-1.11.4/internal/encryption/method/aesgcm/descriptor.go
--- old/opentofu-1.11.3/internal/encryption/method/aesgcm/descriptor.go 
2026-01-13 18:01:54.000000000 +0100
+++ new/opentofu-1.11.4/internal/encryption/method/aesgcm/descriptor.go 
2026-01-21 16:57:28.000000000 +0100
@@ -6,37 +6,58 @@
 package aesgcm
 
 import (
+       "github.com/hashicorp/hcl/v2"
+       "github.com/hashicorp/hcl/v2/gohcl"
+       "github.com/hashicorp/hcl/v2/hclsyntax"
        "github.com/opentofu/opentofu/internal/encryption/keyprovider"
        "github.com/opentofu/opentofu/internal/encryption/method"
 )
 
-// Descriptor integrates the method.Descriptor and provides a TypedConfig for 
easier configuration.
-type Descriptor interface {
-       method.Descriptor
-
-       // TypedConfig returns a config typed for this method.
-       TypedConfig() *Config
-}
-
 // New creates a new descriptor for the AES-GCM encryption method, which 
requires a 32-byte key.
-func New() Descriptor {
+func New() method.Descriptor {
        return &descriptor{}
 }
 
 type descriptor struct {
 }
 
-func (f *descriptor) TypedConfig() *Config {
-       return &Config{
-               Keys: keyprovider.Output{},
-               AAD:  nil,
-       }
-}
-
 func (f *descriptor) ID() method.ID {
        return "aes_gcm"
 }
 
-func (f *descriptor) ConfigStruct() method.Config {
-       return f.TypedConfig()
+func (f *descriptor) DecodeConfig(methodCtx method.EvalContext, body hcl.Body) 
(method.Config, hcl.Diagnostics) {
+       var diags hcl.Diagnostics
+       methodCfg := &Config{}
+
+       content, contentDiags := body.Content(&hcl.BodySchema{
+               Attributes: []hcl.AttributeSchema{
+                       {Name: "keys", Required: true},
+                       {Name: "aad", Required: false},
+               },
+       })
+       diags = diags.Extend(contentDiags)
+       if diags.HasErrors() {
+               return nil, diags
+       }
+
+       keyExpr := content.Attributes["keys"].Expr
+       // keyExpr can either be raw data/references to raw data or a string 
reference to a key provider (JSON support)
+       keyVal, keyDiags := methodCtx.ValueForExpression(keyExpr)
+       diags = diags.Extend(keyDiags)
+       if diags.HasErrors() {
+               return nil, diags
+       }
+
+       methodCfg.Keys, keyDiags = keyprovider.DecodeOutput(keyVal, 
keyExpr.Range())
+       diags = diags.Extend(keyDiags)
+
+       if attr, ok := content.Attributes["aad"]; ok {
+               attrVal, attrDiags := methodCtx.ValueForExpression(attr.Expr)
+               diags = diags.Extend(attrDiags)
+
+               decodeDiags := 
gohcl.DecodeExpression(&hclsyntax.LiteralValueExpr{Val: attrVal, SrcRange: 
attr.Expr.Range()}, nil, &methodCfg.AAD)
+               diags = diags.Extend(decodeDiags)
+       }
+
+       return methodCfg, diags
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/opentofu-1.11.3/internal/encryption/method/aesgcm/example_test.go 
new/opentofu-1.11.4/internal/encryption/method/aesgcm/example_test.go
--- old/opentofu-1.11.3/internal/encryption/method/aesgcm/example_test.go       
2026-01-13 18:01:54.000000000 +0100
+++ new/opentofu-1.11.4/internal/encryption/method/aesgcm/example_test.go       
2026-01-21 16:57:28.000000000 +0100
@@ -6,62 +6,19 @@
 package aesgcm_test
 
 import (
-       "encoding/json"
        "fmt"
 
        "github.com/hashicorp/hcl/v2"
-       "github.com/hashicorp/hcl/v2/gohcl"
        "github.com/hashicorp/hcl/v2/hclsyntax"
        "github.com/opentofu/opentofu/internal/encryption/keyprovider"
+       "github.com/opentofu/opentofu/internal/encryption/method"
        "github.com/opentofu/opentofu/internal/encryption/method/aesgcm"
+       "github.com/zclconf/go-cty/cty"
 )
 
-func Example() {
-       descriptor := aesgcm.New()
-
-       // Get the config struct. You can fill it manually by type-asserting it 
to aesgcm.Config, but you could also use
-       // it as JSON.
-       config := descriptor.ConfigStruct()
-
-       if err := json.Unmarshal(
-               // Set up a randomly generated 32-byte key. In JSON, you can 
base64-encode the value.
-               []byte(`{
-    "keys": {
-               "encryption_key": 
"Y29veTRhaXZ1NWFpeW9vMWlhMG9vR29vVGFlM1BhaTQ=",
-               "decryption_key": "Y29veTRhaXZ1NWFpeW9vMWlhMG9vR29vVGFlM1BhaTQ="
-       }
-}`), &config); err != nil {
-               panic(err)
-       }
-
-       method, err := config.Build()
-       if err != nil {
-               panic(err)
-       }
-
-       // Encrypt some data:
-       encrypted, err := method.Encrypt([]byte("Hello world!"))
-       if err != nil {
-               panic(err)
-       }
-
-       // Now decrypt it:
-       decrypted, err := method.Decrypt(encrypted)
-       if err != nil {
-               panic(err)
-       }
-
-       fmt.Printf("%s", decrypted)
-       // Output: Hello world!
-}
-
 func Example_config() {
-       // First, get the descriptor to make sure we always have the default 
values.
-       descriptor := aesgcm.New()
-
-       // Obtain a modifiable, buildable config. Alternatively, you can also 
use ConfigStruct() method to obtain a
-       // struct you can fill with HCL or JSON tags.
-       config := descriptor.TypedConfig()
+       // Obtain a modifiable, buildable config.
+       config := aesgcm.Config{}
 
        // Set up an encryption key:
        config.Keys = keyprovider.Output{
@@ -91,54 +48,10 @@
        // Output: Hello world!
 }
 
-func Example_config_json() {
-       // First, get the descriptor to make sure we always have the default 
values.
-       descriptor := aesgcm.New()
-
-       // Get an untyped config struct you can use for JSON unmarshalling:
-       config := descriptor.ConfigStruct()
-
-       // Unmarshal JSON into the config struct:
-       if err := json.Unmarshal(
-               // Set up a randomly generated 32-byte key. In JSON, you can 
base64-encode the value.
-               []byte(`{
-    "keys": {
-               "encryption_key": 
"Y29veTRhaXZ1NWFpeW9vMWlhMG9vR29vVGFlM1BhaTQ=",
-               "decryption_key": "Y29veTRhaXZ1NWFpeW9vMWlhMG9vR29vVGFlM1BhaTQ="
-       }
-}`), &config); err != nil {
-               panic(err)
-       }
-
-       // Now you can build a method:
-       method, err := config.Build()
-       if err != nil {
-               panic(err)
-       }
-
-       // Encrypt something:
-       encrypted, err := method.Encrypt([]byte("Hello world!"))
-       if err != nil {
-               panic(err)
-       }
-
-       // Decrypt it:
-       decrypted, err := method.Decrypt(encrypted)
-       if err != nil {
-               panic(err)
-       }
-
-       fmt.Printf("%s", decrypted)
-       // Output: Hello world!
-}
-
 func Example_config_hcl() {
        // First, get the descriptor to make sure we always have the default 
values.
        descriptor := aesgcm.New()
 
-       // Get an untyped config struct you can use for HCL unmarshalling:
-       config := descriptor.ConfigStruct()
-
        // Unmarshal HCL code into the config struct. The input must be a list 
of bytes, so in a real world scenario
        // you may want to put in a hex-decoding function:
        rawHCLInput := `keys = {
@@ -153,7 +66,12 @@
        if diags.HasErrors() {
                panic(diags)
        }
-       if diags := gohcl.DecodeBody(file.Body, nil, config); diags.HasErrors() 
{
+
+       methodCtx := method.EvalContext{ValueForExpression: func(expr 
hcl.Expression) (cty.Value, hcl.Diagnostics) {
+               return expr.Value(nil)
+       }}
+       config, diags := descriptor.DecodeConfig(methodCtx, file.Body)
+       if diags.HasErrors() {
                panic(diags)
        }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/opentofu-1.11.3/internal/encryption/method/compliancetest/compliance.go 
new/opentofu-1.11.4/internal/encryption/method/compliancetest/compliance.go
--- old/opentofu-1.11.3/internal/encryption/method/compliancetest/compliance.go 
2026-01-13 18:01:54.000000000 +0100
+++ new/opentofu-1.11.4/internal/encryption/method/compliancetest/compliance.go 
2026-01-21 16:57:28.000000000 +0100
@@ -11,10 +11,11 @@
        "reflect"
        "testing"
 
-       "github.com/hashicorp/hcl/v2/gohcl"
+       "github.com/hashicorp/hcl/v2"
        "github.com/opentofu/opentofu/internal/encryption/compliancetest"
        "github.com/opentofu/opentofu/internal/encryption/config"
        "github.com/opentofu/opentofu/internal/encryption/method"
+       "github.com/zclconf/go-cty/cty"
 )
 
 // ComplianceTest tests the functionality of a method to make sure it conforms 
to the expectations of the method
@@ -32,9 +33,6 @@
        // function.
        HCLParseTestCases map[string]HCLParseTestCase[TDescriptor, TConfig, 
TMethod]
 
-       // ConfigStructT validates that a certain config results or does not 
result in a valid Build() call.
-       ConfigStructTestCases map[string]ConfigStructTestCase[TConfig, TMethod]
-
        // ProvideTestCase exercises the entire chain and generates two keys.
        EncryptDecryptTestCase EncryptDecryptTestCase[TConfig, TMethod]
 }
@@ -46,9 +44,6 @@
        t.Run("hcl", func(t *testing.T) {
                cfg.testHCL(t)
        })
-       t.Run("config-struct", func(t *testing.T) {
-               cfg.testConfigStruct(t)
-       })
        t.Run("encrypt-decrypt", func(t *testing.T) {
                cfg.EncryptDecryptTestCase.execute(t)
        })
@@ -100,20 +95,6 @@
        })
 }
 
-func (cfg *TestConfiguration[TDescriptor, TConfig, TMethod]) 
testConfigStruct(t *testing.T) {
-       compliancetest.ConfigStruct[TConfig](t, cfg.Descriptor.ConfigStruct())
-
-       if cfg.ConfigStructTestCases == nil {
-               compliancetest.Fail(t, "Please provide a map to 
ConfigStructTestCases.")
-       }
-
-       for name, tc := range cfg.ConfigStructTestCases {
-               t.Run(name, func(t *testing.T) {
-                       tc.execute(t)
-               })
-       }
-}
-
 // HCLParseTestCase contains a test case that parses HCL into a configuration.
 type HCLParseTestCase[TDescriptor method.Descriptor, TConfig method.Config, 
TMethod method.Method] struct {
        // HCL contains the code that should be parsed into the configuration 
structure.
@@ -143,12 +124,11 @@
                }
        }
 
-       configStruct := descriptor.ConfigStruct()
-       diags = gohcl.DecodeBody(
-               parsedConfig.MethodConfigs[0].Body,
-               nil,
-               configStruct,
-       )
+       methodCtx := method.EvalContext{ValueForExpression: func(expr 
hcl.Expression) (cty.Value, hcl.Diagnostics) {
+               return expr.Value(nil)
+       }}
+       configStruct, diags := descriptor.DecodeConfig(methodCtx, 
parsedConfig.MethodConfigs[0].Body)
+
        var m TMethod
        if h.ValidHCL {
                if diags.HasErrors() {
@@ -177,22 +157,6 @@
        }
 }
 
-// ConfigStructTestCase validates that the config struct is behaving correctly 
when Build() is called.
-type ConfigStructTestCase[TConfig method.Config, TMethod method.Method] struct 
{
-       Config     TConfig
-       ValidBuild bool
-       Validate   func(method TMethod) error
-}
-
-func (m ConfigStructTestCase[TConfig, TMethod]) execute(t *testing.T) {
-       newMethod := buildConfigAndValidate[TMethod, TConfig](t, m.Config, 
m.ValidBuild)
-       if m.Validate != nil {
-               if err := m.Validate(newMethod); err != nil {
-                       compliancetest.Fail(t, "method validation failed (%v)", 
err)
-               }
-       }
-}
-
 // EncryptDecryptTestCase handles a full encryption-decryption cycle.
 type EncryptDecryptTestCase[TConfig method.Config, TMethod method.Method] 
struct {
        // ValidEncryptOnlyConfig is a configuration that has no decryption key 
and can only be used for encryption. The
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/opentofu-1.11.3/internal/encryption/method/descriptor.go 
new/opentofu-1.11.4/internal/encryption/method/descriptor.go
--- old/opentofu-1.11.3/internal/encryption/method/descriptor.go        
2026-01-13 18:01:54.000000000 +0100
+++ new/opentofu-1.11.4/internal/encryption/method/descriptor.go        
2026-01-21 16:57:28.000000000 +0100
@@ -5,16 +5,25 @@
 
 package method
 
+import (
+       "github.com/hashicorp/hcl/v2"
+       "github.com/zclconf/go-cty/cty"
+)
+
 // Descriptor contains the details on an encryption method and produces a 
configuration structure with default values.
 type Descriptor interface {
        // ID returns the unique identifier used when parsing HCL or JSON 
configs.
        ID() ID
 
-       // ConfigStruct creates a new configuration struct annotated with hcl 
tags. The Build() receiver on
+       // DecodeConfig creates a new configuration struct. The Build() 
receiver on
        // this struct must be able to build a Method from the configuration.
        //
        // Common errors:
        // - Returning a struct without a pointer
        // - Returning a non-struct
-       ConfigStruct() Config
+       DecodeConfig(methodCtx EvalContext, body hcl.Body) (Config, 
hcl.Diagnostics)
+}
+
+type EvalContext struct {
+       ValueForExpression func(expr hcl.Expression) (cty.Value, 
hcl.Diagnostics)
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/opentofu-1.11.3/internal/encryption/method/external/compliance_test.go 
new/opentofu-1.11.4/internal/encryption/method/external/compliance_test.go
--- old/opentofu-1.11.3/internal/encryption/method/external/compliance_test.go  
2026-01-13 18:01:54.000000000 +0100
+++ new/opentofu-1.11.4/internal/encryption/method/external/compliance_test.go  
2026-01-21 16:57:28.000000000 +0100
@@ -74,13 +74,6 @@
                                },
                        },
                },
-               ConfigStructTestCases: 
map[string]compliancetest.ConfigStructTestCase[*Config, *command]{
-                       "empty": {
-                               Config:     &Config{},
-                               ValidBuild: false,
-                               Validate:   nil,
-                       },
-               },
                EncryptDecryptTestCase: 
compliancetest.EncryptDecryptTestCase[*Config, *command]{
                        ValidEncryptOnlyConfig: &Config{
                                Keys: &keyprovider.Output{
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/opentofu-1.11.3/internal/encryption/method/external/config.go 
new/opentofu-1.11.4/internal/encryption/method/external/config.go
--- old/opentofu-1.11.3/internal/encryption/method/external/config.go   
2026-01-13 18:01:54.000000000 +0100
+++ new/opentofu-1.11.4/internal/encryption/method/external/config.go   
2026-01-21 16:57:28.000000000 +0100
@@ -12,9 +12,9 @@
 
 // Config is the configuration for the AES-GCM method.
 type Config struct {
-       Keys           *keyprovider.Output `hcl:"keys,optional" 
json:"keys,omitempty" yaml:"keys"`
-       EncryptCommand []string            `hcl:"encrypt_command" 
json:"encrypt_command" yaml:"encrypt_command"`
-       DecryptCommand []string            `hcl:"decrypt_command" 
json:"decrypt_command" yaml:"decrypt_command"`
+       Keys           *keyprovider.Output
+       EncryptCommand []string
+       DecryptCommand []string
 }
 
 // Build checks the validity of the configuration and returns a ready-to-use 
AES-GCM implementation.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/opentofu-1.11.3/internal/encryption/method/external/descriptor.go 
new/opentofu-1.11.4/internal/encryption/method/external/descriptor.go
--- old/opentofu-1.11.3/internal/encryption/method/external/descriptor.go       
2026-01-13 18:01:54.000000000 +0100
+++ new/opentofu-1.11.4/internal/encryption/method/external/descriptor.go       
2026-01-21 16:57:28.000000000 +0100
@@ -6,33 +6,79 @@
 package external
 
 import (
+       "github.com/hashicorp/hcl/v2"
+       "github.com/hashicorp/hcl/v2/gohcl"
+       "github.com/hashicorp/hcl/v2/hclsyntax"
+       "github.com/opentofu/opentofu/internal/encryption/keyprovider"
        "github.com/opentofu/opentofu/internal/encryption/method"
 )
 
-// Descriptor integrates the method.Descriptor and provides a TypedConfig for 
easier configuration.
-type Descriptor interface {
-       method.Descriptor
-
-       // TypedConfig returns a config typed for this method.
-       TypedConfig() *Config
-}
-
 // New creates a new descriptor for the AES-GCM encryption method, which 
requires a 32-byte key.
-func New() Descriptor {
+func New() method.Descriptor {
        return &descriptor{}
 }
 
 type descriptor struct {
 }
 
-func (f *descriptor) TypedConfig() *Config {
-       return &Config{}
-}
-
-func (f *descriptor) ID() method.ID {
+func (d *descriptor) ID() method.ID {
        return "external"
 }
 
-func (f *descriptor) ConfigStruct() method.Config {
-       return f.TypedConfig()
+func (d *descriptor) DecodeConfig(methodCtx method.EvalContext, body hcl.Body) 
(method.Config, hcl.Diagnostics) {
+       var diags hcl.Diagnostics
+       methodCfg := &Config{}
+
+       content, contentDiags := body.Content(&hcl.BodySchema{
+               Attributes: []hcl.AttributeSchema{
+                       {Name: "keys", Required: false},
+                       {Name: "encrypt_command", Required: true},
+                       {Name: "decrypt_command", Required: true},
+               },
+       })
+       diags = diags.Extend(contentDiags)
+       if diags.HasErrors() {
+               return nil, diags
+       }
+
+       if keyAttr, ok := content.Attributes["keys"]; ok {
+               keyExpr := keyAttr.Expr
+               // keyExpr can either be raw data/references to raw data or a 
string reference to a key provider (JSON support)
+               keyVal, keyDiags := methodCtx.ValueForExpression(keyExpr)
+               diags = diags.Extend(keyDiags)
+               if diags.HasErrors() {
+                       return nil, diags
+               }
+               keys, decodeDiags := keyprovider.DecodeOutput(keyVal, 
keyExpr.Range())
+               diags = diags.Extend(decodeDiags)
+               if diags.HasErrors() {
+                       return nil, diags
+               }
+               methodCfg.Keys = &keys
+       }
+
+       encryptAttr := content.Attributes["encrypt_command"]
+       encryptVal, valueDiags := methodCtx.ValueForExpression(encryptAttr.Expr)
+       diags = diags.Extend(valueDiags)
+       if diags.HasErrors() {
+               return nil, diags
+       }
+
+       decodeEncryptCmdDiags := 
gohcl.DecodeExpression(&hclsyntax.LiteralValueExpr{Val: encryptVal, SrcRange: 
encryptAttr.Expr.Range()}, nil, &methodCfg.EncryptCommand)
+       diags = diags.Extend(decodeEncryptCmdDiags)
+       if diags.HasErrors() {
+               return nil, diags
+       }
+
+       decryptAttr := content.Attributes["decrypt_command"]
+       decryptVal, valueDiags := methodCtx.ValueForExpression(decryptAttr.Expr)
+       diags = diags.Extend(valueDiags)
+       if diags.HasErrors() {
+               return nil, diags
+       }
+
+       decodeDecryptCmdDiags := 
gohcl.DecodeExpression(&hclsyntax.LiteralValueExpr{Val: decryptVal, SrcRange: 
decryptAttr.Expr.Range()}, nil, &methodCfg.DecryptCommand)
+       diags = diags.Extend(decodeDecryptCmdDiags)
+
+       return methodCfg, diags
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/opentofu-1.11.3/internal/encryption/method/unencrypted/method.go 
new/opentofu-1.11.4/internal/encryption/method/unencrypted/method.go
--- old/opentofu-1.11.3/internal/encryption/method/unencrypted/method.go        
2026-01-13 18:01:54.000000000 +0100
+++ new/opentofu-1.11.4/internal/encryption/method/unencrypted/method.go        
2026-01-21 16:57:28.000000000 +0100
@@ -6,6 +6,7 @@
 package unencrypted
 
 import (
+       "github.com/hashicorp/hcl/v2"
        "github.com/opentofu/opentofu/internal/encryption/config"
        "github.com/opentofu/opentofu/internal/encryption/method"
 )
@@ -19,8 +20,8 @@
 func (f *descriptor) ID() method.ID {
        return "unencrypted"
 }
-func (f *descriptor) ConfigStruct() method.Config {
-       return new(methodConfig)
+func (f *descriptor) DecodeConfig(_ method.EvalContext, _ hcl.Body) 
(method.Config, hcl.Diagnostics) {
+       return new(methodConfig), nil
 }
 
 type methodConfig struct{}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/opentofu-1.11.3/internal/encryption/methods.go 
new/opentofu-1.11.4/internal/encryption/methods.go
--- old/opentofu-1.11.3/internal/encryption/methods.go  2026-01-13 
18:01:54.000000000 +0100
+++ new/opentofu-1.11.4/internal/encryption/methods.go  2026-01-21 
16:57:28.000000000 +0100
@@ -9,14 +9,16 @@
        "context"
        "errors"
        "fmt"
+       "strings"
 
        "github.com/hashicorp/hcl/v2"
-       "github.com/hashicorp/hcl/v2/gohcl"
+       "github.com/hashicorp/hcl/v2/hclsyntax"
        "github.com/opentofu/opentofu/internal/addrs"
        "github.com/opentofu/opentofu/internal/configs"
        "github.com/opentofu/opentofu/internal/encryption/config"
        "github.com/opentofu/opentofu/internal/encryption/method"
        "github.com/opentofu/opentofu/internal/encryption/registry"
+       "github.com/zclconf/go-cty/cty"
 )
 
 // setupMethod sets up a single method for encryption. It returns a list of 
diagnostics if the method is invalid.
@@ -43,37 +45,60 @@
                }}
        }
 
-       methodConfig := encryptionMethod.ConfigStruct()
+       var methodCtx method.EvalContext
+       methodCtx = method.EvalContext{ValueForExpression: func(expr 
hcl.Expression) (cty.Value, hcl.Diagnostics) {
+               var diags hcl.Diagnostics
+
+               deps := expr.Variables()
+
+               kpConfigs, refs, kpDiags := filterKeyProviderReferences(enc, 
deps)
+               diags = diags.Extend(kpDiags)
+               if diags.HasErrors() {
+                       return cty.NilVal, diags
+               }
 
-       deps, diags := gohcl.VariablesInBody(cfg.Body, methodConfig)
-       if diags.HasErrors() {
-               return nil, diags
-       }
+               hclCtx, kpDiags := setupKeyProviders(ctx, enc, kpConfigs, meta, 
reg, staticEval)
+               diags = diags.Extend(kpDiags)
+               if diags.HasErrors() {
+                       return cty.NilVal, diags
+               }
 
-       kpConfigs, refs, kpDiags := filterKeyProviderReferences(enc, deps)
-       diags = diags.Extend(kpDiags)
-       if diags.HasErrors() {
-               return nil, diags
-       }
+               hclCtx, evalDiags := staticEval.EvalContextWithParent(ctx, 
hclCtx, configs.StaticIdentifier{
+                       Module:    addrs.RootModule,
+                       Subject:   fmt.Sprintf("encryption.method.%s.%s", 
cfg.Type, cfg.Name),
+                       DeclRange: enc.DeclRange,
+               }, refs)
+               diags = diags.Extend(evalDiags)
+               if diags.HasErrors() {
+                       return cty.NilVal, diags
+               }
 
-       hclCtx, kpDiags := setupKeyProviders(ctx, enc, kpConfigs, meta, reg, 
staticEval)
-       diags = diags.Extend(kpDiags)
-       if diags.HasErrors() {
-               return nil, diags
-       }
+               val, valDiags := expr.Value(hclCtx)
+               diags = diags.Extend(valDiags)
+               if diags.HasErrors() {
+                       return cty.NilVal, diags
+               }
 
-       hclCtx, evalDiags := staticEval.EvalContextWithParent(ctx, hclCtx, 
configs.StaticIdentifier{
-               Module:    addrs.RootModule,
-               Subject:   fmt.Sprintf("encryption.method.%s.%s", cfg.Type, 
cfg.Name),
-               DeclRange: enc.DeclRange,
-       }, refs)
-       diags = diags.Extend(evalDiags)
-       if diags.HasErrors() {
-               return nil, diags
-       }
+               if val.Type() == cty.String {
+                       // Try to be clever to see if it's a kp string that is 
actually a reference
+                       // We might want a bool to opt-in to this functionality 
for JSON compat on specific fields
+
+                       str := val.AsString()
+                       if strings.HasPrefix(str, "key_provider.") {
+                               traversal, travDiags := 
hclsyntax.ParseTraversalAbs([]byte(str), "", hcl.Pos{})
+                               if !travDiags.HasErrors() {
+                                       // Call into the expr resolver again
+                                       val, valDiags = 
methodCtx.ValueForExpression(&hclsyntax.ScopeTraversalExpr{Traversal: 
traversal, SrcRange: expr.Range()})
+                                       diags = diags.Extend(valDiags)
+                               }
+                       }
+
+               }
+
+               return val, diags
+       }}
 
-       methodDiags := gohcl.DecodeBody(cfg.Body, hclCtx, methodConfig)
-       diags = diags.Extend(methodDiags)
+       methodConfig, diags := encryptionMethod.DecodeConfig(methodCtx, 
cfg.Body)
        if diags.HasErrors() {
                return nil, diags
        }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/opentofu-1.11.3/internal/encryption/registry/compliancetest/compliance_method.go
 
new/opentofu-1.11.4/internal/encryption/registry/compliancetest/compliance_method.go
--- 
old/opentofu-1.11.3/internal/encryption/registry/compliancetest/compliance_method.go
        2026-01-13 18:01:54.000000000 +0100
+++ 
new/opentofu-1.11.4/internal/encryption/registry/compliancetest/compliance_method.go
        2026-01-21 16:57:28.000000000 +0100
@@ -10,6 +10,7 @@
        "fmt"
        "testing"
 
+       "github.com/hashicorp/hcl/v2"
        "github.com/opentofu/opentofu/internal/encryption/method"
        "github.com/opentofu/opentofu/internal/encryption/registry"
 )
@@ -124,8 +125,8 @@
        return t.id
 }
 
-func (t testMethodDescriptor) ConfigStruct() method.Config {
-       return &testMethodConfig{}
+func (t testMethodDescriptor) DecodeConfig(method.EvalContext, hcl.Body) 
(method.Config, hcl.Diagnostics) {
+       return &testMethodConfig{}, nil
 }
 
 type testMethodConfig struct {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/opentofu-1.11.3/internal/encryption/targets_test.go 
new/opentofu-1.11.4/internal/encryption/targets_test.go
--- old/opentofu-1.11.3/internal/encryption/targets_test.go     2026-01-13 
18:01:54.000000000 +0100
+++ new/opentofu-1.11.4/internal/encryption/targets_test.go     2026-01-21 
16:57:28.000000000 +0100
@@ -361,17 +361,72 @@
                                }
                                state {
                                        # Correct format but referencing a 
non-existent method
-                                       method = method.aes_gcm.nonexistent
+                                       method = method.aes_gcm.nonexistent # 
this is interpolated strictly to verify that things work as expected with 
interpolation of methods in the state block
                                        fallback {
                                                method = 
method.unencrypted.for_migration
                                        }
                                }
-                       `,
+`,
                        wantErr: `Test Config Source:12,15-41: Reference to 
undeclared encryption method; There is no method "aes_gcm" "nonexistent" block 
declared in the encryption block.`,
                        wantMethods: []func(method.Method) bool{
                                unencrypted.Is,
                        },
                },
+               // In https://github.com/opentofu/opentofu/issues/3482 was 
discovered that interpolation for
+               // target.method does not work, but only literal reference.
+               // This solves the inconsistencies between the way string 
expressions are evaluated for state.method vs method.keys.
+               "json-config-loads-state-method-interpolated": {
+                       rawConfig: `{
+                         "key_provider": {
+                               "static": {
+                                 "basic": {
+                                       "key": 
"6f6f706830656f67686f6834616872756f3751756165686565796f6f72653169"
+                                 }
+                               }
+                         },
+                         "method": {
+                               "aes_gcm": {
+                                 "example": {
+                                       "keys": "${key_provider.static.basic}"
+                                 }
+                               }
+                         },
+                         "state": {
+                               "enforced": true,
+                               "method": "method.aes_gcm.example"
+                         }
+                       }
+                       `,
+                       wantMethods: []func(method.Method) bool{
+                               aesgcm.Is,
+                       },
+               },
+               "json-config-loads-state-method-not-interpolated": {
+                       rawConfig: `{
+                         "key_provider": {
+                               "static": {
+                                 "basic": {
+                                       "key": 
"6f6f706830656f67686f6834616872756f3751756165686565796f6f72653169"
+                                 }
+                               }
+                         },
+                         "method": {
+                               "aes_gcm": {
+                                 "example": {
+                                       "keys": "key_provider.static.basic"
+                                 }
+                               }
+                         },
+                         "state": {
+                               "enforced": true,
+                               "method": "method.aes_gcm.example"
+                         }
+                       }
+                       `,
+                       wantMethods: []func(method.Method) bool{
+                               aesgcm.Is,
+                       },
+               },
        }
 
        reg := lockingencryptionregistry.New()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/opentofu-1.11.3/internal/plugin6/grpc_provider.go 
new/opentofu-1.11.4/internal/plugin6/grpc_provider.go
--- old/opentofu-1.11.3/internal/plugin6/grpc_provider.go       2026-01-13 
18:01:54.000000000 +0100
+++ new/opentofu-1.11.4/internal/plugin6/grpc_provider.go       2026-01-21 
16:57:28.000000000 +0100
@@ -34,14 +34,6 @@
 }
 
 var clientCapabilities = &proto6.ClientCapabilities{
-       // DeferralAllowed tells the provider that it is allowed to respond to
-       // all of the various post-configuration requests (as described by the
-       // [providers.Configured] interface) by reporting that the request
-       // must be "deferred" because there isn't yet enough information to
-       // satisfy the request. Setting this means that we need to be prepared
-       // for there to be a "deferred" object in the response from various
-       // other provider RPC functions.
-       DeferralAllowed: true,
        // WriteOnlyAttributesAllowed indicates that the current system version
        // supports write-only attributes.
        // This enables the SDK to run specific validations and enable the
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/opentofu-1.11.3/version/VERSION 
new/opentofu-1.11.4/version/VERSION
--- old/opentofu-1.11.3/version/VERSION 2026-01-13 18:01:54.000000000 +0100
+++ new/opentofu-1.11.4/version/VERSION 2026-01-21 16:57:28.000000000 +0100
@@ -1 +1 @@
-1.11.3
+1.11.4
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/opentofu-1.11.3/website/docs/language/syntax/json.mdx 
new/opentofu-1.11.4/website/docs/language/syntax/json.mdx
--- old/opentofu-1.11.3/website/docs/language/syntax/json.mdx   2026-01-13 
18:01:54.000000000 +0100
+++ new/opentofu-1.11.4/website/docs/language/syntax/json.mdx   2026-01-21 
16:57:28.000000000 +0100
@@ -451,11 +451,12 @@
 
 ### `terraform` blocks
 
-Settings within `terraform` blocks are generally interpreted literally, except
-for the `backend` block, which supports expressions. Other settings do not
+Settings within `terraform` blocks are _generally_ interpreted literally, 
except
+for the `backend` and `encryption` blocks, which support expressions. Other 
settings do not
 accept named object references or function calls and therefore do not treat
 string values as string templates.
 
+#### `backend` block
 Since only one `backend` block is allowed per `terraform` block, the compact
 block mapping can be used to represent it, with a nested object containing
 a single property whose name represents the backend type.
@@ -473,3 +474,145 @@
   }
 }
 ```
+
+#### `encryption` block
+As visible in the [encryption documentation](../state/encryption.mdx),
+the `encryption` block supports the following children blocks:
+* `key_provider`
+* `method`
+* `state`
+* `plan`
+* `remote_state_data_sources`
+
+The `key_provider` and `method` can refer to other named blocks, like 
variables, while `state`, `plan` and
+`remote_state_data_sources` can refer only to the other blocks inside the 
`encryption` block.
+
+A `key_provider` can refer to variables to be able to provide key information 
dynamically:
+
+```json
+{
+  "variable": {
+    "state_plan_passphrase": {
+      "type": "string",
+      "default": "myultrasecretpassphrase1!"
+    }
+  },
+  "terraform": {
+    "encryption": {
+      "key_provider": {
+        "pbkdf2": {
+          "state_plan": {
+            "passphrase": "${var.state_plan_passphrase}"
+          }
+        }
+      }
+    }
+  }
+}
+```
+
+A `method` block can refer to `key_provider` blocks by using static string 
references or regular terraform
+interpolated expressions:
+```json
+{
+  "terraform": {
+    "encryption": {
+      "key_provider": {
+        "pbkdf2": {
+          "state_plan": {
+            "passphrase": "myultrasecretpassphrase1!"
+          }
+        }
+      },
+      "method": {
+        "aes_gcm": {
+          "my_key_for_state": {
+            "keys": "${key_provider.pbkdf2.state_plan}"
+          },
+          "my_key_for_plan": {
+            "keys": "key_provider.pbkdf2.state_plan"
+          }
+        }
+      }
+    }
+  }
+}
+```
+
+The `state`, `plan` and `remote_state_data_sources` can strictly refer to 
`method` blocks by using string
+references. These are not expressions, are only references to the methods so 
the evaluation of these are not
+performed in the same way expressions are evaluated in OpenTofu:
+
+```json
+{
+  "variable": {
+    "remote_state_passphrase": {
+      "type": "string",
+      "default": "mysecrettestpassword!"
+    },
+    "state_plan_passphrase": {
+      "type": "string",
+      "default": "mysecrettestpassword2!"
+    }
+  },
+  "terraform": {
+    "encryption": {
+      "key_provider": {
+        "pbkdf2": {
+          "remote_state": {
+            "passphrase": "remotestateultrasecretpassphrase!1"
+          },
+          "state_plan": {
+            "passphrase": "stateplanultrasecretpassphrase!1"
+          }
+        }
+      },
+      "method": {
+        "aes_gcm": {
+          "remote_state": {
+            "keys": "${key_provider.pbkdf2.remote_state_key}"
+          },
+          "state_plan": {
+            "keys": "key_provider.pbkdf2.state_plan"
+          }
+        },
+        "unencrypted": {
+          "unencrypted": {}
+        }
+      },
+      "state": {
+        "method": "method.aes_gcm.state_plan",
+        "fallback": {
+          "method": "method.unencrypted.unencrypted"
+        }
+      },
+      "plan": {
+        "method": "method.aes_gcm.state_plan",
+        "fallback": {
+          "method": "method.unencrypted.unencrypted"
+        }
+      },
+      "remote_state_data_sources": {
+        "default": {
+          "method": "method.aes_gcm.remote_state"
+        },
+        "remote_state_data_source": {
+          "another_state": {
+            "method": "method.aes_gcm.remote_state"
+          }
+        }
+      }
+    }
+  },
+  "data": {
+    "terraform_remote_state": {
+      "another_state": {
+        "backend": "local",
+        "config": {
+          "path": "<path to an encrypted state>"
+        }
+      }
+    }
+  }
+}
+```
\ No newline at end of file

++++++ opentofu.obsinfo ++++++
--- /var/tmp/diff_new_pack.QTVh3R/_old  2026-01-27 16:12:02.177354795 +0100
+++ /var/tmp/diff_new_pack.QTVh3R/_new  2026-01-27 16:12:02.181354962 +0100
@@ -1,5 +1,5 @@
 name: opentofu
-version: 1.11.3
-mtime: 1768323714
-commit: f53d9e3a6f6e04a4f5f22b7c32c1ddc1272ba547
+version: 1.11.4
+mtime: 1769011048
+commit: 2a9b7ac61409f7f22bde65c192c5814eb4b75cdf
 

++++++ vendor.tar.gz ++++++
/work/SRC/openSUSE:Factory/opentofu/vendor.tar.gz 
/work/SRC/openSUSE:Factory/.opentofu.new.1928/vendor.tar.gz differ: char 135, 
line 1

Reply via email to