Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
91cae75
added the parser for the enforced block
Madhav008 Mar 11, 2026
5b68357
Enhance enforced provisioner parsing to support HCL and JSON formats
Madhav008 Mar 16, 2026
1e15d1b
Remove PublishEnforcedBlocks function from Bucket struct
Madhav008 Mar 16, 2026
cfc0ae5
Remove ExtractBuildProvisionerHCL function and unused imports
Madhav008 Mar 21, 2026
edf09ee
Reverted the version upgrade
hariom-hashicorp Mar 21, 2026
da35405
Added the internal-sdk for the enforcedProvsioner api changes
hariom-hashicorp Mar 21, 2026
60132b9
Enhance enforced provisioner handling and error reporting
hariom-hashicorp Mar 24, 2026
21472e6
Implement enforced provisioner parsing and handling
hariom-hashicorp Mar 26, 2026
2f502bc
Add test case for -skip-enforcement flag in BuildArgs
hariom-hashicorp Mar 26, 2026
6217443
Refactor sensitive variable handling in provisioners and add related …
hariom-hashicorp Apr 14, 2026
84f0e02
Refactor enforced provisioner handling: remove internal parser, updat…
hariom-hashicorp Apr 14, 2026
60c3f47
Enhance provisioner block parsing: add error handling for invalid com…
hariom-hashicorp Apr 15, 2026
9e28996
Remove internal SDK replacement for enforced block types in go.mod
hariom-hashicorp Apr 15, 2026
9ca75b7
Update dependencies in go.mod and go.sum: bump hcp-sdk-go and packer-…
hariom-hashicorp Apr 15, 2026
47fbe18
Update hcp-sdk-go dependency to v0.172.0 in go.mod and go.sum
hariom-hashicorp Apr 15, 2026
c7d5d55
Fix formatting in TestBuildCommand_ParseArgs and add newline at end o…
hariom-hashicorp Apr 15, 2026
e0a12d0
Refactor testJSONRegistryWithBuilds: remove environment variable setu…
hariom-hashicorp Apr 15, 2026
6dedc5f
Rename injected variable for clarity in InjectEnforcedProvisioners fu…
hariom-hashicorp Apr 15, 2026
f21d7b1
Merge branch 'main' into feature/enforcedProvisioner
hariom-hashicorp Apr 17, 2026
9f75e39
Merge branch 'main' into feature/enforcedProvisioner
hariom-hashicorp Apr 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions command/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,26 @@ func (c *BuildCommand) RunContext(buildCtx context.Context, cla *BuildArgs) int
return ret
}

// Fetch and inject enforced provisioners from HCP Packer (if configured)
if !cla.SkipEnforcement {
if err := hcpRegistry.FetchEnforcedBlocks(buildCtx); err != nil {
Comment thread
anurag5sh marked this conversation as resolved.
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)
if diags.HasErrors() {
return writeDiags(c.Ui, nil, diags)
}
} else {
c.Ui.Say("Skipping HCP Packer enforced provisioners (--skip-enforcement flag set)")
}

if cla.Debug {
c.Ui.Say("Debug mode enabled. Builds will not be parallelized.")
}
Expand Down Expand Up @@ -456,6 +476,7 @@ Options:
-warn-on-undeclared-var Display warnings for user variable files containing undeclared variables.
-ignore-prerelease-plugins Disable the loading of prerelease plugin binaries (x.y.z-dev).
-use-sequential-evaluation Fallback to using a sequential approach for local/datasource evaluation.
-skip-enforcement Skip injection of HCP Packer enforced provisioners.
`

return strings.TrimSpace(helpText)
Expand Down
10 changes: 10 additions & 0 deletions command/build_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1131,6 +1131,16 @@ func TestBuildCommand_ParseArgs(t *testing.T) {
},
0,
},
{fields{defaultMeta},
args{[]string{"-skip-enforcement", "file.json"}},
&BuildArgs{
MetaArgs: MetaArgs{Path: "file.json"},
ParallelBuilds: math.MaxInt64,
Color: true,
SkipEnforcement: true,
},
0,
},
}
for _, tt := range tests {
t.Run(fmt.Sprintf("%s", tt.args.args), func(t *testing.T) {
Expand Down
3 changes: 3 additions & 0 deletions command/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ func (ba *BuildArgs) AddFlagSets(flags *flag.FlagSet) {

flags.BoolVar(&ba.ReleaseOnly, "ignore-prerelease-plugins", false, "Disable the loading of prerelease plugin binaries (x.y.z-dev).")

flags.BoolVar(&ba.SkipEnforcement, "skip-enforcement", false, "Skip injection of HCP Packer enforced provisioners. Requires admin privileges.")
Comment thread
hariom-hashicorp marked this conversation as resolved.
Comment thread
anurag5sh marked this conversation as resolved.

ba.MetaArgs.AddFlagSets(flags)
}

Expand Down Expand Up @@ -136,6 +138,7 @@ type BuildArgs struct {
ParallelBuilds int64
OnError string
ReleaseOnly bool
SkipEnforcement bool
}

func (ia *InitArgs) AddFlagSets(flags *flag.FlagSet) {
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ require (
github.com/hashicorp/go-uuid v1.0.3
github.com/hashicorp/go-version v1.8.0
github.com/hashicorp/hcl/v2 v2.24.0
github.com/hashicorp/hcp-sdk-go v0.167.0
github.com/hashicorp/hcp-sdk-go v0.172.0
github.com/hashicorp/packer-plugin-sdk v0.6.7
github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869
github.com/klauspost/compress v1.18.5
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -732,8 +732,8 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/hcl/v2 v2.24.0 h1:2QJdZ454DSsYGoaE6QheQZjtKZSUs9Nh2izTWiwQxvE=
github.com/hashicorp/hcl/v2 v2.24.0/go.mod h1:oGoO1FIQYfn/AgyOhlg9qLC6/nOJPX3qGbkZpYAcqfM=
github.com/hashicorp/hcp-sdk-go v0.167.0 h1:t2v+mm3gN1z4qvdJ7g9RuDdXDvIExMtjV1Fvzn2LuVc=
github.com/hashicorp/hcp-sdk-go v0.167.0/go.mod h1:v2vbpNIrmgUTelW4Z+ur+aQuSPxeaVK3xytFdpEXvSg=
github.com/hashicorp/hcp-sdk-go v0.172.0 h1:j4VrSN2yd8prFb8Y0gQWQbTpsV5uVPgYEUozOGfPOOc=
github.com/hashicorp/hcp-sdk-go v0.172.0/go.mod h1:v2vbpNIrmgUTelW4Z+ur+aQuSPxeaVK3xytFdpEXvSg=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY=
github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc=
Expand Down
81 changes: 81 additions & 0 deletions hcl2template/enforced_provisioner.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// Copyright IBM Corp. 2013, 2025
// SPDX-License-Identifier: BUSL-1.1

package hcl2template

import (
"fmt"
"strconv"

"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/packer/packer"
)

// 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, buildName string) (packer.CoreBuildProvisioner, hcl.Diagnostics) {
var diags hcl.Diagnostics

// Get the provisioner plugin
provisioner, err := cfg.parser.PluginConfig.Provisioners.Start(pb.PType)
if err != nil {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: fmt.Sprintf("Failed to start enforced provisioner %q", pb.PType),
Detail: fmt.Sprintf("The provisioner plugin could not be loaded: %s", err.Error()),
})
return packer.CoreBuildProvisioner{}, diags
}

// Create basic builder variables
builderVars := map[string]interface{}{
"packer_core_version": cfg.CorePackerVersionString,
"packer_debug": strconv.FormatBool(cfg.debug),
"packer_force": strconv.FormatBool(cfg.force),
"packer_on_error": cfg.onError,
"packer_sensitive_variables": cfg.sensitiveInputVariableKeys(),
}
Comment thread
hariom-hashicorp marked this conversation as resolved.

// Create evaluation context
ectx := cfg.EvalContext(BuildContext, nil)

// Create the HCL2Provisioner wrapper
hclProvisioner := &HCL2Provisioner{
Provisioner: provisioner,
provisionerBlock: pb,
evalContext: ectx,
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 {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: fmt.Sprintf("Failed to prepare enforced provisioner %q", pb.PType),
Detail: err.Error(),
})
return packer.CoreBuildProvisioner{}, diags
}

// Wrap provisioner with any special behavior (pause, timeout, retry)
wrappedProvisioner := packer.WrapProvisionerWithOptions(hclProvisioner, packer.ProvisionerWrapOptions{
PauseBefore: pb.PauseBefore,
Timeout: pb.Timeout,
MaxRetries: pb.MaxRetries,
})

return packer.CoreBuildProvisioner{
PType: pb.PType,
PName: pb.PName,
Provisioner: wrappedProvisioner,
}, diags
}
146 changes: 146 additions & 0 deletions hcl2template/enforced_provisioner_parser.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
// Copyright IBM Corp. 2013, 2025
// SPDX-License-Identifier: BUSL-1.1

package hcl2template

import (
"encoding/json"
"log"

"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclparse"
"github.com/zclconf/go-cty/cty"
)

var standaloneProvisionerSchema = &hcl.BodySchema{
Blocks: []hcl.BlockHeaderSchema{
{Type: buildProvisionerLabel, LabelNames: []string{"type"}},
},
}

// ParseProvisionerBlocks parses raw provisioner block content into ProvisionerBlocks.
// It accepts HCL, HCL JSON, and the legacy JSON payload used for enforced provisioners.
func ParseProvisionerBlocks(blockContent string) ([]*ProvisionerBlock, hcl.Diagnostics) {
parser := &Parser{Parser: hclparse.NewParser()}
return parser.parseProvisionerBlocks(blockContent)
}

func (p *Parser) parseProvisionerBlocks(blockContent string) ([]*ProvisionerBlock, hcl.Diagnostics) {
hclParser := p.Parser
if hclParser == nil {
hclParser = hclparse.NewParser()
}

log.Printf("[DEBUG] parsing provisioner block content as HCL")

file, diags := hclParser.ParseHCL([]byte(blockContent), "provisioner.pkr.hcl")
if !diags.HasErrors() {
provisioners, provisionerDiags := p.parseProvisionerBlocksFromFile(file, diags)
if provisionerDiags.HasErrors() {
return nil, provisionerDiags
}
log.Printf("[DEBUG] parsed provisioner block content as HCL")
return provisioners, provisionerDiags
}
log.Printf("[DEBUG] failed to parse provisioner block content as HCL, trying JSON fallback")

jsonFile, jsonDiags := hclParser.ParseJSON([]byte(blockContent), "provisioner.pkr.json")
if jsonDiags.HasErrors() {
log.Printf("[DEBUG] failed to parse provisioner block content as JSON")
return nil, append(diags, jsonDiags...)
}

provisioners, provisionerDiags := p.parseProvisionerBlocksFromFile(jsonFile, jsonDiags)
if !provisionerDiags.HasErrors() && len(provisioners) > 0 {
log.Printf("[DEBUG] parsed provisioner block content as JSON")
return provisioners, provisionerDiags
}

legacyJSON, ok, err := normalizeLegacyProvisionersJSON(blockContent)
if err == nil && ok {
legacyFile, legacyDiags := hclParser.ParseJSON([]byte(legacyJSON), "provisioner_legacy.pkr.json")
if !legacyDiags.HasErrors() {
legacyProvisioners, legacyProvisionerDiags := p.parseProvisionerBlocksFromFile(legacyFile, legacyDiags)
if !legacyProvisionerDiags.HasErrors() && len(legacyProvisioners) > 0 {
log.Printf("[DEBUG] parsed provisioner block content as legacy JSON")
return legacyProvisioners, legacyProvisionerDiags
}
}
}

if provisionerDiags.HasErrors() {
return nil, provisionerDiags
}
log.Printf("[DEBUG] parsed provisioner block content as JSON but found no valid provisioner blocks")
return provisioners, provisionerDiags
}

func normalizeLegacyProvisionersJSON(blockContent string) (string, bool, error) {
type legacyPayload struct {
Provisioners []map[string]interface{} `json:"provisioners"`
}

var payload legacyPayload
if err := json.Unmarshal([]byte(blockContent), &payload); err != nil {
return "", false, err
}

if len(payload.Provisioners) == 0 {
return "", false, nil
}

normalized := make([]map[string]interface{}, 0, len(payload.Provisioners))
for _, provisioner := range payload.Provisioners {
typeName, ok := provisioner["type"].(string)
if !ok || typeName == "" {
continue
}

cfg := make(map[string]interface{})
for key, value := range provisioner {
if key == "type" {
continue
}
cfg[key] = value
}

normalized = append(normalized, map[string]interface{}{typeName: cfg})
}

if len(normalized) == 0 {
return "", false, nil
}

out := map[string]interface{}{
buildProvisionerLabel: normalized,
}

b, err := json.Marshal(out)
if err != nil {
return "", false, err
}

return string(b), true, nil
}

func (p *Parser) parseProvisionerBlocksFromFile(file *hcl.File, diags hcl.Diagnostics) ([]*ProvisionerBlock, hcl.Diagnostics) {
content, moreDiags := file.Body.Content(standaloneProvisionerSchema)
diags = append(diags, moreDiags...)
if diags.HasErrors() {
return nil, diags
}

ectx := &hcl.EvalContext{Variables: map[string]cty.Value{}}
provisioners := make([]*ProvisionerBlock, 0, len(content.Blocks))

for _, block := range content.Blocks {
provisioner, moreDiags := p.decodeProvisioner(block, ectx)
diags = append(diags, moreDiags...)
if moreDiags.HasErrors() {
continue
}
provisioners = append(provisioners, provisioner)
}

return provisioners, diags
}
Loading
Loading