Permalink
Browse files

Implemented new schema for charm revision handling in an

independent file in the Go port, including backwards
compatibility with the previous schema, and also SetVersion
methods that enable bundling and expanding charms with
custom revisions (necessary for store).
  • Loading branch information...
1 parent c09ba74 commit ddf98e0f66ebad7ccb9c41d409b64c1febbd4cf3 @niemeyer niemeyer committed Oct 8, 2011
Showing with 313 additions and 55 deletions.
  1. +61 −7 bundle.go
  2. +62 −4 bundle_test.go
  3. +2 −9 charm.go
  4. +1 −0 charm_test.go
  5. +5 −4 config.go
  6. +1 −1 config_test.go
  7. +61 −9 dir.go
  8. +100 −4 dir_test.go
  9. +7 −4 meta.go
  10. +2 −2 meta_test.go
  11. 0 testrepo/{ → series}/dummy/.dir/ignored
  12. 0 testrepo/{ → series}/dummy/.ignored
  13. 0 testrepo/{ → series}/dummy/build/ignored
  14. 0 testrepo/{ → series}/dummy/config.yaml
  15. 0 testrepo/{ → series}/dummy/hooks/install
  16. +0 −1 testrepo/{ → series}/dummy/metadata.yaml
  17. +1 −0 testrepo/series/dummy/revision
  18. 0 testrepo/{ → series}/dummy/src/hello.c
  19. +1 −2 testrepo/{mysql2 → series/mysql-alternative}/metadata.yaml
  20. +1 −0 testrepo/series/mysql-alternative/revision
  21. +0 −1 testrepo/{ → series}/mysql/metadata.yaml
  22. +1 −0 testrepo/series/mysql/revision
  23. +0 −1 testrepo/{old → series/new}/metadata.yaml
  24. +1 −0 testrepo/series/new/revision
  25. +0 −1 testrepo/{new → series/old}/metadata.yaml
  26. +1 −0 testrepo/series/old/revision
  27. +0 −1 testrepo/{ → series}/riak/metadata.yaml
  28. +1 −0 testrepo/series/riak/revision
  29. 0 testrepo/{varnish2 → series/varnish-alternative}/hooks/install
  30. +1 −2 testrepo/{varnish → series/varnish-alternative}/metadata.yaml
  31. +1 −0 testrepo/series/varnish-alternative/revision
  32. +0 −1 testrepo/{varnish2 → series/varnish}/metadata.yaml
  33. +1 −0 testrepo/series/varnish/revision
  34. 0 testrepo/{ → series}/wordpress/config.yaml
  35. +0 −1 testrepo/{ → series}/wordpress/metadata.yaml
  36. +1 −0 testrepo/series/wordpress/revision
