Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

tuunit
Copy link
Member

@tuunit tuunit commented May 4, 2024

Description

This PR introduces mapstructure for decoding and encoding the yaml files. Mapstructure is a defacto standard library used by many libraries like spf13/viper for more dynamic data loading between different interfaces.

Just using the golang json / yaml encoding / decoding would lead to overwriting the default set before loading the config file. Trying to load the default configs afterwards would be rather hard or impossible.

Motivation and Context

Cases to consider:

  1. When directly loading into a struct you cannot identify if the user explicitly set a boolean to false in the config file or if it was set to the boolean default of false. This is problematic as we have boolean like Cookie.Secure that are supposed to be set to true by default.
  2. For better readability we might want to squash elements in the yaml like HeaderValues either being a SecretSource or ClaimSource. Which isn't supported by the default json / yaml parser
# non-squashed
headers:
- name: X-Forwarded-User
  values:
  - claimSource:
      claim: user

# squashed
headers:
- name: X-Forwarded-User
  values:
  - claim: user
  1. Parsing of time primitives like Duration or Time through strings. If you want to be able to configure Duration types with values like 2h. You need to implement a custom wrapper type and Marshal and Unmarshal method. Mapstructure allows for decoding hooks through the usage of relection.

How Has This Been Tested?

  1. Test cases have been extend / adapted.
  2. Starting the application with toml config file
  3. Using the conversion function from toml to yaml
  4. Starting the application with yaml config file

Checklist:

  • My change requires a change to the documentation or CHANGELOG.
  • I have updated the documentation/CHANGELOG accordingly.
  • I have created a feature (non-master) branch for my PR.
  • I have written tests for my code changes.

@tuunit tuunit force-pushed the use-mapstructures-for-parsing-and-merging branch from 710f3a7 to 72dff61 Compare May 4, 2024 14:44
@github-actions github-actions bot added the docs label May 4, 2024
@tuunit tuunit changed the title introduce mapstructure decoder for yaml parsing structured logging #1: introduce mapstructure decoder for yaml parsing May 4, 2024
@tuunit tuunit changed the title structured logging #1: introduce mapstructure decoder for yaml parsing structured config #1: introduce mapstructure decoder for yaml parsing May 4, 2024
@tuunit tuunit force-pushed the use-mapstructures-for-parsing-and-merging branch from 39b625e to 3fb3c1f Compare May 9, 2024 22:00
@tuunit tuunit marked this pull request as ready for review May 10, 2024 07:34
@tuunit tuunit requested a review from a team as a code owner May 10, 2024 07:34
@tuunit tuunit force-pushed the use-mapstructures-for-parsing-and-merging branch from 7f8def6 to cd566ee Compare May 12, 2024 17:40
Copy link
Contributor

This pull request has been inactive for 60 days. If the pull request is still relevant please comment to re-activate the pull request. If no action is taken within 7 days, the pull request will be marked closed.

@github-actions github-actions bot added the Stale label Jul 12, 2024

// Allow users to load the value from a session claim
*ClaimSource `json:",omitempty"`
*ClaimSource `json:"claimSource,omitempty"`
Copy link
Member

Choose a reason for hiding this comment

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

We probably want to call this something different, such as, fromValue with the previous field being fromSecret

Adding json tags to these is odd though since they are inlined, do you recall why this was needed?

I know on another PR we are talking about moving the prefix, is this the PR where in fact we should make this a discriminated union?

Copy link
Member Author

Choose a reason for hiding this comment

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

They are inlined but because they are pointers mapstructures cannot resolve them properly. I wasn't able to get it to work otherwise, leading to this structure change:

image

You have to explicitly mention if you want to use a claimSource or secretSource for each HeaderValue you set.

IssuerURL string `json:"issuerURL,omitempty"`
// InsecureAllowUnverifiedEmail prevents failures if an email address in an id_token is not verified
// default set to 'false'
InsecureAllowUnverifiedEmail bool `json:"insecureAllowUnverifiedEmail,omitempty"`
Copy link
Member

Choose a reason for hiding this comment

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

Why no longer omitempty?

Copy link
Member Author

Choose a reason for hiding this comment

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

I decided to not omit any booleans. To ensure that the conversion flow from toml to yaml explicitly mentions all boolean values.

Copy link
Member

Choose a reason for hiding this comment

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

Booleans really want to be pointers with omitempty IMO, else we don't have a way to tell between an explicitly set false value or implicitly set false just by the fact that false is the zero value

Would making them all pointers be complex for the conversion flow do you think?

@github-actions github-actions bot removed the Stale label Jul 15, 2024
@tuunit tuunit changed the base branch from master to release/v8.0.0 August 20, 2024 08:44
Copy link
Contributor

This pull request has been inactive for 60 days. If the pull request is still relevant please comment to re-activate the pull request. If no action is taken within 7 days, the pull request will be marked closed.

@github-actions github-actions bot added the Stale label Oct 20, 2024
@github-actions github-actions bot closed this Nov 4, 2024
@tuunit tuunit reopened this Jan 4, 2025
@tuunit tuunit added this to the v8.0.0 milestone Jan 4, 2025
@github-actions github-actions bot removed the Stale label Jan 5, 2025
@tuunit tuunit force-pushed the use-mapstructures-for-parsing-and-merging branch from 4941bf1 to 629ac24 Compare February 1, 2025 09:20
@github-actions github-actions bot added the dependencies Pull requests that update a dependency file label Feb 1, 2025
@dolmen
Copy link
Contributor

dolmen commented May 26, 2025

I've developped independently #3064 which just handles the upgrade of viper+mapstructure. I think it would be worth doing that to keep this (2628) focused on behaviour changes.

@tuunit tuunit force-pushed the use-mapstructures-for-parsing-and-merging branch 3 times, most recently from 4158184 to f46e023 Compare July 26, 2025 15:03
@tuunit
Copy link
Member Author

tuunit commented Aug 12, 2025

/e2e

@tuunit tuunit requested a review from Copilot August 13, 2025 06:57
Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR introduces mapstructure as the decoder for YAML configuration parsing, replacing the previous ghodss/yaml library. The change enables better handling of default values, type conversion, and field mapping while preserving existing configuration behavior.

Key changes:

  • Replaces options.Duration wrapper type with native time.Duration throughout the codebase
  • Implements mapstructure-based YAML parsing with decode hooks for duration and byte conversion
  • Updates struct tags from json to yaml across all configuration structs

Reviewed Changes

