Merge lp:~pedronis/ubuntu-push/empty into lp:ubuntu-push

Proposed by Samuele Pedroni
Status: Superseded
Proposed branch: lp:~pedronis/ubuntu-push/empty
Merge into: lp:ubuntu-push
Diff against target: 564 lines (+339/-29)
8 files modified
client/client.go (+6/-6)
client/client_test.go (+14/-0)
config/config.go (+130/-21)
config/config_test.go (+105/-0)
logger/logger.go (+27/-0)
logger/logger_test.go (+25/-0)
server/acceptance/cmd/acceptanceclient.go (+7/-2)
ubuntu-push-client.go (+25/-0)
To merge this branch: bzr merge lp:~pedronis/ubuntu-push/empty
Reviewer Review Type Date Requested Status
Ubuntu Push Hackers Pending
Review via email: mp+215895@code.launchpad.net

This proposal has been superseded by a proposal from 2014-04-15.

Commit message

empty merge for testing

To post a comment you must log in.

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'client/client.go'
2--- client/client.go 2014-04-11 16:37:48 +0000
3+++ client/client.go 2014-04-15 15:10:41 +0000
4@@ -57,7 +57,7 @@
5 // The PEM-encoded server certificate
6 CertPEMFile string `json:"cert_pem_file"`
7 // The logging level (one of "debug", "info", "error")
8- LogLevel string `json:"log_level"`
9+ LogLevel logger.ConfigLogLevel `json:"log_level"`
10 }
11
12 // PushClient is the Ubuntu Push Notifications client-side daemon.
13@@ -95,13 +95,13 @@
14
15 // configure loads its configuration, and sets it up.
16 func (client *PushClient) configure() error {
17- f, err := os.Open(client.configPath)
18+ _, err := os.Stat(client.configPath)
19 if err != nil {
20- return fmt.Errorf("opening config: %v", err)
21+ return fmt.Errorf("config: %v", err)
22 }
23- err = config.ReadConfig(f, &client.config)
24+ err = config.ReadFiles(&client.config, client.configPath, "<flags>")
25 if err != nil {
26- return fmt.Errorf("reading config: %v", err)
27+ return fmt.Errorf("config: %v", err)
28 }
29 // ignore spaces
30 client.config.Addr = strings.Replace(client.config.Addr, " ", "", -1)
31@@ -110,7 +110,7 @@
32 }
33
34 // later, we'll be specifying more logging options in the config file
35- client.log = logger.NewSimpleLogger(os.Stderr, client.config.LogLevel)
36+ client.log = logger.NewSimpleLogger(os.Stderr, client.config.LogLevel.Level())
37
38 // overridden for testing
39 client.idder = identifier.New()
40
41=== modified file 'client/client_test.go'
42--- client/client_test.go 2014-04-11 16:21:45 +0000
43+++ client/client_test.go 2014-04-15 15:10:41 +0000
44@@ -19,10 +19,12 @@
45 import (
46 "encoding/json"
47 "errors"
48+ "flag"
49 "fmt"
50 "io/ioutil"
51 "net/http"
52 "net/http/httptest"
53+ "os"
54 "path/filepath"
55 "reflect"
56 "testing"
57@@ -37,6 +39,7 @@
58 testibus "launchpad.net/ubuntu-push/bus/testing"
59 "launchpad.net/ubuntu-push/client/session"
60 "launchpad.net/ubuntu-push/client/session/levelmap"
61+ "launchpad.net/ubuntu-push/config"
62 helpers "launchpad.net/ubuntu-push/testing"
63 "launchpad.net/ubuntu-push/testing/condition"
64 "launchpad.net/ubuntu-push/util"
65@@ -79,6 +82,7 @@
66 }
67
68 func (cs *clientSuite) SetUpSuite(c *C) {
69+ config.IgnoreParsedFlags = true // because configure() uses <flags>
70 cs.timeouts = util.SwapTimeouts([]time.Duration{0})
71 cs.leveldbPath = ""
72 }
73@@ -142,6 +146,16 @@
74 c.Check(cli.config.ExchangeTimeout.TimeDuration(), Equals, time.Duration(10*time.Millisecond))
75 }
76
77+func (cs *clientSuite) TestConfigureWorksWithFlags(c *C) {
78+ flag.CommandLine = flag.NewFlagSet("client", flag.ContinueOnError)
79+ os.Args = []string{"client", "-addr", "foo:7777"}
80+ cli := NewPushClient(cs.configPath, cs.leveldbPath)
81+ err := cli.configure()
82+ c.Assert(err, IsNil)
83+ c.Assert(cli.config, NotNil)
84+ c.Check(cli.config.Addr, Equals, "foo:7777")
85+}
86+
87 func (cs *clientSuite) TestConfigureSetsUpLog(c *C) {
88 cli := NewPushClient(cs.configPath, cs.leveldbPath)
89 c.Check(cli.log, IsNil)
90
91=== modified file 'config/config.go'
92--- config/config.go 2014-03-25 18:49:18 +0000
93+++ config/config.go 2014-04-15 15:10:41 +0000
94@@ -20,6 +20,7 @@
95 import (
96 "encoding/json"
97 "errors"
98+ "flag"
99 "fmt"
100 "io"
101 "io/ioutil"
102@@ -27,6 +28,7 @@
103 "os"
104 "path/filepath"
105 "reflect"
106+ "strconv"
107 "strings"
108 "time"
109 )
110@@ -118,6 +120,22 @@
111 return fillDestConfig(destValue, p1)
112 }
113
114+// FromString are config holders that can be set by parsing a string.
115+type FromString interface {
116+ SetFromString(enc string) error
117+}
118+
119+// UnmarshalJSONViaString helps unmarshalling from JSON for FromString
120+// supporting config holders.
121+func UnmarshalJSONViaString(dest FromString, b []byte) error {
122+ var enc string
123+ err := json.Unmarshal(b, &enc)
124+ if err != nil {
125+ return err
126+ }
127+ return dest.SetFromString(enc)
128+}
129+
130 // ConfigTimeDuration can hold a time.Duration in a configuration struct,
131 // that is parsed from a string as supported by time.ParseDuration.
132 type ConfigTimeDuration struct {
133@@ -125,13 +143,11 @@
134 }
135
136 func (ctd *ConfigTimeDuration) UnmarshalJSON(b []byte) error {
137- var enc string
138- var v time.Duration
139- err := json.Unmarshal(b, &enc)
140- if err != nil {
141- return err
142- }
143- v, err = time.ParseDuration(enc)
144+ return UnmarshalJSONViaString(ctd, b)
145+}
146+
147+func (ctd *ConfigTimeDuration) SetFromString(enc string) error {
148+ v, err := time.ParseDuration(enc)
149 if err != nil {
150 return err
151 }
152@@ -148,12 +164,11 @@
153 type ConfigHostPort string
154
155 func (chp *ConfigHostPort) UnmarshalJSON(b []byte) error {
156- var enc string
157- err := json.Unmarshal(b, &enc)
158- if err != nil {
159- return err
160- }
161- _, _, err = net.SplitHostPort(enc)
162+ return UnmarshalJSONViaString(chp, b)
163+}
164+
165+func (chp *ConfigHostPort) SetFromString(enc string) error {
166+ _, _, err := net.SplitHostPort(enc)
167 if err != nil {
168 return err
169 }
170@@ -198,23 +213,117 @@
171 return ioutil.ReadFile(p)
172 }
173
174-// ReadFiles reads configuration from a set of files. Uses ReadConfig internally.
175+// used to implement getting config values with flag.Parse()
176+type val struct {
177+ destField destField
178+ accu map[string]json.RawMessage
179+}
180+
181+func (v *val) String() string { // used to show default
182+ return string(v.accu[v.destField.configName()])
183+}
184+
185+func (v *val) IsBoolFlag() bool {
186+ return v.destField.fld.Type.Kind() == reflect.Bool
187+}
188+
189+func (v *val) marshalAsNeeded(s string) (json.RawMessage, error) {
190+ var toMarshal interface{}
191+ switch v.destField.dest.(type) {
192+ case *string, FromString:
193+ toMarshal = s
194+ case *bool:
195+ bit, err := strconv.ParseBool(s)
196+ if err != nil {
197+ return nil, err
198+ }
199+ toMarshal = bit
200+ default:
201+ return json.RawMessage(s), nil
202+ }
203+ return json.Marshal(toMarshal)
204+}
205+
206+func (v *val) Set(s string) error {
207+ marshalled, err := v.marshalAsNeeded(s)
208+ if err != nil {
209+ return err
210+ }
211+ v.accu[v.destField.configName()] = marshalled
212+ return nil
213+}
214+
215+func readOneConfig(accu map[string]json.RawMessage, cfgPath string) error {
216+ r, err := os.Open(cfgPath)
217+ if err != nil {
218+ return err
219+ }
220+ defer r.Close()
221+ err = json.NewDecoder(r).Decode(&accu)
222+ if err != nil {
223+ return err
224+ }
225+ return nil
226+}
227+
228+// used to implement -cfg@=
229+type readConfigAtVal struct {
230+ accu map[string]json.RawMessage
231+}
232+
233+func (v *readConfigAtVal) String() string {
234+ return "<config.json>"
235+}
236+
237+func (v *readConfigAtVal) Set(path string) error {
238+ return readOneConfig(v.accu, path)
239+}
240+
241+// readUsingFlags gets config values from command line flags.
242+func readUsingFlags(accu map[string]json.RawMessage, destValue reflect.Value) error {
243+ if flag.Parsed() {
244+ if IgnoreParsedFlags {
245+ return nil
246+ }
247+ return fmt.Errorf("too late, flags already parsed")
248+ }
249+ destStruct := destValue.Elem()
250+ for destField := range traverseStruct(destStruct) {
251+ help := destField.fld.Tag.Get("help")
252+ flag.Var(&val{destField, accu}, destField.configName(), help)
253+ }
254+ flag.Var(&readConfigAtVal{accu}, "cfg@", "get config values from file")
255+ flag.Parse()
256+ return nil
257+}
258+
259+// IgnoreParsedFlags will just have ReadFiles ignore <flags> if the
260+// command line was already parsed.
261+var IgnoreParsedFlags = false
262+
263+// ReadFiles reads configuration from a set of files. The string
264+// "<flags>" can be used as a pseudo file-path, it will consider
265+// command line flags, invoking flag.Parse(). Among those the flag
266+// -cfg@=FILE can be used to get further config values from FILE.
267 func ReadFiles(destConfig interface{}, cfgFpaths ...string) error {
268 destValue, err := checkDestConfig("destConfig", destConfig)
269 if err != nil {
270 return err
271 }
272 // do the parsing in two phases for better error handling
273- var p1 map[string]json.RawMessage
274+ p1 := make(map[string]json.RawMessage)
275 readOne := false
276 for _, cfgPath := range cfgFpaths {
277+ if cfgPath == "<flags>" {
278+ err := readUsingFlags(p1, destValue)
279+ if err != nil {
280+ return err
281+ }
282+ readOne = true
283+ continue
284+ }
285 if _, err := os.Stat(cfgPath); err == nil {
286- r, err := os.Open(cfgPath)
287- if err != nil {
288- return err
289- }
290- defer r.Close()
291- err = json.NewDecoder(r).Decode(&p1)
292+ err := readOneConfig(p1, cfgPath)
293 if err != nil {
294 return err
295 }
296
297=== modified file 'config/config_test.go'
298--- config/config_test.go 2014-03-25 18:49:18 +0000
299+++ config/config_test.go 2014-04-15 15:10:41 +0000
300@@ -18,6 +18,9 @@
301
302 import (
303 "bytes"
304+ "encoding/json"
305+ "flag"
306+ "fmt"
307 "io/ioutil"
308 "os"
309 "path/filepath"
310@@ -230,3 +233,105 @@
311 c.Check(res, DeepEquals, []string{"b", "c_list", "d"})
312
313 }
314+
315+type testConfig3 struct {
316+ A bool
317+ B string
318+ C []string `json:"c_list"`
319+ D ConfigTimeDuration `help:"duration"`
320+ E ConfigHostPort
321+ F string
322+}
323+
324+type configFlagsSuite struct{}
325+
326+var _ = Suite(&configFlagsSuite{})
327+
328+func (s *configFlagsSuite) SetUpTest(c *C) {
329+ flag.CommandLine = flag.NewFlagSet("cmd", flag.PanicOnError)
330+ // supress outputs
331+ flag.Usage = func() { flag.PrintDefaults() }
332+ flag.CommandLine.SetOutput(ioutil.Discard)
333+}
334+
335+func (s *configFlagsSuite) TestReadUsingFlags(c *C) {
336+ os.Args = []string{"cmd", "-a=1", "-b=foo", "-c_list", `["x","y"]`, "-d", "10s", "-e=localhost:80"}
337+ var cfg testConfig3
338+ p := make(map[string]json.RawMessage)
339+ err := readUsingFlags(p, reflect.ValueOf(&cfg))
340+ c.Assert(err, IsNil)
341+ c.Check(p, DeepEquals, map[string]json.RawMessage{
342+ "a": json.RawMessage("true"),
343+ "b": json.RawMessage(`"foo"`),
344+ "c_list": json.RawMessage(`["x","y"]`),
345+ "d": json.RawMessage(`"10s"`),
346+ "e": json.RawMessage(`"localhost:80"`),
347+ })
348+}
349+
350+func (s *configFlagsSuite) TestReadUsingFlagsBoolError(c *C) {
351+ os.Args = []string{"cmd", "-a=zoo"}
352+ var cfg testConfig3
353+ p := make(map[string]json.RawMessage)
354+ c.Check(func() { readUsingFlags(p, reflect.ValueOf(&cfg)) }, PanicMatches, ".*invalid boolean.*-a.*")
355+}
356+
357+func (s *configFlagsSuite) TestReadFilesAndFlags(c *C) {
358+ // test <flags> pseudo file
359+ os.Args = []string{"cmd", "-b=x"}
360+ tmpDir := c.MkDir()
361+ cfgPath := filepath.Join(tmpDir, "cfg.json")
362+ err := ioutil.WriteFile(cfgPath, []byte(`{"a": 42, "c_list": ["y", "z"]}`), os.ModePerm)
363+ c.Assert(err, IsNil)
364+ var cfg testConfig1
365+ err = ReadFiles(&cfg, cfgPath, "<flags>")
366+ c.Assert(err, IsNil)
367+ c.Check(cfg.A, Equals, 42)
368+ c.Check(cfg.B, Equals, "x")
369+ c.Check(cfg.C, DeepEquals, []string{"y", "z"})
370+}
371+
372+func (s *configFlagsSuite) TestReadFilesAndFlagsConfigAtSupport(c *C) {
373+ // test <flags> pseudo file
374+ tmpDir := c.MkDir()
375+ cfgPath := filepath.Join(tmpDir, "cfg.json")
376+ os.Args = []string{"cmd", "-a=42", fmt.Sprintf("-cfg@=%s", cfgPath)}
377+ err := ioutil.WriteFile(cfgPath, []byte(`{"b": "x", "c_list": ["y", "z"]}`), os.ModePerm)
378+ c.Assert(err, IsNil)
379+ var cfg testConfig1
380+ err = ReadFiles(&cfg, "<flags>")
381+ c.Assert(err, IsNil)
382+ c.Check(cfg.A, Equals, 42)
383+ c.Check(cfg.B, Equals, "x")
384+ c.Check(cfg.C, DeepEquals, []string{"y", "z"})
385+}
386+
387+func (s *configFlagsSuite) TestReadUsingFlagsHelp(c *C) {
388+ os.Args = []string{"cmd", "-h"}
389+ buf := bytes.NewBufferString("")
390+ flag.CommandLine.Init("cmd", flag.ContinueOnError)
391+ flag.CommandLine.SetOutput(buf)
392+ var cfg testConfig3
393+ p := map[string]json.RawMessage{
394+ "d": json.RawMessage(`"2s"`),
395+ }
396+ readUsingFlags(p, reflect.ValueOf(&cfg))
397+ c.Check(buf.String(), Matches, `(?s).*-cfg@=<config.json>: get config values from file\n.*-d="2s": duration.*`)
398+}
399+
400+func (s *configFlagsSuite) TestReadUsingFlagsAlreadyParsed(c *C) {
401+ os.Args = []string{"cmd"}
402+ flag.Parse()
403+ var cfg struct{}
404+ p := make(map[string]json.RawMessage)
405+ err := readUsingFlags(p, reflect.ValueOf(&cfg))
406+ c.Assert(err, ErrorMatches, "too late, flags already parsed")
407+ err = ReadFiles(&cfg, "<flags>")
408+ c.Assert(err, ErrorMatches, "too late, flags already parsed")
409+ IgnoreParsedFlags = true
410+ defer func() {
411+ IgnoreParsedFlags = false
412+ }()
413+ err = ReadFiles(&cfg, "<flags>")
414+ c.Assert(err, IsNil)
415+}
416
417=== modified file 'logger/logger.go'
418--- logger/logger.go 2014-02-24 10:27:38 +0000
419+++ logger/logger.go 2014-04-15 15:10:41 +0000
420@@ -23,6 +23,8 @@
421 "log"
422 "os"
423 "runtime"
424+
425+ "launchpad.net/ubuntu-push/config"
426 )
427
428 // Logger is a simple logger interface with logging at levels.
429@@ -119,3 +121,28 @@
430 lg.outputFunc(2, fmt.Sprintf("DEBUG "+format, v...))
431 }
432 }
433+
434+// config bits
435+
436+// ConfigLogLevel can hold a log level in a configuration struct.
437+type ConfigLogLevel string
438+
439+func (cll *ConfigLogLevel) ConfigFromJSONString() {}
440+
441+func (cll *ConfigLogLevel) UnmarshalJSON(b []byte) error {
442+ return config.UnmarshalJSONViaString(cll, b)
443+}
444+
445+func (cll *ConfigLogLevel) SetFromString(enc string) error {
446+ _, ok := levelToNLevel[enc]
447+ if !ok {
448+ return fmt.Errorf("not a log level: %s", enc)
449+ }
450+ *cll = ConfigLogLevel(enc)
451+ return nil
452+}
453+
454+// Level returns the log level string held in cll.
455+func (cll ConfigLogLevel) Level() string {
456+ return string(cll)
457+}
458
459=== modified file 'logger/logger_test.go'
460--- logger/logger_test.go 2014-02-10 22:51:43 +0000
461+++ logger/logger_test.go 2014-04-15 15:10:41 +0000
462@@ -25,6 +25,8 @@
463 "testing"
464
465 . "launchpad.net/gocheck"
466+
467+ "launchpad.net/ubuntu-push/config"
468 )
469
470 func TestLogger(t *testing.T) { TestingT(t) }
471@@ -138,3 +140,26 @@
472 logger.Output(1, "foobaz")
473 c.Check(buf.String(), Matches, "logger_test.go:[0-9]+: foobar\nlogger_test.go:[0-9]+: foobaz\n")
474 }
475+
476+type testLogLevelConfig struct {
477+ Lvl ConfigLogLevel
478+}
479+
480+func (s *loggerSuite) TestReadConfigLogLevel(c *C) {
481+ buf := bytes.NewBufferString(`{"lvl": "debug"}`)
482+ var cfg testLogLevelConfig
483+ err := config.ReadConfig(buf, &cfg)
484+ c.Assert(err, IsNil)
485+ c.Check(cfg.Lvl.Level(), Equals, "debug")
486+}
487+
488+func (s *loggerSuite) TestReadConfigLogLevelErrors(c *C) {
489+ var cfg testLogLevelConfig
490+ checkError := func(jsonCfg string, expectedError string) {
491+ buf := bytes.NewBufferString(jsonCfg)
492+ err := config.ReadConfig(buf, &cfg)
493+ c.Check(err, ErrorMatches, expectedError)
494+ }
495+ checkError(`{"lvl": 1}`, "lvl:.*type string")
496+ checkError(`{"lvl": "foo"}`, "lvl: not a log level: foo")
497+}
498
499=== modified file 'server/acceptance/cmd/acceptanceclient.go'
500--- server/acceptance/cmd/acceptanceclient.go 2014-04-10 13:52:31 +0000
501+++ server/acceptance/cmd/acceptanceclient.go 2014-04-15 15:10:41 +0000
502@@ -48,13 +48,18 @@
503 fmt.Fprintf(os.Stderr, "Usage: acceptancclient [options] <config.json> <device id>\n")
504 flag.PrintDefaults()
505 }
506+ missingArg := func(what string) {
507+ fmt.Fprintf(os.Stderr, "missing %s\n", what)
508+ flag.Usage()
509+ os.Exit(2)
510+ }
511 flag.Parse()
512 narg := flag.NArg()
513 switch {
514 case narg < 1:
515- log.Fatal("missing config file")
516+ missingArg("config file")
517 case narg < 2:
518- log.Fatal("missing device-id")
519+ missingArg("device-id")
520 }
521 configFName := flag.Arg(0)
522 f, err := os.Open(configFName)
523
524=== modified file 'ubuntu-push-client.go'
525--- ubuntu-push-client.go 2014-03-12 13:25:20 +0000
526+++ ubuntu-push-client.go 2014-04-15 15:10:41 +0000
527@@ -19,12 +19,37 @@
528 import (
529 "log"
530
531+ "launchpad.net/go-dbus/v1"
532 "launchpad.net/go-xdg/v0"
533
534 "launchpad.net/ubuntu-push/client"
535 )
536
537+const NAME = "com.ubuntu.PushNotifications"
538+
539+// grabName grabs ownership of the dbus name, and bails the client as
540+// soon as somebody else grabs it.
541+func grabName() {
542+ conn, err := dbus.Connect(dbus.SessionBus)
543+ if err != nil {
544+ log.Fatalf("bus: %v", err)
545+ }
546+
547+ flags := dbus.NameFlagAllowReplacement | dbus.NameFlagReplaceExisting
548+ n := conn.RequestName(NAME, flags)
549+ go func() {
550+ for err := range n.C {
551+ if err != nil {
552+ log.Fatalf("FATAL: name channel got: %v", err)
553+ }
554+ }
555+ }()
556+}
557+
558 func main() {
559+ // XXX: this is a quick hack to ensure unicity
560+ grabName()
561+
562 cfgFname, err := xdg.Config.Find("ubuntu-push-client/config.json")
563 if err != nil {
564 log.Fatalf("unable to find a configuration file: %v", err)

Subscribers

People subscribed via source and target branches