View
@@ -2,9 +2,11 @@ package charm
import (
"archive/zip"
+ "fmt"
"io"
"os"
"path/filepath"
+ "strconv"
"strings"
)
@@ -39,6 +41,7 @@ func readBundle(r io.ReaderAt, size int64) (bundle *Bundle, err os.Error) {
if err != nil {
return
}
+
reader, err := zipOpen(zipr, "metadata.yaml")
if err != nil {
return
@@ -48,6 +51,7 @@ func readBundle(r io.ReaderAt, size int64) (bundle *Bundle, err os.Error) {
if err != nil {
return
}
+
reader, err = zipOpen(zipr, "config.yaml")
if err != nil {
return
@@ -57,6 +61,19 @@ func readBundle(r io.ReaderAt, size int64) (bundle *Bundle, err os.Error) {
if err != nil {
return
}
+
+ reader, err = zipOpen(zipr, "revision")
+ if err != nil {
+ if _, ok := err.(noBundleFile); !ok {
+ return
+ }
+ b.revision = b.meta.OldRevision
+ } else {
+ _, err = fmt.Fscan(reader, &b.revision)
+ if err != nil {
+ return nil, os.NewError("invalid revision file")
+ }
+ }
return b, nil
}
@@ -66,22 +83,44 @@ func zipOpen(zipr *zip.Reader, path string) (rc io.ReadCloser, err os.Error) {
return fh.Open()
}
}
- return nil, errorf("bundle file not found: %s", path)
+ return nil, noBundleFile{path}
+}
+
+type noBundleFile struct {
+ path string
+}
+
+func (err noBundleFile) String() string {
+ return fmt.Sprintf("bundle file not found: %s", err.path)
}
// The Bundle type encapsulates access to data and operations
// on a charm bundle.
type Bundle struct {
- Path string // May be empty if Bundle wasn't read from a file
- meta *Meta
- config *Config
- r io.ReaderAt
- size int64
+ Path string // May be empty if Bundle wasn't read from a file
+ meta *Meta
+ config *Config
+ revision int
+ r io.ReaderAt
+ size int64
}
// Trick to ensure *Bundle implements the Charm interface.
var _ Charm = (*Bundle)(nil)
+// Revision returns the revision number for the charm
+// expanded in dir.
+func (b *Bundle) Revision() int {
+ return b.revision
+}
+
+// SetRevision changes the charm revision number. This affects the
+// revision reported by Revision and the revision of the charm
+// directory created by ExpandTo.
+func (b *Bundle) SetRevision(revision int) {
+ b.revision = revision
+}
+
// Meta returns the Meta representing the metadata.yaml file from bundle.
func (b *Bundle) Meta() *Meta {
return b.meta
@@ -126,17 +165,32 @@ func (b *Bundle) ExpandTo(dir string) (err os.Error) {
lasterr = err
}
}
+
+ revFile, err := os.Create(filepath.Join(dir, "revision"))
+ if err != nil {
+ return err
+ }
+ _, err = revFile.Write([]byte(strconv.Itoa(b.revision)))
+ revFile.Close()
+ if err != nil {
+ return err
+ }
return lasterr
}
func (b *Bundle) expand(dir string, zfile *zip.File) os.Error {
+ cleanName := filepath.Clean(zfile.Name)
+ if cleanName == "revision" {
+ return nil
+ }
+
r, err := zfile.Open()
if err != nil {
return err
}
defer r.Close()
- path := filepath.Join(dir, filepath.Clean(zfile.Name))
+ path := filepath.Join(dir, cleanName)
if strings.HasSuffix(zfile.Name, "/") {
err = os.MkdirAll(path, 0755)
if err != nil {
View
@@ -2,6 +2,8 @@ package charm_test
import (
//"archive/zip"
+ "exec"
+ "fmt"
"io/ioutil"
. "launchpad.net/gocheck"
"launchpad.net/juju/go/charm"
@@ -19,13 +21,13 @@ func (s *BundleSuite) SetUpSuite(c *C) {
s.bundlePath = bundleDir(c, repoDir("dummy"))
}
-func (s BundleSuite) TestReadBundle(c *C) {
+func (s *BundleSuite) TestReadBundle(c *C) {
bundle, err := charm.ReadBundle(s.bundlePath)
c.Assert(err, IsNil)
checkDummy(c, bundle, s.bundlePath)
}
-func (s BundleSuite) TestReadBundleBytes(c *C) {
+func (s *BundleSuite) TestReadBundleBytes(c *C) {
data, err := ioutil.ReadFile(s.bundlePath)
c.Assert(err, IsNil)
@@ -34,17 +36,66 @@ func (s BundleSuite) TestReadBundleBytes(c *C) {
checkDummy(c, bundle, "")
}
-func (s BundleSuite) TestExpandTo(c *C) {
+func (s *BundleSuite) TestExpandTo(c *C) {
bundle, err := charm.ReadBundle(s.bundlePath)
- path := filepath.Join(c.MkDir(), "charm")
+ c.Assert(err, IsNil)
+ path := filepath.Join(c.MkDir(), "charm")
err = bundle.ExpandTo(path)
c.Assert(err, IsNil)
dir, err := charm.ReadDir(path)
c.Assert(err, IsNil)
checkDummy(c, dir, path)
+}
+
+func (s *BundleSuite) TestBundleRevisionFile(c *C) {
+ charmDir := c.MkDir()
+ copyCharmDir(charmDir, repoDir("dummy"))
+ revPath := filepath.Join(charmDir, "revision")
+
+ // Missing revision file
+ err := os.Remove(revPath)
+ c.Assert(err, IsNil)
+
+ bundle, err := charm.ReadBundle(extBundleDir(c, charmDir))
+ c.Assert(err, IsNil)
+ c.Assert(bundle.Revision(), Equals, 0)
+
+ // Missing revision file with old revision in metadata
+ file, err := os.OpenFile(filepath.Join(charmDir, "metadata.yaml"), os.O_WRONLY|os.O_APPEND, 0)
+ c.Assert(err, IsNil)
+ _, err = file.Write([]byte("\nrevision: 1234\n"))
+ c.Assert(err, IsNil)
+
+ bundle, err = charm.ReadBundle(extBundleDir(c, charmDir))
+ c.Assert(err, IsNil)
+ c.Assert(bundle.Revision(), Equals, 1234)
+
+ // Revision file with bad content
+ err = ioutil.WriteFile(revPath, []byte("garbage"), 0666)
+ c.Assert(err, IsNil)
+ bundle, err = charm.ReadBundle(extBundleDir(c, charmDir))
+ c.Assert(err, Matches, "invalid revision file")
+ c.Assert(bundle, IsNil)
+}
+
+func (s *BundleSuite) TestBundleSetRevision(c *C) {
+ bundle, err := charm.ReadBundle(s.bundlePath)
+ c.Assert(err, IsNil)
+
+ c.Assert(bundle.Revision(), Equals, 1)
+ bundle.SetRevision(42)
+ c.Assert(bundle.Revision(), Equals, 42)
+
+ path := filepath.Join(c.MkDir(), "charm")
+ err = bundle.ExpandTo(path)
+ c.Assert(err, IsNil)
+
+ dir, err := charm.ReadDir(path)
+ c.Assert(err, IsNil)
+ c.Assert(dir.Revision(), Equals, 42)
}
func bundleDir(c *C, dirpath string) (path string) {
@@ -63,3 +114,10 @@ func bundleDir(c *C, dirpath string) (path string) {
return path
}
+func extBundleDir(c *C, dirpath string) (path string) {
+ path = filepath.Join(c.MkDir(), "bundle.charm")
+ cmd := exec.Command("/bin/sh", "-c", fmt.Sprintf("cd %s; zip -r %s .", dirpath, path))
+ output, err := cmd.CombinedOutput()
+ c.Assert(err, IsNil, Bug("Command output: %s", output))
+ return path
+}
View
@@ -1,17 +1,10 @@
package charm
-import (
- "fmt"
- "os"
-)
-
// The Charm interface is implemented by any type that
// may be handled as a charm.
type Charm interface {
Meta() *Meta
Config() *Config
-}
-
-func errorf(format string, args ...interface{}) os.Error {
- return os.NewError(fmt.Sprintf(format, args...))
+ Revision() int
+ SetRevision(revision int)
}
View
@@ -21,6 +21,7 @@ type S struct{}
var _ = Suite(&S{})
func checkDummy(c *C, f charm.Charm, path string) {
+ c.Assert(f.Revision(), Equals, 1)
c.Assert(f.Meta().Name, Equals, "dummy")
c.Assert(f.Config().Options["title"].Default, Equals, "My Title")
switch f := f.(type) {
View
@@ -1,6 +1,7 @@
package charm
import (
+ "fmt"
"io"
"io/ioutil"
"launchpad.net/juju/go/schema"
@@ -70,25 +71,25 @@ func (c *Config) Validate(values map[string]string) (processed map[string]interf
for k, v := range values {
opt, ok := c.Options[k]
if !ok {
- return nil, errorf("Unknown configuration option: %q", k)
+ return nil, fmt.Errorf("Unknown configuration option: %q", k)
}
switch opt.Type {
case "string":
out[k] = v
case "int":
i, err := strconv.Atoi64(v)
if err != nil {
- return nil, errorf("Value for %q is not an int: %q", k, v)
+ return nil, fmt.Errorf("Value for %q is not an int: %q", k, v)
}
out[k] = i
case "float":
f, err := strconv.Atof64(v)
if err != nil {
- return nil, errorf("Value for %q is not a float: %q", k, v)
+ return nil, fmt.Errorf("Value for %q is not a float: %q", k, v)
}
out[k] = f
default:
- panic(errorf("Internal error: option type %q is unknown to Validate", opt.Type))
+ panic(fmt.Errorf("Internal error: option type %q is unknown to Validate", opt.Type))
}
}
for k, opt := range c.Options {
View
@@ -32,7 +32,7 @@ options:
`
func repoConfig(name string) io.Reader {
- file, err := os.Open(filepath.Join("testrepo", name, "config.yaml"))
+ file, err := os.Open(filepath.Join("testrepo", "series", name, "config.yaml"))
if err != nil {
panic(err)
}
Oops, something went wrong.

0 comments on commit ddf98e0

Please sign in to comment.