From f03ea8b847005b7aae988263ff30f99dbf899699 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Thu, 3 Oct 2019 15:45:02 -0700 Subject: [PATCH] addrs: New address types for use in project-level configuration --- addrs/for_each_attr.go | 1 + addrs/local_value.go | 1 + addrs/project.go | 178 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 180 insertions(+) create mode 100644 addrs/project.go diff --git a/addrs/for_each_attr.go b/addrs/for_each_attr.go index 7a6385035d..e3bf33200a 100644 --- a/addrs/for_each_attr.go +++ b/addrs/for_each_attr.go @@ -4,6 +4,7 @@ package addrs // the interpolation scope, addressed using the "each" keyword, ex. "each.key" and "each.value" type ForEachAttr struct { referenceable + projectReferenceable Name string } diff --git a/addrs/local_value.go b/addrs/local_value.go index 61a07b9c75..a402620e64 100644 --- a/addrs/local_value.go +++ b/addrs/local_value.go @@ -7,6 +7,7 @@ import ( // LocalValue is the address of a local value. type LocalValue struct { referenceable + projectReferenceable Name string } diff --git a/addrs/project.go b/addrs/project.go new file mode 100644 index 0000000000..853b9227b9 --- /dev/null +++ b/addrs/project.go @@ -0,0 +1,178 @@ +package addrs + +import ( + "fmt" + + "github.com/hashicorp/hcl/v2" + "github.com/hashicorp/terraform/tfdiags" +) + +// ProjectWorkspace represents a single workspace within the current project. +type ProjectWorkspace struct { + Name string + Key InstanceKey + + projectReferenceable +} + +func (w ProjectWorkspace) String() string { + switch key := w.Key.(type) { + case nil: + return "workspace." + w.Name + case StringKey: + return fmt.Sprintf("workspace.%s.%s", w.Name, key) + default: + // No other key types are valid for project workspaces, but we'll + // tolerate this anyway for robustness. + return fmt.Sprintf("workspace.%s%s", w.Name, key.String()) + } +} + +// ProjectUpstreamWorkspace represents a workspace in some other project whose +// outputs are being imported into the current project. +type ProjectUpstreamWorkspace struct { + Name string + Key InstanceKey + + projectReferenceable +} + +func (w ProjectUpstreamWorkspace) String() string { + switch key := w.Key.(type) { + case nil: + return "upstream." + w.Name + case StringKey: + return fmt.Sprintf("upstream.%s.%s", w.Name, key) + default: + // No other key types are valid for project workspaces, but we'll + // tolerate this anyway for robustness. + return fmt.Sprintf("upstream.%s%s", w.Name, key.String()) + } +} + +// ProjectContextValue represents a named context value within a project +// configuration. This is similar to InputVariable, but within the definition +// of a project rather than within a module. +type ProjectContextValue struct { + Name string + + projectReferenceable +} + +func (v ProjectContextValue) String() string { + return "context." + v.Name +} + +// ProjectReferenceable is an interface implemented by all address types that +// can appear as references in project definition expressions. +type ProjectReferenceable interface { + // All implementations of this interface must be covered by the type switch + // in projectlang.Scope.buildEvalContext. + projectReferenceableSigil() + + // String produces a string representation of the address that could be + // parsed as a HCL traversal and passed to ParseProjectConfigRef to produce + // an equivalent result. + String() string +} + +type projectReferenceable struct { +} + +func (r projectReferenceable) projectReferenceableSigil() { +} + +// ProjectConfigReference describes a reference to an address with source +// location information, within the project configuration context. +type ProjectConfigReference struct { + Subject ProjectReferenceable + SourceRange tfdiags.SourceRange + Remaining hcl.Traversal +} + +// ParseProjectConfigRef attempts to extract a referencable address from the +// prefix of the given traversal, which must be an absolute traversal or this +// function will panic. +// +// This function is like ParseRef, but is for project-level configuration +// instead of module-level configuration. +// +// If no error diagnostics are returned, the returned reference includes the +// address that was extracted, the source range it was extracted from, and any +// remaining relative traversal that was not consumed as part of the +// reference. +// +// If error diagnostics are returned then the Reference value is invalid and +// must not be used. +func ParseProjectConfigRef(traversal hcl.Traversal) (*ProjectConfigReference, tfdiags.Diagnostics) { + ref, diags := parseProjectConfigRef(traversal) + + // Normalize a little to make life easier for callers. + if ref != nil { + if len(ref.Remaining) == 0 { + ref.Remaining = nil + } + } + + return ref, diags +} + +func parseProjectConfigRef(traversal hcl.Traversal) (*ProjectConfigReference, tfdiags.Diagnostics) { + var diags tfdiags.Diagnostics + + root := traversal.RootName() + rootRange := traversal[0].SourceRange() + + switch root { + + case "each": + name, rng, remain, diags := parseSingleAttrRef(traversal) + return &ProjectConfigReference{ + Subject: ForEachAttr{Name: name}, + SourceRange: tfdiags.SourceRangeFromHCL(rng), + Remaining: remain, + }, diags + + case "local": + name, rng, remain, diags := parseSingleAttrRef(traversal) + return &ProjectConfigReference{ + Subject: LocalValue{Name: name}, + SourceRange: tfdiags.SourceRangeFromHCL(rng), + Remaining: remain, + }, diags + + case "context": + name, rng, remain, diags := parseSingleAttrRef(traversal) + return &ProjectConfigReference{ + Subject: ProjectContextValue{Name: name}, + SourceRange: tfdiags.SourceRangeFromHCL(rng), + Remaining: remain, + }, diags + + case "workspace": + name, rng, remain, diags := parseSingleAttrRef(traversal) + return &ProjectConfigReference{ + Subject: ProjectWorkspace{Name: name}, + SourceRange: tfdiags.SourceRangeFromHCL(rng), + Remaining: remain, + }, diags + + case "upstream": + name, rng, remain, diags := parseSingleAttrRef(traversal) + return &ProjectConfigReference{ + Subject: ProjectUpstreamWorkspace{Name: name}, + SourceRange: tfdiags.SourceRangeFromHCL(rng), + Remaining: remain, + }, diags + + default: + diags = diags.Append(&hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Invalid reference", + Detail: fmt.Sprintf("There is no object %q in the project-level configuration language.", root), + Subject: rootRange.Ptr(), + Context: traversal.SourceRange().Ptr(), + }) + return nil, diags + } +}