github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/cmd/juju/status/status.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 "io" 9 "os" 10 "strconv" 11 "strings" 12 "time" 13 14 "github.com/juju/clock" 15 "github.com/juju/cmd" 16 "github.com/juju/collections/set" 17 "github.com/juju/errors" 18 "github.com/juju/gnuflag" 19 "github.com/juju/loggo" 20 21 storageapi "github.com/juju/juju/api/storage" 22 "github.com/juju/juju/apiserver/params" 23 jujucmd "github.com/juju/juju/cmd" 24 "github.com/juju/juju/cmd/juju/storage" 25 "github.com/juju/juju/cmd/modelcmd" 26 "github.com/juju/juju/juju/osenv" 27 ) 28 29 var logger = loggo.GetLogger("juju.cmd.juju.status") 30 31 type statusAPI interface { 32 Status(patterns []string) (*params.FullStatus, error) 33 Close() error 34 } 35 36 // NewStatusCommand returns a new command, which reports on the 37 // runtime state of various system entities. 38 func NewStatusCommand() cmd.Command { 39 return modelcmd.Wrap(&statusCommand{ 40 checkProvidedIgnoredFlagF: func() set.Strings { return set.NewStrings() }, 41 }) 42 } 43 44 // Clock defines the methods needed for the status command. 45 type Clock interface { 46 After(time.Duration) <-chan time.Time 47 } 48 49 type statusCommand struct { 50 modelcmd.ModelCommandBase 51 out cmd.Output 52 patterns []string 53 isoTime bool 54 statusAPI statusAPI 55 storageAPI storage.StorageListAPI 56 clock Clock 57 58 retryCount int 59 retryDelay time.Duration 60 61 color bool 62 63 // relations indicates if 'relations' section is displayed 64 relations bool 65 66 // checkProvidedIgnoredFlagF indicates whether ignored options were provided by the user. 67 checkProvidedIgnoredFlagF func() set.Strings 68 69 // storage indicates if 'storage' section is displayed 70 storage bool 71 } 72 73 var usageSummary = ` 74 Reports the current status of the model, machines, applications and units.`[1:] 75 76 var usageDetails = ` 77 By default (without argument), the status of the model, including all 78 applications and units will be output. 79 80 Application or unit names may be used as output filters (the '*' can be used as 81 a wildcard character). In addition to matched applications and units, related 82 machines, applications, and units will also be displayed. If a subordinate unit 83 is matched, then its principal unit will be displayed. If a principal unit is 84 matched, then all of its subordinates will be displayed. 85 86 Machine numbers may also be used as output filters. This will only display data 87 in each section relevant to the specified machines. For example, application 88 section will only contain the applications that have units on these machines, etc. 89 90 The available output formats are: 91 92 - tabular (default): Displays status in a tabular format with a separate table 93 for the model, machines, applications, relations (if any), storage (if any) 94 and units. 95 Note: in this format, the AZ column refers to the cloud region's 96 availability zone. 97 - {short|line|oneline}: List units and their subordinates. For each unit, the IP 98 address and agent status are listed. 99 - summary: Displays the subnet(s) and port(s) the model utilises. Also displays 100 aggregate information about: 101 - Machines: total #, and # in each state. 102 - Units: total #, and # in each state. 103 - Applications: total #, and # exposed of each application. 104 - yaml: Displays information about the model, machines, applications, and units 105 in structured YAML format. 106 - json: Displays information about the model, machines, applications, and units 107 in structured JSON format. 108 109 In tabular format, 'Relations' section is not displayed by default. 110 Use --relations option to see this section. This option is ignored in all other 111 formats. 112 113 Examples: 114 juju show-status 115 juju show-status mysql 116 juju show-status nova-* 117 juju show-status --relations 118 juju show-status --storage 119 120 See also: 121 machines 122 show-model 123 show-status-log 124 storage 125 ` 126 127 func (c *statusCommand) Info() *cmd.Info { 128 return jujucmd.Info(&cmd.Info{ 129 Name: "show-status", 130 Args: "[filter pattern ...]", 131 Purpose: usageSummary, 132 Doc: usageDetails, 133 Aliases: []string{"status"}, 134 }) 135 } 136 137 func (c *statusCommand) SetFlags(f *gnuflag.FlagSet) { 138 c.ModelCommandBase.SetFlags(f) 139 f.BoolVar(&c.isoTime, "utc", false, "Display time as UTC in RFC3339 format") 140 f.BoolVar(&c.color, "color", false, "Force use of ANSI color codes") 141 142 f.BoolVar(&c.relations, "relations", false, "Show 'relations' section") 143 f.BoolVar(&c.storage, "storage", false, "Show 'storage' section") 144 145 f.IntVar(&c.retryCount, "retry-count", 3, "Number of times to retry API failures") 146 f.DurationVar(&c.retryDelay, "retry-delay", 100*time.Millisecond, "Time to wait between retry attempts") 147 148 c.checkProvidedIgnoredFlagF = func() set.Strings { 149 ignoredFlagForNonTabularFormat := set.NewStrings( 150 "relations", 151 "storage", 152 ) 153 provided := set.NewStrings() 154 f.Visit(func(flag *gnuflag.Flag) { 155 if ignoredFlagForNonTabularFormat.Contains(flag.Name) { 156 provided.Add(flag.Name) 157 } 158 159 }) 160 return provided 161 } 162 163 defaultFormat := "tabular" 164 165 c.out.AddFlags(f, defaultFormat, map[string]cmd.Formatter{ 166 "yaml": cmd.FormatYaml, 167 "json": cmd.FormatJson, 168 "short": FormatOneline, 169 "oneline": FormatOneline, 170 "line": FormatOneline, 171 "tabular": c.FormatTabular, 172 "summary": FormatSummary, 173 }) 174 } 175 176 func (c *statusCommand) Init(args []string) error { 177 c.patterns = args 178 // If use of ISO time not specified on command line, 179 // check env var. 180 if !c.isoTime { 181 var err error 182 envVarValue := os.Getenv(osenv.JujuStatusIsoTimeEnvKey) 183 if envVarValue != "" { 184 if c.isoTime, err = strconv.ParseBool(envVarValue); err != nil { 185 return errors.Annotatef(err, "invalid %s env var, expected true|false", osenv.JujuStatusIsoTimeEnvKey) 186 } 187 } 188 } 189 if c.clock == nil { 190 c.clock = clock.WallClock 191 } 192 return nil 193 } 194 195 var newAPIClientForStatus = func(c *statusCommand) (statusAPI, error) { 196 if c.statusAPI == nil { 197 api, err := c.NewAPIClient() 198 if err != nil { 199 return nil, errors.Trace(err) 200 } 201 c.statusAPI = api 202 } 203 return c.statusAPI, nil 204 } 205 206 var newAPIClientForStorage = func(c *statusCommand) (storage.StorageListAPI, error) { 207 if c.storageAPI == nil { 208 root, err := c.NewAPIRoot() 209 if err != nil { 210 return nil, err 211 } 212 c.storageAPI = storageapi.NewClient(root) 213 } 214 return c.storageAPI, nil 215 } 216 217 func (c *statusCommand) close() { 218 // We really don't care what the errors are if there are some. 219 // The user can't do anything about it. Just try. 220 if c.statusAPI != nil { 221 c.statusAPI.Close() 222 } 223 if c.storageAPI != nil { 224 c.storageAPI.Close() 225 } 226 return 227 } 228 229 func (c *statusCommand) getStatus() (*params.FullStatus, error) { 230 apiclient, err := newAPIClientForStatus(c) 231 if err != nil { 232 return nil, errors.Trace(err) 233 } 234 return apiclient.Status(c.patterns) 235 } 236 237 func (c *statusCommand) getStorageInfo(ctx *cmd.Context) (*storage.CombinedStorage, error) { 238 apiclient, err := newAPIClientForStorage(c) 239 if err != nil { 240 return nil, errors.Trace(err) 241 } 242 return storage.GetCombinedStorageInfo( 243 storage.GetCombinedStorageInfoParams{ 244 Context: ctx, 245 APIClient: apiclient, 246 Ids: []string{}, 247 WantStorage: true, 248 WantVolumes: true, 249 WantFilesystems: true, 250 }) 251 } 252 253 func (c *statusCommand) Run(ctx *cmd.Context) error { 254 defer c.close() 255 256 // Always attempt to get the status at least once, and retry if it fails. 257 status, err := c.getStatus() 258 if err != nil { 259 for i := 0; i < c.retryCount; i++ { 260 // fun bit - make sure a new api connection is used for each new call 261 c.SetModelAPI(nil) 262 // Wait for a bit before retries. 263 <-c.clock.After(c.retryDelay) 264 status, err = c.getStatus() 265 if err == nil { 266 break 267 } 268 } 269 } 270 271 if err != nil { 272 if status == nil { 273 // Status call completely failed, there is nothing to report 274 return errors.Trace(err) 275 } 276 // Display any error, but continue to print status if some was returned 277 fmt.Fprintf(ctx.Stderr, "%v\n", err) 278 } else if status == nil { 279 return errors.Errorf("unable to obtain the current status") 280 } 281 282 controllerName, err := c.ControllerName() 283 if err != nil { 284 return errors.Trace(err) 285 } 286 287 showRelations := c.relations 288 showStorage := c.storage 289 if c.out.Name() != "tabular" { 290 showRelations = true 291 showStorage = true 292 providedIgnoredFlags := c.checkProvidedIgnoredFlagF() 293 if !providedIgnoredFlags.IsEmpty() { 294 // For non-tabular formats this is redundant and needs to be mentioned to the user. 295 joinedMsg := strings.Join(providedIgnoredFlags.SortedValues(), ", ") 296 if providedIgnoredFlags.Size() > 1 { 297 joinedMsg += " options are" 298 } else { 299 joinedMsg += " option is" 300 } 301 ctx.Infof("provided %s always enabled in non tabular formats", joinedMsg) 302 } 303 } 304 formatterParams := newStatusFormatterParams{ 305 status: status, 306 controllerName: controllerName, 307 isoTime: c.isoTime, 308 showRelations: showRelations, 309 } 310 if showStorage { 311 storageInfo, err := c.getStorageInfo(ctx) 312 if err != nil { 313 return errors.Trace(err) 314 } 315 formatterParams.storage = storageInfo 316 } 317 318 formatted, err := newStatusFormatter(formatterParams).format() 319 if err != nil { 320 return errors.Trace(err) 321 } 322 323 if err = c.out.Write(ctx, formatted); err != nil { 324 return err 325 } 326 327 if !status.IsEmpty() { 328 return nil 329 } 330 if len(c.patterns) == 0 { 331 modelName, err := c.ModelName() 332 if err != nil { 333 return err 334 } 335 ctx.Infof("Model %q is empty.", modelName) 336 } else { 337 plural := func() string { 338 if len(c.patterns) == 1 { 339 return "" 340 } 341 return "s" 342 } 343 ctx.Infof("Nothing matched specified filter%v.", plural()) 344 } 345 return nil 346 } 347 348 func (c *statusCommand) FormatTabular(writer io.Writer, value interface{}) error { 349 return FormatTabular(writer, c.color, value) 350 }