github.com/juju/charm/v11@v11.2.0/overlay_test.go (about)

     1  // Copyright 2019 Canonical Ltd.
     2  // Licensed under the LGPLv3, see LICENCE file for details.
     3  
     4  package charm_test
     5  
     6  import (
     7  	"io/ioutil"
     8  	"os"
     9  	"path/filepath"
    10  	"sort"
    11  	"strings"
    12  
    13  	"github.com/juju/errors"
    14  	"github.com/juju/testing"
    15  	jc "github.com/juju/testing/checkers"
    16  	gc "gopkg.in/check.v1"
    17  	yaml "gopkg.in/yaml.v2"
    18  
    19  	"github.com/juju/charm/v11"
    20  )
    21  
    22  type bundleDataOverlaySuite struct {
    23  	testing.IsolationSuite
    24  }
    25  
    26  var _ = gc.Suite(&bundleDataOverlaySuite{})
    27  
    28  func (*bundleDataOverlaySuite) TestEmptyBaseApplication(c *gc.C) {
    29  	data := `
    30  applications:
    31    apache2:
    32  ---
    33  series: trusty
    34  applications:
    35    apache2:
    36      charm: cs:apache2-42
    37      series: bionic
    38  `[1:]
    39  
    40  	_, err := charm.ReadAndMergeBundleData(mustCreateStringDataSource(c, data))
    41  	c.Assert(err, gc.ErrorMatches, `base application "apache2" has no body`)
    42  }
    43  
    44  func (*bundleDataOverlaySuite) TestExtractBaseAndOverlayParts(c *gc.C) {
    45  	data := `
    46  applications:
    47    apache2:
    48      charm: apache2
    49      exposed-endpoints:
    50        www:
    51          expose-to-spaces:
    52            - dmz
    53          expose-to-cidrs:
    54            - 13.37.0.0/16
    55      offers:
    56        my-offer:
    57          endpoints:
    58          - apache-website
    59          - website-cache
    60          acl:
    61            admin: admin
    62            foo: consume
    63        my-other-offer:
    64          endpoints:
    65          - apache-website
    66  saas:
    67      apache2:
    68          url: production:admin/info.apache
    69  series: bionic
    70  `[1:]
    71  
    72  	expBase := `
    73  applications:
    74    apache2:
    75      charm: apache2
    76  saas:
    77    apache2:
    78      url: production:admin/info.apache
    79  series: bionic
    80  `[1:]
    81  
    82  	expOverlay := `
    83  applications:
    84    apache2:
    85      exposed-endpoints:
    86        www:
    87          expose-to-spaces:
    88          - dmz
    89          expose-to-cidrs:
    90          - 13.37.0.0/16
    91      offers:
    92        my-offer:
    93          endpoints:
    94          - apache-website
    95          - website-cache
    96          acl:
    97            admin: admin
    98            foo: consume
    99        my-other-offer:
   100          endpoints:
   101          - apache-website
   102  `[1:]
   103  
   104  	bd, err := charm.ReadBundleData(strings.NewReader(data))
   105  	c.Assert(err, gc.IsNil)
   106  
   107  	base, overlay, err := charm.ExtractBaseAndOverlayParts(bd)
   108  	c.Assert(err, jc.ErrorIsNil)
   109  
   110  	baseYaml, err := yaml.Marshal(base)
   111  	c.Assert(err, jc.ErrorIsNil)
   112  	c.Assert(string(baseYaml), gc.Equals, expBase)
   113  
   114  	overlayYaml, err := yaml.Marshal(overlay)
   115  	c.Assert(err, jc.ErrorIsNil)
   116  	c.Assert(string(overlayYaml), gc.Equals, expOverlay)
   117  }
   118  
   119  func (*bundleDataOverlaySuite) TestExtractBaseAndOverlayPartsWithNoOverlayFields(c *gc.C) {
   120  	data := `
   121  bundle: kubernetes
   122  applications:
   123    mysql:
   124      charm: cs:mysql
   125      scale: 1
   126    wordpress:
   127      charm: cs:wordpress
   128      scale: 2
   129  relations:
   130  - - wordpress:db
   131    - mysql:mysql
   132  `[1:]
   133  
   134  	expBase := `
   135  bundle: kubernetes
   136  applications:
   137    mysql:
   138      charm: cs:mysql
   139      num_units: 1
   140    wordpress:
   141      charm: cs:wordpress
   142      num_units: 2
   143  relations:
   144  - - wordpress:db
   145    - mysql:mysql
   146  `[1:]
   147  
   148  	expOverlay := `
   149  {}
   150  `[1:]
   151  
   152  	bd, err := charm.ReadBundleData(strings.NewReader(data))
   153  	c.Assert(err, gc.IsNil)
   154  
   155  	base, overlay, err := charm.ExtractBaseAndOverlayParts(bd)
   156  	c.Assert(err, jc.ErrorIsNil)
   157  
   158  	baseYaml, err := yaml.Marshal(base)
   159  	c.Assert(err, jc.ErrorIsNil)
   160  	c.Assert(string(baseYaml), gc.Equals, expBase)
   161  
   162  	overlayYaml, err := yaml.Marshal(overlay)
   163  	c.Assert(err, jc.ErrorIsNil)
   164  	c.Assert(string(overlayYaml), gc.Equals, expOverlay)
   165  }
   166  
   167  func (*bundleDataOverlaySuite) TestExtractAndMergeWithMixedOverlayBits(c *gc.C) {
   168  	// In this example, mysql defines an offer whereas wordpress does not.
   169  	//
   170  	// When the visitor code examines the application map, it should report
   171  	// back that the filtered "mysql" application key should be retained
   172  	// but the "wordpress" application key should NOT be retained. The
   173  	// applications map should be retained because at least one of its keys
   174  	// has to be retained. However, the "wordpress" entry must be removed.
   175  	// If not, it would be encoded as an empty object which the overlay
   176  	// merge code would mis-interpret as a request to delete the "wordpress"
   177  	// application from the base bundle!
   178  	data := `
   179  bundle: kubernetes
   180  applications:
   181    mysql:
   182      charm: cs:mysql
   183      scale: 1
   184      offers:
   185        my-offer:
   186          endpoints:
   187          - apache-website
   188          - website-cache
   189          acl:
   190            admin: admin
   191            foo: consume
   192    wordpress:
   193      charm: cs:wordpress
   194      channel: edge
   195      scale: 2
   196      options:
   197        foo: bar
   198  relations:
   199  - - wordpress:db
   200    - mysql:mysql
   201  `[1:]
   202  
   203  	expBase := `
   204  bundle: kubernetes
   205  applications:
   206    mysql:
   207      charm: cs:mysql
   208      num_units: 1
   209    wordpress:
   210      charm: cs:wordpress
   211      channel: edge
   212      num_units: 2
   213      options:
   214        foo: bar
   215  relations:
   216  - - wordpress:db
   217    - mysql:mysql
   218  `[1:]
   219  
   220  	expOverlay := `
   221  applications:
   222    mysql:
   223      offers:
   224        my-offer:
   225          endpoints:
   226          - apache-website
   227          - website-cache
   228          acl:
   229            admin: admin
   230            foo: consume
   231  `[1:]
   232  
   233  	bd, err := charm.ReadBundleData(strings.NewReader(data))
   234  	c.Assert(err, gc.IsNil)
   235  
   236  	base, overlay, err := charm.ExtractBaseAndOverlayParts(bd)
   237  	c.Assert(err, jc.ErrorIsNil)
   238  
   239  	baseYaml, err := yaml.Marshal(base)
   240  	c.Assert(err, jc.ErrorIsNil)
   241  	c.Assert(string(baseYaml), gc.Equals, expBase)
   242  
   243  	overlayYaml, err := yaml.Marshal(overlay)
   244  	c.Assert(err, jc.ErrorIsNil)
   245  	c.Assert(string(overlayYaml), gc.Equals, expOverlay)
   246  
   247  	// Check that merging the output back into a bundle yields the original
   248  	r := strings.NewReader(string(baseYaml) + "\n---\n" + string(overlayYaml))
   249  	ds, err := charm.StreamBundleDataSource(r, "")
   250  	c.Assert(err, jc.ErrorIsNil)
   251  
   252  	newBd, err := charm.ReadAndMergeBundleData(ds)
   253  	c.Assert(err, jc.ErrorIsNil)
   254  	c.Assert(newBd, gc.DeepEquals, bd)
   255  }
   256  
   257  func (*bundleDataOverlaySuite) TestVerifyNoOverlayFieldsPresent(c *gc.C) {
   258  	data := `
   259  applications:
   260    apache2:
   261      charm: apache2
   262      offers:
   263        my-offer:
   264          endpoints:
   265          - apache-website
   266          - website-cache
   267          acl:
   268            admin: admin
   269            foo: consume
   270        my-other-offer:
   271          endpoints:
   272          - apache-website
   273  saas:
   274      apache2:
   275          url: production:admin/info.apache
   276  series: bionic
   277  `
   278  
   279  	bd, err := charm.ReadBundleData(strings.NewReader(data))
   280  	c.Assert(err, gc.IsNil)
   281  
   282  	static, overlay, err := charm.ExtractBaseAndOverlayParts(bd)
   283  	c.Assert(err, jc.ErrorIsNil)
   284  
   285  	c.Assert(charm.VerifyNoOverlayFieldsPresent(static), gc.Equals, nil)
   286  
   287  	expErrors := []string{
   288  		"applications.apache2.offers can only appear in an overlay section",
   289  		"applications.apache2.offers.my-offer.endpoints can only appear in an overlay section",
   290  		"applications.apache2.offers.my-offer.acl can only appear in an overlay section",
   291  		"applications.apache2.offers.my-other-offer.endpoints can only appear in an overlay section",
   292  	}
   293  	err = charm.VerifyNoOverlayFieldsPresent(overlay)
   294  	c.Assert(err, gc.FitsTypeOf, (*charm.VerificationError)(nil))
   295  	errors := err.(*charm.VerificationError).Errors
   296  	errStrings := make([]string, len(errors))
   297  	for i, err := range errors {
   298  		errStrings[i] = err.Error()
   299  	}
   300  	sort.Strings(errStrings)
   301  	sort.Strings(expErrors)
   302  	c.Assert(errStrings, jc.DeepEquals, expErrors)
   303  }
   304  
   305  func (*bundleDataOverlaySuite) TestVerifyNoOverlayFieldsPresentOnNilOptionValue(c *gc.C) {
   306  	data := `
   307  # ssl_ca is left uninitialized so it resolves to nil
   308  ssl_ca: &ssl_ca
   309  
   310  applications:
   311    apache2:
   312      options:
   313        foo: bar
   314        ssl_ca: *ssl_ca
   315  series: bionic
   316  `
   317  
   318  	bd, err := charm.ReadBundleData(strings.NewReader(data))
   319  	c.Assert(err, gc.IsNil)
   320  
   321  	static, _, err := charm.ExtractBaseAndOverlayParts(bd)
   322  	c.Assert(err, jc.ErrorIsNil)
   323  
   324  	c.Assert(charm.VerifyNoOverlayFieldsPresent(static), gc.Equals, nil)
   325  }
   326  
   327  func (*bundleDataOverlaySuite) TestOverrideCharmAndSeries(c *gc.C) {
   328  	testBundleMergeResult(c, `
   329  applications:
   330    apache2:
   331      charm: apache2
   332      num_units: 1
   333  ---
   334  series: trusty
   335  applications:
   336    apache2:
   337      charm: cs:apache2-42
   338      series: bionic
   339  `, `
   340  applications:
   341    apache2:
   342      charm: cs:apache2-42
   343      series: bionic
   344      num_units: 1
   345  series: trusty
   346  `,
   347  	)
   348  }
   349  
   350  func (*bundleDataOverlaySuite) TestOverrideScale(c *gc.C) {
   351  	testBundleMergeResult(c, `
   352  applications:
   353    apache2:
   354      charm: apache2
   355      scale: 1
   356  ---
   357  applications:
   358    apache2:
   359      scale: 2
   360  `, `
   361  applications:
   362    apache2:
   363      charm: apache2
   364      num_units: 2
   365  `,
   366  	)
   367  }
   368  
   369  func (*bundleDataOverlaySuite) TestOverrideScaleWithNumUnits(c *gc.C) {
   370  	// This shouldn't be allowed, but the code does, so we should test it!
   371  	// Notice that scale doesn't exist.
   372  	testBundleMergeResult(c, `
   373  applications:
   374    apache2:
   375      charm: apache2
   376      scale: 1
   377  ---
   378  applications:
   379    apache2:
   380      num_units: 2
   381  `, `
   382  applications:
   383    apache2:
   384      charm: apache2
   385      num_units: 2
   386  `,
   387  	)
   388  }
   389  
   390  func (*bundleDataOverlaySuite) TestMultipleOverrideScale(c *gc.C) {
   391  	testBundleMergeResult(c, `
   392  applications:
   393    apache2:
   394      charm: apache2
   395      scale: 1
   396  ---
   397  applications:
   398    apache2:
   399      scale: 50
   400  ---
   401  applications:
   402    apache2:
   403      scale: 3
   404  `, `
   405  applications:
   406    apache2:
   407      charm: apache2
   408      num_units: 3
   409  `,
   410  	)
   411  }
   412  
   413  func (*bundleDataOverlaySuite) TestOverrideScaleWithZero(c *gc.C) {
   414  	testBundleMergeResult(c, `
   415  applications:
   416    apache2:
   417      charm: apache2
   418      scale: 1
   419  ---
   420  applications:
   421    apache2:
   422      scale: 0
   423  `, `
   424  applications:
   425    apache2:
   426      charm: apache2
   427      num_units: 1
   428  `,
   429  	)
   430  }
   431  
   432  func (*bundleDataOverlaySuite) TestAddAndOverrideResourcesStorageDevicesAndBindings(c *gc.C) {
   433  	testBundleMergeResult(c, `
   434  applications:
   435    apache2:
   436      charm: apache2
   437      resources:
   438        res1: foo
   439      storage:
   440        dsk0: dsk0
   441      devices:
   442        dev0: dev0
   443  ---
   444  applications:
   445    apache2:
   446      resources:
   447        res1: bar
   448        res2: new
   449      storage:
   450        dsk0: vol0
   451        dsk1: new
   452      devices:
   453        dev0: net
   454        dev1: new
   455      bindings:
   456        bnd0: new
   457  `, `
   458  applications:
   459    apache2:
   460      charm: apache2
   461      resources:
   462        res1: bar
   463        res2: new
   464      storage:
   465        dsk0: vol0
   466        dsk1: new
   467      devices:
   468        dev0: net
   469        dev1: new
   470      bindings:
   471        bnd0: new
   472  `,
   473  	)
   474  }
   475  
   476  func (*bundleDataOverlaySuite) TestAddAndOverrideOptionsAndAnnotations(c *gc.C) {
   477  	testBundleMergeResult(c, `
   478  applications:
   479    apache2:
   480      charm: apache2
   481      options:
   482        opt1: foo
   483        opt1: bar
   484        mapOpt:
   485          foo: bar
   486  ---
   487  applications:
   488    apache2:
   489      options:
   490        opt1: foo
   491        opt2: ""
   492        mapOpt:
   493      annotations:
   494        ann1: new
   495  `, `
   496  applications:
   497    apache2:
   498      charm: apache2
   499      options:
   500        opt1: foo
   501        opt2: ""
   502      annotations:
   503        ann1: new
   504  `,
   505  	)
   506  }
   507  
   508  func (*bundleDataOverlaySuite) TestOverrideUnitsTrustConstraintsAndExposeFlags(c *gc.C) {
   509  	testBundleMergeResult(c, `
   510  applications:
   511    apache2:
   512      charm: apache2
   513  ---
   514  applications:
   515    apache2:
   516      num_units: 4
   517      to:
   518      - lxd/0
   519      - lxd/1
   520      - lxd/2
   521      - lxd/3
   522      expose: true
   523  `, `
   524  applications:
   525    apache2:
   526      charm: apache2
   527      num_units: 4
   528      to:
   529      - lxd/0
   530      - lxd/1
   531      - lxd/2
   532      - lxd/3
   533      expose: true
   534  `,
   535  	)
   536  }
   537  
   538  func (*bundleDataOverlaySuite) TestAddModifyAndRemoveApplicationsAndRelations(c *gc.C) {
   539  	testBundleMergeResult(c, `
   540  applications:
   541    apache2:
   542      charm: apache2
   543    wordpress:
   544      charm: wordpress
   545    dummy:
   546      charm: dummy
   547  relations:
   548  - - wordpress:www
   549    - apache2:www
   550  ---
   551  applications:
   552    apache2:
   553      charm: apache2
   554    wordpress: 
   555  relations:
   556  - - dummy:www
   557    - apache2:www
   558  `, `
   559  applications:
   560    apache2:
   561      charm: apache2
   562    dummy:
   563      charm: dummy
   564  relations:
   565  - - dummy:www
   566    - apache2:www
   567  `,
   568  	)
   569  }
   570  
   571  func (*bundleDataOverlaySuite) TestAddModifyAndRemoveSaasBlocksAndRelations(c *gc.C) {
   572  	testBundleMergeResult(c, `
   573  saas:
   574    postgres:
   575      url: jaas:admin/default.postgres
   576  applications:
   577    wordpress:
   578      charm: wordpress
   579  relations:
   580  - - wordpress:db
   581    - postgres:db
   582  ---
   583  saas:
   584    postgres: 
   585    cockroachdb:
   586      url: jaas:admin/default.cockroachdb
   587  `, `
   588  applications:
   589    wordpress:
   590      charm: wordpress
   591  saas:
   592    cockroachdb:
   593      url: jaas:admin/default.cockroachdb
   594  `,
   595  	)
   596  }
   597  
   598  func (*bundleDataOverlaySuite) TestAddAndRemoveOffers(c *gc.C) {
   599  	testBundleMergeResult(c, `
   600  applications:
   601    apache2:
   602      charm: apache2
   603      channel: stable
   604      revision: 26
   605  --- # offer blocks are overlay-specific
   606  applications:
   607    apache2:
   608      offers:
   609        my-offer:
   610          endpoints:
   611          - apache-website
   612          - website-cache
   613          acl:
   614            admin: admin
   615            foo: consume
   616        my-other-offer:
   617          endpoints:
   618          - apache-website
   619  --- 
   620  applications:
   621    apache2:
   622      offers:
   623        my-other-offer:
   624  `, `
   625  applications:
   626    apache2:
   627      charm: apache2
   628      channel: stable
   629      revision: 26
   630      offers:
   631        my-offer:
   632          endpoints:
   633          - apache-website
   634          - website-cache
   635          acl:
   636            admin: admin
   637            foo: consume
   638  `,
   639  	)
   640  }
   641  
   642  func (*bundleDataOverlaySuite) TestAddAndRemoveMachines(c *gc.C) {
   643  	testBundleMergeResult(c, `
   644  applications:
   645    apache2:
   646      charm: apache2
   647      channel: stable
   648      revision: 26
   649  machines:
   650    "0": {}
   651    "1": {}
   652  ---
   653  machines:
   654    "2": {}
   655  `, `
   656  applications:
   657    apache2:
   658      charm: apache2
   659      channel: stable
   660      revision: 26
   661  machines:
   662    "2": {}
   663  `,
   664  	)
   665  }
   666  
   667  func (*bundleDataOverlaySuite) TestYAMLInterpolation(c *gc.C) {
   668  	base := `
   669  applications:
   670      django:
   671          expose: true
   672          charm: django
   673          num_units: 1
   674          options:
   675              general: good
   676          annotations:
   677              key1: value1
   678              key2: value2
   679          to: [1]
   680      memcached:
   681          charm: mem
   682          revision: 47
   683          series: trusty
   684          num_units: 1
   685          options:
   686              key: value
   687  relations:
   688      - - "django"
   689        - "memcached"
   690  machines:
   691      1:
   692          annotations: {foo: bar}`
   693  
   694  	removeDjango := `
   695  applications:
   696      django:
   697  `
   698  
   699  	addWiki := `
   700  defaultwiki: &DEFAULTWIKI
   701      charm: "mediawiki"
   702      revision: 5
   703      series: trusty
   704      num_units: 1
   705      options: &WIKIOPTS
   706          debug: false
   707          name: Please set name of wiki
   708          skin: vector
   709  
   710  applications:
   711      wiki:
   712          <<: *DEFAULTWIKI
   713          options:
   714              <<: *WIKIOPTS
   715              name: The name override
   716  relations:
   717      - - "wiki"
   718        - "memcached"
   719  `
   720  
   721  	bd, err := charm.ReadAndMergeBundleData(
   722  		mustCreateStringDataSource(c, base),
   723  		mustCreateStringDataSource(c, removeDjango),
   724  		mustCreateStringDataSource(c, addWiki),
   725  	)
   726  	c.Assert(err, gc.IsNil)
   727  
   728  	merged, err := yaml.Marshal(bd)
   729  	c.Assert(err, gc.IsNil)
   730  
   731  	exp := `
   732  applications:
   733    memcached:
   734      charm: mem
   735      revision: 47
   736      series: trusty
   737      num_units: 1
   738      options:
   739        key: value
   740    wiki:
   741      charm: mediawiki
   742      revision: 5
   743      series: trusty
   744      num_units: 1
   745      options:
   746        debug: false
   747        name: The name override
   748        skin: vector
   749  machines:
   750    "1":
   751      annotations:
   752        foo: bar
   753  relations:
   754  - - wiki
   755    - memcached
   756  `
   757  
   758  	c.Assert("\n"+string(merged), gc.Equals, exp)
   759  }
   760  
   761  func (*bundleDataOverlaySuite) TestReadAndMergeBundleDataWithIncludes(c *gc.C) {
   762  	data := `
   763  applications:
   764    apache2:
   765      options:
   766        opt-raw: include-file://foo
   767        opt-b64: include-base64://foo
   768        opt-other:
   769          some: value
   770      annotations:
   771        anno-raw: include-file://foo
   772        anno-b64: include-base64://foo
   773        anno-other: value
   774  machines:
   775    "0": {}
   776    "1":
   777      annotations:
   778        anno-raw: include-file://foo
   779        anno-b64: include-base64://foo
   780        anno-other: value
   781  `
   782  
   783  	ds := srcWithFakeIncludeResolver{
   784  		src: mustCreateStringDataSource(c, data),
   785  		resolveMap: map[string][]byte{
   786  			"foo": []byte("lorem$ipsum$"),
   787  		},
   788  	}
   789  
   790  	bd, err := charm.ReadAndMergeBundleData(ds)
   791  	c.Assert(err, gc.IsNil)
   792  
   793  	merged, err := yaml.Marshal(bd)
   794  	c.Assert(err, gc.IsNil)
   795  
   796  	exp := `
   797  applications:
   798    apache2:
   799      options:
   800        opt-b64: bG9yZW0kaXBzdW0k
   801        opt-other:
   802          some: value
   803        opt-raw: lorem$ipsum$
   804      annotations:
   805        anno-b64: bG9yZW0kaXBzdW0k
   806        anno-other: value
   807        anno-raw: lorem$ipsum$
   808  machines:
   809    "0": {}
   810    "1":
   811      annotations:
   812        anno-b64: bG9yZW0kaXBzdW0k
   813        anno-other: value
   814        anno-raw: lorem$ipsum$
   815  `
   816  
   817  	c.Assert("\n"+string(merged), gc.Equals, exp)
   818  }
   819  
   820  func (*bundleDataOverlaySuite) TestBundleDataSourceRelativeIncludes(c *gc.C) {
   821  	base := `
   822  applications:
   823    django:
   824      charm: cs:django
   825      options:
   826        opt1: include-file://relative-to-base.txt
   827  `
   828  
   829  	overlays := `
   830  applications:
   831    django:
   832      charm: cs:django
   833      options:
   834        opt2: include-file://relative-to-overlay.txt
   835  ---
   836  applications:
   837    django:
   838      charm: cs:django
   839      options:
   840        opt3: include-file://relative-to-overlay.txt
   841  `
   842  
   843  	baseDir := c.MkDir()
   844  	mustWriteFile(c, filepath.Join(baseDir, "bundle.yaml"), base)
   845  	mustWriteFile(c, filepath.Join(baseDir, "relative-to-base.txt"), "lorem ipsum")
   846  
   847  	ovlDir := c.MkDir()
   848  	mustWriteFile(c, filepath.Join(ovlDir, "overlays.yaml"), overlays)
   849  	mustWriteFile(c, filepath.Join(ovlDir, "relative-to-overlay.txt"), "dolor")
   850  
   851  	bd, err := charm.ReadAndMergeBundleData(
   852  		mustCreateLocalDataSource(c, filepath.Join(baseDir, "bundle.yaml")),
   853  		mustCreateLocalDataSource(c, filepath.Join(ovlDir, "overlays.yaml")),
   854  	)
   855  	c.Assert(err, gc.IsNil)
   856  
   857  	merged, err := yaml.Marshal(bd)
   858  	c.Assert(err, gc.IsNil)
   859  
   860  	exp := `
   861  applications:
   862    django:
   863      charm: cs:django
   864      options:
   865        opt1: lorem ipsum
   866        opt2: dolor
   867        opt3: dolor
   868  `
   869  
   870  	c.Assert("\n"+string(merged), gc.Equals, exp)
   871  }
   872  
   873  func (*bundleDataOverlaySuite) TestBundleDataSourceWithEmptyOverlay(c *gc.C) {
   874  	base := `
   875  applications:
   876    django:
   877      charm: cs:django
   878  `
   879  
   880  	overlays := `
   881  ---
   882  `
   883  
   884  	baseDir := c.MkDir()
   885  	mustWriteFile(c, filepath.Join(baseDir, "bundle.yaml"), base)
   886  
   887  	ovlDir := c.MkDir()
   888  	mustWriteFile(c, filepath.Join(ovlDir, "overlays.yaml"), overlays)
   889  
   890  	bd, err := charm.ReadAndMergeBundleData(
   891  		mustCreateLocalDataSource(c, filepath.Join(baseDir, "bundle.yaml")),
   892  		mustCreateLocalDataSource(c, filepath.Join(ovlDir, "overlays.yaml")),
   893  	)
   894  	c.Assert(err, gc.IsNil)
   895  
   896  	merged, err := yaml.Marshal(bd)
   897  	c.Assert(err, gc.IsNil)
   898  
   899  	exp := `
   900  applications:
   901    django:
   902      charm: cs:django
   903  `
   904  
   905  	c.Assert("\n"+string(merged), gc.Equals, exp)
   906  }
   907  
   908  func (*bundleDataOverlaySuite) TestReadAndMergeBundleDataWithRelativeCharmPaths(c *gc.C) {
   909  	base := `
   910  applications:
   911    apache2:
   912      charm: ./apache
   913    mysql:
   914      charm: cs:mysql
   915    varnish:
   916      charm: /some/absolute/path
   917  `
   918  
   919  	overlay := `
   920  applications:
   921    wordpress:
   922      charm: ./wordpress
   923  `
   924  	bd, err := charm.ReadAndMergeBundleData(
   925  		mustCreateStringDataSourceWithBasePath(c, base, "/tmp/base"),
   926  		mustCreateStringDataSourceWithBasePath(c, overlay, "/overlay"),
   927  	)
   928  	c.Assert(err, gc.IsNil)
   929  
   930  	merged, err := yaml.Marshal(bd)
   931  	c.Assert(err, gc.IsNil)
   932  
   933  	exp := `
   934  applications:
   935    apache2:
   936      charm: /tmp/base/apache
   937    mysql:
   938      charm: cs:mysql
   939    varnish:
   940      charm: /some/absolute/path
   941    wordpress:
   942      charm: /overlay/wordpress
   943  `[1:]
   944  
   945  	c.Assert(string(merged), gc.Equals, exp)
   946  }
   947  
   948  type srcWithFakeIncludeResolver struct {
   949  	src        charm.BundleDataSource
   950  	resolveMap map[string][]byte
   951  }
   952  
   953  func (s srcWithFakeIncludeResolver) Parts() []*charm.BundleDataPart {
   954  	return s.src.Parts()
   955  }
   956  
   957  func (s srcWithFakeIncludeResolver) BasePath() string {
   958  	return s.src.BasePath()
   959  }
   960  
   961  func (s srcWithFakeIncludeResolver) ResolveInclude(path string) ([]byte, error) {
   962  	var (
   963  		data  []byte
   964  		found bool
   965  	)
   966  
   967  	if s.resolveMap != nil {
   968  		data, found = s.resolveMap[path]
   969  	}
   970  
   971  	if !found {
   972  		return nil, errors.NotFoundf(path)
   973  	}
   974  
   975  	return data, nil
   976  }
   977  
   978  // testBundleMergeResult reads and merges the bundle and any overlays in src,
   979  // serializes the merged bundle back to yaml and compares it with exp.
   980  func testBundleMergeResult(c *gc.C, src, exp string) {
   981  	bd, err := charm.ReadAndMergeBundleData(mustCreateStringDataSource(c, src))
   982  	c.Assert(err, gc.IsNil)
   983  
   984  	merged, err := yaml.Marshal(bd)
   985  	c.Assert(err, gc.IsNil)
   986  	c.Assert("\n"+string(merged), gc.Equals, exp)
   987  }
   988  
   989  func mustWriteFile(c *gc.C, path, content string) {
   990  	err := ioutil.WriteFile(path, []byte(content), os.ModePerm)
   991  	c.Assert(err, gc.IsNil)
   992  }
   993  
   994  func mustCreateLocalDataSource(c *gc.C, path string) charm.BundleDataSource {
   995  	ds, err := charm.LocalBundleDataSource(path)
   996  	c.Assert(err, gc.IsNil, gc.Commentf(path))
   997  	return ds
   998  }
   999  
  1000  func mustCreateStringDataSource(c *gc.C, data string) charm.BundleDataSource {
  1001  	ds, err := charm.StreamBundleDataSource(strings.NewReader(data), "")
  1002  	c.Assert(err, gc.IsNil)
  1003  	return ds
  1004  }
  1005  
  1006  func mustCreateStringDataSourceWithBasePath(c *gc.C, data, basePath string) charm.BundleDataSource {
  1007  	ds, err := charm.StreamBundleDataSource(strings.NewReader(data), basePath)
  1008  	c.Assert(err, gc.IsNil)
  1009  	return ds
  1010  }