github.com/kanishk98/terraform@v1.3.0-dev.0.20220917174235-661ca8088a6a/internal/terraform/context_input.go (about) 1 package terraform 2 3 import ( 4 "context" 5 "log" 6 "sort" 7 8 "github.com/hashicorp/hcl/v2" 9 "github.com/hashicorp/hcl/v2/hcldec" 10 "github.com/zclconf/go-cty/cty" 11 12 "github.com/hashicorp/terraform/internal/addrs" 13 "github.com/hashicorp/terraform/internal/configs" 14 "github.com/hashicorp/terraform/internal/tfdiags" 15 ) 16 17 // Input asks for input to fill unset required arguments in provider 18 // configurations. 19 // 20 // Unlike the other better-behaved operation methods, this one actually 21 // modifies some internal state inside the receving context so that the 22 // captured values will be implicitly available to a subsequent call to Plan, 23 // or to some other operation entry point. Hopefully a future iteration of 24 // this will change design to make that data flow more explicit. 25 // 26 // Because Input saves the results inside the Context object, asking for 27 // input twice on the same Context is invalid and will lead to undefined 28 // behavior. 29 // 30 // Once you've called Input with a particular config, it's invalid to call 31 // any other Context method with a different config, because the aforementioned 32 // modified internal state won't match. Again, this is an architectural wart 33 // that we'll hopefully resolve in future. 34 func (c *Context) Input(config *configs.Config, mode InputMode) tfdiags.Diagnostics { 35 // This function used to be responsible for more than it is now, so its 36 // interface is more general than its current functionality requires. 37 // It now exists only to handle interactive prompts for provider 38 // configurations, with other prompts the responsibility of the CLI 39 // layer prior to calling in to this package. 40 // 41 // (Hopefully in future the remaining functionality here can move to the 42 // CLI layer too in order to avoid this odd situation where core code 43 // produces UI input prompts.) 44 45 var diags tfdiags.Diagnostics 46 defer c.acquireRun("input")() 47 48 schemas, moreDiags := c.Schemas(config, nil) 49 diags = diags.Append(moreDiags) 50 if moreDiags.HasErrors() { 51 return diags 52 } 53 54 if c.uiInput == nil { 55 log.Printf("[TRACE] Context.Input: uiInput is nil, so skipping") 56 return diags 57 } 58 59 ctx := context.Background() 60 61 if mode&InputModeProvider != 0 { 62 log.Printf("[TRACE] Context.Input: Prompting for provider arguments") 63 64 // We prompt for input only for provider configurations defined in 65 // the root module. Provider configurations in other modules are a 66 // legacy thing we no longer recommend, and even if they weren't we 67 // can't practically prompt for their inputs here because we've not 68 // yet done "expansion" and so we don't know whether the modules are 69 // using count or for_each. 70 71 pcs := make(map[string]*configs.Provider) 72 pas := make(map[string]addrs.LocalProviderConfig) 73 for _, pc := range config.Module.ProviderConfigs { 74 addr := pc.Addr() 75 pcs[addr.String()] = pc 76 pas[addr.String()] = addr 77 log.Printf("[TRACE] Context.Input: Provider %s declared at %s", addr, pc.DeclRange) 78 } 79 // We also need to detect _implied_ provider configs from resources. 80 // These won't have *configs.Provider objects, but they will still 81 // exist in the map and we'll just treat them as empty below. 82 for _, rc := range config.Module.ManagedResources { 83 pa := rc.ProviderConfigAddr() 84 if pa.Alias != "" { 85 continue // alias configurations cannot be implied 86 } 87 if _, exists := pcs[pa.String()]; !exists { 88 pcs[pa.String()] = nil 89 pas[pa.String()] = pa 90 log.Printf("[TRACE] Context.Input: Provider %s implied by resource block at %s", pa, rc.DeclRange) 91 } 92 } 93 for _, rc := range config.Module.DataResources { 94 pa := rc.ProviderConfigAddr() 95 if pa.Alias != "" { 96 continue // alias configurations cannot be implied 97 } 98 if _, exists := pcs[pa.String()]; !exists { 99 pcs[pa.String()] = nil 100 pas[pa.String()] = pa 101 log.Printf("[TRACE] Context.Input: Provider %s implied by data block at %s", pa, rc.DeclRange) 102 } 103 } 104 105 for pk, pa := range pas { 106 pc := pcs[pk] // will be nil if this is an implied config 107 108 // Wrap the input into a namespace 109 input := &PrefixUIInput{ 110 IdPrefix: pk, 111 QueryPrefix: pk + ".", 112 UIInput: c.uiInput, 113 } 114 115 providerFqn := config.Module.ProviderForLocalConfig(pa) 116 schema := schemas.ProviderConfig(providerFqn) 117 if schema == nil { 118 // Could either be an incorrect config or just an incomplete 119 // mock in tests. We'll let a later pass decide, and just 120 // ignore this for the purposes of gathering input. 121 log.Printf("[TRACE] Context.Input: No schema available for provider type %q", pa.LocalName) 122 continue 123 } 124 125 // For our purposes here we just want to detect if attrbutes are 126 // set in config at all, so rather than doing a full decode 127 // (which would require us to prepare an evalcontext, etc) we'll 128 // use the low-level HCL API to process only the top-level 129 // structure. 130 var attrExprs hcl.Attributes // nil if there is no config 131 if pc != nil && pc.Config != nil { 132 lowLevelSchema := schemaForInputSniffing(hcldec.ImpliedSchema(schema.DecoderSpec())) 133 content, _, diags := pc.Config.PartialContent(lowLevelSchema) 134 if diags.HasErrors() { 135 log.Printf("[TRACE] Context.Input: %s has decode error, so ignoring: %s", pa, diags.Error()) 136 continue 137 } 138 attrExprs = content.Attributes 139 } 140 141 keys := make([]string, 0, len(schema.Attributes)) 142 for key := range schema.Attributes { 143 keys = append(keys, key) 144 } 145 sort.Strings(keys) 146 147 vals := map[string]cty.Value{} 148 for _, key := range keys { 149 attrS := schema.Attributes[key] 150 if attrS.Optional { 151 continue 152 } 153 if attrExprs != nil { 154 if _, exists := attrExprs[key]; exists { 155 continue 156 } 157 } 158 if !attrS.Type.Equals(cty.String) { 159 continue 160 } 161 162 log.Printf("[TRACE] Context.Input: Prompting for %s argument %s", pa, key) 163 rawVal, err := input.Input(ctx, &InputOpts{ 164 Id: key, 165 Query: key, 166 Description: attrS.Description, 167 }) 168 if err != nil { 169 log.Printf("[TRACE] Context.Input: Failed to prompt for %s argument %s: %s", pa, key, err) 170 continue 171 } 172 173 vals[key] = cty.StringVal(rawVal) 174 } 175 176 absConfigAddr := addrs.AbsProviderConfig{ 177 Provider: providerFqn, 178 Alias: pa.Alias, 179 Module: config.Path, 180 } 181 c.providerInputConfig[absConfigAddr.String()] = vals 182 183 log.Printf("[TRACE] Context.Input: Input for %s: %#v", pk, vals) 184 } 185 } 186 187 return diags 188 } 189 190 // schemaForInputSniffing returns a transformed version of a given schema 191 // that marks all attributes as optional, which the Context.Input method can 192 // use to detect whether a required argument is set without missing arguments 193 // themselves generating errors. 194 func schemaForInputSniffing(schema *hcl.BodySchema) *hcl.BodySchema { 195 ret := &hcl.BodySchema{ 196 Attributes: make([]hcl.AttributeSchema, len(schema.Attributes)), 197 Blocks: schema.Blocks, 198 } 199 200 for i, attrS := range schema.Attributes { 201 ret.Attributes[i] = attrS 202 ret.Attributes[i].Required = false 203 } 204 205 return ret 206 }