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

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  package model
     4  
     5  import (
     6  	"bytes"
     7  	"io"
     8  	"sort"
     9  	"strings"
    10  
    11  	"github.com/juju/cmd"
    12  	"github.com/juju/errors"
    13  	"github.com/juju/gnuflag"
    14  	"github.com/juju/utils/keyvalues"
    15  
    16  	"github.com/juju/juju/api/modelconfig"
    17  	"github.com/juju/juju/cmd/juju/block"
    18  	"github.com/juju/juju/cmd/modelcmd"
    19  	"github.com/juju/juju/cmd/output"
    20  	"github.com/juju/juju/environs/config"
    21  )
    22  
    23  const (
    24  	modelConfigSummary = "Displays or sets configuration values on a model."
    25  	modelConfigHelpDoc = `
    26  By default, all configuration (keys, source, and values) for the current model
    27  are displayed.
    28  
    29  Supplying one key name returns only the value for the key. Supplying key=value
    30  will set the supplied key to the supplied value, this can be repeated for
    31  multiple keys.
    32  
    33  Examples
    34      juju model-config default-series
    35      juju model-config -m mycontroller:mymodel
    36      juju model-config ftp-proxy=10.0.0.1:8000
    37      juju model-config -m othercontroller:mymodel default-series=yakkety test-mode=false
    38      juju model-config --reset default-series test-mode
    39  
    40  See also:
    41      models
    42      model-defaults
    43  `
    44  )
    45  
    46  // NewConfigCommand wraps configCommand with sane model settings.
    47  func NewConfigCommand() cmd.Command {
    48  	return modelcmd.Wrap(&configCommand{})
    49  }
    50  
    51  type attributes map[string]interface{}
    52  
    53  // configCommand is the simplified command for accessing and setting
    54  // attributes related to model configuration.
    55  type configCommand struct {
    56  	api configCommandAPI
    57  	modelcmd.ModelCommandBase
    58  	out cmd.Output
    59  
    60  	action func(configCommandAPI, *cmd.Context) error // The action which we want to handle, set in cmd.Init.
    61  	keys   []string
    62  	reset  bool // Flag denoting whether we are resetting the keys provided.
    63  	values attributes
    64  }
    65  
    66  // Info implements part of the cmd.Command interface.
    67  func (c *configCommand) Info() *cmd.Info {
    68  	return &cmd.Info{
    69  		Args:    "[<model-key>[<=value>] ...]",
    70  		Doc:     modelConfigHelpDoc,
    71  		Name:    "model-config",
    72  		Purpose: modelConfigSummary,
    73  	}
    74  }
    75  
    76  // SetFlags implements part of the cmd.Command interface.
    77  func (c *configCommand) SetFlags(f *gnuflag.FlagSet) {
    78  	c.ModelCommandBase.SetFlags(f)
    79  
    80  	c.out.AddFlags(f, "tabular", map[string]cmd.Formatter{
    81  		"json":    cmd.FormatJson,
    82  		"tabular": formatConfigTabular,
    83  		"yaml":    cmd.FormatYaml,
    84  	})
    85  	f.BoolVar(&c.reset, "reset", false, "Reset the provided keys to be empty")
    86  }
    87  
    88  // Init implements part of the cmd.Command interface.
    89  func (c *configCommand) Init(args []string) error {
    90  	if c.reset {
    91  		// We're doing resetConfig.
    92  		if len(args) == 0 {
    93  			return errors.New("no keys specified")
    94  		}
    95  		for _, k := range args {
    96  			if k == config.AgentVersionKey {
    97  				return errors.Errorf("agent-version cannot be reset")
    98  			}
    99  		}
   100  		c.keys = args
   101  		c.action = c.resetConfig
   102  		return nil
   103  	}
   104  
   105  	if len(args) > 0 && strings.Contains(args[0], "=") {
   106  		// We're setting values.
   107  		options, err := keyvalues.Parse(args, true)
   108  		if err != nil {
   109  			return errors.Trace(err)
   110  		}
   111  		c.values = make(attributes)
   112  		for k, v := range options {
   113  			if k == config.AgentVersionKey {
   114  				return errors.Errorf(`agent-version must be set via "upgrade-juju"`)
   115  			}
   116  			c.values[k] = v
   117  		}
   118  
   119  		c.action = c.setConfig
   120  		return nil
   121  	}
   122  
   123  	val, err := cmd.ZeroOrOneArgs(args)
   124  	if err != nil {
   125  		return errors.New("can only retrieve a single value, or all values")
   126  	}
   127  
   128  	// We're doing getConfig.
   129  	if val != "" {
   130  		c.keys = []string{val}
   131  	}
   132  	c.action = c.getConfig
   133  	return nil
   134  }
   135  
   136  // configCommandAPI defines an API interface to be used during testing.
   137  type configCommandAPI interface {
   138  	Close() error
   139  	ModelGet() (map[string]interface{}, error)
   140  	ModelGetWithMetadata() (config.ConfigValues, error)
   141  	ModelSet(config map[string]interface{}) error
   142  	ModelUnset(keys ...string) error
   143  }
   144  
   145  // isModelAttribute returns if the supplied attribute is a valid model
   146  // attribute.
   147  func (c *configCommand) isModelAttrbute(attr string) bool {
   148  	switch attr {
   149  	case config.NameKey, config.TypeKey, config.UUIDKey:
   150  		return true
   151  	}
   152  	return false
   153  }
   154  
   155  // getAPI returns the API. This allows passing in a test configCommandAPI
   156  // implementation.
   157  func (c *configCommand) getAPI() (configCommandAPI, error) {
   158  	if c.api != nil {
   159  		return c.api, nil
   160  	}
   161  	api, err := c.NewAPIRoot()
   162  	if err != nil {
   163  		return nil, errors.Annotate(err, "opening API connection")
   164  	}
   165  	client := modelconfig.NewClient(api)
   166  	return client, nil
   167  }
   168  
   169  // Run implements the meaty part of the cmd.Command interface.
   170  func (c *configCommand) Run(ctx *cmd.Context) error {
   171  	client, err := c.getAPI()
   172  	if err != nil {
   173  		return err
   174  	}
   175  	defer client.Close()
   176  
   177  	return c.action(client, ctx)
   178  }
   179  
   180  // reset unsets the keys provided to the command.
   181  func (c *configCommand) resetConfig(client configCommandAPI, ctx *cmd.Context) error {
   182  	// ctx unused in this method
   183  
   184  	// extra call to the API to retrieve env config
   185  	envAttrs, err := client.ModelGet()
   186  	if err != nil {
   187  		return err
   188  	}
   189  	for _, key := range c.keys {
   190  		// check if the key exists in the existing env config
   191  		// and warn the user if the key is not defined in
   192  		// the existing config
   193  		if _, exists := envAttrs[key]; !exists {
   194  			// TODO(ro) This error used to be a false positive. Now, if it is
   195  			// printed, there really is a problem or misspelling. Ian would like to
   196  			// do some further testing and look at making this situation a fatal
   197  			// error, not just a warning. I think it's ok to leave for now, but
   198  			// with a todo.
   199  			logger.Warningf("key %q is not defined in the current model configuration: possible misspelling", key)
   200  		}
   201  
   202  	}
   203  	return block.ProcessBlockedError(client.ModelUnset(c.keys...), block.BlockChange)
   204  }
   205  
   206  // set sets the provided key/value pairs on the model.
   207  func (c *configCommand) setConfig(client configCommandAPI, ctx *cmd.Context) error {
   208  	// ctx unused in this method.
   209  	envAttrs, err := client.ModelGet()
   210  	if err != nil {
   211  		return err
   212  	}
   213  	for key := range c.values {
   214  		if _, exists := envAttrs[key]; !exists {
   215  			logger.Warningf("key %q is not defined in the current model configuration: possible misspelling", key)
   216  		}
   217  
   218  	}
   219  	return block.ProcessBlockedError(client.ModelSet(c.values), block.BlockChange)
   220  }
   221  
   222  // get writes the value of a single key or the full output for the model to the cmd.Context.
   223  func (c *configCommand) getConfig(client configCommandAPI, ctx *cmd.Context) error {
   224  	attrs, err := client.ModelGetWithMetadata()
   225  	if err != nil {
   226  		return err
   227  	}
   228  
   229  	for attrName := range attrs {
   230  		// We don't want model attributes included, these are available
   231  		// via show-model.
   232  		if c.isModelAttrbute(attrName) {
   233  			delete(attrs, attrName)
   234  		}
   235  	}
   236  
   237  	if len(c.keys) == 1 {
   238  		key := c.keys[0]
   239  		if value, found := attrs[key]; found {
   240  			if c.out.Name() == "tabular" {
   241  				return cmd.FormatYaml(ctx.Stdout, value.Value)
   242  			}
   243  			attrs = config.ConfigValues{
   244  				key: config.ConfigValue{
   245  					Source: value.Source,
   246  					Value:  value.Value,
   247  				},
   248  			}
   249  		} else {
   250  			return errors.Errorf("key %q not found in %q model.", key, attrs["name"])
   251  		}
   252  	}
   253  	return c.out.Write(ctx, attrs)
   254  }
   255  
   256  // formatConfigTabular writes a tabular summary of config information.
   257  func formatConfigTabular(writer io.Writer, value interface{}) error {
   258  	configValues, ok := value.(config.ConfigValues)
   259  	if !ok {
   260  		return errors.Errorf("expected value of type %T, got %T", configValues, value)
   261  	}
   262  
   263  	tw := output.TabWriter(writer)
   264  	w := output.Wrapper{tw}
   265  
   266  	var valueNames []string
   267  	for name := range configValues {
   268  		valueNames = append(valueNames, name)
   269  	}
   270  	sort.Strings(valueNames)
   271  	w.Println("ATTRIBUTE", "FROM", "VALUE")
   272  
   273  	for _, name := range valueNames {
   274  		info := configValues[name]
   275  		out := &bytes.Buffer{}
   276  		err := cmd.FormatYaml(out, info.Value)
   277  		if err != nil {
   278  			return errors.Annotatef(err, "formatting value for %q", name)
   279  		}
   280  		// Some attribute values have a newline appended
   281  		// which makes the output messy.
   282  		valString := strings.TrimSuffix(out.String(), "\n")
   283  		w.Println(name, info.Source, valString)
   284  	}
   285  
   286  	tw.Flush()
   287  	return nil
   288  }