github.com/rstandt/terraform@v0.12.32-0.20230710220336-b1063613405c/builtin/providers/terraform/data_source_state.go (about) 1 package terraform 2 3 import ( 4 "fmt" 5 "log" 6 7 "github.com/hashicorp/terraform/backend" 8 "github.com/hashicorp/terraform/configs/configschema" 9 "github.com/hashicorp/terraform/providers" 10 "github.com/hashicorp/terraform/tfdiags" 11 "github.com/zclconf/go-cty/cty" 12 13 backendInit "github.com/hashicorp/terraform/backend/init" 14 ) 15 16 func dataSourceRemoteStateGetSchema() providers.Schema { 17 return providers.Schema{ 18 Block: &configschema.Block{ 19 Attributes: map[string]*configschema.Attribute{ 20 "backend": { 21 Type: cty.String, 22 Required: true, 23 }, 24 "config": { 25 Type: cty.DynamicPseudoType, 26 Optional: true, 27 }, 28 "defaults": { 29 Type: cty.DynamicPseudoType, 30 Optional: true, 31 }, 32 "outputs": { 33 Type: cty.DynamicPseudoType, 34 Computed: true, 35 }, 36 "workspace": { 37 Type: cty.String, 38 Optional: true, 39 }, 40 }, 41 }, 42 } 43 } 44 45 func dataSourceRemoteStateValidate(cfg cty.Value) tfdiags.Diagnostics { 46 var diags tfdiags.Diagnostics 47 48 // Getting the backend implicitly validates the configuration for it, 49 // but we can only do that if it's all known already. 50 if cfg.GetAttr("config").IsWhollyKnown() && cfg.GetAttr("backend").IsKnown() { 51 _, moreDiags := getBackend(cfg) 52 diags = diags.Append(moreDiags) 53 } else { 54 // Otherwise we'll just type-check the config object itself. 55 configTy := cfg.GetAttr("config").Type() 56 if configTy != cty.DynamicPseudoType && !(configTy.IsObjectType() || configTy.IsMapType()) { 57 diags = diags.Append(tfdiags.AttributeValue( 58 tfdiags.Error, 59 "Invalid backend configuration", 60 "The configuration must be an object value.", 61 cty.GetAttrPath("config"), 62 )) 63 } 64 } 65 66 { 67 defaultsTy := cfg.GetAttr("defaults").Type() 68 if defaultsTy != cty.DynamicPseudoType && !(defaultsTy.IsObjectType() || defaultsTy.IsMapType()) { 69 diags = diags.Append(tfdiags.AttributeValue( 70 tfdiags.Error, 71 "Invalid default values", 72 "Defaults must be given in an object value.", 73 cty.GetAttrPath("defaults"), 74 )) 75 } 76 } 77 78 return diags 79 } 80 81 func dataSourceRemoteStateRead(d cty.Value) (cty.Value, tfdiags.Diagnostics) { 82 var diags tfdiags.Diagnostics 83 84 b, moreDiags := getBackend(d) 85 diags = diags.Append(moreDiags) 86 if diags.HasErrors() { 87 return cty.NilVal, diags 88 } 89 90 newState := make(map[string]cty.Value) 91 newState["backend"] = d.GetAttr("backend") 92 newState["config"] = d.GetAttr("config") 93 94 workspaceName := backend.DefaultStateName 95 96 if workspaceVal := d.GetAttr("workspace"); !workspaceVal.IsNull() { 97 newState["workspace"] = workspaceVal 98 workspaceName = workspaceVal.AsString() 99 } 100 101 newState["workspace"] = cty.StringVal(workspaceName) 102 103 state, err := b.StateMgrWithoutCheckVersion(workspaceName) 104 if err != nil { 105 diags = diags.Append(tfdiags.AttributeValue( 106 tfdiags.Error, 107 "Error loading state error", 108 fmt.Sprintf("error loading the remote state: %s", err), 109 cty.Path(nil).GetAttr("backend"), 110 )) 111 return cty.NilVal, diags 112 } 113 114 if err := state.RefreshStateWithoutCheckVersion(); err != nil { 115 diags = diags.Append(err) 116 return cty.NilVal, diags 117 } 118 119 outputs := make(map[string]cty.Value) 120 121 if defaultsVal := d.GetAttr("defaults"); !defaultsVal.IsNull() { 122 newState["defaults"] = defaultsVal 123 it := defaultsVal.ElementIterator() 124 for it.Next() { 125 k, v := it.Element() 126 outputs[k.AsString()] = v 127 } 128 } else { 129 newState["defaults"] = cty.NullVal(cty.DynamicPseudoType) 130 } 131 132 remoteState := state.State() 133 if remoteState == nil { 134 diags = diags.Append(tfdiags.AttributeValue( 135 tfdiags.Error, 136 "Unable to find remote state", 137 "No stored state was found for the given workspace in the given backend.", 138 cty.Path(nil).GetAttr("workspace"), 139 )) 140 newState["outputs"] = cty.EmptyObjectVal 141 return cty.ObjectVal(newState), diags 142 } 143 mod := remoteState.RootModule() 144 if mod != nil { // should always have a root module in any valid state 145 for k, os := range mod.OutputValues { 146 outputs[k] = os.Value 147 } 148 } 149 150 newState["outputs"] = cty.ObjectVal(outputs) 151 152 return cty.ObjectVal(newState), diags 153 } 154 155 func getBackend(cfg cty.Value) (backend.Backend, tfdiags.Diagnostics) { 156 var diags tfdiags.Diagnostics 157 158 backendType := cfg.GetAttr("backend").AsString() 159 160 // Don't break people using the old _local syntax - but note warning above 161 if backendType == "_local" { 162 log.Println(`[INFO] Switching old (unsupported) backend "_local" to "local"`) 163 backendType = "local" 164 } 165 166 // Create the client to access our remote state 167 log.Printf("[DEBUG] Initializing remote state backend: %s", backendType) 168 f := backendInit.Backend(backendType) 169 if f == nil { 170 diags = diags.Append(tfdiags.AttributeValue( 171 tfdiags.Error, 172 "Invalid backend configuration", 173 fmt.Sprintf("There is no backend type named %q.", backendType), 174 cty.Path(nil).GetAttr("backend"), 175 )) 176 return nil, diags 177 } 178 b := f() 179 180 config := cfg.GetAttr("config") 181 if config.IsNull() { 182 // We'll treat this as an empty configuration and see if the backend's 183 // schema and validation code will accept it. 184 config = cty.EmptyObjectVal 185 } 186 187 if config.Type().IsMapType() { // The code below expects an object type, so we'll convert 188 config = cty.ObjectVal(config.AsValueMap()) 189 } 190 191 schema := b.ConfigSchema() 192 // Try to coerce the provided value into the desired configuration type. 193 configVal, err := schema.CoerceValue(config) 194 if err != nil { 195 diags = diags.Append(tfdiags.AttributeValue( 196 tfdiags.Error, 197 "Invalid backend configuration", 198 fmt.Sprintf("The given configuration is not valid for backend %q: %s.", backendType, 199 tfdiags.FormatError(err)), 200 cty.Path(nil).GetAttr("config"), 201 )) 202 return nil, diags 203 } 204 205 newVal, validateDiags := b.PrepareConfig(configVal) 206 diags = diags.Append(validateDiags) 207 if validateDiags.HasErrors() { 208 return nil, diags 209 } 210 configVal = newVal 211 212 configureDiags := b.Configure(configVal) 213 if configureDiags.HasErrors() { 214 diags = diags.Append(configureDiags.Err()) 215 return nil, diags 216 } 217 218 return b, diags 219 }