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 }