From 332fc6ef316cc66aef2a3bf1360891214c3adcb5 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Fri, 27 Sep 2019 16:47:10 -0700 Subject: [PATCH] projects/projectconfigs: decode "upstream" block contents --- projects/projectconfigs/config.go | 21 +++++++++ projects/projectconfigs/upstream.go | 69 +++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 projects/projectconfigs/upstream.go diff --git a/projects/projectconfigs/config.go b/projects/projectconfigs/config.go index 2999286cdb..16e90f8eb4 100644 --- a/projects/projectconfigs/config.go +++ b/projects/projectconfigs/config.go @@ -33,6 +33,7 @@ type Config struct { Context map[string]*ContextValue Locals map[string]*LocalValue Workspaces map[string]*Workspace + Upstreams map[string]*Upstream } func loadConfigDir(rootDir string) (*Config, tfdiags.Diagnostics) { @@ -187,6 +188,25 @@ func loadConfig(rootDir, filename string, src []byte, body hcl.Body) (*Config, t config.Workspaces[ws.Name] = ws } + + case "upstream": + u, moreDiags := decodeUpstreamBlock(block) + diags = diags.Append(moreDiags) + if u != nil { + if existing, exists := config.Upstreams[u.Name]; exists { + diags = diags.Append(&hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Duplicate \"upstream\" block", + Detail: fmt.Sprintf("An upstream %q block was already declared at %s.", u.Name, existing.DeclRange.StartString()), + Subject: u.NameRange.ToHCL().Ptr(), + Context: u.DeclRange.ToHCL().Ptr(), + }) + continue + } + + config.Upstreams[u.Name] = u + } + default: // No other block types in our schema, so anything else is a bug. panic(fmt.Sprintf("unexpected block type %q", block.Type)) @@ -201,5 +221,6 @@ var rootSchema = &hcl.BodySchema{ {Type: "context", LabelNames: []string{"name"}}, {Type: "locals"}, {Type: "workspace", LabelNames: []string{"name"}}, + {Type: "upstream", LabelNames: []string{"name"}}, }, } diff --git a/projects/projectconfigs/upstream.go b/projects/projectconfigs/upstream.go new file mode 100644 index 0000000000..f9fdea7988 --- /dev/null +++ b/projects/projectconfigs/upstream.go @@ -0,0 +1,69 @@ +package projectconfigs + +import ( + "fmt" + + "github.com/hashicorp/hcl2/hcl" + "github.com/hashicorp/hcl2/hcl/hclsyntax" + + "github.com/hashicorp/terraform/tfdiags" +) + +// Upstream represents a remote workspace from another project that this +// project derives values from. +type Upstream struct { + // Name is the name label given in the block header. + // + // It is guaranteed to be a valid HCL identifier. + Name string + + // ForEach is the expression given in the for_each argument, or nil if + // that argument wasn't set. + ForEach hcl.Expression + + // Remote is the expression given in the "remote" argument. + Remote hcl.Expression + + // DeclRange is the source range of the block header of this block, + // for use in diagnostic messages. NameRange is the range of the + // Name string specifically. + DeclRange, NameRange tfdiags.SourceRange +} + +func decodeUpstreamBlock(block *hcl.Block) (*Upstream, tfdiags.Diagnostics) { + var diags tfdiags.Diagnostics + u := &Upstream{ + Name: block.Labels[0], + DeclRange: tfdiags.SourceRangeFromHCL(block.DefRange), + NameRange: tfdiags.SourceRangeFromHCL(block.LabelRanges[0]), + } + + if !hclsyntax.ValidIdentifier(u.Name) { + diags = diags.Append(&hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Invalid name for \"upstream\" block", + Detail: fmt.Sprintf("The name %q is not a valid name for an \"upstream\" block. Must start with a letter, followed by zero or more letters, digits, and underscores.", u.Name), + Subject: block.LabelRanges[0].Ptr(), + }) + } + + content, hclDiags := block.Body.Content(workspaceSchema) + diags = diags.Append(hclDiags) + + if attr, ok := content.Attributes["for_each"]; ok { + u.ForEach = attr.Expr + } + + if attr, ok := content.Attributes["remote"]; ok { + u.Remote = attr.Expr + } + + return u, diags +} + +var upstreamSchema = &hcl.BodySchema{ + Attributes: []hcl.AttributeSchema{ + {Name: "for_each"}, + {Name: "remote", Required: true}, + }, +}