Treat legacy licenses as offline in the billing events client

What does this MR do and why?

On an air-gapped instance running a legacy (non-cloud) license, the Secrets Manager billing emitters still tried to reach the billing collector. Each secrets_read event and the nightly secrets_stored count held a billing emitter thread for the 30 s connect timeout and logged an error. With local_billing_persistence on, nothing was written to billable_usage_daily_namespace_aggregates either.

!258078 (merged) fixed this for offline cloud licenses by keying on License#offline_cloud_license?. That predicate is false for a legacy license, so emission stayed on and local persistence stayed off. The Secrets Manager entitlement (SecretsManagement::InstanceEnrollment.offline_license?, exposed as offlineLicense) already treats anything but an online cloud license as offline, so the two layers disagreed about the same instance.

This MR keys both emission_enabled? and local_persistence_enabled? on "a license that is not an online cloud license", which covers offline cloud and legacy licenses alike.

The same split (online cloud license means connected, legacy and offline cloud mean offline) already exists elsewhere:

  • SecretsManagement::InstanceEnrollment.offline_license? returns !License.current&.online_cloud_license?, and its comment names legacy explicitly (ee/app/models/secrets_management/instance_enrollment.rb).
  • SecretsManagement::Entitlement::Resolver resolves online only for Gitlab.com? || license.online_cloud_license?; everything else, legacy included, resolves offline (ee/lib/secrets_management/entitlement/resolver.rb).
  • Ai::SelfHostedDapBilling.self_hosted_dap_billing_enabled?, another billing path, is gated on License.current&.online_cloud_license? (ee/lib/ai/self_hosted_dap_billing.rb).
  • CloudConnector::SyncCloudConnectorAccessService refuses to sync for anything but an online cloud license (ee/app/services/cloud_connector/sync_cloud_connector_access_service.rb), so a legacy instance never has the Cloud Connector token the billing client reads the instance uid from.

Scope and caveats:

  • Only the Secrets Manager emitters use Gitlab::BillingEvents::Client, so nothing else changes behavior.
  • A connected instance on a legacy license also stops emitting and, with the flag on, records locally instead. That matches the entitlement: such an instance cannot hold the Secrets Manager add-on and has no Cloud Connector token sync.
  • An instance with no license keeps its current behavior (emission on, no local persistence). The entitlement counts it as offline, but the EE suite runs CE specs with no license, and a Free instance on the EE package is in the same state, so it keeps the CE behavior. The predicate is named licensed_offline? to make that difference from InstanceEnrollment.offline_license? visible.
  • The SaaS realm guard is unchanged.
  • No extra queries: License.current is already cached.

Resolves https://gitlab.com/gitlab-org/gitlab/-/issues/632021

How to set up and validate locally

  1. Use a legacy license (cloud_licensing_enabled: false) on GDK.
  2. Feature.enable(:local_billing_persistence)
  3. In gdk rails c: Gitlab::BillingEvents::Client.new.emission_enabled? returns false and local_persistence_enabled? returns true (both were the other way round before).
  4. Run SecretsManagement::EmitSecretsStoredBillableEventWorker for a root group with secrets: you get a row in billable_usage_daily_namespace_aggregates and no collector attempt.

Specs

  • ee/spec/lib/ee/gitlab/billing_events/client_spec.rb: the predicate specs now use real licenses (online cloud, offline cloud, legacy, none) instead of stubbing offline_cloud_license?. The file defaults to an online cloud license so the emission paths stay covered (the default test license is a legacy one). Adds an end-to-end example that a legacy license writes a local aggregate instead of emitting. Against master, the 4 legacy-license examples fail.
  • Ran the client, emitter, worker, internal API and aggregate service specs: 156 examples, 0 failures.

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

🤖 Generated with Claude Code

Edited by Dmytro Biryukov

Merge request reports

Loading
Loading