// processGet handles a charm file GET request after authentication. // It returns the bundle path, the requested file path (if any) and an error. func (h *charmsHandler) processGet(r *http.Request) (string, string, error) { query := r.URL.Query() // Retrieve and validate query parameters. curl := query.Get("url") if curl == "" { return "", "", fmt.Errorf("expected url=CharmURL query argument") } var filePath string file := query.Get("file") if file == "" { filePath = "" } else { filePath = path.Clean(file) } // Prepare the bundle directories. name := charm.Quote(curl) charmArchivePath := filepath.Join(h.dataDir, "charm-get-cache", name+".zip") // Check if the charm archive is already in the cache. if _, err := os.Stat(charmArchivePath); os.IsNotExist(err) { // Download the charm archive and save it to the cache. if err = h.downloadCharm(name, charmArchivePath); err != nil { return "", "", fmt.Errorf("unable to retrieve and save the charm: %v", err) } } else if err != nil { return "", "", fmt.Errorf("cannot access the charms cache: %v", err) } return charmArchivePath, filePath, nil }
// CharmArchiveName returns a string that is suitable as a file name // in a storage URL. It is constructed from the charm name, revision // and a random UUID string. func CharmArchiveName(name string, revision int) (string, error) { uuid, err := utils.NewUUID() if err != nil { return "", err } return charm.Quote(fmt.Sprintf("%s-%d-%s", name, revision, uuid)), nil }
func (s *QuoteSuite) TestUnmodified(c *gc.C) { // Check that a string containing only valid // chars stays unmodified. in := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-" out := charm.Quote(in) c.Assert(out, gc.Equals, in) }
func (s *charmsSuite) TestGetUsesCache(c *gc.C) { // Add a fake charm archive in the cache directory. cacheDir := filepath.Join(s.DataDir(), "charm-get-cache") err := os.MkdirAll(cacheDir, 0755) c.Assert(err, gc.IsNil) // Create and save a bundle in it. charmDir := coretesting.Charms.ClonedDir(c.MkDir(), "dummy") testPath := filepath.Join(charmDir.Path, "utils.js") contents := "// blah blah" err = ioutil.WriteFile(testPath, []byte(contents), 0755) c.Assert(err, gc.IsNil) var buffer bytes.Buffer err = charmDir.BundleTo(&buffer) c.Assert(err, gc.IsNil) charmArchivePath := filepath.Join( cacheDir, charm.Quote("local:trusty/django-42")+".zip") err = ioutil.WriteFile(charmArchivePath, buffer.Bytes(), 0644) c.Assert(err, gc.IsNil) // Ensure the cached contents are properly retrieved. uri := s.charmsURI(c, "?url=local:trusty/django-42&file=utils.js") resp, err := s.authRequest(c, "GET", uri, "", nil) c.Assert(err, gc.IsNil) s.assertGetFileResponse(c, resp, contents, "application/javascript") }
// storeManifest stores, into dataPath, the supplied manifest for the supplied charm. func (d *manifestDeployer) storeManifest(url *charm.URL, manifest set.Strings) error { if err := os.MkdirAll(d.DataPath(manifestsDataPath), 0755); err != nil { return err } name := charm.Quote(url.String()) path := filepath.Join(d.DataPath(manifestsDataPath), name) return utils.WriteYaml(path, manifest.SortedValues()) }
func (conn *Conn) addCharm(curl *charm.URL, ch charm.Charm) (*state.Charm, error) { var f *os.File name := charm.Quote(curl.String()) switch ch := ch.(type) { case *charm.Dir: var err error if f, err = ioutil.TempFile("", name); err != nil { return nil, err } defer os.Remove(f.Name()) defer f.Close() err = ch.BundleTo(f) if err != nil { return nil, fmt.Errorf("cannot bundle charm: %v", err) } if _, err := f.Seek(0, 0); err != nil { return nil, err } case *charm.Bundle: var err error if f, err = os.Open(ch.Path); err != nil { return nil, fmt.Errorf("cannot read charm bundle: %v", err) } defer f.Close() default: return nil, fmt.Errorf("unknown charm type %T", ch) } digest, size, err := utils.ReadSHA256(f) if err != nil { return nil, err } if _, err := f.Seek(0, 0); err != nil { return nil, err } stor := conn.Environ.Storage() logger.Infof("writing charm to storage [%d bytes]", size) if err := stor.Put(name, f, size); err != nil { return nil, fmt.Errorf("cannot put charm: %v", err) } ustr, err := stor.URL(name) if err != nil { return nil, fmt.Errorf("cannot get storage URL for charm: %v", err) } u, err := url.Parse(ustr) if err != nil { return nil, fmt.Errorf("cannot parse storage URL: %v", err) } logger.Infof("adding charm to state") sch, err := conn.State.AddCharm(ch, curl, u, digest) if err != nil { return nil, fmt.Errorf("cannot add charm: %v", err) } return sch, nil }
// loadManifest loads, from dataPath, the manifest for the charm identified by the // identity file at the supplied path within the charm directory. func (d *manifestDeployer) loadManifest(urlFilePath string) (*charm.URL, set.Strings, error) { url, err := ReadCharmURL(d.CharmPath(urlFilePath)) if err != nil { return nil, set.NewStrings(), err } name := charm.Quote(url.String()) path := filepath.Join(d.DataPath(manifestsDataPath), name) manifest := []string{} err = utils.ReadYaml(path, &manifest) if os.IsNotExist(err) { logger.Warningf("manifest not found at %q: files from charm %q may be left unremoved", path, url) err = nil } return url, set.NewStrings(manifest...), err }
func (s *StoreSuite) TestGetBadCache(c *gc.C) { c.Assert(os.Mkdir(filepath.Join(charm.CacheDir, "cache"), 0777), gc.IsNil) base := "cs:series/good" charmURL := charm.MustParseURL(base) revCharmURL := charm.MustParseURL(base + "-23") name := charm.Quote(revCharmURL.String()) + ".charm" err := ioutil.WriteFile(filepath.Join(charm.CacheDir, "cache", name), nil, 0666) c.Assert(err, gc.IsNil) ch, err := s.store.Get(charmURL) c.Assert(err, gc.IsNil) c.Assert(ch, gc.NotNil) c.Assert(s.server.Downloads, gc.DeepEquals, []*charm.URL{revCharmURL}) s.assertCached(c, charmURL) s.assertCached(c, revCharmURL) }
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) }
func (s *QuoteSuite) TestQuote(c *gc.C) { // Check that invalid chars are translated correctly. in := "hello_there/how'are~you-today.sir" out := charm.Quote(in) c.Assert(out, gc.Equals, "hello_5f_there_2f_how_27_are_7e_you-today.sir") }
// bundleURLPath returns the path to the location where the verified charm // bundle identified by url will be, or has been, saved. func (d *BundlesDir) bundleURLPath(url *charm.URL) string { return path.Join(d.path, charm.Quote(url.String())) }
// 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 }