github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/cmd/juju/controller/configcommand.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package controller
     5  
     6  import (
     7  	"bytes"
     8  	"io"
     9  	"os"
    10  	"strings"
    11  
    12  	"github.com/juju/cmd"
    13  	"github.com/juju/collections/set"
    14  	"github.com/juju/errors"
    15  	"github.com/juju/gnuflag"
    16  
    17  	apicontroller "github.com/juju/juju/api/controller"
    18  	jujucmd "github.com/juju/juju/cmd"
    19  	"github.com/juju/juju/cmd/juju/common"
    20  	"github.com/juju/juju/cmd/modelcmd"
    21  	"github.com/juju/juju/cmd/output"
    22  	"github.com/juju/juju/controller"
    23  )
    24  
    25  // NewConfigCommand returns a new command that can retrieve or update
    26  // controller configuration.
    27  func NewConfigCommand() cmd.Command {
    28  	return modelcmd.WrapController(&configCommand{})
    29  }
    30  
    31  // configCommand is able to output either the entire environment or
    32  // the requested value in a format of the user's choosing.
    33  type configCommand struct {
    34  	modelcmd.ControllerCommandBase
    35  	api controllerAPI
    36  	out cmd.Output
    37  
    38  	action     func(controllerAPI, *cmd.Context) error // The action we want to perform, set in cmd.Init.
    39  	key        string                                  // One config key to read.
    40  	setOptions common.ConfigFlag                       // Config values to set.
    41  }
    42  
    43  const configCommandHelpDoc = `
    44  By default, all configuration (keys and values) for the controller are
    45  displayed if a key is not specified. Supplying one key name returns
    46  only the value for that key.
    47  
    48  Supplying key=value will set the supplied key to the supplied value;
    49  this can be repeated for multiple keys. You can also specify a yaml
    50  file containing key values. Not all keys can be updated after
    51  bootstrap time.
    52  
    53  Available keys and values can be found here:
    54  https://jujucharms.com/stable/controllers-config
    55  
    56  Examples:
    57  
    58      juju controller-config
    59      juju controller-config api-port
    60      juju controller-config -c mycontroller
    61      juju controller-config auditing-enabled=true audit-log-max-backups=5
    62      juju controller-config auditing-enabled=true path/to/file.yaml
    63      juju controller-config path/to/file.yaml
    64  
    65  See also:
    66      controllers
    67      model-config
    68      show-cloud
    69  `
    70  
    71  // Info returns information about this command - it's part of
    72  // cmd.Command.
    73  func (c *configCommand) Info() *cmd.Info {
    74  	return jujucmd.Info(&cmd.Info{
    75  		Name:    "controller-config",
    76  		Args:    "[<attribute key>[=<value>] ...]",
    77  		Purpose: "Displays or sets configuration settings for a controller.",
    78  		Doc:     strings.TrimSpace(configCommandHelpDoc),
    79  	})
    80  }
    81  
    82  // SetFlags adds command-specific flags to the flag set. It's part of
    83  // cmd.Command.
    84  func (c *configCommand) SetFlags(f *gnuflag.FlagSet) {
    85  	c.ControllerCommandBase.SetFlags(f)
    86  	c.out.AddFlags(f, "tabular", map[string]cmd.Formatter{
    87  		"json":    cmd.FormatJson,
    88  		"tabular": formatConfigTabular,
    89  		"yaml":    cmd.FormatYaml,
    90  	})
    91  }
    92  
    93  // Init initialised the command from the arguments - it's part of
    94  // cmd.Command.
    95  func (c *configCommand) Init(args []string) error {
    96  	switch len(args) {
    97  	case 0:
    98  		return c.handleZeroArgs()
    99  	case 1:
   100  		return c.handleOneArg(args[0])
   101  	default:
   102  		return c.handleArgs(args)
   103  	}
   104  }
   105  
   106  func (c *configCommand) handleZeroArgs() error {
   107  	c.action = c.getConfig
   108  	return nil
   109  }
   110  
   111  func (c *configCommand) handleOneArg(arg string) error {
   112  	// We may have a single config.yaml file
   113  	_, err := os.Stat(arg)
   114  	if err == nil || strings.Contains(arg, "=") {
   115  		return c.parseSetKeys([]string{arg})
   116  	}
   117  	c.key = arg
   118  	c.action = c.getConfig
   119  	return nil
   120  }
   121  
   122  func (c *configCommand) handleArgs(args []string) error {
   123  	if err := c.parseSetKeys(args); err != nil {
   124  		return errors.Trace(err)
   125  	}
   126  	for _, arg := range args {
   127  		// We may have a config.yaml file.
   128  		_, err := os.Stat(arg)
   129  		if err != nil && !strings.Contains(arg, "=") {
   130  			return errors.New("can only retrieve a single value, or all values")
   131  		}
   132  	}
   133  	return nil
   134  }
   135  
   136  // parseSetKeys iterates over the args and make sure that the key=value pairs
   137  // are valid.
   138  func (c *configCommand) parseSetKeys(args []string) error {
   139  	for _, arg := range args {
   140  		if err := c.setOptions.Set(arg); err != nil {
   141  			return errors.Trace(err)
   142  		}
   143  	}
   144  	c.action = c.setConfig
   145  	return nil
   146  }
   147  
   148  type controllerAPI interface {
   149  	Close() error
   150  	ControllerConfig() (controller.Config, error)
   151  	ConfigSet(map[string]interface{}) error
   152  }
   153  
   154  func (c *configCommand) getAPI() (controllerAPI, error) {
   155  	if c.api != nil {
   156  		return c.api, nil
   157  	}
   158  	root, err := c.NewAPIRoot()
   159  	if err != nil {
   160  		return nil, errors.Trace(err)
   161  	}
   162  	return apicontroller.NewClient(root), nil
   163  }
   164  
   165  // Run executes the command as directed by the options and
   166  // arguments. It's part of cmd.Command.
   167  func (c *configCommand) Run(ctx *cmd.Context) error {
   168  	client, err := c.getAPI()
   169  	if err != nil {
   170  		return err
   171  	}
   172  	defer client.Close()
   173  	return c.action(client, ctx)
   174  }
   175  
   176  func (c *configCommand) getConfig(client controllerAPI, ctx *cmd.Context) error {
   177  	controllerName, err := c.ControllerName()
   178  	if err != nil {
   179  		return errors.Trace(err)
   180  	}
   181  	attrs, err := client.ControllerConfig()
   182  	if err != nil {
   183  		return err
   184  	}
   185  
   186  	if c.key != "" {
   187  		if value, found := attrs[c.key]; found {
   188  			if c.out.Name() == "tabular" {
   189  				// The user has not specified that they want
   190  				// YAML or JSON formatting, so we print out
   191  				// the value unadorned.
   192  				return c.out.WriteFormatter(ctx, cmd.FormatSmart, value)
   193  			}
   194  			return c.out.Write(ctx, value)
   195  		}
   196  		return errors.Errorf("key %q not found in %q controller", c.key, controllerName)
   197  	}
   198  	// If key is empty, write out the whole lot.
   199  	return c.out.Write(ctx, attrs)
   200  }
   201  
   202  func (c *configCommand) setConfig(client controllerAPI, ctx *cmd.Context) error {
   203  	attrs, err := c.setOptions.ReadAttrs(ctx)
   204  	if err != nil {
   205  		return errors.Trace(err)
   206  	}
   207  	return errors.Trace(client.ConfigSet(attrs))
   208  }
   209  
   210  func formatConfigTabular(writer io.Writer, value interface{}) error {
   211  	controllerConfig, ok := value.(controller.Config)
   212  	if !ok {
   213  		return errors.Errorf("expected value of type %T, got %T", controllerConfig, value)
   214  	}
   215  
   216  	tw := output.TabWriter(writer)
   217  	w := output.Wrapper{tw}
   218  
   219  	valueNames := make(set.Strings)
   220  	for name := range controllerConfig {
   221  		valueNames.Add(name)
   222  	}
   223  	w.Println("Attribute", "Value")
   224  
   225  	for _, name := range valueNames.SortedValues() {
   226  		value := controllerConfig[name]
   227  
   228  		var out bytes.Buffer
   229  		err := cmd.FormatYaml(&out, value)
   230  		if err != nil {
   231  			return errors.Annotatef(err, "formatting value for %q", name)
   232  		}
   233  		// Some attribute values have a newline appended
   234  		// which makes the output messy.
   235  		valString := strings.TrimSuffix(out.String(), "\n")
   236  
   237  		// Special formatting for multiline exclude-methods lists.
   238  		if name == controller.AuditLogExcludeMethods {
   239  			if strings.Contains(valString, "\n") {
   240  				valString = "\n" + valString
   241  			} else {
   242  				valString = strings.TrimLeft(valString, "- ")
   243  			}
   244  		}
   245  
   246  		w.Println(name, valString)
   247  	}
   248  
   249  	w.Flush()
   250  	return nil
   251  }