1
// Copyright 2016 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
14
"github.com/juju/errors"
15
"github.com/juju/gnuflag"
17
"github.com/juju/juju/api/application"
18
"github.com/juju/juju/apiserver/params"
19
"github.com/juju/juju/cmd/juju/block"
20
"github.com/juju/juju/cmd/modelcmd"
21
"github.com/juju/juju/cmd/output"
22
"github.com/juju/utils/keyvalues"
25
const maxValueSize = 5242880 // Max size for a config file.
28
configSummary = `Gets, sets, or resets configuration for a deployed application.`
29
configDetails = `By default, all configuration (keys, values, metadata) for the application are
30
displayed if a key is not specified.
32
Output includes the name of the charm used to deploy the application and a
33
listing of the application-specific configuration settings.
34
See ` + "`juju status`" + ` for application names.
38
juju config --format=json apache2
39
juju config mysql dataset-size
40
juju config mysql --reset dataset-size backup_dir
41
juju config apache2 --file path/to/config.yaml
42
juju config mysql dataset-size=80% backup_dir=/vol1/mysql/backups
43
juju config apache2 --model mymodel --file /home/ubuntu/mysql.yaml
51
// NewConfigCommand returns a command used to get, reset, and set application
53
func NewConfigCommand() cmd.Command {
54
return modelcmd.Wrap(&configCommand{})
57
type attributes map[string]string
59
// configCommand get, sets, and resets configuration values of an application.
60
type configCommand struct {
62
modelcmd.ModelCommandBase
65
action func(configCommandAPI, *cmd.Context) error // get, set, or reset action set in Init
66
applicationName string
67
configFile cmd.FileVar
74
// configCommandAPI is an interface to allow passing in a fake implementation under test.
75
type configCommandAPI interface {
77
Update(args params.ApplicationUpdate) error
78
Get(application string) (*params.ApplicationGetResults, error)
79
Set(application string, options map[string]string) error
80
Unset(application string, options []string) error
83
// Info is part of the cmd.Command interface.
84
func (c *configCommand) Info() *cmd.Info {
87
Args: "<application name> [[--reset] <attribute-key>][=<value>] ...]",
88
Purpose: configSummary,
93
// SetFlags is part of the cmd.Command interface.
94
func (c *configCommand) SetFlags(f *gnuflag.FlagSet) {
95
c.ModelCommandBase.SetFlags(f)
96
c.out.AddFlags(f, "yaml", output.DefaultFormatters)
97
f.Var(&c.configFile, "file", "path to yaml-formatted application config")
98
f.BoolVar(&c.reset, "reset", false, "Reset the provided keys to be empty")
101
// getAPI either uses the fake API set at test time or that is nil, gets a real
102
// API and sets that as the API.
103
func (c *configCommand) getAPI() (configCommandAPI, error) {
107
root, err := c.NewAPIRoot()
109
return nil, errors.Trace(err)
111
client := application.NewClient(root)
115
// Init is part of the cmd.Command interface.
116
func (c *configCommand) Init(args []string) error {
117
if len(args) == 0 || len(strings.Split(args[0], "=")) > 1 {
118
return errors.New("no application name specified")
120
c.applicationName = args[0]
122
c.action = c.resetConfig
123
return c.parseReset(args[1:])
125
if c.configFile.Path != "" {
126
return c.parseSet(args[1:], true)
128
if len(args[1:]) > 0 && strings.Contains(args[1], "=") {
129
return c.parseSet(args[1:], false)
131
return c.parseGet(args[1:])
134
// parseReset parses command line args when the --reset flag is supplied.
135
func (c *configCommand) parseReset(args []string) error {
137
return errors.New("no configuration options specified")
139
c.action = c.resetConfig
145
// parseSet parses the command line args when --file is set or if the
146
// positional args are key=value pairs.
147
func (c *configCommand) parseSet(args []string, file bool) error {
148
if file && len(args) > 0 {
149
return errors.New("cannot specify --file and key=value arguments simultaneously")
151
c.action = c.setConfig
157
settings, err := keyvalues.Parse(args, true)
166
// parseGet parses the command line args if we aren't setting or resetting.
167
func (c *configCommand) parseGet(args []string) error {
169
return errors.New("can only retrieve a single value, or all values")
171
c.action = c.getConfig
176
// Run implements the cmd.Command interface.
177
func (c *configCommand) Run(ctx *cmd.Context) error {
178
client, err := c.getAPI()
180
return errors.Trace(err)
184
return c.action(client, ctx)
187
// resetConfig is the run action when we are resetting attributes.
188
func (c *configCommand) resetConfig(client configCommandAPI, ctx *cmd.Context) error {
189
return block.ProcessBlockedError(client.Unset(c.applicationName, c.keys), block.BlockChange)
192
// validateValues reads the values provided as args and validates that they are
194
func (c *configCommand) validateValues(ctx *cmd.Context) (map[string]string, error) {
195
settings := map[string]string{}
196
for k, v := range c.values {
197
//empty string is also valid as a setting value
204
if !utf8.ValidString(v) {
205
return nil, errors.Errorf("value for option %q contains non-UTF-8 sequences", k)
210
nv, err := readValue(ctx, v[1:])
214
if !utf8.ValidString(nv) {
215
return nil, errors.Errorf("value for option %q contains non-UTF-8 sequences", k)
223
// setConfig is the run action when we are setting new attribute values as args
224
// or as a file passed in.
225
func (c *configCommand) setConfig(client configCommandAPI, ctx *cmd.Context) error {
227
return c.setConfigFromFile(client, ctx)
230
settings, err := c.validateValues(ctx)
232
return errors.Trace(err)
235
result, err := client.Get(c.applicationName)
240
for k, v := range settings {
241
configValue := result.Config[k]
243
configValueMap, ok := configValue.(map[string]interface{})
245
// convert the value to string and compare
246
if fmt.Sprintf("%v", configValueMap["value"]) == v {
247
logger.Warningf("the configuration setting %q already has the value %q", k, v)
252
return block.ProcessBlockedError(client.Set(c.applicationName, settings), block.BlockChange)
255
// setConfigFromFile sets the application configuration from settings passed
257
func (c *configCommand) setConfigFromFile(client configCommandAPI, ctx *cmd.Context) error {
262
if c.configFile.Path == "-" {
263
buf := bytes.Buffer{}
264
buf.ReadFrom(ctx.Stdin)
267
b, err = c.configFile.Read(ctx)
272
return block.ProcessBlockedError(
274
params.ApplicationUpdate{
275
ApplicationName: c.applicationName,
276
SettingsYAML: string(b)}), block.BlockChange)
280
// getConfig is the run action to return one or all configuration values.
281
func (c *configCommand) getConfig(client configCommandAPI, ctx *cmd.Context) error {
282
results, err := client.Get(c.applicationName)
286
if len(c.keys) == 1 {
288
info, found := results.Config[key].(map[string]interface{})
290
return errors.Errorf("key %q not found in %q application settings.", key, c.applicationName)
292
out := &bytes.Buffer{}
293
err := cmd.FormatYaml(out, info["value"])
297
fmt.Fprint(ctx.Stdout, out.String())
301
resultsMap := map[string]interface{}{
302
"application": results.Application,
303
"charm": results.Charm,
304
"settings": results.Config,
306
return c.out.Write(ctx, resultsMap)
309
// readValue reads the value of an option out of the named file.
310
// An empty content is valid, like in parsing the options. The upper
312
func readValue(ctx *cmd.Context, filename string) (string, error) {
313
absFilename := ctx.AbsPath(filename)
314
fi, err := os.Stat(absFilename)
316
return "", errors.Errorf("cannot read option from file %q: %v", filename, err)
318
if fi.Size() > maxValueSize {
319
return "", errors.Errorf("size of option file is larger than 5M")
321
content, err := ioutil.ReadFile(ctx.AbsPath(filename))
323
return "", errors.Errorf("cannot read option from file %q: %v", filename, err)
325
return string(content), nil