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

     1  // Copyright 2023 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package application
     5  
     6  import (
     7  	"fmt"
     8  	"reflect"
     9  
    10  	"github.com/juju/charm/v12"
    11  	"github.com/juju/charm/v12/resource"
    12  	"github.com/juju/errors"
    13  	"github.com/juju/names/v5"
    14  	jc "github.com/juju/testing/checkers"
    15  	"github.com/kr/pretty"
    16  	"go.uber.org/mock/gomock"
    17  	gc "gopkg.in/check.v1"
    18  
    19  	corecharm "github.com/juju/juju/core/charm"
    20  	coreconfig "github.com/juju/juju/core/config"
    21  	"github.com/juju/juju/core/constraints"
    22  	"github.com/juju/juju/core/instance"
    23  	"github.com/juju/juju/environs/config"
    24  	"github.com/juju/juju/rpc/params"
    25  	"github.com/juju/juju/state"
    26  	coretesting "github.com/juju/juju/testing"
    27  )
    28  
    29  type validatorSuite struct {
    30  	bindings    *MockBindings
    31  	machine     *MockMachine
    32  	model       *MockModel
    33  	repo        *MockRepository
    34  	repoFactory *MockRepositoryFactory
    35  	state       *MockDeployFromRepositoryState
    36  }
    37  
    38  var _ = gc.Suite(&deployRepositorySuite{})
    39  var _ = gc.Suite(&validatorSuite{})
    40  
    41  func (s *validatorSuite) TestValidateSuccess(c *gc.C) {
    42  	defer s.setupMocks(c).Finish()
    43  	s.expectSimpleValidate()
    44  	// resolveCharm
    45  	curl := charm.MustParseURL("testcharm")
    46  	resultURL := charm.MustParseURL("ch:amd64/jammy/testcharm-4")
    47  	origin := corecharm.Origin{
    48  		Source:   "charm-hub",
    49  		Channel:  &charm.Channel{Risk: "stable"},
    50  		Platform: corecharm.Platform{Architecture: "amd64"},
    51  	}
    52  	resolvedOrigin := corecharm.Origin{
    53  		Source:   "charm-hub",
    54  		Type:     "charm",
    55  		Channel:  &charm.Channel{Track: "default", Risk: "stable"},
    56  		Platform: corecharm.Platform{Architecture: "amd64", OS: "ubuntu", Channel: "22.04"},
    57  		Revision: intptr(4),
    58  	}
    59  	charmID := corecharm.CharmID{URL: curl, Origin: origin}
    60  	resolvedData := getResolvedData(resultURL, resolvedOrigin)
    61  	s.repo.EXPECT().ResolveForDeploy(charmID).Return(resolvedData, nil)
    62  	s.repo.EXPECT().ResolveResources(nil, corecharm.CharmID{URL: resultURL, Origin: resolvedOrigin}).Return(nil, nil)
    63  	s.model.EXPECT().UUID().Return("")
    64  
    65  	// getCharm
    66  	s.state.EXPECT().ModelConstraints().Return(constraints.Value{Arch: strptr("arm64")}, nil)
    67  	s.state.EXPECT().Charm(gomock.Any()).Return(nil, errors.NotFoundf("charm"))
    68  
    69  	arg := params.DeployFromRepositoryArg{
    70  		CharmName: "testcharm",
    71  	}
    72  	dt, errs := s.getValidator().validate(arg)
    73  	c.Assert(errs, gc.HasLen, 0, gc.Commentf("%s", pretty.Sprint(errs)))
    74  	c.Assert(dt, gc.DeepEquals, deployTemplate{
    75  		applicationName: "test-charm",
    76  		charm:           corecharm.NewCharmInfoAdapter(resolvedData.EssentialMetadata),
    77  		charmURL:        resultURL,
    78  		numUnits:        1,
    79  		origin:          resolvedOrigin,
    80  	})
    81  }
    82  
    83  func (s *validatorSuite) TestValidateIAASAttachStorageFail(c *gc.C) {
    84  	argStorageNames := []string{"one-0"}
    85  	expectedStorageTags := []names.StorageTag{}
    86  	s.testValidateIAASAttachStorage(c, argStorageNames, expectedStorageTags, errors.NotValid)
    87  }
    88  
    89  func (s *validatorSuite) TestValidateIAASAttachStorageSuccess(c *gc.C) {
    90  	argStorageNames := []string{"one/0", "two/3"}
    91  	expectedStorageTags := []names.StorageTag{names.NewStorageTag("one/0"), names.NewStorageTag("two/3")}
    92  	s.testValidateIAASAttachStorage(c, argStorageNames, expectedStorageTags, "")
    93  }
    94  
    95  func (s *validatorSuite) testValidateIAASAttachStorage(c *gc.C, argStorage []string, expectedStorageTags []names.StorageTag, expectedErr errors.ConstError) {
    96  	defer s.setupMocks(c).Finish()
    97  	s.expectSimpleValidate()
    98  	// resolveCharm
    99  	curl := charm.MustParseURL("testcharm")
   100  	resultURL := charm.MustParseURL("ch:amd64/jammy/testcharm-4")
   101  	origin := corecharm.Origin{
   102  		Source:   "charm-hub",
   103  		Channel:  &charm.Channel{Risk: "stable"},
   104  		Platform: corecharm.Platform{Architecture: "amd64"},
   105  	}
   106  	resolvedOrigin := corecharm.Origin{
   107  		Source:   "charm-hub",
   108  		Type:     "charm",
   109  		Channel:  &charm.Channel{Track: "default", Risk: "stable"},
   110  		Platform: corecharm.Platform{Architecture: "amd64", OS: "ubuntu", Channel: "22.04"},
   111  		Revision: intptr(4),
   112  	}
   113  	charmID := corecharm.CharmID{URL: curl, Origin: origin}
   114  	resolvedData := getResolvedData(resultURL, resolvedOrigin)
   115  	s.repo.EXPECT().ResolveForDeploy(charmID).Return(resolvedData, nil)
   116  	s.repo.EXPECT().ResolveResources(nil, corecharm.CharmID{URL: resultURL, Origin: resolvedOrigin}).Return(nil, nil)
   117  	s.model.EXPECT().UUID().Return("")
   118  	// getCharm
   119  	s.state.EXPECT().ModelConstraints().Return(constraints.Value{Arch: strptr("arm64")}, nil)
   120  	s.state.EXPECT().Charm(gomock.Any()).Return(nil, errors.NotFoundf("charm"))
   121  
   122  	arg := params.DeployFromRepositoryArg{
   123  		CharmName:     "testcharm",
   124  		AttachStorage: argStorage,
   125  	}
   126  	dt, errs := s.iaasDeployFromRepositoryValidator().ValidateArg(arg)
   127  	if expectedErr == "" {
   128  		c.Assert(errs, gc.HasLen, 0)
   129  		c.Assert(dt, gc.DeepEquals, deployTemplate{
   130  			applicationName: "test-charm",
   131  			charm:           corecharm.NewCharmInfoAdapter(resolvedData.EssentialMetadata),
   132  			charmURL:        resultURL,
   133  			numUnits:        1,
   134  			origin:          resolvedOrigin,
   135  			attachStorage:   expectedStorageTags,
   136  		})
   137  	} else {
   138  		c.Assert(errs, gc.HasLen, 1)
   139  		c.Assert(errors.Is(errs[0], expectedErr), jc.IsTrue)
   140  	}
   141  }
   142  
   143  func (s *validatorSuite) TestValidatePlacementSuccess(c *gc.C) {
   144  	defer s.setupMocks(c).Finish()
   145  	s.expectSimpleValidate()
   146  	// resolveCharm
   147  	curl := charm.MustParseURL("testcharm")
   148  	resultURL := charm.MustParseURL("ch:amd64/jammy/testcharm-4")
   149  	origin := corecharm.Origin{
   150  		Source:   "charm-hub",
   151  		Channel:  &charm.Channel{Risk: "stable"},
   152  		Platform: corecharm.Platform{Architecture: "amd64", OS: "ubuntu", Channel: "22.04"},
   153  	}
   154  	resolvedOrigin := corecharm.Origin{
   155  		Source:   "charm-hub",
   156  		Type:     "charm",
   157  		Channel:  &charm.Channel{Track: "default", Risk: "stable"},
   158  		Platform: corecharm.Platform{Architecture: "amd64", OS: "ubuntu", Channel: "22.04"},
   159  		Revision: intptr(4),
   160  	}
   161  	// getCharm
   162  	charmID := corecharm.CharmID{URL: curl, Origin: origin}
   163  	resolvedData := getResolvedData(resultURL, resolvedOrigin)
   164  	s.repo.EXPECT().ResolveForDeploy(charmID).Return(resolvedData, nil)
   165  	s.repo.EXPECT().ResolveResources(nil, corecharm.CharmID{URL: resultURL, Origin: resolvedOrigin}).Return(nil, nil)
   166  	s.model.EXPECT().UUID().Return("")
   167  
   168  	// Placement
   169  	s.state.EXPECT().Machine("0").Return(s.machine, nil).Times(2)
   170  	s.machine.EXPECT().IsLockedForSeriesUpgrade().Return(false, nil)
   171  	s.machine.EXPECT().IsParentLockedForSeriesUpgrade().Return(false, nil)
   172  	s.machine.EXPECT().Base().Return(state.Base{
   173  		OS:      "ubuntu",
   174  		Channel: "22.04",
   175  	})
   176  	hwc := &instance.HardwareCharacteristics{Arch: strptr("amd64")}
   177  	s.machine.EXPECT().HardwareCharacteristics().Return(hwc, nil)
   178  	s.state.EXPECT().ModelConstraints().Return(constraints.Value{Arch: strptr("arm64")}, nil)
   179  	s.state.EXPECT().Charm(gomock.Any()).Return(nil, errors.NotFoundf("charm"))
   180  
   181  	arg := params.DeployFromRepositoryArg{
   182  		CharmName: "testcharm",
   183  		Placement: []*instance.Placement{{Directive: "0", Scope: instance.MachineScope}},
   184  	}
   185  	dt, errs := s.getValidator().validate(arg)
   186  	c.Assert(errs, gc.HasLen, 0)
   187  	c.Assert(dt, gc.DeepEquals, deployTemplate{
   188  		applicationName: "test-charm",
   189  		charm:           corecharm.NewCharmInfoAdapter(resolvedData.EssentialMetadata),
   190  		charmURL:        resultURL,
   191  		numUnits:        1,
   192  		origin:          resolvedOrigin,
   193  		placement:       arg.Placement,
   194  	})
   195  }
   196  
   197  func (s *validatorSuite) TestValidateEndpointBindingSuccess(c *gc.C) {
   198  	defer s.setupMocks(c).Finish()
   199  	s.expectSimpleValidate()
   200  	// resolveCharm
   201  	curl := charm.MustParseURL("testcharm")
   202  	resultURL := charm.MustParseURL("ch:amd64/jammy/testcharm-4")
   203  	origin := corecharm.Origin{
   204  		Source:   "charm-hub",
   205  		Channel:  &charm.Channel{Risk: "stable"},
   206  		Platform: corecharm.Platform{Architecture: "amd64"},
   207  	}
   208  	resolvedOrigin := corecharm.Origin{
   209  		Source:   "charm-hub",
   210  		Type:     "charm",
   211  		Channel:  &charm.Channel{Track: "default", Risk: "stable"},
   212  		Platform: corecharm.Platform{Architecture: "amd64", OS: "ubuntu", Channel: "22.04"},
   213  		Revision: intptr(4),
   214  	}
   215  	// getCharm
   216  	charmID := corecharm.CharmID{URL: curl, Origin: origin}
   217  	resolvedData := getResolvedData(resultURL, resolvedOrigin)
   218  	s.repo.EXPECT().ResolveForDeploy(charmID).Return(resolvedData, nil)
   219  	s.repo.EXPECT().ResolveResources(nil, corecharm.CharmID{URL: resultURL, Origin: resolvedOrigin}).Return(nil, nil)
   220  	s.model.EXPECT().UUID().Return("")
   221  
   222  	// state bindings
   223  	endpointMap := map[string]string{"to": "from"}
   224  	s.bindings.EXPECT().Map().Return(endpointMap)
   225  	s.state.EXPECT().ModelConstraints().Return(constraints.Value{Arch: strptr("arm64")}, nil)
   226  	s.state.EXPECT().Charm(gomock.Any()).Return(nil, errors.NotFoundf("charm"))
   227  
   228  	arg := params.DeployFromRepositoryArg{
   229  		CharmName:        "testcharm",
   230  		EndpointBindings: endpointMap,
   231  	}
   232  	dt, errs := s.getValidator().validate(arg)
   233  	c.Assert(errs, gc.HasLen, 0)
   234  	c.Assert(dt, gc.DeepEquals, deployTemplate{
   235  		applicationName: "test-charm",
   236  		charm:           corecharm.NewCharmInfoAdapter(resolvedData.EssentialMetadata),
   237  		charmURL:        resultURL,
   238  		endpoints:       endpointMap,
   239  		numUnits:        1,
   240  		origin:          resolvedOrigin,
   241  	})
   242  }
   243  
   244  func (s *validatorSuite) TestValidateEndpointBindingFail(c *gc.C) {
   245  	defer s.setupMocks(c).Finish()
   246  	s.expectSimpleValidate()
   247  	// resolveCharm
   248  	curl := charm.MustParseURL("testcharm")
   249  	resultURL := charm.MustParseURL("ch:amd64/jammy/testcharm-4")
   250  	origin := corecharm.Origin{
   251  		Source:   "charm-hub",
   252  		Channel:  &charm.Channel{Risk: "stable"},
   253  		Platform: corecharm.Platform{Architecture: "amd64"},
   254  	}
   255  	resolvedOrigin := corecharm.Origin{
   256  		Source:   "charm-hub",
   257  		Type:     "charm",
   258  		Channel:  &charm.Channel{Track: "default", Risk: "stable"},
   259  		Platform: corecharm.Platform{Architecture: "amd64", OS: "ubuntu", Channel: "22.04"},
   260  		Revision: intptr(4),
   261  	}
   262  	// getCharm
   263  	charmID := corecharm.CharmID{URL: curl, Origin: origin}
   264  	resolvedData := getResolvedData(resultURL, resolvedOrigin)
   265  	s.repo.EXPECT().ResolveForDeploy(charmID).Return(resolvedData, nil)
   266  	s.repo.EXPECT().ResolveResources(nil, corecharm.CharmID{URL: resultURL, Origin: resolvedOrigin}).Return(nil, nil)
   267  	s.model.EXPECT().UUID().Return("")
   268  
   269  	// state bindings
   270  	endpointMap := map[string]string{"to": "from"}
   271  	s.state.EXPECT().ModelConstraints().Return(constraints.Value{Arch: strptr("arm64")}, nil)
   272  	s.state.EXPECT().Charm(gomock.Any()).Return(nil, errors.NotFoundf("charm"))
   273  
   274  	s.repoFactory.EXPECT().GetCharmRepository(gomock.Any()).Return(s.repo, nil).AnyTimes()
   275  	v := &deployFromRepositoryValidator{
   276  		model:       s.model,
   277  		state:       s.state,
   278  		repoFactory: s.repoFactory,
   279  		newStateBindings: func(st state.EndpointBinding, givenMap map[string]string) (Bindings, error) {
   280  			return nil, errors.NotFoundf("space")
   281  		},
   282  	}
   283  
   284  	arg := params.DeployFromRepositoryArg{
   285  		CharmName:        "testcharm",
   286  		EndpointBindings: endpointMap,
   287  	}
   288  	_, errs := v.validate(arg)
   289  	c.Assert(errs, gc.HasLen, 1)
   290  	c.Assert(errs[0], jc.ErrorIs, errors.NotFound)
   291  }
   292  
   293  func (s *validatorSuite) expectSimpleValidate() {
   294  	// createOrigin
   295  	s.state.EXPECT().ModelConstraints().Return(constraints.Value{}, nil)
   296  	s.model.EXPECT().Config().Return(config.New(config.UseDefaults, coretesting.FakeConfig())).AnyTimes()
   297  }
   298  
   299  func (s *validatorSuite) TestResolveCharm(c *gc.C) {
   300  	defer s.setupMocks(c).Finish()
   301  	curl := charm.MustParseURL("testcharm")
   302  	resultURL := charm.MustParseURL("ch:amd64/jammy/testcharm-4")
   303  	origin := corecharm.Origin{
   304  		Source:   "charm-hub",
   305  		Channel:  &charm.Channel{Risk: "stable"},
   306  		Platform: corecharm.Platform{Architecture: "amd64"},
   307  	}
   308  	resolvedOrigin := corecharm.Origin{
   309  		Source:   "charm-hub",
   310  		Type:     "charm",
   311  		Channel:  &charm.Channel{Track: "default", Risk: "stable"},
   312  		Platform: corecharm.Platform{Architecture: "amd64", OS: "ubuntu", Channel: "22.04"},
   313  		Revision: intptr(4),
   314  	}
   315  	charmID := corecharm.CharmID{URL: curl, Origin: origin}
   316  	resolvedData := getResolvedData(resultURL, resolvedOrigin)
   317  	s.repo.EXPECT().ResolveForDeploy(charmID).Return(resolvedData, nil)
   318  	s.model.EXPECT().Config().Return(config.New(config.UseDefaults, coretesting.FakeConfig()))
   319  	s.state.EXPECT().ModelConstraints().Return(constraints.Value{
   320  		Arch: strptr("arm64"),
   321  	}, nil)
   322  
   323  	obtained, err := s.getValidator().resolveCharm(curl, origin, false, false, constraints.Value{})
   324  	c.Assert(err, jc.ErrorIsNil)
   325  	c.Assert(obtained.URL, gc.DeepEquals, resultURL)
   326  	c.Assert(obtained.EssentialMetadata.ResolvedOrigin, gc.DeepEquals, resolvedOrigin)
   327  }
   328  
   329  func (s *validatorSuite) TestResolveCharmArchAll(c *gc.C) {
   330  	defer s.setupMocks(c).Finish()
   331  	curl := charm.MustParseURL("testcharm")
   332  	resultURL := charm.MustParseURL("ch:amd64/jammy/testcharm-4")
   333  	origin := corecharm.Origin{
   334  		Source:   "charm-hub",
   335  		Channel:  &charm.Channel{Risk: "stable"},
   336  		Platform: corecharm.Platform{Architecture: "amd64"},
   337  	}
   338  	resolvedOrigin := corecharm.Origin{
   339  		Source:   "charm-hub",
   340  		Type:     "charm",
   341  		Channel:  &charm.Channel{Track: "default", Risk: "stable"},
   342  		Platform: corecharm.Platform{Architecture: "all", OS: "ubuntu", Channel: "22.04"},
   343  		Revision: intptr(4),
   344  	}
   345  	charmID := corecharm.CharmID{URL: curl, Origin: origin}
   346  	resolvedData := getResolvedData(resultURL, resolvedOrigin)
   347  	s.repo.EXPECT().ResolveForDeploy(charmID).Return(resolvedData, nil)
   348  	s.model.EXPECT().Config().Return(config.New(config.UseDefaults, coretesting.FakeConfig()))
   349  	s.state.EXPECT().ModelConstraints().Return(constraints.Value{Arch: strptr("arm64")}, nil)
   350  
   351  	obtained, err := s.getValidator().resolveCharm(curl, origin, false, false, constraints.Value{})
   352  	c.Assert(err, jc.ErrorIsNil)
   353  	c.Assert(obtained.URL, gc.DeepEquals, resultURL)
   354  	expectedOrigin := resolvedOrigin
   355  	expectedOrigin.Platform.Architecture = "arm64"
   356  	c.Assert(obtained.EssentialMetadata.ResolvedOrigin, gc.DeepEquals, expectedOrigin)
   357  }
   358  
   359  func (s *validatorSuite) TestResolveCharmUnsupportedSeriesErrorForce(c *gc.C) {
   360  	defer s.setupMocks(c).Finish()
   361  	curl := charm.MustParseURL("testcharm")
   362  	resultURL := charm.MustParseURL("ch:amd64/jammy/testcharm-4")
   363  	origin := corecharm.Origin{
   364  		Source:   "charm-hub",
   365  		Channel:  &charm.Channel{Risk: "stable"},
   366  		Platform: corecharm.Platform{Architecture: "amd64", OS: "ubuntu", Channel: "22.04"},
   367  	}
   368  	resolvedOrigin := corecharm.Origin{
   369  		Source:   "charm-hub",
   370  		Type:     "charm",
   371  		Channel:  &charm.Channel{Track: "default", Risk: "stable"},
   372  		Platform: corecharm.Platform{Architecture: "amd64", OS: "ubuntu", Channel: "22.04"},
   373  		Revision: intptr(4),
   374  	}
   375  	supportedSeries := []string{"focal"}
   376  	newErr := charm.NewUnsupportedSeriesError("jammy", supportedSeries)
   377  	charmID := corecharm.CharmID{URL: curl, Origin: origin}
   378  	resolvedData := getResolvedData(resultURL, resolvedOrigin)
   379  	s.repo.EXPECT().ResolveForDeploy(charmID).Return(resolvedData, newErr)
   380  	s.model.EXPECT().Config().Return(config.New(config.UseDefaults, coretesting.FakeConfig()))
   381  	s.state.EXPECT().ModelConstraints().Return(constraints.Value{Arch: strptr("arm64")}, nil)
   382  
   383  	obtained, err := s.getValidator().resolveCharm(curl, origin, true, false, constraints.Value{})
   384  	c.Assert(err, jc.ErrorIsNil)
   385  	c.Assert(obtained.URL, gc.DeepEquals, resultURL)
   386  	c.Assert(obtained.EssentialMetadata.ResolvedOrigin, gc.DeepEquals, resolvedOrigin)
   387  }
   388  
   389  func (s *validatorSuite) TestResolveCharmUnsupportedSeriesError(c *gc.C) {
   390  	defer s.setupMocks(c).Finish()
   391  	curl := charm.MustParseURL("testcharm")
   392  	origin := corecharm.Origin{
   393  		Source:   "charm-hub",
   394  		Channel:  &charm.Channel{Risk: "stable"},
   395  		Platform: corecharm.Platform{Architecture: "amd64", OS: "ubuntu", Channel: "22.04"},
   396  	}
   397  	charmID := corecharm.CharmID{URL: curl, Origin: origin}
   398  	supportedSeries := []string{"focal"}
   399  	newErr := charm.NewUnsupportedSeriesError("jammy", supportedSeries)
   400  	s.repo.EXPECT().ResolveForDeploy(charmID).Return(corecharm.ResolvedDataForDeploy{}, newErr)
   401  
   402  	_, err := s.getValidator().resolveCharm(curl, origin, false, false, constraints.Value{})
   403  	c.Assert(err, gc.ErrorMatches, `series "jammy" not supported by charm, supported series are: focal. Use --force to deploy the charm anyway.`)
   404  }
   405  
   406  func (s *validatorSuite) TestResolveCharmExplicitBaseErrorWhenUserImageID(c *gc.C) {
   407  	defer s.setupMocks(c).Finish()
   408  	curl := charm.MustParseURL("testcharm")
   409  	resultURL := charm.MustParseURL("ch:amd64/jammy/testcharm-4")
   410  	origin := corecharm.Origin{
   411  		Source:   "charm-hub",
   412  		Channel:  &charm.Channel{Risk: "stable"},
   413  		Platform: corecharm.Platform{Architecture: "amd64"},
   414  	}
   415  	resolvedOrigin := corecharm.Origin{
   416  		Source:   "charm-hub",
   417  		Type:     "charm",
   418  		Channel:  &charm.Channel{Track: "default", Risk: "stable"},
   419  		Platform: corecharm.Platform{Architecture: "amd64", OS: "ubuntu", Channel: "22.04/stable"},
   420  		Revision: intptr(4),
   421  	}
   422  	charmID := corecharm.CharmID{URL: curl, Origin: origin}
   423  	resolvedData := getResolvedData(resultURL, resolvedOrigin)
   424  	s.repo.EXPECT().ResolveForDeploy(charmID).Return(resolvedData, nil)
   425  	s.model.EXPECT().Config().Return(config.New(config.UseDefaults, coretesting.FakeConfig()))
   426  	s.state.EXPECT().ModelConstraints().Return(constraints.Value{Arch: strptr("arm64")}, nil)
   427  
   428  	_, err := s.getValidator().resolveCharm(curl, origin, false, false, constraints.Value{ImageID: strptr("ubuntu-bf2")})
   429  	c.Assert(err, gc.ErrorMatches, `base must be explicitly provided when image-id constraint is used`)
   430  }
   431  
   432  func (s *validatorSuite) TestResolveCharmExplicitBaseErrorWhenModelImageID(c *gc.C) {
   433  	defer s.setupMocks(c).Finish()
   434  	curl := charm.MustParseURL("testcharm")
   435  	resultURL := charm.MustParseURL("ch:amd64/jammy/testcharm-4")
   436  	origin := corecharm.Origin{
   437  		Source:   "charm-hub",
   438  		Channel:  &charm.Channel{Risk: "stable"},
   439  		Platform: corecharm.Platform{Architecture: "amd64"},
   440  	}
   441  	resolvedOrigin := corecharm.Origin{
   442  		Source:   "charm-hub",
   443  		Type:     "charm",
   444  		Channel:  &charm.Channel{Track: "default", Risk: "stable"},
   445  		Platform: corecharm.Platform{Architecture: "amd64", OS: "ubuntu", Channel: "22.04/stable"},
   446  		Revision: intptr(4),
   447  	}
   448  	charmID := corecharm.CharmID{URL: curl, Origin: origin}
   449  	resolvedData := getResolvedData(resultURL, resolvedOrigin)
   450  	s.repo.EXPECT().ResolveForDeploy(charmID).Return(resolvedData, nil)
   451  	s.model.EXPECT().Config().Return(config.New(config.UseDefaults, coretesting.FakeConfig()))
   452  	s.state.EXPECT().ModelConstraints().Return(constraints.Value{
   453  		Arch:    strptr("arm64"),
   454  		ImageID: strptr("ubuntu-bf2"),
   455  	}, nil)
   456  
   457  	_, err := s.getValidator().resolveCharm(curl, origin, false, false, constraints.Value{})
   458  	c.Assert(err, gc.ErrorMatches, `base must be explicitly provided when image-id constraint is used`)
   459  }
   460  
   461  func (s *validatorSuite) TestCreateOrigin(c *gc.C) {
   462  	defer s.setupMocks(c).Finish()
   463  	s.state.EXPECT().ModelConstraints().Return(constraints.Value{}, nil)
   464  	s.model.EXPECT().Config().Return(config.New(config.UseDefaults, coretesting.FakeConfig()))
   465  
   466  	arg := params.DeployFromRepositoryArg{
   467  		CharmName: "testcharm",
   468  		Revision:  intptr(7),
   469  	}
   470  	curl, origin, defaultBase, err := s.getValidator().createOrigin(arg)
   471  	c.Assert(err, jc.ErrorIsNil)
   472  	c.Assert(curl, gc.DeepEquals, charm.MustParseURL("ch:testcharm-7"))
   473  	c.Assert(origin, gc.DeepEquals, corecharm.Origin{
   474  		Source:   "charm-hub",
   475  		Revision: intptr(7),
   476  		Channel:  &corecharm.DefaultChannel,
   477  		Platform: corecharm.Platform{Architecture: "amd64"},
   478  	})
   479  	c.Assert(defaultBase, jc.IsFalse)
   480  }
   481  
   482  func (s *validatorSuite) TestCreateOriginChannel(c *gc.C) {
   483  	defer s.setupMocks(c).Finish()
   484  	s.state.EXPECT().ModelConstraints().Return(constraints.Value{}, nil)
   485  	s.model.EXPECT().Config().Return(config.New(config.UseDefaults, coretesting.FakeConfig()))
   486  
   487  	arg := params.DeployFromRepositoryArg{
   488  		CharmName: "testcharm",
   489  		Revision:  intptr(7),
   490  		Channel:   strptr("yoga/candidate"),
   491  	}
   492  	curl, origin, defaultBase, err := s.getValidator().createOrigin(arg)
   493  	c.Assert(err, jc.ErrorIsNil)
   494  	c.Assert(curl, gc.DeepEquals, charm.MustParseURL("ch:testcharm-7"))
   495  	expectedChannel := corecharm.MustParseChannel("yoga/candidate")
   496  	c.Assert(origin, gc.DeepEquals, corecharm.Origin{
   497  		Source:   "charm-hub",
   498  		Revision: intptr(7),
   499  		Channel:  &expectedChannel,
   500  		Platform: corecharm.Platform{Architecture: "amd64"},
   501  	})
   502  	c.Assert(defaultBase, jc.IsFalse)
   503  }
   504  
   505  func (s *validatorSuite) TestGetCharm(c *gc.C) {
   506  	defer s.setupMocks(c).Finish()
   507  	s.expectSimpleValidate()
   508  	s.state.EXPECT().ModelConstraints().Return(constraints.Value{}, nil)
   509  	// resolveCharm
   510  	curl := charm.MustParseURL("testcharm")
   511  	resultURL := charm.MustParseURL("ch:amd64/jammy/testcharm-4")
   512  	origin := corecharm.Origin{
   513  		Source:   "charm-hub",
   514  		Channel:  &charm.Channel{Risk: "stable"},
   515  		Platform: corecharm.Platform{Architecture: "amd64"},
   516  	}
   517  	resolvedOrigin := corecharm.Origin{
   518  		Source:   "charm-hub",
   519  		Type:     "charm",
   520  		Channel:  &charm.Channel{Track: "default", Risk: "stable"},
   521  		Platform: corecharm.Platform{Architecture: "amd64", OS: "ubuntu", Channel: "22.04"},
   522  		Revision: intptr(4),
   523  	}
   524  	charmID := corecharm.CharmID{URL: curl, Origin: origin}
   525  	resolvedData := getResolvedData(resultURL, resolvedOrigin)
   526  	s.repo.EXPECT().ResolveForDeploy(charmID).Return(resolvedData, nil)
   527  	s.state.EXPECT().Charm(gomock.Any()).Return(nil, errors.NotFoundf("charm"))
   528  	// getCharm
   529  
   530  	arg := params.DeployFromRepositoryArg{
   531  		CharmName: "testcharm",
   532  	}
   533  	obtainedURL, obtainedOrigin, obtainedCharm, err := s.getValidator().getCharm(arg)
   534  	c.Assert(err, jc.ErrorIsNil)
   535  	c.Assert(obtainedOrigin, gc.DeepEquals, resolvedOrigin)
   536  	c.Assert(obtainedCharm, gc.DeepEquals, corecharm.NewCharmInfoAdapter(resolvedData.EssentialMetadata))
   537  	c.Assert(obtainedURL, gc.DeepEquals, resultURL)
   538  }
   539  
   540  func (s *validatorSuite) TestGetCharmAlreadyDeployed(c *gc.C) {
   541  	ctrl := s.setupMocks(c)
   542  	defer ctrl.Finish()
   543  	s.expectSimpleValidate()
   544  	s.state.EXPECT().ModelConstraints().Return(constraints.Value{}, nil)
   545  	// resolveCharm
   546  	curl := charm.MustParseURL("testcharm")
   547  	resultURL := charm.MustParseURL("ch:amd64/jammy/testcharm-4")
   548  	origin := corecharm.Origin{
   549  		Source:   "charm-hub",
   550  		Channel:  &charm.Channel{Risk: "stable"},
   551  		Platform: corecharm.Platform{Architecture: "amd64"},
   552  	}
   553  	resolvedOrigin := corecharm.Origin{
   554  		Source:   "charm-hub",
   555  		Type:     "charm",
   556  		Channel:  &charm.Channel{Track: "default", Risk: "stable"},
   557  		Platform: corecharm.Platform{Architecture: "amd64", OS: "ubuntu", Channel: "22.04"},
   558  		Revision: intptr(4),
   559  	}
   560  	charmID := corecharm.CharmID{URL: curl, Origin: origin}
   561  	resolvedData := getResolvedData(resultURL, resolvedOrigin)
   562  	s.repo.EXPECT().ResolveForDeploy(charmID).Return(resolvedData, nil)
   563  	ch := NewMockCharm(ctrl)
   564  	s.state.EXPECT().Charm(gomock.Any()).Return(ch, nil)
   565  
   566  	arg := params.DeployFromRepositoryArg{
   567  		CharmName: "testcharm",
   568  	}
   569  	obtainedURL, obtainedOrigin, obtainedCharm, err := s.getValidator().getCharm(arg)
   570  
   571  	c.Assert(err, jc.ErrorIsNil)
   572  	c.Assert(obtainedOrigin, gc.DeepEquals, resolvedOrigin)
   573  	c.Assert(obtainedCharm, gc.NotNil)
   574  	c.Assert(obtainedURL, gc.DeepEquals, resultURL)
   575  }
   576  
   577  func (s *validatorSuite) TestGetCharmFindsBundle(c *gc.C) {
   578  	defer s.setupMocks(c).Finish()
   579  	s.expectSimpleValidate()
   580  	s.state.EXPECT().ModelConstraints().Return(constraints.Value{}, nil)
   581  	// resolveCharm
   582  	curl := charm.MustParseURL("testcharm")
   583  	resultURL := charm.MustParseURL("ch:amd64/jammy/testcharm-4")
   584  	origin := corecharm.Origin{
   585  		Source:   "charm-hub",
   586  		Channel:  &charm.Channel{Risk: "stable"},
   587  		Platform: corecharm.Platform{Architecture: "amd64"},
   588  	}
   589  	resolvedOrigin := corecharm.Origin{
   590  		Source:   "charm-hub",
   591  		Type:     "bundle",
   592  		Channel:  &charm.Channel{Track: "default", Risk: "stable"},
   593  		Platform: corecharm.Platform{Architecture: "amd64", OS: "ubuntu", Channel: "22.04"},
   594  		Revision: intptr(4),
   595  	}
   596  	charmID := corecharm.CharmID{URL: curl, Origin: origin}
   597  	resolvedData := getResolvedData(resultURL, resolvedOrigin)
   598  	s.repo.EXPECT().ResolveForDeploy(charmID).Return(resolvedData, nil)
   599  	arg := params.DeployFromRepositoryArg{
   600  		CharmName: "testcharm",
   601  	}
   602  	_, _, _, err := s.getValidator().getCharm(arg)
   603  	c.Assert(errors.Is(err, errors.BadRequest), jc.IsTrue)
   604  }
   605  
   606  func (s *validatorSuite) TestGetCharmNoJujuControllerCharm(c *gc.C) {
   607  	defer s.setupMocks(c).Finish()
   608  	s.expectSimpleValidate()
   609  	s.state.EXPECT().ModelConstraints().Return(constraints.Value{}, nil)
   610  	// resolveCharm
   611  	curl := charm.MustParseURL("testcharm")
   612  	resultURL := charm.MustParseURL("ch:amd64/jammy/juju-qa-test-4")
   613  	origin := corecharm.Origin{
   614  		Source:   "charm-hub",
   615  		Channel:  &charm.Channel{Risk: "stable"},
   616  		Platform: corecharm.Platform{Architecture: "amd64"},
   617  	}
   618  	resolvedOrigin := corecharm.Origin{
   619  		Source:   "charm-hub",
   620  		Type:     "charm",
   621  		Channel:  &charm.Channel{Track: "default", Risk: "stable"},
   622  		Platform: corecharm.Platform{Architecture: "amd64", OS: "ubuntu", Channel: "22.04"},
   623  		Revision: intptr(4),
   624  	}
   625  	charmID := corecharm.CharmID{URL: curl, Origin: origin}
   626  	resolvedData := getResolvedData(resultURL, resolvedOrigin)
   627  	resolvedData.EssentialMetadata.Meta.Name = "juju-controller"
   628  	s.repo.EXPECT().ResolveForDeploy(charmID).Return(resolvedData, nil)
   629  
   630  	arg := params.DeployFromRepositoryArg{
   631  		CharmName: "testcharm",
   632  	}
   633  	_, _, _, err := s.getValidator().getCharm(arg)
   634  	c.Assert(errors.Is(err, errors.NotSupported), jc.IsTrue, gc.Commentf("%+v", err))
   635  }
   636  
   637  func (s *validatorSuite) TestDeducePlatformSimple(c *gc.C) {
   638  	defer s.setupMocks(c).Finish()
   639  	//model constraint default
   640  	s.state.EXPECT().ModelConstraints().Return(constraints.Value{Arch: strptr("amd64")}, nil)
   641  	s.model.EXPECT().Config().Return(config.New(config.UseDefaults, coretesting.FakeConfig()))
   642  
   643  	arg := params.DeployFromRepositoryArg{CharmName: "testme"}
   644  	plat, usedModelDefaultBase, err := s.getValidator().deducePlatform(arg)
   645  	c.Assert(err, gc.IsNil)
   646  	c.Assert(usedModelDefaultBase, jc.IsFalse)
   647  	c.Assert(plat, gc.DeepEquals, corecharm.Platform{Architecture: "amd64"})
   648  }
   649  
   650  func (s *validatorSuite) TestDeducePlatformRiskInChannel(c *gc.C) {
   651  	defer s.setupMocks(c).Finish()
   652  	//model constraint default
   653  	s.state.EXPECT().ModelConstraints().Return(constraints.Value{Arch: strptr("amd64")}, nil)
   654  
   655  	arg := params.DeployFromRepositoryArg{
   656  		CharmName: "testme",
   657  		Base: &params.Base{
   658  			Name:    "ubuntu",
   659  			Channel: "22.10/stable",
   660  		},
   661  	}
   662  	plat, usedModelDefaultBase, err := s.getValidator().deducePlatform(arg)
   663  	c.Assert(err, gc.IsNil)
   664  	c.Assert(usedModelDefaultBase, jc.IsFalse)
   665  	c.Assert(plat, gc.DeepEquals, corecharm.Platform{
   666  		Architecture: "amd64",
   667  		OS:           "ubuntu",
   668  		Channel:      "22.10",
   669  	})
   670  }
   671  
   672  func (s *validatorSuite) TestDeducePlatformArgArchBase(c *gc.C) {
   673  	defer s.setupMocks(c).Finish()
   674  
   675  	arg := params.DeployFromRepositoryArg{
   676  		CharmName: "testme",
   677  		Cons:      constraints.Value{Arch: strptr("arm64")},
   678  		Base: &params.Base{
   679  			Name:    "ubuntu",
   680  			Channel: "22.10",
   681  		},
   682  	}
   683  	plat, usedModelDefaultBase, err := s.getValidator().deducePlatform(arg)
   684  	c.Assert(err, gc.IsNil)
   685  	c.Assert(usedModelDefaultBase, jc.IsFalse)
   686  	c.Assert(plat, gc.DeepEquals, corecharm.Platform{
   687  		Architecture: "arm64",
   688  		OS:           "ubuntu",
   689  		Channel:      "22.10",
   690  	})
   691  }
   692  
   693  func (s *validatorSuite) TestDeducePlatformModelDefaultBase(c *gc.C) {
   694  	defer s.setupMocks(c).Finish()
   695  	//model constraint default
   696  	s.state.EXPECT().ModelConstraints().Return(constraints.Value{}, nil)
   697  	sConfig := coretesting.FakeConfig()
   698  	sConfig = sConfig.Merge(coretesting.Attrs{
   699  		"default-base": "ubuntu@22.04",
   700  	})
   701  	cfg, err := config.New(config.NoDefaults, sConfig)
   702  	c.Assert(err, jc.ErrorIsNil)
   703  	s.model.EXPECT().Config().Return(cfg, nil)
   704  
   705  	arg := params.DeployFromRepositoryArg{
   706  		CharmName: "testme",
   707  	}
   708  	plat, usedModelDefaultBase, err := s.getValidator().deducePlatform(arg)
   709  	c.Assert(err, gc.IsNil)
   710  	c.Assert(usedModelDefaultBase, jc.IsTrue)
   711  	c.Assert(plat, gc.DeepEquals, corecharm.Platform{
   712  		Architecture: "amd64",
   713  		OS:           "ubuntu",
   714  		Channel:      "22.04",
   715  	})
   716  }
   717  
   718  func (s *validatorSuite) TestDeducePlatformPlacementSimpleFound(c *gc.C) {
   719  	defer s.setupMocks(c).Finish()
   720  	s.state.EXPECT().ModelConstraints().Return(constraints.Value{}, nil)
   721  	s.state.EXPECT().Machine("0").Return(s.machine, nil)
   722  	s.machine.EXPECT().Base().Return(state.Base{
   723  		OS:      "ubuntu",
   724  		Channel: "18.04",
   725  	})
   726  	hwc := &instance.HardwareCharacteristics{Arch: strptr("arm64")}
   727  	s.machine.EXPECT().HardwareCharacteristics().Return(hwc, nil)
   728  
   729  	arg := params.DeployFromRepositoryArg{
   730  		CharmName: "testme",
   731  		Placement: []*instance.Placement{{
   732  			Directive: "0",
   733  		}},
   734  	}
   735  	plat, usedModelDefaultBase, err := s.getValidator().deducePlatform(arg)
   736  	c.Assert(err, gc.IsNil)
   737  	c.Assert(usedModelDefaultBase, jc.IsFalse)
   738  	c.Assert(plat, gc.DeepEquals, corecharm.Platform{
   739  		Architecture: "arm64",
   740  		OS:           "ubuntu",
   741  		Channel:      "18.04",
   742  	})
   743  }
   744  
   745  func (s *validatorSuite) TestDeducePlatformPlacementSimpleNotFound(c *gc.C) {
   746  	defer s.setupMocks(c).Finish()
   747  	//model constraint default
   748  	s.state.EXPECT().ModelConstraints().Return(constraints.Value{Arch: strptr("amd64")}, nil)
   749  	s.model.EXPECT().Config().Return(config.New(config.UseDefaults, coretesting.FakeConfig()))
   750  	s.state.EXPECT().Machine("0/lxd/0").Return(nil, errors.NotFoundf("machine 0/lxd/0 not found"))
   751  
   752  	arg := params.DeployFromRepositoryArg{
   753  		CharmName: "testme",
   754  		Placement: []*instance.Placement{{
   755  			Directive: "0/lxd/0",
   756  		}},
   757  	}
   758  	plat, usedModelDefaultBase, err := s.getValidator().deducePlatform(arg)
   759  	c.Assert(err, gc.IsNil)
   760  	c.Assert(usedModelDefaultBase, jc.IsFalse)
   761  	c.Assert(plat, gc.DeepEquals, corecharm.Platform{Architecture: "amd64"})
   762  }
   763  
   764  func (s *validatorSuite) TestResolvedCharmValidationSubordinate(c *gc.C) {
   765  	ctrl := s.setupMocks(c)
   766  	defer ctrl.Finish()
   767  	ch := NewMockCharm(ctrl)
   768  	meta := &charm.Meta{
   769  		Name:        "testcharm",
   770  		Subordinate: true,
   771  	}
   772  	ch.EXPECT().Config().Return(nil)
   773  	ch.EXPECT().Meta().Return(meta).AnyTimes()
   774  	arg := params.DeployFromRepositoryArg{
   775  		NumUnits: intptr(1),
   776  	}
   777  	dt, err := s.getValidator().resolvedCharmValidation(ch, arg)
   778  	c.Assert(err, gc.HasLen, 0)
   779  	c.Assert(dt.numUnits, gc.Equals, 0)
   780  }
   781  
   782  func (s *validatorSuite) TestDeducePlatformPlacementMutipleMatch(c *gc.C) {
   783  	defer s.setupMocks(c).Finish()
   784  	s.state.EXPECT().ModelConstraints().Return(constraints.Value{}, nil)
   785  	s.state.EXPECT().Machine(gomock.Any()).Return(s.machine, nil).Times(3)
   786  	s.machine.EXPECT().Base().Return(state.Base{
   787  		OS:      "ubuntu",
   788  		Channel: "18.04",
   789  	}).Times(3)
   790  	hwc := &instance.HardwareCharacteristics{Arch: strptr("arm64")}
   791  	s.machine.EXPECT().HardwareCharacteristics().Return(hwc, nil).Times(3)
   792  
   793  	arg := params.DeployFromRepositoryArg{
   794  		CharmName: "testme",
   795  		Placement: []*instance.Placement{
   796  			{Directive: "0"},
   797  			{Directive: "1"},
   798  			{Directive: "3"},
   799  		},
   800  	}
   801  	plat, usedModelDefaultBase, err := s.getValidator().deducePlatform(arg)
   802  	c.Assert(err, gc.IsNil)
   803  	c.Assert(usedModelDefaultBase, jc.IsFalse)
   804  	c.Assert(plat, gc.DeepEquals, corecharm.Platform{
   805  		Architecture: "arm64",
   806  		OS:           "ubuntu",
   807  		Channel:      "18.04",
   808  	})
   809  }
   810  
   811  func (s *validatorSuite) TestDeducePlatformPlacementMutipleMatchFail(c *gc.C) {
   812  	defer s.setupMocks(c).Finish()
   813  	s.state.EXPECT().ModelConstraints().Return(constraints.Value{}, nil)
   814  	s.state.EXPECT().Machine(gomock.Any()).Return(s.machine, nil).AnyTimes()
   815  	s.machine.EXPECT().Base().Return(
   816  		state.Base{
   817  			OS:      "ubuntu",
   818  			Channel: "18.04",
   819  		}).AnyTimes()
   820  	gomock.InOrder(
   821  		s.machine.EXPECT().HardwareCharacteristics().Return(
   822  			&instance.HardwareCharacteristics{Arch: strptr("arm64")},
   823  			nil),
   824  		s.machine.EXPECT().HardwareCharacteristics().Return(
   825  			&instance.HardwareCharacteristics{Arch: strptr("amd64")},
   826  			nil),
   827  	)
   828  
   829  	arg := params.DeployFromRepositoryArg{
   830  		CharmName: "testme",
   831  		Placement: []*instance.Placement{
   832  			{Directive: "0"},
   833  			{Directive: "1"},
   834  		},
   835  	}
   836  	_, _, err := s.getValidator().deducePlatform(arg)
   837  	c.Assert(errors.Is(err, errors.BadRequest), jc.IsTrue, gc.Commentf("%+v", err))
   838  }
   839  
   840  var configYaml = `
   841  testme:
   842    optionOne: one
   843    optionTwo: 8
   844  `[1:]
   845  
   846  func (s *validatorSuite) TestAppCharmSettings(c *gc.C) {
   847  	defer s.setupMocks(c).Finish()
   848  	s.model.EXPECT().Type().Return(state.ModelTypeIAAS)
   849  
   850  	cfg := charm.NewConfig()
   851  	cfg.Options = map[string]charm.Option{
   852  		"optionOne": {
   853  			Type:        "string",
   854  			Description: "option one",
   855  		},
   856  		"optionTwo": {
   857  			Type:        "int",
   858  			Description: "option two",
   859  		},
   860  	}
   861  
   862  	appCfgSchema, _, err := applicationConfigSchema(state.ModelTypeIAAS)
   863  	c.Assert(err, jc.ErrorIsNil)
   864  
   865  	expectedAppConfig, err := coreconfig.NewConfig(map[string]interface{}{"trust": true}, appCfgSchema, nil)
   866  	c.Assert(err, jc.ErrorIsNil)
   867  
   868  	appConfig, charmConfig, err := s.getValidator().appCharmSettings("testme", true, cfg, configYaml)
   869  	c.Assert(err, jc.ErrorIsNil)
   870  	c.Check(appConfig, gc.DeepEquals, expectedAppConfig)
   871  	c.Assert(charmConfig["optionOne"], gc.DeepEquals, "one")
   872  	c.Assert(charmConfig["optionTwo"], gc.DeepEquals, int64(8))
   873  }
   874  
   875  // The purpose of the resolveResourcesArgsMatcher is
   876  // to compare the slices of resource.Resource, b/c the
   877  // order is non-deterministic.
   878  type resolveResourcesArgsMatcher struct {
   879  	c        *gc.C
   880  	expected *[]resource.Resource
   881  }
   882  
   883  func (m resolveResourcesArgsMatcher) String() string {
   884  	return "match ResolveResources arg map"
   885  }
   886  
   887  func (m resolveResourcesArgsMatcher) Matches(x interface{}) bool {
   888  	obtainedSlice, ok := x.([]resource.Resource)
   889  	if !ok {
   890  		return false
   891  	}
   892  
   893  	m.c.Assert(obtainedSlice, gc.HasLen, len(*m.expected))
   894  	// Unfortunately the jc.SameContents don't work here
   895  	// because resource.Resource is unhashable
   896  	for _, r := range obtainedSlice {
   897  		found := false
   898  		for _, exR := range *m.expected {
   899  			if reflect.DeepEqual(r, exR) {
   900  				found = true
   901  				break
   902  			}
   903  		}
   904  		m.c.Assert(found, gc.Equals, true)
   905  	}
   906  	return true
   907  }
   908  
   909  func (s *validatorSuite) TestResolveResourcesSuccess(c *gc.C) {
   910  	defer s.setupMocks(c).Finish()
   911  	curl := charm.MustParseURL("testcharm")
   912  	origin := corecharm.Origin{
   913  		Source:   "charm-hub",
   914  		Channel:  &charm.Channel{Risk: "stable"},
   915  		Platform: corecharm.Platform{Architecture: "amd64"},
   916  	}
   917  	// Resource 1 : file upload from client
   918  	meta1 := resource.Meta{
   919  		Name:        "foo-resource",
   920  		Type:        resource.TypeFile,
   921  		Path:        "foo.txt",
   922  		Description: "bar",
   923  	}
   924  	res := resource.Resource{
   925  		Meta:     meta1,
   926  		Origin:   resource.OriginUpload,
   927  		Revision: -1,
   928  	}
   929  	// Resource 2 : store resource with --resource <revision> flag
   930  	meta2 := resource.Meta{
   931  		Name:        "foo-resource2",
   932  		Type:        resource.TypeFile,
   933  		Path:        "foo.txt",
   934  		Description: "bar",
   935  	}
   936  	res2 := resource.Resource{
   937  		Meta:     meta2,
   938  		Origin:   resource.OriginStore,
   939  		Revision: 3,
   940  	}
   941  	// Resource 3 : store resource without the --resource flag
   942  	// (revision is reported by the store)
   943  	meta3 := resource.Meta{
   944  		Name:        "foo-resource3",
   945  		Type:        resource.TypeFile,
   946  		Path:        "foo.txt",
   947  		Description: "bar",
   948  	}
   949  	res3 := resource.Resource{
   950  		Meta:     meta3,
   951  		Origin:   resource.OriginStore,
   952  		Revision: -1,
   953  	}
   954  
   955  	resMeta := map[string]resource.Meta{"foo-file": meta1, "foo-file2": meta2, "store-file-res": meta3}
   956  	resArgs := []resource.Resource{res, res2, res3}
   957  	// Note that for the Resource 3, in the args res3 has revision -1, and the result below has revision 4
   958  	r4 := resource.Resource{
   959  		Meta:     meta3,
   960  		Origin:   resource.OriginStore,
   961  		Revision: 4,
   962  	}
   963  	resResult := []resource.Resource{res, res2, r4}
   964  	// First one of below is the file upload for Resource 1, the second is the revision for Resource 2e
   965  	deployResArg := map[string]string{"foo-file": "bar", "foo-file2": "3"}
   966  
   967  	s.repo.EXPECT().ResolveResources(resolveResourcesArgsMatcher{c: c, expected: &resArgs}, corecharm.CharmID{URL: curl, Origin: origin}).Return(resResult, nil)
   968  	resources, pendingResourceUploads, resolveResErr := s.getValidator().resolveResources(curl, origin, deployResArg, resMeta)
   969  	pendUp := &params.PendingResourceUpload{
   970  		Name:     "foo-resource",
   971  		Type:     "file",
   972  		Filename: "bar",
   973  	}
   974  	c.Assert(resolveResErr, jc.ErrorIsNil)
   975  	c.Assert(resources, gc.DeepEquals, resResult)
   976  	c.Assert(pendingResourceUploads, gc.DeepEquals, []*params.PendingResourceUpload{pendUp})
   977  }
   978  
   979  func (s *validatorSuite) TestCaasDeployFromRepositoryValidator(c *gc.C) {
   980  	defer s.setupMocks(c).Finish()
   981  	s.expectSimpleValidate()
   982  	// resolveCharm
   983  	curl := charm.MustParseURL("testcharm")
   984  	resultURL := charm.MustParseURL("ch:amd64/jammy/testcharm-4")
   985  	origin := corecharm.Origin{
   986  		Source:   "charm-hub",
   987  		Channel:  &charm.Channel{Risk: "stable"},
   988  		Platform: corecharm.Platform{Architecture: "amd64"},
   989  	}
   990  	resolvedOrigin := corecharm.Origin{
   991  		Source:   "charm-hub",
   992  		Type:     "charm",
   993  		Channel:  &charm.Channel{Track: "default", Risk: "stable"},
   994  		Platform: corecharm.Platform{Architecture: "amd64", OS: "ubuntu", Channel: "22.04"},
   995  		Revision: intptr(4),
   996  	}
   997  	charmID := corecharm.CharmID{URL: curl, Origin: origin}
   998  	resolvedData := getResolvedData(resultURL, resolvedOrigin)
   999  	s.repo.EXPECT().ResolveForDeploy(charmID).Return(resolvedData, nil)
  1000  	s.repo.EXPECT().ResolveResources(nil, corecharm.CharmID{URL: resultURL, Origin: resolvedOrigin}).Return(nil, nil)
  1001  	s.state.EXPECT().Charm(gomock.Any()).Return(nil, errors.NotFoundf("charm"))
  1002  	s.state.EXPECT().ModelConstraints().Return(constraints.Value{
  1003  		Arch: strptr("arm64"),
  1004  	}, nil)
  1005  	s.model.EXPECT().UUID().Return("")
  1006  
  1007  	arg := params.DeployFromRepositoryArg{
  1008  		CharmName: "testcharm",
  1009  	}
  1010  
  1011  	obtainedDT, errs := s.caasDeployFromRepositoryValidator(c).ValidateArg(arg)
  1012  	c.Assert(errs, gc.HasLen, 0)
  1013  	c.Assert(obtainedDT, gc.DeepEquals, deployTemplate{
  1014  		applicationName: "test-charm",
  1015  		charm:           corecharm.NewCharmInfoAdapter(resolvedData.EssentialMetadata),
  1016  		charmURL:        resultURL,
  1017  		numUnits:        1,
  1018  		origin:          resolvedOrigin,
  1019  	})
  1020  }
  1021  
  1022  func (s *validatorSuite) TestIaaSDeployFromRepositoryFailResolveCharm(c *gc.C) {
  1023  	defer s.setupMocks(c).Finish()
  1024  	s.expectSimpleValidate()
  1025  	s.repo.EXPECT().ResolveForDeploy(gomock.Any()).Return(corecharm.ResolvedDataForDeploy{}, fmt.Errorf("fail resolve"))
  1026  	s.model.EXPECT().UUID().Return("")
  1027  
  1028  	arg := params.DeployFromRepositoryArg{
  1029  		CharmName: "testcharm",
  1030  	}
  1031  
  1032  	_, errs := s.iaasDeployFromRepositoryValidator().ValidateArg(arg)
  1033  	c.Assert(errs, gc.HasLen, 1)
  1034  }
  1035  
  1036  func (s *validatorSuite) TestCaaSDeployFromRepositoryFailResolveCharm(c *gc.C) {
  1037  	defer s.setupMocks(c).Finish()
  1038  	s.expectSimpleValidate()
  1039  	s.repo.EXPECT().ResolveForDeploy(gomock.Any()).Return(corecharm.ResolvedDataForDeploy{}, fmt.Errorf("fail resolve"))
  1040  	s.model.EXPECT().UUID().Return("")
  1041  
  1042  	arg := params.DeployFromRepositoryArg{
  1043  		CharmName: "testcharm",
  1044  	}
  1045  
  1046  	_, errs := s.caasDeployFromRepositoryValidator(c).ValidateArg(arg)
  1047  	c.Assert(errs, gc.HasLen, 1)
  1048  }
  1049  
  1050  func getResolvedData(resultURL *charm.URL, resolvedOrigin corecharm.Origin) corecharm.ResolvedDataForDeploy {
  1051  	expMeta := &charm.Meta{
  1052  		Name: "test-charm",
  1053  	}
  1054  	expManifest := &charm.Manifest{Bases: []charm.Base{
  1055  		{Name: "ubuntu", Channel: charm.Channel{Track: "22.04", Risk: "stable"}},
  1056  		{Name: "ubuntu", Channel: charm.Channel{Track: "20.04", Risk: "stable"}},
  1057  	}}
  1058  	expConfig := new(charm.Config)
  1059  	essMeta := corecharm.EssentialMetadata{
  1060  		Meta:           expMeta,
  1061  		Manifest:       expManifest,
  1062  		Config:         expConfig,
  1063  		ResolvedOrigin: resolvedOrigin,
  1064  	}
  1065  	return corecharm.ResolvedDataForDeploy{
  1066  		URL:               resultURL,
  1067  		EssentialMetadata: essMeta,
  1068  		Resources:         nil,
  1069  	}
  1070  }
  1071  
  1072  func (s *validatorSuite) setupMocks(c *gc.C) *gomock.Controller {
  1073  	ctrl := gomock.NewController(c)
  1074  	s.bindings = NewMockBindings(ctrl)
  1075  	s.machine = NewMockMachine(ctrl)
  1076  	s.model = NewMockModel(ctrl)
  1077  	s.repo = NewMockRepository(ctrl)
  1078  	s.repoFactory = NewMockRepositoryFactory(ctrl)
  1079  	s.state = NewMockDeployFromRepositoryState(ctrl)
  1080  	return ctrl
  1081  }
  1082  
  1083  func (s *validatorSuite) getValidator() *deployFromRepositoryValidator {
  1084  	s.repoFactory.EXPECT().GetCharmRepository(gomock.Any()).Return(s.repo, nil).AnyTimes()
  1085  	return &deployFromRepositoryValidator{
  1086  		model:       s.model,
  1087  		state:       s.state,
  1088  		repoFactory: s.repoFactory,
  1089  		newStateBindings: func(st state.EndpointBinding, givenMap map[string]string) (Bindings, error) {
  1090  			return s.bindings, nil
  1091  		},
  1092  	}
  1093  }
  1094  
  1095  func (s *validatorSuite) caasDeployFromRepositoryValidator(c *gc.C) caasDeployFromRepositoryValidator {
  1096  	return caasDeployFromRepositoryValidator{
  1097  		validator: s.getValidator(),
  1098  		caasPrecheckFunc: func(dt deployTemplate) error {
  1099  			// Do a quick check to ensure the expected deployTemplate
  1100  			// has been passed.
  1101  			c.Assert(dt.applicationName, gc.Equals, "test-charm")
  1102  			return nil
  1103  		},
  1104  	}
  1105  }
  1106  
  1107  func (s *validatorSuite) iaasDeployFromRepositoryValidator() iaasDeployFromRepositoryValidator {
  1108  	return iaasDeployFromRepositoryValidator{
  1109  		validator: s.getValidator(),
  1110  	}
  1111  }
  1112  
  1113  func strptr(s string) *string {
  1114  	return &s
  1115  }
  1116  
  1117  func intptr(i int) *int {
  1118  	return &i
  1119  }
  1120  
  1121  type deployRepositorySuite struct {
  1122  	application *MockApplication
  1123  	charm       *MockCharm
  1124  	state       *MockDeployFromRepositoryState
  1125  	validator   *MockDeployFromRepositoryValidator
  1126  }
  1127  
  1128  func (s *deployRepositorySuite) TestDeployFromRepositoryAPI(c *gc.C) {
  1129  	defer s.setupMocks(c).Finish()
  1130  	arg := params.DeployFromRepositoryArg{
  1131  		CharmName: "testme",
  1132  	}
  1133  	template := deployTemplate{
  1134  		applicationName: "metadata-name",
  1135  		charm:           corecharm.NewCharmInfoAdapter(corecharm.EssentialMetadata{}),
  1136  		charmURL:        charm.MustParseURL("ch:amd64/jammy/testme-5"),
  1137  		endpoints:       map[string]string{"to": "from"},
  1138  		numUnits:        1,
  1139  		origin: corecharm.Origin{
  1140  			Source:   "charm-hub",
  1141  			Revision: intptr(5),
  1142  			Channel:  &charm.Channel{Risk: "stable"},
  1143  			Platform: corecharm.MustParsePlatform("amd64/ubuntu/22.04"),
  1144  		},
  1145  		placement: []*instance.Placement{{Directive: "0", Scope: instance.MachineScope}},
  1146  	}
  1147  	s.validator.EXPECT().ValidateArg(arg).Return(template, nil)
  1148  	info := state.CharmInfo{
  1149  		Charm: template.charm,
  1150  		ID:    "ch:amd64/jammy/testme-5",
  1151  	}
  1152  
  1153  	s.state.EXPECT().AddCharmMetadata(info).Return(s.charm, nil)
  1154  
  1155  	addAppArgs := state.AddApplicationArgs{
  1156  		Name: "metadata-name",
  1157  		// the app.Charm is casted into a state.Charm in the code
  1158  		// we mock it separately here (s.charm above), the test works
  1159  		// thanks to the addApplicationArgsMatcher used below
  1160  		Charm: &state.Charm{},
  1161  		CharmOrigin: &state.CharmOrigin{
  1162  			Source:   "charm-hub",
  1163  			Revision: intptr(5),
  1164  			Channel: &state.Channel{
  1165  				Risk: "stable",
  1166  			},
  1167  			Platform: &state.Platform{
  1168  				Architecture: "amd64",
  1169  				OS:           "ubuntu",
  1170  				Channel:      "22.04",
  1171  			},
  1172  		},
  1173  		Devices:          map[string]state.DeviceConstraints{},
  1174  		EndpointBindings: map[string]string{"to": "from"},
  1175  		NumUnits:         1,
  1176  		Placement:        []*instance.Placement{{Directive: "0", Scope: instance.MachineScope}},
  1177  		Resources:        map[string]string{},
  1178  		Storage:          map[string]state.StorageConstraints{},
  1179  	}
  1180  	s.state.EXPECT().AddApplication(addApplicationArgsMatcher{c: c, expectedArgs: addAppArgs}).Return(s.application, nil)
  1181  
  1182  	deployFromRepositoryAPI := s.getDeployFromRepositoryAPI()
  1183  
  1184  	obtainedInfo, resources, errs := deployFromRepositoryAPI.DeployFromRepository(arg)
  1185  	c.Assert(errs, gc.HasLen, 0)
  1186  	c.Assert(resources, gc.HasLen, 0)
  1187  	c.Assert(obtainedInfo, gc.DeepEquals, params.DeployFromRepositoryInfo{
  1188  		Architecture:     "amd64",
  1189  		Base:             params.Base{Name: "ubuntu", Channel: "22.04"},
  1190  		Channel:          "stable",
  1191  		EffectiveChannel: nil,
  1192  		Name:             "metadata-name",
  1193  		Revision:         5,
  1194  	})
  1195  }
  1196  
  1197  // The reason for this matcher is that the AddApplicationArgs.Charm is
  1198  // obtained by casting application.Charm into a state.Charm, but we
  1199  // can't do that cast with a MockCharm
  1200  type addApplicationArgsMatcher struct {
  1201  	c            *gc.C
  1202  	expectedArgs state.AddApplicationArgs
  1203  }
  1204  
  1205  func (m addApplicationArgsMatcher) String() string {
  1206  	return "match AddApplicationArgs"
  1207  }
  1208  
  1209  func (m addApplicationArgsMatcher) Matches(x interface{}) bool {
  1210  
  1211  	oA, ok := x.(state.AddApplicationArgs)
  1212  	if !ok {
  1213  		return false
  1214  	}
  1215  
  1216  	eA := m.expectedArgs
  1217  	// Check everything but the Charm
  1218  	m.c.Assert(oA.Name, gc.DeepEquals, eA.Name)
  1219  	m.c.Assert(oA.ApplicationConfig, gc.DeepEquals, eA.ApplicationConfig)
  1220  	m.c.Assert(oA.NumUnits, gc.DeepEquals, eA.NumUnits)
  1221  	m.c.Assert(oA.Constraints, gc.DeepEquals, eA.Constraints)
  1222  	m.c.Assert(oA.Storage, gc.DeepEquals, eA.Storage)
  1223  	m.c.Assert(oA.Devices, gc.DeepEquals, eA.Devices)
  1224  	m.c.Assert(eA.AttachStorage, gc.DeepEquals, eA.AttachStorage)
  1225  	m.c.Assert(oA.EndpointBindings, gc.DeepEquals, eA.EndpointBindings)
  1226  	m.c.Assert(oA.CharmConfig, gc.DeepEquals, eA.CharmConfig)
  1227  	m.c.Assert(oA.Placement, gc.DeepEquals, eA.Placement)
  1228  	m.c.Assert(oA.Resources, gc.DeepEquals, eA.Resources)
  1229  	return true
  1230  }
  1231  
  1232  func (s *deployRepositorySuite) TestAddPendingResourcesForDeployFromRepositoryAPI(c *gc.C) {
  1233  	defer s.setupMocks(c).Finish()
  1234  	arg := params.DeployFromRepositoryArg{
  1235  		CharmName: "testme",
  1236  	}
  1237  	pendUp := &params.PendingResourceUpload{
  1238  		Name:     "foo-resource",
  1239  		Type:     "file",
  1240  		Filename: "bar",
  1241  	}
  1242  	meta := resource.Meta{
  1243  		Name:        "foo-resource",
  1244  		Type:        resource.TypeFile,
  1245  		Path:        "foo.txt",
  1246  		Description: "bar",
  1247  	}
  1248  	r := resource.Resource{
  1249  		Meta:   meta,
  1250  		Origin: resource.OriginUpload,
  1251  	}
  1252  
  1253  	template := deployTemplate{
  1254  		applicationName: "metadata-name",
  1255  		charm:           corecharm.NewCharmInfoAdapter(corecharm.EssentialMetadata{}),
  1256  		charmURL:        charm.MustParseURL("ch:amd64/jammy/testme-5"),
  1257  		endpoints:       map[string]string{"to": "from"},
  1258  		numUnits:        1,
  1259  		origin: corecharm.Origin{
  1260  			Source:   "charm-hub",
  1261  			Revision: intptr(5),
  1262  			Channel:  &charm.Channel{Risk: "stable"},
  1263  			Platform: corecharm.MustParsePlatform("amd64/ubuntu/22.04"),
  1264  		},
  1265  		placement:              []*instance.Placement{{Directive: "0", Scope: instance.MachineScope}},
  1266  		resources:              map[string]string{"foo-file": "bar"},
  1267  		pendingResourceUploads: []*params.PendingResourceUpload{pendUp},
  1268  		resolvedResources:      []resource.Resource{r},
  1269  	}
  1270  	s.validator.EXPECT().ValidateArg(arg).Return(template, nil)
  1271  	info := state.CharmInfo{
  1272  		Charm: template.charm,
  1273  		ID:    "ch:amd64/jammy/testme-5",
  1274  	}
  1275  
  1276  	s.state.EXPECT().AddCharmMetadata(info).Return(s.charm, nil)
  1277  
  1278  	s.state.EXPECT().AddPendingResource("metadata-name", r).Return("3", nil)
  1279  
  1280  	addAppArgs := state.AddApplicationArgs{
  1281  		Name: "metadata-name",
  1282  		// the app.Charm is casted into a state.Charm in the code
  1283  		// we mock it separately here (s.charm above), the test works
  1284  		// thanks to the addApplicationArgsMatcher used below
  1285  		Charm: &state.Charm{},
  1286  		CharmOrigin: &state.CharmOrigin{
  1287  			Source:   "charm-hub",
  1288  			Revision: intptr(5),
  1289  			Channel: &state.Channel{
  1290  				Risk: "stable",
  1291  			},
  1292  			Platform: &state.Platform{
  1293  				Architecture: "amd64",
  1294  				OS:           "ubuntu",
  1295  				Channel:      "22.04",
  1296  			},
  1297  		},
  1298  		Devices:          map[string]state.DeviceConstraints{},
  1299  		EndpointBindings: map[string]string{"to": "from"},
  1300  		NumUnits:         1,
  1301  		Placement:        []*instance.Placement{{Directive: "0", Scope: instance.MachineScope}},
  1302  		Resources:        map[string]string{"foo-resource": "3"},
  1303  		Storage:          map[string]state.StorageConstraints{},
  1304  	}
  1305  	s.state.EXPECT().AddApplication(addApplicationArgsMatcher{c: c, expectedArgs: addAppArgs}).Return(s.application, nil)
  1306  
  1307  	deployFromRepositoryAPI := s.getDeployFromRepositoryAPI()
  1308  
  1309  	obtainedInfo, resources, errs := deployFromRepositoryAPI.DeployFromRepository(arg)
  1310  	c.Assert(errs, gc.HasLen, 0)
  1311  	c.Assert(resources, gc.HasLen, 1)
  1312  	c.Assert(obtainedInfo, gc.DeepEquals, params.DeployFromRepositoryInfo{
  1313  		Architecture:     "amd64",
  1314  		Base:             params.Base{Name: "ubuntu", Channel: "22.04"},
  1315  		Channel:          "stable",
  1316  		EffectiveChannel: nil,
  1317  		Name:             "metadata-name",
  1318  		Revision:         5,
  1319  	})
  1320  
  1321  	c.Assert(resources, gc.DeepEquals, []*params.PendingResourceUpload{pendUp})
  1322  }
  1323  
  1324  func (s *deployRepositorySuite) TestRemovePendingResourcesWhenDeployErrors(c *gc.C) {
  1325  	defer s.setupMocks(c).Finish()
  1326  	arg := params.DeployFromRepositoryArg{
  1327  		CharmName: "testme",
  1328  	}
  1329  	pendUp := &params.PendingResourceUpload{
  1330  		Name:     "foo-resource",
  1331  		Type:     "file",
  1332  		Filename: "bar",
  1333  	}
  1334  	meta := resource.Meta{
  1335  		Name:        "foo-resource",
  1336  		Type:        resource.TypeFile,
  1337  		Path:        "foo.txt",
  1338  		Description: "bar",
  1339  	}
  1340  	r := resource.Resource{
  1341  		Meta:   meta,
  1342  		Origin: resource.OriginUpload,
  1343  	}
  1344  	template := deployTemplate{
  1345  		applicationName: "metadata-name",
  1346  		charm:           corecharm.NewCharmInfoAdapter(corecharm.EssentialMetadata{}),
  1347  		charmURL:        charm.MustParseURL("ch:amd64/jammy/testme-5"),
  1348  		endpoints:       map[string]string{"to": "from"},
  1349  		numUnits:        1,
  1350  		origin: corecharm.Origin{
  1351  			Source:   "charm-hub",
  1352  			Revision: intptr(5),
  1353  			Channel:  &charm.Channel{Risk: "stable"},
  1354  			Platform: corecharm.MustParsePlatform("amd64/ubuntu/22.04"),
  1355  		},
  1356  		placement:              []*instance.Placement{{Directive: "0", Scope: instance.MachineScope}},
  1357  		resources:              map[string]string{"foo-file": "bar"},
  1358  		pendingResourceUploads: []*params.PendingResourceUpload{pendUp},
  1359  		resolvedResources:      []resource.Resource{r},
  1360  	}
  1361  	s.validator.EXPECT().ValidateArg(arg).Return(template, nil)
  1362  	info := state.CharmInfo{
  1363  		Charm: template.charm,
  1364  		ID:    "ch:amd64/jammy/testme-5",
  1365  	}
  1366  
  1367  	s.state.EXPECT().AddCharmMetadata(info).Return(s.charm, nil)
  1368  
  1369  	s.state.EXPECT().AddPendingResource("metadata-name", r).Return("3", nil)
  1370  
  1371  	addAppArgs := state.AddApplicationArgs{
  1372  		Name: "metadata-name",
  1373  		// the app.Charm is casted into a state.Charm in the code
  1374  		// we mock it separately here (s.charm above), the test works
  1375  		// thanks to the addApplicationArgsMatcher used below
  1376  		Charm: &state.Charm{},
  1377  		CharmOrigin: &state.CharmOrigin{
  1378  			Source:   "charm-hub",
  1379  			Revision: intptr(5),
  1380  			Channel: &state.Channel{
  1381  				Risk: "stable",
  1382  			},
  1383  			Platform: &state.Platform{
  1384  				Architecture: "amd64",
  1385  				OS:           "ubuntu",
  1386  				Channel:      "22.04",
  1387  			},
  1388  		},
  1389  		Devices:          map[string]state.DeviceConstraints{},
  1390  		EndpointBindings: map[string]string{"to": "from"},
  1391  		NumUnits:         1,
  1392  		Placement:        []*instance.Placement{{Directive: "0", Scope: instance.MachineScope}},
  1393  		Resources:        map[string]string{"foo-resource": "3"},
  1394  		Storage:          map[string]state.StorageConstraints{},
  1395  	}
  1396  
  1397  	s.state.EXPECT().RemovePendingResources("metadata-name", map[string]string{"foo-resource": "3"})
  1398  
  1399  	s.state.EXPECT().AddApplication(addApplicationArgsMatcher{c: c, expectedArgs: addAppArgs}).Return(s.application,
  1400  		errors.New("fail"))
  1401  
  1402  	deployFromRepositoryAPI := s.getDeployFromRepositoryAPI()
  1403  
  1404  	obtainedInfo, resources, errs := deployFromRepositoryAPI.DeployFromRepository(arg)
  1405  	c.Assert(errs, gc.HasLen, 1)
  1406  	c.Assert(resources, gc.HasLen, 0)
  1407  	c.Assert(obtainedInfo, gc.DeepEquals, params.DeployFromRepositoryInfo{})
  1408  }
  1409  
  1410  func (s *deployRepositorySuite) getDeployFromRepositoryAPI() *DeployFromRepositoryAPI {
  1411  	return &DeployFromRepositoryAPI{
  1412  		state:      s.state,
  1413  		validator:  s.validator,
  1414  		stateCharm: func(Charm) *state.Charm { return nil },
  1415  	}
  1416  }
  1417  
  1418  func (s *deployRepositorySuite) setupMocks(c *gc.C) *gomock.Controller {
  1419  	ctrl := gomock.NewController(c)
  1420  	s.charm = NewMockCharm(ctrl)
  1421  	s.state = NewMockDeployFromRepositoryState(ctrl)
  1422  	s.validator = NewMockDeployFromRepositoryValidator(ctrl)
  1423  	return ctrl
  1424  }