Beispiel #1
0
// resolveCharmStoreEntityURL resolves the given charm or bundle URL string
// by looking it up in the appropriate charm repository.
// If it is a charm store URL, the given csParams will
// be used to access the charm store repository.
// If it is a local charm or bundle URL, the local charm repository at
// the given repoPath will be used. The given configuration
// will be used to add any necessary attributes to the repo
// and to return the charm's supported series if possible.
//
// resolveCharmStoreEntityURL also returns the charm repository holding
// the charm or bundle.
func resolveCharmStoreEntityURL(args resolveCharmStoreEntityParams) (*charm.URL, []string, charmrepo.Interface, error) {
	url, err := charm.ParseURL(args.urlStr)
	if err != nil {
		return nil, nil, nil, errors.Trace(err)
	}
	repo, err := charmrepo.InferRepository(url, args.csParams, args.repoPath)
	if err != nil {
		return nil, nil, nil, errors.Trace(err)
	}
	repo = config.SpecializeCharmRepo(repo, args.conf)

	if url.Schema == "local" && url.Series == "" {
		if defaultSeries, ok := args.conf.DefaultSeries(); ok {
			url.Series = defaultSeries
		}
		if url.Series == "" {
			possibleURL := *url
			possibleURL.Series = config.LatestLtsSeries()
			logger.Errorf("The series is not specified in the model (default-series) or with the charm. Did you mean:\n\t%s", &possibleURL)
			return nil, nil, nil, errors.Errorf("cannot resolve series for charm: %q", url)
		}
	}
	resultUrl, supportedSeries, err := repo.Resolve(url)
	if err != nil {
		return nil, nil, nil, errors.Trace(err)
	}
	return resultUrl, supportedSeries, repo, nil
}
Beispiel #2
0
// ResolveCharm resolves the best available charm URLs with series, for charm
// locations without a series specified.
func ResolveCharms(st *state.State, args params.ResolveCharms) (params.ResolveCharmResults, error) {
	var results params.ResolveCharmResults

	envConfig, err := st.ModelConfig()
	if err != nil {
		return params.ResolveCharmResults{}, err
	}
	repo := config.SpecializeCharmRepo(
		NewCharmStoreRepo(csclient.New(csclient.Params{})),
		envConfig)

	for _, ref := range args.References {
		result := params.ResolveCharmResult{}
		curl, err := charm.ParseURL(ref)
		if err != nil {
			result.Error = err.Error()
		} else {
			curl, err := resolveCharm(curl, repo)
			if err != nil {
				result.Error = err.Error()
			} else {
				result.URL = curl.String()
			}
		}
		results.URLs = append(results.URLs, result)
	}
	return results, nil
}
Beispiel #3
0
func (s *ConfigSuite) TestSpecializeCharmRepo(c *gc.C) {
	for i, test := range specializeCharmRepoTests {
		c.Logf("test %d: %s", i, test.about)
		cfg := newTestConfig(c, testing.Attrs{"test-mode": test.testMode})
		repo := config.SpecializeCharmRepo(test.repo, cfg)
		store := repo.(*specializedCharmRepo)
		c.Assert(store.testMode, gc.Equals, test.testMode)
	}
}
Beispiel #4
0
func (c *upgradeCharmCommand) getCharmStore(
	bakeryClient *httpbakery.Client,
	modelConfig *config.Config,
) *charmrepo.CharmStore {
	csClient := newCharmStoreClient(bakeryClient).WithChannel(c.Channel)
	return config.SpecializeCharmRepo(
		charmrepo.NewCharmStoreFromClient(csClient),
		modelConfig,
	).(*charmrepo.CharmStore)
}
Beispiel #5
0
// resolve resolves the given given charm or bundle URL
// string by looking it up in the appropriate charm repository. If it is
// a charm store URL, the given csParams will be used to access the
// charm store repository. If it is a local charm or bundle URL, the
// local charm repository at the given repoPath will be used. The given
// configuration will be used to add any necessary attributes to the
// repo and to return the charm's supported series if possible.
//
// It returns the fully resolved URL, any series supported by the entity,
// and the repository that holds it.
func (r *charmURLResolver) resolve(urlStr string) (*charm.URL, csparams.Channel, []string, charmrepo.Interface, error) {
	var noChannel csparams.Channel
	url, err := charm.ParseURL(urlStr)
	if err != nil {
		return nil, noChannel, nil, nil, errors.Trace(err)
	}
	switch url.Schema {
	case "cs":
		repo := config.SpecializeCharmRepo(r.csRepo, r.conf).(*charmrepo.CharmStore)

		resultUrl, channel, supportedSeries, err := repo.ResolveWithChannel(url)
		if err != nil {
			return nil, noChannel, nil, nil, errors.Trace(err)
		}
		return resultUrl, channel, supportedSeries, repo, nil
	case "local":
		if url.Series == "" {
			if defaultSeries, ok := r.conf.DefaultSeries(); ok {
				url.Series = defaultSeries
			}
		}
		if url.Series == "" {
			possibleURL := *url
			possibleURL.Series = config.LatestLtsSeries()
			logger.Errorf("The series is not specified in the model (default-series) or with the charm. Did you mean:\n\t%s", &possibleURL)
			return nil, noChannel, nil, nil, errors.Errorf("cannot resolve series for charm: %q", url)
		}
		repo, err := charmrepo.NewLocalRepository(r.repoPath)
		if err != nil {
			return nil, noChannel, nil, nil, errors.Mask(err)
		}
		repo = config.SpecializeCharmRepo(repo, r.conf)

		resultUrl, supportedSeries, err := repo.Resolve(url)
		if err != nil {
			return nil, noChannel, nil, nil, errors.Trace(err)
		}
		return resultUrl, noChannel, supportedSeries, repo, nil
	default:
		return nil, noChannel, nil, nil, errors.Errorf("unknown schema for charm reference %q", urlStr)
	}
}
Beispiel #6
0
// resolve resolves the given given charm or bundle URL
// string by looking it up in the charm store.
// The given csParams will be used to access the charm store.
//
// It returns the fully resolved URL, any series supported by the entity,
// and the store that holds it.
func (r *charmURLResolver) resolve(urlStr string) (*charm.URL, csparams.Channel, []string, *charmrepo.CharmStore, error) {
	var noChannel csparams.Channel
	url, err := charm.ParseURL(urlStr)
	if err != nil {
		return nil, noChannel, nil, nil, errors.Trace(err)
	}

	if url.Schema != "cs" {
		return nil, noChannel, nil, nil, errors.Errorf("unknown schema for charm URL %q", url)
	}
	charmStore := config.SpecializeCharmRepo(r.store, r.conf).(*charmrepo.CharmStore)

	resultUrl, channel, supportedSeries, err := charmStore.ResolveWithChannel(url)
	if err != nil {
		return nil, noChannel, nil, nil, errors.Trace(err)
	}
	return resultUrl, channel, supportedSeries, charmStore, nil
}
Beispiel #7
0
func (c *Client) ResolveCharms(args params.ResolveCharms) (params.ResolveCharmResults, error) {
	var results params.ResolveCharmResults

	envConfig, err := c.api.state.EnvironConfig()
	if err != nil {
		return params.ResolveCharmResults{}, err
	}
	repo := config.SpecializeCharmRepo(CharmStore, envConfig)

	for _, ref := range args.References {
		result := params.ResolveCharmResult{}
		curl, err := c.resolveCharm(ref, repo)
		if err != nil {
			result.Error = err.Error()
		} else {
			result.URL = curl
		}
		results.URLs = append(results.URLs, result)
	}
	return results, nil
}
Beispiel #8
0
// ResolveCharm resolves the best available charm URLs with series, for charm
// locations without a series specified.
func ResolveCharms(st *state.State, args params.ResolveCharms) (params.ResolveCharmResults, error) {
	var results params.ResolveCharmResults

	envConfig, err := st.EnvironConfig()
	if err != nil {
		return params.ResolveCharmResults{}, err
	}
	repo := config.SpecializeCharmRepo(
		NewCharmStore(charmrepo.NewCharmStoreParams{}),
		envConfig)

	for _, ref := range args.References {
		result := params.ResolveCharmResult{}
		curl, err := resolveCharm(&ref, repo)
		if err != nil {
			result.Error = err.Error()
		} else {
			result.URL = curl
		}
		results.URLs = append(results.URLs, result)
	}
	return results, nil
}
Beispiel #9
0
// resolveCharmURL resolves the given charm URL string
// by looking it up in the appropriate charm repository.
// If it is a charm store charm URL, the given csParams will
// be used to access the charm store repository.
// If it is a local charm URL, the local charm repository at
// the given repoPath will be used. The given configuration
// will be used to add any necessary attributes to the repo
// and to resolve the default series if possible.
//
// resolveCharmURL also returns the charm repository holding
// the charm.
func resolveCharmURL(curlStr string, csParams charmrepo.NewCharmStoreParams, repoPath string, conf *config.Config) (*charm.URL, charmrepo.Interface, error) {
	ref, err := charm.ParseReference(curlStr)
	if err != nil {
		return nil, nil, errors.Trace(err)
	}
	repo, err := charmrepo.InferRepository(ref, csParams, repoPath)
	if err != nil {
		return nil, nil, errors.Trace(err)
	}
	repo = config.SpecializeCharmRepo(repo, conf)
	if ref.Series == "" {
		if defaultSeries, ok := conf.DefaultSeries(); ok {
			ref.Series = defaultSeries
		}
	}
	if ref.Schema == "local" && ref.Series == "" {
		possibleURL := *ref
		possibleURL.Series = "trusty"
		logger.Errorf("The series is not specified in the environment (default-series) or with the charm. Did you mean:\n\t%s", &possibleURL)
		return nil, nil, errors.Errorf("cannot resolve series for charm: %q", ref)
	}
	if ref.Series != "" && ref.Revision != -1 {
		// The URL is already fully resolved; do not
		// bother with an unnecessary round-trip to the
		// charm store.
		curl, err := ref.URL("")
		if err != nil {
			panic(err)
		}
		return curl, repo, nil
	}
	curl, err := repo.Resolve(ref)
	if err != nil {
		return nil, nil, errors.Trace(err)
	}
	return curl, repo, nil
}
Beispiel #10
0
// Run connects to the specified environment and starts the charm
// upgrade process.
func (c *UpgradeCharmCommand) Run(ctx *cmd.Context) error {
	client, err := c.NewAPIClient()
	if err != nil {
		return err
	}
	defer client.Close()
	oldURL, err := client.ServiceGetCharmURL(c.ServiceName)
	if err != nil {
		return err
	}

	attrs, err := client.EnvironmentGet()
	if err != nil {
		return err
	}
	conf, err := config.New(config.NoDefaults, attrs)
	if err != nil {
		return err
	}

	var newURL *charm.URL
	if c.SwitchURL != "" {
		newURL, err = resolveCharmURL(c.SwitchURL, client, conf)
		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.Reference(), ctx.AbsPath(c.RepoPath))
	if err != nil {
		return err
	}
	repo = config.SpecializeCharmRepo(repo, conf)

	// 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 := charm.Latest(repo, newURL)
		if err != nil {
			return err
		}
		newURL = newURL.WithRevision(latest)
	}
	if *newURL == *oldURL {
		if explicitRevision {
			return fmt.Errorf("already running specified charm %q", newURL)
		} else if newURL.Schema == "cs" {
			// No point in trying to upgrade a charm store charm when
			// we just determined that's the latest revision
			// available.
			return fmt.Errorf("already running latest charm %q", newURL)
		}
	}

	addedURL, err := addCharmViaAPI(client, ctx, newURL, repo)
	if err != nil {
		return err
	}

	return client.ServiceSetCharm(c.ServiceName, addedURL.String(), c.Force)
}
Beispiel #11
0
// AddCharmWithAuthorization 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().
//
// The authorization macaroon, args.CharmStoreMacaroon, may be
// omitted, in which case this call is equivalent to AddCharm.
func AddCharmWithAuthorization(st *state.State, args params.AddCharmWithAuthorization) 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 := st.PrepareStoreCharmUpload(charmURL)
	if err != nil {
		return err
	}
	if stateCharm.IsUploaded() {
		// Charm already in state (it was uploaded already).
		return nil
	}

	// Get the charm and its information from the store.
	envConfig, err := st.EnvironConfig()
	if err != nil {
		return err
	}
	csURL, err := url.Parse(csclient.ServerURL)
	if err != nil {
		return err
	}
	csParams := charmrepo.NewCharmStoreParams{
		URL:        csURL.String(),
		HTTPClient: httpbakery.NewHTTPClient(),
	}
	if args.CharmStoreMacaroon != nil {
		// Set the provided charmstore authorizing macaroon
		// as a cookie in the HTTP client.
		// TODO discharge any third party caveats in the macaroon.
		ms := []*macaroon.Macaroon{args.CharmStoreMacaroon}
		httpbakery.SetCookie(csParams.HTTPClient.Jar, csURL, ms)
	}
	repo := config.SpecializeCharmRepo(
		NewCharmStore(csParams),
		envConfig,
	)
	downloadedCharm, err := repo.Get(charmURL)
	if err != nil {
		cause := errors.Cause(err)
		if httpbakery.IsDischargeError(cause) || httpbakery.IsInteractionError(cause) {
			return errors.NewUnauthorized(err, "")
		}
		return errors.Trace(err)
	}

	// Open it and calculate the SHA256 hash.
	downloadedBundle, ok := downloadedCharm.(*charm.CharmArchive)
	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")
	}

	// Store the charm archive in environment storage.
	return StoreCharmArchive(
		st,
		charmURL,
		downloadedCharm,
		archive,
		size,
		bundleSHA256,
	)
}
Beispiel #12
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 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
}
Beispiel #13
0
// AddCharmWithAuthorization 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().
//
// The authorization macaroon, args.CharmStoreMacaroon, may be
// omitted, in which case this call is equivalent to AddCharm.
func AddCharmWithAuthorization(st *state.State, args params.AddCharmWithAuthorization) 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 := st.PrepareStoreCharmUpload(charmURL)
	if err != nil {
		return err
	}
	if stateCharm.IsUploaded() {
		// Charm already in state (it was uploaded already).
		return nil
	}

	// Open a charm store client.
	repo, err := openCSRepo(args)
	if err != nil {
		return err
	}
	envConfig, err := st.ModelConfig()
	if err != nil {
		return err
	}
	repo = config.SpecializeCharmRepo(repo, envConfig).(*charmrepo.CharmStore)

	// Get the charm and its information from the store.
	downloadedCharm, err := repo.Get(charmURL)
	if err != nil {
		cause := errors.Cause(err)
		if httpbakery.IsDischargeError(cause) || httpbakery.IsInteractionError(cause) {
			return errors.NewUnauthorized(err, "")
		}
		return errors.Trace(err)
	}

	if err := checkMinVersion(downloadedCharm); err != nil {
		return errors.Trace(err)
	}

	// Open it and calculate the SHA256 hash.
	downloadedBundle, ok := downloadedCharm.(*charm.CharmArchive)
	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")
	}

	// Store the charm archive in environment storage.
	return StoreCharmArchive(
		st,
		CharmArchive{
			ID:     charmURL,
			Charm:  downloadedCharm,
			Data:   archive,
			Size:   size,
			SHA256: bundleSHA256,
		},
	)
}
Beispiel #14
0
func (c *DeployCommand) Run(ctx *cmd.Context) error {
	client, err := juju.NewAPIClientFromName(c.EnvName)
	if err != nil {
		return err
	}
	defer client.Close()

	attrs, err := client.EnvironmentGet()
	if err != nil {
		return err
	}
	conf, err := config.New(config.NoDefaults, attrs)
	if err != nil {
		return err
	}

	curl, err := resolveCharmURL(c.CharmName, client, conf)
	if err != nil {
		return err
	}

	repo, err := charm.InferRepository(curl.Reference, ctx.AbsPath(c.RepoPath))
	if err != nil {
		return err
	}

	repo = config.SpecializeCharmRepo(repo, conf)

	curl, err = addCharmViaAPI(client, ctx, curl, repo)
	if err != nil {
		return err
	}

	if c.BumpRevision {
		ctx.Infof("--upgrade (or -u) is deprecated and ignored; charms are always deployed with a unique revision.")
	}

	requestedNetworks, err := networkNamesToTags(parseNetworks(c.Networks))
	if err != nil {
		return err
	}
	// We need to ensure network names are valid below, but we don't need them here.
	_, err = networkNamesToTags(c.Constraints.IncludeNetworks())
	if err != nil {
		return err
	}
	_, err = networkNamesToTags(c.Constraints.ExcludeNetworks())
	if err != nil {
		return err
	}
	haveNetworks := len(requestedNetworks) > 0 || c.Constraints.HaveNetworks()

	charmInfo, err := client.CharmInfo(curl.String())
	if err != nil {
		return err
	}

	numUnits := c.NumUnits
	if charmInfo.Meta.Subordinate {
		if !constraints.IsEmpty(&c.Constraints) {
			return errors.New("cannot use --constraints with subordinate service")
		}
		if numUnits == 1 && c.ToMachineSpec == "" {
			numUnits = 0
		} else {
			return errors.New("cannot use --num-units or --to with subordinate service")
		}
	}
	serviceName := c.ServiceName
	if serviceName == "" {
		serviceName = charmInfo.Meta.Name
	}

	var configYAML []byte
	if c.Config.Path != "" {
		configYAML, err = c.Config.Read(ctx)
		if err != nil {
			return err
		}
	}
	err = client.ServiceDeployWithNetworks(
		curl.String(),
		serviceName,
		numUnits,
		string(configYAML),
		c.Constraints,
		c.ToMachineSpec,
		requestedNetworks,
	)
	if params.IsCodeNotImplemented(err) {
		if haveNetworks {
			return errors.New("cannot use --networks/--constraints networks=...: not supported by the API server")
		}
		err = client.ServiceDeploy(
			curl.String(),
			serviceName,
			numUnits,
			string(configYAML),
			c.Constraints,
			c.ToMachineSpec)
	}
	return err
}