// CharmInfo retrieves the CharmInfo value for the charm at url. func (s *Store) CharmInfo(url *charm.URL) (info *CharmInfo, err error) { session := s.session.Copy() defer session.Close() log.Debugf("store: Retrieving charm info for %s", url) rev := url.Revision url = url.WithRevision(-1) charms := session.Charms() var cdoc charmDoc var qdoc interface{} if rev == -1 { qdoc = bson.D{{"urls", url}} } else { qdoc = bson.D{{"urls", url}, {"revision", rev}} } err = charms.Find(qdoc).Sort("-revision").One(&cdoc) if err != nil { log.Errorf("store: Failed to find charm %s: %v", url, err) return nil, ErrNotFound } info = &CharmInfo{ cdoc.Revision, cdoc.Digest, cdoc.Sha256, cdoc.Size, cdoc.FileId, cdoc.Meta, cdoc.Config, } return info, nil }
// PutCharm uploads the given charm to provider storage, and adds a // state.Charm to the state. The charm is not uploaded if a charm with // the same URL already exists in the state. // If bumpRevision is true, the charm must be a local directory, // and the revision number will be incremented before pushing. func (conn *Conn) PutCharm(curl *charm.URL, repo charm.Repository, bumpRevision bool) (*state.Charm, error) { if curl.Revision == -1 { rev, err := repo.Latest(curl) if err != nil { return nil, fmt.Errorf("cannot get latest charm revision: %v", err) } curl = curl.WithRevision(rev) } ch, err := repo.Get(curl) if err != nil { return nil, fmt.Errorf("cannot get charm: %v", err) } if bumpRevision { chd, ok := ch.(*charm.Dir) if !ok { return nil, fmt.Errorf("cannot increment revision of charm %q: not a directory", curl) } if err = chd.SetDiskRevision(chd.Revision() + 1); err != nil { return nil, fmt.Errorf("cannot increment revision of charm %q: %v", curl, err) } curl = curl.WithRevision(chd.Revision()) } if sch, err := conn.State.Charm(curl); err == nil { return sch, nil } return conn.addCharm(curl, ch) }
// DeleteCharm deletes the charms matching url. If no revision is specified, // all revisions of the charm are deleted. func (s *Store) DeleteCharm(url *charm.URL) ([]*CharmInfo, error) { logger.Debugf("deleting charm %s", url) infos, err := s.getRevisions(url, 0) if err != nil { return nil, err } if len(infos) == 0 { return nil, ErrNotFound } session := s.session.Copy() defer session.Close() var deleted []*CharmInfo for _, info := range infos { err := session.Charms().Remove( bson.D{{"urls", url.WithRevision(-1)}, {"revision", info.Revision()}}) if err != nil { logger.Errorf("failed to delete metadata for charm %s: %v", url, err) return deleted, err } err = session.CharmFS().RemoveId(info.fileId) if err != nil { logger.Errorf("failed to delete GridFS file for charm %s: %v", url, err) return deleted, err } deleted = append(deleted, info) } return deleted, err }
// Latest implements charm.Repository.Latest. func (s *MockCharmStore) Latest(charmURL *charm.URL) (int, error) { charmURL = charmURL.WithRevision(-1) base, rev := s.interpret(charmURL) if _, found := s.charms[base][rev]; !found { return 0, fmt.Errorf("charm not found in mock store: %s", charmURL) } return rev, nil }
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) } h := sha256.New() size, err := io.Copy(h, f) if err != nil { return nil, err } digest := hex.EncodeToString(h.Sum(nil)) if _, err := f.Seek(0, 0); err != nil { return nil, err } storage := conn.Environ.Storage() log.Infof("writing charm to storage [%d bytes]", size) if err := storage.Put(name, f, size); err != nil { return nil, fmt.Errorf("cannot put charm: %v", err) } ustr, err := storage.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) } log.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 }
// interpret extracts from charmURL information relevant to both Latest and // Get. The returned "base" is always the string representation of the // unrevisioned part of charmURL; the "rev" wil be taken from the charmURL if // available, and will otherwise be the revision of the latest charm in the // store with the same "base". func (s *MockCharmStore) interpret(charmURL *charm.URL) (base string, rev int) { base, rev = charmURL.WithRevision(-1).String(), charmURL.Revision if rev == -1 { for candidate := range s.charms[base] { if candidate > rev { rev = candidate } } } return }
func handleEvent(ctx *cmd.Context, curl *charm.URL, event *charm.EventResponse) error { switch event.Kind { case "published": curlRev := curl.WithRevision(event.Revision) log.Infof("charm published at %s as %s", event.Time, curlRev) fmt.Fprintln(ctx.Stdout, curlRev) case "publish-error": return fmt.Errorf("charm could not be published: %s", strings.Join(event.Errors, "; ")) default: return fmt.Errorf("unknown event kind %q for charm %s", event.Kind, curl) } return nil }
// SetCharm adds and removes charms in s. The affected charm is identified by // charmURL, which must be revisioned. If bundle is nil, the charm will be // removed; otherwise, it will be stored. It is an error to store a bundle // under a charmURL that does not share its name and revision. func (s *MockCharmStore) SetCharm(charmURL *charm.URL, bundle *charm.Bundle) error { base := charmURL.WithRevision(-1).String() if charmURL.Revision < 0 { return fmt.Errorf("bad charm url revision") } if bundle == nil { delete(s.charms[base], charmURL.Revision) return nil } bundleRev := bundle.Revision() bundleName := bundle.Meta().Name if bundleName != charmURL.Name || bundleRev != charmURL.Revision { return fmt.Errorf("charm url %s mismatch with bundle %s-%d", charmURL, bundleName, bundleRev) } if _, found := s.charms[base]; !found { s.charms[base] = map[int]*charm.Bundle{} } s.charms[base][charmURL.Revision] = bundle return nil }
// getRevisions returns at most the last n revisions for charm at url, // in descending revision order. For limit n=0, all revisions are returned. func (s *Store) getRevisions(url *charm.URL, n int) ([]*CharmInfo, error) { session := s.session.Copy() defer session.Close() logger.Debugf("retrieving charm info for %s", url) rev := url.Revision url = url.WithRevision(-1) charms := session.Charms() var cdocs []charmDoc var qdoc interface{} if rev == -1 { qdoc = bson.D{{"urls", url}} } else { qdoc = bson.D{{"urls", url}, {"revision", rev}} } q := charms.Find(qdoc).Sort("-revision") if n > 0 { q = q.Limit(n) } if err := q.All(&cdocs); err != nil { logger.Errorf("failed to find charm %s: %v", url, err) return nil, ErrNotFound } var infos []*CharmInfo for _, cdoc := range cdocs { infos = append(infos, &CharmInfo{ cdoc.Revision, cdoc.Digest, cdoc.Sha256, cdoc.Size, cdoc.FileId, cdoc.Meta, cdoc.Config, }) } return infos, nil }
func (s *LocalRepoSuite) checkNotFoundErr(c *C, err error, charmURL *charm.URL) { expect := `charm not found in "` + s.repo.Path + `": ` + charmURL.String() c.Check(err, ErrorMatches, expect) }
// Run connects to the specified environment and starts the charm // upgrade process. func (c *UpgradeCharmCommand) Run(ctx *cmd.Context) error { conn, err := juju.NewConnFromName(c.EnvName) if err != nil { return err } defer conn.Close() service, err := conn.State.Service(c.ServiceName) if err != nil { return err } oldURL, _ := service.CharmURL() var newURL *charm.URL if c.SwitchURL != "" { // A new charm URL was explicitly specified. conf, err := conn.State.EnvironConfig() if err != nil { return err } newURL, err = charm.InferURL(c.SwitchURL, conf.DefaultSeries()) if err != nil { return err } } else { // No new URL specified, but revision might have been. newURL = oldURL.WithRevision(c.Revision) } repo, err := charm.InferRepository(newURL, ctx.AbsPath(c.RepoPath)) if err != nil { return err } // If no explicit revision was set with either SwitchURL // or Revision flags, discover the latest. explicitRevision := true if newURL.Revision == -1 { explicitRevision = false latest, err := repo.Latest(newURL) if err != nil { return err } newURL = newURL.WithRevision(latest) } bumpRevision := false if *newURL == *oldURL { if explicitRevision { return fmt.Errorf("already running specified charm %q", newURL) } // Only try bumping the revision when necessary (local dir charm). if _, isLocal := repo.(*charm.LocalRepository); !isLocal { // TODO(dimitern): If the --force flag is set to something // different to before, we might actually want to allow this // case (and the other error below). LP bug #1174287 return fmt.Errorf("already running latest charm %q", newURL) } // This is a local repository. if ch, err := repo.Get(newURL); err != nil { return err } else if _, bumpRevision = ch.(*charm.Dir); !bumpRevision { // Only bump the revision when it's a directory. return fmt.Errorf("cannot increment revision of charm %q: not a directory", newURL) } } sch, err := conn.PutCharm(newURL, repo, bumpRevision) if err != nil { return err } return service.SetCharm(sch, c.Force) }
// WriteCharmURL writes a charm identity file into the directory. func WriteCharmURL(d *GitDir, url *charm.URL) error { return utils.WriteYaml(filepath.Join(d.path, ".juju-charm"), url.String()) }