Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

viperx: Added viper BindEnvsToSchema helper #85

Merged
merged 4 commits into from
Oct 26, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ require (
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect
github.com/Microsoft/go-winio v0.4.11 // indirect
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869
github.com/cenkalti/backoff v2.1.1+incompatible
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd // indirect
github.com/containerd/continuity v0.0.0-20181203112020-004b46473808 // indirect
Expand Down Expand Up @@ -54,6 +54,7 @@ require (
github.com/spf13/cobra v0.0.5
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/stretchr/testify v1.3.0
github.com/tidwall/gjson v1.3.2
github.com/uber-go/atomic v1.3.2 // indirect
github.com/uber/jaeger-client-go v2.15.0+incompatible
github.com/uber/jaeger-lib v1.5.0 // indirect
Expand Down
15 changes: 7 additions & 8 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -397,8 +397,6 @@ github.com/opencontainers/runc v0.1.1 h1:GlxAyO6x8rfZYN9Tt0Kti5a/cP41iuiO2yYT0IJ
github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
github.com/opentracing/opentracing-go v1.0.2 h1:3jA2P6O1F9UOrWVpwrIo17pu01KWvNWg4X946/Y5Zwg=
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/ory/dockertest v3.3.4+incompatible h1:VrpM6Gqg7CrPm3bL4Wm1skO+zFWLbh7/Xb5kGEbJRh8=
github.com/ory/dockertest v3.3.4+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs=
github.com/ory/dockertest v3.3.5+incompatible h1:iLLK6SQwIhcbrG783Dghaaa3WPzGc+4Emza6EbVUUGA=
github.com/ory/dockertest v3.3.5+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs=
github.com/ory/fosite v0.29.0 h1:qFQfwy2YF1Bn5kgilT1LH3N0xOBvV865EXbj2bdxaoY=
Expand All @@ -409,8 +407,6 @@ github.com/ory/go-convenience v0.1.0 h1:zouLKfF2GoSGnJwGq+PE/nJAE6dj2Zj5QlTgmMTs
github.com/ory/go-convenience v0.1.0/go.mod h1:uEY/a60PL5c12nYz4V5cHY03IBmwIAEm8TWB0yn9KNs=
github.com/ory/gojsonreference v0.0.0-20190720135523-6b606c2d8ee8 h1:e2S2FmxqSbhFyVNP24HncpRY+X1qAZmtE3nZ0gJKR4Q=
github.com/ory/gojsonreference v0.0.0-20190720135523-6b606c2d8ee8/go.mod h1:wsH1C4nIeeQClDtD5AH7kF1uTS6zWyqfjVDTmB0Em7A=
github.com/ory/gojsonschema v0.0.0-20190717132251-f184856edacf h1:4u2kqhTpMYabxKmTYyJasLXFozkGVvbb6+AdMnWadwg=
github.com/ory/gojsonschema v0.0.0-20190717132251-f184856edacf/go.mod h1:ypRRv2Fg8r46Xc3bGggOPzwzJFN2CiW7roiJN5k/p4w=
github.com/ory/gojsonschema v1.1.1-0.20190919112458-f254ca73d5e9 h1:LDIG2Mnha10nFZuVXv3GIBqhQ1+JLwRXPcP4Ykx5VOY=
github.com/ory/gojsonschema v1.1.1-0.20190919112458-f254ca73d5e9/go.mod h1:BNZpdJgB74KOLSsWFvzw6roXg1I6O51WO8roMmW+T7Y=
github.com/ory/herodot v0.5.1 h1:M4rLTHH9KP8wLa2+bBKgLYqC8dlMjDmyTUSdC3KX+7U=
Expand Down Expand Up @@ -499,16 +495,21 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn
github.com/spf13/viper v1.2.1/go.mod h1:P4AexN0a+C9tGAnUFNwDMYYZv3pjFuvmeiMyKRaNVlI=
github.com/spf13/viper v1.3.1 h1:5+8j8FTpnFV4nEImW/ofkzEt8VoOiLXxdYIDsB73T38=
github.com/spf13/viper v1.3.1/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/spf13/viper v1.3.2 h1:VUFqw5KcqRf7i70GOzW7N+Q7+gxVBkSSqiXB12+JQ4M=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU=
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/subosito/gotenv v1.1.1 h1:TWxckSF6WVKWbo2M3tMqCtWa9NFUgqM1SSynxmYONOI=
github.com/subosito/gotenv v1.1.1/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tidwall/gjson v1.3.2 h1:+7p3qQFaH3fOMXAJSrdZwGKcOO/lYdGS0HqGhPqDdTI=
github.com/tidwall/gjson v1.3.2/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls=
github.com/tidwall/match v1.0.1 h1:PnKP62LPNxHKTwvHHZZzdOAOCtsJTjo6dZLCwpKm5xc=
github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E=
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/uber-go/atomic v1.3.2 h1:Azu9lPBWRNKzYXSIwRfgRuDuS0YKsK4NFhiQv98gkxo=
github.com/uber-go/atomic v1.3.2/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g=
Expand All @@ -524,8 +525,6 @@ github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc=
github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c h1:3lbZUMbMiGUW/LMkfsEABsc5zNT9+b1CvsJx47JzJ8g=
Expand Down
115 changes: 115 additions & 0 deletions viperx/bind_env.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package viperx

import (
"encoding/json"
"fmt"
"strings"

"github.com/pkg/errors"

"github.com/tidwall/gjson"

"github.com/ory/viper"

"github.com/ory/x/stringslice"
)

const (
none = iota - 1
properties
ref
allOf
anyOf
oneOf
)

var keys = []string{
"properties",
"$ref",
"allOf",
"anyOf",
"oneOf",
}

// BindEnvsToSchema uses all keys it can find from ``
func BindEnvsToSchema(schema json.RawMessage) error {
keys, err := getSchemaKeys(string(schema), string(schema), []string{}, []string{})
if err != nil {
return err
}
return viper.BindEnv(keys...)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I noted previously this usage of BindEnv is incorrect: this function does not accept multiple keys https://github.com/spf13/viper/blob/master/viper.go#L960 It either accepts one parameter (the key) or two parameters (the key and the corresponding ENV_VAR_NAME)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, how do we deal with the env names? I guess with the effect of SetEnvKeyReplacer it will work automatically, so afterviper.BindEnv("foo.bar.baz") env FOO_BAR_BAZ should work, right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, thanks, I overlooked that completely. Fixed and added a test to make sure this works properly.

Also, how do we deal with the env names? I guess with the effect of SetEnvKeyReplacer it will work automatically, so afterviper.BindEnv("foo.bar.baz") env FOO_BAR_BAZ should work, right?

Exactly!

}

func getSchemaKeys(root, current string, parents []string, traversed []string) ([]string, error) {
var foundKey = -1
var result gjson.Result
for i, value := range gjson.GetMany(
current,
keys...,
) {
if value.Exists() {
foundKey = i
result = value
break
}
}

if foundKey == none {
return nil, nil
}

var paths []string
var err error

traversed = append(traversed, keys[foundKey])
switch foundKey {
case properties:
result.ForEach(func(k, v gjson.Result) bool {
this := append(parents, k.String())
paths = append(paths, strings.Join(this, "."))
if v.IsObject() {
merge, innerErr := getSchemaKeys(root, v.Raw, this, traversed)
if innerErr != nil {
err = innerErr
return false // break out
}
paths = append(paths, merge...)
}
return true // run through all keys
})
case ref:
defpath := result.String()
if !strings.HasPrefix(defpath, "#/definitions/") {
return nil, errors.New("only references to #/definitions/ are supported")
}
path := strings.ReplaceAll(strings.TrimPrefix(defpath, "#/"), "/", ".")
if stringslice.HasI(traversed, path) {
return nil, errors.Errorf("detected circular dependency in schema path: %v", traversed)
}
merge, err := getSchemaKeys(root, gjson.Get(root, path).Raw, parents, append(traversed, path))
if err != nil {
return nil, err
}
paths = append(paths, merge...)
case allOf:
fallthrough
case oneOf:
fallthrough
case anyOf:
for _, item := range result.Array() {
merge, err := getSchemaKeys(root, item.Raw, parents, traversed)
if err != nil {
return nil, err
}
paths = append(paths, merge...)
}
default:
panic(fmt.Sprintf("found unexpected key: %d", foundKey))
}

if err != nil {
return nil, err
}

return stringslice.Unique(paths), err
}
110 changes: 110 additions & 0 deletions viperx/bind_env_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package viperx

import (
"fmt"
"io/ioutil"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestBindEnv(t *testing.T) {
readFile := func(path string) string {
schema, err := ioutil.ReadFile(path)
require.NoError(t, err)
return string(schema)
}

for k, tc := range []struct {
schema string
expectErr bool
expectedKeys []string
}{
{
schema: readFile("./stub/.oathkeeper.schema.json"),
expectedKeys: []string{
"serve", "serve.api", "serve.api.port", "serve.api.host", "serve.api.cors",
"serve.api.cors.enabled", "serve.api.cors.allowed_origins", "serve.api.cors.allowed_methods",
"serve.api.cors.allowed_headers", "serve.api.cors.exposed_headers", "serve.api.cors.allow_credentials",
"serve.api.cors.max_age", "serve.api.cors.debug", "serve.api.tls", "serve.api.tls.key", "serve.api.tls.key.path",
"serve.api.tls.key.base64", "serve.api.tls.cert", "serve.api.tls.cert.path", "serve.api.tls.cert.base64", "serve.proxy",
"serve.proxy.port", "serve.proxy.host", "serve.proxy.timeout", "serve.proxy.timeout.read", "serve.proxy.timeout.write",
"serve.proxy.timeout.idle", "serve.proxy.cors", "serve.proxy.cors.enabled", "serve.proxy.cors.allowed_origins",
"serve.proxy.cors.allowed_methods", "serve.proxy.cors.allowed_headers", "serve.proxy.cors.exposed_headers",
"serve.proxy.cors.allow_credentials", "serve.proxy.cors.max_age", "serve.proxy.cors.debug", "serve.proxy.tls", "serve.proxy.tls.key",
"serve.proxy.tls.key.path", "serve.proxy.tls.key.base64", "serve.proxy.tls.cert", "serve.proxy.tls.cert.path", "serve.proxy.tls.cert.base64",
"access_rules", "access_rules.repositories", "authenticators", "authenticators.anonymous",
"authenticators.anonymous.enabled", "authenticators.anonymous.config", "authenticators.anonymous.config.subject", "authenticators.noop",
"authenticators.noop.enabled", "authenticators.unauthorized", "authenticators.unauthorized.enabled", "authenticators.cookie_session",
"authenticators.cookie_session.enabled", "authenticators.cookie_session.config", "authenticators.cookie_session.config.check_session_url",
"authenticators.cookie_session.config.only", "authenticators.jwt", "authenticators.jwt.enabled", "authenticators.jwt.config",
"authenticators.jwt.config.required_scope", "authenticators.jwt.config.target_audience", "authenticators.jwt.config.trusted_issuers",
"authenticators.jwt.config.allowed_algorithms", "authenticators.jwt.config.jwks_urls", "authenticators.jwt.config.scope_strategy",
"authenticators.jwt.config.token_from", "authenticators.jwt.config.token_from.header", "authenticators.jwt.config.token_from.query_parameter",
"authenticators.oauth2_client_credentials", "authenticators.oauth2_client_credentials.enabled", "authenticators.oauth2_client_credentials.config",
"authenticators.oauth2_client_credentials.config.token_url", "authenticators.oauth2_client_credentials.config.required_scope",
"authenticators.oauth2_introspection", "authenticators.oauth2_introspection.enabled", "authenticators.oauth2_introspection.config",
"authenticators.oauth2_introspection.config.introspection_url", "authenticators.oauth2_introspection.config.scope_strategy",
"authenticators.oauth2_introspection.config.pre_authorization", "authenticators.oauth2_introspection.config.pre_authorization.enabled",
"authenticators.oauth2_introspection.config.pre_authorization.client_id", "authenticators.oauth2_introspection.config.pre_authorization.client_secret",
"authenticators.oauth2_introspection.config.pre_authorization.token_url", "authenticators.oauth2_introspection.config.pre_authorization.scope",
"authenticators.oauth2_introspection.config.required_scope", "authenticators.oauth2_introspection.config.target_audience",
"authenticators.oauth2_introspection.config.trusted_issuers", "authenticators.oauth2_introspection.config.token_from",
"authenticators.oauth2_introspection.config.token_from.header", "authenticators.oauth2_introspection.config.token_from.query_parameter",
"authorizers", "authorizers.allow", "authorizers.allow.enabled", "authorizers.deny", "authorizers.deny.enabled", "authorizers.keto_engine_acp_ory",
"authorizers.keto_engine_acp_ory.enabled", "authorizers.keto_engine_acp_ory.config", "authorizers.keto_engine_acp_ory.config.base_url",
"authorizers.keto_engine_acp_ory.config.required_action", "authorizers.keto_engine_acp_ory.config.required_resource", "authorizers.keto_engine_acp_ory.config.subject",
"authorizers.keto_engine_acp_ory.config.flavor", "mutators", "mutators.noop", "mutators.noop.enabled", "mutators.cookie",
"mutators.cookie.enabled", "mutators.cookie.config", "mutators.cookie.config.cookies", "mutators.header",
"mutators.header.enabled", "mutators.header.config", "mutators.header.config.headers", "mutators.hydrator",
"mutators.hydrator.enabled", "mutators.hydrator.config", "mutators.hydrator.config.api", "mutators.hydrator.config.api.url",
"mutators.hydrator.config.api.auth", "mutators.hydrator.config.api.auth.basic", "mutators.hydrator.config.api.auth.basic.username",
"mutators.hydrator.config.api.auth.basic.password", "mutators.hydrator.config.api.retry", "mutators.hydrator.config.api.retry.number_of_retries",
"mutators.hydrator.config.api.retry.delay_in_milliseconds", "mutators.id_token", "mutators.id_token.enabled",
"mutators.id_token.config", "mutators.id_token.config.claims", "mutators.id_token.config.issuer_url",
"mutators.id_token.config.jwks_url", "mutators.id_token.config.ttl", "log", "log.level", "log.format",
"profiling",
},
},
{
schema: readFile("./stub/config.schema.json"),
expectedKeys: []string{"dsn"},
},
{
schema: `{"$ref": "http://google/schema.json"}`,
expectErr: true,
},
{
// this should fail because of recursion
schema: `{
"definitions": {
"foo": {
"$ref": "#/definitions/foo"
}
},
"type": "object",
"properties": {
"bar": {
"$ref": "#/definitions/foo"
}
}
}`,
expectErr: true,
},
{
schema: `{"oneOf": [{ "type": "object", "properties": { "foo": true, "bar": true } },{ "type": "object", "properties": { "foo": true } }]}`,
expectedKeys: []string{"foo", "bar"},
},
} {
t.Run(fmt.Sprintf("case=%d", k), func(t *testing.T) {
actual, err := getSchemaKeys(tc.schema, tc.schema, []string{}, []string{})
if tc.expectErr {
require.Error(t, err)
return
}
require.NoError(t, err)
assert.EqualValues(t, tc.expectedKeys, actual)
})
}
}
Loading