allow to reference datasources locals and builds

azr/mini-dag
Adrien Delorme 4 years ago
parent 5f3316350d
commit cfd1189baf

@ -0,0 +1,82 @@
package addrs
import (
"fmt"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/gocty"
)
// InstanceKey represents the key of an instance within an object that
// contains multiple instances due to using "count" or "for_each" arguments
// in configuration.
//
// IntKey and StringKey are the two implementations of this type. No other
// implementations are allowed. The single instance of an object that _isn't_
// using "count" or "for_each" is represented by NoKey, which is a nil
// InstanceKey.
type InstanceKey interface {
instanceKeySigil()
String() string
// Value returns the cty.Value of the appropriate type for the InstanceKey
// value.
Value() cty.Value
}
// ParseInstanceKey returns the instance key corresponding to the given value,
// which must be known and non-null.
//
// If an unknown or null value is provided then this function will panic. This
// function is intended to deal with the values that would naturally be found
// in a hcl.TraverseIndex, which (when parsed from source, at least) can never
// contain unknown or null values.
func ParseInstanceKey(key cty.Value) (InstanceKey, error) {
switch key.Type() {
case cty.String:
return StringKey(key.AsString()), nil
case cty.Number:
var idx int
err := gocty.FromCtyValue(key, &idx)
return IntKey(idx), err
default:
return NoKey, fmt.Errorf("either a string or an integer is required")
}
}
// NoKey represents the absense of an InstanceKey, for the single instance
// of a configuration object that does not use "count" or "for_each" at all.
var NoKey InstanceKey
// IntKey is the InstanceKey representation representing integer indices, as
// used when the "count" argument is specified or if for_each is used with
// a sequence type.
type IntKey int
func (k IntKey) instanceKeySigil() {
}
func (k IntKey) String() string {
return fmt.Sprintf("[%d]", int(k))
}
func (k IntKey) Value() cty.Value {
return cty.NumberIntVal(int64(k))
}
// StringKey is the InstanceKey representation representing string indices, as
// used when the "for_each" argument is specified with a map or object type.
type StringKey string
func (k StringKey) instanceKeySigil() {
}
func (k StringKey) String() string {
// FIXME: This isn't _quite_ right because Go's quoted string syntax is
// slightly different than HCL's, but we'll accept it for now.
return fmt.Sprintf("[%q]", string(k))
}
func (k StringKey) Value() cty.Value {
return cty.StringVal(string(k))
}

@ -0,0 +1,11 @@
package addrs
// LocalValue is the address of a local value.
type LocalValue struct {
referenceable
Name string
}
func (v LocalValue) String() string {
return "local." + v.Name
}

