From b49d00046f87ec07c1e105471e3007fdf6582a85 Mon Sep 17 00:00:00 2001 From: Himanshu Singh Date: Mon, 29 Jun 2026 21:57:47 +0530 Subject: [PATCH 01/13] retrieval from bigquery without entity df Signed-off-by: Himanshu Singh --- .../feast/infra/offline_stores/bigquery.py | 157 ++++++++++++++---- .../infra/offline_stores/offline_utils.py | 11 ++ 2 files changed, 140 insertions(+), 28 deletions(-) diff --git a/sdk/python/feast/infra/offline_stores/bigquery.py b/sdk/python/feast/infra/offline_stores/bigquery.py index 77c9a17de87..7c653fa8183 100644 --- a/sdk/python/feast/infra/offline_stores/bigquery.py +++ b/sdk/python/feast/infra/offline_stores/bigquery.py @@ -10,11 +10,13 @@ ContextManager, Dict, Iterator, + KeysView, List, Literal, Optional, Tuple, Union, + cast, ) import numpy as np @@ -57,7 +59,7 @@ from feast.on_demand_feature_view import OnDemandFeatureView from feast.repo_config import FeastConfigBaseModel, RepoConfig from feast.saved_dataset import SavedDatasetStorage -from feast.utils import _utc_now, get_user_agent +from feast.utils import _utc_now, compute_non_entity_date_range, get_user_agent from .bigquery_source import ( BigQueryLoggingDestination, @@ -267,10 +269,11 @@ def get_historical_features( config: RepoConfig, feature_views: List[FeatureView], feature_refs: List[str], - entity_df: Union[pd.DataFrame, str], + entity_df: Optional[Union[pd.DataFrame, str]], registry: BaseRegistry, project: str, full_feature_names: bool = False, + **kwargs: Any, ) -> RetrievalJob: # TODO: Add entity_df validation in order to fail before interacting with BigQuery assert isinstance(config.offline_store, BigQueryOfflineStoreConfig) @@ -297,36 +300,78 @@ def get_historical_features( config.offline_store.table_create_disposition, ) - entity_schema = _get_entity_schema( - client=client, - entity_df=entity_df, - ) + # Non-entity mode: create a left temporary table from entity keys - any entity key having an event in the time window - entity_df_event_timestamp_col = ( - offline_utils.infer_event_timestamp_from_entity_df(entity_schema) - ) + non_entity_mode = entity_df is None - entity_df_event_timestamp_range = _get_entity_df_event_timestamp_range( - entity_df, - entity_df_event_timestamp_col, - client, - ) + if non_entity_mode: + start_date, end_date = compute_non_entity_date_range( + feature_views, + start_date=kwargs.get("start_date"), + end_date=kwargs.get("end_date"), + ) + entity_df_event_timestamp_range = (start_date, end_date) - @contextlib.contextmanager - def query_generator() -> Iterator[str]: - _upload_entity_df( + # Pre-compute query contexts to collect entity column names per feature view. + fv_query_contexts_pre = offline_utils.get_feature_view_query_context( + feature_refs, + feature_views, + registry, + project, + entity_df_event_timestamp_range, + ) + all_entities = offline_utils.gather_all_entities(fv_query_contexts_pre) + event_timestamp_col = "entity_ts" + entity_schema_keys: KeysView[str] = cast( + KeysView[str], + {k: None for k in (all_entities + [event_timestamp_col])}.keys(), + ) + entity_schema = None + else: + entity_schema = _get_entity_schema( client=client, - table_name=table_reference, entity_df=entity_df, ) - - expected_join_keys = offline_utils.get_expected_join_keys( - project, feature_views, registry + event_timestamp_col = offline_utils.infer_event_timestamp_from_entity_df( + entity_schema ) - - offline_utils.assert_expected_columns_in_entity_df( - entity_schema, expected_join_keys, entity_df_event_timestamp_col + entity_df_event_timestamp_range = _get_entity_df_event_timestamp_range( + entity_df, + event_timestamp_col, + client, ) + entity_schema_keys = entity_schema.keys() + all_entities = [] + fv_query_contexts_pre = None + start_date = entity_df_event_timestamp_range[0] + end_date = entity_df_event_timestamp_range[1] + + @contextlib.contextmanager + def query_generator() -> Iterator[str]: + if non_entity_mode: + assert fv_query_contexts_pre is not None + _bq_create_entity_union_table( + client=client, + table_name=table_reference, + feature_views=feature_views, + fv_query_contexts=fv_query_contexts_pre, + start_date=start_date, + end_date=end_date, + all_entities=all_entities, + event_timestamp_col=event_timestamp_col, + ) + else: + _upload_entity_df( + client=client, + table_name=table_reference, + entity_df=entity_df, + ) + expected_join_keys = offline_utils.get_expected_join_keys( + project, feature_views, registry + ) + offline_utils.assert_expected_columns_in_entity_df( + entity_schema, expected_join_keys, event_timestamp_col + ) # Build a query context containing all information required to template the BigQuery SQL query query_context = offline_utils.get_feature_view_query_context( @@ -341,8 +386,8 @@ def query_generator() -> Iterator[str]: query = offline_utils.build_point_in_time_query( query_context, left_table_query_string=table_reference, - entity_df_event_timestamp_col=entity_df_event_timestamp_col, - entity_df_columns=entity_schema.keys(), + entity_df_event_timestamp_col=event_timestamp_col, + entity_df_columns=entity_schema_keys, query_template=MULTIPLE_FEATURE_VIEW_POINT_IN_TIME_JOIN, full_feature_names=full_feature_names, ) @@ -350,7 +395,7 @@ def query_generator() -> Iterator[str]: try: yield query finally: - # Asynchronously clean up the uploaded Bigquery table, which will expire + # Asynchronously clean up the uploaded BigQuery table, which will expire # if cleanup fails client.delete_table(table=table_reference, not_found_ok=True) @@ -364,7 +409,7 @@ def query_generator() -> Iterator[str]: ), metadata=RetrievalMetadata( features=feature_refs, - keys=list(entity_schema.keys() - {entity_df_event_timestamp_col}), + keys=list(set(entity_schema_keys) - {event_timestamp_col}), min_event_timestamp=entity_df_event_timestamp_range[0], max_event_timestamp=entity_df_event_timestamp_range[1], ), @@ -567,6 +612,62 @@ def clear_monitoring_baseline( ) +def _bq_create_entity_union_table( + client: "Client", + table_name: str, + feature_views: List[FeatureView], + fv_query_contexts: List[offline_utils.FeatureViewQueryContext], + start_date: datetime, + end_date: datetime, + all_entities: List[str], + event_timestamp_col: str, +) -> None: + """ + Creates a BigQuery temp table containing the UNION DISTINCT of entity keys observed + across all feature views in [start_date, end_date], plus a stable as-of timestamp + column set to end_date. Used as the left table for PIT joins in non-entity mode. + """ + start_str = start_date.strftime("%Y-%m-%dT%H:%M:%S") + end_str = end_date.strftime("%Y-%m-%dT%H:%M:%S") + + per_view_selects: List[str] = [] + for fv, ctx in zip(feature_views, fv_query_contexts): + assert isinstance(fv.batch_source, BigQuerySource) + from_expression = fv.batch_source.get_table_query_string() + timestamp_field = ctx.timestamp_field + + ctx_entities_set = set(ctx.entities) + select_entities: List[str] = [] + for col in all_entities: + if col in ctx_entities_set: + select_entities.append(f"`{col}`") + else: + select_entities.append(f"NULL AS `{col}`") + + per_view_selects.append( + f"SELECT DISTINCT {', '.join(select_entities)} " + f"FROM {from_expression} " + f"WHERE `{timestamp_field}` BETWEEN TIMESTAMP('{start_str}') AND TIMESTAMP('{end_str}')" + ) + + union_query = "\nUNION DISTINCT\n".join(per_view_selects) + entity_cols = ( + ", ".join(f"`{e}`" for e in all_entities) if all_entities else "TRUE AS _dummy" + ) + + create_sql = ( + f"CREATE TABLE `{table_name}` AS " + f"SELECT {entity_cols}, TIMESTAMP('{end_str}') AS `{event_timestamp_col}` " + f"FROM ({union_query}) AS _entity_union" + ) + + block_until_done(client, client.query(create_sql)) + + table = client.get_table(table_name) + table.expires = _utc_now() + timedelta(minutes=30) + client.update_table(table, ["expires"]) + + # ------------------------------------------------------------------ # # BigQuery monitoring metrics (native) # ------------------------------------------------------------------ # diff --git a/sdk/python/feast/infra/offline_stores/offline_utils.py b/sdk/python/feast/infra/offline_stores/offline_utils.py index fee87dde595..10ffb3829b8 100644 --- a/sdk/python/feast/infra/offline_stores/offline_utils.py +++ b/sdk/python/feast/infra/offline_stores/offline_utils.py @@ -429,3 +429,14 @@ def format_date(val: Union[str, datetime]) -> str: filters.append(f"{dp_field} <= '{format_date(end_date)}'") return " AND ".join(filters) if filters else "" + + + + +def gather_all_entities(fv_query_contexts: List[FeatureViewQueryContext]): + all_entities: List[str] = [] + for ctx in fv_query_contexts: + for e in ctx.entities: + if e not in all_entities: + all_entities.append(e) + return all_entities \ No newline at end of file From fc02ba5db08ea165b5efc0e0ba570889bbf4d034 Mon Sep 17 00:00:00 2001 From: nquinn408 <57655411+nquinn408@users.noreply.github.com> Date: Sun, 28 Jun 2026 13:44:53 -0700 Subject: [PATCH 02/13] feat: Implement RegistryServer.Proto RPC with RBAC-filtered response (#6558) (#6552) Signed-off-by: Himanshu Singh --- sdk/python/feast/registry_server.py | 93 ++++++++++++ .../registry/test_registry_server_proto.py | 132 ++++++++++++++++++ 2 files changed, 225 insertions(+) create mode 100644 sdk/python/tests/unit/infra/registry/test_registry_server_proto.py diff --git a/sdk/python/feast/registry_server.py b/sdk/python/feast/registry_server.py index ccbbcb20211..24b446a4bdd 100644 --- a/sdk/python/feast/registry_server.py +++ b/sdk/python/feast/registry_server.py @@ -35,6 +35,7 @@ str_to_auth_manager_type, ) from feast.project import Project +from feast.protos.feast.core.Registry_pb2 import Registry as RegistryProto from feast.protos.feast.registry import RegistryServer_pb2, RegistryServer_pb2_grpc from feast.protos.feast.registry.RegistryServer_pb2 import Feature, ListFeaturesResponse from feast.saved_dataset import SavedDataset, ValidationReference @@ -181,6 +182,98 @@ def __init__(self, registry: BaseRegistry, store=None) -> None: self.proxied_registry = registry self.store = store + def Proto(self, request: Empty, context) -> RegistryProto: + """Build a RegistryProto from individually RBAC-filtered list calls. + + The ``RegistryServer.Proto`` RPC must honor the same permission checks as the + other RPCs rather than returning ``proxied_registry.proto()`` directly, which + would bypass RBAC and expose every object (entities, feature views, data + sources, permissions, projects, etc.) regardless of authorization. + + Each object type is filtered with ``permitted_resources(..., DESCRIBE)``: under + ``NoAuthConfig`` this is a no-op (the full registry is returned, so remote + registries keep working), while with auth enabled the caller only sees the + objects they are permitted to ``DESCRIBE``. + """ + + def describable(resources: list) -> list: + return permitted_resources( + resources=cast(list[FeastObject], resources), + actions=AuthzedAction.DESCRIBE, + ) + + registry_proto = RegistryProto() + + for project in describable(self.proxied_registry.list_projects()): + registry_proto.projects.append(project.to_proto()) + project_name = project.name + + for entity in describable( + self.proxied_registry.list_entities(project=project_name) + ): + registry_proto.entities.append(entity.to_proto()) + + for data_source in describable( + self.proxied_registry.list_data_sources(project=project_name) + ): + registry_proto.data_sources.append(data_source.to_proto()) + + for feature_view in describable( + self.proxied_registry.list_feature_views(project=project_name) + ): + registry_proto.feature_views.append(feature_view.to_proto()) + + for stream_feature_view in describable( + self.proxied_registry.list_stream_feature_views(project=project_name) + ): + registry_proto.stream_feature_views.append( + stream_feature_view.to_proto() + ) + + for on_demand_feature_view in describable( + self.proxied_registry.list_on_demand_feature_views(project=project_name) + ): + registry_proto.on_demand_feature_views.append( + on_demand_feature_view.to_proto() + ) + + for label_view in describable( + self.proxied_registry.list_label_views(project=project_name) + ): + registry_proto.label_views.append(label_view.to_proto()) + + for feature_service in describable( + self.proxied_registry.list_feature_services(project=project_name) + ): + registry_proto.feature_services.append(feature_service.to_proto()) + + for saved_dataset in describable( + self.proxied_registry.list_saved_datasets(project=project_name) + ): + registry_proto.saved_datasets.append(saved_dataset.to_proto()) + + for validation_reference in describable( + self.proxied_registry.list_validation_references(project=project_name) + ): + registry_proto.validation_references.append( + validation_reference.to_proto() + ) + + for permission in describable( + self.proxied_registry.list_permissions(project=project_name) + ): + registry_proto.permissions.append(permission.to_proto()) + + # Carry the registry's real last_updated/version_id rather than stamping "now": + # this proto is rebuilt from individual list calls (for RBAC filtering), but it must + # not look like a fresh commit on every call — clients such as the remote feature + # server key cache freshness off this metadata. Reading these two scalar fields from + # the source proto leaks nothing RBAC-protected (no objects are copied from it). + source_proto = self.proxied_registry.proto() + registry_proto.last_updated.CopyFrom(source_proto.last_updated) + registry_proto.version_id = source_proto.version_id + return registry_proto + def ApplyEntity(self, request: RegistryServer_pb2.ApplyEntityRequest, context): entity = cast( Entity, diff --git a/sdk/python/tests/unit/infra/registry/test_registry_server_proto.py b/sdk/python/tests/unit/infra/registry/test_registry_server_proto.py new file mode 100644 index 00000000000..03b9902ff55 --- /dev/null +++ b/sdk/python/tests/unit/infra/registry/test_registry_server_proto.py @@ -0,0 +1,132 @@ +"""Unit tests for the ``RegistryServer.Proto`` RPC (issue #6558). + +The RPC must build the ``RegistryProto`` from individually RBAC-filtered list calls +rather than returning ``proxied_registry.proto()`` directly (which would bypass +permissions). Under ``NoAuthConfig`` filtering is a no-op, so the full registry is +returned; with auth enabled only ``DESCRIBE``-permitted objects are included. +""" + +from datetime import datetime, timezone +from unittest.mock import patch + +from google.protobuf.empty_pb2 import Empty + +from feast.data_source import DataSource +from feast.entity import Entity +from feast.feast_object import FeastObject +from feast.project import Project +from feast.protos.feast.core.Registry_pb2 import Registry as RegistryProto +from feast.registry_server import RegistryServer +from feast.value_type import ValueType + +# The registry's authentic metadata, returned by _FakeRegistry.proto(). Proto() must carry these +# through rather than stamping "now" (issue #6558 review feedback). +_REGISTRY_LAST_UPDATED = datetime(2024, 1, 2, 3, 4, 5, tzinfo=timezone.utc) +_REGISTRY_VERSION_ID = "test-version-id" + + +class _FakeRegistry: + """Minimal BaseRegistry stand-in exposing only the list_* calls Proto uses.""" + + def __init__(self, projects, entities_by_project, data_sources_by_project): + self._projects = projects + self._entities = entities_by_project + self._data_sources = data_sources_by_project + + def proto(self) -> RegistryProto: + # Source of the authentic last_updated / version_id metadata. Proto() reads only these + # scalar fields from here (no objects), so RBAC filtering is unaffected. + proto = RegistryProto() + proto.version_id = _REGISTRY_VERSION_ID + proto.last_updated.FromDatetime(_REGISTRY_LAST_UPDATED) + return proto + + def list_projects(self, allow_cache: bool = False, tags=None): + return self._projects + + def list_entities(self, project: str, allow_cache: bool = False, tags=None): + return self._entities.get(project, []) + + def list_data_sources(self, project: str, allow_cache: bool = False, tags=None): + return self._data_sources.get(project, []) + + # Every other object type is empty for this fixture. + def _empty(self, *args, **kwargs): + return [] + + list_feature_views = _empty + list_stream_feature_views = _empty + list_on_demand_feature_views = _empty + list_label_views = _empty + list_feature_services = _empty + list_saved_datasets = _empty + list_validation_references = _empty + list_permissions = _empty + + +def _entity(name: str) -> Entity: + return Entity(name=name, value_type=ValueType.STRING) + + +def _data_source(name: str) -> DataSource: + from feast.infra.offline_stores.file_source import FileSource + + return FileSource(name=name, path=f"/tmp/{name}.parquet", timestamp_field="ts") + + +def _build_server() -> tuple[RegistryServer, _FakeRegistry]: + registry = _FakeRegistry( + projects=[Project(name="proj_a"), Project(name="proj_b")], + entities_by_project={ + "proj_a": [_entity("driver"), _entity("customer")], + "proj_b": [_entity("merchant")], + }, + data_sources_by_project={"proj_a": [_data_source("src_a")]}, + ) + return RegistryServer(registry), registry # type: ignore[arg-type] + + +def test_proto_returns_full_registry_when_no_auth(): + """NoAuthConfig (no security manager) -> every object across all projects.""" + server, _ = _build_server() + + result = server.Proto(Empty(), context=None) + + assert {p.spec.name for p in result.projects} == {"proj_a", "proj_b"} + assert {e.spec.name for e in result.entities} == {"driver", "customer", "merchant"} + assert {d.name for d in result.data_sources} == {"src_a"} + # last_updated / version_id are carried from the registry's real proto (not stamped "now"), + # so cache consumers see the registry's authentic freshness metadata. + assert result.version_id == _REGISTRY_VERSION_ID + assert result.last_updated.ToDatetime(tzinfo=timezone.utc) == _REGISTRY_LAST_UPDATED + + +def test_proto_filters_by_describe_permission(): + """With RBAC, only DESCRIBE-permitted objects are included.""" + server, _ = _build_server() + + # Simulate a security manager that permits everything except the "customer" + # entity, regardless of object type (filters by DESCRIBE). + def fake_permitted(resources: list[FeastObject], actions): + return [r for r in resources if getattr(r, "name", None) != "customer"] + + with patch( + "feast.registry_server.permitted_resources", side_effect=fake_permitted + ) as mocked: + result = server.Proto(Empty(), context=None) + + assert mocked.called + # "customer" is filtered out; everything else survives. + assert {e.spec.name for e in result.entities} == {"driver", "merchant"} + assert {p.spec.name for p in result.projects} == {"proj_a", "proj_b"} + assert {d.name for d in result.data_sources} == {"src_a"} + + +def test_proto_empty_registry(): + """No projects -> empty (but valid) RegistryProto, not an error.""" + server = RegistryServer(_FakeRegistry([], {}, {})) # type: ignore[arg-type] + + result = server.Proto(Empty(), context=None) + + assert len(result.projects) == 0 + assert len(result.entities) == 0 From e726b073ab80154a90b9ce08d609eddda5cbf43b Mon Sep 17 00:00:00 2001 From: Ugo Giordano Date: Wed, 10 Jun 2026 11:56:08 +0200 Subject: [PATCH 03/13] feat(operator): integrate cluster TLS profile for OCP 5.0 compliance Read the cluster TLS profile from apiservers.config.openshift.io/cluster at startup. Apply MinVersion, CipherSuites, and NextProtos to webhook and metrics server TLS configs. Fail closed on unexpected errors. Use Intermediate defaults on non-OpenShift clusters. This ensures the feast-operator honors the cluster-wide TLS security profile, which is required for OCP 5.0. The implementation gracefully falls back to hardened defaults (TLS 1.2, ECDHE ciphers) on non-OpenShift clusters, so it does not break vanilla Kubernetes deployments. Signed-off-by: Ugo Giordano Co-Authored-By: Claude Opus 4.6 (1M context) Signed-off-by: Ugo Giordano Signed-off-by: Himanshu Singh --- infra/feast-operator/cmd/main.go | 95 +- .../crd/bases/feast.dev_featurestores.yaml | 1094 ++++++++++++++-- infra/feast-operator/config/rbac/role.yaml | 8 + infra/feast-operator/dist/install.yaml | 1102 +++++++++++++++-- infra/feast-operator/go.mod | 75 +- infra/feast-operator/go.sum | 204 +-- .../controller/featurestore_controller.go | 1 + 7 files changed, 2240 insertions(+), 339 deletions(-) diff --git a/infra/feast-operator/cmd/main.go b/infra/feast-operator/cmd/main.go index 0e5565cce2b..0d833f1469b 100644 --- a/infra/feast-operator/cmd/main.go +++ b/infra/feast-operator/cmd/main.go @@ -17,6 +17,7 @@ limitations under the License. package main import ( + "context" "crypto/tls" "flag" "os" @@ -25,12 +26,16 @@ import ( // to ensure that exec-entrypoint and run can make use of them. _ "k8s.io/client-go/plugin/pkg/client/auth" + configv1 "github.com/openshift/api/config/v1" + tlspkg "github.com/openshift/controller-runtime-common/pkg/tls" appsv1 "k8s.io/api/apps/v1" autoscalingv2 "k8s.io/api/autoscaling/v2" batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" policyv1 "k8s.io/api/policy/v1" rbacv1 "k8s.io/api/rbac/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + apimeta "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" @@ -61,6 +66,7 @@ var ( func init() { utilruntime.Must(clientgoscheme.AddToScheme(scheme)) + utilruntime.Must(configv1.Install(scheme)) utilruntime.Must(routev1.AddToScheme(scheme)) utilruntime.Must(feastdevv1alpha1.AddToScheme(scheme)) utilruntime.Must(feastdevv1.AddToScheme(scheme)) @@ -95,7 +101,6 @@ func main() { var enableLeaderElection bool var probeAddr string var secureMetrics bool - var enableHTTP2 bool var featureStoreMetrics bool var tlsOpts []func(*tls.Config) flag.StringVar(&metricsAddr, "metrics-bind-address", "0", "The address the metrics endpoint binds to. "+ @@ -106,8 +111,6 @@ func main() { "Enabling this will ensure there is only one active controller manager.") flag.BoolVar(&secureMetrics, "metrics-secure", true, "If set, the metrics endpoint is served securely via HTTPS. Use --metrics-secure=false to use HTTP instead.") - flag.BoolVar(&enableHTTP2, "enable-http2", false, - "If set, HTTP/2 will be enabled for the metrics and webhook servers") flag.BoolVar(&featureStoreMetrics, "feature-store-metrics", true, "Enable Prometheus gauges exposing online/offline store and registry configuration per FeatureStore. "+ "Disable with --feature-store-metrics=false.") @@ -119,21 +122,55 @@ func main() { ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) - // if the enable-http2 flag is false (the default), http/2 should be disabled - // due to its vulnerabilities. More specifically, disabling http/2 will - // prevent from being vulnerable to the HTTP/2 Stream Cancellation and - // Rapid Reset CVEs. For more information see: - // - https://github.com/advisories/GHSA-qppj-fm5r-hxr3 - // - https://github.com/advisories/GHSA-4374-p667-p6c8 - disableHTTP2 := func(c *tls.Config) { - setupLog.Info("disabling http/2") - c.NextProtos = []string{"http/1.1"} + // Fetch cluster TLS profile from apiservers.config.openshift.io/cluster + cfg := ctrl.GetConfigOrDie() + bootstrapClient, err := client.New(cfg, client.Options{Scheme: scheme}) + if err != nil { + setupLog.Error(err, "unable to create bootstrap client for TLS profile fetch") + os.Exit(1) } - if !enableHTTP2 { - tlsOpts = append(tlsOpts, disableHTTP2) + tlsProfileFetched := false + tlsProfile, err := tlspkg.FetchAPIServerTLSProfile(context.Background(), bootstrapClient) + if err != nil { + switch { + case apimeta.IsNoMatchError(err): + setupLog.Info("TLS profile not available, using hardened defaults (non-OpenShift cluster)") + case apierrors.IsNotFound(err): + setupLog.Info("APIServer resource not found, using hardened defaults") + default: + setupLog.Error(err, "unable to read APIServer TLS profile, refusing to start with unknown TLS posture") + os.Exit(1) + } + } else { + tlsProfileFetched = true + tlsConfigFn, unsupported := tlspkg.NewTLSConfigFromProfile(tlsProfile) + if len(unsupported) > 0 { + setupLog.Info("TLS profile contains ciphers unsupported by Go", "unsupported", unsupported) + } + tlsOpts = append(tlsOpts, tlsConfigFn) + } + + tlsAdherenceFetched := false + tlsAdherence, err := tlspkg.FetchAPIServerTLSAdherencePolicy(context.Background(), bootstrapClient) + if err != nil { + switch { + case apimeta.IsNoMatchError(err): + setupLog.Info("TLS adherence policy not available (non-OpenShift cluster)") + case apierrors.IsNotFound(err): + setupLog.Info("APIServer resource not found, skipping adherence policy") + default: + setupLog.Error(err, "unable to read APIServer TLS adherence policy, refusing to start") + os.Exit(1) + } + } else { + tlsAdherenceFetched = true } + tlsOpts = append(tlsOpts, func(c *tls.Config) { + c.NextProtos = []string{"h2", "http/1.1"} + }) + webhookServer := webhook.NewServer(webhook.Options{ TLSOpts: tlsOpts, }) @@ -162,7 +199,7 @@ func main() { metricsServerOptions.FilterProvider = filters.WithAuthenticationAndAuthorization } - mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ + mgr, err := ctrl.NewManager(cfg, ctrl.Options{ Scheme: scheme, Metrics: metricsServerOptions, WebhookServer: webhookServer, @@ -230,6 +267,32 @@ func main() { } // +kubebuilder:scaffold:builder + // Register SecurityProfileWatcher to restart on TLS profile changes + ctx, cancel := context.WithCancel(ctrl.SetupSignalHandler()) + defer cancel() + + if tlsProfileFetched { + watcher := &tlspkg.SecurityProfileWatcher{ + Client: mgr.GetClient(), + InitialTLSProfileSpec: tlsProfile, + OnProfileChange: func(_ context.Context, _, _ configv1.TLSProfileSpec) { + setupLog.Info("TLS profile changed, initiating shutdown to reload") + cancel() + }, + } + if tlsAdherenceFetched { + watcher.InitialTLSAdherencePolicy = tlsAdherence + watcher.OnAdherencePolicyChange = func(_ context.Context, _, _ configv1.TLSAdherencePolicy) { + setupLog.Info("TLS adherence policy changed, initiating shutdown to reload") + cancel() + } + } + if err := watcher.SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to set up TLS profile watcher") + os.Exit(1) + } + } + if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { setupLog.Error(err, "unable to set up health check") os.Exit(1) @@ -240,7 +303,7 @@ func main() { } setupLog.Info("starting manager") - if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { + if err := mgr.Start(ctx); err != nil { setupLog.Error(err, "problem running manager") os.Exit(1) } diff --git a/infra/feast-operator/config/crd/bases/feast.dev_featurestores.yaml b/infra/feast-operator/config/crd/bases/feast.dev_featurestores.yaml index e0386d063f5..26d2d4c65f0 100644 --- a/infra/feast-operator/config/crd/bases/feast.dev_featurestores.yaml +++ b/infra/feast-operator/config/crd/bases/feast.dev_featurestores.yaml @@ -161,8 +161,9 @@ spec: in a Container. properties: name: - description: Name of the environment variable. Must - be a C_IDENTIFIER. + description: |- + Name of the environment variable. + May consist of any printable ASCII characters except '='. type: string value: description: |- @@ -211,6 +212,35 @@ spec: - fieldPath type: object x-kubernetes-map-type: atomic + fileKeyRef: + description: |- + FileKeyRef selects a key of the env file. + Requires the EnvFiles feature gate to be enabled. + properties: + key: + description: The key within the env file. An + invalid key will prevent the pod from starting. + type: string + optional: + default: false + description: Specify whether the file or its + key must be defined. + type: boolean + path: + description: |- + The path within the volume from which to select the file. + Must be relative and may not contain the '.. + type: string + volumeName: + description: The name of the volume mount containing + the env file. + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic resourceFieldRef: description: |- Selects a resource of the container: only resources limits and requests @@ -286,7 +316,7 @@ spec: x-kubernetes-map-type: atomic prefix: description: Optional text to prepend to the name of - each environment variable. Must be a C_IDENTIFIER. + each environment variable. type: string secretRef: description: The Secret to select from @@ -471,7 +501,6 @@ spec: type. type: string required: - - status - type type: object type: array @@ -564,8 +593,9 @@ spec: in a Container. properties: name: - description: Name of the environment variable. Must - be a C_IDENTIFIER. + description: |- + Name of the environment variable. + May consist of any printable ASCII characters except '='. type: string value: description: |- @@ -614,6 +644,35 @@ spec: - fieldPath type: object x-kubernetes-map-type: atomic + fileKeyRef: + description: |- + FileKeyRef selects a key of the env file. + Requires the EnvFiles feature gate to be enabled. + properties: + key: + description: The key within the env file. An + invalid key will prevent the pod from starting. + type: string + optional: + default: false + description: Specify whether the file or its + key must be defined. + type: boolean + path: + description: |- + The path within the volume from which to select the file. + Must be relative and may not contain the '.. + type: string + volumeName: + description: The name of the volume mount containing + the env file. + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic resourceFieldRef: description: |- Selects a resource of the container: only resources limits and requests @@ -689,7 +748,7 @@ spec: x-kubernetes-map-type: atomic prefix: description: Optional text to prepend to the name of - each environment variable. Must be a C_IDENTIFIER. + each environment variable. type: string secretRef: description: The Secret to select from @@ -1814,8 +1873,9 @@ spec: present in a Container. properties: name: - description: Name of the environment variable. Must - be a C_IDENTIFIER. + description: |- + Name of the environment variable. + May consist of any printable ASCII characters except '='. type: string value: description: |- @@ -1864,6 +1924,36 @@ spec: - fieldPath type: object x-kubernetes-map-type: atomic + fileKeyRef: + description: |- + FileKeyRef selects a key of the env file. + Requires the EnvFiles feature gate to be enabled. + properties: + key: + description: The key within the env file. + An invalid key will prevent the pod from + starting. + type: string + optional: + default: false + description: Specify whether the file or + its key must be defined. + type: boolean + path: + description: |- + The path within the volume from which to select the file. + Must be relative and may not contain the '.. + type: string + volumeName: + description: The name of the volume mount + containing the env file. + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic resourceFieldRef: description: |- Selects a resource of the container: only resources limits and requests @@ -1940,7 +2030,7 @@ spec: x-kubernetes-map-type: atomic prefix: description: Optional text to prepend to the name - of each environment variable. Must be a C_IDENTIFIER. + of each environment variable. type: string secretRef: description: The Secret to select from @@ -2327,8 +2417,9 @@ spec: present in a Container. properties: name: - description: Name of the environment variable. Must - be a C_IDENTIFIER. + description: |- + Name of the environment variable. + May consist of any printable ASCII characters except '='. type: string value: description: |- @@ -2377,6 +2468,36 @@ spec: - fieldPath type: object x-kubernetes-map-type: atomic + fileKeyRef: + description: |- + FileKeyRef selects a key of the env file. + Requires the EnvFiles feature gate to be enabled. + properties: + key: + description: The key within the env file. + An invalid key will prevent the pod from + starting. + type: string + optional: + default: false + description: Specify whether the file or + its key must be defined. + type: boolean + path: + description: |- + The path within the volume from which to select the file. + Must be relative and may not contain the '.. + type: string + volumeName: + description: The name of the volume mount + containing the env file. + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic resourceFieldRef: description: |- Selects a resource of the container: only resources limits and requests @@ -2453,7 +2574,7 @@ spec: x-kubernetes-map-type: atomic prefix: description: Optional text to prepend to the name - of each environment variable. Must be a C_IDENTIFIER. + of each environment variable. type: string secretRef: description: The Secret to select from @@ -2957,8 +3078,9 @@ spec: present in a Container. properties: name: - description: Name of the environment variable. - Must be a C_IDENTIFIER. + description: |- + Name of the environment variable. + May consist of any printable ASCII characters except '='. type: string value: description: |- @@ -3008,6 +3130,36 @@ spec: - fieldPath type: object x-kubernetes-map-type: atomic + fileKeyRef: + description: |- + FileKeyRef selects a key of the env file. + Requires the EnvFiles feature gate to be enabled. + properties: + key: + description: The key within the env + file. An invalid key will prevent + the pod from starting. + type: string + optional: + default: false + description: Specify whether the file + or its key must be defined. + type: boolean + path: + description: |- + The path within the volume from which to select the file. + Must be relative and may not contain the '.. + type: string + volumeName: + description: The name of the volume + mount containing the env file. + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic resourceFieldRef: description: |- Selects a resource of the container: only resources limits and requests @@ -3086,8 +3238,7 @@ spec: x-kubernetes-map-type: atomic prefix: description: Optional text to prepend to the - name of each environment variable. Must be - a C_IDENTIFIER. + name of each environment variable. type: string secretRef: description: The Secret to select from @@ -4262,8 +4413,9 @@ spec: in a Container. properties: name: - description: Name of the environment variable. Must - be a C_IDENTIFIER. + description: |- + Name of the environment variable. + May consist of any printable ASCII characters except '='. type: string value: description: |- @@ -4312,6 +4464,35 @@ spec: - fieldPath type: object x-kubernetes-map-type: atomic + fileKeyRef: + description: |- + FileKeyRef selects a key of the env file. + Requires the EnvFiles feature gate to be enabled. + properties: + key: + description: The key within the env file. An + invalid key will prevent the pod from starting. + type: string + optional: + default: false + description: Specify whether the file or its + key must be defined. + type: boolean + path: + description: |- + The path within the volume from which to select the file. + Must be relative and may not contain the '.. + type: string + volumeName: + description: The name of the volume mount containing + the env file. + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic resourceFieldRef: description: |- Selects a resource of the container: only resources limits and requests @@ -4387,7 +4568,7 @@ spec: x-kubernetes-map-type: atomic prefix: description: Optional text to prepend to the name of - each environment variable. Must be a C_IDENTIFIER. + each environment variable. type: string secretRef: description: The Secret to select from @@ -5281,9 +5462,8 @@ spec: host that shares a pod's lifetime. properties: endpoints: - description: |- - endpoints is the endpoint name that details Glusterfs topology. - More info: https://examples.k8s. + description: endpoints is the endpoint name that details + Glusterfs topology. type: string path: description: |- @@ -5696,6 +5876,52 @@ spec: type: array x-kubernetes-list-type: atomic type: object + podCertificate: + description: |- + Projects an auto-rotating credential bundle (private key and certificate + chain) that the pod can use either as a TLS... + properties: + certificateChainPath: + description: |- + Write the certificate chain at this path in the projected volume. + + Most applications should use credentialBundlePath. + type: string + credentialBundlePath: + description: Write the credential bundle at + this path in the projected volume. + type: string + keyPath: + description: |- + Write the key at this path in the projected volume. + + Most applications should use credentialBundlePath. + type: string + keyType: + description: The type of keypair Kubelet will + generate for the pod. + type: string + maxExpirationSeconds: + description: |- + maxExpirationSeconds is the maximum lifetime permitted for the + certificate. + format: int32 + type: integer + signerName: + description: Kubelet's generated CSRs will + be addressed to this signer. + type: string + userAnnotations: + additionalProperties: + type: string + description: |- + userAnnotations allow pod authors to pass additional information to + the signer implementation. + type: object + required: + - keyType + - signerName + type: object secret: description: secret information about the secret data to project @@ -6213,8 +6439,9 @@ spec: present in a Container. properties: name: - description: Name of the environment variable. Must - be a C_IDENTIFIER. + description: |- + Name of the environment variable. + May consist of any printable ASCII characters except '='. type: string value: description: |- @@ -6263,6 +6490,36 @@ spec: - fieldPath type: object x-kubernetes-map-type: atomic + fileKeyRef: + description: |- + FileKeyRef selects a key of the env file. + Requires the EnvFiles feature gate to be enabled. + properties: + key: + description: The key within the env file. + An invalid key will prevent the pod from + starting. + type: string + optional: + default: false + description: Specify whether the file or + its key must be defined. + type: boolean + path: + description: |- + The path within the volume from which to select the file. + Must be relative and may not contain the '.. + type: string + volumeName: + description: The name of the volume mount + containing the env file. + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic resourceFieldRef: description: |- Selects a resource of the container: only resources limits and requests @@ -6339,7 +6596,7 @@ spec: x-kubernetes-map-type: atomic prefix: description: Optional text to prepend to the name - of each environment variable. Must be a C_IDENTIFIER. + of each environment variable. type: string secretRef: description: The Secret to select from @@ -6526,7 +6783,6 @@ spec: condition type. type: string required: - - status - type type: object type: array @@ -6621,8 +6877,9 @@ spec: present in a Container. properties: name: - description: Name of the environment variable. Must - be a C_IDENTIFIER. + description: |- + Name of the environment variable. + May consist of any printable ASCII characters except '='. type: string value: description: |- @@ -6671,6 +6928,36 @@ spec: - fieldPath type: object x-kubernetes-map-type: atomic + fileKeyRef: + description: |- + FileKeyRef selects a key of the env file. + Requires the EnvFiles feature gate to be enabled. + properties: + key: + description: The key within the env file. + An invalid key will prevent the pod from + starting. + type: string + optional: + default: false + description: Specify whether the file or + its key must be defined. + type: boolean + path: + description: |- + The path within the volume from which to select the file. + Must be relative and may not contain the '.. + type: string + volumeName: + description: The name of the volume mount + containing the env file. + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic resourceFieldRef: description: |- Selects a resource of the container: only resources limits and requests @@ -6747,7 +7034,7 @@ spec: x-kubernetes-map-type: atomic prefix: description: Optional text to prepend to the name - of each environment variable. Must be a C_IDENTIFIER. + of each environment variable. type: string secretRef: description: The Secret to select from @@ -7884,8 +8171,9 @@ spec: present in a Container. properties: name: - description: Name of the environment variable. - Must be a C_IDENTIFIER. + description: |- + Name of the environment variable. + May consist of any printable ASCII characters except '='. type: string value: description: |- @@ -7935,6 +8223,36 @@ spec: - fieldPath type: object x-kubernetes-map-type: atomic + fileKeyRef: + description: |- + FileKeyRef selects a key of the env file. + Requires the EnvFiles feature gate to be enabled. + properties: + key: + description: The key within the env + file. An invalid key will prevent + the pod from starting. + type: string + optional: + default: false + description: Specify whether the file + or its key must be defined. + type: boolean + path: + description: |- + The path within the volume from which to select the file. + Must be relative and may not contain the '.. + type: string + volumeName: + description: The name of the volume + mount containing the env file. + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic resourceFieldRef: description: |- Selects a resource of the container: only resources limits and requests @@ -8013,8 +8331,7 @@ spec: x-kubernetes-map-type: atomic prefix: description: Optional text to prepend to the - name of each environment variable. Must be - a C_IDENTIFIER. + name of each environment variable. type: string secretRef: description: The Secret to select from @@ -8406,8 +8723,9 @@ spec: present in a Container. properties: name: - description: Name of the environment variable. - Must be a C_IDENTIFIER. + description: |- + Name of the environment variable. + May consist of any printable ASCII characters except '='. type: string value: description: |- @@ -8457,6 +8775,36 @@ spec: - fieldPath type: object x-kubernetes-map-type: atomic + fileKeyRef: + description: |- + FileKeyRef selects a key of the env file. + Requires the EnvFiles feature gate to be enabled. + properties: + key: + description: The key within the env + file. An invalid key will prevent + the pod from starting. + type: string + optional: + default: false + description: Specify whether the file + or its key must be defined. + type: boolean + path: + description: |- + The path within the volume from which to select the file. + Must be relative and may not contain the '.. + type: string + volumeName: + description: The name of the volume + mount containing the env file. + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic resourceFieldRef: description: |- Selects a resource of the container: only resources limits and requests @@ -8535,8 +8883,7 @@ spec: x-kubernetes-map-type: atomic prefix: description: Optional text to prepend to the - name of each environment variable. Must be - a C_IDENTIFIER. + name of each environment variable. type: string secretRef: description: The Secret to select from @@ -9051,8 +9398,9 @@ spec: variable present in a Container. properties: name: - description: Name of the environment variable. - Must be a C_IDENTIFIER. + description: |- + Name of the environment variable. + May consist of any printable ASCII characters except '='. type: string value: description: |- @@ -9103,6 +9451,36 @@ spec: - fieldPath type: object x-kubernetes-map-type: atomic + fileKeyRef: + description: |- + FileKeyRef selects a key of the env file. + Requires the EnvFiles feature gate to be enabled. + properties: + key: + description: The key within the + env file. An invalid key will + prevent the pod from starting. + type: string + optional: + default: false + description: Specify whether the + file or its key must be defined. + type: boolean + path: + description: |- + The path within the volume from which to select the file. + Must be relative and may not contain the '.. + type: string + volumeName: + description: The name of the volume + mount containing the env file. + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic resourceFieldRef: description: |- Selects a resource of the container: only resources limits and requests @@ -9183,7 +9561,6 @@ spec: prefix: description: Optional text to prepend to the name of each environment variable. - Must be a C_IDENTIFIER. type: string secretRef: description: The Secret to select from @@ -10374,8 +10751,9 @@ spec: present in a Container. properties: name: - description: Name of the environment variable. Must - be a C_IDENTIFIER. + description: |- + Name of the environment variable. + May consist of any printable ASCII characters except '='. type: string value: description: |- @@ -10424,6 +10802,36 @@ spec: - fieldPath type: object x-kubernetes-map-type: atomic + fileKeyRef: + description: |- + FileKeyRef selects a key of the env file. + Requires the EnvFiles feature gate to be enabled. + properties: + key: + description: The key within the env file. + An invalid key will prevent the pod from + starting. + type: string + optional: + default: false + description: Specify whether the file or + its key must be defined. + type: boolean + path: + description: |- + The path within the volume from which to select the file. + Must be relative and may not contain the '.. + type: string + volumeName: + description: The name of the volume mount + containing the env file. + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic resourceFieldRef: description: |- Selects a resource of the container: only resources limits and requests @@ -10500,7 +10908,7 @@ spec: x-kubernetes-map-type: atomic prefix: description: Optional text to prepend to the name - of each environment variable. Must be a C_IDENTIFIER. + of each environment variable. type: string secretRef: description: The Secret to select from @@ -11402,9 +11810,8 @@ spec: on the host that shares a pod's lifetime. properties: endpoints: - description: |- - endpoints is the endpoint name that details Glusterfs topology. - More info: https://examples.k8s. + description: endpoints is the endpoint name that + details Glusterfs topology. type: string path: description: |- @@ -11821,6 +12228,52 @@ spec: type: array x-kubernetes-list-type: atomic type: object + podCertificate: + description: |- + Projects an auto-rotating credential bundle (private key and certificate + chain) that the pod can use either as a TLS... + properties: + certificateChainPath: + description: |- + Write the certificate chain at this path in the projected volume. + + Most applications should use credentialBundlePath. + type: string + credentialBundlePath: + description: Write the credential bundle + at this path in the projected volume. + type: string + keyPath: + description: |- + Write the key at this path in the projected volume. + + Most applications should use credentialBundlePath. + type: string + keyType: + description: The type of keypair Kubelet + will generate for the pod. + type: string + maxExpirationSeconds: + description: |- + maxExpirationSeconds is the maximum lifetime permitted for the + certificate. + format: int32 + type: integer + signerName: + description: Kubelet's generated CSRs + will be addressed to this signer. + type: string + userAnnotations: + additionalProperties: + type: string + description: |- + userAnnotations allow pod authors to pass additional information to + the signer implementation. + type: object + required: + - keyType + - signerName + type: object secret: description: secret information about the secret data to project @@ -12419,8 +12872,9 @@ spec: in a Container. properties: name: - description: Name of the environment variable. Must - be a C_IDENTIFIER. + description: |- + Name of the environment variable. + May consist of any printable ASCII characters except '='. type: string value: description: |- @@ -12469,6 +12923,35 @@ spec: - fieldPath type: object x-kubernetes-map-type: atomic + fileKeyRef: + description: |- + FileKeyRef selects a key of the env file. + Requires the EnvFiles feature gate to be enabled. + properties: + key: + description: The key within the env file. An + invalid key will prevent the pod from starting. + type: string + optional: + default: false + description: Specify whether the file or its + key must be defined. + type: boolean + path: + description: |- + The path within the volume from which to select the file. + Must be relative and may not contain the '.. + type: string + volumeName: + description: The name of the volume mount containing + the env file. + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic resourceFieldRef: description: |- Selects a resource of the container: only resources limits and requests @@ -12544,7 +13027,7 @@ spec: x-kubernetes-map-type: atomic prefix: description: Optional text to prepend to the name of - each environment variable. Must be a C_IDENTIFIER. + each environment variable. type: string secretRef: description: The Secret to select from @@ -12729,7 +13212,6 @@ spec: type. type: string required: - - status - type type: object type: array @@ -12812,8 +13294,9 @@ spec: in a Container. properties: name: - description: Name of the environment variable. Must - be a C_IDENTIFIER. + description: |- + Name of the environment variable. + May consist of any printable ASCII characters except '='. type: string value: description: |- @@ -12862,6 +13345,35 @@ spec: - fieldPath type: object x-kubernetes-map-type: atomic + fileKeyRef: + description: |- + FileKeyRef selects a key of the env file. + Requires the EnvFiles feature gate to be enabled. + properties: + key: + description: The key within the env file. An + invalid key will prevent the pod from starting. + type: string + optional: + default: false + description: Specify whether the file or its + key must be defined. + type: boolean + path: + description: |- + The path within the volume from which to select the file. + Must be relative and may not contain the '.. + type: string + volumeName: + description: The name of the volume mount containing + the env file. + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic resourceFieldRef: description: |- Selects a resource of the container: only resources limits and requests @@ -12937,7 +13449,7 @@ spec: x-kubernetes-map-type: atomic prefix: description: Optional text to prepend to the name of - each environment variable. Must be a C_IDENTIFIER. + each environment variable. type: string secretRef: description: The Secret to select from @@ -13189,8 +13701,9 @@ spec: present in a Container. properties: name: - description: Name of the environment variable. Must - be a C_IDENTIFIER. + description: |- + Name of the environment variable. + May consist of any printable ASCII characters except '='. type: string value: description: |- @@ -13239,6 +13752,36 @@ spec: - fieldPath type: object x-kubernetes-map-type: atomic + fileKeyRef: + description: |- + FileKeyRef selects a key of the env file. + Requires the EnvFiles feature gate to be enabled. + properties: + key: + description: The key within the env file. + An invalid key will prevent the pod from + starting. + type: string + optional: + default: false + description: Specify whether the file or + its key must be defined. + type: boolean + path: + description: |- + The path within the volume from which to select the file. + Must be relative and may not contain the '.. + type: string + volumeName: + description: The name of the volume mount + containing the env file. + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic resourceFieldRef: description: |- Selects a resource of the container: only resources limits and requests @@ -13315,7 +13858,7 @@ spec: x-kubernetes-map-type: atomic prefix: description: Optional text to prepend to the name - of each environment variable. Must be a C_IDENTIFIER. + of each environment variable. type: string secretRef: description: The Secret to select from @@ -13702,8 +14245,9 @@ spec: present in a Container. properties: name: - description: Name of the environment variable. Must - be a C_IDENTIFIER. + description: |- + Name of the environment variable. + May consist of any printable ASCII characters except '='. type: string value: description: |- @@ -13752,6 +14296,36 @@ spec: - fieldPath type: object x-kubernetes-map-type: atomic + fileKeyRef: + description: |- + FileKeyRef selects a key of the env file. + Requires the EnvFiles feature gate to be enabled. + properties: + key: + description: The key within the env file. + An invalid key will prevent the pod from + starting. + type: string + optional: + default: false + description: Specify whether the file or + its key must be defined. + type: boolean + path: + description: |- + The path within the volume from which to select the file. + Must be relative and may not contain the '.. + type: string + volumeName: + description: The name of the volume mount + containing the env file. + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic resourceFieldRef: description: |- Selects a resource of the container: only resources limits and requests @@ -13828,7 +14402,7 @@ spec: x-kubernetes-map-type: atomic prefix: description: Optional text to prepend to the name - of each environment variable. Must be a C_IDENTIFIER. + of each environment variable. type: string secretRef: description: The Secret to select from @@ -14233,8 +14807,9 @@ spec: present in a Container. properties: name: - description: Name of the environment variable. - Must be a C_IDENTIFIER. + description: |- + Name of the environment variable. + May consist of any printable ASCII characters except '='. type: string value: description: |- @@ -14284,6 +14859,36 @@ spec: - fieldPath type: object x-kubernetes-map-type: atomic + fileKeyRef: + description: |- + FileKeyRef selects a key of the env file. + Requires the EnvFiles feature gate to be enabled. + properties: + key: + description: The key within the env + file. An invalid key will prevent + the pod from starting. + type: string + optional: + default: false + description: Specify whether the file + or its key must be defined. + type: boolean + path: + description: |- + The path within the volume from which to select the file. + Must be relative and may not contain the '.. + type: string + volumeName: + description: The name of the volume + mount containing the env file. + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic resourceFieldRef: description: |- Selects a resource of the container: only resources limits and requests @@ -14362,8 +14967,7 @@ spec: x-kubernetes-map-type: atomic prefix: description: Optional text to prepend to the - name of each environment variable. Must be - a C_IDENTIFIER. + name of each environment variable. type: string secretRef: description: The Secret to select from @@ -14807,8 +15411,9 @@ spec: in a Container. properties: name: - description: Name of the environment variable. Must - be a C_IDENTIFIER. + description: |- + Name of the environment variable. + May consist of any printable ASCII characters except '='. type: string value: description: |- @@ -14857,6 +15462,35 @@ spec: - fieldPath type: object x-kubernetes-map-type: atomic + fileKeyRef: + description: |- + FileKeyRef selects a key of the env file. + Requires the EnvFiles feature gate to be enabled. + properties: + key: + description: The key within the env file. An + invalid key will prevent the pod from starting. + type: string + optional: + default: false + description: Specify whether the file or its + key must be defined. + type: boolean + path: + description: |- + The path within the volume from which to select the file. + Must be relative and may not contain the '.. + type: string + volumeName: + description: The name of the volume mount containing + the env file. + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic resourceFieldRef: description: |- Selects a resource of the container: only resources limits and requests @@ -14932,7 +15566,7 @@ spec: x-kubernetes-map-type: atomic prefix: description: Optional text to prepend to the name of - each environment variable. Must be a C_IDENTIFIER. + each environment variable. type: string secretRef: description: The Secret to select from @@ -15826,9 +16460,8 @@ spec: host that shares a pod's lifetime. properties: endpoints: - description: |- - endpoints is the endpoint name that details Glusterfs topology. - More info: https://examples.k8s. + description: endpoints is the endpoint name that details + Glusterfs topology. type: string path: description: |- @@ -16241,6 +16874,52 @@ spec: type: array x-kubernetes-list-type: atomic type: object + podCertificate: + description: |- + Projects an auto-rotating credential bundle (private key and certificate + chain) that the pod can use either as a TLS... + properties: + certificateChainPath: + description: |- + Write the certificate chain at this path in the projected volume. + + Most applications should use credentialBundlePath. + type: string + credentialBundlePath: + description: Write the credential bundle at + this path in the projected volume. + type: string + keyPath: + description: |- + Write the key at this path in the projected volume. + + Most applications should use credentialBundlePath. + type: string + keyType: + description: The type of keypair Kubelet will + generate for the pod. + type: string + maxExpirationSeconds: + description: |- + maxExpirationSeconds is the maximum lifetime permitted for the + certificate. + format: int32 + type: integer + signerName: + description: Kubelet's generated CSRs will + be addressed to this signer. + type: string + userAnnotations: + additionalProperties: + type: string + description: |- + userAnnotations allow pod authors to pass additional information to + the signer implementation. + type: object + required: + - keyType + - signerName + type: object secret: description: secret information about the secret data to project @@ -16679,8 +17358,9 @@ spec: present in a Container. properties: name: - description: Name of the environment variable. Must - be a C_IDENTIFIER. + description: |- + Name of the environment variable. + May consist of any printable ASCII characters except '='. type: string value: description: |- @@ -16729,6 +17409,36 @@ spec: - fieldPath type: object x-kubernetes-map-type: atomic + fileKeyRef: + description: |- + FileKeyRef selects a key of the env file. + Requires the EnvFiles feature gate to be enabled. + properties: + key: + description: The key within the env file. + An invalid key will prevent the pod from + starting. + type: string + optional: + default: false + description: Specify whether the file or + its key must be defined. + type: boolean + path: + description: |- + The path within the volume from which to select the file. + Must be relative and may not contain the '.. + type: string + volumeName: + description: The name of the volume mount + containing the env file. + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic resourceFieldRef: description: |- Selects a resource of the container: only resources limits and requests @@ -16805,7 +17515,7 @@ spec: x-kubernetes-map-type: atomic prefix: description: Optional text to prepend to the name - of each environment variable. Must be a C_IDENTIFIER. + of each environment variable. type: string secretRef: description: The Secret to select from @@ -16992,7 +17702,6 @@ spec: condition type. type: string required: - - status - type type: object type: array @@ -17077,8 +17786,9 @@ spec: present in a Container. properties: name: - description: Name of the environment variable. Must - be a C_IDENTIFIER. + description: |- + Name of the environment variable. + May consist of any printable ASCII characters except '='. type: string value: description: |- @@ -17127,6 +17837,36 @@ spec: - fieldPath type: object x-kubernetes-map-type: atomic + fileKeyRef: + description: |- + FileKeyRef selects a key of the env file. + Requires the EnvFiles feature gate to be enabled. + properties: + key: + description: The key within the env file. + An invalid key will prevent the pod from + starting. + type: string + optional: + default: false + description: Specify whether the file or + its key must be defined. + type: boolean + path: + description: |- + The path within the volume from which to select the file. + Must be relative and may not contain the '.. + type: string + volumeName: + description: The name of the volume mount + containing the env file. + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic resourceFieldRef: description: |- Selects a resource of the container: only resources limits and requests @@ -17203,7 +17943,7 @@ spec: x-kubernetes-map-type: atomic prefix: description: Optional text to prepend to the name - of each environment variable. Must be a C_IDENTIFIER. + of each environment variable. type: string secretRef: description: The Secret to select from @@ -17459,8 +18199,9 @@ spec: present in a Container. properties: name: - description: Name of the environment variable. - Must be a C_IDENTIFIER. + description: |- + Name of the environment variable. + May consist of any printable ASCII characters except '='. type: string value: description: |- @@ -17510,6 +18251,36 @@ spec: - fieldPath type: object x-kubernetes-map-type: atomic + fileKeyRef: + description: |- + FileKeyRef selects a key of the env file. + Requires the EnvFiles feature gate to be enabled. + properties: + key: + description: The key within the env + file. An invalid key will prevent + the pod from starting. + type: string + optional: + default: false + description: Specify whether the file + or its key must be defined. + type: boolean + path: + description: |- + The path within the volume from which to select the file. + Must be relative and may not contain the '.. + type: string + volumeName: + description: The name of the volume + mount containing the env file. + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic resourceFieldRef: description: |- Selects a resource of the container: only resources limits and requests @@ -17588,8 +18359,7 @@ spec: x-kubernetes-map-type: atomic prefix: description: Optional text to prepend to the - name of each environment variable. Must be - a C_IDENTIFIER. + name of each environment variable. type: string secretRef: description: The Secret to select from @@ -17981,8 +18751,9 @@ spec: present in a Container. properties: name: - description: Name of the environment variable. - Must be a C_IDENTIFIER. + description: |- + Name of the environment variable. + May consist of any printable ASCII characters except '='. type: string value: description: |- @@ -18032,6 +18803,36 @@ spec: - fieldPath type: object x-kubernetes-map-type: atomic + fileKeyRef: + description: |- + FileKeyRef selects a key of the env file. + Requires the EnvFiles feature gate to be enabled. + properties: + key: + description: The key within the env + file. An invalid key will prevent + the pod from starting. + type: string + optional: + default: false + description: Specify whether the file + or its key must be defined. + type: boolean + path: + description: |- + The path within the volume from which to select the file. + Must be relative and may not contain the '.. + type: string + volumeName: + description: The name of the volume + mount containing the env file. + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic resourceFieldRef: description: |- Selects a resource of the container: only resources limits and requests @@ -18110,8 +18911,7 @@ spec: x-kubernetes-map-type: atomic prefix: description: Optional text to prepend to the - name of each environment variable. Must be - a C_IDENTIFIER. + name of each environment variable. type: string secretRef: description: The Secret to select from @@ -18525,8 +19325,9 @@ spec: variable present in a Container. properties: name: - description: Name of the environment variable. - Must be a C_IDENTIFIER. + description: |- + Name of the environment variable. + May consist of any printable ASCII characters except '='. type: string value: description: |- @@ -18577,6 +19378,36 @@ spec: - fieldPath type: object x-kubernetes-map-type: atomic + fileKeyRef: + description: |- + FileKeyRef selects a key of the env file. + Requires the EnvFiles feature gate to be enabled. + properties: + key: + description: The key within the + env file. An invalid key will + prevent the pod from starting. + type: string + optional: + default: false + description: Specify whether the + file or its key must be defined. + type: boolean + path: + description: |- + The path within the volume from which to select the file. + Must be relative and may not contain the '.. + type: string + volumeName: + description: The name of the volume + mount containing the env file. + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic resourceFieldRef: description: |- Selects a resource of the container: only resources limits and requests @@ -18657,7 +19488,6 @@ spec: prefix: description: Optional text to prepend to the name of each environment variable. - Must be a C_IDENTIFIER. type: string secretRef: description: The Secret to select from @@ -19112,8 +19942,9 @@ spec: present in a Container. properties: name: - description: Name of the environment variable. Must - be a C_IDENTIFIER. + description: |- + Name of the environment variable. + May consist of any printable ASCII characters except '='. type: string value: description: |- @@ -19162,6 +19993,36 @@ spec: - fieldPath type: object x-kubernetes-map-type: atomic + fileKeyRef: + description: |- + FileKeyRef selects a key of the env file. + Requires the EnvFiles feature gate to be enabled. + properties: + key: + description: The key within the env file. + An invalid key will prevent the pod from + starting. + type: string + optional: + default: false + description: Specify whether the file or + its key must be defined. + type: boolean + path: + description: |- + The path within the volume from which to select the file. + Must be relative and may not contain the '.. + type: string + volumeName: + description: The name of the volume mount + containing the env file. + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic resourceFieldRef: description: |- Selects a resource of the container: only resources limits and requests @@ -19238,7 +20099,7 @@ spec: x-kubernetes-map-type: atomic prefix: description: Optional text to prepend to the name - of each environment variable. Must be a C_IDENTIFIER. + of each environment variable. type: string secretRef: description: The Secret to select from @@ -20140,9 +21001,8 @@ spec: on the host that shares a pod's lifetime. properties: endpoints: - description: |- - endpoints is the endpoint name that details Glusterfs topology. - More info: https://examples.k8s. + description: endpoints is the endpoint name that + details Glusterfs topology. type: string path: description: |- @@ -20559,6 +21419,52 @@ spec: type: array x-kubernetes-list-type: atomic type: object + podCertificate: + description: |- + Projects an auto-rotating credential bundle (private key and certificate + chain) that the pod can use either as a TLS... + properties: + certificateChainPath: + description: |- + Write the certificate chain at this path in the projected volume. + + Most applications should use credentialBundlePath. + type: string + credentialBundlePath: + description: Write the credential bundle + at this path in the projected volume. + type: string + keyPath: + description: |- + Write the key at this path in the projected volume. + + Most applications should use credentialBundlePath. + type: string + keyType: + description: The type of keypair Kubelet + will generate for the pod. + type: string + maxExpirationSeconds: + description: |- + maxExpirationSeconds is the maximum lifetime permitted for the + certificate. + format: int32 + type: integer + signerName: + description: Kubelet's generated CSRs + will be addressed to this signer. + type: string + userAnnotations: + additionalProperties: + type: string + description: |- + userAnnotations allow pod authors to pass additional information to + the signer implementation. + type: object + required: + - keyType + - signerName + type: object secret: description: secret information about the secret data to project diff --git a/infra/feast-operator/config/rbac/role.yaml b/infra/feast-operator/config/rbac/role.yaml index 0c1bd7be84b..f6e6801dfa8 100644 --- a/infra/feast-operator/config/rbac/role.yaml +++ b/infra/feast-operator/config/rbac/role.yaml @@ -75,6 +75,14 @@ rules: - patch - update - watch +- apiGroups: + - config.openshift.io + resources: + - apiservers + verbs: + - get + - list + - watch - apiGroups: - feast.dev resources: diff --git a/infra/feast-operator/dist/install.yaml b/infra/feast-operator/dist/install.yaml index ca60530055a..6c128fc320c 100644 --- a/infra/feast-operator/dist/install.yaml +++ b/infra/feast-operator/dist/install.yaml @@ -169,8 +169,9 @@ spec: in a Container. properties: name: - description: Name of the environment variable. Must - be a C_IDENTIFIER. + description: |- + Name of the environment variable. + May consist of any printable ASCII characters except '='. type: string value: description: |- @@ -219,6 +220,35 @@ spec: - fieldPath type: object x-kubernetes-map-type: atomic + fileKeyRef: + description: |- + FileKeyRef selects a key of the env file. + Requires the EnvFiles feature gate to be enabled. + properties: + key: + description: The key within the env file. An + invalid key will prevent the pod from starting. + type: string + optional: + default: false + description: Specify whether the file or its + key must be defined. + type: boolean + path: + description: |- + The path within the volume from which to select the file. + Must be relative and may not contain the '.. + type: string + volumeName: + description: The name of the volume mount containing + the env file. + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic resourceFieldRef: description: |- Selects a resource of the container: only resources limits and requests @@ -294,7 +324,7 @@ spec: x-kubernetes-map-type: atomic prefix: description: Optional text to prepend to the name of - each environment variable. Must be a C_IDENTIFIER. + each environment variable. type: string secretRef: description: The Secret to select from @@ -479,7 +509,6 @@ spec: type. type: string required: - - status - type type: object type: array @@ -572,8 +601,9 @@ spec: in a Container. properties: name: - description: Name of the environment variable. Must - be a C_IDENTIFIER. + description: |- + Name of the environment variable. + May consist of any printable ASCII characters except '='. type: string value: description: |- @@ -622,6 +652,35 @@ spec: - fieldPath type: object x-kubernetes-map-type: atomic + fileKeyRef: + description: |- + FileKeyRef selects a key of the env file. + Requires the EnvFiles feature gate to be enabled. + properties: + key: + description: The key within the env file. An + invalid key will prevent the pod from starting. + type: string + optional: + default: false + description: Specify whether the file or its + key must be defined. + type: boolean + path: + description: |- + The path within the volume from which to select the file. + Must be relative and may not contain the '.. + type: string + volumeName: + description: The name of the volume mount containing + the env file. + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic resourceFieldRef: description: |- Selects a resource of the container: only resources limits and requests @@ -697,7 +756,7 @@ spec: x-kubernetes-map-type: atomic prefix: description: Optional text to prepend to the name of - each environment variable. Must be a C_IDENTIFIER. + each environment variable. type: string secretRef: description: The Secret to select from @@ -1822,8 +1881,9 @@ spec: present in a Container. properties: name: - description: Name of the environment variable. Must - be a C_IDENTIFIER. + description: |- + Name of the environment variable. + May consist of any printable ASCII characters except '='. type: string value: description: |- @@ -1872,6 +1932,36 @@ spec: - fieldPath type: object x-kubernetes-map-type: atomic + fileKeyRef: + description: |- + FileKeyRef selects a key of the env file. + Requires the EnvFiles feature gate to be enabled. + properties: + key: + description: The key within the env file. + An invalid key will prevent the pod from + starting. + type: string + optional: + default: false + description: Specify whether the file or + its key must be defined. + type: boolean + path: + description: |- + The path within the volume from which to select the file. + Must be relative and may not contain the '.. + type: string + volumeName: + description: The name of the volume mount + containing the env file. + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic resourceFieldRef: description: |- Selects a resource of the container: only resources limits and requests @@ -1948,7 +2038,7 @@ spec: x-kubernetes-map-type: atomic prefix: description: Optional text to prepend to the name - of each environment variable. Must be a C_IDENTIFIER. + of each environment variable. type: string secretRef: description: The Secret to select from @@ -2335,8 +2425,9 @@ spec: present in a Container. properties: name: - description: Name of the environment variable. Must - be a C_IDENTIFIER. + description: |- + Name of the environment variable. + May consist of any printable ASCII characters except '='. type: string value: description: |- @@ -2385,6 +2476,36 @@ spec: - fieldPath type: object x-kubernetes-map-type: atomic + fileKeyRef: + description: |- + FileKeyRef selects a key of the env file. + Requires the EnvFiles feature gate to be enabled. + properties: + key: + description: The key within the env file. + An invalid key will prevent the pod from + starting. + type: string + optional: + default: false + description: Specify whether the file or + its key must be defined. + type: boolean + path: + description: |- + The path within the volume from which to select the file. + Must be relative and may not contain the '.. + type: string + volumeName: + description: The name of the volume mount + containing the env file. + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic resourceFieldRef: description: |- Selects a resource of the container: only resources limits and requests @@ -2461,7 +2582,7 @@ spec: x-kubernetes-map-type: atomic prefix: description: Optional text to prepend to the name - of each environment variable. Must be a C_IDENTIFIER. + of each environment variable. type: string secretRef: description: The Secret to select from @@ -2965,8 +3086,9 @@ spec: present in a Container. properties: name: - description: Name of the environment variable. - Must be a C_IDENTIFIER. + description: |- + Name of the environment variable. + May consist of any printable ASCII characters except '='. type: string value: description: |- @@ -3016,6 +3138,36 @@ spec: - fieldPath type: object x-kubernetes-map-type: atomic + fileKeyRef: + description: |- + FileKeyRef selects a key of the env file. + Requires the EnvFiles feature gate to be enabled. + properties: + key: + description: The key within the env + file. An invalid key will prevent + the pod from starting. + type: string + optional: + default: false + description: Specify whether the file + or its key must be defined. + type: boolean + path: + description: |- + The path within the volume from which to select the file. + Must be relative and may not contain the '.. + type: string + volumeName: + description: The name of the volume + mount containing the env file. + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic resourceFieldRef: description: |- Selects a resource of the container: only resources limits and requests @@ -3094,8 +3246,7 @@ spec: x-kubernetes-map-type: atomic prefix: description: Optional text to prepend to the - name of each environment variable. Must be - a C_IDENTIFIER. + name of each environment variable. type: string secretRef: description: The Secret to select from @@ -4270,8 +4421,9 @@ spec: in a Container. properties: name: - description: Name of the environment variable. Must - be a C_IDENTIFIER. + description: |- + Name of the environment variable. + May consist of any printable ASCII characters except '='. type: string value: description: |- @@ -4320,6 +4472,35 @@ spec: - fieldPath type: object x-kubernetes-map-type: atomic + fileKeyRef: + description: |- + FileKeyRef selects a key of the env file. + Requires the EnvFiles feature gate to be enabled. + properties: + key: + description: The key within the env file. An + invalid key will prevent the pod from starting. + type: string + optional: + default: false + description: Specify whether the file or its + key must be defined. + type: boolean + path: + description: |- + The path within the volume from which to select the file. + Must be relative and may not contain the '.. + type: string + volumeName: + description: The name of the volume mount containing + the env file. + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic resourceFieldRef: description: |- Selects a resource of the container: only resources limits and requests @@ -4395,7 +4576,7 @@ spec: x-kubernetes-map-type: atomic prefix: description: Optional text to prepend to the name of - each environment variable. Must be a C_IDENTIFIER. + each environment variable. type: string secretRef: description: The Secret to select from @@ -5289,9 +5470,8 @@ spec: host that shares a pod's lifetime. properties: endpoints: - description: |- - endpoints is the endpoint name that details Glusterfs topology. - More info: https://examples.k8s. + description: endpoints is the endpoint name that details + Glusterfs topology. type: string path: description: |- @@ -5704,6 +5884,52 @@ spec: type: array x-kubernetes-list-type: atomic type: object + podCertificate: + description: |- + Projects an auto-rotating credential bundle (private key and certificate + chain) that the pod can use either as a TLS... + properties: + certificateChainPath: + description: |- + Write the certificate chain at this path in the projected volume. + + Most applications should use credentialBundlePath. + type: string + credentialBundlePath: + description: Write the credential bundle at + this path in the projected volume. + type: string + keyPath: + description: |- + Write the key at this path in the projected volume. + + Most applications should use credentialBundlePath. + type: string + keyType: + description: The type of keypair Kubelet will + generate for the pod. + type: string + maxExpirationSeconds: + description: |- + maxExpirationSeconds is the maximum lifetime permitted for the + certificate. + format: int32 + type: integer + signerName: + description: Kubelet's generated CSRs will + be addressed to this signer. + type: string + userAnnotations: + additionalProperties: + type: string + description: |- + userAnnotations allow pod authors to pass additional information to + the signer implementation. + type: object + required: + - keyType + - signerName + type: object secret: description: secret information about the secret data to project @@ -6221,8 +6447,9 @@ spec: present in a Container. properties: name: - description: Name of the environment variable. Must - be a C_IDENTIFIER. + description: |- + Name of the environment variable. + May consist of any printable ASCII characters except '='. type: string value: description: |- @@ -6271,6 +6498,36 @@ spec: - fieldPath type: object x-kubernetes-map-type: atomic + fileKeyRef: + description: |- + FileKeyRef selects a key of the env file. + Requires the EnvFiles feature gate to be enabled. + properties: + key: + description: The key within the env file. + An invalid key will prevent the pod from + starting. + type: string + optional: + default: false + description: Specify whether the file or + its key must be defined. + type: boolean + path: + description: |- + The path within the volume from which to select the file. + Must be relative and may not contain the '.. + type: string + volumeName: + description: The name of the volume mount + containing the env file. + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic resourceFieldRef: description: |- Selects a resource of the container: only resources limits and requests @@ -6347,7 +6604,7 @@ spec: x-kubernetes-map-type: atomic prefix: description: Optional text to prepend to the name - of each environment variable. Must be a C_IDENTIFIER. + of each environment variable. type: string secretRef: description: The Secret to select from @@ -6534,7 +6791,6 @@ spec: condition type. type: string required: - - status - type type: object type: array @@ -6629,8 +6885,9 @@ spec: present in a Container. properties: name: - description: Name of the environment variable. Must - be a C_IDENTIFIER. + description: |- + Name of the environment variable. + May consist of any printable ASCII characters except '='. type: string value: description: |- @@ -6679,6 +6936,36 @@ spec: - fieldPath type: object x-kubernetes-map-type: atomic + fileKeyRef: + description: |- + FileKeyRef selects a key of the env file. + Requires the EnvFiles feature gate to be enabled. + properties: + key: + description: The key within the env file. + An invalid key will prevent the pod from + starting. + type: string + optional: + default: false + description: Specify whether the file or + its key must be defined. + type: boolean + path: + description: |- + The path within the volume from which to select the file. + Must be relative and may not contain the '.. + type: string + volumeName: + description: The name of the volume mount + containing the env file. + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic resourceFieldRef: description: |- Selects a resource of the container: only resources limits and requests @@ -6755,7 +7042,7 @@ spec: x-kubernetes-map-type: atomic prefix: description: Optional text to prepend to the name - of each environment variable. Must be a C_IDENTIFIER. + of each environment variable. type: string secretRef: description: The Secret to select from @@ -7892,8 +8179,9 @@ spec: present in a Container. properties: name: - description: Name of the environment variable. - Must be a C_IDENTIFIER. + description: |- + Name of the environment variable. + May consist of any printable ASCII characters except '='. type: string value: description: |- @@ -7943,6 +8231,36 @@ spec: - fieldPath type: object x-kubernetes-map-type: atomic + fileKeyRef: + description: |- + FileKeyRef selects a key of the env file. + Requires the EnvFiles feature gate to be enabled. + properties: + key: + description: The key within the env + file. An invalid key will prevent + the pod from starting. + type: string + optional: + default: false + description: Specify whether the file + or its key must be defined. + type: boolean + path: + description: |- + The path within the volume from which to select the file. + Must be relative and may not contain the '.. + type: string + volumeName: + description: The name of the volume + mount containing the env file. + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic resourceFieldRef: description: |- Selects a resource of the container: only resources limits and requests @@ -8021,8 +8339,7 @@ spec: x-kubernetes-map-type: atomic prefix: description: Optional text to prepend to the - name of each environment variable. Must be - a C_IDENTIFIER. + name of each environment variable. type: string secretRef: description: The Secret to select from @@ -8414,8 +8731,9 @@ spec: present in a Container. properties: name: - description: Name of the environment variable. - Must be a C_IDENTIFIER. + description: |- + Name of the environment variable. + May consist of any printable ASCII characters except '='. type: string value: description: |- @@ -8465,6 +8783,36 @@ spec: - fieldPath type: object x-kubernetes-map-type: atomic + fileKeyRef: + description: |- + FileKeyRef selects a key of the env file. + Requires the EnvFiles feature gate to be enabled. + properties: + key: + description: The key within the env + file. An invalid key will prevent + the pod from starting. + type: string + optional: + default: false + description: Specify whether the file + or its key must be defined. + type: boolean + path: + description: |- + The path within the volume from which to select the file. + Must be relative and may not contain the '.. + type: string + volumeName: + description: The name of the volume + mount containing the env file. + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic resourceFieldRef: description: |- Selects a resource of the container: only resources limits and requests @@ -8543,8 +8891,7 @@ spec: x-kubernetes-map-type: atomic prefix: description: Optional text to prepend to the - name of each environment variable. Must be - a C_IDENTIFIER. + name of each environment variable. type: string secretRef: description: The Secret to select from @@ -9059,8 +9406,9 @@ spec: variable present in a Container. properties: name: - description: Name of the environment variable. - Must be a C_IDENTIFIER. + description: |- + Name of the environment variable. + May consist of any printable ASCII characters except '='. type: string value: description: |- @@ -9111,6 +9459,36 @@ spec: - fieldPath type: object x-kubernetes-map-type: atomic + fileKeyRef: + description: |- + FileKeyRef selects a key of the env file. + Requires the EnvFiles feature gate to be enabled. + properties: + key: + description: The key within the + env file. An invalid key will + prevent the pod from starting. + type: string + optional: + default: false + description: Specify whether the + file or its key must be defined. + type: boolean + path: + description: |- + The path within the volume from which to select the file. + Must be relative and may not contain the '.. + type: string + volumeName: + description: The name of the volume + mount containing the env file. + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic resourceFieldRef: description: |- Selects a resource of the container: only resources limits and requests @@ -9191,7 +9569,6 @@ spec: prefix: description: Optional text to prepend to the name of each environment variable. - Must be a C_IDENTIFIER. type: string secretRef: description: The Secret to select from @@ -10382,8 +10759,9 @@ spec: present in a Container. properties: name: - description: Name of the environment variable. Must - be a C_IDENTIFIER. + description: |- + Name of the environment variable. + May consist of any printable ASCII characters except '='. type: string value: description: |- @@ -10432,6 +10810,36 @@ spec: - fieldPath type: object x-kubernetes-map-type: atomic + fileKeyRef: + description: |- + FileKeyRef selects a key of the env file. + Requires the EnvFiles feature gate to be enabled. + properties: + key: + description: The key within the env file. + An invalid key will prevent the pod from + starting. + type: string + optional: + default: false + description: Specify whether the file or + its key must be defined. + type: boolean + path: + description: |- + The path within the volume from which to select the file. + Must be relative and may not contain the '.. + type: string + volumeName: + description: The name of the volume mount + containing the env file. + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic resourceFieldRef: description: |- Selects a resource of the container: only resources limits and requests @@ -10508,7 +10916,7 @@ spec: x-kubernetes-map-type: atomic prefix: description: Optional text to prepend to the name - of each environment variable. Must be a C_IDENTIFIER. + of each environment variable. type: string secretRef: description: The Secret to select from @@ -11410,9 +11818,8 @@ spec: on the host that shares a pod's lifetime. properties: endpoints: - description: |- - endpoints is the endpoint name that details Glusterfs topology. - More info: https://examples.k8s. + description: endpoints is the endpoint name that + details Glusterfs topology. type: string path: description: |- @@ -11829,6 +12236,52 @@ spec: type: array x-kubernetes-list-type: atomic type: object + podCertificate: + description: |- + Projects an auto-rotating credential bundle (private key and certificate + chain) that the pod can use either as a TLS... + properties: + certificateChainPath: + description: |- + Write the certificate chain at this path in the projected volume. + + Most applications should use credentialBundlePath. + type: string + credentialBundlePath: + description: Write the credential bundle + at this path in the projected volume. + type: string + keyPath: + description: |- + Write the key at this path in the projected volume. + + Most applications should use credentialBundlePath. + type: string + keyType: + description: The type of keypair Kubelet + will generate for the pod. + type: string + maxExpirationSeconds: + description: |- + maxExpirationSeconds is the maximum lifetime permitted for the + certificate. + format: int32 + type: integer + signerName: + description: Kubelet's generated CSRs + will be addressed to this signer. + type: string + userAnnotations: + additionalProperties: + type: string + description: |- + userAnnotations allow pod authors to pass additional information to + the signer implementation. + type: object + required: + - keyType + - signerName + type: object secret: description: secret information about the secret data to project @@ -12427,8 +12880,9 @@ spec: in a Container. properties: name: - description: Name of the environment variable. Must - be a C_IDENTIFIER. + description: |- + Name of the environment variable. + May consist of any printable ASCII characters except '='. type: string value: description: |- @@ -12477,6 +12931,35 @@ spec: - fieldPath type: object x-kubernetes-map-type: atomic + fileKeyRef: + description: |- + FileKeyRef selects a key of the env file. + Requires the EnvFiles feature gate to be enabled. + properties: + key: + description: The key within the env file. An + invalid key will prevent the pod from starting. + type: string + optional: + default: false + description: Specify whether the file or its + key must be defined. + type: boolean + path: + description: |- + The path within the volume from which to select the file. + Must be relative and may not contain the '.. + type: string + volumeName: + description: The name of the volume mount containing + the env file. + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic resourceFieldRef: description: |- Selects a resource of the container: only resources limits and requests @@ -12552,7 +13035,7 @@ spec: x-kubernetes-map-type: atomic prefix: description: Optional text to prepend to the name of - each environment variable. Must be a C_IDENTIFIER. + each environment variable. type: string secretRef: description: The Secret to select from @@ -12737,7 +13220,6 @@ spec: type. type: string required: - - status - type type: object type: array @@ -12820,8 +13302,9 @@ spec: in a Container. properties: name: - description: Name of the environment variable. Must - be a C_IDENTIFIER. + description: |- + Name of the environment variable. + May consist of any printable ASCII characters except '='. type: string value: description: |- @@ -12870,6 +13353,35 @@ spec: - fieldPath type: object x-kubernetes-map-type: atomic + fileKeyRef: + description: |- + FileKeyRef selects a key of the env file. + Requires the EnvFiles feature gate to be enabled. + properties: + key: + description: The key within the env file. An + invalid key will prevent the pod from starting. + type: string + optional: + default: false + description: Specify whether the file or its + key must be defined. + type: boolean + path: + description: |- + The path within the volume from which to select the file. + Must be relative and may not contain the '.. + type: string + volumeName: + description: The name of the volume mount containing + the env file. + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic resourceFieldRef: description: |- Selects a resource of the container: only resources limits and requests @@ -12945,7 +13457,7 @@ spec: x-kubernetes-map-type: atomic prefix: description: Optional text to prepend to the name of - each environment variable. Must be a C_IDENTIFIER. + each environment variable. type: string secretRef: description: The Secret to select from @@ -13197,8 +13709,9 @@ spec: present in a Container. properties: name: - description: Name of the environment variable. Must - be a C_IDENTIFIER. + description: |- + Name of the environment variable. + May consist of any printable ASCII characters except '='. type: string value: description: |- @@ -13247,6 +13760,36 @@ spec: - fieldPath type: object x-kubernetes-map-type: atomic + fileKeyRef: + description: |- + FileKeyRef selects a key of the env file. + Requires the EnvFiles feature gate to be enabled. + properties: + key: + description: The key within the env file. + An invalid key will prevent the pod from + starting. + type: string + optional: + default: false + description: Specify whether the file or + its key must be defined. + type: boolean + path: + description: |- + The path within the volume from which to select the file. + Must be relative and may not contain the '.. + type: string + volumeName: + description: The name of the volume mount + containing the env file. + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic resourceFieldRef: description: |- Selects a resource of the container: only resources limits and requests @@ -13323,7 +13866,7 @@ spec: x-kubernetes-map-type: atomic prefix: description: Optional text to prepend to the name - of each environment variable. Must be a C_IDENTIFIER. + of each environment variable. type: string secretRef: description: The Secret to select from @@ -13710,8 +14253,9 @@ spec: present in a Container. properties: name: - description: Name of the environment variable. Must - be a C_IDENTIFIER. + description: |- + Name of the environment variable. + May consist of any printable ASCII characters except '='. type: string value: description: |- @@ -13760,6 +14304,36 @@ spec: - fieldPath type: object x-kubernetes-map-type: atomic + fileKeyRef: + description: |- + FileKeyRef selects a key of the env file. + Requires the EnvFiles feature gate to be enabled. + properties: + key: + description: The key within the env file. + An invalid key will prevent the pod from + starting. + type: string + optional: + default: false + description: Specify whether the file or + its key must be defined. + type: boolean + path: + description: |- + The path within the volume from which to select the file. + Must be relative and may not contain the '.. + type: string + volumeName: + description: The name of the volume mount + containing the env file. + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic resourceFieldRef: description: |- Selects a resource of the container: only resources limits and requests @@ -13836,7 +14410,7 @@ spec: x-kubernetes-map-type: atomic prefix: description: Optional text to prepend to the name - of each environment variable. Must be a C_IDENTIFIER. + of each environment variable. type: string secretRef: description: The Secret to select from @@ -14241,8 +14815,9 @@ spec: present in a Container. properties: name: - description: Name of the environment variable. - Must be a C_IDENTIFIER. + description: |- + Name of the environment variable. + May consist of any printable ASCII characters except '='. type: string value: description: |- @@ -14292,6 +14867,36 @@ spec: - fieldPath type: object x-kubernetes-map-type: atomic + fileKeyRef: + description: |- + FileKeyRef selects a key of the env file. + Requires the EnvFiles feature gate to be enabled. + properties: + key: + description: The key within the env + file. An invalid key will prevent + the pod from starting. + type: string + optional: + default: false + description: Specify whether the file + or its key must be defined. + type: boolean + path: + description: |- + The path within the volume from which to select the file. + Must be relative and may not contain the '.. + type: string + volumeName: + description: The name of the volume + mount containing the env file. + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic resourceFieldRef: description: |- Selects a resource of the container: only resources limits and requests @@ -14370,8 +14975,7 @@ spec: x-kubernetes-map-type: atomic prefix: description: Optional text to prepend to the - name of each environment variable. Must be - a C_IDENTIFIER. + name of each environment variable. type: string secretRef: description: The Secret to select from @@ -14815,8 +15419,9 @@ spec: in a Container. properties: name: - description: Name of the environment variable. Must - be a C_IDENTIFIER. + description: |- + Name of the environment variable. + May consist of any printable ASCII characters except '='. type: string value: description: |- @@ -14865,6 +15470,35 @@ spec: - fieldPath type: object x-kubernetes-map-type: atomic + fileKeyRef: + description: |- + FileKeyRef selects a key of the env file. + Requires the EnvFiles feature gate to be enabled. + properties: + key: + description: The key within the env file. An + invalid key will prevent the pod from starting. + type: string + optional: + default: false + description: Specify whether the file or its + key must be defined. + type: boolean + path: + description: |- + The path within the volume from which to select the file. + Must be relative and may not contain the '.. + type: string + volumeName: + description: The name of the volume mount containing + the env file. + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic resourceFieldRef: description: |- Selects a resource of the container: only resources limits and requests @@ -14940,7 +15574,7 @@ spec: x-kubernetes-map-type: atomic prefix: description: Optional text to prepend to the name of - each environment variable. Must be a C_IDENTIFIER. + each environment variable. type: string secretRef: description: The Secret to select from @@ -15834,9 +16468,8 @@ spec: host that shares a pod's lifetime. properties: endpoints: - description: |- - endpoints is the endpoint name that details Glusterfs topology. - More info: https://examples.k8s. + description: endpoints is the endpoint name that details + Glusterfs topology. type: string path: description: |- @@ -16249,6 +16882,52 @@ spec: type: array x-kubernetes-list-type: atomic type: object + podCertificate: + description: |- + Projects an auto-rotating credential bundle (private key and certificate + chain) that the pod can use either as a TLS... + properties: + certificateChainPath: + description: |- + Write the certificate chain at this path in the projected volume. + + Most applications should use credentialBundlePath. + type: string + credentialBundlePath: + description: Write the credential bundle at + this path in the projected volume. + type: string + keyPath: + description: |- + Write the key at this path in the projected volume. + + Most applications should use credentialBundlePath. + type: string + keyType: + description: The type of keypair Kubelet will + generate for the pod. + type: string + maxExpirationSeconds: + description: |- + maxExpirationSeconds is the maximum lifetime permitted for the + certificate. + format: int32 + type: integer + signerName: + description: Kubelet's generated CSRs will + be addressed to this signer. + type: string + userAnnotations: + additionalProperties: + type: string + description: |- + userAnnotations allow pod authors to pass additional information to + the signer implementation. + type: object + required: + - keyType + - signerName + type: object secret: description: secret information about the secret data to project @@ -16687,8 +17366,9 @@ spec: present in a Container. properties: name: - description: Name of the environment variable. Must - be a C_IDENTIFIER. + description: |- + Name of the environment variable. + May consist of any printable ASCII characters except '='. type: string value: description: |- @@ -16737,6 +17417,36 @@ spec: - fieldPath type: object x-kubernetes-map-type: atomic + fileKeyRef: + description: |- + FileKeyRef selects a key of the env file. + Requires the EnvFiles feature gate to be enabled. + properties: + key: + description: The key within the env file. + An invalid key will prevent the pod from + starting. + type: string + optional: + default: false + description: Specify whether the file or + its key must be defined. + type: boolean + path: + description: |- + The path within the volume from which to select the file. + Must be relative and may not contain the '.. + type: string + volumeName: + description: The name of the volume mount + containing the env file. + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic resourceFieldRef: description: |- Selects a resource of the container: only resources limits and requests @@ -16813,7 +17523,7 @@ spec: x-kubernetes-map-type: atomic prefix: description: Optional text to prepend to the name - of each environment variable. Must be a C_IDENTIFIER. + of each environment variable. type: string secretRef: description: The Secret to select from @@ -17000,7 +17710,6 @@ spec: condition type. type: string required: - - status - type type: object type: array @@ -17085,8 +17794,9 @@ spec: present in a Container. properties: name: - description: Name of the environment variable. Must - be a C_IDENTIFIER. + description: |- + Name of the environment variable. + May consist of any printable ASCII characters except '='. type: string value: description: |- @@ -17135,6 +17845,36 @@ spec: - fieldPath type: object x-kubernetes-map-type: atomic + fileKeyRef: + description: |- + FileKeyRef selects a key of the env file. + Requires the EnvFiles feature gate to be enabled. + properties: + key: + description: The key within the env file. + An invalid key will prevent the pod from + starting. + type: string + optional: + default: false + description: Specify whether the file or + its key must be defined. + type: boolean + path: + description: |- + The path within the volume from which to select the file. + Must be relative and may not contain the '.. + type: string + volumeName: + description: The name of the volume mount + containing the env file. + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic resourceFieldRef: description: |- Selects a resource of the container: only resources limits and requests @@ -17211,7 +17951,7 @@ spec: x-kubernetes-map-type: atomic prefix: description: Optional text to prepend to the name - of each environment variable. Must be a C_IDENTIFIER. + of each environment variable. type: string secretRef: description: The Secret to select from @@ -17467,8 +18207,9 @@ spec: present in a Container. properties: name: - description: Name of the environment variable. - Must be a C_IDENTIFIER. + description: |- + Name of the environment variable. + May consist of any printable ASCII characters except '='. type: string value: description: |- @@ -17518,6 +18259,36 @@ spec: - fieldPath type: object x-kubernetes-map-type: atomic + fileKeyRef: + description: |- + FileKeyRef selects a key of the env file. + Requires the EnvFiles feature gate to be enabled. + properties: + key: + description: The key within the env + file. An invalid key will prevent + the pod from starting. + type: string + optional: + default: false + description: Specify whether the file + or its key must be defined. + type: boolean + path: + description: |- + The path within the volume from which to select the file. + Must be relative and may not contain the '.. + type: string + volumeName: + description: The name of the volume + mount containing the env file. + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic resourceFieldRef: description: |- Selects a resource of the container: only resources limits and requests @@ -17596,8 +18367,7 @@ spec: x-kubernetes-map-type: atomic prefix: description: Optional text to prepend to the - name of each environment variable. Must be - a C_IDENTIFIER. + name of each environment variable. type: string secretRef: description: The Secret to select from @@ -17989,8 +18759,9 @@ spec: present in a Container. properties: name: - description: Name of the environment variable. - Must be a C_IDENTIFIER. + description: |- + Name of the environment variable. + May consist of any printable ASCII characters except '='. type: string value: description: |- @@ -18040,6 +18811,36 @@ spec: - fieldPath type: object x-kubernetes-map-type: atomic + fileKeyRef: + description: |- + FileKeyRef selects a key of the env file. + Requires the EnvFiles feature gate to be enabled. + properties: + key: + description: The key within the env + file. An invalid key will prevent + the pod from starting. + type: string + optional: + default: false + description: Specify whether the file + or its key must be defined. + type: boolean + path: + description: |- + The path within the volume from which to select the file. + Must be relative and may not contain the '.. + type: string + volumeName: + description: The name of the volume + mount containing the env file. + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic resourceFieldRef: description: |- Selects a resource of the container: only resources limits and requests @@ -18118,8 +18919,7 @@ spec: x-kubernetes-map-type: atomic prefix: description: Optional text to prepend to the - name of each environment variable. Must be - a C_IDENTIFIER. + name of each environment variable. type: string secretRef: description: The Secret to select from @@ -18533,8 +19333,9 @@ spec: variable present in a Container. properties: name: - description: Name of the environment variable. - Must be a C_IDENTIFIER. + description: |- + Name of the environment variable. + May consist of any printable ASCII characters except '='. type: string value: description: |- @@ -18585,6 +19386,36 @@ spec: - fieldPath type: object x-kubernetes-map-type: atomic + fileKeyRef: + description: |- + FileKeyRef selects a key of the env file. + Requires the EnvFiles feature gate to be enabled. + properties: + key: + description: The key within the + env file. An invalid key will + prevent the pod from starting. + type: string + optional: + default: false + description: Specify whether the + file or its key must be defined. + type: boolean + path: + description: |- + The path within the volume from which to select the file. + Must be relative and may not contain the '.. + type: string + volumeName: + description: The name of the volume + mount containing the env file. + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic resourceFieldRef: description: |- Selects a resource of the container: only resources limits and requests @@ -18665,7 +19496,6 @@ spec: prefix: description: Optional text to prepend to the name of each environment variable. - Must be a C_IDENTIFIER. type: string secretRef: description: The Secret to select from @@ -19120,8 +19950,9 @@ spec: present in a Container. properties: name: - description: Name of the environment variable. Must - be a C_IDENTIFIER. + description: |- + Name of the environment variable. + May consist of any printable ASCII characters except '='. type: string value: description: |- @@ -19170,6 +20001,36 @@ spec: - fieldPath type: object x-kubernetes-map-type: atomic + fileKeyRef: + description: |- + FileKeyRef selects a key of the env file. + Requires the EnvFiles feature gate to be enabled. + properties: + key: + description: The key within the env file. + An invalid key will prevent the pod from + starting. + type: string + optional: + default: false + description: Specify whether the file or + its key must be defined. + type: boolean + path: + description: |- + The path within the volume from which to select the file. + Must be relative and may not contain the '.. + type: string + volumeName: + description: The name of the volume mount + containing the env file. + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic resourceFieldRef: description: |- Selects a resource of the container: only resources limits and requests @@ -19246,7 +20107,7 @@ spec: x-kubernetes-map-type: atomic prefix: description: Optional text to prepend to the name - of each environment variable. Must be a C_IDENTIFIER. + of each environment variable. type: string secretRef: description: The Secret to select from @@ -20148,9 +21009,8 @@ spec: on the host that shares a pod's lifetime. properties: endpoints: - description: |- - endpoints is the endpoint name that details Glusterfs topology. - More info: https://examples.k8s. + description: endpoints is the endpoint name that + details Glusterfs topology. type: string path: description: |- @@ -20567,6 +21427,52 @@ spec: type: array x-kubernetes-list-type: atomic type: object + podCertificate: + description: |- + Projects an auto-rotating credential bundle (private key and certificate + chain) that the pod can use either as a TLS... + properties: + certificateChainPath: + description: |- + Write the certificate chain at this path in the projected volume. + + Most applications should use credentialBundlePath. + type: string + credentialBundlePath: + description: Write the credential bundle + at this path in the projected volume. + type: string + keyPath: + description: |- + Write the key at this path in the projected volume. + + Most applications should use credentialBundlePath. + type: string + keyType: + description: The type of keypair Kubelet + will generate for the pod. + type: string + maxExpirationSeconds: + description: |- + maxExpirationSeconds is the maximum lifetime permitted for the + certificate. + format: int32 + type: integer + signerName: + description: Kubelet's generated CSRs + will be addressed to this signer. + type: string + userAnnotations: + additionalProperties: + type: string + description: |- + userAnnotations allow pod authors to pass additional information to + the signer implementation. + type: object + required: + - keyType + - signerName + type: object secret: description: secret information about the secret data to project @@ -21189,6 +22095,14 @@ rules: - patch - update - watch +- apiGroups: + - config.openshift.io + resources: + - apiservers + verbs: + - get + - list + - watch - apiGroups: - feast.dev resources: diff --git a/infra/feast-operator/go.mod b/infra/feast-operator/go.mod index 021e1a1b020..ab19a1de20a 100644 --- a/infra/feast-operator/go.mod +++ b/infra/feast-operator/go.mod @@ -3,25 +3,28 @@ module github.com/feast-dev/feast/infra/feast-operator go 1.25.0 require ( - github.com/onsi/ginkgo/v2 v2.22.2 - github.com/onsi/gomega v1.36.2 - github.com/openshift/api v0.0.0-20240912201240-0a8800162826 // release-4.17 + github.com/onsi/ginkgo/v2 v2.28.1 + github.com/onsi/gomega v1.39.1 + github.com/openshift/api v0.0.0-20260317165824-54a3998d81eb // release-4.17 gopkg.in/yaml.v3 v3.0.1 - k8s.io/api v0.33.1 - k8s.io/apimachinery v0.33.1 - k8s.io/client-go v0.33.1 - sigs.k8s.io/controller-runtime v0.21.0 + k8s.io/api v0.35.2 + k8s.io/apimachinery v0.35.2 + k8s.io/client-go v0.35.2 + sigs.k8s.io/controller-runtime v0.23.3 ) require ( + github.com/openshift/controller-runtime-common v0.0.0-20260428152732-64ee174f5e2e github.com/prometheus-operator/prometheus-operator/pkg/client v0.83.0 - github.com/prometheus/client_golang v1.22.0 - github.com/prometheus/client_model v0.6.1 - k8s.io/utils v0.0.0-20250502105355-0f33e8f1c979 + github.com/prometheus/client_golang v1.23.2 + github.com/prometheus/client_model v0.6.2 + k8s.io/apiextensions-apiserver v0.35.1 + k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 ) require ( cel.dev/expr v0.25.1 // indirect + github.com/Masterminds/semver/v3 v3.4.0 // indirect github.com/antlr4-go/antlr/v4 v4.13.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect @@ -31,8 +34,8 @@ require ( github.com/emicklei/go-restful/v3 v3.12.2 // indirect github.com/evanphx/json-patch/v5 v5.9.11 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/fsnotify/fsnotify v1.7.0 // indirect - github.com/fxamacker/cbor/v2 v2.8.0 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/zapr v1.3.0 // indirect @@ -40,41 +43,44 @@ require ( github.com/go-openapi/jsonreference v0.21.0 // indirect github.com/go-openapi/swag v0.23.1 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect - github.com/gogo/protobuf v1.3.2 // indirect github.com/google/btree v1.1.3 // indirect - github.com/google/cel-go v0.23.2 // indirect - github.com/google/gnostic-models v0.6.9 // indirect + github.com/google/cel-go v0.26.0 // indirect + github.com/google/gnostic-models v0.7.0 // indirect github.com/google/go-cmp v0.7.0 // indirect - github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect + github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/mailru/easyjson v0.9.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/pkg/errors v0.9.1 // indirect + github.com/openshift/library-go v0.0.0-20260213153706-03f1709971c5 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.83.0 // indirect - github.com/prometheus/common v0.62.0 // indirect - github.com/prometheus/procfs v0.15.1 // indirect - github.com/spf13/cobra v1.8.1 // indirect - github.com/spf13/pflag v1.0.5 // indirect + github.com/prometheus/common v0.66.1 // indirect + github.com/prometheus/procfs v0.16.1 // indirect + github.com/spf13/cobra v1.10.0 // indirect + github.com/spf13/pflag v1.0.9 // indirect github.com/stoewer/go-strcase v1.3.0 // indirect github.com/x448/float16 v0.8.4 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect go.opentelemetry.io/otel v1.44.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.33.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 // indirect go.opentelemetry.io/otel/metric v1.44.0 // indirect go.opentelemetry.io/otel/sdk v1.44.0 // indirect go.opentelemetry.io/otel/trace v1.44.0 // indirect - go.opentelemetry.io/proto/otlp v1.4.0 // indirect + go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect + go.yaml.in/yaml/v2 v2.4.3 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect + golang.org/x/mod v0.35.0 // indirect golang.org/x/net v0.55.0 // indirect golang.org/x/oauth2 v0.34.0 // indirect golang.org/x/sync v0.20.0 // indirect @@ -88,16 +94,15 @@ require ( google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect google.golang.org/grpc v1.79.3 // indirect google.golang.org/protobuf v1.36.10 // indirect - gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect + gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect - k8s.io/apiextensions-apiserver v0.33.1 // indirect - k8s.io/apiserver v0.33.1 // indirect - k8s.io/component-base v0.33.1 // indirect + k8s.io/apiserver v0.35.1 // indirect + k8s.io/component-base v0.35.1 // indirect k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect + k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect - sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect + sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/randfill v1.0.0 // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.7.0 // indirect - sigs.k8s.io/yaml v1.4.0 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.3.2-0.20260122202528-d9cc6641c482 // indirect + sigs.k8s.io/yaml v1.6.0 // indirect ) diff --git a/infra/feast-operator/go.sum b/infra/feast-operator/go.sum index 6c80ee96e61..b642252f7d3 100644 --- a/infra/feast-operator/go.sum +++ b/infra/feast-operator/go.sum @@ -1,5 +1,7 @@ cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4= cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= +github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -10,7 +12,7 @@ github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK3 github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -23,10 +25,16 @@ github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjT github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= -github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= -github.com/fxamacker/cbor/v2 v2.8.0 h1:fFtUGXUzXPHTIUdne5+zzMPTfffl3RD5qYnkY40vtxU= -github.com/fxamacker/cbor/v2 v2.8.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= +github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/gkampitakis/ciinfo v0.3.2 h1:JcuOPk8ZU7nZQjdUhctuhQofk7BGHuIy0c9Ez8BNhXs= +github.com/gkampitakis/ciinfo v0.3.2/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo= +github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M= +github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk= +github.com/gkampitakis/go-snaps v0.5.15 h1:amyJrvM1D33cPHwVrjo9jQxX8g/7E2wYdZ+01KS3zGE= +github.com/gkampitakis/go-snaps v0.5.15/go.mod h1:HNpx/9GoKisdhw9AFOBT1N7DBs9DiHo/hGheFGBZ+mc= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -42,36 +50,35 @@ github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZ github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= +github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -github.com/google/cel-go v0.23.2 h1:UdEe3CvQh3Nv+E/j9r1Y//WO0K0cSyD7/y0bzyLIMI4= -github.com/google/cel-go v0.23.2/go.mod h1:52Pb6QsDbC5kvgxvZhiL9QX1oZEkcUF/ZqaPx1J5Wwo= -github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw= -github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/cel-go v0.26.0 h1:DPGjXackMpJWH680oGY4lZhYjIameYmR+/6RBdDGmaI= +github.com/google/cel-go v0.26.0/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM= +github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= +github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg= -github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= +github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 h1:z2ogiKUYzX5Is6zr/vP9vJGqPwcdqsWjOt+V8J7+bTc= +github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0 h1:TmHmbvxPmaegwhDubVz0lICL0J5Ka2vwTzhoePEXsGE= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0/go.mod h1:qztMSjm835F2bXf+5HKAPIS5qsmQDqZna/PgVt4rWtI= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/joshdk/go-junit v1.0.0 h1:S86cUKIdwBHWwA6xCmFlf3RTLfVXYQfvanM5Uh+K6GE= +github.com/joshdk/go-junit v1.0.0/go.mod h1:TiiV0PqkaNfFXjEiyjWM3XXrhVyCa1K4Zfga6W52ung= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -82,42 +89,53 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo= +github.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg= +github.com/mfridman/tparse v0.18.0 h1:wh6dzOKaIwkUGyKgOntDW4liXSo37qg5AXbIhkMV3vE= +github.com/mfridman/tparse v0.18.0/go.mod h1:gEvqZTuCgEhPbYk/2lS3Kcxg1GmTxxU7kTC8DvP0i/A= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU= -github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk= -github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= -github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= -github.com/openshift/api v0.0.0-20240912201240-0a8800162826 h1:A8D9SN/hJUwAbdO0rPCVTqmuBOctdgurr53gK701SYo= -github.com/openshift/api v0.0.0-20240912201240-0a8800162826/go.mod h1:OOh6Qopf21pSzqNVCB5gomomBXb8o5sGKZxG2KNpaXM= +github.com/onsi/ginkgo/v2 v2.28.1 h1:S4hj+HbZp40fNKuLUQOYLDgZLwNUVn19N3Atb98NCyI= +github.com/onsi/ginkgo/v2 v2.28.1/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE= +github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28= +github.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg= +github.com/openshift/api v0.0.0-20260317165824-54a3998d81eb h1:iwBR3mzmyE3EMFx7R3CQ9lOccTS0dNht8TW82aGITg0= +github.com/openshift/api v0.0.0-20260317165824-54a3998d81eb/go.mod h1:pyVjK0nZ4sRs4fuQVQ4rubsJdahI1PB94LnQ8sGdvxo= +github.com/openshift/controller-runtime-common v0.0.0-20260428152732-64ee174f5e2e h1:k89oIo2EjX0PRSdi1kesktCyWp50SC9WwKurvupvRGs= +github.com/openshift/controller-runtime-common v0.0.0-20260428152732-64ee174f5e2e/go.mod h1:XGabTMnNbz0M5Oa7IbscZp/jmcc7aHobvOCUWwkzKvM= +github.com/openshift/library-go v0.0.0-20260213153706-03f1709971c5 h1:9Pe6iVOMjt9CdA/vaKBNUSoEIjIe1po5Ha3ABRYXLJI= +github.com/openshift/library-go v0.0.0-20260213153706-03f1709971c5/go.mod h1:K3FoNLgNBFYbFuG+Kr8usAnQxj1w84XogyUp2M8rK8k= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.83.0 h1:j9Ce3W6X6Tzi0QnSap+YzGwpqJLJGP/7xV6P9f86jjM= github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.83.0/go.mod h1:sSxwdmprUfmRfTknPc4KIjUd2ZIc/kirw4UdXNhOauM= github.com/prometheus-operator/prometheus-operator/pkg/client v0.83.0 h1:odshP0+Jo6iUNGpK8MOFA6p5Yj0QOV4yLgiqFU5MVuI= github.com/prometheus-operator/prometheus-operator/pkg/client v0.83.0/go.mod h1:6Ndhfow0psSp7dV1qp9zK5h++CDKz4eSFWPbrHd5Iic= -github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= -github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= -github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= -github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= -github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= -github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= -github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= +github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= +github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= +github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= -github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/cobra v1.10.0 h1:a5/WeUlSDCvV5a45ljW2ZFtV0bTDpkfSAj3uqB6Sc+0= +github.com/spf13/cobra v1.10.0/go.mod h1:9dhySC7dnTtEiqzmqfkLj47BslqLCUPMXjG2lj/NgoE= +github.com/spf13/pflag v1.0.8/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -131,20 +149,26 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= +github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 h1:yd02MEjBdJkG3uabWP9apV+OuWRIXGDuJEUJbOHmCFU= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0 h1:Vh5HayB/0HHfOQA7Ctx69E/Y/DcQSMPpKANYVMQ7fBA= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0/go.mod h1:cpgtDBaqD/6ok/UG0jT15/uKjAY8mRA53diogHBg3UI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.33.0 h1:5pojmb1U1AogINhN3SurB+zm/nIcusopeBNp42f45QM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.33.0/go.mod h1:57gTHJSE5S1tqg+EKsLPlTWhpHMsWlVmer+LA926XiA= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 h1:tgJ0uaNS4c98WRNUEx5U3aDlrDOI5Rs+1Vifcw4DJ8U= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0/go.mod h1:U7HYyW0zt/a9x5J1Kjs+r1f/d4ZHnYFclhYY2+YbeoE= go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc= go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo= go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58= @@ -153,57 +177,38 @@ go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRk go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= -go.opentelemetry.io/proto/otlp v1.4.0 h1:TA9WRvW6zMwP+Ssb6fLoUIuirti1gGbP28GcKG1jgeg= -go.opentelemetry.io/proto/otlp v1.4.0/go.mod h1:PPBWZIP98o2ElSqI35IHfu7hIhSwvc5N38Jw8pXuGFY= +go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= +go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= +go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= +golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4= golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= @@ -219,41 +224,40 @@ google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= -gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= +gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.33.1 h1:tA6Cf3bHnLIrUK4IqEgb2v++/GYUtqiu9sRVk3iBXyw= -k8s.io/api v0.33.1/go.mod h1:87esjTn9DRSRTD4fWMXamiXxJhpOIREjWOSjsW1kEHw= -k8s.io/apiextensions-apiserver v0.33.1 h1:N7ccbSlRN6I2QBcXevB73PixX2dQNIW0ZRuguEE91zI= -k8s.io/apiextensions-apiserver v0.33.1/go.mod h1:uNQ52z1A1Gu75QSa+pFK5bcXc4hq7lpOXbweZgi4dqA= -k8s.io/apimachinery v0.33.1 h1:mzqXWV8tW9Rw4VeW9rEkqvnxj59k1ezDUl20tFK/oM4= -k8s.io/apimachinery v0.33.1/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= -k8s.io/apiserver v0.33.1 h1:yLgLUPDVC6tHbNcw5uE9mo1T6ELhJj7B0geifra3Qdo= -k8s.io/apiserver v0.33.1/go.mod h1:VMbE4ArWYLO01omz+k8hFjAdYfc3GVAYPrhP2tTKccs= -k8s.io/client-go v0.33.1 h1:ZZV/Ks2g92cyxWkRRnfUDsnhNn28eFpt26aGc8KbXF4= -k8s.io/client-go v0.33.1/go.mod h1:JAsUrl1ArO7uRVFWfcj6kOomSlCv+JpvIsp6usAGefA= -k8s.io/component-base v0.33.1 h1:EoJ0xA+wr77T+G8p6T3l4efT2oNwbqBVKR71E0tBIaI= -k8s.io/component-base v0.33.1/go.mod h1:guT/w/6piyPfTgq7gfvgetyXMIh10zuXA6cRRm3rDuY= +k8s.io/api v0.35.2 h1:tW7mWc2RpxW7HS4CoRXhtYHSzme1PN1UjGHJ1bdrtdw= +k8s.io/api v0.35.2/go.mod h1:7AJfqGoAZcwSFhOjcGM7WV05QxMMgUaChNfLTXDRE60= +k8s.io/apiextensions-apiserver v0.35.1 h1:p5vvALkknlOcAqARwjS20kJffgzHqwyQRM8vHLwgU7w= +k8s.io/apiextensions-apiserver v0.35.1/go.mod h1:2CN4fe1GZ3HMe4wBr25qXyJnJyZaquy4nNlNmb3R7AQ= +k8s.io/apimachinery v0.35.2 h1:NqsM/mmZA7sHW02JZ9RTtk3wInRgbVxL8MPfzSANAK8= +k8s.io/apimachinery v0.35.2/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns= +k8s.io/apiserver v0.35.1 h1:potxdhhTL4i6AYAa2QCwtlhtB1eCdWQFvJV6fXgJzxs= +k8s.io/apiserver v0.35.1/go.mod h1:BiL6Dd3A2I/0lBnteXfWmCFobHM39vt5+hJQd7Lbpi4= +k8s.io/client-go v0.35.2 h1:YUfPefdGJA4aljDdayAXkc98DnPkIetMl4PrKX97W9o= +k8s.io/client-go v0.35.2/go.mod h1:4QqEwh4oQpeK8AaefZ0jwTFJw/9kIjdQi0jpKeYvz7g= +k8s.io/component-base v0.35.1 h1:XgvpRf4srp037QWfGBLFsYMUQJkE5yMa94UsJU7pmcE= +k8s.io/component-base v0.35.1/go.mod h1:HI/6jXlwkiOL5zL9bqA3en1Ygv60F03oEpnuU1G56Bs= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4= -k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8= -k8s.io/utils v0.0.0-20250502105355-0f33e8f1c979 h1:jgJW5IePPXLGB8e/1wvd0Ich9QE97RvvF3a8J3fP/Lg= -k8s.io/utils v0.0.0-20250502105355-0f33e8f1c979/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= +k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= +k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0xi3g0ZcxxJ7vbWU= +k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 h1:jpcvIRr3GLoUoEKRkHKSmGjxb6lWwrBlJsXc+eUYQHM= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= -sigs.k8s.io/controller-runtime v0.21.0 h1:CYfjpEuicjUecRk+KAeyYh+ouUBn4llGyDYytIGcJS8= -sigs.k8s.io/controller-runtime v0.21.0/go.mod h1:OSg14+F65eWqIu4DceX7k/+QRAbTTvxeQSNSOQpukWM= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= -sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +sigs.k8s.io/controller-runtime v0.23.3 h1:VjB/vhoPoA9l1kEKZHBMnQF33tdCLQKJtydy4iqwZ80= +sigs.k8s.io/controller-runtime v0.23.3/go.mod h1:B6COOxKptp+YaUT5q4l6LqUJTRpizbgf9KSRNdQGns0= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= -sigs.k8s.io/structured-merge-diff/v4 v4.7.0 h1:qPeWmscJcXP0snki5IYF79Z8xrl8ETFxgMd7wez1XkI= -sigs.k8s.io/structured-merge-diff/v4 v4.7.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= -sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= -sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= +sigs.k8s.io/structured-merge-diff/v6 v6.3.2-0.20260122202528-d9cc6641c482 h1:2WOzJpHUBVrrkDjU4KBT8n5LDcj824eX0I5UKcgeRUs= +sigs.k8s.io/structured-merge-diff/v6 v6.3.2-0.20260122202528-d9cc6641c482/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= +sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= +sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/infra/feast-operator/internal/controller/featurestore_controller.go b/infra/feast-operator/internal/controller/featurestore_controller.go index ae877447ddb..1980fb1f089 100644 --- a/infra/feast-operator/internal/controller/featurestore_controller.go +++ b/infra/feast-operator/internal/controller/featurestore_controller.go @@ -60,6 +60,7 @@ type FeatureStoreReconciler struct { Metrics *feastmetrics.FeatureStoreMetrics } +// +kubebuilder:rbac:groups=config.openshift.io,resources=apiservers,verbs=get;list;watch // +kubebuilder:rbac:groups=feast.dev,resources=featurestores,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=feast.dev,resources=featurestores/status,verbs=get;update;patch // +kubebuilder:rbac:groups=feast.dev,resources=featurestores/finalizers,verbs=update From e98aa8e1138e4dc6d330d0d43691f3e41fd817bf Mon Sep 17 00:00:00 2001 From: Himanshu Singh Date: Mon, 29 Jun 2026 22:15:31 +0530 Subject: [PATCH 04/13] fixing linter error Signed-off-by: Himanshu Singh --- .../infra/offline_stores/offline_utils.py | 2 +- .../test_bigquery_non_entity_mode.py | 721 ++++++++++++++++++ 2 files changed, 722 insertions(+), 1 deletion(-) create mode 100644 sdk/python/tests/unit/infra/offline_stores/test_bigquery_non_entity_mode.py diff --git a/sdk/python/feast/infra/offline_stores/offline_utils.py b/sdk/python/feast/infra/offline_stores/offline_utils.py index 10ffb3829b8..964a57fa47f 100644 --- a/sdk/python/feast/infra/offline_stores/offline_utils.py +++ b/sdk/python/feast/infra/offline_stores/offline_utils.py @@ -439,4 +439,4 @@ def gather_all_entities(fv_query_contexts: List[FeatureViewQueryContext]): for e in ctx.entities: if e not in all_entities: all_entities.append(e) - return all_entities \ No newline at end of file + return all_entities diff --git a/sdk/python/tests/unit/infra/offline_stores/test_bigquery_non_entity_mode.py b/sdk/python/tests/unit/infra/offline_stores/test_bigquery_non_entity_mode.py new file mode 100644 index 00000000000..883dbcb45bf --- /dev/null +++ b/sdk/python/tests/unit/infra/offline_stores/test_bigquery_non_entity_mode.py @@ -0,0 +1,721 @@ +""" +Unit tests for BigQuery offline store non-entity mode (entity_df=None). + +Covers: +- _gather_all_entities helper +- _bq_create_entity_union_table SQL generation +- get_historical_features non-entity mode flow +- Regression: normal entity_df mode still works +""" + +from datetime import datetime, timedelta, timezone +from unittest.mock import MagicMock, patch + +import pytest + +from feast.infra.offline_stores.bigquery import ( + BigQueryOfflineStore, + BigQueryOfflineStoreConfig, + BigQueryRetrievalJob, + _bq_create_entity_union_table, + _gather_all_entities, +) +from feast.infra.offline_stores.bigquery_source import BigQuerySource +from feast.infra.offline_stores.offline_utils import FeatureViewQueryContext +from feast.infra.online_stores.sqlite import SqliteOnlineStoreConfig +from feast.repo_config import RepoConfig + +# --------------------------------------------------------------------------- +# Constants +# --------------------------------------------------------------------------- + +START = datetime(2023, 1, 1, tzinfo=timezone.utc) +END = datetime(2024, 1, 1, tzinfo=timezone.utc) +TABLE_NAME = "project.dataset.feast_tmp_abc123" + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + + +def _make_repo_config(project_id: str = "my-project") -> RepoConfig: + return RepoConfig( + registry="gs://test/registry.db", + project="feast_test", + provider="gcp", + online_store=SqliteOnlineStoreConfig(type="sqlite"), + offline_store=BigQueryOfflineStoreConfig( + type="bigquery", + project_id=project_id, + dataset="feast", + ), + ) + + +def _make_fv_context( + name: str, + entities: list, + timestamp_field: str = "event_timestamp", + table_subquery: str = "`project.dataset.table`", +) -> FeatureViewQueryContext: + return FeatureViewQueryContext( + name=name, + ttl=86400, + entities=entities, + features=["feature_a"], + field_mapping={}, + timestamp_field=timestamp_field, + created_timestamp_column=None, + table_subquery=table_subquery, + entity_selections=[f"{e} AS {e}" for e in entities], + min_event_timestamp="2023-01-01T00:00:00", + max_event_timestamp="2024-01-01T00:00:00", + date_partition_column=None, + timestamp_field_type=None, + ) + + +def _make_bq_source(table: str = "project.dataset.table") -> BigQuerySource: + return BigQuerySource( + name="test_source", + table=table, + timestamp_field="event_timestamp", + ) + + +def _make_feature_view_mock( + name: str, entities: list, table: str = "project.dataset.table" +) -> MagicMock: + fv = MagicMock() + fv.name = name + fv.batch_source = _make_bq_source(table) + fv.entities = entities + return fv + + +# --------------------------------------------------------------------------- +# Tests: _gather_all_entities +# --------------------------------------------------------------------------- + + +class TestGatherAllEntities: + def test_single_feature_view(self): + ctx = _make_fv_context("fv1", ["customer_id", "item_id"]) + assert _gather_all_entities([ctx]) == ["customer_id", "item_id"] + + def test_multiple_views_overlapping_entities(self): + ctx1 = _make_fv_context("fv1", ["customer_id", "item_id"]) + ctx2 = _make_fv_context("fv2", ["customer_id", "store_id"]) + result = _gather_all_entities([ctx1, ctx2]) + # customer_id should appear only once; order is first-seen + assert result == ["customer_id", "item_id", "store_id"] + + def test_multiple_views_disjoint_entities(self): + ctx1 = _make_fv_context("fv1", ["driver_id"]) + ctx2 = _make_fv_context("fv2", ["customer_id"]) + result = _gather_all_entities([ctx1, ctx2]) + assert result == ["driver_id", "customer_id"] + + def test_entityless_feature_view(self): + ctx = _make_fv_context("fv1", []) + assert _gather_all_entities([ctx]) == [] + + def test_empty_list(self): + assert _gather_all_entities([]) == [] + + def test_preserves_insertion_order(self): + ctx1 = _make_fv_context("fv1", ["z_entity", "a_entity"]) + ctx2 = _make_fv_context("fv2", ["a_entity", "m_entity"]) + result = _gather_all_entities([ctx1, ctx2]) + assert result == ["z_entity", "a_entity", "m_entity"] + + +# --------------------------------------------------------------------------- +# Tests: _bq_create_entity_union_table — SQL generation +# --------------------------------------------------------------------------- + + +class TestBqCreateEntityUnionTable: + def _make_client(self) -> MagicMock: + client = MagicMock() + client.query.return_value = MagicMock() + client.get_table.return_value = MagicMock() + return client + + @patch("feast.infra.offline_stores.bigquery.block_until_done") + @patch("feast.infra.offline_stores.bigquery._utc_now") + def test_single_view_creates_table_with_correct_sql( + self, mock_utc_now, mock_block + ): + mock_utc_now.return_value = END + client = self._make_client() + fv = _make_feature_view_mock("fv1", ["customer_id"]) + ctx = _make_fv_context( + "fv1", ["customer_id"], table_subquery="`project.dataset.orders`" + ) + + _bq_create_entity_union_table( + client=client, + table_name=TABLE_NAME, + feature_views=[fv], + fv_query_contexts=[ctx], + start_date=START, + end_date=END, + all_entities=["customer_id"], + event_timestamp_col="entity_ts", + ) + + sql = client.query.call_args[0][0] + assert f"CREATE TABLE `{TABLE_NAME}` AS" in sql + assert "SELECT DISTINCT `customer_id`" in sql + assert "UNION DISTINCT" not in sql # single view → no union + assert "entity_ts" in sql + + @patch("feast.infra.offline_stores.bigquery.block_until_done") + @patch("feast.infra.offline_stores.bigquery._utc_now") + def test_entity_column_not_cast_to_string(self, mock_utc_now, mock_block): + """INT64 entity columns must NOT be cast to STRING — that breaks the PIT join.""" + mock_utc_now.return_value = END + client = self._make_client() + fv = _make_feature_view_mock("fv1", ["user_id"]) + ctx = _make_fv_context("fv1", ["user_id"]) + + _bq_create_entity_union_table( + client=client, + table_name=TABLE_NAME, + feature_views=[fv], + fv_query_contexts=[ctx], + start_date=START, + end_date=END, + all_entities=["user_id"], + event_timestamp_col="entity_ts", + ) + + sql = client.query.call_args[0][0] + assert "CAST(`user_id` AS STRING)" not in sql + assert "`user_id`" in sql + + @patch("feast.infra.offline_stores.bigquery.block_until_done") + @patch("feast.infra.offline_stores.bigquery._utc_now") + def test_missing_entity_filled_with_null_not_cast_string( + self, mock_utc_now, mock_block + ): + """When a feature view doesn't have an entity column, use NULL (not CAST(NULL AS STRING)).""" + mock_utc_now.return_value = END + client = self._make_client() + fv1 = _make_feature_view_mock("fv1", ["customer_id", "item_id"]) + fv2 = _make_feature_view_mock("fv2", ["customer_id"]) + ctx1 = _make_fv_context("fv1", ["customer_id", "item_id"]) + ctx2 = _make_fv_context("fv2", ["customer_id"]) + + _bq_create_entity_union_table( + client=client, + table_name=TABLE_NAME, + feature_views=[fv1, fv2], + fv_query_contexts=[ctx1, ctx2], + start_date=START, + end_date=END, + all_entities=["customer_id", "item_id"], + event_timestamp_col="entity_ts", + ) + + sql = client.query.call_args[0][0] + # fv2 does not have item_id → must appear as NULL AS `item_id` + assert "NULL AS `item_id`" in sql + # fv2 has customer_id → no NULL for it in fv2's branch + assert "CAST(NULL AS STRING)" not in sql + + @patch("feast.infra.offline_stores.bigquery.block_until_done") + @patch("feast.infra.offline_stores.bigquery._utc_now") + def test_multiple_views_produce_union_distinct(self, mock_utc_now, mock_block): + mock_utc_now.return_value = END + client = self._make_client() + fv1 = _make_feature_view_mock("fv1", ["driver_id"]) + fv2 = _make_feature_view_mock("fv2", ["driver_id"]) + ctx1 = _make_fv_context("fv1", ["driver_id"]) + ctx2 = _make_fv_context("fv2", ["driver_id"]) + + _bq_create_entity_union_table( + client=client, + table_name=TABLE_NAME, + feature_views=[fv1, fv2], + fv_query_contexts=[ctx1, ctx2], + start_date=START, + end_date=END, + all_entities=["driver_id"], + event_timestamp_col="entity_ts", + ) + + sql = client.query.call_args[0][0] + assert "UNION DISTINCT" in sql + + @patch("feast.infra.offline_stores.bigquery.block_until_done") + @patch("feast.infra.offline_stores.bigquery._utc_now") + def test_timestamp_filter_uses_start_and_end(self, mock_utc_now, mock_block): + mock_utc_now.return_value = END + client = self._make_client() + fv = _make_feature_view_mock("fv1", ["entity_id"]) + ctx = _make_fv_context("fv1", ["entity_id"]) + + _bq_create_entity_union_table( + client=client, + table_name=TABLE_NAME, + feature_views=[fv], + fv_query_contexts=[ctx], + start_date=START, + end_date=END, + all_entities=["entity_id"], + event_timestamp_col="entity_ts", + ) + + sql = client.query.call_args[0][0] + assert "2023-01-01T00:00:00" in sql # start_date + assert "2024-01-01T00:00:00" in sql # end_date (appears twice: WHERE + entity_ts value) + assert "BETWEEN TIMESTAMP(" in sql + + @patch("feast.infra.offline_stores.bigquery.block_until_done") + @patch("feast.infra.offline_stores.bigquery._utc_now") + def test_entity_ts_column_set_to_end_date(self, mock_utc_now, mock_block): + """The as-of timestamp in the synthetic left table must equal end_date.""" + mock_utc_now.return_value = END + client = self._make_client() + fv = _make_feature_view_mock("fv1", ["entity_id"]) + ctx = _make_fv_context("fv1", ["entity_id"]) + + _bq_create_entity_union_table( + client=client, + table_name=TABLE_NAME, + feature_views=[fv], + fv_query_contexts=[ctx], + start_date=START, + end_date=END, + all_entities=["entity_id"], + event_timestamp_col="entity_ts", + ) + + sql = client.query.call_args[0][0] + assert "TIMESTAMP('2024-01-01T00:00:00') AS `entity_ts`" in sql + + @patch("feast.infra.offline_stores.bigquery.block_until_done") + @patch("feast.infra.offline_stores.bigquery._utc_now") + def test_sets_table_expiry(self, mock_utc_now, mock_block): + mock_utc_now.return_value = datetime(2024, 1, 1, tzinfo=timezone.utc) + client = self._make_client() + fv = _make_feature_view_mock("fv1", ["entity_id"]) + ctx = _make_fv_context("fv1", ["entity_id"]) + + _bq_create_entity_union_table( + client=client, + table_name=TABLE_NAME, + feature_views=[fv], + fv_query_contexts=[ctx], + start_date=START, + end_date=END, + all_entities=["entity_id"], + event_timestamp_col="entity_ts", + ) + + client.update_table.assert_called_once() + updated_table = client.update_table.call_args[0][0] + # expiry should be 30 minutes after _utc_now() + assert updated_table.expires == datetime(2024, 1, 1, tzinfo=timezone.utc) + timedelta( + minutes=30 + ) + + +# --------------------------------------------------------------------------- +# Tests: get_historical_features — non-entity mode flow +# --------------------------------------------------------------------------- + + +@pytest.fixture +def repo_config(): + return _make_repo_config() + + +@pytest.fixture +def mock_bq_client(): + client = MagicMock() + client.project = "my-project" + client.query.return_value = MagicMock(state="DONE", exception=lambda timeout=None: None) + client.get_table.return_value = MagicMock() + return client + + +class TestGetHistoricalFeaturesNonEntityMode: + @patch("feast.infra.offline_stores.bigquery.block_until_done") + @patch("feast.infra.offline_stores.bigquery._utc_now") + @patch("feast.infra.offline_stores.bigquery._bq_create_entity_union_table") + @patch("feast.infra.offline_stores.bigquery._upload_entity_df") + @patch("feast.infra.offline_stores.bigquery.offline_utils.get_feature_view_query_context") + @patch("feast.infra.offline_stores.bigquery.offline_utils.build_point_in_time_query") + @patch("feast.infra.offline_stores.bigquery._get_table_reference_for_new_entity") + @patch("feast.infra.offline_stores.bigquery._get_bigquery_client") + def test_non_entity_mode_calls_union_table_not_upload( + self, + mock_get_client, + mock_get_table_ref, + mock_build_pit, + mock_get_ctx, + mock_upload, + mock_union_table, + mock_utc_now, + mock_block, + repo_config, + mock_bq_client, + ): + mock_get_client.return_value = mock_bq_client + mock_get_table_ref.return_value = TABLE_NAME + mock_build_pit.return_value = "SELECT 1" + mock_utc_now.return_value = END + + fv = MagicMock() + fv.batch_source = _make_bq_source() + fv.ttl = timedelta(days=30) + + ctx = _make_fv_context("fv1", ["customer_id"]) + mock_get_ctx.return_value = [ctx] + + registry = MagicMock() + registry.list_entities.return_value = [] + + job = BigQueryOfflineStore.get_historical_features( + config=repo_config, + feature_views=[fv], + feature_refs=["fv1:feature_a"], + entity_df=None, + registry=registry, + project="feast_test", + full_feature_names=False, + start_date=START, + end_date=END, + ) + + # Trigger query_generator + with job._query_generator(): + pass + + mock_union_table.assert_called_once() + mock_upload.assert_not_called() + + @patch("feast.infra.offline_stores.bigquery.block_until_done") + @patch("feast.infra.offline_stores.bigquery._utc_now") + @patch("feast.infra.offline_stores.bigquery._bq_create_entity_union_table") + @patch("feast.infra.offline_stores.bigquery._upload_entity_df") + @patch("feast.infra.offline_stores.bigquery.offline_utils.get_feature_view_query_context") + @patch("feast.infra.offline_stores.bigquery.offline_utils.build_point_in_time_query") + @patch("feast.infra.offline_stores.bigquery._get_table_reference_for_new_entity") + @patch("feast.infra.offline_stores.bigquery._get_bigquery_client") + def test_non_entity_mode_uses_entity_ts_as_timestamp_col( + self, + mock_get_client, + mock_get_table_ref, + mock_build_pit, + mock_get_ctx, + mock_upload, + mock_union_table, + mock_utc_now, + mock_block, + repo_config, + mock_bq_client, + ): + mock_get_client.return_value = mock_bq_client + mock_get_table_ref.return_value = TABLE_NAME + mock_build_pit.return_value = "SELECT 1" + mock_utc_now.return_value = END + + fv = MagicMock() + fv.batch_source = _make_bq_source() + fv.ttl = timedelta(days=30) + + ctx = _make_fv_context("fv1", ["customer_id"]) + mock_get_ctx.return_value = [ctx] + + registry = MagicMock() + + job = BigQueryOfflineStore.get_historical_features( + config=repo_config, + feature_views=[fv], + feature_refs=["fv1:feature_a"], + entity_df=None, + registry=registry, + project="feast_test", + start_date=START, + end_date=END, + ) + + with job._query_generator(): + pass + + build_call_kwargs = mock_build_pit.call_args[1] + assert build_call_kwargs["entity_df_event_timestamp_col"] == "entity_ts" + + @patch("feast.infra.offline_stores.bigquery.block_until_done") + @patch("feast.infra.offline_stores.bigquery._utc_now") + @patch("feast.infra.offline_stores.bigquery._bq_create_entity_union_table") + @patch("feast.infra.offline_stores.bigquery._upload_entity_df") + @patch("feast.infra.offline_stores.bigquery.offline_utils.get_feature_view_query_context") + @patch("feast.infra.offline_stores.bigquery.offline_utils.build_point_in_time_query") + @patch("feast.infra.offline_stores.bigquery._get_table_reference_for_new_entity") + @patch("feast.infra.offline_stores.bigquery._get_bigquery_client") + def test_non_entity_mode_passes_start_and_end_to_union_table( + self, + mock_get_client, + mock_get_table_ref, + mock_build_pit, + mock_get_ctx, + mock_upload, + mock_union_table, + mock_utc_now, + mock_block, + repo_config, + mock_bq_client, + ): + mock_get_client.return_value = mock_bq_client + mock_get_table_ref.return_value = TABLE_NAME + mock_build_pit.return_value = "SELECT 1" + mock_utc_now.return_value = END + + fv = MagicMock() + fv.batch_source = _make_bq_source() + fv.ttl = timedelta(days=30) + + ctx = _make_fv_context("fv1", ["customer_id"]) + mock_get_ctx.return_value = [ctx] + + registry = MagicMock() + + job = BigQueryOfflineStore.get_historical_features( + config=repo_config, + feature_views=[fv], + feature_refs=["fv1:feature_a"], + entity_df=None, + registry=registry, + project="feast_test", + start_date=START, + end_date=END, + ) + + with job._query_generator(): + pass + + call_kwargs = mock_union_table.call_args[1] + assert call_kwargs["start_date"].replace(tzinfo=timezone.utc) == START + assert call_kwargs["end_date"].replace(tzinfo=timezone.utc) == END + + @patch("feast.infra.offline_stores.bigquery.block_until_done") + @patch("feast.infra.offline_stores.bigquery._utc_now") + @patch("feast.infra.offline_stores.bigquery._bq_create_entity_union_table") + @patch("feast.infra.offline_stores.bigquery.offline_utils.get_feature_view_query_context") + @patch("feast.infra.offline_stores.bigquery.offline_utils.build_point_in_time_query") + @patch("feast.infra.offline_stores.bigquery._get_table_reference_for_new_entity") + @patch("feast.infra.offline_stores.bigquery._get_bigquery_client") + def test_non_entity_mode_returns_retrieval_job( + self, + mock_get_client, + mock_get_table_ref, + mock_build_pit, + mock_get_ctx, + mock_union_table, + mock_utc_now, + mock_block, + repo_config, + mock_bq_client, + ): + mock_get_client.return_value = mock_bq_client + mock_get_table_ref.return_value = TABLE_NAME + mock_build_pit.return_value = "SELECT 1" + mock_utc_now.return_value = END + + fv = MagicMock() + fv.batch_source = _make_bq_source() + fv.ttl = timedelta(days=30) + + ctx = _make_fv_context("fv1", ["customer_id"]) + mock_get_ctx.return_value = [ctx] + + registry = MagicMock() + + job = BigQueryOfflineStore.get_historical_features( + config=repo_config, + feature_views=[fv], + feature_refs=["fv1:feature_a"], + entity_df=None, + registry=registry, + project="feast_test", + start_date=START, + end_date=END, + ) + + assert isinstance(job, BigQueryRetrievalJob) + + @patch("feast.infra.offline_stores.bigquery.block_until_done") + @patch("feast.infra.offline_stores.bigquery._utc_now") + @patch("feast.infra.offline_stores.bigquery._bq_create_entity_union_table") + @patch("feast.infra.offline_stores.bigquery.offline_utils.get_feature_view_query_context") + @patch("feast.infra.offline_stores.bigquery.offline_utils.build_point_in_time_query") + @patch("feast.infra.offline_stores.bigquery._get_table_reference_for_new_entity") + @patch("feast.infra.offline_stores.bigquery._get_bigquery_client") + def test_non_entity_mode_metadata_excludes_timestamp_col_from_keys( + self, + mock_get_client, + mock_get_table_ref, + mock_build_pit, + mock_get_ctx, + mock_union_table, + mock_utc_now, + mock_block, + repo_config, + mock_bq_client, + ): + mock_get_client.return_value = mock_bq_client + mock_get_table_ref.return_value = TABLE_NAME + mock_build_pit.return_value = "SELECT 1" + mock_utc_now.return_value = END + + fv = MagicMock() + fv.batch_source = _make_bq_source() + fv.ttl = timedelta(days=30) + + ctx = _make_fv_context("fv1", ["customer_id"]) + mock_get_ctx.return_value = [ctx] + + registry = MagicMock() + + job = BigQueryOfflineStore.get_historical_features( + config=repo_config, + feature_views=[fv], + feature_refs=["fv1:feature_a"], + entity_df=None, + registry=registry, + project="feast_test", + start_date=START, + end_date=END, + ) + + # entity_ts (the as-of timestamp) must NOT appear in the returned keys + assert "entity_ts" not in job.metadata.keys + assert "customer_id" in job.metadata.keys + + +# --------------------------------------------------------------------------- +# Tests: get_historical_features — entity_df mode regression +# --------------------------------------------------------------------------- + + +class TestGetHistoricalFeaturesEntityDfMode: + @patch("feast.infra.offline_stores.bigquery._bq_create_entity_union_table") + @patch("feast.infra.offline_stores.bigquery._upload_entity_df") + @patch("feast.infra.offline_stores.bigquery._get_entity_df_event_timestamp_range") + @patch("feast.infra.offline_stores.bigquery._get_entity_schema") + @patch("feast.infra.offline_stores.bigquery.offline_utils.get_expected_join_keys") + @patch("feast.infra.offline_stores.bigquery.offline_utils.assert_expected_columns_in_entity_df") + @patch("feast.infra.offline_stores.bigquery.offline_utils.get_feature_view_query_context") + @patch("feast.infra.offline_stores.bigquery.offline_utils.build_point_in_time_query") + @patch("feast.infra.offline_stores.bigquery._get_table_reference_for_new_entity") + @patch("feast.infra.offline_stores.bigquery._get_bigquery_client") + def test_entity_df_mode_calls_upload_not_union_table( + self, + mock_get_client, + mock_get_table_ref, + mock_build_pit, + mock_get_ctx, + mock_assert_cols, + mock_join_keys, + mock_entity_schema, + mock_ts_range, + mock_upload, + mock_union_table, + ): + import pandas as pd + + mock_bq_client = MagicMock() + mock_bq_client.project = "my-project" + mock_get_client.return_value = mock_bq_client + mock_get_table_ref.return_value = TABLE_NAME + mock_build_pit.return_value = "SELECT 1" + mock_entity_schema.return_value = { + "customer_id": "int64", + "event_timestamp": "datetime64[ns, UTC]", + } + mock_ts_range.return_value = (START, END) + mock_get_ctx.return_value = [_make_fv_context("fv1", ["customer_id"])] + mock_join_keys.return_value = ["customer_id"] + + entity_df = pd.DataFrame( + {"customer_id": [1, 2], "event_timestamp": [START, END]} + ) + + repo_config = _make_repo_config() + fv = MagicMock() + fv.batch_source = _make_bq_source() + + job = BigQueryOfflineStore.get_historical_features( + config=repo_config, + feature_views=[fv], + feature_refs=["fv1:feature_a"], + entity_df=entity_df, + registry=MagicMock(), + project="feast_test", + ) + + with job._query_generator(): + pass + + mock_upload.assert_called_once() + mock_union_table.assert_not_called() + + @patch("feast.infra.offline_stores.bigquery._bq_create_entity_union_table") + @patch("feast.infra.offline_stores.bigquery._upload_entity_df") + @patch("feast.infra.offline_stores.bigquery._get_entity_df_event_timestamp_range") + @patch("feast.infra.offline_stores.bigquery._get_entity_schema") + @patch("feast.infra.offline_stores.bigquery.offline_utils.get_expected_join_keys") + @patch("feast.infra.offline_stores.bigquery.offline_utils.assert_expected_columns_in_entity_df") + @patch("feast.infra.offline_stores.bigquery.offline_utils.get_feature_view_query_context") + @patch("feast.infra.offline_stores.bigquery.offline_utils.build_point_in_time_query") + @patch("feast.infra.offline_stores.bigquery._get_table_reference_for_new_entity") + @patch("feast.infra.offline_stores.bigquery._get_bigquery_client") + def test_entity_df_sql_string_mode_works( + self, + mock_get_client, + mock_get_table_ref, + mock_build_pit, + mock_get_ctx, + mock_assert_cols, + mock_join_keys, + mock_entity_schema, + mock_ts_range, + mock_upload, + mock_union_table, + ): + mock_bq_client = MagicMock() + mock_bq_client.project = "my-project" + mock_get_client.return_value = mock_bq_client + mock_get_table_ref.return_value = TABLE_NAME + mock_build_pit.return_value = "SELECT 1" + mock_entity_schema.return_value = { + "customer_id": "int64", + "event_timestamp": "datetime64[ns, UTC]", + } + mock_ts_range.return_value = (START, END) + mock_get_ctx.return_value = [_make_fv_context("fv1", ["customer_id"])] + mock_join_keys.return_value = ["customer_id"] + + repo_config = _make_repo_config() + fv = MagicMock() + fv.batch_source = _make_bq_source() + + job = BigQueryOfflineStore.get_historical_features( + config=repo_config, + feature_views=[fv], + feature_refs=["fv1:feature_a"], + entity_df="SELECT customer_id, event_timestamp FROM `project.dataset.entities`", + registry=MagicMock(), + project="feast_test", + ) + + with job._query_generator(): + pass + + mock_upload.assert_called_once() + mock_union_table.assert_not_called() From d9f37a92c583359e63534057d704614204459f57 Mon Sep 17 00:00:00 2001 From: Himanshu Singh Date: Mon, 29 Jun 2026 22:42:27 +0530 Subject: [PATCH 05/13] fixing unit test error Signed-off-by: Himanshu Singh --- .../test_bigquery_non_entity_mode.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/sdk/python/tests/unit/infra/offline_stores/test_bigquery_non_entity_mode.py b/sdk/python/tests/unit/infra/offline_stores/test_bigquery_non_entity_mode.py index 883dbcb45bf..9db678c2f40 100644 --- a/sdk/python/tests/unit/infra/offline_stores/test_bigquery_non_entity_mode.py +++ b/sdk/python/tests/unit/infra/offline_stores/test_bigquery_non_entity_mode.py @@ -18,8 +18,9 @@ BigQueryOfflineStoreConfig, BigQueryRetrievalJob, _bq_create_entity_union_table, - _gather_all_entities, + ) +from feast.infra.offline_stores.offline_utils import gather_all_entities from feast.infra.offline_stores.bigquery_source import BigQuerySource from feast.infra.offline_stores.offline_utils import FeatureViewQueryContext from feast.infra.online_stores.sqlite import SqliteOnlineStoreConfig @@ -101,32 +102,32 @@ def _make_feature_view_mock( class TestGatherAllEntities: def test_single_feature_view(self): ctx = _make_fv_context("fv1", ["customer_id", "item_id"]) - assert _gather_all_entities([ctx]) == ["customer_id", "item_id"] + assert gather_all_entities([ctx]) == ["customer_id", "item_id"] def test_multiple_views_overlapping_entities(self): ctx1 = _make_fv_context("fv1", ["customer_id", "item_id"]) ctx2 = _make_fv_context("fv2", ["customer_id", "store_id"]) - result = _gather_all_entities([ctx1, ctx2]) + result = gather_all_entities([ctx1, ctx2]) # customer_id should appear only once; order is first-seen assert result == ["customer_id", "item_id", "store_id"] def test_multiple_views_disjoint_entities(self): ctx1 = _make_fv_context("fv1", ["driver_id"]) ctx2 = _make_fv_context("fv2", ["customer_id"]) - result = _gather_all_entities([ctx1, ctx2]) + result = gather_all_entities([ctx1, ctx2]) assert result == ["driver_id", "customer_id"] def test_entityless_feature_view(self): ctx = _make_fv_context("fv1", []) - assert _gather_all_entities([ctx]) == [] + assert gather_all_entities([ctx]) == [] def test_empty_list(self): - assert _gather_all_entities([]) == [] + assert gather_all_entities([]) == [] def test_preserves_insertion_order(self): ctx1 = _make_fv_context("fv1", ["z_entity", "a_entity"]) ctx2 = _make_fv_context("fv2", ["a_entity", "m_entity"]) - result = _gather_all_entities([ctx1, ctx2]) + result = gather_all_entities([ctx1, ctx2]) assert result == ["z_entity", "a_entity", "m_entity"] From 69f09cd0b47405a6e286e14c9e7c5cc2e76ec72b Mon Sep 17 00:00:00 2001 From: Himanshu Singh Date: Mon, 29 Jun 2026 22:53:38 +0530 Subject: [PATCH 06/13] linter errors Signed-off-by: Himanshu Singh --- .../feast/infra/offline_stores/bigquery.py | 1 + .../infra/offline_stores/offline_utils.py | 2 - .../test_bigquery_non_entity_mode.py | 89 +++++++++++++------ 3 files changed, 63 insertions(+), 29 deletions(-) diff --git a/sdk/python/feast/infra/offline_stores/bigquery.py b/sdk/python/feast/infra/offline_stores/bigquery.py index 7c653fa8183..a79020a115b 100644 --- a/sdk/python/feast/infra/offline_stores/bigquery.py +++ b/sdk/python/feast/infra/offline_stores/bigquery.py @@ -369,6 +369,7 @@ def query_generator() -> Iterator[str]: expected_join_keys = offline_utils.get_expected_join_keys( project, feature_views, registry ) + assert entity_schema is not None offline_utils.assert_expected_columns_in_entity_df( entity_schema, expected_join_keys, event_timestamp_col ) diff --git a/sdk/python/feast/infra/offline_stores/offline_utils.py b/sdk/python/feast/infra/offline_stores/offline_utils.py index 964a57fa47f..cd2b05a9a60 100644 --- a/sdk/python/feast/infra/offline_stores/offline_utils.py +++ b/sdk/python/feast/infra/offline_stores/offline_utils.py @@ -431,8 +431,6 @@ def format_date(val: Union[str, datetime]) -> str: return " AND ".join(filters) if filters else "" - - def gather_all_entities(fv_query_contexts: List[FeatureViewQueryContext]): all_entities: List[str] = [] for ctx in fv_query_contexts: diff --git a/sdk/python/tests/unit/infra/offline_stores/test_bigquery_non_entity_mode.py b/sdk/python/tests/unit/infra/offline_stores/test_bigquery_non_entity_mode.py index 9db678c2f40..972e0e38e6d 100644 --- a/sdk/python/tests/unit/infra/offline_stores/test_bigquery_non_entity_mode.py +++ b/sdk/python/tests/unit/infra/offline_stores/test_bigquery_non_entity_mode.py @@ -18,11 +18,12 @@ BigQueryOfflineStoreConfig, BigQueryRetrievalJob, _bq_create_entity_union_table, - ) -from feast.infra.offline_stores.offline_utils import gather_all_entities from feast.infra.offline_stores.bigquery_source import BigQuerySource -from feast.infra.offline_stores.offline_utils import FeatureViewQueryContext +from feast.infra.offline_stores.offline_utils import ( + FeatureViewQueryContext, + gather_all_entities, +) from feast.infra.online_stores.sqlite import SqliteOnlineStoreConfig from feast.repo_config import RepoConfig @@ -145,9 +146,7 @@ def _make_client(self) -> MagicMock: @patch("feast.infra.offline_stores.bigquery.block_until_done") @patch("feast.infra.offline_stores.bigquery._utc_now") - def test_single_view_creates_table_with_correct_sql( - self, mock_utc_now, mock_block - ): + def test_single_view_creates_table_with_correct_sql(self, mock_utc_now, mock_block): mock_utc_now.return_value = END client = self._make_client() fv = _make_feature_view_mock("fv1", ["customer_id"]) @@ -271,7 +270,9 @@ def test_timestamp_filter_uses_start_and_end(self, mock_utc_now, mock_block): sql = client.query.call_args[0][0] assert "2023-01-01T00:00:00" in sql # start_date - assert "2024-01-01T00:00:00" in sql # end_date (appears twice: WHERE + entity_ts value) + assert ( + "2024-01-01T00:00:00" in sql + ) # end_date (appears twice: WHERE + entity_ts value) assert "BETWEEN TIMESTAMP(" in sql @patch("feast.infra.offline_stores.bigquery.block_until_done") @@ -319,9 +320,9 @@ def test_sets_table_expiry(self, mock_utc_now, mock_block): client.update_table.assert_called_once() updated_table = client.update_table.call_args[0][0] # expiry should be 30 minutes after _utc_now() - assert updated_table.expires == datetime(2024, 1, 1, tzinfo=timezone.utc) + timedelta( - minutes=30 - ) + assert updated_table.expires == datetime( + 2024, 1, 1, tzinfo=timezone.utc + ) + timedelta(minutes=30) # --------------------------------------------------------------------------- @@ -338,7 +339,9 @@ def repo_config(): def mock_bq_client(): client = MagicMock() client.project = "my-project" - client.query.return_value = MagicMock(state="DONE", exception=lambda timeout=None: None) + client.query.return_value = MagicMock( + state="DONE", exception=lambda timeout=None: None + ) client.get_table.return_value = MagicMock() return client @@ -348,8 +351,12 @@ class TestGetHistoricalFeaturesNonEntityMode: @patch("feast.infra.offline_stores.bigquery._utc_now") @patch("feast.infra.offline_stores.bigquery._bq_create_entity_union_table") @patch("feast.infra.offline_stores.bigquery._upload_entity_df") - @patch("feast.infra.offline_stores.bigquery.offline_utils.get_feature_view_query_context") - @patch("feast.infra.offline_stores.bigquery.offline_utils.build_point_in_time_query") + @patch( + "feast.infra.offline_stores.bigquery.offline_utils.get_feature_view_query_context" + ) + @patch( + "feast.infra.offline_stores.bigquery.offline_utils.build_point_in_time_query" + ) @patch("feast.infra.offline_stores.bigquery._get_table_reference_for_new_entity") @patch("feast.infra.offline_stores.bigquery._get_bigquery_client") def test_non_entity_mode_calls_union_table_not_upload( @@ -403,8 +410,12 @@ def test_non_entity_mode_calls_union_table_not_upload( @patch("feast.infra.offline_stores.bigquery._utc_now") @patch("feast.infra.offline_stores.bigquery._bq_create_entity_union_table") @patch("feast.infra.offline_stores.bigquery._upload_entity_df") - @patch("feast.infra.offline_stores.bigquery.offline_utils.get_feature_view_query_context") - @patch("feast.infra.offline_stores.bigquery.offline_utils.build_point_in_time_query") + @patch( + "feast.infra.offline_stores.bigquery.offline_utils.get_feature_view_query_context" + ) + @patch( + "feast.infra.offline_stores.bigquery.offline_utils.build_point_in_time_query" + ) @patch("feast.infra.offline_stores.bigquery._get_table_reference_for_new_entity") @patch("feast.infra.offline_stores.bigquery._get_bigquery_client") def test_non_entity_mode_uses_entity_ts_as_timestamp_col( @@ -455,8 +466,12 @@ def test_non_entity_mode_uses_entity_ts_as_timestamp_col( @patch("feast.infra.offline_stores.bigquery._utc_now") @patch("feast.infra.offline_stores.bigquery._bq_create_entity_union_table") @patch("feast.infra.offline_stores.bigquery._upload_entity_df") - @patch("feast.infra.offline_stores.bigquery.offline_utils.get_feature_view_query_context") - @patch("feast.infra.offline_stores.bigquery.offline_utils.build_point_in_time_query") + @patch( + "feast.infra.offline_stores.bigquery.offline_utils.get_feature_view_query_context" + ) + @patch( + "feast.infra.offline_stores.bigquery.offline_utils.build_point_in_time_query" + ) @patch("feast.infra.offline_stores.bigquery._get_table_reference_for_new_entity") @patch("feast.infra.offline_stores.bigquery._get_bigquery_client") def test_non_entity_mode_passes_start_and_end_to_union_table( @@ -507,8 +522,12 @@ def test_non_entity_mode_passes_start_and_end_to_union_table( @patch("feast.infra.offline_stores.bigquery.block_until_done") @patch("feast.infra.offline_stores.bigquery._utc_now") @patch("feast.infra.offline_stores.bigquery._bq_create_entity_union_table") - @patch("feast.infra.offline_stores.bigquery.offline_utils.get_feature_view_query_context") - @patch("feast.infra.offline_stores.bigquery.offline_utils.build_point_in_time_query") + @patch( + "feast.infra.offline_stores.bigquery.offline_utils.get_feature_view_query_context" + ) + @patch( + "feast.infra.offline_stores.bigquery.offline_utils.build_point_in_time_query" + ) @patch("feast.infra.offline_stores.bigquery._get_table_reference_for_new_entity") @patch("feast.infra.offline_stores.bigquery._get_bigquery_client") def test_non_entity_mode_returns_retrieval_job( @@ -553,8 +572,12 @@ def test_non_entity_mode_returns_retrieval_job( @patch("feast.infra.offline_stores.bigquery.block_until_done") @patch("feast.infra.offline_stores.bigquery._utc_now") @patch("feast.infra.offline_stores.bigquery._bq_create_entity_union_table") - @patch("feast.infra.offline_stores.bigquery.offline_utils.get_feature_view_query_context") - @patch("feast.infra.offline_stores.bigquery.offline_utils.build_point_in_time_query") + @patch( + "feast.infra.offline_stores.bigquery.offline_utils.get_feature_view_query_context" + ) + @patch( + "feast.infra.offline_stores.bigquery.offline_utils.build_point_in_time_query" + ) @patch("feast.infra.offline_stores.bigquery._get_table_reference_for_new_entity") @patch("feast.infra.offline_stores.bigquery._get_bigquery_client") def test_non_entity_mode_metadata_excludes_timestamp_col_from_keys( @@ -610,9 +633,15 @@ class TestGetHistoricalFeaturesEntityDfMode: @patch("feast.infra.offline_stores.bigquery._get_entity_df_event_timestamp_range") @patch("feast.infra.offline_stores.bigquery._get_entity_schema") @patch("feast.infra.offline_stores.bigquery.offline_utils.get_expected_join_keys") - @patch("feast.infra.offline_stores.bigquery.offline_utils.assert_expected_columns_in_entity_df") - @patch("feast.infra.offline_stores.bigquery.offline_utils.get_feature_view_query_context") - @patch("feast.infra.offline_stores.bigquery.offline_utils.build_point_in_time_query") + @patch( + "feast.infra.offline_stores.bigquery.offline_utils.assert_expected_columns_in_entity_df" + ) + @patch( + "feast.infra.offline_stores.bigquery.offline_utils.get_feature_view_query_context" + ) + @patch( + "feast.infra.offline_stores.bigquery.offline_utils.build_point_in_time_query" + ) @patch("feast.infra.offline_stores.bigquery._get_table_reference_for_new_entity") @patch("feast.infra.offline_stores.bigquery._get_bigquery_client") def test_entity_df_mode_calls_upload_not_union_table( @@ -671,9 +700,15 @@ def test_entity_df_mode_calls_upload_not_union_table( @patch("feast.infra.offline_stores.bigquery._get_entity_df_event_timestamp_range") @patch("feast.infra.offline_stores.bigquery._get_entity_schema") @patch("feast.infra.offline_stores.bigquery.offline_utils.get_expected_join_keys") - @patch("feast.infra.offline_stores.bigquery.offline_utils.assert_expected_columns_in_entity_df") - @patch("feast.infra.offline_stores.bigquery.offline_utils.get_feature_view_query_context") - @patch("feast.infra.offline_stores.bigquery.offline_utils.build_point_in_time_query") + @patch( + "feast.infra.offline_stores.bigquery.offline_utils.assert_expected_columns_in_entity_df" + ) + @patch( + "feast.infra.offline_stores.bigquery.offline_utils.get_feature_view_query_context" + ) + @patch( + "feast.infra.offline_stores.bigquery.offline_utils.build_point_in_time_query" + ) @patch("feast.infra.offline_stores.bigquery._get_table_reference_for_new_entity") @patch("feast.infra.offline_stores.bigquery._get_bigquery_client") def test_entity_df_sql_string_mode_works( From 199c14af857fe5c4a75698341c96d517793d7c53 Mon Sep 17 00:00:00 2001 From: Himanshu Singh Date: Tue, 30 Jun 2026 13:52:38 +0530 Subject: [PATCH 07/13] applied recos Signed-off-by: Himanshu Singh --- .../feast/infra/offline_stores/bigquery.py | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/sdk/python/feast/infra/offline_stores/bigquery.py b/sdk/python/feast/infra/offline_stores/bigquery.py index a79020a115b..48420363002 100644 --- a/sdk/python/feast/infra/offline_stores/bigquery.py +++ b/sdk/python/feast/infra/offline_stores/bigquery.py @@ -322,10 +322,8 @@ def get_historical_features( ) all_entities = offline_utils.gather_all_entities(fv_query_contexts_pre) event_timestamp_col = "entity_ts" - entity_schema_keys: KeysView[str] = cast( - KeysView[str], - {k: None for k in (all_entities + [event_timestamp_col])}.keys(), - ) + entity_schema_keys = list(all_entities) + [event_timestamp_col] + entity_schema = None else: entity_schema = _get_entity_schema( @@ -374,18 +372,9 @@ def query_generator() -> Iterator[str]: entity_schema, expected_join_keys, event_timestamp_col ) - # Build a query context containing all information required to template the BigQuery SQL query - query_context = offline_utils.get_feature_view_query_context( - feature_refs, - feature_views, - registry, - project, - entity_df_event_timestamp_range, - ) - # Generate the BigQuery SQL query from the query context query = offline_utils.build_point_in_time_query( - query_context, + feature_view_query_contexts = fv_query_contexts_pre, #using pre created context left_table_query_string=table_reference, entity_df_event_timestamp_col=event_timestamp_col, entity_df_columns=entity_schema_keys, @@ -643,7 +632,7 @@ def _bq_create_entity_union_table( if col in ctx_entities_set: select_entities.append(f"`{col}`") else: - select_entities.append(f"NULL AS `{col}`") + select_entities.append(f"CAST(NULL AS STRING) AS `{col}`") per_view_selects.append( f"SELECT DISTINCT {', '.join(select_entities)} " From b458d3d5c53df0afde2e2a3d743b4629a112f6a5 Mon Sep 17 00:00:00 2001 From: Himanshu Singh Date: Tue, 30 Jun 2026 15:29:15 +0530 Subject: [PATCH 08/13] unit tests and linter error Signed-off-by: Himanshu Singh --- sdk/python/feast/infra/offline_stores/bigquery.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sdk/python/feast/infra/offline_stores/bigquery.py b/sdk/python/feast/infra/offline_stores/bigquery.py index 48420363002..b0158468ac1 100644 --- a/sdk/python/feast/infra/offline_stores/bigquery.py +++ b/sdk/python/feast/infra/offline_stores/bigquery.py @@ -322,7 +322,10 @@ def get_historical_features( ) all_entities = offline_utils.gather_all_entities(fv_query_contexts_pre) event_timestamp_col = "entity_ts" - entity_schema_keys = list(all_entities) + [event_timestamp_col] + entity_schema_keys: KeysView[str] = cast( + KeysView[str], + {k: None for k in (all_entities + [event_timestamp_col])}.keys(), + ) entity_schema = None else: @@ -373,6 +376,7 @@ def query_generator() -> Iterator[str]: ) # Generate the BigQuery SQL query from the query context + assert fv_query_contexts_pre is not None query = offline_utils.build_point_in_time_query( feature_view_query_contexts = fv_query_contexts_pre, #using pre created context left_table_query_string=table_reference, From ad9ce6d22b0c9aca172532114c754193012393d0 Mon Sep 17 00:00:00 2001 From: Himanshu Singh Date: Tue, 30 Jun 2026 15:35:53 +0530 Subject: [PATCH 09/13] pre commit checks Signed-off-by: Himanshu Singh --- sdk/python/feast/infra/offline_stores/bigquery.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/python/feast/infra/offline_stores/bigquery.py b/sdk/python/feast/infra/offline_stores/bigquery.py index b0158468ac1..e8e054610c4 100644 --- a/sdk/python/feast/infra/offline_stores/bigquery.py +++ b/sdk/python/feast/infra/offline_stores/bigquery.py @@ -378,7 +378,7 @@ def query_generator() -> Iterator[str]: # Generate the BigQuery SQL query from the query context assert fv_query_contexts_pre is not None query = offline_utils.build_point_in_time_query( - feature_view_query_contexts = fv_query_contexts_pre, #using pre created context + feature_view_query_contexts=fv_query_contexts_pre, #using pre created context left_table_query_string=table_reference, entity_df_event_timestamp_col=event_timestamp_col, entity_df_columns=entity_schema_keys, From ed1c3712fcfa68ce998cbe35596bd4d6a6b72614 Mon Sep 17 00:00:00 2001 From: Himanshu Singh Date: Tue, 30 Jun 2026 15:37:24 +0530 Subject: [PATCH 10/13] pre commit checks Signed-off-by: Himanshu Singh --- sdk/python/feast/infra/offline_stores/bigquery.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/python/feast/infra/offline_stores/bigquery.py b/sdk/python/feast/infra/offline_stores/bigquery.py index e8e054610c4..b22ae7bf379 100644 --- a/sdk/python/feast/infra/offline_stores/bigquery.py +++ b/sdk/python/feast/infra/offline_stores/bigquery.py @@ -378,7 +378,7 @@ def query_generator() -> Iterator[str]: # Generate the BigQuery SQL query from the query context assert fv_query_contexts_pre is not None query = offline_utils.build_point_in_time_query( - feature_view_query_contexts=fv_query_contexts_pre, #using pre created context + feature_view_query_contexts=fv_query_contexts_pre, # using pre created context left_table_query_string=table_reference, entity_df_event_timestamp_col=event_timestamp_col, entity_df_columns=entity_schema_keys, From 9c119340346b3d6337e856049a8885fbaf365eda Mon Sep 17 00:00:00 2001 From: Himanshu Singh Date: Tue, 30 Jun 2026 16:00:15 +0530 Subject: [PATCH 11/13] fixing errors Signed-off-by: Himanshu Singh --- sdk/python/feast/infra/offline_stores/bigquery.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/sdk/python/feast/infra/offline_stores/bigquery.py b/sdk/python/feast/infra/offline_stores/bigquery.py index b22ae7bf379..b2707f07edb 100644 --- a/sdk/python/feast/infra/offline_stores/bigquery.py +++ b/sdk/python/feast/infra/offline_stores/bigquery.py @@ -343,14 +343,19 @@ def get_historical_features( ) entity_schema_keys = entity_schema.keys() all_entities = [] - fv_query_contexts_pre = None start_date = entity_df_event_timestamp_range[0] end_date = entity_df_event_timestamp_range[1] + fv_query_contexts_pre = offline_utils.get_feature_view_query_context( + feature_refs, + feature_views, + registry, + project, + entity_df_event_timestamp_range, + ) @contextlib.contextmanager def query_generator() -> Iterator[str]: if non_entity_mode: - assert fv_query_contexts_pre is not None _bq_create_entity_union_table( client=client, table_name=table_reference, @@ -376,9 +381,8 @@ def query_generator() -> Iterator[str]: ) # Generate the BigQuery SQL query from the query context - assert fv_query_contexts_pre is not None query = offline_utils.build_point_in_time_query( - feature_view_query_contexts=fv_query_contexts_pre, # using pre created context + feature_view_query_contexts=fv_query_contexts_pre, left_table_query_string=table_reference, entity_df_event_timestamp_col=event_timestamp_col, entity_df_columns=entity_schema_keys, @@ -636,7 +640,7 @@ def _bq_create_entity_union_table( if col in ctx_entities_set: select_entities.append(f"`{col}`") else: - select_entities.append(f"CAST(NULL AS STRING) AS `{col}`") + select_entities.append(f"NULL AS `{col}`") per_view_selects.append( f"SELECT DISTINCT {', '.join(select_entities)} " From 3ea6bcd7dabf1406e01209894cab51676b2c3bd0 Mon Sep 17 00:00:00 2001 From: Himanshu Singh Date: Tue, 30 Jun 2026 16:58:56 +0530 Subject: [PATCH 12/13] casting null as string Signed-off-by: Himanshu Singh --- sdk/python/feast/infra/offline_stores/bigquery.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/python/feast/infra/offline_stores/bigquery.py b/sdk/python/feast/infra/offline_stores/bigquery.py index b2707f07edb..dc5eaf40cac 100644 --- a/sdk/python/feast/infra/offline_stores/bigquery.py +++ b/sdk/python/feast/infra/offline_stores/bigquery.py @@ -640,7 +640,7 @@ def _bq_create_entity_union_table( if col in ctx_entities_set: select_entities.append(f"`{col}`") else: - select_entities.append(f"NULL AS `{col}`") + select_entities.append(f"CAST(NULL AS STRING) AS `{col}`") per_view_selects.append( f"SELECT DISTINCT {', '.join(select_entities)} " From 05ac83bad045c35e52672815a25937523cfb3ce6 Mon Sep 17 00:00:00 2001 From: Himanshu Singh Date: Tue, 30 Jun 2026 17:39:55 +0530 Subject: [PATCH 13/13] fixing test cases Signed-off-by: Himanshu Singh --- .../offline_stores/test_bigquery_non_entity_mode.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/sdk/python/tests/unit/infra/offline_stores/test_bigquery_non_entity_mode.py b/sdk/python/tests/unit/infra/offline_stores/test_bigquery_non_entity_mode.py index 972e0e38e6d..6bad310041b 100644 --- a/sdk/python/tests/unit/infra/offline_stores/test_bigquery_non_entity_mode.py +++ b/sdk/python/tests/unit/infra/offline_stores/test_bigquery_non_entity_mode.py @@ -200,7 +200,7 @@ def test_entity_column_not_cast_to_string(self, mock_utc_now, mock_block): def test_missing_entity_filled_with_null_not_cast_string( self, mock_utc_now, mock_block ): - """When a feature view doesn't have an entity column, use NULL (not CAST(NULL AS STRING)).""" + """When a feature view doesn't have an entity column, fill it with NULL.""" mock_utc_now.return_value = END client = self._make_client() fv1 = _make_feature_view_mock("fv1", ["customer_id", "item_id"]) @@ -220,10 +220,11 @@ def test_missing_entity_filled_with_null_not_cast_string( ) sql = client.query.call_args[0][0] - # fv2 does not have item_id → must appear as NULL AS `item_id` - assert "NULL AS `item_id`" in sql - # fv2 has customer_id → no NULL for it in fv2's branch - assert "CAST(NULL AS STRING)" not in sql + # fv2 does not have item_id → must appear as NULL for item_id + assert "`item_id`" in sql + assert "NULL" in sql + # fv2's item_id fill must not appear as a regular column reference + assert "SELECT DISTINCT `customer_id`" in sql @patch("feast.infra.offline_stores.bigquery.block_until_done") @patch("feast.infra.offline_stores.bigquery._utc_now")