// 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 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 }
// runMachineUpdate connects via ssh to the machine and runs the update script func runMachineUpdate(client *api.Client, id string, sshArg string) error { progress("updating machine: %v\n", id) addr, err := client.PublicAddress(id) if err != nil { return errors.Annotate(err, "no public address found") } return runViaSsh(addr, sshArg) }
func getMetaResources(cURL *charm.URL, client *api.Client) (map[string]charmresource.Meta, error) { // this gets the charm info that was added to the controller using addcharm. charmInfo, err := client.CharmInfo(cURL.String()) if err != nil { return nil, errors.Trace(err) } return charmInfo.Meta.Resources, nil }
func (c *destroyEnvironmentCommand) destroyEnv(apiclient *api.Client) (result error) { defer func() { result = c.ensureUserFriendlyErrorLog(result) }() err := apiclient.DestroyEnvironment() if cmdErr := processDestroyError(err); cmdErr != nil { return cmdErr } return 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, channel csparams.Channel, csClient *csclient.Client) (*charm.URL, *macaroon.Macaroon, error) { var csMac *macaroon.Macaroon 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 } return curl, csMac, 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. 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 }
// upgradeCharm upgrades the charm for the given service to the given charm id. // If the service is already deployed using the given charm id, do nothing. // This function returns an error if the existing charm and the target one are // incompatible, meaning an upgrade from one to the other is not allowed. func upgradeCharm(client *api.Client, log deploymentLogger, service, id string) error { existing, err := client.ServiceGetCharmURL(service) if err != nil { return errors.Annotatef(err, "cannot retrieve info for service %q", service) } if existing.String() == id { log.Infof("reusing service %s (charm: %s)", service, id) return nil } url, err := charm.ParseURL(id) if err != nil { return errors.Annotatef(err, "cannot parse charm URL %q", id) } if url.WithRevision(-1).Path() != existing.WithRevision(-1).Path() { return errors.Errorf("bundle charm %q is incompatible with existing charm %q", id, existing) } if err := client.ServiceSetCharm(service, id, false); err != nil { return errors.Annotatef(err, "cannot upgrade charm to %q", id) } log.Infof("upgraded charm for existing service %s (from %s to %s)", service, existing, id) return 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) } } }
// resolveCharmURL returns a resolved charm URL, given a charm location string. // If the series is not resolved, the environment default-series is used, or if // not set, the series is resolved with the state server. func resolveCharmURL(url string, client *api.Client, conf *config.Config) (*charm.URL, error) { ref, err := charm.ParseReference(url) if err != nil { return nil, err } // If series is not set, use configured default series if ref.Series == "" { if defaultSeries, ok := conf.DefaultSeries(); ok { ref.Series = defaultSeries } } if ref.Series != "" { return ref.URL("") } // Otherwise, look up the best supported series for this charm if ref.Schema != "local" { return client.ResolveCharm(ref) } possibleURL := *ref possibleURL.Series = "precise" 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, fmt.Errorf("cannot resolve series for charm: %q", ref) }
// initVersions collects state relevant to an upgrade decision. The returned // agent and client versions, and the list of currently available tools, will // always be accurate; the chosen version, and the flag indicating development // mode, may remain blank until uploadTools or validate is called. func (c *UpgradeJujuCommand) initVersions(client *api.Client, cfg *config.Config) (*upgradeContext, error) { agent, ok := cfg.AgentVersion() if !ok { // Can't happen. In theory. return nil, fmt.Errorf("incomplete environment configuration") } if c.Version == agent { return nil, errUpToDate } clientVersion := version.Current.Number findResult, err := client.FindTools(clientVersion.Major, -1, "", "") if err != nil { return nil, err } err = findResult.Error if findResult.Error != nil { if !params.IsCodeNotFound(err) { return nil, err } if !c.UploadTools { // No tools found and we shouldn't upload any, so if we are not asking for a // major upgrade, pretend there is no more recent version available. if c.Version == version.Zero && agent.Major == clientVersion.Major { return nil, errUpToDate } return nil, err } } return &upgradeContext{ agent: agent, client: clientVersion, chosen: c.Version, tools: findResult.List, apiClient: client, config: cfg, }, 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. // // 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 }
// deployBundle deploys the given bundle data using the given API client and // charm store client. The deployment is not transactional, and its progress is // notified using the given deployment logger. func deployBundle( data *charm.BundleData, client *api.Client, serviceDeployer *serviceDeployer, csclient *csClient, repoPath string, conf *config.Config, log deploymentLogger, bundleStorage map[string]map[string]storage.Constraints, ) error { verifyConstraints := func(s string) error { _, err := constraints.Parse(s) return err } verifyStorage := func(s string) error { _, err := storage.ParseConstraints(s) return err } if err := data.Verify(verifyConstraints, verifyStorage); err != nil { return errors.Annotate(err, "cannot deploy bundle") } // Retrieve bundle changes. changes := bundlechanges.FromData(data) numChanges := len(changes) // Initialize the unit status. status, err := client.Status(nil) if err != nil { return errors.Annotate(err, "cannot get model status") } unitStatus := make(map[string]string, numChanges) for _, serviceData := range status.Services { for unit, unitData := range serviceData.Units { unitStatus[unit] = unitData.Machine } } // Instantiate a watcher used to follow the deployment progress. watcher, err := watchAll(client) if err != nil { return errors.Annotate(err, "cannot watch model") } defer watcher.Stop() serviceClient, err := serviceDeployer.newServiceAPIClient() if err != nil { return errors.Annotate(err, "cannot get service client") } annotationsClient, err := serviceDeployer.newAnnotationsAPIClient() if err != nil { return errors.Annotate(err, "cannot get annotations client") } // Instantiate the bundle handler. h := &bundleHandler{ changes: changes, results: make(map[string]string, numChanges), client: client, serviceClient: serviceClient, annotationsClient: annotationsClient, serviceDeployer: serviceDeployer, bundleStorage: bundleStorage, csclient: csclient, repoPath: repoPath, conf: conf, log: log, data: data, unitStatus: unitStatus, ignoredMachines: make(map[string]bool, len(data.Services)), ignoredUnits: make(map[string]bool, len(data.Services)), watcher: watcher, } // Deploy the bundle. for _, change := range changes { switch change := change.(type) { case *bundlechanges.AddCharmChange: err = h.addCharm(change.Id(), change.Params) case *bundlechanges.AddMachineChange: err = h.addMachine(change.Id(), change.Params) case *bundlechanges.AddRelationChange: err = h.addRelation(change.Id(), change.Params) case *bundlechanges.AddServiceChange: err = h.addService(change.Id(), change.Params) case *bundlechanges.AddUnitChange: err = h.addUnit(change.Id(), change.Params) case *bundlechanges.ExposeChange: err = h.exposeService(change.Id(), change.Params) case *bundlechanges.SetAnnotationsChange: err = h.setAnnotations(change.Id(), change.Params) default: return errors.Errorf("unknown change type: %T", change) } if err != nil { return errors.Annotate(err, "cannot deploy bundle") } } return 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 assertEnvironmentName(c *gc.C, client *api.Client, expectName string) { envInfo, err := client.EnvironmentInfo() c.Assert(err, jc.ErrorIsNil) c.Assert(envInfo.Name, gc.Equals, expectName) }
// deployBundle deploys the given bundle data using the given API client and // charm store client. The deployment is not transactional, and its progress is // notified using the given deployment logger. func deployBundle( bundleFilePath string, data *charm.BundleData, channel csparams.Channel, client *api.Client, serviceDeployer *serviceDeployer, resolver *charmURLResolver, log deploymentLogger, bundleStorage map[string]map[string]storage.Constraints, ) (map[*charm.URL]*macaroon.Macaroon, error) { verifyConstraints := func(s string) error { _, err := constraints.Parse(s) return err } verifyStorage := func(s string) error { _, err := storage.ParseConstraints(s) return err } var verifyError error if bundleFilePath == "" { verifyError = data.Verify(verifyConstraints, verifyStorage) } else { verifyError = data.VerifyLocal(bundleFilePath, verifyConstraints, verifyStorage) } if verifyError != nil { if verr, ok := verifyError.(*charm.VerificationError); ok { errs := make([]string, len(verr.Errors)) for i, err := range verr.Errors { errs[i] = err.Error() } return nil, errors.New("the provided bundle has the following errors:\n" + strings.Join(errs, "\n")) } return nil, errors.Annotate(verifyError, "cannot deploy bundle") } // Retrieve bundle changes. changes := bundlechanges.FromData(data) numChanges := len(changes) // Initialize the unit status. status, err := client.Status(nil) if err != nil { return nil, errors.Annotate(err, "cannot get model status") } unitStatus := make(map[string]string, numChanges) for _, serviceData := range status.Services { for unit, unitData := range serviceData.Units { unitStatus[unit] = unitData.Machine } } // Instantiate a watcher used to follow the deployment progress. watcher, err := watchAll(client) if err != nil { return nil, errors.Annotate(err, "cannot watch model") } defer watcher.Stop() serviceClient, err := serviceDeployer.newServiceAPIClient() if err != nil { return nil, errors.Annotate(err, "cannot get service client") } annotationsClient, err := serviceDeployer.newAnnotationsAPIClient() if err != nil { return nil, errors.Annotate(err, "cannot get annotations client") } // Instantiate the bundle handler. h := &bundleHandler{ bundleDir: bundleFilePath, changes: changes, results: make(map[string]string, numChanges), channel: channel, client: client, serviceClient: serviceClient, annotationsClient: annotationsClient, serviceDeployer: serviceDeployer, bundleStorage: bundleStorage, resolver: resolver, log: log, data: data, unitStatus: unitStatus, ignoredMachines: make(map[string]bool, len(data.Services)), ignoredUnits: make(map[string]bool, len(data.Services)), watcher: watcher, } // Deploy the bundle. csMacs := make(map[*charm.URL]*macaroon.Macaroon) channels := make(map[*charm.URL]csparams.Channel) for _, change := range changes { switch change := change.(type) { case *bundlechanges.AddCharmChange: cURL, channel, csMac, err2 := h.addCharm(change.Id(), change.Params) if err2 == nil { csMacs[cURL] = csMac channels[cURL] = channel } err = err2 case *bundlechanges.AddMachineChange: err = h.addMachine(change.Id(), change.Params) case *bundlechanges.AddRelationChange: err = h.addRelation(change.Id(), change.Params) case *bundlechanges.AddServiceChange: var cURL *charm.URL cURL, err = charm.ParseURL(resolve(change.Params.Charm, h.results)) if err == nil { chID := charmstore.CharmID{ URL: cURL, Channel: channels[cURL], } csMac := csMacs[cURL] err = h.addService(change.Id(), change.Params, chID, csMac) } case *bundlechanges.AddUnitChange: err = h.addUnit(change.Id(), change.Params) case *bundlechanges.ExposeChange: err = h.exposeService(change.Id(), change.Params) case *bundlechanges.SetAnnotationsChange: err = h.setAnnotations(change.Id(), change.Params) default: return nil, errors.Errorf("unknown change type: %T", change) } if err != nil { return nil, errors.Annotate(err, "cannot deploy bundle") } } return csMacs, nil }
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, }) }
func (c *DeployCommand) deployCharm( curl *charm.URL, series string, ctx *cmd.Context, client *api.Client, deployer *serviceDeployer, ) (rErr error) { if c.BumpRevision { ctx.Infof("--upgrade (or -u) is deprecated and ignored; charms are always deployed with a unique revision.") } 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.PlacementSpec == "" { 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 } } state, err := c.NewAPIRoot() if err != nil { return errors.Trace(err) } httpClient, err := c.HTTPClient() if err != nil { return errors.Trace(err) } deployInfo := DeploymentInfo{ CharmURL: curl, ServiceName: serviceName, ModelUUID: client.ModelUUID(), } for _, step := range c.Steps { err = step.RunPre(state, httpClient, ctx, deployInfo) if err != nil { return err } } defer func() { for _, step := range c.Steps { err = step.RunPost(state, httpClient, ctx, deployInfo, rErr) if err != nil { rErr = err } } }() if err := deployer.serviceDeploy(serviceDeployParams{ curl.String(), serviceName, series, numUnits, string(configYAML), c.Constraints, c.Placement, c.Networks, c.Storage, }); err != nil { return err } state, err = c.NewAPIRoot() if err != nil { return errors.Trace(err) } httpClient, err = c.HTTPClient() if err != nil { return errors.Trace(err) } return err }
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) }