func addLocalCharm(c *gc.C, client *api.Client, name string) (*charm.URL, *charm.CharmArchive) { charmArchive := testcharms.Repo.CharmArchive(c.MkDir(), name) curl := charm.MustParseURL(fmt.Sprintf("local:quantal/%s-%d", charmArchive.Meta().Name, charmArchive.Revision())) _, err := client.AddLocalCharm(curl, charmArchive) c.Assert(err, jc.ErrorIsNil) return curl, charmArchive }
// addCharmViaAPI calls the appropriate client API calls to add the // given charm URL to state. For non-public charm URLs, this function also // handles the macaroon authorization process using the given csClient. // The resulting charm URL of the added charm is displayed on stdout. func addCharmViaAPI(client *api.Client, ctx *cmd.Context, curl *charm.URL, repo charmrepo.Interface, csclient *csClient) (*charm.URL, error) { switch curl.Schema { case "local": ch, err := repo.Get(curl) if err != nil { return nil, err } stateCurl, err := client.AddLocalCharm(curl, ch) if err != nil { return nil, err } curl = stateCurl case "cs": if err := client.AddCharm(curl); err != nil { if !params.IsCodeUnauthorized(err) { return nil, errors.Mask(err) } m, err := csclient.authorize(curl) if err != nil { return nil, errors.Mask(err) } if err := client.AddCharmWithAuthorization(curl, m); err != nil { return nil, errors.Mask(err) } } default: return nil, fmt.Errorf("unsupported charm URL schema: %q", curl.Schema) } ctx.Infof("Added charm %q to the environment.", curl) return curl, nil }
// addCharmFromURL calls the appropriate client API calls to add the // given charm URL to state. For non-public charm URLs, this function also // handles the macaroon authorization process using the given csClient. // The resulting charm URL of the added charm is displayed on stdout. func addCharmFromURL(client *api.Client, curl *charm.URL, repo charmrepo.Interface, csclient *csClient) (*charm.URL, error) { switch curl.Schema { case "local": ch, err := repo.Get(curl) if err != nil { return nil, err } stateCurl, err := client.AddLocalCharm(curl, ch) if err != nil { return nil, err } curl = stateCurl case "cs": if err := client.AddCharm(curl); err != nil { if !params.IsCodeUnauthorized(err) { return nil, errors.Trace(err) } m, err := csclient.authorize(curl) if err != nil { return nil, maybeTermsAgreementError(err) } if err := client.AddCharmWithAuthorization(curl, m); err != nil { return nil, errors.Trace(err) } } default: return nil, fmt.Errorf("unsupported charm URL schema: %q", curl.Schema) } return curl, nil }
// addCharmViaAPI calls the appropriate client API calls to add the // given charm URL to state. Also displays the charm URL of the added // charm on stdout. func addCharmViaAPI(client *api.Client, ctx *cmd.Context, curl *charm.URL, repo charm.Repository) (*charm.URL, error) { if curl.Revision < 0 { latest, err := charm.Latest(repo, curl) if err != nil { return nil, err } curl = curl.WithRevision(latest) } switch curl.Schema { case "local": ch, err := repo.Get(curl) if err != nil { return nil, err } stateCurl, err := client.AddLocalCharm(curl, ch) if err != nil { return nil, err } curl = stateCurl case "cs": err := client.AddCharm(curl) if err != nil { return nil, err } default: return nil, fmt.Errorf("unsupported charm URL schema: %q", curl.Schema) } ctx.Infof("Added charm %q to the environment.", curl) return curl, nil }
func testMinVer(client *api.Client, t minverTest, c *gc.C) { charmMinVer := version.MustParse(t.charm) jujuVer := version.MustParse(t.juju) cleanup := api.PatchClientFacadeCall(client, func(request string, paramsIn interface{}, response interface{}) error { c.Assert(paramsIn, gc.IsNil) if response, ok := response.(*params.AgentVersionResult); ok { response.Version = jujuVer } else { c.Log("wrong output structure") c.Fail() } return nil }, ) defer cleanup() charmArchive := testcharms.Repo.CharmArchive(c.MkDir(), "dummy") curl := charm.MustParseURL( fmt.Sprintf("local:quantal/%s-%d", charmArchive.Meta().Name, charmArchive.Revision()), ) charmArchive.Meta().MinJujuVersion = charmMinVer _, err := client.AddLocalCharm(curl, charmArchive) if t.ok { if err != nil { c.Errorf("Unexpected non-nil error for jujuver %v, minver %v: %#v", t.juju, t.charm, err) } } else { if err == nil { c.Errorf("Unexpected nil error for jujuver %v, minver %v", t.juju, t.charm) } else if !api.IsMinVersionError(err) { c.Errorf("Wrong error for jujuver %v, minver %v: expected minVersionError, got: %#v", t.juju, t.charm, err) } } }
// addCharmFromURL calls the appropriate client API calls to add the // given charm URL to state. For non-public charm URLs, this function also // handles the macaroon authorization process using the given csClient. // The resulting charm URL of the added charm is displayed on stdout. // // The repo holds the charm repository associated with with the URL // by resolveCharmStoreEntityURL. func addCharmFromURL(client *api.Client, curl *charm.URL, channel csparams.Channel, repo charmrepo.Interface) (*charm.URL, *macaroon.Macaroon, error) { var csMac *macaroon.Macaroon switch curl.Schema { case "local": ch, err := repo.Get(curl) if err != nil { return nil, nil, err } stateCurl, err := client.AddLocalCharm(curl, ch) if err != nil { return nil, nil, err } curl = stateCurl case "cs": repo, ok := repo.(*charmrepo.CharmStore) if !ok { return nil, nil, errors.Errorf("(cannot happen) cs-schema URL with unexpected repo type %T", repo) } csClient := repo.Client() if err := client.AddCharm(curl, channel); err != nil { if !params.IsCodeUnauthorized(err) { return nil, nil, errors.Trace(err) } m, err := authorizeCharmStoreEntity(csClient, curl) if err != nil { return nil, nil, maybeTermsAgreementError(err) } if err := client.AddCharmWithAuthorization(curl, channel, m); err != nil { return nil, nil, errors.Trace(err) } csMac = m } default: return nil, nil, fmt.Errorf("unsupported charm URL schema: %q", curl.Schema) } return curl, csMac, nil }
// addCharm interprets the new charmRef and adds the specified charm if the new charm is different // to what's already deployed as specified by oldURL. func (c *upgradeCharmCommand) addCharm( oldURL *charm.URL, charmRef string, client *api.Client, resolver *charmURLResolver, ) (charmstore.CharmID, *macaroon.Macaroon, error) { var id charmstore.CharmID // Charm may have been supplied via a path reference. ch, newURL, err := charmrepo.NewCharmAtPathForceSeries(charmRef, oldURL.Series, c.ForceSeries) if err == nil { _, newName := filepath.Split(charmRef) if newName != oldURL.Name { return id, nil, fmt.Errorf("cannot upgrade %q to %q", oldURL.Name, newName) } addedURL, err := client.AddLocalCharm(newURL, ch) id.URL = addedURL return id, nil, err } if _, ok := err.(*charmrepo.NotFoundError); ok { return id, nil, errors.Errorf("no charm found at %q", charmRef) } // If we get a "not exists" or invalid path error then we attempt to interpret // the supplied charm reference as a URL below, otherwise we return the error. if err != os.ErrNotExist && !charmrepo.IsInvalidPathError(err) { return id, nil, err } // Charm has been supplied as a URL so we resolve and deploy using the store. newURL, channel, supportedSeries, repo, err := resolver.resolve(charmRef) if err != nil { return id, nil, errors.Trace(err) } id.Channel = channel if !c.ForceSeries && oldURL.Series != "" && newURL.Series == "" && !isSeriesSupported(oldURL.Series, supportedSeries) { series := []string{"no series"} if len(supportedSeries) > 0 { series = supportedSeries } return id, nil, errors.Errorf( "cannot upgrade from single series %q charm to a charm supporting %q. Use --force-series to override.", oldURL.Series, series, ) } // If no explicit revision was set with either SwitchURL // or Revision flags, discover the latest. if *newURL == *oldURL { newRef, _ := charm.ParseURL(charmRef) if newRef.Revision != -1 { return id, nil, fmt.Errorf("already running specified charm %q", newURL) } 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 id, nil, fmt.Errorf("already running latest charm %q", newURL) } } curl, csMac, err := addCharmFromURL(client, newURL, channel, repo) if err != nil { return id, nil, errors.Trace(err) } id.URL = curl return id, csMac, nil }
// addCharm interprets the new charmRef and adds the specified charm if the new charm is different // to what's already deployed as specified by oldURL. func (c *upgradeCharmCommand) addCharm(oldURL *charm.URL, charmRef string, ctx *cmd.Context, client *api.Client, csClient *csClient, ) (*charm.URL, error) { // Charm may have been supplied via a path reference. ch, newURL, err := charmrepo.NewCharmAtPathForceSeries(charmRef, oldURL.Series, c.ForceSeries) if err == nil { _, newName := filepath.Split(charmRef) if newName != oldURL.Name { return nil, fmt.Errorf("cannot upgrade %q to %q", oldURL.Name, newName) } return client.AddLocalCharm(newURL, ch) } if _, ok := err.(*charmrepo.NotFoundError); ok { return nil, errors.Errorf("no charm found at %q", charmRef) } // If we get a "not exists" or invalid path error then we attempt to interpret // the supplied charm reference as a URL below, otherwise we return the error. if err != os.ErrNotExist && !charmrepo.IsInvalidPathError(err) { return nil, err } // Charm has been supplied as a URL so we resolve and deploy using the store. conf, err := getClientConfig(client) if err != nil { return nil, err } newURL, supportedSeries, repo, err := resolveCharmStoreEntityURL(resolveCharmStoreEntityParams{ urlStr: charmRef, requestedSeries: oldURL.Series, forceSeries: c.ForceSeries, csParams: csClient.params, repoPath: ctx.AbsPath(c.RepoPath), conf: conf, }) if err != nil { return nil, errors.Trace(err) } if !c.ForceSeries && oldURL.Series != "" && newURL.Series == "" && !isSeriesSupported(oldURL.Series, supportedSeries) { series := []string{"no series"} if len(supportedSeries) > 0 { series = supportedSeries } return nil, errors.Errorf( "cannot upgrade from single series %q charm to a charm supporting %q. Use --force-series to override.", oldURL.Series, series, ) } // If no explicit revision was set with either SwitchURL // or Revision flags, discover the latest. if *newURL == *oldURL { newRef, _ := charm.ParseURL(charmRef) if newRef.Revision != -1 { return nil, fmt.Errorf("already running specified charm %q", newURL) } 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 nil, fmt.Errorf("already running latest charm %q", newURL) } } addedURL, err := addCharmFromURL(client, newURL, repo, csClient) if err != nil { return nil, err } ctx.Infof("Added charm %q to the model.", addedURL) return addedURL, nil }
func (c *DeployCommand) deployCharmOrBundle(ctx *cmd.Context, client *api.Client) error { deployer := serviceDeployer{ctx, c.newServiceAPIClient, c.newAnnotationsAPIClient} // We may have been given a local bundle file. bundlePath := c.CharmOrBundle bundleData, err := charmrepo.ReadBundleFile(bundlePath) if err != nil { // We may have been given a local bundle archive or exploded directory. if bundle, burl, pathErr := charmrepo.NewBundleAtPath(bundlePath); err == nil { bundleData = bundle.Data() bundlePath = burl.String() err = pathErr } } // If not a bundle then maybe a local charm. if err != nil { // Charm may have been supplied via a path reference. ch, curl, charmErr := charmrepo.NewCharmAtPathForceSeries(c.CharmOrBundle, c.Series, c.Force) if charmErr == nil { if curl, charmErr = client.AddLocalCharm(curl, ch); charmErr != nil { return charmErr } return c.deployCharm(curl, curl.Series, ctx, client, &deployer) } // We check for several types of known error which indicate // that the supplied reference was indeed a path but there was // an issue reading the charm located there. if charm.IsMissingSeriesError(charmErr) { return charmErr } if charm.IsUnsupportedSeriesError(charmErr) { return errors.Errorf("%v. Use --force to deploy the charm anyway.", charmErr) } err = charmErr } if _, ok := err.(*charmrepo.NotFoundError); ok { return errors.Errorf("no charm or bundle found at %q", c.CharmOrBundle) } // If we get a "not exists" error then we attempt to interpret the supplied // charm or bundle reference as a URL below, otherwise we return the error. if err != nil && err != os.ErrNotExist { return err } repoPath := ctx.AbsPath(c.RepoPath) conf, err := getClientConfig(client) if err != nil { return err } httpClient, err := c.HTTPClient() if err != nil { return errors.Trace(err) } csClient := newCharmStoreClient(httpClient) var charmOrBundleURL *charm.URL var repo charmrepo.Interface var supportedSeries []string // If we don't already have a bundle loaded, we try the charm store for a charm or bundle. if bundleData == nil { // Charm or bundle has been supplied as a URL so we resolve and deploy using the store. charmOrBundleURL, supportedSeries, repo, err = resolveCharmStoreEntityURL(resolveCharmStoreEntityParams{ urlStr: c.CharmOrBundle, requestedSeries: c.Series, forceSeries: c.Force, csParams: csClient.params, repoPath: repoPath, conf: conf, }) if charm.IsUnsupportedSeriesError(err) { return errors.Errorf("%v. Use --force to deploy the charm anyway.", err) } if err != nil { return errors.Trace(err) } if charmOrBundleURL.Series == "bundle" { // Load the bundle entity. bundle, err := repo.GetBundle(charmOrBundleURL) if err != nil { return errors.Trace(err) } bundleData = bundle.Data() bundlePath = charmOrBundleURL.String() } } // Handle a bundle. if bundleData != nil { if flags := getFlags(c.flagSet, charmOnlyFlags); len(flags) > 0 { return errors.Errorf("Flags provided but not supported when deploying a bundle: %s.", strings.Join(flags, ", ")) } if err := deployBundle( bundleData, client, &deployer, csClient, repoPath, conf, ctx, c.BundleStorage, ); err != nil { return errors.Trace(err) } ctx.Infof("deployment of bundle %q completed", bundlePath) return nil } // Handle a charm. if flags := getFlags(c.flagSet, bundleOnlyFlags); len(flags) > 0 { return errors.Errorf("Flags provided but not supported when deploying a charm: %s.", strings.Join(flags, ", ")) } // Get the series to use. series, message, err := charmSeries(c.Series, charmOrBundleURL.Series, supportedSeries, c.Force, conf) if charm.IsUnsupportedSeriesError(err) { return errors.Errorf("%v. Use --force to deploy the charm anyway.", err) } // Store the charm in state. curl, err := addCharmFromURL(client, charmOrBundleURL, repo, csClient) if err != nil { if err1, ok := errors.Cause(err).(*termsRequiredError); ok { terms := strings.Join(err1.Terms, " ") return errors.Errorf(`Declined: please agree to the following terms %s. Try: "juju agree %s"`, terms, terms) } return errors.Annotatef(err, "storing charm for URL %q", charmOrBundleURL) } ctx.Infof("Added charm %q to the model.", curl) ctx.Infof("Deploying charm %q %v.", curl, fmt.Sprintf(message, series)) return c.deployCharm(curl, series, ctx, client, &deployer) }
func (c *DeployCommand) deployCharmOrBundle(ctx *cmd.Context, client *api.Client) error { deployer := serviceDeployer{ctx, c} // We may have been given a local bundle file. bundleData, bundleIdent, bundleFilePath, err := c.maybeReadLocalBundleData(ctx) // If the bundle files existed but we couldn't read them, then // return that error rather than trying to interpret as a charm. if err != nil { if info, statErr := os.Stat(c.CharmOrBundle); statErr == nil { if info.IsDir() { if _, ok := err.(*charmrepo.NotFoundError); !ok { return err } } } } // If not a bundle then maybe a local charm. if err != nil { // Charm may have been supplied via a path reference. ch, curl, charmErr := charmrepo.NewCharmAtPathForceSeries(c.CharmOrBundle, c.Series, c.Force) if charmErr == nil { if curl, charmErr = client.AddLocalCharm(curl, ch); charmErr != nil { return charmErr } id := charmstore.CharmID{ URL: curl, // Local charms don't need a channel. } var csMac *macaroon.Macaroon // local charms don't need one. return c.deployCharm(deployCharmArgs{ id: id, csMac: csMac, series: curl.Series, ctx: ctx, client: client, deployer: &deployer, }) } // We check for several types of known error which indicate // that the supplied reference was indeed a path but there was // an issue reading the charm located there. if charm.IsMissingSeriesError(charmErr) { return charmErr } if charm.IsUnsupportedSeriesError(charmErr) { return errors.Errorf("%v. Use --force to deploy the charm anyway.", charmErr) } if errors.Cause(charmErr) == zip.ErrFormat { return errors.Errorf("invalid charm or bundle provided at %q", c.CharmOrBundle) } err = charmErr } if _, ok := err.(*charmrepo.NotFoundError); ok { return errors.Errorf("no charm or bundle found at %q", c.CharmOrBundle) } // If we get a "not exists" error then we attempt to interpret the supplied // charm or bundle reference as a URL below, otherwise we return the error. if err != nil && err != os.ErrNotExist { return err } conf, err := getClientConfig(client) if err != nil { return err } bakeryClient, err := c.BakeryClient() if err != nil { return errors.Trace(err) } csClient := newCharmStoreClient(bakeryClient).WithChannel(c.Channel) resolver := newCharmURLResolver(conf, csClient) var storeCharmOrBundleURL *charm.URL var store *charmrepo.CharmStore var supportedSeries []string // If we don't already have a bundle loaded, we try the charm store for a charm or bundle. if bundleData == nil { // Charm or bundle has been supplied as a URL so we resolve and deploy using the store. storeCharmOrBundleURL, c.Channel, supportedSeries, store, err = resolver.resolve(c.CharmOrBundle) if charm.IsUnsupportedSeriesError(err) { return errors.Errorf("%v. Use --force to deploy the charm anyway.", err) } if err != nil { return errors.Trace(err) } if storeCharmOrBundleURL.Series == "bundle" { // Load the bundle entity. bundle, err := store.GetBundle(storeCharmOrBundleURL) if err != nil { return errors.Trace(err) } bundleData = bundle.Data() bundleIdent = storeCharmOrBundleURL.String() } } // Handle a bundle. if bundleData != nil { if flags := getFlags(c.flagSet, charmOnlyFlags); len(flags) > 0 { return errors.Errorf("Flags provided but not supported when deploying a bundle: %s.", strings.Join(flags, ", ")) } // TODO(ericsnow) Do something with the CS macaroons that were returned? if _, err := deployBundle( bundleFilePath, bundleData, c.Channel, client, &deployer, resolver, ctx, c.BundleStorage, ); err != nil { return errors.Trace(err) } ctx.Infof("deployment of bundle %q completed", bundleIdent) return nil } // Handle a charm. if flags := getFlags(c.flagSet, bundleOnlyFlags); len(flags) > 0 { return errors.Errorf("Flags provided but not supported when deploying a charm: %s.", strings.Join(flags, ", ")) } // Get the series to use. series, message, err := charmSeries(c.Series, storeCharmOrBundleURL.Series, supportedSeries, c.Force, conf, deployFromCharm) if charm.IsUnsupportedSeriesError(err) { return errors.Errorf("%v. Use --force to deploy the charm anyway.", err) } // Store the charm in state. curl, csMac, err := addCharmFromURL(client, storeCharmOrBundleURL, c.Channel, csClient) if err != nil { if err1, ok := errors.Cause(err).(*termsRequiredError); ok { terms := strings.Join(err1.Terms, " ") return errors.Errorf(`Declined: please agree to the following terms %s. Try: "juju agree %s"`, terms, terms) } return errors.Annotatef(err, "storing charm for URL %q", storeCharmOrBundleURL) } ctx.Infof("Added charm %q to the model.", curl) ctx.Infof("Deploying charm %q %v.", curl, fmt.Sprintf(message, series)) id := charmstore.CharmID{ URL: curl, Channel: c.Channel, } return c.deployCharm(deployCharmArgs{ id: id, csMac: csMac, series: series, ctx: ctx, client: client, deployer: &deployer, }) }