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 }