Skip to content

fix: env var decoding for bool config fields and redis sentinel multi-node support and change fs.FS path construction#4496

Open
vsengar-79 wants to merge 5 commits into
openmeterio:mainfrom
vsengar-79:main
Open

fix: env var decoding for bool config fields and redis sentinel multi-node support and change fs.FS path construction#4496
vsengar-79 wants to merge 5 commits into
openmeterio:mainfrom
vsengar-79:main

Conversation

@vsengar-79

@vsengar-79 vsengar-79 commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Overview

Four small fixes for running OpenMeter in Kubernetes where config values arrive as environment variable strings rather than typed YAML values.

  1. app/config/viper.gobool env var decoding

Added StringToBasicTypeHookFunc() to the global DecodeHook. When config values are set via env vars or ConfigMaps, they are always strings — "true", "false", "1". Without this hook, any bool config field panics on startup with:

expected type 'bool', got unconvertible type 'string', value: 'false'

This affects fields like DEDUPE_ENABLED, NOTIFICATION_ENABLED, and any other boolean config sourced from the environment.

  1. app/config/sink.goconsumer group ID key name typo

sink.kafka.consumerGroupIdsink.kafka.consumerGroupID (capital ID to match the struct field's mapstructure tag). The previous key never matched the actual config field, so the default value "openmeter-sink-worker" was never applied. The sink-worker joined Kafka with an empty consumer group ID, which caused Kafka to reject partition assignment and left events unsynced to ClickHouse.

  1. pkg/redis/client.goRedis Sentinel multi-node support

The Address field now supports a comma-separated list of sentinel nodes (e.g. "sentinel1:26379,sentinel2:26379,sentinel3:26379"). Previously it was hardcoded to []string{o.Address}, which only ever used a single sentinel endpoint regardless of what was configured.

  1. tools/migrate/fs.gouse path.Join instead of filepath.Join for fs.FS path construction

filepath.Join uses the OS path separator (\ on Windows), which breaks fs.FS implementations that require forward-slash paths per the io/fs spec. Replace with path.Join which always uses /

Notes for reviewer

  • StringToBasicTypeHookFunc only activates when the target struct field is a basic type (bool, int, float64) — it has no effect on string fields or interface{} maps. Existing YAML-based configs are unaffected since YAML already deserialises native types correctly.
  • The sentinel change is backward compatible — strings.Split on a non-comma string returns a single-element slice, identical to the previous []string{o.Address} behaviour.
  • The consumer group typo fix has no migration concern — the field was silently broken before, so no existing deployment relied on the wrong default being applied.
  • The path package (always forward-slash) is used instead of filepath (OS-dependent separator) because fs.FS requires forward-slash paths on all platforms per the io/fs spec; the function parameter was renamed from path to dir to avoid shadowing the import.

Summary by CodeRabbit

  • Chores
    • Normalized Kafka consumer group configuration key naming for consistency.
    • Redis Sentinel failover now accepts comma-separated sentinel node addresses.
    • Configuration decoding enhanced to support more string-to-type conversions.

Signed-off-by: vsengar-79 <146079793+vsengar-79@users.noreply.github.com>
Signed-off-by: vsengar-79 <146079793+vsengar-79@users.noreply.github.com>
Signed-off-by: vsengar-79 <146079793+vsengar-79@users.noreply.github.com>
@vsengar-79 vsengar-79 requested a review from a team as a code owner June 9, 2026 14:47
@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Three focused edits: normalize the Kafka consumer group viper key casing, add a mapstructure string-to-basic-type decode hook, support comma-separated Redis Sentinel addresses, and switch migration FS path joins to forward-slash path usage.

Changes

Configuration and Client Setup

Layer / File(s) Summary
Mapstructure decode hook chain extension
app/config/viper.go
mapstructure.StringToBasicTypeHookFunc() added to the composed DecodeHook() chain after the string-to-slice hook.
Sink Kafka consumer group config key normalization
app/config/sink.go
Viper default key changed from sink.kafka.consumerGroupId to sink.kafka.consumerGroupID.
Redis Sentinel comma-separated address parsing
pkg/redis/client.go
Adds strings import and updates Sentinel failover initialization to split Options.Address on commas, trim entries, and pass the resulting slice to redis.FailoverOptions.SentinelAddrs.
Migration FS path handling (forward-slash)
tools/migrate/fs.go
Replaces filepath usage with path, renames ReadDir parameter to dir, and uses path.Join(dir, entry.Name()) for recursion to align with fs.FS paths.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title covers three distinct fixes (bool config decoding, Redis Sentinel support, and fs.FS path construction) but is somewhat condensed and could be clearer about which change is primary.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
pkg/redis/client.go (1)

3-11: ⚠️ Potential issue | 🔴 Critical

Add the missing strings import in pkg/redis/client.go

strings.Split / strings.TrimSpace are referenced, but the strings package isn’t imported, causing undefined: strings build failure.

🔧 Proposed fix
 import (
 	"crypto/tls"
 	"fmt"
+	"strings"
 
 	"github.com/redis/go-redis/extra/redisotel/v9"
 	"github.com/redis/go-redis/v9"
 	"go.opentelemetry.io/otel/metric"
 	"go.opentelemetry.io/otel/trace"
 )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/redis/client.go` around lines 3 - 11, The build fails because
strings.Split and strings.TrimSpace are used but the strings package isn't
imported; update the import block in pkg/redis/client.go to include "strings"
(alongside existing imports like "crypto/tls", "fmt", and
github.com/redis/go-redis/v9) so functions that call strings.Split and
strings.TrimSpace compile correctly.
🧹 Nitpick comments (2)
pkg/redis/client.go (1)

61-64: ⚡ Quick win

Consider filtering out empty addresses.

If the config has consecutive commas (like "node1:26379,,node3:26379") or trailing/leading commas, you'll end up with empty strings in sentinelAddrs after trimming. While this is an edge case, filtering them out would make the initialization more robust and avoid potential confusing errors from the Redis library.

♻️ Optional enhancement
 	// Address may be a comma-separated list of sentinel nodes.
 	sentinelAddrs := strings.Split(o.Address, ",")
-	for i, a := range sentinelAddrs {
-		sentinelAddrs[i] = strings.TrimSpace(a)
+	var trimmed []string
+	for _, a := range sentinelAddrs {
+		if addr := strings.TrimSpace(a); addr != "" {
+			trimmed = append(trimmed, addr)
+		}
 	}
+	sentinelAddrs = trimmed
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/redis/client.go` around lines 61 - 64, The sentinel address parsing
currently splits o.Address and trims entries into sentinelAddrs but doesn't
remove empty strings; update the logic in the sentinelAddrs construction (the
code that splits and trims o.Address in pkg/redis/client.go) to filter out any
empty entries after trimming (e.g., build a new slice or use a filter) so
sentinelAddrs contains only non-empty addresses before it's used to initialize
the Redis sentinel client.
app/config/viper.go (1)

14-14: StringToBasicTypeHookFunc() is real in your pinned mapstructure, and the hook order looks safe—worth adding a bool-from-string test

  • app/config/viper.go uses github.com/go-viper/mapstructure/v2@v2.5.0; StringToBasicTypeHookFunc() exists and is a composite of string→basic-type hooks (including bool via strconv.ParseBool).
  • The bool conversion only runs when the input is a string and the destination kind is bool, so it won’t affect YAML-native non-strings or interface{}/map targets.
  • strconv.ParseBool accepts true/false (any case) plus t/f and 1/0, but not yes/no/on/off.
  • Placing it after StringToSliceHookFunc(",") is consistent: comma-splitting happens first, and bool parsing only kicks in when elements are decoded as strings.

Optional: add a focused unit test for decoding bool fields from env-var-style strings ("true", "false", "1", "0") to lock this behavior down (and optionally assert that "yes" fails).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/config/viper.go` at line 14, The review notes that
StringToBasicTypeHookFunc() in app/config/viper.go is present and safe but
recommends adding a focused unit test to lock bool-from-string behavior; add a
test that decodes into a bool field using mapstructure.Decoder/Unmarshal (or
your existing viper decode path) with env-style string inputs
("true","false","1","0") and assert they decode to the expected booleans, and
also include a negative case (e.g., "yes") to assert it fails or does not decode
to true; reference the existing StringToBasicTypeHookFunc() and
StringToSliceHookFunc(",") usage to ensure the test covers string→slice then
string→bool ordering.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@pkg/redis/client.go`:
- Around line 3-11: The build fails because strings.Split and strings.TrimSpace
are used but the strings package isn't imported; update the import block in
pkg/redis/client.go to include "strings" (alongside existing imports like
"crypto/tls", "fmt", and github.com/redis/go-redis/v9) so functions that call
strings.Split and strings.TrimSpace compile correctly.

---

Nitpick comments:
In `@app/config/viper.go`:
- Line 14: The review notes that StringToBasicTypeHookFunc() in
app/config/viper.go is present and safe but recommends adding a focused unit
test to lock bool-from-string behavior; add a test that decodes into a bool
field using mapstructure.Decoder/Unmarshal (or your existing viper decode path)
with env-style string inputs ("true","false","1","0") and assert they decode to
the expected booleans, and also include a negative case (e.g., "yes") to assert
it fails or does not decode to true; reference the existing
StringToBasicTypeHookFunc() and StringToSliceHookFunc(",") usage to ensure the
test covers string→slice then string→bool ordering.

In `@pkg/redis/client.go`:
- Around line 61-64: The sentinel address parsing currently splits o.Address and
trims entries into sentinelAddrs but doesn't remove empty strings; update the
logic in the sentinelAddrs construction (the code that splits and trims
o.Address in pkg/redis/client.go) to filter out any empty entries after trimming
(e.g., build a new slice or use a filter) so sentinelAddrs contains only
non-empty addresses before it's used to initialize the Redis sentinel client.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 45548d14-973d-4460-a050-d15780ea0e08

📥 Commits

Reviewing files that changed from the base of the PR and between c394e83 and d331837.

📒 Files selected for processing (3)
  • app/config/sink.go
  • app/config/viper.go
  • pkg/redis/client.go

Signed-off-by: vsengar-79 <146079793+vsengar-79@users.noreply.github.com>
filepath.Join uses the OS path separator (\ on Windows), which breaks fs.FS implementations that require forward-slash paths per the io/fs spec. Replace with path.Join which always uses /.

Signed-off-by: vsengar-79 <146079793+vsengar-79@users.noreply.github.com>
@vsengar-79 vsengar-79 changed the title fix: env var decoding for bool config fields and redis sentinel multi-node support fix: env var decoding for bool config fields and redis sentinel multi-node support and change fs.FS path construction Jun 9, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tools/migrate/fs.go (1)

42-55: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Directory entries are read as files after recursion

Nice move switching to path.Join, but there’s a control-flow bug here: after handling a directory (Line 42-49), the code still falls through to fs.ReadFile (Line 53) for that same directory path. That can return an error (is a directory) and stop traversal for nested migration trees.

Suggested fix
 		if entry.IsDir() {
 			r, err := s.ReadDir(filePath)
 			if err != nil {
 				return nil, err
 			}

 			results = append(results, r...)
+			continue
 		}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tools/migrate/fs.go` around lines 42 - 55, The code recurses into directories
with s.ReadDir(entry) but then still falls through to call fs.ReadFile(filePath)
for that directory; add control flow to skip file reading for directories (e.g.,
after appending results from s.ReadDir(filePath) return/continue the loop or use
an else) so that fs.ReadFile is only called for non-directory entries; update
the block around entry.IsDir(), s.ReadDir(filePath), results append, and
fs.ReadFile(s.fsys, filePath) accordingly to prevent "is a directory" read
errors.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@tools/migrate/fs.go`:
- Around line 42-55: The code recurses into directories with s.ReadDir(entry)
but then still falls through to call fs.ReadFile(filePath) for that directory;
add control flow to skip file reading for directories (e.g., after appending
results from s.ReadDir(filePath) return/continue the loop or use an else) so
that fs.ReadFile is only called for non-directory entries; update the block
around entry.IsDir(), s.ReadDir(filePath), results append, and
fs.ReadFile(s.fsys, filePath) accordingly to prevent "is a directory" read
errors.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: de577d74-cf2f-4a6c-bd75-eab5aea5d936

📥 Commits

Reviewing files that changed from the base of the PR and between 560b95b and 658ad9d.

📒 Files selected for processing (1)
  • tools/migrate/fs.go

@vsengar-79

Copy link
Copy Markdown
Contributor Author

Also changed the fs.FS path construction.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant