hcl2template: err on malformed local/data dep (#13340)

* hcl2template: err on malformed local/data dep

When introducing the DAG for locals and datasources, we forgot to handle
one limit case: if a dependency for a local or data is malformed, we
didn't check that a vertex was associated to it, leading to the final
DAG being malformed, and the DAG library would crash in this case.

This commit fixes this problem by checking that the dependency does
exist before attempting to add it to the graph as an edge for a vertex,
so that it is reported accurately, and do that Packer doesn't crash.

* error message change

---------

Co-authored-by: anshul sharma <anshul.sharma@hashicorp.com>
This commit is contained in:
Lucas Bajolet 2025-06-16 23:52:09 -04:00 committed by GitHub
parent 65c2eb0469
commit 4cd7ad4721
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 94 additions and 9 deletions

View file

@ -9,6 +9,7 @@ import (
"path/filepath"
"reflect"
"github.com/hashicorp/go-multierror"
"github.com/hashicorp/go-version"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/ext/dynblock"
@ -403,6 +404,8 @@ func (cfg *PackerConfig) buildPrereqsDAG() (*dag.AcyclicGraph, error) {
verticesMap := map[string]dag.Vertex{}
var err error
// Do a first pass to create all the vertices
for ref := range cfg.Datasources {
// We keep a reference to the datasource separately from where it
@ -434,27 +437,48 @@ func (cfg *PackerConfig) buildPrereqsDAG() (*dag.AcyclicGraph, error) {
for _, ds := range cfg.Datasources {
dsName := fmt.Sprintf("data.%s", ds.Name())
source := verticesMap[dsName]
if source == nil {
err = multierror.Append(err, fmt.Errorf("unable to find source vertex %q for dependency analysis, this is likely a Packer bug", dsName))
continue
}
for _, dep := range ds.Dependencies {
retGraph.Connect(
dag.BasicEdge(verticesMap[dsName],
verticesMap[dep.String()]))
target := verticesMap[dep.String()]
if target == nil {
err = multierror.Append(err, fmt.Errorf("could not get dependency %q for %q, %q missing in template", dep.String(), dsName, dep.String()))
continue
}
retGraph.Connect(dag.BasicEdge(source, target))
}
}
for _, loc := range cfg.LocalBlocks {
locName := fmt.Sprintf("local.%s", loc.LocalName)
source := verticesMap[locName]
if source == nil {
err = multierror.Append(err, fmt.Errorf("unable to find source vertex %q for dependency analysis, this is likely a Packer bug", locName))
continue
}
for _, dep := range loc.dependencies {
retGraph.Connect(
dag.BasicEdge(verticesMap[locName],
verticesMap[dep.String()]))
target := verticesMap[dep.String()]
if target == nil {
err = multierror.Append(err, fmt.Errorf("could not get dependency %q for %q, %q missing in template", dep.String(), locName, dep.String()))
continue
}
retGraph.Connect(dag.BasicEdge(source, target))
}
}
if err := retGraph.Validate(); err != nil {
return nil, err
if validateErr := retGraph.Validate(); validateErr != nil {
err = multierror.Append(err, validateErr)
}
return &retGraph, nil
return &retGraph, err
}
func (cfg *PackerConfig) evaluateBuildPrereqs(skipDatasources bool) hcl.Diagnostics {

View file

@ -0,0 +1,49 @@
package main
import (
"fmt"
"github.com/hashicorp/packer/packer_test/common/check"
)
type malformedDepTestCase struct {
name string
command string
templatePath string
useSequential bool
}
func genMalformedDepTestCases() []malformedDepTestCase {
retVals := []malformedDepTestCase{}
for _, cmd := range []string{"build", "validate"} {
for _, template := range []string{"./templates/malformed_data_dep.pkr.hcl", "./templates/malformed_local_dep.pkr.hcl"} {
for _, seq := range []bool{true, false} {
retVals = append(retVals, malformedDepTestCase{
name: fmt.Sprintf("Malformed dep packer %s --use-sequential-evaluation=%t %s",
cmd, seq, template),
command: cmd,
templatePath: template,
useSequential: seq,
})
}
}
}
return retVals
}
func (ts *PackerDAGTestSuite) TestMalformedDependency() {
pluginDir := ts.MakePluginDir()
defer pluginDir.Cleanup()
for _, tc := range genMalformedDepTestCases() {
ts.Run(tc.name, func() {
ts.PackerCommand().UsePluginDir(pluginDir).
SetArgs(tc.command,
fmt.Sprintf("--use-sequential-evaluation=%t", tc.useSequential),
tc.templatePath).
Assert(check.MustFail())
})
}
}

View file

@ -0,0 +1,8 @@
data "http" "trusted_ca_certificates" {
method = "GET"
url = "http://example.com/ca-bundle.crt"
}
locals {
test = data.trusted_ca_certificates.url
}

View file

@ -0,0 +1,4 @@
data "http" "trusted_ca_certificates" {
method = "GET"
url = local.no_dep
}