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

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package bundle_test
     5  
     6  import (
     7  	"fmt"
     8  	"strings"
     9  
    10  	"github.com/juju/charm/v12"
    11  	"github.com/juju/charm/v12/resource"
    12  	"github.com/juju/description/v5"
    13  	"github.com/juju/names/v5"
    14  	jc "github.com/juju/testing/checkers"
    15  	"github.com/kr/pretty"
    16  	gc "gopkg.in/check.v1"
    17  
    18  	appFacade "github.com/juju/juju/apiserver/facades/client/application"
    19  	"github.com/juju/juju/apiserver/facades/client/bundle"
    20  	apiservertesting "github.com/juju/juju/apiserver/testing"
    21  	"github.com/juju/juju/core/network"
    22  	"github.com/juju/juju/core/network/firewall"
    23  	"github.com/juju/juju/rpc/params"
    24  	coretesting "github.com/juju/juju/testing"
    25  )
    26  
    27  type bundleSuite struct {
    28  	coretesting.BaseSuite
    29  	auth     *apiservertesting.FakeAuthorizer
    30  	facade   *bundle.APIv6
    31  	st       *mockState
    32  	modelTag names.ModelTag
    33  }
    34  
    35  var _ = gc.Suite(&bundleSuite{})
    36  
    37  func (s *bundleSuite) SetUpTest(c *gc.C) {
    38  	s.BaseSuite.SetUpTest(c)
    39  	s.auth = &apiservertesting.FakeAuthorizer{
    40  		Tag: names.NewUserTag("read"),
    41  	}
    42  
    43  	s.st = newMockState()
    44  	s.modelTag = names.NewModelTag("some-uuid")
    45  	s.facade = s.makeAPI(c)
    46  }
    47  
    48  func (s *bundleSuite) makeAPI(c *gc.C) *bundle.APIv6 {
    49  	api, err := bundle.NewBundleAPI(
    50  		s.st,
    51  		s.auth,
    52  		s.modelTag,
    53  	)
    54  	c.Assert(err, jc.ErrorIsNil)
    55  	return &bundle.APIv6{api}
    56  }
    57  
    58  func (s *bundleSuite) TestGetChangesBundleContentError(c *gc.C) {
    59  	args := params.BundleChangesParams{
    60  		BundleDataYAML: ":",
    61  	}
    62  	r, err := s.facade.GetChanges(args)
    63  	c.Assert(err, gc.ErrorMatches, `cannot read bundle YAML: malformed bundle: bundle is empty not valid`)
    64  	c.Assert(r, gc.DeepEquals, params.BundleChangesResults{})
    65  }
    66  
    67  func (s *bundleSuite) TestGetChangesBundleVerificationErrors(c *gc.C) {
    68  	args := params.BundleChangesParams{
    69  		BundleDataYAML: `
    70              applications:
    71                  django:
    72                      charm: django
    73                      to: [1]
    74                  haproxy:
    75                      charm: 42
    76                      num_units: -1
    77          `,
    78  	}
    79  	r, err := s.facade.GetChanges(args)
    80  	c.Assert(err, jc.ErrorIsNil)
    81  	c.Assert(r.Changes, gc.IsNil)
    82  	c.Assert(r.Errors, jc.SameContents, []string{
    83  		`placement "1" refers to a machine not defined in this bundle`,
    84  		`too many units specified in unit placement for application "django"`,
    85  		`invalid charm URL in application "haproxy": cannot parse name and/or revision in URL "42": name "42" not valid`,
    86  		`negative number of units specified on application "haproxy"`,
    87  	})
    88  }
    89  
    90  func (s *bundleSuite) TestGetChangesBundleConstraintsError(c *gc.C) {
    91  	args := params.BundleChangesParams{
    92  		BundleDataYAML: `
    93              applications:
    94                  django:
    95                      charm: django
    96                      num_units: 1
    97                      constraints: bad=wolf
    98          `,
    99  	}
   100  	r, err := s.facade.GetChanges(args)
   101  	c.Assert(err, jc.ErrorIsNil)
   102  	c.Assert(r.Changes, gc.IsNil)
   103  	c.Assert(r.Errors, jc.SameContents, []string{
   104  		`invalid constraints "bad=wolf" in application "django": unknown constraint "bad"`,
   105  	})
   106  }
   107  
   108  func (s *bundleSuite) TestGetChangesBundleStorageError(c *gc.C) {
   109  	args := params.BundleChangesParams{
   110  		BundleDataYAML: `
   111              applications:
   112                  django:
   113                      charm: django
   114                      num_units: 1
   115                      storage:
   116                          bad: 0,100M
   117          `,
   118  	}
   119  	r, err := s.facade.GetChanges(args)
   120  	c.Assert(err, jc.ErrorIsNil)
   121  	c.Assert(r.Changes, gc.IsNil)
   122  	c.Assert(r.Errors, jc.SameContents, []string{
   123  		`invalid storage "bad" in application "django": cannot parse count: count must be greater than zero, got "0"`,
   124  	})
   125  }
   126  
   127  func (s *bundleSuite) TestGetChangesBundleDevicesError(c *gc.C) {
   128  	args := params.BundleChangesParams{
   129  		BundleDataYAML: `
   130              applications:
   131                  django:
   132                      charm: django
   133                      num_units: 1
   134                      devices:
   135                          bad-gpu: -1,nvidia.com/gpu
   136          `,
   137  	}
   138  	r, err := s.facade.GetChanges(args)
   139  	c.Assert(err, jc.ErrorIsNil)
   140  	c.Assert(r.Changes, gc.IsNil)
   141  	c.Assert(r.Errors, jc.SameContents, []string{
   142  		`invalid device "bad-gpu" in application "django": count must be greater than zero, got "-1"`,
   143  	})
   144  }
   145  
   146  func (s *bundleSuite) TestGetChangesSuccessV2(c *gc.C) {
   147  	args := params.BundleChangesParams{
   148  		BundleDataYAML: `
   149              applications:
   150                  django:
   151                      charm: django
   152                      options:
   153                          debug: true
   154                      storage:
   155                          tmpfs: tmpfs,1G
   156                      devices:
   157                          bitcoinminer: 2,nvidia.com/gpu
   158                  haproxy:
   159                      charm: haproxy
   160                      revision: 42
   161                      channel: stable
   162                      base: ubuntu@22.04/stable
   163              relations:
   164                  - - django:web
   165                    - haproxy:web
   166          `,
   167  	}
   168  	r, err := s.facade.GetChanges(args)
   169  	c.Assert(err, jc.ErrorIsNil)
   170  	c.Check(r.Changes, jc.DeepEquals, []*params.BundleChange{{
   171  		Id:     "addCharm-0",
   172  		Method: "addCharm",
   173  		Args:   []interface{}{"django", "", ""},
   174  	}, {
   175  		Id:     "deploy-1",
   176  		Method: "deploy",
   177  		Args: []interface{}{
   178  			"$addCharm-0",
   179  			"",
   180  			"django",
   181  			map[string]interface{}{"debug": true},
   182  			"",
   183  			map[string]string{"tmpfs": "tmpfs,1G"},
   184  			map[string]string{"bitcoinminer": "2,nvidia.com/gpu"},
   185  			map[string]string{},
   186  			map[string]int{},
   187  			0,
   188  			"",
   189  		},
   190  		Requires: []string{"addCharm-0"},
   191  	}, {
   192  		Id:     "addCharm-2",
   193  		Method: "addCharm",
   194  		Args:   []interface{}{"haproxy", "jammy", "stable"},
   195  	}, {
   196  		Id:     "deploy-3",
   197  		Method: "deploy",
   198  		Args: []interface{}{
   199  			"$addCharm-2",
   200  			"jammy",
   201  			"haproxy",
   202  			map[string]interface{}{},
   203  			"",
   204  			map[string]string{},
   205  			map[string]string{},
   206  			map[string]string{},
   207  			map[string]int{},
   208  			0,
   209  			"stable",
   210  		},
   211  		Requires: []string{"addCharm-2"},
   212  	}, {
   213  		Id:       "addRelation-4",
   214  		Method:   "addRelation",
   215  		Args:     []interface{}{"$deploy-1:web", "$deploy-3:web"},
   216  		Requires: []string{"deploy-1", "deploy-3"},
   217  	}}, gc.Commentf("\nobtained: %s\n", pretty.Sprint(r.Changes)))
   218  	c.Assert(r.Errors, gc.IsNil)
   219  }
   220  
   221  func (s *bundleSuite) TestGetChangesWithOverlaysV6(c *gc.C) {
   222  	args := params.BundleChangesParams{
   223  		BundleDataYAML: `
   224              applications:
   225                  django:
   226                      charm: django
   227                      options:
   228                          debug: true
   229                      storage:
   230                          tmpfs: tmpfs,1G
   231                      devices:
   232                          bitcoinminer: 2,nvidia.com/gpu
   233                  haproxy:
   234                      charm: ch:haproxy-42
   235              relations:
   236                  - - django:web
   237                    - haproxy:web
   238  --- # overlay
   239  description: remove haproxy
   240  applications:
   241      haproxy:
   242          `,
   243  	}
   244  
   245  	expectedChanges_V6 := []*params.BundleChange{{
   246  		Id:     "addCharm-0",
   247  		Method: "addCharm",
   248  		Args:   []interface{}{"django", "", ""},
   249  	}, {
   250  		Id:     "deploy-1",
   251  		Method: "deploy",
   252  		Args: []interface{}{
   253  			"$addCharm-0",
   254  			"",
   255  			"django",
   256  			map[string]interface{}{"debug": true},
   257  			"",
   258  			map[string]string{"tmpfs": "tmpfs,1G"},
   259  			map[string]string{"bitcoinminer": "2,nvidia.com/gpu"},
   260  			map[string]string{},
   261  			map[string]int{},
   262  			0,
   263  			"",
   264  		},
   265  		Requires: []string{"addCharm-0"},
   266  	}}
   267  
   268  	apiv6 := s.makeAPI(c)
   269  	r_v6, err_v6 := apiv6.GetChanges(args)
   270  	c.Assert(err_v6, jc.ErrorIsNil)
   271  	c.Assert(r_v6.Changes, jc.DeepEquals, expectedChanges_V6)
   272  }
   273  
   274  func (s *bundleSuite) TestGetChangesKubernetes(c *gc.C) {
   275  	args := params.BundleChangesParams{
   276  		BundleDataYAML: `
   277              bundle: kubernetes
   278              applications:
   279                  django:
   280                      charm: django
   281                      scale: 1
   282                      options:
   283                          debug: true
   284                      storage:
   285                          tmpfs: tmpfs,1G
   286                      devices:
   287                          bitcoinminer: 2,nvidia.com/gpu
   288                  haproxy:
   289                      charm: ch:haproxy
   290                      revision: 42
   291                      channel: stable
   292              relations:
   293                  - - django:web
   294                    - haproxy:web
   295          `,
   296  	}
   297  	r, err := s.facade.GetChanges(args)
   298  	c.Assert(err, jc.ErrorIsNil)
   299  	c.Check(r.Changes, jc.DeepEquals, []*params.BundleChange{{
   300  		Id:     "addCharm-0",
   301  		Method: "addCharm",
   302  		Args:   []interface{}{"django", "", ""},
   303  	}, {
   304  		Id:     "deploy-1",
   305  		Method: "deploy",
   306  		Args: []interface{}{
   307  			"$addCharm-0",
   308  			"",
   309  			"django",
   310  			map[string]interface{}{"debug": true},
   311  			"",
   312  			map[string]string{"tmpfs": "tmpfs,1G"},
   313  			map[string]string{"bitcoinminer": "2,nvidia.com/gpu"},
   314  			map[string]string{},
   315  			map[string]int{},
   316  			1,
   317  			"",
   318  		},
   319  		Requires: []string{"addCharm-0"},
   320  	}, {
   321  		Id:     "addCharm-2",
   322  		Method: "addCharm",
   323  		Args:   []interface{}{"ch:haproxy", "", "stable"},
   324  	}, {
   325  		Id:     "deploy-3",
   326  		Method: "deploy",
   327  		Args: []interface{}{
   328  			"$addCharm-2",
   329  			"",
   330  			"haproxy",
   331  			map[string]interface{}{},
   332  			"",
   333  			map[string]string{},
   334  			map[string]string{},
   335  			map[string]string{},
   336  			map[string]int{},
   337  			0,
   338  			"stable",
   339  		},
   340  		Requires: []string{"addCharm-2"},
   341  	}, {
   342  		Id:       "addRelation-4",
   343  		Method:   "addRelation",
   344  		Args:     []interface{}{"$deploy-1:web", "$deploy-3:web"},
   345  		Requires: []string{"deploy-1", "deploy-3"},
   346  	}}, gc.Commentf("\nobtained: %s\n", pretty.Sprint(r.Changes)))
   347  	c.Assert(r.Errors, gc.IsNil)
   348  }
   349  
   350  func (s *bundleSuite) TestGetChangesBundleEndpointBindingsSuccess(c *gc.C) {
   351  	args := params.BundleChangesParams{
   352  		BundleDataYAML: `
   353              applications:
   354                  django:
   355                      charm: django
   356                      num_units: 1
   357                      bindings:
   358                          url: public
   359          `,
   360  	}
   361  	r, err := s.facade.GetChanges(args)
   362  	c.Assert(err, jc.ErrorIsNil)
   363  
   364  	for _, change := range r.Changes {
   365  		if change.Method == "deploy" {
   366  			c.Assert(change, jc.DeepEquals, &params.BundleChange{
   367  				Id:     "deploy-1",
   368  				Method: "deploy",
   369  				Args: []interface{}{
   370  					"$addCharm-0",
   371  					"",
   372  					"django",
   373  					map[string]interface{}{},
   374  					"",
   375  					map[string]string{},
   376  					map[string]string{},
   377  					map[string]string{"url": "public"},
   378  					map[string]int{},
   379  					0,
   380  					"",
   381  				},
   382  				Requires: []string{"addCharm-0"},
   383  			})
   384  		}
   385  	}
   386  }
   387  
   388  func (s *bundleSuite) TestGetChangesMapArgsBundleContentError(c *gc.C) {
   389  	args := params.BundleChangesParams{
   390  		BundleDataYAML: ":",
   391  	}
   392  	r, err := s.facade.GetChangesMapArgs(args)
   393  	c.Assert(err, gc.ErrorMatches, `cannot read bundle YAML: malformed bundle: bundle is empty not valid`)
   394  	c.Assert(r, gc.DeepEquals, params.BundleChangesMapArgsResults{})
   395  }
   396  
   397  func (s *bundleSuite) TestGetChangesMapArgsBundleVerificationErrors(c *gc.C) {
   398  	args := params.BundleChangesParams{
   399  		BundleDataYAML: `
   400              applications:
   401                  django:
   402                      charm: django
   403                      to: [1]
   404                  haproxy:
   405                      charm: 42
   406                      num_units: -1
   407          `,
   408  	}
   409  	r, err := s.facade.GetChangesMapArgs(args)
   410  	c.Assert(err, jc.ErrorIsNil)
   411  	c.Assert(r.Changes, gc.IsNil)
   412  	c.Assert(r.Errors, jc.SameContents, []string{
   413  		`placement "1" refers to a machine not defined in this bundle`,
   414  		`too many units specified in unit placement for application "django"`,
   415  		`invalid charm URL in application "haproxy": cannot parse name and/or revision in URL "42": name "42" not valid`,
   416  		`negative number of units specified on application "haproxy"`,
   417  	})
   418  }
   419  
   420  func (s *bundleSuite) TestGetChangesMapArgsBundleConstraintsError(c *gc.C) {
   421  	args := params.BundleChangesParams{
   422  		BundleDataYAML: `
   423              applications:
   424                  django:
   425                      charm: django
   426                      num_units: 1
   427                      constraints: bad=wolf
   428          `,
   429  	}
   430  	r, err := s.facade.GetChangesMapArgs(args)
   431  	c.Assert(err, jc.ErrorIsNil)
   432  	c.Assert(r.Changes, gc.IsNil)
   433  	c.Assert(r.Errors, jc.SameContents, []string{
   434  		`invalid constraints "bad=wolf" in application "django": unknown constraint "bad"`,
   435  	})
   436  }
   437  
   438  func (s *bundleSuite) TestGetChangesMapArgsBundleStorageError(c *gc.C) {
   439  	args := params.BundleChangesParams{
   440  		BundleDataYAML: `
   441              applications:
   442                  django:
   443                      charm: django
   444                      num_units: 1
   445                      storage:
   446                          bad: 0,100M
   447          `,
   448  	}
   449  	r, err := s.facade.GetChangesMapArgs(args)
   450  	c.Assert(err, jc.ErrorIsNil)
   451  	c.Assert(r.Changes, gc.IsNil)
   452  	c.Assert(r.Errors, jc.SameContents, []string{
   453  		`invalid storage "bad" in application "django": cannot parse count: count must be greater than zero, got "0"`,
   454  	})
   455  }
   456  
   457  func (s *bundleSuite) TestGetChangesMapArgsBundleDevicesError(c *gc.C) {
   458  	args := params.BundleChangesParams{
   459  		BundleDataYAML: `
   460              applications:
   461                  django:
   462                      charm: django
   463                      num_units: 1
   464                      devices:
   465                          bad-gpu: -1,nvidia.com/gpu
   466          `,
   467  	}
   468  	r, err := s.facade.GetChangesMapArgs(args)
   469  	c.Assert(err, jc.ErrorIsNil)
   470  	c.Assert(r.Changes, gc.IsNil)
   471  	c.Assert(r.Errors, jc.SameContents, []string{
   472  		`invalid device "bad-gpu" in application "django": count must be greater than zero, got "-1"`,
   473  	})
   474  }
   475  
   476  func (s *bundleSuite) TestGetChangesMapArgsSuccess(c *gc.C) {
   477  	args := params.BundleChangesParams{
   478  		BundleDataYAML: `
   479              applications:
   480                  django:
   481                      charm: django
   482                      options:
   483                          debug: true
   484                      storage:
   485                          tmpfs: tmpfs,1G
   486                      devices:
   487                          bitcoinminer: 2,nvidia.com/gpu
   488                  haproxy:
   489                      charm: ch:haproxy
   490                      revision: 42
   491                      channel: stable
   492                      base: ubuntu@22.04/stable
   493              relations:
   494                  - - django:web
   495                    - haproxy:web
   496          `,
   497  	}
   498  	r, err := s.facade.GetChangesMapArgs(args)
   499  	c.Assert(err, jc.ErrorIsNil)
   500  	c.Check(r.Changes, jc.DeepEquals, []*params.BundleChangesMapArgs{{
   501  		Id:     "addCharm-0",
   502  		Method: "addCharm",
   503  		Args: map[string]interface{}{
   504  			"charm": "django",
   505  		},
   506  	}, {
   507  		Id:     "deploy-1",
   508  		Method: "deploy",
   509  		Args: map[string]interface{}{
   510  			"application": "django",
   511  			"charm":       "$addCharm-0",
   512  			"devices": map[string]interface{}{
   513  				"bitcoinminer": "2,nvidia.com/gpu",
   514  			},
   515  			"options": map[string]interface{}{
   516  				"debug": true,
   517  			},
   518  			"storage": map[string]interface{}{
   519  				"tmpfs": "tmpfs,1G",
   520  			},
   521  		},
   522  		Requires: []string{"addCharm-0"},
   523  	}, {
   524  		Id:     "addCharm-2",
   525  		Method: "addCharm",
   526  		Args: map[string]interface{}{
   527  			"channel":  "stable",
   528  			"charm":    "ch:haproxy",
   529  			"revision": float64(42),
   530  			"series":   "jammy",
   531  			"base":     "ubuntu@22.04/stable",
   532  		},
   533  	}, {
   534  		Id:     "deploy-3",
   535  		Method: "deploy",
   536  		Args: map[string]interface{}{
   537  			"channel":     "stable",
   538  			"application": "haproxy",
   539  			"charm":       "$addCharm-2",
   540  			"series":      "jammy",
   541  			"base":        "ubuntu@22.04/stable",
   542  		},
   543  		Requires: []string{"addCharm-2"},
   544  	}, {
   545  		Id:     "addRelation-4",
   546  		Method: "addRelation",
   547  		Args: map[string]interface{}{
   548  			"endpoint1": "$deploy-1:web",
   549  			"endpoint2": "$deploy-3:web",
   550  		},
   551  		Requires: []string{"deploy-1", "deploy-3"},
   552  	}}, gc.Commentf("\nobtained: %s\n", pretty.Sprint(r.Changes)))
   553  	for _, err := range r.Errors {
   554  		c.Assert(err, gc.Equals, "")
   555  	}
   556  }
   557  
   558  func (s *bundleSuite) TestGetChangesMapArgsSuccessCharmHubRevision(c *gc.C) {
   559  	args := params.BundleChangesParams{
   560  		BundleDataYAML: `
   561              applications:
   562                  django:
   563                      charm: django
   564                      revision: 76
   565                      channel: candidate
   566          `,
   567  	}
   568  	r, err := s.facade.GetChangesMapArgs(args)
   569  	c.Assert(err, jc.ErrorIsNil)
   570  	c.Check(r.Changes, jc.DeepEquals, []*params.BundleChangesMapArgs{{
   571  		Id:     "addCharm-0",
   572  		Method: "addCharm",
   573  		Args: map[string]interface{}{
   574  			"revision": float64(76),
   575  			"channel":  "candidate",
   576  			"charm":    "django",
   577  		},
   578  	}, {
   579  		Id:     "deploy-1",
   580  		Method: "deploy",
   581  		Args: map[string]interface{}{
   582  			"application": "django",
   583  			"channel":     "candidate",
   584  			"charm":       "$addCharm-0",
   585  		},
   586  		Requires: []string{"addCharm-0"},
   587  	}})
   588  	for _, err := range r.Errors {
   589  		c.Assert(err, gc.Equals, "")
   590  	}
   591  }
   592  
   593  func (s *bundleSuite) TestGetChangesMapArgsKubernetes(c *gc.C) {
   594  	args := params.BundleChangesParams{
   595  		BundleDataYAML: `
   596              bundle: kubernetes
   597              applications:
   598                  django:
   599                      charm: django
   600                      scale: 1
   601                      options:
   602                          debug: true
   603                      storage:
   604                          tmpfs: tmpfs,1G
   605                      devices:
   606                          bitcoinminer: 2,nvidia.com/gpu
   607                  haproxy:
   608                      charm: ch:haproxy
   609                      revision: 42
   610                      channel: stable
   611              relations:
   612                  - - django:web
   613                    - haproxy:web
   614          `,
   615  	}
   616  	r, err := s.facade.GetChangesMapArgs(args)
   617  	c.Assert(err, jc.ErrorIsNil)
   618  	c.Check(r.Changes, jc.DeepEquals, []*params.BundleChangesMapArgs{{
   619  		Id:     "addCharm-0",
   620  		Method: "addCharm",
   621  		Args: map[string]interface{}{
   622  			"charm": "django",
   623  		},
   624  	}, {
   625  		Id:     "deploy-1",
   626  		Method: "deploy",
   627  		Args: map[string]interface{}{
   628  			"application": "django",
   629  			"charm":       "$addCharm-0",
   630  			"devices": map[string]interface{}{
   631  				"bitcoinminer": "2,nvidia.com/gpu",
   632  			},
   633  			"num-units": float64(1),
   634  			"options": map[string]interface{}{
   635  				"debug": true,
   636  			},
   637  			"storage": map[string]interface{}{
   638  				"tmpfs": "tmpfs,1G",
   639  			},
   640  		},
   641  		Requires: []string{"addCharm-0"},
   642  	}, {
   643  		Id:     "addCharm-2",
   644  		Method: "addCharm",
   645  		Args: map[string]interface{}{
   646  			"channel":  "stable",
   647  			"charm":    "ch:haproxy",
   648  			"revision": float64(42),
   649  		},
   650  	}, {
   651  		Id:     "deploy-3",
   652  		Method: "deploy",
   653  		Args: map[string]interface{}{
   654  			"channel":     "stable",
   655  			"application": "haproxy",
   656  			"charm":       "$addCharm-2",
   657  		},
   658  		Requires: []string{"addCharm-2"},
   659  	}, {
   660  		Id:     "addRelation-4",
   661  		Method: "addRelation",
   662  		Args: map[string]interface{}{
   663  			"endpoint1": "$deploy-1:web",
   664  			"endpoint2": "$deploy-3:web",
   665  		},
   666  		Requires: []string{"deploy-1", "deploy-3"},
   667  	}}, gc.Commentf("\nobtained: %s\n", pretty.Sprint(r.Changes)))
   668  	for _, err := range r.Errors {
   669  		c.Assert(err, gc.Equals, "")
   670  	}
   671  }
   672  
   673  func (s *bundleSuite) TestGetChangesMapArgsBundleEndpointBindingsSuccess(c *gc.C) {
   674  	args := params.BundleChangesParams{
   675  		BundleDataYAML: `
   676              applications:
   677                  django:
   678                      charm: django
   679                      num_units: 1
   680                      bindings:
   681                          url: public
   682          `,
   683  	}
   684  	r, err := s.facade.GetChangesMapArgs(args)
   685  	c.Assert(err, jc.ErrorIsNil)
   686  
   687  	for _, change := range r.Changes {
   688  		if change.Method == "deploy" {
   689  			c.Assert(change, jc.DeepEquals, &params.BundleChangesMapArgs{
   690  				Id:     "deploy-1",
   691  				Method: "deploy",
   692  				Args: map[string]interface{}{
   693  					"application": "django",
   694  					"charm":       "$addCharm-0",
   695  					"endpoint-bindings": map[string]interface{}{
   696  						"url": "public",
   697  					},
   698  				},
   699  				Requires: []string{"addCharm-0"},
   700  			})
   701  		}
   702  	}
   703  }
   704  
   705  func (s *bundleSuite) TestExportBundleFailNoApplication(c *gc.C) {
   706  	s.st.model = description.NewModel(description.ModelArgs{Owner: names.NewUserTag("magic"),
   707  		Config:      coretesting.FakeConfig(),
   708  		CloudRegion: "some-region"})
   709  	s.st.model.SetStatus(description.StatusArgs{Value: "available"})
   710  
   711  	result, err := s.facade.ExportBundle(params.ExportBundleParams{})
   712  	c.Assert(err, gc.NotNil)
   713  	c.Assert(result, gc.Equals, params.StringResult{})
   714  	c.Check(err, gc.ErrorMatches, "nothing to export as there are no applications")
   715  	s.st.CheckCall(c, 0, "ExportPartial", s.st.GetExportConfig())
   716  }
   717  
   718  func (s *bundleSuite) minimalApplicationArgs(modelType string) description.ApplicationArgs {
   719  	return s.minimalApplicationArgsWithCharmConfig(modelType, map[string]interface{}{
   720  		"key": "value",
   721  	})
   722  }
   723  
   724  func (s *bundleSuite) minimalApplicationArgsWithCharmConfig(modelType string, charmConfig map[string]interface{}) description.ApplicationArgs {
   725  	s.st.Spaces["1"] = "vlan2"
   726  	s.st.Spaces[network.AlphaSpaceId] = network.AlphaSpaceName
   727  	result := description.ApplicationArgs{
   728  		Tag:                  names.NewApplicationTag("ubuntu"),
   729  		Type:                 modelType,
   730  		CharmURL:             "ch:ubuntu",
   731  		Channel:              "stable",
   732  		CharmModifiedVersion: 1,
   733  		CharmConfig:          charmConfig,
   734  		Leader:               "ubuntu/0",
   735  		LeadershipSettings: map[string]interface{}{
   736  			"leader": true,
   737  		},
   738  		MetricsCredentials: []byte("sekrit"),
   739  		EndpointBindings:   map[string]string{"juju-info": "1", "another": "0"},
   740  	}
   741  	if modelType == description.CAAS {
   742  		result.PasswordHash = "some-hash"
   743  		result.PodSpec = "some-spec"
   744  		result.CloudService = &description.CloudServiceArgs{
   745  			ProviderId: "some-provider",
   746  			Addresses: []description.AddressArgs{
   747  				{Value: "10.0.0.1", Type: "special"},
   748  				{Value: "10.0.0.2", Type: "other"},
   749  			},
   750  		}
   751  	}
   752  	return result
   753  }
   754  
   755  func minimalUnitArgs(modelType string) description.UnitArgs {
   756  	result := description.UnitArgs{
   757  		Tag:          names.NewUnitTag("ubuntu/0"),
   758  		Type:         modelType,
   759  		Machine:      names.NewMachineTag("0"),
   760  		PasswordHash: "secure-hash",
   761  	}
   762  	if modelType == description.CAAS {
   763  		result.CloudContainer = &description.CloudContainerArgs{
   764  			ProviderId: "some-provider",
   765  			Address:    description.AddressArgs{Value: "10.0.0.1", Type: "special"},
   766  			Ports:      []string{"80", "443"},
   767  		}
   768  	}
   769  	return result
   770  }
   771  
   772  func minimalStatusArgs() description.StatusArgs {
   773  	return description.StatusArgs{
   774  		Value: "running",
   775  	}
   776  }
   777  
   778  func (s *bundleSuite) TestExportBundleWithApplication(c *gc.C) {
   779  	s.st.model = description.NewModel(description.ModelArgs{Owner: names.NewUserTag("magic"),
   780  		Config:      coretesting.FakeConfig(),
   781  		CloudRegion: "some-region"})
   782  
   783  	app := s.st.model.AddApplication(s.minimalApplicationArgs(description.IAAS))
   784  	app.SetCharmOrigin(description.CharmOriginArgs{Platform: "amd64/ubuntu/20.04/stable"})
   785  	app.SetStatus(minimalStatusArgs())
   786  
   787  	u := app.AddUnit(minimalUnitArgs(app.Type()))
   788  	u.SetAgentStatus(minimalStatusArgs())
   789  
   790  	s.st.model.SetStatus(description.StatusArgs{Value: "available"})
   791  
   792  	result, err := s.facade.ExportBundle(params.ExportBundleParams{})
   793  	c.Assert(err, jc.ErrorIsNil)
   794  	expectedResult := params.StringResult{Result: `
   795  default-base: ubuntu@20.04/stable
   796  applications:
   797    ubuntu:
   798      charm: ubuntu
   799      channel: stable
   800      num_units: 1
   801      to:
   802      - "0"
   803      options:
   804        key: value
   805      bindings:
   806        another: alpha
   807        juju-info: vlan2
   808  `[1:]}
   809  
   810  	c.Assert(result, gc.Equals, expectedResult)
   811  	s.st.CheckCall(c, 0, "ExportPartial", s.st.GetExportConfig())
   812  }
   813  
   814  func (s *bundleSuite) TestExportBundleWithApplicationResources(c *gc.C) {
   815  	s.st.model = description.NewModel(description.ModelArgs{Owner: names.NewUserTag("magic"),
   816  		Config:      coretesting.FakeConfig(),
   817  		CloudRegion: "some-region"})
   818  
   819  	app := s.st.model.AddApplication(s.minimalApplicationArgs(description.IAAS))
   820  	app.SetCharmOrigin(description.CharmOriginArgs{Platform: "amd64/ubuntu/20.04/stable"})
   821  	app.SetStatus(minimalStatusArgs())
   822  
   823  	res := app.AddResource(description.ResourceArgs{Name: "foo-file"})
   824  	res.SetApplicationRevision(description.ResourceRevisionArgs{
   825  		Revision: 42,
   826  		Type:     "file",
   827  		Origin:   resource.OriginStore.String(),
   828  	})
   829  	res2 := app.AddResource(description.ResourceArgs{Name: "bar-file"})
   830  	res2.SetApplicationRevision(description.ResourceRevisionArgs{
   831  		Revision: 0,
   832  		Type:     "file",
   833  		Origin:   resource.OriginUpload.String(),
   834  	})
   835  
   836  	u := app.AddUnit(minimalUnitArgs(app.Type()))
   837  	u.SetAgentStatus(minimalStatusArgs())
   838  
   839  	s.st.model.SetStatus(description.StatusArgs{Value: "available"})
   840  
   841  	result, err := s.facade.ExportBundle(params.ExportBundleParams{})
   842  	c.Assert(err, jc.ErrorIsNil)
   843  	expectedResult := params.StringResult{Result: `
   844  default-base: ubuntu@20.04/stable
   845  applications:
   846    ubuntu:
   847      charm: ubuntu
   848      channel: stable
   849      resources:
   850        foo-file: 42
   851      num_units: 1
   852      to:
   853      - "0"
   854      options:
   855        key: value
   856      bindings:
   857        another: alpha
   858        juju-info: vlan2
   859  `[1:]}
   860  
   861  	c.Assert(result, gc.Equals, expectedResult)
   862  	s.st.CheckCall(c, 0, "ExportPartial", s.st.GetExportConfig())
   863  }
   864  
   865  func (s *bundleSuite) TestExportBundleWithApplicationStorage(c *gc.C) {
   866  	s.st.model = description.NewModel(description.ModelArgs{Owner: names.NewUserTag("magic"),
   867  		Config:      coretesting.FakeConfig(),
   868  		CloudRegion: "some-region"})
   869  
   870  	args := s.minimalApplicationArgs(description.IAAS)
   871  	// Add storage directives to the app
   872  	args.StorageDirectives = map[string]description.StorageDirectiveArgs{
   873  		"storage1": {
   874  			Pool:  "pool1",
   875  			Size:  1024,
   876  			Count: 3,
   877  		},
   878  		"storage2": {
   879  			Pool: "pool2",
   880  			Size: 4096,
   881  		},
   882  		"storage3": {
   883  			Size: 2048,
   884  		},
   885  	}
   886  	app := s.st.model.AddApplication(args)
   887  	app.SetCharmOrigin(description.CharmOriginArgs{Platform: "amd64/ubuntu/20.04/stable"})
   888  	app.SetStatus(minimalStatusArgs())
   889  
   890  	u := app.AddUnit(minimalUnitArgs(app.Type()))
   891  	u.SetAgentStatus(minimalStatusArgs())
   892  
   893  	s.st.model.SetStatus(description.StatusArgs{Value: "available"})
   894  
   895  	result, err := s.facade.ExportBundle(params.ExportBundleParams{})
   896  	c.Assert(err, jc.ErrorIsNil)
   897  	expectedResult := params.StringResult{Result: `
   898  default-base: ubuntu@20.04/stable
   899  applications:
   900    ubuntu:
   901      charm: ubuntu
   902      channel: stable
   903      num_units: 1
   904      to:
   905      - "0"
   906      options:
   907        key: value
   908      storage:
   909        storage1: pool1,3,1024M
   910        storage2: pool2,4096M
   911        storage3: 2048M
   912      bindings:
   913        another: alpha
   914        juju-info: vlan2
   915  `[1:]}
   916  
   917  	c.Assert(result, gc.Equals, expectedResult)
   918  	s.st.CheckCall(c, 0, "ExportPartial", s.st.GetExportConfig())
   919  }
   920  
   921  func (s *bundleSuite) TestExportBundleWithTrustedApplication(c *gc.C) {
   922  	s.st.model = description.NewModel(description.ModelArgs{Owner: names.NewUserTag("magic"),
   923  		Config:      coretesting.FakeConfig(),
   924  		CloudRegion: "some-region"})
   925  
   926  	appArgs := s.minimalApplicationArgs(description.IAAS)
   927  	appArgs.ApplicationConfig = map[string]interface{}{
   928  		appFacade.TrustConfigOptionName: true,
   929  	}
   930  
   931  	app := s.st.model.AddApplication(appArgs)
   932  	app.SetCharmOrigin(description.CharmOriginArgs{Platform: "amd64/ubuntu/20.04/stable"})
   933  	app.SetStatus(minimalStatusArgs())
   934  
   935  	u := app.AddUnit(minimalUnitArgs(app.Type()))
   936  	u.SetAgentStatus(minimalStatusArgs())
   937  
   938  	s.st.model.SetStatus(description.StatusArgs{Value: "available"})
   939  
   940  	result, err := s.facade.ExportBundle(params.ExportBundleParams{})
   941  	c.Assert(err, jc.ErrorIsNil)
   942  	expectedResult := params.StringResult{Result: `
   943  default-base: ubuntu@20.04/stable
   944  applications:
   945    ubuntu:
   946      charm: ubuntu
   947      channel: stable
   948      num_units: 1
   949      to:
   950      - "0"
   951      options:
   952        key: value
   953      bindings:
   954        another: alpha
   955        juju-info: vlan2
   956      trust: true
   957  `[1:]}
   958  
   959  	c.Assert(result, gc.Equals, expectedResult)
   960  	s.st.CheckCall(c, 0, "ExportPartial", s.st.GetExportConfig())
   961  }
   962  
   963  func (s *bundleSuite) TestExportBundleWithApplicationOffers(c *gc.C) {
   964  	s.st.model = description.NewModel(description.ModelArgs{Owner: names.NewUserTag("magic"),
   965  		Config:      coretesting.FakeConfig(),
   966  		CloudRegion: "some-region"})
   967  
   968  	app := s.st.model.AddApplication(s.minimalApplicationArgs(description.IAAS))
   969  	app.SetCharmOrigin(description.CharmOriginArgs{Platform: "amd64/ubuntu/20.04/stable"})
   970  	app.SetStatus(minimalStatusArgs())
   971  	u := app.AddUnit(minimalUnitArgs(app.Type()))
   972  	u.SetAgentStatus(minimalStatusArgs())
   973  
   974  	_ = app.AddOffer(description.ApplicationOfferArgs{
   975  		OfferName: "my-offer",
   976  		Endpoints: map[string]string{
   977  			"endpoint-1": "endpoint-1",
   978  			"endpoint-2": "endpoint-2",
   979  		},
   980  		ACL: map[string]string{
   981  			"admin": "admin",
   982  			"foo":   "consume",
   983  		},
   984  	})
   985  
   986  	_ = app.AddOffer(description.ApplicationOfferArgs{
   987  		OfferName: "my-other-offer",
   988  		Endpoints: map[string]string{
   989  			"endpoint-1": "endpoint-1",
   990  			"endpoint-2": "endpoint-2",
   991  		},
   992  	})
   993  
   994  	// Add second app without an offer
   995  	app2Args := s.minimalApplicationArgs(description.IAAS)
   996  	app2Args.Tag = names.NewApplicationTag("foo")
   997  	app2 := s.st.model.AddApplication(app2Args)
   998  	app2.SetCharmOrigin(description.CharmOriginArgs{Platform: "amd64/ubuntu/20.04/stable"})
   999  	app2.SetStatus(minimalStatusArgs())
  1000  
  1001  	s.st.model.SetStatus(description.StatusArgs{Value: "available"})
  1002  
  1003  	result, err := s.facade.ExportBundle(params.ExportBundleParams{})
  1004  	c.Assert(err, jc.ErrorIsNil)
  1005  	expectedResult := params.StringResult{Result: `
  1006  default-base: ubuntu@20.04/stable
  1007  applications:
  1008    foo:
  1009      charm: ubuntu
  1010      channel: stable
  1011      options:
  1012        key: value
  1013      bindings:
  1014        another: alpha
  1015        juju-info: vlan2
  1016    ubuntu:
  1017      charm: ubuntu
  1018      channel: stable
  1019      num_units: 1
  1020      to:
  1021      - "0"
  1022      options:
  1023        key: value
  1024      bindings:
  1025        another: alpha
  1026        juju-info: vlan2
  1027  --- # overlay.yaml
  1028  applications:
  1029    ubuntu:
  1030      offers:
  1031        my-offer:
  1032          endpoints:
  1033          - endpoint-1
  1034          - endpoint-2
  1035          acl:
  1036            admin: admin
  1037            foo: consume
  1038        my-other-offer:
  1039          endpoints:
  1040          - endpoint-1
  1041          - endpoint-2
  1042  `[1:]}
  1043  
  1044  	c.Assert(result, gc.Equals, expectedResult)
  1045  	s.st.CheckCall(c, 0, "ExportPartial", s.st.GetExportConfig())
  1046  }
  1047  
  1048  func (s *bundleSuite) TestExportBundleWithApplicationCharmConfig(c *gc.C) {
  1049  	s.st.model = description.NewModel(description.ModelArgs{Owner: names.NewUserTag("magic"),
  1050  		Config:      coretesting.FakeConfig(),
  1051  		CloudRegion: "some-region"})
  1052  
  1053  	app := s.st.model.AddApplication(s.minimalApplicationArgsWithCharmConfig(description.IAAS, map[string]interface{}{
  1054  		"key": "value",
  1055  		"ssl_ca": `-----BEGIN RSA PRIVATE KEY-----
  1056  Proc-Type: 4,ENCRYPTED
  1057  DEK-Info: DES-EDE3-CBC,EA657EE6FE842AD0
  1058  
  1059  B8UX3J49NUiCPo0y/fhUJWdSzKjWicY13BJrsX5cv20gVjPLkkSo54BvUwhn8QgJ
  1060  D0n4TJ4TcpUtQCDhbXZNiNPNQOoahRi7AkViDSoGxZ/6HKaJR7NqDHaiikgQFcb5
  1061  6dMG8C30VjuTrbS4y5O/LqmYV9Gj6bkxuHGgxJaDN2og2RJCRJb16Ubl0HR2S129
  1062  dZA0UyQTtDkUwlhVBaQW0Rp0nwVTFZo8g5PXpvT+MsdGgs5Pop+lYq4gSCpuw3vV
  1063  T2UBOEQyYcok//9J3N+GRgfQbTidbgs8uGFGT4L8b8Ybji7Dhm/SkKpgVdH2wMuD
  1064  yxKSfIt/Gm7j1I2DnZtLrd+zUSJql2UiYMeQ7suHBHBKtFriv01zFeCzChQ17uiJ
  1065  NUo5IBzc+fagnN6aNaA5ImR07PHwMu4es4ORAEB+q/N3/0Eoe1U9h+ZBd6a7DM5V
  1066  5A/wizu2jM25tfTNwQfaSkYCZzpGmucVwMj7NO5Rbjw2/PCOo5IP8GNahXF/cxPQ
  1067  KqbIyT9nMPvKX8o9MR3Adzv5to5s9cPSyKSFfSZhJv+oXRdr8z9WaElIBJ0K0um/
  1068  ueteAX4s+U2F1Y7FAG1ivUnCOrzezvivuiPq7JS2PJh/jbdyu30xAu2gG/LZRWx+
  1069  m9zwCMUSxdrsBefl05WTzTjlpclwdjoG1sztRZ1d3/PoaMtjfLGePyEPbPFpPraq
  1070  //Dj/MNKy9sefqX4NcqRtMvGj/0VuFspXsl3B1fHRrM7BsB8IM0+BheS22+dXfOK
  1071  0EicO5ePw5Ji2vptC3ME+X3OztzJtfG/FN2CNvnLIKaR/N0v9Qt66U6aNoKPWD8l
  1072  2PW+b/BV0dz0YTo16sd6txcmcaUIo5ysZrCWLYGsoRLL1uNAlgRT8Qb2jO8liEhg
  1073  rfg5GH6RWpUL4mPo6cZ6GFAVPcdbPPSPMUSGscixeRo7oUqhnZrhvJ7L+I3TF7Pp
  1074  +aWbBpRkTvYiB3/BkgMUfZzozAv6WB2MQttHqjo7cQYgT9MklCJ80a92BkVtQsRa
  1075  hgDd6BUMA3ag4j0LivJflEmLd7prFVAcfyuN13UFku49soESUVnbPaWogyrGtc5w
  1076  CvbTazcsKiDEJqGuZhfT0ixCNPrgLj5dNJrWOmFdpZRqa/tgShkf91qCjtMq7AEi
  1077  eRQ/yOFb0sPjntORQoo0hdmXP3Y+5Ob4NqL2MNbCEf4uMOZ6OtsK4yCTfzVCMQve
  1078  pQeoosXle1nOjMrRotk6/QzbKh5rMApEjaNKekwcb47Qa8pnw2RFYZT9wsF0a7wd
  1079  wqINbgN2rnQ6Aea4If7O6Nh1Ftkrk2BW7g8N3OVwryQTFIKQ1W6Pgq6SeqoOVy0m
  1080  S6funfA+kqwEK+iXu2afvewDQkWY8+e5WkQc/5aGkbJ3K7pX8mnTgLdwGe8lZa+M
  1081  3VClT/XiHxU0ka8meio/4tq/SQUqlxiNWzgw7TJZfD1if0lAStEMAcBzZ96EQrre
  1082  HaXavAO1UPl2BczwaU2O2MDcXSE21Jw1cDCas2jc90X/iBEBM50xXTwAtNmJ84mg
  1083  UGNmDMvj8tUYI7+SvffHrTBwBPvcGeXa7XP4Au+GoJUN0jHspCeik/04KwanRCmu
  1084  -----END RSA PRIVATE KEY-----`,
  1085  	}))
  1086  	app.SetCharmOrigin(description.CharmOriginArgs{Platform: "amd64/ubuntu/20.04/stable"})
  1087  	app.SetStatus(minimalStatusArgs())
  1088  	u := app.AddUnit(minimalUnitArgs(app.Type()))
  1089  	u.SetAgentStatus(minimalStatusArgs())
  1090  
  1091  	_ = app.AddOffer(description.ApplicationOfferArgs{
  1092  		OfferName: "my-offer",
  1093  		Endpoints: map[string]string{
  1094  			"endpoint-1": "endpoint-1",
  1095  			"endpoint-2": "endpoint-2",
  1096  		},
  1097  		ACL: map[string]string{
  1098  			"admin": "admin",
  1099  			"foo":   "--- {}",
  1100  			"ssl_ca": `-----BEGIN RSA PRIVATE KEY-----
  1101  Proc-Type: 4,ENCRYPTED
  1102  DEK-Info: DES-EDE3-CBC,EA657EE6FE842AD0
  1103  
  1104  B8UX3J49NUiCPo0y/fhUJWdSzKjWicY13BJrsX5cv20gVjPLkkSo54BvUwhn8QgJ
  1105  D0n4TJ4TcpUtQCDhbXZNiNPNQOoahRi7AkViDSoGxZ/6HKaJR7NqDHaiikgQFcb5
  1106  6dMG8C30VjuTrbS4y5O/LqmYV9Gj6bkxuHGgxJaDN2og2RJCRJb16Ubl0HR2S129
  1107  dZA0UyQTtDkUwlhVBaQW0Rp0nwVTFZo8g5PXpvT+MsdGgs5Pop+lYq4gSCpuw3vV
  1108  T2UBOEQyYcok//9J3N+GRgfQbTidbgs8uGFGT4L8b8Ybji7Dhm/SkKpgVdH2wMuD
  1109  yxKSfIt/Gm7j1I2DnZtLrd+zUSJql2UiYMeQ7suHBHBKtFriv01zFeCzChQ17uiJ
  1110  NUo5IBzc+fagnN6aNaA5ImR07PHwMu4es4ORAEB+q/N3/0Eoe1U9h+ZBd6a7DM5V
  1111  5A/wizu2jM25tfTNwQfaSkYCZzpGmucVwMj7NO5Rbjw2/PCOo5IP8GNahXF/cxPQ
  1112  KqbIyT9nMPvKX8o9MR3Adzv5to5s9cPSyKSFfSZhJv+oXRdr8z9WaElIBJ0K0um/
  1113  ueteAX4s+U2F1Y7FAG1ivUnCOrzezvivuiPq7JS2PJh/jbdyu30xAu2gG/LZRWx+
  1114  m9zwCMUSxdrsBefl05WTzTjlpclwdjoG1sztRZ1d3/PoaMtjfLGePyEPbPFpPraq
  1115  //Dj/MNKy9sefqX4NcqRtMvGj/0VuFspXsl3B1fHRrM7BsB8IM0+BheS22+dXfOK
  1116  0EicO5ePw5Ji2vptC3ME+X3OztzJtfG/FN2CNvnLIKaR/N0v9Qt66U6aNoKPWD8l
  1117  2PW+b/BV0dz0YTo16sd6txcmcaUIo5ysZrCWLYGsoRLL1uNAlgRT8Qb2jO8liEhg
  1118  rfg5GH6RWpUL4mPo6cZ6GFAVPcdbPPSPMUSGscixeRo7oUqhnZrhvJ7L+I3TF7Pp
  1119  +aWbBpRkTvYiB3/BkgMUfZzozAv6WB2MQttHqjo7cQYgT9MklCJ80a92BkVtQsRa
  1120  hgDd6BUMA3ag4j0LivJflEmLd7prFVAcfyuN13UFku49soESUVnbPaWogyrGtc5w
  1121  CvbTazcsKiDEJqGuZhfT0ixCNPrgLj5dNJrWOmFdpZRqa/tgShkf91qCjtMq7AEi
  1122  eRQ/yOFb0sPjntORQoo0hdmXP3Y+5Ob4NqL2MNbCEf4uMOZ6OtsK4yCTfzVCMQve
  1123  pQeoosXle1nOjMrRotk6/QzbKh5rMApEjaNKekwcb47Qa8pnw2RFYZT9wsF0a7wd
  1124  wqINbgN2rnQ6Aea4If7O6Nh1Ftkrk2BW7g8N3OVwryQTFIKQ1W6Pgq6SeqoOVy0m
  1125  S6funfA+kqwEK+iXu2afvewDQkWY8+e5WkQc/5aGkbJ3K7pX8mnTgLdwGe8lZa+M
  1126  3VClT/XiHxU0ka8meio/4tq/SQUqlxiNWzgw7TJZfD1if0lAStEMAcBzZ96EQrre
  1127  HaXavAO1UPl2BczwaU2O2MDcXSE21Jw1cDCas2jc90X/iBEBM50xXTwAtNmJ84mg
  1128  UGNmDMvj8tUYI7+SvffHrTBwBPvcGeXa7XP4Au+GoJUN0jHspCeik/04KwanRCmu
  1129  -----END RSA PRIVATE KEY-----`,
  1130  		},
  1131  	})
  1132  
  1133  	_ = app.AddOffer(description.ApplicationOfferArgs{
  1134  		OfferName: "my-other-offer",
  1135  		Endpoints: map[string]string{
  1136  			"endpoint-1": "endpoint-1",
  1137  			"endpoint-2": "endpoint-2",
  1138  		},
  1139  	})
  1140  
  1141  	// Add second app without an offer
  1142  	app2Args := s.minimalApplicationArgs(description.IAAS)
  1143  	app2Args.Tag = names.NewApplicationTag("foo")
  1144  	app2 := s.st.model.AddApplication(app2Args)
  1145  	app2.SetCharmOrigin(description.CharmOriginArgs{Platform: "amd64/ubuntu/20.04/stable"})
  1146  	app2.SetStatus(minimalStatusArgs())
  1147  
  1148  	s.st.model.SetStatus(description.StatusArgs{Value: "available"})
  1149  
  1150  	result, err := s.facade.ExportBundle(params.ExportBundleParams{})
  1151  	c.Assert(err, jc.ErrorIsNil)
  1152  	expectedResult := params.StringResult{Result: `
  1153  default-base: ubuntu@20.04/stable
  1154  applications:
  1155    foo:
  1156      charm: ubuntu
  1157      channel: stable
  1158      options:
  1159        key: value
  1160      bindings:
  1161        another: alpha
  1162        juju-info: vlan2
  1163    ubuntu:
  1164      charm: ubuntu
  1165      channel: stable
  1166      num_units: 1
  1167      to:
  1168      - "0"
  1169      options:
  1170        key: value
  1171        ssl_ca: |-
  1172          -----BEGIN RSA PRIVATE KEY-----
  1173          Proc-Type: 4,ENCRYPTED
  1174          DEK-Info: DES-EDE3-CBC,EA657EE6FE842AD0
  1175  
  1176          B8UX3J49NUiCPo0y/fhUJWdSzKjWicY13BJrsX5cv20gVjPLkkSo54BvUwhn8QgJ
  1177          D0n4TJ4TcpUtQCDhbXZNiNPNQOoahRi7AkViDSoGxZ/6HKaJR7NqDHaiikgQFcb5
  1178          6dMG8C30VjuTrbS4y5O/LqmYV9Gj6bkxuHGgxJaDN2og2RJCRJb16Ubl0HR2S129
  1179          dZA0UyQTtDkUwlhVBaQW0Rp0nwVTFZo8g5PXpvT+MsdGgs5Pop+lYq4gSCpuw3vV
  1180          T2UBOEQyYcok//9J3N+GRgfQbTidbgs8uGFGT4L8b8Ybji7Dhm/SkKpgVdH2wMuD
  1181          yxKSfIt/Gm7j1I2DnZtLrd+zUSJql2UiYMeQ7suHBHBKtFriv01zFeCzChQ17uiJ
  1182          NUo5IBzc+fagnN6aNaA5ImR07PHwMu4es4ORAEB+q/N3/0Eoe1U9h+ZBd6a7DM5V
  1183          5A/wizu2jM25tfTNwQfaSkYCZzpGmucVwMj7NO5Rbjw2/PCOo5IP8GNahXF/cxPQ
  1184          KqbIyT9nMPvKX8o9MR3Adzv5to5s9cPSyKSFfSZhJv+oXRdr8z9WaElIBJ0K0um/
  1185          ueteAX4s+U2F1Y7FAG1ivUnCOrzezvivuiPq7JS2PJh/jbdyu30xAu2gG/LZRWx+
  1186          m9zwCMUSxdrsBefl05WTzTjlpclwdjoG1sztRZ1d3/PoaMtjfLGePyEPbPFpPraq
  1187          //Dj/MNKy9sefqX4NcqRtMvGj/0VuFspXsl3B1fHRrM7BsB8IM0+BheS22+dXfOK
  1188          0EicO5ePw5Ji2vptC3ME+X3OztzJtfG/FN2CNvnLIKaR/N0v9Qt66U6aNoKPWD8l
  1189          2PW+b/BV0dz0YTo16sd6txcmcaUIo5ysZrCWLYGsoRLL1uNAlgRT8Qb2jO8liEhg
  1190          rfg5GH6RWpUL4mPo6cZ6GFAVPcdbPPSPMUSGscixeRo7oUqhnZrhvJ7L+I3TF7Pp
  1191          +aWbBpRkTvYiB3/BkgMUfZzozAv6WB2MQttHqjo7cQYgT9MklCJ80a92BkVtQsRa
  1192          hgDd6BUMA3ag4j0LivJflEmLd7prFVAcfyuN13UFku49soESUVnbPaWogyrGtc5w
  1193          CvbTazcsKiDEJqGuZhfT0ixCNPrgLj5dNJrWOmFdpZRqa/tgShkf91qCjtMq7AEi
  1194          eRQ/yOFb0sPjntORQoo0hdmXP3Y+5Ob4NqL2MNbCEf4uMOZ6OtsK4yCTfzVCMQve
  1195          pQeoosXle1nOjMrRotk6/QzbKh5rMApEjaNKekwcb47Qa8pnw2RFYZT9wsF0a7wd
  1196          wqINbgN2rnQ6Aea4If7O6Nh1Ftkrk2BW7g8N3OVwryQTFIKQ1W6Pgq6SeqoOVy0m
  1197          S6funfA+kqwEK+iXu2afvewDQkWY8+e5WkQc/5aGkbJ3K7pX8mnTgLdwGe8lZa+M
  1198          3VClT/XiHxU0ka8meio/4tq/SQUqlxiNWzgw7TJZfD1if0lAStEMAcBzZ96EQrre
  1199          HaXavAO1UPl2BczwaU2O2MDcXSE21Jw1cDCas2jc90X/iBEBM50xXTwAtNmJ84mg
  1200          UGNmDMvj8tUYI7+SvffHrTBwBPvcGeXa7XP4Au+GoJUN0jHspCeik/04KwanRCmu
  1201          -----END RSA PRIVATE KEY-----
  1202      bindings:
  1203        another: alpha
  1204        juju-info: vlan2
  1205  --- # overlay.yaml
  1206  applications:
  1207    ubuntu:
  1208      offers:
  1209        my-offer:
  1210          endpoints:
  1211          - endpoint-1
  1212          - endpoint-2
  1213          acl:
  1214            admin: admin
  1215            foo: '--- {}'
  1216            ssl_ca: |-
  1217              -----BEGIN RSA PRIVATE KEY-----
  1218              Proc-Type: 4,ENCRYPTED
  1219              DEK-Info: DES-EDE3-CBC,EA657EE6FE842AD0
  1220  
  1221              B8UX3J49NUiCPo0y/fhUJWdSzKjWicY13BJrsX5cv20gVjPLkkSo54BvUwhn8QgJ
  1222              D0n4TJ4TcpUtQCDhbXZNiNPNQOoahRi7AkViDSoGxZ/6HKaJR7NqDHaiikgQFcb5
  1223              6dMG8C30VjuTrbS4y5O/LqmYV9Gj6bkxuHGgxJaDN2og2RJCRJb16Ubl0HR2S129
  1224              dZA0UyQTtDkUwlhVBaQW0Rp0nwVTFZo8g5PXpvT+MsdGgs5Pop+lYq4gSCpuw3vV
  1225              T2UBOEQyYcok//9J3N+GRgfQbTidbgs8uGFGT4L8b8Ybji7Dhm/SkKpgVdH2wMuD
  1226              yxKSfIt/Gm7j1I2DnZtLrd+zUSJql2UiYMeQ7suHBHBKtFriv01zFeCzChQ17uiJ
  1227              NUo5IBzc+fagnN6aNaA5ImR07PHwMu4es4ORAEB+q/N3/0Eoe1U9h+ZBd6a7DM5V
  1228              5A/wizu2jM25tfTNwQfaSkYCZzpGmucVwMj7NO5Rbjw2/PCOo5IP8GNahXF/cxPQ
  1229              KqbIyT9nMPvKX8o9MR3Adzv5to5s9cPSyKSFfSZhJv+oXRdr8z9WaElIBJ0K0um/
  1230              ueteAX4s+U2F1Y7FAG1ivUnCOrzezvivuiPq7JS2PJh/jbdyu30xAu2gG/LZRWx+
  1231              m9zwCMUSxdrsBefl05WTzTjlpclwdjoG1sztRZ1d3/PoaMtjfLGePyEPbPFpPraq
  1232              //Dj/MNKy9sefqX4NcqRtMvGj/0VuFspXsl3B1fHRrM7BsB8IM0+BheS22+dXfOK
  1233              0EicO5ePw5Ji2vptC3ME+X3OztzJtfG/FN2CNvnLIKaR/N0v9Qt66U6aNoKPWD8l
  1234              2PW+b/BV0dz0YTo16sd6txcmcaUIo5ysZrCWLYGsoRLL1uNAlgRT8Qb2jO8liEhg
  1235              rfg5GH6RWpUL4mPo6cZ6GFAVPcdbPPSPMUSGscixeRo7oUqhnZrhvJ7L+I3TF7Pp
  1236              +aWbBpRkTvYiB3/BkgMUfZzozAv6WB2MQttHqjo7cQYgT9MklCJ80a92BkVtQsRa
  1237              hgDd6BUMA3ag4j0LivJflEmLd7prFVAcfyuN13UFku49soESUVnbPaWogyrGtc5w
  1238              CvbTazcsKiDEJqGuZhfT0ixCNPrgLj5dNJrWOmFdpZRqa/tgShkf91qCjtMq7AEi
  1239              eRQ/yOFb0sPjntORQoo0hdmXP3Y+5Ob4NqL2MNbCEf4uMOZ6OtsK4yCTfzVCMQve
  1240              pQeoosXle1nOjMrRotk6/QzbKh5rMApEjaNKekwcb47Qa8pnw2RFYZT9wsF0a7wd
  1241              wqINbgN2rnQ6Aea4If7O6Nh1Ftkrk2BW7g8N3OVwryQTFIKQ1W6Pgq6SeqoOVy0m
  1242              S6funfA+kqwEK+iXu2afvewDQkWY8+e5WkQc/5aGkbJ3K7pX8mnTgLdwGe8lZa+M
  1243              3VClT/XiHxU0ka8meio/4tq/SQUqlxiNWzgw7TJZfD1if0lAStEMAcBzZ96EQrre
  1244              HaXavAO1UPl2BczwaU2O2MDcXSE21Jw1cDCas2jc90X/iBEBM50xXTwAtNmJ84mg
  1245              UGNmDMvj8tUYI7+SvffHrTBwBPvcGeXa7XP4Au+GoJUN0jHspCeik/04KwanRCmu
  1246              -----END RSA PRIVATE KEY-----
  1247        my-other-offer:
  1248          endpoints:
  1249          - endpoint-1
  1250          - endpoint-2
  1251  `[1:]}
  1252  
  1253  	c.Assert(result, gc.Equals, expectedResult)
  1254  	s.st.CheckCall(c, 0, "ExportPartial", s.st.GetExportConfig())
  1255  }
  1256  
  1257  func (s *bundleSuite) TestExportBundleWithSaas(c *gc.C) {
  1258  	s.st.model = description.NewModel(description.ModelArgs{Owner: names.NewUserTag("magic"),
  1259  		Config:      coretesting.FakeConfig(),
  1260  		CloudRegion: "some-region"})
  1261  
  1262  	app := s.st.model.AddApplication(s.minimalApplicationArgs(description.IAAS))
  1263  	app.SetCharmOrigin(description.CharmOriginArgs{Platform: "amd64/ubuntu/20.04/stable"})
  1264  	app.SetStatus(minimalStatusArgs())
  1265  
  1266  	remoteApp := s.st.model.AddRemoteApplication(description.RemoteApplicationArgs{
  1267  		Tag: names.NewApplicationTag("awesome"),
  1268  		URL: "test:admin/default.awesome",
  1269  	})
  1270  	remoteApp.SetStatus(minimalStatusArgs())
  1271  
  1272  	u := app.AddUnit(minimalUnitArgs(app.Type()))
  1273  	u.SetAgentStatus(minimalStatusArgs())
  1274  
  1275  	s.st.model.SetStatus(description.StatusArgs{Value: "available"})
  1276  
  1277  	result, err := s.facade.ExportBundle(params.ExportBundleParams{})
  1278  	c.Assert(err, jc.ErrorIsNil)
  1279  	expectedResult := params.StringResult{Result: `
  1280  default-base: ubuntu@20.04/stable
  1281  saas:
  1282    awesome:
  1283      url: test:admin/default.awesome
  1284  applications:
  1285    ubuntu:
  1286      charm: ubuntu
  1287      channel: stable
  1288      num_units: 1
  1289      to:
  1290      - "0"
  1291      options:
  1292        key: value
  1293      bindings:
  1294        another: alpha
  1295        juju-info: vlan2
  1296  `[1:]}
  1297  
  1298  	c.Assert(result, gc.Equals, expectedResult)
  1299  	s.st.CheckCall(c, 0, "ExportPartial", s.st.GetExportConfig())
  1300  }
  1301  
  1302  func (s *bundleSuite) addApplicationToModel(model description.Model, name string, numUnits int) string {
  1303  	var charmURL string
  1304  	var channel string
  1305  
  1306  	if strings.HasPrefix(name, "ch:") {
  1307  		charmURL = name
  1308  		name = name[3:]
  1309  		channel = "stable"
  1310  	} else if strings.HasPrefix(name, "local:") {
  1311  		charmURL = name
  1312  		curl := charm.MustParseURL(name)
  1313  		name = curl.Name
  1314  	} else {
  1315  		charmURL = "ch:" + name
  1316  	}
  1317  	application := model.AddApplication(description.ApplicationArgs{
  1318  		Tag:                names.NewApplicationTag(name),
  1319  		CharmURL:           charmURL,
  1320  		Channel:            channel,
  1321  		CharmConfig:        map[string]interface{}{},
  1322  		LeadershipSettings: map[string]interface{}{},
  1323  	})
  1324  	application.SetCharmOrigin(description.CharmOriginArgs{Platform: "amd64/ubuntu/20.04/stable"})
  1325  	application.SetStatus(minimalStatusArgs())
  1326  	for i := 0; i < numUnits; i++ {
  1327  		machine := model.AddMachine(description.MachineArgs{
  1328  			Id:   names.NewMachineTag(fmt.Sprint(i)),
  1329  			Base: "ubuntu@20.04",
  1330  		})
  1331  		unit := application.AddUnit(description.UnitArgs{
  1332  			Tag:     names.NewUnitTag(fmt.Sprintf("%s/%d", name, i)),
  1333  			Machine: machine.Tag(),
  1334  		})
  1335  		unit.SetAgentStatus(minimalStatusArgs())
  1336  	}
  1337  
  1338  	return name
  1339  }
  1340  
  1341  func (s *bundleSuite) setEndpointSettings(ep description.Endpoint, units ...string) {
  1342  	for _, unit := range units {
  1343  		ep.SetUnitSettings(unit, map[string]interface{}{
  1344  			"key": "value",
  1345  		})
  1346  	}
  1347  }
  1348  
  1349  func (s *bundleSuite) newModel(modelType string, app1 string, app2 string) description.Model {
  1350  	s.st.model = description.NewModel(description.ModelArgs{
  1351  		Type:        modelType,
  1352  		Owner:       names.NewUserTag("magic"),
  1353  		Config:      coretesting.FakeConfig(),
  1354  		CloudRegion: "some-region"})
  1355  
  1356  	appName1 := s.addApplicationToModel(s.st.model, app1, 2)
  1357  	appName2 := s.addApplicationToModel(s.st.model, app2, 1)
  1358  
  1359  	// Add a relation between wordpress and mysql.
  1360  	rel := s.st.model.AddRelation(description.RelationArgs{
  1361  		Id:  42,
  1362  		Key: "special key",
  1363  	})
  1364  	rel.SetStatus(minimalStatusArgs())
  1365  
  1366  	app1Endpoint := rel.AddEndpoint(description.EndpointArgs{
  1367  		ApplicationName: appName1,
  1368  		Name:            "db",
  1369  		// Ignoring other aspects of endpoints.
  1370  	})
  1371  	s.setEndpointSettings(app1Endpoint, appName1+"/0", appName1+"/1")
  1372  
  1373  	app2Endpoint := rel.AddEndpoint(description.EndpointArgs{
  1374  		ApplicationName: "mysql",
  1375  		Name:            "mysql",
  1376  		// Ignoring other aspects of endpoints.
  1377  	})
  1378  	s.setEndpointSettings(app2Endpoint, appName2+"/0")
  1379  
  1380  	return s.st.model
  1381  }
  1382  
  1383  func (s *bundleSuite) TestExportBundleModelWithSettingsRelations(c *gc.C) {
  1384  	model := s.newModel("iaas", "wordpress", "mysql")
  1385  	model.SetStatus(description.StatusArgs{Value: "available"})
  1386  
  1387  	result, err := s.facade.ExportBundle(params.ExportBundleParams{})
  1388  	c.Assert(err, jc.ErrorIsNil)
  1389  
  1390  	output := `
  1391  default-base: ubuntu@20.04/stable
  1392  applications:
  1393    mysql:
  1394      charm: mysql
  1395      num_units: 1
  1396      to:
  1397      - "0"
  1398    wordpress:
  1399      charm: wordpress
  1400      num_units: 2
  1401      to:
  1402      - "0"
  1403      - "1"
  1404  machines:
  1405    "0": {}
  1406    "1": {}
  1407  relations:
  1408  - - wordpress:db
  1409    - mysql:mysql
  1410  `[1:]
  1411  	expectedResult := params.StringResult{Result: output}
  1412  
  1413  	c.Assert(result, gc.Equals, expectedResult)
  1414  	s.st.CheckCall(c, 0, "ExportPartial", s.st.GetExportConfig())
  1415  }
  1416  
  1417  func (s *bundleSuite) TestExportBundleModelWithCharmDefaults(c *gc.C) {
  1418  	model := s.newModel("iaas", "wordpress", "mysql")
  1419  	model.SetStatus(description.StatusArgs{Value: "available"})
  1420  	app := model.AddApplication(description.ApplicationArgs{
  1421  		Tag:      names.NewApplicationTag("mariadb"),
  1422  		CharmURL: "ch:mariadb",
  1423  		CharmConfig: map[string]interface{}{
  1424  			"mem": 200,
  1425  			"foo": "baz",
  1426  		},
  1427  	})
  1428  	app.SetCharmOrigin(description.CharmOriginArgs{Platform: "amd64/ubuntu/20.04/stable"})
  1429  
  1430  	result, err := s.facade.ExportBundle(params.ExportBundleParams{IncludeCharmDefaults: true})
  1431  	c.Assert(err, jc.ErrorIsNil)
  1432  
  1433  	output := `
  1434  default-base: ubuntu@20.04/stable
  1435  applications:
  1436    mariadb:
  1437      charm: mariadb
  1438      options:
  1439        foo: baz
  1440        mem: 200
  1441    mysql:
  1442      charm: mysql
  1443      num_units: 1
  1444      to:
  1445      - "0"
  1446      options:
  1447        foo: bar
  1448    wordpress:
  1449      charm: wordpress
  1450      num_units: 2
  1451      to:
  1452      - "0"
  1453      - "1"
  1454      options:
  1455        foo: bar
  1456  machines:
  1457    "0": {}
  1458    "1": {}
  1459  relations:
  1460  - - wordpress:db
  1461    - mysql:mysql
  1462  `[1:]
  1463  	expectedResult := params.StringResult{Result: output}
  1464  
  1465  	c.Assert(result, gc.Equals, expectedResult)
  1466  	s.st.CheckCall(c, 0, "ExportPartial", s.st.GetExportConfig())
  1467  }
  1468  
  1469  func (s *bundleSuite) TestExportBundleModelWithIncludeSeries(c *gc.C) {
  1470  	s.st.model = description.NewModel(description.ModelArgs{Owner: names.NewUserTag("magic"),
  1471  		Config: coretesting.FakeConfig().Merge(map[string]interface{}{
  1472  			"default-base": "ubuntu@20.04",
  1473  		}),
  1474  		CloudRegion: "some-region"})
  1475  
  1476  	application := s.st.model.AddApplication(description.ApplicationArgs{
  1477  		Tag:      names.NewApplicationTag("magic"),
  1478  		CharmURL: "ch:magic",
  1479  	})
  1480  	application.SetCharmOrigin(description.CharmOriginArgs{Platform: "amd64/ubuntu/20.04/stable"})
  1481  	application.AddUnit(description.UnitArgs{
  1482  		Tag:     names.NewUnitTag("magic/0"),
  1483  		Machine: names.NewMachineTag("0"),
  1484  	})
  1485  	s.st.model.AddMachine(description.MachineArgs{
  1486  		Id:   names.NewMachineTag("0"),
  1487  		Base: "ubuntu@20.04",
  1488  	})
  1489  
  1490  	application = s.st.model.AddApplication(description.ApplicationArgs{
  1491  		Tag:      names.NewApplicationTag("mojo"),
  1492  		CharmURL: "ch:mojo",
  1493  	})
  1494  	application.SetCharmOrigin(description.CharmOriginArgs{Platform: "amd64/ubuntu/22.04/stable"})
  1495  	application.AddUnit(description.UnitArgs{
  1496  		Tag:     names.NewUnitTag("mojo/0"),
  1497  		Machine: names.NewMachineTag("1"),
  1498  	})
  1499  	s.st.model.AddMachine(description.MachineArgs{
  1500  		Id:   names.NewMachineTag("1"),
  1501  		Base: "ubuntu@22.04",
  1502  	})
  1503  
  1504  	result, err := s.facade.ExportBundle(params.ExportBundleParams{IncludeSeries: true})
  1505  	c.Assert(err, jc.ErrorIsNil)
  1506  
  1507  	expectedResult := params.StringResult{Result: `
  1508  default-base: ubuntu@20.04/stable
  1509  series: focal
  1510  applications:
  1511    magic:
  1512      charm: magic
  1513      num_units: 1
  1514      to:
  1515      - "0"
  1516    mojo:
  1517      charm: mojo
  1518      series: jammy
  1519      base: ubuntu@22.04/stable
  1520      num_units: 1
  1521      to:
  1522      - "1"
  1523  machines:
  1524    "0": {}
  1525    "1":
  1526      series: jammy
  1527      base: ubuntu@22.04/stable
  1528  `[1:]}
  1529  
  1530  	c.Assert(result, gc.Equals, expectedResult)
  1531  	s.st.CheckCall(c, 0, "ExportPartial", s.st.GetExportConfig())
  1532  }
  1533  
  1534  func (s *bundleSuite) addSubordinateEndpoints(rel description.Relation, app string) (description.Endpoint, description.Endpoint) {
  1535  	appEndpoint := rel.AddEndpoint(description.EndpointArgs{
  1536  		ApplicationName: app,
  1537  		Name:            "logging",
  1538  		Scope:           "container",
  1539  		// Ignoring other aspects of endpoints.
  1540  	})
  1541  	loggingEndpoint := rel.AddEndpoint(description.EndpointArgs{
  1542  		ApplicationName: "logging",
  1543  		Name:            "logging",
  1544  		Scope:           "container",
  1545  		// Ignoring other aspects of endpoints.
  1546  	})
  1547  	return appEndpoint, loggingEndpoint
  1548  }
  1549  
  1550  func (s *bundleSuite) TestExportBundleModelRelationsWithSubordinates(c *gc.C) {
  1551  	model := s.newModel("iaas", "wordpress", "mysql")
  1552  	model.SetStatus(description.StatusArgs{Value: "available"})
  1553  
  1554  	// Add a subordinate relations between logging and both wordpress and mysql.
  1555  	rel := model.AddRelation(description.RelationArgs{
  1556  		Id:  43,
  1557  		Key: "some key",
  1558  	})
  1559  	wordpressEndpoint, loggingEndpoint := s.addSubordinateEndpoints(rel, "wordpress")
  1560  	s.setEndpointSettings(wordpressEndpoint, "wordpress/0", "wordpress/1")
  1561  	s.setEndpointSettings(loggingEndpoint, "logging/0", "logging/1")
  1562  
  1563  	rel = model.AddRelation(description.RelationArgs{
  1564  		Id:  44,
  1565  		Key: "other key",
  1566  	})
  1567  	mysqlEndpoint, loggingEndpoint := s.addSubordinateEndpoints(rel, "mysql")
  1568  	s.setEndpointSettings(mysqlEndpoint, "mysql/0")
  1569  	s.setEndpointSettings(loggingEndpoint, "logging/2")
  1570  
  1571  	result, err := s.facade.ExportBundle(params.ExportBundleParams{})
  1572  	c.Assert(err, jc.ErrorIsNil)
  1573  
  1574  	expectedResult := params.StringResult{Result: `
  1575  default-base: ubuntu@20.04/stable
  1576  applications:
  1577    mysql:
  1578      charm: mysql
  1579      num_units: 1
  1580      to:
  1581      - "0"
  1582    wordpress:
  1583      charm: wordpress
  1584      num_units: 2
  1585      to:
  1586      - "0"
  1587      - "1"
  1588  machines:
  1589    "0": {}
  1590    "1": {}
  1591  relations:
  1592  - - wordpress:db
  1593    - mysql:mysql
  1594  - - wordpress:logging
  1595    - logging:logging
  1596  - - mysql:logging
  1597    - logging:logging
  1598  `[1:]}
  1599  
  1600  	c.Assert(result, gc.Equals, expectedResult)
  1601  	s.st.CheckCall(c, 0, "ExportPartial", s.st.GetExportConfig())
  1602  }
  1603  
  1604  func (s *bundleSuite) TestExportBundleSubordinateApplication(c *gc.C) {
  1605  	s.st.model = description.NewModel(description.ModelArgs{Owner: names.NewUserTag("magic"),
  1606  		Config:      coretesting.FakeConfig(),
  1607  		CloudRegion: "some-region"})
  1608  
  1609  	s.st.Spaces[network.AlphaSpaceId] = network.AlphaSpaceName
  1610  	s.st.Spaces["2"] = "some-space"
  1611  	application := s.st.model.AddApplication(description.ApplicationArgs{
  1612  		Tag:                  names.NewApplicationTag("magic"),
  1613  		Subordinate:          true,
  1614  		CharmURL:             "ch:magic",
  1615  		Channel:              "stable",
  1616  		CharmModifiedVersion: 1,
  1617  		ForceCharm:           true,
  1618  		Exposed:              true,
  1619  		EndpointBindings: map[string]string{
  1620  			"rel-name": "2",
  1621  			"magic":    "0",
  1622  		},
  1623  		ApplicationConfig: map[string]interface{}{
  1624  			"config key": "config value",
  1625  		},
  1626  		CharmConfig: map[string]interface{}{
  1627  			"key": "value",
  1628  		},
  1629  		Leader: "magic/1",
  1630  		LeadershipSettings: map[string]interface{}{
  1631  			"leader": true,
  1632  		},
  1633  		MetricsCredentials: []byte("sekrit"),
  1634  		PasswordHash:       "passwordhash",
  1635  		PodSpec:            "podspec",
  1636  	})
  1637  	application.SetCharmOrigin(description.CharmOriginArgs{Platform: "amd64/ubuntu/18.04/stable"})
  1638  	application.SetStatus(minimalStatusArgs())
  1639  
  1640  	result, err := s.facade.ExportBundle(params.ExportBundleParams{})
  1641  	c.Assert(err, jc.ErrorIsNil)
  1642  
  1643  	expectedResult := params.StringResult{Result: `
  1644  default-base: ubuntu@18.04/stable
  1645  applications:
  1646    magic:
  1647      charm: magic
  1648      channel: stable
  1649      expose: true
  1650      options:
  1651        key: value
  1652      bindings:
  1653        magic: alpha
  1654        rel-name: some-space
  1655  `[1:]}
  1656  
  1657  	c.Assert(result, gc.Equals, expectedResult)
  1658  	s.st.CheckCall(c, 0, "ExportPartial", s.st.GetExportConfig())
  1659  }
  1660  
  1661  func (s *bundleSuite) setupExportBundleEndpointBindingsPrinted(all, oneOff string) {
  1662  	s.st.model = description.NewModel(description.ModelArgs{Owner: names.NewUserTag("magic"),
  1663  		Config:      coretesting.FakeConfig(),
  1664  		CloudRegion: "some-region"})
  1665  
  1666  	args := s.minimalApplicationArgs(description.IAAS)
  1667  	args.EndpointBindings = map[string]string{
  1668  		"rel-name": all,
  1669  		"another":  all,
  1670  	}
  1671  	app := s.st.model.AddApplication(args)
  1672  	app.SetCharmOrigin(description.CharmOriginArgs{Platform: "amd64/ubuntu/20.04/stable"})
  1673  
  1674  	app = s.st.model.AddApplication(description.ApplicationArgs{
  1675  		Tag:                  names.NewApplicationTag("magic"),
  1676  		Subordinate:          true,
  1677  		CharmURL:             "ch:magic",
  1678  		Channel:              "stable",
  1679  		CharmModifiedVersion: 1,
  1680  		ForceCharm:           true,
  1681  		Exposed:              true,
  1682  		EndpointBindings: map[string]string{
  1683  			"rel-name": all,
  1684  			"another":  oneOff,
  1685  		},
  1686  		ApplicationConfig: map[string]interface{}{
  1687  			"config key": "config value",
  1688  		},
  1689  	})
  1690  	app.SetCharmOrigin(description.CharmOriginArgs{Platform: "amd64/ubuntu/18.04/stable"})
  1691  }
  1692  
  1693  func (s *bundleSuite) TestExportBundleNoEndpointBindingsPrinted(c *gc.C) {
  1694  	s.setupExportBundleEndpointBindingsPrinted("0", "0")
  1695  	result, err := s.facade.ExportBundle(params.ExportBundleParams{})
  1696  	c.Assert(err, jc.ErrorIsNil)
  1697  
  1698  	expectedResult := params.StringResult{Result: `
  1699  applications:
  1700    magic:
  1701      charm: magic
  1702      channel: stable
  1703      base: ubuntu@18.04/stable
  1704      expose: true
  1705    ubuntu:
  1706      charm: ubuntu
  1707      channel: stable
  1708      base: ubuntu@20.04/stable
  1709      options:
  1710        key: value
  1711  `[1:]}
  1712  	c.Assert(result, gc.Equals, expectedResult)
  1713  }
  1714  
  1715  func (s *bundleSuite) TestExportBundleEndpointBindingsPrinted(c *gc.C) {
  1716  	s.setupExportBundleEndpointBindingsPrinted("0", "1")
  1717  	result, err := s.facade.ExportBundle(params.ExportBundleParams{})
  1718  	c.Assert(err, jc.ErrorIsNil)
  1719  
  1720  	expectedResult := params.StringResult{Result: `
  1721  applications:
  1722    magic:
  1723      charm: magic
  1724      channel: stable
  1725      base: ubuntu@18.04/stable
  1726      expose: true
  1727      bindings:
  1728        another: vlan2
  1729        rel-name: alpha
  1730    ubuntu:
  1731      charm: ubuntu
  1732      channel: stable
  1733      base: ubuntu@20.04/stable
  1734      options:
  1735        key: value
  1736      bindings:
  1737        another: alpha
  1738        rel-name: alpha
  1739  `[1:]}
  1740  	c.Assert(result, gc.Equals, expectedResult)
  1741  }
  1742  
  1743  func (s *bundleSuite) TestExportBundleSubordinateApplicationAndMachine(c *gc.C) {
  1744  	s.st.model = description.NewModel(description.ModelArgs{Owner: names.NewUserTag("magic"),
  1745  		Config:      coretesting.FakeConfig(),
  1746  		CloudRegion: "some-region"})
  1747  
  1748  	application := s.st.model.AddApplication(description.ApplicationArgs{
  1749  		Tag:         names.NewApplicationTag("magic"),
  1750  		Subordinate: true,
  1751  		CharmURL:    "ch:amd64/zesty/magic",
  1752  		Channel:     "stable",
  1753  		Exposed:     true,
  1754  		CharmConfig: map[string]interface{}{
  1755  			"key": "value",
  1756  		},
  1757  	})
  1758  	application.SetCharmOrigin(description.CharmOriginArgs{Platform: "amd64/ubuntu/17.04/stable"})
  1759  	application.SetStatus(minimalStatusArgs())
  1760  
  1761  	s.addMinimalMachineWithConstraints(s.st.model, "0")
  1762  
  1763  	result, err := s.facade.ExportBundle(params.ExportBundleParams{})
  1764  	c.Assert(err, jc.ErrorIsNil)
  1765  
  1766  	expectedResult := params.StringResult{Result: `
  1767  default-base: ubuntu@17.04/stable
  1768  applications:
  1769    magic:
  1770      charm: magic
  1771      channel: stable
  1772      expose: true
  1773      options:
  1774        key: value
  1775  `[1:]}
  1776  
  1777  	c.Assert(result, gc.Equals, expectedResult)
  1778  	s.st.CheckCall(c, 0, "ExportPartial", s.st.GetExportConfig())
  1779  }
  1780  
  1781  func (s *bundleSuite) addMinimalMachineWithConstraints(model description.Model, id string) {
  1782  	m := model.AddMachine(description.MachineArgs{
  1783  		Id:           names.NewMachineTag(id),
  1784  		Nonce:        "a-nonce",
  1785  		PasswordHash: "some-hash",
  1786  		Base:         "ubuntu@20.04",
  1787  		Jobs:         []string{"host-units"},
  1788  	})
  1789  	args := description.ConstraintsArgs{
  1790  		Architecture:   "amd64",
  1791  		Memory:         8 * 1024,
  1792  		RootDisk:       40 * 1024,
  1793  		CpuCores:       2,
  1794  		CpuPower:       100,
  1795  		InstanceType:   "big-inst",
  1796  		Tags:           []string{"foo", "bar"},
  1797  		VirtType:       "pv",
  1798  		Container:      "kvm",
  1799  		Spaces:         []string{"internal"},
  1800  		Zones:          []string{"zone-a"},
  1801  		RootDiskSource: "source-of-all-evil",
  1802  	}
  1803  	m.SetConstraints(args)
  1804  	m.SetStatus(minimalStatusArgs())
  1805  }
  1806  
  1807  func (s *bundleSuite) TestExportBundleModelWithConstraints(c *gc.C) {
  1808  	model := s.newModel("iaas", "mediawiki", "mysql")
  1809  
  1810  	s.addMinimalMachineWithConstraints(model, "0")
  1811  	s.addMinimalMachineWithConstraints(model, "1")
  1812  
  1813  	model.SetStatus(description.StatusArgs{Value: "available"})
  1814  
  1815  	result, err := s.facade.ExportBundle(params.ExportBundleParams{})
  1816  	c.Assert(err, jc.ErrorIsNil)
  1817  	expectedResult := params.StringResult{Result: `
  1818  default-base: ubuntu@20.04/stable
  1819  applications:
  1820    mediawiki:
  1821      charm: mediawiki
  1822      num_units: 2
  1823      to:
  1824      - "0"
  1825      - "1"
  1826    mysql:
  1827      charm: mysql
  1828      num_units: 1
  1829      to:
  1830      - "0"
  1831  machines:
  1832    "0":
  1833      constraints: arch=amd64 cpu-cores=2 cpu-power=100 mem=8192 root-disk=40960 instance-type=big-inst
  1834        container=kvm virt-type=pv tags=foo,bar spaces=internal zones=zone-a root-disk-source=source-of-all-evil
  1835    "1":
  1836      constraints: arch=amd64 cpu-cores=2 cpu-power=100 mem=8192 root-disk=40960 instance-type=big-inst
  1837        container=kvm virt-type=pv tags=foo,bar spaces=internal zones=zone-a root-disk-source=source-of-all-evil
  1838  relations:
  1839  - - mediawiki:db
  1840    - mysql:mysql
  1841  `[1:]}
  1842  
  1843  	c.Assert(result, gc.Equals, expectedResult)
  1844  
  1845  	s.st.CheckCall(c, 0, "ExportPartial", s.st.GetExportConfig())
  1846  }
  1847  
  1848  func (s *bundleSuite) addMinimalMachineWithAnnotations(model description.Model, id string) {
  1849  	m := model.AddMachine(description.MachineArgs{
  1850  		Id:           names.NewMachineTag(id),
  1851  		Nonce:        "a-nonce",
  1852  		PasswordHash: "some-hash",
  1853  		Base:         "ubuntu@20.04",
  1854  		Jobs:         []string{"host-units"},
  1855  	})
  1856  	m.SetAnnotations(map[string]string{
  1857  		"string":  "value",
  1858  		"another": "one",
  1859  	})
  1860  	m.SetStatus(minimalStatusArgs())
  1861  }
  1862  
  1863  func (s *bundleSuite) TestExportBundleModelWithAnnotations(c *gc.C) {
  1864  	model := s.newModel("iaas", "wordpress", "mysql")
  1865  
  1866  	s.addMinimalMachineWithAnnotations(model, "0")
  1867  	s.addMinimalMachineWithAnnotations(model, "1")
  1868  
  1869  	model.SetStatus(description.StatusArgs{Value: "available"})
  1870  
  1871  	result, err := s.facade.ExportBundle(params.ExportBundleParams{})
  1872  	c.Assert(err, jc.ErrorIsNil)
  1873  	expectedResult := params.StringResult{Result: `
  1874  default-base: ubuntu@20.04/stable
  1875  applications:
  1876    mysql:
  1877      charm: mysql
  1878      num_units: 1
  1879      to:
  1880      - "0"
  1881    wordpress:
  1882      charm: wordpress
  1883      num_units: 2
  1884      to:
  1885      - "0"
  1886      - "1"
  1887  machines:
  1888    "0":
  1889      annotations:
  1890        another: one
  1891        string: value
  1892    "1":
  1893      annotations:
  1894        another: one
  1895        string: value
  1896  relations:
  1897  - - wordpress:db
  1898    - mysql:mysql
  1899  `[1:]}
  1900  
  1901  	c.Assert(result, gc.Equals, expectedResult)
  1902  	s.st.CheckCall(c, 0, "ExportPartial", s.st.GetExportConfig())
  1903  }
  1904  
  1905  func (s *bundleSuite) TestExportBundleWithContainers(c *gc.C) {
  1906  	s.st.model = description.NewModel(description.ModelArgs{Owner: names.NewUserTag("magic"),
  1907  		Config:      coretesting.FakeConfig(),
  1908  		CloudRegion: "some-region"})
  1909  
  1910  	application0 := s.st.model.AddApplication(description.ApplicationArgs{
  1911  		Tag:      names.NewApplicationTag("wordpress"),
  1912  		CharmURL: "ch:wordpress",
  1913  	})
  1914  	application0.SetCharmOrigin(description.CharmOriginArgs{Platform: "amd64/ubuntu/20.04/stable"})
  1915  	application0.SetStatus(minimalStatusArgs())
  1916  
  1917  	m0 := s.st.model.AddMachine(description.MachineArgs{
  1918  		Id:   names.NewMachineTag("0"),
  1919  		Base: "ubuntu@20.04",
  1920  	})
  1921  	args := description.ConstraintsArgs{
  1922  		Architecture: "amd64",
  1923  		Memory:       8 * 1024,
  1924  		RootDisk:     40 * 1024,
  1925  	}
  1926  	m0.SetConstraints(args)
  1927  	ut0 := application0.AddUnit(description.UnitArgs{
  1928  		Tag:     names.NewUnitTag("wordpress/0"),
  1929  		Machine: names.NewMachineTag("0"),
  1930  	})
  1931  	ut0.SetAgentStatus(minimalStatusArgs())
  1932  
  1933  	application1 := s.st.model.AddApplication(description.ApplicationArgs{
  1934  		Tag:      names.NewApplicationTag("mysql"),
  1935  		CharmURL: "ch:mysql",
  1936  	})
  1937  	application1.SetCharmOrigin(description.CharmOriginArgs{Platform: "amd64/ubuntu/20.04/stable"})
  1938  	application1.SetStatus(minimalStatusArgs())
  1939  
  1940  	m1 := s.st.model.AddMachine(description.MachineArgs{
  1941  		Id:   names.NewMachineTag("1"),
  1942  		Base: "ubuntu@20.04",
  1943  	})
  1944  	args = description.ConstraintsArgs{
  1945  		Architecture: "amd64",
  1946  		Memory:       8 * 1024,
  1947  		RootDisk:     40 * 1024,
  1948  	}
  1949  	m1.SetConstraints(args)
  1950  
  1951  	ut := application1.AddUnit(description.UnitArgs{
  1952  		Tag:     names.NewUnitTag("mysql/1"),
  1953  		Machine: names.NewMachineTag("1/lxd/0"),
  1954  	})
  1955  	ut.SetAgentStatus(minimalStatusArgs())
  1956  
  1957  	result, err := s.facade.ExportBundle(params.ExportBundleParams{})
  1958  	c.Assert(err, jc.ErrorIsNil)
  1959  	expectedResult := params.StringResult{Result: `
  1960  default-base: ubuntu@20.04/stable
  1961  applications:
  1962    mysql:
  1963      charm: mysql
  1964      num_units: 1
  1965      to:
  1966      - lxd:1
  1967    wordpress:
  1968      charm: wordpress
  1969      num_units: 1
  1970      to:
  1971      - "0"
  1972  machines:
  1973    "0":
  1974      constraints: arch=amd64 mem=8192 root-disk=40960
  1975    "1":
  1976      constraints: arch=amd64 mem=8192 root-disk=40960
  1977  `[1:]}
  1978  
  1979  	c.Assert(result, gc.Equals, expectedResult)
  1980  	s.st.CheckCall(c, 0, "ExportPartial", s.st.GetExportConfig())
  1981  }
  1982  
  1983  func (s *bundleSuite) TestMixedSeries(c *gc.C) {
  1984  	s.st.model = description.NewModel(description.ModelArgs{Owner: names.NewUserTag("magic"),
  1985  		Config: coretesting.FakeConfig().Merge(map[string]interface{}{
  1986  			"default-base": "ubuntu@20.04",
  1987  		}),
  1988  		CloudRegion: "some-region"})
  1989  
  1990  	application := s.st.model.AddApplication(description.ApplicationArgs{
  1991  		Tag:      names.NewApplicationTag("magic"),
  1992  		CharmURL: "ch:magic",
  1993  	})
  1994  	application.SetCharmOrigin(description.CharmOriginArgs{Platform: "amd64/ubuntu/20.04/stable"})
  1995  	application.AddUnit(description.UnitArgs{
  1996  		Tag:     names.NewUnitTag("magic/0"),
  1997  		Machine: names.NewMachineTag("0"),
  1998  	})
  1999  	s.st.model.AddMachine(description.MachineArgs{
  2000  		Id:   names.NewMachineTag("0"),
  2001  		Base: "ubuntu@20.04",
  2002  	})
  2003  
  2004  	application = s.st.model.AddApplication(description.ApplicationArgs{
  2005  		Tag:      names.NewApplicationTag("mojo"),
  2006  		CharmURL: "ch:mojo",
  2007  	})
  2008  	application.SetCharmOrigin(description.CharmOriginArgs{Platform: "amd64/ubuntu/22.04/stable"})
  2009  	application.AddUnit(description.UnitArgs{
  2010  		Tag:     names.NewUnitTag("mojo/0"),
  2011  		Machine: names.NewMachineTag("1"),
  2012  	})
  2013  	s.st.model.AddMachine(description.MachineArgs{
  2014  		Id:   names.NewMachineTag("1"),
  2015  		Base: "ubuntu@22.04",
  2016  	})
  2017  
  2018  	result, err := s.facade.ExportBundle(params.ExportBundleParams{})
  2019  	c.Assert(err, jc.ErrorIsNil)
  2020  
  2021  	expectedResult := params.StringResult{Result: `
  2022  default-base: ubuntu@20.04/stable
  2023  applications:
  2024    magic:
  2025      charm: magic
  2026      num_units: 1
  2027      to:
  2028      - "0"
  2029    mojo:
  2030      charm: mojo
  2031      base: ubuntu@22.04/stable
  2032      num_units: 1
  2033      to:
  2034      - "1"
  2035  machines:
  2036    "0": {}
  2037    "1":
  2038      base: ubuntu@22.04/stable
  2039  `[1:]}
  2040  
  2041  	c.Assert(result, gc.Equals, expectedResult)
  2042  	s.st.CheckCall(c, 0, "ExportPartial", s.st.GetExportConfig())
  2043  }
  2044  
  2045  func (s *bundleSuite) TestMixedSeriesNoDefaultSeries(c *gc.C) {
  2046  	s.st.model = description.NewModel(description.ModelArgs{Owner: names.NewUserTag("magic"),
  2047  		Config: coretesting.FakeConfig().Merge(map[string]interface{}{
  2048  			"default-base": "ubuntu@20.04",
  2049  		}),
  2050  		CloudRegion: "some-region"})
  2051  
  2052  	application := s.st.model.AddApplication(description.ApplicationArgs{
  2053  		Tag:      names.NewApplicationTag("magic"),
  2054  		CharmURL: "ch:magic",
  2055  	})
  2056  
  2057  	// TODO(jack-w-shaw) this test is somewhat contrived since ubuntu@21.04 is not a supported base.
  2058  	// However, since this test requires at least 3 different bases, we need to do this until 24.04
  2059  	// is supported
  2060  	application.SetCharmOrigin(description.CharmOriginArgs{Platform: "amd64/ubuntu/21.04/stable"})
  2061  	application.AddUnit(description.UnitArgs{
  2062  		Tag:     names.NewUnitTag("magic/0"),
  2063  		Machine: names.NewMachineTag("0"),
  2064  	})
  2065  	s.st.model.AddMachine(description.MachineArgs{
  2066  		Id:   names.NewMachineTag("0"),
  2067  		Base: "ubuntu@21.04",
  2068  	})
  2069  
  2070  	application = s.st.model.AddApplication(description.ApplicationArgs{
  2071  		Tag:      names.NewApplicationTag("mojo"),
  2072  		CharmURL: "ch:mojo",
  2073  	})
  2074  	application.SetCharmOrigin(description.CharmOriginArgs{Platform: "amd64/ubuntu/22.04/stable"})
  2075  	application.AddUnit(description.UnitArgs{
  2076  		Tag:     names.NewUnitTag("mojo/0"),
  2077  		Machine: names.NewMachineTag("1"),
  2078  	})
  2079  	s.st.model.AddMachine(description.MachineArgs{
  2080  		Id:   names.NewMachineTag("1"),
  2081  		Base: "ubuntu@22.04",
  2082  	})
  2083  
  2084  	result, err := s.facade.ExportBundle(params.ExportBundleParams{})
  2085  	c.Assert(err, jc.ErrorIsNil)
  2086  
  2087  	expectedResult := params.StringResult{Result: `
  2088  applications:
  2089    magic:
  2090      charm: magic
  2091      base: ubuntu@21.04/stable
  2092      num_units: 1
  2093      to:
  2094      - "0"
  2095    mojo:
  2096      charm: mojo
  2097      base: ubuntu@22.04/stable
  2098      num_units: 1
  2099      to:
  2100      - "1"
  2101  machines:
  2102    "0":
  2103      base: ubuntu@21.04/stable
  2104    "1":
  2105      base: ubuntu@22.04/stable
  2106  `[1:]}
  2107  
  2108  	c.Assert(result, gc.Equals, expectedResult)
  2109  	s.st.CheckCall(c, 0, "ExportPartial", s.st.GetExportConfig())
  2110  }
  2111  
  2112  func (s *bundleSuite) TestExportKubernetesBundle(c *gc.C) {
  2113  	model := s.newModel("caas", "wordpress", "mysql")
  2114  	model.SetStatus(description.StatusArgs{Value: "available"})
  2115  
  2116  	result, err := s.facade.ExportBundle(params.ExportBundleParams{})
  2117  	c.Assert(err, jc.ErrorIsNil)
  2118  
  2119  	output := `
  2120  bundle: kubernetes
  2121  applications:
  2122    mysql:
  2123      charm: mysql
  2124      scale: 1
  2125    wordpress:
  2126      charm: wordpress
  2127      scale: 2
  2128  relations:
  2129  - - wordpress:db
  2130    - mysql:mysql
  2131  `[1:]
  2132  	expectedResult := params.StringResult{Result: output}
  2133  
  2134  	c.Assert(result, gc.Equals, expectedResult)
  2135  	s.st.CheckCall(c, 0, "ExportPartial", s.st.GetExportConfig())
  2136  }
  2137  
  2138  func (s *bundleSuite) TestExportCharmhubBundle(c *gc.C) {
  2139  	model := s.newModel("iaas", "ch:wordpress", "ch:mysql")
  2140  	model.SetStatus(description.StatusArgs{Value: "available"})
  2141  
  2142  	result, err := s.facade.ExportBundle(params.ExportBundleParams{})
  2143  	c.Assert(err, jc.ErrorIsNil)
  2144  
  2145  	output := `
  2146  default-base: ubuntu@20.04/stable
  2147  applications:
  2148    mysql:
  2149      charm: mysql
  2150      channel: stable
  2151      num_units: 1
  2152      to:
  2153      - "0"
  2154    wordpress:
  2155      charm: wordpress
  2156      channel: stable
  2157      num_units: 2
  2158      to:
  2159      - "0"
  2160      - "1"
  2161  machines:
  2162    "0": {}
  2163    "1": {}
  2164  relations:
  2165  - - wordpress:db
  2166    - mysql:mysql
  2167  `[1:]
  2168  	expectedResult := params.StringResult{Result: output}
  2169  
  2170  	c.Assert(result, gc.Equals, expectedResult)
  2171  	s.st.CheckCall(c, 0, "ExportPartial", s.st.GetExportConfig())
  2172  }
  2173  
  2174  func (s *bundleSuite) TestExportLocalBundle(c *gc.C) {
  2175  	model := s.newModel("iaas", "local:wordpress", "local:mysql")
  2176  	model.SetStatus(description.StatusArgs{Value: "available"})
  2177  
  2178  	result, err := s.facade.ExportBundle(params.ExportBundleParams{})
  2179  	c.Assert(err, jc.ErrorIsNil)
  2180  
  2181  	output := `
  2182  default-base: ubuntu@20.04/stable
  2183  applications:
  2184    mysql:
  2185      charm: local:mysql
  2186      num_units: 1
  2187      to:
  2188      - "0"
  2189    wordpress:
  2190      charm: local:wordpress
  2191      num_units: 2
  2192      to:
  2193      - "0"
  2194      - "1"
  2195  machines:
  2196    "0": {}
  2197    "1": {}
  2198  relations:
  2199  - - wordpress:db
  2200    - mysql:mysql
  2201  `[1:]
  2202  	expectedResult := params.StringResult{Result: output}
  2203  
  2204  	c.Assert(result, gc.Equals, expectedResult)
  2205  	s.st.CheckCall(c, 0, "ExportPartial", s.st.GetExportConfig())
  2206  }
  2207  
  2208  func (s *bundleSuite) TestExportLocalBundleWithSeries(c *gc.C) {
  2209  	model := s.newModel("iaas", "local:focal/wordpress", "local:mysql")
  2210  	model.SetStatus(description.StatusArgs{Value: "available"})
  2211  
  2212  	result, err := s.facade.ExportBundle(params.ExportBundleParams{})
  2213  	c.Assert(err, jc.ErrorIsNil)
  2214  
  2215  	output := `
  2216  default-base: ubuntu@20.04/stable
  2217  applications:
  2218    mysql:
  2219      charm: local:mysql
  2220      num_units: 1
  2221      to:
  2222      - "0"
  2223    wordpress:
  2224      charm: local:wordpress
  2225      num_units: 2
  2226      to:
  2227      - "0"
  2228      - "1"
  2229  machines:
  2230    "0": {}
  2231    "1": {}
  2232  relations:
  2233  - - wordpress:db
  2234    - mysql:mysql
  2235  `[1:]
  2236  	expectedResult := params.StringResult{Result: output}
  2237  
  2238  	c.Assert(result, gc.Equals, expectedResult)
  2239  	s.st.CheckCall(c, 0, "ExportPartial", s.st.GetExportConfig())
  2240  }
  2241  
  2242  func (s *bundleSuite) TestExportBundleWithExposedEndpointSettings(c *gc.C) {
  2243  	specs := []struct {
  2244  		descr            string
  2245  		exposed          bool
  2246  		exposedEndpoints map[string]description.ExposedEndpointArgs
  2247  		expBundle        string
  2248  	}{
  2249  		{
  2250  			descr:   "exposed application without exposed endpoint settings (upgraded 2.8 controller)",
  2251  			exposed: true,
  2252  			expBundle: `
  2253  default-base: ubuntu@20.04/stable
  2254  applications:
  2255    magic:
  2256      charm: magic
  2257      channel: stable
  2258      expose: true
  2259      bindings:
  2260        hat: some-space
  2261        rabbit: alpha
  2262  `[1:],
  2263  		},
  2264  		{
  2265  			descr:   "exposed application where all endpoints are exposed to 0.0.0.0/0",
  2266  			exposed: true,
  2267  			// This is equivalent to having "expose:true" and skipping the exposed settings section from the overlay
  2268  			exposedEndpoints: map[string]description.ExposedEndpointArgs{
  2269  				"": {
  2270  					ExposeToCIDRs: []string{firewall.AllNetworksIPV4CIDR},
  2271  				},
  2272  			},
  2273  			expBundle: `
  2274  default-base: ubuntu@20.04/stable
  2275  applications:
  2276    magic:
  2277      charm: magic
  2278      channel: stable
  2279      expose: true
  2280      bindings:
  2281        hat: some-space
  2282        rabbit: alpha
  2283  `[1:],
  2284  		},
  2285  		{
  2286  			descr:   "exposed application with per-endpoint expose settings",
  2287  			exposed: true,
  2288  			exposedEndpoints: map[string]description.ExposedEndpointArgs{
  2289  				"rabbit": {
  2290  					ExposeToSpaceIDs: []string{"2"},
  2291  					ExposeToCIDRs:    []string{"10.0.0.0/24", "192.168.0.0/24"},
  2292  				},
  2293  				"hat": {
  2294  					ExposeToCIDRs: []string{"192.168.0.0/24"},
  2295  				},
  2296  			},
  2297  			// The exposed:true will be omitted and only the exposed-endpoints section (in the overlay) will be present
  2298  			expBundle: `
  2299  default-base: ubuntu@20.04/stable
  2300  applications:
  2301    magic:
  2302      charm: magic
  2303      channel: stable
  2304      bindings:
  2305        hat: some-space
  2306        rabbit: alpha
  2307  --- # overlay.yaml
  2308  applications:
  2309    magic:
  2310      exposed-endpoints:
  2311        hat:
  2312          expose-to-cidrs:
  2313          - 192.168.0.0/24
  2314        rabbit:
  2315          expose-to-spaces:
  2316          - some-space
  2317          expose-to-cidrs:
  2318          - 10.0.0.0/24
  2319          - 192.168.0.0/24
  2320  `[1:],
  2321  		},
  2322  	}
  2323  
  2324  	for i, spec := range specs {
  2325  		c.Logf("%d. %s", i, spec.descr)
  2326  
  2327  		s.st.model = description.NewModel(description.ModelArgs{Owner: names.NewUserTag("magic"),
  2328  			Config:      coretesting.FakeConfig(),
  2329  			CloudRegion: "some-region"},
  2330  		)
  2331  		s.st.Spaces[network.AlphaSpaceId] = network.AlphaSpaceName
  2332  		s.st.Spaces["2"] = "some-space"
  2333  
  2334  		application := s.st.model.AddApplication(description.ApplicationArgs{
  2335  			Tag:                  names.NewApplicationTag("magic"),
  2336  			CharmURL:             "ch:amd64/focal/magic",
  2337  			Channel:              "stable",
  2338  			CharmModifiedVersion: 1,
  2339  			ForceCharm:           true,
  2340  			Exposed:              spec.exposed,
  2341  			ExposedEndpoints:     spec.exposedEndpoints,
  2342  			EndpointBindings: map[string]string{
  2343  				"hat":    "2",
  2344  				"rabbit": "0",
  2345  			},
  2346  		})
  2347  		application.SetCharmOrigin(description.CharmOriginArgs{Platform: "amd64/ubuntu/20.04/stable"})
  2348  		application.SetStatus(minimalStatusArgs())
  2349  
  2350  		result, err := s.facade.ExportBundle(params.ExportBundleParams{})
  2351  		c.Assert(err, jc.ErrorIsNil)
  2352  
  2353  		exp := params.StringResult{Result: spec.expBundle}
  2354  		c.Assert(result, gc.Equals, exp)
  2355  	}
  2356  }