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.
This commit is contained in:
commit
1f4e633eff
|
@ -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),
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
29
packer.go
29
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 {
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -47,7 +47,7 @@ func TestRemoteCmd_StartWithUi(t *testing.T) {
|
|||
Stdout: rcOutput,
|
||||
}
|
||||
|
||||
testUi := &ReaderWriterUi{
|
||||
testUi := &BasicUi{
|
||||
Reader: new(bytes.Buffer),
|
||||
Writer: uiOutput,
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
115
packer/ui.go
115
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
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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 != "" {
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
layout: "docs"
|
||||
page_title: "Build - Command-Line"
|
||||
---
|
||||
|
||||
# Command-Line: Build
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
layout: "docs"
|
||||
page_title: "Command-line: Fix"
|
||||
page_title: "Fix - Command-Line"
|
||||
---
|
||||
|
||||
# Command-Line: Fix
|
||||
|
|
|
@ -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.
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
layout: "docs"
|
||||
page_title: "Validate - Command-Line"
|
||||
---
|
||||
|
||||
# Command-Line: Validate
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
<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/validate.html">Validate</a></li>
|
||||
<li><a href="/docs/command-line/machine-readable.html">Machine-Readable Output</a></li>
|
||||
</ul>
|
||||
|
||||
<ul>
|
||||
|
|
Loading…
Reference in New Issue