github.com/opentofu/opentofu@v1.7.1/internal/configs/module_merge.go (about)

     1  // Copyright (c) The OpenTofu Authors
     2  // SPDX-License-Identifier: MPL-2.0
     3  // Copyright (c) 2023 HashiCorp, Inc.
     4  // SPDX-License-Identifier: MPL-2.0
     5  
     6  package configs
     7  
     8  import (
     9  	"fmt"
    10  
    11  	"github.com/opentofu/opentofu/internal/addrs"
    12  
    13  	"github.com/hashicorp/hcl/v2"
    14  	"github.com/zclconf/go-cty/cty"
    15  	"github.com/zclconf/go-cty/cty/convert"
    16  )
    17  
    18  // The methods in this file are used by Module.mergeFile to apply overrides
    19  // to our different configuration elements. These methods all follow the
    20  // pattern of mutating the receiver to incorporate settings from the parameter,
    21  // returning error diagnostics if any aspect of the parameter cannot be merged
    22  // into the receiver for some reason.
    23  //
    24  // User expectation is that anything _explicitly_ set in the given object
    25  // should take precedence over the corresponding settings in the receiver,
    26  // but that anything omitted in the given object should be left unchanged.
    27  // In some cases it may be reasonable to do a "deep merge" of certain nested
    28  // features, if it is possible to unambiguously correlate the nested elements
    29  // and their behaviors are orthogonal to each other.
    30  
    31  func (p *Provider) merge(op *Provider) hcl.Diagnostics {
    32  	var diags hcl.Diagnostics
    33  
    34  	if op.Version.Required != nil {
    35  		p.Version = op.Version
    36  	}
    37  
    38  	p.Config = MergeBodies(p.Config, op.Config)
    39  
    40  	return diags
    41  }
    42  
    43  func (v *Variable) merge(ov *Variable) hcl.Diagnostics {
    44  	var diags hcl.Diagnostics
    45  
    46  	if ov.DescriptionSet {
    47  		v.Description = ov.Description
    48  		v.DescriptionSet = ov.DescriptionSet
    49  	}
    50  	if ov.SensitiveSet {
    51  		v.Sensitive = ov.Sensitive
    52  		v.SensitiveSet = ov.SensitiveSet
    53  	}
    54  	if ov.Default != cty.NilVal {
    55  		v.Default = ov.Default
    56  	}
    57  	if ov.Type != cty.NilType {
    58  		v.Type = ov.Type
    59  		v.ConstraintType = ov.ConstraintType
    60  	}
    61  	if ov.ParsingMode != 0 {
    62  		v.ParsingMode = ov.ParsingMode
    63  	}
    64  	if ov.NullableSet {
    65  		v.Nullable = ov.Nullable
    66  		v.NullableSet = ov.NullableSet
    67  	}
    68  
    69  	// If the override file overrode type without default or vice-versa then
    70  	// it may have created an invalid situation, which we'll catch now by
    71  	// attempting to re-convert the value.
    72  	//
    73  	// Note that here we may be re-converting an already-converted base value
    74  	// from the base config. This will be a no-op if the type was not changed,
    75  	// but in particular might be user-observable in the edge case where the
    76  	// literal value in config could've been converted to the overridden type
    77  	// constraint but the converted value cannot. In practice, this situation
    78  	// should be rare since most of our conversions are interchangable.
    79  	if v.Default != cty.NilVal {
    80  		val, err := convert.Convert(v.Default, v.ConstraintType)
    81  		if err != nil {
    82  			// What exactly we'll say in the error message here depends on whether
    83  			// it was Default or Type that was overridden here.
    84  			switch {
    85  			case ov.Type != cty.NilType && ov.Default == cty.NilVal:
    86  				// If only the type was overridden
    87  				diags = append(diags, &hcl.Diagnostic{
    88  					Severity: hcl.DiagError,
    89  					Summary:  "Invalid default value for variable",
    90  					Detail:   fmt.Sprintf("Overriding this variable's type constraint has made its default value invalid: %s.", err),
    91  					Subject:  &ov.DeclRange,
    92  				})
    93  			case ov.Type == cty.NilType && ov.Default != cty.NilVal:
    94  				// Only the default was overridden
    95  				diags = append(diags, &hcl.Diagnostic{
    96  					Severity: hcl.DiagError,
    97  					Summary:  "Invalid default value for variable",
    98  					Detail:   fmt.Sprintf("The overridden default value for this variable is not compatible with the variable's type constraint: %s.", err),
    99  					Subject:  &ov.DeclRange,
   100  				})
   101  			default:
   102  				diags = append(diags, &hcl.Diagnostic{
   103  					Severity: hcl.DiagError,
   104  					Summary:  "Invalid default value for variable",
   105  					Detail:   fmt.Sprintf("This variable's default value is not compatible with its type constraint: %s.", err),
   106  					Subject:  &ov.DeclRange,
   107  				})
   108  			}
   109  		} else {
   110  			v.Default = val
   111  		}
   112  
   113  		// ensure a null default wasn't merged in when it is not allowed
   114  		if !v.Nullable && v.Default.IsNull() {
   115  			diags = append(diags, &hcl.Diagnostic{
   116  				Severity: hcl.DiagError,
   117  				Summary:  "Invalid default value for variable",
   118  				Detail:   "A null default value is not valid when nullable=false.",
   119  				Subject:  &ov.DeclRange,
   120  			})
   121  		}
   122  	}
   123  
   124  	return diags
   125  }
   126  
   127  func (l *Local) merge(ol *Local) hcl.Diagnostics {
   128  	var diags hcl.Diagnostics
   129  
   130  	// Since a local is just a single expression in configuration, the
   131  	// override definition entirely replaces the base definition, including
   132  	// the source range so that we'll send the user to the right place if
   133  	// there is an error.
   134  	l.Expr = ol.Expr
   135  	l.DeclRange = ol.DeclRange
   136  
   137  	return diags
   138  }
   139  
   140  func (o *Output) merge(oo *Output) hcl.Diagnostics {
   141  	var diags hcl.Diagnostics
   142  
   143  	if oo.Description != "" {
   144  		o.Description = oo.Description
   145  	}
   146  	if oo.Expr != nil {
   147  		o.Expr = oo.Expr
   148  	}
   149  	if oo.SensitiveSet {
   150  		o.Sensitive = oo.Sensitive
   151  		o.SensitiveSet = oo.SensitiveSet
   152  	}
   153  
   154  	// We don't allow depends_on to be overridden because that is likely to
   155  	// cause confusing misbehavior.
   156  	if len(oo.DependsOn) != 0 {
   157  		diags = append(diags, &hcl.Diagnostic{
   158  			Severity: hcl.DiagError,
   159  			Summary:  "Unsupported override",
   160  			Detail:   "The depends_on argument may not be overridden.",
   161  			Subject:  oo.DependsOn[0].SourceRange().Ptr(), // the first item is the closest range we have
   162  		})
   163  	}
   164  
   165  	return diags
   166  }
   167  
   168  func (mc *ModuleCall) merge(omc *ModuleCall) hcl.Diagnostics {
   169  	var diags hcl.Diagnostics
   170  
   171  	if omc.SourceSet {
   172  		mc.SourceAddr = omc.SourceAddr
   173  		mc.SourceAddrRaw = omc.SourceAddrRaw
   174  		mc.SourceAddrRange = omc.SourceAddrRange
   175  		mc.SourceSet = omc.SourceSet
   176  	}
   177  
   178  	if omc.Count != nil {
   179  		mc.Count = omc.Count
   180  	}
   181  
   182  	if omc.ForEach != nil {
   183  		mc.ForEach = omc.ForEach
   184  	}
   185  
   186  	if len(omc.Version.Required) != 0 {
   187  		mc.Version = omc.Version
   188  	}
   189  
   190  	mc.Config = MergeBodies(mc.Config, omc.Config)
   191  
   192  	if len(omc.Providers) != 0 {
   193  		mc.Providers = omc.Providers
   194  	}
   195  
   196  	// We don't allow depends_on to be overridden because that is likely to
   197  	// cause confusing misbehavior.
   198  	if len(omc.DependsOn) != 0 {
   199  		diags = append(diags, &hcl.Diagnostic{
   200  			Severity: hcl.DiagError,
   201  			Summary:  "Unsupported override",
   202  			Detail:   "The depends_on argument may not be overridden.",
   203  			Subject:  omc.DependsOn[0].SourceRange().Ptr(), // the first item is the closest range we have
   204  		})
   205  	}
   206  
   207  	return diags
   208  }
   209  
   210  func (r *Resource) merge(or *Resource, rps map[string]*RequiredProvider) hcl.Diagnostics {
   211  	var diags hcl.Diagnostics
   212  
   213  	if r.Mode != or.Mode {
   214  		// This is always a programming error, since managed and data resources
   215  		// are kept in separate maps in the configuration structures.
   216  		panic(fmt.Errorf("can't merge %s into %s", or.Mode, r.Mode))
   217  	}
   218  
   219  	if or.Count != nil {
   220  		r.Count = or.Count
   221  	}
   222  	if or.ForEach != nil {
   223  		r.ForEach = or.ForEach
   224  	}
   225  
   226  	if or.ProviderConfigRef != nil {
   227  		r.ProviderConfigRef = or.ProviderConfigRef
   228  		if existing, exists := rps[or.ProviderConfigRef.Name]; exists {
   229  			r.Provider = existing.Type
   230  		} else {
   231  			r.Provider = addrs.ImpliedProviderForUnqualifiedType(r.ProviderConfigRef.Name)
   232  		}
   233  	}
   234  
   235  	// Provider FQN is set by OpenTofu during Merge
   236  
   237  	if r.Mode == addrs.ManagedResourceMode {
   238  		// or.Managed is always non-nil for managed resource mode
   239  
   240  		if or.Managed.Connection != nil {
   241  			r.Managed.Connection = or.Managed.Connection
   242  		}
   243  		if or.Managed.CreateBeforeDestroySet {
   244  			r.Managed.CreateBeforeDestroy = or.Managed.CreateBeforeDestroy
   245  			r.Managed.CreateBeforeDestroySet = or.Managed.CreateBeforeDestroySet
   246  		}
   247  		if len(or.Managed.IgnoreChanges) != 0 {
   248  			r.Managed.IgnoreChanges = or.Managed.IgnoreChanges
   249  		}
   250  		if or.Managed.IgnoreAllChanges {
   251  			r.Managed.IgnoreAllChanges = true
   252  		}
   253  		if or.Managed.PreventDestroySet {
   254  			r.Managed.PreventDestroy = or.Managed.PreventDestroy
   255  			r.Managed.PreventDestroySet = or.Managed.PreventDestroySet
   256  		}
   257  		if len(or.Managed.Provisioners) != 0 {
   258  			r.Managed.Provisioners = or.Managed.Provisioners
   259  		}
   260  	}
   261  
   262  	r.Config = MergeBodies(r.Config, or.Config)
   263  
   264  	// We don't allow depends_on to be overridden because that is likely to
   265  	// cause confusing misbehavior.
   266  	if len(or.DependsOn) != 0 {
   267  		diags = append(diags, &hcl.Diagnostic{
   268  			Severity: hcl.DiagError,
   269  			Summary:  "Unsupported override",
   270  			Detail:   "The depends_on argument may not be overridden.",
   271  			Subject:  or.DependsOn[0].SourceRange().Ptr(), // the first item is the closest range we have
   272  		})
   273  	}
   274  
   275  	return diags
   276  }