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