github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/cmd/juju/status/history.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package status
     5  
     6  import (
     7  	"fmt"
     8  	"os"
     9  	"strconv"
    10  	"time"
    11  
    12  	"github.com/juju/cmd"
    13  	"github.com/juju/errors"
    14  	"github.com/juju/gnuflag"
    15  	"gopkg.in/juju/names.v2"
    16  
    17  	"github.com/juju/juju/cmd/juju/common"
    18  	"github.com/juju/juju/cmd/modelcmd"
    19  	"github.com/juju/juju/juju/osenv"
    20  	"github.com/juju/juju/status"
    21  )
    22  
    23  // TODO(peritto666) - add tests
    24  
    25  // NewStatusHistoryCommand returns a command that reports the history
    26  // of status changes for the specified unit.
    27  func NewStatusHistoryCommand() cmd.Command {
    28  	return modelcmd.Wrap(&statusHistoryCommand{})
    29  }
    30  
    31  type statusHistoryCommand struct {
    32  	modelcmd.ModelCommandBase
    33  	out             cmd.Output
    34  	outputContent   string
    35  	backlogSize     int
    36  	backlogSizeDays int
    37  	backlogDate     string
    38  	isoTime         bool
    39  	entityName      string
    40  	date            time.Time
    41  }
    42  
    43  var statusHistoryDoc = `
    44  This command will report the history of status changes for
    45  a given entity.
    46  The statuses are available for the following types.
    47  -type supports:
    48      juju-unit: will show statuses for the unit's juju agent.
    49      workload: will show statuses for the unit's workload.
    50      unit: will show workload and juju agent combined for the specified unit.
    51      juju-machine: will show statuses for machine's juju agent.
    52      machine: will show statuses for machines.
    53      juju-container: will show statuses for the container's juju agent.
    54      container: will show statuses for containers.
    55   and sorted by time of occurrence.
    56   The default is unit.
    57  `
    58  
    59  func (c *statusHistoryCommand) Info() *cmd.Info {
    60  	return &cmd.Info{
    61  		Name:    "show-status-log",
    62  		Args:    "<entity name>",
    63  		Purpose: "Output past statuses for the specified entity.",
    64  		Doc:     statusHistoryDoc,
    65  	}
    66  }
    67  
    68  func (c *statusHistoryCommand) SetFlags(f *gnuflag.FlagSet) {
    69  	c.ModelCommandBase.SetFlags(f)
    70  	f.StringVar(&c.outputContent, "type", "unit", "Type of statuses to be displayed [agent|workload|combined|machine|machineInstance|container|containerinstance]")
    71  	f.IntVar(&c.backlogSize, "n", 0, "Returns the last N logs (cannot be combined with --days or --date)")
    72  	f.IntVar(&c.backlogSizeDays, "days", 0, "Returns the logs for the past <days> days (cannot be combined with -n or --date)")
    73  	f.StringVar(&c.backlogDate, "date", "", "Returns logs for any date after the passed one, the expected date format is YYYY-MM-DD (cannot be combined with -n or --days)")
    74  	f.BoolVar(&c.isoTime, "utc", false, "Display time as UTC in RFC3339 format")
    75  }
    76  
    77  func (c *statusHistoryCommand) Init(args []string) error {
    78  	switch {
    79  	case len(args) > 1:
    80  		return errors.Errorf("unexpected arguments after entity name.")
    81  	case len(args) == 0:
    82  		return errors.Errorf("entity name is missing.")
    83  	default:
    84  		c.entityName = args[0]
    85  	}
    86  	// If use of ISO time not specified on command line,
    87  	// check env var.
    88  	if !c.isoTime {
    89  		var err error
    90  		envVarValue := os.Getenv(osenv.JujuStatusIsoTimeEnvKey)
    91  		if envVarValue != "" {
    92  			if c.isoTime, err = strconv.ParseBool(envVarValue); err != nil {
    93  				return errors.Annotatef(err, "invalid %s env var, expected true|false", osenv.JujuStatusIsoTimeEnvKey)
    94  			}
    95  		}
    96  	}
    97  	emptyDate := c.backlogDate == ""
    98  	emptySize := c.backlogSize == 0
    99  	emptyDays := c.backlogSizeDays == 0
   100  	if emptyDate && emptySize && emptyDays {
   101  		c.backlogSize = 20
   102  	}
   103  	if (!emptyDays && !emptySize) || (!emptyDays && !emptyDate) || (!emptySize && !emptyDate) {
   104  		return errors.Errorf("backlog size, backlog date and backlog days back cannot be specified together")
   105  	}
   106  	if c.backlogDate != "" {
   107  		var err error
   108  		c.date, err = time.Parse("2006-01-02", c.backlogDate)
   109  		if err != nil {
   110  			return errors.Annotate(err, "parsing backlog date")
   111  		}
   112  	}
   113  
   114  	kind := status.HistoryKind(c.outputContent)
   115  	if kind.Valid() {
   116  		return nil
   117  	}
   118  	return errors.Errorf("unexpected status type %q", c.outputContent)
   119  }
   120  
   121  func (c *statusHistoryCommand) Run(ctx *cmd.Context) error {
   122  	apiclient, err := c.NewAPIClient()
   123  	if err != nil {
   124  		return errors.Trace(err)
   125  	}
   126  	defer apiclient.Close()
   127  	kind := status.HistoryKind(c.outputContent)
   128  	var delta *time.Duration
   129  
   130  	if c.backlogSizeDays != 0 {
   131  		t := time.Duration(c.backlogSizeDays*24) * time.Hour
   132  		delta = &t
   133  	}
   134  	filterArgs := status.StatusHistoryFilter{
   135  		Size:  c.backlogSize,
   136  		Delta: delta,
   137  	}
   138  	if !c.date.IsZero() {
   139  		filterArgs.Date = &c.date
   140  	}
   141  	var tag names.Tag
   142  	switch kind {
   143  	case status.KindUnit, status.KindWorkload, status.KindUnitAgent:
   144  		if !names.IsValidUnit(c.entityName) {
   145  			return errors.Errorf("%q is not a valid name for a %s", c.entityName, kind)
   146  		}
   147  		tag = names.NewUnitTag(c.entityName)
   148  	default:
   149  		if !names.IsValidMachine(c.entityName) {
   150  			return errors.Errorf("%q is not a valid name for a %s", c.entityName, kind)
   151  		}
   152  		tag = names.NewMachineTag(c.entityName)
   153  	}
   154  	statuses, err := apiclient.StatusHistory(kind, tag, filterArgs)
   155  	historyLen := len(statuses)
   156  	if err != nil {
   157  		if historyLen == 0 {
   158  			return errors.Trace(err)
   159  		}
   160  		// Display any error, but continue to print status if some was returned
   161  		fmt.Fprintf(ctx.Stderr, "%v\n", err)
   162  	}
   163  
   164  	if historyLen == 0 {
   165  		return errors.Errorf("no status history available")
   166  	}
   167  
   168  	table := [][]string{{"TIME", "TYPE", "STATUS", "MESSAGE"}}
   169  	lengths := []int{1, 1, 1, 1}
   170  
   171  	statuses = statuses.SquashLogs(1)
   172  	statuses = statuses.SquashLogs(2)
   173  	statuses = statuses.SquashLogs(3)
   174  	for _, v := range statuses {
   175  		fields := []string{common.FormatTime(v.Since, c.isoTime), string(v.Kind), string(v.Status), v.Info}
   176  		for k, v := range fields {
   177  			if len(v) > lengths[k] {
   178  				lengths[k] = len(v)
   179  			}
   180  		}
   181  		table = append(table, fields)
   182  	}
   183  	f := fmt.Sprintf("%%-%ds\t%%-%ds\t%%-%ds\t%%-%ds\n", lengths[0], lengths[1], lengths[2], lengths[3])
   184  	for _, v := range table {
   185  		fmt.Printf(f, v[0], v[1], v[2], v[3])
   186  	}
   187  	return nil
   188  }