Merge lp:~niemeyer/pyjuju/go-formula-bundle into lp:pyjuju/go

Proposed by Gustavo Niemeyer
Status: Merged
Approved by: Gustavo Niemeyer
Approved revision: 23
Merged at revision: 10
Proposed branch: lp:~niemeyer/pyjuju/go-formula-bundle
Merge into: lp:pyjuju/go
Prerequisite: lp:~niemeyer/pyjuju/go-formula-dir
Diff against target: 309 lines (+245/-4)
6 files modified
formula/Makefile (+1/-0)
formula/bundle.go (+161/-0)
formula/bundle_test.go (+66/-0)
formula/dir.go (+1/-1)
formula/dir_test.go (+1/-3)
formula/formula_test.go (+15/-0)
To merge this branch: bzr merge lp:~niemeyer/pyjuju/go-formula-bundle
Reviewer Review Type Date Requested Status
Kapil Thangavelu (community) Approve
Review via email: mp+75247@code.launchpad.net

Description of the change

Go port must support formula bundles

In addition to formula directories, the Go port
must support formula bundles.

To post a comment you must log in.
Revision history for this message
Kapil Thangavelu (hazmat) wrote :

[0] I don't see any verification of exec bits on scripts which we need to execute hooks from extracted formulas. On unixie platforms, in the python impl, we read off extra bits stored in the zip info entry, which seems to be common for unix zip tools, albeit outside of the zip standard.

[1] ReadBundleBytes reads the whole file into memory, which for naive usage of the amazon api is needed, but that seems like something that would be better exposed via a file path to the zip file and incorporate directly into the provider storage usage, rather than as an api on the bundle. What's the reasoning for exposing this api directly on the bundle?

[2] ReadAt seems to be untested, the comment on why its awesome escapes me. The construction also seems odd, although thats probably my lack of go knowledge, its a public method returning a private type?

[3] Due to the changes in formula-dir (filepath.Rel) i'm unable to run the tests for this.

review: Needs Fixing
Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

[0]

Very good point. Will check it out.

[1]

I don't know what you mean by "exposing the API directly in the bundle". This is a function,
that reads a bundle in memory. I can't see a reason to kill that possibility, even more
considering we can easily decide based on size whether we want to handle something in
memory or not. The documentation also warns about it.

[2]

It's awesome because you have a method on top of a bunch of raw bytes.

It's definitely tested, because ReadBundleBytes depends on it.

[3]

