diff --git a/builder/file/builder.go b/builder/file/builder.go index 3b00aae60..ea3206dad 100644 --- a/builder/file/builder.go +++ b/builder/file/builder.go @@ -6,7 +6,10 @@ any virutalization or network resources, it's very fast and useful for testing. */ import ( + "fmt" + "io" "io/ioutil" + "os" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" @@ -47,7 +50,35 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { 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) + if b.config.Source != "" { + source, err := os.Open(b.config.Source) + defer source.Close() + if err != nil { + return nil, err + } + + target, err := os.OpenFile(b.config.Target, os.O_WRONLY, 0600) + defer target.Close() + if err != nil { + return nil, err + } + + ui.Say(fmt.Sprintf("Copying %s to %s", source.Name(), target.Name())) + bytes, err := io.Copy(source, target) + if err != nil { + return nil, err + } + ui.Say(fmt.Sprintf("Copied %d bytes", bytes)) + artifact.filename = target.Name() + } else { + // We're going to write Contents; if it's empty we'll just create an + // empty file. + err := ioutil.WriteFile(b.config.Target, []byte(b.config.Content), 0600) + if err != nil { + return nil, err + } + artifact.filename = b.config.Target + } return artifact, nil } diff --git a/builder/file/config.go b/builder/file/config.go index 534428ca4..6702e6894 100644 --- a/builder/file/config.go +++ b/builder/file/config.go @@ -9,11 +9,15 @@ import ( "github.com/mitchellh/packer/template/interpolate" ) +var ErrTargetRequired = fmt.Errorf("target required") +var ErrContentSourceConflict = fmt.Errorf("Cannot specify source file AND content") + type Config struct { common.PackerConfig `mapstructure:",squash"` - Filename string `mapstructure:"filename"` - Contents string `mapstructure:"contents"` + Source string `mapstructure:"source"` + Target string `mapstructure:"target"` + Content string `mapstructure:"content"` } func NewConfig(raws ...interface{}) (*Config, []string, error) { @@ -32,12 +36,16 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { var errs *packer.MultiError - if c.Filename == "" { - errs = packer.MultiErrorAppend(errs, fmt.Errorf("filename is required")) + if c.Target == "" { + errs = packer.MultiErrorAppend(errs, ErrTargetRequired) + } + + if c.Content == "" && c.Source == "" { + warnings = append(warnings, "Both source file and contents are blank; target will have no content") } - if c.Contents == "" { - warnings = append(warnings, "contents is empty") + if c.Content != "" && c.Source != "" { + errs = packer.MultiErrorAppend(errs, ErrContentSourceConflict) } if errs != nil && len(errs.Errors) > 0 { diff --git a/builder/file/config_test.go b/builder/file/config_test.go index 061bb97e5..6d8039558 100644 --- a/builder/file/config_test.go +++ b/builder/file/config_test.go @@ -2,13 +2,24 @@ package file import ( "fmt" + "strings" "testing" ) func testConfig() map[string]interface{} { return map[string]interface{}{ - "filename": "test.txt", - "contents": "Hello, world!", + "source": "src.txt", + "target": "dst.txt", + "content": "Hello, world!", + } +} + +func TestContentSourceConflict(t *testing.T) { + raw := testConfig() + + _, _, errs := NewConfig(raw) + if !strings.Contains(errs.Error(), ErrContentSourceConflict.Error()) { + t.Errorf("Expected config error: %s", ErrContentSourceConflict.Error()) } } @@ -18,18 +29,19 @@ func TestNoFilename(t *testing.T) { delete(raw, "filename") _, _, errs := NewConfig(raw) if errs == nil { - t.Error("Expected config to error without a filename") + t.Errorf("Expected config error: %s", ErrTargetRequired.Error()) } } func TestNoContent(t *testing.T) { raw := testConfig() - delete(raw, "contents") + delete(raw, "content") + delete(raw, "source") _, 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") + t.Error("Expected config warning without any content") } }