github.com/pulumi/terraform@v1.4.0/pkg/addrs/check.go (about)

     1  package addrs
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/hashicorp/hcl/v2"
     7  	"github.com/hashicorp/hcl/v2/hclsyntax"
     8  	"github.com/pulumi/terraform/pkg/tfdiags"
     9  )
    10  
    11  // Check is the address of a check rule within a checkable object.
    12  //
    13  // This represents the check rule globally within a configuration, and is used
    14  // during graph evaluation to identify a condition result object to update with
    15  // the result of check rule evaluation.
    16  //
    17  // The check address is not distinct from resource traversals, and check rule
    18  // values are not intended to be available to the language, so the address is
    19  // not Referenceable.
    20  //
    21  // Note also that the check address is only relevant within the scope of a run,
    22  // as reordering check blocks between runs will result in their addresses
    23  // changing. Check is therefore for internal use only and should not be exposed
    24  // in durable artifacts such as state snapshots.
    25  type Check struct {
    26  	Container Checkable
    27  	Type      CheckType
    28  	Index     int
    29  }
    30  
    31  func NewCheck(container Checkable, typ CheckType, index int) Check {
    32  	return Check{
    33  		Container: container,
    34  		Type:      typ,
    35  		Index:     index,
    36  	}
    37  }
    38  
    39  func (c Check) String() string {
    40  	container := c.Container.String()
    41  	switch c.Type {
    42  	case ResourcePrecondition:
    43  		return fmt.Sprintf("%s.precondition[%d]", container, c.Index)
    44  	case ResourcePostcondition:
    45  		return fmt.Sprintf("%s.postcondition[%d]", container, c.Index)
    46  	case OutputPrecondition:
    47  		return fmt.Sprintf("%s.precondition[%d]", container, c.Index)
    48  	default:
    49  		// This should not happen
    50  		return fmt.Sprintf("%s.condition[%d]", container, c.Index)
    51  	}
    52  }
    53  
    54  func (c Check) UniqueKey() UniqueKey {
    55  	return checkKey{
    56  		ContainerKey: c.Container.UniqueKey(),
    57  		Type:         c.Type,
    58  		Index:        c.Index,
    59  	}
    60  }
    61  
    62  type checkKey struct {
    63  	ContainerKey UniqueKey
    64  	Type         CheckType
    65  	Index        int
    66  }
    67  
    68  func (k checkKey) uniqueKeySigil() {}
    69  
    70  // CheckType describes a category of check. We use this only to establish
    71  // uniqueness for Check values, and do not expose this concept of "check types"
    72  // (which is subject to change in future) in any durable artifacts such as
    73  // state snapshots.
    74  //
    75  // (See [CheckableKind] for an enumeration that we _do_ use externally, to
    76  // describe the type of object being checked rather than the type of the check
    77  // itself.)
    78  type CheckType int
    79  
    80  //go:generate go run golang.org/x/tools/cmd/stringer -type=CheckType check.go
    81  
    82  const (
    83  	InvalidCondition      CheckType = 0
    84  	ResourcePrecondition  CheckType = 1
    85  	ResourcePostcondition CheckType = 2
    86  	OutputPrecondition    CheckType = 3
    87  )
    88  
    89  // Description returns a human-readable description of the check type. This is
    90  // presented in the user interface through a diagnostic summary.
    91  func (c CheckType) Description() string {
    92  	switch c {
    93  	case ResourcePrecondition:
    94  		return "Resource precondition"
    95  	case ResourcePostcondition:
    96  		return "Resource postcondition"
    97  	case OutputPrecondition:
    98  		return "Module output value precondition"
    99  	default:
   100  		// This should not happen
   101  		return "Condition"
   102  	}
   103  }
   104  
   105  // Checkable is an interface implemented by all address types that can contain
   106  // condition blocks.
   107  type Checkable interface {
   108  	UniqueKeyer
   109  
   110  	checkableSigil()
   111  
   112  	// Check returns the address of an individual check rule of a specified
   113  	// type and index within this checkable container.
   114  	Check(CheckType, int) Check
   115  
   116  	// ConfigCheckable returns the address of the configuration construct that
   117  	// this Checkable belongs to.
   118  	//
   119  	// Checkable objects can potentially be dynamically declared during a
   120  	// plan operation using constructs like resource for_each, and so
   121  	// ConfigCheckable gives us a way to talk about the static containers
   122  	// those dynamic objects belong to, in case we wish to group together
   123  	// dynamic checkable objects into their static checkable for reporting
   124  	// purposes.
   125  	ConfigCheckable() ConfigCheckable
   126  
   127  	CheckableKind() CheckableKind
   128  	String() string
   129  }
   130  
   131  var (
   132  	_ Checkable = AbsResourceInstance{}
   133  	_ Checkable = AbsOutputValue{}
   134  )
   135  
   136  // CheckableKind describes the different kinds of checkable objects.
   137  type CheckableKind rune
   138  
   139  //go:generate go run golang.org/x/tools/cmd/stringer -type=CheckableKind check.go
   140  
   141  const (
   142  	CheckableKindInvalid CheckableKind = 0
   143  	CheckableResource    CheckableKind = 'R'
   144  	CheckableOutputValue CheckableKind = 'O'
   145  )
   146  
   147  // ConfigCheckable is an interfaces implemented by address types that represent
   148  // configuration constructs that can have Checkable addresses associated with
   149  // them.
   150  //
   151  // This address type therefore in a sense represents a container for zero or
   152  // more checkable objects all declared by the same configuration construct,
   153  // so that we can talk about these groups of checkable objects before we're
   154  // ready to decide how many checkable objects belong to each one.
   155  type ConfigCheckable interface {
   156  	UniqueKeyer
   157  
   158  	configCheckableSigil()
   159  
   160  	CheckableKind() CheckableKind
   161  	String() string
   162  }
   163  
   164  var (
   165  	_ ConfigCheckable = ConfigResource{}
   166  	_ ConfigCheckable = ConfigOutputValue{}
   167  )
   168  
   169  // ParseCheckableStr attempts to parse the given string as a Checkable address
   170  // of the given kind.
   171  //
   172  // This should be the opposite of Checkable.String for any Checkable address
   173  // type, as long as "kind" is set to the value returned by the address's
   174  // CheckableKind method.
   175  //
   176  // We do not typically expect users to write out checkable addresses as input,
   177  // but we use them as part of some of our wire formats for persisting check
   178  // results between runs.
   179  func ParseCheckableStr(kind CheckableKind, src string) (Checkable, tfdiags.Diagnostics) {
   180  	var diags tfdiags.Diagnostics
   181  
   182  	traversal, parseDiags := hclsyntax.ParseTraversalAbs([]byte(src), "", hcl.InitialPos)
   183  	diags = diags.Append(parseDiags)
   184  	if parseDiags.HasErrors() {
   185  		return nil, diags
   186  	}
   187  
   188  	path, remain, diags := parseModuleInstancePrefix(traversal)
   189  	if diags.HasErrors() {
   190  		return nil, diags
   191  	}
   192  
   193  	if remain.IsRelative() {
   194  		// (relative means that there's either nothing left or what's next isn't an identifier)
   195  		diags = diags.Append(&hcl.Diagnostic{
   196  			Severity: hcl.DiagError,
   197  			Summary:  "Invalid checkable address",
   198  			Detail:   "Module path must be followed by either a resource instance address or an output value address.",
   199  			Subject:  remain.SourceRange().Ptr(),
   200  		})
   201  		return nil, diags
   202  	}
   203  
   204  	// We use "kind" to disambiguate here because unfortunately we've
   205  	// historically never reserved "output" as a possible resource type name
   206  	// and so it is in principle possible -- albeit unlikely -- that there
   207  	// might be a resource whose type is literally "output".
   208  	switch kind {
   209  	case CheckableResource:
   210  		riAddr, moreDiags := parseResourceInstanceUnderModule(path, remain)
   211  		diags = diags.Append(moreDiags)
   212  		if diags.HasErrors() {
   213  			return nil, diags
   214  		}
   215  		return riAddr, diags
   216  
   217  	case CheckableOutputValue:
   218  		if len(remain) != 2 {
   219  			diags = diags.Append(&hcl.Diagnostic{
   220  				Severity: hcl.DiagError,
   221  				Summary:  "Invalid checkable address",
   222  				Detail:   "Output address must have only one attribute part after the keyword 'output', giving the name of the output value.",
   223  				Subject:  remain.SourceRange().Ptr(),
   224  			})
   225  			return nil, diags
   226  		}
   227  		if remain.RootName() != "output" {
   228  			diags = diags.Append(&hcl.Diagnostic{
   229  				Severity: hcl.DiagError,
   230  				Summary:  "Invalid checkable address",
   231  				Detail:   "Output address must follow the module address with the keyword 'output'.",
   232  				Subject:  remain.SourceRange().Ptr(),
   233  			})
   234  			return nil, diags
   235  		}
   236  		if step, ok := remain[1].(hcl.TraverseAttr); !ok {
   237  			diags = diags.Append(&hcl.Diagnostic{
   238  				Severity: hcl.DiagError,
   239  				Summary:  "Invalid checkable address",
   240  				Detail:   "Output address must have only one attribute part after the keyword 'output', giving the name of the output value.",
   241  				Subject:  remain.SourceRange().Ptr(),
   242  			})
   243  			return nil, diags
   244  		} else {
   245  			return OutputValue{Name: step.Name}.Absolute(path), diags
   246  		}
   247  
   248  	default:
   249  		panic(fmt.Sprintf("unsupported CheckableKind %s", kind))
   250  	}
   251  }