@ -4,7 +4,9 @@
package hcl2template
import (
"encoding/json"
"fmt"
"log"
"strconv"
"github.com/hashicorp/hcl/v2"
@ -19,15 +21,104 @@ var enforcedProvisionerSchema = &hcl.BodySchema{
} ,
}
// ParseProvisionerBlocks parses a partial HCL string that contains only
// top-level provisioner blocks and returns the parsed ProvisionerBlock list.
// ParseProvisionerBlocks parses a string containing one or more top-level provisioner blocks
// in either HCL or JSON syntax, and returns a slice of parsed ProvisionerBlock objects along
// with any diagnostics encountered during parsing.
func ParseProvisionerBlocks ( blockContent string ) ( [ ] * ProvisionerBlock , hcl . Diagnostics ) {
parser := & Parser { Parser : hclparse . NewParser ( ) }
log . Printf ( "[DEBUG] parsing enforced provisioner block content as HCL" )
file , diags := parser . ParseHCL ( [ ] byte ( blockContent ) , "enforced_provisioner.pkr.hcl" )
if diags . HasErrors ( ) {
return nil , diags
if ! diags . HasErrors ( ) {
log . Printf ( "[DEBUG] parsed enforced provisioner block content as HCL" )
return parseProvisionerBlocksFromFile ( parser , file , diags )
}
log . Printf ( "[DEBUG] failed to parse enforced provisioner block content as HCL, trying JSON fallback" )
// Fallback to HCL-JSON for enforced block content authored in JSON syntax.
jsonFile , jsonDiags := parser . ParseJSON ( [ ] byte ( blockContent ) , "enforced_provisioner.pkr.json" )
if jsonDiags . HasErrors ( ) {
log . Printf ( "[DEBUG] failed to parse enforced provisioner block content as JSON" )
return nil , append ( diags , jsonDiags ... )
}
provisioners , provisionerDiags := parseProvisionerBlocksFromFile ( parser , jsonFile , jsonDiags )
if ! provisionerDiags . HasErrors ( ) && len ( provisioners ) > 0 {
log . Printf ( "[DEBUG] parsed enforced provisioner block content as JSON" )
return provisioners , provisionerDiags
}
// Backward compatibility fallback for legacy JSON shape:
// {"provisioners":[{"type":"shell", ...}]}
legacyJSON , ok , err := normalizeLegacyEnforcedProvisionersJSON ( blockContent )
if err == nil && ok {
legacyFile , legacyDiags := parser . ParseJSON ( [ ] byte ( legacyJSON ) , "enforced_provisioner_legacy.pkr.json" )
if ! legacyDiags . HasErrors ( ) {
legacyProvisioners , legacyProvisionerDiags := parseProvisionerBlocksFromFile ( parser , legacyFile , legacyDiags )
if ! legacyProvisionerDiags . HasErrors ( ) && len ( legacyProvisioners ) > 0 {
log . Printf ( "[DEBUG] parsed enforced provisioner block content as legacy JSON" )
return legacyProvisioners , legacyProvisionerDiags
}
}
}
if provisionerDiags . HasErrors ( ) {
return nil , provisionerDiags
}
log . Printf ( "[DEBUG] parsed enforced provisioner block content as JSON but found no valid provisioner blocks" )
return provisioners , provisionerDiags
}
func normalizeLegacyEnforcedProvisionersJSON ( 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 _ , p := range payload . Provisioners {
typeName , ok := p [ "type" ] . ( string )
if ! ok || typeName == "" {
continue
}
cfg := make ( map [ string ] interface { } )
for k , v := range p {
if k == "type" {
continue
}
cfg [ k ] = v
}
normalized = append ( normalized , map [ string ] interface { } { typeName : cfg } )
}
if len ( normalized ) == 0 {
return "" , false , nil
}
out := map [ string ] interface { } {
"provisioner" : normalized ,
}
b , err := json . Marshal ( out )
if err != nil {
return "" , false , err
}
return string ( b ) , true , nil
}
func parseProvisionerBlocksFromFile ( parser * Parser , file * hcl . File , diags hcl . Diagnostics ) ( [ ] * ProvisionerBlock , hcl . Diagnostics ) {
content , moreDiags := file . Body . Content ( enforcedProvisionerSchema )
diags = append ( diags , moreDiags ... )
if diags . HasErrors ( ) {