Merge lp:~pedronis/ubuntu-push/server-lego-preps into lp:ubuntu-push

Proposed by Samuele Pedroni
Status: Merged
Approved by: Samuele Pedroni
Approved revision: 17
Merged at revision: 16
Proposed branch: lp:~pedronis/ubuntu-push/server-lego-preps
Merge into: lp:ubuntu-push
Diff against target: 398 lines (+212/-56)
7 files modified
config/config.go (+95/-21)
config/config_test.go (+55/-0)
server/acceptance/acceptance_test.go (+4/-14)
server/listener/listener.go (+3/-0)
server/listener/listener_test.go (+2/-21)
testing/helpers.go (+12/-0)
testing/tls.go (+41/-0)
To merge this branch: bzr merge lp:~pedronis/ubuntu-push/server-lego-preps
Reviewer Review Type Date Requested Status
John Lenton (community) Approve
Review via email: mp+202625@code.launchpad.net

Commit message

teach config how to behave with anonymous fields, unexported fields, and to compose a config reading from many files; move some shared/sharable things into testing/

Description of the change

* teach config how to behave with anonymous fields, unexported fields, and to compose a config reading from many files

* move some shared/sharable things into testing/

To post a comment you must log in.
Revision history for this message
John Lenton (chipaca) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'config/config.go'
2--- config/config.go 2014-01-14 15:35:20 +0000
3+++ config/config.go 2014-01-22 09:46:32 +0000
4@@ -24,43 +24,69 @@
5 "io"
6 "io/ioutil"
7 "net"
8+ "os"
9 "path/filepath"
10 "reflect"
11 "strings"
12 "time"
13 )
14
15-// ReadConfig reads a JSON configuration into destConfig which should
16-// be a pointer to a structure, it does some more configuration
17-// specific error checking than plain JSON decoding and mentions
18-// fields in errors . Configuration fields are expected to start with
19-// lower case in the JSON object.
20-func ReadConfig(r io.Reader, destConfig interface{}) error {
21+func checkDestConfig(destConfig interface{}) (reflect.Value, error) {
22 destValue := reflect.ValueOf(destConfig)
23 if destValue.Kind() != reflect.Ptr || destValue.Elem().Kind() != reflect.Struct {
24- return errors.New("destConfig not *struct")
25- }
26- // do the parsing in two phases for better error handling
27- var p1 map[string]json.RawMessage
28- err := json.NewDecoder(r).Decode(&p1)
29- if err != nil {
30- return err
31- }
32+ return reflect.Value{}, errors.New("destConfig not *struct")
33+ }
34+ return destValue, nil
35+}
36+
37+type destField struct {
38+ fld reflect.StructField
39+ dest interface{}
40+}
41+
42+func traverseStruct(destStruct reflect.Value) <-chan destField {
43+ ch := make(chan destField)
44+ var traverse func(reflect.Value, chan<- destField)
45+ traverse = func(destStruct reflect.Value, ch chan<- destField) {
46+ structType := destStruct.Type()
47+ n := structType.NumField()
48+ for i := 0; i < n; i++ {
49+ fld := structType.Field(i)
50+ val := destStruct.Field(i)
51+ if fld.PkgPath != "" { // unexported
52+ continue
53+ }
54+ if fld.Anonymous {
55+ traverse(val, ch)
56+ continue
57+ }
58+ ch <- destField{
59+ fld: fld,
60+ dest: val.Addr().Interface(),
61+ }
62+ }
63+ }
64+ go func() {
65+ traverse(destStruct, ch)
66+ close(ch)
67+ }()
68+ return ch
69+}
70+
71+func fillDestConfig(destValue reflect.Value, p map[string]json.RawMessage) error {
72 destStruct := destValue.Elem()
73- structType := destStruct.Type()
74- n := structType.NumField()
75- for i := 0; i < n; i++ {
76- fld := structType.Field(i)
77+ for destField := range traverseStruct(destStruct) {
78+ fld := destField.fld
79 configName := strings.Split(fld.Tag.Get("json"), ",")[0]
80 if configName == "" {
81 configName = strings.ToLower(fld.Name[:1]) + fld.Name[1:]
82 }
83- raw, found := p1[configName]
84+ raw, found := p[configName]
85 if !found { // assume all fields are mandatory for now
86 return fmt.Errorf("missing %s", configName)
87 }
88- dest := destStruct.Field(i).Addr().Interface()
89- err = json.Unmarshal([]byte(raw), dest)
90+ dest := destField.dest
91+ err := json.Unmarshal([]byte(raw), dest)
92 if err != nil {
93 return fmt.Errorf("%s: %v", configName, err)
94 }
95@@ -68,6 +94,25 @@
96 return nil
97 }
98
99+// ReadConfig reads a JSON configuration into destConfig which should
100+// be a pointer to a structure, it does some more configuration
101+// specific error checking than plain JSON decoding and mentions
102+// fields in errors . Configuration fields are expected to start with
103+// lower case in the JSON object.
104+func ReadConfig(r io.Reader, destConfig interface{}) error {
105+ destValue, err := checkDestConfig(destConfig)
106+ if err != nil {
107+ return err
108+ }
109+ // do the parsing in two phases for better error handling
110+ var p1 map[string]json.RawMessage
111+ err = json.NewDecoder(r).Decode(&p1)
112+ if err != nil {
113+ return err
114+ }
115+ return fillDestConfig(destValue, p1)
116+}
117+
118 // ConfigTimeDuration can hold a time.Duration in a configuration struct,
119 // that is parsed from a string as supported by time.ParseDuration.
120 type ConfigTimeDuration struct {
121@@ -147,3 +192,32 @@
122 }
123 return ioutil.ReadFile(p)
124 }
125+
126+// ReadFiles reads configuration from a set of files. Uses ReadConfig internally.
127+func ReadFiles(destConfig interface{}, cfgFpaths ...string) error {
128+ destValue, err := checkDestConfig(destConfig)
129+ if err != nil {
130+ return err
131+ }
132+ // do the parsing in two phases for better error handling
133+ var p1 map[string]json.RawMessage
134+ readOne := false
135+ for _, cfgPath := range cfgFpaths {
136+ if _, err := os.Stat(cfgPath); err == nil {
137+ r, err := os.Open(cfgPath)
138+ if err != nil {
139+ return err
140+ }
141+ defer r.Close()
142+ err = json.NewDecoder(r).Decode(&p1)
143+ if err != nil {
144+ return err
145+ }
146+ readOne = true
147+ }
148+ }
149+ if !readOne {
150+ return fmt.Errorf("no config to read")
151+ }
152+ return fillDestConfig(destValue, p1)
153+}
154
155=== modified file 'config/config_test.go'
156--- config/config_test.go 2014-01-14 15:35:20 +0000
157+++ config/config_test.go 2014-01-22 09:46:32 +0000
158@@ -22,6 +22,7 @@
159 . "launchpad.net/gocheck"
160 "os"
161 "path/filepath"
162+ "reflect"
163 "testing"
164 "time"
165 )
166@@ -133,3 +134,57 @@
167 c.Check(err, IsNil)
168 c.Check(string(d), Equals, "Example")
169 }
170+
171+func (s *configSuite) TestReadFiles(c *C) {
172+ tmpDir := c.MkDir()
173+ cfg1Path := filepath.Join(tmpDir, "cfg1.json")
174+ err := ioutil.WriteFile(cfg1Path, []byte(`{"a": 42}`), os.ModePerm)
175+ c.Assert(err, IsNil)
176+ cfg2Path := filepath.Join(tmpDir, "cfg2.json")
177+ err = ioutil.WriteFile(cfg2Path, []byte(`{"b": "x", "c_list": ["y", "z"]}`), os.ModePerm)
178+ c.Assert(err, IsNil)
179+ var cfg testConfig1
180+ err = ReadFiles(&cfg, cfg1Path, cfg2Path)
181+ c.Assert(err, IsNil)
182+ c.Check(cfg.A, Equals, 42)
183+ c.Check(cfg.B, Equals, "x")
184+ c.Check(cfg.C, DeepEquals, []string{"y", "z"})
185+}
186+
187+func (s *configSuite) TestReadFilesErrors(c *C) {
188+ var cfg testConfig1
189+ err := ReadFiles(1)
190+ c.Check(err, ErrorMatches, `destConfig not \*struct`)
191+ err = ReadFiles(&cfg, "non-existent")
192+ c.Check(err, ErrorMatches, "no config to read")
193+ err = ReadFiles(&cfg, "/root")
194+ c.Check(err, ErrorMatches, ".*permission denied")
195+ tmpDir := c.MkDir()
196+ err = ReadFiles(&cfg, tmpDir)
197+ c.Check(err, ErrorMatches, ".*is a directory")
198+ brokenCfgPath := filepath.Join(tmpDir, "b.json")
199+ err = ioutil.WriteFile(brokenCfgPath, []byte(`{"a"-`), os.ModePerm)
200+ c.Assert(err, IsNil)
201+ err = ReadFiles(&cfg, brokenCfgPath)
202+ c.Check(err, NotNil)
203+}
204+
205+type B struct {
206+ BFld int
207+}
208+
209+type A struct {
210+ AFld int
211+ B
212+ private int
213+}
214+
215+func (s *configSuite) TestTraverseStruct(c *C) {
216+ var a A
217+ var i = 1
218+ for destField := range traverseStruct(reflect.ValueOf(&a).Elem()) {
219+ *(destField.dest.(*int)) = i
220+ i++
221+ }
222+ c.Check(a, DeepEquals, A{1, B{2}, 0})
223+}
224
225=== modified file 'server/acceptance/acceptance_test.go'
226--- server/acceptance/acceptance_test.go 2014-01-17 18:29:24 +0000
227+++ server/acceptance/acceptance_test.go 2014-01-22 09:46:32 +0000
228@@ -26,6 +26,7 @@
229 . "launchpad.net/gocheck"
230 "launchpad.net/ubuntu-push/protocol"
231 "launchpad.net/ubuntu-push/server/api"
232+ helpers "launchpad.net/ubuntu-push/testing"
233 "net"
234 "net/http"
235 "os"
236@@ -52,17 +53,6 @@
237
238 var serverCmd = flag.String("server", "", "server to test")
239
240-// SourceRelative produces a path relative to the source code, makes
241-// sense only for tests when the code is available on disk.
242-// xxx later move it to a testing helpers package
243-func SourceRelative(relativePath string) string {
244- _, file, _, ok := runtime.Caller(1)
245- if !ok {
246- panic("failed to get source filename using Caller()")
247- }
248- return filepath.Join(filepath.Dir(file), relativePath)
249-}
250-
251 func testServerConfig(addr, httpAddr string) map[string]interface{} {
252 cfg := map[string]interface{}{
253 "exchange_timeout": "0.1s",
254@@ -70,8 +60,8 @@
255 "session_queue_size": 10,
256 "broker_queue_size": 100,
257 "addr": addr,
258- "key_pem_file": SourceRelative("config/testing.key"),
259- "cert_pem_file": SourceRelative("config/testing.cert"),
260+ "key_pem_file": helpers.SourceRelative("config/testing.key"),
261+ "cert_pem_file": helpers.SourceRelative("config/testing.cert"),
262 "http_addr": httpAddr,
263 "http_read_timeout": "1s",
264 "http_write_timeout": "1s",
265@@ -80,7 +70,7 @@
266 }
267
268 func testClientSession(addr string, deviceId string, reportPings bool) *ClientSession {
269- certPEMBlock, err := ioutil.ReadFile(SourceRelative("config/testing.cert"))
270+ certPEMBlock, err := ioutil.ReadFile(helpers.SourceRelative("config/testing.cert"))
271 if err != nil {
272 panic(fmt.Sprintf("could not read config/testing.cert: %v", err))
273 }
274
275=== modified file 'server/listener/listener.go'
276--- server/listener/listener.go 2014-01-16 13:43:00 +0000
277+++ server/listener/listener.go 2014-01-22 09:46:32 +0000
278@@ -50,6 +50,9 @@
279 SessionTicketsDisabled: true,
280 }
281 lst, err := tls.Listen("tcp", cfg.Addr(), tlsCfg)
282+ if err != nil {
283+ return nil, err
284+ }
285 return &DeviceListener{lst}, err
286 }
287
288
289=== modified file 'server/listener/listener_test.go'
290--- server/listener/listener_test.go 2014-01-16 13:43:00 +0000
291+++ server/listener/listener_test.go 2014-01-22 09:46:32 +0000
292@@ -58,31 +58,12 @@
293 return cfg.addr
294 }
295
296-// key&cert generated with go run /usr/lib/go/src/pkg/crypto/tls/generate_cert.go -ca -host localhost -rsa-bits 512 -duration 87600h
297-
298 func (cfg *testDevListenerCfg) KeyPEMBlock() []byte {
299- return []byte(`-----BEGIN RSA PRIVATE KEY-----
300-MIIBPAIBAAJBAPw+niki17X2qALE2A2AzE1q5dvK9CI4OduRtT9IgbFLC6psqAT2
301-1NA+QbY17nWSSpyP65zkMkwKXrbDzstwLPkCAwEAAQJAKwXbIBULScP6QA6m8xam
302-wgWbkvN41GVWqPafPV32kPBvKwSc+M1e+JR7g3/xPZE7TCELcfYi4yXEHZZI3Pbh
303-oQIhAP/UsgJbsfH1GFv8Y8qGl5l/kmwwkwHhuKvEC87Yur9FAiEA/GlQv3ZfaXnT
304-lcCFT0aL02O0RDiRYyMUG/JAZQJs6CUCIQCHO5SZYIUwxIGK5mCNxxXOAzyQSiD7
305-hqkKywf+4FvfDQIhALa0TLyqJFom0t7c4iIGAIRc8UlIYQSPiajI64+x9775AiEA
306-0v4fgSK/Rq059zW1753JjuB6aR0Uh+3RqJII4dUR1Wg=
307------END RSA PRIVATE KEY-----`)
308+ return helpers.TestKeyPEMBlock
309 }
310
311 func (cfg *testDevListenerCfg) CertPEMBlock() []byte {
312- return []byte(`-----BEGIN CERTIFICATE-----
313-MIIBYzCCAQ+gAwIBAgIBADALBgkqhkiG9w0BAQUwEjEQMA4GA1UEChMHQWNtZSBD
314-bzAeFw0xMzEyMTkyMDU1NDNaFw0yMzEyMTcyMDU1NDNaMBIxEDAOBgNVBAoTB0Fj
315-bWUgQ28wWjALBgkqhkiG9w0BAQEDSwAwSAJBAPw+niki17X2qALE2A2AzE1q5dvK
316-9CI4OduRtT9IgbFLC6psqAT21NA+QbY17nWSSpyP65zkMkwKXrbDzstwLPkCAwEA
317-AaNUMFIwDgYDVR0PAQH/BAQDAgCkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1Ud
318-EwEB/wQFMAMBAf8wGgYDVR0RBBMwEYIJbG9jYWxob3N0hwR/AAABMAsGCSqGSIb3
319-DQEBBQNBAFqiVI+Km2XPSO+pxITaPvhmuzg+XG3l1+2di3gL+HlDobocjBqRctRU
320-YySO32W07acjGJmCHUKpCJuq9X8hpmk=
321------END CERTIFICATE-----`)
322+ return helpers.TestCertPEMBlock
323 }
324
325 func (s *listenerSuite) TestDeviceListen(c *C) {
326
327=== modified file 'testing/helpers.go'
328--- testing/helpers.go 2014-01-14 15:35:20 +0000
329+++ testing/helpers.go 2014-01-22 09:46:32 +0000
330@@ -19,6 +19,8 @@
331
332 import (
333 "bytes"
334+ "path/filepath"
335+ "runtime"
336 "sync"
337 )
338
339@@ -45,3 +47,13 @@
340 defer buf.lock.Unlock()
341 return buf.Buffer.String()
342 }
343+
344+// SourceRelative produces a path relative to the source code, makes
345+// sense only for tests when the code is available on disk.
346+func SourceRelative(relativePath string) string {
347+ _, file, _, ok := runtime.Caller(1)
348+ if !ok {
349+ panic("failed to get source filename using Caller()")
350+ }
351+ return filepath.Join(filepath.Dir(file), relativePath)
352+}
353
354=== added file 'testing/tls.go'
355--- testing/tls.go 1970-01-01 00:00:00 +0000
356+++ testing/tls.go 2014-01-22 09:46:32 +0000
357@@ -0,0 +1,41 @@
358+/*
359+ Copyright 2013-2014 Canonical Ltd.
360+
361+ This program is free software: you can redistribute it and/or modify it
362+ under the terms of the GNU General Public License version 3, as published
363+ by the Free Software Foundation.
364+
365+ This program is distributed in the hope that it will be useful, but
366+ WITHOUT ANY WARRANTY; without even the implied warranties of
367+ MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
368+ PURPOSE. See the GNU General Public License for more details.
369+
370+ You should have received a copy of the GNU General Public License along
371+ with this program. If not, see <http://www.gnu.org/licenses/>.
372+*/
373+
374+package testing
375+
376+// key&cert generated with go run /usr/lib/go/src/pkg/crypto/tls/generate_cert.go -ca -host localhost -rsa-bits 512 -duration 87600h
377+var (
378+ TestKeyPEMBlock = []byte(`-----BEGIN RSA PRIVATE KEY-----
379+MIIBPAIBAAJBAPw+niki17X2qALE2A2AzE1q5dvK9CI4OduRtT9IgbFLC6psqAT2
380+1NA+QbY17nWSSpyP65zkMkwKXrbDzstwLPkCAwEAAQJAKwXbIBULScP6QA6m8xam
381+wgWbkvN41GVWqPafPV32kPBvKwSc+M1e+JR7g3/xPZE7TCELcfYi4yXEHZZI3Pbh
382+oQIhAP/UsgJbsfH1GFv8Y8qGl5l/kmwwkwHhuKvEC87Yur9FAiEA/GlQv3ZfaXnT
383+lcCFT0aL02O0RDiRYyMUG/JAZQJs6CUCIQCHO5SZYIUwxIGK5mCNxxXOAzyQSiD7
384+hqkKywf+4FvfDQIhALa0TLyqJFom0t7c4iIGAIRc8UlIYQSPiajI64+x9775AiEA
385+0v4fgSK/Rq059zW1753JjuB6aR0Uh+3RqJII4dUR1Wg=
386+-----END RSA PRIVATE KEY-----`)
387+
388+ TestCertPEMBlock = []byte(`-----BEGIN CERTIFICATE-----
389+MIIBYzCCAQ+gAwIBAgIBADALBgkqhkiG9w0BAQUwEjEQMA4GA1UEChMHQWNtZSBD
390+bzAeFw0xMzEyMTkyMDU1NDNaFw0yMzEyMTcyMDU1NDNaMBIxEDAOBgNVBAoTB0Fj
391+bWUgQ28wWjALBgkqhkiG9w0BAQEDSwAwSAJBAPw+niki17X2qALE2A2AzE1q5dvK
392+9CI4OduRtT9IgbFLC6psqAT21NA+QbY17nWSSpyP65zkMkwKXrbDzstwLPkCAwEA
393+AaNUMFIwDgYDVR0PAQH/BAQDAgCkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1Ud
394+EwEB/wQFMAMBAf8wGgYDVR0RBBMwEYIJbG9jYWxob3N0hwR/AAABMAsGCSqGSIb3
395+DQEBBQNBAFqiVI+Km2XPSO+pxITaPvhmuzg+XG3l1+2di3gL+HlDobocjBqRctRU
396+YySO32W07acjGJmCHUKpCJuq9X8hpmk=
397+-----END CERTIFICATE-----`)
398+)

Subscribers

People subscribed via source and target branches