diff --git a/command/build/command_test.go b/command/build/command_test.go index 433f0ef46..33559cfc5 100644 --- a/command/build/command_test.go +++ b/command/build/command_test.go @@ -9,7 +9,7 @@ import ( func testEnvironment() packer.Environment { config := packer.DefaultEnvironmentConfig() - config.Ui = &packer.ReaderWriterUi{ + config.Ui = &packer.BasicUi{ Reader: new(bytes.Buffer), Writer: new(bytes.Buffer), } diff --git a/command/build/help.go b/command/build/help.go index 07c247c23..65ec5eb65 100644 --- a/command/build/help.go +++ b/command/build/help.go @@ -10,6 +10,7 @@ Options: -debug Debug mode enabled for builds -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 -only=foo,bar,baz Only build the given builds by name -var 'key=value' Variable for templates, can be used multiple times. diff --git a/packer.go b/packer.go index e1049b231..c14d778f5 100644 --- a/packer.go +++ b/packer.go @@ -76,8 +76,13 @@ func main() { log.Printf("Setting cache directory: %s", 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() + // Create the environment configuration envConfig := packer.DefaultEnvironmentConfig() envConfig.Cache = cache envConfig.Commands = config.CommandNames() @@ -86,6 +91,11 @@ func main() { envConfig.Components.Hook = config.LoadHook envConfig.Components.PostProcessor = config.LoadPostProcessor envConfig.Components.Provisioner = config.LoadProvisioner + if machineReadable { + envConfig.Ui = &packer.MachineReadableUi{ + Writer: os.Stdout, + } + } env, err := packer.NewEnvironment(envConfig) if err != nil { @@ -96,7 +106,7 @@ func main() { setupSignalHandlers(env) - exitCode, err := env.Cli(os.Args[1:]) + exitCode, err := env.Cli(args) if err != nil { fmt.Fprintf(os.Stderr, "Error executing CLI: %s\n", err.Error()) plugin.CleanupClients() @@ -107,6 +117,23 @@ func main() { 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) { var config config if err := decodeConfig(bytes.NewBufferString(defaultConfig), &config); err != nil { diff --git a/packer/build.go b/packer/build.go index bff34767e..39e54a559 100644 --- a/packer/build.go +++ b/packer/build.go @@ -213,11 +213,10 @@ func (b *coreBuild) Run(originalUi Ui, cache Cache) ([]Artifact, error) { hook := &DispatchHook{hooks} artifacts := make([]Artifact, 0, 1) - // The builder just has a normal Ui, but prefixed - builderUi := &PrefixedUi{ - fmt.Sprintf("==> %s", b.Name()), - fmt.Sprintf(" %s", b.Name()), - originalUi, + // The builder just has a normal Ui, but targetted + builderUi := &TargettedUi{ + Target: b.Name(), + Ui: originalUi, } log.Printf("Running builder: %s", b.builderType) @@ -240,10 +239,9 @@ PostProcessorRunSeqLoop: for _, ppSeq := range b.postProcessors { priorArtifact := builderArtifact for i, corePP := range ppSeq { - ppUi := &PrefixedUi{ - fmt.Sprintf("==> %s (%s)", b.Name(), corePP.processorType), - fmt.Sprintf(" %s (%s)", b.Name(), corePP.processorType), - originalUi, + ppUi := &TargettedUi{ + Target: fmt.Sprintf("%s (%s)", b.Name(), corePP.processorType), + Ui: originalUi, } builderUi.Say(fmt.Sprintf("Running post-processor: %s", corePP.processorType)) diff --git a/packer/communicator_test.go b/packer/communicator_test.go index d9f31c2bd..ac4c5b3e8 100644 --- a/packer/communicator_test.go +++ b/packer/communicator_test.go @@ -47,7 +47,7 @@ func TestRemoteCmd_StartWithUi(t *testing.T) { Stdout: rcOutput, } - testUi := &ReaderWriterUi{ + testUi := &BasicUi{ Reader: new(bytes.Buffer), Writer: uiOutput, } diff --git a/packer/environment.go b/packer/environment.go index b0ee2579f..1ef6c5c2a 100644 --- a/packer/environment.go +++ b/packer/environment.go @@ -73,7 +73,7 @@ type EnvironmentConfig struct { func DefaultEnvironmentConfig() *EnvironmentConfig { config := &EnvironmentConfig{} config.Commands = make([]string, 0) - config.Ui = &ReaderWriterUi{ + config.Ui = &BasicUi{ Reader: os.Stdin, Writer: os.Stdout, } @@ -299,6 +299,9 @@ func (e *coreEnvironment) printHelp() { // Output the command and the 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 diff --git a/packer/environment_test.go b/packer/environment_test.go index 125058bf2..3a15edea7 100644 --- a/packer/environment_test.go +++ b/packer/environment_test.go @@ -19,7 +19,7 @@ func init() { func testEnvironment() Environment { config := DefaultEnvironmentConfig() - config.Ui = &ReaderWriterUi{ + config.Ui = &BasicUi{ Reader: new(bytes.Buffer), Writer: new(bytes.Buffer), } @@ -45,8 +45,8 @@ func TestEnvironment_DefaultConfig_Ui(t *testing.T) { config := DefaultEnvironmentConfig() assert.NotNil(config.Ui, "default UI should not be nil") - rwUi, ok := config.Ui.(*ReaderWriterUi) - assert.True(ok, "default UI should be ReaderWriterUi") + rwUi, ok := config.Ui.(*BasicUi) + assert.True(ok, "default UI should be BasicUi") assert.Equal(rwUi.Writer, os.Stdout, "default UI should go to stdout") assert.Equal(rwUi.Reader, os.Stdin, "default UI should read from stdin") } @@ -175,7 +175,7 @@ func TestEnvironment_DefaultCli_Help(t *testing.T) { // A little lambda to help us test the output actually contains help testOutput := func() { - buffer := defaultEnv.Ui().(*ReaderWriterUi).Writer.(*bytes.Buffer) + buffer := defaultEnv.Ui().(*BasicUi).Writer.(*bytes.Buffer) output := buffer.String() buffer.Reset() assert.True(strings.Contains(output, "usage: packer"), "should print help") @@ -341,7 +341,7 @@ func TestEnvironmentProvisioner_Error(t *testing.T) { func TestEnvironment_SettingUi(t *testing.T) { assert := asserts.NewTestingAsserts(t, true) - ui := &ReaderWriterUi{ + ui := &BasicUi{ Reader: new(bytes.Buffer), Writer: new(bytes.Buffer), } diff --git a/packer/rpc/ui.go b/packer/rpc/ui.go index 6d69ca244..2a3807528 100644 --- a/packer/rpc/ui.go +++ b/packer/rpc/ui.go @@ -17,6 +17,12 @@ type UiServer struct { 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) { err = u.client.Call("Ui.Ask", query, &result) return @@ -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) { if err := u.client.Call("Ui.Message", message, new(interface{})); err != nil { panic(err) @@ -52,6 +69,13 @@ func (u *UiServer) Error(message *string, reply *interface{}) error { 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 { u.ui.Message(*message) *reply = nil diff --git a/packer/rpc/ui_test.go b/packer/rpc/ui_test.go index 78c203540..7dae40763 100644 --- a/packer/rpc/ui_test.go +++ b/packer/rpc/ui_test.go @@ -11,6 +11,9 @@ type testUi struct { askQuery string errorCalled bool errorMessage string + machineCalled bool + machineType string + machineArgs []string messageCalled bool messageMessage string sayCalled bool @@ -28,6 +31,12 @@ func (u *testUi) Error(message string) { 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) { u.messageCalled = true u.messageMessage = message diff --git a/packer/ui.go b/packer/ui.go index f19ebc04b..3d82b281a 100644 --- a/packer/ui.go +++ b/packer/ui.go @@ -11,6 +11,7 @@ import ( "runtime" "strings" "sync" + "time" "unicode" ) @@ -33,6 +34,7 @@ type Ui interface { Say(string) Message(string) Error(string) + Machine(string, ...string) } // ColoredUi is a UI that is colored using terminal colors. @@ -42,23 +44,32 @@ type ColoredUi struct { Ui Ui } -// PrefixedUi is a UI that wraps another UI implementation and adds a -// prefix to all the messages going out. -type PrefixedUi struct { - SayPrefix string - MessagePrefix string - Ui Ui +// TargettedUi is a UI that wraps another UI implementation and modifies +// the output to indicate a specific target. Specifically, all Say output +// is prefixed with the target name. Message output is not prefixed but +// is offset by the length of the target so that output is lined up properly +// with Say output. Machine-readable output has the proper target set. +type TargettedUi struct { + Target string + Ui Ui } -// The ReaderWriterUi is a UI that writes and reads from standard Go -// io.Reader and io.Writer. -type ReaderWriterUi struct { +// The BasicUI is a UI that reads and writes from a standard Go reader +// and writer. It is safe to be called from multiple goroutines. Machine +// readable output is simply logged for this UI. +type BasicUi struct { Reader io.Reader Writer io.Writer l sync.Mutex 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) { return u.Ui.Ask(u.colorize(query, u.Color, true)) } @@ -80,6 +91,11 @@ func (u *ColoredUi) Error(message string) { 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 { if !u.supportsColors() { return message @@ -107,33 +123,43 @@ func (u *ColoredUi) supportsColors() bool { return cygwin } -func (u *PrefixedUi) Ask(query string) (string, error) { - return u.Ui.Ask(u.prefixLines(u.SayPrefix, query)) +func (u *TargettedUi) Ask(query string) (string, error) { + return u.Ui.Ask(u.prefixLines(true, query)) } -func (u *PrefixedUi) Say(message string) { - u.Ui.Say(u.prefixLines(u.SayPrefix, message)) +func (u *TargettedUi) Say(message string) { + u.Ui.Say(u.prefixLines(true, message)) } -func (u *PrefixedUi) Message(message string) { - u.Ui.Message(u.prefixLines(u.MessagePrefix, message)) +func (u *TargettedUi) Message(message string) { + u.Ui.Message(u.prefixLines(false, message)) } -func (u *PrefixedUi) Error(message string) { - u.Ui.Error(u.prefixLines(u.SayPrefix, message)) +func (u *TargettedUi) Error(message string) { + 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 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) } -func (rw *ReaderWriterUi) Ask(query string) (string, error) { +func (rw *BasicUi) Ask(query string) (string, error) { rw.l.Lock() defer rw.l.Unlock() @@ -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() defer rw.l.Unlock() @@ -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() defer rw.l.Unlock() @@ -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() defer rw.l.Unlock() @@ -209,3 +235,48 @@ func (rw *ReaderWriterUi) Error(message string) { 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) + } +} diff --git a/packer/ui_test.go b/packer/ui_test.go index 6a48a80a3..87091f189 100644 --- a/packer/ui_test.go +++ b/packer/ui_test.go @@ -3,11 +3,12 @@ package packer import ( "bytes" "cgl.tideland.biz/asserts" + "strings" "testing" ) -func testUi() *ReaderWriterUi { - return &ReaderWriterUi{ +func testUi() *BasicUi { + return &BasicUi{ Reader: new(bytes.Buffer), Writer: new(bytes.Buffer), } @@ -36,23 +37,26 @@ func TestColoredUi(t *testing.T) { } } -func TestPrefixedUi(t *testing.T) { +func TestTargettedUi(t *testing.T) { assert := asserts.NewTestingAsserts(t, true) bufferUi := testUi() - prefixUi := &PrefixedUi{"mitchell", "bar", bufferUi} + targettedUi := &TargettedUi{ + Target: "foo", + Ui: bufferUi, + } - prefixUi.Say("foo") - assert.Equal(readWriter(bufferUi), "mitchell: foo\n", "should have prefix") + targettedUi.Say("foo") + assert.Equal(readWriter(bufferUi), "==> foo: foo\n", "should have prefix") - prefixUi.Message("foo") - assert.Equal(readWriter(bufferUi), "bar: foo\n", "should have prefix") + targettedUi.Message("foo") + assert.Equal(readWriter(bufferUi), " foo: foo\n", "should have prefix") - prefixUi.Error("bar") - assert.Equal(readWriter(bufferUi), "mitchell: bar\n", "should have prefix") + targettedUi.Error("bar") + assert.Equal(readWriter(bufferUi), "==> foo: bar\n", "should have prefix") - prefixUi.Say("foo\nbar") - assert.Equal(readWriter(bufferUi), "mitchell: foo\nmitchell: bar\n", "should multiline") + targettedUi.Say("foo\nbar") + assert.Equal(readWriter(bufferUi), "==> foo: foo\n==> foo: bar\n", "should multiline") } 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{} - raw = &PrefixedUi{} + raw = &TargettedUi{} 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{} - raw = &ReaderWriterUi{} + raw = &BasicUi{} 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) bufferUi := testUi() @@ -91,7 +95,7 @@ func TestReaderWriterUi_Error(t *testing.T) { assert.Equal(readWriter(bufferUi), "5\n", "formatting") } -func TestReaderWriterUi_Say(t *testing.T) { +func TestBasicUi_Say(t *testing.T) { assert := asserts.NewTestingAsserts(t, true) bufferUi := testUi() @@ -103,9 +107,59 @@ func TestReaderWriterUi_Say(t *testing.T) { 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 // and then resets the buffer. -func readWriter(ui *ReaderWriterUi) (result string) { +func readWriter(ui *BasicUi) (result string) { buffer := ui.Writer.(*bytes.Buffer) result = buffer.String() buffer.Reset() diff --git a/packer/version.go b/packer/version.go index 4c2413b7c..bb2ca744f 100644 --- a/packer/version.go +++ b/packer/version.go @@ -27,6 +27,10 @@ command-line flags for this command.` } 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 fmt.Fprintf(&versionString, "Packer v%s", Version) if VersionPrerelease != "" { diff --git a/packer_test.go b/packer_test.go index 06ab7d0f9..5a08f8431 100644 --- a/packer_test.go +++ b/packer_test.go @@ -1 +1,35 @@ 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") + } +} diff --git a/provisioner/file/provisioner_test.go b/provisioner/file/provisioner_test.go index 9c57ac452..c35f67cdb 100644 --- a/provisioner/file/provisioner_test.go +++ b/provisioner/file/provisioner_test.go @@ -106,6 +106,9 @@ func (su *stubUi) Ask(string) (string, error) { func (su *stubUi) Error(string) { } +func (su *stubUi) Machine(string, ...string) { +} + func (su *stubUi) Message(string) { } diff --git a/website/source/docs/command-line/build.html.markdown b/website/source/docs/command-line/build.html.markdown index 09a271af8..198bd85b4 100644 --- a/website/source/docs/command-line/build.html.markdown +++ b/website/source/docs/command-line/build.html.markdown @@ -1,5 +1,6 @@ --- layout: "docs" +page_title: "Build - Command-Line" --- # Command-Line: 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 the previous build. This will allow the user to repeat a build without having to manually clean these artifacts beforehand. - + * `-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, unless a specific `name` attribute is specified within the configuration. diff --git a/website/source/docs/command-line/fix.html.markdown b/website/source/docs/command-line/fix.html.markdown index 614777d4c..fa96f394c 100644 --- a/website/source/docs/command-line/fix.html.markdown +++ b/website/source/docs/command-line/fix.html.markdown @@ -1,6 +1,6 @@ --- layout: "docs" -page_title: "Command-line: Fix" +page_title: "Fix - Command-Line" --- # Command-Line: Fix diff --git a/website/source/docs/command-line/machine-readable.html.markdown b/website/source/docs/command-line/machine-readable.html.markdown new file mode 100644 index 000000000..b5f5b5b71 --- /dev/null +++ b/website/source/docs/command-line/machine-readable.html.markdown @@ -0,0 +1,83 @@ +--- +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. diff --git a/website/source/docs/command-line/validate.html.markdown b/website/source/docs/command-line/validate.html.markdown index fcf992f1f..e05c9410a 100644 --- a/website/source/docs/command-line/validate.html.markdown +++ b/website/source/docs/command-line/validate.html.markdown @@ -1,5 +1,6 @@ --- layout: "docs" +page_title: "Validate - Command-Line" --- # Command-Line: Validate diff --git a/website/source/layouts/docs.erb b/website/source/layouts/docs.erb index 8b3ef5ddd..2ab4b4a8e 100644 --- a/website/source/layouts/docs.erb +++ b/website/source/layouts/docs.erb @@ -13,6 +13,7 @@