Exemple #1
0
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
}
Exemple #2
0
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)
}
Exemple #3
0
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)
}
Exemple #4
0
// 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
}
Exemple #5
0
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)
}
Exemple #6
0
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)
}
Exemple #7
0
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)
}
Exemple #8
0
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)
}
Exemple #9
0
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)
}
Exemple #10
0
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))
	}
}
Exemple #11
0
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)
}
Exemple #12
0
// 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
}
Exemple #13
0
// 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
}
Exemple #14
0
// 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
}
Exemple #15
0
// 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
}