2023-03-02 15:37:05 -05:00
// Copyright (c) HashiCorp, Inc.
2023-08-10 15:53:29 -07:00
// SPDX-License-Identifier: BUSL-1.1
2023-03-02 15:37:05 -05:00
2019-10-14 17:02:53 +02:00
package hcl2template
import (
2020-02-06 11:49:21 +01:00
"fmt"
2020-10-30 15:38:29 +01:00
"log"
2020-02-06 11:49:21 +01:00
"strings"
2020-10-30 12:26:22 +01:00
"unicode"
2020-02-06 11:49:21 +01:00
2019-10-14 17:02:53 +02:00
"github.com/hashicorp/hcl/v2"
2020-02-06 11:49:21 +01:00
"github.com/hashicorp/hcl/v2/ext/typeexpr"
2019-10-15 11:34:26 +02:00
"github.com/hashicorp/hcl/v2/gohcl"
2020-02-06 11:49:21 +01:00
"github.com/hashicorp/hcl/v2/hclsyntax"
2020-10-30 12:26:22 +01:00
"github.com/hashicorp/packer/hcl2template/addrs"
2020-02-06 11:49:21 +01:00
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/convert"
2019-10-14 17:02:53 +02:00
)
2020-10-30 12:26:22 +01:00
// A consistent detail message for all "not a valid identifier" diagnostics.
const badIdentifierDetail = "A name must start with a letter or underscore and may contain only letters, digits, underscores, and dashes."
2020-10-27 10:03:36 +01:00
// Local represents a single entry from a "locals" block in a file.
2020-02-21 12:12:30 +01:00
// The "locals" block itself is not represented, because it serves only to
// provide context for us to interpret its contents.
2020-07-24 10:58:03 +02:00
type LocalBlock struct {
2020-02-21 12:12:30 +01:00
Name string
Expr hcl . Expression
2021-01-26 01:21:44 -08:00
// When Sensitive is set to true Packer will try its best to hide/obfuscate
// the variable from the output stream. By replacing the text.
Sensitive bool
2020-02-21 12:12:30 +01:00
}
2020-11-02 11:49:40 +01:00
// VariableAssignment represents a way a variable was set: the expression
// setting it and the value of that expression. It helps pinpoint were
// something was set in diagnostics.
type VariableAssignment struct {
// From tells were it was taken from, command/varfile/env/default
From string
Value cty . Value
Expr hcl . Expression
}
2020-02-06 11:49:21 +01:00
type Variable struct {
2020-11-02 11:49:40 +01:00
// Values contains possible values for the variable; The last value set
// from these will be the one used. If none is set; an error will be
// returned by Value().
Values [] VariableAssignment
// Validations contains all variables validation rules to be applied to the
// used value. Only the used value - the last value from Values - is
// validated.
Validations [] * VariableValidation
2019-10-14 17:02:53 +02:00
2020-02-06 11:49:21 +01:00
// Cty Type of the variable. If the default value or a collected value is
// not of this type nor can be converted to this type an error diagnostic
// will show up. This allows us to assume that values are valid later in
// code.
//
// When a default value - and no type - is passed in the variable
// declaration, the type of the default variable will be used. This will
// allow to ensure that users set this variable correctly.
Type cty . Type
2020-02-17 18:05:15 +01:00
// Common name of the variable
Name string
2020-02-06 11:49:21 +01:00
// Description of the variable
Description string
// When Sensitive is set to true Packer will try it best to hide/obfuscate
// the variable from the output stream. By replacing the text.
Sensitive bool
2020-03-04 13:19:07 +01:00
Range hcl . Range
2019-10-14 17:02:53 +02:00
}
2020-02-06 11:49:21 +01:00
func ( v * Variable ) GoString () string {
2020-11-02 11:49:40 +01:00
b := & strings . Builder {}
fmt . Fprintf ( b , "{type:%s" , v . Type . GoString ())
for _ , vv := range v . Values {
fmt . Fprintf ( b , ",%s:%s" , vv . From , vv . Value )
}
fmt . Fprintf ( b , "}" )
return b . String ()
2020-02-06 11:49:21 +01:00
}
2019-10-14 17:02:53 +02:00
2020-10-30 15:38:29 +01:00
// validateValue ensures that all of the configured custom validations for a
// variable value are passing.
2020-11-02 11:49:40 +01:00
func ( v * Variable ) validateValue ( val VariableAssignment ) ( diags hcl . Diagnostics ) {
2020-10-30 15:38:29 +01:00
if len ( v . Validations ) == 0 {
2021-02-15 12:25:45 +01:00
log . Printf ( "[TRACE] validateValue: not active for %s, so skipping" , v . Name )
2020-10-30 15:38:29 +01:00
return nil
}
hclCtx := & hcl . EvalContext {
Variables : map [ string ] cty . Value {
"var" : cty . ObjectVal ( map [ string ] cty . Value {
2020-11-02 11:49:40 +01:00
v . Name : val . Value ,
2020-10-30 15:38:29 +01:00
}),
},
Functions : Functions ( "" ),
}
for _ , validation := range v . Validations {
const errInvalidCondition = "Invalid variable validation result"
result , moreDiags := validation . Condition . Value ( hclCtx )
diags = append ( diags , moreDiags ... )
if moreDiags . HasErrors () {
log . Printf ( "[TRACE] evalVariableValidations: %s rule %s condition expression failed: %s" , v . Name , validation . DeclRange , moreDiags . Error ())
}
if ! result . IsKnown () {
log . Printf ( "[TRACE] evalVariableValidations: %s rule %s condition value is unknown, so skipping validation for now" , v . Name , validation . DeclRange )
continue // We'll wait until we've learned more, then.
}
if result . IsNull () {
diags = append ( diags , & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : errInvalidCondition ,
Detail : "Validation condition expression must return either true or false, not null." ,
Subject : validation . Condition . Range (). Ptr (),
Expression : validation . Condition ,
EvalContext : hclCtx ,
})
continue
}
var err error
result , err = convert . Convert ( result , cty . Bool )
if err != nil {
diags = append ( diags , & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : errInvalidCondition ,
Detail : fmt . Sprintf ( "Invalid validation condition result value: %s." , err ),
Subject : validation . Condition . Range (). Ptr (),
Expression : validation . Condition ,
EvalContext : hclCtx ,
})
continue
}
if result . False () {
2020-11-02 11:49:40 +01:00
subj := validation . DeclRange . Ptr ()
if val . Expr != nil {
subj = val . Expr . Range (). Ptr ()
}
2020-10-30 15:38:29 +01:00
diags = append ( diags , & hcl . Diagnostic {
Severity : hcl . DiagError ,
2020-11-02 11:49:40 +01:00
Summary : fmt . Sprintf ( "Invalid value for %s variable" , val . From ),
2020-10-30 15:38:29 +01:00
Detail : fmt . Sprintf ( "%s\n\nThis was checked by the validation rule at %s." , validation . ErrorMessage , validation . DeclRange . String ()),
2020-11-02 11:49:40 +01:00
Subject : subj ,
2020-10-30 15:38:29 +01:00
})
}
}
return diags
}
2020-11-02 11:49:40 +01:00
// Value returns the last found value from the list of variable settings.
2021-03-30 20:58:26 +02:00
func ( v * Variable ) Value () cty . Value {
2020-11-02 11:49:40 +01:00
if len ( v . Values ) == 0 {
2021-03-30 20:58:26 +02:00
return cty . UnknownVal ( v . Type )
}
val := v . Values [ len ( v . Values ) - 1 ]
return val . Value
}
// ValidateValue tells if the selected value for the Variable is valid according
// to its validation settings.
func ( v * Variable ) ValidateValue () hcl . Diagnostics {
if len ( v . Values ) == 0 {
return hcl . Diagnostics { & hcl . Diagnostic {
2020-11-02 11:49:40 +01:00
Severity : hcl . DiagError ,
Summary : fmt . Sprintf ( "Unset variable %q" , v . Name ),
Detail : "A used variable must be set or have a default value; see " +
2021-01-14 14:38:28 -08:00
"https://packer.io/docs/templates/hcl_templates/syntax for " +
2020-11-02 11:49:40 +01:00
"details." ,
Context : v . Range . Ptr (),
}}
2020-02-06 11:49:21 +01:00
}
2021-03-30 20:58:26 +02:00
return v . validateValue ( v . Values [ len ( v . Values ) - 1 ])
2020-02-06 11:49:21 +01:00
}
type Variables map [ string ] * Variable
2020-09-01 15:27:01 +02:00
func ( variables Variables ) Keys () [] string {
keys := make ([] string , 0 , len ( variables ))
for key := range variables {
keys = append ( keys , key )
}
return keys
}
2021-03-30 20:58:26 +02:00
func ( variables Variables ) Values () map [ string ] cty . Value {
2020-02-06 11:49:21 +01:00
res := map [ string ] cty . Value {}
2019-10-14 17:02:53 +02:00
for k , v := range variables {
2021-03-30 20:58:26 +02:00
value := v . Value ()
2020-03-09 17:25:56 +01:00
res [ k ] = value
2020-02-06 11:49:21 +01:00
}
2021-03-30 20:58:26 +02:00
return res
}
func ( variables Variables ) ValidateValues () hcl . Diagnostics {
var diags hcl . Diagnostics
for _ , v := range variables {
diags = append ( diags , v . ValidateValue () ... )
}
return diags
2020-02-06 11:49:21 +01:00
}
2020-02-21 12:12:30 +01:00
// decodeVariable decodes a variable key and value into Variables
func ( variables * Variables ) decodeVariable ( key string , attr * hcl . Attribute , ectx * hcl . EvalContext ) hcl . Diagnostics {
var diags hcl . Diagnostics
2020-02-06 11:49:21 +01:00
if ( * variables ) == nil {
( * variables ) = Variables {}
}
2020-02-21 12:12:30 +01:00
if _ , found := ( * variables )[ key ]; found {
diags = append ( diags , & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Duplicate variable" ,
Detail : "Duplicate " + key + " variable definition found." ,
Subject : attr . NameRange . Ptr (),
})
2020-02-06 11:49:21 +01:00
return diags
}
2020-02-21 12:12:30 +01:00
value , moreDiags := attr . Expr . Value ( ectx )
diags = append ( diags , moreDiags ... )
if moreDiags . HasErrors () {
return diags
}
( * variables )[ key ] = & Variable {
2020-11-02 11:49:40 +01:00
Name : key ,
Values : [] VariableAssignment {{
From : "default" ,
Value : value ,
Expr : attr . Expr ,
}},
Type : value . Type (),
Range : attr . Range ,
2019-10-14 17:02:53 +02:00
}
2020-02-06 11:49:21 +01:00
return diags
}
2020-10-30 12:26:22 +01:00
var variableBlockSchema = & hcl . BodySchema {
Attributes : [] hcl . AttributeSchema {
{
Name : "description" ,
},
{
Name : "default" ,
},
{
Name : "type" ,
},
{
Name : "sensitive" ,
},
},
Blocks : [] hcl . BlockHeaderSchema {
{
Type : "validation" ,
},
},
}
2021-01-26 01:21:44 -08:00
var localBlockSchema = & hcl . BodySchema {
Attributes : [] hcl . AttributeSchema {
{
Name : "expression" ,
},
{
Name : "sensitive" ,
},
},
}
2022-01-28 19:21:53 +01:00
func decodeLocalBlock ( block * hcl . Block ) ( * LocalBlock , hcl . Diagnostics ) {
2021-01-26 01:21:44 -08:00
name := block . Labels [ 0 ]
content , diags := block . Body . Content ( localBlockSchema )
if ! hclsyntax . ValidIdentifier ( name ) {
diags = append ( diags , & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Invalid local name" ,
Detail : badIdentifierDetail ,
Subject : & block . LabelRanges [ 0 ],
})
}
l := & LocalBlock {
Name : name ,
}
if attr , exists := content . Attributes [ "sensitive" ]; exists {
valDiags := gohcl . DecodeExpression ( attr . Expr , nil , & l . Sensitive )
diags = append ( diags , valDiags ... )
}
if def , ok := content . Attributes [ "expression" ]; ok {
l . Expr = def . Expr
}
return l , diags
}
2020-11-11 11:27:32 +01:00
// decodeVariableBlock decodes a "variable" block
// ectx is passed only in the evaluation of the default value.
2020-02-21 12:12:30 +01:00
func ( variables * Variables ) decodeVariableBlock ( block * hcl . Block , ectx * hcl . EvalContext ) hcl . Diagnostics {
2020-02-06 11:49:21 +01:00
if ( * variables ) == nil {
( * variables ) = Variables {}
}
if _ , found := ( * variables )[ block . Labels [ 0 ]]; found {
return [] * hcl . Diagnostic {{
Severity : hcl . DiagError ,
Summary : "Duplicate variable" ,
Detail : "Duplicate " + block . Labels [ 0 ] + " variable definition found." ,
Context : block . DefRange . Ptr (),
}}
}
2020-02-17 18:05:15 +01:00
name := block . Labels [ 0 ]
2020-10-30 12:26:22 +01:00
content , diags := block . Body . Content ( variableBlockSchema )
if ! hclsyntax . ValidIdentifier ( name ) {
diags = append ( diags , & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Invalid variable name" ,
Detail : badIdentifierDetail ,
Subject : & block . LabelRanges [ 0 ],
})
2020-02-06 11:49:21 +01:00
}
2020-10-30 12:26:22 +01:00
v := & Variable {
Name : name ,
Range : block . DefRange ,
2022-02-14 17:00:41 +01:00
Type : cty . DynamicPseudoType ,
2020-10-30 12:26:22 +01:00
}
2020-02-06 11:49:21 +01:00
2020-10-30 12:26:22 +01:00
if attr , exists := content . Attributes [ "description" ]; exists {
valDiags := gohcl . DecodeExpression ( attr . Expr , nil , & v . Description )
diags = append ( diags , valDiags ... )
}
if t , ok := content . Attributes [ "type" ]; ok {
2020-02-06 11:49:21 +01:00
tp , moreDiags := typeexpr . Type ( t . Expr )
diags = append ( diags , moreDiags ... )
if moreDiags . HasErrors () {
return diags
}
2020-10-30 12:26:22 +01:00
v . Type = tp
2020-02-06 11:49:21 +01:00
}
2020-10-30 12:26:22 +01:00
if attr , exists := content . Attributes [ "sensitive" ]; exists {
valDiags := gohcl . DecodeExpression ( attr . Expr , nil , & v . Sensitive )
diags = append ( diags , valDiags ... )
}
if def , ok := content . Attributes [ "default" ]; ok {
2020-02-06 11:49:21 +01:00
defaultValue , moreDiags := def . Expr . Value ( ectx )
diags = append ( diags , moreDiags ... )
if moreDiags . HasErrors () {
return diags
}
2020-10-30 12:26:22 +01:00
if v . Type != cty . NilType {
2020-02-06 11:49:21 +01:00
var err error
2020-10-30 12:26:22 +01:00
defaultValue , err = convert . Convert ( defaultValue , v . Type )
2020-02-06 11:49:21 +01:00
if err != nil {
diags = append ( diags , & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Invalid default value for variable" ,
Detail : fmt . Sprintf ( "This default value is not compatible with the variable's type constraint: %s." , err ),
Subject : def . Expr . Range (). Ptr (),
})
defaultValue = cty . DynamicVal
}
}
2020-11-02 11:49:40 +01:00
v . Values = append ( v . Values , VariableAssignment {
From : "default" ,
Value : defaultValue ,
Expr : def . Expr ,
})
2020-03-06 09:12:26 -05:00
2020-08-27 11:54:06 +02:00
// It's possible no type attribute was assigned so lets make sure we
// have a valid type otherwise there could be issues parsing the value.
2022-02-14 17:00:41 +01:00
if v . Type == cty . DynamicPseudoType &&
! defaultValue . Type (). Equals ( cty . EmptyObject ) &&
! defaultValue . Type (). Equals ( cty . EmptyTuple ) {
2020-11-02 11:49:40 +01:00
v . Type = defaultValue . Type ()
2020-03-06 09:12:26 -05:00
}
2020-02-06 11:49:21 +01:00
}
2020-10-30 12:26:22 +01:00
for _ , block := range content . Blocks {
switch block . Type {
case "validation" :
vv , moreDiags := decodeVariableValidationBlock ( v . Name , block )
diags = append ( diags , moreDiags ... )
v . Validations = append ( v . Validations , vv )
}
}
( * variables )[ name ] = v
2020-02-06 11:49:21 +01:00
return diags
}
2020-10-30 12:26:22 +01:00
var variableValidationBlockSchema = & hcl . BodySchema {
Attributes : [] hcl . AttributeSchema {
{
Name : "condition" ,
Required : true ,
},
{
Name : "error_message" ,
Required : true ,
},
},
}
// VariableValidation represents a configuration-defined validation rule
// for a particular input variable, given as a "validation" block inside
// a "variable" block.
type VariableValidation struct {
// Condition is an expression that refers to the variable being tested and
// contains no other references. The expression must return true to
// indicate that the value is valid or false to indicate that it is
// invalid. If the expression produces an error, that's considered a bug in
// the block defining the validation rule, not an error in the caller.
Condition hcl . Expression
2020-11-04 13:13:45 +01:00
// ErrorMessage is one or more full sentences, which _should_ be in English
// for consistency with the rest of the error message output but can in
// practice be in any language as long as it ends with a period. The
// message should describe what is required for the condition to return
2020-10-30 12:26:22 +01:00
// true in a way that would make sense to a caller of the module.
ErrorMessage string
DeclRange hcl . Range
}
func decodeVariableValidationBlock ( varName string , block * hcl . Block ) ( * VariableValidation , hcl . Diagnostics ) {
var diags hcl . Diagnostics
vv := & VariableValidation {
DeclRange : block . DefRange ,
}
content , moreDiags := block . Body . Content ( variableValidationBlockSchema )
diags = append ( diags , moreDiags ... )
if attr , exists := content . Attributes [ "condition" ]; exists {
vv . Condition = attr . Expr
// The validation condition must refer to the variable itself and
// nothing else; to ensure that the variable declaration can't create
// additional edges in the dependency graph.
goodRefs := 0
for _ , traversal := range vv . Condition . Variables () {
ref , moreDiags := addrs . ParseRef ( traversal )
if ! moreDiags . HasErrors () {
if addr , ok := ref . Subject .( addrs . InputVariable ); ok {
if addr . Name == varName {
goodRefs ++
continue // Reference is valid
}
}
}
// If we fall out here then the reference is invalid.
diags = diags . Append ( & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Invalid reference in variable validation" ,
Detail : fmt . Sprintf ( "The condition for variable %q can only refer to the variable itself, using var.%s." , varName , varName ),
Subject : traversal . SourceRange (). Ptr (),
})
}
if goodRefs < 1 {
diags = diags . Append ( & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Invalid variable validation condition" ,
Detail : fmt . Sprintf ( "The condition for variable %q must refer to var.%s in order to test incoming values." , varName , varName ),
Subject : attr . Expr . Range (). Ptr (),
})
}
}
if attr , exists := content . Attributes [ "error_message" ]; exists {
moreDiags := gohcl . DecodeExpression ( attr . Expr , nil , & vv . ErrorMessage )
diags = append ( diags , moreDiags ... )
if ! moreDiags . HasErrors () {
const errSummary = "Invalid validation error message"
switch {
case vv . ErrorMessage == "" :
diags = diags . Append ( & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : errSummary ,
Detail : "An empty string is not a valid nor useful error message." ,
Subject : attr . Expr . Range (). Ptr (),
})
case ! looksLikeSentences ( vv . ErrorMessage ):
// Because we're going to include this string verbatim as part
2020-11-04 13:13:45 +01:00
// of a bigger error message written in our usual style, we'll
// require the given error message to conform to that. We might
// relax this in future if e.g. we start presenting these error
// messages in a different way, or if Packer starts supporting
// producing error messages in other human languages, etc. For
// pragmatism we also allow sentences ending with exclamation
// points, but we don't mention it explicitly here because
// that's not really consistent with the Packer UI writing
// style.
2020-10-30 12:26:22 +01:00
diags = diags . Append ( & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : errSummary ,
2020-11-04 13:13:45 +01:00
Detail : "Validation error message must be at least one full sentence starting with an uppercase letter ( if the alphabet permits it ) and ending with a period or question mark." ,
2020-10-30 12:26:22 +01:00
Subject : attr . Expr . Range (). Ptr (),
})
}
}
}
return vv , diags
}
// looksLikeSentence is a simple heuristic that encourages writing error
// messages that will be presentable when included as part of a larger error
// diagnostic whose other text is written in the UI writing style.
//
// This is intentionally not a very strong validation since we're assuming that
// authors want to write good messages and might just need a nudge about
// Packer's specific style, rather than that they are going to try to work
// around these rules to write a lower-quality message.
func looksLikeSentences ( s string ) bool {
2020-11-11 11:27:32 +01:00
s = strings . TrimSpace ( s )
2020-10-30 12:26:22 +01:00
if len ( s ) < 1 {
return false
}
runes := [] rune ( s ) // HCL guarantees that all strings are valid UTF-8
first := runes [ 0 ]
last := runes [ len ( runes ) - 1 ]
2020-11-04 13:13:45 +01:00
// If the first rune is a letter then it must be an uppercase letter. To
// sorts of nudge people into writting sentences. For alphabets that don't
// have the notion of 'upper', this does nothing.
2020-10-30 12:26:22 +01:00
if unicode . IsLetter ( first ) && ! unicode . IsUpper ( first ) {
return false
}
// The string must be at least one full sentence, which implies having
// sentence-ending punctuation.
return last == '.' || last == '?' || last == '!'
}
2020-02-06 11:49:21 +01:00
// Prefix your environment variables with VarEnvPrefix so that Packer can see
// them.
const VarEnvPrefix = "PKR_VAR_"
2020-03-09 16:16:59 +01:00
func ( cfg * PackerConfig ) collectInputVariableValues ( env [] string , files [] * hcl . File , argv map [ string ] string ) hcl . Diagnostics {
2020-02-06 11:49:21 +01:00
var diags hcl . Diagnostics
2020-03-09 16:16:59 +01:00
variables := cfg . InputVariables
2020-02-06 11:49:21 +01:00
for _ , raw := range env {
if ! strings . HasPrefix ( raw , VarEnvPrefix ) {
continue
}
raw = raw [ len ( VarEnvPrefix ):] // trim the prefix
eq := strings . Index ( raw , "=" )
if eq == - 1 {
// Seems invalid, so we'll ignore it.
continue
}
name := raw [: eq ]
value := raw [ eq + 1 :]
variable , found := variables [ name ]
if ! found {
// this variable was not defined in the hcl files, let's skip it !
continue
}
fakeFilename := fmt . Sprintf ( "<value for var.%s from env>" , name )
2020-03-06 09:12:26 -05:00
expr , moreDiags := expressionFromVariableDefinition ( fakeFilename , value , variable . Type )
2020-02-06 11:49:21 +01:00
diags = append ( diags , moreDiags ... )
if moreDiags . HasErrors () {
continue
}
2020-03-03 11:15:56 +01:00
2020-02-06 11:49:21 +01:00
val , valDiags := expr . Value ( nil )
diags = append ( diags , valDiags ... )
if variable . Type != cty . NilType {
var err error
val , err = convert . Convert ( val , variable . Type )
if err != nil {
diags = append ( diags , & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Invalid value for variable" ,
Detail : fmt . Sprintf ( "The value for %s is not compatible with the variable's type constraint: %s." , name , err ),
Subject : expr . Range (). Ptr (),
})
val = cty . DynamicVal
}
}
2020-11-02 11:49:40 +01:00
variable . Values = append ( variable . Values , VariableAssignment {
From : "env" ,
Value : val ,
Expr : expr ,
})
2020-02-06 11:49:21 +01:00
}
// files will contain files found in the folder then files passed as
// arguments.
for _ , file := range files {
// Before we do our real decode, we'll probe to see if there are any
// blocks of type "variable" in this body, since it's a common mistake
// for new users to put variable declarations in pkrvars rather than
// variable value definitions, and otherwise our error message for that
// case is not so helpful.
{
content , _ , _ := file . Body . PartialContent ( & hcl . BodySchema {
Blocks : [] hcl . BlockHeaderSchema {
{
Type : "variable" ,
LabelNames : [] string { "name" },
},
},
})
for _ , block := range content . Blocks {
name := block . Labels [ 0 ]
2020-10-30 15:38:29 +01:00
diags = append ( diags , & hcl . Diagnostic {
2020-02-06 11:49:21 +01:00
Severity : hcl . DiagError ,
Summary : "Variable declaration in a .pkrvar file" ,
Detail : fmt . Sprintf ( "A .pkrvar file is used to assign " +
"values to variables that have already been declared " +
"in .pkr files, not to declare new variables. To " +
"declare variable %q, place this block in one of your" +
2020-03-12 23:40:25 +11:00
" .pkr files, such as variables.pkr.hcl\n\nTo set a " +
2020-02-06 11:49:21 +01:00
"value for this variable in %s, use the definition " +
"syntax instead:\n %s = <value>" ,
name , block . TypeRange . Filename , name ),
Subject : & block . TypeRange ,
})
}
if diags . HasErrors () {
// If we already found problems then JustAttributes below will find
// the same problems with less-helpful messages, so we'll bail for
// now to let the user focus on the immediate problem.
return diags
}
}
attrs , moreDiags := file . Body . JustAttributes ()
diags = append ( diags , moreDiags ... )
for name , attr := range attrs {
variable , found := variables [ name ]
if ! found {
2022-11-14 17:06:45 -05:00
if ! cfg . ValidationOptions . WarnOnUndeclaredVar {
continue
2020-03-09 16:16:59 +01:00
}
2022-11-14 17:06:45 -05:00
2020-03-09 16:16:59 +01:00
diags = append ( diags , & hcl . Diagnostic {
2022-11-14 17:06:45 -05:00
Severity : hcl . DiagWarning ,
2020-03-09 16:16:59 +01:00
Summary : "Undefined variable" ,
2022-11-14 17:06:45 -05:00
Detail : fmt . Sprintf ( "The variable %[1]q was set but was not declared as an input variable." +
"\nTo declare variable %[1]q place this block in one of your .pkr.hcl files, " +
"such as variables.pkr.hcl\n\n" +
2022-09-27 17:21:45 -04:00
"variable %[1]q {\n" +
2022-11-14 17:06:45 -05:00
" type = string\n" +
" default = null\n" +
2022-09-27 17:21:45 -04:00
"}" ,
name ),
2020-03-09 16:16:59 +01:00
Context : attr . Range . Ptr (),
})
2020-02-06 11:49:21 +01:00
continue
}
val , moreDiags := attr . Expr . Value ( nil )
diags = append ( diags , moreDiags ... )
if variable . Type != cty . NilType {
var err error
val , err = convert . Convert ( val , variable . Type )
if err != nil {
diags = append ( diags , & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Invalid value for variable" ,
Detail : fmt . Sprintf ( "The value for %s is not compatible with the variable's type constraint: %s." , name , err ),
Subject : attr . Expr . Range (). Ptr (),
})
val = cty . DynamicVal
}
}
2020-11-02 11:49:40 +01:00
variable . Values = append ( variable . Values , VariableAssignment {
From : "varfile" ,
Value : val ,
Expr : attr . Expr ,
})
2020-02-06 11:49:21 +01:00
}
}
// Finally we process values given explicitly on the command line.
for name , value := range argv {
variable , found := variables [ name ]
if ! found {
diags = append ( diags , & hcl . Diagnostic {
2020-03-09 16:16:59 +01:00
Severity : hcl . DiagError ,
Summary : "Undefined -var variable" ,
2020-02-06 11:49:21 +01:00
Detail : fmt . Sprintf ( "A %q variable was passed in the command " +
2020-03-12 23:40:25 +11:00
"line but was not found in known variables. " +
2020-02-06 11:49:21 +01:00
"To declare variable %q, place this block in one of your" +
2020-03-12 23:40:25 +11:00
" .pkr files, such as variables.pkr.hcl" ,
2020-02-06 11:49:21 +01:00
name , name ),
})
continue
}
fakeFilename := fmt . Sprintf ( "<value for var.%s from arguments>" , name )
2020-03-06 09:12:26 -05:00
expr , moreDiags := expressionFromVariableDefinition ( fakeFilename , value , variable . Type )
2020-02-06 11:49:21 +01:00
diags = append ( diags , moreDiags ... )
if moreDiags . HasErrors () {
continue
}
2020-03-06 09:12:26 -05:00
2020-02-06 11:49:21 +01:00
val , valDiags := expr . Value ( nil )
diags = append ( diags , valDiags ... )
if variable . Type != cty . NilType {
var err error
val , err = convert . Convert ( val , variable . Type )
if err != nil {
diags = append ( diags , & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Invalid argument value for -var variable" ,
Detail : fmt . Sprintf ( "The received arg value for %s is not compatible with the variable's type constraint: %s." , name , err ),
})
val = cty . DynamicVal
}
}
2020-11-02 11:49:40 +01:00
variable . Values = append ( variable . Values , VariableAssignment {
From : "cmd" ,
Value : val ,
Expr : expr ,
})
2020-02-06 11:49:21 +01:00
}
return diags
2019-10-14 17:02:53 +02:00
}
2020-03-06 09:12:26 -05:00
// expressionFromVariableDefinition creates an hclsyntax.Expression that is capable of evaluating the specified value for a given cty.Type.
// The specified filename is to identify the source of where value originated from in the diagnostics report, if there is an error.
func expressionFromVariableDefinition ( filename string , value string , variableType cty . Type ) ( hclsyntax . Expression , hcl . Diagnostics ) {
switch variableType {
2022-02-14 17:00:41 +01:00
case cty . String , cty . Number , cty . NilType , cty . DynamicPseudoType :
2020-09-02 16:26:50 +02:00
// when the type is nil (not set in a variable block) we default to
// interpreting everything as a string literal.
2020-03-06 09:12:26 -05:00
return & hclsyntax . LiteralValueExpr { Val : cty . StringVal ( value )}, nil
default :
return hclsyntax . ParseExpression ([] byte ( value ), filename , hcl . Pos { Line : 1 , Column : 1 })
}
}