@ -46,6 +46,14 @@ func parseRef(traversal hcl.Traversal) (*Reference, hcl.Diagnostics) {
switch root {
case "local":
name, rng, remain, diags := parseSingleAttrRef(traversal)
return &Reference{
Subject: LocalValue{Name: name},
SourceRange: rng,
Remaining: remain,
}, diags
case "var":
name, rng, remain, diags := parseSingleAttrRef(traversal)
return &Reference{
@ -54,6 +62,19 @@ func parseRef(traversal hcl.Traversal) (*Reference, hcl.Diagnostics) {
Remaining: remain,
}, diags
case "data":
if len(traversal) < 3 {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid reference",
Detail: `The "data" object must be followed by two attribute names: the data source type and the resource name.`,
Subject: traversal.SourceRange().Ptr(),
})
return nil, diags
}
remain := traversal[1:] // trim off "data" so we can use our shared resource reference parser
return parseResourceRef(DataResourceMode, rootRange, remain)
default:
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
@ -65,6 +86,94 @@ func parseRef(traversal hcl.Traversal) (*Reference, hcl.Diagnostics) {
return nil, diags
}
// parseResourceRef parses any kind of resource reference that is not a local or
// a var. It is handy to tell what is being referenced in a datasource, and in
// the future for a build. This function was taken from terraform core, hence
// why it is already refactored.
func parseResourceRef(mode ResourceMode, startRange hcl.Range, traversal hcl.Traversal) (*Reference, hcl.Diagnostics) {
var diags hcl.Diagnostics
var typeName, name string
switch tt := traversal[0].(type) { // Could be either root or attr, depending on our resource mode
case hcl.TraverseRoot:
typeName = tt.Name
case hcl.TraverseAttr:
typeName = tt.Name
default:
// If it isn't a TraverseRoot then it must be a "data" reference.
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid reference",
Detail: `The "data" object does not support this operation.`,
Subject: traversal[0].SourceRange().Ptr(),
})
return nil, diags
}
attrTrav, ok := traversal[1].(hcl.TraverseAttr)
if !ok {
var what string
switch mode {
case DataResourceMode:
what = "data source"
default:
what = "build type"
}
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid reference",
Detail: fmt.Sprintf(`A reference to a %s must be followed by at least one attribute access, specifying the resource name.`, what),
Subject: traversal[1].SourceRange().Ptr(),
})
return nil, diags
}
name = attrTrav.Name
rng := hcl.RangeBetween(startRange, attrTrav.SrcRange)
remain := traversal[2:]
resourceAddr := Resource{
Mode: mode,
Type: typeName,
Name: name,
}
resourceInstAddr := ResourceInstance{
Resource: resourceAddr,
Key: NoKey,
}
if len(remain) == 0 {
// This might actually be a reference to the collection of all instances
// of the resource, but we don't have enough context here to decide
// so we'll let the caller resolve that ambiguity.
return &Reference{
Subject: resourceAddr,
SourceRange: rng,
}, diags
}
if idxTrav, ok := remain[0].(hcl.TraverseIndex); ok {
var err error
resourceInstAddr.Key, err = ParseInstanceKey(idxTrav.Key)
if err != nil {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid index key",
Detail: fmt.Sprintf("Invalid index for resource instance: %s.", err),
Subject: &idxTrav.SrcRange,
})
return nil, diags
}
remain = remain[1:]
rng = hcl.RangeBetween(rng, idxTrav.SrcRange)
}
return &Reference{
Subject: resourceInstAddr,
SourceRange: rng,
Remaining: remain,
}, diags
}
func parseSingleAttrRef(traversal hcl.Traversal) (string, hcl.Range, hcl.Traversal, hcl.Diagnostics) {
var diags hcl.Diagnostics

@ -0,0 +1,74 @@
package addrs
import (
"fmt"
)
// Resource is an address for a resource block within configuration, which
// contains potentially-multiple resource instances if that configuration
// block uses "count" or "for_each".
type Resource struct {
referenceable
Mode ResourceMode
Type string
Name string
}
func (r Resource) String() string {
switch r.Mode {
case DataResourceMode:
return fmt.Sprintf("data.%s.%s", r.Type, r.Name)
default:
// Should never happen, but we'll return a string here rather than
// crashing just in case it does.
return fmt.Sprintf("<invalid>.%s.%s", r.Type, r.Name)
}
}
func (r Resource) Equal(o Resource) bool {
return r.Mode == o.Mode && r.Name == o.Name && r.Type == o.Type
}
// ResourceInstance is an address for a specific instance of a resource.
// When a resource is defined in configuration with "count" or "for_each" it
// produces zero or more instances, which can be addressed using this type.
type ResourceInstance struct {
referenceable
Resource Resource
Key InstanceKey
}
func (r ResourceInstance) ContainingResource() Resource {
return r.Resource
}
func (r ResourceInstance) String() string {
if r.Key == NoKey {
return r.Resource.String()
}
return r.Resource.String() + r.Key.String()
}
func (r ResourceInstance) Equal(o ResourceInstance) bool {
return r.Key == o.Key && r.Resource.Equal(o.Resource)
}
// ResourceMode defines which lifecycle applies to a given resource. Each
// resource lifecycle has a slightly different address format.
type ResourceMode rune
//go:generate go run golang.org/x/tools/cmd/stringer -type ResourceMode
const (
// InvalidResourceMode is the zero value of ResourceMode and is not
// a valid resource mode.
InvalidResourceMode ResourceMode = 0
// BuildResourceMode indicates a build, as defined by "build" blocks in
// configuration.
BuildResourceMode ResourceMode = 'B'
// DataResourceMode indicates a data resource, as defined by
// "data" blocks in configuration.
DataResourceMode ResourceMode = 'D'
)
Loading…
Cancel
Save