github.com/opentofu/opentofu@v1.7.1/internal/builtin/providers/tf/data_source_state.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 tf 7 8 import ( 9 "fmt" 10 "log" 11 12 "github.com/opentofu/opentofu/internal/backend" 13 "github.com/opentofu/opentofu/internal/backend/remote" 14 "github.com/opentofu/opentofu/internal/configs/configschema" 15 "github.com/opentofu/opentofu/internal/encryption" 16 "github.com/opentofu/opentofu/internal/providers" 17 "github.com/opentofu/opentofu/internal/tfdiags" 18 "github.com/zclconf/go-cty/cty" 19 20 backendInit "github.com/opentofu/opentofu/internal/backend/init" 21 ) 22 23 func dataSourceRemoteStateGetSchema() providers.Schema { 24 return providers.Schema{ 25 Block: &configschema.Block{ 26 Attributes: map[string]*configschema.Attribute{ 27 "backend": { 28 Type: cty.String, 29 Description: "The remote backend to use, e.g. `remote` or `http`.", 30 DescriptionKind: configschema.StringMarkdown, 31 Required: true, 32 }, 33 "config": { 34 Type: cty.DynamicPseudoType, 35 Description: "The configuration of the remote backend. " + 36 "Although this is optional, most backends require " + 37 "some configuration.\n\n" + 38 "The object can use any arguments that would be valid " + 39 "in the equivalent `terraform { backend \"<TYPE>\" { ... } }` " + 40 "block.", 41 DescriptionKind: configschema.StringMarkdown, 42 Optional: true, 43 }, 44 "defaults": { 45 Type: cty.DynamicPseudoType, 46 Description: "Default values for outputs, in case " + 47 "the state file is empty or lacks a required output.", 48 DescriptionKind: configschema.StringMarkdown, 49 Optional: true, 50 }, 51 "outputs": { 52 Type: cty.DynamicPseudoType, 53 Description: "An object containing every root-level " + 54 "output in the remote state.", 55 DescriptionKind: configschema.StringMarkdown, 56 Computed: true, 57 }, 58 "workspace": { 59 Type: cty.String, 60 Description: "The OpenTofu workspace to use, if " + 61 "the backend supports workspaces.", 62 DescriptionKind: configschema.StringMarkdown, 63 Optional: true, 64 }, 65 }, 66 }, 67 } 68 } 69 70 func dataSourceRemoteStateValidate(cfg cty.Value) tfdiags.Diagnostics { 71 var diags tfdiags.Diagnostics 72 73 // Getting the backend implicitly validates the configuration for it, 74 // but we can only do that if it's all known already. 75 if cfg.GetAttr("config").IsWhollyKnown() && cfg.GetAttr("backend").IsKnown() { 76 _, _, moreDiags := getBackend(cfg, nil) // Don't need the encryption for validation here 77 diags = diags.Append(moreDiags) 78 } else { 79 // Otherwise we'll just type-check the config object itself. 80 configTy := cfg.GetAttr("config").Type() 81 if configTy != cty.DynamicPseudoType && !(configTy.IsObjectType() || configTy.IsMapType()) { 82 diags = diags.Append(tfdiags.AttributeValue( 83 tfdiags.Error, 84 "Invalid backend configuration", 85 "The configuration must be an object value.", 86 cty.GetAttrPath("config"), 87 )) 88 } 89 } 90 91 { 92 defaultsTy := cfg.GetAttr("defaults").Type() 93 if defaultsTy != cty.DynamicPseudoType && !(defaultsTy.IsObjectType() || defaultsTy.IsMapType()) { 94 diags = diags.Append(tfdiags.AttributeValue( 95 tfdiags.Error, 96 "Invalid default values", 97 "Defaults must be given in an object value.", 98 cty.GetAttrPath("defaults"), 99 )) 100 } 101 } 102 103 return diags 104 } 105 106 func dataSourceRemoteStateRead(d cty.Value, enc encryption.StateEncryption) (cty.Value, tfdiags.Diagnostics) { 107 var diags tfdiags.Diagnostics 108 109 b, cfg, moreDiags := getBackend(d, enc) 110 diags = diags.Append(moreDiags) 111 if moreDiags.HasErrors() { 112 return cty.NilVal, diags 113 } 114 115 configureDiags := b.Configure(cfg) 116 if configureDiags.HasErrors() { 117 diags = diags.Append(configureDiags.Err()) 118 return cty.NilVal, diags 119 } 120 121 newState := make(map[string]cty.Value) 122 newState["backend"] = d.GetAttr("backend") 123 newState["config"] = d.GetAttr("config") 124 125 workspaceVal := d.GetAttr("workspace") 126 // This attribute is not computed, so we always have to store the state 127 // value, even if we implicitly use a default. 128 newState["workspace"] = workspaceVal 129 130 workspaceName := backend.DefaultStateName 131 if !workspaceVal.IsNull() { 132 workspaceName = workspaceVal.AsString() 133 } 134 135 state, err := b.StateMgr(workspaceName) 136 if err != nil { 137 diags = diags.Append(tfdiags.AttributeValue( 138 tfdiags.Error, 139 "Error loading state error", 140 fmt.Sprintf("error loading the remote state: %s", err), 141 cty.Path(nil).GetAttr("backend"), 142 )) 143 return cty.NilVal, diags 144 } 145 146 if err := state.RefreshState(); err != nil { 147 diags = diags.Append(err) 148 return cty.NilVal, diags 149 } 150 151 outputs := make(map[string]cty.Value) 152 153 if defaultsVal := d.GetAttr("defaults"); !defaultsVal.IsNull() { 154 newState["defaults"] = defaultsVal 155 it := defaultsVal.ElementIterator() 156 for it.Next() { 157 k, v := it.Element() 158 outputs[k.AsString()] = v 159 } 160 } else { 161 newState["defaults"] = cty.NullVal(cty.DynamicPseudoType) 162 } 163 164 remoteState := state.State() 165 if remoteState == nil { 166 diags = diags.Append(tfdiags.AttributeValue( 167 tfdiags.Error, 168 "Unable to find remote state", 169 "No stored state was found for the given workspace in the given backend.", 170 cty.Path(nil).GetAttr("workspace"), 171 )) 172 newState["outputs"] = cty.EmptyObjectVal 173 return cty.ObjectVal(newState), diags 174 } 175 mod := remoteState.RootModule() 176 if mod != nil { // should always have a root module in any valid state 177 for k, os := range mod.OutputValues { 178 outputs[k] = os.Value 179 } 180 } 181 182 newState["outputs"] = cty.ObjectVal(outputs) 183 184 return cty.ObjectVal(newState), diags 185 } 186 187 func getBackend(cfg cty.Value, enc encryption.StateEncryption) (backend.Backend, cty.Value, tfdiags.Diagnostics) { 188 var diags tfdiags.Diagnostics 189 190 backendType := cfg.GetAttr("backend").AsString() 191 192 // Don't break people using the old _local syntax - but note warning above 193 if backendType == "_local" { 194 log.Println(`[INFO] Switching old (unsupported) backend "_local" to "local"`) 195 backendType = "local" 196 } 197 198 // Create the client to access our remote state 199 log.Printf("[DEBUG] Initializing remote state backend: %s", backendType) 200 f := getBackendFactory(backendType) 201 if f == nil { 202 detail := fmt.Sprintf("There is no backend type named %q.", backendType) 203 if msg, removed := backendInit.RemovedBackends[backendType]; removed { 204 detail = msg 205 } 206 207 diags = diags.Append(tfdiags.AttributeValue( 208 tfdiags.Error, 209 "Invalid backend configuration", 210 detail, 211 cty.Path(nil).GetAttr("backend"), 212 )) 213 return nil, cty.NilVal, diags 214 } 215 b := f(enc) 216 217 config := cfg.GetAttr("config") 218 if config.IsNull() { 219 // We'll treat this as an empty configuration and see if the backend's 220 // schema and validation code will accept it. 221 config = cty.EmptyObjectVal 222 } 223 224 if config.Type().IsMapType() { // The code below expects an object type, so we'll convert 225 config = cty.ObjectVal(config.AsValueMap()) 226 } 227 228 schema := b.ConfigSchema() 229 // Try to coerce the provided value into the desired configuration type. 230 configVal, err := schema.CoerceValue(config) 231 if err != nil { 232 diags = diags.Append(tfdiags.AttributeValue( 233 tfdiags.Error, 234 "Invalid backend configuration", 235 fmt.Sprintf("The given configuration is not valid for backend %q: %s.", backendType, 236 tfdiags.FormatError(err)), 237 cty.Path(nil).GetAttr("config"), 238 )) 239 return nil, cty.NilVal, diags 240 } 241 242 newVal, validateDiags := b.PrepareConfig(configVal) 243 diags = diags.Append(validateDiags) 244 if validateDiags.HasErrors() { 245 return nil, cty.NilVal, diags 246 } 247 248 // If this is the enhanced remote backend, we want to disable the version 249 // check, because this is a read-only operation 250 if rb, ok := b.(*remote.Remote); ok { 251 rb.IgnoreVersionConflict() 252 } 253 254 return b, newVal, diags 255 } 256 257 // overrideBackendFactories allows test cases to control the set of available 258 // backends to allow for more self-contained tests. This should never be set 259 // in non-test code. 260 var overrideBackendFactories map[string]backend.InitFn 261 262 func getBackendFactory(backendType string) backend.InitFn { 263 if len(overrideBackendFactories) > 0 { 264 // Tests may override the set of backend factories. 265 return overrideBackendFactories[backendType] 266 } 267 268 return backendInit.Backend(backendType) 269 }