template: builder parsing
This commit is contained in:
parent
1e745d9508
commit
95890003b7
|
@ -0,0 +1,123 @@
|
|||
package template
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
)
|
||||
|
||||
// rawTemplate is the direct JSON document format of the template file.
|
||||
// This is what is decoded directly from the file, and then it is turned
|
||||
// into a Template object thereafter.
|
||||
type rawTemplate struct {
|
||||
MinVersion string `mapstructure:"min_packer_version"`
|
||||
Description string
|
||||
|
||||
Builders []map[string]interface{}
|
||||
Push map[string]interface{}
|
||||
PostProcesors []interface{} `mapstructure:"post-processors"`
|
||||
Provisioners []map[string]interface{}
|
||||
Variables map[string]interface{}
|
||||
}
|
||||
|
||||
// Template returns the actual Template object built from this raw
|
||||
// structure.
|
||||
func (r *rawTemplate) Template() (*Template, error) {
|
||||
var result Template
|
||||
var errs error
|
||||
|
||||
// Let's start by gathering all the builders
|
||||
result.Builders = make(map[string]*Builder)
|
||||
for i, rawB := range r.Builders {
|
||||
var b Builder
|
||||
if err := mapstructure.WeakDecode(rawB, &b); err != nil {
|
||||
errs = multierror.Append(errs, fmt.Errorf(
|
||||
"builder %d: %s", i+1, err))
|
||||
continue
|
||||
}
|
||||
|
||||
// Set the raw configuration and delete any special keys
|
||||
b.Config = rawB
|
||||
delete(b.Config, "name")
|
||||
delete(b.Config, "type")
|
||||
if len(b.Config) == 0 {
|
||||
b.Config = nil
|
||||
}
|
||||
|
||||
// If there is no type set, it is an error
|
||||
if b.Type == "" {
|
||||
errs = multierror.Append(errs, fmt.Errorf(
|
||||
"builder %d: missing 'type'", i+1))
|
||||
continue
|
||||
}
|
||||
|
||||
// The name defaults to the type if it isn't set
|
||||
if b.Name == "" {
|
||||
b.Name = b.Type
|
||||
}
|
||||
|
||||
// If this builder already exists, it is an error
|
||||
if _, ok := result.Builders[b.Name]; ok {
|
||||
errs = multierror.Append(errs, fmt.Errorf(
|
||||
"builder %d: builder with name '%s' already exists",
|
||||
i+1, b.Name))
|
||||
continue
|
||||
}
|
||||
|
||||
// Append the builders
|
||||
result.Builders[b.Name] = &b
|
||||
}
|
||||
|
||||
// If we have errors, return those with a nil result
|
||||
if errs != nil {
|
||||
return nil, errs
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// Parse takes the given io.Reader and parses a Template object out of it.
|
||||
func Parse(r io.Reader) (*Template, error) {
|
||||
// First, decode the object into an interface{}. We do this instead of
|
||||
// the rawTemplate directly because we'd rather use mapstructure to
|
||||
// decode since it has richer errors.
|
||||
var raw interface{}
|
||||
if err := json.NewDecoder(r).Decode(&raw); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create our decoder
|
||||
var md mapstructure.Metadata
|
||||
var rawTpl rawTemplate
|
||||
decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
|
||||
Metadata: &md,
|
||||
Result: &rawTpl,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Do the actual decode into our structure
|
||||
if err := decoder.Decode(raw); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Build an error if there are unused root level keys
|
||||
if len(md.Unused) > 0 {
|
||||
sort.Strings(md.Unused)
|
||||
for _, unused := range md.Unused {
|
||||
err = multierror.Append(err, fmt.Errorf(
|
||||
"Unknown root level key in template: '%s'", unused))
|
||||
}
|
||||
|
||||
// Return early for these errors
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Return the template parsed from the raw structure
|
||||
return rawTpl.Template()
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
package template
|
||||
|
||||
import (
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
cases := []struct {
|
||||
File string
|
||||
Result *Template
|
||||
Err bool
|
||||
}{
|
||||
{
|
||||
"parse-basic.json",
|
||||
&Template{
|
||||
Builders: map[string]*Builder{
|
||||
"something": &Builder{
|
||||
Name: "something",
|
||||
Type: "something",
|
||||
},
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"parse-builder-no-type.json",
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"parse-builder-repeat.json",
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
f, err := os.Open(fixtureDir(tc.File))
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
tpl, err := Parse(f)
|
||||
f.Close()
|
||||
if (err != nil) != tc.Err {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(tpl, tc.Result) {
|
||||
t.Fatalf("bad: %#v", tpl)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
package template
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Template represents the parsed template that is used to configure
|
||||
// Packer builds.
|
||||
type Template struct {
|
||||
Description string
|
||||
MinVersion string
|
||||
|
||||
Variables map[string]*Variable
|
||||
Builders map[string]*Builder
|
||||
Provisioners []*Provisioner
|
||||
PostProcessors [][]*PostProcessor
|
||||
Push *Push
|
||||
}
|
||||
|
||||
// Builder represents a builder configured in the template
|
||||
type Builder struct {
|
||||
Name string
|
||||
Type string
|
||||
Config map[string]interface{}
|
||||
}
|
||||
|
||||
// PostProcessor represents a post-processor within the template.
|
||||
type PostProcessor struct {
|
||||
OnlyExcept
|
||||
|
||||
Type string
|
||||
KeepInputArtifact bool
|
||||
Config map[string]interface{}
|
||||
}
|
||||
|
||||
// Provisioner represents a provisioner within the template.
|
||||
type Provisioner struct {
|
||||
OnlyExcept
|
||||
|
||||
Type string
|
||||
Config map[string]interface{}
|
||||
Override map[string]interface{}
|
||||
PauseBefore time.Duration
|
||||
}
|
||||
|
||||
// Push represents the configuration for pushing the template to Atlas.
|
||||
type Push struct {
|
||||
Name string
|
||||
Address string
|
||||
BaseDir string `mapstructure:"base_dir"`
|
||||
Include []string
|
||||
Exclude []string
|
||||
Token string
|
||||
VCS bool
|
||||
}
|
||||
|
||||
// Variable represents a variable within the template
|
||||
type Variable struct {
|
||||
Default string
|
||||
Required bool
|
||||
}
|
||||
|
||||
// OnlyExcept is a struct that is meant to be embedded that contains the
|
||||
// logic required for "only" and "except" meta-parameters.
|
||||
type OnlyExcept struct {
|
||||
Only []string
|
||||
Except []string
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// GoStringer
|
||||
//-------------------------------------------------------------------
|
||||
|
||||
func (b *Builder) GoString() string {
|
||||
return fmt.Sprintf("*%#v", *b)
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package template
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
const FixturesDir = "./test-fixtures"
|
||||
|
||||
// fixtureDir returns the path to a test fixtures directory
|
||||
func fixtureDir(n string) string {
|
||||
return filepath.Join(FixturesDir, n)
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"builders": [{"type": "something"}]
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"builders": [{"foo": "something"}]
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"builders": [
|
||||
{"type": "something"},
|
||||
{"type": "something"}
|
||||
]
|
||||
}
|
Loading…
Reference in New Issue