Sorry about that! :-(

Revision history for this message
Kapil Thangavelu (hazmat) wrote :

ah.. for some reason i thought [1] was being used to read something off disk into memory, [2] cool. assuming a fix/test for [0] LGTM

review: Approve
Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

I'm going to merge this without [0] handled so that I can move forward with the renaming, but I've filed a bug to track the problem and will fix it shortly:

https://bugs.launchpad.net/juju/+bug/858267

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'formula/Makefile'
2--- formula/Makefile 2011-09-13 19:55:40 +0000
3+++ formula/Makefile 2011-09-13 19:55:40 +0000
4@@ -5,6 +5,7 @@
5 TARG=launchpad.net/ensemble/go/formula
6
7 GOFILES=\
8+ bundle.go\
9 config.go\
10 dir.go\
11 formula.go\
12
13=== added file 'formula/bundle.go'
14--- formula/bundle.go 1970-01-01 00:00:00 +0000
15+++ formula/bundle.go 2011-09-13 19:55:40 +0000
16@@ -0,0 +1,161 @@
17+package formula
18+
19+import (
20+ "archive/zip"
21+ "io"
22+ "os"
23+ "path/filepath"
24+)
25+
26+// ReadBundle returns a Bundle for the formula in path.
27+func ReadBundle(path string) (bundle *Bundle, err os.Error) {
28+ f, err := os.Open(path)
29+ if err != nil {
30+ return
31+ }
32+ defer f.Close()
33+ fi, err := f.Stat()
34+ if err != nil {
35+ return
36+ }
37+ b, err := readBundle(f, fi.Size)
38+ if err != nil {
39+ return
40+ }
41+ b.Path = path
42+ return b, nil
43+}
44+
45+// ReadBundleBytes returns a Bundle read from the given data.
46+// Make sure the bundle fits in memory before using this.
47+func ReadBundleBytes(data []byte) (bundle *Bundle, err os.Error) {
48+ return readBundle(readAtBytes(data), int64(len(data)))
49+}
50+
51+func readBundle(r io.ReaderAt, size int64) (bundle *Bundle, err os.Error) {
52+ b := &Bundle{r: r, size: size}
53+ zipr, err := zip.NewReader(r, size)
54+ if err != nil {
55+ return
56+ }
57+ reader, err := zipOpen(zipr, "metadata.yaml")
58+ if err != nil {
59+ return
60+ }
61+ b.meta, err = ReadMeta(reader)
62+ reader.Close()
63+ if err != nil {
64+ return
65+ }
66+ reader, err = zipOpen(zipr, "config.yaml")
67+ if err != nil {
68+ return
69+ }
70+ b.config, err = ReadConfig(reader)
71+ reader.Close()
72+ if err != nil {
73+ return
74+ }
75+ return b, nil
76+}
77+
78+func zipOpen(zipr *zip.Reader, path string) (rc io.ReadCloser, err os.Error) {
79+ for _, fh := range zipr.File {
80+ if fh.Name == path {
81+ return fh.Open()
82+ }
83+ }
84+ return nil, errorf("bundle file not found: %s", path)
85+}
86+
87+// The Bundle type encapsulates access to data and operations
88+// on a formula bundle.
89+type Bundle struct {
90+ Path string // May be empty if Bundle wasn't read from a file
91+ meta *Meta
92+ config *Config
93+ r io.ReaderAt
94+ size int64
95+}
96+
97+// Trick to ensure *Bundle implements the Formula interface.
98+var _ Formula = (*Bundle)(nil)
99+
100+// Meta returns the Meta representing the metadata.yaml file from bundle.
101+func (b *Bundle) Meta() *Meta {
102+ return b.meta
103+}
104+
105+// Config returns the Config representing the config.yaml file
106+// for the formula bundle.
107+func (b *Bundle) Config() *Config {
108+ return b.config
109+}
110+
111+// ExpandTo expands the formula bundle into dir, creating it if necessary.
112+// If any errors occur during the expansion procedure, the process will
113+// continue. Only the last error found is returned.
114+func (b *Bundle) ExpandTo(dir string) (err os.Error) {
115+ // If we have a Path, reopen the file. Otherwise, try to use
116+ // the original ReaderAt.
117+ r := b.r
118+ size := b.size
119+ if b.Path != "" {
120+ f, err := os.Open(b.Path)
121+ if err != nil {
122+ return err
123+ }
124+ defer f.Close()
125+ fi, err := f.Stat()
126+ if err != nil {
127+ return err
128+ }
129+ r = f
130+ size = fi.Size
131+ }
132+
133+ zipr, err := zip.NewReader(r, size)
134+ if err != nil {
135+ return err
136+ }
137+
138+ // From here on we won't stop with an error.
139+ var lasterr os.Error
140+
141+ for _, header := range zipr.File {
142+ zf, err := header.Open()
143+ if err != nil {
144+ lasterr = err
145+ continue
146+ }
147+ path := filepath.Join(dir, filepath.Clean(header.Name))
148+ base, _ := filepath.Split(path)
149+ err = os.MkdirAll(base, 0755)
150+ if err != nil {
151+ zf.Close()
152+ lasterr = err
153+ continue
154+ }
155+ f, err := os.Create(path)
156+ if err != nil {
157+ zf.Close()
158+ lasterr = err
159+ continue
160+ }
161+ _, err = io.Copy(f, zf)
162+ if err != nil {
163+ lasterr = err
164+ }
165+ f.Close()
166+ zf.Close()
167+ }
168+
169+ return lasterr
170+}
171+
172+// FWIW, being able to do this is awesome.
173+type readAtBytes []byte
174+
175+func (b readAtBytes) ReadAt(out []byte, off int64) (n int, err os.Error) {
176+ return copy(out, b[off:]), nil
177+}
178
179=== added file 'formula/bundle_test.go'
180--- formula/bundle_test.go 1970-01-01 00:00:00 +0000
181+++ formula/bundle_test.go 2011-09-13 19:55:40 +0000
182@@ -0,0 +1,66 @@
183+package formula_test
184+
185+import (
186+ //"archive/zip"
187+ "io/ioutil"
188+ . "launchpad.net/gocheck"
189+ "launchpad.net/ensemble/go/formula"
190+ "os"
191+ "path/filepath"
192+)
193+
194+type BundleSuite struct {
195+ S
196+ bundlePath string
197+}
198+
199+var _ = Suite(&BundleSuite{})
200+
201+func (s *BundleSuite) SetUpSuite(c *C) {
202+ s.bundlePath = bundleDir(c, repoDir("dummy"))
203+}
204+
205+func (s BundleSuite) TestReadBundle(c *C) {
206+ bundle, err := formula.ReadBundle(s.bundlePath)
207+ c.Assert(err, IsNil)
208+ checkDummy(c, bundle, s.bundlePath)
209+}
210+
211+func (s BundleSuite) TestReadBundleBytes(c *C) {
212+ data, err := ioutil.ReadFile(s.bundlePath)
213+ c.Assert(err, IsNil)
214+
215+ bundle, err := formula.ReadBundleBytes(data)
216+ c.Assert(err, IsNil)
217+ checkDummy(c, bundle, "")
218+}
219+
220+func (s BundleSuite) TestExpandTo(c *C) {
221+ bundle, err := formula.ReadBundle(s.bundlePath)
222+ path := filepath.Join(c.MkDir(), "formula")
223+
224+ err = bundle.ExpandTo(path)
225+ c.Assert(err, IsNil)
226+
227+ dir, err := formula.ReadDir(path)
228+ c.Assert(err, IsNil)
229+ checkDummy(c, dir, path)
230+
231+}
232+
233+func bundleDir(c *C, dirpath string) (path string) {
234+ dir, err := formula.ReadDir(dirpath)
235+ c.Assert(err, IsNil)
236+
237+ path = filepath.Join(c.MkDir(), "bundle.charm")
238+
239+ file, err := os.Create(path)
240+ c.Assert(err, IsNil)
241+
242+ err = dir.BundleTo(file)
243+ c.Assert(err, IsNil)
244+ file.Close()
245+
246+ return path
247+}
248+
249
250=== modified file 'formula/dir.go'
251--- formula/dir.go 2011-09-13 19:55:40 +0000
252+++ formula/dir.go 2011-09-13 19:55:40 +0000
253@@ -40,7 +40,7 @@
254 config *Config
255 }
256
257-// Trick to ensure Dir implements the Formula interface.
258+// Trick to ensure *Dir implements the Formula interface.
259 var _ Formula = (*Dir)(nil)
260
261 // Meta returns the Meta representing the metadata.yaml file
262
263=== modified file 'formula/dir_test.go'
264--- formula/dir_test.go 2011-09-13 19:55:40 +0000
265+++ formula/dir_test.go 2011-09-13 19:55:40 +0000
266@@ -16,9 +16,7 @@
267 path := repoDir("dummy")
268 dir, err := formula.ReadDir(path)
269 c.Assert(err, IsNil)
270- c.Assert(dir.Path, Equals, path)
271- c.Assert(dir.Meta().Name, Equals, "dummy")
272- c.Assert(dir.Config().Options["title"].Default, Equals, "My Title")
273+ checkDummy(c, dir, path)
274 }
275
276 func (s *S) TestBundleTo(c *C) {
277
278=== modified file 'formula/formula_test.go'
279--- formula/formula_test.go 2011-09-13 19:55:40 +0000
280+++ formula/formula_test.go 2011-09-13 19:55:40 +0000
281@@ -8,6 +8,8 @@
282 . "launchpad.net/gocheck"
283 "launchpad.net/ensemble/go/formula"
284 "launchpad.net/goyaml"
285+ "os"
286+ "path/filepath"
287 )
288
289 func Test(t *testing.T) {
290@@ -38,6 +40,19 @@
291 c.Assert(err, Matches, `Missing formula revision: "local:foo-x"`)
292 }
293
294+func checkDummy(c *C, f formula.Formula, path string) {
295+ c.Assert(f.Meta().Name, Equals, "dummy")
296+ c.Assert(f.Config().Options["title"].Default, Equals, "My Title")
297+ switch f := f.(type) {
298+ case *formula.Bundle:
299+ c.Assert(f.Path, Equals, path)
300+ case *formula.Dir:
301+ c.Assert(f.Path, Equals, path)
302+ _, err := os.Stat(filepath.Join(path, "src", "hello.c"))
303+ c.Assert(err, IsNil)
304+ }
305+}
306+
307 type YamlHacker map[interface{}]interface{}
308
309 func ReadYaml(r io.Reader) YamlHacker {

Subscribers

People subscribed via source and target branches