You've already forked oauth2-proxy
							
							
				mirror of
				https://github.com/oauth2-proxy/oauth2-proxy.git
				synced 2025-10-30 23:47:52 +02:00 
			
		
		
		
	Introduce alpha configuration loading
This commit is contained in:
		
							
								
								
									
										1
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								go.mod
									
									
									
									
									
								
							| @@ -11,6 +11,7 @@ require ( | ||||
| 	github.com/dgrijalva/jwt-go v3.2.0+incompatible | ||||
| 	github.com/frankban/quicktest v1.10.0 // indirect | ||||
| 	github.com/fsnotify/fsnotify v1.4.9 | ||||
| 	github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 | ||||
| 	github.com/go-redis/redis/v8 v8.2.3 | ||||
| 	github.com/justinas/alice v1.2.0 | ||||
| 	github.com/mbland/hmacauth v0.0.0-20170912233209-44256dfd4bfa | ||||
|   | ||||
							
								
								
									
										3
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										3
									
								
								go.sum
									
									
									
									
									
								
							| @@ -64,7 +64,10 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo | ||||
| github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= | ||||
| github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= | ||||
| github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= | ||||
| github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= | ||||
| github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= | ||||
| github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 h1:Mn26/9ZMNWSw9C9ERFA1PUxfmGpolnw2v0bKOREu5ew= | ||||
| github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I= | ||||
| github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= | ||||
| github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= | ||||
| github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= | ||||
|   | ||||
							
								
								
									
										96
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										96
									
								
								main.go
									
									
									
									
									
								
							| @@ -12,36 +12,26 @@ import ( | ||||
| 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options" | ||||
| 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger" | ||||
| 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/validation" | ||||
| 	"github.com/spf13/pflag" | ||||
| ) | ||||
|  | ||||
| func main() { | ||||
| 	logger.SetFlags(logger.Lshortfile) | ||||
| 	flagSet := options.NewFlagSet() | ||||
|  | ||||
| 	config := flagSet.String("config", "", "path to config file") | ||||
| 	showVersion := flagSet.Bool("version", false, "print version string") | ||||
|  | ||||
| 	err := flagSet.Parse(os.Args[1:]) | ||||
| 	if err != nil { | ||||
| 		logger.Printf("ERROR: Failed to parse flags: %v", err) | ||||
| 		os.Exit(1) | ||||
| 	} | ||||
| 	configFlagSet := pflag.NewFlagSet("oauth2-proxy", pflag.ContinueOnError) | ||||
| 	config := configFlagSet.String("config", "", "path to config file") | ||||
| 	alphaConfig := configFlagSet.String("alpha-config", "", "path to alpha config file (use at your own risk - the structure in this config file may change between minor releases)") | ||||
| 	showVersion := configFlagSet.Bool("version", false, "print version string") | ||||
| 	configFlagSet.Parse(os.Args[1:]) | ||||
|  | ||||
| 	if *showVersion { | ||||
| 		fmt.Printf("oauth2-proxy %s (built with %s)\n", VERSION, runtime.Version()) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	legacyOpts := options.NewLegacyOptions() | ||||
| 	err = options.Load(*config, flagSet, legacyOpts) | ||||
| 	opts, err := loadConfiguration(*config, *alphaConfig, configFlagSet, os.Args[1:]) | ||||
| 	if err != nil { | ||||
| 		logger.Errorf("ERROR: Failed to load config: %v", err) | ||||
| 		os.Exit(1) | ||||
| 	} | ||||
|  | ||||
| 	opts, err := legacyOpts.ToOptions() | ||||
| 	if err != nil { | ||||
| 		logger.Errorf("ERROR: Failed to convert config: %v", err) | ||||
| 		logger.Printf("ERROR: %v", err) | ||||
| 		os.Exit(1) | ||||
| 	} | ||||
|  | ||||
| @@ -74,3 +64,73 @@ func main() { | ||||
| 	}() | ||||
| 	s.ListenAndServe() | ||||
| } | ||||
|  | ||||
| // loadConfiguration will load in the user's configuration. | ||||
| // It will either load the alpha configuration (if alphaConfig is given) | ||||
| // or the legacy configuration. | ||||
| func loadConfiguration(config, alphaConfig string, extraFlags *pflag.FlagSet, args []string) (*options.Options, error) { | ||||
| 	if alphaConfig != "" { | ||||
| 		logger.Printf("WARNING: You are using alpha configuration. The structure in this configuration file may change without notice. You MUST remove conflicting options from your existing configuration.") | ||||
| 		return loadAlphaOptions(config, alphaConfig, extraFlags, args) | ||||
| 	} | ||||
| 	return loadLegacyOptions(config, extraFlags, args) | ||||
| } | ||||
|  | ||||
| // loadLegacyOptions loads the old toml options using the legacy flagset | ||||
| // and legacy options struct. | ||||
| func loadLegacyOptions(config string, extraFlags *pflag.FlagSet, args []string) (*options.Options, error) { | ||||
| 	optionsFlagSet := options.NewLegacyFlagSet() | ||||
| 	optionsFlagSet.AddFlagSet(extraFlags) | ||||
| 	if err := optionsFlagSet.Parse(args); err != nil { | ||||
| 		return nil, fmt.Errorf("failed to parse flags: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	legacyOpts := options.NewLegacyOptions() | ||||
| 	if err := options.Load(config, optionsFlagSet, legacyOpts); err != nil { | ||||
| 		return nil, fmt.Errorf("failed to load config: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	opts, err := legacyOpts.ToOptions() | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("failed to convert config: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	return opts, nil | ||||
| } | ||||
|  | ||||
| // loadAlphaOptions loads the old style config excluding options converted to | ||||
| // the new alpha format, then merges the alpha options, loaded from YAML, | ||||
| // into the core configuration. | ||||
| func loadAlphaOptions(config, alphaConfig string, extraFlags *pflag.FlagSet, args []string) (*options.Options, error) { | ||||
| 	opts, err := loadOptions(config, extraFlags, args) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("failed to load core options: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	alphaOpts := &options.AlphaOptions{} | ||||
| 	if err := options.LoadYAML(alphaConfig, alphaOpts); err != nil { | ||||
| 		return nil, fmt.Errorf("failed to load alpha options: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	alphaOpts.MergeInto(opts) | ||||
| 	return opts, nil | ||||
| } | ||||
|  | ||||
| // loadOptions loads the configuration using the old style format into the | ||||
| // core options.Options struct. | ||||
| // This means that none of the options that have been converted to alpha config | ||||
| // will be loaded using this method. | ||||
| func loadOptions(config string, extraFlags *pflag.FlagSet, args []string) (*options.Options, error) { | ||||
| 	optionsFlagSet := options.NewFlagSet() | ||||
| 	optionsFlagSet.AddFlagSet(extraFlags) | ||||
| 	if err := optionsFlagSet.Parse(args); err != nil { | ||||
| 		return nil, fmt.Errorf("failed to parse flags: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	opts := options.NewOptions() | ||||
| 	if err := options.Load(config, optionsFlagSet, opts); err != nil { | ||||
| 		return nil, fmt.Errorf("failed to load config: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	return opts, nil | ||||
| } | ||||
|   | ||||
							
								
								
									
										16
									
								
								main_suite_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								main_suite_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger" | ||||
| 	. "github.com/onsi/ginkgo" | ||||
| 	. "github.com/onsi/gomega" | ||||
| ) | ||||
|  | ||||
| func TestMainSuite(t *testing.T) { | ||||
| 	logger.SetOutput(GinkgoWriter) | ||||
|  | ||||
| 	RegisterFailHandler(Fail) | ||||
| 	RunSpecs(t, "Main Suite") | ||||
| } | ||||
							
								
								
									
										215
									
								
								main_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										215
									
								
								main_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,215 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"io/ioutil" | ||||
| 	"os" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options" | ||||
| 	. "github.com/onsi/ginkgo" | ||||
| 	. "github.com/onsi/ginkgo/extensions/table" | ||||
| 	. "github.com/onsi/gomega" | ||||
| 	"github.com/spf13/pflag" | ||||
| ) | ||||
|  | ||||
| var _ = Describe("Configuration Loading Suite", func() { | ||||
| 	const testLegacyConfig = ` | ||||
| upstreams="http://httpbin" | ||||
| set_basic_auth="true" | ||||
| basic_auth_password="super-secret-password" | ||||
| ` | ||||
|  | ||||
| 	const testAlphaConfig = ` | ||||
| upstreams: | ||||
|   - id: / | ||||
|     path: / | ||||
|     uri: http://httpbin | ||||
|     flushInterval: 1s | ||||
|     passHostHeader: true | ||||
|     proxyWebSockets: true | ||||
| injectRequestHeaders: | ||||
| - name: Authorization | ||||
|   values: | ||||
|   - claim: user | ||||
|     prefix: "Basic " | ||||
|     basicAuthPassword: | ||||
|       value: c3VwZXItc2VjcmV0LXBhc3N3b3Jk | ||||
| - name: X-Forwarded-Groups | ||||
|   values: | ||||
|   - claim: groups | ||||
| - name: X-Forwarded-User | ||||
|   values: | ||||
|   - claim: user | ||||
| - name: X-Forwarded-Email | ||||
|   values: | ||||
|   - claim: email | ||||
| - name: X-Forwarded-Preferred-Username | ||||
|   values: | ||||
|   - claim: preferred_username | ||||
| injectResponseHeaders: | ||||
| - name: Authorization | ||||
|   values: | ||||
|   - claim: user | ||||
|     prefix: "Basic " | ||||
|     basicAuthPassword: | ||||
|       value: c3VwZXItc2VjcmV0LXBhc3N3b3Jk | ||||
| ` | ||||
|  | ||||
| 	const testCoreConfig = ` | ||||
| http_address="0.0.0.0:4180" | ||||
| cookie_secret="OQINaROshtE9TcZkNAm-5Zs2Pv3xaWytBmc5W7sPX7w=" | ||||
| provider="oidc" | ||||
| email_domains="example.com" | ||||
| oidc_issuer_url="http://dex.localhost:4190/dex" | ||||
| client_secret="b2F1dGgyLXByb3h5LWNsaWVudC1zZWNyZXQK" | ||||
| client_id="oauth2-proxy" | ||||
| cookie_secure="false" | ||||
|  | ||||
| redirect_url="http://localhost:4180/oauth2/callback" | ||||
| ` | ||||
|  | ||||
| 	boolPtr := func(b bool) *bool { | ||||
| 		return &b | ||||
| 	} | ||||
|  | ||||
| 	durationPtr := func(d time.Duration) *options.Duration { | ||||
| 		du := options.Duration(d) | ||||
| 		return &du | ||||
| 	} | ||||
|  | ||||
| 	testExpectedOptions := func() *options.Options { | ||||
| 		opts, err := options.NewLegacyOptions().ToOptions() | ||||
| 		Expect(err).ToNot(HaveOccurred()) | ||||
|  | ||||
| 		opts.HTTPAddress = "0.0.0.0:4180" | ||||
| 		opts.Cookie.Secret = "OQINaROshtE9TcZkNAm-5Zs2Pv3xaWytBmc5W7sPX7w=" | ||||
| 		opts.ProviderType = "oidc" | ||||
| 		opts.EmailDomains = []string{"example.com"} | ||||
| 		opts.OIDCIssuerURL = "http://dex.localhost:4190/dex" | ||||
| 		opts.ClientSecret = "b2F1dGgyLXByb3h5LWNsaWVudC1zZWNyZXQK" | ||||
| 		opts.ClientID = "oauth2-proxy" | ||||
| 		opts.Cookie.Secure = false | ||||
| 		opts.RawRedirectURL = "http://localhost:4180/oauth2/callback" | ||||
|  | ||||
| 		opts.UpstreamServers = options.Upstreams{ | ||||
| 			{ | ||||
| 				ID:              "/", | ||||
| 				Path:            "/", | ||||
| 				URI:             "http://httpbin", | ||||
| 				FlushInterval:   durationPtr(options.DefaultUpstreamFlushInterval), | ||||
| 				PassHostHeader:  boolPtr(true), | ||||
| 				ProxyWebSockets: boolPtr(true), | ||||
| 			}, | ||||
| 		} | ||||
|  | ||||
| 		authHeader := options.Header{ | ||||
| 			Name: "Authorization", | ||||
| 			Values: []options.HeaderValue{ | ||||
| 				{ | ||||
| 					ClaimSource: &options.ClaimSource{ | ||||
| 						Claim:  "user", | ||||
| 						Prefix: "Basic ", | ||||
| 						BasicAuthPassword: &options.SecretSource{ | ||||
| 							Value: []byte("super-secret-password"), | ||||
| 						}, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 		} | ||||
|  | ||||
| 		opts.InjectRequestHeaders = append([]options.Header{authHeader}, opts.InjectRequestHeaders...) | ||||
| 		opts.InjectResponseHeaders = append(opts.InjectResponseHeaders, authHeader) | ||||
| 		return opts | ||||
| 	} | ||||
|  | ||||
| 	type loadConfigurationTableInput struct { | ||||
| 		configContent      string | ||||
| 		alphaConfigContent string | ||||
| 		args               []string | ||||
| 		extraFlags         func() *pflag.FlagSet | ||||
| 		expectedOptions    func() *options.Options | ||||
| 		expectedErr        error | ||||
| 	} | ||||
|  | ||||
| 	DescribeTable("LoadConfiguration", | ||||
| 		func(in loadConfigurationTableInput) { | ||||
| 			var configFileName, alphaConfigFileName string | ||||
|  | ||||
| 			defer func() { | ||||
| 				if configFileName != "" { | ||||
| 					Expect(os.Remove(configFileName)).To(Succeed()) | ||||
| 				} | ||||
| 				if alphaConfigFileName != "" { | ||||
| 					Expect(os.Remove(alphaConfigFileName)).To(Succeed()) | ||||
| 				} | ||||
| 			}() | ||||
|  | ||||
| 			if in.configContent != "" { | ||||
| 				By("Writing the config to a temporary file", func() { | ||||
| 					file, err := ioutil.TempFile("", "oauth2-proxy-test-config-XXXX.cfg") | ||||
| 					Expect(err).ToNot(HaveOccurred()) | ||||
| 					defer file.Close() | ||||
|  | ||||
| 					configFileName = file.Name() | ||||
|  | ||||
| 					_, err = file.WriteString(in.configContent) | ||||
| 					Expect(err).ToNot(HaveOccurred()) | ||||
| 				}) | ||||
| 			} | ||||
|  | ||||
| 			if in.alphaConfigContent != "" { | ||||
| 				By("Writing the config to a temporary file", func() { | ||||
| 					file, err := ioutil.TempFile("", "oauth2-proxy-test-alpha-config-XXXX.yaml") | ||||
| 					Expect(err).ToNot(HaveOccurred()) | ||||
| 					defer file.Close() | ||||
|  | ||||
| 					alphaConfigFileName = file.Name() | ||||
|  | ||||
| 					_, err = file.WriteString(in.alphaConfigContent) | ||||
| 					Expect(err).ToNot(HaveOccurred()) | ||||
| 				}) | ||||
| 			} | ||||
|  | ||||
| 			extraFlags := pflag.NewFlagSet("test-flagset", pflag.ExitOnError) | ||||
| 			if in.extraFlags != nil { | ||||
| 				extraFlags = in.extraFlags() | ||||
| 			} | ||||
|  | ||||
| 			opts, err := loadConfiguration(configFileName, alphaConfigFileName, extraFlags, in.args) | ||||
| 			if in.expectedErr != nil { | ||||
| 				Expect(err).To(MatchError(in.expectedErr.Error())) | ||||
| 			} else { | ||||
| 				Expect(err).ToNot(HaveOccurred()) | ||||
| 			} | ||||
| 			Expect(in.expectedOptions).ToNot(BeNil()) | ||||
| 			Expect(opts).To(Equal(in.expectedOptions())) | ||||
| 		}, | ||||
| 		Entry("with legacy configuration", loadConfigurationTableInput{ | ||||
| 			configContent:   testCoreConfig + testLegacyConfig, | ||||
| 			expectedOptions: testExpectedOptions, | ||||
| 		}), | ||||
| 		Entry("with alpha configuration", loadConfigurationTableInput{ | ||||
| 			configContent:      testCoreConfig, | ||||
| 			alphaConfigContent: testAlphaConfig, | ||||
| 			expectedOptions:    testExpectedOptions, | ||||
| 		}), | ||||
| 		Entry("with bad legacy configuration", loadConfigurationTableInput{ | ||||
| 			configContent:   testCoreConfig + "unknown_field=\"something\"", | ||||
| 			expectedOptions: func() *options.Options { return nil }, | ||||
| 			expectedErr:     errors.New("failed to load config: error unmarshalling config: 1 error(s) decoding:\n\n* '' has invalid keys: unknown_field"), | ||||
| 		}), | ||||
| 		Entry("with bad alpha configuration", loadConfigurationTableInput{ | ||||
| 			configContent:      testCoreConfig, | ||||
| 			alphaConfigContent: testAlphaConfig + ":", | ||||
| 			expectedOptions:    func() *options.Options { return nil }, | ||||
| 			expectedErr:        errors.New("failed to load alpha options: error unmarshalling config: error converting YAML to JSON: yaml: line 34: did not find expected key"), | ||||
| 		}), | ||||
| 		Entry("with alpha configuration and bad core configuration", loadConfigurationTableInput{ | ||||
| 			configContent:      testCoreConfig + "unknown_field=\"something\"", | ||||
| 			alphaConfigContent: testAlphaConfig, | ||||
| 			expectedOptions:    func() *options.Options { return nil }, | ||||
| 			expectedErr:        errors.New("failed to load core options: failed to load config: error unmarshalling config: 1 error(s) decoding:\n\n* '' has invalid keys: unknown_field"), | ||||
| 		}), | ||||
| 	) | ||||
| }) | ||||
| @@ -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 | ||||
| } | ||||
|   | ||||
| @@ -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 { | ||||
|   | ||||
| @@ -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 | ||||
| } | ||||
|   | ||||
| @@ -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"), | ||||
| 							}, | ||||
| 						}, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 		})) | ||||
| 	}) | ||||
| }) | ||||
|   | ||||
| @@ -246,8 +246,6 @@ func NewFlagSet() *pflag.FlagSet { | ||||
|  | ||||
| 	flagSet.AddFlagSet(cookieFlagSet()) | ||||
| 	flagSet.AddFlagSet(loggingFlagSet()) | ||||
| 	flagSet.AddFlagSet(legacyUpstreamsFlagSet()) | ||||
| 	flagSet.AddFlagSet(legacyHeadersFlagSet()) | ||||
|  | ||||
| 	return flagSet | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user