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 }