| // Copyright (c) HashiCorp, Inc. |
| // SPDX-License-Identifier: MPL-2.0 |
| |
| package configs |
| |
| import ( |
| "fmt" |
| |
| "github.com/hashicorp/terraform/internal/addrs" |
| |
| "github.com/hashicorp/hcl/v2" |
| "github.com/zclconf/go-cty/cty" |
| "github.com/zclconf/go-cty/cty/convert" |
| ) |
| |
| // The methods in this file are used by Module.mergeFile to apply overrides |
| // to our different configuration elements. These methods all follow the |
| // pattern of mutating the receiver to incorporate settings from the parameter, |
| // returning error diagnostics if any aspect of the parameter cannot be merged |
| // into the receiver for some reason. |
| // |
| // User expectation is that anything _explicitly_ set in the given object |
| // should take precedence over the corresponding settings in the receiver, |
| // but that anything omitted in the given object should be left unchanged. |
| // In some cases it may be reasonable to do a "deep merge" of certain nested |
| // features, if it is possible to unambiguously correlate the nested elements |
| // and their behaviors are orthogonal to each other. |
| |
| func (p *Provider) merge(op *Provider) hcl.Diagnostics { |
| var diags hcl.Diagnostics |
| |
| if op.Version.Required != nil { |
| p.Version = op.Version |
| } |
| |
| p.Config = MergeBodies(p.Config, op.Config) |
| |
| return diags |
| } |
| |
| func (v *Variable) merge(ov *Variable) hcl.Diagnostics { |
| var diags hcl.Diagnostics |
| |
| if ov.DescriptionSet { |
| v.Description = ov.Description |
| v.DescriptionSet = ov.DescriptionSet |
| } |
| if ov.SensitiveSet { |
| v.Sensitive = ov.Sensitive |
| v.SensitiveSet = ov.SensitiveSet |
| } |
| if ov.Default != cty.NilVal { |
| v.Default = ov.Default |
| } |
| if ov.Type != cty.NilType { |
| v.Type = ov.Type |
| v.ConstraintType = ov.ConstraintType |
| } |
| if ov.ParsingMode != 0 { |
| v.ParsingMode = ov.ParsingMode |
| } |
| if ov.NullableSet { |
| v.Nullable = ov.Nullable |
| v.NullableSet = ov.NullableSet |
| } |
| |
| // If the override file overrode type without default or vice-versa then |
| // it may have created an invalid situation, which we'll catch now by |
| // attempting to re-convert the value. |
| // |
| // Note that here we may be re-converting an already-converted base value |
| // from the base config. This will be a no-op if the type was not changed, |
| // but in particular might be user-observable in the edge case where the |
| // literal value in config could've been converted to the overridden type |
| // constraint but the converted value cannot. In practice, this situation |
| // should be rare since most of our conversions are interchangable. |
| if v.Default != cty.NilVal { |
| val, err := convert.Convert(v.Default, v.ConstraintType) |
| if err != nil { |
| // What exactly we'll say in the error message here depends on whether |
| // it was Default or Type that was overridden here. |
| switch { |
| case ov.Type != cty.NilType && ov.Default == cty.NilVal: |
| // If only the type was overridden |
| diags = append(diags, &hcl.Diagnostic{ |
| Severity: hcl.DiagError, |
| Summary: "Invalid default value for variable", |
| Detail: fmt.Sprintf("Overriding this variable's type constraint has made its default value invalid: %s.", err), |
| Subject: &ov.DeclRange, |
| }) |
| case ov.Type == cty.NilType && ov.Default != cty.NilVal: |
| // Only the default was overridden |
| diags = append(diags, &hcl.Diagnostic{ |
| Severity: hcl.DiagError, |
| Summary: "Invalid default value for variable", |
| Detail: fmt.Sprintf("The overridden default value for this variable is not compatible with the variable's type constraint: %s.", err), |
| Subject: &ov.DeclRange, |
| }) |
| default: |
| diags = append(diags, &hcl.Diagnostic{ |
| Severity: hcl.DiagError, |
| Summary: "Invalid default value for variable", |
| Detail: fmt.Sprintf("This variable's default value is not compatible with its type constraint: %s.", err), |
| Subject: &ov.DeclRange, |
| }) |
| } |
| } else { |
| v.Default = val |
| } |
| |
| // ensure a null default wasn't merged in when it is not allowed |
| if !v.Nullable && v.Default.IsNull() { |
| diags = append(diags, &hcl.Diagnostic{ |
| Severity: hcl.DiagError, |
| Summary: "Invalid default value for variable", |
| Detail: "A null default value is not valid when nullable=false.", |
| Subject: &ov.DeclRange, |
| }) |
| } |
| } |
| |
| return diags |
| } |
| |
| func (l *Local) merge(ol *Local) hcl.Diagnostics { |
| var diags hcl.Diagnostics |
| |
| // Since a local is just a single expression in configuration, the |
| // override definition entirely replaces the base definition, including |
| // the source range so that we'll send the user to the right place if |
| // there is an error. |
| l.Expr = ol.Expr |
| l.DeclRange = ol.DeclRange |
| |
| return diags |
| } |
| |
| func (o *Output) merge(oo *Output) hcl.Diagnostics { |
| var diags hcl.Diagnostics |
| |
| if oo.Description != "" { |
| o.Description = oo.Description |
| } |
| if oo.Expr != nil { |
| o.Expr = oo.Expr |
| } |
| if oo.SensitiveSet { |
| o.Sensitive = oo.Sensitive |
| o.SensitiveSet = oo.SensitiveSet |
| } |
| |
| // We don't allow depends_on to be overridden because that is likely to |
| // cause confusing misbehavior. |
| if len(oo.DependsOn) != 0 { |
| diags = append(diags, &hcl.Diagnostic{ |
| Severity: hcl.DiagError, |
| Summary: "Unsupported override", |
| Detail: "The depends_on argument may not be overridden.", |
| Subject: oo.DependsOn[0].SourceRange().Ptr(), // the first item is the closest range we have |
| }) |
| } |
| |
| return diags |
| } |
| |
| func (mc *ModuleCall) merge(omc *ModuleCall) hcl.Diagnostics { |
| var diags hcl.Diagnostics |
| |
| if omc.SourceSet { |
| mc.SourceAddr = omc.SourceAddr |
| mc.SourceAddrRaw = omc.SourceAddrRaw |
| mc.SourceAddrRange = omc.SourceAddrRange |
| mc.SourceSet = omc.SourceSet |
| } |
| |
| if omc.Count != nil { |
| mc.Count = omc.Count |
| } |
| |
| if omc.ForEach != nil { |
| mc.ForEach = omc.ForEach |
| } |
| |
| if len(omc.Version.Required) != 0 { |
| mc.Version = omc.Version |
| } |
| |
| mc.Config = MergeBodies(mc.Config, omc.Config) |
| |
| if len(omc.Providers) != 0 { |
| mc.Providers = omc.Providers |
| } |
| |
| // We don't allow depends_on to be overridden because that is likely to |
| // cause confusing misbehavior. |
| if len(omc.DependsOn) != 0 { |
| diags = append(diags, &hcl.Diagnostic{ |
| Severity: hcl.DiagError, |
| Summary: "Unsupported override", |
| Detail: "The depends_on argument may not be overridden.", |
| Subject: omc.DependsOn[0].SourceRange().Ptr(), // the first item is the closest range we have |
| }) |
| } |
| |
| return diags |
| } |
| |
| func (r *Resource) merge(or *Resource, rps map[string]*RequiredProvider) hcl.Diagnostics { |
| var diags hcl.Diagnostics |
| |
| if r.Mode != or.Mode { |
| // This is always a programming error, since managed and data resources |
| // are kept in separate maps in the configuration structures. |
| panic(fmt.Errorf("can't merge %s into %s", or.Mode, r.Mode)) |
| } |
| |
| if or.Count != nil { |
| r.Count = or.Count |
| } |
| if or.ForEach != nil { |
| r.ForEach = or.ForEach |
| } |
| |
| if or.ProviderConfigRef != nil { |
| r.ProviderConfigRef = or.ProviderConfigRef |
| if existing, exists := rps[or.ProviderConfigRef.Name]; exists { |
| r.Provider = existing.Type |
| } else { |
| r.Provider = addrs.ImpliedProviderForUnqualifiedType(r.ProviderConfigRef.Name) |
| } |
| } |
| |
| // Provider FQN is set by Terraform during Merge |
| |
| if r.Mode == addrs.ManagedResourceMode { |
| // or.Managed is always non-nil for managed resource mode |
| |
| if or.Managed.Connection != nil { |
| r.Managed.Connection = or.Managed.Connection |
| } |
| if or.Managed.CreateBeforeDestroySet { |
| r.Managed.CreateBeforeDestroy = or.Managed.CreateBeforeDestroy |
| r.Managed.CreateBeforeDestroySet = or.Managed.CreateBeforeDestroySet |
| } |
| if len(or.Managed.IgnoreChanges) != 0 { |
| r.Managed.IgnoreChanges = or.Managed.IgnoreChanges |
| } |
| if or.Managed.IgnoreAllChanges { |
| r.Managed.IgnoreAllChanges = true |
| } |
| if or.Managed.PreventDestroySet { |
| r.Managed.PreventDestroy = or.Managed.PreventDestroy |
| r.Managed.PreventDestroySet = or.Managed.PreventDestroySet |
| } |
| if len(or.Managed.Provisioners) != 0 { |
| r.Managed.Provisioners = or.Managed.Provisioners |
| } |
| } |
| |
| r.Config = MergeBodies(r.Config, or.Config) |
| |
| // We don't allow depends_on to be overridden because that is likely to |
| // cause confusing misbehavior. |
| if len(or.DependsOn) != 0 { |
| diags = append(diags, &hcl.Diagnostic{ |
| Severity: hcl.DiagError, |
| Summary: "Unsupported override", |
| Detail: "The depends_on argument may not be overridden.", |
| Subject: or.DependsOn[0].SourceRange().Ptr(), // the first item is the closest range we have |
| }) |
| } |
| |
| return diags |
| } |