582 lines
10 KiB
Go
582 lines
10 KiB
Go
// +build !windows
|
|
|
|
package template
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"path/filepath"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
)
|
|
|
|
func boolPointer(tf bool) *bool {
|
|
return &tf
|
|
}
|
|
|
|
func TestParse(t *testing.T) {
|
|
cases := []struct {
|
|
File string
|
|
Result *Template
|
|
Err bool
|
|
}{
|
|
/*
|
|
* Builders
|
|
*/
|
|
{
|
|
"parse-basic.json",
|
|
&Template{
|
|
Builders: map[string]*Builder{
|
|
"something": {
|
|
Name: "something",
|
|
Type: "something",
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"parse-basic-config.json",
|
|
&Template{
|
|
Builders: map[string]*Builder{
|
|
"something": {
|
|
Name: "something",
|
|
Type: "something",
|
|
Config: map[string]interface{}{
|
|
"foo": "bar",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"parse-builder-no-type.json",
|
|
nil,
|
|
true,
|
|
},
|
|
{
|
|
"parse-builder-repeat.json",
|
|
nil,
|
|
true,
|
|
},
|
|
|
|
/*
|
|
* Provisioners
|
|
*/
|
|
{
|
|
"parse-provisioner-basic.json",
|
|
&Template{
|
|
Provisioners: []*Provisioner{
|
|
{
|
|
Type: "something",
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"parse-provisioner-config.json",
|
|
&Template{
|
|
Provisioners: []*Provisioner{
|
|
{
|
|
Type: "something",
|
|
Config: map[string]interface{}{
|
|
"inline": "echo 'foo'",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"parse-provisioner-pause-before.json",
|
|
&Template{
|
|
Provisioners: []*Provisioner{
|
|
{
|
|
Type: "something",
|
|
PauseBefore: 1 * time.Second,
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
},
|
|
|
|
{
|
|
"parse-provisioner-timeout.json",
|
|
&Template{
|
|
Provisioners: []*Provisioner{
|
|
{
|
|
Type: "something",
|
|
Timeout: 5 * time.Minute,
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
},
|
|
|
|
{
|
|
"parse-provisioner-only.json",
|
|
&Template{
|
|
Provisioners: []*Provisioner{
|
|
{
|
|
Type: "something",
|
|
OnlyExcept: OnlyExcept{
|
|
Only: []string{"foo"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
},
|
|
|
|
{
|
|
"parse-provisioner-except.json",
|
|
&Template{
|
|
Provisioners: []*Provisioner{
|
|
{
|
|
Type: "something",
|
|
OnlyExcept: OnlyExcept{
|
|
Except: []string{"foo"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
},
|
|
|
|
{
|
|
"parse-provisioner-override.json",
|
|
&Template{
|
|
Provisioners: []*Provisioner{
|
|
{
|
|
Type: "something",
|
|
Override: map[string]interface{}{
|
|
"foo": map[string]interface{}{},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
},
|
|
|
|
{
|
|
"parse-provisioner-no-type.json",
|
|
nil,
|
|
true,
|
|
},
|
|
|
|
{
|
|
"parse-variable-default.json",
|
|
&Template{
|
|
Variables: map[string]*Variable{
|
|
"foo": {
|
|
Default: "foo",
|
|
Key: "foo",
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
},
|
|
|
|
{
|
|
"parse-variable-required.json",
|
|
&Template{
|
|
Variables: map[string]*Variable{
|
|
"foo": {
|
|
Required: true,
|
|
Key: "foo",
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
},
|
|
|
|
{
|
|
"parse-pp-basic.json",
|
|
&Template{
|
|
PostProcessors: [][]*PostProcessor{
|
|
{
|
|
{
|
|
Name: "foo",
|
|
Type: "foo",
|
|
Config: map[string]interface{}{
|
|
"foo": "bar",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
},
|
|
|
|
{
|
|
"parse-pp-keep.json",
|
|
&Template{
|
|
PostProcessors: [][]*PostProcessor{
|
|
{
|
|
{
|
|
Name: "foo",
|
|
Type: "foo",
|
|
KeepInputArtifact: boolPointer(true),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
},
|
|
|
|
{
|
|
"parse-pp-only.json",
|
|
&Template{
|
|
PostProcessors: [][]*PostProcessor{
|
|
{
|
|
{
|
|
Name: "foo",
|
|
Type: "foo",
|
|
OnlyExcept: OnlyExcept{
|
|
Only: []string{"bar"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
},
|
|
|
|
{
|
|
"parse-pp-except.json",
|
|
&Template{
|
|
PostProcessors: [][]*PostProcessor{
|
|
{
|
|
{
|
|
Name: "foo",
|
|
Type: "foo",
|
|
OnlyExcept: OnlyExcept{
|
|
Except: []string{"bar"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
},
|
|
|
|
{
|
|
"parse-pp-string.json",
|
|
&Template{
|
|
PostProcessors: [][]*PostProcessor{
|
|
{
|
|
{
|
|
Name: "foo",
|
|
Type: "foo",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
},
|
|
|
|
{
|
|
"parse-pp-map.json",
|
|
&Template{
|
|
PostProcessors: [][]*PostProcessor{
|
|
{
|
|
{
|
|
Name: "foo",
|
|
Type: "foo",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
},
|
|
|
|
{
|
|
"parse-pp-slice.json",
|
|
&Template{
|
|
PostProcessors: [][]*PostProcessor{
|
|
{
|
|
{
|
|
Name: "foo",
|
|
Type: "foo",
|
|
},
|
|
},
|
|
{
|
|
{
|
|
Name: "bar",
|
|
Type: "bar",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
},
|
|
|
|
{
|
|
"parse-pp-multi.json",
|
|
&Template{
|
|
PostProcessors: [][]*PostProcessor{
|
|
{
|
|
{
|
|
Name: "foo",
|
|
Type: "foo",
|
|
},
|
|
{
|
|
Name: "bar",
|
|
Type: "bar",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
},
|
|
|
|
{
|
|
"parse-pp-no-type.json",
|
|
nil,
|
|
true,
|
|
},
|
|
|
|
{
|
|
"parse-description.json",
|
|
&Template{
|
|
Description: "foo",
|
|
},
|
|
false,
|
|
},
|
|
|
|
{
|
|
"parse-min-version.json",
|
|
&Template{
|
|
MinVersion: "1.2",
|
|
},
|
|
false,
|
|
},
|
|
|
|
{
|
|
"parse-comment.json",
|
|
&Template{
|
|
Builders: map[string]*Builder{
|
|
"something": {
|
|
Name: "something",
|
|
Type: "something",
|
|
},
|
|
},
|
|
Comments: map[string]string{
|
|
"_info": "foo",
|
|
},
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"parse-monolithic.json",
|
|
&Template{
|
|
Comments: map[string]string{
|
|
"_comment": "comment",
|
|
},
|
|
Description: "Description Test",
|
|
MinVersion: "1.3.0",
|
|
SensitiveVariables: []*Variable{
|
|
{
|
|
Required: false,
|
|
Key: "one",
|
|
Default: "1",
|
|
},
|
|
},
|
|
Variables: map[string]*Variable{
|
|
"one": {
|
|
Required: false,
|
|
Key: "one",
|
|
Default: "1",
|
|
},
|
|
"two": {
|
|
Required: false,
|
|
Key: "two",
|
|
Default: "2",
|
|
},
|
|
"three": {
|
|
Required: true,
|
|
Key: "three",
|
|
Default: "",
|
|
},
|
|
},
|
|
Builders: map[string]*Builder{
|
|
"amazon-ebs": {
|
|
Name: "amazon-ebs",
|
|
Type: "amazon-ebs",
|
|
Config: map[string]interface{}{
|
|
"ami_name": "AMI Name",
|
|
"instance_type": "t2.micro",
|
|
"ssh_username": "ec2-user",
|
|
"source_ami": "ami-aaaaaaaaaaaaaa",
|
|
},
|
|
},
|
|
"docker": {
|
|
Name: "docker",
|
|
Type: "docker",
|
|
Config: map[string]interface{}{
|
|
"image": "ubuntu",
|
|
"export_path": "image.tar",
|
|
},
|
|
},
|
|
},
|
|
Provisioners: []*Provisioner{
|
|
{
|
|
Type: "shell",
|
|
Config: map[string]interface{}{
|
|
"script": "script.sh",
|
|
},
|
|
},
|
|
{
|
|
Type: "shell",
|
|
Config: map[string]interface{}{
|
|
"script": "script.sh",
|
|
},
|
|
Override: map[string]interface{}{
|
|
"docker": map[string]interface{}{
|
|
"execute_command": "echo 'override'",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
PostProcessors: [][]*PostProcessor{
|
|
{
|
|
{
|
|
Name: "compress",
|
|
Type: "compress",
|
|
},
|
|
{
|
|
Name: "vagrant",
|
|
Type: "vagrant",
|
|
OnlyExcept: OnlyExcept{
|
|
Only: []string{"docker"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
{
|
|
Name: "shell-local",
|
|
Type: "shell-local",
|
|
Config: map[string]interface{}{
|
|
"inline": []interface{}{"echo foo"},
|
|
},
|
|
OnlyExcept: OnlyExcept{
|
|
Except: []string{"amazon-ebs"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
},
|
|
}
|
|
|
|
for i, tc := range cases {
|
|
path, _ := filepath.Abs(fixtureDir(tc.File))
|
|
tpl, err := ParseFile(fixtureDir(tc.File))
|
|
if (err != nil) != tc.Err {
|
|
t.Fatalf("%s\n\nerr: %s", tc.File, err)
|
|
}
|
|
|
|
if tc.Result != nil {
|
|
tc.Result.Path = path
|
|
}
|
|
if tpl != nil {
|
|
tpl.RawContents = nil
|
|
}
|
|
if diff := cmp.Diff(tpl, tc.Result); diff != "" {
|
|
t.Fatalf("[%d]bad: %s\n%v", i, tc.File, diff)
|
|
}
|
|
|
|
// Only test template writing if the template is valid
|
|
if tc.Err == false {
|
|
// Get rawTemplate
|
|
raw, err := tpl.Raw()
|
|
if err != nil {
|
|
t.Fatalf("Failed to convert back to raw template: %s\n\n%v\n\n%s", tc.File, tpl, err)
|
|
}
|
|
|
|
out, _ := json.MarshalIndent(raw, "", " ")
|
|
if err != nil {
|
|
t.Fatalf("Failed to marshal raw template: %s\n\n%v\n\n%s", tc.File, raw, err)
|
|
}
|
|
|
|
// Write JSON to a buffer (emulates filesystem write without dirtying the workspace)
|
|
fileBuf := bytes.NewBuffer(out)
|
|
|
|
// Parse the JSON template we wrote to our buffer
|
|
tplRewritten, err := Parse(fileBuf)
|
|
if err != nil {
|
|
t.Fatalf("Failed to re-read marshalled template: %s\n\n%v\n\n%s\n\n%s", tc.File, tpl, out, err)
|
|
}
|
|
|
|
// Override the metadata we don't care about (file path, raw file contents)
|
|
tplRewritten.Path = path
|
|
tplRewritten.RawContents = nil
|
|
|
|
// Test that our output raw template is functionally equal
|
|
if !reflect.DeepEqual(tpl, tplRewritten) {
|
|
t.Fatalf("Data lost when writing template to file: %s\n\n%v\n\n%v\n\n%s", tc.File, tpl, tplRewritten, out)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParse_contents(t *testing.T) {
|
|
tpl, err := ParseFile(fixtureDir("parse-contents.json"))
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
actual := strings.TrimSpace(string(tpl.RawContents))
|
|
expected := `{"builders":[{"type":"test"}]}`
|
|
if actual != expected {
|
|
t.Fatalf("bad: %s\n\n%s", actual, expected)
|
|
}
|
|
}
|
|
|
|
func TestParse_bad(t *testing.T) {
|
|
cases := []struct {
|
|
File string
|
|
Expected string
|
|
}{
|
|
{"error-beginning.json", "line 1, column 1 (offset 1)"},
|
|
{"error-middle.json", "line 5, column 6 (offset 50)"},
|
|
{"error-end.json", "line 1, column 30 (offset 30)"},
|
|
{"malformed.json", "line 16, column 3 (offset 433)"},
|
|
}
|
|
for _, tc := range cases {
|
|
_, err := ParseFile(fixtureDir(tc.File))
|
|
if err == nil {
|
|
t.Fatalf("expected error")
|
|
}
|
|
if !strings.Contains(err.Error(), tc.Expected) {
|
|
t.Fatalf("file: %s\nExpected: %s\n%s\n", tc.File, tc.Expected, err.Error())
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParse_checkForDuplicateFields(t *testing.T) {
|
|
cases := []struct {
|
|
File string
|
|
Expected string
|
|
}{
|
|
{"error-duplicate-variables.json", "template has duplicate field: variables"},
|
|
{"error-duplicate-config.json", "template has duplicate field: foo"},
|
|
}
|
|
for _, tc := range cases {
|
|
_, err := ParseFile(fixtureDir(tc.File))
|
|
if err == nil {
|
|
t.Fatalf("expected error")
|
|
}
|
|
if !strings.Contains(err.Error(), tc.Expected) {
|
|
t.Fatalf("file: %s\nExpected: %s\n%s\n", tc.File, tc.Expected, err.Error())
|
|
}
|
|
}
|
|
}
|