github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/cmd/juju/service/set.go (about) 1 // Copyright 2012-2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package service 5 6 import ( 7 "fmt" 8 "io/ioutil" 9 "os" 10 "strings" 11 "unicode/utf8" 12 13 "github.com/juju/cmd" 14 "github.com/juju/errors" 15 "github.com/juju/utils/keyvalues" 16 "launchpad.net/gnuflag" 17 18 "github.com/juju/juju/api/service" 19 "github.com/juju/juju/apiserver/params" 20 "github.com/juju/juju/cmd/juju/block" 21 "github.com/juju/juju/cmd/modelcmd" 22 ) 23 24 // NewSetCommand returns a command used to set service attributes. 25 func NewSetCommand() cmd.Command { 26 return modelcmd.Wrap(&setCommand{}) 27 } 28 29 // setCommand updates the configuration of a service. 30 type setCommand struct { 31 modelcmd.ModelCommandBase 32 ServiceName string 33 SettingsStrings map[string]string 34 Options []string 35 SettingsYAML cmd.FileVar 36 SetDefault bool 37 serviceApi serviceAPI 38 } 39 40 var usageSetConfigSummary = ` 41 Sets configuration options for a service.`[1:] 42 43 var usageSetConfigDetails = ` 44 Charms may, and frequently do, expose a number of configuration settings 45 for a service to the user. These can be set at deploy time, but may be set 46 at any time by using the `[1:] + "`juju set-config`" + ` command. The actual options 47 vary per charm (you can check the charm documentation, or use ` + "`juju get-\nconfig`" + 48 ` to check which options may be set). 49 If ‘value’ begins with the ‘@’ character, it is interpreted as a filename 50 and the actual value is read from it. The maximum size of the filename is 51 5M. 52 Values may be any UTF-8 encoded string. UTF-8 is accepted on the command 53 line and in referenced files. 54 See ` + "`juju status`" + ` for service names. 55 56 Examples: 57 juju set-config mysql dataset-size=80% backup_dir=/vol1/mysql/backups 58 juju set-config apache2 --model mymodel --config /home/ubuntu/mysql.yaml 59 60 See also: 61 get-config 62 deploy 63 status` 64 65 const maxValueSize = 5242880 66 67 // Info implements Command.Info. 68 func (c *setCommand) Info() *cmd.Info { 69 return &cmd.Info{ 70 Name: "set-config", 71 Args: "<service name> <service key>=<value> ...", 72 Purpose: usageSetConfigSummary, 73 Doc: usageSetConfigDetails, 74 Aliases: []string{"set-configs"}, 75 } 76 } 77 78 // SetFlags implements Command.SetFlags. 79 func (c *setCommand) SetFlags(f *gnuflag.FlagSet) { 80 f.Var(&c.SettingsYAML, "config", "path to yaml-formatted service config") 81 f.BoolVar(&c.SetDefault, "to-default", false, "set service option values to default") 82 } 83 84 // Init implements Command.Init. 85 func (c *setCommand) Init(args []string) error { 86 if len(args) == 0 || len(strings.Split(args[0], "=")) > 1 { 87 return errors.New("no service name specified") 88 } 89 if c.SettingsYAML.Path != "" && len(args) > 1 { 90 return errors.New("cannot specify --config when using key=value arguments") 91 } 92 c.ServiceName = args[0] 93 if c.SetDefault { 94 c.Options = args[1:] 95 if len(c.Options) == 0 { 96 return errors.New("no configuration options specified") 97 } 98 return nil 99 } 100 settings, err := keyvalues.Parse(args[1:], true) 101 if err != nil { 102 return err 103 } 104 c.SettingsStrings = settings 105 return nil 106 } 107 108 // serviceAPI defines the methods on the client API 109 // that the service set command calls. 110 type serviceAPI interface { 111 Close() error 112 Update(args params.ServiceUpdate) error 113 Get(service string) (*params.ServiceGetResults, error) 114 Set(service string, options map[string]string) error 115 Unset(service string, options []string) error 116 } 117 118 func (c *setCommand) getServiceAPI() (serviceAPI, error) { 119 if c.serviceApi != nil { 120 return c.serviceApi, nil 121 } 122 root, err := c.NewAPIRoot() 123 if err != nil { 124 return nil, errors.Trace(err) 125 } 126 return service.NewClient(root), nil 127 } 128 129 // Run updates the configuration of a service. 130 func (c *setCommand) Run(ctx *cmd.Context) error { 131 apiclient, err := c.getServiceAPI() 132 if err != nil { 133 return err 134 } 135 defer apiclient.Close() 136 137 if c.SettingsYAML.Path != "" { 138 b, err := c.SettingsYAML.Read(ctx) 139 if err != nil { 140 return err 141 } 142 return block.ProcessBlockedError(apiclient.Update(params.ServiceUpdate{ 143 ServiceName: c.ServiceName, 144 SettingsYAML: string(b), 145 }), block.BlockChange) 146 } else if c.SetDefault { 147 return block.ProcessBlockedError(apiclient.Unset(c.ServiceName, c.Options), block.BlockChange) 148 } else if len(c.SettingsStrings) == 0 { 149 return nil 150 } 151 settings := map[string]string{} 152 for k, v := range c.SettingsStrings { 153 //empty string is also valid as a setting value 154 if v == "" { 155 settings[k] = v 156 continue 157 } 158 159 if v[0] != '@' { 160 if !utf8.ValidString(v) { 161 return fmt.Errorf("value for option %q contains non-UTF-8 sequences", k) 162 } 163 settings[k] = v 164 continue 165 } 166 nv, err := readValue(ctx, v[1:]) 167 if err != nil { 168 return err 169 } 170 if !utf8.ValidString(nv) { 171 return fmt.Errorf("value for option %q contains non-UTF-8 sequences", k) 172 } 173 settings[k] = nv 174 } 175 176 result, err := apiclient.Get(c.ServiceName) 177 if err != nil { 178 return err 179 } 180 181 for k, v := range settings { 182 configValue := result.Config[k] 183 184 configValueMap, ok := configValue.(map[string]interface{}) 185 if ok { 186 // convert the value to string and compare 187 if fmt.Sprintf("%v", configValueMap["value"]) == v { 188 logger.Warningf("the configuration setting %q already has the value %q", k, v) 189 } 190 } 191 } 192 193 return block.ProcessBlockedError(apiclient.Set(c.ServiceName, settings), block.BlockChange) 194 } 195 196 // readValue reads the value of an option out of the named file. 197 // An empty content is valid, like in parsing the options. The upper 198 // size is 5M. 199 func readValue(ctx *cmd.Context, filename string) (string, error) { 200 absFilename := ctx.AbsPath(filename) 201 fi, err := os.Stat(absFilename) 202 if err != nil { 203 return "", fmt.Errorf("cannot read option from file %q: %v", filename, err) 204 } 205 if fi.Size() > maxValueSize { 206 return "", fmt.Errorf("size of option file is larger than 5M") 207 } 208 content, err := ioutil.ReadFile(ctx.AbsPath(filename)) 209 if err != nil { 210 return "", fmt.Errorf("cannot read option from file %q: %v", filename, err) 211 } 212 return string(content), nil 213 }