1
// Copyright 2012-2015 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
14
"github.com/juju/errors"
15
"github.com/juju/utils/keyvalues"
16
"launchpad.net/gnuflag"
18
"github.com/juju/juju/api/application"
19
"github.com/juju/juju/apiserver/params"
20
"github.com/juju/juju/cmd/juju/block"
21
"github.com/juju/juju/cmd/modelcmd"
24
// NewSetCommand returns a command used to set application attributes.
25
func NewSetCommand() cmd.Command {
26
return modelcmd.Wrap(&setCommand{})
29
// setCommand updates the configuration of an application.
30
type setCommand struct {
31
modelcmd.ModelCommandBase
32
ApplicationName string
33
SettingsStrings map[string]string
35
SettingsYAML cmd.FileVar
40
var usageSetConfigSummary = `
41
Sets configuration options for an application.`[1:]
43
var usageSetConfigDetails = `
44
Charms may, and frequently do, expose a number of configuration settings
45
for an application 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
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 application names.
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
65
const maxValueSize = 5242880
67
// Info implements Command.Info.
68
func (c *setCommand) Info() *cmd.Info {
71
Args: "<application name> <application key>=<value> ...",
72
Purpose: usageSetConfigSummary,
73
Doc: usageSetConfigDetails,
74
Aliases: []string{"set-configs"},
78
// SetFlags implements Command.SetFlags.
79
func (c *setCommand) SetFlags(f *gnuflag.FlagSet) {
80
f.Var(&c.SettingsYAML, "config", "path to yaml-formatted application config")
81
f.BoolVar(&c.SetDefault, "to-default", false, "set application option values to default")
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 application name specified")
89
if c.SettingsYAML.Path != "" && len(args) > 1 {
90
return errors.New("cannot specify --config when using key=value arguments")
92
c.ApplicationName = args[0]
95
if len(c.Options) == 0 {
96
return errors.New("no configuration options specified")
100
settings, err := keyvalues.Parse(args[1:], true)
104
c.SettingsStrings = settings
108
// serviceAPI defines the methods on the client API
109
// that the application set command calls.
110
type serviceAPI interface {
112
Update(args params.ApplicationUpdate) error
113
Get(application string) (*params.ApplicationGetResults, error)
114
Set(application string, options map[string]string) error
115
Unset(application string, options []string) error
118
func (c *setCommand) getServiceAPI() (serviceAPI, error) {
119
if c.serviceApi != nil {
120
return c.serviceApi, nil
122
root, err := c.NewAPIRoot()
124
return nil, errors.Trace(err)
126
return application.NewClient(root), nil
129
// Run updates the configuration of an application.
130
func (c *setCommand) Run(ctx *cmd.Context) error {
131
apiclient, err := c.getServiceAPI()
135
defer apiclient.Close()
137
if c.SettingsYAML.Path != "" {
138
b, err := c.SettingsYAML.Read(ctx)
142
return block.ProcessBlockedError(apiclient.Update(params.ApplicationUpdate{
143
ApplicationName: c.ApplicationName,
144
SettingsYAML: string(b),
145
}), block.BlockChange)
146
} else if c.SetDefault {
147
return block.ProcessBlockedError(apiclient.Unset(c.ApplicationName, c.Options), block.BlockChange)
148
} else if len(c.SettingsStrings) == 0 {
151
settings := map[string]string{}
152
for k, v := range c.SettingsStrings {
153
//empty string is also valid as a setting value
160
if !utf8.ValidString(v) {
161
return fmt.Errorf("value for option %q contains non-UTF-8 sequences", k)
166
nv, err := readValue(ctx, v[1:])
170
if !utf8.ValidString(nv) {
171
return fmt.Errorf("value for option %q contains non-UTF-8 sequences", k)
176
result, err := apiclient.Get(c.ApplicationName)
181
for k, v := range settings {
182
configValue := result.Config[k]
184
configValueMap, ok := configValue.(map[string]interface{})
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)
193
return block.ProcessBlockedError(apiclient.Set(c.ApplicationName, settings), block.BlockChange)
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
199
func readValue(ctx *cmd.Context, filename string) (string, error) {
200
absFilename := ctx.AbsPath(filename)
201
fi, err := os.Stat(absFilename)
203
return "", fmt.Errorf("cannot read option from file %q: %v", filename, err)
205
if fi.Size() > maxValueSize {
206
return "", fmt.Errorf("size of option file is larger than 5M")
208
content, err := ioutil.ReadFile(ctx.AbsPath(filename))
210
return "", fmt.Errorf("cannot read option from file %q: %v", filename, err)
212
return string(content), nil