diff --git a/application.go b/application.go index 6d3cf7a..03a0199 100644 --- a/application.go +++ b/application.go @@ -117,6 +117,9 @@ type application struct { EndpointBindings_ map[string]string `yaml:"endpoint-bindings,omitempty"` + // CharmConfig_ and ApplicationConfig_ are the actual configuration values + // for the charm and application, respectively. These are the values that + // have been set by the user or the charm itself. CharmConfig_ map[string]interface{} `yaml:"settings"` ApplicationConfig_ map[string]interface{} `yaml:"application-config,omitempty"` @@ -153,9 +156,14 @@ type application struct { // CharmOrigin fields CharmOrigin_ *charmOrigin `yaml:"charm-origin,omitempty"` - // CharmMetadata and CharmManifest fields + + // The following fields represent the actual charm data for the + // application. These are the immutable parts of the application, either + // provided by the charm itself. CharmMetadata_ *charmMetadata `yaml:"charm-metadata,omitempty"` CharmManifest_ *charmManifest `yaml:"charm-manifest,omitempty"` + CharmActions_ *charmActions `yaml:"charm-actions,omitempty"` + CharmConfigs_ *charmConfigs `yaml:"charm-configs,omitempty"` } // ApplicationArgs is an argument struct used to add an application to the Model. @@ -554,6 +562,34 @@ func (a *application) SetCharmManifest(args CharmManifestArgs) { a.CharmManifest_ = newCharmManifest(args) } +// CharmActions implements Application. +func (a *application) CharmActions() CharmActions { + // To avoid a typed nil, check before returning. + if a.CharmActions_ == nil { + return nil + } + return a.CharmActions_ +} + +// SetCharmActions implements Application. +func (a *application) SetCharmActions(args CharmActionsArgs) { + a.CharmActions_ = newCharmActions(args) +} + +// CharmConfigs implements Application. +func (a *application) CharmConfigs() CharmConfigs { + // To avoid a typed nil, check before returning. + if a.CharmConfigs_ == nil { + return nil + } + return a.CharmConfigs_ +} + +// SetCharmConfigs implements Application. +func (a *application) SetCharmConfigs(args CharmConfigsArgs) { + a.CharmConfigs_ = newCharmConfigs(args) +} + // Offers implements Application. func (a *application) Offers() []ApplicationOffer { if a.Offers_ == nil || len(a.Offers_.Offers) == 0 { @@ -811,8 +847,12 @@ func applicationV13Fields() (schema.Fields, schema.Defaults) { fields, defaults := applicationV12Fields() fields["charm-metadata"] = schema.StringMap(schema.Any()) fields["charm-manifest"] = schema.StringMap(schema.Any()) + fields["charm-actions"] = schema.StringMap(schema.Any()) + fields["charm-configs"] = schema.StringMap(schema.Any()) defaults["charm-metadata"] = schema.Omit defaults["charm-manifest"] = schema.Omit + defaults["charm-actions"] = schema.Omit + defaults["charm-configs"] = schema.Omit return fields, defaults } @@ -991,6 +1031,10 @@ func importApplication(fields schema.Fields, defaults schema.Defaults, importVer } if importVersion >= 13 { + // These fields are used to populate the charm data for the application. + // This ensures that correct RI is maintained for the charm data + // when migrating between models. + if charmMetadataMap, ok := valid["charm-metadata"]; ok { charmMetadata, err := importCharmMetadata(charmMetadataMap.(map[string]interface{})) if err != nil { @@ -1006,6 +1050,22 @@ func importApplication(fields schema.Fields, defaults schema.Defaults, importVer } result.CharmManifest_ = charmManifest } + + if charmActionsMap, ok := valid["charm-actions"]; ok { + charmActions, err := importCharmActions(charmActionsMap.(map[string]interface{})) + if err != nil { + return nil, errors.Trace(err) + } + result.CharmActions_ = charmActions + } + + if charmConfigMap, ok := valid["charm-configs"]; ok { + charmConfig, err := importCharmConfigs(charmConfigMap.(map[string]interface{})) + if err != nil { + return nil, errors.Trace(err) + } + result.CharmConfigs_ = charmConfig + } } result.importAnnotations(valid) diff --git a/application_test.go b/application_test.go index cd17945..a94e22d 100644 --- a/application_test.go +++ b/application_test.go @@ -67,6 +67,8 @@ func minimalApplicationMap() map[interface{}]interface{} { "charm-origin": minimalCharmOriginMap(), "charm-metadata": minimalCharmMetadataMap(), "charm-manifest": minimalCharmManifestMap(), + "charm-actions": minimalCharmActionsMap(), + "charm-configs": minimalCharmConfigsMap(), } } @@ -108,6 +110,8 @@ func minimalApplicationMapCAAS() map[interface{}]interface{} { result["charm-origin"] = minimalCharmOriginMap() result["charm-metadata"] = minimalCharmMetadataMap() result["charm-manifest"] = minimalCharmManifestMap() + result["charm-actions"] = minimalCharmActionsMap() + result["charm-configs"] = minimalCharmConfigsMap() return result } @@ -130,6 +134,8 @@ func minimalApplication(args ...ApplicationArgs) *application { a.SetCharmOrigin(minimalCharmOriginArgs()) a.SetCharmMetadata(minimalCharmMetadataArgs()) a.SetCharmManifest(minimalCharmManifestArgs()) + a.SetCharmActions(minimalCharmActionsArgs()) + a.SetCharmConfigs(minimalCharmConfigsArgs()) return a } @@ -391,6 +397,8 @@ func (s *ApplicationSerializationSuite) TestV1ParsingReturnsLatest(c *gc.C) { appLatest.CharmOrigin_ = nil appLatest.CharmMetadata_ = nil appLatest.CharmManifest_ = nil + appLatest.CharmActions_ = nil + appLatest.CharmConfigs_ = nil appResult := s.exportImportVersion(c, appV1, 1) appLatest.Series_ = "" @@ -416,6 +424,8 @@ func (s *ApplicationSerializationSuite) TestV2ParsingReturnsLatest(c *gc.C) { appLatest.CharmOrigin_ = nil appLatest.CharmMetadata_ = nil appLatest.CharmManifest_ = nil + appLatest.CharmActions_ = nil + appLatest.CharmConfigs_ = nil appResult := s.exportImportVersion(c, appV1, 2) appLatest.Series_ = "" @@ -437,6 +447,8 @@ func (s *ApplicationSerializationSuite) TestV3ParsingReturnsLatest(c *gc.C) { appLatest.CharmOrigin_ = nil appLatest.CharmMetadata_ = nil appLatest.CharmManifest_ = nil + appLatest.CharmActions_ = nil + appLatest.CharmConfigs_ = nil appResult := s.exportImportVersion(c, appV2, 3) appLatest.Series_ = "" @@ -454,6 +466,8 @@ func (s *ApplicationSerializationSuite) TestV5ParsingReturnsLatest(c *gc.C) { appLatest.CharmOrigin_ = nil appLatest.CharmMetadata_ = nil appLatest.CharmManifest_ = nil + appLatest.CharmActions_ = nil + appLatest.CharmConfigs_ = nil appResult := s.exportImportVersion(c, appV5, 5) appLatest.Series_ = "" @@ -470,6 +484,8 @@ func (s *ApplicationSerializationSuite) TestV6ParsingReturnsLatest(c *gc.C) { appLatest.CharmOrigin_ = nil appLatest.CharmMetadata_ = nil appLatest.CharmManifest_ = nil + appLatest.CharmActions_ = nil + appLatest.CharmConfigs_ = nil appResult := s.exportImportVersion(c, appV6, 6) appLatest.Series_ = "" diff --git a/charmactions.go b/charmactions.go new file mode 100644 index 0000000..020f5f7 --- /dev/null +++ b/charmactions.go @@ -0,0 +1,161 @@ +// Copyright 2024 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package description + +import ( + "github.com/juju/errors" + "github.com/juju/schema" +) + +type CharmActionsArgs struct { + Actions map[string]CharmAction +} + +func newCharmActions(args CharmActionsArgs) *charmActions { + actions := make(map[string]charmAction) + if args.Actions != nil { + for name, a := range args.Actions { + actions[name] = charmAction{ + Description_: a.Description(), + Parallel_: a.Parallel(), + ExecutionGroup_: a.ExecutionGroup(), + Parameters_: a.Parameters(), + } + } + } + + return &charmActions{ + Version_: 1, + Actions_: actions, + } +} + +type charmActions struct { + Version_ int `yaml:"version"` + Actions_ map[string]charmAction `yaml:"actions"` +} + +// Actions returns the actions of the charm. +func (a *charmActions) Actions() map[string]CharmAction { + actions := make(map[string]CharmAction) + for i, b := range a.Actions_ { + actions[i] = b + } + return actions +} + +func importCharmActions(source map[string]interface{}) (*charmActions, error) { + version, err := getVersion(source) + if err != nil { + return nil, errors.Annotate(err, "charmActions version schema check failed") + } + + importFunc, ok := charmActionsDeserializationFuncs[version] + if !ok { + return nil, errors.NotValidf("version %d", version) + } + return importFunc(source) +} + +var charmActionsDeserializationFuncs = map[int]func(map[string]interface{}) (*charmActions, error){ + 1: importCharmActionsV1, +} + +func importCharmActionsV1(source map[string]interface{}) (*charmActions, error) { + return importCharmActionsVersion(source, 1) +} + +func importCharmActionsVersion(source map[string]interface{}, version int) (*charmActions, error) { + fields := schema.Fields{ + "actions": schema.StringMap(schema.Any()), + } + defaults := schema.Defaults{ + "actions": schema.Omit, + } + checker := schema.FieldMap(fields, defaults) + + coerced, err := checker.Coerce(source, nil) + if err != nil { + return nil, errors.Annotatef(err, "charmActions v1 schema check failed") + } + valid := coerced.(map[string]interface{}) + + var actions map[string]charmAction + if valid["actions"] != nil { + actions = make(map[string]charmAction, len(valid["actions"].(map[string]interface{}))) + for name, v := range valid["actions"].(map[string]interface{}) { + var err error + actions[name], err = importCharmAction(v) + if err != nil { + return nil, errors.Annotate(err, "charmActions actions schema check failed") + } + } + } + + return &charmActions{ + Version_: 1, + Actions_: actions, + }, nil +} + +func importCharmAction(source interface{}) (charmAction, error) { + fields := schema.Fields{ + "description": schema.String(), + "parallel": schema.Bool(), + "execution-group": schema.String(), + "parameters": schema.StringMap(schema.Any()), + } + defaults := schema.Defaults{ + "description": schema.Omit, + "parallel": false, + "execution-group": schema.Omit, + "parameters": schema.Omit, + } + checker := schema.FieldMap(fields, defaults) + + coerced, err := checker.Coerce(source, nil) + if err != nil { + return charmAction{}, errors.Annotatef(err, "charmAction schema check failed") + } + valid := coerced.(map[string]interface{}) + + var parameters map[string]interface{} + if valid["parameters"] != nil { + parameters = valid["parameters"].(map[string]interface{}) + } + + return charmAction{ + Description_: valid["description"].(string), + Parallel_: valid["parallel"].(bool), + ExecutionGroup_: valid["execution-group"].(string), + Parameters_: parameters, + }, nil +} + +type charmAction struct { + Description_ string `yaml:"description"` + Parallel_ bool `yaml:"parallel"` + ExecutionGroup_ string `yaml:"execution-group"` + Parameters_ map[string]interface{} `yaml:"parameters"` +} + +// Description returns the description of the action. +func (a charmAction) Description() string { + return a.Description_ +} + +// Parallel returns whether the action can be run in parallel. +func (a charmAction) Parallel() bool { + return a.Parallel_ +} + +// ExecutionGroup returns the execution group of the action. +func (a charmAction) ExecutionGroup() string { + return a.ExecutionGroup_ +} + +// Parameters returns the parameters of the action. +func (a charmAction) Parameters() map[string]interface{} { + return a.Parameters_ +} diff --git a/charmactions_test.go b/charmactions_test.go new file mode 100644 index 0000000..39e2988 --- /dev/null +++ b/charmactions_test.go @@ -0,0 +1,171 @@ +// Copyright 2024 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package description + +import ( + jc "github.com/juju/testing/checkers" + gc "gopkg.in/check.v1" + "gopkg.in/yaml.v2" +) + +type CharmActionsSerializationSuite struct { + SerializationSuite +} + +var _ = gc.Suite(&CharmActionsSerializationSuite{}) + +func (s *CharmActionsSerializationSuite) SetUpTest(c *gc.C) { + s.importName = "charmActions" + s.importFunc = func(m map[string]interface{}) (interface{}, error) { + return importCharmActions(m) + } +} + +func (s *CharmActionsSerializationSuite) TestNewCharmActions(c *gc.C) { + args := CharmActionsArgs{ + Actions: map[string]CharmAction{ + "echo": charmAction{ + Description_: "echo description", + Parallel_: true, + ExecutionGroup_: "group1", + Parameters_: map[string]interface{}{ + "message": "string", + }, + }, + }, + } + metadata := newCharmActions(args) + + c.Assert(metadata.Actions(), gc.DeepEquals, map[string]CharmAction{ + "echo": charmAction{ + Description_: "echo description", + Parallel_: true, + ExecutionGroup_: "group1", + Parameters_: map[string]interface{}{ + "message": "string", + }, + }, + }) +} + +func minimalCharmActionsMap() map[interface{}]interface{} { + return map[interface{}]interface{}{ + "version": 1, + "actions": map[interface{}]interface{}{}, + } +} + +func minimalCharmActionsArgs() CharmActionsArgs { + return CharmActionsArgs{} +} + +func minimalCharmActions() *charmActions { + return newCharmActions(minimalCharmActionsArgs()) +} + +func maximalCharmActionsMap() map[interface{}]interface{} { + return map[interface{}]interface{}{ + "version": 1, + "actions": map[interface{}]interface{}{ + "echo": map[interface{}]interface{}{ + "description": "echo description", + "parallel": true, + "execution-group": "group1", + "parameters": map[interface{}]interface{}{ + "message": "string", + }, + }, + }, + } +} + +func maximalCharmActionsArgs() CharmActionsArgs { + return CharmActionsArgs{ + Actions: map[string]CharmAction{ + "echo": charmAction{ + Description_: "echo description", + Parallel_: true, + ExecutionGroup_: "group1", + Parameters_: map[string]interface{}{ + "message": "string", + }, + }, + }, + } +} + +func maximalCharmActions() *charmActions { + return newCharmActions(maximalCharmActionsArgs()) +} + +func (s *CharmActionsSerializationSuite) TestMinimalMatches(c *gc.C) { + bytes, err := yaml.Marshal(minimalCharmActions()) + c.Assert(err, jc.ErrorIsNil) + + var source map[interface{}]interface{} + err = yaml.Unmarshal(bytes, &source) + c.Assert(err, jc.ErrorIsNil) + c.Assert(source, jc.DeepEquals, minimalCharmActionsMap()) +} + +func (s *CharmActionsSerializationSuite) TestMaximalMatches(c *gc.C) { + bytes, err := yaml.Marshal(maximalCharmActions()) + c.Assert(err, jc.ErrorIsNil) + + var source map[interface{}]interface{} + err = yaml.Unmarshal(bytes, &source) + c.Assert(err, jc.ErrorIsNil) + c.Assert(source, jc.DeepEquals, maximalCharmActionsMap()) +} + +func (s *CharmActionsSerializationSuite) TestMinimalParsingSerializedData(c *gc.C) { + initial := minimalCharmActions() + bytes, err := yaml.Marshal(initial) + c.Assert(err, jc.ErrorIsNil) + + var source map[string]interface{} + err = yaml.Unmarshal(bytes, &source) + c.Assert(err, jc.ErrorIsNil) + + instance, err := importCharmActions(source) + c.Assert(err, jc.ErrorIsNil) + c.Assert(instance, jc.DeepEquals, initial) +} + +func (s *CharmActionsSerializationSuite) TestMaximalParsingSerializedData(c *gc.C) { + initial := maximalCharmActions() + bytes, err := yaml.Marshal(initial) + c.Assert(err, jc.ErrorIsNil) + + var source map[string]interface{} + err = yaml.Unmarshal(bytes, &source) + c.Assert(err, jc.ErrorIsNil) + + instance, err := importCharmActions(source) + c.Assert(err, jc.ErrorIsNil) + c.Assert(instance, jc.DeepEquals, initial) +} + +func (s *CharmActionsSerializationSuite) exportImportVersion(c *gc.C, origin_ *charmActions, version int) *charmActions { + origin_.Version_ = version + bytes, err := yaml.Marshal(origin_) + c.Assert(err, jc.ErrorIsNil) + + var source map[string]interface{} + err = yaml.Unmarshal(bytes, &source) + c.Assert(err, jc.ErrorIsNil) + + origin, err := importCharmActions(source) + c.Assert(err, jc.ErrorIsNil) + return origin +} + +func (s *CharmActionsSerializationSuite) TestV1ParsingReturnsLatest(c *gc.C) { + args := maximalCharmActionsArgs() + originV1 := newCharmActions(args) + + originLatest := *originV1 + originResult := s.exportImportVersion(c, originV1, 1) + c.Assert(*originResult, jc.DeepEquals, originLatest) +} diff --git a/charmconfigs.go b/charmconfigs.go new file mode 100644 index 0000000..bd970cc --- /dev/null +++ b/charmconfigs.go @@ -0,0 +1,147 @@ +// Copyright 2024 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package description + +import ( + "github.com/juju/errors" + "github.com/juju/schema" +) + +type CharmConfigsArgs struct { + Configs map[string]CharmConfig +} + +func newCharmConfigs(args CharmConfigsArgs) *charmConfigs { + config := make(map[string]charmConfig) + if args.Configs != nil { + for name, c := range args.Configs { + config[name] = charmConfig{ + Type_: c.Type(), + Default_: c.Default(), + Description_: c.Description(), + } + } + } + + return &charmConfigs{ + Version_: 1, + Configs_: config, + } +} + +type charmConfigs struct { + Version_ int `yaml:"version"` + Configs_ map[string]charmConfig `yaml:"configs"` +} + +// Configs returns the configs of the charm. +func (c *charmConfigs) Configs() map[string]CharmConfig { + configs := make(map[string]CharmConfig) + for i, b := range c.Configs_ { + configs[i] = b + } + return configs +} + +func importCharmConfigs(source map[string]interface{}) (*charmConfigs, error) { + version, err := getVersion(source) + if err != nil { + return nil, errors.Annotate(err, "charmConfigs version schema check failed") + } + + importFunc, ok := charmConfigsDeserializationFuncs[version] + if !ok { + return nil, errors.NotValidf("version %d", version) + } + return importFunc(source) +} + +var charmConfigsDeserializationFuncs = map[int]func(map[string]interface{}) (*charmConfigs, error){ + 1: importCharmConfigsV1, +} + +func importCharmConfigsV1(source map[string]interface{}) (*charmConfigs, error) { + return importCharmConfigsVersion(source, 1) +} + +func importCharmConfigsVersion(source map[string]interface{}, version int) (*charmConfigs, error) { + fields := schema.Fields{ + "configs": schema.StringMap(schema.Any()), + } + defaults := schema.Defaults{ + "configs": schema.Omit, + } + checker := schema.FieldMap(fields, defaults) + + coerced, err := checker.Coerce(source, nil) + if err != nil { + return nil, errors.Annotatef(err, "charmConfigs v1 schema check failed") + } + valid := coerced.(map[string]interface{}) + + var configs map[string]charmConfig + if valid["configs"] != nil { + configs = make(map[string]charmConfig) + for name, raw := range valid["configs"].(map[string]interface{}) { + var err error + configs[name], err = importCharmConfig(raw) + if err != nil { + return nil, errors.Annotatef(err, "charmConfig %q schema check failed", name) + } + } + } + + return &charmConfigs{ + Version_: 1, + Configs_: configs, + }, nil +} + +func importCharmConfig(source interface{}) (charmConfig, error) { + fields := schema.Fields{ + "type": schema.String(), + "default": schema.Any(), + "description": schema.String(), + } + defaults := schema.Defaults{ + "type": schema.Omit, + "default": schema.Omit, + "description": schema.Omit, + } + checker := schema.FieldMap(fields, defaults) + + coerced, err := checker.Coerce(source, nil) + if err != nil { + return charmConfig{}, errors.Annotate(err, "charmConfig schema check failed") + } + valid := coerced.(map[string]interface{}) + + config := charmConfig{ + Type_: valid["type"].(string), + Default_: valid["default"], + Description_: valid["description"].(string), + } + return config, nil +} + +type charmConfig struct { + Type_ string `yaml:"type"` + Default_ interface{} `yaml:"default"` + Description_ string `yaml:"description"` +} + +// Type returns the type of the config. +func (c charmConfig) Type() string { + return c.Type_ +} + +// Default returns the default value of the config. +func (c charmConfig) Default() interface{} { + return c.Default_ +} + +// Description returns the description of the config. +func (c charmConfig) Description() string { + return c.Description_ +} diff --git a/charmconfigs_test.go b/charmconfigs_test.go new file mode 100644 index 0000000..3765ae3 --- /dev/null +++ b/charmconfigs_test.go @@ -0,0 +1,159 @@ +// Copyright 2024 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package description + +import ( + jc "github.com/juju/testing/checkers" + gc "gopkg.in/check.v1" + "gopkg.in/yaml.v2" +) + +type CharmConfigsSerializationSuite struct { + SerializationSuite +} + +var _ = gc.Suite(&CharmConfigsSerializationSuite{}) + +func (s *CharmConfigsSerializationSuite) SetUpTest(c *gc.C) { + s.importName = "charmConfigs" + s.importFunc = func(m map[string]interface{}) (interface{}, error) { + return importCharmConfigs(m) + } +} + +func (s *CharmConfigsSerializationSuite) TestNewCharmConfigs(c *gc.C) { + args := CharmConfigsArgs{ + Configs: map[string]CharmConfig{ + "foo": charmConfig{ + Description_: "description", + Type_: "string", + Default_: "default", + }, + }, + } + metadata := newCharmConfigs(args) + + c.Assert(metadata.Configs(), gc.DeepEquals, map[string]CharmConfig{ + "foo": charmConfig{ + Description_: "description", + Type_: "string", + Default_: "default", + }, + }) +} + +func minimalCharmConfigsMap() map[interface{}]interface{} { + return map[interface{}]interface{}{ + "version": 1, + "configs": map[interface{}]interface{}{}, + } +} + +func minimalCharmConfigsArgs() CharmConfigsArgs { + return CharmConfigsArgs{} +} + +func minimalCharmConfigs() *charmConfigs { + return newCharmConfigs(minimalCharmConfigsArgs()) +} + +func maximalCharmConfigsMap() map[interface{}]interface{} { + return map[interface{}]interface{}{ + "version": 1, + "configs": map[interface{}]interface{}{ + "foo": map[interface{}]interface{}{ + "description": "description", + "type": "string", + "default": "default", + }, + }, + } +} + +func maximalCharmConfigsArgs() CharmConfigsArgs { + return CharmConfigsArgs{ + Configs: map[string]CharmConfig{ + "foo": charmConfig{ + Description_: "description", + Type_: "string", + Default_: "default", + }, + }, + } +} + +func maximalCharmConfigs() *charmConfigs { + return newCharmConfigs(maximalCharmConfigsArgs()) +} + +func (s *CharmConfigsSerializationSuite) TestMinimalMatches(c *gc.C) { + bytes, err := yaml.Marshal(minimalCharmConfigs()) + c.Assert(err, jc.ErrorIsNil) + + var source map[interface{}]interface{} + err = yaml.Unmarshal(bytes, &source) + c.Assert(err, jc.ErrorIsNil) + c.Assert(source, jc.DeepEquals, minimalCharmConfigsMap()) +} + +func (s *CharmConfigsSerializationSuite) TestMaximalMatches(c *gc.C) { + bytes, err := yaml.Marshal(maximalCharmConfigs()) + c.Assert(err, jc.ErrorIsNil) + + var source map[interface{}]interface{} + err = yaml.Unmarshal(bytes, &source) + c.Assert(err, jc.ErrorIsNil) + c.Assert(source, jc.DeepEquals, maximalCharmConfigsMap()) +} + +func (s *CharmConfigsSerializationSuite) TestMinimalParsingSerializedData(c *gc.C) { + initial := minimalCharmConfigs() + bytes, err := yaml.Marshal(initial) + c.Assert(err, jc.ErrorIsNil) + + var source map[string]interface{} + err = yaml.Unmarshal(bytes, &source) + c.Assert(err, jc.ErrorIsNil) + + instance, err := importCharmConfigs(source) + c.Assert(err, jc.ErrorIsNil) + c.Assert(instance, jc.DeepEquals, initial) +} + +func (s *CharmConfigsSerializationSuite) TestMaximalParsingSerializedData(c *gc.C) { + initial := maximalCharmConfigs() + bytes, err := yaml.Marshal(initial) + c.Assert(err, jc.ErrorIsNil) + + var source map[string]interface{} + err = yaml.Unmarshal(bytes, &source) + c.Assert(err, jc.ErrorIsNil) + + instance, err := importCharmConfigs(source) + c.Assert(err, jc.ErrorIsNil) + c.Assert(instance, jc.DeepEquals, initial) +} + +func (s *CharmConfigsSerializationSuite) exportImportVersion(c *gc.C, origin_ *charmConfigs, version int) *charmConfigs { + origin_.Version_ = version + bytes, err := yaml.Marshal(origin_) + c.Assert(err, jc.ErrorIsNil) + + var source map[string]interface{} + err = yaml.Unmarshal(bytes, &source) + c.Assert(err, jc.ErrorIsNil) + + origin, err := importCharmConfigs(source) + c.Assert(err, jc.ErrorIsNil) + return origin +} + +func (s *CharmConfigsSerializationSuite) TestV1ParsingReturnsLatest(c *gc.C) { + args := maximalCharmConfigsArgs() + originV1 := newCharmConfigs(args) + + originLatest := *originV1 + originResult := s.exportImportVersion(c, originV1, 1) + c.Assert(*originResult, jc.DeepEquals, originLatest) +} diff --git a/charmmanifest.go b/charmmanifest.go index bb89552..d0db57d 100644 --- a/charmmanifest.go +++ b/charmmanifest.go @@ -1,4 +1,4 @@ -// Copyright 2020 Canonical Ltd. +// Copyright 2024 Canonical Ltd. // Licensed under the LGPLv3, see LICENCE file for details. package description @@ -83,7 +83,7 @@ func importCharmManifestVersion(source map[string]interface{}, importVersion int coerced, err := checker.Coerce(source, nil) if err != nil { - return nil, errors.Annotatef(err, "charmOrigin v1 schema check failed") + return nil, errors.Annotatef(err, "charmManifest v1 schema check failed") } valid := coerced.(map[string]interface{}) diff --git a/charmmetadata.go b/charmmetadata.go index 81c4505..ef3dc62 100644 --- a/charmmetadata.go +++ b/charmmetadata.go @@ -30,6 +30,7 @@ type CharmMetadataArgs struct { Resources map[string]CharmMetadataResource Terms []string Containers map[string]CharmMetadataContainer + LXDProfile string } func newCharmMetadata(args CharmMetadataArgs) *charmMetadata { @@ -176,6 +177,7 @@ func newCharmMetadata(args CharmMetadataArgs) *charmMetadata { Resources_: resources, Terms_: args.Terms, Containers_: containers, + LXDProfile_: args.LXDProfile, } } @@ -201,6 +203,7 @@ type charmMetadata struct { Resources_ map[string]charmMetadataResource `yaml:"resources,omitempty"` Terms_ []string `yaml:"terms,omitempty"` Containers_ map[string]charmMetadataContainer `yaml:"containers,omitempty"` + LXDProfile_ string `yaml:"lxd-profile,omitempty"` } // Name returns the name of the charm. @@ -330,6 +333,11 @@ func (m *charmMetadata) Containers() map[string]CharmMetadataContainer { return containers } +// LXDPofile returns the LXD profile of the charm. +func (m *charmMetadata) LXDProfile() string { + return m.LXDProfile_ +} + func importCharmMetadata(source map[string]interface{}) (*charmMetadata, error) { version, err := getVersion(source) if err != nil { @@ -375,6 +383,7 @@ func importCharmMetadataVersion(source map[string]interface{}, importVersion int "resources": schema.StringMap(schema.Any()), "terms": schema.List(schema.String()), "containers": schema.StringMap(schema.Any()), + "lxd-profile": schema.String(), } defaults := schema.Defaults{ "summary": schema.Omit, @@ -395,6 +404,7 @@ func importCharmMetadataVersion(source map[string]interface{}, importVersion int "resources": schema.Omit, "terms": schema.Omit, "containers": schema.Omit, + "lxd-profile": schema.Omit, } checker := schema.FieldMap(fields, defaults) @@ -531,6 +541,7 @@ func importCharmMetadataVersion(source map[string]interface{}, importVersion int minJujuVersion string runAs string assumes string + lxdProfile string ) if valid["summary"] != nil { @@ -551,6 +562,9 @@ func importCharmMetadataVersion(source map[string]interface{}, importVersion int if valid["assumes"] != nil { assumes = valid["assumes"].(string) } + if valid["lxd-profile"] != nil { + lxdProfile = valid["lxd-profile"].(string) + } return &charmMetadata{ Version_: 1, @@ -573,6 +587,7 @@ func importCharmMetadataVersion(source map[string]interface{}, importVersion int Payloads_: payloads, Containers_: containers, Terms_: terms, + LXDProfile_: lxdProfile, }, nil } diff --git a/charmmetadata_test.go b/charmmetadata_test.go index f445422..91dbb43 100644 --- a/charmmetadata_test.go +++ b/charmmetadata_test.go @@ -58,6 +58,7 @@ func maximalCharmMetadataMap() map[interface{}]interface{} { "run-as": "root", "assumes": "{}", "min-juju-version": "4.0.0", + "lxd-profile": "{}", "categories": []interface{}{"test", "testing"}, "tags": []interface{}{"foo", "bar"}, "terms": []interface{}{"baz", "qux"}, @@ -154,6 +155,7 @@ func maximalCharmMetadataArgs() CharmMetadataArgs { RunAs: "root", Assumes: "{}", MinJujuVersion: "4.0.0", + LXDProfile: "{}", Categories: []string{"test", "testing"}, Tags: []string{"foo", "bar"}, Terms: []string{"baz", "qux"}, diff --git a/interfaces.go b/interfaces.go index 987884f..22f0c5e 100644 --- a/interfaces.go +++ b/interfaces.go @@ -332,3 +332,28 @@ type CharmManifestBase interface { Channel() string Architectures() []string } + +// CharmActions represents the actions of a charm. +type CharmActions interface { + Actions() map[string]CharmAction +} + +// CharmAction represents an action in the metadata of a charm. +type CharmAction interface { + Description() string + Parallel() bool + ExecutionGroup() string + Parameters() map[string]interface{} +} + +// CharmConfigs represents the configuration of a charm. +type CharmConfigs interface { + Configs() map[string]CharmConfig +} + +// CharmConfig represents the configuration of a charm. +type CharmConfig interface { + Type() string + Default() interface{} + Description() string +}