Skip to content

Commit

Permalink
Merge pull request #416 from ycliuhw/feature/user-secrets
Browse files Browse the repository at this point in the history
#416

Add a new charm config option type `secret`;
  • Loading branch information
jujubot authored Aug 3, 2023
2 parents 2e07e54 + b34b3e9 commit b9b4232
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 60 deletions.
36 changes: 33 additions & 3 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ import (
"fmt"
"io"
"io/ioutil"
"net/url"
"strconv"

"github.com/juju/errors"
"github.com/juju/schema"
"gopkg.in/yaml.v2"
)
Expand All @@ -33,6 +35,33 @@ func (option Option) error(err *error, name string, value interface{}) {
}
}

const secretScheme = "secret"

type secretC struct{}

// Coerce implements schema.Checker.Coerce for secretC.
func (c secretC) Coerce(v interface{}, path []string) (interface{}, error) {
s, err := schema.String().Coerce(v, path)
if err != nil {
return nil, err
}
str := s.(string)
if str == "" {
return "", nil
}
u, err := url.Parse(str)
if err != nil {
return nil, errors.Trace(err)
}
if u.Scheme == "" {
return nil, errors.NotValidf("secret URI scheme missing")
}
if u.Scheme != secretScheme {
return nil, errors.NotValidf("secret URI scheme %q", u.Scheme)
}
return str, nil
}

// validate returns an appropriately-typed value for the supplied value, or
// returns an error if it cannot be converted to the correct type. Nil values
// are always considered valid.
Expand All @@ -55,11 +84,12 @@ var optionTypeCheckers = map[string]schema.Checker{
"int": schema.Int(),
"float": schema.Float(),
"boolean": schema.Bool(),
"secret": secretC{},
}

