2021-02-02 12:05:04 -05:00
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 {
2021-02-15 06:21:10 -05:00
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.
2021-02-02 12:05:04 -05:00
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 {
2021-02-15 06:21:10 -05:00
RequiredPlugins : nil ,
2021-02-02 12:05:04 -05:00
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 ( ) :
2021-02-15 05:47:44 -05:00
c := "version"
if cs , _ := decodeVersionConstraint ( attr ) ; len ( cs . Required ) > 0 {
c = cs . Required . String ( )
2021-02-02 12:05:04 -05:00
}
2021-02-15 05:47:44 -05:00
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 ( ) ,
} )
2021-02-15 06:21:10 -05:00
continue
2021-02-15 05:47:44 -05:00
2021-02-02 12:05:04 -05:00
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" ,
2021-02-15 05:49:02 -05:00
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 ( ) ,
2021-02-02 12:05:04 -05:00
} )
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 ... )
2021-02-15 06:21:10 -05:00
continue
2021-02-02 12:05:04 -05:00
} 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 ( ) ,
} )
}
2021-02-15 06:21:10 -05:00
if ret . RequiredPlugins == nil {
ret . RequiredPlugins = make ( map [ string ] * RequiredPlugin )
}
2021-02-02 12:05:04 -05:00
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
}