opentofu/experiments/experiment.go
Martin Atkins 0bbbb9c64b configs: Experimental support for optional object type attributes
This builds on an experimental feature in the underlying cty library which
allows marking specific attribtues of an object type constraint as
optional, which in turn modifies how the cty conversion package handles
missing attributes in a source value: it will silently substitute a null
value of the appropriate type rather than returning an error.

In order to implement the experiment this commit temporarily forks the
HCL typeexpr extension package into a local internal/typeexpr package,
where I've extended the type constraint syntax to allow annotating object
type attributes as being optional using the HCL function call syntax.
If the experiment is successful -- both at the Terraform layer and in
the underlying cty library -- we'll likely send these modifications to
upstream HCL so that other HCL-based languages can potentially benefit
from this new capability.

Because it's experimental, the optional attribute modifier is allowed only
with an explicit opt-in to the module_variable_optional_attrs experiment.
2020-10-12 10:12:28 -07:00

96 lines
3.5 KiB
Go

package experiments
// Experiment represents a particular experiment, which can be activated
// independently of all other experiments.
type Experiment string
// All active and defunct experiments must be represented by constants whose
// internal string values are unique.
//
// Each of these declared constants must also be registered as either a
// current or a defunct experiment in the init() function below.
//
// Each experiment is represented by a string that must be a valid HCL
// identifier so that it can be specified in configuration.
const (
VariableValidation = Experiment("variable_validation")
ModuleVariableOptionalAttrs = Experiment("module_variable_optional_attrs")
)
func init() {
// Each experiment constant defined above must be registered here as either
// a current or a concluded experiment.
registerConcludedExperiment(VariableValidation, "Custom variable validation can now be used by default, without enabling an experiment.")
registerCurrentExperiment(ModuleVariableOptionalAttrs)
}
// GetCurrent takes an experiment name and returns the experiment value
// representing that expression if and only if it is a current experiment.
//
// If the selected experiment is concluded, GetCurrent will return an
// error of type ConcludedError whose message hopefully includes some guidance
// for users of the experiment on how to migrate to a stable feature that
// succeeded it.
//
// If the selected experiment is not known at all, GetCurrent will return an
// error of type UnavailableError.
func GetCurrent(name string) (Experiment, error) {
exp := Experiment(name)
if currentExperiments.Has(exp) {
return exp, nil
}
if msg, concluded := concludedExperiments[exp]; concluded {
return Experiment(""), ConcludedError{ExperimentName: name, Message: msg}
}
return Experiment(""), UnavailableError{ExperimentName: name}
}
// Keyword returns the keyword that would be used to activate this experiment
// in the configuration.
func (e Experiment) Keyword() string {
return string(e)
}
// IsCurrent returns true if the receiver is considered a currently-selectable
// experiment.
func (e Experiment) IsCurrent() bool {
return currentExperiments.Has(e)
}
// IsConcluded returns true if the receiver is a concluded experiment.
func (e Experiment) IsConcluded() bool {
_, exists := concludedExperiments[e]
return exists
}
// currentExperiments are those which are available to activate in the current
// version of Terraform.
//
// Members of this set are registered in the init function above.
var currentExperiments = make(Set)
// concludedExperiments are those which were available to activate in an earlier
// version of Terraform but are no longer available, either because the feature
// in question has been implemented or because the experiment failed and the
// feature was abandoned. Each experiment maps to a message describing the
// outcome, so we can give users feedback about what they might do in modules
// using concluded experiments.
//
// After an experiment has been concluded for a whole major release span it can
// be removed, since we expect users to perform upgrades one major release at
// at time without skipping and thus they will see the concludedness error
// message as they upgrade through a prior major version.
//
// Members of this map are registered in the init function above.
var concludedExperiments = make(map[Experiment]string)
func registerCurrentExperiment(exp Experiment) {
currentExperiments.Add(exp)
}
func registerConcludedExperiment(exp Experiment, message string) {
concludedExperiments[exp] = message
}