Repository navigation
feat(hooks): add codereadiness hook - #148
NeaguGeorgiana23 wants to merge 4 commits into
Conversation
Signed-off-by: NeaguGeorgiana23 <neagugeorgiana@google.com>
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars.
|
Signed-off-by: NeaguGeorgiana23 <neagugeorgiana@google.com>
cd2ef9d to
b6340ef
Compare
There was a problem hiding this comment.
🟡 Changes recommended
The global -mno-bmi2 flag breaks non-x86 builds, while CI omits the new hook tests and lenient mode still invokes comparison after comparator initialization fails.
3 open issues
What changed in this PR
Adds a configurable OpenFeature code-readiness hook for #147, allowing flag evaluations to fall back to their default value when the application version is below the flag’s required minimum.
Changes:
- Adds the hook with strict and lenient validation, a configurable metadata key, and custom comparator support.
- Implements SemVer parsing and comparison, with unit tests and usage documentation.
- Adds Bazel library/test targets and compiler workarounds.
| File | Description |
|---|---|
hooks/codereadiness/tests/semver_test.cpp |
Tests parsing and version precedence. |
hooks/codereadiness/tests/semver_comparator_test.cpp |
Tests comparator initialization, results, and concurrent calls. |
hooks/codereadiness/tests/codereadiness_test.cpp |
Tests validation modes, metadata, and custom comparators. |
hooks/codereadiness/tests/BUILD |
Defines three test targets. |
hooks/codereadiness/src/version_comparator.h |
Defines the custom comparator interface. |
hooks/codereadiness/src/semver.h |
Declares the semantic-version model. |
hooks/codereadiness/src/semver.cpp |
Implements parsing and precedence rules. |
hooks/codereadiness/src/semver_comparator.h |
Declares the default comparator. |
hooks/codereadiness/src/semver_comparator.cpp |
Implements minimum-version comparisons. |
hooks/codereadiness/src/codereadiness.h |
Defines hook options and public API. |
hooks/codereadiness/src/codereadiness.cpp |
Implements metadata validation and evaluation failures. |
hooks/codereadiness/src/BUILD |
Defines public library targets. |
hooks/codereadiness/README.md |
Documents setup, behavior, and customization. |
hooks/codereadiness/LICENSE |
Adds the Apache 2.0 license. |
.bazelrc |
Adds BoringSSL warning and BMI2 compiler workarounds. |
Review effort: Balanced
| build --copt=-mno-bmi2 | ||
| build --host_copt=-mno-bmi2 |
There was a problem hiding this comment.
@m-olko wdyt about this comment? I added the workaround because build was failing without it.
There was a problem hiding this comment.
Have you tried updating bazel to 20260817.0? I have seen some threads about similar issues and theoretically they have been fixed with newer versions
Other question is why abseil is not updated automatically by renovate
| VLOG(1) << "codereadiness: invalid current version \"" << current_version | ||
| << "\": " << status; | ||
| } |
| load("@rules_cc//cc:defs.bzl", "cc_test") | ||
|
|
||
| cc_test( | ||
| name = "semver_test", |
bb3eab4 to
751290a
Compare
|
The current version of cpp-sdk doesn't have HookLifecycle implemented, from what I have seen, that's why you need to create manual RunAfter function in your tests. Could you first create new release on cpp-sdk with Hooks fully implemented, and then test the hook in e2e fashion, using openfeature provider? |
Signed-off-by: NeaguGeorgiana23 <neagugeorgiana@google.com>
| if (absl::Status status = comparator->Initialize(current_version); | ||
| !status.ok()) { | ||
| if (options.strict_validation) { | ||
| return absl::Status( | ||
| status.code(), | ||
| absl::StrCat("codereadiness: comparator initialization failed: ", | ||
| status.message())); | ||
| } | ||
| VLOG(1) << "codereadiness: invalid current version \"" << current_version | ||
| << "\": " << status; | ||
| } |
There was a problem hiding this comment.
Inability to initialize the comparator should fail Create() in all modes, not only when strict_validation is enabled. An invalid current_version at startup means the hook is misconfigured, so we should surface that error to the caller immediately rather than creating a broken hook that never validates anything.
Failing Create() unconditionally when comparator->Initialize(current_version) returns a non-OK status also means Compare() is only ever called on a successfully initialized comparator, which simplifies SemVerComparator (no need to track current_version_ or std::optional<SemVer> for uninitialized state) and custom VersionComparator implementations.
| current_version_, "\" - minimum: \"", | ||
| *min_code_version, "\" for flag \"", flag_key, | ||
| "\": ", meets_min_version.status().message())); | ||
| return; | ||
| } | ||
| if (!*meets_min_version) { | ||
| throw std::runtime_error( | ||
| absl::StrCat("current version: \"", current_version_, | ||
| "\" required minimum version: \"", *min_code_version, | ||
| "\" flag \"", flag_key, "\", check failed")); | ||
| } | ||
| } | ||
|
|
||
| void CodeReadinessHook::FailOrSkip(const std::string& message) const { | ||
| if (strict_validation_) { | ||
| throw std::runtime_error(message); | ||
| } | ||
| VLOG(1) << "codereadiness: " << message << ", skipping validation"; | ||
| } |
There was a problem hiding this comment.
The PR description notes that error messages follow the format codereadiness: flag "<key>": <detail>, and Create() prefixes statuses with codereadiness: , but After() and FailOrSkip() only include the codereadiness: prefix in the VLOG(1) output and omit it from the thrown std::runtime_error messages.
Could we align the thrown exception messages with the documented format (or update the PR description if the Go hook's message format is intentional here)?
| load("@rules_cc//cc:defs.bzl", "cc_test") | ||
|
|
||
| cc_test( | ||
| name = "semver_test", |
There was a problem hiding this comment.
+1 to updating .github/workflows/ci-format-test-lint.yml so the test step runs bazel test //... (or includes //hooks/...), and updating HeaderFilterRegex in .clang-tidy from ".*/flagd/.*" to ".*/(flagd|hooks)/.*" so headers under hooks/ are also checked by clang-tidy.
| namespace codereadiness { | ||
|
|
||
| // Represents a Semantic Version (SemVer 2.0.0) for comparison. | ||
| // Parsing follows golang.org/x/mod/semver: optional leading 'v'/'V', MAJOR and |
There was a problem hiding this comment.
NIT: I think other languages accept only 'v' prefix: https://github.com/open-feature/go-sdk-contrib/blob/main/hooks/codereadiness/codereadiness.go#L17


This PR
codereadinesshook: an OpenFeatureafterhook that failsa flag evaluation when the running binary's version is lower than the
minimum version stored in the flag's metadata (default key
minCodeVersion). On failure the hook throws, so the SDK resolves theflag to its default value.
CodeReadinessHookOptions:strict_validation: whentrue, missing or malformed version datafails the evaluation; when
false(the default) it is logged atVLOG(1)and the check is skipped. A well-formed current versionlower than the required one always fails the evaluation.
min_version_metadata_key: the metadata key to read the requiredversion from.
comparator: an optional customVersionComparator.VersionComparatorinterface as an extension point fornon-SemVer version schemes.
SemVerandSemVerComparator, the default implementation:SemVer 2.0.0 precedence with
golang.org/x/mod/semver-style parsing(optional
vprefix,MAJORandMAJOR.MINORshorthands). Buildmetadata is validated but ignored.
codereadiness: flag "<key>": <detail>.SemVerparsing and precedence, forSemVerComparator(including concurrentCompare()calls), and forthe hook in strict and lenient modes.
Related Issues
Fixes #147
Follow-up Tasks
SemVerlass is already implemented inproviders/flagd/src/evaluator/flagd_ops.cpp. Move this to a common folder such that the code can be reused.How to test
bazel test //hooks/codereadiness/...