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 }