Merge pull request #136 from ericlathrop/file-provisioner
File upload provisioner [GH-118]
This commit is contained in:
commit
6d6d8795df
|
@ -35,6 +35,7 @@ const defaultConfig = `
|
||||||
},
|
},
|
||||||
|
|
||||||
"provisioners": {
|
"provisioners": {
|
||||||
|
"file": "packer-provisioner-file",
|
||||||
"shell": "packer-provisioner-shell"
|
"shell": "packer-provisioner-shell"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/mitchellh/packer/packer/plugin"
|
||||||
|
"github.com/mitchellh/packer/provisioner/file"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
plugin.ServeProvisioner(new(file.Provisioner))
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
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)
|
||||||
|
}
|
|
@ -0,0 +1,128 @@
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
---
|
||||||
|
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
|
## 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.
|
required element is either "inline" or "script". Every other option is optional.
|
||||||
|
|
||||||
Exactly _one_ of the following is required:
|
Exactly _one_ of the following is required:
|
||||||
|
|
|
@ -36,6 +36,7 @@
|
||||||
<ul>
|
<ul>
|
||||||
<li><h4>Provisioners</h4></li>
|
<li><h4>Provisioners</h4></li>
|
||||||
<li><a href="/docs/provisioners/shell.html">Shell Scripts</a></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>
|
<li><a href="/docs/provisioners/custom.html">Custom</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue