Files
Packer-Cn/hcl2template/enforced_provisioner_parser.go
HariandHari Om eee3805c06 Feature/enforced provisioner (#13591)
* added the parser for the enforced block

* Enhance enforced provisioner parsing to support HCL and JSON formats

- Updated ParseProvisionerBlocks to handle both HCL and JSON syntax, including legacy JSON format.

- Added comprehensive test cases for JSON provisioner parsing.

- Improved ExtractBuildProvisionerHCL to merge inline commands from shell provisioners.

- Enhanced logging for enforced block operations in HCP Packer.

* Remove PublishEnforcedBlocks function from Bucket struct

* Remove ExtractBuildProvisionerHCL function and unused imports

* Reverted the version upgrade

* Added the internal-sdk for the enforcedProvsioner api changes

* 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.

* Implement enforced provisioner parsing and handling

- Introduced a new package `enforcedparser` to handle parsing of enforced provisioner blocks from HCL and JSON formats.

- Refactored existing code to utilize the new `ParseProvisionerBlocks` function from the `enforcedparser` package.

- Updated `GetCoreBuildProvisionerFromEnforcedBlock` method to convert enforced provisioner blocks into core build provisioners.

- Enhanced error handling and logging during the parsing process.

- Added tests for the new parsing functionality and ensured existing tests were updated to reflect changes.

- Modified `InjectEnforcedProvisioners` method in JSON registry to utilize the new parsing logic.

* Add test case for -skip-enforcement flag in BuildArgs

* Refactor sensitive variable handling in provisioners and add related tests

* Refactor enforced provisioner handling: remove internal parser, update tests, and streamline API interactions

* Enhance provisioner block parsing: add error handling for invalid combinations and expand test coverage

* Remove internal SDK replacement for enforced block types in go.mod

* Update dependencies in go.mod and go.sum: bump hcp-sdk-go and packer-plugin-sdk versions, adjust syft version, and update OpenTelemetry packages

* Update hcp-sdk-go dependency to v0.172.0 in go.mod and go.sum

* Fix formatting in TestBuildCommand_ParseArgs and add newline at end of json_enforced_test.go

* Refactor testJSONRegistryWithBuilds: remove environment variable setup and streamline registry initialization

* Rename injected variable for clarity in InjectEnforcedProvisioners function

---------

Co-authored-by: Hari Om <[email protected]>
2026-04-17 14:17:28 +05:30

147 lines
4.4 KiB
Go

// 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
}