func (s *DirSuite) TestDirRevisionFile(c *C) { charmDir := testing.Charms.ClonedDirPath(c.MkDir(), "dummy") revPath := filepath.Join(charmDir, "revision") // Missing revision file err := os.Remove(revPath) c.Assert(err, IsNil) dir, err := charm.ReadDir(charmDir) c.Assert(err, IsNil) c.Assert(dir.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) dir, err = charm.ReadDir(charmDir) c.Assert(err, IsNil) c.Assert(dir.Revision(), Equals, 1234) // Revision file with bad content err = ioutil.WriteFile(revPath, []byte("garbage"), 0666) c.Assert(err, IsNil) dir, err = charm.ReadDir(charmDir) c.Assert(err, ErrorMatches, "invalid revision file") c.Assert(dir, IsNil) }
func (s *DirSuite) TestDirSetDiskRevision(c *C) { charmDir := testing.Charms.ClonedDirPath(c.MkDir(), "dummy") dir, err := charm.ReadDir(charmDir) c.Assert(err, IsNil) c.Assert(dir.Revision(), Equals, 1) dir.SetDiskRevision(42) c.Assert(dir.Revision(), Equals, 42) dir, err = charm.ReadDir(charmDir) c.Assert(err, IsNil) c.Assert(dir.Revision(), Equals, 42) }
func (s *DirSuite) TestReadDirWithoutConfig(c *C) { path := testing.Charms.DirPath("varnish") dir, err := charm.ReadDir(path) c.Assert(err, IsNil) // A lacking config.yaml file still causes a proper // Config value to be returned. c.Assert(dir.Config().Options, HasLen, 0) }
func (s *UpgradeCharmSuccessSuite) TestDoesntBumpRevisionWhenNotNecessary(c *C) { dir, err := charm.ReadDir(s.path) c.Assert(err, IsNil) err = dir.SetDiskRevision(42) c.Assert(err, IsNil) err = runUpgradeCharm(c, "riak") c.Assert(err, IsNil) s.assertUpgraded(c, 42, false) s.assertLocalRevision(c, 42, s.path) }
func (s *DeploySuite) TestUpgradeCharmDir(c *C) { dirPath := coretesting.Charms.ClonedDirPath(s.SeriesPath, "dummy") err := runDeploy(c, "local:dummy", "-u") c.Assert(err, IsNil) curl := charm.MustParseURL("local:precise/dummy-2") s.AssertService(c, "dummy", curl, 1, 0) // Check the charm really was upgraded. ch, err := charm.ReadDir(dirPath) c.Assert(err, IsNil) c.Assert(ch.Revision(), Equals, 2) }
func (s *BundleSuite) TestBundleFileModes(c *C) { // Apply subtler mode differences than can be expressed in Bazaar. srcPath := testing.Charms.ClonedDirPath(c.MkDir(), "dummy") modes := []struct { path string mode os.FileMode }{ {"hooks/install", 0751}, {"empty", 0750}, {"src/hello.c", 0614}, } for _, m := range modes { err := os.Chmod(filepath.Join(srcPath, m.path), m.mode) c.Assert(err, IsNil) } var haveSymlinks = true if err := os.Symlink("../target", filepath.Join(srcPath, "hooks/symlink")); err != nil { haveSymlinks = false } // Bundle and extract the charm to a new directory. dir, err := charm.ReadDir(srcPath) c.Assert(err, IsNil) buf := new(bytes.Buffer) err = dir.BundleTo(buf) c.Assert(err, IsNil) bundle, err := charm.ReadBundleBytes(buf.Bytes()) c.Assert(err, IsNil) path := c.MkDir() err = bundle.ExpandTo(path) c.Assert(err, IsNil) // Check sensible file modes once round-tripped. info, err := os.Stat(filepath.Join(path, "src", "hello.c")) c.Assert(err, IsNil) c.Assert(info.Mode()&0777, Equals, os.FileMode(0644)) c.Assert(info.Mode()&os.ModeType, Equals, os.FileMode(0)) info, err = os.Stat(filepath.Join(path, "hooks", "install")) c.Assert(err, IsNil) c.Assert(info.Mode()&0777, Equals, os.FileMode(0755)) c.Assert(info.Mode()&os.ModeType, Equals, os.FileMode(0)) info, err = os.Stat(filepath.Join(path, "empty")) c.Assert(err, IsNil) c.Assert(info.Mode()&0777, Equals, os.FileMode(0755)) if haveSymlinks { target, err := os.Readlink(filepath.Join(path, "hooks", "symlink")) c.Assert(err, IsNil) c.Assert(target, Equals, "../target") } }
func (s *BundleSuite) TestExpandTo(c *C) { bundle, err := charm.ReadBundle(s.bundlePath) 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 AddCustomCharm(c *C, st *State, name, filename, content, series string, revision int) *Charm { path := testing.Charms.ClonedDirPath(c.MkDir(), name) if filename != "" { config := filepath.Join(path, filename) err := ioutil.WriteFile(config, []byte(content), 0644) c.Assert(err, IsNil) } ch, err := charm.ReadDir(path) c.Assert(err, IsNil) if revision != -1 { ch.SetRevision(revision) } return addCharm(c, st, series, ch) }
func (s *DirSuite) TestBundleToWithBadType(c *C) { charmDir := testing.Charms.ClonedDirPath(c.MkDir(), "dummy") badFile := filepath.Join(charmDir, "hooks", "badfile") // Symlink targeting a path outside of the charm. err := os.Symlink("../../target", badFile) c.Assert(err, IsNil) dir, err := charm.ReadDir(charmDir) c.Assert(err, IsNil) err = dir.BundleTo(&bytes.Buffer{}) c.Assert(err, ErrorMatches, `symlink "hooks/badfile" links out of charm: "../../target"`) // Symlink targeting an absolute path. os.Remove(badFile) err = os.Symlink("/target", badFile) c.Assert(err, IsNil) dir, err = charm.ReadDir(charmDir) c.Assert(err, IsNil) err = dir.BundleTo(&bytes.Buffer{}) c.Assert(err, ErrorMatches, `symlink "hooks/badfile" is absolute: "/target"`) // Can't bundle special files either. os.Remove(badFile) err = syscall.Mkfifo(badFile, 0644) c.Assert(err, IsNil) dir, err = charm.ReadDir(charmDir) c.Assert(err, IsNil) err = dir.BundleTo(&bytes.Buffer{}) c.Assert(err, ErrorMatches, `file is a named pipe: "hooks/badfile"`) }
func (s *UpgradeCharmSuccessSuite) TestUpgradesWithBundle(c *C) { dir, err := charm.ReadDir(s.path) c.Assert(err, IsNil) dir.SetRevision(42) buf := &bytes.Buffer{} err = dir.BundleTo(buf) c.Assert(err, IsNil) bundlePath := path.Join(s.SeriesPath, "riak.charm") err = ioutil.WriteFile(bundlePath, buf.Bytes(), 0644) c.Assert(err, IsNil) err = runUpgradeCharm(c, "riak") c.Assert(err, IsNil) s.assertUpgraded(c, 42, false) s.assertLocalRevision(c, 7, s.path) }
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 (s *DeployerSuite) bundle(c *C, customize func(path string)) *corecharm.Bundle { base := c.MkDir() dirpath := testing.Charms.ClonedDirPath(base, "dummy") customize(dirpath) dir, err := corecharm.ReadDir(dirpath) c.Assert(err, IsNil) bunpath := filepath.Join(base, "bundle") file, err := os.Create(bunpath) c.Assert(err, IsNil) defer file.Close() err = dir.BundleTo(file) c.Assert(err, IsNil) bun, err := corecharm.ReadBundle(bunpath) c.Assert(err, IsNil) return bun }
func (s createCharm) step(c *C, ctx *context) { base := coretesting.Charms.ClonedDirPath(c.MkDir(), "series", "wordpress") for _, name := range charmHooks { path := filepath.Join(base, "hooks", name) good := true for _, bad := range s.badHooks { if name == bad { good = false } } ctx.writeHook(c, path, good) } if s.customize != nil { s.customize(c, base) } dir, err := charm.ReadDir(base) c.Assert(err, IsNil) err = dir.SetDiskRevision(s.revision) c.Assert(err, IsNil) step(c, ctx, addCharm{dir, curl(s.revision)}) }
func (s *BundleSuite) TestExpandToSetsHooksExecutable(c *C) { charmDir := testing.Charms.ClonedDir(c.MkDir(), "all-hooks") // Bundle manually, so we can check ExpandTo(), unaffected // by BundleTo()'s behavior bundlePath := filepath.Join(c.MkDir(), "bundle.charm") s.prepareBundle(c, charmDir, bundlePath) bundle, err := charm.ReadBundle(bundlePath) c.Assert(err, IsNil) path := filepath.Join(c.MkDir(), "charm") err = bundle.ExpandTo(path) c.Assert(err, IsNil) _, err = charm.ReadDir(path) c.Assert(err, IsNil) for name := range bundle.Meta().Hooks() { hookName := string(name) info, err := os.Stat(filepath.Join(path, "hooks", hookName)) c.Assert(err, IsNil) perm := info.Mode() & 0777 c.Assert(perm&0100 != 0, Equals, true, Commentf("hook %q is not executable", hookName)) } }
func (s *DirSuite) TestReadDir(c *C) { path := testing.Charms.DirPath("dummy") dir, err := charm.ReadDir(path) c.Assert(err, IsNil) checkDummy(c, dir, path) }
func (c *PublishCommand) Run(ctx *cmd.Context) (err error) { branch := bzr.New(ctx.AbsPath(c.CharmPath)) if _, err := os.Stat(branch.Join(".bzr")); err != nil { return fmt.Errorf("not a charm branch: %s", branch.Location()) } if err := branch.CheckClean(); err != nil { return err } var curl *charm.URL if c.URL == "" { if err == nil { loc, err := branch.PushLocation() if err != nil { return fmt.Errorf("no charm URL provided and cannot infer from current directory (no push location)") } curl, err = charm.Store.CharmURL(loc) if err != nil { return fmt.Errorf("cannot infer charm URL from branch location: %q", loc) } } } else { curl, err = charm.InferURL(c.URL, "") if err != nil { return err } } pushLocation := charm.Store.BranchLocation(curl) if c.changePushLocation != nil { pushLocation = c.changePushLocation(pushLocation) } repo, err := charm.InferRepository(curl, "/not/important") if err != nil { return err } if repo != charm.Store { return fmt.Errorf("charm URL must reference the juju charm store") } localDigest, err := branch.RevisionId() if err != nil { return fmt.Errorf("cannot obtain local digest: %v", err) } log.Infof("local digest is %s", localDigest) ch, err := charm.ReadDir(branch.Location()) if err != nil { return err } if ch.Meta().Name != curl.Name { return fmt.Errorf("charm name in metadata must match name in URL: %q != %q", ch.Meta().Name, curl.Name) } oldEvent, err := charm.Store.Event(curl, localDigest) if _, ok := err.(*charm.NotFoundError); ok { oldEvent, err = charm.Store.Event(curl, "") if _, ok := err.(*charm.NotFoundError); ok { log.Infof("charm %s is not yet in the store", curl) err = nil } } if err != nil { return fmt.Errorf("cannot obtain event details from the store: %s", err) } if oldEvent != nil && oldEvent.Digest == localDigest { return handleEvent(ctx, curl, oldEvent) } log.Infof("sending charm to the charm store...") err = branch.Push(&bzr.PushAttr{Location: pushLocation, Remember: true}) if err != nil { return err } log.Infof("charm sent; waiting for it to be published...") for { time.Sleep(c.pollDelay) newEvent, err := charm.Store.Event(curl, "") if _, ok := err.(*charm.NotFoundError); ok { continue } if err != nil { return fmt.Errorf("cannot obtain event details from the store: %s", err) } if oldEvent != nil && oldEvent.Digest == newEvent.Digest { continue } if newEvent.Digest != localDigest { // TODO Check if the published digest is in the local history. return fmt.Errorf("charm changed but not to local charm digest; publishing race?") } return handleEvent(ctx, curl, newEvent) } return nil }
// PublishBazaarBranch checks out the Bazaar branch from burl and // publishes its latest revision at urls in the given store. // The digest parameter must be the most recent known Bazaar // revision id for the branch tip. If publishing this specific digest // for these URLs has been attempted already, the publishing // procedure may abort early. The published digest is the Bazaar // revision id of the checked out branch's tip, though, which may // differ from the digest parameter. func PublishBazaarBranch(store *Store, urls []*charm.URL, burl string, digest string) error { // Prevent other publishers from updating these specific URLs // concurrently. lock, err := store.LockUpdates(urls) if err != nil { return err } defer lock.Unlock() var branchDir string NewTip: // Prepare the charm publisher. This will compute the revision // to be assigned to the charm, and it will also fail if the // operation is unnecessary because charms are up-to-date. pub, err := store.CharmPublisher(urls, digest) if err != nil { return err } // Figure if publishing this charm was already attempted before and // failed. We won't try again endlessly if so. In the future we may // retry automatically in certain circumstances. event, err := store.CharmEvent(urls[0], digest) if err == nil && event.Kind != EventPublished { return fmt.Errorf("charm publishing previously failed: %s", strings.Join(event.Errors, "; ")) } else if err != nil && err != ErrNotFound { return err } if branchDir == "" { // Retrieve the branch with a lightweight checkout, so that it // builds a working tree as cheaply as possible. History // doesn't matter here. tempDir, err := ioutil.TempDir("", "publish-branch-") if err != nil { return err } defer os.RemoveAll(tempDir) branchDir = filepath.Join(tempDir, "branch") output, err := exec.Command("bzr", "checkout", "--lightweight", burl, branchDir).CombinedOutput() if err != nil { return outputErr(output, err) } // Pick actual digest from tip. Publishing the real tip // revision rather than the revision for the digest provided is // strictly necessary to prevent a race condition. If the // provided digest was published instead, there's a chance // another publisher concurrently running could have found a // newer revision and published that first, and the digest // parameter provided is in fact an old version that would // overwrite the new version. tipDigest, err := bzrRevisionId(branchDir) if err != nil { return err } if tipDigest != digest { digest = tipDigest goto NewTip } } ch, err := charm.ReadDir(branchDir) if err == nil { // Hand over the charm to the store for bundling and // streaming its content into the database. err = pub.Publish(ch) if err == ErrUpdateConflict { // A conflict may happen in edge cases if the whole // locking mechanism fails due to an expiration event, // and then the expired concurrent publisher revives // for whatever reason and attempts to finish // publishing. The state of the system is still // consistent in that case, and the error isn't logged // since the revision was properly published before. return err } } // Publishing is done. Log failure or error. event = &CharmEvent{ URLs: urls, Digest: digest, } if err == nil { event.Kind = EventPublished event.Revision = pub.Revision() } else { event.Kind = EventPublishError event.Errors = []string{err.Error()} } if logerr := store.LogCharmEvent(event); logerr != nil { if err == nil { err = logerr } else { err = fmt.Errorf("%v; %v", err, logerr) } } return err }
// updateRelations responds to changes in the life states of the relations // with the supplied ids. If any id corresponds to an alive relation not // known to the unit, the uniter will join that relation and return its // relationer in the added list. func (u *Uniter) updateRelations(ids []int) (added []*Relationer, err error) { for _, id := range ids { if r, found := u.relationers[id]; found { rel := r.ru.Relation() if err := rel.Refresh(); err != nil { return nil, fmt.Errorf("cannot update relation %q: %v", rel, err) } if rel.Life() == state.Dying { if err := r.SetDying(); err != nil { return nil, err } else if r.IsImplicit() { delete(u.relationers, id) } } continue } // Relations that are not alive are simply skipped, because they // were not previously known anyway. rel, err := u.st.Relation(id) if err != nil { if errors.IsNotFoundError(err) { continue } return nil, err } if rel.Life() != state.Alive { continue } // Make sure we ignore relations not implemented by the unit's charm ch, err := corecharm.ReadDir(u.charm.Path()) if err != nil { return nil, err } if ep, err := rel.Endpoint(u.unit.ServiceName()); err != nil { return nil, err } else if !ep.ImplementedBy(ch) { log.Warningf("worker/uniter: skipping relation with unknown endpoint %q", ep) continue } dir, err := relation.ReadStateDir(u.relationsDir, id) if err != nil { return nil, err } err = u.addRelation(rel, dir) if err == nil { added = append(added, u.relationers[id]) continue } e := dir.Remove() if err != state.ErrCannotEnterScope { return nil, err } if e != nil { return nil, e } } if u.unit.IsPrincipal() { return added, nil } // If no Alive relations remain between a subordinate unit's service // and its principal's service, the subordinate must become Dying. keepAlive := false for _, r := range u.relationers { scope := r.ru.Endpoint().Scope if scope == corecharm.ScopeContainer && !r.dying { keepAlive = true break } } if !keepAlive { if err := u.unit.Destroy(); err != nil { return nil, err } } return added, nil }
// Dir returns the actual charm.Dir named name. func (r *Repo) Dir(name string) *charm.Dir { ch, err := charm.ReadDir(r.DirPath(name)) check(err) return ch }
func (s *DirSuite) TestBundleTo(c *C) { baseDir := c.MkDir() charmDir := testing.Charms.ClonedDirPath(baseDir, "dummy") var haveSymlinks = true if err := os.Symlink("../target", filepath.Join(charmDir, "hooks/symlink")); err != nil { haveSymlinks = false } dir, err := charm.ReadDir(charmDir) c.Assert(err, IsNil) path := filepath.Join(baseDir, "bundle.charm") file, err := os.Create(path) c.Assert(err, IsNil) err = dir.BundleTo(file) file.Close() c.Assert(err, IsNil) zipr, err := zip.OpenReader(path) c.Assert(err, IsNil) defer zipr.Close() var metaf, instf, emptyf, revf, symf *zip.File for _, f := range zipr.File { c.Logf("Bundled file: %s", f.Name) switch f.Name { case "revision": revf = f case "metadata.yaml": metaf = f case "hooks/install": instf = f case "hooks/symlink": symf = f case "empty/": emptyf = f case "build/ignored": c.Errorf("bundle includes build/*: %s", f.Name) case ".ignored", ".dir/ignored": c.Errorf("bundle includes .* entries: %s", f.Name) } } c.Assert(revf, NotNil) reader, err := revf.Open() c.Assert(err, IsNil) data, err := ioutil.ReadAll(reader) reader.Close() c.Assert(err, IsNil) c.Assert(string(data), Equals, "1") c.Assert(metaf, NotNil) reader, err = metaf.Open() c.Assert(err, IsNil) meta, err := charm.ReadMeta(reader) reader.Close() c.Assert(err, IsNil) c.Assert(meta.Name, Equals, "dummy") c.Assert(instf, NotNil) // Despite it being 0751, we pack and unpack it as 0755. c.Assert(instf.Mode()&0777, Equals, os.FileMode(0755)) if haveSymlinks { c.Assert(symf, NotNil) c.Assert(symf.Mode()&0777, Equals, os.FileMode(0777)) reader, err = symf.Open() c.Assert(err, IsNil) data, err = ioutil.ReadAll(reader) reader.Close() c.Assert(err, IsNil) c.Assert(string(data), Equals, "../target") } else { c.Assert(symf, IsNil) } c.Assert(emptyf, NotNil) c.Assert(emptyf.Mode()&os.ModeType, Equals, os.ModeDir) // Despite it being 0750, we pack and unpack it as 0755. c.Assert(emptyf.Mode()&0777, Equals, os.FileMode(0755)) }
func (s *UpgradeCharmSuccessSuite) assertLocalRevision(c *C, revision int, path string) { dir, err := charm.ReadDir(path) c.Assert(err, IsNil) c.Assert(dir.Revision(), Equals, revision) }
// ClonedDir returns an actual charm.Dir based on a new copy of the charm directory // named name, in the directory dst. func (r *Repo) ClonedDir(dst, name string) *charm.Dir { ch, err := charm.ReadDir(r.ClonedDirPath(dst, name)) check(err) return ch }
func (s *ServiceSuite) TestEndpoints(c *C) { // Check state for charm with no explicit relations. eps, err := s.service.Endpoints() c.Assert(err, IsNil) jujuInfo := state.Endpoint{ ServiceName: "mysql", Interface: "juju-info", RelationName: "juju-info", RelationRole: state.RoleProvider, RelationScope: charm.ScopeGlobal, } c.Assert(eps, DeepEquals, []state.Endpoint{jujuInfo}) checkCommonNames := func() { ep, err := s.service.Endpoint("juju-info") c.Assert(err, IsNil) c.Assert(ep, DeepEquals, jujuInfo) _, err = s.service.Endpoint("voodoo-economy") c.Assert(err, ErrorMatches, `service "mysql" has no "voodoo-economy" relation`) } checkCommonNames() // Set a new charm, with a few relations. path := testing.Charms.ClonedDirPath(c.MkDir(), "series", "dummy") metaPath := filepath.Join(path, "metadata.yaml") f, err := os.OpenFile(metaPath, os.O_WRONLY|os.O_APPEND, 0644) c.Assert(err, IsNil) _, err = f.Write([]byte(` provides: db: mysql db-admin: mysql requires: foo: interface: bar scope: container peers: pressure: pressure `)) f.Close() c.Assert(err, IsNil) ch, err := charm.ReadDir(path) c.Assert(err, IsNil) sch, err := s.State.AddCharm( // Fake everything; just use a different URL. ch, s.charm.URL().WithRevision(99), s.charm.BundleURL(), s.charm.BundleSha256(), ) c.Assert(err, IsNil) err = s.service.SetCharm(sch, false) c.Assert(err, IsNil) // Check single endpoint. checkCommonNames() pressure := state.Endpoint{ ServiceName: "mysql", Interface: "pressure", RelationName: "pressure", RelationRole: state.RolePeer, RelationScope: charm.ScopeGlobal, } ep, err := s.service.Endpoint("pressure") c.Assert(err, IsNil) c.Assert(ep, DeepEquals, pressure) // Check the full list of endpoints. eps, err = s.service.Endpoints() c.Assert(err, IsNil) c.Assert(eps, HasLen, 5) actual := map[string]state.Endpoint{} for _, ep := range eps { actual[ep.RelationName] = ep } c.Assert(actual, DeepEquals, map[string]state.Endpoint{ "juju-info": jujuInfo, "pressure": pressure, "db": { ServiceName: "mysql", Interface: "mysql", RelationName: "db", RelationRole: state.RoleProvider, RelationScope: charm.ScopeGlobal, }, "db-admin": { ServiceName: "mysql", Interface: "mysql", RelationName: "db-admin", RelationRole: state.RoleProvider, RelationScope: charm.ScopeGlobal, }, "foo": { ServiceName: "mysql", Interface: "bar", RelationName: "foo", RelationRole: state.RoleRequirer, RelationScope: charm.ScopeContainer, }, }) }