Skip to content

Commit

Permalink
config: Do not modify the structure of opaque config
Browse files Browse the repository at this point in the history
HCL and JSON config may have different structures. With this change the
opaque configuration will not be modified. When the config is
deserialized the same hook is used to handle this difference in
structure.
  • Loading branch information
dnephin committed May 27, 2020
1 parent 09f5c48 commit e11c27a
Show file tree
Hide file tree
Showing 6 changed files with 62 additions and 121 deletions.
117 changes: 0 additions & 117 deletions agent/config/runtime_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3257,120 +3257,6 @@ func TestConfigFlagsAndEdgecases(t *testing.T) {
}`},
err: "config_entries.bootstrap[0]: invalid name (\"invalid-name\"), only \"global\" is supported",
},
{
desc: "ConfigEntry bootstrap proxy-defaults (snake-case)",
args: []string{`-data-dir=` + dataDir},
json: []string{`{
"config_entries": {
"bootstrap": [
{
"kind": "proxy-defaults",
"name": "global",
"config": {
"bar": "abc",
"moreconfig": {
"moar": "config"
}
},
"mesh_gateway": {
"mode": "remote"
}
}
]
}
}`},
hcl: []string{`
config_entries {
bootstrap {
kind = "proxy-defaults"
name = "global"
config {
"bar" = "abc"
"moreconfig" {
"moar" = "config"
}
}
mesh_gateway {
mode = "remote"
}
}
}`},
patch: func(rt *RuntimeConfig) {
rt.DataDir = dataDir
rt.ConfigEntryBootstrap = []structs.ConfigEntry{
&structs.ProxyConfigEntry{
Kind: structs.ProxyDefaults,
Name: structs.ProxyConfigGlobal,
Config: map[string]interface{}{
"bar": "abc",
"moreconfig": map[string]interface{}{
"moar": "config",
},
},
MeshGateway: structs.MeshGatewayConfig{
Mode: structs.MeshGatewayModeRemote,
},
},
}
},
},
{
desc: "ConfigEntry bootstrap proxy-defaults (camel-case)",
args: []string{`-data-dir=` + dataDir},
json: []string{`{
"config_entries": {
"bootstrap": [
{
"Kind": "proxy-defaults",
"Name": "global",
"Config": {
"bar": "abc",
"moreconfig": {
"moar": "config"
}
},
"MeshGateway": {
"Mode": "remote"
}
}
]
}
}`},
hcl: []string{`
config_entries {
bootstrap {
Kind = "proxy-defaults"
Name = "global"
Config {
"bar" = "abc"
"moreconfig" {
"moar" = "config"
}
}
MeshGateway {
Mode = "remote"
}
}
}`},
patch: func(rt *RuntimeConfig) {
rt.DataDir = dataDir
rt.ConfigEntryBootstrap = []structs.ConfigEntry{
&structs.ProxyConfigEntry{
Kind: structs.ProxyDefaults,
Name: structs.ProxyConfigGlobal,
Config: map[string]interface{}{
"bar": "abc",
"moreconfig": map[string]interface{}{
"moar": "config",
},
},
MeshGateway: structs.MeshGatewayConfig{
Mode: structs.MeshGatewayModeRemote,
},
},
}
},
},
{
desc: "ConfigEntry bootstrap service-defaults (snake-case)",
args: []string{`-data-dir=` + dataDir},
Expand Down Expand Up @@ -3891,9 +3777,6 @@ func testConfig(t *testing.T, tests []configTest, dataDir string) {
if tt.patch != nil {
tt.patch(&patchedRT)
}
// if err := x.Validate(wantRT); err != nil {
// t.Fatalf("validate default failed: %s", err)
// }
if got, want := rt, patchedRT; !verify.Values(t, "", got, want) {
t.FailNow()
}
Expand Down
12 changes: 12 additions & 0 deletions agent/connect/ca/provider_consul.go
Original file line number Diff line number Diff line change
Expand Up @@ -687,6 +687,18 @@ func (c *ConsulProvider) parseTestState(rawConfig map[string]interface{}, state
return
}

// If the state comes from an HCL config it may be wrapped in a slice,
// because that is how HCL decodes blocks.
if ts, ok := rawTestState.([]map[string]interface{}); ok && len(ts) == 1 {
c.testState = make(map[string]string)
for k, v := range ts[0] {
if s, ok := v.(string); ok {
c.testState[k] = s
}
}
return
}

// Secondary's config takes a trip through the state store before Configure
// is called and RPC calls that msgpack encode also have the same effect. It
// means we end up with map[string]string encoded as map[string]interface{}.
Expand Down
4 changes: 2 additions & 2 deletions agent/structs/config_entry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@ func TestDecodeConfigEntry(t *testing.T) {
Config: map[string]interface{}{
"foo": 19,
"bar": "abc",
"moreconfig": map[string]interface{}{
"moar": "config",
"moreconfig": []map[string]interface{}{
{"moar": "config"},
},
},
MeshGateway: MeshGatewayConfig{
Expand Down
4 changes: 2 additions & 2 deletions command/config/write/config_write_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,8 +222,8 @@ func TestParseConfigEntry(t *testing.T) {
Config: map[string]interface{}{
"foo": 19,
"bar": "abc",
"moreconfig": map[string]interface{}{
"moar": "config",
"moreconfig": []map[string]interface{}{
{"moar": "config"},
},
},
MeshGateway: api.MeshGatewayConfig{
Expand Down
8 changes: 8 additions & 0 deletions lib/decode/decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ func canonicalFieldKey(field reflect.StructField) string {
return parts[0]
}

var typeOfEmptyInterface = reflect.TypeOf((*interface{})(nil)).Elem()

// HookWeakDecodeFromSlice looks for []map[string]interface{} in the source
// data. If the target is not a slice or array it attempts to unpack 1 item
// out of the slice. If there are more items the source data is left unmodified,
Expand All @@ -112,6 +114,12 @@ func HookWeakDecodeFromSlice(from, to reflect.Type, data interface{}) (interface
return data, nil
}

// If the target is interface{} then the config is opaque, and it should not
// be modified.
if to == typeOfEmptyInterface {
return data, nil
}

switch d := data.(type) {
case []map[string]interface{}:
switch {
Expand Down
38 changes: 38 additions & 0 deletions lib/decode/decode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -272,3 +272,41 @@ item {
}
require.Equal(t, target, expected)
}

func TestHookNormalizeHCLNestedBlocks_IgnoresOpaqueConfigMaps(t *testing.T) {
source := `
item {
name = "first"
}
O {
something {
inner = [1, 2, 3, 4]
}
another {
name = "again"
}
another {
name = "last"
}
}
`
target := &nested{}
err := decodeHCLToMapStructure(source, target)
require.NoError(t, err)

expected := &nested{
Item: Item{Name: "first"},
O: raw{
"something": []raw{
{"inner": []interface{}{1, 2, 3, 4}},
},
"another": []raw{
{"name": "again"},
{"name": "last"},
},
},
}
require.Equal(t, expected, target)
}

type raw = map[string]interface{}

0 comments on commit e11c27a

Please sign in to comment.