// 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 } conf, err := service.GetClientConfig(client) if err != nil { return errors.Trace(err) } var newRef *charm.Reference if c.SwitchURL != "" { newRef, err = charm.ParseReference(c.SwitchURL) if err != nil { return err } } else { // No new URL specified, but revision might have been. newRef = oldURL.WithRevision(c.Revision).Reference() } httpClient, err := c.HTTPClient() if err != nil { return errors.Trace(err) } csClient := newCharmStoreClient(httpClient) newURL, repo, err := resolveCharmStoreEntityURL(newRef.String(), csClient.params, ctx.AbsPath(c.RepoPath), conf) if err != nil { return errors.Trace(err) } // If no explicit revision was set with either SwitchURL // or Revision flags, discover the latest. if *newURL == *oldURL { if newRef.Revision != -1 { return 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 fmt.Errorf("already running latest charm %q", newURL) } } addedURL, err := addCharmViaAPI(client, newURL, repo, csClient) if err != nil { return block.ProcessBlockedError(err, block.BlockChange) } ctx.Infof("Added charm %q to the environment.", addedURL) return block.ProcessBlockedError(client.ServiceSetCharm(c.ServiceName, addedURL.String(), c.Force), block.BlockChange) }
func (c *DeployCommand) Run(ctx *cmd.Context) error { client, err := c.NewAPIClient() if err != nil { return err } defer client.Close() conf, err := service.GetClientConfig(client) if err != nil { return err } if err := c.CheckProvider(conf); err != nil { return err } csClient, err := newCharmStoreClient() if err != nil { return errors.Trace(err) } defer csClient.jar.Save() curl, repo, err := resolveCharmURL(c.CharmName, csClient.params, ctx.AbsPath(c.RepoPath), conf) if err != nil { return errors.Trace(err) } curl, err = addCharmViaAPI(client, ctx, curl, repo, csClient) if err != nil { return block.ProcessBlockedError(err, block.BlockChange) } 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 } } // If storage or placement is specified, we attempt to use a new API on the service facade. if len(c.Storage) > 0 || len(c.Placement) > 0 { notSupported := errors.New("cannot deploy charms with storage or placement: not supported by the API server") serviceClient, err := c.newServiceAPIClient() if err != nil { return notSupported } defer serviceClient.Close() for i, p := range c.Placement { if p.Scope == "env-uuid" { p.Scope = serviceClient.EnvironmentUUID() } c.Placement[i] = p } err = serviceClient.ServiceDeploy( curl.String(), serviceName, numUnits, string(configYAML), c.Constraints, c.PlacementSpec, c.Placement, []string{}, c.Storage, ) if params.IsCodeNotImplemented(err) { return notSupported } return block.ProcessBlockedError(err, block.BlockChange) } if len(c.Networks) > 0 { ctx.Infof("use of --networks is deprecated and is ignored. Please use spaces to manage placement within networks") } err = client.ServiceDeploy( curl.String(), serviceName, numUnits, string(configYAML), c.Constraints, c.PlacementSpec) if err != nil { return block.ProcessBlockedError(err, block.BlockChange) } state, err := c.NewAPIRoot() if err != nil { return err } err = registerMeteredCharm(c.RegisterURL, state, csClient.jar, curl.String(), serviceName, client.EnvironmentUUID()) if params.IsCodeNotImplemented(err) { // The state server is too old to support metering. Warn // the user, but don't return an error. logger.Warningf("current state server version does not support charm metering") return nil } return block.ProcessBlockedError(err, block.BlockChange) }
func (c *DeployCommand) deployCharmOrBundle(ctx *cmd.Context, client *api.Client) error { deployer := serviceDeployer{ctx, c.newServiceAPIClient} // 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 := service.GetClientConfig(client) if err != nil { return err } if err := c.CheckProvider(conf); 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 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. // 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 environment.", curl) ctx.Infof("Deploying charm %q %v.", curl, fmt.Sprintf(message, series)) return c.deployCharm(curl, series, ctx, client, &deployer) }
func (c *DeployCommand) Run(ctx *cmd.Context) error { client, err := c.NewAPIClient() if err != nil { return err } defer client.Close() conf, err := service.GetClientConfig(client) if err != nil { return err } if err := c.CheckProvider(conf); err != nil { return err } csClient, err := newCharmStoreClient() if err != nil { return errors.Trace(err) } defer csClient.jar.Save() curl, repo, err := resolveCharmURL(c.CharmName, csClient.params, ctx.AbsPath(c.RepoPath), conf) if err != nil { return errors.Trace(err) } curl, err = addCharmViaAPI(client, ctx, curl, repo, csClient) if err != nil { return block.ProcessBlockedError(err, block.BlockChange) } 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 } } // If storage is specified, we attempt to use a new API on the service facade. if len(c.Storage) > 0 { notSupported := errors.New("cannot deploy charms with storage: not supported by the API server") serviceClient, err := c.newServiceAPIClient() if err != nil { return notSupported } defer serviceClient.Close() err = serviceClient.ServiceDeploy( curl.String(), serviceName, numUnits, string(configYAML), c.Constraints, c.ToMachineSpec, requestedNetworks, c.Storage, ) if params.IsCodeNotImplemented(err) { return notSupported } return block.ProcessBlockedError(err, block.BlockChange) } 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) } if err != nil { return block.ProcessBlockedError(err, block.BlockChange) } state, err := c.NewAPIRoot() if err != nil { return err } err = registerMeteredCharm(c.RegisterURL, state, csClient.jar, curl.String(), serviceName, client.EnvironmentUUID()) if err != nil { return err } return block.ProcessBlockedError(err, block.BlockChange) }
// 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 := service.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 environment.", addedURL) return addedURL, nil }
func (c *deployCommand) Run(ctx *cmd.Context) error { client, err := c.NewAPIClient() if err != nil { return err } defer client.Close() conf, err := service.GetClientConfig(client) if err != nil { return err } if err := c.CheckProvider(conf); err != nil { return err } httpClient, err := c.HTTPClient() if err != nil { return errors.Trace(err) } csClient := newCharmStoreClient(httpClient) repoPath := ctx.AbsPath(c.RepoPath) // Handle local bundle paths. f, err := os.Open(c.CharmOrBundle) if err == nil { defer f.Close() info, err := f.Stat() if err != nil { return block.ProcessBlockedError(err, block.BlockChange) } if info.IsDir() { return errors.New("deployment of bundle directories not yet supported") } bundleData, err := charm.ReadBundleData(f) if err != nil { return block.ProcessBlockedError(err, block.BlockChange) } if err := deployBundle(bundleData, client, csClient, repoPath, conf, ctx); err != nil { return block.ProcessBlockedError(err, block.BlockChange) } ctx.Infof("deployment of bundle %q completed", f.Name()) return nil } else if !os.IsNotExist(err) { logger.Warningf("cannot open %q: %v; falling back to using charm repository", c.CharmOrBundle, err) } curl, repo, err := resolveCharmStoreEntityURL(c.CharmOrBundle, csClient.params, repoPath, conf) if err != nil { return errors.Trace(err) } // Handle bundle URLs. if curl.Series == "bundle" { // Deploy a bundle entity. bundle, err := repo.GetBundle(curl) if err != nil { return block.ProcessBlockedError(err, block.BlockChange) } if err := deployBundle(bundle.Data(), client, csClient, repoPath, conf, ctx); err != nil { return block.ProcessBlockedError(err, block.BlockChange) } ctx.Infof("deployment of bundle %q completed", curl) return nil } curl, err = addCharmViaAPI(client, curl, repo, csClient) if err != nil { return block.ProcessBlockedError(err, block.BlockChange) } ctx.Infof("Added charm %q to the environment.", curl) 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 } } // If storage or placement is specified, we attempt to use a new API on the service facade. if len(c.Storage) > 0 || len(c.Placement) > 0 { notSupported := errors.New("cannot deploy charms with storage or placement: not supported by the API server") serviceClient, err := c.newServiceAPIClient() if err != nil { return notSupported } defer serviceClient.Close() for i, p := range c.Placement { if p.Scope == "env-uuid" { p.Scope = serviceClient.EnvironmentUUID() } c.Placement[i] = p } err = serviceClient.ServiceDeploy( curl.String(), serviceName, numUnits, string(configYAML), c.Constraints, c.PlacementSpec, c.Placement, []string{}, c.Storage, ) if params.IsCodeNotImplemented(err) { return notSupported } return block.ProcessBlockedError(err, block.BlockChange) } if len(c.Networks) > 0 { ctx.Infof("use of --networks is deprecated and is ignored. Please use spaces to manage placement within networks") } err = client.ServiceDeploy( curl.String(), serviceName, numUnits, string(configYAML), c.Constraints, c.PlacementSpec) if err != nil { return block.ProcessBlockedError(err, block.BlockChange) } state, err := c.NewAPIRoot() if err != nil { return err } err = registerMeteredCharm(c.RegisterURL, state, httpClient, curl.String(), serviceName, client.EnvironmentUUID()) if params.IsCodeNotImplemented(err) { // The state server is too old to support metering. Warn // the user, but don't return an error. logger.Warningf("current state server version does not support charm metering") return nil } return block.ProcessBlockedError(err, block.BlockChange) }