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