github.com/opentofu/opentofu@v1.7.1/internal/tofu/node_provider.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 tofu 7 8 import ( 9 "fmt" 10 "log" 11 12 "github.com/hashicorp/hcl/v2" 13 "github.com/opentofu/opentofu/internal/configs/configschema" 14 "github.com/opentofu/opentofu/internal/providers" 15 "github.com/opentofu/opentofu/internal/tfdiags" 16 "github.com/zclconf/go-cty/cty" 17 ) 18 19 // NodeApplyableProvider represents a provider during an apply. 20 type NodeApplyableProvider struct { 21 *NodeAbstractProvider 22 } 23 24 var ( 25 _ GraphNodeExecutable = (*NodeApplyableProvider)(nil) 26 ) 27 28 // GraphNodeExecutable 29 func (n *NodeApplyableProvider) Execute(ctx EvalContext, op walkOperation) (diags tfdiags.Diagnostics) { 30 _, err := ctx.InitProvider(n.Addr) 31 diags = diags.Append(err) 32 if diags.HasErrors() { 33 return diags 34 } 35 provider, _, err := getProvider(ctx, n.Addr) 36 diags = diags.Append(err) 37 if diags.HasErrors() { 38 return diags 39 } 40 41 switch op { 42 case walkValidate: 43 log.Printf("[TRACE] NodeApplyableProvider: validating configuration for %s", n.Addr) 44 return diags.Append(n.ValidateProvider(ctx, provider)) 45 case walkPlan, walkPlanDestroy, walkApply, walkDestroy: 46 log.Printf("[TRACE] NodeApplyableProvider: configuring %s", n.Addr) 47 return diags.Append(n.ConfigureProvider(ctx, provider, false)) 48 case walkImport: 49 log.Printf("[TRACE] NodeApplyableProvider: configuring %s (requiring that configuration is wholly known)", n.Addr) 50 return diags.Append(n.ConfigureProvider(ctx, provider, true)) 51 } 52 return diags 53 } 54 55 func (n *NodeApplyableProvider) ValidateProvider(ctx EvalContext, provider providers.Interface) (diags tfdiags.Diagnostics) { 56 57 configBody := buildProviderConfig(ctx, n.Addr, n.ProviderConfig()) 58 59 // if a provider config is empty (only an alias), return early and don't continue 60 // validation. validate doesn't need to fully configure the provider itself, so 61 // skipping a provider with an implied configuration won't prevent other validation from completing. 62 _, noConfigDiags := configBody.Content(&hcl.BodySchema{}) 63 if !noConfigDiags.HasErrors() { 64 return nil 65 } 66 67 schemaResp := provider.GetProviderSchema() 68 diags = diags.Append(schemaResp.Diagnostics.InConfigBody(configBody, n.Addr.String())) 69 if diags.HasErrors() { 70 return diags 71 } 72 73 configSchema := schemaResp.Provider.Block 74 if configSchema == nil { 75 // Should never happen in real code, but often comes up in tests where 76 // mock schemas are being used that tend to be incomplete. 77 log.Printf("[WARN] ValidateProvider: no config schema is available for %s, so using empty schema", n.Addr) 78 configSchema = &configschema.Block{} 79 } 80 81 configVal, _, evalDiags := ctx.EvaluateBlock(configBody, configSchema, nil, EvalDataForNoInstanceKey) 82 if evalDiags.HasErrors() { 83 return diags.Append(evalDiags) 84 } 85 diags = diags.Append(evalDiags) 86 87 // If our config value contains any marked values, ensure those are 88 // stripped out before sending this to the provider 89 unmarkedConfigVal, _ := configVal.UnmarkDeep() 90 91 req := providers.ValidateProviderConfigRequest{ 92 Config: unmarkedConfigVal, 93 } 94 95 validateResp := provider.ValidateProviderConfig(req) 96 diags = diags.Append(validateResp.Diagnostics.InConfigBody(configBody, n.Addr.String())) 97 98 return diags 99 } 100 101 // ConfigureProvider configures a provider that is already initialized and retrieved. 102 // If verifyConfigIsKnown is true, ConfigureProvider will return an error if the 103 // provider configVal is not wholly known and is meant only for use during import. 104 func (n *NodeApplyableProvider) ConfigureProvider(ctx EvalContext, provider providers.Interface, verifyConfigIsKnown bool) (diags tfdiags.Diagnostics) { 105 config := n.ProviderConfig() 106 107 configBody := buildProviderConfig(ctx, n.Addr, config) 108 109 resp := provider.GetProviderSchema() 110 diags = diags.Append(resp.Diagnostics.InConfigBody(configBody, n.Addr.String())) 111 if diags.HasErrors() { 112 return diags 113 } 114 115 configSchema := resp.Provider.Block 116 configVal, configBody, evalDiags := ctx.EvaluateBlock(configBody, configSchema, nil, EvalDataForNoInstanceKey) 117 diags = diags.Append(evalDiags) 118 if evalDiags.HasErrors() { 119 return diags 120 } 121 122 if verifyConfigIsKnown && !configVal.IsWhollyKnown() { 123 diags = diags.Append(&hcl.Diagnostic{ 124 Severity: hcl.DiagError, 125 Summary: "Invalid provider configuration", 126 Detail: fmt.Sprintf("The configuration for %s depends on values that cannot be determined until apply.", n.Addr), 127 Subject: &config.DeclRange, 128 }) 129 return diags 130 } 131 132 // If our config value contains any marked values, ensure those are 133 // stripped out before sending this to the provider 134 unmarkedConfigVal, _ := configVal.UnmarkDeep() 135 136 // Allow the provider to validate and insert any defaults into the full 137 // configuration. 138 req := providers.ValidateProviderConfigRequest{ 139 Config: unmarkedConfigVal, 140 } 141 142 // ValidateProviderConfig is only used for validation. We are intentionally 143 // ignoring the PreparedConfig field to maintain existing behavior. 144 validateResp := provider.ValidateProviderConfig(req) 145 diags = diags.Append(validateResp.Diagnostics.InConfigBody(configBody, n.Addr.String())) 146 if diags.HasErrors() && config == nil { 147 // If there isn't an explicit "provider" block in the configuration, 148 // this error message won't be very clear. Add some detail to the error 149 // message in this case. 150 diags = diags.Append(tfdiags.Sourceless( 151 tfdiags.Error, 152 "Invalid provider configuration", 153 fmt.Sprintf(providerConfigErr, n.Addr.Provider), 154 )) 155 } 156 157 if diags.HasErrors() { 158 return diags 159 } 160 161 // If the provider returns something different, log a warning to help 162 // indicate to provider developers that the value is not used. 163 preparedCfg := validateResp.PreparedConfig 164 if preparedCfg != cty.NilVal && !preparedCfg.IsNull() && !preparedCfg.RawEquals(unmarkedConfigVal) { 165 log.Printf("[WARN] ValidateProviderConfig from %q changed the config value, but that value is unused", n.Addr) 166 } 167 168 configDiags := ctx.ConfigureProvider(n.Addr, unmarkedConfigVal) 169 diags = diags.Append(configDiags.InConfigBody(configBody, n.Addr.String())) 170 if diags.HasErrors() && config == nil { 171 // If there isn't an explicit "provider" block in the configuration, 172 // this error message won't be very clear. Add some detail to the error 173 // message in this case. 174 diags = diags.Append(tfdiags.Sourceless( 175 tfdiags.Error, 176 "Invalid provider configuration", 177 fmt.Sprintf(providerConfigErr, n.Addr.Provider), 178 )) 179 } 180 return diags 181 } 182 183 const providerConfigErr = `Provider %q requires explicit configuration. Add a provider block to the root module and configure the provider's required arguments as described in the provider documentation. 184 `