From f5850199bad0354ad6b10b3d475fabb92d844f3b Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 9 Sep 2013 13:58:23 -0700 Subject: [PATCH] provisioner/file: support uploading directories [GH-251] --- CHANGELOG.md | 1 + packer/communicator.go | 2 +- provisioner/file/provisioner.go | 11 ++++++ .../docs/provisioners/file.html.markdown | 36 +++++++++++++++++-- 4 files changed, 46 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2274984ce..6cde7c8d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ IMPROVEMENTS: * builder/digitalocean: Retry on any pending event errors. * builder/openstack: Can now specify a project. [GH-382] * builder/virtualbox: Can now attach hard drive over SATA. [GH-391] +* provisioner/file: Can now upload directories. [GH-251] BUG FIXES: diff --git a/packer/communicator.go b/packer/communicator.go index 236d34633..159e5e380 100644 --- a/packer/communicator.go +++ b/packer/communicator.go @@ -67,7 +67,7 @@ type Communicator interface { // is a trailing slash on the source "/". For example: "/tmp/src" as // the source will create a "src" directory in the destination unless // a trailing slash is added. This is identical behavior to rsync(1). - UploadDir(string, string, []string) error + UploadDir(dst string, src string, exclude []string) error // Download downloads a file from the machine from the given remote path // with the contents writing to the given writer. This method will diff --git a/provisioner/file/provisioner.go b/provisioner/file/provisioner.go index 096b10437..c20cbd0f2 100644 --- a/provisioner/file/provisioner.go +++ b/provisioner/file/provisioner.go @@ -72,6 +72,17 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { ui.Say(fmt.Sprintf("Uploading %s => %s", p.config.Source, p.config.Destination)) + info, err := os.Stat(p.config.Source) + if err != nil { + return err + } + + // If we're uploading a directory, short circuit and do that + if info.IsDir() { + return comm.UploadDir(p.config.Destination, p.config.Source, nil) + } + + // We're uploading a file... f, err := os.Open(p.config.Source) if err != nil { return err diff --git a/website/source/docs/provisioners/file.html.markdown b/website/source/docs/provisioners/file.html.markdown index cf2a6081f..4dea52ad8 100644 --- a/website/source/docs/provisioners/file.html.markdown +++ b/website/source/docs/provisioners/file.html.markdown @@ -12,6 +12,8 @@ recommended usage of the file provisioner is to use it to upload files, and then use [shell provisioner](/docs/provisioners/shell.html) to move them to the proper place, set permissions, etc. +The file provisioner can upload both single files and complete directories. + ## Basic Example
@@ -26,10 +28,38 @@ them to the proper place, set permissions, etc.
 
 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.
+* `source` (string) - The path to a local file or directory 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. If this is a
+  directory, the existence of a trailing slash is important. Read below on
+  uploading directories.
 
 * `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.
+
+## Directory Uploads
+
+The file provisioner is also able to upload a complete directory to the
+remote machine. When uploading a directory, there are a few important things
+you should know.
+
+First, the destination directory must already exist. If you need to
+create it, use a shell provisioner just prior to the file provisioner
+in order to create the directory.
+
+Next, the existence of a trailing slash on the source path will determine
+whether the directory name will be embedded within the destination, or
+whether the destination will be created. An example explains this best:
+
+If the source is `/foo` (no trailing slash), and the destination is
+`/tmp`, then the contents of `/foo` on the local machine will be uploaded
+to `/tmp/foo` on the remote machine. The `foo` directory on the remote
+machine will be created by Packer.
+
+If the source, however, is `/foo/` (a trailing slash is present), and
+the destination is `/tmp`, then the contents of `/foo` will be uploaded
+directly into `/tmp` directly.
+
+This behavior was adopted from the standard behavior of rsync. Note that
+under the covers, rsync may or may not be used.