1
0
mirror of https://github.com/oauth2-proxy/oauth2-proxy.git synced 2025-08-08 22:46:33 +02:00

Introduce alpha configuration loading

This commit is contained in:
Joel Speed
2020-11-09 20:17:43 +00:00
parent 5b003a5657
commit f36dfbb494
10 changed files with 550 additions and 21 deletions

View File

@ -29,3 +29,11 @@ type AlphaOptions struct {
// or from a static secret value.
InjectResponseHeaders []Header `json:"injectResponseHeaders,omitempty"`
}
// MergeInto replaces alpha options in the Options struct with the values
// from the AlphaOptions
func (a *AlphaOptions) MergeInto(opts *Options) {
opts.UpstreamServers = a.Upstreams
opts.InjectRequestHeaders = a.InjectRequestHeaders
opts.InjectResponseHeaders = a.InjectResponseHeaders
}

View File

@ -39,6 +39,15 @@ func NewLegacyOptions() *LegacyOptions {
}
}
func NewLegacyFlagSet() *pflag.FlagSet {
flagSet := NewFlagSet()
flagSet.AddFlagSet(legacyUpstreamsFlagSet())
flagSet.AddFlagSet(legacyHeadersFlagSet())
return flagSet
}
func (l *LegacyOptions) ToOptions() (*Options, error) {
upstreams, err := l.LegacyUpstreams.convert()
if err != nil {

View File

@ -1,10 +1,13 @@
package options
import (
"errors"
"fmt"
"io/ioutil"
"reflect"
"strings"
"github.com/ghodss/yaml"
"github.com/mitchellh/mapstructure"
"github.com/spf13/pflag"
"github.com/spf13/viper"
@ -132,3 +135,28 @@ func isUnexported(name string) bool {
first := string(name[0])
return first == strings.ToLower(first)
}
// LoadYAML will load a YAML based configuration file into the options interface provided.
func LoadYAML(configFileName string, into interface{}) error {
v := viper.New()
v.SetConfigFile(configFileName)
v.SetConfigType("yaml")
v.SetTypeByDefaultValue(true)
if configFileName == "" {
return errors.New("no configuration file provided")
}
data, err := ioutil.ReadFile(configFileName)
if err != nil {
return fmt.Errorf("unable to load config file: %w", err)
}
// UnmarshalStrict will return an error if the config includes options that are
// not mapped to felds of the into struct
if err := yaml.UnmarshalStrict(data, into, yaml.DisallowUnknownFields); err != nil {
return fmt.Errorf("error unmarshalling config: %w", err)
}
return nil
}

View File

@ -1,9 +1,11 @@
package options
import (
"errors"
"fmt"
"io/ioutil"
"os"
"time"
. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/extensions/table"
@ -295,10 +297,199 @@ var _ = Describe("Load", func() {
expectedOutput: NewOptions(),
}),
Entry("with an empty LegacyOptions struct, should return default values", &testOptionsTableInput{
flagSet: NewFlagSet,
flagSet: NewLegacyFlagSet,
input: &LegacyOptions{},
expectedOutput: NewLegacyOptions(),
}),
)
})
})
var _ = Describe("LoadYAML", func() {
Context("with a testOptions structure", func() {
type TestOptionSubStruct struct {
StringSliceOption []string `yaml:"stringSliceOption,omitempty"`
}
type TestOptions struct {
StringOption string `yaml:"stringOption,omitempty"`
Sub TestOptionSubStruct `yaml:"sub,omitempty"`
// Check that embedded fields can be unmarshalled
TestOptionSubStruct `yaml:",inline,squash"`
}
var testOptionsConfigBytesFull = []byte(`
stringOption: foo
stringSliceOption:
- a
- b
- c
sub:
stringSliceOption:
- d
- e
`)
type loadYAMLTableInput struct {
configFile []byte
input interface{}
expectedErr error
expectedOutput interface{}
}
DescribeTable("LoadYAML",
func(in loadYAMLTableInput) {
var configFileName string
if in.configFile != nil {
By("Creating a config file")
configFile, err := ioutil.TempFile("", "oauth2-proxy-test-config-file")
Expect(err).ToNot(HaveOccurred())
defer configFile.Close()
_, err = configFile.Write(in.configFile)
Expect(err).ToNot(HaveOccurred())
defer os.Remove(configFile.Name())
configFileName = configFile.Name()
}
var input interface{}
if in.input != nil {
input = in.input
} else {
input = &TestOptions{}
}
err := LoadYAML(configFileName, input)
if in.expectedErr != nil {
Expect(err).To(MatchError(in.expectedErr.Error()))
} else {
Expect(err).ToNot(HaveOccurred())
}
Expect(input).To(Equal(in.expectedOutput))
},
Entry("with a valid input", loadYAMLTableInput{
configFile: testOptionsConfigBytesFull,
input: &TestOptions{},
expectedOutput: &TestOptions{
StringOption: "foo",
Sub: TestOptionSubStruct{
StringSliceOption: []string{"d", "e"},
},
TestOptionSubStruct: TestOptionSubStruct{
StringSliceOption: []string{"a", "b", "c"},
},
},
}),
Entry("with no config file", loadYAMLTableInput{
configFile: nil,
input: &TestOptions{},
expectedOutput: &TestOptions{},
expectedErr: errors.New("no configuration file provided"),
}),
Entry("with invalid YAML", loadYAMLTableInput{
configFile: []byte("\tfoo: bar"),
input: &TestOptions{},
expectedOutput: &TestOptions{},
expectedErr: errors.New("error unmarshalling config: error converting YAML to JSON: yaml: found character that cannot start any token"),
}),
Entry("with extra fields in the YAML", loadYAMLTableInput{
configFile: append(testOptionsConfigBytesFull, []byte("foo: bar\n")...),
input: &TestOptions{},
expectedOutput: &TestOptions{
StringOption: "foo",
Sub: TestOptionSubStruct{
StringSliceOption: []string{"d", "e"},
},
TestOptionSubStruct: TestOptionSubStruct{
StringSliceOption: []string{"a", "b", "c"},
},
},
expectedErr: errors.New("error unmarshalling config: error unmarshaling JSON: while decoding JSON: json: unknown field \"foo\""),
}),
Entry("with an incorrect type for a string field", loadYAMLTableInput{
configFile: []byte(`stringOption: ["a", "b"]`),
input: &TestOptions{},
expectedOutput: &TestOptions{},
expectedErr: errors.New("error unmarshalling config: error unmarshaling JSON: while decoding JSON: json: cannot unmarshal array into Go struct field TestOptions.StringOption of type string"),
}),
Entry("with an incorrect type for an array field", loadYAMLTableInput{
configFile: []byte(`stringSliceOption: "a"`),
input: &TestOptions{},
expectedOutput: &TestOptions{},
expectedErr: errors.New("error unmarshalling config: error unmarshaling JSON: while decoding JSON: json: cannot unmarshal string into Go struct field TestOptions.StringSliceOption of type []string"),
}),
)
})
It("should load a full example AlphaOptions", func() {
config := []byte(`
upstreams:
- id: httpbin
path: /
uri: http://httpbin
flushInterval: 500ms
injectRequestHeaders:
- name: X-Forwarded-User
values:
- claim: user
injectResponseHeaders:
- name: X-Secret
values:
- value: c2VjcmV0
`)
By("Creating a config file")
configFile, err := ioutil.TempFile("", "oauth2-proxy-test-alpha-config-file")
Expect(err).ToNot(HaveOccurred())
defer configFile.Close()
_, err = configFile.Write(config)
Expect(err).ToNot(HaveOccurred())
defer os.Remove(configFile.Name())
configFileName := configFile.Name()
By("Loading the example config")
into := &AlphaOptions{}
Expect(LoadYAML(configFileName, into)).To(Succeed())
flushInterval := Duration(500 * time.Millisecond)
Expect(into).To(Equal(&AlphaOptions{
Upstreams: []Upstream{
{
ID: "httpbin",
Path: "/",
URI: "http://httpbin",
FlushInterval: &flushInterval,
},
},
InjectRequestHeaders: []Header{
{
Name: "X-Forwarded-User",
Values: []HeaderValue{
{
ClaimSource: &ClaimSource{
Claim: "user",
},
},
},
},
},
InjectResponseHeaders: []Header{
{
Name: "X-Secret",
Values: []HeaderValue{
{
SecretSource: &SecretSource{
Value: []byte("secret"),
},
},
},
},
},
}))
})
})

View File

@ -246,8 +246,6 @@ func NewFlagSet() *pflag.FlagSet {
flagSet.AddFlagSet(cookieFlagSet())
flagSet.AddFlagSet(loggingFlagSet())
flagSet.AddFlagSet(legacyUpstreamsFlagSet())
flagSet.AddFlagSet(legacyHeadersFlagSet())
return flagSet
}