Commit df977c4c authored by Mitchell Hashimoto's avatar Mitchell Hashimoto

Merge pull request #136 from ericlathrop/file-provisioner

File upload provisioner [GH-118]
parents 6591f8e9 a9715efd
......@@ -35,6 +35,7 @@ const defaultConfig = `
},
"provisioners": {
"file": "packer-provisioner-file",
"shell": "packer-provisioner-shell"
}
}
......
package main
import (
"github.com/mitchellh/packer/packer/plugin"
"github.com/mitchellh/packer/provisioner/file"
)
func main() {
plugin.ServeProvisioner(new(file.Provisioner))
}
package file
import (
"errors"
"fmt"
"github.com/mitchellh/mapstructure"
"github.com/mitchellh/packer/packer"
"os"
)
type config struct {
// The local path of the file to upload.
Source string
// The remote path where the local file will be uploaded to.
Destination string
}
type Provisioner struct {
config config
}
func (p *Provisioner) Prepare(raws ...interface{}) error {
for _, raw := range raws {
if err := mapstructure.Decode(raw, &p.config); err != nil {
return err
}
}
errs := []error{}
if _, err := os.Stat(p.config.Source); err != nil {
errs = append(errs, fmt.Errorf("Bad source file '%s': %s", p.config.Source, err))
}
if len(p.config.Destination) == 0 {
errs = append(errs, errors.New("Destination must be specified."))
}
if len(errs) > 0 {
return &packer.MultiError{errs}
}
return nil
}
func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
ui.Say(fmt.Sprintf("Uploading %s => %s", p.config.Source, p.config.Destination))
f, err := os.Open(p.config.Source)
if err != nil {
return err
}
return comm.Upload(p.config.Destination, f)
}
package file
import (
"github.com/mitchellh/packer/packer"
"io"
"io/ioutil"
"os"
"strings"
"testing"
)
func TestProvisioner_Impl(t *testing.T) {
var raw interface{}
raw = &Provisioner{}
if _, ok := raw.(packer.Provisioner); !ok {
t.Fatalf("must be a provisioner")
}
}
func TestProvisionerPrepare_InvalidSource(t *testing.T) {
var p Provisioner
config := map[string]interface{}{"source": "/this/should/not/exist", "destination": "something"}
err := p.Prepare(config)
if err == nil {
t.Fatalf("should require existing file")
}
}
func TestProvisionerPrepare_ValidSource(t *testing.T) {
var p Provisioner
tf, err := ioutil.TempFile("", "packer")
if err != nil {
t.Fatalf("error tempfile: %s", err)
}
defer os.Remove(tf.Name())
config := map[string]interface{}{"source": tf.Name(), "destination": "something"}
err = p.Prepare(config)
if err != nil {
t.Fatalf("should allow valid file: %s", err)
}
}
func TestProvisionerPrepare_EmptyDestination(t *testing.T) {
var p Provisioner
config := map[string]interface{}{"source": "/this/exists"}
err := p.Prepare(config)
if err == nil {
t.Fatalf("should require destination path")
}
}
type stubUploadCommunicator struct {
dest string
data io.Reader
}
func (suc *stubUploadCommunicator) Download(src string, data io.Writer) error {
return nil
}
func (suc *stubUploadCommunicator) Upload(dest string, data io.Reader) error {
suc.dest = dest
suc.data = data
return nil
}
func (suc *stubUploadCommunicator) Start(cmd *packer.RemoteCmd) error {
return nil
}
type stubUi struct {
sayMessages string
}
func (su *stubUi) Ask(string) (string, error) {
return "", nil
}
func (su *stubUi) Error(string) {
}
func (su *stubUi) Message(string) {
}
func (su *stubUi) Say(msg string) {
su.sayMessages += msg
}
func TestProvisionerProvision_SendsFile(t *testing.T) {
var p Provisioner
tf, err := ioutil.TempFile("", "packer")
if err != nil {
t.Fatalf("error tempfile: %s", err)
}
defer os.Remove(tf.Name())
if _, err = tf.Write([]byte("hello")); err != nil {
t.Fatalf("error writing tempfile: %s", err)
}
config := map[string]interface{}{"source": tf.Name(), "destination": "something"}
p.Prepare(config)
ui := &stubUi{}
comm := &stubUploadCommunicator{}
err = p.Provision(ui, comm)
if err != nil {
t.Fatalf("should successfully provision: %s", err)
}
if !strings.Contains(ui.sayMessages, tf.Name()) {
t.Fatalf("should print source filename")
}
if !strings.Contains(ui.sayMessages, "something") {
t.Fatalf("should print destination filename")
}
if comm.dest != "something" {
t.Fatalf("should upload to configured destination")
}
read, err := ioutil.ReadAll(comm.data)
if err != nil || string(read) != "hello" {
t.Fatalf("should upload with source file's data")
}
}
---
layout: "docs"
---
# File Provisioner
Type: `file`
The file provisioner uploads files to machines build by Packer.
## Basic Example
<pre class="prettyprint">
{
"type": "file",
"source": "app.tar.gz",
"destination": "/tmp/app.tar.gz"
}
</pre>
## Configuration Reference
The available configuration options are listed below. All elements are required.
* `source` (string) - The path to a local file to upload to the machine. The
path can be absolute or relative. If it is relative, it is relative to the
working directory when ?Packer is executed.
* `destination` (string) - The path where the file will be uploaded to in the
machine. This value must be a writable location and any parent directories
must already exist.
......@@ -23,7 +23,7 @@ The example below is fully functional.
## Configuration Reference
The reference of available configuratin options is listed below. The only
The reference of available configuration options is listed below. The only
required element is either "inline" or "script". Every other option is optional.
Exactly _one_ of the following is required:
......
......@@ -36,6 +36,7 @@
<ul>
<li><h4>Provisioners</h4></li>
<li><a href="/docs/provisioners/shell.html">Shell Scripts</a></li>
<li><a href="/docs/provisioners/file.html">File Uploads</a></li>
<li><a href="/docs/provisioners/custom.html">Custom</a></li>
</ul>
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment