github.com/pulumi/terraform@v1.4.0/pkg/configs/experiments.go (about) 1 package configs 2 3 import ( 4 "fmt" 5 6 "github.com/hashicorp/hcl/v2" 7 "github.com/pulumi/terraform/pkg/experiments" 8 "github.com/pulumi/terraform/version" 9 ) 10 11 // When developing UI for experimental features, you can temporarily disable 12 // the experiment warning by setting this package-level variable to a non-empty 13 // value using a link-time flag: 14 // 15 // go install -ldflags="-X 'github.com/pulumi/terraform/pkg/configs.disableExperimentWarnings=yes'" 16 // 17 // This functionality is for development purposes only and is not a feature we 18 // are committing to supporting for end users. 19 var disableExperimentWarnings = "" 20 21 // sniffActiveExperiments does minimal parsing of the given body for 22 // "terraform" blocks with "experiments" attributes, returning the 23 // experiments found. 24 // 25 // This is separate from other processing so that we can be sure that all of 26 // the experiments are known before we process the result of the module config, 27 // and thus we can take into account which experiments are active when deciding 28 // how to decode. 29 func sniffActiveExperiments(body hcl.Body, allowed bool) (experiments.Set, hcl.Diagnostics) { 30 rootContent, _, diags := body.PartialContent(configFileTerraformBlockSniffRootSchema) 31 32 ret := experiments.NewSet() 33 34 for _, block := range rootContent.Blocks { 35 content, _, blockDiags := block.Body.PartialContent(configFileExperimentsSniffBlockSchema) 36 diags = append(diags, blockDiags...) 37 38 if attr, exists := content.Attributes["language"]; exists { 39 // We don't yet have a sense of selecting an edition of the 40 // language, but we're reserving this syntax for now so that 41 // if and when we do this later older versions of Terraform 42 // will emit a more helpful error message than just saying 43 // this attribute doesn't exist. Handling this as part of 44 // experiments is a bit odd for now but justified by the 45 // fact that a future fuller implementation of switchable 46 // languages would be likely use a similar implementation 47 // strategy as experiments, and thus would lead to this 48 // function being refactored to deal with both concerns at 49 // once. We'll see, though! 50 kw := hcl.ExprAsKeyword(attr.Expr) 51 currentVersion := version.SemVer.String() 52 const firstEdition = "TF2021" 53 switch { 54 case kw == "": // (the expression wasn't a keyword at all) 55 diags = diags.Append(&hcl.Diagnostic{ 56 Severity: hcl.DiagError, 57 Summary: "Invalid language edition", 58 Detail: fmt.Sprintf( 59 "The language argument expects a bare language edition keyword. Terraform %s supports only language edition %s, which is the default.", 60 currentVersion, firstEdition, 61 ), 62 Subject: attr.Expr.Range().Ptr(), 63 }) 64 case kw != firstEdition: 65 rel := "different" 66 if kw > firstEdition { // would be weird for this not to be true, but it's user input so anything goes 67 rel = "newer" 68 } 69 diags = diags.Append(&hcl.Diagnostic{ 70 Severity: hcl.DiagError, 71 Summary: "Unsupported language edition", 72 Detail: fmt.Sprintf( 73 "Terraform v%s only supports language edition %s. This module requires a %s version of Terraform CLI.", 74 currentVersion, firstEdition, rel, 75 ), 76 Subject: attr.Expr.Range().Ptr(), 77 }) 78 } 79 } 80 81 attr, exists := content.Attributes["experiments"] 82 if !exists { 83 continue 84 } 85 86 exps, expDiags := decodeExperimentsAttr(attr) 87 88 // Because we concluded this particular experiment in the same 89 // release as we made experiments alpha-releases-only, we need to 90 // treat it as special to avoid masking the "experiment has concluded" 91 // error with the more general "experiments are not available at all" 92 // error. Note that this experiment is marked as concluded so this 93 // only "allows" showing the different error message that it is 94 // concluded, and does not allow actually using the experiment outside 95 // of an alpha. 96 // NOTE: We should be able to remove this special exception a release 97 // or two after v1.3 when folks have had a chance to notice that the 98 // experiment has concluded and update their modules accordingly. 99 // When we do so, we might also consider changing decodeExperimentsAttr 100 // to _not_ include concluded experiments in the returned set, since 101 // we're doing that right now only to make this condition work. 102 if exps.Has(experiments.ModuleVariableOptionalAttrs) && len(exps) == 1 { 103 allowed = true 104 } 105 106 if allowed { 107 diags = append(diags, expDiags...) 108 if !expDiags.HasErrors() { 109 ret = experiments.SetUnion(ret, exps) 110 } 111 } else { 112 diags = diags.Append(&hcl.Diagnostic{ 113 Severity: hcl.DiagError, 114 Summary: "Module uses experimental features", 115 Detail: "Experimental features are intended only for gathering early feedback on new language designs, and so are available only in alpha releases of Terraform.", 116 Subject: attr.NameRange.Ptr(), 117 }) 118 } 119 } 120 121 return ret, diags 122 } 123 124 func decodeExperimentsAttr(attr *hcl.Attribute) (experiments.Set, hcl.Diagnostics) { 125 var diags hcl.Diagnostics 126 127 exprs, moreDiags := hcl.ExprList(attr.Expr) 128 diags = append(diags, moreDiags...) 129 if moreDiags.HasErrors() { 130 return nil, diags 131 } 132 133 var ret = experiments.NewSet() 134 for _, expr := range exprs { 135 kw := hcl.ExprAsKeyword(expr) 136 if kw == "" { 137 diags = diags.Append(&hcl.Diagnostic{ 138 Severity: hcl.DiagError, 139 Summary: "Invalid experiment keyword", 140 Detail: "Elements of \"experiments\" must all be keywords representing active experiments.", 141 Subject: expr.Range().Ptr(), 142 }) 143 continue 144 } 145 146 exp, err := experiments.GetCurrent(kw) 147 switch err := err.(type) { 148 case experiments.UnavailableError: 149 diags = diags.Append(&hcl.Diagnostic{ 150 Severity: hcl.DiagError, 151 Summary: "Unknown experiment keyword", 152 Detail: fmt.Sprintf("There is no current experiment with the keyword %q.", kw), 153 Subject: expr.Range().Ptr(), 154 }) 155 case experiments.ConcludedError: 156 // As a special case we still include the optional attributes 157 // experiment if it's present, because our caller treats that 158 // as special. See the comment in sniffActiveExperiments for 159 // more information, and remove this special case here one the 160 // special case up there is also removed. 161 if kw == "module_variable_optional_attrs" { 162 ret.Add(experiments.ModuleVariableOptionalAttrs) 163 } 164 165 diags = diags.Append(&hcl.Diagnostic{ 166 Severity: hcl.DiagError, 167 Summary: "Experiment has concluded", 168 Detail: fmt.Sprintf("Experiment %q is no longer available. %s", kw, err.Message), 169 Subject: expr.Range().Ptr(), 170 }) 171 case nil: 172 // No error at all means it's valid and current. 173 ret.Add(exp) 174 175 if disableExperimentWarnings == "" { 176 // However, experimental features are subject to breaking changes 177 // in future releases, so we'll warn about them to help make sure 178 // folks aren't inadvertently using them in places where that'd be 179 // inappropriate, particularly if the experiment is active in a 180 // shared module they depend on. 181 diags = diags.Append(&hcl.Diagnostic{ 182 Severity: hcl.DiagWarning, 183 Summary: fmt.Sprintf("Experimental feature %q is active", exp.Keyword()), 184 Detail: "Experimental features are available only in alpha releases of Terraform and are subject to breaking changes or total removal in later versions, based on feedback. We recommend against using experimental features in production.\n\nIf you have feedback on the design of this feature, please open a GitHub issue to discuss it.", 185 Subject: expr.Range().Ptr(), 186 }) 187 } 188 189 default: 190 // This should never happen, because GetCurrent is not documented 191 // to return any other error type, but we'll handle it to be robust. 192 diags = diags.Append(&hcl.Diagnostic{ 193 Severity: hcl.DiagError, 194 Summary: "Invalid experiment keyword", 195 Detail: fmt.Sprintf("Could not parse %q as an experiment keyword: %s.", kw, err.Error()), 196 Subject: expr.Range().Ptr(), 197 }) 198 } 199 } 200 return ret, diags 201 } 202 203 func checkModuleExperiments(m *Module) hcl.Diagnostics { 204 var diags hcl.Diagnostics 205 206 // When we have current experiments, this is a good place to check that 207 // the features in question can only be used when the experiments are 208 // active. Return error diagnostics if a feature is being used without 209 // opting in to the feature. For example: 210 /* 211 if !m.ActiveExperiments.Has(experiments.ResourceForEach) { 212 for _, rc := range m.ManagedResources { 213 if rc.ForEach != nil { 214 diags = append(diags, &hcl.Diagnostic{ 215 Severity: hcl.DiagError, 216 Summary: "Resource for_each is experimental", 217 Detail: "This feature is currently an opt-in experiment, subject to change in future releases based on feedback.\n\nActivate the feature for this module by adding resource_for_each to the list of active experiments.", 218 Subject: rc.ForEach.Range().Ptr(), 219 }) 220 } 221 } 222 for _, rc := range m.DataResources { 223 if rc.ForEach != nil { 224 diags = append(diags, &hcl.Diagnostic{ 225 Severity: hcl.DiagError, 226 Summary: "Resource for_each is experimental", 227 Detail: "This feature is currently an opt-in experiment, subject to change in future releases based on feedback.\n\nActivate the feature for this module by adding resource_for_each to the list of active experiments.", 228 Subject: rc.ForEach.Range().Ptr(), 229 }) 230 } 231 } 232 } 233 */ 234 235 return diags 236 }