-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request juju#17668 from SimonRichardson/charm-state-part-deux
juju#17668 Adds state get implementations, the set charm is going to be in subsequent PR, as I believe that it will take a lot of time to correctly implement. Manifest, actions, config, and LXD profiles are row-based structs, which is the normal form, unlike charm metadata. The manifest bases are defined as an array and an index was added to the state schema to ensure correct ordering was maintained (although we do run into another sqlair bug[1]). Architectures will not be guaranteed to be in order, because we're storing the base array and the architectures as one flat row. If we did want to do that, we should create another table that kept track of that one as well. For now, the order of architectures shouldn't matter as we don't pick one over the other. 1. canonical/sqlair#155 <!-- The PR title should match: <type>(optional <scope>): <description>. Please also ensure all commits in this PR comply with our conventional commits specification: https://docs.google.com/document/d/1SYUo9G7qZ_jdoVXpUVamS5VCgHmtZ0QA-wZxKoMS-C0 --> <!-- Why this change is needed and what it does. --> ## Checklist <!-- If an item is not applicable, use `~strikethrough~`. --> - [x] Code style: imports ordered, good names, simple structure, etc - [x] Comments saying why design decisions were made - [x] Go unit tests, with comments saying what you're testing ## QA steps The state tests should pass. Once the `SetCharm` is implemented in subsequent PRs then we should start to see full end to end tests, which would better validate all the state implementation. ## Links **Jira card:** [JUJU-6015](https://warthogs.atlassian.net/browse/JUJU-6015) [JUJU-6015]: https://warthogs.atlassian.net/browse/JUJU-6015?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
- Loading branch information
Showing
10 changed files
with
1,077 additions
and
68 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
// Copyright 2024 Canonical Ltd. | ||
// Licensed under the AGPLv3, see LICENCE file for details. | ||
|
||
package state | ||
|
||
import ( | ||
"github.com/juju/juju/domain/charm" | ||
) | ||
|
||
func decodeActions(actions []charmAction) charm.Actions { | ||
result := charm.Actions{ | ||
Actions: make(map[string]charm.Action), | ||
} | ||
for _, action := range actions { | ||
result.Actions[action.Key] = charm.Action{ | ||
Description: action.Description, | ||
Parallel: action.Parallel, | ||
ExecutionGroup: action.ExecutionGroup, | ||
Params: action.Params, | ||
} | ||
} | ||
return result | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
// Copyright 2024 Canonical Ltd. | ||
// Licensed under the AGPLv3, see LICENCE file for details. | ||
|
||
package state | ||
|
||
import ( | ||
gc "gopkg.in/check.v1" | ||
|
||
"github.com/juju/juju/domain/charm" | ||
schematesting "github.com/juju/juju/domain/schema/testing" | ||
) | ||
|
||
type actionsSuite struct { | ||
schematesting.ModelSuite | ||
} | ||
|
||
var _ = gc.Suite(&actionsSuite{}) | ||
|
||
var actionsTestCases = [...]struct { | ||
name string | ||
input []charmAction | ||
output charm.Actions | ||
}{ | ||
{ | ||
name: "empty", | ||
input: []charmAction{}, | ||
output: charm.Actions{ | ||
Actions: make(map[string]charm.Action), | ||
}, | ||
}, | ||
{ | ||
name: "single", | ||
input: []charmAction{ | ||
{ | ||
Key: "action", | ||
Description: "description", | ||
Parallel: true, | ||
ExecutionGroup: "group", | ||
Params: []byte("{}"), | ||
}, | ||
}, | ||
output: charm.Actions{ | ||
Actions: map[string]charm.Action{ | ||
"action": { | ||
Description: "description", | ||
Parallel: true, | ||
ExecutionGroup: "group", | ||
Params: []byte("{}"), | ||
}, | ||
}, | ||
}, | ||
}, | ||
} | ||
|
||
func (s *actionsSuite) TestConvertActions(c *gc.C) { | ||
for _, tc := range actionsTestCases { | ||
c.Logf("Running test case %q", tc.name) | ||
|
||
result := decodeActions(tc.input) | ||
c.Check(result, gc.DeepEquals, tc.output) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
// Copyright 2024 Canonical Ltd. | ||
// Licensed under the AGPLv3, see LICENCE file for details. | ||
|
||
package state | ||
|
||
import ( | ||
"fmt" | ||
"strconv" | ||
|
||
"github.com/juju/juju/domain/charm" | ||
) | ||
|
||
func decodeConfig(configs []charmConfig) (charm.Config, error) { | ||
result := charm.Config{ | ||
Options: make(map[string]charm.Option), | ||
} | ||
for _, config := range configs { | ||
optionType, err := decodeConfigType(config.Type) | ||
if err != nil { | ||
return charm.Config{}, fmt.Errorf("cannot decode config type %q: %w", config.Type, err) | ||
} | ||
|
||
defaultValue, err := decodeConfigDefaultValue(optionType, config.DefaultValue) | ||
if err != nil { | ||
return charm.Config{}, fmt.Errorf("cannot decode config default value %q: %w", config.DefaultValue, err) | ||
} | ||
|
||
result.Options[config.Key] = charm.Option{ | ||
Type: optionType, | ||
Description: config.Description, | ||
Default: defaultValue, | ||
} | ||
} | ||
return result, nil | ||
} | ||
|
||
func decodeConfigType(t string) (charm.OptionType, error) { | ||
switch t { | ||
case "string": | ||
return charm.OptionString, nil | ||
case "int": | ||
return charm.OptionInt, nil | ||
case "float": | ||
return charm.OptionFloat, nil | ||
case "boolean": | ||
return charm.OptionBool, nil | ||
case "secret": | ||
return charm.OptionSecret, nil | ||
default: | ||
return "", fmt.Errorf("unknown config type %q", t) | ||
} | ||
} | ||
|
||
func decodeConfigDefaultValue(t charm.OptionType, value string) (any, error) { | ||
switch t { | ||
case charm.OptionString, charm.OptionSecret: | ||
return value, nil | ||
case charm.OptionInt: | ||
return strconv.Atoi(value) | ||
case charm.OptionFloat: | ||
return strconv.ParseFloat(value, 64) | ||
case charm.OptionBool: | ||
return strconv.ParseBool(value) | ||
default: | ||
return nil, fmt.Errorf("unknown config type %q", t) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
// Copyright 2024 Canonical Ltd. | ||
// Licensed under the AGPLv3, see LICENCE file for details. | ||
|
||
package state | ||
|
||
import ( | ||
jc "github.com/juju/testing/checkers" | ||
gc "gopkg.in/check.v1" | ||
|
||
"github.com/juju/juju/domain/charm" | ||
schematesting "github.com/juju/juju/domain/schema/testing" | ||
) | ||
|
||
type configSuite struct { | ||
schematesting.ModelSuite | ||
} | ||
|
||
var _ = gc.Suite(&configSuite{}) | ||
|
||
var configTestCases = [...]struct { | ||
name string | ||
input []charmConfig | ||
output charm.Config | ||
}{ | ||
{ | ||
name: "empty", | ||
input: []charmConfig{}, | ||
output: charm.Config{ | ||
Options: make(map[string]charm.Option), | ||
}, | ||
}, | ||
{ | ||
name: "string", | ||
input: []charmConfig{ | ||
{ | ||
Key: "string", | ||
Type: "string", | ||
Description: "description", | ||
DefaultValue: "default", | ||
}, | ||
}, | ||
output: charm.Config{ | ||
Options: map[string]charm.Option{ | ||
"string": { | ||
Type: charm.OptionString, | ||
Description: "description", | ||
Default: "default", | ||
}, | ||
}, | ||
}, | ||
}, | ||
{ | ||
name: "secret", | ||
input: []charmConfig{ | ||
{ | ||
Key: "secret", | ||
Type: "secret", | ||
Description: "description", | ||
DefaultValue: "default", | ||
}, | ||
}, | ||
output: charm.Config{ | ||
Options: map[string]charm.Option{ | ||
"secret": { | ||
Type: charm.OptionSecret, | ||
Description: "description", | ||
Default: "default", | ||
}, | ||
}, | ||
}, | ||
}, | ||
{ | ||
name: "int", | ||
input: []charmConfig{ | ||
{ | ||
Key: "int", | ||
Type: "int", | ||
Description: "description", | ||
DefaultValue: "1", | ||
}, | ||
}, | ||
output: charm.Config{ | ||
Options: map[string]charm.Option{ | ||
"int": { | ||
Type: charm.OptionInt, | ||
Description: "description", | ||
Default: 1, | ||
}, | ||
}, | ||
}, | ||
}, | ||
{ | ||
name: "float", | ||
input: []charmConfig{ | ||
{ | ||
Key: "float", | ||
Type: "float", | ||
Description: "description", | ||
DefaultValue: "4.2", | ||
}, | ||
}, | ||
output: charm.Config{ | ||
Options: map[string]charm.Option{ | ||
"float": { | ||
Type: charm.OptionFloat, | ||
Description: "description", | ||
Default: 4.2, | ||
}, | ||
}, | ||
}, | ||
}, | ||
{ | ||
name: "boolean", | ||
input: []charmConfig{ | ||
{ | ||
Key: "boolean", | ||
Type: "boolean", | ||
Description: "description", | ||
DefaultValue: "true", | ||
}, | ||
}, | ||
output: charm.Config{ | ||
Options: map[string]charm.Option{ | ||
"boolean": { | ||
Type: charm.OptionBool, | ||
Description: "description", | ||
Default: true, | ||
}, | ||
}, | ||
}, | ||
}, | ||
} | ||
|
||
func (s *configSuite) TestConvertConfig(c *gc.C) { | ||
for _, tc := range configTestCases { | ||
c.Logf("Running test case %q", tc.name) | ||
|
||
result, err := decodeConfig(tc.input) | ||
c.Assert(err, jc.ErrorIsNil) | ||
c.Check(result, gc.DeepEquals, tc.output) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
// Copyright 2024 Canonical Ltd. | ||
// Licensed under the AGPLv3, see LICENCE file for details. | ||
|
||
package state | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/juju/juju/domain/charm" | ||
) | ||
|
||
// decodeManifest decodes the given manifests into a charm.Manifest. | ||
// It should respect the order of the bases in the manifest. Although the | ||
// order of architectures within a base is not guaranteed. | ||
func decodeManifest(manifests []charmManifest) (charm.Manifest, error) { | ||
bases := make(map[int]charm.Base) | ||
|
||
var largestIndex int | ||
for _, base := range manifests { | ||
channel, err := decodeManifestChannel(base) | ||
if err != nil { | ||
return charm.Manifest{}, fmt.Errorf("cannot decode channel: %w", err) | ||
} | ||
|
||
if b, ok := bases[base.Index]; ok { | ||
b.Architectures = append(b.Architectures, base.Architecture) | ||
bases[base.Index] = b | ||
|
||
continue | ||
} | ||
|
||
var architectures []string | ||
if base.Architecture != "" { | ||
architectures = append(architectures, base.Architecture) | ||
} | ||
|
||
bases[base.Index] = charm.Base{ | ||
Name: base.OS, | ||
Channel: channel, | ||
Architectures: architectures, | ||
} | ||
|
||
if base.Index > largestIndex { | ||
largestIndex = base.Index | ||
} | ||
} | ||
|
||
// Convert the map into a slice using the largest index as the length. This | ||
// means that we preserved the order of the bases even faced with holes in | ||
// the array. | ||
result := make([]charm.Base, largestIndex+1) | ||
for index, base := range bases { | ||
result[index] = base | ||
} | ||
|
||
return charm.Manifest{ | ||
Bases: result, | ||
}, nil | ||
} | ||
|
||
// decodeManifestChannel decodes the given base into a charm.Channel. | ||
func decodeManifestChannel(base charmManifest) (charm.Channel, error) { | ||
risk, err := decodeManifestChannelRisk(base.Risk) | ||
if err != nil { | ||
return charm.Channel{}, fmt.Errorf("cannot decode risk: %w", err) | ||
} | ||
|
||
return charm.Channel{ | ||
Track: base.Track, | ||
Risk: risk, | ||
Branch: base.Branch, | ||
}, nil | ||
} | ||
|
||
// decodeManifestChannelRisk decodes the given risk into a charm.ChannelRisk. | ||
func decodeManifestChannelRisk(risk string) (charm.ChannelRisk, error) { | ||
switch risk { | ||
case "stable": | ||
return charm.RiskStable, nil | ||
case "candidate": | ||
return charm.RiskCandidate, nil | ||
case "beta": | ||
return charm.RiskBeta, nil | ||
case "edge": | ||
return charm.RiskEdge, nil | ||
default: | ||
return "", fmt.Errorf("unknown risk %q", risk) | ||
} | ||
} |
Oops, something went wrong.