// 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 }
// 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 }
// 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 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 }