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 {
|
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:
|
||||||
|
|
||||||
-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.
|
||||||
|
|
29
packer.go
29
packer.go
|
@ -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() {
|
||||||
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() {
|
||||||
|
|
||||||
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() {
|
||||||
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) {
|
||||||
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:
|
||||||
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) {
|
||||||
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 {
|
||||||
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() {
|
||||||
// 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() {
|
||||||
|
|
||||||
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) {
|
||||||
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) {
|
||||||
|
|
||||||
// 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) {
|
||||||
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 {
|
||||||
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) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
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 {
|
||||||
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) {
|
||||||
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
|
||||||
|
|
117
packer/ui.go
117
packer/ui.go
|
@ -11,6 +11,7 @@ import (
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
"unicode"
|
"unicode"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
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) {
|
||||||
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 {
|
||||||
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) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
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
|
||||||
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) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
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) {
|
||||||
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.`
|
||||||
}
|
}
|
||||||
|
|
||||||
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 != "" {
|
||||||
|
|
|
@ -1 +1,35 @@
|
||||||
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) {
|
||||||
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) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
---
|
---
|
||||||
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.
|
||||||
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.
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
layout: "docs"
|
layout: "docs"
|
||||||
page_title: "Command-line: Fix"
|
page_title: "Fix - Command-Line"
|
||||||
---
|
---
|
||||||
|
|
||||||
# Command-Line: Fix
|
# 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"
|
layout: "docs"
|
||||||
|
page_title: "Validate - Command-Line"
|
||||||
---
|
---
|
||||||
|
|
||||||
# Command-Line: Validate
|
# Command-Line: Validate
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in New Issue