github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/cmd/juju/model/defaultscommand.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/modelmanager"
    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  	modelDefaultsSummary = `Displays or sets default configuration settings for a model.`
    25  	modelDefaultsHelpDoc = `
    26  By default, all default configuration (keys and values) are
    27  displayed if a key is not specified. Supplying key=value will set the
    28  supplied key to the supplied value. This can be repeated for multiple keys.
    29  By default, the model is the current model.
    30  
    31  
    32  Examples:
    33      juju model-defaults
    34      juju model-defaults http-proxy
    35      juju model-defaults -m mymodel type
    36      juju model-defaults ftp-proxy=10.0.0.1:8000
    37      juju model-defaults -m othercontroller:mymodel default-series=yakkety test-mode=false
    38      juju model-defaults --reset default-series test-mode
    39  
    40  See also:
    41      models
    42      model-config
    43  `
    44  )
    45  
    46  // NewDefaultsCommand wraps defaultsCommand with sane model settings.
    47  func NewDefaultsCommand() cmd.Command {
    48  	return modelcmd.WrapController(&defaultsCommand{})
    49  }
    50  
    51  // defaultsCommand is compound command for accessing and setting attributes
    52  // related to default model configuration.
    53  type defaultsCommand struct {
    54  	modelcmd.ControllerCommandBase
    55  	api defaultsCommandAPI
    56  	out cmd.Output
    57  
    58  	action func(defaultsCommandAPI, *cmd.Context) error // The function handling the input, set in Init.
    59  	keys   []string
    60  	reset  bool // Flag indicating if we are resetting the keys provided.
    61  	values attributes
    62  }
    63  
    64  // defaultsCommandAPI defines an API to be used during testing.
    65  type defaultsCommandAPI interface {
    66  	// Close closes the api connection.
    67  	Close() error
    68  
    69  	// ModelDefaults returns the default config values used when creating a new model.
    70  	ModelDefaults() (config.ModelDefaultAttributes, error)
    71  
    72  	// SetModelDefaults sets the default config values to use
    73  	// when creating new models.
    74  	SetModelDefaults(cloud, region string, config map[string]interface{}) error
    75  
    76  	// UnsetModelDefaults clears the default model
    77  	// configuration values.
    78  	UnsetModelDefaults(cloud, region string, keys ...string) error
    79  }
    80  
    81  // Info implements part of the cmd.Command interface.
    82  func (c *defaultsCommand) Info() *cmd.Info {
    83  	return &cmd.Info{
    84  		Args:    "[<model-key>[<=value>] ...]",
    85  		Doc:     modelDefaultsHelpDoc,
    86  		Name:    "model-defaults",
    87  		Purpose: modelDefaultsSummary,
    88  	}
    89  }
    90  
    91  // SetFlags implements part of the cmd.Command interface.
    92  func (c *defaultsCommand) SetFlags(f *gnuflag.FlagSet) {
    93  	c.ControllerCommandBase.SetFlags(f)
    94  
    95  	c.out.AddFlags(f, "tabular", map[string]cmd.Formatter{
    96  		"yaml":    cmd.FormatYaml,
    97  		"json":    cmd.FormatJson,
    98  		"tabular": formatDefaultConfigTabular,
    99  	})
   100  	f.BoolVar(&c.reset, "reset", false, "Reset the provided keys to be empty")
   101  }
   102  
   103  // Init implements part of the cmd.Command interface.
   104  func (c *defaultsCommand) Init(args []string) error {
   105  	if c.reset {
   106  		// We're resetting defaults.
   107  		if len(args) == 0 {
   108  			return errors.New("no keys specified")
   109  		}
   110  		for _, k := range args {
   111  			if k == config.AgentVersionKey {
   112  				return errors.Errorf("%q cannot be reset", config.AgentVersionKey)
   113  			}
   114  		}
   115  		c.keys = args
   116  
   117  		c.action = c.resetDefaults
   118  		return nil
   119  	}
   120  
   121  	if len(args) > 0 && strings.Contains(args[0], "=") {
   122  		// We're setting defaults.
   123  		options, err := keyvalues.Parse(args, true)
   124  		if err != nil {
   125  			return errors.Trace(err)
   126  		}
   127  		c.values = make(attributes)
   128  		for k, v := range options {
   129  			if k == config.AgentVersionKey {
   130  				return errors.Errorf(`%q must be set via "upgrade-juju"`, config.AgentVersionKey)
   131  			}
   132  			c.values[k] = v
   133  		}
   134  
   135  		c.action = c.setDefaults
   136  		return nil
   137  
   138  	}
   139  	// We're getting defaults.
   140  	val, err := cmd.ZeroOrOneArgs(args)
   141  	if err != nil {
   142  		return errors.New("can only retrieve a single value, or all values")
   143  	}
   144  	if val != "" {
   145  		c.keys = []string{val}
   146  	}
   147  	c.action = c.getDefaults
   148  	return nil
   149  }
   150  
   151  // getAPI sets the api on the command. This allows passing in a test
   152  // ModelDefaultsAPI implementation.
   153  func (c *defaultsCommand) getAPI() (defaultsCommandAPI, error) {
   154  	if c.api != nil {
   155  		return c.api, nil
   156  	}
   157  
   158  	api, err := c.NewAPIRoot()
   159  	if err != nil {
   160  		return nil, errors.Annotate(err, "opening API connection")
   161  	}
   162  	client := modelmanager.NewClient(api)
   163  
   164  	return client, nil
   165  }
   166  
   167  // Run implements part of the cmd.Command interface.
   168  func (c *defaultsCommand) Run(ctx *cmd.Context) error {
   169  	client, err := c.getAPI()
   170  	if err != nil {
   171  		return errors.Trace(err)
   172  	}
   173  	defer client.Close()
   174  
   175  	return c.action(client, ctx)
   176  }
   177  
   178  func (c *defaultsCommand) getDefaults(client defaultsCommandAPI, ctx *cmd.Context) error {
   179  	attrs, err := client.ModelDefaults()
   180  	if err != nil {
   181  		return err
   182  	}
   183  
   184  	if len(c.keys) == 1 {
   185  		key := c.keys[0]
   186  		if value, ok := attrs[key]; ok {
   187  			attrs = config.ModelDefaultAttributes{
   188  				key: value,
   189  			}
   190  		} else {
   191  			return errors.Errorf("key %q not found in %q model defaults.", key, attrs["name"])
   192  		}
   193  	}
   194  	// If c.keys is empty, write out the whole lot.
   195  	return c.out.Write(ctx, attrs)
   196  }
   197  
   198  func (c *defaultsCommand) setDefaults(client defaultsCommandAPI, ctx *cmd.Context) error {
   199  	// ctx unused in this method.
   200  	if err := c.verifyKnownKeys(client); err != nil {
   201  		return errors.Trace(err)
   202  	}
   203  	// TODO(wallyworld) - call with cloud and region when that bit is done
   204  	return block.ProcessBlockedError(client.SetModelDefaults("", "", c.values), block.BlockChange)
   205  }
   206  
   207  func (c *defaultsCommand) resetDefaults(client defaultsCommandAPI, ctx *cmd.Context) error {
   208  	// ctx unused in this method.
   209  	if err := c.verifyKnownKeys(client); err != nil {
   210  		return errors.Trace(err)
   211  	}
   212  	// TODO(wallyworld) - call with cloud and region when that bit is done
   213  	return block.ProcessBlockedError(client.UnsetModelDefaults("", "", c.keys...), block.BlockChange)
   214  
   215  }
   216  
   217  // verifyKnownKeys is a helper to validate the keys we are operating with
   218  // against the set of known attributes from the model.
   219  func (c *defaultsCommand) verifyKnownKeys(client defaultsCommandAPI) error {
   220  	known, err := client.ModelDefaults()
   221  	if err != nil {
   222  		return errors.Trace(err)
   223  	}
   224  	keys := func() []string {
   225  		if c.keys != nil {
   226  			return c.keys
   227  		}
   228  		keys := []string{}
   229  		for k, _ := range c.values {
   230  			keys = append(keys, k)
   231  		}
   232  		return keys
   233  	}
   234  	for _, key := range keys() {
   235  		// check if the key exists in the known config
   236  		// and warn the user if the key is not defined
   237  		if _, exists := known[key]; !exists {
   238  			logger.Warningf(
   239  				"key %q is not defined in the known model configuration: possible misspelling", key)
   240  		}
   241  	}
   242  	return nil
   243  }
   244  
   245  // formatConfigTabular writes a tabular summary of default config information.
   246  func formatDefaultConfigTabular(writer io.Writer, value interface{}) error {
   247  	defaultValues, ok := value.(config.ModelDefaultAttributes)
   248  	if !ok {
   249  		return errors.Errorf("expected value of type %T, got %T", defaultValues, value)
   250  	}
   251  
   252  	tw := output.TabWriter(writer)
   253  	w := output.Wrapper{tw}
   254  
   255  	p := func(name string, value config.AttributeDefaultValues) {
   256  		var c, d interface{}
   257  		switch value.Default {
   258  		case nil:
   259  			d = "-"
   260  		case "":
   261  			d = `""`
   262  		default:
   263  			d = value.Default
   264  		}
   265  		switch value.Controller {
   266  		case nil:
   267  			c = "-"
   268  		case "":
   269  			c = `""`
   270  		default:
   271  			c = value.Controller
   272  		}
   273  		w.Println(name, d, c)
   274  		for _, region := range value.Regions {
   275  			w.Println("  "+region.Name, region.Value, "-")
   276  		}
   277  	}
   278  	var valueNames []string
   279  	for name := range defaultValues {
   280  		valueNames = append(valueNames, name)
   281  	}
   282  	sort.Strings(valueNames)
   283  
   284  	w.Println("ATTRIBUTE", "DEFAULT", "CONTROLLER")
   285  
   286  	for _, name := range valueNames {
   287  		info := defaultValues[name]
   288  		out := &bytes.Buffer{}
   289  		err := cmd.FormatYaml(out, info)
   290  		if err != nil {
   291  			return errors.Annotatef(err, "formatting value for %q", name)
   292  		}
   293  		p(name, info)
   294  	}
   295  
   296  	tw.Flush()
   297  	return nil
   298  }