~nskaggs/+junk/xenial-test

« back to all changes in this revision

Viewing changes to src/github.com/juju/juju/cmd/juju/application/set.go

  • Committer: Nicholas Skaggs
  • Date: 2016-10-24 20:56:05 UTC
  • Revision ID: nicholas.skaggs@canonical.com-20161024205605-z8lta0uvuhtxwzwl
Initi with beta15

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// Copyright 2012-2015 Canonical Ltd.
 
2
// Licensed under the AGPLv3, see LICENCE file for details.
 
3
 
 
4
package application
 
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/application"
 
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 application attributes.
 
25
func NewSetCommand() cmd.Command {
 
26
        return modelcmd.Wrap(&setCommand{})
 
27
}
 
28
 
 
29
// setCommand updates the configuration of an application.
 
30
type setCommand struct {
 
31
        modelcmd.ModelCommandBase
 
32
        ApplicationName 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 an application.`[1:]
 
42
 
 
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
 
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 application 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:    "<application name> <application 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 application config")
 
81
        f.BoolVar(&c.SetDefault, "to-default", false, "set application 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 application 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.ApplicationName = 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 application set command calls.
 
110
type serviceAPI interface {
 
111
        Close() error
 
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
 
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 application.NewClient(root), nil
 
127
}
 
128
 
 
129
// Run updates the configuration of an application.
 
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.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 {
 
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.ApplicationName)
 
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.ApplicationName, 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
}