func (s *changesSuite) TestFromData(c *gc.C) { for i, test := range fromDataTests { c.Logf("test %d: %s", i, test.about) // Retrieve and validate the bundle data. data, err := charm.ReadBundleData(strings.NewReader(test.content)) c.Assert(err, jc.ErrorIsNil) err = data.Verify(nil) c.Assert(err, jc.ErrorIsNil) // Retrieve the changes, and convert them to a sequence of records. changes := bundlechanges.FromData(data) records := make([]record, len(changes)) for i, change := range changes { r := record{ Id: change.Id(), Requires: change.Requires(), Method: change.Method(), GUIArgs: change.GUIArgs(), } r.Params = reflect.ValueOf(change).Elem().FieldByName("Params").Interface() records[i] = r } // Output the records for debugging. b, err := json.MarshalIndent(records, "", " ") c.Assert(err, jc.ErrorIsNil) c.Logf("obtained records: %s", b) // Check that the obtained records are what we expect. c.Assert(records, jc.DeepEquals, test.expected) } }
// process generates and print to w the set of changes required to deploy // the bundle data to be retrieved using r. func process(r io.Reader, w io.Writer) error { // Read the bundle data. data, err := charm.ReadBundleData(r) if err != nil { return err } // Validate the bundle. if err := data.Verify(nil); err != nil { return err } // Generate the changes and convert them to the standard form. changes := bundlechanges.FromData(data) records := make([]*record, len(changes)) for i, change := range changes { records[i] = &record{ Id: change.Id(), Requires: change.Requires(), Method: change.Method(), Args: change.GUIArgs(), } } // Serialize and print the records. content, err := json.MarshalIndent(records, "", " ") if err != nil { return err } fmt.Fprintln(w, string(content)) 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) { var results params.GetBundleChangesResults data, err := charm.ReadBundleData(strings.NewReader(args.BundleDataYAML)) if err != nil { return results, errors.Annotate(err, "cannot read bundle YAML") } if err := data.Verify(func(s string) error { _, err := constraints.Parse(s) return err }); 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 }
// 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 }