// Set implements gnuflag.Value.Set. func (f storageFlag) Set(s string) error { fields := strings.SplitN(s, "=", 2) if len(fields) < 2 { return errors.New("expected [<service>:]<store>=<constraints>") } var serviceName, storageName string if colon := strings.IndexRune(fields[0], ':'); colon >= 0 { serviceName = fields[0][:colon] storageName = fields[0][colon+1:] } else { storageName = fields[0] } cons, err := storage.ParseConstraints(fields[1]) if err != nil { return errors.Annotate(err, "cannot parse disk constraints") } var stores map[string]storage.Constraints if serviceName != "" { if *f.bundleStores == nil { *f.bundleStores = make(map[string]map[string]storage.Constraints) } stores = (*f.bundleStores)[serviceName] if stores == nil { stores = make(map[string]storage.Constraints) (*f.bundleStores)[serviceName] = stores } } else { if *f.stores == nil { *f.stores = make(map[string]storage.Constraints) } stores = *f.stores } stores[storageName] = cons return nil }
// Set implements gnuflag.Value.Set. func (f disksFlag) Set(s string) error { for _, field := range strings.Fields(s) { cons, err := storage.ParseConstraints(field) if err != nil { return errors.Annotate(err, "cannot parse disk constraints") } *f.disks = append(*f.disks, cons) } return nil }
// Set implements gnuflag.Value.Set. func (f storageFlag) Set(s string) error { fields := strings.SplitN(s, "=", 2) if len(fields) < 2 { return errors.New("expected <store>=<constraints>") } cons, err := storage.ParseConstraints(fields[1]) if err != nil { return errors.Annotate(err, "cannot parse disk constraints") } if *f.stores == nil { *f.stores = make(map[string]storage.Constraints) } (*f.stores)[fields[0]] = cons return nil }
// GetBundleChanges returns the list of changes required to deploy the given // bundle data. The changes are sorted by requirements, so that they can be // applied in order. func (c *Client) GetBundleChanges(args params.GetBundleChangesParams) (params.GetBundleChangesResults, error) { if err := c.checkCanRead(); err != nil { return params.GetBundleChangesResults{}, err } var results params.GetBundleChangesResults data, err := charm.ReadBundleData(strings.NewReader(args.BundleDataYAML)) if err != nil { return results, errors.Annotate(err, "cannot read bundle YAML") } 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 { if err, ok := err.(*charm.VerificationError); ok { results.Errors = make([]string, len(err.Errors)) for i, e := range err.Errors { results.Errors[i] = e.Error() } return results, nil } // This should never happen as Verify only returns verification errors. return results, errors.Annotate(err, "cannot verify bundle") } changes := bundlechanges.FromData(data) results.Changes = make([]*params.BundleChangesChange, len(changes)) for i, c := range changes { results.Changes[i] = ¶ms.BundleChangesChange{ Id: c.Id(), Method: c.Method(), Args: c.GUIArgs(), Requires: c.Requires(), } } return results, 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( 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 }
// addService deploys or update a service with no units. Service options are // also set or updated. func (h *bundleHandler) addService(id string, p bundlechanges.AddServiceParams, chID charmstore.CharmID, csMac *macaroon.Macaroon) error { h.results[id] = p.Service ch := chID.URL.String() // Handle service configuration. configYAML := "" if len(p.Options) > 0 { config, err := yaml.Marshal(map[string]map[string]interface{}{p.Service: p.Options}) if err != nil { return errors.Annotatef(err, "cannot marshal options for service %q", p.Service) } configYAML = string(config) } // Handle service constraints. cons, err := constraints.Parse(p.Constraints) if err != nil { // This should never happen, as the bundle is already verified. return errors.Annotate(err, "invalid constraints for service") } storageConstraints := h.bundleStorage[p.Service] if len(p.Storage) > 0 { if storageConstraints == nil { storageConstraints = make(map[string]storage.Constraints) } for k, v := range p.Storage { if _, ok := storageConstraints[k]; ok { // Storage constraints overridden // on the command line. continue } cons, err := storage.ParseConstraints(v) if err != nil { return errors.Annotate(err, "invalid storage constraints") } storageConstraints[k] = cons } } resources := make(map[string]string) for resName, revision := range p.Resources { resources[resName] = fmt.Sprint(revision) } charmInfo, err := h.client.CharmInfo(ch) if err != nil { return err } resNames2IDs, err := handleResources(h.serviceDeployer.api, resources, p.Service, chID, csMac, charmInfo.Meta.Resources) if err != nil { return errors.Trace(err) } // Figure out what series we need to deploy with. conf, err := getClientConfig(h.client) if err != nil { return err } supportedSeries := charmInfo.Meta.Series series, message, err := charmSeries(p.Series, chID.URL.Series, supportedSeries, false, conf, deployFromBundle) if err != nil { return errors.Trace(err) } // Deploy the service. if err := h.serviceDeployer.serviceDeploy(serviceDeployParams{ charmID: chID, serviceName: p.Service, series: series, configYAML: configYAML, constraints: cons, storage: storageConstraints, spaceBindings: p.EndpointBindings, resources: resNames2IDs, }); err == nil { h.log.Infof("service %s deployed (charm %s %v)", p.Service, ch, fmt.Sprintf(message, series)) for resName := range resNames2IDs { h.log.Infof("added resource %s", resName) } return nil } else if !isErrServiceExists(err) { return errors.Annotatef(err, "cannot deploy service %q", p.Service) } // The service is already deployed in the environment: check that its // charm is compatible with the one declared in the bundle. If it is, // reuse the existing service or upgrade to a specified revision. // Exit with an error otherwise. if err := h.upgradeCharm(p.Service, chID, csMac, resources); err != nil { return errors.Annotatef(err, "cannot upgrade service %q", p.Service) } // Update service configuration. if configYAML != "" { if err := h.serviceClient.Update(params.ServiceUpdate{ ServiceName: p.Service, SettingsYAML: configYAML, }); err != nil { // This should never happen as possible errors are already returned // by the service Deploy call above. return errors.Annotatef(err, "cannot update options for service %q", p.Service) } h.log.Infof("configuration updated for service %s", p.Service) } // Update service constraints. if p.Constraints != "" { if err := h.serviceClient.SetConstraints(p.Service, cons); err != nil { // This should never happen, as the bundle is already verified. return errors.Annotatef(err, "cannot update constraints for service %q", p.Service) } h.log.Infof("constraints applied for service %s", p.Service) } return 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 }
// addService deploys or update a service with no units. Service options are // also set or updated. func (h *bundleHandler) addService(id string, p bundlechanges.AddServiceParams) error { h.results[id] = p.Service ch := resolve(p.Charm, h.results) // Handle service configuration. configYAML := "" if len(p.Options) > 0 { config, err := yaml.Marshal(map[string]map[string]interface{}{p.Service: p.Options}) if err != nil { return errors.Annotatef(err, "cannot marshal options for service %q", p.Service) } configYAML = string(config) } // Handle service constraints. cons, err := constraints.Parse(p.Constraints) if err != nil { // This should never happen, as the bundle is already verified. return errors.Annotate(err, "invalid constraints for service") } storageConstraints := h.bundleStorage[p.Service] if len(p.Storage) > 0 { if storageConstraints == nil { storageConstraints = make(map[string]storage.Constraints) } for k, v := range p.Storage { if _, ok := storageConstraints[k]; ok { // Storage constraints overridden // on the command line. continue } cons, err := storage.ParseConstraints(v) if err != nil { return errors.Annotate(err, "invalid storage constraints") } storageConstraints[k] = cons } } // Deploy the service. if err := h.serviceDeployer.serviceDeploy(serviceDeployParams{ charmURL: ch, serviceName: p.Service, configYAML: configYAML, constraints: cons, storage: storageConstraints, spaceBindings: p.EndpointBindings, }); err == nil { h.log.Infof("service %s deployed (charm: %s)", p.Service, ch) return nil } else if !isErrServiceExists(err) { return errors.Annotatef(err, "cannot deploy service %q", p.Service) } // The service is already deployed in the environment: check that its // charm is compatible with the one declared in the bundle. If it is, // reuse the existing service or upgrade to a specified revision. // Exit with an error otherwise. if err := upgradeCharm(h.serviceClient, h.log, p.Service, ch); err != nil { return errors.Annotatef(err, "cannot upgrade service %q", p.Service) } // Update service configuration. if configYAML != "" { if err := h.serviceClient.Update(params.ServiceUpdate{ ServiceName: p.Service, SettingsYAML: configYAML, }); err != nil { // This should never happen as possible errors are already returned // by the service Deploy call above. return errors.Annotatef(err, "cannot update options for service %q", p.Service) } h.log.Infof("configuration updated for service %s", p.Service) } // Update service constraints. if p.Constraints != "" { if err := h.serviceClient.SetConstraints(p.Service, cons); err != nil { // This should never happen, as the bundle is already verified. return errors.Annotatef(err, "cannot update constraints for service %q", p.Service) } h.log.Infof("constraints applied for service %s", p.Service) } return nil }
// addService deploys or update an application with no units. Service options are // also set or updated. func (h *bundleHandler) addService( api DeployAPI, id string, p bundlechanges.AddApplicationParams, chID charmstore.CharmID, csMac *macaroon.Macaroon, ) error { h.results[id] = p.Application ch := chID.URL.String() // Handle application configuration. configYAML := "" if len(p.Options) > 0 { config, err := yaml.Marshal(map[string]map[string]interface{}{p.Application: p.Options}) if err != nil { return errors.Annotatef(err, "cannot marshal options for application %q", p.Application) } configYAML = string(config) } // Handle application constraints. cons, err := constraints.Parse(p.Constraints) if err != nil { // This should never happen, as the bundle is already verified. return errors.Annotate(err, "invalid constraints for application") } storageConstraints := h.bundleStorage[p.Application] if len(p.Storage) > 0 { if storageConstraints == nil { storageConstraints = make(map[string]storage.Constraints) } for k, v := range p.Storage { if _, ok := storageConstraints[k]; ok { // Storage constraints overridden // on the command line. continue } cons, err := storage.ParseConstraints(v) if err != nil { return errors.Annotate(err, "invalid storage constraints") } storageConstraints[k] = cons } } resources := make(map[string]string) for resName, revision := range p.Resources { resources[resName] = fmt.Sprint(revision) } charmInfo, err := h.api.CharmInfo(ch) if err != nil { return err } resNames2IDs, err := resourceadapters.DeployResources( p.Application, chID, csMac, resources, charmInfo.Meta.Resources, api, ) if err != nil { return errors.Trace(err) } // Figure out what series we need to deploy with. conf, err := getModelConfig(h.api) if err != nil { return err } supportedSeries := charmInfo.Meta.Series if len(supportedSeries) == 0 && chID.URL.Series != "" { supportedSeries = []string{chID.URL.Series} } selector := seriesSelector{ seriesFlag: p.Series, charmURLSeries: chID.URL.Series, supportedSeries: supportedSeries, conf: conf, fromBundle: true, } series, err := selector.charmSeries() if err != nil { return errors.Trace(err) } // Deploy the application. logger.Debugf("application %s is deploying (charm %s)", p.Application, ch) h.log.Infof("Deploying charm %q", ch) if err := api.Deploy(application.DeployArgs{ CharmID: chID, Cons: cons, ApplicationName: p.Application, Series: series, ConfigYAML: configYAML, Storage: storageConstraints, Resources: resNames2IDs, EndpointBindings: p.EndpointBindings, }); err == nil { for resName := range resNames2IDs { h.log.Infof("added resource %s", resName) } return nil } else if !isErrServiceExists(err) { return errors.Annotatef(err, "cannot deploy application %q", p.Application) } // The application is already deployed in the environment: check that its // charm is compatible with the one declared in the bundle. If it is, // reuse the existing application or upgrade to a specified revision. // Exit with an error otherwise. if err := h.upgradeCharm(api, p.Application, chID, csMac, resources); err != nil { return errors.Annotatef(err, "cannot upgrade application %q", p.Application) } // Update application configuration. if configYAML != "" { if err := h.api.Update(params.ApplicationUpdate{ ApplicationName: p.Application, SettingsYAML: configYAML, }); err != nil { // This should never happen as possible errors are already returned // by the application Deploy call above. return errors.Annotatef(err, "cannot update options for application %q", p.Application) } h.log.Infof("configuration updated for application %s", p.Application) } // Update application constraints. if p.Constraints != "" { if err := h.api.SetConstraints(p.Application, cons); err != nil { // This should never happen, as the bundle is already verified. return errors.Annotatef(err, "cannot update constraints for application %q", p.Application) } h.log.Infof("constraints applied for application %s", p.Application) } return nil }
func (*ConstraintsSuite) testParseError(c *gc.C, s, expectErr string) { _, err := storage.ParseConstraints(s) c.Check(err, gc.ErrorMatches, expectErr) }
func (*ConstraintsSuite) testParse(c *gc.C, s string, expect storage.Constraints) { cons, err := storage.ParseConstraints(s) c.Check(err, jc.ErrorIsNil) c.Check(cons, gc.DeepEquals, expect) }