packer-cn/hcl2template/types.required_plugins.go

259 lines
7.8 KiB
Go

package hcl2template
import (
"fmt"
"github.com/hashicorp/go-version"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/packer/hcl2template/addrs"
"github.com/zclconf/go-cty/cty"
)
func (cfg *PackerConfig) decodeRequiredPluginsBlock(f *hcl.File) hcl.Diagnostics {
var diags hcl.Diagnostics
content, moreDiags := f.Body.Content(configSchema)
diags = append(diags, moreDiags...)
for _, block := range content.Blocks {
switch block.Type {
case packerLabel:
content, contentDiags := block.Body.Content(packerBlockSchema)
diags = append(diags, contentDiags...)
// We ignore "packer_version"" here because
// sniffCoreVersionRequirements already dealt with that
for _, innerBlock := range content.Blocks {
switch innerBlock.Type {
case "required_plugins":
reqs, reqsDiags := decodeRequiredPluginsBlock(innerBlock)
diags = append(diags, reqsDiags...)
cfg.Packer.RequiredPlugins = append(cfg.Packer.RequiredPlugins, reqs)
default:
continue
}
}
}
}
return diags
}
func (cfg *PackerConfig) decodeImplicitRequiredPluginsBlocks(f *hcl.File) hcl.Diagnostics {
// when a plugin is used but not defined in the required plugin blocks, it
// is 'implicitly used'. Here we read common configuration blocks to try to
// guess plugins.
var diags hcl.Diagnostics
content, moreDiags := f.Body.Content(configSchema)
diags = append(diags, moreDiags...)
for _, block := range content.Blocks {
switch block.Type {
case sourceLabel:
// TODO
}
}
return diags
}
// RequiredPlugin represents a declaration of a dependency on a particular
// Plugin version or source.
type RequiredPlugin struct {
Name string
// Source used to be able to tell how the template referenced this source,
// for example, "awesomecloud" instead of github.com/awesome/awesomecloud.
// This one is left here in case we want to go back to allowing inexplicit
// source url definitions.
Source string
Type *addrs.Plugin
Requirement VersionConstraint
DeclRange hcl.Range
}
type RequiredPlugins struct {
RequiredPlugins map[string]*RequiredPlugin
DeclRange hcl.Range
}
func decodeRequiredPluginsBlock(block *hcl.Block) (*RequiredPlugins, hcl.Diagnostics) {
attrs, diags := block.Body.JustAttributes()
ret := &RequiredPlugins{
RequiredPlugins: nil,
DeclRange: block.DefRange,
}
for name, attr := range attrs {
expr, err := attr.Expr.Value(nil)
if err != nil {
diags = append(diags, err...)
}
nameDiags := checkPluginNameNormalized(name, attr.Expr.Range())
diags = append(diags, nameDiags...)
rp := &RequiredPlugin{
Name: name,
DeclRange: attr.Expr.Range(),
}
switch {
case expr.Type().IsPrimitiveType():
c := "version"
if cs, _ := decodeVersionConstraint(attr); len(cs.Required) > 0 {
c = cs.Required.String()
}
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid plugin requirement",
Detail: fmt.Sprintf(`'%s = "%s"' plugin requirement calls are not possible.`+
` You must define a whole block. For example:`+"\n"+
`%[1]s = {`+"\n"+
` source = "github.com/hashicorp/%[1]s"`+"\n"+
` version = "%[2]s"`+"\n"+`}`,
name, c),
Subject: attr.Range.Ptr(),
})
continue
case expr.Type().IsObjectType():
if !expr.Type().HasAttribute("version") {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "No version constraint was set",
Detail: "The version field must be specified as a string. Ex: `version = \">= 1.2.0, < 2.0.0\". See https://www.packer.io/docs/templates/hcl_templates/blocks/packer#version-constraints for docs",
Subject: attr.Expr.Range().Ptr(),
})
continue
}
vc := VersionConstraint{
DeclRange: attr.Range,
}
constraint := expr.GetAttr("version")
if !constraint.Type().Equals(cty.String) || constraint.IsNull() {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid version constraint",
Detail: "Version must be specified as a string. See https://www.packer.io/docs/templates/hcl_templates/blocks/packer#version-constraint-syntax for docs.",
Subject: attr.Expr.Range().Ptr(),
})
continue
}
constraintStr := constraint.AsString()
constraints, err := version.NewConstraint(constraintStr)
if err != nil {
// NewConstraint doesn't return user-friendly errors, so we'll just
// ignore the provided error and produce our own generic one.
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid version constraint",
Detail: "This string does not use correct version constraint syntax. " +
"See https://www.packer.io/docs/templates/hcl_templates/blocks/packer#version-constraint-syntax for docs.\n" +
err.Error(),
Subject: attr.Expr.Range().Ptr(),
})
continue
}
vc.Required = constraints
rp.Requirement = vc
if !expr.Type().HasAttribute("source") {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "No source was set",
Detail: "The source field must be specified as a string. Ex: `source = \"coolcloud\". See https://www.packer.io/docs/templates/hcl_templates/blocks/packer#specifying-plugin-requirements for docs",
Subject: attr.Expr.Range().Ptr(),
})
continue
}
source := expr.GetAttr("source")
if !source.Type().Equals(cty.String) || source.IsNull() {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid source",
Detail: "Source must be specified as a string. For example: " + `source = "coolcloud"`,
Subject: attr.Expr.Range().Ptr(),
})
continue
}
rp.Source = source.AsString()
p, sourceDiags := addrs.ParsePluginSourceString(rp.Source)
if sourceDiags.HasErrors() {
for _, diag := range sourceDiags {
if diag.Subject == nil {
diag.Subject = attr.Expr.Range().Ptr()
}
}
diags = append(diags, sourceDiags...)
continue
} else {
rp.Type = p
}
attrTypes := expr.Type().AttributeTypes()
for name := range attrTypes {
if name == "version" || name == "source" {
continue
}
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid required_plugins object",
Detail: `required_plugins objects can only contain "version" and "source" attributes.`,
Subject: attr.Expr.Range().Ptr(),
})
break
}
default:
// should not happen
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid required_plugins syntax",
Detail: "required_plugins entries must be objects.",
Subject: attr.Expr.Range().Ptr(),
})
}
if ret.RequiredPlugins == nil {
ret.RequiredPlugins = make(map[string]*RequiredPlugin)
}
ret.RequiredPlugins[rp.Name] = rp
}
return ret, diags
}
// checkPluginNameNormalized verifies that the given string is already
// normalized and returns an error if not.
func checkPluginNameNormalized(name string, declrange hcl.Range) hcl.Diagnostics {
var diags hcl.Diagnostics
// verify that the plugin local name is normalized
normalized, err := addrs.IsPluginPartNormalized(name)
if err != nil {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid plugin local name",
Detail: fmt.Sprintf("%s is an invalid plugin local name: %s", name, err),
Subject: &declrange,
})
return diags
}
if !normalized {
// we would have returned this error already
normalizedPlugin, _ := addrs.ParsePluginPart(name)
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid plugin local name",
Detail: fmt.Sprintf("Plugin names must be normalized. Replace %q with %q to fix this error.", name, normalizedPlugin),
Subject: &declrange,
})
}
return diags
}