Enhance enforced provisioner handling and error reporting

- Update error handling in FetchEnforcedBlocks to return detailed errors instead of warnings.
- Modify GetCoreBuildProvisionerFromBlock to accept build name for overrides.
- Add tests for FetchEnforcedBlocks to ensure correct behavior and error handling.
- Implement diagnostics for unsupported legacy JSON templates.
Hari Om 2 months ago
parent df17085a72
commit 7122c28125

@ -153,7 +153,13 @@ func (c *BuildCommand) RunContext(buildCtx context.Context, cla *BuildArgs) int
// Fetch and inject enforced provisioners from HCP Packer (if configured)
if !cla.SkipEnforcement {
if err := hcpRegistry.FetchEnforcedBlocks(buildCtx); err != nil {
c.Ui.Error(fmt.Sprintf("Warning: failed to fetch enforced provisioners: %s", err))
return writeDiags(c.Ui, nil, hcl.Diagnostics{
&hcl.Diagnostic{
Summary: "HCP: fetching enforced provisioners failed",
Severity: hcl.DiagError,
Detail: err.Error(),
},
})
}
diags := hcpRegistry.InjectEnforcedProvisioners(builds)

@ -142,7 +142,7 @@ func parseProvisionerBlocksFromFile(parser *Parser, file *hcl.File, diags hcl.Di
// GetCoreBuildProvisionerFromBlock converts a ProvisionerBlock to a CoreBuildProvisioner.
// This is used for enforced provisioners that need to be injected into builds.
func (cfg *PackerConfig) GetCoreBuildProvisionerFromBlock(pb *ProvisionerBlock) (packer.CoreBuildProvisioner, hcl.Diagnostics) {
func (cfg *PackerConfig) GetCoreBuildProvisionerFromBlock(pb *ProvisionerBlock, buildName string) (packer.CoreBuildProvisioner, hcl.Diagnostics) {
var diags hcl.Diagnostics
// Get the provisioner plugin
@ -176,6 +176,14 @@ func (cfg *PackerConfig) GetCoreBuildProvisionerFromBlock(pb *ProvisionerBlock)
builderVariables: builderVars,
}
if pb.Override != nil {
if override, ok := pb.Override[buildName]; ok {
if typedOverride, ok := override.(map[string]interface{}); ok {
hclProvisioner.override = typedOverride
}
}
}
// Prepare the provisioner
err = hclProvisioner.HCL2Prepare(nil)
if err != nil {

@ -7,6 +7,88 @@ import (
"testing"
)
func TestGetCoreBuildProvisionerFromBlock_AppliesOverrideForBuild(t *testing.T) {
parser := getBasicParser()
cfg := &PackerConfig{
parser: parser,
CorePackerVersionString: lockedVersion,
}
blocks, diags := ParseProvisionerBlocks(`
provisioner "shell" {
override = {
"amazon-ebs.ubuntu" = {
bool = false
}
}
}
`)
if diags.HasErrors() {
t.Fatalf("ParseProvisionerBlocks() unexpected error: %v", diags)
}
if len(blocks) != 1 {
t.Fatalf("expected 1 block, got %d", len(blocks))
}
coreProv, diags := cfg.GetCoreBuildProvisionerFromBlock(blocks[0], "amazon-ebs.ubuntu")
if diags.HasErrors() {
t.Fatalf("GetCoreBuildProvisionerFromBlock() unexpected error: %v", diags)
}
hclProv, ok := coreProv.Provisioner.(*HCL2Provisioner)
if !ok {
t.Fatalf("expected *HCL2Provisioner, got %T", coreProv.Provisioner)
}
if hclProv.override == nil {
t.Fatal("expected override to be applied, got nil")
}
if got, ok := hclProv.override["bool"]; !ok || got != false {
t.Fatalf("expected override bool=false, got %#v", hclProv.override["bool"])
}
}
func TestGetCoreBuildProvisionerFromBlock_OverrideNotAppliedForOtherBuild(t *testing.T) {
parser := getBasicParser()
cfg := &PackerConfig{
parser: parser,
CorePackerVersionString: lockedVersion,
}
blocks, diags := ParseProvisionerBlocks(`
provisioner "shell" {
override = {
"amazon-ebs.ubuntu" = {
bool = false
}
}
}
`)
if diags.HasErrors() {
t.Fatalf("ParseProvisionerBlocks() unexpected error: %v", diags)
}
if len(blocks) != 1 {
t.Fatalf("expected 1 block, got %d", len(blocks))
}
coreProv, diags := cfg.GetCoreBuildProvisionerFromBlock(blocks[0], "virtualbox-iso.base")
if diags.HasErrors() {
t.Fatalf("GetCoreBuildProvisionerFromBlock() unexpected error: %v", diags)
}
hclProv, ok := coreProv.Provisioner.(*HCL2Provisioner)
if !ok {
t.Fatalf("expected *HCL2Provisioner, got %T", coreProv.Provisioner)
}
if hclProv.override != nil {
t.Fatalf("expected no override to be applied, got %#v", hclProv.override)
}
}
func TestParseProvisionerBlocks(t *testing.T) {
tests := []struct {
name string

@ -114,7 +114,7 @@ func (h *HCLRegistry) InjectEnforcedProvisioners(builds []*packer.CoreBuild) hcl
provBlocks, diags := hcl2template.ParseProvisionerBlocks(eb.BlockContent)
if diags.HasErrors() {
allDiags = append(allDiags, &hcl.Diagnostic{
Severity: hcl.DiagWarning,
Severity: hcl.DiagError,
Summary: fmt.Sprintf("Failed to parse enforced block %q", eb.Name),
Detail: diags.Error(),
})
@ -135,7 +135,7 @@ func (h *HCLRegistry) InjectEnforcedProvisioners(builds []*packer.CoreBuild) hcl
continue
}
coreProv, moreDiags := h.configuration.GetCoreBuildProvisionerFromBlock(pb)
coreProv, moreDiags := h.configuration.GetCoreBuildProvisionerFromBlock(pb, build.Type)
if moreDiags.HasErrors() {
allDiags = append(allDiags, moreDiags...)
continue

@ -123,7 +123,13 @@ func (h *JSONRegistry) FetchEnforcedBlocks(ctx context.Context) error {
// Note: JSON templates don't support enforced provisioners as they are a legacy format
func (h *JSONRegistry) InjectEnforcedProvisioners(builds []*packer.CoreBuild) hcl.Diagnostics {
if len(h.bucket.EnforcedBlocks) > 0 {
h.ui.Say("Warning: Enforced provisioners are not supported for legacy JSON templates")
return hcl.Diagnostics{
&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Enforced provisioners are not supported for legacy JSON templates",
Detail: "Linked enforced blocks were found for this bucket, but the current build is a legacy JSON template.",
},
}
}
return nil
}

@ -165,9 +165,13 @@ func (bucket *Bucket) FetchEnforcedBlocks(ctx context.Context) error {
resp, err := bucket.client.GetEnforcedBlocksForBucket(ctx, bucket.Name)
if err != nil {
// If the API doesn't support enforced blocks yet or returns not found, continue silently
log.Printf("[DEBUG] fetching enforced blocks for bucket %q: %v", bucket.Name, err)
return nil
if hcpPackerAPI.CheckErrorCode(err, codes.NotFound) || hcpPackerAPI.CheckErrorCode(err, codes.Unimplemented) {
// If the API doesn't support enforced blocks yet or returns not found, continue silently.
log.Printf("[DEBUG] fetching enforced blocks for bucket %q: %v", bucket.Name, err)
return nil
}
return fmt.Errorf("failed fetching enforced blocks for bucket %q: %w", bucket.Name, err)
}
if resp == nil {
@ -192,6 +196,7 @@ func (bucket *Bucket) FetchEnforcedBlocks(ctx context.Context) error {
if detail.Version.TemplateType != nil {
block.TemplateType = string(*detail.Version.TemplateType)
}
bucket.EnforcedBlocks = append(bucket.EnforcedBlocks, block)
log.Printf("[INFO] linked enforced block found for bucket %q: name=%q id=%q version=%q",
bucket.Name, block.Name, block.ID, block.Version)

@ -0,0 +1,117 @@
// Copyright IBM Corp. 2013, 2025
// SPDX-License-Identifier: BUSL-1.1
package registry
import (
"context"
"errors"
"fmt"
"testing"
hcpPackerModels "github.com/hashicorp/hcp-sdk-go/clients/cloud-packer-service/stable/2023-01-01/models"
hcpPackerAPI "github.com/hashicorp/packer/internal/hcp/api"
"google.golang.org/grpc/codes"
)
func TestBucket_FetchEnforcedBlocks_ReturnsAllBlocks(t *testing.T) {
hcl2Type := hcpPackerModels.HashicorpCloudPacker20230101TemplateTypeHCL2
jsonType := hcpPackerModels.HashicorpCloudPacker20230101TemplateTypeJSON
mockService := hcpPackerAPI.NewMockPackerClientService()
mockService.GetEnforcedBlocksByBucketResp = &hcpPackerModels.HashicorpCloudPacker20230101GetEnforcedBlocksByBucketResponse{
EnforcedBlockDetail: []*hcpPackerModels.HashicorpCloudPacker20230101EnforcedBlockDetail{
{
ID: "hcl-id",
Name: "hcl-block",
Version: &hcpPackerModels.HashicorpCloudPacker20230101EnforcedBlockVersion{
ID: "hcl-v1",
Version: "1",
BlockContent: "provisioner \"shell\" {}",
TemplateType: &hcl2Type,
},
},
{
ID: "json-id",
Name: "json-block",
Version: &hcpPackerModels.HashicorpCloudPacker20230101EnforcedBlockVersion{
ID: "json-v1",
Version: "1",
BlockContent: "{\"provisioner\":[{\"shell\":{}}]}",
TemplateType: &jsonType,
},
},
{
ID: "unset-id",
Name: "unset-block",
Version: &hcpPackerModels.HashicorpCloudPacker20230101EnforcedBlockVersion{
ID: "unset-v1",
Version: "1",
BlockContent: "provisioner \"shell\" {}",
},
},
},
}
bucket := &Bucket{
Name: "test-bucket",
client: &hcpPackerAPI.Client{
Packer: mockService,
},
}
err := bucket.FetchEnforcedBlocks(context.Background())
if err != nil {
t.Fatalf("FetchEnforcedBlocks() unexpected error: %v", err)
}
if len(bucket.EnforcedBlocks) != 3 {
t.Fatalf("FetchEnforcedBlocks() got %d blocks, want 3", len(bucket.EnforcedBlocks))
}
if bucket.EnforcedBlocks[0].Name != "hcl-block" {
t.Fatalf("first block name = %q, want %q", bucket.EnforcedBlocks[0].Name, "hcl-block")
}
if bucket.EnforcedBlocks[1].Name != "json-block" {
t.Fatalf("second block name = %q, want %q", bucket.EnforcedBlocks[1].Name, "json-block")
}
if bucket.EnforcedBlocks[2].Name != "unset-block" {
t.Fatalf("third block name = %q, want %q", bucket.EnforcedBlocks[2].Name, "unset-block")
}
}
func TestBucket_FetchEnforcedBlocks_ReturnsErrorOnServiceFailure(t *testing.T) {
mockService := hcpPackerAPI.NewMockPackerClientService()
mockService.GetEnforcedBlocksByBucketErr = errors.New("service unavailable")
bucket := &Bucket{
Name: "test-bucket",
client: &hcpPackerAPI.Client{
Packer: mockService,
},
}
err := bucket.FetchEnforcedBlocks(context.Background())
if err == nil {
t.Fatal("FetchEnforcedBlocks() expected error, got nil")
}
}
func TestBucket_FetchEnforcedBlocks_NotFoundIsNonFatal(t *testing.T) {
mockService := hcpPackerAPI.NewMockPackerClientService()
mockService.GetEnforcedBlocksByBucketErr = fmt.Errorf("Code:%d %s", codes.NotFound, codes.NotFound.String())
bucket := &Bucket{
Name: "test-bucket",
client: &hcpPackerAPI.Client{
Packer: mockService,
},
}
err := bucket.FetchEnforcedBlocks(context.Background())
if err != nil {
t.Fatalf("FetchEnforcedBlocks() expected nil error for NotFound, got: %v", err)
}
}
Loading…
Cancel
Save