func (s *charmsSuite) TestUploadRepackagesNestedArchives(c *gc.C) { // Make a clone of the dummy charm in a nested directory. rootDir := c.MkDir() dirPath := filepath.Join(rootDir, "subdir1", "subdir2") err := os.MkdirAll(dirPath, 0755) c.Assert(err, gc.IsNil) dir := coretesting.Charms.ClonedDir(dirPath, "dummy") // Now tweak the path the dir thinks it is in and bundle it. dir.Path = rootDir tempFile, err := ioutil.TempFile(c.MkDir(), "charm") c.Assert(err, gc.IsNil) defer tempFile.Close() defer os.Remove(tempFile.Name()) err = dir.BundleTo(tempFile) c.Assert(err, gc.IsNil) // Try reading it as a bundle - should fail due to nested dirs. _, err = charm.ReadBundle(tempFile.Name()) c.Assert(err, gc.ErrorMatches, "bundle file not found: metadata.yaml") // Now try uploading it - should succeeed and be repackaged. resp, err := s.uploadRequest(c, s.charmsURI(c, "?series=quantal"), true, tempFile.Name()) c.Assert(err, gc.IsNil) expectedURL := charm.MustParseURL("local:quantal/dummy-1") s.assertUploadResponse(c, resp, expectedURL.String()) sch, err := s.State.Charm(expectedURL) c.Assert(err, gc.IsNil) c.Assert(sch.URL(), gc.DeepEquals, expectedURL) c.Assert(sch.Revision(), gc.Equals, 1) c.Assert(sch.IsUploaded(), jc.IsTrue) // Get it from the storage and try to read it as a bundle - it // should succeed, because it was repackaged during upload to // strip nested dirs. archiveName := strings.TrimPrefix(sch.BundleURL().RequestURI(), "/dummyenv/private/") storage, err := environs.GetStorage(s.State) c.Assert(err, gc.IsNil) reader, err := storage.Get(archiveName) c.Assert(err, gc.IsNil) defer reader.Close() data, err := ioutil.ReadAll(reader) c.Assert(err, gc.IsNil) downloadedFile, err := ioutil.TempFile(c.MkDir(), "downloaded") c.Assert(err, gc.IsNil) defer downloadedFile.Close() defer os.Remove(downloadedFile.Name()) err = ioutil.WriteFile(downloadedFile.Name(), data, 0644) c.Assert(err, gc.IsNil) bundle, err := charm.ReadBundle(downloadedFile.Name()) c.Assert(err, gc.IsNil) c.Assert(bundle.Revision(), jc.DeepEquals, sch.Revision()) c.Assert(bundle.Meta(), jc.DeepEquals, sch.Meta()) c.Assert(bundle.Config(), jc.DeepEquals, sch.Config()) }
func (s *charmsSuite) TestUploadRespectsLocalRevision(c *gc.C) { // Make a dummy charm dir with revision 123. dir := coretesting.Charms.ClonedDir(c.MkDir(), "dummy") dir.SetDiskRevision(123) // Now bundle the dir. tempFile, err := ioutil.TempFile(c.MkDir(), "charm") c.Assert(err, gc.IsNil) defer tempFile.Close() defer os.Remove(tempFile.Name()) err = dir.BundleTo(tempFile) c.Assert(err, gc.IsNil) // Now try uploading it and ensure the revision persists. resp, err := s.uploadRequest(c, s.charmsURI(c, "?series=quantal"), true, tempFile.Name()) c.Assert(err, gc.IsNil) expectedURL := charm.MustParseURL("local:quantal/dummy-123") s.assertUploadResponse(c, resp, expectedURL.String()) sch, err := s.State.Charm(expectedURL) c.Assert(err, gc.IsNil) c.Assert(sch.URL(), gc.DeepEquals, expectedURL) c.Assert(sch.Revision(), gc.Equals, 123) c.Assert(sch.IsUploaded(), jc.IsTrue) // First rewind the reader, which was reset but BundleTo() above. _, err = tempFile.Seek(0, 0) c.Assert(err, gc.IsNil) // Finally, verify the SHA256 and uploaded URL. expectedSHA256, _, err := utils.ReadSHA256(tempFile) c.Assert(err, gc.IsNil) name := charm.Quote(expectedURL.String()) storage, err := environs.GetStorage(s.State) c.Assert(err, gc.IsNil) expectedUploadURL, err := storage.URL(name) c.Assert(err, gc.IsNil) c.Assert(sch.BundleURL().String(), gc.Equals, expectedUploadURL) c.Assert(sch.BundleSha256(), gc.Equals, expectedSHA256) reader, err := storage.Get(name) c.Assert(err, gc.IsNil) defer reader.Close() downloadedSHA256, _, err := utils.ReadSHA256(reader) c.Assert(err, gc.IsNil) c.Assert(downloadedSHA256, gc.Equals, expectedSHA256) }
// downloadCharm downloads the given charm name from the provider storage and // saves the corresponding zip archive to the given charmArchivePath. func (h *charmsHandler) downloadCharm(name, charmArchivePath string) error { // Get the provider storage. storage, err := environs.GetStorage(h.state) if err != nil { return errgo.Annotate(err, "cannot access provider storage") } // Use the storage to retrieve and save the charm archive. reader, err := storage.Get(name) if err != nil { return errgo.Annotate(err, "charm not found in the provider storage") } defer reader.Close() data, err := ioutil.ReadAll(reader) if err != nil { return errgo.Annotate(err, "cannot read charm data") } // In order to avoid races, the archive is saved in a temporary file which // is then atomically renamed. The temporary file is created in the // charm cache directory so that we can safely assume the rename source and // target live in the same file system. cacheDir := filepath.Dir(charmArchivePath) if err = os.MkdirAll(cacheDir, 0755); err != nil { return errgo.Annotate(err, "cannot create the charms cache") } tempCharmArchive, err := ioutil.TempFile(cacheDir, "charm") if err != nil { return errgo.Annotate(err, "cannot create charm archive temp file") } defer tempCharmArchive.Close() if err = ioutil.WriteFile(tempCharmArchive.Name(), data, 0644); err != nil { return errgo.Annotate(err, "error processing charm archive download") } if err = os.Rename(tempCharmArchive.Name(), charmArchivePath); err != nil { defer os.Remove(tempCharmArchive.Name()) return errgo.Annotate(err, "error renaming the charm archive") } return nil }
// 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 errgo.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 errgo.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 errgo.Annotate(err, "cannot extract uploaded charm") } charmDir, err := charm.ReadDir(extractPath) if err != nil { return errgo.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 errgo.Annotate(err, "cannot repackage uploaded charm") } bundleSHA256 := hex.EncodeToString(hash.Sum(nil)) size, err := repackagedArchive.Seek(0, 2) if err != nil { return errgo.Annotate(err, "cannot get charm file size") } // Now upload to provider storage. if _, err := repackagedArchive.Seek(0, 0); err != nil { return errgo.Annotate(err, "cannot rewind the charm file reader") } storage, err := environs.GetStorage(h.state) if err != nil { return errgo.Annotate(err, "cannot access provider storage") } name := charm.Quote(curl.String()) if err := storage.Put(name, repackagedArchive, size); err != nil { return errgo.Annotate(err, "cannot upload charm to provider storage") } storageURL, err := storage.URL(name) if err != nil { return errgo.Annotate(err, "cannot get storage URL for charm") } bundleURL, err := url.Parse(storageURL) if err != nil { return errgo.Annotate(err, "cannot parse storage URL") } // And finally, update state. _, err = h.state.UpdateUploadedCharm(archive, curl, bundleURL, bundleSHA256) if err != nil { return errgo.Annotate(err, "cannot update uploaded charm in state") } return nil }