diff --git a/model.go b/model.go index 5729d19..d3994ad 100644 --- a/model.go +++ b/model.go @@ -33,6 +33,9 @@ type Model interface { HasStatus HasStatusHistory + // AgentVersion returns the version currently in use by the model. + AgentVersion() string + Type() string Cloud() string CloudRegion() string @@ -144,6 +147,9 @@ type Model interface { // ModelArgs represent the bare minimum information that is needed // to represent a model. type ModelArgs struct { + // AgentVersion defines the current version in use by the model. + AgentVersion string + Type string Owner names.UserTag Config map[string]interface{} @@ -159,7 +165,8 @@ type ModelArgs struct { // NewModel returns a Model based on the args specified. func NewModel(args ModelArgs) Model { m := &model{ - Version: 10, + Version: 11, + AgentVersion_: args.AgentVersion, Type_: args.Type, Owner_: args.Owner.Id(), Config_: args.Config, @@ -254,6 +261,9 @@ func parentId(machineId string) string { type model struct { Version int `yaml:"version"` + // AgentVersion_ defines the agent version in use by the model. + AgentVersion_ string `yaml:"agent-version"` + Type_ string `yaml:"type"` Owner_ string `yaml:"owner"` Config_ map[string]interface{} `yaml:"config"` @@ -314,6 +324,11 @@ type model struct { PasswordHash_ string `yaml:"password-hash,omitempty"` } +// AgentVersion returns the current agent version in use the by the model. +func (m *model) AgentVersion() string { + return m.AgentVersion_ +} + func (m *model) Type() string { return m.Type_ } @@ -1092,6 +1107,15 @@ func (m *model) Validate() error { return errors.NotValidf("missing status") } + if m.AgentVersion_ != "" { + agentVersion, err := version.Parse(m.AgentVersion_) + if err != nil { + return errors.Annotate(err, "agent version not parsable") + } else if agentVersion == version.Zero { + return errors.NotValidf("agent version cannot be zero") + } + } + validationCtx := newValidationContext() for _, machine := range m.Machines_.Machines_ { if err := m.validateMachine(validationCtx, machine); err != nil { @@ -1522,6 +1546,7 @@ var modelDeserializationFuncs = map[int]modelDeserializationFunc{ 8: newModelImporter(8, schema.FieldMap(modelV8Fields())), 9: newModelImporter(9, schema.FieldMap(modelV9Fields())), 10: newModelImporter(10, schema.FieldMap(modelV10Fields())), + 11: newModelImporter(11, schema.FieldMap(modelV11Fields())), } func modelV1Fields() (schema.Fields, schema.Defaults) { @@ -1635,11 +1660,17 @@ func modelV10Fields() (schema.Fields, schema.Defaults) { return fields, defaults } +func modelV11Fields() (schema.Fields, schema.Defaults) { + fields, defaults := modelV10Fields() + fields["agent-version"] = schema.String() + return fields, defaults +} + func newModelFromValid(valid map[string]interface{}, importVersion int) (*model, error) { // We're always making a version 8 model, no matter what we got on // the way in. result := &model{ - Version: 10, + Version: 11, Type_: IAAS, Owner_: valid["owner"].(string), Config_: valid["config"].(map[string]interface{}), @@ -1903,6 +1934,16 @@ func newModelFromValid(valid map[string]interface{}, importVersion int) (*model, result.SecretBackendID_ = valid["secret-backend-id"].(string) } + + // When we are importing v11 onwards agent version will be a first class + // citizen on the model. Before this we can attempt to get the value from + // config. + if importVersion >= 11 { + result.AgentVersion_ = valid["agent-version"].(string) + } else if result.Config_ != nil && result.Config_["agent-version"] != nil { + result.AgentVersion_ = result.Config_["agent-version"].(string) + } + return result, nil } diff --git a/model_test.go b/model_test.go index 241b827..8e9fb3a 100644 --- a/model_test.go +++ b/model_test.go @@ -171,8 +171,9 @@ func (s *ModelSerializationSuite) TestParsingYAMLWithMissingModificationStatus(c func (s *ModelSerializationSuite) testParsingYAMLWithMachine(c *gc.C, machineFn func(Model)) { args := ModelArgs{ - Type: IAAS, - Owner: names.NewUserTag("magic"), + AgentVersion: "3.1.1", + Type: IAAS, + Owner: names.NewUserTag("magic"), Config: map[string]interface{}{ "name": "awesome", "uuid": "some-uuid", @@ -202,6 +203,7 @@ func (s *ModelSerializationSuite) testParsingYAMLWithMachine(c *gc.C, machineFn addMinimalApplication(initial) model := s.exportImport(c, initial) + c.Check(model.AgentVersion(), gc.Equals, "3.1.1") c.Assert(model.Type(), gc.Equals, IAAS) c.Assert(model.Owner(), gc.Equals, args.Owner) c.Assert(model.Tag().Id(), gc.Equals, "some-uuid") @@ -1165,7 +1167,7 @@ func (s *ModelSerializationSuite) TestSerializesToLatestVersion(c *gc.C) { c.Assert(ok, jc.IsTrue) version, ok := versionValue.(int) c.Assert(ok, jc.IsTrue) - c.Assert(version, gc.Equals, 10) + c.Assert(version, gc.Equals, 11) } func (s *ModelSerializationSuite) TestVersion1Works(c *gc.C) { @@ -1634,6 +1636,23 @@ func (s *ModelSerializationSuite) TestRemoteSecretsValidate(c *gc.C) { c.Assert(err, gc.ErrorMatches, `remote secret\[0\] consumer \(foo\) not valid`) } +func (s *ModelSerializationSuite) TestAgentVersionPre11Import(c *gc.C) { + initial := s.newModel(ModelArgs{ + Config: map[string]any{ + "agent-version": "3.3.3", + }, + }) + data := asStringMap(c, initial) + data["version"] = 10 + bytes, err := yaml.Marshal(data) + c.Assert(err, jc.ErrorIsNil) + + model, err := Deserialize(bytes) + c.Check(err, jc.ErrorIsNil) + + c.Check(model.AgentVersion(), gc.Equals, "3.3.3") +} + // modelV1example was taken from a Juju 2.1 model dump, which is version // 1, and among other things is missing model status, which version 2 makes // manditory.