diff --git a/cmd/juju/application/deployer/deployer.go b/cmd/juju/application/deployer/deployer.go index ac9cc938a21..8209df64849 100644 --- a/cmd/juju/application/deployer/deployer.go +++ b/cmd/juju/application/deployer/deployer.go @@ -266,6 +266,10 @@ func (d *factory) maybeReadLocalBundle() (Deployer, error) { if err != nil { return nil, errors.Annotate(err, "cannot deploy bundle") } + if err = handleUnmarshallError(ds.Parts()); err != nil && !d.force { + return nil, errors.Annotate(err, "cannot deploy bundle, invalid fields") + } + if err := d.validateBundleFlags(); err != nil { return nil, errors.Trace(err) } @@ -284,6 +288,22 @@ func (d *factory) maybeReadLocalBundle() (Deployer, error) { return &localBundle{deployBundle: db}, nil } +func handleUnmarshallError(parts []*charm.BundleDataPart) error { + messages := set.NewStrings() + for _, part := range parts { + if part == nil { + continue + } + if part.UnmarshallError != nil { + messages.Add(part.UnmarshallError.Error()) + } + } + if messages.IsEmpty() { + return nil + } + return errors.New(strings.Join(messages.Values(), "\n")) +} + // newDeployBundle returns the config needed to eventually call // deployBundle.deploy. This is used by all types of bundles to // be deployed diff --git a/cmd/juju/application/deployer/deployer_test.go b/cmd/juju/application/deployer/deployer_test.go index 5759d73620f..1488fb5c0f1 100644 --- a/cmd/juju/application/deployer/deployer_test.go +++ b/cmd/juju/application/deployer/deployer_test.go @@ -235,6 +235,54 @@ func (s *deployerSuite) TestGetDeployerLocalBundle(c *gc.C) { c.Assert(deployer.String(), gc.Equals, fmt.Sprintf("deploy local bundle from: %s", bundlePath)) } +func (s *deployerSuite) TestGetDeployerLocalBundleStrict(c *gc.C) { + defer s.setupMocks(c).Finish() + + _, err := s.testGetDeployerLocalBundleStrict(c, false) + c.Assert(err.Error(), gc.Equals, ("cannot deploy bundle, invalid fields: unmarshal document 0: yaml: unmarshal errors:\n line 3: field descriptn not found in bundle\n line 8: field contstraints not found in applications")) +} + +func (s *deployerSuite) TestGetDeployerLocalBundleStrictUseForce(c *gc.C) { + defer s.setupMocks(c).Finish() + + deployer, err := s.testGetDeployerLocalBundleStrict(c, true) + c.Assert(err, jc.ErrorIsNil) + c.Assert(deployer.String(), gc.Matches, "deploy local bundle from: .*") + +} + +func (s *deployerSuite) testGetDeployerLocalBundleStrict(c *gc.C, force bool) (Deployer, error) { + s.expectFilesystem() + + cfg := s.basicDeployerConfig() + cfg.Series = "bionic" + cfg.FlagSet = &gnuflag.FlagSet{} + cfg.Force = force + s.expectModelType() + + content := ` + series: xenial + descriptn: bundle to fail strict parsing + applications: + wordpress: + charm: wordpress + num_units: 1 + contstraints: "mem=8G" + mysql: + charm: mysql + num_units: 2 + relations: + - ["wordpress:db", "mysql:server"] +` + + bundlePath := s.makeBundleDir(c, content) + s.expectStat(bundlePath, nil) + cfg.CharmOrBundle = bundlePath + + factory := s.newDeployerFactory() + return factory.GetDeployer(cfg, s.modelConfigGetter, s.resolver) +} + func (s *deployerSuite) TestGetDeployerCharmStoreBundle(c *gc.C) { bundle := charm.MustParseURL("cs:test-bundle") cfg := s.basicDeployerConfig()