diff --git a/api/common/charms/common.go b/api/common/charms/common.go index a7f972f8df23..8223e8fd51fe 100644 --- a/api/common/charms/common.go +++ b/api/common/charms/common.go @@ -390,7 +390,7 @@ func (c *charmImpl) Revision() int { func convertCharmManifest(input *params.CharmManifest) (*charm.Manifest, error) { if input == nil { - return &charm.Manifest{}, nil + return nil, nil } res := []charm.Base(nil) for _, v := range input.Bases { diff --git a/api/common/charms/common_test.go b/api/common/charms/common_test.go index 41b3ee46beb2..1ac9eb3cfb02 100644 --- a/api/common/charms/common_test.go +++ b/api/common/charms/common_test.go @@ -189,7 +189,6 @@ func (s *suite) TestApplicationCharmInfo(c *gc.C) { Name: "foobar", MinJujuVersion: version.MustParse("2.9.0"), }, - Manifest: &charm.Manifest{}, } c.Assert(got, gc.DeepEquals, want) } diff --git a/apiserver/facades/client/bundle/bundle.go b/apiserver/facades/client/bundle/bundle.go index 6fbf71cee59e..f17d95dd202e 100644 --- a/apiserver/facades/client/bundle/bundle.go +++ b/apiserver/facades/client/bundle/bundle.go @@ -210,6 +210,7 @@ func (b *BundleAPI) GetChangesMapArgs(args params.BundleChangesParams) (params.B } return b.doGetBundleChangesMapArgs(args, vs, func(changes []bundlechanges.Change, results *params.BundleChangesMapArgsResults) error { results.Changes = make([]*params.BundleChangesMapArgs, len(changes)) + results.Errors = make([]string, len(changes)) for i, c := range changes { args, err := c.Args() if err != nil { diff --git a/cmd/juju/application/deployer/bundlehandler.go b/cmd/juju/application/deployer/bundlehandler.go index f1a96e660a0e..8c7d9d287952 100644 --- a/cmd/juju/application/deployer/bundlehandler.go +++ b/cmd/juju/application/deployer/bundlehandler.go @@ -339,12 +339,13 @@ func (h *bundleHandler) resolveCharmsAndEndpoints() error { } var base corebase.Base - if spec.Series != "" { - var err error + if spec.Base != "" { + base, err = corebase.ParseBaseFromString(spec.Base) + } else if spec.Series != "" { base, err = corebase.GetBaseFromSeries(spec.Series) - if err != nil { - return errors.Trace(err) - } + } + if err != nil { + return errors.Trace(err) } channel, origin, err := h.constructChannelAndOrigin(ch, base, spec.Channel, cons) diff --git a/core/bundle/changes/changes.go b/core/bundle/changes/changes.go index a5038c51ab7b..4339e7d256d1 100644 --- a/core/bundle/changes/changes.go +++ b/core/bundle/changes/changes.go @@ -95,7 +95,10 @@ func FromData(config ChangesConfig) ([]Change, error) { var addedMachines map[string]*AddMachineChange if resolver.bundle.Type != kubernetes { - addedMachines = resolver.handleMachines() + addedMachines, err = resolver.handleMachines() + if err != nil { + return nil, errors.Trace(err) + } } deployedBundleApps := alreadyDeployedApplicationsFromBundle(model, config.Bundle.Applications) diff --git a/core/bundle/changes/changes_test.go b/core/bundle/changes/changes_test.go index ffbece53167c..70e30200a304 100644 --- a/core/bundle/changes/changes_test.go +++ b/core/bundle/changes/changes_test.go @@ -900,6 +900,193 @@ func (s *changesSuite) TestSimpleBundle(c *gc.C) { s.assertParseData(c, content, expected) } +func (s *changesSuite) TestSimpleBundleWithBases(c *gc.C) { + content := ` + applications: + mediawiki: + charm: ch:mediawiki + base: ubuntu@20.04 + num_units: 1 + expose: true + options: + debug: false + annotations: + gui-x: "609" + gui-y: "-15" + resources: + data: 3 + mysql: + charm: ch:mysql + base: ubuntu@20.04 + num_units: 1 + resources: + data: "./resources/data.tar" + default-base: ubuntu@22.04 + relations: + - - mediawiki:db + - mysql:db + ` + expected := []record{{ + Id: "addCharm-0", + Method: "addCharm", + Params: bundlechanges.AddCharmParams{ + Charm: "ch:mediawiki", + Series: "focal", + }, + GUIArgs: []interface{}{"ch:mediawiki", "focal", ""}, + Args: map[string]interface{}{ + "charm": "ch:mediawiki", + "series": "focal", + }, + }, { + Id: "deploy-1", + Method: "deploy", + Params: bundlechanges.AddApplicationParams{ + Charm: "$addCharm-0", + Application: "mediawiki", + Series: "focal", + Options: map[string]interface{}{"debug": false}, + Resources: map[string]int{"data": 3}, + }, + GUIArgs: []interface{}{ + "$addCharm-0", + "focal", + "mediawiki", + map[string]interface{}{"debug": false}, + "", + map[string]string{}, + map[string]string{}, + map[string]int{"data": 3}, + 0, + "", + }, + Args: map[string]interface{}{ + "application": "mediawiki", + "charm": "$addCharm-0", + "options": map[string]interface{}{ + "debug": false, + }, + "resources": map[string]interface{}{ + "data": float64(3), + }, + "series": "focal", + }, + Requires: []string{"addCharm-0"}, + }, { + Id: "expose-2", + Method: "expose", + Params: bundlechanges.ExposeParams{ + Application: "$deploy-1", + }, + GUIArgs: []interface{}{"$deploy-1", nil}, + Args: map[string]interface{}{ + "application": "$deploy-1", + }, + Requires: []string{"deploy-1"}, + }, { + Id: "setAnnotations-3", + Method: "setAnnotations", + Params: bundlechanges.SetAnnotationsParams{ + Id: "$deploy-1", + EntityType: bundlechanges.ApplicationType, + Annotations: map[string]string{"gui-x": "609", "gui-y": "-15"}, + }, + GUIArgs: []interface{}{ + "$deploy-1", + "application", + map[string]string{"gui-x": "609", "gui-y": "-15"}, + }, + Args: map[string]interface{}{ + "annotations": map[string]interface{}{ + "gui-x": "609", + "gui-y": "-15", + }, + "entity-type": "application", + "id": "$deploy-1", + }, + Requires: []string{"deploy-1"}, + }, { + Id: "addCharm-4", + Method: "addCharm", + Params: bundlechanges.AddCharmParams{ + Charm: "ch:mysql", + Series: "focal", + }, + GUIArgs: []interface{}{"ch:mysql", "focal", ""}, + Args: map[string]interface{}{ + "charm": "ch:mysql", + "series": "focal", + }, + }, { + Id: "deploy-5", + Method: "deploy", + Params: bundlechanges.AddApplicationParams{ + Charm: "$addCharm-4", + Application: "mysql", + Series: "focal", + LocalResources: map[string]string{"data": "./resources/data.tar"}, + }, + GUIArgs: []interface{}{ + "$addCharm-4", + "focal", + "mysql", + map[string]interface{}{}, + "", + map[string]string{}, + map[string]string{}, + map[string]int{}, + 0, + "", + }, + Args: map[string]interface{}{ + "application": "mysql", + "charm": "$addCharm-4", + "local-resources": map[string]interface{}{ + "data": "./resources/data.tar", + }, + "series": "focal", + }, + Requires: []string{"addCharm-4"}, + }, { + Id: "addRelation-6", + Method: "addRelation", + Params: bundlechanges.AddRelationParams{ + Endpoint1: "$deploy-1:db", + Endpoint2: "$deploy-5:db", + }, + GUIArgs: []interface{}{"$deploy-1:db", "$deploy-5:db"}, + Args: map[string]interface{}{ + "endpoint1": "$deploy-1:db", + "endpoint2": "$deploy-5:db", + }, + Requires: []string{"deploy-1", "deploy-5"}, + }, { + Id: "addUnit-7", + Method: "addUnit", + Params: bundlechanges.AddUnitParams{ + Application: "$deploy-1", + }, + GUIArgs: []interface{}{"$deploy-1", nil}, + Args: map[string]interface{}{ + "application": "$deploy-1", + }, + Requires: []string{"deploy-1"}, + }, { + Id: "addUnit-8", + Method: "addUnit", + Params: bundlechanges.AddUnitParams{ + Application: "$deploy-5", + }, + GUIArgs: []interface{}{"$deploy-5", nil}, + Args: map[string]interface{}{ + "application": "$deploy-5", + }, + Requires: []string{"deploy-5"}, + }} + + s.assertParseData(c, content, expected) +} + func (s *changesSuite) TestSimpleBundleWithDevices(c *gc.C) { content := ` applications: diff --git a/core/bundle/changes/handlers.go b/core/bundle/changes/handlers.go index 6798b45e8e2b..4db0f0b131a3 100644 --- a/core/bundle/changes/handlers.go +++ b/core/bundle/changes/handlers.go @@ -33,7 +33,16 @@ type resolver struct { func (r *resolver) handleApplications() (map[string]string, error) { add := r.changes.add applications := r.bundle.Applications - defaultSeries := r.bundle.Series + + var defaultSeries string + if r.bundle.Series != "" { + defaultSeries = r.bundle.Series + } else if r.bundle.DefaultBase != "" { + var err error + if defaultSeries, err = baseToSeries(r.bundle.DefaultBase); err != nil { + return nil, errors.Trace(err) + } + } existing := r.model charms := make(map[string]string, len(applications)) @@ -405,12 +414,21 @@ func equalStringSlice(a, b []string) bool { // handleMachines populates the change set with "addMachines" records. // This function also handles adding machine annotations. -func (r *resolver) handleMachines() map[string]*AddMachineChange { +func (r *resolver) handleMachines() (map[string]*AddMachineChange, error) { add := r.changes.add machines := r.bundle.Machines - defaultSeries := r.bundle.Series existing := r.model + var defaultSeries string + if r.bundle.Series != "" { + defaultSeries = r.bundle.Series + } else if r.bundle.DefaultBase != "" { + var err error + if defaultSeries, err = baseToSeries(r.bundle.DefaultBase); err != nil { + return nil, errors.Trace(err) + } + } + addedMachines := make(map[string]*AddMachineChange, len(machines)) // Iterate over the map using its sorted keys so that results are // deterministic and easier to test. @@ -427,11 +445,15 @@ func (r *resolver) handleMachines() map[string]*AddMachineChange { if machine == nil { machine = &charm.MachineSpec{} } - series := machine.Series - if series == "" { - series = defaultSeries + series := defaultSeries + if machine.Series != "" { + series = machine.Series + } else if machine.Base != "" { + var err error + if series, err = baseToSeries(machine.Base); err != nil { + return nil, errors.Trace(err) + } } - var id string var target string var requires []string @@ -469,7 +491,7 @@ func (r *resolver) handleMachines() map[string]*AddMachineChange { }, requires...)) } } - return addedMachines + return addedMachines, nil } // handleRelations populates the change set with "addRelation" records. @@ -1140,11 +1162,22 @@ func (r *resolver) handleUnits(addedApplications map[string]string, addedMachine machChangeIDs.Add(v.Id()) } + var defaultSeries string + if r.bundle.Series != "" { + defaultSeries = r.bundle.Series + } else if r.bundle.DefaultBase != "" { + var err error + defaultSeries, err = baseToSeries(r.bundle.DefaultBase) + if err != nil { + return errors.Trace(err) + } + } + processor := &unitProcessor{ add: r.changes.add, existing: r.model, bundle: r.bundle, - defaultSeries: r.bundle.Series, + defaultSeries: defaultSeries, logger: r.logger, addedApplications: addedApplications, addedMachines: addedMachines, @@ -1260,6 +1293,9 @@ func getSeries(application *charm.ApplicationSpec, defaultSeries string) (string if application.Series != "" { return application.Series, nil } + if application.Base != "" { + return baseToSeries(application.Base) + } // Handle local charm paths. if charm.IsValidLocalCharmOrBundlePath(application.Charm) { @@ -1295,6 +1331,18 @@ func getSeries(application *charm.ApplicationSpec, defaultSeries string) (string return defaultSeries, nil } +func baseToSeries(b string) (string, error) { + base, err := corebase.ParseBaseFromString(b) + if err != nil { + return "", errors.Trace(err) + } + series, err := corebase.GetSeriesFromBase(base) + if err != nil { + return "", errors.Trace(err) + } + return series, nil +} + // parseEndpoint creates an endpoint from its string representation. func parseEndpoint(e string) *endpoint { parts := strings.SplitN(e, ":", 2)