// CharmURL returns the charm URL this unit is currently using. // // NOTE: This differs from state.Unit.CharmURL() by returning // an error instead of a bool, because it needs to make an API call. func (u *Unit) CharmURL() (*charm.URL, error) { var results params.StringBoolResults args := params.Entities{ Entities: []params.Entity{{Tag: u.tag.String()}}, } err := u.st.call("CharmURL", args, &results) if err != nil { return nil, err } if len(results.Results) != 1 { return nil, fmt.Errorf("expected 1 result, got %d", len(results.Results)) } result := results.Results[0] if result.Error != nil { return nil, result.Error } if result.Result != "" { curl, err := charm.ParseURL(result.Result) if err != nil { return nil, err } return curl, nil } return nil, ErrNoCharmURLSet }
// CharmURL returns the service's charm URL, and whether units should // upgrade to the charm with that URL even if they are in an error // state (force flag). // // NOTE: This differs from state.Service.CharmURL() by returning // an error instead as well, because it needs to make an API call. func (s *Service) CharmURL() (*charm.URL, bool, error) { var results params.StringBoolResults args := params.Entities{ Entities: []params.Entity{{Tag: s.tag}}, } err := s.st.call("CharmURL", args, &results) if err != nil { return nil, false, err } if len(results.Results) != 1 { return nil, false, fmt.Errorf("expected 1 result, got %d", len(results.Results)) } result := results.Results[0] if result.Error != nil { return nil, false, result.Error } if result.Result != "" { curl, err := charm.ParseURL(result.Result) if err != nil { return nil, false, err } return curl, result.Ok, nil } return nil, false, fmt.Errorf("%q has no charm url set", s.tag) }
func (s *MockStore) serveInfo(w http.ResponseWriter, r *http.Request) { if metadata := r.Header.Get("Juju-Metadata"); metadata != "" { s.Metadata = append(s.Metadata, metadata) logger.Infof("Juju metadata: " + metadata) } r.ParseForm() if r.Form.Get("stats") == "0" { s.InfoRequestCountNoStats += 1 } else { s.InfoRequestCount += 1 } response := map[string]*charm.InfoResponse{} for _, url := range r.Form["charms"] { cr := &charm.InfoResponse{} response[url] = cr charmURL, err := charm.ParseURL(url) if err == charm.ErrUnresolvedUrl { ref, _, err := charm.ParseReference(url) if err != nil { panic(err) } if s.DefaultSeries == "" { panic(fmt.Errorf("mock store lacks a default series cannot resolve charm URL: %q", url)) } charmURL = &charm.URL{Reference: ref, Series: s.DefaultSeries} } switch charmURL.Name { case "borken": cr.Errors = append(cr.Errors, "badness") case "terracotta": cr.Errors = append(cr.Errors, "cannot get revision") case "unwise": cr.Warnings = append(cr.Warnings, "foolishness") fallthrough default: if rev, ok := s.charms[charmURL.WithRevision(-1).String()]; ok { if charmURL.Revision == -1 { cr.Revision = rev } else { cr.Revision = charmURL.Revision } cr.Sha256 = s.bundleSha256 cr.CanonicalURL = charmURL.String() } else { cr.Errors = append(cr.Errors, "entry not found") } } } data, err := json.Marshal(response) if err != nil { panic(err) } w.Header().Set("Content-Type", "application/json") _, err = w.Write(data) if err != nil { panic(err) } }
// SetCharmURL sets the charm URL for each given unit. An error will // be returned if a unit is dead, or the charm URL is not know. func (u *UniterAPI) SetCharmURL(args params.EntitiesCharmURL) (params.ErrorResults, error) { result := params.ErrorResults{ Results: make([]params.ErrorResult, len(args.Entities)), } canAccess, err := u.accessUnit() if err != nil { return params.ErrorResults{}, err } for i, entity := range args.Entities { err := common.ErrPerm if canAccess(entity.Tag) { var unit *state.Unit unit, err = u.getUnit(entity.Tag) if err == nil { var curl *charm.URL curl, err = charm.ParseURL(entity.CharmURL) if err == nil { err = unit.SetCharmURL(curl) } } } result.Results[i].Error = common.ServerError(err) } return result, nil }
// ReadCharmURL reads a charm identity file from the supplied path. func ReadCharmURL(path string) (*charm.URL, error) { surl := "" if err := utils.ReadYaml(path, &surl); err != nil { return nil, err } return charm.ParseURL(surl) }
func (s *URLSuite) TestParseUnresolved(c *gc.C) { for _, t := range inferNoDefaultSeriesTests { if t.resolved { url, err := charm.ParseURL(t.vague) c.Assert(err, gc.IsNil) c.Assert(url.Series, gc.Not(gc.Equals), "") } else { _, series, err := charm.ParseReference(t.vague) c.Assert(err, gc.IsNil) c.Assert(series, gc.Equals, "") _, err = charm.ParseURL(t.vague) c.Assert(err, gc.NotNil) c.Assert(err, gc.Equals, charm.ErrUnresolvedUrl) } } }
func (c *DeleteCharmCommand) Run(ctx *cmd.Context) error { // Read config err := c.ConfigCommand.ReadConfig(ctx) if err != nil { return err } // Parse the charm URL charmUrl, err := charm.ParseURL(c.Url) if err != nil { return err } // Open the charm store storage s, err := charmstore.Open(c.Config.MongoURL) if err != nil { return err } defer s.Close() // Delete the charm by URL _, err = s.DeleteCharm(charmUrl) if err != nil { return err } fmt.Fprintln(ctx.Stdout, "Charm", charmUrl, "deleted.") return nil }
// CharmArchiveURL returns the URL, corresponding to the charm archive // (bundle) in the provider storage for each given charm URL, along // with the DisableSSLHostnameVerification flag. func (u *UniterAPI) CharmArchiveURL(args params.CharmURLs) (params.CharmArchiveURLResults, error) { result := params.CharmArchiveURLResults{ Results: make([]params.CharmArchiveURLResult, len(args.URLs)), } // Get the SSL hostname verification environment setting. envConfig, err := u.st.EnvironConfig() if err != nil { return result, err } // SSLHostnameVerification defaults to true, so we need to // invert that, for backwards-compatibility (older versions // will have DisableSSLHostnameVerification: false by default). disableSSLHostnameVerification := !envConfig.SSLHostnameVerification() for i, arg := range args.URLs { curl, err := charm.ParseURL(arg.URL) if err != nil { err = common.ErrPerm } else { var sch *state.Charm sch, err = u.st.Charm(curl) if errors.IsNotFound(err) { err = common.ErrPerm } if err == nil { result.Results[i].Result = sch.BundleURL().String() result.Results[i].DisableSSLHostnameVerification = disableSSLHostnameVerification } } result.Results[i].Error = common.ServerError(err) } return result, nil }
// ServiceDeploy fetches the charm from the charm store and deploys it. // AddCharm or AddLocalCharm should be called to add the charm // before calling ServiceDeploy, although for backward compatibility // this is not necessary until 1.16 support is removed. func (c *Client) ServiceDeploy(args params.ServiceDeploy) error { curl, err := charm.ParseURL(args.CharmUrl) if err != nil { return err } if curl.Revision < 0 { return fmt.Errorf("charm url must include revision") } // Try to find the charm URL in state first. ch, err := c.api.state.Charm(curl) if errors.IsNotFound(err) { // Remove this whole if block when 1.16 compatibility is dropped. if curl.Schema != "cs" { return fmt.Errorf(`charm url has unsupported schema %q`, curl.Schema) } err = c.AddCharm(params.CharmURL{args.CharmUrl}) if err != nil { return err } ch, err = c.api.state.Charm(curl) if err != nil { return err } } else if err != nil { return err } var settings charm.Settings if len(args.ConfigYAML) > 0 { settings, err = ch.Config().ParseSettingsYAML([]byte(args.ConfigYAML), args.ServiceName) } else if len(args.Config) > 0 { // Parse config in a compatile way (see function comment). settings, err = parseSettingsCompatible(ch, args.Config) } if err != nil { return err } // Convert network tags to names for any given networks. requestedNetworks, err := networkTagsToNames(args.Networks) if err != nil { return err } _, err = juju.DeployService(c.api.state, juju.DeployServiceParams{ ServiceName: args.ServiceName, // TODO(dfc) ServiceOwner should be a tag ServiceOwner: c.api.auth.GetAuthTag().String(), Charm: ch, NumUnits: args.NumUnits, ConfigSettings: settings, Constraints: args.Constraints, ToMachineSpec: args.ToMachineSpec, Networks: requestedNetworks, }) return err }
// ServiceGetCharmURL returns the charm URL the given service is // running at present. func (c *Client) ServiceGetCharmURL(serviceName string) (*charm.URL, error) { result := new(params.StringResult) args := params.ServiceGet{ServiceName: serviceName} err := c.call("ServiceGetCharmURL", args, &result) if err != nil { return nil, err } return charm.ParseURL(result.Result) }
func (s *URLSuite) TestInferURLNoDefaultSeries(c *gc.C) { for _, t := range inferNoDefaultSeriesTests { inferred, err := charm.InferURL(t.vague, "") if t.exact == "" { c.Assert(err, gc.ErrorMatches, fmt.Sprintf("cannot infer charm URL for %q: no series provided", t.vague)) } else { parsed, err := charm.ParseURL(t.exact) c.Assert(err, gc.IsNil) c.Assert(inferred, gc.DeepEquals, parsed, gc.Commentf(`InferURL(%q, "")`, t.vague)) } } }
// uniqueNameURLs returns the branch URL and the charm URL for the // provided Launchpad branch unique name. The unique name must be // in the form: // // ~<user>/charms/<series>/<charm name>/trunk // // For testing purposes, if name has a prefix preceding a string in // this format, the prefix is stripped out for computing the charm // URL, and the unique name is returned unchanged as the branch URL. func uniqueNameURLs(name string) (burl string, curl *charm.URL, err error) { u := strings.Split(name, "/") if len(u) > 5 { u = u[len(u)-5:] burl = name } else { burl = "lp:" + name } if len(u) < 5 || u[1] != "charms" || u[4] != "trunk" || len(u[0]) == 0 || u[0][0] != '~' { return "", nil, fmt.Errorf("unwanted branch name: %s", name) } curl, err = charm.ParseURL(fmt.Sprintf("cs:%s/%s/%s", u[0], u[2], u[3])) if err != nil { return "", nil, err } return burl, curl, nil }
// CharmInfo returns information about the requested charm. func (c *Client) CharmInfo(args params.CharmInfo) (api.CharmInfo, error) { curl, err := charm.ParseURL(args.CharmURL) if err != nil { return api.CharmInfo{}, err } charm, err := c.api.state.Charm(curl) if err != nil { return api.CharmInfo{}, err } info := api.CharmInfo{ Revision: charm.Revision(), URL: curl.String(), Config: charm.Config(), Meta: charm.Meta(), } return info, nil }
// serviceSetCharm sets the charm for the given service. func (c *Client) serviceSetCharm(service *state.Service, url string, force bool) error { curl, err := charm.ParseURL(url) if err != nil { return err } sch, err := c.api.state.Charm(curl) if errors.IsNotFound(err) { // Charms should be added before trying to use them, with // AddCharm or AddLocalCharm API calls. When they're not, // we're reverting to 1.16 compatibility mode. return c.serviceSetCharm1dot16(service, curl, force) } if err != nil { return err } return service.SetCharm(sch, force) }
// CharmArchiveSha256 returns the SHA256 digest of the charm archive // (bundle) data for each charm url in the given parameters. func (u *UniterAPI) CharmArchiveSha256(args params.CharmURLs) (params.StringResults, error) { result := params.StringResults{ Results: make([]params.StringResult, len(args.URLs)), } for i, arg := range args.URLs { curl, err := charm.ParseURL(arg.URL) if err != nil { err = common.ErrPerm } else { var sch *state.Charm sch, err = u.st.Charm(curl) if errors.IsNotFound(err) { err = common.ErrPerm } if err == nil { result.Results[i].Result = sch.BundleSha256() } } result.Results[i].Error = common.ServerError(err) } return result, nil }
func (s *URLSuite) TestInferURL(c *gc.C) { for i, t := range inferTests { c.Logf("test %d", i) comment := gc.Commentf("InferURL(%q, %q)", t.vague, "defseries") inferred, ierr := charm.InferURL(t.vague, "defseries") parsed, perr := charm.ParseURL(t.exact) if perr == nil { c.Check(inferred, gc.DeepEquals, parsed, comment) c.Check(ierr, gc.IsNil) } else { expect := perr.Error() if t.vague != t.exact { if colIdx := strings.Index(expect, ":"); colIdx > 0 { expect = expect[:colIdx] } } c.Check(ierr.Error(), gc.Matches, expect+".*", comment) } } u, err := charm.InferURL("~blah", "defseries") c.Assert(u, gc.IsNil) c.Assert(err, gc.ErrorMatches, "charm URL without charm name: .*") }
func (s *URLSuite) TestParseURL(c *gc.C) { for i, t := range urlTests { c.Logf("test %d", i) url, uerr := charm.ParseURL(t.s) ref, series, rerr := charm.ParseReference(t.s) comment := gc.Commentf("ParseURL(%q)", t.s) if t.url != nil && t.url.Series == "" { if t.err != "" { // Expected error should match c.Assert(rerr, gc.NotNil, comment) c.Check(rerr.Error(), gc.Matches, t.err, comment) } else { // Expected charm reference should match c.Check(ref, gc.DeepEquals, t.url.Reference, comment) c.Check(t.url.Reference.String(), gc.Equals, t.s) } if rerr != nil { // If ParseReference has an error, ParseURL should share it c.Check(uerr.Error(), gc.Equals, rerr.Error(), comment) } else { // Otherwise, ParseURL with an empty series should error unresolved. c.Check(uerr.Error(), gc.Equals, charm.ErrUnresolvedUrl.Error(), comment) } } else { if t.err != "" { c.Assert(uerr, gc.NotNil, comment) c.Check(uerr.Error(), gc.Matches, t.err, comment) c.Check(uerr.Error(), gc.Equals, rerr.Error(), comment) } else { c.Check(url.Series, gc.Equals, series, comment) c.Check(url, gc.DeepEquals, t.url, comment) c.Check(t.url.String(), gc.Equals, t.s) } } } }
// AddCharm adds the given charm URL (which must include revision) to // the environment, if it does not exist yet. Local charms are not // supported, only charm store URLs. See also AddLocalCharm(). func (c *Client) AddCharm(args params.CharmURL) error { charmURL, err := charm.ParseURL(args.URL) if err != nil { return err } if charmURL.Schema != "cs" { return fmt.Errorf("only charm store charm URLs are supported, with cs: schema") } if charmURL.Revision < 0 { return fmt.Errorf("charm URL must include revision") } // First, check if a pending or a real charm exists in state. stateCharm, err := c.api.state.PrepareStoreCharmUpload(charmURL) if err == nil && stateCharm.IsUploaded() { // Charm already in state (it was uploaded already). return nil } else if err != nil { return err } // Get the charm and its information from the store. envConfig, err := c.api.state.EnvironConfig() if err != nil { return err } store := config.SpecializeCharmRepo(CharmStore, envConfig) downloadedCharm, err := store.Get(charmURL) if err != nil { return errors.Annotatef(err, "cannot download charm %q", charmURL.String()) } // Open it and calculate the SHA256 hash. downloadedBundle, ok := downloadedCharm.(*charm.Bundle) if !ok { return errors.Errorf("expected a charm archive, got %T", downloadedCharm) } archive, err := os.Open(downloadedBundle.Path) if err != nil { return errors.Annotate(err, "cannot read downloaded charm") } defer archive.Close() bundleSHA256, size, err := utils.ReadSHA256(archive) if err != nil { return errors.Annotate(err, "cannot calculate SHA256 hash of charm") } if _, err := archive.Seek(0, 0); err != nil { return errors.Annotate(err, "cannot rewind charm archive") } // Get the environment storage and upload the charm. env, err := environs.New(envConfig) if err != nil { return errors.Annotate(err, "cannot access environment") } storage := env.Storage() archiveName, err := CharmArchiveName(charmURL.Name, charmURL.Revision) if err != nil { return errors.Annotate(err, "cannot generate charm archive name") } if err := storage.Put(archiveName, archive, size); err != nil { return errors.Annotate(err, "cannot upload charm to provider storage") } storageURL, err := storage.URL(archiveName) 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") } // Finally, update the charm data in state and mark it as no longer pending. _, err = c.api.state.UpdateUploadedCharm(downloadedCharm, charmURL, bundleURL, bundleSHA256) if err == state.ErrCharmRevisionAlreadyModified || state.IsCharmAlreadyUploadedError(err) { // This is not an error, it just signifies somebody else // managed to upload and update the charm in state before // us. This means we have to delete what we just uploaded // to storage. if err := storage.Remove(archiveName); err != nil { errors.Annotate(err, "cannot remove duplicated charm from storage") } return nil } return err }