Skip to content

Commit

Permalink
provisioner/ansible: assume scp target is file
Browse files Browse the repository at this point in the history
Assume the scp target is a file instead of a directory. Assuming the scp
target is a file instead of a directory allows uploading files to a node
being provisioned with the ssh communciator using sftp and with the
winrm communicator. It is fully compatible with ansible; ansible
communicators only allow for files to be uploaded (when the copy module
is used to upload a directory, ansible walks the directory and uploads
files one at a time).

Update docuemntation to explain how to provision a Windows image.

Extend tests that use ssh to communicate with the node to include single
files, recursive copies, and content-only recursive copies.

Add test to verify support for the winrm communicator.

Remove the err argument from adapter.scpExec, because it was unused.

Fixes hashicorp#3911
  • Loading branch information
bhcleek committed Nov 26, 2016
1 parent 16d38ed commit 4797d8c
Show file tree
Hide file tree
Showing 17 changed files with 843 additions and 20 deletions.
12 changes: 10 additions & 2 deletions provisioner/ansible/adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"net"
"strings"

"github.com/google/shlex"
"github.com/mitchellh/packer/packer"
"golang.org/x/crypto/ssh"
)
Expand Down Expand Up @@ -189,7 +190,7 @@ func (c *adapter) exec(command string, in io.Reader, out io.Writer, err io.Write
var exitStatus int
switch {
case strings.HasPrefix(command, "scp ") && serveSCP(command[4:]):
err := c.scpExec(command[4:], in, out, err)
err := c.scpExec(command[4:], in, out)
if err != nil {
log.Println(err)
exitStatus = 1
Expand All @@ -205,9 +206,16 @@ func serveSCP(args string) bool {
return bytes.IndexAny(opts, "tf") >= 0
}

func (c *adapter) scpExec(args string, in io.Reader, out io.Writer, err io.Writer) error {
func (c *adapter) scpExec(args string, in io.Reader, out io.Writer) error {
opts, rest := scpOptions(args)

// remove the quoting that ansible added to rest for shell safety.
shargs, err := shlex.Split(rest)
if err != nil {
return err
}
rest = strings.Join(shargs, "")

if i := bytes.IndexByte(opts, 't'); i >= 0 {
return scpUploadSession(opts, rest, in, out, c.comm)
}
Expand Down
29 changes: 20 additions & 9 deletions provisioner/ansible/scp.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,12 @@ func scpUploadSession(opts []byte, rest string, in io.Reader, out io.Writer, com
}
defer os.RemoveAll(d)

state := &scpUploadState{destRoot: rest, srcRoot: d, comm: comm}
// To properly implement scp, rest should be checked to see if it is a
// directory on the remote side, but ansible only sends files, so there's no
// need to set targetIsDir, because it can be safely assumed that rest is
// intended to be a file, and whatever names are used in 'C' commands are
// irrelavant.
state := &scpUploadState{target: rest, srcRoot: d, comm: comm}

fmt.Fprintf(out, scpOK) // signal the client to start the transfer.
return state.Protocol(bufio.NewReader(in), out)
Expand Down Expand Up @@ -117,16 +122,17 @@ func (state *scpDownloadState) FileProtocol(path string, info os.FileInfo, in *b
}

type scpUploadState struct {
comm packer.Communicator
destRoot string // destRoot is the directory on the target
srcRoot string // srcRoot is the directory on the host
mtime time.Time
atime time.Time
dir string // dir is a path relative to the roots
comm packer.Communicator
target string // target is the directory on the target
srcRoot string // srcRoot is the directory on the host
mtime time.Time
atime time.Time
dir string // dir is a path relative to the roots
targetIsDir bool
}

func (scp scpUploadState) DestPath() string {
return filepath.Join(scp.destRoot, scp.dir)
return filepath.Join(scp.target, scp.dir)
}

func (scp scpUploadState) SrcPath() string {
Expand Down Expand Up @@ -177,7 +183,12 @@ func (state *scpUploadState) FileProtocol(in *bufio.Reader, out io.Writer) error

var fi os.FileInfo = fileInfo{name: name, size: size, mode: mode, mtime: state.mtime}

err = state.comm.Upload(filepath.Join(state.DestPath(), fi.Name()), io.LimitReader(in, fi.Size()), &fi)
dest := state.DestPath()
if state.targetIsDir {
dest = filepath.Join(dest, fi.Name())
}

err = state.comm.Upload(dest, io.LimitReader(in, fi.Size()), &fi)
if err != nil {
fmt.Fprintf(out, scpEmptyError)
return err
Expand Down
2 changes: 1 addition & 1 deletion test/fixtures/provisioner-ansible/all_options.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"type": "ansible",
"playbook_file": "./playbook.yml",
"extra_arguments": [
"-vvvv", "--private-key", "ansible-test-id"
"--private-key", "ansible-test-id"
],
"sftp_command": "/usr/lib/sftp-server -e -l INFO",
"use_sftp": true,
Expand Down
16 changes: 16 additions & 0 deletions test/fixtures/provisioner-ansible/connection_plugins/packer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type

from ansible.plugins.connection.ssh import Connection as SSHConnection

class Connection(SSHConnection):
''' ssh based connections for powershell via packer'''

transport = 'packer'
has_pipelining = True
become_methods = []
allow_executable = False
module_implementation_preferences = ('.ps1', '')

def __init__(self, *args, **kwargs):
super(Connection, self).__init__(*args, **kwargs)
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
this file's parent directory should not be transferred to the node.
1 change: 1 addition & 0 deletions test/fixtures/provisioner-ansible/dir/subdir/file.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This file and its parent directory should be transferred to the node.
26 changes: 20 additions & 6 deletions test/fixtures/provisioner-ansible/playbook.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,26 @@
- hosts: default:packer-test
gather_facts: no
tasks:
- raw: touch /root/ansible-raw-test
- raw: date
- command: echo "the command module"
- command: mkdir /tmp/remote-dir
- name: touch /root/ansible-raw-test
raw: touch /root/ansible-raw-test
- name: raw test
raw: date
- name: command test
command: echo "the command module"
- name: prepare remote directory
command: mkdir /tmp/remote-dir
args:
creates: /tmp/remote-dir
- copy: src=dir/file.txt dest=/tmp/remote-dir/file.txt
- fetch: src=/tmp/remote-dir/file.txt dest=fetched-dir validate=yes fail_on_missing=yes
- name: transfer file.txt
copy: src=dir/file.txt dest=/tmp/remote-dir/file.txt
- name: fetch file.text
fetch: src=/tmp/remote-dir/file.txt dest=fetched-dir validate=yes fail_on_missing=yes
- name: copy contents of directory
copy: src=dir/contents-only/ dest=/tmp/remote-dir
- name: fetch contents of directory
fetch: src=/tmp/remote-dir/file.txt dest="fetched-dir/{{ inventory_hostname }}/tmp/remote-dir/contents-only/" flat=yes validate=yes fail_on_missing=yes
- name: copy directory recursively
copy: src=dir/subdir dest=/tmp/remote-dir
- name: fetch recursively copied directory
fetch: src=/tmp/remote-dir/subdir/file.txt dest=fetched-dir validate=yes fail_on_missing=yes
- copy: src=largish-file.txt dest=/tmp/largish-file.txt
24 changes: 24 additions & 0 deletions test/fixtures/provisioner-ansible/scp-to-sftp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"variables": {},
"provisioners": [
{
"type": "ansible",
"playbook_file": "./playbook.yml",
"extra_arguments": [
],
"sftp_command": "/usr/bin/false",
"use_sftp": false
}
],
"builders": [
{
"type": "googlecompute",
"account_file": "{{user `account_file`}}",
"project_id": "{{user `project_id`}}",
"image_name": "packerbats-scp-to-sftp-{{timestamp}}",
"source_image": "debian-7-wheezy-v20141108",
"zone": "us-central1-a",
"ssh_file_transfer_method": "sftp"
}
]
}
4 changes: 2 additions & 2 deletions test/fixtures/provisioner-ansible/scp.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
"type": "ansible",
"playbook_file": "./playbook.yml",
"extra_arguments": [
"-vvvv"
],
"sftp_command": "/usr/bin/false"
"sftp_command": "/usr/bin/false",
"use_sftp": false
}
],
"builders": [
Expand Down
17 changes: 17 additions & 0 deletions test/fixtures/provisioner-ansible/win-playbook.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
- hosts: default:packer-test
gather_facts: no
tasks:
#- debug: msg="testing regular modules that function with Windows: raw, fetch, slurp, setup"
- name: raw test
raw: date /t
- debug: msg="testing windows modules"
#- win_file: path=tmp/remote-dir state=directory
#- name: win_shell test
#win_shell: date /t
- name: win_copy test
win_copy: src=dir/file.txt dest=file.txt
#- win_copy: src=dir/file.txt dest=/tmp/remote-dir/file.txt
#- fetch: src=/tmp/remote-dir/file.txt dest=fetched-dir validate=yes fail_on_missing=yes
#- win_copy: src=largish-file.txt dest=/tmp/largish-file.txt
- debug: msg="packer does not support downloading from windows"
31 changes: 31 additions & 0 deletions test/fixtures/provisioner-ansible/winrm.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"variables": {},
"provisioners": [
{
"type": "ansible",
"playbook_file": "./win-playbook.yml",
"extra_arguments": [
"--connection", "packer",
"--extra-vars", "ansible_shell_type=powershell ansible_shell_executable=None"
]
}
],
"builders": [
{
"type": "googlecompute",
"account_file": "{{user `account_file`}}",
"project_id": "{{user `project_id`}}",
"image_name": "packerbats-winrm-{{timestamp}}",
"source_image": "windows-server-2012-r2-dc-v20160916",
"communicator": "winrm",
"zone": "us-central1-a",
"disk_size": 50,
"winrm_username": "packer",
"winrm_use_ssl": true,
"winrm_insecure": true,
"metadata": {
"sysprep-specialize-script-cmd": "winrm set winrm/config/service/auth @{Basic=\"true\"}"
}
}
]
}
16 changes: 16 additions & 0 deletions test/provisioner_ansible.bats
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,14 @@ teardown() {
diff -r dir fetched-dir/default/tmp/remote-dir > /dev/null
}

@test "ansible provisioner: build scp-to-sftp.json" {
cd $FIXTURE_ROOT
run packer build ${USER_VARS} $FIXTURE_ROOT/scp-to-sftp.json
[ "$status" -eq 0 ]
[ "$(gc_has_image "packerbats-scp-to-sftp")" -eq 1 ]
diff -r dir fetched-dir/default/tmp/remote-dir > /dev/null
}

@test "ansible provisioner: build sftp.json" {
cd $FIXTURE_ROOT
run packer build ${USER_VARS} $FIXTURE_ROOT/sftp.json
Expand All @@ -75,3 +83,11 @@ teardown() {
diff -r dir fetched-dir/default/tmp/remote-dir > /dev/null
}

@test "ansible provisioner: build winrm.json" {
cd $FIXTURE_ROOT
run packer build ${USER_VARS} $FIXTURE_ROOT/winrm.json
[ "$status" -eq 0 ]
[ "$(gc_has_image "packerbats-winrm")" -eq 1 ]
echo "packer does not support downloading files from download, skipping verification"
#diff -r dir fetched-dir/default/tmp/remote-dir > /dev/null
}
Loading

0 comments on commit 4797d8c

Please sign in to comment.