Skip to content

Commit 05f6ec0

Browse files
authored
Merge pull request juju#6178 from reedobrien/feature/rework-model-defaults-cli
feature/rework model defaults cli Update tests accordingly. Refs: juju-model-defaults-collapse QA: 1. All unit tests pass 2. juju bootstrap reed-aws-west-1 awstest # with cloud like http://paste.ubuntu.com/23080517/ 3. juju model-defaults # verify that the output matches what you'd expect from the config above. 4. juju model-defaults image-stream=develop test-mode=true # ensure settings are updated with juju model-config 5. juju model-defaults --reset image-stream test-mode # ensure they are unset 6. juju model-defaults image-stream ATTRIBUTE DEFAULT CONTROLLER image-stream released - 7. juju model-defaults --format=json image-stream {"image-stream":{"default":"released"}} 8. Maybe try a few more (Review request: http://reviews.vapour.ws/r/5616/)
2 parents 8bc3008 + 22f83b8 commit 05f6ec0

12 files changed

+514
-655
lines changed

cmd/juju/commands/main.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -302,9 +302,7 @@ func registerCommands(r commandRegistry, ctx *cmd.Context) {
302302

303303
// Manage model
304304
r.Register(model.NewConfigCommand())
305-
r.Register(model.NewModelDefaultsCommand())
306-
r.Register(model.NewSetModelDefaultsCommand())
307-
r.Register(model.NewUnsetModelDefaultsCommand())
305+
r.Register(model.NewDefaultsCommand())
308306
r.Register(model.NewRetryProvisioningCommand())
309307
r.Register(model.NewDestroyCommand())
310308
r.Register(model.NewUsersCommand())

cmd/juju/commands/main_test.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -491,7 +491,6 @@ var commandNames = []string{
491491
"set-default-region",
492492
"set-meter-status",
493493
"set-model-constraints",
494-
"set-model-default",
495494
"set-plan",
496495
"shares",
497496
"show-action-output",
@@ -519,7 +518,6 @@ var commandNames = []string{
519518
"update-allocation",
520519
"upload-backup",
521520
"unregister",
522-
"unset-model-default",
523521
"update-clouds",
524522
"upgrade-charm",
525523
"upgrade-gui",

cmd/juju/model/configcommand.go

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,20 @@ package model
44

55
import (
66
"bytes"
7-
"fmt"
87
"io"
98
"sort"
109
"strings"
1110

1211
"github.com/juju/cmd"
1312
"github.com/juju/errors"
1413
"github.com/juju/gnuflag"
14+
"github.com/juju/utils/keyvalues"
15+
1516
"github.com/juju/juju/api/modelconfig"
1617
"github.com/juju/juju/cmd/juju/block"
1718
"github.com/juju/juju/cmd/modelcmd"
1819
"github.com/juju/juju/cmd/output"
1920
"github.com/juju/juju/environs/config"
20-
"github.com/juju/utils/keyvalues"
2121
)
2222

2323
const (
@@ -93,7 +93,7 @@ func (c *configCommand) Init(args []string) error {
9393
return errors.New("no keys specified")
9494
}
9595
for _, k := range args {
96-
if k == "agent-version" {
96+
if k == config.AgentVersionKey {
9797
return errors.Errorf("agent-version cannot be reset")
9898
}
9999
}
@@ -110,7 +110,7 @@ func (c *configCommand) Init(args []string) error {
110110
}
111111
c.values = make(attributes)
112112
for k, v := range options {
113-
if k == "agent-version" {
113+
if k == config.AgentVersionKey {
114114
return errors.Errorf(`agent-version must be set via "upgrade-juju"`)
115115
}
116116
c.values[k] = v
@@ -262,16 +262,14 @@ func formatConfigTabular(writer io.Writer, value interface{}) error {
262262
}
263263

264264
tw := output.TabWriter(writer)
265-
p := func(values ...string) {
266-
text := strings.Join(values, "\t")
267-
fmt.Fprintln(tw, text)
268-
}
265+
p := output.TabWriterPrintln(tw)
266+
269267
var valueNames []string
270268
for name := range configValues {
271269
valueNames = append(valueNames, name)
272270
}
273271
sort.Strings(valueNames)
274-
p("ATTRIBUTE\tFROM\tVALUE")
272+
p("ATTRIBUTE", "FROM", "VALUE")
275273

276274
for _, name := range valueNames {
277275
info := configValues[name]

cmd/juju/model/defaultscommand.go

Lines changed: 304 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,304 @@
1+
// Copyright 2016 Canonical Ltd.
2+
// Licensed under the AGPLv3, see LICENCE file for details.
3+
package model
4+
5+
import (
6+
"bytes"
7+
"fmt"
8+
"io"
9+
"sort"
10+
"strings"
11+
12+
"github.com/juju/cmd"
13+
"github.com/juju/errors"
14+
"github.com/juju/gnuflag"
15+
"github.com/juju/utils/keyvalues"
16+
17+
"github.com/juju/juju/api/modelconfig"
18+
"github.com/juju/juju/cmd/juju/block"
19+
"github.com/juju/juju/cmd/modelcmd"
20+
"github.com/juju/juju/cmd/output"
21+
"github.com/juju/juju/environs/config"
22+
)
23+
24+
const (
25+
modelDefaultsSummary = `Displays or sets default configuration settings for a model.`
26+
modelDefaultsHelpDoc = `
27+
By default, all default configuration (keys and values) are
28+
displayed if a key is not specified. Supplying key=value will set the
29+
supplied key to the supplied value. This can be repeated for multiple keys.
30+
By default, the model is the current model.
31+
32+
33+
Examples:
34+
juju model-defaults
35+
juju model-defaults http-proxy
36+
juju model-defaults -m mymodel type
37+
juju model-defaults ftp-proxy=10.0.0.1:8000
38+
juju model-defaults -m othercontroller:mymodel default-series=yakkety test-mode=false
39+
juju model-defaults --reset default-series test-mode
40+
41+
See also:
42+
models
43+
model-config
44+
`
45+
)
46+
47+
// NewDefaultsCommand wraps defaultsCommand with sane model settings.
48+
func NewDefaultsCommand() cmd.Command {
49+
return modelcmd.Wrap(&defaultsCommand{})
50+
}
51+
52+
// defaultsCommand is compound command for accessing and setting attributes
53+
// related to default model configuration.
54+
type defaultsCommand struct {
55+
modelcmd.ModelCommandBase
56+
api defaultsCommandAPI
57+
out cmd.Output
58+
59+
action func(*cmd.Context) error // The function handling the input, set in Init.
60+
keys []string
61+
reset bool // Flag indicating if we are resetting the keys provided.
62+
values attributes
63+
}
64+
65+
// defaultsCommandAPI defines an API to be used during testing.
66+
type defaultsCommandAPI interface {
67+
// Close closes the api connection.
68+
Close() error
69+
70+
// ModelConfig returns all known configuration attributes and values.
71+
ModelGet() (map[string]interface{}, error)
72+
73+
// ModelDefaults returns the default config values used when creating a new model.
74+
ModelDefaults() (config.ModelDefaultAttributes, error)
75+
76+
// SetModelDefaults sets the default config values to use
77+
// when creating new models.
78+
SetModelDefaults(cloud, region string, config map[string]interface{}) error
79+
80+
// UnsetModelDefaults clears the default model
81+
// configuration values.
82+
UnsetModelDefaults(cloud, region string, keys ...string) error
83+
}
84+
85+
// Info implements part of the cmd.Command interface.
86+
func (c *defaultsCommand) Info() *cmd.Info {
87+
return &cmd.Info{
88+
Args: "[<model-key>[<=value>] ...]",
89+
Doc: modelDefaultsHelpDoc,
90+
Name: "model-defaults",
91+
Purpose: modelDefaultsSummary,
92+
}
93+
}
94+
95+
// SetFlags implements part of the cmd.Command interface.
96+
func (c *defaultsCommand) SetFlags(f *gnuflag.FlagSet) {
97+
c.ModelCommandBase.SetFlags(f)
98+
99+
c.out.AddFlags(f, "tabular", map[string]cmd.Formatter{
100+
"yaml": cmd.FormatYaml,
101+
"json": cmd.FormatJson,
102+
"tabular": formatDefaultConfigTabular,
103+
})
104+
f.BoolVar(&c.reset, "reset", false, "Reset the provided keys to be empty")
105+
}
106+
107+
// Init implements part of the cmd.Command interface.
108+
func (c *defaultsCommand) Init(args []string) error {
109+
if c.reset {
110+
// We're resetting defaults.
111+
if len(args) == 0 {
112+
return errors.New("no keys specified")
113+
}
114+
for _, k := range args {
115+
if k == config.AgentVersionKey {
116+
return errors.Errorf("%q cannot be reset", config.AgentVersionKey)
117+
}
118+
}
119+
c.keys = args
120+
121+
c.action = c.resetDefaults
122+
return nil
123+
}
124+
125+
if len(args) > 0 && strings.Contains(args[0], "=") {
126+
// We're setting defaults.
127+
options, err := keyvalues.Parse(args, true)
128+
if err != nil {
129+
return errors.Trace(err)
130+
}
131+
c.values = make(attributes)
132+
for k, v := range options {
133+
if k == config.AgentVersionKey {
134+
return errors.Errorf(`%q must be set via "upgrade-juju"`, config.AgentVersionKey)
135+
}
136+
c.values[k] = v
137+
}
138+
139+
c.action = c.setDefaults
140+
return nil
141+
142+
}
143+
// We're getting defaults.
144+
val, err := cmd.ZeroOrOneArgs(args)
145+
if err != nil {
146+
return errors.New("can only retrieve a single value, or all values")
147+
}
148+
if val != "" {
149+
c.keys = []string{val}
150+
}
151+
c.action = c.getDefaults
152+
return nil
153+
}
154+
155+
// getAPI sets the api on the command. This allows passing in a test
156+
// ModelDefaultsAPI implementation.
157+
func (c *defaultsCommand) getAPI() (func(), error) {
158+
if c.api != nil {
159+
return func() { c.api.Close() }, nil
160+
}
161+
162+
api, err := c.NewAPIRoot()
163+
if err != nil {
164+
return nil, errors.Annotate(err, "opening API connection")
165+
}
166+
c.api = modelconfig.NewClient(api)
167+
168+
return func() { c.api.Close() }, nil
169+
}
170+
171+
// Run implements part of the cmd.Command interface.
172+
func (c *defaultsCommand) Run(ctx *cmd.Context) error {
173+
apiCloser, err := c.getAPI()
174+
if err != nil {
175+
return errors.Trace(err)
176+
}
177+
defer apiCloser()
178+
179+
return c.action(ctx)
180+
}
181+
182+
func (c *defaultsCommand) getDefaults(ctx *cmd.Context) error {
183+
attrs, err := c.api.ModelDefaults()
184+
if err != nil {
185+
return err
186+
}
187+
188+
if len(c.keys) == 1 {
189+
key := c.keys[0]
190+
if value, ok := attrs[key]; ok {
191+
attrs = config.ModelDefaultAttributes{
192+
key: value,
193+
}
194+
} else {
195+
return errors.Errorf("key %q not found in %q model defaults.", key, attrs["name"])
196+
}
197+
}
198+
// If c.keys is empty, write out the whole lot.
199+
return c.out.Write(ctx, attrs)
200+
}
201+
202+
func (c *defaultsCommand) setDefaults(ctx *cmd.Context) error {
203+
// ctx unused in this method.
204+
if err := c.verifyKnownKeys(); err != nil {
205+
return errors.Trace(err)
206+
}
207+
// TODO(wallyworld) - call with cloud and region when that bit is done
208+
return block.ProcessBlockedError(c.api.SetModelDefaults("", "", c.values), block.BlockChange)
209+
}
210+
211+
func (c *defaultsCommand) resetDefaults(ctx *cmd.Context) error {
212+
// ctx unused in this method.
213+
if err := c.verifyKnownKeys(); err != nil {
214+
return errors.Trace(err)
215+
}
216+
// TODO(wallyworld) - call with cloud and region when that bit is done
217+
return block.ProcessBlockedError(c.api.UnsetModelDefaults("", "", c.keys...), block.BlockChange)
218+
219+
}
220+
221+
// verifyKnownKeys is a helper to validate the keys we are operating with
222+
// against the set of known attributes from the model.
223+
func (c *defaultsCommand) verifyKnownKeys() error {
224+
known, err := c.api.ModelGet()
225+
if err != nil {
226+
return errors.Trace(err)
227+
}
228+
keys := func() []string {
229+
if c.keys != nil {
230+
return c.keys
231+
}
232+
keys := []string{}
233+
for k, _ := range c.values {
234+
keys = append(keys, k)
235+
}
236+
return keys
237+
}
238+
for _, key := range keys() {
239+
// check if the key exists in the known config
240+
// and warn the user if the key is not defined
241+
if _, exists := known[key]; !exists {
242+
logger.Warningf(
243+
"key %q is not defined in the known model configuration: possible misspelling", key)
244+
}
245+
}
246+
return nil
247+
}
248+
249+
// formatConfigTabular writes a tabular summary of default config information.
250+
func formatDefaultConfigTabular(writer io.Writer, value interface{}) error {
251+
defaultValues, ok := value.(config.ModelDefaultAttributes)
252+
if !ok {
253+
return errors.Errorf("expected value of type %T, got %T", defaultValues, value)
254+
}
255+
256+
tw := output.TabWriter(writer)
257+
ph := output.TabWriterPrintln(tw)
258+
259+
p := func(name string, value config.AttributeDefaultValues) {
260+
var c, d interface{}
261+
switch value.Default {
262+
case nil:
263+
d = "-"
264+
case "":
265+
d = `""`
266+
default:
267+
d = value.Default
268+
}
269+
switch value.Controller {
270+
case nil:
271+
c = "-"
272+
case "":
273+
c = `""`
274+
default:
275+
c = value.Controller
276+
}
277+
row := fmt.Sprintf("%s\t%v\t%v", name, d, c)
278+
fmt.Fprintln(tw, row)
279+
for _, region := range value.Regions {
280+
row := fmt.Sprintf(" %s\t%v\t-", region.Name, region.Value)
281+
fmt.Fprintln(tw, row)
282+
}
283+
}
284+
var valueNames []string
285+
for name := range defaultValues {
286+
valueNames = append(valueNames, name)
287+
}
288+
sort.Strings(valueNames)
289+
290+
ph("ATTRIBUTE", "DEFAULT", "CONTROLLER")
291+
292+
for _, name := range valueNames {
293+
info := defaultValues[name]
294+
out := &bytes.Buffer{}
295+
err := cmd.FormatYaml(out, info)
296+
if err != nil {
297+
return errors.Annotatef(err, "formatting value for %q", name)
298+
}
299+
p(name, info)
300+
}
301+
302+
tw.Flush()
303+
return nil
304+
}

0 commit comments

Comments
 (0)