You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
packer/hcl2template/enforced_provisioner_test.go

497 lines
11 KiB

// Copyright IBM Corp. 2013, 2025
// SPDX-License-Identifier: BUSL-1.1
package hcl2template
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 TestGetCoreBuildProvisionerFromBlock_IncludesSensitiveVariables(t *testing.T) {
parser := getBasicParser()
cfg := &PackerConfig{
parser: parser,
CorePackerVersionString: lockedVersion,
InputVariables: Variables{
"visible": &Variable{Name: "visible"},
"secret": &Variable{Name: "secret", Sensitive: true},
},
}
blocks, diags := ParseProvisionerBlocks(`
provisioner "shell" {
override = {
"amazon-ebs.ubuntu" = {
bool = false
}
}
}
`)
if diags.HasErrors() {
t.Fatalf("ParseProvisionerBlocks() unexpected error: %v", diags)
}
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)
}
sensitiveVars, ok := hclProv.builderVariables["packer_sensitive_variables"].([]string)
if !ok {
t.Fatalf("expected []string packer_sensitive_variables, got %T", hclProv.builderVariables["packer_sensitive_variables"])
}
if len(sensitiveVars) != 1 || sensitiveVars[0] != "secret" {
t.Fatalf("expected sensitive vars [secret], got %#v", sensitiveVars)
}
}
func TestParseProvisionerBlocks(t *testing.T) {
tests := []struct {
name string
blockContent string
wantCount int
wantTypes []string
wantErr bool
}{
{
name: "single shell provisioner",
blockContent: `
provisioner "shell" {
inline = ["echo 'Hello from enforced provisioner'"]
}
`,
wantCount: 1,
wantTypes: []string{"shell"},
wantErr: false,
},
{
name: "multiple provisioners",
blockContent: `
provisioner "shell" {
inline = ["echo 'First enforced provisioner'"]
}
provisioner "shell" {
name = "security-scan"
inline = ["echo 'Security scan running...'"]
}
`,
wantCount: 2,
wantTypes: []string{"shell", "shell"},
wantErr: false,
},
{
name: "provisioner with pause_before",
blockContent: `
provisioner "shell" {
pause_before = "10s"
inline = ["echo 'Waiting before execution'"]
}
`,
wantCount: 1,
wantTypes: []string{"shell"},
wantErr: false,
},
{
name: "provisioner with max_retries",
blockContent: `
provisioner "shell" {
max_retries = 3
inline = ["echo 'Retry test'"]
}
`,
wantCount: 1,
wantTypes: []string{"shell"},
wantErr: false,
},
{
name: "provisioner with only filter",
blockContent: `
provisioner "shell" {
only = ["amazon-ebs.ubuntu"]
inline = ["echo 'Only for amazon-ebs.ubuntu'"]
}
`,
wantCount: 1,
wantTypes: []string{"shell"},
wantErr: false,
},
{
name: "provisioner with except filter",
blockContent: `
provisioner "shell" {
except = ["null.test"]
inline = ["echo 'Except for null.test'"]
}
`,
wantCount: 1,
wantTypes: []string{"shell"},
wantErr: false,
},
{
name: "provisioner with both only and except",
blockContent: `
provisioner "shell" {
only = ["amazon-ebs.ubuntu"]
except = ["null.test"]
inline = ["echo 'invalid filter combination'"]
}
`,
wantCount: 0,
wantTypes: nil,
wantErr: true,
},
{
name: "empty block content",
blockContent: "",
wantCount: 0,
wantTypes: nil,
wantErr: false,
},
{
name: "whitespace only block content",
blockContent: "\n\n\t \n",
wantCount: 0,
wantTypes: nil,
wantErr: false,
},
{
name: "invalid HCL syntax",
blockContent: "this is not valid { hcl }}}",
wantCount: 0,
wantTypes: nil,
wantErr: true,
},
{
name: "json single shell provisioner",
blockContent: `{
"provisioner": [
{
"shell": {
"inline": ["echo 'Hello from enforced provisioner JSON'"]
}
}
]
}`,
wantCount: 1,
wantTypes: []string{"shell"},
wantErr: false,
},
{
name: "json provisioner with escaped newline in string value",
blockContent: `{
"provisioner": [
{
"shell": {
"inline": ["echo first line\necho second line"]
}
}
]
}`,
wantCount: 1,
wantTypes: []string{"shell"},
wantErr: false,
},
{
name: "json multiple provisioners",
blockContent: `{
"provisioner": [
{
"shell": {
"inline": ["echo 'first'"]
}
},
{
"shell": {
"name": "security-scan",
"inline": ["echo 'second'"]
}
}
]
}`,
wantCount: 2,
wantTypes: []string{"shell", "shell"},
wantErr: false,
},
{
name: "invalid json syntax",
blockContent: `{"provisioner": [ { "shell": { "inline": ["test"] } ] }`,
wantCount: 0,
wantTypes: nil,
wantErr: true,
},
{
name: "legacy json provisioners format",
blockContent: `{
"provisioners": [
{
"type": "shell",
"inline": ["echo legacy json format"]
}
]
}`,
wantCount: 1,
wantTypes: []string{"shell"},
wantErr: false,
},
{
name: "legacy json provisioners with escaped newline and multiple types",
blockContent: `{
"provisioners": [
{
"type": "shell",
"inline": ["echo legacy line 1\necho legacy line 2"]
},
{
"type": "file",
"source": "source.txt",
"destination": "destination.txt"
}
]
}`,
wantCount: 2,
wantTypes: []string{"shell", "file"},
wantErr: false,
},
{
name: "hcl provisioners with windows newlines",
blockContent: "provisioner \"shell\" {\r\n inline = [\"echo first\"]\r\n}\r\n\r\nprovisioner \"file\" {\r\n source = \"source.txt\"\r\n destination = \"destination.txt\"\r\n}\r\n",
wantCount: 2,
wantTypes: []string{"shell", "file"},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
blocks, diags := ParseProvisionerBlocks(tt.blockContent)
if tt.wantErr {
if !diags.HasErrors() {
t.Errorf("ParseProvisionerBlocks() expected error but got none")
}
return
}
if diags.HasErrors() {
t.Errorf("ParseProvisionerBlocks() unexpected error: %v", diags)
return
}
if len(blocks) != tt.wantCount {
t.Errorf("ParseProvisionerBlocks() got %d blocks, want %d", len(blocks), tt.wantCount)
return
}
for i, wantType := range tt.wantTypes {
if blocks[i].PType != wantType {
t.Errorf("ParseProvisionerBlocks() block[%d].PType = %q, want %q", i, blocks[i].PType, wantType)
}
}
})
}
}
func TestParseProvisionerBlocksWithPauseBefore(t *testing.T) {
blockContent := `
provisioner "shell" {
pause_before = "30s"
inline = ["echo 'test'"]
}
`
blocks, diags := ParseProvisionerBlocks(blockContent)
if diags.HasErrors() {
t.Fatalf("ParseProvisionerBlocks() unexpected error: %v", diags)
}
if len(blocks) != 1 {
t.Fatalf("Expected 1 block, got %d", len(blocks))
}
// pause_before should be parsed as 30 seconds
if blocks[0].PauseBefore.Seconds() != 30 {
t.Errorf("Expected PauseBefore=30s, got %v", blocks[0].PauseBefore)
}
}
func TestParseProvisionerBlocksWithMaxRetries(t *testing.T) {
blockContent := `
provisioner "shell" {
max_retries = 5
inline = ["echo 'test'"]
}
`
blocks, diags := ParseProvisionerBlocks(blockContent)
if diags.HasErrors() {
t.Fatalf("ParseProvisionerBlocks() unexpected error: %v", diags)
}
if len(blocks) != 1 {
t.Fatalf("Expected 1 block, got %d", len(blocks))
}
if blocks[0].MaxRetries != 5 {
t.Errorf("Expected MaxRetries=5, got %d", blocks[0].MaxRetries)
}
}
func TestParseProvisionerBlocksWithOnlyExcept(t *testing.T) {
blockContent := `
provisioner "shell" {
only = ["amazon-ebs.ubuntu", "azure-arm.windows"]
inline = ["echo 'test'"]
}
`
blocks, diags := ParseProvisionerBlocks(blockContent)
if diags.HasErrors() {
t.Fatalf("ParseProvisionerBlocks() unexpected error: %v", diags)
}
if len(blocks) != 1 {
t.Fatalf("Expected 1 block, got %d", len(blocks))
}
// Check only filter
if len(blocks[0].OnlyExcept.Only) != 2 {
t.Errorf("Expected 2 only values, got %d", len(blocks[0].OnlyExcept.Only))
}
// Skip should return true for sources not in the only list
if !blocks[0].OnlyExcept.Skip("null.test") {
t.Error("Skip() should return true for source not in only list")
}
// Skip should return false for sources in the only list
if blocks[0].OnlyExcept.Skip("amazon-ebs.ubuntu") {
t.Error("Skip() should return false for source in only list")
}
}
func TestParseProvisionerBlocksJSONWithOptions(t *testing.T) {
blockContent := `{
"provisioner": [
{
"shell": {
"pause_before": "15s",
"max_retries": 2,
"only": ["docker.ubuntu"],
"inline": ["echo 'json test'"]
}
}
]
}`
blocks, diags := ParseProvisionerBlocks(blockContent)
if diags.HasErrors() {
t.Fatalf("ParseProvisionerBlocks() unexpected error: %v", diags)
}
if len(blocks) != 1 {
t.Fatalf("Expected 1 block, got %d", len(blocks))
}
if blocks[0].PauseBefore.Seconds() != 15 {
t.Errorf("Expected PauseBefore=15s, got %v", blocks[0].PauseBefore)
}
if blocks[0].MaxRetries != 2 {
t.Errorf("Expected MaxRetries=2, got %d", blocks[0].MaxRetries)
}
if blocks[0].OnlyExcept.Skip("docker.ubuntu") {
t.Error("Skip() should return false for source in only list")
}
if !blocks[0].OnlyExcept.Skip("null.test") {
t.Error("Skip() should return true for source not in only list")
}
}