diff --git a/builder/file/artifact.go b/builder/file/artifact.go new file mode 100644 index 000000000..35bf06e6c --- /dev/null +++ b/builder/file/artifact.go @@ -0,0 +1,36 @@ +package file + +import ( + "fmt" + "log" + "os" +) + +type FileArtifact struct { + filename string +} + +func (*FileArtifact) BuilderId() string { + return BuilderId +} + +func (a *FileArtifact) Files() []string { + return []string{a.filename} +} + +func (a *FileArtifact) Id() string { + return "File" +} + +func (a *FileArtifact) String() string { + return fmt.Sprintf("Stored file: %s", a.filename) +} + +func (a *FileArtifact) State(name string) interface{} { + return nil +} + +func (a *FileArtifact) Destroy() error { + log.Printf("Deleting %s", a.filename) + return os.Remove(a.filename) +} diff --git a/builder/file/artifact_test.go b/builder/file/artifact_test.go new file mode 100644 index 000000000..0aa77894b --- /dev/null +++ b/builder/file/artifact_test.go @@ -0,0 +1,11 @@ +package file + +import ( + "testing" + + "github.com/mitchellh/packer/packer" +) + +func TestNullArtifact(t *testing.T) { + var _ packer.Artifact = new(FileArtifact) +} diff --git a/builder/file/builder.go b/builder/file/builder.go new file mode 100644 index 000000000..89047ab75 --- /dev/null +++ b/builder/file/builder.go @@ -0,0 +1,53 @@ +package file + +/* +The File builder creates an artifact from a file. Because it does not require +any virutalization or network resources, it's very fast and useful for testing. +*/ + +import ( + "io/ioutil" + + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" +) + +const BuilderId = "cbednarski.file" + +type Builder struct { + config *Config + runner multistep.Runner +} + +// Prepare is responsible for configuring the builder and validating +// that configuration. Any setup should be done in this method. Note that +// NO side effects should take place in prepare, it is meant as a state +// setup only. Calling Prepare is not necessarilly followed by a Run. +// +// The parameters to Prepare are a set of interface{} values of the +// configuration. These are almost always `map[string]interface{}` +// parsed from a template, but no guarantee is made. +// +// Each of the configuration values should merge into the final +// configuration. +// +// Prepare should return a list of warnings along with any errors +// that occured while preparing. +func (b *Builder) Prepare(...interface{}) ([]string, error) { + return nil, nil +} + +// Run is where the actual build should take place. It takes a Build and a Ui. +func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) { + artifact := new(FileArtifact) + + ioutil.WriteFile(b.config.Filename, []byte(b.config.Contents), 0600) + + return artifact, nil +} + +// Cancel cancels a possibly running Builder. This should block until +// the builder actually cancels and cleans up after itself. +func (b *Builder) Cancel() { + b.runner.Cancel() +} diff --git a/builder/file/builder_test.go b/builder/file/builder_test.go new file mode 100644 index 000000000..63d36a0a5 --- /dev/null +++ b/builder/file/builder_test.go @@ -0,0 +1,11 @@ +package file + +import ( + "testing" + + "github.com/mitchellh/packer/packer" +) + +func TestBuilder_implBuilder(t *testing.T) { + var _ packer.Builder = new(Builder) +} diff --git a/builder/file/config.go b/builder/file/config.go new file mode 100644 index 000000000..534428ca4 --- /dev/null +++ b/builder/file/config.go @@ -0,0 +1,48 @@ +package file + +import ( + "fmt" + + "github.com/mitchellh/packer/common" + "github.com/mitchellh/packer/helper/config" + "github.com/mitchellh/packer/packer" + "github.com/mitchellh/packer/template/interpolate" +) + +type Config struct { + common.PackerConfig `mapstructure:",squash"` + + Filename string `mapstructure:"filename"` + Contents string `mapstructure:"contents"` +} + +func NewConfig(raws ...interface{}) (*Config, []string, error) { + c := new(Config) + warnings := []string{} + + err := config.Decode(c, &config.DecodeOpts{ + Interpolate: true, + InterpolateFilter: &interpolate.RenderFilter{ + Exclude: []string{}, + }, + }, raws...) + if err != nil { + return nil, warnings, err + } + + var errs *packer.MultiError + + if c.Filename == "" { + errs = packer.MultiErrorAppend(errs, fmt.Errorf("filename is required")) + } + + if c.Contents == "" { + warnings = append(warnings, "contents is empty") + } + + if errs != nil && len(errs.Errors) > 0 { + return nil, warnings, errs + } + + return c, warnings, nil +} diff --git a/builder/file/config_test.go b/builder/file/config_test.go new file mode 100644 index 000000000..061bb97e5 --- /dev/null +++ b/builder/file/config_test.go @@ -0,0 +1,35 @@ +package file + +import ( + "fmt" + "testing" +) + +func testConfig() map[string]interface{} { + return map[string]interface{}{ + "filename": "test.txt", + "contents": "Hello, world!", + } +} + +func TestNoFilename(t *testing.T) { + raw := testConfig() + + delete(raw, "filename") + _, _, errs := NewConfig(raw) + if errs == nil { + t.Error("Expected config to error without a filename") + } +} + +func TestNoContent(t *testing.T) { + raw := testConfig() + + delete(raw, "contents") + _, warns, _ := NewConfig(raw) + fmt.Println(len(warns)) + fmt.Printf("%#v\n", warns) + if len(warns) == 0 { + t.Error("Expected config to warn without any content") + } +}