Copilot reviewed 31 out of 32 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
pkg/apis/options/load.go Implements new LoadYAML function using mapstructure decoder with custom hooks
pkg/apis/options/hooks.go Adds decode hooks for duration parsing and string-to-bytes conversion
pkg/apis/options/upstreams.go Updates FlushInterval and Timeout from Duration wrapper to time.Duration
pkg/apis/options/*.go Changes struct tags from json to yaml across configuration structs
pkg/validation/upstreams.go Updates duration comparison logic for new time.Duration type
pkg/upstream/http.go Removes Duration wrapper usage in favor of direct time.Duration
main.go Updates configuration loading flow and YAML marshaling logic

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
You can also share your feedback on Copilot code review for a chance to win a $100 gift card. Take the survey.

@tuunit tuunit force-pushed the use-mapstructures-for-parsing-and-merging branch 2 times, most recently from a471d71 to bc1d897 Compare August 18, 2025 19:37
Comment on lines +9 to +10
// FromEnv expects the name of an environment variable.
FromEnv string `yaml:"fromEnv,omitempty"`
Copy link
Member

Choose a reason for hiding this comment

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

Not blocking for this PR, but I thought now we have env substitution, we won't need this 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.

I would like to do a cleanup and review of our options API after we have the full alpha config support done

IssuerURL string `json:"issuerURL,omitempty"`
// InsecureAllowUnverifiedEmail prevents failures if an email address in an id_token is not verified
// default set to 'false'
InsecureAllowUnverifiedEmail bool `json:"insecureAllowUnverifiedEmail,omitempty"`
Copy link
Member

Choose a reason for hiding this comment

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

Booleans really want to be pointers with omitempty IMO, else we don't have a way to tell between an explicitly set false value or implicitly set false just by the fact that false is the zero value

Would making them all pointers be complex for the conversion flow do you think?

),
Metadata: nil, // Don't track any metadata
Result: result, // Decode the result into the prefilled options
TagName: "yaml", // Parse all fields that use the json tag
Copy link
Member

Choose a reason for hiding this comment

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

Is there a reason to switch to yaml instead of json? Is some other library relying on yaml?

Copy link
Member Author

@tuunit tuunit Aug 19, 2025

Choose a reason for hiding this comment

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

Yes the yaml parser expects it:
https://pkg.go.dev/gopkg.in/yaml.v3

Copy link
Member

Choose a reason for hiding this comment

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

Ack, can you just explain in the comment here why we use the yaml tag

Some yaml libraries like the json tag instead

@tuunit tuunit force-pushed the use-mapstructures-for-parsing-and-merging branch from bc1d897 to d2f72ef Compare August 19, 2025 10:59
@@ -0,0 +1,5 @@
package ptr
Copy link
Member

Choose a reason for hiding this comment

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

Could create generics here?

Also, ptr.Deref which allows you to provide a default value if the pointer is nil?

Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Member Author

Choose a reason for hiding this comment

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

Good idea

@tuunit tuunit force-pushed the use-mapstructures-for-parsing-and-merging branch from f84ed08 to 179e390 Compare August 20, 2025 18:00
func Int(v int) *int { return &v }

// Ptr generically returns a pointer to the given value.
func Ptr[T any](v T) *T {
Copy link
Member

Choose a reason for hiding this comment

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

Maybe To to make it read as ptr.To "pointer to"?

Comment on lines 3 to 5
func Bool(v bool) *bool { return &v }
func String(v string) *string { return &v }
func Int(v int) *int { return &v }
Copy link
Member

Choose a reason for hiding this comment

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

We shouldn't need these anymore right?

}

// Deref returns the value of the pointer or def(ault) if nil.
func Deref[T any](p *T, def T) T {
Copy link
Member

Choose a reason for hiding this comment

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

I saw lots of places where this isn't currently used where it could be, lets try and be exhaustive within the current PR and hopefully avoid panics

headersToStrip := []string{}
for _, header := range headers {
if !header.PreserveRequestValue {
if !(*header.PreserveRequestValue) {
Copy link
Member

Choose a reason for hiding this comment

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

Deref?

// Change default duration for waiting for an upstream response
if upstream.Timeout != nil {
transport.ResponseHeaderTimeout = upstream.Timeout.Duration()
transport.ResponseHeaderTimeout = *upstream.Timeout
Copy link
Member

Choose a reason for hiding this comment

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

Deref?

}

if upstreams.ProxyRawPath {
if *upstreams.ProxyRawPath {
Copy link
Member

Choose a reason for hiding this comment

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

Deref?

tuunit added 12 commits August 27, 2025 09:15
remove color output in tests for better readability in github actions

bugfix: remove google as default provider for alpha options

fix conversion flow for toml to yaml

revert ginkgo color deactivation

revert claim- and secret source back to pointers

regenerate alpha config
Signed-off-by: Jan Larwig <[email protected]>
Signed-off-by: Jan Larwig <[email protected]>
@tuunit tuunit force-pushed the use-mapstructures-for-parsing-and-merging branch from 179e390 to 9db7738 Compare August 27, 2025 07:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
dependencies Pull requests that update a dependency file docs e2e/failed go provider tests
Projects
Development

Successfully merging this pull request may close these issues.

4 participants