github.com/bhameyie/otto@v0.2.1-0.20160406174117-16052efa52ec/helper/packer/ui.go (about) 1 package packer 2 3 import ( 4 "fmt" 5 "log" 6 "strings" 7 "sync" 8 9 "github.com/armon/circbuf" 10 "github.com/hashicorp/otto/ui" 11 ) 12 13 // OutputCallback is the type that is called when there is a matching 14 // machine-readable output line for the UI. 15 type OutputCallback func(*Output) 16 17 // Output is a single line of machine-readable output from Packer. 18 type Output struct { 19 Timestamp string 20 Target string 21 Type string 22 Data []string 23 } 24 25 // packerUi is an implementation of ui.Ui that we pass to helper/exec 26 // that parses the machine-readable output of Packer and calls callbacks 27 // for the various entries. 28 type packerUi struct { 29 Callbacks map[string]OutputCallback 30 31 buf *circbuf.Buffer 32 once sync.Once 33 } 34 35 // Finish should be called when you're done to force the final newline 36 // to parse the final event. 37 func (u *packerUi) Finish() { 38 // We only need to do anything if we have stuff in our buffer 39 if u.buf != nil && u.buf.TotalWritten() > 0 { 40 u.Raw("\n") 41 } 42 } 43 44 // Ignore these because we don't use them from helper/exec 45 func (u *packerUi) Header(string) {} 46 func (u *packerUi) Message(string) {} 47 func (u *packerUi) Input(*ui.InputOpts) (string, error) { return "", nil } 48 49 func (u *packerUi) Raw(msg string) { 50 if msg == "" { 51 // Not sure how this would happen, but there is nothing to do here. 52 return 53 } 54 55 u.once.Do(u.init) 56 57 // We loop while we have a newline in the message 58 for { 59 // Get the index for the newline if there is one 60 idx := strings.IndexRune(msg, '\n') 61 if idx == -1 { 62 // The newline isn't there, write it to the circular buffer 63 // and wait longer. 64 u.buf.Write([]byte(msg)) 65 break 66 } 67 68 // We got a newline! Grab the contents from the circular buffer and 69 // copy it so we can clear the buffer. 70 bufRaw := u.buf.Bytes() 71 buf := string(bufRaw) + msg[:idx] 72 bufRaw = nil 73 u.buf.Reset() 74 75 // If we have more data, clear msg to that point. If we have 76 // no more data, then just set message to empty and we'll break 77 // on the next loop iteration 78 if idx < len(msg) { 79 msg = msg[idx+1:] 80 } else { 81 msg = "" 82 } 83 84 // Combine the data from the buffer up to the newline so we 85 // have the full line, and split that by the commas. 86 parts := strings.Split(buf, ",") 87 if len(parts) < 3 { 88 // Uh, invalid event? 89 log.Printf("[ERROR] Invalid Packer event line: %s", buf) 90 return 91 } 92 93 // Look for the callback 94 cb, ok := u.Callbacks[parts[2]] 95 if !ok { 96 // No callback registered for this type, drop it 97 return 98 } 99 100 // We have a callback, construct the output! 101 var data []string 102 if len(parts) > 3 { 103 data = make([]string, len(parts)-3) 104 for i, raw := range parts[3:] { 105 data[i] = strings.Replace( 106 strings.Replace( 107 strings.Replace(raw, "%!(PACKER_COMMA)", ",", -1), 108 "\\n", "\n", -1), 109 "\\r", "\r", -1) 110 } 111 } 112 113 // Callback 114 cb(&Output{ 115 Timestamp: parts[0], 116 Target: parts[1], 117 Type: parts[2], 118 Data: data, 119 }) 120 } 121 } 122 123 func (u *packerUi) init() { 124 // Allocating the circular buffer. The circular buffer should only 125 // keep track up to the point that there is a \n found so it doesn't 126 // need to be huge, but it also limits the max length of an event. 127 var err error 128 u.buf, err = circbuf.NewBuffer(4096) 129 if err != nil { 130 panic(err) 131 } 132 } 133 134 // For testing 135 func (o *Output) GoString() string { 136 return fmt.Sprintf("*%#v", *o) 137 }