forked from hashicorp/packer
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request hashicorp#2283 from mitchellh/f-local-shell
Local shell provisioner
- Loading branch information
Showing
7 changed files
with
364 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package main | ||
|
||
import ( | ||
"github.com/mitchellh/packer/packer/plugin" | ||
"github.com/mitchellh/packer/provisioner/shell-local" | ||
) | ||
|
||
func main() { | ||
server, err := plugin.Server() | ||
if err != nil { | ||
panic(err) | ||
} | ||
server.RegisterProvisioner(new(shell.Provisioner)) | ||
server.Serve() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
package shell | ||
|
||
import ( | ||
"fmt" | ||
"io" | ||
"os" | ||
"os/exec" | ||
"syscall" | ||
|
||
"github.com/mitchellh/packer/packer" | ||
"github.com/mitchellh/packer/template/interpolate" | ||
) | ||
|
||
type Communicator struct { | ||
ExecuteCommand []string | ||
Ctx interpolate.Context | ||
} | ||
|
||
func (c *Communicator) Start(cmd *packer.RemoteCmd) error { | ||
// Render the template so that we know how to execute the command | ||
c.Ctx.Data = &ExecuteCommandTemplate{ | ||
Command: cmd.Command, | ||
} | ||
for i, field := range c.ExecuteCommand { | ||
command, err := interpolate.Render(field, &c.Ctx) | ||
if err != nil { | ||
return fmt.Errorf("Error processing command: %s", err) | ||
} | ||
|
||
c.ExecuteCommand[i] = command | ||
} | ||
|
||
// Build the local command to execute | ||
localCmd := exec.Command(c.ExecuteCommand[0], c.ExecuteCommand[1:]...) | ||
localCmd.Stdin = cmd.Stdin | ||
localCmd.Stdout = cmd.Stdout | ||
localCmd.Stderr = cmd.Stderr | ||
|
||
// Start it. If it doesn't work, then error right away. | ||
if err := localCmd.Start(); err != nil { | ||
return err | ||
} | ||
|
||
// We've started successfully. Start a goroutine to wait for | ||
// it to complete and track exit status. | ||
go func() { | ||
var exitStatus int | ||
err := localCmd.Wait() | ||
if err != nil { | ||
if exitErr, ok := err.(*exec.ExitError); ok { | ||
exitStatus = 1 | ||
|
||
// There is no process-independent way to get the REAL | ||
// exit status so we just try to go deeper. | ||
if status, ok := exitErr.Sys().(syscall.WaitStatus); ok { | ||
exitStatus = status.ExitStatus() | ||
} | ||
} | ||
} | ||
|
||
cmd.SetExited(exitStatus) | ||
}() | ||
|
||
return nil | ||
} | ||
|
||
func (c *Communicator) Upload(string, io.Reader, *os.FileInfo) error { | ||
return fmt.Errorf("upload not supported") | ||
} | ||
|
||
func (c *Communicator) UploadDir(string, string, []string) error { | ||
return fmt.Errorf("uploadDir not supported") | ||
} | ||
|
||
func (c *Communicator) Download(string, io.Writer) error { | ||
return fmt.Errorf("download not supported") | ||
} | ||
|
||
type ExecuteCommandTemplate struct { | ||
Command string | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package shell | ||
|
||
import ( | ||
"bytes" | ||
"runtime" | ||
"strings" | ||
"testing" | ||
|
||
"github.com/mitchellh/packer/packer" | ||
) | ||
|
||
func TestCommunicator_impl(t *testing.T) { | ||
var _ packer.Communicator = new(Communicator) | ||
} | ||
|
||
func TestCommunicator(t *testing.T) { | ||
if runtime.GOOS == "windows" { | ||
t.Skip("windows not supported for this test") | ||
return | ||
} | ||
|
||
c := &Communicator{ | ||
ExecuteCommand: []string{"/bin/sh", "-c", "{{.Command}}"}, | ||
} | ||
|
||
var buf bytes.Buffer | ||
cmd := &packer.RemoteCmd{ | ||
Command: "echo foo", | ||
Stdout: &buf, | ||
} | ||
|
||
if err := c.Start(cmd); err != nil { | ||
t.Fatalf("err: %s", err) | ||
} | ||
|
||
cmd.Wait() | ||
|
||
if cmd.ExitStatus != 0 { | ||
t.Fatalf("err bad exit status: %d", cmd.ExitStatus) | ||
} | ||
|
||
if strings.TrimSpace(buf.String()) != "foo" { | ||
t.Fatalf("bad: %s", buf.String()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
package shell | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"runtime" | ||
|
||
"github.com/mitchellh/packer/common" | ||
"github.com/mitchellh/packer/helper/config" | ||
"github.com/mitchellh/packer/packer" | ||
"github.com/mitchellh/packer/template/interpolate" | ||
) | ||
|
||
type Config struct { | ||
common.PackerConfig `mapstructure:",squash"` | ||
|
||
// Command is the command to execute | ||
Command string | ||
|
||
// ExecuteCommand is the command used to execute the command. | ||
ExecuteCommand []string `mapstructure:"execute_command"` | ||
|
||
ctx interpolate.Context | ||
} | ||
|
||
type Provisioner struct { | ||
config Config | ||
} | ||
|
||
func (p *Provisioner) Prepare(raws ...interface{}) error { | ||
err := config.Decode(&p.config, &config.DecodeOpts{ | ||
Interpolate: true, | ||
InterpolateFilter: &interpolate.RenderFilter{ | ||
Exclude: []string{ | ||
"execute_command", | ||
}, | ||
}, | ||
}, raws...) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if len(p.config.ExecuteCommand) == 0 { | ||
if runtime.GOOS == "windows" { | ||
p.config.ExecuteCommand = []string{ | ||
"cmd", | ||
"/C", | ||
"{{.Command}}", | ||
} | ||
} else { | ||
p.config.ExecuteCommand = []string{ | ||
"/bin/sh", | ||
"-c", | ||
"{{.Command}}", | ||
} | ||
} | ||
} | ||
|
||
var errs *packer.MultiError | ||
if p.config.Command == "" { | ||
errs = packer.MultiErrorAppend(errs, | ||
errors.New("command must be specified")) | ||
} | ||
|
||
if len(p.config.ExecuteCommand) == 0 { | ||
errs = packer.MultiErrorAppend(errs, | ||
errors.New("execute_command must not be empty")) | ||
} | ||
|
||
if errs != nil && len(errs.Errors) > 0 { | ||
return errs | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (p *Provisioner) Provision(ui packer.Ui, _ packer.Communicator) error { | ||
// Make another communicator for local | ||
comm := &Communicator{ | ||
Ctx: p.config.ctx, | ||
ExecuteCommand: p.config.ExecuteCommand, | ||
} | ||
|
||
// Build the remote command | ||
cmd := &packer.RemoteCmd{Command: p.config.Command} | ||
|
||
ui.Say(fmt.Sprintf( | ||
"Executing local command: %s", | ||
p.config.Command)) | ||
if err := cmd.StartWithUi(comm, ui); err != nil { | ||
return fmt.Errorf( | ||
"Error executing command: %s\n\n"+ | ||
"Please see output above for more information.", | ||
p.config.Command) | ||
} | ||
if cmd.ExitStatus != 0 { | ||
return fmt.Errorf( | ||
"Erroneous exit code %s while executing command: %s\n\n"+ | ||
"Please see output above for more information.", | ||
cmd.ExitStatus, | ||
p.config.Command) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (p *Provisioner) Cancel() { | ||
// Just do nothing. When the process ends, so will our provisioner | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
package shell | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/mitchellh/packer/packer" | ||
) | ||
|
||
func TestProvisioner_impl(t *testing.T) { | ||
var _ packer.Provisioner = new(Provisioner) | ||
} | ||
|
||
func TestConfigPrepare(t *testing.T) { | ||
cases := []struct { | ||
Key string | ||
Value interface{} | ||
Err bool | ||
}{ | ||
{ | ||
"unknown_key", | ||
"bad", | ||
true, | ||
}, | ||
|
||
{ | ||
"command", | ||
nil, | ||
true, | ||
}, | ||
} | ||
|
||
for _, tc := range cases { | ||
raw := testConfig(t) | ||
|
||
if tc.Value == nil { | ||
delete(raw, tc.Key) | ||
} else { | ||
raw[tc.Key] = tc.Value | ||
} | ||
|
||
var p Provisioner | ||
err := p.Prepare(raw) | ||
if tc.Err { | ||
testConfigErr(t, err, tc.Key) | ||
} else { | ||
testConfigOk(t, err) | ||
} | ||
} | ||
} | ||
|
||
func testConfig(t *testing.T) map[string]interface{} { | ||
return map[string]interface{}{ | ||
"command": "echo foo", | ||
} | ||
} | ||
|
||
func testConfigErr(t *testing.T, err error, extra string) { | ||
if err == nil { | ||
t.Fatalf("should error: %s", extra) | ||
} | ||
} | ||
|
||
func testConfigOk(t *testing.T, err error) { | ||
if err != nil { | ||
t.Fatalf("bad: %s", err) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
--- | ||
layout: "docs" | ||
page_title: "Local Shell Provisioner" | ||
description: |- | ||
The shell Packer provisioner provisions machines built by Packer using shell scripts. Shell provisioning is the easiest way to get software installed and configured on a machine. | ||
--- | ||
|
||
# Local Shell Provisioner | ||
|
||
Type: `shell-local` | ||
|
||
The local shell provisioner executes a local shell script on the machine | ||
running Packer. The [remote shell](/docs/provisioners/shell.html) | ||
provisioner executes shell scripts on a remote machine. | ||
|
||
## Basic Example | ||
|
||
The example below is fully functional. | ||
|
||
```javascript | ||
{ | ||
"type": "shell-local", | ||
"command": "echo foo" | ||
} | ||
``` | ||
|
||
## Configuration Reference | ||
|
||
The reference of available configuration options is listed below. The only | ||
required element is "command". | ||
|
||
Required: | ||
|
||
* `command` (string) - The command to execute. This will be executed | ||
within the context of a shell as specified by `execute_command`. | ||
|
||
Optional parameters: | ||
|
||
* `execute_command` (array of strings) - The command to use to execute the script. | ||
By default this is `["/bin/sh", "-c", "{{.Command}"]`. The value is an array | ||
of arguments executed directly by the OS. | ||
The value of this is | ||
treated as [configuration template](/docs/templates/configuration-templates.html). | ||
The only available variable is `Command` which is the command to execute. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters