Skip to content

feat(entitlement): allow grants on subscription-managed entitlements#4206

Open
ankitk0305 wants to merge 3 commits into
openmeterio:mainfrom
ankitk0305:fix/3293-grants-on-subscription-entitlements
Open

feat(entitlement): allow grants on subscription-managed entitlements#4206
ankitk0305 wants to merge 3 commits into
openmeterio:mainfrom
ankitk0305:fix/3293-grants-on-subscription-entitlements

Conversation

@ankitk0305

@ankitk0305 ankitk0305 commented Apr 22, 2026

Copy link
Copy Markdown

Problem

Fixes #3293

Users cannot apply grants (one-time credit top-ups) to entitlements that are managed by a subscription. The PreUpdate hook in the subscription guard returns a 403 Forbidden for any external modification of a subscription-managed entitlement, including grant creation.

This forces users into a poor workaround: abandoning subscriptions entirely and managing entitlements + grants manually with raw entitlements.

Use case

A customer has a subscription plan that grants 100 API calls/month. They exceed their limit and the operator wants to add a one-time overage grant of 50 extra calls — without changing the subscription plan or creating a recurring add-on. This is a natural billing pattern that should be supported.

Root Cause

In openmeter/entitlement/hooks/subscription/hook.go, PreUpdate blocks all external mutations on subscription-managed entitlements:

func (h *hook) PreUpdate(ctx context.Context, ent *entitlement.Entitlement) error {
    if subscription.IsSubscriptionOperation(ctx) {
        return nil
    }
    if subscription.AnnotationParser.HasSubscription(ent.Annotations) {
        return models.NewGenericForbiddenError(...) // blocks grants too
    }
    return nil
}

CreateGrant in entitlement/metered/entitlement_grant.go calls this hook before creating the grant, so every grant attempt on a subscription-managed entitlement is rejected.

Solution

Grants are ad-hoc credit top-ups — they do not alter the entitlement structure (type, feature key, config) that the subscription owns. The guard should distinguish between structural mutations (still blocked) and grant creation (now allowed).

The fix follows the existing IsSubscriptionOperation context pattern:

  1. subscription/context.go — add NewGrantOperationContext / IsGrantOperation to mark a context as a grant operation
  2. entitlement/hooks/subscription/hook.goPreUpdate passes through when IsGrantOperation(ctx) is true
  3. entitlement/metered/entitlement_grant.goCreateGrant wraps ctx with NewGrantOperationContext before calling PreUpdate

Changes

openmeter/subscription/context.go                  +15
openmeter/entitlement/hooks/subscription/hook.go   +6
openmeter/entitlement/metered/entitlement_grant.go +2

Invariants preserved:

  • Subscriptions still fully own entitlement lifecycle (create / delete / config changes remain blocked for external callers)
  • Grant creation on non-subscription entitlements is unaffected
  • The IsSubscriptionOperation bypass for the subscription subsystem itself is unchanged

Test Plan

  • Create a subscription with a metered entitlement
  • Issue a grant on the subscription-managed entitlement via POST /api/v2/customers/{id}/entitlements/{featureKey}/grants — should return 200 instead of 403
  • Verify the grant appears in GET .../grants
  • Verify the subscription still controls the entitlement lifecycle (delete entitlement directly should still return 403)
  • Run make test

Summary by CodeRabbit

  • Chores

    • Updated Helm chart dependencies: Kafka (30.1.8 → 32.4.4), PostgreSQL (16.1.2 → 17.1.0), and Redis (20.2.1 → 23.1.1)
  • New Features

    • Added support for grant operations in subscription entitlements, enabling specific entitlement updates during grant creation workflows

Ankit Khandelwal and others added 3 commits April 19, 2026 12:52
Bitnami stopped publishing versioned images to Docker Hub in Nov 2024.
Override image.registry for kafka, postgresql, and redis sub-charts to
pull from registry.bitnami.com where the versioned tags remain available.

Fixes openmeterio#4172

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Supersedes the previous registry.bitnami.com override approach, which
CodeRabbit correctly identified as non-functional (registry.bitnami.com
is not publicly accessible).

Root cause: old Bitnami chart versions (kafka@30.1.8, postgresql@16.1.2,
redis@20.2.1) pin image tags that have been removed from Docker Hub.
Newer Bitnami chart versions pin up-to-date tags that are still public.

Bumps:
  kafka:      30.1.8  → 32.4.4  (Kafka 4.0.0)
  postgresql: 16.1.2  → 17.1.0  (PostgreSQL 17.6.0)
  redis:      20.2.1  → 23.1.1  (Redis 8.2.1)

Chart.lock digest placeholder — run `helm dependency update
deploy/charts/openmeter` to regenerate the lock file with correct digests.

Fixes openmeterio#4172

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fixes openmeterio#3293

Grants are ad-hoc credit top-ups (e.g. one-time overage allowances) that
do not alter the entitlement structure owned by a subscription. The
previous blanket PreUpdate guard blocked them with a 403, forcing users
to work around the restriction via raw entitlements instead.

Changes:
- subscription/context.go: add NewGrantOperationContext /
  IsGrantOperation so callers can mark a context as a grant operation
- entitlement/hooks/subscription/hook.go: PreUpdate now allows the
  operation when IsGrantOperation(ctx) is true, in addition to the
  existing IsSubscriptionOperation bypass
- entitlement/metered/entitlement_grant.go: CreateGrant wraps ctx with
  NewGrantOperationContext before calling PreUpdate

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@ankitk0305 ankitk0305 requested a review from a team as a code owner April 22, 2026 11:44
@coderabbitai

coderabbitai Bot commented Apr 22, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Helm chart dependencies updated to newer versions (Kafka 32.4.4, PostgreSQL 17.1.0, Redis 23.1.1). Concurrently, context utilities added and subscription hook modified to allow grant operations on subscription-managed entitlements by bypassing prior restrictions.

Changes

Cohort / File(s) Summary
Helm Chart Dependencies
deploy/charts/openmeter/Chart.yaml
Updated Kafka (30.1.8 → 32.4.4), PostgreSQL (16.1.2 → 17.1.0), and Redis (20.2.1 → 23.1.1) versions. Repository and conditional flags unchanged.
Grant Operation Context
openmeter/subscription/context.go
Added NewGrantOperationContext() and IsGrantOperation() functions to mark and detect grant operations via context values.
Subscription Hook Logic
openmeter/entitlement/hooks/subscription/hook.go
Modified PreUpdate to allow grant operations by adding early return when subscription.IsGrantOperation(ctx) is true, bypassing subscription-managed entitlement check.
Grant Creation Integration
openmeter/entitlement/metered/entitlement_grant.go
Updated CreateGrant to pass subscription-scoped operation context to hook via subscription.NewGrantOperationContext(ctx).

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning All changes are directly scoped to enabling grants on subscription-managed entitlements; the Helm chart version updates in Chart.yaml are unrelated to this objective. Remove the Helm chart dependency version updates from this PR (kafka, postgresql, redis) and include them in a separate dependency-update PR to keep this PR focused.
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: allowing grants on subscription-managed entitlements, which is the core objective of this PR.
Linked Issues check ✅ Passed The PR fully addresses issue #3293 by implementing the exact mechanism needed: context markers for grant operations and conditional bypass logic in the subscription hook.

✏️ 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.

Actionable comments posted: 1

🧹 Nitpick comments (2)
openmeter/entitlement/hooks/subscription/hook.go (1)

44-58: Works as intended — one small asymmetry worth thinking about.

PreUpdate now lets grants through on subscription-managed entitlements, but PreDelete doesn't have (and shouldn't need) a grant bypass. That's fine today because grants go through PreUpdate, but the bypass as written is unconditional — it returns nil for grant operations even on entitlements that aren't subscription-managed, which is harmless but makes the guard a no-op for the entire grant path. If you ever add non-subscription validations here later, remember that grant ops will skip them too.

