github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/apiserver/facades/client/bundle/bundle.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package bundle
     5  
     6  import (
     7  	"bytes"
     8  	"fmt"
     9  	"sort"
    10  	"strconv"
    11  	"strings"
    12  
    13  	"github.com/juju/charm/v12"
    14  	"github.com/juju/charm/v12/resource"
    15  	"github.com/juju/collections/set"
    16  	"github.com/juju/description/v5"
    17  	"github.com/juju/errors"
    18  	"github.com/juju/loggo"
    19  	"github.com/juju/names/v5"
    20  	"gopkg.in/yaml.v2"
    21  
    22  	"github.com/juju/juju/apiserver/common"
    23  	apiservererrors "github.com/juju/juju/apiserver/errors"
    24  	"github.com/juju/juju/apiserver/facade"
    25  	appFacade "github.com/juju/juju/apiserver/facades/client/application"
    26  	corebase "github.com/juju/juju/core/base"
    27  	bundlechanges "github.com/juju/juju/core/bundle/changes"
    28  	corecharm "github.com/juju/juju/core/charm"
    29  	"github.com/juju/juju/core/constraints"
    30  	"github.com/juju/juju/core/devices"
    31  	"github.com/juju/juju/core/network"
    32  	"github.com/juju/juju/core/network/firewall"
    33  	"github.com/juju/juju/core/permission"
    34  	"github.com/juju/juju/environs/config"
    35  	"github.com/juju/juju/rpc/params"
    36  	"github.com/juju/juju/state"
    37  	"github.com/juju/juju/storage"
    38  	"github.com/juju/juju/version"
    39  )
    40  
    41  // APIv6 provides the Bundle API facade for version 6. It is otherwise
    42  // identical to V5 with the exception that the V6 adds the support for
    43  // multi-part yaml handling to GetChanges and GetChangesMapArgs.
    44  type APIv6 struct {
    45  	*BundleAPI
    46  }
    47  
    48  // BundleAPI implements the Bundle interface and is the concrete implementation
    49  // of the API end point.
    50  type BundleAPI struct {
    51  	backend    Backend
    52  	authorizer facade.Authorizer
    53  	modelTag   names.ModelTag
    54  }
    55  
    56  // NewFacade provides the required signature for facade registration.
    57  func newFacade(ctx facade.Context) (*BundleAPI, error) {
    58  	authorizer := ctx.Auth()
    59  	st := ctx.State()
    60  
    61  	return NewBundleAPI(
    62  		NewStateShim(st),
    63  		authorizer,
    64  		names.NewModelTag(st.ModelUUID()),
    65  	)
    66  }
    67  
    68  // NewBundleAPI returns the new Bundle API facade.
    69  func NewBundleAPI(
    70  	st Backend,
    71  	auth facade.Authorizer,
    72  	tag names.ModelTag,
    73  ) (*BundleAPI, error) {
    74  	if !auth.AuthClient() {
    75  		return nil, apiservererrors.ErrPerm
    76  	}
    77  
    78  	return &BundleAPI{
    79  		backend:    st,
    80  		authorizer: auth,
    81  		modelTag:   tag,
    82  	}, nil
    83  }
    84  
    85  func (b *BundleAPI) checkCanRead() error {
    86  	return b.authorizer.HasPermission(permission.ReadAccess, b.modelTag)
    87  }
    88  
    89  type validators struct {
    90  	verifyConstraints func(string) error
    91  	verifyStorage     func(string) error
    92  	verifyDevices     func(string) error
    93  }
    94  
    95  // GetChanges returns the list of changes required to deploy the given bundle
    96  // data. The changes are sorted by requirements, so that they can be applied in
    97  // order.
    98  // GetChanges has been superseded in favour of GetChangesMapArgs. It's
    99  // preferable to use that new method to add new functionality and move clients
   100  // away from this one.
   101  func (b *BundleAPI) GetChanges(args params.BundleChangesParams) (params.BundleChangesResults, error) {
   102  	vs := validators{
   103  		verifyConstraints: func(s string) error {
   104  			_, err := constraints.Parse(s)
   105  			return err
   106  		},
   107  		verifyStorage: func(s string) error {
   108  			_, err := storage.ParseConstraints(s)
   109  			return err
   110  		},
   111  		verifyDevices: func(s string) error {
   112  			_, err := devices.ParseConstraints(s)
   113  			return err
   114  		},
   115  	}
   116  
   117  	var results params.BundleChangesResults
   118  	changes, validationErrors, err := b.doGetBundleChanges(args, vs)
   119  	if err != nil {
   120  		return results, errors.Trace(err)
   121  	}
   122  	if len(validationErrors) > 0 {
   123  		results.Errors = make([]string, len(validationErrors))
   124  		for k, v := range validationErrors {
   125  			results.Errors[k] = v.Error()
   126  		}
   127  		return results, nil
   128  	}
   129  	err = mapBundleChanges(changes, &results)
   130  	return results, errors.Trace(err)
   131  
   132  }
   133  
   134  func mapBundleChanges(changes []bundlechanges.Change, results *params.BundleChangesResults) error {
   135  	results.Changes = make([]*params.BundleChange, len(changes))
   136  	for i, c := range changes {
   137  		var guiArgs []interface{}
   138  		switch c := c.(type) {
   139  		case *bundlechanges.AddApplicationChange:
   140  			guiArgs = c.GUIArgsWithDevices()
   141  		default:
   142  			guiArgs = c.GUIArgs()
   143  		}
   144  		results.Changes[i] = &params.BundleChange{
   145  			Id:       c.Id(),
   146  			Method:   c.Method(),
   147  			Args:     guiArgs,
   148  			Requires: c.Requires(),
   149  		}
   150  	}
   151  	return nil
   152  }
   153  
   154  func (b *BundleAPI) doGetBundleChanges(
   155  	args params.BundleChangesParams,
   156  	vs validators,
   157  ) ([]bundlechanges.Change, []error, error) {
   158  	dataSource, _ := charm.StreamBundleDataSource(strings.NewReader(args.BundleDataYAML), args.BundleURL)
   159  	data, err := charm.ReadAndMergeBundleData(dataSource)
   160  	if err != nil {
   161  		return nil, nil, errors.Annotate(err, "cannot read bundle YAML")
   162  	}
   163  	if err := data.Verify(vs.verifyConstraints, vs.verifyStorage, vs.verifyDevices); err != nil {
   164  		if verificationError, ok := err.(*charm.VerificationError); ok {
   165  			validationErrors := make([]error, len(verificationError.Errors))
   166  			for i, e := range verificationError.Errors {
   167  				validationErrors[i] = e
   168  			}
   169  			return nil, validationErrors, nil
   170  		}
   171  		// This should never happen as Verify only returns verification errors.
   172  		return nil, nil, errors.Annotate(err, "cannot verify bundle")
   173  	}
   174  	changes, err := bundlechanges.FromData(
   175  		bundlechanges.ChangesConfig{
   176  			Bundle:    data,
   177  			BundleURL: args.BundleURL,
   178  			Logger:    loggo.GetLogger("juju.apiserver.bundlechanges"),
   179  		})
   180  	if err != nil {
   181  		return nil, nil, errors.Trace(err)
   182  	}
   183  	return changes, nil, nil
   184  }
   185  
   186  // GetChangesMapArgs returns the list of changes required to deploy the given
   187  // bundle data. The changes are sorted by requirements, so that they can be
   188  // applied in order.
   189  // V4 GetChangesMapArgs is not supported on anything less than v4
   190  func (b *BundleAPI) GetChangesMapArgs(args params.BundleChangesParams) (params.BundleChangesMapArgsResults, error) {
   191  	vs := validators{
   192  		verifyConstraints: func(s string) error {
   193  			_, err := constraints.Parse(s)
   194  			return err
   195  		},
   196  		verifyStorage: func(s string) error {
   197  			_, err := storage.ParseConstraints(s)
   198  			return err
   199  		},
   200  		verifyDevices: func(s string) error {
   201  			_, err := devices.ParseConstraints(s)
   202  			return err
   203  		},
   204  	}
   205  	return b.doGetBundleChangesMapArgs(args, vs, func(changes []bundlechanges.Change, results *params.BundleChangesMapArgsResults) error {
   206  		results.Changes = make([]*params.BundleChangesMapArgs, len(changes))
   207  		results.Errors = make([]string, len(changes))
   208  		for i, c := range changes {
   209  			args, err := c.Args()
   210  			if err != nil {
   211  				results.Errors[i] = err.Error()
   212  				continue
   213  			}
   214  			results.Changes[i] = &params.BundleChangesMapArgs{
   215  				Id:       c.Id(),
   216  				Method:   c.Method(),
   217  				Args:     args,
   218  				Requires: c.Requires(),
   219  			}
   220  		}
   221  		return nil
   222  	})
   223  }
   224  
   225  func (b *BundleAPI) doGetBundleChangesMapArgs(
   226  	args params.BundleChangesParams,
   227  	vs validators,
   228  	postProcess func([]bundlechanges.Change, *params.BundleChangesMapArgsResults) error,
   229  ) (params.BundleChangesMapArgsResults, error) {
   230  	var results params.BundleChangesMapArgsResults
   231  	changes, validationErrors, err := b.doGetBundleChanges(args, vs)
   232  	if err != nil {
   233  		return results, errors.Trace(err)
   234  	}
   235  	if len(validationErrors) > 0 {
   236  		results.Errors = make([]string, len(validationErrors))
   237  		for k, v := range validationErrors {
   238  			results.Errors[k] = v.Error()
   239  		}
   240  		return results, nil
   241  	}
   242  	err = postProcess(changes, &results)
   243  	return results, err
   244  }
   245  
   246  // ExportBundle exports the current model configuration as bundle.
   247  func (b *BundleAPI) ExportBundle(arg params.ExportBundleParams) (params.StringResult, error) {
   248  	fail := func(failErr error) (params.StringResult, error) {
   249  		return params.StringResult{}, apiservererrors.ServerError(failErr)
   250  	}
   251  
   252  	if err := b.checkCanRead(); err != nil {
   253  		return fail(err)
   254  	}
   255  
   256  	exportConfig := b.backend.GetExportConfig()
   257  	model, err := b.backend.ExportPartial(exportConfig)
   258  	if err != nil {
   259  		return fail(err)
   260  	}
   261  
   262  	// Fill it in charm.BundleData data structure.
   263  	bundleData, err := b.fillBundleData(model, arg.IncludeCharmDefaults, arg.IncludeSeries, b.backend)
   264  	if err != nil {
   265  		return fail(err)
   266  	}
   267  
   268  	// Split the bundle into a base and overlay bundle and encode as a
   269  	// yaml multi-doc.
   270  	base, overlay, err := charm.ExtractBaseAndOverlayParts(bundleData)
   271  	if err != nil {
   272  		return fail(err)
   273  	}
   274  
   275  	// First create a bundle output from the bundle data.
   276  	var buf bytes.Buffer
   277  	enc := yaml.NewEncoder(&buf)
   278  	if err != nil {
   279  		return fail(err)
   280  	}
   281  	if err = enc.Encode(bundleOutputFromBundleData(base)); err != nil {
   282  		return fail(err)
   283  	}
   284  
   285  	// Secondly create an output from the overlay. We do it this way, so we can
   286  	// insert the correct comments for users.
   287  	output := buf.String()
   288  	buf.Reset()
   289  	if err = enc.Encode(overlay); err != nil {
   290  		return fail(err)
   291  	} else if err = enc.Close(); err != nil {
   292  		return fail(err)
   293  	}
   294  	overlayOutput := buf.String()
   295  
   296  	// If the overlay part is empty, ignore it; otherwise, inject a
   297  	// comment to let users know that the second document can be extracted
   298  	// out and used as a standalone overlay.
   299  	if !strings.HasPrefix(overlayOutput, "--- {}\n") {
   300  		// strip off the first three dashes and merge the base bundle and the
   301  		// overlay.
   302  		if strings.HasPrefix(overlayOutput, "---") {
   303  			overlayOutput = strings.Replace(overlayOutput, "---", "--- # overlay.yaml", 1)
   304  			output += overlayOutput
   305  		} else {
   306  			return fail(errors.Errorf("expected yaml encoder to delineate multiple documents with \"---\" separator"))
   307  		}
   308  	}
   309  
   310  	return params.StringResult{Result: output}, nil
   311  }
   312  
   313  // bundleOutput has the same top level keys as the charm.BundleData
   314  // but in a more user oriented output order, with the description first,
   315  // then the distro base/series, then the apps, machines and releations.
   316  type bundleOutput struct {
   317  	Type         string                            `yaml:"bundle,omitempty"`
   318  	Description  string                            `yaml:"description,omitempty"`
   319  	DefaultBase  string                            `yaml:"default-base,omitempty"`
   320  	Series       string                            `yaml:"series,omitempty"`
   321  	Saas         map[string]*charm.SaasSpec        `yaml:"saas,omitempty"`
   322  	Applications map[string]*charm.ApplicationSpec `yaml:"applications,omitempty"`
   323  	Machines     map[string]*charm.MachineSpec     `yaml:"machines,omitempty"`
   324  	Relations    [][]string                        `yaml:"relations,omitempty"`
   325  }
   326  
   327  func bundleOutputFromBundleData(bd *charm.BundleData) *bundleOutput {
   328  	return &bundleOutput{
   329  		Type:         bd.Type,
   330  		Description:  bd.Description,
   331  		DefaultBase:  bd.DefaultBase,
   332  		Series:       bd.Series,
   333  		Saas:         bd.Saas,
   334  		Applications: bd.Applications,
   335  		Machines:     bd.Machines,
   336  		Relations:    bd.Relations,
   337  	}
   338  }
   339  
   340  func (b *BundleAPI) fillBundleData(model description.Model, includeCharmDefaults, includeSeries bool, backend Backend) (*charm.BundleData, error) {
   341  	cfg, err := config.New(config.NoDefaults, model.Config())
   342  	if err != nil {
   343  		return nil, errors.Trace(err)
   344  	}
   345  	var defaultBase corebase.Base
   346  	value, ok := cfg.DefaultBase()
   347  	if ok {
   348  		var err error
   349  		defaultBase, err = corebase.ParseBaseFromString(value)
   350  		if err != nil {
   351  			return nil, err
   352  		}
   353  	} else {
   354  		defaultBase = version.DefaultSupportedLTSBase()
   355  	}
   356  
   357  	data := &charm.BundleData{}
   358  
   359  	isCAAS := model.Type() == description.CAAS
   360  	if isCAAS {
   361  		data.Type = "kubernetes"
   362  	} else {
   363  		data.DefaultBase = defaultBase.String()
   364  		if includeSeries {
   365  			defaultSeries, err := corebase.GetSeriesFromBase(defaultBase)
   366  			if err != nil {
   367  				return nil, err
   368  			}
   369  			data.Series = defaultSeries
   370  		}
   371  	}
   372  
   373  	if len(model.Applications()) == 0 {
   374  		return nil, errors.Errorf("nothing to export as there are no applications")
   375  	}
   376  
   377  	// Application bundle data.
   378  	var (
   379  		usedBases  set.Strings
   380  		machineIds set.Strings
   381  	)
   382  	data.Applications, machineIds, usedBases, err = b.bundleDataApplications(model.Applications(), defaultBase, isCAAS, includeCharmDefaults, includeSeries, backend)
   383  	if err != nil {
   384  		return nil, err
   385  	}
   386  
   387  	// Machine bundle data.
   388  	var machineBases set.Strings
   389  	data.Machines, machineBases, err = b.bundleDataMachines(model.Machines(), machineIds, defaultBase, includeSeries)
   390  	if err != nil {
   391  		return nil, err
   392  	}
   393  	usedBases = usedBases.Union(machineBases)
   394  
   395  	// Remote Application bundle data.
   396  	data.Saas = bundleDataRemoteApplications(model.RemoteApplications())
   397  
   398  	// Relation bundle data.
   399  	data.Relations = bundleDataRelations(model.Relations())
   400  
   401  	// If there is only one base used, make it the default and remove
   402  	// base from all the apps and machines.
   403  	size := usedBases.Size()
   404  	switch {
   405  	case size == 1:
   406  		used, err := corebase.ParseBaseFromString(usedBases.Values()[0])
   407  		if err != nil {
   408  			return nil, errors.Trace(err)
   409  		}
   410  		if used != defaultBase {
   411  			data.DefaultBase = used.String()
   412  			if includeSeries {
   413  				series, err := corebase.GetSeriesFromBase(used)
   414  				if err != nil {
   415  					return nil, errors.Trace(err)
   416  				}
   417  				data.Series = series
   418  			}
   419  			for _, app := range data.Applications {
   420  				app.Base = ""
   421  				app.Series = ""
   422  			}
   423  			for _, mac := range data.Machines {
   424  				mac.Base = ""
   425  				mac.Series = ""
   426  			}
   427  		}
   428  	case size > 1:
   429  		if !usedBases.Contains(defaultBase.String()) {
   430  			data.DefaultBase = ""
   431  			data.Series = ""
   432  		}
   433  	}
   434  
   435  	if isCAAS {
   436  		// Kubernetes bundles don't specify series right now.
   437  		data.DefaultBase = ""
   438  		data.Series = ""
   439  	}
   440  
   441  	return data, nil
   442  }
   443  
   444  func (b *BundleAPI) bundleDataApplications(
   445  	apps []description.Application,
   446  	defaultBase corebase.Base,
   447  	isCAAS, includeCharmDefaults, includeSeries bool,
   448  	backend Backend,
   449  ) (map[string]*charm.ApplicationSpec, set.Strings, set.Strings, error) {
   450  
   451  	allSpacesInfoLookup, err := b.backend.AllSpaceInfos()
   452  	if err != nil {
   453  		return nil, nil, nil, errors.Annotate(err, "unable to retrieve all space information")
   454  	}
   455  
   456  	applicationData := make(map[string]*charm.ApplicationSpec)
   457  	machineIds := set.NewStrings()
   458  	usedBases := set.NewStrings()
   459  
   460  	charmConfigCache := make(map[string]*charm.Config)
   461  	printEndpointBindingSpaceNames := b.printSpaceNamesInEndpointBindings(apps)
   462  
   463  	for _, application := range apps {
   464  		if application.CharmOrigin() == nil || application.CharmOrigin().Platform() == "" {
   465  			return nil, nil, nil, errors.Errorf("missing charm origin data for %q", application)
   466  		}
   467  		var newApplication *charm.ApplicationSpec
   468  		p, err := corecharm.ParsePlatformNormalize(application.CharmOrigin().Platform())
   469  		if err != nil {
   470  			return nil, nil, nil, fmt.Errorf("extracting charm origin from application description %w", err)
   471  		}
   472  
   473  		appBase, err := corebase.ParseBase(p.OS, p.Channel)
   474  		if err != nil {
   475  			return nil, nil, nil, fmt.Errorf("extracting base from application description %w", err)
   476  		}
   477  		usedBases.Add(appBase.String())
   478  
   479  		var appSeries string
   480  		if includeSeries {
   481  			appSeries, err = corebase.GetSeriesFromBase(appBase)
   482  			if err != nil {
   483  				return nil, nil, nil, errors.Trace(err)
   484  			}
   485  		}
   486  
   487  		endpointsWithSpaceNames, err := b.endpointBindings(application.EndpointBindings(), allSpacesInfoLookup, printEndpointBindingSpaceNames)
   488  		if err != nil {
   489  			return nil, nil, nil, errors.Trace(err)
   490  		}
   491  
   492  		// For security purposes we do not allow both the expose flag
   493  		// and the exposed endpoints fields to be populated in exported
   494  		// bundles. Otherwise, exporting a bundle from a 2.9 controller
   495  		// (with per-endpoint expose settings) and deploying it to a
   496  		// 2.8 controller would result in all application ports to be
   497  		// made accessible from 0.0.0.0/0.
   498  		exposedEndpoints, err := mapExposedEndpoints(application.ExposedEndpoints(), allSpacesInfoLookup)
   499  		if err != nil {
   500  			return nil, nil, nil, errors.Trace(err)
   501  		}
   502  		exposedFlag := application.Exposed() && len(exposedEndpoints) == 0
   503  
   504  		// We need to correctly handle charmhub urls. The internal
   505  		// representation of a charm url is not the same as a external
   506  		// representation, in that only the application name should be rendered.
   507  		// For charmhub charms, ensure that the revision is listed separately,
   508  		// not in the charm url.
   509  		curl, err := charm.ParseURL(application.CharmURL())
   510  		if err != nil {
   511  			return nil, nil, nil, errors.Trace(err)
   512  		}
   513  		var charmURL string
   514  		var revision *int
   515  		switch {
   516  		case charm.CharmHub.Matches(curl.Schema):
   517  			charmURL = curl.Name
   518  			if curl.Revision >= 0 {
   519  				cRev := curl.Revision
   520  				revision = &cRev
   521  			}
   522  		case charm.Local.Matches(curl.Schema):
   523  			charmURL = fmt.Sprintf("local:%s", curl.Name)
   524  			if curl.Revision >= 0 {
   525  				charmURL = fmt.Sprintf("%s-%d", charmURL, curl.Revision)
   526  			}
   527  		default:
   528  			return nil, nil, nil, errors.NotValidf("charm schema %q", curl.Schema)
   529  		}
   530  
   531  		var channel string
   532  		if origin := application.CharmOrigin(); origin != nil {
   533  			channel = origin.Channel()
   534  		}
   535  		if channel == "" {
   536  			channel = application.Channel()
   537  		}
   538  
   539  		charmCfg := application.CharmConfig()
   540  		if includeCharmDefaults {
   541  			// Augment the user specified config with defaults
   542  			// from the charm config metadata.
   543  			cfgInfo, ok := charmConfigCache[charmURL]
   544  			if !ok {
   545  				ch, err := backend.Charm(curl.String())
   546  				if err != nil {
   547  					return nil, nil, nil, errors.Trace(err)
   548  				}
   549  				cfgInfo = ch.Config()
   550  				charmConfigCache[charmURL] = cfgInfo
   551  			}
   552  			for name, opt := range cfgInfo.Options {
   553  				if _, ok := charmCfg[name]; ok {
   554  					continue
   555  				}
   556  				charmCfg[name] = opt.Default
   557  			}
   558  		}
   559  		if application.Subordinate() {
   560  			newApplication = &charm.ApplicationSpec{
   561  				Charm:            charmURL,
   562  				Revision:         revision,
   563  				Channel:          channel,
   564  				Expose:           exposedFlag,
   565  				ExposedEndpoints: exposedEndpoints,
   566  				Options:          charmCfg,
   567  				Annotations:      application.Annotations(),
   568  				EndpointBindings: endpointsWithSpaceNames,
   569  			}
   570  		} else {
   571  			var (
   572  				numUnits  int
   573  				scale     int
   574  				placement string
   575  				ut        []string
   576  			)
   577  			if isCAAS {
   578  				placement = application.Placement()
   579  				scale = len(application.Units())
   580  			} else {
   581  				numUnits = len(application.Units())
   582  				for _, unit := range application.Units() {
   583  					machineID := unit.Machine().Id()
   584  					unitMachine := unit.Machine()
   585  					if names.IsContainerMachine(machineID) {
   586  						machineIds.Add(unitMachine.Parent().Id())
   587  						id := unitMachine.ContainerType() + ":" + unitMachine.Parent().Id()
   588  						ut = append(ut, id)
   589  					} else {
   590  						machineIds.Add(unitMachine.Id())
   591  						ut = append(ut, unitMachine.Id())
   592  					}
   593  				}
   594  			}
   595  
   596  			newApplication = &charm.ApplicationSpec{
   597  				Charm:            charmURL,
   598  				Revision:         revision,
   599  				Channel:          channel,
   600  				NumUnits:         numUnits,
   601  				Scale_:           scale,
   602  				Placement_:       placement,
   603  				To:               ut,
   604  				Expose:           exposedFlag,
   605  				ExposedEndpoints: exposedEndpoints,
   606  				Options:          charmCfg,
   607  				Annotations:      application.Annotations(),
   608  				EndpointBindings: endpointsWithSpaceNames,
   609  			}
   610  		}
   611  
   612  		newApplication.Resources = applicationDataResources(application.Resources())
   613  
   614  		if appBase != defaultBase {
   615  			newApplication.Base = appBase.String()
   616  			if includeSeries {
   617  				newApplication.Series = appSeries
   618  			}
   619  		}
   620  		if result := b.constraints(application.Constraints()); len(result) != 0 {
   621  			newApplication.Constraints = strings.Join(result, " ")
   622  		}
   623  		if cons := application.StorageDirectives(); len(cons) != 0 {
   624  			newApplication.Storage = make(map[string]string)
   625  			for name, constr := range cons {
   626  				if newApplication.Storage[name], err = storage.ToString(storage.Constraints{
   627  					Pool:  constr.Pool(),
   628  					Size:  constr.Size(),
   629  					Count: constr.Count(),
   630  				}); err != nil {
   631  					return nil, nil, nil, errors.NotValidf("storage %q for %q", name, application.Name())
   632  				}
   633  			}
   634  		}
   635  
   636  		// If this application has been trusted by the operator, set the
   637  		// Trust field of the ApplicationSpec to true
   638  		if appConfig := application.ApplicationConfig(); appConfig != nil {
   639  			newApplication.RequiresTrust = appConfig[appFacade.TrustConfigOptionName] == true
   640  		}
   641  
   642  		// Populate offer list
   643  		if offerList := application.Offers(); offerList != nil {
   644  			newApplication.Offers = make(map[string]*charm.OfferSpec)
   645  			for _, offer := range offerList {
   646  				endpoints := offer.Endpoints()
   647  				exposedEndpointNames := make([]string, 0, len(endpoints))
   648  				for _, ep := range endpoints {
   649  					exposedEndpointNames = append(exposedEndpointNames, ep)
   650  				}
   651  				sort.Strings(exposedEndpointNames)
   652  				newApplication.Offers[offer.OfferName()] = &charm.OfferSpec{
   653  					Endpoints: exposedEndpointNames,
   654  					ACL:       b.filterOfferACL(offer.ACL()),
   655  				}
   656  			}
   657  		}
   658  
   659  		applicationData[application.Name()] = newApplication
   660  	}
   661  	return applicationData, machineIds, usedBases, nil
   662  }
   663  
   664  func applicationDataResources(resources []description.Resource) map[string]interface{} {
   665  	var resourceData map[string]interface{}
   666  	for _, res := range resources {
   667  		appRev := res.ApplicationRevision()
   668  		if appRev == nil || appRev.Origin() != resource.OriginStore.String() {
   669  			continue
   670  		}
   671  		if resourceData == nil {
   672  			resourceData = make(map[string]interface{})
   673  		}
   674  		resourceData[res.Name()] = res.ApplicationRevision().Revision()
   675  	}
   676  	return resourceData
   677  }
   678  
   679  func (b *BundleAPI) bundleDataMachines(machines []description.Machine, machineIds set.Strings, defaultBase corebase.Base, includeSeries bool) (map[string]*charm.MachineSpec, set.Strings, error) {
   680  	usedBases := set.NewStrings()
   681  	machineData := make(map[string]*charm.MachineSpec)
   682  	for _, machine := range machines {
   683  		if !machineIds.Contains(machine.Tag().Id()) {
   684  			continue
   685  		}
   686  		macBase, err := corebase.ParseBaseFromString(machine.Base())
   687  		if err != nil {
   688  			return nil, nil, errors.Trace(err)
   689  		}
   690  		usedBases.Add(macBase.String())
   691  
   692  		var macSeries string
   693  		if includeSeries {
   694  			macSeries, err = corebase.GetSeriesFromBase(macBase)
   695  			if err != nil {
   696  				return nil, nil, errors.Trace(err)
   697  			}
   698  		}
   699  		newMachine := &charm.MachineSpec{
   700  			Annotations: machine.Annotations(),
   701  		}
   702  		if macBase != defaultBase {
   703  			newMachine.Base = macBase.String()
   704  			if includeSeries {
   705  				newMachine.Series = macSeries
   706  			}
   707  		}
   708  
   709  		if result := b.constraints(machine.Constraints()); len(result) != 0 {
   710  			newMachine.Constraints = strings.Join(result, " ")
   711  		}
   712  
   713  		machineData[machine.Id()] = newMachine
   714  	}
   715  	return machineData, usedBases, nil
   716  }
   717  
   718  func bundleDataRemoteApplications(remoteApps []description.RemoteApplication) map[string]*charm.SaasSpec {
   719  	Saas := make(map[string]*charm.SaasSpec, len(remoteApps))
   720  	for _, application := range remoteApps {
   721  		newSaas := &charm.SaasSpec{
   722  			URL: application.URL(),
   723  		}
   724  		Saas[application.Name()] = newSaas
   725  	}
   726  	return Saas
   727  }
   728  
   729  func bundleDataRelations(relations []description.Relation) [][]string {
   730  	var relationData [][]string
   731  	for _, relation := range relations {
   732  		var endpointRelation []string
   733  		for _, endpoint := range relation.Endpoints() {
   734  			// skipping the 'peer' role which is not of concern in exporting the current model configuration.
   735  			if endpoint.Role() == "peer" {
   736  				continue
   737  			}
   738  			endpointRelation = append(endpointRelation, endpoint.ApplicationName()+":"+endpoint.Name())
   739  		}
   740  		if len(endpointRelation) != 0 {
   741  			relationData = append(relationData, endpointRelation)
   742  		}
   743  	}
   744  	return relationData
   745  }
   746  
   747  // mapExposedEndpoints converts the description package representation of the
   748  // exposed endpoint settings into a format that can be included in the exported
   749  // bundle output.  The provided spaceInfos list is used to convert space IDs
   750  // into space names.
   751  func mapExposedEndpoints(exposedEndpoints map[string]description.ExposedEndpoint, spaceInfos network.SpaceInfos) (map[string]charm.ExposedEndpointSpec, error) {
   752  	if len(exposedEndpoints) == 0 {
   753  		return nil, nil
   754  	} else if allEndpointParams, found := exposedEndpoints[""]; found && len(exposedEndpoints) == 1 {
   755  		// We have a single entry for the wildcard endpoint; check if
   756  		// it only includes an expose to all networks CIDR.
   757  		var allNetworkCIDRCount int
   758  		for _, cidr := range allEndpointParams.ExposeToCIDRs() {
   759  			if cidr == firewall.AllNetworksIPV4CIDR || cidr == firewall.AllNetworksIPV6CIDR {
   760  				allNetworkCIDRCount++
   761  			}
   762  		}
   763  
   764  		if len(allEndpointParams.ExposeToSpaceIDs()) == 0 &&
   765  			len(allEndpointParams.ExposeToCIDRs()) == allNetworkCIDRCount {
   766  			return nil, nil // equivalent to using non-granular expose like pre 2.9 juju
   767  		}
   768  	}
   769  
   770  	res := make(map[string]charm.ExposedEndpointSpec, len(exposedEndpoints))
   771  	for endpointName, exposeDetails := range exposedEndpoints {
   772  		exposeToCIDRs := exposeDetails.ExposeToCIDRs()
   773  		exposeToSpaceNames, err := mapSpaceIDsToNames(spaceInfos, exposeDetails.ExposeToSpaceIDs())
   774  		if err != nil {
   775  			return nil, errors.Trace(err)
   776  		}
   777  
   778  		// Ensure consistent ordering of results
   779  		sort.Strings(exposeToSpaceNames)
   780  		sort.Strings(exposeToCIDRs)
   781  
   782  		res[endpointName] = charm.ExposedEndpointSpec{
   783  			ExposeToSpaces: exposeToSpaceNames,
   784  			ExposeToCIDRs:  exposeToCIDRs,
   785  		}
   786  	}
   787  
   788  	return res, nil
   789  }
   790  
   791  func mapSpaceIDsToNames(spaceInfos network.SpaceInfos, spaceIDs []string) ([]string, error) {
   792  	if len(spaceIDs) == 0 {
   793  		return nil, nil
   794  	}
   795  
   796  	spaceNames := make([]string, len(spaceIDs))
   797  	for i, spaceID := range spaceIDs {
   798  		sp := spaceInfos.GetByID(spaceID)
   799  		if sp == nil {
   800  			return nil, errors.NotFoundf("space with ID %q", spaceID)
   801  		}
   802  
   803  		spaceNames[i] = string(sp.Name)
   804  	}
   805  
   806  	return spaceNames, nil
   807  }
   808  
   809  func (b *BundleAPI) printSpaceNamesInEndpointBindings(apps []description.Application) bool {
   810  	// Assumption: if all endpoint bindings in the bundle are in the
   811  	// same space, spaces aren't really in use and will "muddy the waters"
   812  	// for export bundle.
   813  	spaceName := set.NewStrings()
   814  	for _, app := range apps {
   815  		for _, v := range app.EndpointBindings() {
   816  			spaceName.Add(v)
   817  		}
   818  		if spaceName.Size() > 1 {
   819  			return true
   820  		}
   821  	}
   822  	return false
   823  }
   824  
   825  func (b *BundleAPI) endpointBindings(bindings map[string]string, spaceLookup network.SpaceInfos, printValue bool) (map[string]string, error) {
   826  	if !printValue {
   827  		return nil, nil
   828  	}
   829  	endpointBindings, err := state.NewBindings(b.backend, bindings)
   830  	if err != nil {
   831  		return nil, errors.Trace(err)
   832  	}
   833  	return endpointBindings.MapWithSpaceNames(spaceLookup)
   834  }
   835  
   836  // filterOfferACL prunes the input offer ACL to remove internal juju users that
   837  // we shouldn't export as part of the bundle.
   838  func (b *BundleAPI) filterOfferACL(in map[string]string) map[string]string {
   839  	delete(in, common.EveryoneTagName)
   840  	return in
   841  }
   842  
   843  func (b *BundleAPI) constraints(cons description.Constraints) []string {
   844  	if cons == nil {
   845  		return []string{}
   846  	}
   847  
   848  	var result []string
   849  	if arch := cons.Architecture(); arch != "" {
   850  		result = append(result, "arch="+arch)
   851  	}
   852  	if cores := cons.CpuCores(); cores != 0 {
   853  		result = append(result, "cpu-cores="+strconv.Itoa(int(cores)))
   854  	}
   855  	if power := cons.CpuPower(); power != 0 {
   856  		result = append(result, "cpu-power="+strconv.Itoa(int(power)))
   857  	}
   858  	if mem := cons.Memory(); mem != 0 {
   859  		result = append(result, "mem="+strconv.Itoa(int(mem)))
   860  	}
   861  	if disk := cons.RootDisk(); disk != 0 {
   862  		result = append(result, "root-disk="+strconv.Itoa(int(disk)))
   863  	}
   864  	if instType := cons.InstanceType(); instType != "" {
   865  		result = append(result, "instance-type="+instType)
   866  	}
   867  	if imageID := cons.ImageID(); imageID != "" {
   868  		result = append(result, "image-id="+imageID)
   869  	}
   870  	if container := cons.Container(); container != "" {
   871  		result = append(result, "container="+container)
   872  	}
   873  	if virtType := cons.VirtType(); virtType != "" {
   874  		result = append(result, "virt-type="+virtType)
   875  	}
   876  	if tags := cons.Tags(); len(tags) != 0 {
   877  		result = append(result, "tags="+strings.Join(tags, ","))
   878  	}
   879  	if spaces := cons.Spaces(); len(spaces) != 0 {
   880  		result = append(result, "spaces="+strings.Join(spaces, ","))
   881  	}
   882  	if zones := cons.Zones(); len(zones) != 0 {
   883  		result = append(result, "zones="+strings.Join(zones, ","))
   884  	}
   885  	if rootDiskSource := cons.RootDiskSource(); rootDiskSource != "" {
   886  		result = append(result, "root-disk-source="+rootDiskSource)
   887  	}
   888  	return result
   889  }