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 }