A slightly more surgical version would keep the guard's scope unchanged:

♻️ Optional: only bypass when the restriction would actually fire
-	// Grants are ad-hoc credit top-ups and do not alter the entitlement structure,
-	// so they are permitted even when the entitlement is subscription-managed.
-	if subscription.IsGrantOperation(ctx) {
-		return nil
-	}
-
-	if subscription.AnnotationParser.HasSubscription(ent.Annotations) {
+	if subscription.AnnotationParser.HasSubscription(ent.Annotations) {
+		// Grants are ad-hoc credit top-ups and don't alter the entitlement
+		// structure managed by the subscription, so allow them through.
+		if subscription.IsGrantOperation(ctx) {
+			return nil
+		}
 		return models.NewGenericForbiddenError(fmt.Errorf("entitlement is managed by subscription"))
 	}

Totally a judgment call — current form is readable too.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openmeter/entitlement/hooks/subscription/hook.go` around lines 44 - 58,
PreUpdate unconditionally lets grant operations through even for entitlements
that aren't subscription-managed; narrow the bypass by only returning nil for
grants when the entitlement is subscription-managed. Update the logic in the
hook.PreUpdate function (and reference subscription.IsGrantOperation and
subscription.AnnotationParser.HasSubscription and
models.NewGenericForbiddenError) so the grant early-return is conditional on
HasSubscription(ent.Annotations) (e.g. check HasSubscription first or combine
conditions) and keep the existing forbidden error for subscription-managed
entitlements otherwise.
openmeter/subscription/context.go (1)

29-37: LGTM — clean, symmetric with the subscription-operation helpers. 🙌

Nice touch making IsGrantOperation the slightly tidier ok && u form; consider giving IsSubscriptionOperation the same one-liner treatment in a follow-up for consistency. Totally optional.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openmeter/subscription/context.go` around lines 29 - 37, The reviewer
suggests making IsSubscriptionOperation consistent with IsGrantOperation by
adopting the concise one-liner style; update the IsSubscriptionOperation
function to use the same pattern (use ctx.Value(subscriptionoperation).(bool)
and return ok && v) so both helpers
(NewSubscriptionOperationContext/IsSubscriptionOperation and
NewGrantOperationContext/IsGrantOperation) have symmetric, tidy implementations.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@deploy/charts/openmeter/Chart.yaml`:
- Around line 22-32: Update the Helm chart values and documentation to account
for Kafka chart breaking changes: in your values.yaml replace any
kafka.extraConfig keys with kafka.overrideConfiguration (YAML block format),
switch references from the removed $CLIENT_CONF environment variable to using
/shared/client.properties, and review kafka controller quorum-related settings
for any needed adjustments; also verify that postgresql and redis usage aligns
with version bumps (Postgres 16→17 may require data migration workflows) and
scan each chart CHANGELOG for any other values.yaml key renames or image-pull
registry changes before merging.

---

Nitpick comments:
In `@openmeter/entitlement/hooks/subscription/hook.go`:
- Around line 44-58: PreUpdate unconditionally lets grant operations through
even for entitlements that aren't subscription-managed; narrow the bypass by
only returning nil for grants when the entitlement is subscription-managed.
Update the logic in the hook.PreUpdate function (and reference
subscription.IsGrantOperation and subscription.AnnotationParser.HasSubscription
and models.NewGenericForbiddenError) so the grant early-return is conditional on
HasSubscription(ent.Annotations) (e.g. check HasSubscription first or combine
conditions) and keep the existing forbidden error for subscription-managed
entitlements otherwise.

In `@openmeter/subscription/context.go`:
- Around line 29-37: The reviewer suggests making IsSubscriptionOperation
consistent with IsGrantOperation by adopting the concise one-liner style; update
the IsSubscriptionOperation function to use the same pattern (use
ctx.Value(subscriptionoperation).(bool) and return ok && v) so both helpers
(NewSubscriptionOperationContext/IsSubscriptionOperation and
NewGrantOperationContext/IsGrantOperation) have symmetric, tidy implementations.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d1d598f5-60dc-4175-9cfe-d542748a8594

📥 Commits

Reviewing files that changed from the base of the PR and between da06064 and cc15141.

⛔ Files ignored due to path filters (1)
  • deploy/charts/openmeter/Chart.lock is excluded by !**/*.lock, !**/*.lock
📒 Files selected for processing (4)
  • deploy/charts/openmeter/Chart.yaml
  • openmeter/entitlement/hooks/subscription/hook.go
  • openmeter/entitlement/metered/entitlement_grant.go
  • openmeter/subscription/context.go

Comment on lines +22 to 32
version: "32.4.4"
repository: "oci://registry-1.docker.io/bitnamicharts"
condition: kafka.enabled
- name: postgresql
version: "16.1.2"
version: "17.1.0"
repository: "oci://registry-1.docker.io/bitnamicharts"
condition: postgresql.enabled
- name: redis
version: "20.2.1"
version: "23.1.1"
repository: "oci://registry-1.docker.io/bitnamicharts"
condition: redis.enabled

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.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

bitnami helm chart kafka 32.0.0 breaking changes upgrade notes

💡 Result:

The Bitnami Kafka Helm chart version 32.0.0, released on 2025-03-25, introduces several breaking changes primarily due to the upgrade to Apache Kafka 4.0.0, which fully transitions to KRaft mode (no ZooKeeper dependency). Official upgrade notes are documented in the chart's repository at https://github.com/bitnami/charts/tree/main/bitnami/kafka#to-3200 (referenced in issues). Key breaking changes and upgrade considerations: 1. extraConfig parameter removed: Replaced by overrideConfiguration. Migrate any extraConfig settings (e.g., ACLs like allow.everyone.if.no.acl.found=true) to overrideConfiguration in YAML format: overrideConfiguration: allow.everyone.if.no.acl.found: true authorizer.class.name: org.apache.kafka.metadata.authorizer.StandardAuthorizer super.users: "User:admin;User:controller_user;User:inter_broker_user" Source: GitHub issue #33873. 2. Provisioning command changes: $CLIENT_CONF environment variable removed; use /shared/client.properties instead in extraProvisioningCommands. 3. KRaft secret handling issues: Early 32.0.x versions (e.g., 32.0.1-32.0.2) had upgrade bugs where templates expected controller-{i}-id keys in kafka-kraft secret that weren't present, causing "PASSWORDS ERROR". Fixed in later patches (e.g., PR #32621). Upgrade to at least 32.0.3+ or latest (32.4.3 as of 2025-08-26). 4. Controller quorum configuration: Upgrades from 31.x may fail due to switch from controller.quorum.voters (static, kraft.version=0) to controller.quorum.bootstrap.servers (dynamic, kraft.version=1). Kafka 4.0 does not support automatic migration between static/dynamic quorums. Use new kraftVersion parameter (added in 32.3.0) to set static quorum if needed. Avoid setting kraft.version: 1 manually in overrideConfiguration during upgrade. 5. General upgrade steps: - Review your values.yaml for deprecated params. - Backup cluster data and secrets. - Test upgrade in staging: helm upgrade --install kafka oci://registry-1.docker.io/bitnamicharts/kafka --version 32.4.3 -f values.yaml. - Monitor pods/secrets post-upgrade. - Latest chart uses Kafka image docker.io/bitnami/kafka:4.0.0-debian-12-r10. Current latest version is 32.4.3 (2025-08-26). Always check CHANGELOG.md and README.md in the repo for full details. Primary sources: CHANGELOG.md, issues #33873, #32620, #34015, #32516.

Citations:


🌐 Web query:

bitnami helm chart postgresql 17.0.0 breaking changes upgrade notes

💡 Result:

No specific breaking changes or dedicated upgrade notes found for Bitnami PostgreSQL Helm chart version 17.0.0 in the official CHANGELOG.md or Chart.yaml. The current latest chart version is 17.1.0 (appVersion PostgreSQL 17.6.0), with prior versions including 17.0.1 and 17.0.2 noted in CHANGELOG.md. Changes around these versions are minor: configurable data source credentials for exporter, primary.service.enabled parameter, README updates, annotations removal, and metrics collection as postgres user. No explicit breaking changes listed. For upgrades involving PostgreSQL major version changes (e.g., to PG 17), the data directory incompatibility requires manual intervention: backup data, potentially delete StatefulSet with orphan pods, upgrade Helm release reusing existing PVC, and use pg_upgrade or pg_dump/pg_restore if needed. See official README upgrading sections for prior major versions (e.g., to 16.x, 15.x) which follow similar patterns, and GitHub issues #14926, #4468 for procedures. Always backup before upgrading.

Citations:


🌐 Web query:

bitnami helm chart redis 23.0.0 breaking changes upgrade notes

💡 Result:

No breaking changes or specific upgrade notes are documented for Bitnami Redis Helm chart version 23.0.0. The official CHANGELOG.md lists patch releases starting from 23.0.1 (2025-09-05) with bugfixes and minor updates like README changes, sentinel fixes, and dependency updates. Earlier commits around August 2025 include multiple dependency updates and fixes for sentinel with single replica, but no explicit "breaking changes" or major upgrade warnings for 23.0.0. General upgrade advice from the chart README: reuse existing secrets or update auth.password/auth.existingSecret. Current latest version is 23.1.1 (appVersion Redis 8.2.1). Review full CHANGELOG at https://github.com/bitnami/charts/blob/main/bitnami/redis/CHANGELOG.md and test upgrades in staging.

Citations:


🏁 Script executed:

# Let me gather the current chart version you're upgrading to for Kafka to confirm patch level
grep -A2 "name: kafka" deploy/charts/openmeter/Chart.yaml

Repository: openmeterio/openmeter

Length of output: 161


Kafka chart has documented breaking changes—worth reviewing your values.yaml before merge.

Good news: you're hitting Kafka 32.4.4, which sidesteps the critical KRaft secret bugs in early 32.0.x. But there ARE config migrations needed:

  • extraConfigoverrideConfiguration (YAML format now, not flat strings)
  • $CLIENT_CONF env var removed; switch to /shared/client.properties
  • Controller quorum config may need updates depending on your current setup

PostgreSQL 16→17 and Redis 20→23 don't have chart-level breaking changes, though note that Postgres major version upgrades do require the usual database data directory migration (backup + pg_upgrade or pg_dump/restore path).

Worth a quick scan of each chart's CHANGELOG to spot any values.yaml key renames or security defaults that might affect your setup. Also fair game to spot-check that image pulls still resolve in any air-gapped registries you're using.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@deploy/charts/openmeter/Chart.yaml` around lines 22 - 32, Update the Helm
chart values and documentation to account for Kafka chart breaking changes: in
your values.yaml replace any kafka.extraConfig keys with
kafka.overrideConfiguration (YAML block format), switch references from the
removed $CLIENT_CONF environment variable to using /shared/client.properties,
and review kafka controller quorum-related settings for any needed adjustments;
also verify that postgresql and redis usage aligns with version bumps (Postgres
16→17 may require data migration workflows) and scan each chart CHANGELOG for
any other values.yaml key renames or image-pull registry changes before merging.

@GAlexIHU

Copy link
Copy Markdown
Contributor

Hi @ankitk0305

The subscription system works by "closing" and "recreating" entitlements according to subscription / addon changes. This is partly due to the limitations of the current entitlement-grant system. Manual granting is disabled on subscription managed entitlements because the two systems cannot currently reconcile and the results would be confusing to the end user: let's say, you add a manual grant of +100. On the next subscription edit (or any general "change type event"), that 100 grant would be lost with the closed entitlement.

@GAlexIHU

GAlexIHU commented Apr 23, 2026

Copy link
Copy Markdown
Contributor

On a related note, if you check recent activity in the repo, the first version of the new credit system is close to its first release, that will be a better solution for most usecases.

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.

Granting on Subscription-managed Entitlements

2 participants