Merge lp:~wallyworld/juju-core/set-env-boolean into lp:~go-bot/juju-core/trunk

Proposed by Ian Booth
Status: Merged
Approved by: Ian Booth
Approved revision: no longer in the source branch.
Merged at revision: 2103
Proposed branch: lp:~wallyworld/juju-core/set-env-boolean
Merge into: lp:~go-bot/juju-core/trunk
Diff against target: 184 lines (+75/-23)
4 files modified
cmd/juju/environment_test.go (+13/-1)
environs/config/config_test.go (+2/-2)
schema/schema.go (+34/-10)
schema/schema_test.go (+26/-10)
To merge this branch: bzr merge lp:~wallyworld/juju-core/set-env-boolean
Reviewer Review Type Date Requested Status
Juju Engineering Pending
Review via email: mp+196819@code.launchpad.net

Commit message

Allow strings as bool/int config values

Fix the parsing of config values to allow suitable
string values to be used to set bool and int
attributes. eg "true" mean true, "42" means 42 etc.
This is needed to allow juju set-env to work for those
types of values.

https://codereview.appspot.com/33680043/

Description of the change

Allow strings as bool/int config values

Fix the parsing of config values to allow suitable
string values to be used to set bool and int
attributes. eg "true" mean true, "42" means 42 etc.
This is needed to allow juju set-env to work for those
types of values.

https://codereview.appspot.com/33680043/

To post a comment you must log in.
Revision history for this message
Ian Booth (wallyworld) wrote :
Download full text (6.2 KiB)

Reviewers: mp+196819_code.launchpad.net,

Message:
Please take a look.

Description:
Allow strings as bool/int config values

Fix the parsing of config values to allow suitable
string values to be used to set bool and int
attributes. eg "true" mean true, "42" means 42 etc.
This is needed to allow juju set-env to work for those
types of values.

https://code.launchpad.net/~wallyworld/juju-core/set-env-boolean/+merge/196819

(do not edit description out of merge proposal)

Please review this at https://codereview.appspot.com/33680043/

Affected files (+61, -34 lines):
   A [revision details]
   M cmd/juju/environment_test.go
   M schema/schema.go
   M schema/schema_test.go

Index: [revision details]
=== added file '[revision details]'
--- [revision details] 2012-01-01 00:00:00 +0000
+++ [revision details] 2012-01-01 00:00:00 +0000
@@ -0,0 +1,2 @@
+Old revision: tarmac-20131126164544-1janyqs4x0e2mt9l
+New revision: <email address hidden>

Index: schema/schema.go
=== modified file 'schema/schema.go'
--- schema/schema.go 2013-09-26 23:26:19 +0000
+++ schema/schema.go 2013-11-27 05:21:41 +0000
@@ -102,8 +102,11 @@
  type boolC struct{}

  func (c boolC) Coerce(v interface{}, path []string) (interface{}, error) {
- if v != nil && reflect.TypeOf(v).Kind() == reflect.Bool {
- return v, nil
+ if v != nil {
+ val, err := strconv.ParseBool(fmt.Sprintf("%v", v))
+ if err == nil {
+ return val, nil
+ }
   }
   return nil, error_{"bool", v, path}
  }
@@ -117,19 +120,13 @@
  type intC struct{}

  func (c intC) Coerce(v interface{}, path []string) (interface{}, error) {
- if v == nil {
- return nil, error_{"int", v, path}
- }
- switch reflect.TypeOf(v).Kind() {
- case reflect.Int:
- case reflect.Int8:
- case reflect.Int16:
- case reflect.Int32:
- case reflect.Int64:
- default:
- return nil, error_{"int", v, path}
- }
- return reflect.ValueOf(v).Int(), nil
+ if v != nil {
+ val, err := strconv.ParseInt(fmt.Sprintf("%v", v), 0, 64)
+ if err == nil {
+ return val, nil
+ }
+ }
+ return nil, error_{"int", v, path}
  }

  // ForceInt returns a Checker that accepts any integer or float value, and
@@ -143,14 +140,18 @@
  type forceIntC struct{}

  func (c forceIntC) Coerce(v interface{}, path []string) (interface{},
error) {
- if v == nil {
- return nil, error_{"number", v, path}
- }
- switch reflect.TypeOf(v).Kind() {
- case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
reflect.Int64:
- return int(reflect.ValueOf(v).Int()), nil
- case reflect.Float32, reflect.Float64:
- return int(reflect.ValueOf(v).Float()), nil
+ if v != nil {
+ switch reflect.TypeOf(v).Kind() {
+ case reflect.String:
+ intValue, err := strconv.ParseInt(fmt.Sprintf("%v", v), 0, 64)
+ if err == nil {
+ return int(intValue), nil
+ }
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
reflect.Int64:
+ return int(reflect.ValueOf(v).Int()), nil
+ case reflect.Float32, reflect.Float64:
+ return int(reflect.ValueOf(v).Float()), nil
+ }
   }
   return nil, error_{"number", v, path}
  }

Index: schema/schema_test.go
=== modified file 'schema/schema_test.go'
--- schema/schema_test.go 2013-09-27 01:50:46 +0000
...

Read more...

Revision history for this message
Roger Peppe (rogpeppe) wrote :

Looks good, thanks, with a few suggestions below.

https://codereview.appspot.com/33680043/diff/1/schema/schema.go
File schema/schema.go (right):

https://codereview.appspot.com/33680043/diff/1/schema/schema.go#newcode105
schema/schema.go:105: if v != nil {
I think I'd be inclined to do something like this:

func (c boolC) Coerce(v interface{}, path []string) (interface{}, error)
{
     switch vv := reflect.TypeOf(v); vv.Kind() {
     case reflect.Bool:
         return vv.Bool(), nil
     case reflect.String:
         val, err := strconv.ParseBool(vv.String())
         if err == nil {
             return val, nil
         }
     }
     return nil, error{"bool", v, path}
}

the Sprintf seems unnecessary overkill.

https://codereview.appspot.com/33680043/diff/1/schema/schema.go#newcode123
schema/schema.go:123: if v != nil {
similar here

https://codereview.appspot.com/33680043/diff/1/schema/schema.go#newcode146
schema/schema.go:146: intValue, err :=
strconv.ParseInt(fmt.Sprintf("%v", v), 0, 64)
Again, no need to use Sprintf. And given that we allow float values, we
should probably allow float string values too.

switch vv := reflect.ValueOf(v); vv.Kind() {
case reflect.String:
    intValue, err := strconv.ParseInt(vv.String(), 0, 64)
    if err == nil {
        return int(intValue), nil
    }
    floatValue, err := strconv.ParseFloat(vv.String(), 0, 64)
    if err = nil {
        return int(floatValue), nil
    }
case reflect.Int, reflect.Int8, etc etc

https://codereview.appspot.com/33680043/diff/1/schema/schema_test.go
File schema/schema_test.go (right):

https://codereview.appspot.com/33680043/diff/1/schema/schema_test.go#newcode92
schema/schema_test.go:92: c.Assert(err, gc.ErrorMatches, `<path>:
expected bool, got int\(42\)`)
could we have a string value that fails too here, please?

https://codereview.appspot.com/33680043/diff/1/schema/schema_test.go#newcode161
schema/schema_test.go:161: c.Assert(err, gc.ErrorMatches, "<path>:
expected number, got nothing")
ditto.
and a test for a string containing a float value.

https://codereview.appspot.com/33680043/

Revision history for this message
Ian Booth (wallyworld) wrote :

https://codereview.appspot.com/33680043/diff/1/schema/schema.go
File schema/schema.go (right):

https://codereview.appspot.com/33680043/diff/1/schema/schema.go#newcode105
schema/schema.go:105: if v != nil {
On 2013/11/27 09:00:08, rog wrote:
> I think I'd be inclined to do something like this:

> func (c boolC) Coerce(v interface{}, path []string) (interface{},
error) {
> switch vv := reflect.TypeOf(v); vv.Kind() {
> case reflect.Bool:
> return vv.Bool(), nil
> case reflect.String:
> val, err := strconv.ParseBool(vv.String())
> if err == nil {
> return val, nil
> }
> }
> return nil, error{"bool", v, path}
> }

> the Sprintf seems unnecessary overkill.

Why all that logic when strconv.ParseBool() does what we need with fewer
lines of code required? I can't see a reason for the reflection etc.

https://codereview.appspot.com/33680043/

Revision history for this message
Roger Peppe (rogpeppe) wrote :

On 27 November 2013 09:40, <email address hidden> wrote:
>
> https://codereview.appspot.com/33680043/diff/1/schema/schema.go
> File schema/schema.go (right):
>
> https://codereview.appspot.com/33680043/diff/1/schema/schema.go#newcode105
> schema/schema.go:105: if v != nil {
> On 2013/11/27 09:00:08, rog wrote:
>>
>> I think I'd be inclined to do something like this:
>
>
>> func (c boolC) Coerce(v interface{}, path []string) (interface{},
>
> error) {
>>
>> switch vv := reflect.TypeOf(v); vv.Kind() {
>> case reflect.Bool:
>> return vv.Bool(), nil
>> case reflect.String:
>> val, err := strconv.ParseBool(vv.String())
>> if err == nil {
>> return val, nil
>> }
>> }
>> return nil, error{"bool", v, path}
>> }
>
>
>> the Sprintf seems unnecessary overkill.
>
>
> Why all that logic when strconv.ParseBool() does what we need with fewer
> lines of code required? I can't see a reason for the reflection etc.

For a start, my suggested code is more efficient and does no
unnecessary allocation, which is a good property to have in
a low level part of the code like this.

Secondly, your code will accept any type at all, as long as Sprintf
happens to marshal it to something that looks like a bool.
I'd much prefer to keep things more limited here, so that we
know exactly what types we accept.

My suggested code might be slightly longer, but it's much more
explicit in its behaviour.

Revision history for this message
William Reade (fwereade) wrote :

On 2013/11/27 10:14:39, rog wrote:
> Secondly, your code will accept any type at all, as long as Sprintf
> happens to marshal it to something that looks like a bool.
> I'd much prefer to keep things more limited here, so that we
> know exactly what types we accept.

For me, that's the clincher. I think there's actually a pretty strong
argument to be made for maximally-forgiving checkers in the schema
package, but I'd prefer to keep the change as limited as possible given
that we're rolling it into 1.16 and I don't want unnecessary divergence.

FWIW, roger, I am very much unconvinced by the efficiency argument here
when viewed in the light of the opinions you expressed earlier today re
rpc calls ;p.

So, with the reflect-based version, LGTM.

https://codereview.appspot.com/33680043/

Revision history for this message
Go Bot (go-bot) wrote :
Download full text (13.7 KiB)

The attempt to merge lp:~wallyworld/juju-core/set-env-boolean into lp:juju-core failed. Below is the output from the failed tests.

ok launchpad.net/juju-core/agent 0.742s
ok launchpad.net/juju-core/agent/tools 0.226s
ok launchpad.net/juju-core/bzr 6.865s
ok launchpad.net/juju-core/cert 3.843s
ok launchpad.net/juju-core/charm 0.578s
? launchpad.net/juju-core/charm/hooks [no test files]
ok launchpad.net/juju-core/cloudinit 0.027s
ok launchpad.net/juju-core/cmd 0.220s
? launchpad.net/juju-core/cmd/charmd [no test files]
? launchpad.net/juju-core/cmd/charmload [no test files]
ok launchpad.net/juju-core/cmd/juju 194.242s
ok launchpad.net/juju-core/cmd/jujud 56.731s
ok launchpad.net/juju-core/cmd/plugins/juju-metadata 2.815s
ok launchpad.net/juju-core/constraints 0.028s
ok launchpad.net/juju-core/container 0.034s
ok launchpad.net/juju-core/container/factory 0.074s
ok launchpad.net/juju-core/container/kvm 0.319s
ok launchpad.net/juju-core/container/kvm/mock 0.034s
? launchpad.net/juju-core/container/kvm/testing [no test files]
ok launchpad.net/juju-core/container/lxc 0.343s
? launchpad.net/juju-core/container/lxc/mock [no test files]
? launchpad.net/juju-core/container/lxc/testing [no test files]
ok launchpad.net/juju-core/downloader 5.312s
ok launchpad.net/juju-core/environs 3.167s
ok launchpad.net/juju-core/environs/bootstrap 4.996s
ok launchpad.net/juju-core/environs/cloudinit 0.544s

----------------------------------------------------------------------
FAIL: config_test.go:581: ConfigSuite.TestConfig

test 0. The minimum good configuration
test 1. Metadata URLs
test 2. Deprecated tools metadata URL used
[LOG] 47.94224 WARNING juju.environs.config Config attribute "tools-url" (tools-metadata-url-value) is deprecated.
The location to find tools is now specified using the "tools-metadata-url" attribute.
Your configuration should be updated to set "tools-metadata-url" as follows
tools-metadata-url: tools-metadata-url-value.
test 3. Deprecated tools metadata URL ignored
[LOG] 47.96187 WARNING juju.environs.config Config attribute "tools-url" (ignore-me) is deprecated and will be ignored since
the new tools URL attribute "tools-metadata-url" has also been used.
The attribute "tools-url" should be removed from your configuration.
test 4. Explicit series
test 5. Implicit series with empty value
test 6. Explicit logging
test 7. Explicit authorized-keys
test 8. Load authorized-keys from path
test 9. CA cert & key from path
test 10. CA cert & key from path; cert attribute set too
test 11. CA cert & key from ~ path
test 12. CA cert and key as attributes
test 13. Mismatched CA cert and key
test 14. Invalid CA cert
test 15. Invalid CA key
test 16. CA cert specified as non-existent file
test 17. CA key specified as non-existent file
test 18. Specified agent version
test 19. Specified development flag
test 20. Specified admin secret
test 21. Invalid development flag
config_test.go:600:
    test.check(c, h)
config_test.go:705:
    c.Check(cfg, gc.IsNil)
... value *config.Config = &config.Config{m:map[string]interface {}{"admin-secret":"", "logging-config":"<root>=DEBUG", "syslog-port":514, "tools-url":"...

Revision history for this message
Ian Booth (wallyworld) wrote :

https://codereview.appspot.com/33680043/diff/1/schema/schema.go
File schema/schema.go (right):

https://codereview.appspot.com/33680043/diff/1/schema/schema.go#newcode105
schema/schema.go:105: if v != nil {
On 2013/11/27 09:00:08, rog wrote:
> I think I'd be inclined to do something like this:

> func (c boolC) Coerce(v interface{}, path []string) (interface{},
error) {
> switch vv := reflect.TypeOf(v); vv.Kind() {
> case reflect.Bool:
> return vv.Bool(), nil
> case reflect.String:
> val, err := strconv.ParseBool(vv.String())
> if err == nil {
> return val, nil
> }
> }
> return nil, error{"bool", v, path}
> }

> the Sprintf seems unnecessary overkill.

Done.

https://codereview.appspot.com/33680043/diff/1/schema/schema.go#newcode123
schema/schema.go:123: if v != nil {
On 2013/11/27 09:00:08, rog wrote:
> similar here

Done.

https://codereview.appspot.com/33680043/

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'cmd/juju/environment_test.go'
2--- cmd/juju/environment_test.go 2013-09-12 12:38:04 +0000
3+++ cmd/juju/environment_test.go 2013-11-28 01:04:32 +0000
4@@ -129,6 +129,15 @@
5 c.Assert(stateConfig.DefaultSeries(), gc.Equals, "raring")
6 }
7
8+func (s *SetEnvironmentSuite) TestChangeBooleanAttribute(c *gc.C) {
9+ _, err := testing.RunCommand(c, &SetEnvironmentCommand{}, []string{"ssl-hostname-verification=false"})
10+ c.Assert(err, gc.IsNil)
11+
12+ stateConfig, err := s.State.EnvironConfig()
13+ c.Assert(err, gc.IsNil)
14+ c.Assert(stateConfig.SSLHostnameVerification(), gc.Equals, false)
15+}
16+
17 func (s *SetEnvironmentSuite) TestChangeMultipleValues(c *gc.C) {
18 _, err := testing.RunCommand(c, &SetEnvironmentCommand{}, []string{"default-series=spartan", "broken=nope", "secret=sekrit"})
19 c.Assert(err, gc.IsNil)
20@@ -156,13 +165,16 @@
21 "name": "foo",
22 "type": "foo",
23 "firewall-mode": "global",
24+ "state-port": "1",
25+ "api-port": "666",
26+ "syslog-port": "42",
27 }
28
29 func (s *SetEnvironmentSuite) TestImmutableConfigValues(c *gc.C) {
30 for name, value := range immutableConfigTests {
31 param := fmt.Sprintf("%s=%s", name, value)
32 _, err := testing.RunCommand(c, &SetEnvironmentCommand{}, []string{param})
33- errorPattern := fmt.Sprintf("cannot change %s from .* to %q", name, value)
34+ errorPattern := fmt.Sprintf("cannot change %s from .* to [\"]?%v[\"]?", name, value)
35 c.Assert(err, gc.ErrorMatches, errorPattern)
36 }
37 }
38
39=== modified file 'environs/config/config_test.go'
40--- environs/config/config_test.go 2013-11-27 15:26:18 +0000
41+++ environs/config/config_test.go 2013-11-28 01:04:32 +0000
42@@ -303,9 +303,9 @@
43 "type": "my-type",
44 "name": "my-name",
45 "authorized-keys": "my-keys",
46- "development": "true",
47+ "development": "invalid",
48 },
49- err: `development: expected bool, got string\("true"\)`,
50+ err: `development: expected bool, got string\("invalid"\)`,
51 }, {
52 about: "Invalid agent version",
53 useDefaults: config.UseDefaults,
54
55=== modified file 'schema/schema.go'
56--- schema/schema.go 2013-09-26 23:26:19 +0000
57+++ schema/schema.go 2013-11-28 01:04:32 +0000
58@@ -102,8 +102,16 @@
59 type boolC struct{}
60
61 func (c boolC) Coerce(v interface{}, path []string) (interface{}, error) {
62- if v != nil && reflect.TypeOf(v).Kind() == reflect.Bool {
63- return v, nil
64+ if v != nil {
65+ switch reflect.TypeOf(v).Kind() {
66+ case reflect.Bool:
67+ return v, nil
68+ case reflect.String:
69+ val, err := strconv.ParseBool(reflect.ValueOf(v).String())
70+ if err == nil {
71+ return val, nil
72+ }
73+ }
74 }
75 return nil, error_{"bool", v, path}
76 }
77@@ -126,6 +134,13 @@
78 case reflect.Int16:
79 case reflect.Int32:
80 case reflect.Int64:
81+ case reflect.String:
82+ val, err := strconv.ParseInt(reflect.ValueOf(v).String(), 0, 64)
83+ if err == nil {
84+ return val, nil
85+ } else {
86+ return nil, error_{"int", v, path}
87+ }
88 default:
89 return nil, error_{"int", v, path}
90 }
91@@ -143,14 +158,23 @@
92 type forceIntC struct{}
93
94 func (c forceIntC) Coerce(v interface{}, path []string) (interface{}, error) {
95- if v == nil {
96- return nil, error_{"number", v, path}
97- }
98- switch reflect.TypeOf(v).Kind() {
99- case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
100- return int(reflect.ValueOf(v).Int()), nil
101- case reflect.Float32, reflect.Float64:
102- return int(reflect.ValueOf(v).Float()), nil
103+ if v != nil {
104+ switch vv := reflect.TypeOf(v); vv.Kind() {
105+ case reflect.String:
106+ vstr := reflect.ValueOf(v).String()
107+ intValue, err := strconv.ParseInt(vstr, 0, 64)
108+ if err == nil {
109+ return int(intValue), nil
110+ }
111+ floatValue, err := strconv.ParseFloat(vstr, 64)
112+ if err == nil {
113+ return int(floatValue), nil
114+ }
115+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
116+ return int(reflect.ValueOf(v).Int()), nil
117+ case reflect.Float32, reflect.Float64:
118+ return int(reflect.ValueOf(v).Float()), nil
119+ }
120 }
121 return nil, error_{"number", v, path}
122 }
123
124=== modified file 'schema/schema_test.go'
125--- schema/schema_test.go 2013-09-27 01:50:46 +0000
126+++ schema/schema_test.go 2013-11-28 01:04:32 +0000
127@@ -75,17 +75,21 @@
128 func (s *S) TestBool(c *gc.C) {
129 sch := schema.Bool()
130
131- out, err := sch.Coerce(true, aPath)
132- c.Assert(err, gc.IsNil)
133- c.Assert(out, gc.Equals, true)
134-
135- out, err = sch.Coerce(false, aPath)
136- c.Assert(err, gc.IsNil)
137- c.Assert(out, gc.Equals, false)
138-
139- out, err = sch.Coerce(1, aPath)
140+ for _, trueValue := range []interface{}{true, "1", "true", "True", "TRUE"} {
141+ out, err := sch.Coerce(trueValue, aPath)
142+ c.Assert(err, gc.IsNil)
143+ c.Assert(out, gc.Equals, true)
144+ }
145+
146+ for _, falseValue := range []interface{}{false, "0", "false", "False", "FALSE"} {
147+ out, err := sch.Coerce(falseValue, aPath)
148+ c.Assert(err, gc.IsNil)
149+ c.Assert(out, gc.Equals, false)
150+ }
151+
152+ out, err := sch.Coerce(42, aPath)
153 c.Assert(out, gc.IsNil)
154- c.Assert(err, gc.ErrorMatches, `<path>: expected bool, got int\(1\)`)
155+ c.Assert(err, gc.ErrorMatches, `<path>: expected bool, got int\(42\)`)
156
157 out, err = sch.Coerce(nil, aPath)
158 c.Assert(out, gc.IsNil)
159@@ -103,6 +107,10 @@
160 c.Assert(err, gc.IsNil)
161 c.Assert(out, gc.Equals, int64(42))
162
163+ out, err = sch.Coerce("42", aPath)
164+ c.Assert(err, gc.IsNil)
165+ c.Assert(out, gc.Equals, int64(42))
166+
167 out, err = sch.Coerce(true, aPath)
168 c.Assert(out, gc.IsNil)
169 c.Assert(err, gc.ErrorMatches, `<path>: expected int, got bool\(true\)`)
170@@ -119,6 +127,14 @@
171 c.Assert(err, gc.IsNil)
172 c.Assert(out, gc.Equals, int(42))
173
174+ out, err = sch.Coerce("42", aPath)
175+ c.Assert(err, gc.IsNil)
176+ c.Assert(out, gc.Equals, int(42))
177+
178+ out, err = sch.Coerce("42.66", aPath)
179+ c.Assert(err, gc.IsNil)
180+ c.Assert(out, gc.Equals, int(42))
181+
182 out, err = sch.Coerce(int8(42), aPath)
183 c.Assert(err, gc.IsNil)
184 c.Assert(out, gc.Equals, int(42))

Subscribers

People subscribed via source and target branches

to status/vote changes: