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  }