]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blob - vendor/github.com/hashicorp/terraform/terraform/context_input.go
Upgrade to 0.12
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / terraform / terraform / context_input.go
1 package terraform
2
3 import (
4 "context"
5 "fmt"
6 "log"
7 "sort"
8
9 "github.com/hashicorp/hcl2/hcl"
10 "github.com/hashicorp/hcl2/hcldec"
11 "github.com/zclconf/go-cty/cty"
12
13 "github.com/hashicorp/terraform/addrs"
14 "github.com/hashicorp/terraform/configs"
15 "github.com/hashicorp/terraform/tfdiags"
16 )
17
18 // Input asks for input to fill variables and provider configurations.
19 // This modifies the configuration in-place, so asking for Input twice
20 // may result in different UI output showing different current values.
21 func (c *Context) Input(mode InputMode) tfdiags.Diagnostics {
22 var diags tfdiags.Diagnostics
23 defer c.acquireRun("input")()
24
25 if c.uiInput == nil {
26 log.Printf("[TRACE] Context.Input: uiInput is nil, so skipping")
27 return diags
28 }
29
30 ctx := context.Background()
31
32 if mode&InputModeVar != 0 {
33 log.Printf("[TRACE] Context.Input: Prompting for variables")
34
35 // Walk the variables first for the root module. We walk them in
36 // alphabetical order for UX reasons.
37 configs := c.config.Module.Variables
38 names := make([]string, 0, len(configs))
39 for name := range configs {
40 names = append(names, name)
41 }
42 sort.Strings(names)
43 Variables:
44 for _, n := range names {
45 v := configs[n]
46
47 // If we only care about unset variables, then we should set any
48 // variable that is already set.
49 if mode&InputModeVarUnset != 0 {
50 if _, isSet := c.variables[n]; isSet {
51 continue
52 }
53 }
54
55 // this should only happen during tests
56 if c.uiInput == nil {
57 log.Println("[WARN] Context.uiInput is nil during input walk")
58 continue
59 }
60
61 // Ask the user for a value for this variable
62 var rawValue string
63 retry := 0
64 for {
65 var err error
66 rawValue, err = c.uiInput.Input(ctx, &InputOpts{
67 Id: fmt.Sprintf("var.%s", n),
68 Query: fmt.Sprintf("var.%s", n),
69 Description: v.Description,
70 })
71 if err != nil {
72 diags = diags.Append(tfdiags.Sourceless(
73 tfdiags.Error,
74 "Failed to request interactive input",
75 fmt.Sprintf("Terraform attempted to request a value for var.%s interactively, but encountered an error: %s.", n, err),
76 ))
77 return diags
78 }
79
80 if rawValue == "" && v.Default == cty.NilVal {
81 // Redo if it is required, but abort if we keep getting
82 // blank entries
83 if retry > 2 {
84 diags = diags.Append(tfdiags.Sourceless(
85 tfdiags.Error,
86 "Required variable not assigned",
87 fmt.Sprintf("The variable %q is required, so Terraform cannot proceed without a defined value for it.", n),
88 ))
89 continue Variables
90 }
91 retry++
92 continue
93 }
94
95 break
96 }
97
98 val, valDiags := v.ParsingMode.Parse(n, rawValue)
99 diags = diags.Append(valDiags)
100 if diags.HasErrors() {
101 continue
102 }
103
104 c.variables[n] = &InputValue{
105 Value: val,
106 SourceType: ValueFromInput,
107 }
108 }
109 }
110
111 if mode&InputModeProvider != 0 {
112 log.Printf("[TRACE] Context.Input: Prompting for provider arguments")
113
114 // We prompt for input only for provider configurations defined in
115 // the root module. At the time of writing that is an arbitrary
116 // restriction, but we have future plans to support "count" and
117 // "for_each" on modules that will then prevent us from supporting
118 // input for child module configurations anyway (since we'd need to
119 // dynamic-expand first), and provider configurations in child modules
120 // are not recommended since v0.11 anyway, so this restriction allows
121 // us to keep this relatively simple without significant hardship.
122
123 pcs := make(map[string]*configs.Provider)
124 pas := make(map[string]addrs.ProviderConfig)
125 for _, pc := range c.config.Module.ProviderConfigs {
126 addr := pc.Addr()
127 pcs[addr.String()] = pc
128 pas[addr.String()] = addr
129 log.Printf("[TRACE] Context.Input: Provider %s declared at %s", addr, pc.DeclRange)
130 }
131 // We also need to detect _implied_ provider configs from resources.
132 // These won't have *configs.Provider objects, but they will still
133 // exist in the map and we'll just treat them as empty below.
134 for _, rc := range c.config.Module.ManagedResources {
135 pa := rc.ProviderConfigAddr()
136 if pa.Alias != "" {
137 continue // alias configurations cannot be implied
138 }
139 if _, exists := pcs[pa.String()]; !exists {
140 pcs[pa.String()] = nil
141 pas[pa.String()] = pa
142 log.Printf("[TRACE] Context.Input: Provider %s implied by resource block at %s", pa, rc.DeclRange)
143 }
144 }
145 for _, rc := range c.config.Module.DataResources {
146 pa := rc.ProviderConfigAddr()
147 if pa.Alias != "" {
148 continue // alias configurations cannot be implied
149 }
150 if _, exists := pcs[pa.String()]; !exists {
151 pcs[pa.String()] = nil
152 pas[pa.String()] = pa
153 log.Printf("[TRACE] Context.Input: Provider %s implied by data block at %s", pa, rc.DeclRange)
154 }
155 }
156
157 for pk, pa := range pas {
158 pc := pcs[pk] // will be nil if this is an implied config
159
160 // Wrap the input into a namespace
161 input := &PrefixUIInput{
162 IdPrefix: pk,
163 QueryPrefix: pk + ".",
164 UIInput: c.uiInput,
165 }
166
167 schema := c.schemas.ProviderConfig(pa.Type)
168 if schema == nil {
169 // Could either be an incorrect config or just an incomplete
170 // mock in tests. We'll let a later pass decide, and just
171 // ignore this for the purposes of gathering input.
172 log.Printf("[TRACE] Context.Input: No schema available for provider type %q", pa.Type)
173 continue
174 }
175
176 // For our purposes here we just want to detect if attrbutes are
177 // set in config at all, so rather than doing a full decode
178 // (which would require us to prepare an evalcontext, etc) we'll
179 // use the low-level HCL API to process only the top-level
180 // structure.
181 var attrExprs hcl.Attributes // nil if there is no config
182 if pc != nil && pc.Config != nil {
183 lowLevelSchema := schemaForInputSniffing(hcldec.ImpliedSchema(schema.DecoderSpec()))
184 content, _, diags := pc.Config.PartialContent(lowLevelSchema)
185 if diags.HasErrors() {
186 log.Printf("[TRACE] Context.Input: %s has decode error, so ignoring: %s", pa, diags.Error())
187 continue
188 }
189 attrExprs = content.Attributes
190 }
191
192 keys := make([]string, 0, len(schema.Attributes))
193 for key := range schema.Attributes {
194 keys = append(keys, key)
195 }
196 sort.Strings(keys)
197
198 vals := map[string]cty.Value{}
199 for _, key := range keys {
200 attrS := schema.Attributes[key]
201 if attrS.Optional {
202 continue
203 }
204 if attrExprs != nil {
205 if _, exists := attrExprs[key]; exists {
206 continue
207 }
208 }
209 if !attrS.Type.Equals(cty.String) {
210 continue
211 }
212
213 log.Printf("[TRACE] Context.Input: Prompting for %s argument %s", pa, key)
214 rawVal, err := input.Input(ctx, &InputOpts{
215 Id: key,
216 Query: key,
217 Description: attrS.Description,
218 })
219 if err != nil {
220 log.Printf("[TRACE] Context.Input: Failed to prompt for %s argument %s: %s", pa, key, err)
221 continue
222 }
223
224 vals[key] = cty.StringVal(rawVal)
225 }
226
227 c.providerInputConfig[pk] = vals
228 log.Printf("[TRACE] Context.Input: Input for %s: %#v", pk, vals)
229 }
230 }
231
232 return diags
233 }
234
235 // schemaForInputSniffing returns a transformed version of a given schema
236 // that marks all attributes as optional, which the Context.Input method can
237 // use to detect whether a required argument is set without missing arguments
238 // themselves generating errors.
239 func schemaForInputSniffing(schema *hcl.BodySchema) *hcl.BodySchema {
240 ret := &hcl.BodySchema{
241 Attributes: make([]hcl.AttributeSchema, len(schema.Attributes)),
242 Blocks: schema.Blocks,
243 }
244
245 for i, attrS := range schema.Attributes {
246 ret.Attributes[i] = attrS
247 ret.Attributes[i].Required = false
248 }
249
250 return ret
251 }