github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/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 "errors" 8 "fmt" 9 "io/ioutil" 10 "os" 11 "strings" 12 "unicode/utf8" 13 14 "github.com/juju/cmd" 15 "github.com/juju/utils/keyvalues" 16 "launchpad.net/gnuflag" 17 18 "github.com/juju/juju/apiserver/params" 19 "github.com/juju/juju/cmd/envcmd" 20 "github.com/juju/juju/cmd/juju/block" 21 ) 22 23 // SetCommand updates the configuration of a service. 24 type SetCommand struct { 25 envcmd.EnvCommandBase 26 ServiceName string 27 SettingsStrings map[string]string 28 SettingsYAML cmd.FileVar 29 api SetServiceAPI 30 } 31 32 const setDoc = ` 33 Set one or more configuration options for the specified service. See also the 34 unset command which sets one or more configuration options for a specified 35 service to their default value. 36 37 In case a value starts with an at sign (@) the rest of the value is interpreted 38 as a filename. The value itself is then read out of the named file. The maximum 39 size of this value is 5M. 40 41 Option values may be any UTF-8 encoded string. UTF-8 is accepted on the command 42 line and in configuration files. 43 ` 44 45 const maxValueSize = 5242880 46 47 func (c *SetCommand) Info() *cmd.Info { 48 return &cmd.Info{ 49 Name: "set", 50 Args: "<service> name=value ...", 51 Purpose: "set service config options", 52 Doc: setDoc, 53 } 54 } 55 56 func (c *SetCommand) SetFlags(f *gnuflag.FlagSet) { 57 f.Var(&c.SettingsYAML, "config", "path to yaml-formatted service config") 58 } 59 60 func (c *SetCommand) Init(args []string) error { 61 if len(args) == 0 || len(strings.Split(args[0], "=")) > 1 { 62 return errors.New("no service name specified") 63 } 64 if c.SettingsYAML.Path != "" && len(args) > 1 { 65 return errors.New("cannot specify --config when using key=value arguments") 66 } 67 c.ServiceName = args[0] 68 settings, err := keyvalues.Parse(args[1:], true) 69 if err != nil { 70 return err 71 } 72 c.SettingsStrings = settings 73 return nil 74 } 75 76 // SetServiceAPI defines the methods on the client API 77 // that the service set command calls. 78 type SetServiceAPI interface { 79 Close() error 80 ServiceSetYAML(service string, yaml string) error 81 ServiceGet(service string) (*params.ServiceGetResults, error) 82 ServiceSet(service string, options map[string]string) error 83 } 84 85 func (c *SetCommand) getAPI() (SetServiceAPI, error) { 86 if c.api != nil { 87 return c.api, nil 88 } 89 return c.NewAPIClient() 90 } 91 92 // Run updates the configuration of a service. 93 func (c *SetCommand) Run(ctx *cmd.Context) error { 94 api, err := c.getAPI() 95 if err != nil { 96 return err 97 } 98 defer api.Close() 99 100 if c.SettingsYAML.Path != "" { 101 b, err := c.SettingsYAML.Read(ctx) 102 if err != nil { 103 return err 104 } 105 return block.ProcessBlockedError(api.ServiceSetYAML(c.ServiceName, string(b)), block.BlockChange) 106 } else if len(c.SettingsStrings) == 0 { 107 return nil 108 } 109 settings := map[string]string{} 110 for k, v := range c.SettingsStrings { 111 //empty string is also valid as a setting value 112 if v == "" { 113 settings[k] = v 114 continue 115 } 116 117 if v[0] != '@' { 118 if !utf8.ValidString(v) { 119 return fmt.Errorf("value for option %q contains non-UTF-8 sequences", k) 120 } 121 settings[k] = v 122 continue 123 } 124 nv, err := readValue(ctx, v[1:]) 125 if err != nil { 126 return err 127 } 128 if !utf8.ValidString(nv) { 129 return fmt.Errorf("value for option %q contains non-UTF-8 sequences", k) 130 } 131 settings[k] = nv 132 } 133 134 result, err := api.ServiceGet(c.ServiceName) 135 if err != nil { 136 return err 137 } 138 139 for k, v := range settings { 140 configValue := result.Config[k] 141 142 configValueMap, ok := configValue.(map[string]interface{}) 143 if ok { 144 // convert the value to string and compare 145 if fmt.Sprintf("%v", configValueMap["value"]) == v { 146 logger.Warningf("the configuration setting %q already has the value %q", k, v) 147 } 148 } 149 } 150 151 return block.ProcessBlockedError(api.ServiceSet(c.ServiceName, settings), block.BlockChange) 152 } 153 154 // readValue reads the value of an option out of the named file. 155 // An empty content is valid, like in parsing the options. The upper 156 // size is 5M. 157 func readValue(ctx *cmd.Context, filename string) (string, error) { 158 absFilename := ctx.AbsPath(filename) 159 fi, err := os.Stat(absFilename) 160 if err != nil { 161 return "", fmt.Errorf("cannot read option from file %q: %v", filename, err) 162 } 163 if fi.Size() > maxValueSize { 164 return "", fmt.Errorf("size of option file is larger than 5M") 165 } 166 content, err := ioutil.ReadFile(ctx.AbsPath(filename)) 167 if err != nil { 168 return "", fmt.Errorf("cannot read option from file %q: %v", filename, err) 169 } 170 return string(content), nil 171 }