func (option Option) parse(name, str string) (val interface{}, err error) {
switch option.Type {
case "string":
case "string", "secret":
return str, nil
case "int":
val, err = strconv.ParseInt(str, 10, 64)
Expand Down Expand Up @@ -115,15 +145,15 @@ func ReadConfig(r io.Reader) (*Config, error) {
}
for name, option := range config.Options {
switch option.Type {
case "string", "int", "float", "boolean":
case "string", "secret", "int", "float", "boolean":
case "":
// Missing type is valid in python.
option.Type = "string"
default:
return nil, fmt.Errorf("invalid config: option %q has unknown type %q", name, option.Type)
}
def := option.Default
if def == "" && option.Type == "string" {
if def == "" && (option.Type == "string" || option.Type == "secret") {
// Skip normal validation for compatibility with pyjuju.
} else if option.Default, err = option.validate(name, def); err != nil {
option.error(&err, name, def)
Expand Down
132 changes: 75 additions & 57 deletions config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ options:
reticulate-splines:
description: Whether to reticulate splines on launch, or not.
type: boolean
secret-foo:
description: A secret value.
type: secret
`)))
c.Assert(err, gc.IsNil)
}
Expand Down Expand Up @@ -87,6 +90,10 @@ func (s *ConfigSuite) TestReadSample(c *gc.C) {
Description: "Whether to reticulate splines on launch, or not.",
Type: "boolean",
},
"secret-foo": {
Description: "A secret value.",
Type: "secret",
},
})
}

Expand All @@ -95,6 +102,7 @@ func (s *ConfigSuite) TestDefaultSettings(c *gc.C) {
"title": "My Title",
"subtitle": "",
"username": "admin001",
"secret-foo": nil,
"outlook": nil,
"skill-level": nil,
"agility-ratio": nil,
Expand Down Expand Up @@ -125,63 +133,73 @@ func (s *ConfigSuite) TestValidateSettings(c *gc.C) {
input charm.Settings
expect charm.Settings
err string
}{{
info: "nil settings are valid",
expect: charm.Settings{},
}, {
info: "empty settings are valid",
input: charm.Settings{},
}, {
info: "unknown keys are not valid",
input: charm.Settings{"foo": nil},
err: `unknown option "foo"`,
}, {
info: "nil is valid for every value type",
input: charm.Settings{
"outlook": nil,
"skill-level": nil,
"agility-ratio": nil,
"reticulate-splines": nil,
},
}, {
info: "correctly-typed values are valid",
input: charm.Settings{
"outlook": "stormy",
"skill-level": int64(123),
"agility-ratio": 0.5,
"reticulate-splines": true,
},
}, {
info: "empty string-typed values stay empty",
input: charm.Settings{"outlook": ""},
expect: charm.Settings{"outlook": ""},
}, {
info: "almost-correctly-typed values are valid",
input: charm.Settings{
"skill-level": 123,
"agility-ratio": float32(0.5),
}{
{
info: "nil settings are valid",
expect: charm.Settings{},
}, {
info: "empty settings are valid",
input: charm.Settings{},
}, {
info: "unknown keys are not valid",
input: charm.Settings{"foo": nil},
err: `unknown option "foo"`,
}, {
info: "nil is valid for every value type",
input: charm.Settings{
"outlook": nil,
"skill-level": nil,
"agility-ratio": nil,
"reticulate-splines": nil,
},
}, {
info: "correctly-typed values are valid",
input: charm.Settings{
"outlook": "stormy",
"skill-level": int64(123),
"agility-ratio": 0.5,
"reticulate-splines": true,
},
}, {
info: "empty string-typed values stay empty",
input: charm.Settings{"outlook": ""},
expect: charm.Settings{"outlook": ""},
}, {
info: "almost-correctly-typed values are valid",
input: charm.Settings{
"skill-level": 123,
"agility-ratio": float32(0.5),
},
expect: charm.Settings{
"skill-level": int64(123),
"agility-ratio": 0.5,
},
}, {
info: "bad string",
input: charm.Settings{"outlook": false},
err: `option "outlook" expected string, got false`,
}, {
info: "bad int",
input: charm.Settings{"skill-level": 123.4},
err: `option "skill-level" expected int, got 123.4`,
}, {
info: "bad float",
input: charm.Settings{"agility-ratio": "cheese"},
err: `option "agility-ratio" expected float, got "cheese"`,
}, {
info: "bad boolean",
input: charm.Settings{"reticulate-splines": 101},
err: `option "reticulate-splines" expected boolean, got 101`,
}, {
info: "invalid secret",
input: charm.Settings{"secret-foo": "cheese"},
err: `option "secret-foo" expected secret, got "cheese"`,
}, {
info: "valid secret",
input: charm.Settings{"secret-foo": "secret:cj4v5vm78ohs79o84r4g"},
expect: charm.Settings{"secret-foo": "secret:cj4v5vm78ohs79o84r4g"},
},
expect: charm.Settings{
"skill-level": int64(123),
"agility-ratio": 0.5,
},
}, {
info: "bad string",
input: charm.Settings{"outlook": false},
err: `option "outlook" expected string, got false`,
}, {
info: "bad int",
input: charm.Settings{"skill-level": 123.4},
err: `option "skill-level" expected int, got 123.4`,
}, {
info: "bad float",
input: charm.Settings{"agility-ratio": "cheese"},
err: `option "agility-ratio" expected float, got "cheese"`,
}, {
info: "bad boolean",
input: charm.Settings{"reticulate-splines": 101},
err: `option "reticulate-splines" expected boolean, got 101`,
}} {
} {
c.Logf("test %d: %s", i, test.info)
result, err := s.config.ValidateSettings(test.input)
if test.err != "" {
Expand Down Expand Up @@ -472,7 +490,7 @@ options:

func (s *ConfigSuite) TestErrorOnInvalidOptionTypes(c *gc.C) {
cfg := charm.Config{
Options: map[string]charm.Option{"testOption": charm.Option{Type: "invalid type"}},
Options: map[string]charm.Option{"testOption": {Type: "invalid type"}},
}
_, err := cfg.ParseSettingsYAML([]byte("testKey:\n testOption: 12.345"), "testKey")
c.Assert(err, gc.ErrorMatches, "option \"testOption\" has unknown type \"invalid type\"")
Expand Down

0 comments on commit b9b4232

Please sign in to comment.