gopkg.in/juju/charm.v6-unstable@v6.0.0-20171026192109-50d0c219b496/bundledata.go (about)

     1  // Copyright 2014 Canonical Ltd.
     2  // Licensed under the LGPLv3, see LICENCE file for details.
     3  
     4  package charm
     5  
     6  import (
     7  	"encoding/json"
     8  	"fmt"
     9  	"io"
    10  	"io/ioutil"
    11  	"os"
    12  	"path/filepath"
    13  	"regexp"
    14  	"sort"
    15  	"strconv"
    16  	"strings"
    17  
    18  	"gopkg.in/juju/names.v2"
    19  	"gopkg.in/mgo.v2/bson"
    20  	"gopkg.in/yaml.v2"
    21  )
    22  
    23  type noMethodsBundleData BundleData
    24  
    25  type legacyBundleData struct {
    26  	noMethodsBundleData `bson:",inline" yaml:",inline" json:",inline"`
    27  
    28  	// LegacyServices holds application entries for older bundle files
    29  	// that have not been migrated to use the new "application" terminology.
    30  	LegacyServices map[string]*ApplicationSpec `json:"services" yaml:"services" bson:"services"`
    31  }
    32  
    33  func (lbd *legacyBundleData) setBundleData(bd *BundleData) error {
    34  	if len(lbd.Applications) > 0 && len(lbd.LegacyServices) > 0 {
    35  		return fmt.Errorf("cannot specify both applications and services")
    36  	}
    37  	if len(lbd.LegacyServices) > 0 {
    38  		// We account for the fact that the YAML may contain a legacy entry
    39  		// for "services" instead of "applications".
    40  		lbd.Applications = lbd.LegacyServices
    41  		lbd.unmarshaledWithServices = true
    42  	}
    43  	*bd = BundleData(lbd.noMethodsBundleData)
    44  	return nil
    45  }
    46  
    47  // UnmarshalJSON implements the json.Unmarshaler interface.
    48  func (bd *BundleData) UnmarshalJSON(b []byte) error {
    49  	var bdc legacyBundleData
    50  	if err := json.Unmarshal(b, &bdc); err != nil {
    51  		return err
    52  	}
    53  	return bdc.setBundleData(bd)
    54  }
    55  
    56  // UnmarshalYAML implements the yaml.Unmarshaler interface.
    57  func (bd *BundleData) UnmarshalYAML(f func(interface{}) error) error {
    58  	var bdc legacyBundleData
    59  	if err := f(&bdc); err != nil {
    60  		return err
    61  	}
    62  	return bdc.setBundleData(bd)
    63  }
    64  
    65  // SetBSON implements the bson.Setter interface.
    66  func (bd *BundleData) SetBSON(raw bson.Raw) error {
    67  	// TODO(wallyworld) - bson deserialisation is not handling the inline directive,
    68  	// so we need to unmarshal the bundle data manually.
    69  	var b *noMethodsBundleData
    70  	if err := raw.Unmarshal(&b); err != nil {
    71  		return err
    72  	}
    73  	if b == nil {
    74  		return bson.SetZero
    75  	}
    76  
    77  	var bdc legacyBundleData
    78  	if err := raw.Unmarshal(&bdc); err != nil {
    79  		return err
    80  	}
    81  	// As per the above TODO, we manually set the inline data.
    82  	bdc.noMethodsBundleData = *b
    83  	return bdc.setBundleData(bd)
    84  }
    85  
    86  // BundleData holds the contents of the bundle.
    87  type BundleData struct {
    88  	// Applications holds one entry for each application
    89  	// that the bundle will create, indexed by
    90  	// the application name.
    91  	Applications map[string]*ApplicationSpec `bson:"applications,omitempty" json:"applications,omitempty" yaml:"applications,omitempty"`
    92  
    93  	// Machines holds one entry for each machine referred to
    94  	// by unit placements. These will be mapped onto actual
    95  	// machines at bundle deployment time.
    96  	// It is an error if a machine is specified but
    97  	// not referred to by a unit placement directive.
    98  	Machines map[string]*MachineSpec `bson:",omitempty" json:",omitempty" yaml:",omitempty"`
    99  
   100  	// Series holds the default series to use when
   101  	// the bundle chooses charms.
   102  	Series string `bson:",omitempty" json:",omitempty" yaml:",omitempty"`
   103  
   104  	// Relations holds a slice of 2-element slices,
   105  	// each specifying a relation between two applications.
   106  	// Each two-element slice holds two endpoints,
   107  	// each specified as either colon-separated
   108  	// (application, relation) pair or just an application name.
   109  	// The relation is made between each. If the relation
   110  	// name is omitted, it will be inferred from the available
   111  	// relations defined in the applications' charms.
   112  	Relations [][]string `bson:",omitempty" json:",omitempty" yaml:",omitempty"`
   113  
   114  	// White listed set of tags to categorize bundles as we do charms.
   115  	Tags []string `bson:",omitempty" json:",omitempty" yaml:",omitempty"`
   116  
   117  	// Short paragraph explaining what the bundle is useful for.
   118  	Description string `bson:",omitempty" json:",omitempty" yaml:",omitempty"`
   119  
   120  	// unmarshaledWithServices holds whether the original marshaled data held a
   121  	// legacy "services" field rather than the "applications" field.
   122  	unmarshaledWithServices bool
   123  }
   124  
   125  // UnmarshaledWithServices reports whether the bundle data was
   126  // unmarshaled from a representation that used the legacy "services"
   127  // field rather than the "applications" field.
   128  func (d *BundleData) UnmarshaledWithServices() bool {
   129  	return d.unmarshaledWithServices
   130  }
   131  
   132  // MachineSpec represents a notional machine that will be mapped
   133  // onto an actual machine at bundle deployment time.
   134  type MachineSpec struct {
   135  	Constraints string            `bson:",omitempty" json:",omitempty" yaml:",omitempty"`
   136  	Annotations map[string]string `bson:",omitempty" json:",omitempty" yaml:",omitempty"`
   137  	Series      string            `bson:",omitempty" json:",omitempty" yaml:",omitempty"`
   138  }
   139  
   140  // ApplicationSpec represents a single application that will
   141  // be deployed as part of the bundle.
   142  type ApplicationSpec struct {
   143  	// Charm holds the charm URL of the charm to
   144  	// use for the given application.
   145  	Charm string
   146  
   147  	// Series is the series to use when deploying a local charm,
   148  	// if the charm does not specify a default or the default
   149  	// is not the desired value.
   150  	// Series is not compatible with charm store charms where
   151  	// the series is specified in the URL.
   152  	Series string `bson:",omitempty" yaml:",omitempty" json:",omitempty"`
   153  
   154  	// Resources is the set of resource revisions to deploy for the
   155  	// application. Bundles only support charm store resources and not ones
   156  	// that were uploaded to the controller.
   157  	Resources map[string]interface{} `bson:",omitempty" yaml:",omitempty" json:",omitempty"`
   158  
   159  	// NumUnits holds the number of units of the
   160  	// application that will be deployed.
   161  	//
   162  	// For a subordinate application, this actually represents
   163  	// an arbitrary number of units depending on
   164  	// the application it is related to.
   165  	NumUnits int `bson:",omitempty" yaml:"num_units,omitempty" json:",omitempty"`
   166  
   167  	// To may hold up to NumUnits members with
   168  	// each member specifying a desired placement
   169  	// for the respective unit of the application.
   170  	//
   171  	// In regular-expression-like notation, each
   172  	// element matches the following pattern:
   173  	//
   174  	//      (<containertype>:)?(<unit>|<machine>|new)
   175  	//
   176  	// If containertype is specified, the unit is deployed
   177  	// into a new container of that type, otherwise
   178  	// it will be "hulk-smashed" into the specified location,
   179  	// by co-locating it with any other units that happen to
   180  	// be there, which may result in unintended behavior.
   181  	//
   182  	// The second part (after the colon) specifies where
   183  	// the new unit should be placed - it may refer to
   184  	// a unit of another application specified in the bundle,
   185  	// a machine id specified in the machines section,
   186  	// or the special name "new" which specifies a newly
   187  	// created machine.
   188  	//
   189  	// A unit placement may be specified with an application name only,
   190  	// in which case its unit number is assumed to
   191  	// be one more than the unit number of the previous
   192  	// unit in the list with the same application, or zero
   193  	// if there were none.
   194  	//
   195  	// If there are less elements in To than NumUnits,
   196  	// the last element is replicated to fill it. If there
   197  	// are no elements (or To is omitted), "new" is replicated.
   198  	//
   199  	// For example:
   200  	//
   201  	//     wordpress/0 wordpress/1 lxc:0 kvm:new
   202  	//
   203  	//  specifies that the first two units get hulk-smashed
   204  	//  onto the first two units of the wordpress application,
   205  	//  the third unit gets allocated onto an lxc container
   206  	//  on machine 0, and subsequent units get allocated
   207  	//  on kvm containers on new machines.
   208  	//
   209  	// The above example is the same as this:
   210  	//
   211  	//     wordpress wordpress lxc:0 kvm:new
   212  	To []string `bson:",omitempty" json:",omitempty" yaml:",omitempty"`
   213  
   214  	// Expose holds whether the application must be exposed.
   215  	Expose bool `bson:",omitempty" json:",omitempty" yaml:",omitempty"`
   216  
   217  	// Options holds the configuration values
   218  	// to apply to the new application. They should
   219  	// be compatible with the charm configuration.
   220  	Options map[string]interface{} `bson:",omitempty" json:",omitempty" yaml:",omitempty"`
   221  
   222  	// Annotations holds any annotations to apply to the
   223  	// application when deployed.
   224  	Annotations map[string]string `bson:",omitempty" json:",omitempty" yaml:",omitempty"`
   225  
   226  	// Constraints holds the default constraints to apply
   227  	// when creating new machines for units of the application.
   228  	// This is ignored for units with explicit placement directives.
   229  	Constraints string `bson:",omitempty" json:",omitempty" yaml:",omitempty"`
   230  
   231  	// Storage holds the constraints for storage to assign
   232  	// to units of the application.
   233  	Storage map[string]string `bson:",omitempty" json:",omitempty" yaml:",omitempty"`
   234  
   235  	// EndpointBindings maps how endpoints are bound to spaces
   236  	EndpointBindings map[string]string `bson:"bindings,omitempty" json:"bindings,omitempty" yaml:"bindings,omitempty"`
   237  }
   238  
   239  // ReadBundleData reads bundle data from the given reader.
   240  // The returned data is not verified - call Verify to ensure
   241  // that it is OK.
   242  func ReadBundleData(r io.Reader) (*BundleData, error) {
   243  	bytes, err := ioutil.ReadAll(r)
   244  	if err != nil {
   245  		return nil, err
   246  	}
   247  	var bd BundleData
   248  	if err := yaml.Unmarshal(bytes, &bd); err != nil {
   249  		return nil, fmt.Errorf("cannot unmarshal bundle data: %v", err)
   250  	}
   251  	return &bd, nil
   252  }
   253  
   254  // VerificationError holds an error generated by BundleData.Verify,
   255  // holding all the verification errors found when verifying.
   256  type VerificationError struct {
   257  	Errors []error
   258  }
   259  
   260  func (err *VerificationError) Error() string {
   261  	switch len(err.Errors) {
   262  	case 0:
   263  		return "no verification errors!"
   264  	case 1:
   265  		return err.Errors[0].Error()
   266  	}
   267  	return fmt.Sprintf("%s (and %d more errors)", err.Errors[0], len(err.Errors)-1)
   268  }
   269  
   270  type bundleDataVerifier struct {
   271  	// bundleDir is the directory containing the bundle file
   272  	bundleDir string
   273  	bd        *BundleData
   274  
   275  	// machines holds the reference counts of all machines
   276  	// as referred to by placement directives.
   277  	machineRefCounts map[string]int
   278  
   279  	charms map[string]Charm
   280  
   281  	errors            []error
   282  	verifyConstraints func(c string) error
   283  	verifyStorage     func(s string) error
   284  }
   285  
   286  func (verifier *bundleDataVerifier) addErrorf(f string, a ...interface{}) {
   287  	verifier.addError(fmt.Errorf(f, a...))
   288  }
   289  
   290  func (verifier *bundleDataVerifier) addError(err error) {
   291  	verifier.errors = append(verifier.errors, err)
   292  }
   293  
   294  func (verifier *bundleDataVerifier) err() error {
   295  	if len(verifier.errors) > 0 {
   296  		return &VerificationError{verifier.errors}
   297  	}
   298  	return nil
   299  }
   300  
   301  // RequiredCharms returns a sorted slice of all the charm URLs
   302  // required by the bundle.
   303  func (bd *BundleData) RequiredCharms() []string {
   304  	req := make([]string, 0, len(bd.Applications))
   305  	for _, svc := range bd.Applications {
   306  		req = append(req, svc.Charm)
   307  	}
   308  	sort.Strings(req)
   309  	return req
   310  }
   311  
   312  // VerifyLocal verifies that a local bundle file is consistent.
   313  // A local bundle file may contain references to charms which are
   314  // referred to by a directory, either relative or absolute.
   315  //
   316  // bundleDir is used to construct the full path for charms specified
   317  // using a relative directory path. The charm path is therefore expected
   318  // to be relative to the bundle.yaml file.
   319  func (bd *BundleData) VerifyLocal(
   320  	bundleDir string,
   321  	verifyConstraints func(c string) error,
   322  	verifyStorage func(s string) error,
   323  ) error {
   324  	return bd.verifyBundle(bundleDir, verifyConstraints, verifyStorage, nil)
   325  }
   326  
   327  // Verify is a convenience method that calls VerifyWithCharms
   328  // with a nil charms map.
   329  func (bd *BundleData) Verify(
   330  	verifyConstraints func(c string) error,
   331  	verifyStorage func(s string) error,
   332  ) error {
   333  	return bd.VerifyWithCharms(verifyConstraints, verifyStorage, nil)
   334  }
   335  
   336  // VerifyWithCharms verifies that the bundle is consistent.
   337  // The verifyConstraints function is called to verify any constraints
   338  // that are found. If verifyConstraints is nil, no checking
   339  // of constraints will be done. Similarly, a non-nil verifyStorage
   340  // function is called to verify any storage constraints.
   341  //
   342  // It verifies the following:
   343  //
   344  // - All defined machines are referred to by placement directives.
   345  // - All applications referred to by placement directives are specified in the bundle.
   346  // - All applications referred to by relations are specified in the bundle.
   347  // - All basic constraints are valid.
   348  // - All storage constraints are valid.
   349  //
   350  // If charms is not nil, it should hold a map with an entry for each
   351  // charm url returned by bd.RequiredCharms. The verification will then
   352  // also check that applications are defined with valid charms,
   353  // relations are correctly made and options are defined correctly.
   354  //
   355  // If the verification fails, Verify returns a *VerificationError describing
   356  // all the problems found.
   357  func (bd *BundleData) VerifyWithCharms(
   358  	verifyConstraints func(c string) error,
   359  	verifyStorage func(s string) error,
   360  	charms map[string]Charm,
   361  ) error {
   362  	return bd.verifyBundle("", verifyConstraints, verifyStorage, charms)
   363  }
   364  
   365  func (bd *BundleData) verifyBundle(
   366  	bundleDir string,
   367  	verifyConstraints func(c string) error,
   368  	verifyStorage func(s string) error,
   369  	charms map[string]Charm,
   370  ) error {
   371  	if verifyConstraints == nil {
   372  		verifyConstraints = func(string) error {
   373  			return nil
   374  		}
   375  	}
   376  	if verifyStorage == nil {
   377  		verifyStorage = func(string) error {
   378  			return nil
   379  		}
   380  	}
   381  	verifier := &bundleDataVerifier{
   382  		bundleDir:         bundleDir,
   383  		verifyConstraints: verifyConstraints,
   384  		verifyStorage:     verifyStorage,
   385  		bd:                bd,
   386  		machineRefCounts:  make(map[string]int),
   387  		charms:            charms,
   388  	}
   389  	for id := range bd.Machines {
   390  		verifier.machineRefCounts[id] = 0
   391  	}
   392  	if bd.Series != "" && !IsValidSeries(bd.Series) {
   393  		verifier.addErrorf("bundle declares an invalid series %q", bd.Series)
   394  	}
   395  	verifier.verifyMachines()
   396  	verifier.verifyApplications()
   397  	verifier.verifyRelations()
   398  	verifier.verifyOptions()
   399  	verifier.verifyEndpointBindings()
   400  
   401  	for id, count := range verifier.machineRefCounts {
   402  		if count == 0 {
   403  			verifier.addErrorf("machine %q is not referred to by a placement directive", id)
   404  		}
   405  	}
   406  	return verifier.err()
   407  }
   408  
   409  var (
   410  	validMachineId   = regexp.MustCompile("^" + names.NumberSnippet + "$")
   411  	validStorageName = regexp.MustCompile("^" + names.StorageNameSnippet + "$")
   412  )
   413  
   414  func (verifier *bundleDataVerifier) verifyMachines() {
   415  	for id, m := range verifier.bd.Machines {
   416  		if !validMachineId.MatchString(id) {
   417  			verifier.addErrorf("invalid machine id %q found in machines", id)
   418  		}
   419  		if m == nil {
   420  			continue
   421  		}
   422  		if m.Constraints != "" {
   423  			if err := verifier.verifyConstraints(m.Constraints); err != nil {
   424  				verifier.addErrorf("invalid constraints %q in machine %q: %v", m.Constraints, id, err)
   425  			}
   426  		}
   427  		if m.Series != "" && !IsValidSeries(m.Series) {
   428  			verifier.addErrorf("invalid series %s for machine %q", m.Series, id)
   429  		}
   430  	}
   431  }
   432  
   433  func (verifier *bundleDataVerifier) verifyApplications() {
   434  	if len(verifier.bd.Applications) == 0 {
   435  		verifier.addErrorf("at least one application must be specified")
   436  		return
   437  	}
   438  	for name, svc := range verifier.bd.Applications {
   439  		if svc.Charm == "" {
   440  			verifier.addErrorf("empty charm path")
   441  		}
   442  		// Charm may be a local directory or a charm URL.
   443  		var curl *URL
   444  		var err error
   445  		if strings.HasPrefix(svc.Charm, ".") || filepath.IsAbs(svc.Charm) {
   446  			charmPath := svc.Charm
   447  			if !filepath.IsAbs(charmPath) {
   448  				charmPath = filepath.Join(verifier.bundleDir, charmPath)
   449  			}
   450  			if _, err := os.Stat(charmPath); err != nil {
   451  				if os.IsNotExist(err) {
   452  					verifier.addErrorf("charm path in application %q does not exist: %v", name, charmPath)
   453  				} else {
   454  					verifier.addErrorf("invalid charm path in application %q: %v", name, err)
   455  				}
   456  			}
   457  		} else if curl, err = ParseURL(svc.Charm); err != nil {
   458  			verifier.addErrorf("invalid charm URL in application %q: %v", name, err)
   459  		}
   460  
   461  		// Check the series.
   462  		if curl != nil && curl.Series != "" && svc.Series != "" && curl.Series != svc.Series {
   463  			verifier.addErrorf("the charm URL for application %q has a series which does not match, please remove the series from the URL", name)
   464  		}
   465  		if svc.Series != "" && !IsValidSeries(svc.Series) {
   466  			verifier.addErrorf("application %q declares an invalid series %q", name, svc.Series)
   467  		}
   468  
   469  		if err := verifier.verifyConstraints(svc.Constraints); err != nil {
   470  			verifier.addErrorf("invalid constraints %q in application %q: %v", svc.Constraints, name, err)
   471  		}
   472  		for storageName, storageConstraints := range svc.Storage {
   473  			if !validStorageName.MatchString(storageName) {
   474  				verifier.addErrorf("invalid storage name %q in application %q", storageName, name)
   475  			}
   476  			if err := verifier.verifyStorage(storageConstraints); err != nil {
   477  				verifier.addErrorf("invalid storage %q in application %q: %v", storageName, name, err)
   478  			}
   479  		}
   480  		if verifier.charms != nil {
   481  			if ch, ok := verifier.charms[svc.Charm]; ok {
   482  				if ch.Meta().Subordinate {
   483  					if len(svc.To) > 0 {
   484  						verifier.addErrorf("application %q is subordinate but specifies unit placement", name)
   485  					}
   486  					if svc.NumUnits > 0 {
   487  						verifier.addErrorf("application %q is subordinate but has non-zero num_units", name)
   488  					}
   489  				}
   490  			} else {
   491  				verifier.addErrorf("application %q refers to non-existent charm %q", name, svc.Charm)
   492  			}
   493  		}
   494  		for resName := range svc.Resources {
   495  			if resName == "" {
   496  				verifier.addErrorf("missing resource name on application %q", name)
   497  			}
   498  			// We do not check the revisions because all values
   499  			// are allowed.
   500  		}
   501  		if svc.NumUnits < 0 {
   502  			verifier.addErrorf("negative number of units specified on application %q", name)
   503  		} else if len(svc.To) > svc.NumUnits {
   504  			verifier.addErrorf("too many units specified in unit placement for application %q", name)
   505  		}
   506  		verifier.verifyPlacement(svc.To)
   507  	}
   508  }
   509  
   510  func (verifier *bundleDataVerifier) verifyPlacement(to []string) {
   511  	for _, p := range to {
   512  		up, err := ParsePlacement(p)
   513  		if err != nil {
   514  			verifier.addError(err)
   515  			continue
   516  		}
   517  		switch {
   518  		case up.Application != "":
   519  			spec, ok := verifier.bd.Applications[up.Application]
   520  			if !ok {
   521  				verifier.addErrorf("placement %q refers to an application not defined in this bundle", p)
   522  				continue
   523  			}
   524  			if up.Unit >= 0 && up.Unit >= spec.NumUnits {
   525  				verifier.addErrorf("placement %q specifies a unit greater than the %d unit(s) started by the target application", p, spec.NumUnits)
   526  			}
   527  		case up.Machine == "new":
   528  		default:
   529  			_, ok := verifier.bd.Machines[up.Machine]
   530  			if !ok {
   531  				verifier.addErrorf("placement %q refers to a machine not defined in this bundle", p)
   532  				continue
   533  			}
   534  			verifier.machineRefCounts[up.Machine]++
   535  		}
   536  	}
   537  }
   538  
   539  func (verifier *bundleDataVerifier) getCharmMetaForApplication(appName string) (*Meta, error) {
   540  	svc, ok := verifier.bd.Applications[appName]
   541  	if !ok {
   542  		return nil, fmt.Errorf("application %q not found", appName)
   543  	}
   544  	ch, ok := verifier.charms[svc.Charm]
   545  	if !ok {
   546  		return nil, fmt.Errorf("charm %q from application %q not found", svc.Charm, appName)
   547  	}
   548  	return ch.Meta(), nil
   549  }
   550  
   551  func (verifier *bundleDataVerifier) verifyRelations() {
   552  	seen := make(map[[2]endpoint]bool)
   553  	for _, relPair := range verifier.bd.Relations {
   554  		if len(relPair) != 2 {
   555  			verifier.addErrorf("relation %q has %d endpoint(s), not 2", relPair, len(relPair))
   556  			continue
   557  		}
   558  		var epPair [2]endpoint
   559  		relParseErr := false
   560  		for i, svcRel := range relPair {
   561  			ep, err := parseEndpoint(svcRel)
   562  			if err != nil {
   563  				verifier.addError(err)
   564  				relParseErr = true
   565  				continue
   566  			}
   567  			if _, ok := verifier.bd.Applications[ep.application]; !ok {
   568  				verifier.addErrorf("relation %q refers to application %q not defined in this bundle", relPair, ep.application)
   569  			}
   570  			epPair[i] = ep
   571  		}
   572  		if relParseErr {
   573  			// We failed to parse at least one relation, so don't
   574  			// bother checking further.
   575  			continue
   576  		}
   577  		if epPair[0].application == epPair[1].application {
   578  			verifier.addErrorf("relation %q relates an application to itself", relPair)
   579  		}
   580  		// Resolve endpoint relations if necessary and we have
   581  		// the necessary charm information.
   582  		if (epPair[0].relation == "" || epPair[1].relation == "") && verifier.charms != nil {
   583  			iep0, iep1, err := inferEndpoints(epPair[0], epPair[1], verifier.getCharmMetaForApplication)
   584  			if err != nil {
   585  				verifier.addErrorf("cannot infer endpoint between %s and %s: %v", epPair[0], epPair[1], err)
   586  			} else {
   587  				// Change the endpoints that get recorded
   588  				// as seen, so we'll diagnose a duplicate
   589  				// relation even if one relation specifies
   590  				// the relations explicitly and the other does
   591  				// not.
   592  				epPair[0], epPair[1] = iep0, iep1
   593  			}
   594  		}
   595  
   596  		// Re-order pairs so that we diagnose duplicate relations
   597  		// whichever way they're specified.
   598  		if epPair[1].less(epPair[0]) {
   599  			epPair[1], epPair[0] = epPair[0], epPair[1]
   600  		}
   601  		if _, ok := seen[epPair]; ok {
   602  			verifier.addErrorf("relation %q is defined more than once", relPair)
   603  		}
   604  		if verifier.charms != nil && epPair[0].relation != "" && epPair[1].relation != "" {
   605  			// We have charms to verify against, and the
   606  			// endpoint has been fully specified or inferred.
   607  			verifier.verifyRelation(epPair[0], epPair[1])
   608  		}
   609  		seen[epPair] = true
   610  	}
   611  }
   612  
   613  func (verifier *bundleDataVerifier) verifyEndpointBindings() {
   614  	for name, svc := range verifier.bd.Applications {
   615  		charm, ok := verifier.charms[name]
   616  		// Only thest the ok path here because the !ok path is tested in verifyApplications
   617  		if !ok {
   618  			continue
   619  		}
   620  		for endpoint, space := range svc.EndpointBindings {
   621  			_, isInProvides := charm.Meta().Provides[endpoint]
   622  			_, isInRequires := charm.Meta().Requires[endpoint]
   623  			_, isInPeers := charm.Meta().Peers[endpoint]
   624  			_, isInExtraBindings := charm.Meta().ExtraBindings[endpoint]
   625  
   626  			if !(isInProvides || isInRequires || isInPeers || isInExtraBindings) {
   627  				verifier.addErrorf(
   628  					"application %q wants to bind endpoint %q to space %q, "+
   629  						"but the endpoint is not defined by the charm",
   630  					name, endpoint, space)
   631  			}
   632  		}
   633  
   634  	}
   635  }
   636  
   637  var infoRelation = Relation{
   638  	Name:      "juju-info",
   639  	Role:      RoleProvider,
   640  	Interface: "juju-info",
   641  	Scope:     ScopeContainer,
   642  }
   643  
   644  // verifyRelation verifies a single relation.
   645  // It checks that both endpoints of the relation are
   646  // defined, and that the relationship is correctly
   647  // symmetrical (provider to requirer) and shares
   648  // the same interface.
   649  func (verifier *bundleDataVerifier) verifyRelation(ep0, ep1 endpoint) {
   650  	svc0 := verifier.bd.Applications[ep0.application]
   651  	svc1 := verifier.bd.Applications[ep1.application]
   652  	if svc0 == nil || svc1 == nil || svc0 == svc1 {
   653  		// An error will be produced by verifyRelations for this case.
   654  		return
   655  	}
   656  	charm0 := verifier.charms[svc0.Charm]
   657  	charm1 := verifier.charms[svc1.Charm]
   658  	if charm0 == nil || charm1 == nil {
   659  		// An error will be produced by verifyApplications for this case.
   660  		return
   661  	}
   662  	relProv0, okProv0 := charm0.Meta().Provides[ep0.relation]
   663  	// The juju-info relation is provided implicitly by every
   664  	// charm - use it if required.
   665  	if !okProv0 && ep0.relation == infoRelation.Name {
   666  		relProv0, okProv0 = infoRelation, true
   667  	}
   668  	relReq0, okReq0 := charm0.Meta().Requires[ep0.relation]
   669  	if !okProv0 && !okReq0 {
   670  		verifier.addErrorf("charm %q used by application %q does not define relation %q", svc0.Charm, ep0.application, ep0.relation)
   671  	}
   672  	relProv1, okProv1 := charm1.Meta().Provides[ep1.relation]
   673  	// The juju-info relation is provided implicitly by every
   674  	// charm - use it if required.
   675  	if !okProv1 && ep1.relation == infoRelation.Name {
   676  		relProv1, okProv1 = infoRelation, true
   677  	}
   678  	relReq1, okReq1 := charm1.Meta().Requires[ep1.relation]
   679  	if !okProv1 && !okReq1 {
   680  		verifier.addErrorf("charm %q used by application %q does not define relation %q", svc1.Charm, ep1.application, ep1.relation)
   681  	}
   682  
   683  	var relProv, relReq Relation
   684  	var epProv, epReq endpoint
   685  	switch {
   686  	case okProv0 && okReq1:
   687  		relProv, relReq = relProv0, relReq1
   688  		epProv, epReq = ep0, ep1
   689  	case okReq0 && okProv1:
   690  		relProv, relReq = relProv1, relReq0
   691  		epProv, epReq = ep1, ep0
   692  	case okProv0 && okProv1:
   693  		verifier.addErrorf("relation %q to %q relates provider to provider", ep0, ep1)
   694  		return
   695  	case okReq0 && okReq1:
   696  		verifier.addErrorf("relation %q to %q relates requirer to requirer", ep0, ep1)
   697  		return
   698  	default:
   699  		// Errors were added above.
   700  		return
   701  	}
   702  	if relProv.Interface != relReq.Interface {
   703  		verifier.addErrorf("mismatched interface between %q and %q (%q vs %q)", epProv, epReq, relProv.Interface, relReq.Interface)
   704  	}
   705  }
   706  
   707  // verifyOptions verifies that the options are correctly defined
   708  // with respect to the charm config options.
   709  func (verifier *bundleDataVerifier) verifyOptions() {
   710  	if verifier.charms == nil {
   711  		return
   712  	}
   713  	for appName, svc := range verifier.bd.Applications {
   714  		charm := verifier.charms[svc.Charm]
   715  		if charm == nil {
   716  			// An error will be produced by verifyApplications for this case.
   717  			continue
   718  		}
   719  		config := charm.Config()
   720  		for name, value := range svc.Options {
   721  			opt, ok := config.Options[name]
   722  			if !ok {
   723  				verifier.addErrorf("cannot validate application %q: configuration option %q not found in charm %q", appName, name, svc.Charm)
   724  				continue
   725  			}
   726  			_, err := opt.validate(name, value)
   727  			if err != nil {
   728  				verifier.addErrorf("cannot validate application %q: %v", appName, err)
   729  			}
   730  		}
   731  	}
   732  }
   733  
   734  var validApplicationRelation = regexp.MustCompile("^(" + names.ApplicationSnippet + "):(" + names.RelationSnippet + ")$")
   735  
   736  type endpoint struct {
   737  	application string
   738  	relation    string
   739  }
   740  
   741  func (ep endpoint) String() string {
   742  	if ep.relation == "" {
   743  		return ep.application
   744  	}
   745  	return fmt.Sprintf("%s:%s", ep.application, ep.relation)
   746  }
   747  
   748  func (ep1 endpoint) less(ep2 endpoint) bool {
   749  	if ep1.application == ep2.application {
   750  		return ep1.relation < ep2.relation
   751  	}
   752  	return ep1.application < ep2.application
   753  }
   754  
   755  func parseEndpoint(ep string) (endpoint, error) {
   756  	m := validApplicationRelation.FindStringSubmatch(ep)
   757  	if m != nil {
   758  		return endpoint{
   759  			application: m[1],
   760  			relation:    m[2],
   761  		}, nil
   762  	}
   763  	if !names.IsValidApplication(ep) {
   764  		return endpoint{}, fmt.Errorf("invalid relation syntax %q", ep)
   765  	}
   766  	return endpoint{
   767  		application: ep,
   768  	}, nil
   769  }
   770  
   771  // endpointInfo holds information about one endpoint of a relation.
   772  type endpointInfo struct {
   773  	applicationName string
   774  	Relation
   775  }
   776  
   777  // String returns the unique identifier of the relation endpoint.
   778  func (ep endpointInfo) String() string {
   779  	return ep.applicationName + ":" + ep.Name
   780  }
   781  
   782  // canRelateTo returns whether a relation may be established between ep
   783  // and other.
   784  func (ep endpointInfo) canRelateTo(other endpointInfo) bool {
   785  	return ep.applicationName != other.applicationName &&
   786  		ep.Interface == other.Interface &&
   787  		ep.Role != RolePeer &&
   788  		counterpartRole(ep.Role) == other.Role
   789  }
   790  
   791  // endpoint returns the endpoint specifier for ep.
   792  func (ep endpointInfo) endpoint() endpoint {
   793  	return endpoint{
   794  		application: ep.applicationName,
   795  		relation:    ep.Name,
   796  	}
   797  }
   798  
   799  // counterpartRole returns the RelationRole that the given RelationRole
   800  // can relate to.
   801  func counterpartRole(r RelationRole) RelationRole {
   802  	switch r {
   803  	case RoleProvider:
   804  		return RoleRequirer
   805  	case RoleRequirer:
   806  		return RoleProvider
   807  	case RolePeer:
   808  		return RolePeer
   809  	}
   810  	panic(fmt.Errorf("unknown relation role %q", r))
   811  }
   812  
   813  type UnitPlacement struct {
   814  	// ContainerType holds the container type of the new
   815  	// new unit, or empty if unspecified.
   816  	ContainerType string
   817  
   818  	// Machine holds the numeric machine id, or "new",
   819  	// or empty if the placement specifies an application.
   820  	Machine string
   821  
   822  	// application holds the application name, or empty if
   823  	// the placement specifies a machine.
   824  	Application string
   825  
   826  	// Unit holds the unit number of the application, or -1
   827  	// if unspecified.
   828  	Unit int
   829  }
   830  
   831  var snippetReplacer = strings.NewReplacer(
   832  	"container", names.ContainerTypeSnippet,
   833  	"number", names.NumberSnippet,
   834  	"application", names.ApplicationSnippet,
   835  )
   836  
   837  // validPlacement holds regexp that matches valid placement requests. To
   838  // make the expression easier to comprehend and maintain, we replace
   839  // symbolic snippet references in the regexp by their actual regexps
   840  // using snippetReplacer.
   841  var validPlacement = regexp.MustCompile(
   842  	snippetReplacer.Replace(
   843  		"^(?:(container):)?(?:(application)(?:/(number))?|(number))$",
   844  	),
   845  )
   846  
   847  // ParsePlacement parses a unit placement directive, as
   848  // specified in the To clause of an application entry in the
   849  // applications section of a bundle.
   850  func ParsePlacement(p string) (*UnitPlacement, error) {
   851  	m := validPlacement.FindStringSubmatch(p)
   852  	if m == nil {
   853  		return nil, fmt.Errorf("invalid placement syntax %q", p)
   854  	}
   855  	up := UnitPlacement{
   856  		ContainerType: m[1],
   857  		Application:   m[2],
   858  		Machine:       m[4],
   859  	}
   860  	if unitStr := m[3]; unitStr != "" {
   861  		// We know that unitStr must be a valid integer because
   862  		// it's specified as such in the regexp.
   863  		up.Unit, _ = strconv.Atoi(unitStr)
   864  	} else {
   865  		up.Unit = -1
   866  	}
   867  	if up.Application == "new" {
   868  		if up.Unit != -1 {
   869  			return nil, fmt.Errorf("invalid placement syntax %q", p)
   870  		}
   871  		up.Machine, up.Application = "new", ""
   872  	}
   873  	return &up, nil
   874  }
   875  
   876  // inferEndpoints infers missing relation names from the given endpoint
   877  // specifications, using the given get function to retrieve charm
   878  // data if necessary. It returns the fully specified endpoints.
   879  func inferEndpoints(epSpec0, epSpec1 endpoint, get func(svc string) (*Meta, error)) (endpoint, endpoint, error) {
   880  	if epSpec0.relation != "" && epSpec1.relation != "" {
   881  		// The endpoints are already specified explicitly so
   882  		// there is no need to fetch any charm data to infer
   883  		// them.
   884  		return epSpec0, epSpec1, nil
   885  	}
   886  	eps0, err := possibleEndpoints(epSpec0, get)
   887  	if err != nil {
   888  		return endpoint{}, endpoint{}, err
   889  	}
   890  	eps1, err := possibleEndpoints(epSpec1, get)
   891  	if err != nil {
   892  		return endpoint{}, endpoint{}, err
   893  	}
   894  	var candidates [][]endpointInfo
   895  	for _, ep0 := range eps0 {
   896  		for _, ep1 := range eps1 {
   897  			if ep0.canRelateTo(ep1) {
   898  				candidates = append(candidates, []endpointInfo{ep0, ep1})
   899  			}
   900  		}
   901  	}
   902  	switch len(candidates) {
   903  	case 0:
   904  		return endpoint{}, endpoint{}, fmt.Errorf("no relations found")
   905  	case 1:
   906  		return candidates[0][0].endpoint(), candidates[0][1].endpoint(), nil
   907  	}
   908  
   909  	// There's ambiguity; try discarding implicit relations.
   910  	filtered := discardImplicitRelations(candidates)
   911  	if len(filtered) == 1 {
   912  		return filtered[0][0].endpoint(), filtered[0][1].endpoint(), nil
   913  	}
   914  	// The ambiguity cannot be resolved, so return an error.
   915  	var keys []string
   916  	for _, cand := range candidates {
   917  		keys = append(keys, fmt.Sprintf("%q", relationKey(cand)))
   918  	}
   919  	sort.Strings(keys)
   920  	return endpoint{}, endpoint{}, fmt.Errorf("ambiguous relation: %s %s could refer to %s",
   921  		epSpec0, epSpec1, strings.Join(keys, "; "))
   922  }
   923  
   924  func discardImplicitRelations(candidates [][]endpointInfo) [][]endpointInfo {
   925  	var filtered [][]endpointInfo
   926  outer:
   927  	for _, cand := range candidates {
   928  		for _, ep := range cand {
   929  			if ep.IsImplicit() {
   930  				continue outer
   931  			}
   932  		}
   933  		filtered = append(filtered, cand)
   934  	}
   935  	return filtered
   936  }
   937  
   938  // relationKey returns a string describing the relation defined by
   939  // endpoints, for use in various contexts (including error messages).
   940  func relationKey(endpoints []endpointInfo) string {
   941  	var names []string
   942  	for _, ep := range endpoints {
   943  		names = append(names, ep.String())
   944  	}
   945  	sort.Strings(names)
   946  	return strings.Join(names, " ")
   947  }
   948  
   949  // possibleEndpoints returns all the endpoints that the given endpoint spec
   950  // could refer to.
   951  func possibleEndpoints(epSpec endpoint, get func(svc string) (*Meta, error)) ([]endpointInfo, error) {
   952  	meta, err := get(epSpec.application)
   953  	if err != nil {
   954  		return nil, err
   955  	}
   956  
   957  	var eps []endpointInfo
   958  	add := func(r Relation) {
   959  		if epSpec.relation == "" || epSpec.relation == r.Name {
   960  			eps = append(eps, endpointInfo{
   961  				applicationName: epSpec.application,
   962  				Relation:        r,
   963  			})
   964  		}
   965  	}
   966  
   967  	for _, r := range meta.Provides {
   968  		add(r)
   969  	}
   970  	for _, r := range meta.Requires {
   971  		add(r)
   972  	}
   973  	// Every application implicitly provides a juju-info relation.
   974  	add(Relation{
   975  		Name:      "juju-info",
   976  		Role:      RoleProvider,
   977  		Interface: "juju-info",
   978  		Scope:     ScopeGlobal,
   979  	})
   980  	return eps, nil
   981  }