Commit 1f4e633e authored by Mitchell Hashimoto's avatar Mitchell Hashimoto

Merge branch 'machine-readable'

This adds the -machine-readable flag to Packer which turns all output
into machine-readable format. This is documented within the website
source.
parents 7004813a d9bfe58c
...@@ -9,7 +9,7 @@ import ( ...@@ -9,7 +9,7 @@ import (
func testEnvironment() packer.Environment { func testEnvironment() packer.Environment {
config := packer.DefaultEnvironmentConfig() config := packer.DefaultEnvironmentConfig()
config.Ui = &packer.ReaderWriterUi{ config.Ui = &packer.BasicUi{
Reader: new(bytes.Buffer), Reader: new(bytes.Buffer),
Writer: new(bytes.Buffer), Writer: new(bytes.Buffer),
} }
......
...@@ -10,6 +10,7 @@ Options: ...@@ -10,6 +10,7 @@ Options:
-debug Debug mode enabled for builds -debug Debug mode enabled for builds
-force Force a build to continue if artifacts exist, deletes existing artifacts -force Force a build to continue if artifacts exist, deletes existing artifacts
-machine-readable Machine-readable output
-except=foo,bar,baz Build all builds other than these -except=foo,bar,baz Build all builds other than these
-only=foo,bar,baz Only build the given builds by name -only=foo,bar,baz Only build the given builds by name
-var 'key=value' Variable for templates, can be used multiple times. -var 'key=value' Variable for templates, can be used multiple times.
......
...@@ -76,8 +76,13 @@ func main() { ...@@ -76,8 +76,13 @@ func main() {
log.Printf("Setting cache directory: %s", cacheDir) log.Printf("Setting cache directory: %s", cacheDir)
cache := &packer.FileCache{CacheDir: cacheDir} cache := &packer.FileCache{CacheDir: cacheDir}
// Determine if we're in machine-readable mode by mucking around with
// the arguments...
args, machineReadable := extractMachineReadable(os.Args[1:])
defer plugin.CleanupClients() defer plugin.CleanupClients()
// Create the environment configuration
envConfig := packer.DefaultEnvironmentConfig() envConfig := packer.DefaultEnvironmentConfig()
envConfig.Cache = cache envConfig.Cache = cache
envConfig.Commands = config.CommandNames() envConfig.Commands = config.CommandNames()
...@@ -86,6 +91,11 @@ func main() { ...@@ -86,6 +91,11 @@ func main() {
envConfig.Components.Hook = config.LoadHook envConfig.Components.Hook = config.LoadHook
envConfig.Components.PostProcessor = config.LoadPostProcessor envConfig.Components.PostProcessor = config.LoadPostProcessor
envConfig.Components.Provisioner = config.LoadProvisioner envConfig.Components.Provisioner = config.LoadProvisioner
if machineReadable {
envConfig.Ui = &packer.MachineReadableUi{
Writer: os.Stdout,
}
}
env, err := packer.NewEnvironment(envConfig) env, err := packer.NewEnvironment(envConfig)
if err != nil { if err != nil {
...@@ -96,7 +106,7 @@ func main() { ...@@ -96,7 +106,7 @@ func main() {
setupSignalHandlers(env) setupSignalHandlers(env)
exitCode, err := env.Cli(os.Args[1:]) exitCode, err := env.Cli(args)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error executing CLI: %s\n", err.Error()) fmt.Fprintf(os.Stderr, "Error executing CLI: %s\n", err.Error())
plugin.CleanupClients() plugin.CleanupClients()
...@@ -107,6 +117,23 @@ func main() { ...@@ -107,6 +117,23 @@ func main() {
os.Exit(exitCode) os.Exit(exitCode)
} }
// extractMachineReadable checks the args for the machine readable
// flag and returns whether or not it is on. It modifies the args
// to remove this flag.
func extractMachineReadable(args []string) ([]string, bool) {
for i, arg := range args {
if arg == "-machine-readable" {
// We found it. Slice it out.
result := make([]string, len(args)-1)
copy(result, args[:i])
copy(result[i:], args[i+1:])
return result, true
}
}
return args, false
}
func loadConfig() (*config, error) { func loadConfig() (*config, error) {
var config config var config config
if err := decodeConfig(bytes.NewBufferString(defaultConfig), &config); err != nil { if err := decodeConfig(bytes.NewBufferString(defaultConfig), &config); err != nil {
......
...@@ -213,11 +213,10 @@ func (b *coreBuild) Run(originalUi Ui, cache Cache) ([]Artifact, error) { ...@@ -213,11 +213,10 @@ func (b *coreBuild) Run(originalUi Ui, cache Cache) ([]Artifact, error) {
hook := &DispatchHook{hooks} hook := &DispatchHook{hooks}
artifacts := make([]Artifact, 0, 1) artifacts := make([]Artifact, 0, 1)
// The builder just has a normal Ui, but prefixed // The builder just has a normal Ui, but targetted
builderUi := &PrefixedUi{ builderUi := &TargettedUi{
fmt.Sprintf("==> %s", b.Name()), Target: b.Name(),
fmt.Sprintf(" %s", b.Name()), Ui: originalUi,
originalUi,
} }
log.Printf("Running builder: %s", b.builderType) log.Printf("Running builder: %s", b.builderType)
...@@ -240,10 +239,9 @@ PostProcessorRunSeqLoop: ...@@ -240,10 +239,9 @@ PostProcessorRunSeqLoop:
for _, ppSeq := range b.postProcessors { for _, ppSeq := range b.postProcessors {
priorArtifact := builderArtifact priorArtifact := builderArtifact
for i, corePP := range ppSeq { for i, corePP := range ppSeq {
ppUi := &PrefixedUi{ ppUi := &TargettedUi{
fmt.Sprintf("==> %s (%s)", b.Name(), corePP.processorType), Target: fmt.Sprintf("%s (%s)", b.Name(), corePP.processorType),
fmt.Sprintf(" %s (%s)", b.Name(), corePP.processorType), Ui: originalUi,
originalUi,
} }
builderUi.Say(fmt.Sprintf("Running post-processor: %s", corePP.processorType)) builderUi.Say(fmt.Sprintf("Running post-processor: %s", corePP.processorType))
......
...@@ -47,7 +47,7 @@ func TestRemoteCmd_StartWithUi(t *testing.T) { ...@@ -47,7 +47,7 @@ func TestRemoteCmd_StartWithUi(t *testing.T) {
Stdout: rcOutput, Stdout: rcOutput,
} }
testUi := &ReaderWriterUi{ testUi := &BasicUi{
Reader: new(bytes.Buffer), Reader: new(bytes.Buffer),
Writer: uiOutput, Writer: uiOutput,
} }
......
...@@ -73,7 +73,7 @@ type EnvironmentConfig struct { ...@@ -73,7 +73,7 @@ type EnvironmentConfig struct {
func DefaultEnvironmentConfig() *EnvironmentConfig { func DefaultEnvironmentConfig() *EnvironmentConfig {
config := &EnvironmentConfig{} config := &EnvironmentConfig{}
config.Commands = make([]string, 0) config.Commands = make([]string, 0)
config.Ui = &ReaderWriterUi{ config.Ui = &BasicUi{
Reader: os.Stdin, Reader: os.Stdin,
Writer: os.Stdout, Writer: os.Stdout,
} }
...@@ -299,6 +299,9 @@ func (e *coreEnvironment) printHelp() { ...@@ -299,6 +299,9 @@ func (e *coreEnvironment) printHelp() {
// Output the command and the synopsis // Output the command and the synopsis
e.ui.Say(fmt.Sprintf(" %v %v", key, synopsis)) e.ui.Say(fmt.Sprintf(" %v %v", key, synopsis))
} }
e.ui.Say("\nGlobally recognized options:")
e.ui.Say(" -machine-readable Machine-readable output format.")
} }
// Returns the UI for the environment. The UI is the interface that should // Returns the UI for the environment. The UI is the interface that should
......
...@@ -19,7 +19,7 @@ func init() { ...@@ -19,7 +19,7 @@ func init() {
func testEnvironment() Environment { func testEnvironment() Environment {
config := DefaultEnvironmentConfig() config := DefaultEnvironmentConfig()
config.Ui = &ReaderWriterUi{ config.Ui = &BasicUi{
Reader: new(bytes.Buffer), Reader: new(bytes.Buffer),
Writer: new(bytes.Buffer), Writer: new(bytes.Buffer),
} }
...@@ -45,8 +45,8 @@ func TestEnvironment_DefaultConfig_Ui(t *testing.T) { ...@@ -45,8 +45,8 @@ func TestEnvironment_DefaultConfig_Ui(t *testing.T) {
config := DefaultEnvironmentConfig() config := DefaultEnvironmentConfig()
assert.NotNil(config.Ui, "default UI should not be nil") assert.NotNil(config.Ui, "default UI should not be nil")
rwUi, ok := config.Ui.(*ReaderWriterUi) rwUi, ok := config.Ui.(*BasicUi)
assert.True(ok, "default UI should be ReaderWriterUi") assert.True(ok, "default UI should be BasicUi")
assert.Equal(rwUi.Writer, os.Stdout, "default UI should go to stdout") assert.Equal(rwUi.Writer, os.Stdout, "default UI should go to stdout")
assert.Equal(rwUi.Reader, os.Stdin, "default UI should read from stdin") assert.Equal(rwUi.Reader, os.Stdin, "default UI should read from stdin")
} }
...@@ -175,7 +175,7 @@ func TestEnvironment_DefaultCli_Help(t *testing.T) { ...@@ -175,7 +175,7 @@ func TestEnvironment_DefaultCli_Help(t *testing.T) {
// A little lambda to help us test the output actually contains help // A little lambda to help us test the output actually contains help
testOutput := func() { testOutput := func() {
buffer := defaultEnv.Ui().(*ReaderWriterUi).Writer.(*bytes.Buffer) buffer := defaultEnv.Ui().(*BasicUi).Writer.(*bytes.Buffer)
output := buffer.String() output := buffer.String()
buffer.Reset() buffer.Reset()
assert.True(strings.Contains(output, "usage: packer"), "should print help") assert.True(strings.Contains(output, "usage: packer"), "should print help")
...@@ -341,7 +341,7 @@ func TestEnvironmentProvisioner_Error(t *testing.T) { ...@@ -341,7 +341,7 @@ func TestEnvironmentProvisioner_Error(t *testing.T) {
func TestEnvironment_SettingUi(t *testing.T) { func TestEnvironment_SettingUi(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true) assert := asserts.NewTestingAsserts(t, true)
ui := &ReaderWriterUi{ ui := &BasicUi{
Reader: new(bytes.Buffer), Reader: new(bytes.Buffer),
Writer: new(bytes.Buffer), Writer: new(bytes.Buffer),
} }
......
...@@ -17,6 +17,12 @@ type UiServer struct { ...@@ -17,6 +17,12 @@ type UiServer struct {
ui packer.Ui ui packer.Ui
} }
// The arguments sent to Ui.Machine
type UiMachineArgs struct {
category string
args []string
}
func (u *Ui) Ask(query string) (result string, err error) { func (u *Ui) Ask(query string) (result string, err error) {
err = u.client.Call("Ui.Ask", query, &result) err = u.client.Call("Ui.Ask", query, &result)
return return
...@@ -28,6 +34,17 @@ func (u *Ui) Error(message string) { ...@@ -28,6 +34,17 @@ func (u *Ui) Error(message string) {
} }
} }
func (u *Ui) Machine(t string, args ...string) {
rpcArgs := &UiMachineArgs{
category: t,
args: args,
}
if err := u.client.Call("Ui.Message", rpcArgs, new(interface{})); err != nil {
panic(err)
}
}
func (u *Ui) Message(message string) { func (u *Ui) Message(message string) {
if err := u.client.Call("Ui.Message", message, new(interface{})); err != nil { if err := u.client.Call("Ui.Message", message, new(interface{})); err != nil {
panic(err) panic(err)
...@@ -52,6 +69,13 @@ func (u *UiServer) Error(message *string, reply *interface{}) error { ...@@ -52,6 +69,13 @@ func (u *UiServer) Error(message *string, reply *interface{}) error {
return nil return nil
} }
func (u *UiServer) Machine(args *UiMachineArgs, reply *interface{}) error {
u.ui.Machine(args.category, args.args...)
*reply = nil
return nil
}
func (u *UiServer) Message(message *string, reply *interface{}) error { func (u *UiServer) Message(message *string, reply *interface{}) error {
u.ui.Message(*message) u.ui.Message(*message)
*reply = nil *reply = nil
......
...@@ -11,6 +11,9 @@ type testUi struct { ...@@ -11,6 +11,9 @@ type testUi struct {
askQuery string askQuery string
errorCalled bool errorCalled bool
errorMessage string errorMessage string
machineCalled bool
machineType string
machineArgs []string
messageCalled bool messageCalled bool
messageMessage string messageMessage string
sayCalled bool sayCalled bool
...@@ -28,6 +31,12 @@ func (u *testUi) Error(message string) { ...@@ -28,6 +31,12 @@ func (u *testUi) Error(message string) {
u.errorMessage = message u.errorMessage = message
} }
func (u *testUi) Machine(t string, args ...string) {
u.machineCalled = true
u.machineType = t
u.machineArgs = args
}
func (u *testUi) Message(message string) { func (u *testUi) Message(message string) {
u.messageCalled = true u.messageCalled = true
u.messageMessage = message u.messageMessage = message
......
...@@ -11,6 +11,7 @@ import ( ...@@ -11,6 +11,7 @@ import (
"runtime" "runtime"
"strings" "strings"
"sync" "sync"
"time"
"unicode" "unicode"
) )
...@@ -33,6 +34,7 @@ type Ui interface { ...@@ -33,6 +34,7 @@ type Ui interface {
Say(string) Say(string)
Message(string) Message(string)
Error(string) Error(string)
Machine(string, ...string)
} }
// ColoredUi is a UI that is colored using terminal colors. // ColoredUi is a UI that is colored using terminal colors.
...@@ -42,23 +44,32 @@ type ColoredUi struct { ...@@ -42,23 +44,32 @@ type ColoredUi struct {
Ui Ui Ui Ui
} }
// PrefixedUi is a UI that wraps another UI implementation and adds a // TargettedUi is a UI that wraps another UI implementation and modifies
// prefix to all the messages going out. // the output to indicate a specific target. Specifically, all Say output
type PrefixedUi struct { // is prefixed with the target name. Message output is not prefixed but
SayPrefix string // is offset by the length of the target so that output is lined up properly
MessagePrefix string // with Say output. Machine-readable output has the proper target set.
Ui Ui type TargettedUi struct {
Target string
Ui Ui
} }
// The ReaderWriterUi is a UI that writes and reads from standard Go // The BasicUI is a UI that reads and writes from a standard Go reader
// io.Reader and io.Writer. // and writer. It is safe to be called from multiple goroutines. Machine
type ReaderWriterUi struct { // readable output is simply logged for this UI.
type BasicUi struct {
Reader io.Reader Reader io.Reader
Writer io.Writer Writer io.Writer
l sync.Mutex l sync.Mutex
interrupted bool interrupted bool
} }
// MachineReadableUi is a UI that only outputs machine-readable output
// to the given Writer.
type MachineReadableUi struct {
Writer io.Writer
}
func (u *ColoredUi) Ask(query string) (string, error) { func (u *ColoredUi) Ask(query string) (string, error) {
return u.Ui.Ask(u.colorize(query, u.Color, true)) return u.Ui.Ask(u.colorize(query, u.Color, true))
} }
...@@ -80,6 +91,11 @@ func (u *ColoredUi) Error(message string) { ...@@ -80,6 +91,11 @@ func (u *ColoredUi) Error(message string) {
u.Ui.Error(u.colorize(message, color, true)) u.Ui.Error(u.colorize(message, color, true))
} }
func (u *ColoredUi) Machine(t string, args ...string) {
// Don't colorize machine-readable output
u.Ui.Machine(t, args...)
}
func (u *ColoredUi) colorize(message string, color UiColor, bold bool) string { func (u *ColoredUi) colorize(message string, color UiColor, bold bool) string {
if !u.supportsColors() { if !u.supportsColors() {
return message return message
...@@ -107,33 +123,43 @@ func (u *ColoredUi) supportsColors() bool { ...@@ -107,33 +123,43 @@ func (u *ColoredUi) supportsColors() bool {
return cygwin return cygwin
} }
func (u *PrefixedUi) Ask(query string) (string, error) { func (u *TargettedUi) Ask(query string) (string, error) {
return u.Ui.Ask(u.prefixLines(u.SayPrefix, query)) return u.Ui.Ask(u.prefixLines(true, query))
} }
func (u *PrefixedUi) Say(message string) { func (u *TargettedUi) Say(message string) {
u.Ui.Say(u.prefixLines(u.SayPrefix, message)) u.Ui.Say(u.prefixLines(true, message))
} }
func (u *PrefixedUi) Message(message string) { func (u *TargettedUi) Message(message string) {
u.Ui.Message(u.prefixLines(u.MessagePrefix, message)) u.Ui.Message(u.prefixLines(false, message))
} }
func (u *PrefixedUi) Error(message string) { func (u *TargettedUi) Error(message string) {
u.Ui.Error(u.prefixLines(u.SayPrefix, message)) u.Ui.Error(u.prefixLines(true, message))
} }
func (u *PrefixedUi) prefixLines(prefix, message string) string { func (u *TargettedUi) Machine(t string, args ...string) {
// Prefix in the target, then pass through
u.Ui.Machine(fmt.Sprintf("%s,%s", u.Target, t), args...)
}
func (u *TargettedUi) prefixLines(arrow bool, message string) string {
arrowText := "==>"
if !arrow {
arrowText = strings.Repeat(" ", len(arrowText))
}
var result bytes.Buffer var result bytes.Buffer
for _, line := range strings.Split(message, "\n") { for _, line := range strings.Split(message, "\n") {
result.WriteString(fmt.Sprintf("%s: %s\n", prefix, line)) result.WriteString(fmt.Sprintf("%s %s: %s\n", arrowText, u.Target, line))
} }
return strings.TrimRightFunc(result.String(), unicode.IsSpace) return strings.TrimRightFunc(result.String(), unicode.IsSpace)
} }
func (rw *ReaderWriterUi) Ask(query string) (string, error) { func (rw *BasicUi) Ask(query string) (string, error) {
rw.l.Lock() rw.l.Lock()
defer rw.l.Unlock() defer rw.l.Unlock()
...@@ -177,7 +203,7 @@ func (rw *ReaderWriterUi) Ask(query string) (string, error) { ...@@ -177,7 +203,7 @@ func (rw *ReaderWriterUi) Ask(query string) (string, error) {
} }
} }
func (rw *ReaderWriterUi) Say(message string) { func (rw *BasicUi) Say(message string) {
rw.l.Lock() rw.l.Lock()
defer rw.l.Unlock() defer rw.l.Unlock()
...@@ -188,7 +214,7 @@ func (rw *ReaderWriterUi) Say(message string) { ...@@ -188,7 +214,7 @@ func (rw *ReaderWriterUi) Say(message string) {
} }
} }
func (rw *ReaderWriterUi) Message(message string) { func (rw *BasicUi) Message(message string) {
rw.l.Lock() rw.l.Lock()
defer rw.l.Unlock() defer rw.l.Unlock()
...@@ -199,7 +225,7 @@ func (rw *ReaderWriterUi) Message(message string) { ...@@ -199,7 +225,7 @@ func (rw *ReaderWriterUi) Message(message string) {
} }
} }
func (rw *ReaderWriterUi) Error(message string) { func (rw *BasicUi) Error(message string) {
rw.l.Lock() rw.l.Lock()
defer rw.l.Unlock() defer rw.l.Unlock()
...@@ -209,3 +235,48 @@ func (rw *ReaderWriterUi) Error(message string) { ...@@ -209,3 +235,48 @@ func (rw *ReaderWriterUi) Error(message string) {
panic(err) panic(err)
} }
} }
func (rw *BasicUi) Machine(t string, args ...string) {
log.Printf("machine readable: %s %#v", t, args)
}
func (u *MachineReadableUi) Ask(query string) (string, error) {
return "", errors.New("machine-readable UI can't ask")
}
func (u *MachineReadableUi) Say(message string) {
u.Machine("ui", "say", message)
}
func (u *MachineReadableUi) Message(message string) {
u.Machine("ui", "message", message)
}
func (u *MachineReadableUi) Error(message string) {
u.Machine("ui", "error", message)
}
func (u *MachineReadableUi) Machine(category string, args ...string) {
now := time.Now().UTC()
// Determine if we have a target, and set it
target := ""
commaIdx := strings.Index(category, ",")
if commaIdx > -1 {
target = category[0:commaIdx]
category = category[commaIdx+1:]
}
// Prepare the args
for i, v := range args {
args[i] = strings.Replace(v, ",", "%!(PACKER_COMMA)", -1)
args[i] = strings.Replace(args[i], "\r", "\\r", -1)
args[i] = strings.Replace(args[i], "\n", "\\n", -1)
}
argsString := strings.Join(args, ",")
_, err := fmt.Fprintf(u.Writer, "%d,%s,%s,%s\n", now.Unix(), target, category, argsString)
if err != nil {
panic(err)
}
}
...@@ -3,11 +3,12 @@ package packer ...@@ -3,11 +3,12 @@ package packer
import ( import (
"bytes" "bytes"
"cgl.tideland.biz/asserts" "cgl.tideland.biz/asserts"
"strings"
"testing" "testing"
) )
func testUi() *ReaderWriterUi { func testUi() *BasicUi {
return &ReaderWriterUi{ return &BasicUi{
Reader: new(bytes.Buffer), Reader: new(bytes.Buffer),
Writer: new(bytes.Buffer), Writer: new(bytes.Buffer),
} }
...@@ -36,23 +37,26 @@ func TestColoredUi(t *testing.T) { ...@@ -36,23 +37,26 @@ func TestColoredUi(t *testing.T) {
} }
} }
func TestPrefixedUi(t *testing.T) { func TestTargettedUi(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true) assert := asserts.NewTestingAsserts(t, true)
bufferUi := testUi() bufferUi := testUi()
prefixUi := &PrefixedUi{"mitchell", "bar", bufferUi} targettedUi := &TargettedUi{
Target: "foo",
Ui: bufferUi,
}
prefixUi.Say("foo") targettedUi.Say("foo")
assert.Equal(readWriter(bufferUi), "mitchell: foo\n", "should have prefix") assert.Equal(readWriter(bufferUi), "==> foo: foo\n", "should have prefix")
prefixUi.Message("foo") targettedUi.Message("foo")
assert.Equal(readWriter(bufferUi), "bar: foo\n", "should have prefix") assert.Equal(readWriter(bufferUi), " foo: foo\n", "should have prefix")
prefixUi.Error("bar") targettedUi.Error("bar")
assert.Equal(readWriter(bufferUi), "mitchell: bar\n", "should have prefix") assert.Equal(readWriter(bufferUi), "==> foo: bar\n", "should have prefix")
prefixUi.Say("foo\nbar") targettedUi.Say("foo\nbar")
assert.Equal(readWriter(bufferUi), "mitchell: foo\nmitchell: bar\n", "should multiline") assert.Equal(readWriter(bufferUi), "==> foo: foo\n==> foo: bar\n", "should multiline")
} }
func TestColoredUi_ImplUi(t *testing.T) { func TestColoredUi_ImplUi(t *testing.T) {
...@@ -63,23 +67,23 @@ func TestColoredUi_ImplUi(t *testing.T) { ...@@ -63,23 +67,23 @@ func TestColoredUi_ImplUi(t *testing.T) {
} }
} }
func TestPrefixedUi_ImplUi(t *testing.T) { func TestTargettedUi_ImplUi(t *testing.T) {
var raw interface{} var raw interface{}
raw = &PrefixedUi{} raw = &TargettedUi{}
if _, ok := raw.(Ui); !ok { if _, ok := raw.(Ui); !ok {
t.Fatalf("PrefixedUi must implement Ui") t.Fatalf("TargettedUi must implement Ui")
} }
} }
func TestReaderWriterUi_ImplUi(t *testing.T) { func TestBasicUi_ImplUi(t *testing.T) {
var raw interface{} var raw interface{}
raw = &ReaderWriterUi{} raw = &BasicUi{}
if _, ok := raw.(Ui); !ok { if _, ok := raw.(Ui); !ok {
t.Fatalf("ReaderWriterUi must implement Ui") t.Fatalf("BasicUi must implement Ui")
} }
} }
func TestReaderWriterUi_Error(t *testing.T) { func TestBasicUi_Error(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true) assert := asserts.NewTestingAsserts(t, true)
bufferUi := testUi() bufferUi := testUi()
...@@ -91,7 +95,7 @@ func TestReaderWriterUi_Error(t *testing.T) { ...@@ -91,7 +95,7 @@ func TestReaderWriterUi_Error(t *testing.T) {
assert.Equal(readWriter(bufferUi), "5\n", "formatting") assert.Equal(readWriter(bufferUi), "5\n", "formatting")
} }
func TestReaderWriterUi_Say(t *testing.T) { func TestBasicUi_Say(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true) assert := asserts.NewTestingAsserts(t, true)
bufferUi := testUi() bufferUi := testUi()
...@@ -103,9 +107,59 @@ func TestReaderWriterUi_Say(t *testing.T) { ...@@ -103,9 +107,59 @@ func TestReaderWriterUi_Say(t *testing.T) {
assert.Equal(readWriter(bufferUi), "5\n", "formatting") assert.Equal(readWriter(bufferUi), "5\n", "formatting")
} }
func TestMachineReadableUi_ImplUi(t *testing.T) {
var raw interface{}
raw = &MachineReadableUi{}
if _, ok := raw.(Ui); !ok {
t.Fatalf("MachineReadableUi must implement Ui")
}
}
func TestMachineReadableUi(t *testing.T) {
var data, expected string
buf := new(bytes.Buffer)
ui := &MachineReadableUi{Writer: buf}
// No target
ui.Machine("foo", "bar", "baz")
data = strings.SplitN(buf.String(), ",", 2)[1]
expected = ",foo,bar,baz\n"
if data != expected {
t.Fatalf("bad: %s", data)
}
// Target
buf.Reset()
ui.Machine("mitchellh,foo", "bar", "baz")
data = strings.SplitN(buf.String(), ",", 2)[1]
expected = "mitchellh,foo,bar,baz\n"
if data != expected {
t.Fatalf("bad: %s", data)
}
// Commas
buf.Reset()
ui.Machine("foo", "foo,bar")
data = strings.SplitN(buf.String(), ",", 2)[1]
expected = ",foo,foo%!(PACKER_COMMA)bar\n"
if data != expected {
t.Fatalf("bad: %s", data)
}
// New lines
buf.Reset()
ui.Machine("foo", "foo\n")
data = strings.SplitN(buf.String(), ",", 2)[1]
expected = ",foo,foo\\n\n"
if data != expected {
t.Fatalf("bad: %#v", data)
}
}
// This reads the output from the bytes.Buffer in our test object // This reads the output from the bytes.Buffer in our test object
// and then resets the buffer. // and then resets the buffer.
func readWriter(ui *ReaderWriterUi) (result string) { func readWriter(ui *BasicUi) (result string) {
buffer := ui.Writer.(*bytes.Buffer) buffer := ui.Writer.(*bytes.Buffer)
result = buffer.String() result = buffer.String()
buffer.Reset() buffer.Reset()
......
...@@ -27,6 +27,10 @@ command-line flags for this command.` ...@@ -27,6 +27,10 @@ command-line flags for this command.`
} }
func (versionCommand) Run(env Environment, args []string) int { func (versionCommand) Run(env Environment, args []string) int {
env.Ui().Machine("version", Version)
env.Ui().Machine("version-prelease", VersionPrerelease)
env.Ui().Machine("version-commit", GitCommit)
var versionString bytes.Buffer var versionString bytes.Buffer
fmt.Fprintf(&versionString, "Packer v%s", Version) fmt.Fprintf(&versionString, "Packer v%s", Version)
if VersionPrerelease != "" { if VersionPrerelease != "" {
......
package main package main
import (
"reflect"
"testing"
)
func TestExtractMachineReadable(t *testing.T) {
var args, expected, result []string
var mr bool
// Not
args = []string{"foo", "bar", "baz"}
result, mr = extractMachineReadable(args)
expected = []string{"foo", "bar", "baz"}
if !reflect.DeepEqual(result, expected) {
t.Fatalf("bad: %#v", result)
}
if mr {
t.Fatal("should not be mr")
}
// Yes
args = []string{"foo", "--machine-readable", "baz"}
result, mr = extractMachineReadable(args)
expected = []string{"foo", "baz"}
if !reflect.DeepEqual(result, expected) {
t.Fatalf("bad: %#v", result)
}
if !mr {
t.Fatal("should be mr")
}
}
...@@ -106,6 +106,9 @@ func (su *stubUi) Ask(string) (string, error) { ...@@ -106,6 +106,9 @@ func (su *stubUi) Ask(string) (string, error) {
func (su *stubUi) Error(string) { func (su *stubUi) Error(string) {
} }
func (su *stubUi) Machine(string, ...string) {
}
func (su *stubUi) Message(string) { func (su *stubUi) Message(string) {
} }
......
--- ---
layout: "docs" layout: "docs"
page_title: "Build - Command-Line"
--- ---
# Command-Line: Build # Command-Line: Build
...@@ -22,7 +23,7 @@ artifacts that are created will be outputted at the end of the build. ...@@ -22,7 +23,7 @@ artifacts that are created will be outputted at the end of the build.
In general, a builder supporting the forced build will remove the artifacts from In general, a builder supporting the forced build will remove the artifacts from
the previous build. This will allow the user to repeat a build without having to the previous build. This will allow the user to repeat a build without having to
manually clean these artifacts beforehand. manually clean these artifacts beforehand.
* `-except=foo,bar,baz` - Builds all the builds except those with the given * `-except=foo,bar,baz` - Builds all the builds except those with the given
comma-separated names. Build names by default are the names of their builders, comma-separated names. Build names by default are the names of their builders,
unless a specific `name` attribute is specified within the configuration. unless a specific `name` attribute is specified within the configuration.
......
--- ---
layout: "docs" layout: "docs"
page_title: "Command-line: Fix" page_title: "Fix - Command-Line"
--- ---
# Command-Line: Fix # Command-Line: Fix
......
---
layout: "docs"
page_title: "Machine-Readable Output - Command-Line"
---
# Machine-Readable Output
By default, the output of Packer is very human-readable. It uses nice
formatting, spacing, and colors in order to make Packer a pleasure to use.
However, Packer was built with automation in mind. To that end, Packer
supports a fully machine-readable output setting, allowing you to use
Packer in automated environments.
The machine-readable output format is easy to use and read and was made
with Unix tools in mind, so it is awk/sed/grep/etc. friendly.
## Enabling
The machine-readable output format can be enabled by passing the
`-machine-readable` flag to any Packer command. This immediately enables
all output to become machine-readable on stdout. Logging, if enabled,
continues to appear on stderr. An example of the output is shown
below:
```
$ packer -machine-readable version
1376289459,,version,0.2.4
1376289459,,version-prerelease,
1376289459,,version-commit,eed6ece
1376289459,,ui,say,Packer v0.2.4.dev (eed6ece+CHANGES)
```
The format will be covered in more detail later. But as you can see,
the output immediately becomes machine-friendly. Try some other commands
with the `-machine-readable` flag to see!
## Format
The machine readable format is a line-oriented, comma-delimeted text
format. This makes it extremely to parse using standard Unix tools such
as awk or grep in addition to full programming languages like Ruby or
Python.
The format is:
```
timestamp,target,type,data...
```
Each component is explained below:
* **timestamp** is a Unix timestamp in UTC of when the message was
printed.
* **target** is the target of the following output. This is empty if
the message is related to Packer globally. Otherwise, this is generally
a build name so you can relate output to a specific build while parallel
builds are running.
* **type** is the type of machine-readable message being outputted. There
are a set of standard types which are covered later, but each component
of Packer (builders, provisioners, etc.) may output their own custom types
as well, allowing the machine-readable output to be infinitely flexible.
* **data** is zero or more comma-seperated values associated with the prior
type. The exact amount and meaning of this data is type-dependent, so you
must read the documentation associated with the type to understand fully.
Within the format, if data contains a comma, it is replaced with
`%!(PACKER_COMMA)`. This was preferred over an escape character such as
`\'` because it is more friendly to tools like awk.
Newlines within the format are replaced with their respective standard
escape sequence. Newlines become a literal `\n` within the output. Carriage
returns become a literal `\r`.
## Message Types
The set of machine-readable message types can be found in the
[machine-readable format](#)
complete documentation section. This section contains documentation
on all the message types exposed by Packer core as well as all the
components that ship with Packer by default.
--- ---
layout: "docs" layout: "docs"
page_title: "Validate - Command-Line"
--- ---
# Command-Line: Validate # Command-Line: Validate
......
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
<li><a href="/docs/command-line/build.html">Build</a></li> <li><a href="/docs/command-line/build.html">Build</a></li>
<li><a href="/docs/command-line/fix.html">Fix</a></li> <li><a href="/docs/command-line/fix.html">Fix</a></li>
<li><a href="/docs/command-line/validate.html">Validate</a></li> <li><a href="/docs/command-line/validate.html">Validate</a></li>
<li><a href="/docs/command-line/machine-readable.html">Machine-Readable Output</a></li>
</ul> </ul>
<ul> <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