github.com/franciscocpg/up@v0.1.10/reporter/text.go (about) 1 // Package reporter provides event-based reporting for the CLI, 2 // aka this is what the user sees. 3 package reporter 4 5 import ( 6 "fmt" 7 "io" 8 "os" 9 "time" 10 11 "github.com/aws/aws-sdk-go/service/cloudformation" 12 "github.com/dustin/go-humanize" 13 "github.com/tj/go-progress" 14 "github.com/tj/go-spin" 15 "github.com/tj/go/term" 16 17 "github.com/apex/up/internal/colors" 18 "github.com/apex/up/internal/util" 19 "github.com/apex/up/platform/event" 20 "github.com/apex/up/platform/lambda/stack" 21 ) 22 23 // TODO: platform-specific reporting should live in the platform 24 // TODO: typed events would be nicer.. refactor event names 25 // TODO: refactor, this is a hot mess :D 26 27 // Text outputs human friendly textual reporting. 28 func Text(events <-chan *event.Event) { 29 r := reporter{ 30 events: events, 31 spinner: spin.New(), 32 } 33 34 r.Start() 35 } 36 37 // reporter struct. 38 type reporter struct { 39 events <-chan *event.Event 40 spinner *spin.Spinner 41 prevTime time.Time 42 pendingName string 43 pendingValue string 44 } 45 46 // spin the spinner by moving to the start of the line and re-printing. 47 func (r *reporter) spin() { 48 if r.pendingName != "" { 49 r.pending(r.pendingName, r.pendingValue) 50 } 51 } 52 53 // pending log with spinner. 54 func (r *reporter) pending(name, value string) { 55 r.pendingName = name 56 r.pendingValue = value 57 term.ClearLine() 58 fmt.Printf("\r %30s %s", colors.Purple(r.spinner.Next()+" "+name+":"), value) 59 } 60 61 // complete log with duration. 62 func (r *reporter) complete(name, value string, d time.Duration) { 63 r.pendingName = "" 64 r.pendingValue = "" 65 term.ClearLine() 66 duration := fmt.Sprintf("(%s)", d.Round(time.Millisecond)) 67 fmt.Printf("\r %30s %s %s\n", colors.Purple(name+":"), value, colors.Gray(duration)) 68 } 69 70 // log line 71 func (r *reporter) log(name, value string) { 72 fmt.Printf("\r %35s %s\n", colors.Purple(name+":"), value) 73 } 74 75 // Start handling events. 76 func (r *reporter) Start() { 77 tick := time.NewTicker(150 * time.Millisecond) 78 defer tick.Stop() 79 80 var bar *progress.Bar 81 var events []*cloudformation.StackEvent 82 var inlineProgress bool 83 84 for { 85 select { 86 case <-tick.C: 87 r.spin() 88 case e := <-r.events: 89 switch e.Name { 90 case "hook": 91 r.pending("hook", e.String("name")) 92 case "hook.complete": 93 r.complete("hook", e.String("name"), e.Duration("duration")) 94 case "deploy": 95 term.HideCursor() 96 case "deploy.complete": 97 term.ShowCursor() 98 case "platform.build": 99 r.pending("build", "") 100 case "platform.build.zip": 101 s := fmt.Sprintf("%s files, %s", humanize.Comma(e.Int64("files")), humanize.Bytes(uint64(e.Int("size_compressed")))) 102 r.complete("build", s, e.Duration("duration")) 103 case "platform.deploy": 104 r.pending("deploy", "") 105 case "platform.deploy.complete": 106 r.complete("deploy", "complete", e.Duration("duration")) 107 case "platform.function.create": 108 inlineProgress = true 109 case "platform.stack.create": 110 bar = util.NewInlineProgressInt(e.Int("resources")) 111 r.pending("stack", bar.String()) 112 case "platform.stack.delete": 113 bar = util.NewProgressInt(e.Int("resources")) 114 term.HideCursor() 115 io.WriteString(os.Stdout, term.CenterLine(bar.String())) 116 case "platform.stack.create.event": 117 bar.ValueInt(countEventsByState(events, stack.CreateComplete)) 118 events = append(events, e.Fields["event"].(*cloudformation.StackEvent)) 119 if inlineProgress { 120 r.pending("stack", bar.String()) 121 } else { 122 io.WriteString(os.Stdout, term.CenterLine(bar.String())) 123 } 124 case "platform.stack.delete.event": 125 events = append(events, e.Fields["event"].(*cloudformation.StackEvent)) 126 bar.ValueInt(countEventsByState(events, stack.DeleteComplete)) 127 io.WriteString(os.Stdout, term.CenterLine(bar.String())) 128 case "platform.stack.create.complete": 129 if inlineProgress { 130 r.complete("stack", "complete", e.Duration("duration")) 131 } else { 132 term.ClearAll() 133 } 134 case "platform.stack.delete.complete": 135 term.ClearAll() 136 term.ShowCursor() 137 case "platform.stack.show", "platform.stack.show.complete": 138 fmt.Printf("\n") 139 case "platform.stack.show.stack": 140 s := e.Fields["stack"].(*cloudformation.Stack) 141 fmt.Printf(" %s: %s\n", colors.Purple("status"), stack.Status(*s.StackStatus)) 142 if reason := s.StackStatusReason; reason != nil { 143 fmt.Printf(" %s: %s\n", colors.Purple("reason"), *reason) 144 } 145 fmt.Printf("\n") 146 case "platform.stack.show.event": 147 event := e.Fields["event"].(*cloudformation.StackEvent) 148 kind := *event.ResourceType 149 status := stack.Status(*event.ResourceStatus) 150 color := colors.Purple 151 if status.State() == stack.Failure { 152 color = colors.Red 153 } 154 fmt.Printf(" %s\n", color(kind)) 155 fmt.Printf(" %s: %v\n", color("id"), *event.LogicalResourceId) 156 fmt.Printf(" %s: %s\n", color("status"), status) 157 if reason := event.ResourceStatusReason; reason != nil { 158 fmt.Printf(" %s: %s\n", color("reason"), *reason) 159 } 160 fmt.Printf("\n") 161 case "metrics", "metrics.complete": 162 fmt.Printf("\n") 163 case "metrics.value": 164 switch n := e.String("name"); n { 165 case "Avg Latency", "Min Latency", "Max Latency": 166 r.log(n, fmt.Sprintf("%dms", e.Int("value"))) 167 default: 168 r.log(n, fmt.Sprintf("%d", e.Int("value"))) 169 } 170 } 171 172 r.prevTime = time.Now() 173 } 174 } 175 } 176 177 // countEventsByState returns the number of events with the given state. 178 func countEventsByState(events []*cloudformation.StackEvent, desired stack.Status) (n int) { 179 for _, e := range events { 180 status := stack.Status(*e.ResourceStatus) 181 182 if *e.ResourceType == "AWS::CloudFormation::Stack" { 183 continue 184 } 185 186 if status == desired { 187 n++ 188 } 189 } 190 191 return 192 }