Exemplo n.º 1
0
// 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
}
Exemplo n.º 2
0
// 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}},
	}
	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
}
Exemplo n.º 3
0
// 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)
}
Exemplo n.º 4
0
// 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
}
Exemplo n.º 5
0
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)
		}
	}
}
Exemplo n.º 6
0
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 := store.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
}
Exemplo n.º 7
0
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)
	}
}
Exemplo n.º 8
0
// 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)
}
Exemplo n.º 9
0
// 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)
}
Exemplo n.º 10
0
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))
		}
	}
}
Exemplo n.º 11
0
// 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
}
Exemplo n.º 12
0
// 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)
}
Exemplo n.º 13
0
// 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
}
Exemplo n.º 14
0
// 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
}
Exemplo n.º 15
0
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: .*")
}
Exemplo n.º 16
0
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)
			}
		}
	}
}
Exemplo n.º 17
0
// 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.
	includeNetworks, err := networkTagsToNames(args.IncludeNetworks)
	if err != nil {
		return err
	}
	excludeNetworks, err := networkTagsToNames(args.ExcludeNetworks)
	if err != nil {
		return err
	}

	_, err = juju.DeployService(c.api.state,
		juju.DeployServiceParams{
			ServiceName:     args.ServiceName,
			ServiceOwner:    c.api.auth.GetAuthTag(),
			Charm:           ch,
			NumUnits:        args.NumUnits,
			ConfigSettings:  settings,
			Constraints:     args.Constraints,
			ToMachineSpec:   args.ToMachineSpec,
			IncludeNetworks: includeNetworks,
			ExcludeNetworks: excludeNetworks,
		})
	return err
}
Exemplo n.º 18
0
// 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 errgo.Annotatef(err, "cannot download charm %q", charmURL.String())
	}

	// Open it and calculate the SHA256 hash.
	downloadedBundle, ok := downloadedCharm.(*charm.Bundle)
	if !ok {
		return errgo.New("expected a charm archive, got %T", downloadedCharm)
	}
	archive, err := os.Open(downloadedBundle.Path)
	if err != nil {
		return errgo.Annotate(err, "cannot read downloaded charm")
	}
	defer archive.Close()
	bundleSHA256, size, err := utils.ReadSHA256(archive)
	if err != nil {
		return errgo.Annotate(err, "cannot calculate SHA256 hash of charm")
	}
	if _, err := archive.Seek(0, 0); err != nil {
		return errgo.Annotate(err, "cannot rewind charm archive")
	}

	// Get the environment storage and upload the charm.
	env, err := environs.New(envConfig)
	if err != nil {
		return errgo.Annotate(err, "cannot access environment")
	}
	storage := env.Storage()
	archiveName, err := CharmArchiveName(charmURL.Name, charmURL.Revision)
	if err != nil {
		return errgo.Annotate(err, "cannot generate charm archive name")
	}
	if err := storage.Put(archiveName, archive, size); err != nil {
		return errgo.Annotate(err, "cannot upload charm to provider storage")
	}
	storageURL, err := storage.URL(archiveName)
	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")
	}

	// 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 {
			errgo.Annotate(err, "cannot remove duplicated charm from storage")
		}
		return nil
	}
	return err
}