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  }