func bundleDir(c *gc.C, dirpath string) *charm.Bundle { dir, err := charm.ReadDir(dirpath) c.Assert(err, gc.IsNil) buf := new(bytes.Buffer) err = dir.BundleTo(buf) c.Assert(err, gc.IsNil) bundle, err := charm.ReadBundleBytes(buf.Bytes()) c.Assert(err, gc.IsNil) return bundle }
func (s *UpgradeCharmSuccessSuite) TestRespectsLocalRevisionWhenPossible(c *gc.C) { dir, err := charm.ReadDir(s.path) c.Assert(err, gc.IsNil) err = dir.SetDiskRevision(42) c.Assert(err, gc.IsNil) err = runUpgradeCharm(c, "riak") c.Assert(err, gc.IsNil) s.assertUpgraded(c, 42, false) s.assertLocalRevision(c, 42, s.path) }
func (s *BundleSuite) TestExpandTo(c *gc.C) { bundle, err := charm.ReadBundle(s.bundlePath) c.Assert(err, gc.IsNil) path := filepath.Join(c.MkDir(), "charm") err = bundle.ExpandTo(path) c.Assert(err, gc.IsNil) dir, err := charm.ReadDir(path) c.Assert(err, gc.IsNil) checkDummy(c, dir, path) }
// processUploadedArchive opens the given charm archive from path, // inspects it to see if it has all files at the root of the archive // or it has subdirs. It repackages the archive so it has all the // files at the root dir, if necessary, replacing the original archive // at path. func (h *charmsHandler) processUploadedArchive(path string) error { // Open the archive as a zip. f, err := os.OpenFile(path, os.O_RDWR, 0644) if err != nil { return err } defer f.Close() fi, err := f.Stat() if err != nil { return err } zipr, err := zip.NewReader(f, fi.Size()) if err != nil { return errors.Annotate(err, "cannot open charm archive") } // Find out the root dir prefix from the archive. rootDir, err := h.findArchiveRootDir(zipr) if err != nil { return errors.Annotate(err, "cannot read charm archive") } if rootDir == "." { // Normal charm, just use charm.ReadBundle(). return nil } // There is one or more subdirs, so we need extract it to a temp // dir and then read it as a charm dir. tempDir, err := ioutil.TempDir("", "charm-extract") if err != nil { return errors.Annotate(err, "cannot create temp directory") } defer os.RemoveAll(tempDir) if err := ziputil.Extract(zipr, tempDir, rootDir); err != nil { return errors.Annotate(err, "cannot extract charm archive") } dir, err := charm.ReadDir(tempDir) if err != nil { return errors.Annotate(err, "cannot read extracted archive") } // Now repackage the dir as a bundle at the original path. if err := f.Truncate(0); err != nil { return err } if err := dir.BundleTo(f); err != nil { return err } return nil }
func AddCustomCharm(c *gc.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, gc.IsNil) } ch, err := charm.ReadDir(path) c.Assert(err, gc.IsNil) if revision != -1 { ch.SetRevision(revision) } return addCharm(c, st, series, ch) }
func (s *UpgradeCharmSuccessSuite) TestUpgradesWithBundle(c *gc.C) { dir, err := charm.ReadDir(s.path) c.Assert(err, gc.IsNil) dir.SetRevision(42) buf := &bytes.Buffer{} err = dir.BundleTo(buf) c.Assert(err, gc.IsNil) bundlePath := path.Join(s.SeriesPath, "riak.charm") err = ioutil.WriteFile(bundlePath, buf.Bytes(), 0644) c.Assert(err, gc.IsNil) err = runUpgradeCharm(c, "riak") c.Assert(err, gc.IsNil) s.assertUpgraded(c, 42, false) s.assertLocalRevision(c, 7, s.path) }
func (s *DeploySuite) TestUpgradeCharmDir(c *gc.C) { // Add the charm, so the url will exist and a new revision will be // picked in ServiceDeploy. dummyCharm := s.AddTestingCharm(c, "dummy") dirPath := coretesting.Charms.ClonedDirPath(s.SeriesPath, "dummy") err := runDeploy(c, "local:quantal/dummy") c.Assert(err, gc.IsNil) upgradedRev := dummyCharm.Revision() + 1 curl := dummyCharm.URL().WithRevision(upgradedRev) s.AssertService(c, "dummy", curl, 1, 0) // Check the charm dir was left untouched. ch, err := charm.ReadDir(dirPath) c.Assert(err, gc.IsNil) c.Assert(ch.Revision(), gc.Equals, 1) }
func (s *BundleSuite) TestBundleSetRevision(c *gc.C) { bundle, err := charm.ReadBundle(s.bundlePath) c.Assert(err, gc.IsNil) c.Assert(bundle.Revision(), gc.Equals, 1) bundle.SetRevision(42) c.Assert(bundle.Revision(), gc.Equals, 42) path := filepath.Join(c.MkDir(), "charm") err = bundle.ExpandTo(path) c.Assert(err, gc.IsNil) dir, err := charm.ReadDir(path) c.Assert(err, gc.IsNil) c.Assert(dir.Revision(), gc.Equals, 42) }
func (br *bundleReader) AddCustomBundle(c *gc.C, url *corecharm.URL, customize func(path string)) charm.BundleInfo { base := c.MkDir() dirpath := coretesting.Charms.ClonedDirPath(base, "dummy") if customize != nil { customize(dirpath) } dir, err := corecharm.ReadDir(dirpath) c.Assert(err, gc.IsNil) err = dir.SetDiskRevision(url.Revision) c.Assert(err, gc.IsNil) bunpath := filepath.Join(base, "bundle") file, err := os.Create(bunpath) c.Assert(err, gc.IsNil) defer file.Close() err = dir.BundleTo(file) c.Assert(err, gc.IsNil) bundle, err := corecharm.ReadBundle(bunpath) c.Assert(err, gc.IsNil) return br.AddBundle(c, url, bundle) }
func (s *BundleSuite) TestExpandToSetsHooksExecutable(c *gc.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, gc.IsNil) path := filepath.Join(c.MkDir(), "charm") err = bundle.ExpandTo(path) c.Assert(err, gc.IsNil) _, err = charm.ReadDir(path) c.Assert(err, gc.IsNil) for name := range bundle.Meta().Hooks() { hookName := string(name) info, err := os.Stat(filepath.Join(path, "hooks", hookName)) c.Assert(err, gc.IsNil) perm := info.Mode() & 0777 c.Assert(perm&0100 != 0, gc.Equals, true, gc.Commentf("hook %q is not executable", hookName)) } }
func (s *UpgradeCharmSuccessSuite) assertLocalRevision(c *gc.C, revision int, path string) { dir, err := charm.ReadDir(path) c.Assert(err, gc.IsNil) c.Assert(dir.Revision(), gc.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 }
// 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 }
// repackageAndUploadCharm expands the given charm archive to a // temporary directoy, repackages it with the given curl's revision, // then uploads it to providr storage, and finally updates the state. func (h *charmsHandler) repackageAndUploadCharm(archive *charm.Bundle, curl *charm.URL) error { // Create a temp dir to contain the extracted charm // dir and the repackaged archive. tempDir, err := ioutil.TempDir("", "charm-download") if err != nil { return errors.Annotate(err, "cannot create temp directory") } defer os.RemoveAll(tempDir) extractPath := filepath.Join(tempDir, "extracted") repackagedPath := filepath.Join(tempDir, "repackaged.zip") repackagedArchive, err := os.Create(repackagedPath) if err != nil { return errors.Annotate(err, "cannot repackage uploaded charm") } defer repackagedArchive.Close() // Expand and repack it with the revision specified by curl. archive.SetRevision(curl.Revision) if err := archive.ExpandTo(extractPath); err != nil { return errors.Annotate(err, "cannot extract uploaded charm") } charmDir, err := charm.ReadDir(extractPath) if err != nil { return errors.Annotate(err, "cannot read extracted charm") } // Bundle the charm and calculate its sha256 hash at the // same time. hash := sha256.New() err = charmDir.BundleTo(io.MultiWriter(hash, repackagedArchive)) if err != nil { return errors.Annotate(err, "cannot repackage uploaded charm") } bundleSHA256 := hex.EncodeToString(hash.Sum(nil)) size, err := repackagedArchive.Seek(0, 2) if err != nil { return errors.Annotate(err, "cannot get charm file size") } // Now upload to provider storage. if _, err := repackagedArchive.Seek(0, 0); err != nil { return errors.Annotate(err, "cannot rewind the charm file reader") } storage, err := environs.GetStorage(h.state) if err != nil { return errors.Annotate(err, "cannot access provider storage") } name := charm.Quote(curl.String()) if err := storage.Put(name, repackagedArchive, size); err != nil { return errors.Annotate(err, "cannot upload charm to provider storage") } storageURL, err := storage.URL(name) if err != nil { return errors.Annotate(err, "cannot get storage URL for charm") } bundleURL, err := url.Parse(storageURL) if err != nil { return errors.Annotate(err, "cannot parse storage URL") } // And finally, update state. _, err = h.state.UpdateUploadedCharm(archive, curl, bundleURL, bundleSHA256) if err != nil { return errors.Annotate(err, "cannot update uploaded charm in state") } 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 }