Skip to content

Commit

Permalink
Merge pull request #151 from SimonRichardson/charm-actions
Browse files Browse the repository at this point in the history
#151

Import/export charm actions, config, and lxd-profiles for a given charm. These represent the actual files that can be found in a charm. We require these configuration files, so that when we import a 3.6 or 4.0 model into a 4.0 controller, we can correctly infer all of the RI (referential integrity) when creating applications.
  • Loading branch information
jujubot authored Jul 31, 2024
2 parents 4fc4023 + dfac62c commit 62f37b7
Show file tree
Hide file tree
Showing 10 changed files with 759 additions and 3 deletions.
62 changes: 61 additions & 1 deletion application.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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 {
Expand All @@ -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)
Expand Down
16 changes: 16 additions & 0 deletions application_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ func minimalApplicationMap() map[interface{}]interface{} {
"charm-origin": minimalCharmOriginMap(),
"charm-metadata": minimalCharmMetadataMap(),
"charm-manifest": minimalCharmManifestMap(),
"charm-actions": minimalCharmActionsMap(),
"charm-configs": minimalCharmConfigsMap(),
}
}

Expand Down Expand Up @@ -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
}

Expand All @@ -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
}

Expand Down Expand Up @@ -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_ = ""
Expand All @@ -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_ = ""
Expand All @@ -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_ = ""
Expand All @@ -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_ = ""
Expand All @@ -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_ = ""
Expand Down
161 changes: 161 additions & 0 deletions charmactions.go
Original file line number Diff line number Diff line change
@@ -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_
}
Loading

0 comments on commit 62f37b7

Please sign in to comment.