From 24f16e2f2f10d2103855a1238dff999b6352a721 Mon Sep 17 00:00:00 2001 From: Andrii Stelmashenko Date: Tue, 20 Jan 2026 12:07:23 +0200 Subject: [PATCH 01/10] added broker filter --- cmd/filter/main.go | 39 ++ pkg/broker/filter/consumer.go | 346 ++++++++++++++ pkg/broker/filter/consumer_test.go | 117 +++++ pkg/broker/filter/controller.go | 203 ++++++++ pkg/broker/filter/handler.go | 351 ++++++++++++++ pkg/broker/filter/handler_test.go | 437 ++++++++++++++++++ pkg/broker/filter/reconciler.go | 160 +++++++ pkg/common/nats/conn.go | 38 +- .../informers/eventing/v1/trigger/trigger.go | 52 +++ vendor/modules.txt | 1 + 10 files changed, 1743 insertions(+), 1 deletion(-) create mode 100644 cmd/filter/main.go create mode 100644 pkg/broker/filter/consumer.go create mode 100644 pkg/broker/filter/consumer_test.go create mode 100644 pkg/broker/filter/controller.go create mode 100644 pkg/broker/filter/handler.go create mode 100644 pkg/broker/filter/handler_test.go create mode 100644 pkg/broker/filter/reconciler.go create mode 100644 vendor/knative.dev/eventing/pkg/client/injection/informers/eventing/v1/trigger/trigger.go diff --git a/cmd/filter/main.go b/cmd/filter/main.go new file mode 100644 index 000000000..c689d640d --- /dev/null +++ b/cmd/filter/main.go @@ -0,0 +1,39 @@ +/* +Copyright 2024 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "os" + + "knative.dev/pkg/injection" + "knative.dev/pkg/injection/sharedmain" + "knative.dev/pkg/signals" + + "knative.dev/eventing-natss/pkg/broker/filter" +) + +func main() { + component := "natsjs-broker-filter" + + ctx := signals.NewContext() + ns := os.Getenv("NAMESPACE") + if ns != "" { + ctx = injection.WithNamespaceScope(ctx, ns) + } + + sharedmain.MainWithContext(ctx, component, filter.NewController) +} diff --git a/pkg/broker/filter/consumer.go b/pkg/broker/filter/consumer.go new file mode 100644 index 000000000..711b7b2f7 --- /dev/null +++ b/pkg/broker/filter/consumer.go @@ -0,0 +1,346 @@ +/* +Copyright 2024 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package filter + +import ( + "context" + "errors" + "fmt" + "sync" + "time" + + "github.com/nats-io/nats.go" + "go.uber.org/zap" + + duckv1 "knative.dev/pkg/apis/duck/v1" + "knative.dev/pkg/logging" + + eventingv1 "knative.dev/eventing/pkg/apis/eventing/v1" + "knative.dev/eventing/pkg/auth" + "knative.dev/eventing/pkg/eventingtls" + "knative.dev/eventing/pkg/kncloudevents" + + brokerutils "knative.dev/eventing-natss/pkg/broker/utils" +) + +const ( + // DefaultFetchBatchSize is the default number of messages to fetch in each batch + DefaultFetchBatchSize = 10 + // DefaultFetchTimeout is the default timeout for fetching messages + DefaultFetchTimeout = 200 * time.Millisecond +) + +// ConsumerManagerConfig holds configuration for the ConsumerManager +type ConsumerManagerConfig struct { + // FetchBatchSize is the number of messages to fetch in each batch. + // Defaults to DefaultFetchBatchSize if not set. + FetchBatchSize int + + // FetchTimeout is the timeout for fetching messages. + // Defaults to DefaultFetchTimeout if not set. + FetchTimeout time.Duration +} + +// ConsumerManager manages JetStream consumer subscriptions for triggers +type ConsumerManager struct { + logger *zap.SugaredLogger + ctx context.Context + + js nats.JetStreamContext + conn *nats.Conn + + // Configuration + fetchBatchSize int + fetchTimeout time.Duration + + // Event dispatcher + dispatcher *kncloudevents.Dispatcher + + // Map of trigger UID to subscription + subscriptions map[string]*TriggerSubscription + mu sync.RWMutex +} + +// TriggerSubscription holds the subscription and handler for a trigger +type TriggerSubscription struct { + trigger *eventingv1.Trigger + subscription *nats.Subscription + handler *TriggerHandler + streamName string + consumerName string + cancel context.CancelFunc +} + +// NewConsumerManager creates a new consumer manager +func NewConsumerManager(ctx context.Context, conn *nats.Conn, js nats.JetStreamContext, config *ConsumerManagerConfig) *ConsumerManager { + // Create OIDC token provider and dispatcher + oidcTokenProvider := auth.NewOIDCTokenProvider(ctx) + dispatcher := kncloudevents.NewDispatcher(eventingtls.ClientConfig{}, oidcTokenProvider) + + // Apply defaults + fetchBatchSize := DefaultFetchBatchSize + fetchTimeout := DefaultFetchTimeout + + if config != nil { + if config.FetchBatchSize > 0 { + fetchBatchSize = config.FetchBatchSize + } + if config.FetchTimeout > 0 { + fetchTimeout = config.FetchTimeout + } + } + + return &ConsumerManager{ + logger: logging.FromContext(ctx), + ctx: ctx, + js: js, + conn: conn, + fetchBatchSize: fetchBatchSize, + fetchTimeout: fetchTimeout, + dispatcher: dispatcher, + subscriptions: make(map[string]*TriggerSubscription), + } +} + +// SubscribeTrigger creates a pull-based subscription for a trigger's consumer +func (m *ConsumerManager) SubscribeTrigger( + trigger *eventingv1.Trigger, + broker *eventingv1.Broker, + subscriber duckv1.Addressable, + brokerIngressURL *duckv1.Addressable, + deadLetterSink *duckv1.Addressable, + retryConfig *kncloudevents.RetryConfig, + noRetryConfig *kncloudevents.RetryConfig, +) error { + m.mu.Lock() + defer m.mu.Unlock() + + triggerUID := string(trigger.UID) + logger := m.logger.With( + zap.String("trigger", trigger.Name), + zap.String("namespace", trigger.Namespace), + zap.String("trigger_uid", triggerUID), + ) + + // Check if we already have a subscription for this trigger + if existing, ok := m.subscriptions[triggerUID]; ok { + existing.handler.noRetryConfig = noRetryConfig + existing.handler.retryConfig = retryConfig + existing.handler.filter = buildTriggerFilter(logger, trigger) + existing.handler.deadLetterSink = deadLetterSink + + // Check if configuration has changed + if existing.handler.subscriber.URL.String() == subscriber.URL.String() { + logger.Debugw("trigger subscription already exists and is up to date") + return nil + } + // Configuration changed, unsubscribe and re-subscribe + logger.Infow("trigger configuration changed, re-subscribing") + if err := m.unsubscribeLocked(triggerUID); err != nil { + logger.Warnw("failed to unsubscribe old trigger subscription", zap.Error(err)) + } + } + + // Create the trigger handler + handler, err := NewTriggerHandler( + m.ctx, + trigger, + subscriber, + brokerIngressURL, + deadLetterSink, + retryConfig, + noRetryConfig, + m.dispatcher, + ) + if err != nil { + return fmt.Errorf("failed to create trigger handler: %w", err) + } + + // Derive stream and consumer names + streamName := brokerutils.BrokerStreamName(broker) + consumerName := brokerutils.TriggerConsumerName(triggerUID) + + // Get consumer info (also verifies consumer exists) + consumerInfo, err := m.js.ConsumerInfo(streamName, consumerName) + if err != nil { + handler.Cleanup() + if errors.Is(err, nats.ErrConsumerNotFound) { + return fmt.Errorf("consumer %s not found in stream %s: trigger controller may not have reconciled yet", consumerName, streamName) + } + return fmt.Errorf("failed to get consumer info: %w", err) + } + + // Get the filter subject from the consumer's configuration + filterSubject := brokerutils.BrokerPublishSubjectName(broker.Namespace, broker.Name) + ".>" + + logger.Infow("creating pull subscription for trigger consumer", + zap.String("stream", streamName), + zap.String("consumer", consumerName), + zap.String("filter_subject", filterSubject), + ) + + // Create pull subscription bound to the existing consumer + sub, err := m.js.PullSubscribe( + filterSubject, + consumerName, + nats.Bind(streamName, consumerName), + ) + if err != nil { + handler.Cleanup() + return fmt.Errorf("failed to create pull subscription: %w", err) + } + + // Set subscription and consumer info on handler + handler.subscription = sub + handler.consumer = consumerInfo + + // Create cancellable context for the fetch loop + ctx, cancel := context.WithCancel(m.ctx) + + // Store the subscription + m.subscriptions[triggerUID] = &TriggerSubscription{ + trigger: trigger, + subscription: sub, + handler: handler, + streamName: streamName, + consumerName: consumerName, + cancel: cancel, + } + + // Start the message fetch loop + go m.fetchLoop(ctx, sub, handler, logger) + + logger.Infow("successfully started pull subscription for trigger consumer") + return nil +} + +// fetchLoop continuously fetches messages from the pull consumer +func (m *ConsumerManager) fetchLoop( + ctx context.Context, + sub *nats.Subscription, + handler *TriggerHandler, + logger *zap.SugaredLogger, +) { + for { + select { + case <-ctx.Done(): + logger.Debugw("fetch loop stopped") + return + default: + // Fetch a batch of messages + msgs, err := sub.Fetch(m.fetchBatchSize, nats.MaxWait(m.fetchTimeout)) + if err != nil { + if errors.Is(err, nats.ErrTimeout) { + // No messages available, continue polling + continue + } + if errors.Is(err, nats.ErrConnectionClosed) || errors.Is(err, nats.ErrConsumerDeleted) || errors.Is(err, nats.ErrBadSubscription) { + logger.Warnw("subscription closed, stopping fetch loop", zap.Error(err)) + return + } + if errors.Is(err, context.Canceled) { + return + } + logger.Errorw("error fetching messages", zap.Error(err)) + // Back off on errors + time.Sleep(time.Second) + continue + } + + // Process fetched messages + for _, msg := range msgs { + handler.HandleMessage(msg) + } + } + } +} + +// UnsubscribeTrigger removes a subscription for a trigger +func (m *ConsumerManager) UnsubscribeTrigger(triggerUID string) error { + m.mu.Lock() + defer m.mu.Unlock() + + return m.unsubscribeLocked(triggerUID) +} + +// unsubscribeLocked removes a subscription (must be called with lock held) +func (m *ConsumerManager) unsubscribeLocked(triggerUID string) error { + sub, ok := m.subscriptions[triggerUID] + if !ok { + return nil + } + + logger := m.logger.With( + zap.String("trigger", sub.trigger.Name), + zap.String("namespace", sub.trigger.Namespace), + ) + + logger.Infow("unsubscribing from trigger consumer") + + // Cancel the fetch loop first + if sub.cancel != nil { + sub.cancel() + } + + // Unsubscribe from the pull consumer + if err := sub.subscription.Unsubscribe(); err != nil { + logger.Warnw("failed to unsubscribe", zap.Error(err)) + } + + // Cleanup the handler + sub.handler.Cleanup() + + // Remove from map + delete(m.subscriptions, triggerUID) + + return nil +} + +// Close closes all subscriptions +func (m *ConsumerManager) Close() error { + m.mu.Lock() + defer m.mu.Unlock() + + m.logger.Infow("closing consumer manager", zap.Int("subscription_count", len(m.subscriptions))) + + var errs []error + for uid := range m.subscriptions { + if err := m.unsubscribeLocked(uid); err != nil { + errs = append(errs, err) + } + } + + if len(errs) > 0 { + return fmt.Errorf("errors closing subscriptions: %v", errs) + } + return nil +} + +// GetSubscriptionCount returns the number of active subscriptions +func (m *ConsumerManager) GetSubscriptionCount() int { + m.mu.RLock() + defer m.mu.RUnlock() + return len(m.subscriptions) +} + +// HasSubscription checks if a subscription exists for a trigger +func (m *ConsumerManager) HasSubscription(triggerUID string) bool { + m.mu.RLock() + defer m.mu.RUnlock() + _, ok := m.subscriptions[triggerUID] + return ok +} diff --git a/pkg/broker/filter/consumer_test.go b/pkg/broker/filter/consumer_test.go new file mode 100644 index 000000000..866a8c5f0 --- /dev/null +++ b/pkg/broker/filter/consumer_test.go @@ -0,0 +1,117 @@ +/* +Copyright 2024 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package filter + +import ( + "testing" + "time" +) + +func TestConsumerManagerConfigDefaults(t *testing.T) { + // Verify default values + if DefaultFetchBatchSize != 10 { + t.Errorf("DefaultFetchBatchSize = %v, want 10", DefaultFetchBatchSize) + } + + if DefaultFetchTimeout != 200*time.Millisecond { + t.Errorf("DefaultFetchTimeout = %v, want 500ms", DefaultFetchTimeout) + } +} + +func TestConsumerManagerConfig(t *testing.T) { + tests := []struct { + name string + config *ConsumerManagerConfig + wantFetchBatchSize int + wantFetchTimeout time.Duration + }{ + { + name: "nil config uses defaults", + config: nil, + wantFetchBatchSize: DefaultFetchBatchSize, + wantFetchTimeout: DefaultFetchTimeout, + }, + { + name: "empty config uses defaults", + config: &ConsumerManagerConfig{}, + wantFetchBatchSize: DefaultFetchBatchSize, + wantFetchTimeout: DefaultFetchTimeout, + }, + { + name: "zero values use defaults", + config: &ConsumerManagerConfig{ + FetchBatchSize: 0, + FetchTimeout: 0, + }, + wantFetchBatchSize: DefaultFetchBatchSize, + wantFetchTimeout: DefaultFetchTimeout, + }, + { + name: "custom batch size only", + config: &ConsumerManagerConfig{ + FetchBatchSize: 20, + FetchTimeout: 0, + }, + wantFetchBatchSize: 20, + wantFetchTimeout: DefaultFetchTimeout, + }, + { + name: "custom timeout only", + config: &ConsumerManagerConfig{ + FetchBatchSize: 0, + FetchTimeout: 1 * time.Second, + }, + wantFetchBatchSize: DefaultFetchBatchSize, + wantFetchTimeout: 1 * time.Second, + }, + { + name: "both custom values", + config: &ConsumerManagerConfig{ + FetchBatchSize: 50, + FetchTimeout: 2 * time.Second, + }, + wantFetchBatchSize: 50, + wantFetchTimeout: 2 * time.Second, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // We can't easily test NewConsumerManager without a real NATS connection, + // so we test the config application logic directly + fetchBatchSize := DefaultFetchBatchSize + fetchTimeout := DefaultFetchTimeout + + if tt.config != nil { + if tt.config.FetchBatchSize > 0 { + fetchBatchSize = tt.config.FetchBatchSize + } + if tt.config.FetchTimeout > 0 { + fetchTimeout = tt.config.FetchTimeout + } + } + + if fetchBatchSize != tt.wantFetchBatchSize { + t.Errorf("fetchBatchSize = %v, want %v", fetchBatchSize, tt.wantFetchBatchSize) + } + + if fetchTimeout != tt.wantFetchTimeout { + t.Errorf("fetchTimeout = %v, want %v", fetchTimeout, tt.wantFetchTimeout) + } + }) + } +} diff --git a/pkg/broker/filter/controller.go b/pkg/broker/filter/controller.go new file mode 100644 index 000000000..67cd57f34 --- /dev/null +++ b/pkg/broker/filter/controller.go @@ -0,0 +1,203 @@ +/* +Copyright 2024 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package filter + +import ( + "context" + "net/http" + "time" + + "github.com/kelseyhightower/envconfig" + "github.com/nats-io/nats.go" + "go.uber.org/zap" + "k8s.io/client-go/tools/cache" + + "knative.dev/pkg/configmap" + "knative.dev/pkg/controller" + "knative.dev/pkg/logging" + "knative.dev/pkg/reconciler" + + eventingv1 "knative.dev/eventing/pkg/apis/eventing/v1" + brokerinformer "knative.dev/eventing/pkg/client/injection/informers/eventing/v1/broker" + triggerinformer "knative.dev/eventing/pkg/client/injection/informers/eventing/v1/trigger" + eventinglisters "knative.dev/eventing/pkg/client/listers/eventing/v1" + + "knative.dev/eventing-natss/pkg/broker/constants" + commonnats "knative.dev/eventing-natss/pkg/common/nats" +) + +type envConfig struct { + PodName string `envconfig:"POD_NAME" required:"true"` + ContainerName string `envconfig:"CONTAINER_NAME" required:"true"` + NatsURL string `envconfig:"NATS_URL" required:"true"` + FetchBatchSize int `envconfig:"CONSUMER_FETCH_BATCH_SIZE" default:"0"` + FetchTimeout time.Duration `envconfig:"CONSUMER_FETCH_TIMEOUT" default:"0"` +} + +// NewController creates a new filter controller +func NewController(ctx context.Context, _ configmap.Watcher) *controller.Impl { + logger := logging.FromContext(ctx) + + env := &envConfig{} + if err := envconfig.Process("", env); err != nil { + logger.Fatalw("Failed to process environment variables", zap.Error(err)) + } + + // Create NATS connection using URL from environment variable + natsConn, err := commonnats.NewNatsConnFromURL(ctx, env.NatsURL) + if err != nil { + logger.Fatalw("Failed to create NATS connection", zap.Error(err)) + } + + // Create JetStream context + js, err := natsConn.JetStream() + if err != nil { + logger.Fatalw("Failed to create JetStream context", zap.Error(err)) + } + + // Get informers + triggerInformer := triggerinformer.Get(ctx) + brokerInformer := brokerinformer.Get(ctx) + + // Create consumer manager with optional configuration from environment + consumerConfig := &ConsumerManagerConfig{ + FetchBatchSize: env.FetchBatchSize, + FetchTimeout: env.FetchTimeout, + } + consumerManager := NewConsumerManager(ctx, natsConn, js, consumerConfig) + + // Create filter reconciler + reconciler := NewFilterReconciler( + ctx, + triggerInformer.Lister(), + brokerInformer.Lister(), + consumerManager, + ) + + // Create a simple controller impl - we don't use the standard reconciler pattern + // because we handle events directly via informer handlers + impl := controller.NewContext(ctx, &noopReconciler{}, controller.ControllerOptions{ + WorkQueueName: "NatsJetStreamBrokerFilter", + Logger: logger, + }) + + // Set up event handlers for Trigger resources + triggerInformer.Informer().AddEventHandler(cache.FilteringResourceEventHandler{ + FilterFunc: filterTriggersByBrokerClass(brokerInformer.Lister()), + Handler: cache.ResourceEventHandlerFuncs{ + AddFunc: func(obj interface{}) { + trigger := obj.(*eventingv1.Trigger) + if err := reconciler.ReconcileTrigger(ctx, trigger); err != nil { + logger.Errorw("Failed to reconcile trigger on add", zap.Error(err), + zap.String("trigger", trigger.Name), + zap.String("namespace", trigger.Namespace)) + } + }, + UpdateFunc: func(_, newObj interface{}) { + trigger := newObj.(*eventingv1.Trigger) + if err := reconciler.ReconcileTrigger(ctx, trigger); err != nil { + logger.Errorw("Failed to reconcile trigger on update", zap.Error(err), + zap.String("trigger", trigger.Name), + zap.String("namespace", trigger.Namespace)) + } + }, + DeleteFunc: func(obj interface{}) { + trigger := obj.(*eventingv1.Trigger) + if err := reconciler.DeleteTrigger(string(trigger.UID)); err != nil { + logger.Errorw("Failed to delete trigger subscription", zap.Error(err), + zap.String("trigger", trigger.Name), + zap.String("namespace", trigger.Namespace)) + } + }, + }, + }) + + // Start health server in background + go startHealthServer(ctx, logger, natsConn) + + logger.Info("Filter controller initialized") + return impl +} + +// noopReconciler is a no-op reconciler since we handle events directly +type noopReconciler struct { + reconciler.LeaderAwareFuncs +} + +func (r *noopReconciler) Reconcile(ctx context.Context, key string) error { + return nil +} + +// filterTriggersByBrokerClass returns a filter function that only passes triggers +// referencing brokers of class NatsJetStreamBroker +func filterTriggersByBrokerClass(brokerLister eventinglisters.BrokerLister) func(obj interface{}) bool { + return func(obj interface{}) bool { + trigger, ok := obj.(*eventingv1.Trigger) + if !ok { + return false + } + + // Get the broker referenced by this trigger + broker, err := brokerLister.Brokers(trigger.Namespace).Get(trigger.Spec.Broker) + if err != nil { + // If we can't get the broker, include the trigger anyway + // and let the reconciler handle the error + return true + } + + // Check if the broker is of class NatsJetStreamBroker + return broker.GetAnnotations()[eventingv1.BrokerClassAnnotationKey] == constants.BrokerClassName + } +} + +func startHealthServer(ctx context.Context, logger *zap.SugaredLogger, natsConn *nats.Conn) { + mux := http.NewServeMux() + mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) { + if natsConn.IsConnected() { + w.WriteHeader(http.StatusOK) + w.Write([]byte("ok")) + } else { + w.WriteHeader(http.StatusServiceUnavailable) + w.Write([]byte("nats disconnected")) + } + }) + mux.HandleFunc("/readyz", func(w http.ResponseWriter, r *http.Request) { + if natsConn.IsConnected() { + w.WriteHeader(http.StatusOK) + w.Write([]byte("ok")) + } else { + w.WriteHeader(http.StatusServiceUnavailable) + w.Write([]byte("nats disconnected")) + } + }) + + server := &http.Server{ + Addr: ":8080", + Handler: mux, + } + + logger.Info("Starting health server on :8080") + go func() { + if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { + logger.Errorw("Health server error", zap.Error(err)) + } + }() + + <-ctx.Done() + logger.Info("Shutting down health server") + server.Shutdown(context.Background()) +} diff --git a/pkg/broker/filter/handler.go b/pkg/broker/filter/handler.go new file mode 100644 index 000000000..a4ae4aff1 --- /dev/null +++ b/pkg/broker/filter/handler.go @@ -0,0 +1,351 @@ +/* +Copyright 2024 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package filter + +import ( + "context" + "net/http" + + cejs "github.com/cloudevents/sdk-go/protocol/nats_jetstream/v2" + cloudevents "github.com/cloudevents/sdk-go/v2" + "github.com/cloudevents/sdk-go/v2/binding" + "github.com/cloudevents/sdk-go/v2/binding/spec" + "github.com/cloudevents/sdk-go/v2/protocol" + "github.com/cloudevents/sdk-go/v2/types" + "github.com/nats-io/nats.go" + "go.uber.org/zap" + + duckv1 "knative.dev/pkg/apis/duck/v1" + "knative.dev/pkg/logging" + + jsutils "knative.dev/eventing-natss/pkg/channel/jetstream/utils" + "knative.dev/eventing-natss/pkg/tracing" + eventingduckv1 "knative.dev/eventing/pkg/apis/duck/v1" + eventingv1 "knative.dev/eventing/pkg/apis/eventing/v1" + "knative.dev/eventing/pkg/eventfilter" + "knative.dev/eventing/pkg/eventfilter/attributes" + "knative.dev/eventing/pkg/eventfilter/subscriptionsapi" + "knative.dev/eventing/pkg/kncloudevents" +) + +var retryMax int32 = 3 +var retryTimeout = "PT1S" +var retryBackoffDelay = "PT0.5S" + +// TypeExtractorTransformer extracts the CloudEvent type from a message. +// Copied from channel/jetstream/dispatcher to avoid importing that package +// which registers channel informers. +type TypeExtractorTransformer string + +func (a *TypeExtractorTransformer) Transform(reader binding.MessageMetadataReader, _ binding.MessageMetadataWriter) error { + _, ty := reader.GetAttribute(spec.Type) + if ty != nil { + tyParsed, err := types.ToString(ty) + if err != nil { + return err + } + *a = TypeExtractorTransformer(tyParsed) + } + return nil +} + +var retryDelivery = eventingduckv1.BackoffPolicyLinear +var defaultRetry, _ = kncloudevents.RetryConfigFromDeliverySpec(eventingduckv1.DeliverySpec{ + Retry: &retryMax, + Timeout: &retryTimeout, + BackoffPolicy: &retryDelivery, + BackoffDelay: &retryBackoffDelay, +}) + +// TriggerHandler handles message dispatch for a single trigger +type TriggerHandler struct { + logger *zap.SugaredLogger + ctx context.Context + + // Trigger configuration + trigger *eventingv1.Trigger + subscriber duckv1.Addressable + filter eventfilter.Filter + + // Broker ingress URL for reply events + brokerIngressURL *duckv1.Addressable + + // Dispatcher for sending events + dispatcher *kncloudevents.Dispatcher + + // Retry configuration + retryConfig *kncloudevents.RetryConfig + noRetryConfig *kncloudevents.RetryConfig + + // Dead letter sink + deadLetterSink *duckv1.Addressable + + subscription *nats.Subscription + consumer *nats.ConsumerInfo +} + +// NewTriggerHandler creates a new handler for a trigger +func NewTriggerHandler( + ctx context.Context, + trigger *eventingv1.Trigger, + subscriber duckv1.Addressable, + brokerIngressURL *duckv1.Addressable, + deadLetterSink *duckv1.Addressable, + retryConfig *kncloudevents.RetryConfig, + noRetryConfig *kncloudevents.RetryConfig, + dispatcher *kncloudevents.Dispatcher, +) (*TriggerHandler, error) { + logger := logging.FromContext(ctx).With( + zap.String("trigger", trigger.Name), + zap.String("namespace", trigger.Namespace), + ) + + return &TriggerHandler{ + logger: logger, + ctx: ctx, + trigger: trigger, + subscriber: subscriber, + filter: buildTriggerFilter(logger, trigger), + brokerIngressURL: brokerIngressURL, + dispatcher: dispatcher, + retryConfig: retryConfig, + noRetryConfig: noRetryConfig, + deadLetterSink: deadLetterSink, + }, nil +} + +// HandleMessage processes a NATS message, applies filter, and dispatches to subscriber. +// With pull-based subscriptions, this is called synchronously from the fetch loop. +func (h *TriggerHandler) HandleMessage(msg *nats.Msg) { + logger := h.logger.With(zap.String("msg_id", msg.Header.Get(nats.MsgIdHdr))) + ctx := logging.WithLogger(h.ctx, logger) + + h.doHandle(ctx, msg) +} + +// doHandle processes the message synchronously +func (h *TriggerHandler) doHandle(ctx context.Context, msg *nats.Msg) { + logger := logging.FromContext(ctx) + + // Convert NATS message to CloudEvents message + message := cejs.NewMessage(msg) + if message.ReadEncoding() == binding.EncodingUnknown { + logger.Errorw("received a message with unknown encoding") + if err := msg.Term(); err != nil { + logger.Errorw("failed to terminate message", zap.Error(err)) + } + return + } + + // Convert to CloudEvent for filtering + event, err := binding.ToEvent(ctx, message) + if err != nil { + logger.Errorw("failed to convert message to CloudEvent", zap.Error(err)) + if err := msg.Term(); err != nil { + logger.Errorw("failed to terminate message", zap.Error(err)) + } + return + } + + // Apply filter + if h.filter != nil { + filterResult := h.filter.Filter(ctx, *event) + if filterResult == eventfilter.FailFilter { + logger.Debugw("event filtered out", + zap.String("type", event.Type()), + zap.String("source", event.Source()), + ) + // Ack the message since it was intentionally filtered + if err := msg.Ack(); err != nil { + logger.Errorw("failed to ack filtered message", zap.Error(err)) + } + return + } + } + + // Dispatch to subscriber + logger.Debugw("dispatching event to subscriber", + zap.String("subscriber", h.subscriber.URL.String()), + zap.String("type", event.Type()), + zap.String("source", event.Source()), + zap.String("id", event.ID()), + ) + + dispatchInfo, err := h.dispatchEvent(ctx, event, msg) + if err != nil { + logger.Errorw("failed to dispatch event", + zap.Error(err), + zap.Int("response_code", dispatchInfo.ResponseCode), + ) + return + } + + logger.Debugw("event dispatched successfully", + zap.Int("response_code", dispatchInfo.ResponseCode), + ) +} + +// dispatchEvent sends the event to the subscriber and handles ack/nack +func (h *TriggerHandler) dispatchEvent(ctx context.Context, event *cloudevents.Event, msg *nats.Msg) (*kncloudevents.DispatchInfo, error) { + logger := logging.FromContext(ctx) + + additionalHeaders := tracing.ConvertEventToHttpHeader(event) + te := TypeExtractorTransformer("") + + // Get retry number from message metadata + retryNumber := 1 + if meta, err := msg.Metadata(); err == nil { + retryNumber = int(meta.NumDelivered) + } + + // Determine if this is the last try + maxRetries := 1 + if h.retryConfig != nil { + maxRetries = h.retryConfig.RetryMax + } + lastTry := retryNumber > maxRetries + + // Dispatch the message to trigger's destination + dispatchInfo, err := h.dispatcher.SendEvent(ctx, *event, h.subscriber, + kncloudevents.WithHeader(additionalHeaders), + kncloudevents.WithTransformers(&te), + kncloudevents.WithRetryConfig(h.noRetryConfig), + ) + + result := determineNatsResult(dispatchInfo.ResponseCode, err) + + // Handle ack/nack/term based on result + switch { + case protocol.IsACK(result): + // process reply url case first + if h.brokerIngressURL != nil { + // TODO: should we retry in-memory reply url or go with re-delivery of the message? + replyDispatchInfo, replyErr := h.dispatcher.SendEvent(ctx, *event, *h.brokerIngressURL, + kncloudevents.WithRetryConfig(&defaultRetry), + kncloudevents.WithHeader(additionalHeaders), + kncloudevents.WithTransformers(&te), + ) + if replyErr != nil { + logger.Errorw("failed to send reply to broker ingress", + zap.Error(replyErr), + zap.Int("response_code", replyDispatchInfo.ResponseCode), + ) + } + } + if err := msg.Ack(nats.Context(ctx)); err != nil { + logger.Errorw("failed to ack message", zap.Error(err)) + } + case protocol.IsNACK(result): + if lastTry { + if h.deadLetterSink != nil { + // Send to dead letter sink + dlsDispatchInfo, dlsErr := h.dispatcher.SendEvent(ctx, *event, *h.deadLetterSink, + kncloudevents.WithRetryConfig(&defaultRetry), + kncloudevents.WithHeader(additionalHeaders), + kncloudevents.WithTransformers(&te), + ) + if dlsErr != nil { + logger.Errorw("failed to send to dead letter sink", + zap.Error(dlsErr), + zap.Int("response_code", dlsDispatchInfo.ResponseCode), + ) + } + } + + // Ack after DLS attempt + if err := msg.Ack(nats.Context(ctx)); err != nil { + logger.Errorw("failed to ack message after last retry", zap.Error(err)) + } + } else { + // Nack for retry + nakDelay := jsutils.CalculateNakDelayForRetryNumber(retryNumber, h.retryConfig) + if err := msg.NakWithDelay(nakDelay, nats.Context(ctx)); err != nil { + logger.Errorw("failed to nack message", zap.Error(err)) + } + } + default: + // Terminate - non-retriable error + if lastTry && h.deadLetterSink != nil { + // Send to dead letter sink + dlsDispatchInfo, dlsErr := h.dispatcher.SendEvent(ctx, *event, *h.deadLetterSink, + kncloudevents.WithRetryConfig(&defaultRetry), + kncloudevents.WithHeader(additionalHeaders), + kncloudevents.WithTransformers(&te), + ) + if dlsErr != nil { + logger.Errorw("failed to send to dead letter sink", + zap.Error(dlsErr), + zap.Int("response_code", dlsDispatchInfo.ResponseCode), + ) + } + } + + if err := msg.Term(nats.Context(ctx)); err != nil { + logger.Errorw("failed to term message", zap.Error(err)) + } + } + + return dispatchInfo, err +} + +// Cleanup releases resources +func (h *TriggerHandler) Cleanup() { + if h.filter != nil { + h.filter.Cleanup() + } +} + +func determineNatsResult(responseCode int, err error) protocol.Result { + result := protocol.ResultACK + if err != nil { + code := responseCode + if code/100 == 5 || code == http.StatusTooManyRequests || code == http.StatusRequestTimeout { + // Retriable error, effectively this is nats protocol NACK + result = protocol.NewReceipt(false, "%w", err) + } else { + // Non-retriable error + result = err + } + } + return result +} + +// buildTriggerFilter builds a filter from the trigger spec. +// Priority: +// 1. trigger.Spec.Filters (new subscriptions API filters) - if defined +// 2. trigger.Spec.Filter (legacy attributes filter) - if defined +// 3. nil (pass all events) - if neither is defined +func buildTriggerFilter(logger *zap.SugaredLogger, trigger *eventingv1.Trigger) eventfilter.Filter { + switch { + case len(trigger.Spec.Filters) > 0: + // Use new subscriptions API filters + logger.Debugw("using subscriptions API filters", + zap.Any("filters", trigger.Spec.Filters), + ) + return subscriptionsapi.CreateSubscriptionsAPIFilters(logger.Desugar(), trigger.Spec.Filters) + case trigger.Spec.Filter != nil && trigger.Spec.Filter.Attributes != nil: + // Use legacy attributes filter + logger.Debugw("using legacy attributes filter", + zap.Any("filter", trigger.Spec.Filter), + ) + return attributes.NewAttributesFilter(trigger.Spec.Filter.Attributes) + default: + // No filter defined, pass all events + logger.Debugw("no filter defined, passing all events") + return nil + } +} diff --git a/pkg/broker/filter/handler_test.go b/pkg/broker/filter/handler_test.go new file mode 100644 index 000000000..33ac7fceb --- /dev/null +++ b/pkg/broker/filter/handler_test.go @@ -0,0 +1,437 @@ +/* +Copyright 2024 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package filter + +import ( + "context" + "errors" + "net/http" + "testing" + + cloudevents "github.com/cloudevents/sdk-go/v2" + "github.com/cloudevents/sdk-go/v2/protocol" + "go.uber.org/zap" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + eventingv1 "knative.dev/eventing/pkg/apis/eventing/v1" + "knative.dev/eventing/pkg/eventfilter" +) + +func TestDetermineNatsResult(t *testing.T) { + tests := []struct { + name string + responseCode int + err error + wantACK bool + wantNACK bool + }{ + { + name: "success - no error", + responseCode: http.StatusOK, + err: nil, + wantACK: true, + wantNACK: false, + }, + { + name: "success - 2xx response", + responseCode: http.StatusAccepted, + err: nil, + wantACK: true, + wantNACK: false, + }, + { + name: "retriable - 500 error", + responseCode: http.StatusInternalServerError, + err: errors.New("server error"), + wantACK: false, + wantNACK: true, + }, + { + name: "retriable - 502 bad gateway", + responseCode: http.StatusBadGateway, + err: errors.New("bad gateway"), + wantACK: false, + wantNACK: true, + }, + { + name: "retriable - 503 service unavailable", + responseCode: http.StatusServiceUnavailable, + err: errors.New("service unavailable"), + wantACK: false, + wantNACK: true, + }, + { + name: "retriable - 504 gateway timeout", + responseCode: http.StatusGatewayTimeout, + err: errors.New("gateway timeout"), + wantACK: false, + wantNACK: true, + }, + { + name: "retriable - 429 too many requests", + responseCode: http.StatusTooManyRequests, + err: errors.New("too many requests"), + wantACK: false, + wantNACK: true, + }, + { + name: "retriable - 408 request timeout", + responseCode: http.StatusRequestTimeout, + err: errors.New("request timeout"), + wantACK: false, + wantNACK: true, + }, + { + name: "non-retriable - 400 bad request", + responseCode: http.StatusBadRequest, + err: errors.New("bad request"), + wantACK: false, + wantNACK: false, + }, + { + name: "non-retriable - 401 unauthorized", + responseCode: http.StatusUnauthorized, + err: errors.New("unauthorized"), + wantACK: false, + wantNACK: false, + }, + { + name: "non-retriable - 403 forbidden", + responseCode: http.StatusForbidden, + err: errors.New("forbidden"), + wantACK: false, + wantNACK: false, + }, + { + name: "non-retriable - 404 not found", + responseCode: http.StatusNotFound, + err: errors.New("not found"), + wantACK: false, + wantNACK: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := determineNatsResult(tt.responseCode, tt.err) + + isACK := protocol.IsACK(result) + isNACK := protocol.IsNACK(result) + + if isACK != tt.wantACK { + t.Errorf("IsACK() = %v, want %v", isACK, tt.wantACK) + } + + if isNACK != tt.wantNACK { + t.Errorf("IsNACK() = %v, want %v", isNACK, tt.wantNACK) + } + }) + } +} + +func TestTypeExtractorTransformer(t *testing.T) { + // TypeExtractorTransformer is a string type that implements binding.Transformer + // It extracts the CloudEvent type from a message + + te := TypeExtractorTransformer("") + + // Initial value should be empty + if string(te) != "" { + t.Errorf("Initial value = %v, want empty string", string(te)) + } +} + +func TestRetryConfigDefaults(t *testing.T) { + // Verify the default retry configuration values + if retryMax != 3 { + t.Errorf("retryMax = %v, want 3", retryMax) + } + + if retryTimeout != "PT1S" { + t.Errorf("retryTimeout = %v, want PT1S", retryTimeout) + } + + if retryBackoffDelay != "PT0.5S" { + t.Errorf("retryBackoffDelay = %v, want PT0.5S", retryBackoffDelay) + } +} + +func TestDetermineNatsResult_EdgeCases(t *testing.T) { + tests := []struct { + name string + responseCode int + err error + wantACK bool + }{ + { + name: "zero response code with no error", + responseCode: 0, + err: nil, + wantACK: true, + }, + { + name: "zero response code with error", + responseCode: 0, + err: errors.New("some error"), + wantACK: false, + }, + { + name: "1xx informational (no error)", + responseCode: 100, + err: nil, + wantACK: true, + }, + { + name: "3xx redirect with error", + responseCode: 301, + err: errors.New("redirect"), + wantACK: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := determineNatsResult(tt.responseCode, tt.err) + + isACK := protocol.IsACK(result) + if isACK != tt.wantACK { + t.Errorf("IsACK() = %v, want %v", isACK, tt.wantACK) + } + }) + } +} + +func TestBuildTriggerFilter(t *testing.T) { + logger := zap.NewNop().Sugar() + + tests := []struct { + name string + trigger *eventingv1.Trigger + wantNilFilter bool + testEvent cloudevents.Event + wantFilterPass bool + }{ + { + name: "no filter - passes all events", + trigger: &eventingv1.Trigger{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-trigger", + Namespace: "default", + }, + Spec: eventingv1.TriggerSpec{ + Broker: "test-broker", + }, + }, + wantNilFilter: true, + wantFilterPass: true, + }, + { + name: "legacy filter only - uses attributes filter", + trigger: &eventingv1.Trigger{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-trigger", + Namespace: "default", + }, + Spec: eventingv1.TriggerSpec{ + Broker: "test-broker", + Filter: &eventingv1.TriggerFilter{ + Attributes: map[string]string{ + "type": "test.event.type", + }, + }, + }, + }, + wantNilFilter: false, + testEvent: func() cloudevents.Event { + e := cloudevents.NewEvent() + e.SetType("test.event.type") + e.SetSource("test-source") + return e + }(), + wantFilterPass: true, + }, + { + name: "legacy filter - event does not match", + trigger: &eventingv1.Trigger{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-trigger", + Namespace: "default", + }, + Spec: eventingv1.TriggerSpec{ + Broker: "test-broker", + Filter: &eventingv1.TriggerFilter{ + Attributes: map[string]string{ + "type": "test.event.type", + }, + }, + }, + }, + wantNilFilter: false, + testEvent: func() cloudevents.Event { + e := cloudevents.NewEvent() + e.SetType("different.type") + e.SetSource("test-source") + return e + }(), + wantFilterPass: false, + }, + { + name: "new filters take priority over legacy filter", + trigger: &eventingv1.Trigger{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-trigger", + Namespace: "default", + }, + Spec: eventingv1.TriggerSpec{ + Broker: "test-broker", + // New filters - should take priority + Filters: []eventingv1.SubscriptionsAPIFilter{ + { + Exact: map[string]string{ + "type": "new.filter.type", + }, + }, + }, + // Legacy filter - should be ignored + Filter: &eventingv1.TriggerFilter{ + Attributes: map[string]string{ + "type": "legacy.filter.type", + }, + }, + }, + }, + wantNilFilter: false, + testEvent: func() cloudevents.Event { + e := cloudevents.NewEvent() + e.SetType("new.filter.type") + e.SetSource("test-source") + return e + }(), + wantFilterPass: true, + }, + { + name: "new filters - event matches legacy but not new filter", + trigger: &eventingv1.Trigger{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-trigger", + Namespace: "default", + }, + Spec: eventingv1.TriggerSpec{ + Broker: "test-broker", + Filters: []eventingv1.SubscriptionsAPIFilter{ + { + Exact: map[string]string{ + "type": "new.filter.type", + }, + }, + }, + Filter: &eventingv1.TriggerFilter{ + Attributes: map[string]string{ + "type": "legacy.filter.type", + }, + }, + }, + }, + wantNilFilter: false, + testEvent: func() cloudevents.Event { + e := cloudevents.NewEvent() + e.SetType("legacy.filter.type") // matches legacy but not new + e.SetSource("test-source") + return e + }(), + wantFilterPass: false, // new filters take priority, so should fail + }, + { + name: "new filters with prefix", + trigger: &eventingv1.Trigger{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-trigger", + Namespace: "default", + }, + Spec: eventingv1.TriggerSpec{ + Broker: "test-broker", + Filters: []eventingv1.SubscriptionsAPIFilter{ + { + Prefix: map[string]string{ + "type": "test.event.", + }, + }, + }, + }, + }, + wantNilFilter: false, + testEvent: func() cloudevents.Event { + e := cloudevents.NewEvent() + e.SetType("test.event.created") + e.SetSource("test-source") + return e + }(), + wantFilterPass: true, + }, + { + name: "new filters with suffix", + trigger: &eventingv1.Trigger{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-trigger", + Namespace: "default", + }, + Spec: eventingv1.TriggerSpec{ + Broker: "test-broker", + Filters: []eventingv1.SubscriptionsAPIFilter{ + { + Suffix: map[string]string{ + "type": ".created", + }, + }, + }, + }, + }, + wantNilFilter: false, + testEvent: func() cloudevents.Event { + e := cloudevents.NewEvent() + e.SetType("order.created") + e.SetSource("test-source") + return e + }(), + wantFilterPass: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + filter := buildTriggerFilter(logger, tt.trigger) + + if tt.wantNilFilter { + if filter != nil { + t.Errorf("expected nil filter, got %v", filter) + } + return + } + + if filter == nil { + t.Fatal("expected non-nil filter") + } + + // Test the filter with the test event + result := filter.Filter(context.Background(), tt.testEvent) + passed := result != eventfilter.FailFilter + + if passed != tt.wantFilterPass { + t.Errorf("filter result = %v (passed=%v), want passed=%v", result, passed, tt.wantFilterPass) + } + }) + } +} diff --git a/pkg/broker/filter/reconciler.go b/pkg/broker/filter/reconciler.go new file mode 100644 index 000000000..cb6f98d21 --- /dev/null +++ b/pkg/broker/filter/reconciler.go @@ -0,0 +1,160 @@ +/* +Copyright 2024 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package filter + +import ( + "context" + "fmt" + "net/http" + "time" + + "go.uber.org/zap" + apierrs "k8s.io/apimachinery/pkg/api/errors" + + duckv1 "knative.dev/pkg/apis/duck/v1" + "knative.dev/pkg/logging" + + eventingv1 "knative.dev/eventing/pkg/apis/eventing/v1" + eventinglisters "knative.dev/eventing/pkg/client/listers/eventing/v1" + "knative.dev/eventing/pkg/kncloudevents" + + "knative.dev/eventing-natss/pkg/broker/constants" +) + +// FilterReconciler reconciles triggers and manages consumer subscriptions +type FilterReconciler struct { + logger *zap.SugaredLogger + + triggerLister eventinglisters.TriggerLister + brokerLister eventinglisters.BrokerLister + + consumerManager *ConsumerManager +} + +// NewFilterReconciler creates a new filter reconciler +func NewFilterReconciler( + ctx context.Context, + triggerLister eventinglisters.TriggerLister, + brokerLister eventinglisters.BrokerLister, + consumerManager *ConsumerManager, +) *FilterReconciler { + return &FilterReconciler{ + logger: logging.FromContext(ctx), + triggerLister: triggerLister, + brokerLister: brokerLister, + consumerManager: consumerManager, + } +} + +// ReconcileTrigger reconciles a trigger to ensure the filter has a subscription +func (r *FilterReconciler) ReconcileTrigger(ctx context.Context, trigger *eventingv1.Trigger) error { + logger := r.logger.With( + zap.String("trigger", trigger.Name), + zap.String("namespace", trigger.Namespace), + ) + + // Get the broker + broker, err := r.brokerLister.Brokers(trigger.Namespace).Get(trigger.Spec.Broker) + if err != nil { + if apierrs.IsNotFound(err) { + logger.Debugw("broker not found, skipping trigger") + return nil + } + return fmt.Errorf("failed to get broker: %w", err) + } + + // Check broker class + if broker.GetAnnotations()[eventingv1.BrokerClassAnnotationKey] != constants.BrokerClassName { + logger.Debugw("broker is not NatsJetStreamBroker, skipping") + return nil + } + + // Check if broker is ready + if !broker.IsReady() { + logger.Debugw("broker is not ready, skipping trigger") + return nil + } + + // Check if trigger is ready + if trigger.Status.SubscriberURI == nil { + logger.Debugw("trigger subscriber URI not resolved yet, skipping") + return nil + } + + // Build subscriber addressable from trigger status + subscriber := duckv1.Addressable{URL: trigger.Status.SubscriberURI} + + // Get broker ingress URL for reply events + var brokerIngressURL *duckv1.Addressable + if broker.Status.Address != nil && broker.Status.Address.URL != nil { + brokerIngressURL = &duckv1.Addressable{URL: broker.Status.Address.URL.DeepCopy()} + } + + // Get dead letter sink if configured + var deadLetterSink *duckv1.Addressable + if trigger.Status.DeadLetterSinkURI != nil { + deadLetterSink = &duckv1.Addressable{URL: trigger.Status.DeadLetterSinkURI.DeepCopy()} + } + + // Build retry config from trigger delivery spec + var retryConfig *kncloudevents.RetryConfig + if trigger.Spec.Delivery != nil { + config, err := kncloudevents.RetryConfigFromDeliverySpec(*trigger.Spec.Delivery) + if err != nil { + logger.Warnw("failed to build retry config from delivery spec", zap.Error(err)) + } else { + retryConfig = &config + } + } + + // Build no-retry config (JetStream handles retries via redelivery) + var requestTimeout time.Duration + if retryConfig != nil { + requestTimeout = retryConfig.RequestTimeout + } + var noRetryConfig = kncloudevents.RetryConfig{ + RetryMax: 0, + CheckRetry: func(ctx context.Context, resp *http.Response, err error) (bool, error) { + return false, nil + }, + Backoff: func(attemptNum int, resp *http.Response) time.Duration { + return 0 + }, + RequestTimeout: requestTimeout, + } + + // Subscribe to the trigger's consumer + err = r.consumerManager.SubscribeTrigger( + trigger, + broker, + subscriber, + brokerIngressURL, + deadLetterSink, + retryConfig, + &noRetryConfig, + ) + if err != nil { + return fmt.Errorf("failed to subscribe to trigger: %w", err) + } + + return nil +} + +// DeleteTrigger removes the subscription for a deleted trigger +func (r *FilterReconciler) DeleteTrigger(triggerUID string) error { + return r.consumerManager.UnsubscribeTrigger(triggerUID) +} diff --git a/pkg/common/nats/conn.go b/pkg/common/nats/conn.go index d1c5f27e6..0c8946586 100644 --- a/pkg/common/nats/conn.go +++ b/pkg/common/nats/conn.go @@ -28,6 +28,7 @@ import ( v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" clientsetcorev1 "k8s.io/client-go/kubernetes/typed/core/v1" + "k8s.io/client-go/rest" "knative.dev/pkg/injection" "knative.dev/pkg/system" @@ -49,6 +50,31 @@ var ( ErrBadTLSOption = errors.New("bad tls option") ) +// NewNatsConnFromURL creates a NATS connection using just the URL. +// This is useful when the NATS URL is passed via environment variable. +func NewNatsConnFromURL(ctx context.Context, url string) (*nats.Conn, error) { + logger := logging.FromContext(ctx) + + if url == "" { + url = constants.DefaultNatsURL + } + + opts := []nats.Option{ + nats.Name("kn jsm broker component"), + nats.DisconnectErrHandler(func(conn *nats.Conn, err error) { + logger.Warnf("Disconnected from NATS: err=%v", err) + }), + nats.ReconnectHandler(func(nc *nats.Conn) { + logger.Infof("Reconnected to NATS [%s]", nc.ConnectedUrl()) + }), + nats.ClosedHandler(func(nc *nats.Conn) { + logger.Warnf("NATS connection closed") + }), + } + + return nats.Connect(url, opts...) +} + func NewNatsConn(ctx context.Context, config commonconfig.EventingNatsConfig) (*nats.Conn, error) { logger := logging.FromContext(ctx) @@ -57,7 +83,17 @@ func NewNatsConn(ctx context.Context, config commonconfig.EventingNatsConfig) (* url = constants.DefaultNatsURL } - coreV1Client, err := clientsetcorev1.NewForConfig(injection.GetConfig(ctx)) + // Try to get config from injection, fall back to in-cluster config + cfg := injection.GetConfig(ctx) + if cfg == nil { + var err error + cfg, err = rest.InClusterConfig() + if err != nil { + return nil, fmt.Errorf("failed to get kubernetes config: %w", err) + } + } + + coreV1Client, err := clientsetcorev1.NewForConfig(cfg) if err != nil { return nil, err } diff --git a/vendor/knative.dev/eventing/pkg/client/injection/informers/eventing/v1/trigger/trigger.go b/vendor/knative.dev/eventing/pkg/client/injection/informers/eventing/v1/trigger/trigger.go new file mode 100644 index 000000000..2435cda2f --- /dev/null +++ b/vendor/knative.dev/eventing/pkg/client/injection/informers/eventing/v1/trigger/trigger.go @@ -0,0 +1,52 @@ +/* +Copyright 2021 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by injection-gen. DO NOT EDIT. + +package trigger + +import ( + context "context" + + v1 "knative.dev/eventing/pkg/client/informers/externalversions/eventing/v1" + factory "knative.dev/eventing/pkg/client/injection/informers/factory" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" + logging "knative.dev/pkg/logging" +) + +func init() { + injection.Default.RegisterInformer(withInformer) +} + +// Key is used for associating the Informer inside the context.Context. +type Key struct{} + +func withInformer(ctx context.Context) (context.Context, controller.Informer) { + f := factory.Get(ctx) + inf := f.Eventing().V1().Triggers() + return context.WithValue(ctx, Key{}, inf), inf.Informer() +} + +// Get extracts the typed informer from the context. +func Get(ctx context.Context) v1.TriggerInformer { + untyped := ctx.Value(Key{}) + if untyped == nil { + logging.FromContext(ctx).Panic( + "Unable to fetch knative.dev/eventing/pkg/client/informers/externalversions/eventing/v1.TriggerInformer from context.") + } + return untyped.(v1.TriggerInformer) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 0a1172edb..bcdd5710a 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1274,6 +1274,7 @@ knative.dev/eventing/pkg/client/informers/externalversions/sources/v1beta2 knative.dev/eventing/pkg/client/injection/client knative.dev/eventing/pkg/client/injection/client/fake knative.dev/eventing/pkg/client/injection/informers/eventing/v1/broker +knative.dev/eventing/pkg/client/injection/informers/eventing/v1/trigger knative.dev/eventing/pkg/client/injection/informers/factory knative.dev/eventing/pkg/client/injection/reconciler/eventing/v1/broker knative.dev/eventing/pkg/client/listers/eventing/v1 From 2dbbd6f360af49b36d4fe1c31d58cb7a575e5b0d Mon Sep 17 00:00:00 2001 From: Knative Automation Date: Tue, 20 Jan 2026 08:54:14 -0500 Subject: [PATCH 02/10] upgrade to latest dependencies (#721) bumping knative.dev/reconciler-test b74774d...d1b946d: > d1b946d upgrade to latest dependencies (# 853) bumping knative.dev/hack ee8a1b2...bf6758c: > bf6758c bump GKE default version to 1.33 (# 458) bumping knative.dev/pkg af2d223...4a022ed: > 4a022ed upgrade to latest dependencies (# 3313) > 49cf63e bump K8s min version to 1.33 (# 3312) Signed-off-by: Knative Automation --- go.mod | 6 +++--- go.sum | 12 ++++++------ vendor/knative.dev/hack/infra-library.sh | 2 +- vendor/knative.dev/pkg/version/version.go | 2 +- vendor/modules.txt | 6 +++--- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/go.mod b/go.mod index fa3162725..dfb1b841c 100644 --- a/go.mod +++ b/go.mod @@ -30,9 +30,9 @@ require ( k8s.io/code-generator v0.34.3 k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 knative.dev/eventing v0.47.1-0.20260114135450-59b517c8aca0 - knative.dev/hack v0.0.0-20260114013932-ee8a1b2d08eb - knative.dev/pkg v0.0.0-20260119145652-af2d22303fb9 - knative.dev/reconciler-test v0.0.0-20260114013820-b74774d63680 + knative.dev/hack v0.0.0-20260120115810-bf6758cba446 + knative.dev/pkg v0.0.0-20260120122510-4a022ed9999a + knative.dev/reconciler-test v0.0.0-20260120021612-d1b946d91170 ) require sigs.k8s.io/yaml v1.6.0 diff --git a/go.sum b/go.sum index 0d0f96a03..fc5f4917d 100644 --- a/go.sum +++ b/go.sum @@ -1073,12 +1073,12 @@ k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8 k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= knative.dev/eventing v0.47.1-0.20260114135450-59b517c8aca0 h1:CvNtQXuqmWrMvNlzav8qFU4dv5wM1H9JbzP/pUaKqJo= knative.dev/eventing v0.47.1-0.20260114135450-59b517c8aca0/go.mod h1:jzeVf6j2Ba13KsUIQ4gAY1rtPQfQd4wgOVrSgG+0QEI= -knative.dev/hack v0.0.0-20260114013932-ee8a1b2d08eb h1:Q62pnvCRASIMMcIj5mK+9rXDx9KoQCCDQk3lhIDz4nA= -knative.dev/hack v0.0.0-20260114013932-ee8a1b2d08eb/go.mod h1:L5RzHgbvam0u8QFHfzCX6MKxu/a/gIGEdaRBqNiVbl0= -knative.dev/pkg v0.0.0-20260119145652-af2d22303fb9 h1:ABYjcOz2gvE1ltZtyxXsB+5iykV8jRS+v7ooLvydZho= -knative.dev/pkg v0.0.0-20260119145652-af2d22303fb9/go.mod h1:9mZVfPCgjTOrwZb5nZSiIGGsIWtIq97LK8RWJk7O3G8= -knative.dev/reconciler-test v0.0.0-20260114013820-b74774d63680 h1:129fXCrBQQjp9TMUwMx6EHdm8DqX5v1ql085yOQ/ixc= -knative.dev/reconciler-test v0.0.0-20260114013820-b74774d63680/go.mod h1:qZfVnTPMH0YPk3fBo09BsIJkk2HbdSMoq1rhHuWTtow= +knative.dev/hack v0.0.0-20260120115810-bf6758cba446 h1:Y8raYHIuAL9/gUKGYD9/dD+EqUTmrpqVDowzfUVSlGs= +knative.dev/hack v0.0.0-20260120115810-bf6758cba446/go.mod h1:L5RzHgbvam0u8QFHfzCX6MKxu/a/gIGEdaRBqNiVbl0= +knative.dev/pkg v0.0.0-20260120122510-4a022ed9999a h1:9f29OTA7w/iVIX6PS6yveVVzNbcUS74eQfchVe8o2/4= +knative.dev/pkg v0.0.0-20260120122510-4a022ed9999a/go.mod h1:Tz3GoxcNC5vH3Zo//cW3mnHL474u+Y1wbsUIZ11p8No= +knative.dev/reconciler-test v0.0.0-20260120021612-d1b946d91170 h1:siTPzFfjDWnhpmGgmzwbXfU7NkipB9HGFGZL4n4fT1k= +knative.dev/reconciler-test v0.0.0-20260120021612-d1b946d91170/go.mod h1:YsisaEbweHy5OWWRnhivSPfXJU1QGoUnGOdTAZ+L1FA= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/vendor/knative.dev/hack/infra-library.sh b/vendor/knative.dev/hack/infra-library.sh index 896883976..5ec188b58 100644 --- a/vendor/knative.dev/hack/infra-library.sh +++ b/vendor/knative.dev/hack/infra-library.sh @@ -21,7 +21,7 @@ source "$(dirname "${BASH_SOURCE[0]:-$0}")/library.sh" # Default Kubernetes version to use for GKE, if not overridden with # the `--cluster-version` parameter. -readonly GKE_DEFAULT_CLUSTER_VERSION="1.32" +readonly GKE_DEFAULT_CLUSTER_VERSION="1.33" # Dumps the k8s api server metrics. Spins up a proxy, waits a little bit and # dumps the metrics to ${ARTIFACTS}/k8s.metrics.txt diff --git a/vendor/knative.dev/pkg/version/version.go b/vendor/knative.dev/pkg/version/version.go index 6d40e9553..ea3e5692c 100644 --- a/vendor/knative.dev/pkg/version/version.go +++ b/vendor/knative.dev/pkg/version/version.go @@ -33,7 +33,7 @@ const ( // NOTE: If you are changing this line, please also update the minimum kubernetes // version listed here: // https://github.com/knative/docs/blob/main/docs/snippets/prerequisites.md - defaultMinimumVersion = "v1.32.0" + defaultMinimumVersion = "v1.33.0" ) func getMinimumVersion() string { diff --git a/vendor/modules.txt b/vendor/modules.txt index bcdd5710a..1b30bd415 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1306,10 +1306,10 @@ knative.dev/eventing/test/lib/sender knative.dev/eventing/test/test_images knative.dev/eventing/test/test_images/event-sender knative.dev/eventing/test/test_images/print -# knative.dev/hack v0.0.0-20260114013932-ee8a1b2d08eb +# knative.dev/hack v0.0.0-20260120115810-bf6758cba446 ## explicit; go 1.24 knative.dev/hack -# knative.dev/pkg v0.0.0-20260119145652-af2d22303fb9 +# knative.dev/pkg v0.0.0-20260120122510-4a022ed9999a ## explicit; go 1.24.0 knative.dev/pkg/apis knative.dev/pkg/apis/duck @@ -1396,7 +1396,7 @@ knative.dev/pkg/webhook/json knative.dev/pkg/webhook/resourcesemantics knative.dev/pkg/webhook/resourcesemantics/defaulting knative.dev/pkg/webhook/resourcesemantics/validation -# knative.dev/reconciler-test v0.0.0-20260114013820-b74774d63680 +# knative.dev/reconciler-test v0.0.0-20260120021612-d1b946d91170 ## explicit; go 1.24.0 knative.dev/reconciler-test/cmd/eventshub knative.dev/reconciler-test/pkg/environment From 029d78fc009941414606178772a103d94398c6b3 Mon Sep 17 00:00:00 2001 From: Knative Automation Date: Mon, 26 Jan 2026 21:09:32 -0500 Subject: [PATCH 03/10] upgrade to latest dependencies (#725) bumping knative.dev/eventing 59b517c...cb840ef: > cb840ef fix: Wait for full deployment rollout before marking IntegrationSink Ready (# 8858) > 86c43a6 Increase poll timings for IntegrationSource tests (# 8860) > 3226294 Prevent AuthZ test pollution by ensuring unready test runs last (# 8859) > ccf232a fix unused linter errors (# 8851) > 4303f77 Remove k8s 1.32 runs in KinD e2e tests (# 8857) > e169933 Run eventing office hours Slack reminder every other week (# 8855) > 71a6c9f fix: EventTransform not updating when expression changes (# 8848) > 45c284b [main] Upgrade to latest dependencies (# 8847) bumping knative.dev/reconciler-test d1b946d...4301404: > 4301404 upgrade to latest dependencies (# 854) Signed-off-by: Knative Automation --- go.mod | 4 +- go.sum | 8 ++-- .../v1alpha1/integration_sink_lifecycle.go | 47 +++++++++++++++---- vendor/modules.txt | 4 +- 4 files changed, 46 insertions(+), 17 deletions(-) diff --git a/go.mod b/go.mod index dfb1b841c..c5a8873ac 100644 --- a/go.mod +++ b/go.mod @@ -29,10 +29,10 @@ require ( k8s.io/client-go v0.34.3 k8s.io/code-generator v0.34.3 k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 - knative.dev/eventing v0.47.1-0.20260114135450-59b517c8aca0 + knative.dev/eventing v0.47.1-0.20260126225531-cb840efa2fbb knative.dev/hack v0.0.0-20260120115810-bf6758cba446 knative.dev/pkg v0.0.0-20260120122510-4a022ed9999a - knative.dev/reconciler-test v0.0.0-20260120021612-d1b946d91170 + knative.dev/reconciler-test v0.0.0-20260120140419-4301404c03ce ) require sigs.k8s.io/yaml v1.6.0 diff --git a/go.sum b/go.sum index fc5f4917d..5404df11b 100644 --- a/go.sum +++ b/go.sum @@ -1071,14 +1071,14 @@ k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOP k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts= k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -knative.dev/eventing v0.47.1-0.20260114135450-59b517c8aca0 h1:CvNtQXuqmWrMvNlzav8qFU4dv5wM1H9JbzP/pUaKqJo= -knative.dev/eventing v0.47.1-0.20260114135450-59b517c8aca0/go.mod h1:jzeVf6j2Ba13KsUIQ4gAY1rtPQfQd4wgOVrSgG+0QEI= +knative.dev/eventing v0.47.1-0.20260126225531-cb840efa2fbb h1:cokLprP7InWi/f/BLtggVxuXQ3E789NwHyMwUTUxvpc= +knative.dev/eventing v0.47.1-0.20260126225531-cb840efa2fbb/go.mod h1:IFPgtLfE39zQAXTsgWTJdKjdCp3ehekqmosQu6CHS2A= knative.dev/hack v0.0.0-20260120115810-bf6758cba446 h1:Y8raYHIuAL9/gUKGYD9/dD+EqUTmrpqVDowzfUVSlGs= knative.dev/hack v0.0.0-20260120115810-bf6758cba446/go.mod h1:L5RzHgbvam0u8QFHfzCX6MKxu/a/gIGEdaRBqNiVbl0= knative.dev/pkg v0.0.0-20260120122510-4a022ed9999a h1:9f29OTA7w/iVIX6PS6yveVVzNbcUS74eQfchVe8o2/4= knative.dev/pkg v0.0.0-20260120122510-4a022ed9999a/go.mod h1:Tz3GoxcNC5vH3Zo//cW3mnHL474u+Y1wbsUIZ11p8No= -knative.dev/reconciler-test v0.0.0-20260120021612-d1b946d91170 h1:siTPzFfjDWnhpmGgmzwbXfU7NkipB9HGFGZL4n4fT1k= -knative.dev/reconciler-test v0.0.0-20260120021612-d1b946d91170/go.mod h1:YsisaEbweHy5OWWRnhivSPfXJU1QGoUnGOdTAZ+L1FA= +knative.dev/reconciler-test v0.0.0-20260120140419-4301404c03ce h1:pIQCFDsDTRkzrJZDTs2laryYOI6VpcnGF5zezL0NXOw= +knative.dev/reconciler-test v0.0.0-20260120140419-4301404c03ce/go.mod h1:FUaadFiniAaqqBp/D2g2cO/FUABVR8W4yZd2azDzp7I= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/vendor/knative.dev/eventing/pkg/apis/sinks/v1alpha1/integration_sink_lifecycle.go b/vendor/knative.dev/eventing/pkg/apis/sinks/v1alpha1/integration_sink_lifecycle.go index d95a44680..78e1418b9 100644 --- a/vendor/knative.dev/eventing/pkg/apis/sinks/v1alpha1/integration_sink_lifecycle.go +++ b/vendor/knative.dev/eventing/pkg/apis/sinks/v1alpha1/integration_sink_lifecycle.go @@ -106,22 +106,51 @@ func (s *IntegrationSinkStatus) MarkEventPoliciesTrueWithReason(reason, messageF IntegrationSinkCondSet.Manage(s).MarkTrueWithReason(IntegrationSinkConditionEventPoliciesReady, reason, messageFormat, messageA...) } -func (s *IntegrationSinkStatus) PropagateDeploymentStatus(d *appsv1.DeploymentStatus) { +func (s *IntegrationSinkStatus) PropagateDeploymentStatus(d *appsv1.Deployment) { + // A deployment is fully rolled out when: + // 1. ObservedGeneration == Generation: controller has observed the latest spec + // 2. UpdatedReplicas == Replicas: all pods updated to the new pod template + // 3. AvailableReplicas == Replicas: all updated pods are ready + // This ensures EventPolicy changes have propagated to all pods before marking Ready. + desiredReplicas := int32(1) + if d.Spec.Replicas != nil { + desiredReplicas = *d.Spec.Replicas + } + + deploymentFullyRolledOut := d.Status.ObservedGeneration == d.Generation && + d.Status.UpdatedReplicas == desiredReplicas && + d.Status.AvailableReplicas == desiredReplicas + + if deploymentFullyRolledOut { + IntegrationSinkCondSet.Manage(s).MarkTrue(IntegrationSinkConditionDeploymentReady) + return + } + + // Check deployment conditions for error states deploymentAvailableFound := false - for _, cond := range d.Conditions { + for _, cond := range d.Status.Conditions { if cond.Type == appsv1.DeploymentAvailable { deploymentAvailableFound = true - if cond.Status == corev1.ConditionTrue { - IntegrationSinkCondSet.Manage(s).MarkTrue(IntegrationSinkConditionDeploymentReady) - } else if cond.Status == corev1.ConditionFalse { + if cond.Status == corev1.ConditionFalse { IntegrationSinkCondSet.Manage(s).MarkFalse(IntegrationSinkConditionDeploymentReady, cond.Reason, cond.Message) - } else if cond.Status == corev1.ConditionUnknown { - IntegrationSinkCondSet.Manage(s).MarkUnknown(IntegrationSinkConditionDeploymentReady, cond.Reason, cond.Message) + return } } + // Also check Progressing condition for failures (e.g., ImagePullBackOff, insufficient quota) + if cond.Type == appsv1.DeploymentProgressing && cond.Status == corev1.ConditionFalse { + IntegrationSinkCondSet.Manage(s).MarkFalse(IntegrationSinkConditionDeploymentReady, cond.Reason, cond.Message) + return + } } - if !deploymentAvailableFound { - IntegrationSinkCondSet.Manage(s).MarkUnknown(IntegrationSinkConditionDeploymentReady, "DeploymentUnavailable", "The Deployment '%s' is unavailable.", d) + + // Deployment is progressing but not fully rolled out yet + if deploymentAvailableFound { + IntegrationSinkCondSet.Manage(s).MarkUnknown(IntegrationSinkConditionDeploymentReady, + "DeploymentRollingOut", + "Deployment rollout in progress: %d/%d replicas updated and available", + d.Status.AvailableReplicas, desiredReplicas) + } else { + IntegrationSinkCondSet.Manage(s).MarkUnknown(IntegrationSinkConditionDeploymentReady, "DeploymentUnavailable", "The Deployment is unavailable") } } diff --git a/vendor/modules.txt b/vendor/modules.txt index 1b30bd415..043401f4d 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1198,7 +1198,7 @@ k8s.io/utils/net k8s.io/utils/pointer k8s.io/utils/ptr k8s.io/utils/trace -# knative.dev/eventing v0.47.1-0.20260114135450-59b517c8aca0 +# knative.dev/eventing v0.47.1-0.20260126225531-cb840efa2fbb ## explicit; go 1.24.0 knative.dev/eventing/pkg/apis knative.dev/eventing/pkg/apis/common/integration/v1alpha1 @@ -1396,7 +1396,7 @@ knative.dev/pkg/webhook/json knative.dev/pkg/webhook/resourcesemantics knative.dev/pkg/webhook/resourcesemantics/defaulting knative.dev/pkg/webhook/resourcesemantics/validation -# knative.dev/reconciler-test v0.0.0-20260120021612-d1b946d91170 +# knative.dev/reconciler-test v0.0.0-20260120140419-4301404c03ce ## explicit; go 1.24.0 knative.dev/reconciler-test/cmd/eventshub knative.dev/reconciler-test/pkg/environment From 0a7da7c8b75ec68ec63deefb9d8efc36dbb58654 Mon Sep 17 00:00:00 2001 From: Andrii Stelmashenko Date: Tue, 27 Jan 2026 18:22:31 +0200 Subject: [PATCH 04/10] added trigger controller and reconciler (#716) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * added trigger controller and reconciler * added unit tests * fixed ling and style errors * removed unused parameter * proceed deleting consumer even when broker not found * removed todo * removed empty lines * run go mod vendor * update-codegen * Update community files (#717) Signed-off-by: Knative Automation * upgrade to latest dependencies (#708) bumping k8s.io/gengo/v2 1244d31...85fd79d: > 85fd79d Merge pull request # 304 from jpbetz/full-parser > b22feca Merge pull request # 306 from carsontham/support-omitzero-in-LookupJSON > b37a58d Add utility functions to access args > cc0d9c4 Add named arg parser (# 299) > c990d91 added support for omitzero > db65aa6 Apply feedback > 3d52566 Merge pull request # 303 from thockin/master > 1460848 Switch to # for comments > 5d81b21 Merge pull request # 297 from jpbetz/tag-parser > ea49fb3 Treat embedded fields as inline > 44a1af5 Limit whitespace to after : and , for named args > 221fad0 Merge pull request # 296 from aaron-prindle/rawstring-tag-parsing > ae132b4 Apply feedback > dcc70af Simplify whitespace handling and collapse needless states > e3bc6f1 Merge pull request # 293 from shashankram/fix-ignore > fc15268 feat: add raw string support to gengo comment tag arg parsing > 5502674 Add JSON tag argument support > 53609f6 Rename TypedTag to Tag > 83e62b2 parser/test: fix bug in comparison > 271cbf8 Apply feedback > c926ad7 Add missing break > c375a88 Add dedicated scanner tests > 7d6753c Add godoc to TypeTag.String(), add a 4 level nesting test > 4842742 Add a simple scanner and move literal value parsing into micro-FSMs. > 461f54c Test parse string for extract, fix incomplete value bug > 2f7fe8a Add opt-in support for value parsing to ExtractFunctionStyleCommentTags and ExtractSingleBoolCommentTag > e0a2d5e Expand and clean up tests > 88534e7 Add chained tag parsing bumping go.opentelemetry.io/otel/metric 84e3f3a...6ce1429: > 6ce1429 Release v1.39.0 (# 7676) > 12e421a sdk/log: move Enabled method from FilterProcessor to Processor (# 7639) > 5982f16 fix(deps): update module golang.org/x/sys to v0.39.0 (# 7684) > 9288378 chore(deps): update module golang.org/x/sync to v0.19.0 (# 7683) > ee3dfef chore(deps): update github.com/securego/gosec/v2 digest to 41f28e2 (# 7682) > 9345d1f fix(deps): update module github.com/golangci/golangci-lint/v2 to v2.7.2 (# 7680) > d03b033 Check context prior to delaying retry in OTLP exporters (# 7678) > 61765e7 Fix flaky `TestClientInstrumentation` (# 7677) > a54721c chore(deps): update module github.com/go-git/go-billy/v5 to v5.7.0 (# 7679) > 746d086 chore(deps): update github/codeql-action action to v4.31.7 (# 7675) > 1bc9713 Regenerate `ErrorType` documentation in `semconv/v1.37.0` (# 7667) > 5a692cf Fix whitespace in `semconv/v1.33.0` (# 7665) > 4eff89b Fix whitespace in `semconv/v1.32.0` (# 7666) > d1825df Fix whitespace in `semconv/v1.36.0` (# 7663) > ddc307d Fix package name documentation and missing copyright in `semconv/v1.32.0` (# 7659) > 3e85447 Fix whitespace in `semconv/v1.34.0` (# 7664) > a64b9ec Fix package documentation name and return error in `semconv/v1.36.0` (# 7656) > be85ff8 Fix whitespace in `semconv/v1.37.0` (# 7660) > cddeb68 Fix package name documentation and missing copyright in `semconv/v1.34.0` (# 7657) > 3659648 Fix package name documentation and missing copyright in `semconv/v1.33.0` (# 7658) > e69beb8 Fix package documentation name and return err in `semconv/v1.37.0` (# 7655) > ddd0420 fix(deps): update module github.com/golangci/golangci-lint/v2 to v2.7.1 (# 7671) > 21e75b9 chore(deps): update module github.com/ldez/gomoddirectives to v0.8.0 (# 7669) > ca53078 Instrument the `SimpleLogProcessor` from sdk/log (# 7548) > df83919 chore(deps): update module github.com/spf13/cobra to v1.10.2 (# 7668) > 6af2f2f Generate semconv/v1.38.0 (# 7648) > 20fdce2 fix(deps): update module github.com/golangci/golangci-lint/v2 to v2.7.0 (# 7661) > 79144c5 chore(deps): update golang.org/x/telemetry digest to 8fff8a5 (# 7654) > d6aef9b chore(deps): update actions/checkout action to v6.0.1 (# 7651) > 98f784d fix(deps): update googleapis to ff82c1b (# 7652) > 5ea2a32 chore(deps): update actions/stale action to v10.1.1 (# 7653) > 85c1dfe chore(deps): update module github.com/godoc-lint/godoc-lint to v0.10.2 (# 7650) > 729bd6e fix(deps): update module go.opentelemetry.io/collector/pdata to v1.47.0 (# 7646) > d5faa3e chore(deps): update github/codeql-action action to v4.31.6 (# 7644) > 495a2c2 chore(deps): update golang.org/x/telemetry digest to abf20d0 (# 7643) > a72103c chore(deps): update module github.com/hashicorp/go-version to v1.8.0 (# 7641) > a0a0acd fix(deps): update golang.org/x to 87e1e73 (# 7636) > c152342 fix(deps): update googleapis to 79d6a2a (# 7634) > 6a16d13 chore(deps): update golang.org/x/telemetry digest to 55bbf37 (# 7633) > d3b4232 chore(deps): update module github.com/tomarrell/wrapcheck/v2 to v2.12.0 (# 7632) > cbcbb2d chore(deps): update github/codeql-action action to v4.31.5 (# 7631) > 414432d chore(deps): update module github.com/go-git/go-git/v5 to v5.16.4 (# 7629) > 7323fc7 chore(deps): update golang.org/x/telemetry digest to e487659 (# 7619) > d799e06 chore(deps): update module github.com/prometheus/common to v0.67.4 (# 7626) > c6e4cca chore(deps): update module dev.gaijin.team/go/golib to v0.8.0 (# 7627) > da01323 otlploghttp: support OTEL_EXPORTER_OTLP_LOGS_INSECURE and OTEL_EXPORTER_OTLP_INSECURE env vars (# 7608) > 4200d1e chore(deps): update actions/checkout action to v6 (# 7624) > 67d264a chore(deps): update module golang.org/x/crypto to v0.45.0 [security] (# 7622) > 7d39542 chore(deps): update module github.com/cyphar/filepath-securejoin to v0.6.1 (# 7618) > 9c98f52 chore(deps): update module go.uber.org/zap to v1.27.1 (# 7620) > 61649a4 chore(deps): update actions/setup-go action to v6.1.0 (# 7623) > 4929285 Replace fnv with xxhash (# 7497) > 98eb065 chore(deps): update github/codeql-action action to v4.31.4 (# 7615) > 5eea765 Upgrade macos tests to latest (# 7597) > dafdbf8 fix(deps): update module go.opentelemetry.io/collector/pdata to v1.46.0 (# 7611) > 205b584 chore(deps): update module github.com/prometheus/common to v0.67.3 (# 7613) > dfffbcf fix(deps): update module google.golang.org/grpc to v1.77.0 (# 7612) > e2f25cd chore(deps): update actions/checkout action to v5.0.1 (# 7609) > af4399f chore(deps): update module go.opentelemetry.io/collector/featuregate to v1.46.0 (# 7610) > 48eaa6c fix(deps): update module github.com/golangci/golangci-lint/v2 to v2.6.2 (# 7604) > e35220d chore(deps): update module github.com/mgechev/revive to v1.13.0 (# 7605) > d75bcb2 chore(deps): update github/codeql-action action to v4.31.3 (# 7603) > 6142eb6 fix(deps): update golang.org/x to e25ba8c (# 7602) > 8c2fb6f chore: exporters/prometheus/internal/x - Generate x package from x component template (# 7491) > cb5b1b6 fix(deps): update module golang.org/x/tools to v0.39.0 (# 7601) > bfb946a chore(deps): update golang.org/x/telemetry digest to 03ef243 (# 7600) > cbe16b6 fix(deps): update googleapis to 95abcf5 (# 7598) > 19a640a chore(deps): update golang.org/x (# 7599) > 1411938 fix(deps): update googleapis to 83f4791 (# 7594) > d1ebd7b fix(deps): update golang.org/x (# 7590) > bcf8234 chore(deps): update module github.com/catenacyber/perfsprint to v0.10.1 (# 7588) > 5c3ba32 chore(deps): update module github.com/maratori/testpackage to v1.1.2 (# 7586) > dff05f9 chore(deps): update module github.com/maratori/testableexamples to v1.0.1 (# 7585) > 093cdb6 chore(deps): update golang.org/x/telemetry digest to 5cc343d (# 7584) > fe47da3 sdk/log: update ExampleProcessor_eventName to use otel.event.name attribute (# 7568) > 13b122c chore(deps): update golang.org/x/telemetry digest to cbe4531 (# 7582) > ac5fbd7 chore(deps): update module github.com/mirrexone/unqueryvet to v1.3.0 (# 7583) > a80ee2d feat: instrument periodic reader from sdk/metric (# 7571) > 22b5704 chore(deps): update otel/weaver docker tag to v0.19.0 (# 7580) > 6120ee8 trace: add fuzz tests for TraceIDFromHex and SpanIDFromHex (# 7577) > ac5b181 fix(deps): update module github.com/golangci/golangci-lint/v2 to v2.6.1 (# 7579) > 389571b chore(deps): update golang.org/x/telemetry digest to ab4e49a (# 7578) > 69dfcc5 fix(deps): update module go.opentelemetry.io/collector/pdata to v1.45.0 (# 7575) > 38203b6 docs: sign artifacts before releasing (# 7567) > 635cf8d docs: remove demo repository update after release (# 7566) > 68190cc Instrument manual reader from sdk/metric (# 7524) > 4438ec3 fix(deps): update module go.opentelemetry.io/proto/otlp to v1.9.0 (# 7570) > 0e4d4ed fix(deps): update googleapis to f26f940 (# 7569) > d0483a7 chore(deps): update module github.com/cyphar/filepath-securejoin to v0.6.0 (# 7564) > 8a930a9 chore(deps): update module github.com/cyphar/filepath-securejoin to v0.5.1 (# 7563) > adbaa43 fix(deps): update build-tools to v0.29.0 (# 7561) > c1dc104 chore(deps): update module github.com/charmbracelet/x/term to v0.2.2 (# 7560) > e308db8 chore: handle Float64Histogram in log/observe errMeter (# 7555) > 5616ce4 chore(deps): update module github.com/go-critic/go-critic to v0.14.2 (# 7559) > d7ceecf fix(deps): update module github.com/golangci/golangci-lint/v2 to v2.6.0 (# 7554) > 893166a chore(deps): update module github.com/prometheus/procfs to v0.19.2 (# 7558) > 6fe90a7 chore(deps): update github/codeql-action action to v4.31.2 (# 7556) > 5ab687f chore(deps): update module github.com/go-critic/go-critic to v0.14.1 (# 7557) > c3eda34 Add disclaimer to span.SetAttributes (# 7550) > 8b61c33 chore(deps): update lycheeverse/lychee-action action to v2.7.0 (# 7552) > 9401e21 fix(deps): update googleapis to ab9386a (# 7553) > b209eca chore(deps): update module github.com/stbenjam/no-sprintf-host-port to v0.3.1 (# 7551) > 89da8a1 chore(deps): update module github.com/prometheus/common to v0.67.2 (# 7547) > ab21764 chore(deps): update golang.org/x/telemetry digest to d7a2859 (# 7546) > 82c8953 chore(deps): update module github.com/karamaru-alpha/copyloopvar to v1.2.2 (# 7545) > 0b0a4d3 chore(deps): update mvdan.cc/unparam digest to 5beb8c8 (# 7544) > c534ddd chore(deps): update module github.com/clipperhouse/uax29/v2 to v2.3.0 (# 7543) > 508eb2e chore(deps): update module github.com/prometheus/procfs to v0.19.1 (# 7542) > bd5db0f chore(deps): update module github.com/ashanbrown/forbidigo/v2 to v2.3.0 (# 7540) > c9b7ecf chore(deps): update module github.com/ashanbrown/makezero/v2 to v2.1.0 (# 7538) > af4f6ae chore(deps): update module github.com/prometheus/procfs to v0.19.0 (# 7539) > c434d43 chore(deps): update github/codeql-action action to v4.31.0 (# 7536) > 3ecd36a chore(deps): update github artifact actions (major) (# 7537) > e71de16 chore(deps): update module github.com/charithe/durationcheck to v0.0.11 (# 7534) > b15942f Added the `internal/observ` package to log (# 7532) > f1ba319 fix(deps): update golang.org/x to a4bb9ff (# 7533) > f0c2457 chore(deps): update golang.org/x/telemetry digest to 5be28d7 (# 7528) > 6f7ffc1 fix(deps): update googleapis to 3a174f9 (# 7529) > 060af76 chore(deps): update module github.com/prometheus/procfs to v0.18.0 (# 7530) > bc28867 Move scorpionknifes to emeritus (# 7526) > 7958d6f chore(deps): update module mvdan.cc/gofumpt to v0.9.2 (# 7527) > eadb423 Instrument the `otlploghttp` exporter (# 7512) > eb87c43 chore(deps): update module github.com/abirdcfly/dupword to v0.1.7 (# 7525) > 26ce126 fix(deps): update module go.opentelemetry.io/collector/pdata to v1.44.0 (# 7523) > 713012c chore(deps): update module go.opentelemetry.io/collector/featuregate to v1.44.0 (# 7522) > f0fefb9 fix(deps): update googleapis to 88f65dc (# 7521) > 56138d1 fix(deps): update golang.org/x (# 7482) > eb7ec3e Simulate failures for histogram creation paths without risking a nil-interface panic (# 7518) > f7d2882 feat: sdk/trace: span processed metric for simple span processor (# 7374) > 2e5fdd1 chore(deps): update github/codeql-action action to v4.30.9 (# 7515) > b5b6989 sdk/log: Fix AddAttributes, SetAttributes, SetBody on Record to not mutate input (# 7403) > f346dec chore: sdk/internal/x - generate x package from shared template (# 7495) > b37822b Prometheus exporter tests: iterate through all scopes rather than looking only at the first (# 7510) > 9dea78c chore(deps): update module github.com/go-critic/go-critic to v0.14.0 (# 7509) > ee0c203 fix(observ): correct rejected items and update comment style (# 7502) > 86e673c chore(deps): update module github.com/godoc-lint/godoc-lint to v0.10.1 (# 7508) > 0e18cf4 fix(deps): update googleapis to 4626949 (# 7506) > b78550d Added the `internal/observ` package to otlploghttp (# 7484) > b3129d3 docs: remove demo-accounting service from dependency list in releasing (# 7503) > 643e735 chore(deps): update module github.com/kunwardeep/paralleltest to v1.0.15 (# 7501) > 1935e60 Fix typos and linguistic errors in documentation / hacktoberfest (# 7494) > fa8e48b OTLP trace exporter include W3C trace flags (bits 0–7) in Span.Flags (# 7438) > 07a26be chore(deps): update module github.com/catenacyber/perfsprint to v0.10.0 (# 7496) > 73cbc69 chore(deps): update github/codeql-action action to v4.30.8 (# 7489) > f58f79b Instrument the `otlptracehttp` exporter (# 7486) > 5c78f7c chore(deps): update module github.com/gofrs/flock to v0.13.0 (# 7487) > 691638a Move sdk/internal/env to sdk/trace/internal/env (# 7437) > c8fc171 Add the `internal/observ` pkg to `otlptracehttp` (# 7480) > 874c4c3 feat: Improve error handling in prometheus exporter (# 7363) > a817caa Add a version const to otlptracehttp (# 7479) > 04ea40e Add the internal `x` package to `otlptracehttp` (# 7476) > 8e8c8c8 chore(deps): update module github.com/ldez/exptostd to v0.4.5 (# 7483) > f89d9f9 chore(deps): update module github.com/nunnatsa/ginkgolinter to v0.21.2 (# 7481) > dc8303b chore(deps): update golang.org/x (# 7477) > 0c97dfd fix(deps): update golang.org/x (# 7475) > 798133c chore(deps): update module github.com/skeema/knownhosts to v1.3.2 (# 7471) > 12bae3a chore(deps): update github/codeql-action action to v4 (# 7472) > 7375147 chore(deps): update module golang.org/x/net to v0.45.0 (# 7470) > 8f0e60d fix(deps): update google.golang.org/genproto/googleapis/rpc digest to 49b9836 (# 7469) > c692bc4 Instrument the `otlptracegrpc` exporter (# 7459) > ce38247 chore(deps): update google.golang.org/genproto/googleapis/api digest to 49b9836 (# 7468) > dd9576c chore(deps): update github/codeql-action action to v3.30.7 (# 7467) > 5f5f4af Document the ordering guarantees provided by the metrics SDK (# 7453) > b64883d chore(deps): update module github.com/prometheus/common to v0.67.1 (# 7465) > 78548fb chore(deps): update module github.com/stretchr/objx to v0.5.3 (# 7464) > c8e3897 Use sync.Map and atomics to improve sum performance (# 7427) > cfd8570 fix(deps): update module go.opentelemetry.io/collector/pdata to v1.43.0 (# 7462) > 681f607 fix(deps): update module google.golang.org/grpc to v1.76.0 (# 7463) > 94f243d chore(deps): update module go.opentelemetry.io/collector/featuregate to v1.43.0 (# 7461) > 11260cd fix(deps): update googleapis to 65f7160 (# 7460) > 14d6372 Add the `internal/observ` package to `otlptracegrpc` (# 7404) > a10652b sdk/trace: trace id high 64 bit tests (# 7212) > 5937fc8 chore(deps): update module github.com/bombsimon/wsl/v5 to v5.3.0 (# 7457) > a39337f chore(deps): update module github.com/go-git/go-git/v5 to v5.16.3 (# 7456) > 64d2bed fix(deps): update build-tools to v0.28.1 (# 7455) > c4bdd87 Support custom error type semantics (# 7442) > 931a5bd chore(deps): update actions/stale action to v10.1.0 (# 7452) > cf1f668 chore(deps): update module github.com/ghostiam/protogetter to v0.3.17 (# 7451) > bd1b3da Add exemplar reservoir parallel benchmarks (# 7441) > dc906d6 chore(deps): update module github.com/grpc-ecosystem/grpc-gateway/v2 to v2.27.3 (# 7450) > 3757239 fix(deps): update googleapis to 7c0ddcb (# 7449) > 7352831 fix(deps): update golang.org/x to 27f1f14 (# 7448) > 5dd35ce feat: logs SDK observability - otlploggrpc exporter metrics (# 7353) > 4b9e111 Skip link checking for acm.org which blocks the link checker (# 7444) > 48cbdac chore(deps): update github/codeql-action action to v3.30.6 (# 7446) > 3ea8606 fix(deps): update module google.golang.org/protobuf to v1.36.10 (# 7445) > dee11e6 Add temporality selector functions (# 7434) > ffeeee8 chore(deps): update peter-evans/create-issue-from-file action to v6 (# 7440) > 862a41a chore(deps): update golang.org/x/telemetry digest to 4eae98a (# 7439) > ea38204 Allow optimizing locking for built-in exemplar reservoirs (# 7423) > 6c54ef6 chore(deps): update ossf/scorecard-action action to v2.4.3 (# 7435) > 4d2b735 chore(deps): update golang.org/x/telemetry digest to 8e64475 (# 7431) > e54c038 chore(deps): update module github.com/charmbracelet/x/ansi to v0.10.2 (# 7432) > addcd63 fix(deps): update googleapis to 57b25ae (# 7429) > 45539cf Only enforce cardinality limits when the attribute set does not already exist (# 7422) > 59ac46c Prometheus exporter: change default translation strategy (# 7421) > 692e519 chore(deps): update module github.com/mattn/go-runewidth to v0.0.19 (# 7428) > 6cb0e90 Generate gRPC Client target parsing func (# 7424) > 81aeace chore(deps): update module go.augendre.info/fatcontext to v0.9.0 (# 7426) > 0db5ac7 sdk/trace/internal/x: generate x package from x component template # 7385 (# 7411) > fef6ee5 chore(deps): update github/codeql-action action to v3.30.5 (# 7425) > 22cfbce Add concurrent safe tests for metric aggregations (# 7379) > fc89784 chore(deps): update module github.com/cyphar/filepath-securejoin to v0.5.0 (# 7419) > bdd4881 chore(deps): update module github.com/mattn/go-runewidth to v0.0.17 (# 7418) > ac8d8e9 Optimize Observability return types in in Prometheus exporter (# 7410) > 88d3fed Optimize the return type of ExportSpans (# 7405) > 63ed041 chore(deps): update module github.com/quasilyte/go-ruleguard/dsl to v0.3.23 (# 7417) > 016b175 chore(deps): update module github.com/quasilyte/go-ruleguard to v0.4.5 (# 7416) > a883fa1 chore(deps): update github/codeql-action action to v3.30.4 (# 7414) > 97a78c1 Add measure benchmarks with exemplars recorded (# 7406) > 2e0b5b4 Add benchmark for synchronous gauge measurement (# 7407) > 97e2244 [chore]: Clean-up unused obsScopeName const (# 7408) > b85e2c1 chore(deps): update actions/cache action to v4.3.0 (# 7409) > 250a11e Add experimental `x` package to `otlptracegrpc` (# 7401) > 466f0cd chore(deps): update module dev.gaijin.team/go/golib to v0.7.0 (# 7402) > 3f05c91 chore(deps): update module github.com/ldez/gomoddirectives to v0.7.1 (# 7400) > 4c9c611 Link checker: ignore https localhost uris (# 7399) > 0cc2eb9 sdk/log: BenchmarkAddAttributes, BenchmarkSetAttributes, BenchmarkSetBody (# 7387) > 80cb909 refactor: replace `context.Background()` with `t.Context()`/`b.Context()` in tests (# 7352) > 2389f44 fix(deps): update module go.opentelemetry.io/collector/pdata to v1.42.0 (# 7397) > 5d3ce38 fix(deps): update googleapis to 9219d12 (# 7393) > dd938b2 fix(deps): update build-tools to v0.28.0 (# 7395) > 4e3152d chore(deps): update module go.opentelemetry.io/build-tools to v0.28.0 (# 7394) > 56498ab chore: sdk/log/internal/x - generate x package from x component template (# 7389) > a579a3e fix(deps): update module github.com/golangci/golangci-lint/v2 to v2.5.0 (# 7392) > de1563e chore(deps): update module github.com/tetafro/godot to v1.5.4 (# 7391) > b5d5bba chore(deps): update module github.com/sagikazarmark/locafero to v0.12.0 (# 7390) > a189c6b sdk/log: add TestRecordMethodsInputConcurrentSafe (# 7378) > 6180f83 Return partial OTLP export errors to the caller (# 7372) > 60f9f39 feat(prometheus): Add observability for prometheus exporter (# 7345) > d1dddde fix(deps): update github.com/opentracing-contrib/go-grpc/test digest to a6e64aa (# 7375) > e4bb37c chore(deps): update otel/weaver docker tag to v0.18.0 (# 7377) > be8e7b2 chore(deps): update module github.com/kulti/thelper to v0.7.1 (# 7376) > b866e36 chore(deps): update module github.com/ldez/grignotin to v0.10.1 (# 7373) > 9d52bde Use Set hash in Distinct (2nd attempt) (# 7175) > 666f95c Fix the typo in test names (# 7369) > 1298533 chore(deps): update module github.com/djarvur/go-err113 to v0.1.1 (# 7368) > 18b114b fix(deps): update module github.com/prometheus/otlptranslator to v1 (# 7358) > 7e4006a chore: generate feature flag files from shared (# 7361) > 5b808c6 Encapsulate SDK Tracer observability (# 7331) > e4ab314 Encapsulate SDK BatchSpanProcessor observability (# 7332) > 6243f21 fix(deps): update module go.opentelemetry.io/auto/sdk to v1.2.1 (# 7365) > 4fdd552 Track context containing span in `recordingSpan` (# 7354) > 59563f7 Do not use the user-defined empty set when comparing sets. (# 7357) > 01611d3 chore(deps): update module github.com/tetafro/godot to v1.5.2 (# 7360) > 305ec06 chore(deps): update module github.com/nunnatsa/ginkgolinter to v0.21.0 (# 7362) > 0d5aa14 chore(deps): update module github.com/antonboom/testifylint to v1.6.4 (# 7359) > 285cbe9 sdk/metric: add example for metricdatatest package (# 7323) > e2da30d trace,metric,log: change WithInstrumentationAttributes to not de-depuplicate the passed attributes in a closure (# 7266) > b168550 fix(deps): update golang.org/x to df92998 (# 7350) > 7fdebbe Rename Self-Observability as just Observability (# 7302) > b06d273 chore(deps): update github/codeql-action action to v3.30.3 (# 7348) > 38d7c39 chore(deps): update module go.yaml.in/yaml/v2 to v2.4.3 (# 7349) > 31629e2 fix(deps): update module google.golang.org/grpc to v1.75.1 (# 7344) > 6d1f9d0 fix(deps): update module golang.org/x/tools to v0.37.0 (# 7347) > df6058a chore(deps): update module golang.org/x/net to v0.44.0 (# 7341) > e26cebf chore(deps): update module github.com/antonboom/nilnil to v1.1.1 (# 7343) > 3baabce Do not allocate instrument options if possible in generated semconv packages (# 7328) > 9b6585a Encapsulate `stdouttrace.Exporter` instrumentation in internal package (# 7307) > a07b7e6 fix(deps): update module google.golang.org/protobuf to v1.36.9 (# 7340) > 71fda10 chore(deps): update golang.org/x (# 7326) > f2ea3f1 chore(deps): update github/codeql-action action to v3.30.2 (# 7339) > 6f50705 fix(deps): update googleapis to 9702482 (# 7335) > c4a6339 chore(deps): update module github.com/spf13/cast to v1.10.0 (# 7333) > 4368300 chore(deps): update module github.com/spf13/viper to v1.21.0 (# 7334) > d0b6f18 fix(deps): update module go.opentelemetry.io/collector/pdata to v1.41.0 (# 7337) > 81c1aca chore(deps): update module github.com/antonboom/errname to v1.1.1 (# 7338) > eb9fd73 chore(deps): update module github.com/lucasb-eyer/go-colorful to v1.3.0 (# 7327) > e759fbd chore(deps): update module github.com/sagikazarmark/locafero to v0.11.0 (# 7329) > cec9d59 chore(deps): update module github.com/spf13/afero to v1.15.0 (# 7330) > 07a91dd trace,metric,log: add WithInstrumentationAttributeSet option (# 7287) > b335c07 Encapsulate observability in Logs SDK (# 7315) > dcf14aa trace,metric,log: WithInstrumentationAttributes options to merge attributes (# 7300) > 63f1ee7 chore(deps): update module mvdan.cc/gofumpt to v0.9.1 (# 7322) > 6f04175 chore(deps): update golang.org/x (# 7324) > 567ef26 Add benchmark for exponential histogram measurements (# 7305) > 8ac554a fix(deps): update golang.org/x (# 7320) > b218e4b Don't track min and max when disabled (# 7306) > 810095e chore(deps): update benchmark-action/github-action-benchmark action to v1.20.7 (# 7319) > f8a9510 fix(deps): update module github.com/prometheus/client_golang to v1.23.2 (# 7314) > 8cea039 chore(deps): update golang.org/x/telemetry digest to af835b0 (# 7313) > 4a0606d chore(deps): update module github.com/pjbgf/sha1cd to v0.5.0 (# 7317) > 2de26d1 chore(deps): update github.com/grafana/regexp digest to f7b3be9 (# 7311) > 97c4e6c chore(deps): update github/codeql-action action to v3.30.1 (# 7312) > e2a4fb3 chore(deps): update golang.org/x/telemetry digest to 9b996f7 (# 7308) > de4b553 chore(deps): update module github.com/bombsimon/wsl/v5 to v5.2.0 (# 7309) > a5dcd68 Add Observability section to CONTRIBUTING doc (# 7272) > 4107421 chore(deps): update codecov/codecov-action action to v5.5.1 (# 7303) > d8d6e52 fix(deps): update module github.com/prometheus/client_golang to v1.23.1 (# 7304) > e54519a chore(deps): update actions/setup-go action to v6 (# 7298) > a0cc03c chore(deps): update actions/stale action to v10 (# 7299) > 8c8cd0a fix(deps): update module go.opentelemetry.io/proto/otlp to v1.8.0 (# 7296) > 83c041a chore(deps): update module mvdan.cc/gofumpt to v0.9.0 (# 7292) > 295fbdc chore(deps): update module github.com/golangci/go-printf-func-name to v0.1.1 (# 7290) > 09e5d69 chore(deps): update module github.com/ghostiam/protogetter to v0.3.16 (# 7289) > 7cb19f9 chore(deps): update benchmark-action/github-action-benchmark action to v1.20.5 (# 7293) > b8f00e3 chore(deps): update module github.com/spf13/pflag to v1.0.10 (# 7291) > 0174808 Fix schema urls (# 7288) > 5e3b939 Add tracetest example for testing instrumentation (# 7107) > 090e9ef chore(deps): update module github.com/spf13/cobra to v1.10.1 (# 7286) > a389393 chore(deps): update github/codeql-action action to v3.30.0 (# 7284) > 6ccc387 chore(deps): update module github.com/spf13/cobra to v1.10.0 (# 7285) > 774c740 Fix missing link in changelog (# 7273) > 5d1ec3a fix(deps): update github.com/opentracing-contrib/go-grpc/test digest to 0261db7 (# 7278) > f74ab34 chore(deps): update module github.com/spf13/pflag to v1.0.9 (# 7282) > b0903af chore(deps): update module github.com/rogpeppe/go-internal to v1.14.1 (# 7283) > 358fa01 fix(deps): update googleapis to ef028d9 (# 7279) > 68b1c4c fix(deps): update module github.com/opentracing-contrib/go-grpc to v0.1.2 (# 7281) > c8632bc fix(deps): update golang.org/x (# 7188) > 18ad4a1 fix(deps): update module github.com/golangci/golangci-lint/v2 to v2.4.0 (# 7277) > c2fea5f chore(deps): update module github.com/securego/gosec/v2 to v2.22.8 (# 7276) > 83403d3 fix(deps): update module go.opentelemetry.io/collector/pdata to v1.40.0 (# 7275) > 8ab8e42 Drop support for Go 1.23 (# 7274) bumping k8s.io/utils 24370be...4c0f3b2: > 4c0f3b2 Add generic buffer.TypedRingGrowing and shrinkable buffer.Ring > 0f33e8f Merge pull request # 326 from aojea/update_owners > 8884aa7 Merge pull request # 325 from aojea/fake_clock_waiters > 3f9e719 update owners > 1f6e0b7 Merge pull request # 321 from xigang/ring_growing > 755dc6a fake clock: expose the number of waiters > 336e707 Add Len and Cap methods for ring buffer bumping golang.org/x/net 2002a06...35e1306: > 35e1306 go.mod: update golang.org/x dependencies > 7c36036 http2, webdav, websocket: fix %q verb uses with wrong type > ec11ecc trace: fix data race in RenderEvents > bff14c5 http2: don't PING a responsive server when resetting a stream > 88a6421 dns/dnsmessage: avoid use of "strings" and "math" in dns/dnsmessage > 123d099 http2: support net/http.Transport.NewClientConn > 346cc61 webdav: relax test to check for any redirect status, not just 301 > 9a29643 go.mod: update golang.org/x dependencies > 07cefd8 context: deprecate > 5ac9dac publicsuffix: don't treat ip addresses as domain names > d1f64cc quic: use testing/synctest > fff0469 http2: document that RFC 7540 prioritization does not work with small payloads > f35e3a4 http2: fix weight overflow in RFC 7540 write scheduler > 89adc90 http2: fix typo referring to RFC 9218 as RFC 9128 instead > 8d76a2c quic: don't defer MAX_STREAMS frames indefinitely > 027f8b7 quic: fix expected ACK Delay in client's ACK after HANDSHAKE_DONE > dec9fe7 dns/dnsmessage: update SVCB packing to prohibit name compression > 9be1ff2 all: fix some comments > 6e243da quic: update Initial keys when handling Retry > 98daa2e quic: send ECN feedback to peers > c296faf net/http2: pool transport gzip readers > ef82ae8 dns/dnsmessage: return an error for too long SVCParam.Value > 3ba82d2 internal/quic/cmd/interop: test ChaCha20 on server > bb2055d dns/dnsmessage: add https svcb dns types > 63d1a51 http2: Allow reading frame header and body separately > 9f2f0b9 http2: avoid data race on DebugGoroutines in TestGoroutineLock > e7c005d http2: implement a more efficient writeQueue that avoids unnecessary copies. > b93acc2 all: use reflect.TypeFor instead of reflect.TypeOf bumping go.opentelemetry.io/otel/trace 84e3f3a...6ce1429: > 6ce1429 Release v1.39.0 (# 7676) > 12e421a sdk/log: move Enabled method from FilterProcessor to Processor (# 7639) > 5982f16 fix(deps): update module golang.org/x/sys to v0.39.0 (# 7684) > 9288378 chore(deps): update module golang.org/x/sync to v0.19.0 (# 7683) > ee3dfef chore(deps): update github.com/securego/gosec/v2 digest to 41f28e2 (# 7682) > 9345d1f fix(deps): update module github.com/golangci/golangci-lint/v2 to v2.7.2 (# 7680) > d03b033 Check context prior to delaying retry in OTLP exporters (# 7678) > 61765e7 Fix flaky `TestClientInstrumentation` (# 7677) > a54721c chore(deps): update module github.com/go-git/go-billy/v5 to v5.7.0 (# 7679) > 746d086 chore(deps): update github/codeql-action action to v4.31.7 (# 7675) > 1bc9713 Regenerate `ErrorType` documentation in `semconv/v1.37.0` (# 7667) > 5a692cf Fix whitespace in `semconv/v1.33.0` (# 7665) > 4eff89b Fix whitespace in `semconv/v1.32.0` (# 7666) > d1825df Fix whitespace in `semconv/v1.36.0` (# 7663) > ddc307d Fix package name documentation and missing copyright in `semconv/v1.32.0` (# 7659) > 3e85447 Fix whitespace in `semconv/v1.34.0` (# 7664) > a64b9ec Fix package documentation name and return error in `semconv/v1.36.0` (# 7656) > be85ff8 Fix whitespace in `semconv/v1.37.0` (# 7660) > cddeb68 Fix package name documentation and missing copyright in `semconv/v1.34.0` (# 7657) > 3659648 Fix package name documentation and missing copyright in `semconv/v1.33.0` (# 7658) > e69beb8 Fix package documentation name and return err in `semconv/v1.37.0` (# 7655) > ddd0420 fix(deps): update module github.com/golangci/golangci-lint/v2 to v2.7.1 (# 7671) > 21e75b9 chore(deps): update module github.com/ldez/gomoddirectives to v0.8.0 (# 7669) > ca53078 Instrument the `SimpleLogProcessor` from sdk/log (# 7548) > df83919 chore(deps): update module github.com/spf13/cobra to v1.10.2 (# 7668) > 6af2f2f Generate semconv/v1.38.0 (# 7648) > 20fdce2 fix(deps): update module github.com/golangci/golangci-lint/v2 to v2.7.0 (# 7661) > 79144c5 chore(deps): update golang.org/x/telemetry digest to 8fff8a5 (# 7654) > d6aef9b chore(deps): update actions/checkout action to v6.0.1 (# 7651) > 98f784d fix(deps): update googleapis to ff82c1b (# 7652) > 5ea2a32 chore(deps): update actions/stale action to v10.1.1 (# 7653) > 85c1dfe chore(deps): update module github.com/godoc-lint/godoc-lint to v0.10.2 (# 7650) > 729bd6e fix(deps): update module go.opentelemetry.io/collector/pdata to v1.47.0 (# 7646) > d5faa3e chore(deps): update github/codeql-action action to v4.31.6 (# 7644) > 495a2c2 chore(deps): update golang.org/x/telemetry digest to abf20d0 (# 7643) > a72103c chore(deps): update module github.com/hashicorp/go-version to v1.8.0 (# 7641) > a0a0acd fix(deps): update golang.org/x to 87e1e73 (# 7636) > c152342 fix(deps): update googleapis to 79d6a2a (# 7634) > 6a16d13 chore(deps): update golang.org/x/telemetry digest to 55bbf37 (# 7633) > d3b4232 chore(deps): update module github.com/tomarrell/wrapcheck/v2 to v2.12.0 (# 7632) > cbcbb2d chore(deps): update github/codeql-action action to v4.31.5 (# 7631) > 414432d chore(deps): update module github.com/go-git/go-git/v5 to v5.16.4 (# 7629) > 7323fc7 chore(deps): update golang.org/x/telemetry digest to e487659 (# 7619) > d799e06 chore(deps): update module github.com/prometheus/common to v0.67.4 (# 7626) > c6e4cca chore(deps): update module dev.gaijin.team/go/golib to v0.8.0 (# 7627) > da01323 otlploghttp: support OTEL_EXPORTER_OTLP_LOGS_INSECURE and OTEL_EXPORTER_OTLP_INSECURE env vars (# 7608) > 4200d1e chore(deps): update actions/checkout action to v6 (# 7624) > 67d264a chore(deps): update module golang.org/x/crypto to v0.45.0 [security] (# 7622) > 7d39542 chore(deps): update module github.com/cyphar/filepath-securejoin to v0.6.1 (# 7618) > 9c98f52 chore(deps): update module go.uber.org/zap to v1.27.1 (# 7620) > 61649a4 chore(deps): update actions/setup-go action to v6.1.0 (# 7623) > 4929285 Replace fnv with xxhash (# 7497) > 98eb065 chore(deps): update github/codeql-action action to v4.31.4 (# 7615) > 5eea765 Upgrade macos tests to latest (# 7597) > dafdbf8 fix(deps): update module go.opentelemetry.io/collector/pdata to v1.46.0 (# 7611) > 205b584 chore(deps): update module github.com/prometheus/common to v0.67.3 (# 7613) > dfffbcf fix(deps): update module google.golang.org/grpc to v1.77.0 (# 7612) > e2f25cd chore(deps): update actions/checkout action to v5.0.1 (# 7609) > af4399f chore(deps): update module go.opentelemetry.io/collector/featuregate to v1.46.0 (# 7610) > 48eaa6c fix(deps): update module github.com/golangci/golangci-lint/v2 to v2.6.2 (# 7604) > e35220d chore(deps): update module github.com/mgechev/revive to v1.13.0 (# 7605) > d75bcb2 chore(deps): update github/codeql-action action to v4.31.3 (# 7603) > 6142eb6 fix(deps): update golang.org/x to e25ba8c (# 7602) > 8c2fb6f chore: exporters/prometheus/internal/x - Generate x package from x component template (# 7491) > cb5b1b6 fix(deps): update module golang.org/x/tools to v0.39.0 (# 7601) > bfb946a chore(deps): update golang.org/x/telemetry digest to 03ef243 (# 7600) > cbe16b6 fix(deps): update googleapis to 95abcf5 (# 7598) > 19a640a chore(deps): update golang.org/x (# 7599) > 1411938 fix(deps): update googleapis to 83f4791 (# 7594) > d1ebd7b fix(deps): update golang.org/x (# 7590) > bcf8234 chore(deps): update module github.com/catenacyber/perfsprint to v0.10.1 (# 7588) > 5c3ba32 chore(deps): update module github.com/maratori/testpackage to v1.1.2 (# 7586) > dff05f9 chore(deps): update module github.com/maratori/testableexamples to v1.0.1 (# 7585) > 093cdb6 chore(deps): update golang.org/x/telemetry digest to 5cc343d (# 7584) > fe47da3 sdk/log: update ExampleProcessor_eventName to use otel.event.name attribute (# 7568) > 13b122c chore(deps): update golang.org/x/telemetry digest to cbe4531 (# 7582) > ac5fbd7 chore(deps): update module github.com/mirrexone/unqueryvet to v1.3.0 (# 7583) > a80ee2d feat: instrument periodic reader from sdk/metric (# 7571) > 22b5704 chore(deps): update otel/weaver docker tag to v0.19.0 (# 7580) > 6120ee8 trace: add fuzz tests for TraceIDFromHex and SpanIDFromHex (# 7577) > ac5b181 fix(deps): update module github.com/golangci/golangci-lint/v2 to v2.6.1 (# 7579) > 389571b chore(deps): update golang.org/x/telemetry digest to ab4e49a (# 7578) > 69dfcc5 fix(deps): update module go.opentelemetry.io/collector/pdata to v1.45.0 (# 7575) > 38203b6 docs: sign artifacts before releasing (# 7567) > 635cf8d docs: remove demo repository update after release (# 7566) > 68190cc Instrument manual reader from sdk/metric (# 7524) > 4438ec3 fix(deps): update module go.opentelemetry.io/proto/otlp to v1.9.0 (# 7570) > 0e4d4ed fix(deps): update googleapis to f26f940 (# 7569) > d0483a7 chore(deps): update module github.com/cyphar/filepath-securejoin to v0.6.0 (# 7564) > 8a930a9 chore(deps): update module github.com/cyphar/filepath-securejoin to v0.5.1 (# 7563) > adbaa43 fix(deps): update build-tools to v0.29.0 (# 7561) > c1dc104 chore(deps): update module github.com/charmbracelet/x/term to v0.2.2 (# 7560) > e308db8 chore: handle Float64Histogram in log/observe errMeter (# 7555) > 5616ce4 chore(deps): update module github.com/go-critic/go-critic to v0.14.2 (# 7559) > d7ceecf fix(deps): update module github.com/golangci/golangci-lint/v2 to v2.6.0 (# 7554) > 893166a chore(deps): update module github.com/prometheus/procfs to v0.19.2 (# 7558) > 6fe90a7 chore(deps): update github/codeql-action action to v4.31.2 (# 7556) > 5ab687f chore(deps): update module github.com/go-critic/go-critic to v0.14.1 (# 7557) > c3eda34 Add disclaimer to span.SetAttributes (# 7550) > 8b61c33 chore(deps): update lycheeverse/lychee-action action to v2.7.0 (# 7552) > 9401e21 fix(deps): update googleapis to ab9386a (# 7553) > b209eca chore(deps): update module github.com/stbenjam/no-sprintf-host-port to v0.3.1 (# 7551) > 89da8a1 chore(deps): update module github.com/prometheus/common to v0.67.2 (# 7547) > ab21764 chore(deps): update golang.org/x/telemetry digest to d7a2859 (# 7546) > 82c8953 chore(deps): update module github.com/karamaru-alpha/copyloopvar to v1.2.2 (# 7545) > 0b0a4d3 chore(deps): update mvdan.cc/unparam digest to 5beb8c8 (# 7544) > c534ddd chore(deps): update module github.com/clipperhouse/uax29/v2 to v2.3.0 (# 7543) > 508eb2e chore(deps): update module github.com/prometheus/procfs to v0.19.1 (# 7542) > bd5db0f chore(deps): update module github.com/ashanbrown/forbidigo/v2 to v2.3.0 (# 7540) > c9b7ecf chore(deps): update module github.com/ashanbrown/makezero/v2 to v2.1.0 (# 7538) > af4f6ae chore(deps): update module github.com/prometheus/procfs to v0.19.0 (# 7539) > c434d43 chore(deps): update github/codeql-action action to v4.31.0 (# 7536) > 3ecd36a chore(deps): update github artifact actions (major) (# 7537) > e71de16 chore(deps): update module github.com/charithe/durationcheck to v0.0.11 (# 7534) > b15942f Added the `internal/observ` package to log (# 7532) > f1ba319 fix(deps): update golang.org/x to a4bb9ff (# 7533) > f0c2457 chore(deps): update golang.org/x/telemetry digest to 5be28d7 (# 7528) > 6f7ffc1 fix(deps): update googleapis to 3a174f9 (# 7529) > 060af76 chore(deps): update module github.com/prometheus/procfs to v0.18.0 (# 7530) > bc28867 Move scorpionknifes to emeritus (# 7526) > 7958d6f chore(deps): update module mvdan.cc/gofumpt to v0.9.2 (# 7527) > eadb423 Instrument the `otlploghttp` exporter (# 7512) > eb87c43 chore(deps): update module github.com/abirdcfly/dupword to v0.1.7 (# 7525) > 26ce126 fix(deps): update module go.opentelemetry.io/collector/pdata to v1.44.0 (# 7523) > 713012c chore(deps): update module go.opentelemetry.io/collector/featuregate to v1.44.0 (# 7522) > f0fefb9 fix(deps): update googleapis to 88f65dc (# 7521) > 56138d1 fix(deps): update golang.org/x (# 7482) > eb7ec3e Simulate failures for histogram creation paths without risking a nil-interface panic (# 7518) > f7d2882 feat: sdk/trace: span processed metric for simple span processor (# 7374) > 2e5fdd1 chore(deps): update github/codeql-action action to v4.30.9 (# 7515) > b5b6989 sdk/log: Fix AddAttributes, SetAttributes, SetBody on Record to not mutate input (# 7403) > f346dec chore: sdk/internal/x - generate x package from shared template (# 7495) > b37822b Prometheus exporter tests: iterate through all scopes rather than looking only at the first (# 7510) > 9dea78c chore(deps): update module github.com/go-critic/go-critic to v0.14.0 (# 7509) > ee0c203 fix(observ): correct rejected items and update comment style (# 7502) > 86e673c chore(deps): update module github.com/godoc-lint/godoc-lint to v0.10.1 (# 7508) > 0e18cf4 fix(deps): update googleapis to 4626949 (# 7506) > b78550d Added the `internal/observ` package to otlploghttp (# 7484) > b3129d3 docs: remove demo-accounting service from dependency list in releasing (# 7503) > 643e735 chore(deps): update module github.com/kunwardeep/paralleltest to v1.0.15 (# 7501) > 1935e60 Fix typos and linguistic errors in documentation / hacktoberfest (# 7494) > fa8e48b OTLP trace exporter include W3C trace flags (bits 0–7) in Span.Flags (# 7438) > 07a26be chore(deps): update module github.com/catenacyber/perfsprint to v0.10.0 (# 7496) > 73cbc69 chore(deps): update github/codeql-action action to v4.30.8 (# 7489) > f58f79b Instrument the `otlptracehttp` exporter (# 7486) > 5c78f7c chore(deps): update module github.com/gofrs/flock to v0.13.0 (# 7487) > 691638a Move sdk/internal/env to sdk/trace/internal/env (# 7437) > c8fc171 Add the `internal/observ` pkg to `otlptracehttp` (# 7480) > 874c4c3 feat: Improve error handling in prometheus exporter (# 7363) > a817caa Add a version const to otlptracehttp (# 7479) > 04ea40e Add the internal `x` package to `otlptracehttp` (# 7476) > 8e8c8c8 chore(deps): update module github.com/ldez/exptostd to v0.4.5 (# 7483) > f89d9f9 chore(deps): update module github.com/nunnatsa/ginkgolinter to v0.21.2 (# 7481) > dc8303b chore(deps): update golang.org/x (# 7477) > 0c97dfd fix(deps): update golang.org/x (# 7475) > 798133c chore(deps): update module github.com/skeema/knownhosts to v1.3.2 (# 7471) > 12bae3a chore(deps): update github/codeql-action action to v4 (# 7472) > 7375147 chore(deps): update module golang.org/x/net to v0.45.0 (# 7470) > 8f0e60d fix(deps): update google.golang.org/genproto/googleapis/rpc digest to 49b9836 (# 7469) > c692bc4 Instrument the `otlptracegrpc` exporter (# 7459) > ce38247 chore(deps): update google.golang.org/genproto/googleapis/api digest to 49b9836 (# 7468) > dd9576c chore(deps): update github/codeql-action action to v3.30.7 (# 7467) > 5f5f4af Document the ordering guarantees provided by the metrics SDK (# 7453) > b64883d chore(deps): update module github.com/prometheus/common to v0.67.1 (# 7465) > 78548fb chore(deps): update module github.com/stretchr/objx to v0.5.3 (# 7464) > c8e3897 Use sync.Map and atomics to improve sum performance (# 7427) > cfd8570 fix(deps): update module go.opentelemetry.io/collector/pdata to v1.43.0 (# 7462) > 681f607 fix(deps): update module google.golang.org/grpc to v1.76.0 (# 7463) > 94f243d chore(deps): update module go.opentelemetry.io/collector/featuregate to v1.43.0 (# 7461) > 11260cd fix(deps): update googleapis to 65f7160 (# 7460) > 14d6372 Add the `internal/observ` package to `otlptracegrpc` (# 7404) > a10652b sdk/trace: trace id high 64 bit tests (# 7212) > 5937fc8 chore(deps): update module github.com/bombsimon/wsl/v5 to v5.3.0 (# 7457) > a39337f chore(deps): update module github.com/go-git/go-git/v5 to v5.16.3 (# 7456) > 64d2bed fix(deps): update build-tools to v0.28.1 (# 7455) > c4bdd87 Support custom error type semantics (# 7442) > 931a5bd chore(deps): update actions/stale action to v10.1.0 (# 7452) > cf1f668 chore(deps): update module github.com/ghostiam/protogetter to v0.3.17 (# 7451) > bd1b3da Add exemplar reservoir parallel benchmarks (# 7441) > dc906d6 chore(deps): update module github.com/grpc-ecosystem/grpc-gateway/v2 to v2.27.3 (# 7450) > 3757239 fix(deps): update googleapis to 7c0ddcb (# 7449) > 7352831 fix(deps): update golang.org/x to 27f1f14 (# 7448) > 5dd35ce feat: logs SDK observability - otlploggrpc exporter metrics (# 7353) > 4b9e111 Skip link checking for acm.org which blocks the link checker (# 7444) > 48cbdac chore(deps): update github/codeql-action action to v3.30.6 (# 7446) > 3ea8606 fix(deps): update module google.golang.org/protobuf to v1.36.10 (# 7445) > dee11e6 Add temporality selector functions (# 7434) > ffeeee8 chore(deps): update peter-evans/create-issue-from-file action to v6 (# 7440) > 862a41a chore(deps): update golang.org/x/telemetry digest to 4eae98a (# 7439) > ea38204 Allow optimizing locking for built-in exemplar reservoirs (# 7423) > 6c54ef6 chore(deps): update ossf/scorecard-action action to v2.4.3 (# 7435) > 4d2b735 chore(deps): update golang.org/x/telemetry digest to 8e64475 (# 7431) > e54c038 chore(deps): update module github.com/charmbracelet/x/ansi to v0.10.2 (# 7432) > addcd63 fix(deps): update googleapis to 57b25ae (# 7429) > 45539cf Only enforce cardinality limits when the attribute set does not already exist (# 7422) > 59ac46c Prometheus exporter: change default translation strategy (# 7421) > 692e519 chore(deps): update module github.com/mattn/go-runewidth to v0.0.19 (# 7428) > 6cb0e90 Generate gRPC Client target parsing func (# 7424) > 81aeace chore(deps): update module go.augendre.info/fatcontext to v0.9.0 (# 7426) > 0db5ac7 sdk/trace/internal/x: generate x package from x component template # 7385 (# 7411) > fef6ee5 chore(deps): update github/codeql-action action to v3.30.5 (# 7425) > 22cfbce Add concurrent safe tests for metric aggregations (# 7379) > fc89784 chore(deps): update module github.com/cyphar/filepath-securejoin to v0.5.0 (# 7419) > bdd4881 chore(deps): update module github.com/mattn/go-runewidth to v0.0.17 (# 7418) > ac8d8e9 Optimize Observability return types in in Prometheus exporter (# 7410) > 88d3fed Optimize the return type of ExportSpans (# 7405) > 63ed041 chore(deps): update module github.com/quasilyte/go-ruleguard/dsl to v0.3.23 (# 7417) > 016b175 chore(deps): update module github.com/quasilyte/go-ruleguard to v0.4.5 (# 7416) > a883fa1 chore(deps): update github/codeql-action action to v3.30.4 (# 7414) > 97a78c1 Add measure benchmarks with exemplars recorded (# 7406) > 2e0b5b4 Add benchmark for synchronous gauge measurement (# 7407) > 97e2244 [chore]: Clean-up unused obsScopeName const (# 7408) > b85e2c1 chore(deps): update actions/cache action to v4.3.0 (# 7409) > 250a11e Add experimental `x` package to `otlptracegrpc` (# 7401) > 466f0cd chore(deps): update module dev.gaijin.team/go/golib to v0.7.0 (# 7402) > 3f05c91 chore(deps): update module github.com/ldez/gomoddirectives to v0.7.1 (# 7400) > 4c9c611 Link checker: ignore https localhost uris (# 7399) > 0cc2eb9 sdk/log: BenchmarkAddAttributes, BenchmarkSetAttributes, BenchmarkSetBody (# 7387) > 80cb909 refactor: replace `context.Background()` with `t.Context()`/`b.Context()` in tests (# 7352) > 2389f44 fix(deps): update module go.opentelemetry.io/collector/pdata to v1.42.0 (# 7397) > 5d3ce38 fix(deps): update googleapis to 9219d12 (# 7393) > dd938b2 fix(deps): update build-tools to v0.28.0 (# 7395) > 4e3152d chore(deps): update module go.opentelemetry.io/build-tools to v0.28.0 (# 7394) > 56498ab chore: sdk/log/internal/x - generate x package from x component template (# 7389) > a579a3e fix(deps): update module github.com/golangci/golangci-lint/v2 to v2.5.0 (# 7392) > de1563e chore(deps): update module github.com/tetafro/godot to v1.5.4 (# 7391) > b5d5bba chore(deps): update module github.com/sagikazarmark/locafero to v0.12.0 (# 7390) > a189c6b sdk/log: add TestRecordMethodsInputConcurrentSafe (# 7378) > 6180f83 Return partial OTLP export errors to the caller (# 7372) > 60f9f39 feat(prometheus): Add observability for prometheus exporter (# 7345) > d1dddde fix(deps): update github.com/opentracing-contrib/go-grpc/test digest to a6e64aa (# 7375) > e4bb37c chore(deps): update otel/weaver docker tag to v0.18.0 (# 7377) > be8e7b2 chore(deps): update module github.com/kulti/thelper to v0.7.1 (# 7376) > b866e36 chore(deps): update module github.com/ldez/grignotin to v0.10.1 (# 7373) > 9d52bde Use Set hash in Distinct (2nd attempt) (# 7175) > 666f95c Fix the typo in test names (# 7369) > 1298533 chore(deps): update module github.com/djarvur/go-err113 to v0.1.1 (# 7368) > 18b114b fix(deps): update module github.com/prometheus/otlptranslator to v1 (# 7358) > 7e4006a chore: generate feature flag files from shared (# 7361) > 5b808c6 Encapsulate SDK Tracer observability (# 7331) > e4ab314 Encapsulate SDK BatchSpanProcessor observability (# 7332) > 6243f21 fix(deps): update module go.opentelemetry.io/auto/sdk to v1.2.1 (# 7365) > 4fdd552 Track context containing span in `recordingSpan` (# 7354) > 59563f7 Do not use the user-defined empty set when comparing sets. (# 7357) > 01611d3 chore(deps): update module github.com/tetafro/godot to v1.5.2 (# 7360) > 305ec06 chore(deps): update module github.com/nunnatsa/ginkgolinter to v0.21.0 (# 7362) > 0d5aa14 chore(deps): update module github.com/antonboom/testifylint to v1.6.4 (# 7359) > 285cbe9 sdk/metric: add example for metricdatatest package (# 7323) > e2da30d trace,metric,log: change WithInstrumentationAttributes to not de-depuplicate the passed attributes in a closure (# 7266) > b168550 fix(deps): update golang.org/x to df92998 (# 7350) > 7fdebbe Rename Self-Observability as just Observability (# 7302) > b06d273 chore(deps): update github/codeql-action action to v3.30.3 (# 7348) > 38d7c39 chore(deps): update module go.yaml.in/yaml/v2 to v2.4.3 (# 7349) > 31629e2 fix(deps): update module google.golang.org/grpc to v1.75.1 (# 7344) > 6d1f9d0 fix(deps): update module golang.org/x/tools to v0.37.0 (# 7347) > df6058a chore(deps): update module golang.org/x/net to v0.44.0 (# 7341) > e26cebf chore(deps): update module github.com/antonboom/nilnil to v1.1.1 (# 7343) > 3baabce Do not allocate instrument options if possible in generated semconv packages (# 7328) > 9b6585a Encapsulate `stdouttrace.Exporter` instrumentation in internal package (# 7307) > a07b7e6 fix(deps): update module google.golang.org/protobuf to v1.36.9 (# 7340) > 71fda10 chore(deps): update golang.org/x (# 7326) > f2ea3f1 chore(deps): update github/codeql-action action to v3.30.2 (# 7339) > 6f50705 fix(deps): update googleapis to 9702482 (# 7335) > c4a6339 chore(deps): update module github.com/spf13/cast to v1.10.0 (# 7333) > 4368300 chore(deps): update module github.com/spf13/viper to v1.21.0 (# 7334) > d0b6f18 fix(deps): update module go.opentelemetry.io/collector/pdata to v1.41.0 (# 7337) > 81c1aca chore(deps): update module github.com/antonboom/errname to v1.1.1 (# 7338) > eb9fd73 chore(deps): update module github.com/lucasb-eyer/go-colorful to v1.3.0 (# 7327) > e759fbd chore(deps): update module github.com/sagikazarmark/locafero to v0.11.0 (# 7329) > cec9d59 chore(deps): update module github.com/spf13/afero to v1.15.0 (# 7330) > 07a91dd trace,metric,log: add WithInstrumentationAttributeSet option (# 7287) > b335c07 Encapsulate observability in Logs SDK (# 7315) > dcf14aa trace,metric,log: WithInstrumentationAttributes options to merge attributes (# 7300) > 63f1ee7 chore(deps): update module mvdan.cc/gofumpt to v0.9.1 (# 7322) > 6f04175 chore(deps): update golang.org/x (# 7324) > 567ef26 Add benchmark for exponential histogram measurements (# 7305) > 8ac554a fix(deps): update golang.org/x (# 7320) > b218e4b Don't track min and max when disabled (# 7306) > 810095e chore(deps): update benchmark-action/github-action-benchmark action to v1.20.7 (# 7319) > f8a9510 fix(deps): update module github.com/prometheus/client_golang to v1.23.2 (# 7314) > 8cea039 chore(deps): update golang.org/x/telemetry digest to af835b0 (# 7313) > 4a0606d chore(deps): update module github.com/pjbgf/sha1cd to v0.5.0 (# 7317) > 2de26d1 chore(deps): update github.com/grafana/regexp digest to f7b3be9 (# 7311) > 97c4e6c chore(deps): update github/codeql-action action to v3.30.1 (# 7312) > e2a4fb3 chore(deps): update golang.org/x/telemetry digest to 9b996f7 (# 7308) > de4b553 chore(deps): update module github.com/bombsimon/wsl/v5 to v5.2.0 (# 7309) > a5dcd68 Add Observability section to CONTRIBUTING doc (# 7272) > 4107421 chore(deps): update codecov/codecov-action action to v5.5.1 (# 7303) > d8d6e52 fix(deps): update module github.com/prometheus/client_golang to v1.23.1 (# 7304) > e54519a chore(deps): update actions/setup-go action to v6 (# 7298) > a0cc03c chore(deps): update actions/stale action to v10 (# 7299) > 8c8cd0a fix(deps): update module go.opentelemetry.io/proto/otlp to v1.8.0 (# 7296) > 83c041a chore(deps): update module mvdan.cc/gofumpt to v0.9.0 (# 7292) > 295fbdc chore(deps): update module github.com/golangci/go-printf-func-name to v0.1.1 (# 7290) > 09e5d69 chore(deps): update module github.com/ghostiam/protogetter to v0.3.16 (# 7289) > 7cb19f9 chore(deps): update benchmark-action/github-action-benchmark action to v1.20.5 (# 7293) > b8f00e3 chore(deps): update module github.com/spf13/pflag to v1.0.10 (# 7291) > 0174808 Fix schema urls (# 7288) > 5e3b939 Add tracetest example for testing instrumentation (# 7107) > 090e9ef chore(deps): update module github.com/spf13/cobra to v1.10.1 (# 7286) > a389393 chore(deps): update github/codeql-action action to v3.30.0 (# 7284) > 6ccc387 chore(deps): update module github.com/spf13/cobra to v1.10.0 (# 7285) > 774c740 Fix missing link in changelog (# 7273) > 5d1ec3a fix(deps): update github.com/opentracing-contrib/go-grpc/test digest to 0261db7 (# 7278) > f74ab34 chore(deps): update module github.com/spf13/pflag to v1.0.9 (# 7282) > b0903af chore(deps): update module github.com/rogpeppe/go-internal to v1.14.1 (# 7283) > 358fa01 fix(deps): update googleapis to ef028d9 (# 7279) > 68b1c4c fix(deps): update module github.com/opentracing-contrib/go-grpc to v0.1.2 (# 7281) > c8632bc fix(deps): update golang.org/x (# 7188) > 18ad4a1 fix(deps): update module github.com/golangci/golangci-lint/v2 to v2.4.0 (# 7277) > c2fea5f chore(deps): update module github.com/securego/gosec/v2 to v2.22.8 (# 7276) > 83403d3 fix(deps): update module go.opentelemetry.io/collector/pdata to v1.40.0 (# 7275) > 8ab8e42 Drop support for Go 1.23 (# 7274) bumping k8s.io/apiextensions-apiserver d7702f9...a4ffeda: > a4ffeda Update dependencies to v0.34.3 tag > 4a9fea1 Merge pull request # 133901 from yongruilin/automated-cherry-pick-of-# 133896-upstream-release-1.34 > 3896d9f fix: Only warn for unrecognized formats on type=string > aada5e8 Merge remote-tracking branch 'origin/master' into release-1.34 > bad5b2a clarify that staging repos are automatically published > f498996 add pointer to CONTRIBUTING.md for more details on contributing, clarify read-only > f782221 link to what a staging repository is > 3625d64 docs: clarify that this is a staging repository and not for direct contributions > 71e26b6 Bump etcd sdk to v3.6.4 > 056a425 Merge pull request # 133180 from ylink-lfs/chore/ptr_cast_replace > 63d27f2 Merge pull request # 132942 from thockin/kyaml > f5ec1b6 chore: replace ptr caster with unified ptr.To > 6f8ce15 Allow white-spaced CABundle during webhook client creation and validation (# 132514) > 4a2cee4 Re-vendor sigs.k8s.io/yaml @ v1.6.0 > 69bd66b Merge pull request # 132935 from benluddy/cbor-bump-custom-marshalers > 243cae7 Merge pull request # 132837 from JoelSpeed/fix-max-elements-x-int-or-string > a5cddfa Bump to github.com/fxamacker/cbor/v2 v2.9.0. > 041e1fe Merge pull request # 133136 from yongruilin/crd-format-warning > d494e38 Add test case to prove MaxElements correctly set on IntOrString > 73c2e76 Merge pull request # 133104 from aman4433/chore/replace-float64ptr-with-ptrTo > 7f83928 feat: Implement warnings for unrecognized formats in CRDs > af2d308 chore: replace float64Ptr with ptr.To helper in validation and integration tests > 45ab34f feat: Add func to export the supportedVersionedFormats > ca04741 Merge pull request # 131700 from cici37/celList > 07fddd6 Add support for CEL list library. > bcc01b2 Merge pull request # 133010 from cici37/promote-Cel > 08239fc Update cel-go to v0.26.0 > 10bfaba Merge pull request # 133020 from pohly/apimachinery-list-map-keys > 79701ff Merge pull request # 131293 from skitt/typo-watchAction > 3876cff sigs.k8s.io/structured-merge-diff/v6 v6.3.0 > 9fbc252 Merge pull request # 132871 from dims/bump-k8s.io/kube-openapi-to-latest-SHA-f3f2b991d03b > 9990808 Typo fix: watchActcion > f6db3df Merge pull request # 132513 from xiaoweim/validation-cleanup-invalid > c072574 Bump k8s.io/kube-openapi to latest SHA (f3f2b991d03b) > a2eb7d0 address review comments > 890aa9b Cleanup: Remove field name from invalid field detail message > 83f842a Merge pull request # 132920 from ylink-lfs/chore/maxptr_removal > 3ca544d chore: maxPtr utility removal with ptr.To > 5505d2e Merge pull request # 132845 from ylink-lfs/chore/int64ptr_removal > d1cc299 chore: replace int64ptr with ptr.To > bdaffda Merge pull request # 132819 from ylink-lfs/chore/uint64ptr_usage_removal > 2149a4d chore: remove residual uint64ptr usage with ptr package > 6ffe60d Merge pull request # 132788 from ylink-lfs/chore/strptr_removal > 2923d2d chore: remove strPtr usage with ptr.To instead > 0366b97 Merge pull request # 132723 from PatrickLaabs/132086-apiextensions > baaa8ae Merge pull request # 132726 from PatrickLaabs/132086-apiext-apiserver-validation > f657946 chore: depr. pointer pkg replacement for apiextensions in general > 68ece1e Merge pull request # 132721 from PatrickLaabs/132086-apiext-integration > fe7f7a6 chore: depr. pointer pkg replacement for apiext. apiservers validations > e745655 Merge pull request # 132724 from PatrickLaabs/132086-apiext-controller > e1fdac0 chore: depr. pointer pkg replacement for apiext. integration > 0aa4758 Merge pull request # 132725 from PatrickLaabs/132086-apiext-registry > 5cdcaff chore: depr. pointer pkg replacement for apiext. pkg/cntroller > d867714 chore: depr. pointer pkg replacement for apiext. pkg/registry > 412ddb5 Merge pull request # 132314 from thockin/jp_nicer_api_errors > 9658f29 Merge pull request # 132675 from dims/bump-sigs-k8s-io-json-no-code-changes > 0ef7765 WIP: Fix tests > 18a002b Merge pull request # 132677 from dims/update-github.com/emicklei/go-restful/v3-to-v3.12.2 > 9f725bb Bump sigs.k8s.io/json to latest - no code changes > df1e55a Merge pull request # 132676 from dims/bump-go.yaml.in/yaml/v3-to-v3.0.4 > f882edd Update github.com/emicklei/go-restful/v3 to v3.12.2 > f7ec911 Bump go.yaml.in/yaml/v3 to v3.0.4 > fa53f68 Merge pull request # 132654 from Jefftree/b-openapi > 88058dd Update vendor > 2822a00 pin kube-openapi to v0.0.0-20250628140032-d90c4fd18f59 > 184ebbc Merge pull request # 132472 from xiaoweim/validation-cleanup > 0648b56 Merge pull request # 132584 from alvaroaleman/fix-asfafs > f0841a7 Cleanup: Remove redundant detail messages in field.Required > fbfcda5 Merge pull request # 132438 from dims/golangci-plugin-for-sorting-feature-gates > b38c31b Re-generate applyconfigurations > d045c7f Ensure all the files have the updated sorting > 16011e1 Merge pull request # 132357 from dims/drop-usage-of-forked-copies-of-goyaml.v2-and-goyaml.v3 > b0ff13f Merge pull request # 132517 from michaelasp/fixflake > cf24e04 switch to latest sigs.k8s.io/yaml v1.5.0 (run update-gofmt.sh as well) > d7e6677 Merge pull request # 132504 from jpbetz/name-formats > db34fc0 fix: Add wait for cache sync for customresourcediscovery tests > beaf7e4 Drop usage of forked copies of goyaml.v2 and goyaml.v3 > c895380 Merge pull request # 132467 from sdowell/ssa-terminating > 4035caa Add printer column validation tests > e732112 fix: prevent SSA from creating CR while CRD terminating > 01feeeb Introduce k8s-short-name and k8s-long-name to the OpenAPI formats supported by CRDs > 1c5db78 Merge pull request # 132194 from alvaroaleman/local0dev > 2ec638a Bump to latest kube-openapi > 35570bf Test that generated applyconfigs are a runtime.ApplyConfig > 46aa98f Re-Generate applyconfigs > b6ed36e Merge pull request # 132269 from dims/update-to-latest-github.com/modern-go/reflect2 > 404e9dd Update to latest github.com/modern-go/reflect2 > d7893ce Merge pull request # 132239 from dims/update-to-etcd-3.6.1-in-vendor > 1b90fee Update to etcd v3.6.1 in vendor/ > 0fe9bd2 Merge pull request # 132209 from dims/update-github.com/spf13/cobra-v1.9.1eksctl > d0531ef update github.com/spf13/cobra v1.9.1 > df69040 Merge pull request # 132103 from nojnhuh/typed-ring-buffer > 4b42467 Merge pull request # 132110 from jpbetz/gengo-bump > 3d9987c Update k8s.io/utils for new generic ring buffer > f0171f1 Bump gengo/v2 to latest > 1e69f9c Merge pull request # 131951 from dims/drop-usages-of-deprecated-otelgrpc-methods > de00027 Merge pull request # 131664 from jpbetz/subresources-enable-replicas > 66c0b02 Drop usages of deprecated otelgrpc methods > d928225 generate code > 80ea9f4 Merge pull request # 131838 from dims/bump-google.golang.org/grpc-to-google-v1.72.1 > 080d7cc Bump google.golang.org/grpc v1.72.1 > cf935fa Merge pull request # 128419 from liggitt/etcd-3.6 > f39952d bump etcd client to 3.6 > fc47812 Merge pull request # 131777 from BenTheElder/exit-codes > 6c2c4f7 verify scripts: preserve exit code > daf1c52 Merge pull request # 131616 from jpbetz/typeconverter-cleanup > b6bb912 Reorganize scheme type converter into apimachinery utils > be7b464 Merge pull request # 131477 from pohly/golangci-lint@v2 > f3d6de6 Merge pull request # 131595 from aojea/utils_fake_clock > 44000e7 chore: bump golangci-lint to v2 > 4b2d6d4 update k8s.io/utils to bring fakeClock.Waiters() > 83f8037 Merge pull request # 130989 from liggitt/creationTimestamp-omitzero > 7451720 Drop null creationTimestamp from test fixtures > 6207442 bump cbor to add omitzero support > f699de7 bump structured-merge-diff to add omitzero support > bf1974c Merge pull request # 131434 from pacoxu/fsnotify > 7396204 Merge pull request # 131444 from erdii/update-cel-go > e79d54b bump fsnotify v1.9.0 > d63635a chore: update github.com/google/cel-go dependency to v0.25.0 > 1e127a9 Merge pull request # 130995 from xigang/utils > f05aec0 Merge pull request # 131204 from dims/move-to-released-version-of-prometheus/client_golang-v1.22.0-from-rc.0 > 3f819e6 bump k8s.io/utils > 04f887e Move to released version of prometheus/client_golang v1.22.0 from rc.0 > ec6bea2 Merge pull request # 131103 from ahrtr/etcd_sdk_20250328 Signed-off-by: Knative Automation * upgrade to latest dependencies (#718) bumping golang.org/x/sys 08e5482...2f44229: > 2f44229 sys/cpu: add symbolic constants for remaining cpuid bits > e5770d2 sys/cpu: use symbolic names for masks > 714a44c sys/cpu: modify x86 port to match what internal/cpu does bumping golang.org/x/term 3863673...a7e5b04: > a7e5b04 go.mod: update golang.org/x dependencies > 943f25d x/term: handle transpose > 9b991dd x/term: handle delete key bumping golang.org/x/mod d271cf3...4c04067: > 4c04067 go.mod: update golang.org/x dependencies bumping golang.org/x/crypto 19acf81...506e022: > 506e022 go.mod: update golang.org/x dependencies > 7dacc38 chacha20poly1305: error out in fips140=only mode bumping golang.org/x/tools 00b22d9...2ad2b30: > 2ad2b30 go.mod: update golang.org/x dependencies > 5832cce internal/diff/lcs: introduce line diffs > 67c4257 gopls/internal/golang: Definition: fix Windows bug wrt //go:embed > 12c1f04 gopls/completion: check Selection invariant > 6d87185 internal/server: add vulncheck scanning after vulncheck prompt > 0c3a1fe go/ast/inspector: FindByPos returns the first innermost node > ca281cf go/analysis/passes/ctrlflow: add noreturn funcs from popular pkgs > 09c21a9 gopls/internal/analysis/unusedfunc: remove warnings for unused enum consts > 03cb455 internal/modindex: suppress missing modcacheindex message > 15d13e8 gopls/internal/util/typesutil: refine EnclosingSignature bug.Report > 02e1c6b gopls/internal/analysis/modernize: mapsloop: undefined loop-var > 41cca47 go/analysis/passes/modernize: fix stringsbuilder bug with in var(...) > cfae896 go/analysis/passes/modernize: stringsbuilder: avoid overlapping fixes > 0f94e53 go/{cfg,analysis/passes/{ctrlflow,buildssa}}: noreturn > 78e6aac go/analysis/passes/modernize: treat 'i += 1' as 'i++' > f12a0ae gopls/benchmark: skip unimported completion if not local > 5ca1b57 gopls/internal/server: disable TestVulncheckPreference for wasm > cee4451 go/analysis/passes/stdversion: suppress synctest warning > 267fc6b go/analysis/passes/modernize: disable BLoop analyzer > 761a007 gopls/mod: find references to required modules > 9136dd1 go/analysis/passes/modernize: document stringsbuilder/QF1012 synergy > eaaac7c gopls/internal/server: store vulncheck prompt preference > 24c2d6d go/analysis,gopls: update two modernizes' URLs > 61da5cd internal/astutil: return enclosing node when selection contains no nodes > 454a5c0 gopls/internal/server: disable TestCheckGoModDeps for js and wasm > acdd27b gopls/internal/analysis/modernize: fix URL > 95246c4 go/analysis/passes/modernize: rangeint: result vars are implicit uses > 6c613c8 gopls/internal/cache/parsego: construct File.Cursor lazily > 60782aa internal: fix unused errors reported by ineffassign > 3f45d3b internal/stdlib: update stdlib index for Go 1.26 Release Candidate 1 > 5a815de go/types/internal/play: show astutil.Select results too > 7f7199c gopls/internal/protocol: patch RenameParams to extend PositionParams > 1fe6e2b gopls/internal/server: modify checkGoModDeps condition > 995ffe4 gopls/internal/golang: implement type definition for builtin types > 1d652d9 gopls/internal/test/marker: add optional error to typedef marker > dcf2e8d gopls/internal/server: support type def with selected range > 54e2c60 gopls/internal/cache: avoid unnecessary mod tidy invalidation > 29e97f8 gopls/internal/marker: make checkDiffs less fragile > 1f3446a gopls/go.mod: update dependencies following the v0.21.0 release > f719e32 gopls/internal/server: consolidate showMessageRequest in prompt.go > 01fd755 gopls/internal/server: trigger vulncheck prompt for go.mod changes bumping golang.org/x/text 0dd57a6...536231a: > 536231a go.mod: update golang.org/x dependencies bumping knative.dev/pkg 116bf6c...af2d223: > af2d223 Bump the golang-x group with 2 updates (# 3311) > 8c84044 Add OnConnect and OnDisconnect callbacks to ManagedConnection (# 3309) bumping golang.org/x/net 35e1306...d977772: > d977772 go.mod: update golang.org/x dependencies > eea413e internal/http3: use go1.25 synctest.Test instead of go1.24 synctest.Run > 9ace223 websocket: add missing call to resp.Body.Close > 7d3dbb0 http2: buffer the most recently received PRIORITY_UPDATE frame bumping knative.dev/eventing 12a8924...59b517c: > 59b517c Run hack/update-codegen.sh on Dependabot PRs (# 8805) > bb47496 [Automated] Update eventing-eventing-integrations nightly (# 8842) Signed-off-by: Knative Automation * upgrade to latest dependencies (#721) bumping knative.dev/reconciler-test b74774d...d1b946d: > d1b946d upgrade to latest dependencies (# 853) bumping knative.dev/hack ee8a1b2...bf6758c: > bf6758c bump GKE default version to 1.33 (# 458) bumping knative.dev/pkg af2d223...4a022ed: > 4a022ed upgrade to latest dependencies (# 3313) > 49cf63e bump K8s min version to 1.33 (# 3312) Signed-off-by: Knative Automation * update-codegen * fixed deprecated api * fixed tests * ran update-codegen * fixed permissions for new endpointslices * upgrade to latest dependencies (#725) bumping knative.dev/eventing 59b517c...cb840ef: > cb840ef fix: Wait for full deployment rollout before marking IntegrationSink Ready (# 8858) > 86c43a6 Increase poll timings for IntegrationSource tests (# 8860) > 3226294 Prevent AuthZ test pollution by ensuring unready test runs last (# 8859) > ccf232a fix unused linter errors (# 8851) > 4303f77 Remove k8s 1.32 runs in KinD e2e tests (# 8857) > e169933 Run eventing office hours Slack reminder every other week (# 8855) > 71a6c9f fix: EventTransform not updating when expression changes (# 8848) > 45c284b [main] Upgrade to latest dependencies (# 8847) bumping knative.dev/reconciler-test d1b946d...4301404: > 4301404 upgrade to latest dependencies (# 854) Signed-off-by: Knative Automation --------- Signed-off-by: Knative Automation Co-authored-by: Knative Automation --- cmd/broker/main.go | 4 +- .../200-jsm-controller-clusterrole.yaml | 12 +- pkg/broker/trigger/controller.go | 189 +++++ pkg/broker/trigger/controller_test.go | 377 ++++++++++ pkg/broker/trigger/reconciler.go | 305 +++++++++ pkg/broker/trigger/reconciler_test.go | 648 ++++++++++++++++++ pkg/broker/utils/naming.go | 6 +- .../jetstream/controller/controller.go | 7 - .../controller/natsjetstreamchannel_test.go | 36 +- .../jetstream/controller/reconciler.go | 40 +- pkg/reconciler/testing/listers.go | 6 +- test/e2e/cmd/producer/main.go | 7 +- .../eventing/v1/trigger/controller.go | 178 +++++ .../eventing/v1/trigger/reconciler.go | 561 +++++++++++++++ .../reconciler/eventing/v1/trigger/state.go | 97 +++ .../informers/core/v1/endpoints/endpoints.go | 52 -- vendor/modules.txt | 2 +- 17 files changed, 2434 insertions(+), 93 deletions(-) create mode 100644 pkg/broker/trigger/controller.go create mode 100644 pkg/broker/trigger/controller_test.go create mode 100644 pkg/broker/trigger/reconciler.go create mode 100644 pkg/broker/trigger/reconciler_test.go create mode 100644 vendor/knative.dev/eventing/pkg/client/injection/reconciler/eventing/v1/trigger/controller.go create mode 100644 vendor/knative.dev/eventing/pkg/client/injection/reconciler/eventing/v1/trigger/reconciler.go create mode 100644 vendor/knative.dev/eventing/pkg/client/injection/reconciler/eventing/v1/trigger/state.go delete mode 100644 vendor/knative.dev/pkg/client/injection/kube/informers/core/v1/endpoints/endpoints.go diff --git a/cmd/broker/main.go b/cmd/broker/main.go index 86e9881e1..d10a82bb5 100644 --- a/cmd/broker/main.go +++ b/cmd/broker/main.go @@ -28,7 +28,7 @@ import ( "knative.dev/pkg/signals" "knative.dev/eventing-natss/pkg/broker/controller" - // "knative.dev/eventing-natss/pkg/broker/trigger" + "knative.dev/eventing-natss/pkg/broker/trigger" "knative.dev/eventing-natss/pkg/common/configloader/fsloader" ) @@ -44,6 +44,6 @@ func main() { sharedmain.MainWithContext(ctx, controller.ComponentName, controller.NewController, - // trigger.NewController, + trigger.NewController, ) } diff --git a/config/jetstream/200-jsm-controller-clusterrole.yaml b/config/jetstream/200-jsm-controller-clusterrole.yaml index 985c60f57..2e41fc22b 100644 --- a/config/jetstream/200-jsm-controller-clusterrole.yaml +++ b/config/jetstream/200-jsm-controller-clusterrole.yaml @@ -49,12 +49,20 @@ rules: - apiGroups: - "" resources: - - endpoints - pods verbs: - get - list - watch + # EndpointSlices for checking dispatcher readiness + - apiGroups: + - discovery.k8s.io + resources: + - endpointslices + verbs: + - get + - list + - watch - apiGroups: - "" resources: @@ -101,4 +109,4 @@ rules: verbs: - get resourceNames: - - jetstream-ch-controller \ No newline at end of file + - jetstream-ch-controller diff --git a/pkg/broker/trigger/controller.go b/pkg/broker/trigger/controller.go new file mode 100644 index 000000000..5ecc71b12 --- /dev/null +++ b/pkg/broker/trigger/controller.go @@ -0,0 +1,189 @@ +/* +Copyright 2026 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package trigger + +import ( + "context" + + "github.com/kelseyhightower/envconfig" + "go.uber.org/zap" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" + + "knative.dev/pkg/configmap" + "knative.dev/pkg/controller" + "knative.dev/pkg/logging" + "knative.dev/pkg/resolver" + + eventingv1 "knative.dev/eventing/pkg/apis/eventing/v1" + brokerinformer "knative.dev/eventing/pkg/client/injection/informers/eventing/v1/broker" + triggerinformer "knative.dev/eventing/pkg/client/injection/informers/eventing/v1/trigger" + triggerreconciler "knative.dev/eventing/pkg/client/injection/reconciler/eventing/v1/trigger" + eventinglisters "knative.dev/eventing/pkg/client/listers/eventing/v1" + + "knative.dev/eventing-natss/pkg/broker/constants" + "knative.dev/eventing-natss/pkg/common/configloader/fsloader" + commonnats "knative.dev/eventing-natss/pkg/common/nats" +) + +const ( + // ComponentName is the name of this controller component + ComponentName = "natsjs-trigger-controller" +) + +// envConfig holds configuration from environment variables +type envConfig struct { + FilterServiceName string `envconfig:"FILTER_SERVICE_NAME" default:"natsjs-broker-filter"` +} + +// NewController creates a new controller for the NATS JetStream Trigger +func NewController( + ctx context.Context, + cmw configmap.Watcher, +) *controller.Impl { + logger := logging.FromContext(ctx) + + // Load environment configuration + var env envConfig + if err := envconfig.Process("", &env); err != nil { + logger.Fatalw("Failed to process environment variables", zap.Error(err)) + } + + logger.Infow("Trigger controller configuration", + zap.String("filter_service_name", env.FilterServiceName), + ) + + // Get the config loader from context + fsLoader, err := fsloader.Get(ctx) + if err != nil { + logger.Fatalw("Failed to get ConfigmapLoader from context", zap.Error(err)) + } + + // Load NATS configuration from mounted ConfigMap + configMap, err := fsLoader(constants.SettingsConfigMapMountPath) + if err != nil { + logger.Fatalw("Failed to load NATS configmap", zap.Error(err)) + } + + // Parse NATS configuration + natsConfig, err := commonnats.LoadEventingNatsConfig(configMap) + if err != nil { + logger.Fatalw("Failed to parse NATS configuration", zap.Error(err)) + } + + // Create NATS connection + natsConn, err := commonnats.NewNatsConn(ctx, natsConfig) + if err != nil { + logger.Fatalw("Failed to create NATS connection", zap.Error(err)) + } + + // Create JetStream context + js, err := natsConn.JetStream() + if err != nil { + logger.Fatalw("Failed to create JetStream context", zap.Error(err)) + } + + // Get informers + triggerInformer := triggerinformer.Get(ctx) + brokerInformer := brokerinformer.Get(ctx) + + // Create reconciler + r := &Reconciler{ + brokerLister: brokerInformer.Lister(), + js: js, + filterServiceName: env.FilterServiceName, + } + + // Create controller implementation using the generated NewImpl + impl := triggerreconciler.NewImpl(ctx, r, func(impl *controller.Impl) controller.Options { + return controller.Options{ + AgentName: ComponentName, + PromoteFilterFunc: filterTriggersByBrokerClass(r), + } + }) + + // Create URI resolver for resolving subscriber and dead letter sink addresses + r.uriResolver = resolver.NewURIResolverFromTracker(ctx, impl.Tracker) + + // Set up event handlers for Trigger resources + triggerInformer.Informer().AddEventHandler(cache.FilteringResourceEventHandler{ + FilterFunc: filterTriggersByBrokerClass(r), + Handler: controller.HandleAll(impl.Enqueue), + }) + + // Watch Broker resources to re-reconcile triggers when broker changes + brokerInformer.Informer().AddEventHandler(cache.FilteringResourceEventHandler{ + FilterFunc: filterBrokersByClass, + Handler: controller.HandleAll(enqueueTriggerOfBroker(triggerInformer.Lister(), impl)), + }) + + logger.Info("NATS JetStream Trigger controller initialized") + + return impl +} + +// filterTriggersByBrokerClass returns a filter function that only passes triggers +// referencing brokers of class NatsJetStreamBroker +func filterTriggersByBrokerClass(r *Reconciler) func(obj interface{}) bool { + return func(obj interface{}) bool { + trigger, ok := obj.(*eventingv1.Trigger) + if !ok { + return false + } + + // Get the broker referenced by this trigger + broker, err := r.brokerLister.Brokers(trigger.Namespace).Get(trigger.Spec.Broker) + if err != nil { + // If we can't get the broker, let the reconciler handle the error + return true + } + + // Check if the broker is of class NatsJetStreamBroker + return broker.GetAnnotations()[eventingv1.BrokerClassAnnotationKey] == constants.BrokerClassName + } +} + +// filterBrokersByClass filters brokers by their class annotation +func filterBrokersByClass(obj interface{}) bool { + broker, ok := obj.(*eventingv1.Broker) + if !ok { + return false + } + return broker.GetAnnotations()[eventingv1.BrokerClassAnnotationKey] == constants.BrokerClassName +} + +// enqueueTriggerOfBroker returns a handler that enqueues all triggers associated with a broker +func enqueueTriggerOfBroker(lister eventinglisters.TriggerLister, impl *controller.Impl) func(obj interface{}) { + return func(obj interface{}) { + broker, ok := obj.(*eventingv1.Broker) + if !ok { + return + } + + // Find all triggers in the same namespace that reference this broker + triggers, err := lister.Triggers(broker.Namespace).List(labels.Everything()) + if err != nil { + return + } + + for _, trigger := range triggers { + if trigger.Spec.Broker == broker.Name { + impl.Enqueue(trigger) + } + } + } +} diff --git a/pkg/broker/trigger/controller_test.go b/pkg/broker/trigger/controller_test.go new file mode 100644 index 000000000..e266a2690 --- /dev/null +++ b/pkg/broker/trigger/controller_test.go @@ -0,0 +1,377 @@ +/* +Copyright 2026 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package trigger + +import ( + "testing" + + apierrs "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime/schema" + + eventingv1 "knative.dev/eventing/pkg/apis/eventing/v1" + eventinglisters "knative.dev/eventing/pkg/client/listers/eventing/v1" + + "knative.dev/eventing-natss/pkg/broker/constants" +) + +const ( + testNamespace = "test-namespace" + testBrokerName = "test-broker" + testTriggerName = "test-trigger" + testTriggerUID = "test-trigger-uid-12345" +) + +// newTestBroker creates a test broker with the given class annotation +func newTestBroker(namespace, name, brokerClass string) *eventingv1.Broker { + annotations := make(map[string]string) + if brokerClass != "" { + annotations[eventingv1.BrokerClassAnnotationKey] = brokerClass + } + return &eventingv1.Broker{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: name, + Annotations: annotations, + }, + } +} + +// newTestTrigger creates a test trigger referencing a broker +func newTestTrigger(namespace, name, brokerName string) *eventingv1.Trigger { + return &eventingv1.Trigger{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: name, + UID: "test-uid", + }, + Spec: eventingv1.TriggerSpec{ + Broker: brokerName, + }, + } +} + +// fakeBrokerLister implements the BrokerLister interface for testing +type fakeBrokerLister struct { + brokers map[string]map[string]*eventingv1.Broker +} + +func newFakeBrokerLister() *fakeBrokerLister { + return &fakeBrokerLister{ + brokers: make(map[string]map[string]*eventingv1.Broker), + } +} + +func (f *fakeBrokerLister) addBroker(broker *eventingv1.Broker) { + if f.brokers[broker.Namespace] == nil { + f.brokers[broker.Namespace] = make(map[string]*eventingv1.Broker) + } + f.brokers[broker.Namespace][broker.Name] = broker +} + +func (f *fakeBrokerLister) List(selector labels.Selector) ([]*eventingv1.Broker, error) { + var result []*eventingv1.Broker + for _, ns := range f.brokers { + for _, broker := range ns { + result = append(result, broker) + } + } + return result, nil +} + +func (f *fakeBrokerLister) Brokers(namespace string) eventinglisters.BrokerNamespaceLister { + return &fakeBrokerNamespaceLister{ + namespace: namespace, + brokers: f.brokers[namespace], + } +} + +type fakeBrokerNamespaceLister struct { + namespace string + brokers map[string]*eventingv1.Broker +} + +func (f *fakeBrokerNamespaceLister) List(selector labels.Selector) ([]*eventingv1.Broker, error) { + result := make([]*eventingv1.Broker, 0, len(f.brokers)) + for _, broker := range f.brokers { + result = append(result, broker) + } + return result, nil +} + +func (f *fakeBrokerNamespaceLister) Get(name string) (*eventingv1.Broker, error) { + if broker, ok := f.brokers[name]; ok { + return broker, nil + } + return nil, apierrs.NewNotFound(schema.GroupResource{Group: "eventing.knative.dev", Resource: "brokers"}, name) +} + +// fakeTriggerLister implements the TriggerLister interface for testing +type fakeTriggerLister struct { + triggers map[string]map[string]*eventingv1.Trigger +} + +func newFakeTriggerLister() *fakeTriggerLister { + return &fakeTriggerLister{ + triggers: make(map[string]map[string]*eventingv1.Trigger), + } +} + +func (f *fakeTriggerLister) addTrigger(trigger *eventingv1.Trigger) { + if f.triggers[trigger.Namespace] == nil { + f.triggers[trigger.Namespace] = make(map[string]*eventingv1.Trigger) + } + f.triggers[trigger.Namespace][trigger.Name] = trigger +} + +func (f *fakeTriggerLister) List(selector labels.Selector) ([]*eventingv1.Trigger, error) { + var result []*eventingv1.Trigger + for _, ns := range f.triggers { + for _, trigger := range ns { + result = append(result, trigger) + } + } + return result, nil +} + +func (f *fakeTriggerLister) Triggers(namespace string) eventinglisters.TriggerNamespaceLister { + return &fakeTriggerNamespaceLister{ + namespace: namespace, + triggers: f.triggers[namespace], + } +} + +type fakeTriggerNamespaceLister struct { + namespace string + triggers map[string]*eventingv1.Trigger +} + +func (f *fakeTriggerNamespaceLister) List(selector labels.Selector) ([]*eventingv1.Trigger, error) { + result := make([]*eventingv1.Trigger, 0, len(f.triggers)) + for _, trigger := range f.triggers { + result = append(result, trigger) + } + return result, nil +} + +func (f *fakeTriggerNamespaceLister) Get(name string) (*eventingv1.Trigger, error) { + if trigger, ok := f.triggers[name]; ok { + return trigger, nil + } + return nil, apierrs.NewNotFound(schema.GroupResource{Group: "eventing.knative.dev", Resource: "triggers"}, name) +} + +func TestFilterTriggersByBrokerClass(t *testing.T) { + tests := []struct { + name string + broker *eventingv1.Broker + trigger *eventingv1.Trigger + wantFiltered bool + }{ + { + name: "trigger with NatsJetStreamBroker class broker", + broker: newTestBroker(testNamespace, testBrokerName, constants.BrokerClassName), + trigger: newTestTrigger(testNamespace, testTriggerName, testBrokerName), + wantFiltered: true, + }, + { + name: "trigger with different broker class", + broker: newTestBroker(testNamespace, testBrokerName, "OtherBrokerClass"), + trigger: newTestTrigger(testNamespace, testTriggerName, testBrokerName), + wantFiltered: false, + }, + { + name: "trigger with broker without class annotation", + broker: newTestBroker(testNamespace, testBrokerName, ""), + trigger: newTestTrigger(testNamespace, testTriggerName, testBrokerName), + wantFiltered: false, + }, + { + name: "trigger referencing non-existent broker - should pass to reconciler", + broker: nil, + trigger: newTestTrigger(testNamespace, testTriggerName, "non-existent-broker"), + wantFiltered: true, // Let the reconciler handle the error + }, + { + name: "non-trigger object", + broker: newTestBroker(testNamespace, testBrokerName, constants.BrokerClassName), + trigger: nil, + wantFiltered: false, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + brokerLister := newFakeBrokerLister() + if tc.broker != nil { + brokerLister.addBroker(tc.broker) + } + + r := &Reconciler{ + brokerLister: brokerLister, + } + + filterFunc := filterTriggersByBrokerClass(r) + + var obj interface{} + if tc.trigger != nil { + obj = tc.trigger + } else { + obj = "not a trigger" + } + + got := filterFunc(obj) + if got != tc.wantFiltered { + t.Errorf("filterTriggersByBrokerClass() = %v, want %v", got, tc.wantFiltered) + } + }) + } +} + +func TestFilterBrokersByClass(t *testing.T) { + tests := []struct { + name string + obj interface{} + wantFiltered bool + }{ + { + name: "broker with NatsJetStreamBroker class", + obj: newTestBroker(testNamespace, testBrokerName, constants.BrokerClassName), + wantFiltered: true, + }, + { + name: "broker with different class", + obj: newTestBroker(testNamespace, testBrokerName, "OtherBrokerClass"), + wantFiltered: false, + }, + { + name: "broker without class annotation", + obj: newTestBroker(testNamespace, testBrokerName, ""), + wantFiltered: false, + }, + { + name: "non-broker object", + obj: "not a broker", + wantFiltered: false, + }, + { + name: "nil object", + obj: nil, + wantFiltered: false, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + got := filterBrokersByClass(tc.obj) + if got != tc.wantFiltered { + t.Errorf("filterBrokersByClass() = %v, want %v", got, tc.wantFiltered) + } + }) + } +} + +// TestEnqueueTriggerOfBrokerLogic tests the logic used by enqueueTriggerOfBroker +// This tests the filtering logic without requiring a real controller.Impl +func TestEnqueueTriggerOfBrokerLogic(t *testing.T) { + tests := []struct { + name string + broker *eventingv1.Broker + triggers []*eventingv1.Trigger + obj interface{} + wantEnqueueCount int + }{ + { + name: "enqueue triggers that reference the broker", + broker: newTestBroker(testNamespace, testBrokerName, constants.BrokerClassName), + triggers: []*eventingv1.Trigger{ + newTestTrigger(testNamespace, "trigger1", testBrokerName), + newTestTrigger(testNamespace, "trigger2", testBrokerName), + newTestTrigger(testNamespace, "trigger3", "other-broker"), + }, + obj: newTestBroker(testNamespace, testBrokerName, constants.BrokerClassName), + wantEnqueueCount: 2, + }, + { + name: "no triggers reference the broker", + broker: newTestBroker(testNamespace, testBrokerName, constants.BrokerClassName), + triggers: []*eventingv1.Trigger{ + newTestTrigger(testNamespace, "trigger1", "other-broker"), + }, + obj: newTestBroker(testNamespace, testBrokerName, constants.BrokerClassName), + wantEnqueueCount: 0, + }, + { + name: "triggers in different namespace", + broker: newTestBroker(testNamespace, testBrokerName, constants.BrokerClassName), + triggers: []*eventingv1.Trigger{newTestTrigger("other-namespace", "trigger1", testBrokerName)}, + obj: newTestBroker(testNamespace, testBrokerName, constants.BrokerClassName), + wantEnqueueCount: 0, + }, + { + name: "non-broker object does nothing", + broker: newTestBroker(testNamespace, testBrokerName, constants.BrokerClassName), + triggers: []*eventingv1.Trigger{newTestTrigger(testNamespace, "trigger1", testBrokerName)}, + obj: "not a broker", + wantEnqueueCount: 0, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + triggerLister := newFakeTriggerLister() + for _, trigger := range tc.triggers { + triggerLister.addTrigger(trigger) + } + + // Test the core logic of enqueueTriggerOfBroker + var enqueued []*eventingv1.Trigger + + broker, ok := tc.obj.(*eventingv1.Broker) + if !ok { + // Non-broker object should result in no enqueues + if len(enqueued) != tc.wantEnqueueCount { + t.Errorf("enqueue logic for non-broker: got %d items, want %d", len(enqueued), tc.wantEnqueueCount) + } + return + } + + // Find all triggers in the same namespace that reference this broker + triggers, err := triggerLister.Triggers(broker.Namespace).List(labels.Everything()) + if err != nil { + return + } + + for _, trigger := range triggers { + if trigger.Spec.Broker == broker.Name { + enqueued = append(enqueued, trigger) + } + } + + if len(enqueued) != tc.wantEnqueueCount { + t.Errorf("enqueue logic: got %d items, want %d", len(enqueued), tc.wantEnqueueCount) + } + }) + } +} + +func TestComponentName(t *testing.T) { + if ComponentName != "natsjs-trigger-controller" { + t.Errorf("ComponentName = %q, want %q", ComponentName, "natsjs-trigger-controller") + } +} diff --git a/pkg/broker/trigger/reconciler.go b/pkg/broker/trigger/reconciler.go new file mode 100644 index 000000000..4238a6343 --- /dev/null +++ b/pkg/broker/trigger/reconciler.go @@ -0,0 +1,305 @@ +/* +Copyright 2026 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package trigger + +import ( + "context" + "errors" + "fmt" + "time" + + "github.com/nats-io/nats.go" + "github.com/rickb777/date/period" + "go.uber.org/zap" + corev1 "k8s.io/api/core/v1" + apierrs "k8s.io/apimachinery/pkg/api/errors" + + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" + "knative.dev/pkg/controller" + "knative.dev/pkg/logging" + pkgreconciler "knative.dev/pkg/reconciler" + "knative.dev/pkg/resolver" + + eventingv1 "knative.dev/eventing/pkg/apis/eventing/v1" + triggerreconciler "knative.dev/eventing/pkg/client/injection/reconciler/eventing/v1/trigger" + eventinglisters "knative.dev/eventing/pkg/client/listers/eventing/v1" + + "knative.dev/eventing-natss/pkg/broker/constants" + brokerutils "knative.dev/eventing-natss/pkg/broker/utils" +) + +const ( + // Event reasons + ReasonConsumerCreated = "JetStreamConsumerCreated" + ReasonConsumerUpdated = "JetStreamConsumerUpdated" + ReasonConsumerFailed = "JetStreamConsumerFailed" + ReasonConsumerDeleted = "JetStreamConsumerDeleted" +) + +// Reconciler implements triggerreconciler.Interface for Trigger resources. +type Reconciler struct { + // Listers for Kubernetes resources + brokerLister eventinglisters.BrokerLister + + // NATS JetStream connection + js nats.JetStreamContext + + // URI resolver for resolving subscriber and dead letter sink addresses + uriResolver *resolver.URIResolver + + // Configuration + filterServiceName string +} + +// Check that our Reconciler implements Interface +var _ triggerreconciler.Interface = (*Reconciler)(nil) +var _ triggerreconciler.Finalizer = (*Reconciler)(nil) + +// ReconcileKind implements triggerreconciler.Interface +func (r *Reconciler) ReconcileKind(ctx context.Context, trigger *eventingv1.Trigger) pkgreconciler.Event { + logger := logging.FromContext(ctx) + logger.Infow("Reconciling trigger", zap.String("trigger", trigger.Name), zap.String("namespace", trigger.Namespace)) + + // Step 1: Get the broker and check it's ready + broker, err := r.brokerLister.Brokers(trigger.Namespace).Get(trigger.Spec.Broker) + if err != nil { + if apierrs.IsNotFound(err) { + trigger.Status.MarkBrokerFailed("BrokerNotFound", "Broker %q does not exist", trigger.Spec.Broker) + return nil + } + trigger.Status.MarkBrokerFailed("BrokerGetFailed", "Failed to get broker: %v", err) + return fmt.Errorf("failed to get broker: %w", err) + } + + // Check broker class + if broker.GetAnnotations()[eventingv1.BrokerClassAnnotationKey] != constants.BrokerClassName { + trigger.Status.MarkBrokerFailed("BrokerClassMismatch", "Broker %q is not of class %s", trigger.Spec.Broker, constants.BrokerClassName) + return nil + } + + // Check if broker is ready + if !broker.IsReady() { + trigger.Status.MarkBrokerFailed("BrokerNotReady", "Broker %q is not ready", trigger.Spec.Broker) + return nil + } + + // Broker is ready + trigger.Status.PropagateBrokerCondition(broker.Status.GetTopLevelCondition()) + + // Step 2: Resolve the subscriber URI + subscriberURI, err := r.resolveSubscriberURI(ctx, trigger) + if err != nil { + trigger.Status.MarkSubscriberResolvedFailed("SubscriberResolveFailed", "Failed to resolve subscriber: %v", err) + return fmt.Errorf("failed to resolve subscriber: %w", err) + } + trigger.Status.SubscriberURI = subscriberURI + trigger.Status.MarkSubscriberResolvedSucceeded() + + // Step 3: Handle dead letter sink if configured + if trigger.Spec.Delivery != nil && trigger.Spec.Delivery.DeadLetterSink != nil { + deadLetterURI, err := r.resolveDeadLetterURI(ctx, trigger) + if err != nil { + trigger.Status.MarkDeadLetterSinkResolvedFailed("DeadLetterSinkResolveFailed", "Failed to resolve dead letter sink: %v", err) + return fmt.Errorf("failed to resolve dead letter sink: %w", err) + } + trigger.Status.DeadLetterSinkURI = deadLetterURI + trigger.Status.MarkDeadLetterSinkResolvedSucceeded() + } else { + trigger.Status.MarkDeadLetterSinkNotConfigured() + } + + // Step 4: Reconcile the JetStream consumer + if err := r.reconcileConsumer(ctx, trigger, broker); err != nil { + trigger.Status.MarkNotSubscribed("ConsumerFailed", "Failed to create JetStream consumer: %v", err) + return err + } + + // Mark subscription as ready + trigger.Status.PropagateSubscriptionCondition(&apis.Condition{ + Type: apis.ConditionReady, + Status: corev1.ConditionTrue, + }) + + // Mark dependency as succeeded (we don't have external dependencies) + trigger.Status.MarkDependencySucceeded() + + // Mark OIDC identity as not needed (OIDC authentication is not enabled) + trigger.Status.MarkOIDCIdentityCreatedSucceededWithReason("OIDCIdentitySkipped", "OIDC authentication is not enabled") + + logger.Infow("Trigger reconciliation completed successfully", zap.String("trigger", trigger.Name)) + return nil +} + +// FinalizeKind cleans up resources when the trigger is deleted +func (r *Reconciler) FinalizeKind(ctx context.Context, trigger *eventingv1.Trigger) pkgreconciler.Event { + logger := logging.FromContext(ctx) + logger.Infow("Finalizing trigger", zap.String("trigger", trigger.Name)) + + // Get the broker to find the stream name + _, err := r.brokerLister.Brokers(trigger.Namespace).Get(trigger.Spec.Broker) + if err != nil && apierrs.IsNotFound(err) { + // Broker is gone, nothing to clean up + logger.Warnw("Broker not found during finalization") + } + + streamName := brokerutils.BrokerStreamNameByNsAndName(trigger.Namespace, trigger.Spec.Broker) + consumerName := brokerutils.TriggerConsumerName(string(trigger.UID)) + + // Delete the consumer + err = r.js.DeleteConsumer(streamName, consumerName) + if err != nil && !errors.Is(err, nats.ErrConsumerNotFound) { + logger.Errorw("Failed to delete JetStream consumer", zap.Error(err), zap.String("consumer", consumerName)) + return fmt.Errorf("failed to delete consumer: %w", err) + } + + logger.Infow("Trigger finalization completed", zap.String("trigger", trigger.Name)) + controller.GetEventRecorder(ctx).Event(trigger, corev1.EventTypeNormal, ReasonConsumerDeleted, "JetStream consumer deleted") + return nil +} + +// resolveSubscriberURI resolves the subscriber URI from the trigger spec +func (r *Reconciler) resolveSubscriberURI(ctx context.Context, trigger *eventingv1.Trigger) (*apis.URL, error) { + dest := trigger.Spec.Subscriber + + // Convert to duckv1.Destination for the resolver + destination := duckv1.Destination{ + URI: dest.URI, + } + if dest.Ref != nil { + namespace := dest.Ref.Namespace + if namespace == "" { + namespace = trigger.Namespace + } + destination.Ref = &duckv1.KReference{ + Kind: dest.Ref.Kind, + Namespace: namespace, + Name: dest.Ref.Name, + APIVersion: dest.Ref.APIVersion, + } + } + + return r.uriResolver.URIFromDestinationV1(ctx, destination, trigger) +} + +// resolveDeadLetterURI resolves the dead letter sink URI from the trigger spec +func (r *Reconciler) resolveDeadLetterURI(ctx context.Context, trigger *eventingv1.Trigger) (*apis.URL, error) { + if trigger.Spec.Delivery == nil || trigger.Spec.Delivery.DeadLetterSink == nil { + return nil, nil + } + + dest := trigger.Spec.Delivery.DeadLetterSink + + // Convert to duckv1.Destination for the resolver + destination := duckv1.Destination{ + URI: dest.URI, + } + if dest.Ref != nil { + namespace := dest.Ref.Namespace + if namespace == "" { + namespace = trigger.Namespace + } + destination.Ref = &duckv1.KReference{ + Kind: dest.Ref.Kind, + Namespace: namespace, + Name: dest.Ref.Name, + APIVersion: dest.Ref.APIVersion, + } + } + + return r.uriResolver.URIFromDestinationV1(ctx, destination, trigger) +} + +// reconcileConsumer creates or updates the JetStream consumer for the trigger +func (r *Reconciler) reconcileConsumer(ctx context.Context, trigger *eventingv1.Trigger, broker *eventingv1.Broker) error { + logger := logging.FromContext(ctx) + + streamName := brokerutils.BrokerStreamName(broker) + consumerName := brokerutils.TriggerConsumerName(string(trigger.UID)) + + // Build consumer configuration + consumerConfig := r.buildConsumerConfig(trigger, broker, consumerName) + + // Check if consumer exists + _, err := r.js.ConsumerInfo(streamName, consumerName) + if err != nil { + if !errors.Is(err, nats.ErrConsumerNotFound) { + logger.Errorw("Failed to get consumer info", zap.Error(err), zap.String("consumer", consumerName)) + controller.GetEventRecorder(ctx).Event(trigger, corev1.EventTypeWarning, ReasonConsumerFailed, err.Error()) + return fmt.Errorf("failed to get consumer info: %w", err) + } + + // Consumer doesn't exist, create it + _, err = r.js.AddConsumer(streamName, consumerConfig) + if err != nil { + logger.Errorw("Failed to create JetStream consumer", zap.Error(err), zap.String("consumer", consumerName)) + controller.GetEventRecorder(ctx).Event(trigger, corev1.EventTypeWarning, ReasonConsumerFailed, err.Error()) + return fmt.Errorf("failed to create consumer: %w", err) + } + + logger.Infow("JetStream consumer created", zap.String("consumer", consumerName)) + controller.GetEventRecorder(ctx).Event(trigger, corev1.EventTypeNormal, ReasonConsumerCreated, "JetStream consumer created") + return nil + } + + // Consumer exists, update it + _, err = r.js.UpdateConsumer(streamName, consumerConfig) + if err != nil { + // Some fields cannot be updated, so log and continue if it's just a minor mismatch + logger.Warnw("Failed to update JetStream consumer", zap.Error(err), zap.String("consumer", consumerName)) + } else { + logger.Debugw("JetStream consumer updated", zap.String("consumer", consumerName)) + controller.GetEventRecorder(ctx).Event(trigger, corev1.EventTypeNormal, ReasonConsumerUpdated, "JetStream consumer updated") + } + + return nil +} + +// buildConsumerConfig creates a NATS JetStream pull consumer configuration for the trigger +func (r *Reconciler) buildConsumerConfig(trigger *eventingv1.Trigger, broker *eventingv1.Broker, consumerName string) *nats.ConsumerConfig { + // The filter subject matches all events published to the broker + filterSubject := brokerutils.BrokerPublishSubjectName(broker.Namespace, broker.Name) + ".>" + + // Default delivery configuration + ackWait := 30 * time.Second + maxDeliver := 3 + + // Apply delivery configuration from trigger spec + if trigger.Spec.Delivery != nil { + if trigger.Spec.Delivery.Retry != nil { + maxDeliver = int(*trigger.Spec.Delivery.Retry) + 1 + } + if trigger.Spec.Delivery.Timeout != nil { + timeout, err := period.Parse(*trigger.Spec.Delivery.Timeout) + if err == nil { + ackWait, _ = timeout.Duration() + } + } + } + + // Pull consumer configuration (no DeliverSubject or DeliverGroup) + return &nats.ConsumerConfig{ + Durable: consumerName, + Name: consumerName, + FilterSubject: filterSubject, + AckPolicy: nats.AckExplicitPolicy, + AckWait: ackWait, + MaxDeliver: maxDeliver, + DeliverPolicy: nats.DeliverNewPolicy, + ReplayPolicy: nats.ReplayInstantPolicy, + } +} diff --git a/pkg/broker/trigger/reconciler_test.go b/pkg/broker/trigger/reconciler_test.go new file mode 100644 index 000000000..1b9eb5e35 --- /dev/null +++ b/pkg/broker/trigger/reconciler_test.go @@ -0,0 +1,648 @@ +/* +Copyright 2026 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package trigger + +import ( + "context" + "errors" + "testing" + "time" + + "github.com/nats-io/nats.go" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/tools/record" + + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" + "knative.dev/pkg/controller" + "knative.dev/pkg/logging" + logtesting "knative.dev/pkg/logging/testing" + + eventingduckv1 "knative.dev/eventing/pkg/apis/duck/v1" + eventingv1 "knative.dev/eventing/pkg/apis/eventing/v1" + + "knative.dev/eventing-natss/pkg/broker/constants" +) + +const ( + testFilterServiceName = "natsjs-broker-filter" +) + +// mockJetStreamContext implements nats.JetStreamContext for testing +type mockJetStreamContext struct { + consumers map[string]map[string]*nats.ConsumerInfo + addConsumerErr error + updateConsumer error + deleteConsumer error + consumerInfoErr error +} + +func newMockJetStreamContext() *mockJetStreamContext { + return &mockJetStreamContext{ + consumers: make(map[string]map[string]*nats.ConsumerInfo), + } +} + +func (m *mockJetStreamContext) setAddConsumerError(err error) { + m.addConsumerErr = err +} + +func (m *mockJetStreamContext) setDeleteConsumerError(err error) { + m.deleteConsumer = err +} + +func (m *mockJetStreamContext) setConsumerInfoError(err error) { + m.consumerInfoErr = err +} + +func (m *mockJetStreamContext) addConsumerInfo(streamName, consumerName string) { + if m.consumers[streamName] == nil { + m.consumers[streamName] = make(map[string]*nats.ConsumerInfo) + } + m.consumers[streamName][consumerName] = &nats.ConsumerInfo{ + Name: consumerName, + Stream: streamName, + } +} + +// JetStreamContext interface implementation +func (m *mockJetStreamContext) ConsumerInfo(stream, consumer string, opts ...nats.JSOpt) (*nats.ConsumerInfo, error) { + if m.consumerInfoErr != nil { + return nil, m.consumerInfoErr + } + if m.consumers[stream] == nil { + return nil, nats.ErrConsumerNotFound + } + if info, ok := m.consumers[stream][consumer]; ok { + return info, nil + } + return nil, nats.ErrConsumerNotFound +} + +func (m *mockJetStreamContext) AddConsumer(stream string, cfg *nats.ConsumerConfig, opts ...nats.JSOpt) (*nats.ConsumerInfo, error) { + if m.addConsumerErr != nil { + return nil, m.addConsumerErr + } + if m.consumers[stream] == nil { + m.consumers[stream] = make(map[string]*nats.ConsumerInfo) + } + info := &nats.ConsumerInfo{ + Name: cfg.Name, + Stream: stream, + Config: *cfg, + } + m.consumers[stream][cfg.Name] = info + return info, nil +} + +func (m *mockJetStreamContext) UpdateConsumer(stream string, cfg *nats.ConsumerConfig, opts ...nats.JSOpt) (*nats.ConsumerInfo, error) { + if m.updateConsumer != nil { + return nil, m.updateConsumer + } + if m.consumers[stream] == nil || m.consumers[stream][cfg.Name] == nil { + return nil, nats.ErrConsumerNotFound + } + info := &nats.ConsumerInfo{ + Name: cfg.Name, + Stream: stream, + Config: *cfg, + } + m.consumers[stream][cfg.Name] = info + return info, nil +} + +func (m *mockJetStreamContext) DeleteConsumer(stream, consumer string, opts ...nats.JSOpt) error { + if m.deleteConsumer != nil { + return m.deleteConsumer + } + if m.consumers[stream] == nil || m.consumers[stream][consumer] == nil { + return nats.ErrConsumerNotFound + } + delete(m.consumers[stream], consumer) + return nil +} + +// JetStream interface methods +func (m *mockJetStreamContext) Publish(subj string, data []byte, opts ...nats.PubOpt) (*nats.PubAck, error) { + return nil, errors.New("not implemented") +} +func (m *mockJetStreamContext) PublishMsg(msg *nats.Msg, opts ...nats.PubOpt) (*nats.PubAck, error) { + return nil, errors.New("not implemented") +} +func (m *mockJetStreamContext) PublishAsync(subj string, data []byte, opts ...nats.PubOpt) (nats.PubAckFuture, error) { + return nil, errors.New("not implemented") +} +func (m *mockJetStreamContext) PublishMsgAsync(msg *nats.Msg, opts ...nats.PubOpt) (nats.PubAckFuture, error) { + return nil, errors.New("not implemented") +} +func (m *mockJetStreamContext) PublishAsyncPending() int { return 0 } +func (m *mockJetStreamContext) PublishAsyncComplete() <-chan struct{} { + ch := make(chan struct{}) + close(ch) + return ch +} +func (m *mockJetStreamContext) CleanupPublisher() {} +func (m *mockJetStreamContext) Subscribe(subj string, cb nats.MsgHandler, opts ...nats.SubOpt) (*nats.Subscription, error) { + return nil, errors.New("not implemented") +} +func (m *mockJetStreamContext) SubscribeSync(subj string, opts ...nats.SubOpt) (*nats.Subscription, error) { + return nil, errors.New("not implemented") +} +func (m *mockJetStreamContext) ChanSubscribe(subj string, ch chan *nats.Msg, opts ...nats.SubOpt) (*nats.Subscription, error) { + return nil, errors.New("not implemented") +} +func (m *mockJetStreamContext) ChanQueueSubscribe(subj, queue string, ch chan *nats.Msg, opts ...nats.SubOpt) (*nats.Subscription, error) { + return nil, errors.New("not implemented") +} +func (m *mockJetStreamContext) QueueSubscribe(subj, queue string, cb nats.MsgHandler, opts ...nats.SubOpt) (*nats.Subscription, error) { + return nil, errors.New("not implemented") +} +func (m *mockJetStreamContext) QueueSubscribeSync(subj, queue string, opts ...nats.SubOpt) (*nats.Subscription, error) { + return nil, errors.New("not implemented") +} +func (m *mockJetStreamContext) PullSubscribe(subj, durable string, opts ...nats.SubOpt) (*nats.Subscription, error) { + return nil, errors.New("not implemented") +} + +// JetStreamManager interface methods +func (m *mockJetStreamContext) AddStream(cfg *nats.StreamConfig, opts ...nats.JSOpt) (*nats.StreamInfo, error) { + return nil, errors.New("not implemented") +} +func (m *mockJetStreamContext) UpdateStream(cfg *nats.StreamConfig, opts ...nats.JSOpt) (*nats.StreamInfo, error) { + return nil, errors.New("not implemented") +} +func (m *mockJetStreamContext) DeleteStream(name string, opts ...nats.JSOpt) error { + return errors.New("not implemented") +} +func (m *mockJetStreamContext) StreamInfo(stream string, opts ...nats.JSOpt) (*nats.StreamInfo, error) { + return nil, errors.New("not implemented") +} +func (m *mockJetStreamContext) PurgeStream(name string, opts ...nats.JSOpt) error { + return errors.New("not implemented") +} +func (m *mockJetStreamContext) StreamsInfo(opts ...nats.JSOpt) <-chan *nats.StreamInfo { + return nil +} +func (m *mockJetStreamContext) Streams(opts ...nats.JSOpt) <-chan *nats.StreamInfo { return nil } +func (m *mockJetStreamContext) StreamNames(opts ...nats.JSOpt) <-chan string { return nil } +func (m *mockJetStreamContext) ConsumersInfo(stream string, opts ...nats.JSOpt) <-chan *nats.ConsumerInfo { + return nil +} +func (m *mockJetStreamContext) Consumers(stream string, opts ...nats.JSOpt) <-chan *nats.ConsumerInfo { + return nil +} +func (m *mockJetStreamContext) ConsumerNames(stream string, opts ...nats.JSOpt) <-chan string { + return nil +} +func (m *mockJetStreamContext) AccountInfo(opts ...nats.JSOpt) (*nats.AccountInfo, error) { + return nil, errors.New("not implemented") +} +func (m *mockJetStreamContext) GetMsg(name string, seq uint64, opts ...nats.JSOpt) (*nats.RawStreamMsg, error) { + return nil, errors.New("not implemented") +} +func (m *mockJetStreamContext) GetLastMsg(name, subject string, opts ...nats.JSOpt) (*nats.RawStreamMsg, error) { + return nil, errors.New("not implemented") +} +func (m *mockJetStreamContext) DeleteMsg(name string, seq uint64, opts ...nats.JSOpt) error { + return errors.New("not implemented") +} +func (m *mockJetStreamContext) SecureDeleteMsg(name string, seq uint64, opts ...nats.JSOpt) error { + return errors.New("not implemented") +} +func (m *mockJetStreamContext) StreamNameBySubject(subject string, opts ...nats.JSOpt) (string, error) { + return "", errors.New("not implemented") +} + +// KeyValueManager interface methods +func (m *mockJetStreamContext) KeyValue(bucket string) (nats.KeyValue, error) { + return nil, errors.New("not implemented") +} +func (m *mockJetStreamContext) CreateKeyValue(cfg *nats.KeyValueConfig) (nats.KeyValue, error) { + return nil, errors.New("not implemented") +} +func (m *mockJetStreamContext) DeleteKeyValue(bucket string) error { + return errors.New("not implemented") +} +func (m *mockJetStreamContext) KeyValueStoreNames() <-chan string { return nil } +func (m *mockJetStreamContext) KeyValueStores() <-chan nats.KeyValueStatus { + return nil +} + +// ObjectStoreManager interface methods +func (m *mockJetStreamContext) ObjectStore(bucket string) (nats.ObjectStore, error) { + return nil, errors.New("not implemented") +} +func (m *mockJetStreamContext) CreateObjectStore(cfg *nats.ObjectStoreConfig) (nats.ObjectStore, error) { + return nil, errors.New("not implemented") +} +func (m *mockJetStreamContext) DeleteObjectStore(bucket string) error { + return errors.New("not implemented") +} +func (m *mockJetStreamContext) ObjectStoreNames(opts ...nats.ObjectOpt) <-chan string { return nil } +func (m *mockJetStreamContext) ObjectStores(opts ...nats.ObjectOpt) <-chan nats.ObjectStoreStatus { + return nil +} + +// Ensure mockJetStreamContext implements nats.JetStreamContext +var _ nats.JetStreamContext = (*mockJetStreamContext)(nil) + +func newReconcilerForTest(brokerLister *fakeBrokerLister, js nats.JetStreamContext) *Reconciler { + return &Reconciler{ + brokerLister: brokerLister, + js: js, + filterServiceName: testFilterServiceName, + } +} + +func newReadyBroker(namespace, name string) *eventingv1.Broker { + broker := newTestBroker(namespace, name, constants.BrokerClassName) + broker.Status.InitializeConditions() + // Set address to make broker ready + broker.Status.SetAddress(&duckv1.Addressable{ + URL: &apis.URL{ + Scheme: "http", + Host: "broker-ingress.knative-eventing.svc.cluster.local", + }, + }) + return broker +} + +func newTriggerWithSubscriber(namespace, name, brokerName string, subscriberURI string) *eventingv1.Trigger { + trigger := newTestTrigger(namespace, name, brokerName) + trigger.UID = types.UID(testTriggerUID) + trigger.Spec.Subscriber = duckv1.Destination{ + URI: apis.HTTP(subscriberURI), + } + trigger.Status.InitializeConditions() + return trigger +} + +func testContextWithRecorder(t *testing.T) context.Context { + ctx := logging.WithLogger(context.Background(), logtesting.TestLogger(t)) + recorder := record.NewFakeRecorder(10) + return controller.WithEventRecorder(ctx, recorder) +} + +func TestReconcileKind_BrokerNotFound(t *testing.T) { + ctx := testContextWithRecorder(t) + brokerLister := newFakeBrokerLister() + js := newMockJetStreamContext() + + r := newReconcilerForTest(brokerLister, js) + + trigger := newTriggerWithSubscriber(testNamespace, testTriggerName, "non-existent-broker", "subscriber.example.com") + + err := r.ReconcileKind(ctx, trigger) + + if err != nil { + t.Errorf("ReconcileKind() returned error = %v, want nil (soft failure)", err) + } + + // Check that the trigger status reflects the broker not found + cond := trigger.Status.GetCondition(eventingv1.TriggerConditionBroker) + if cond == nil || cond.Status != corev1.ConditionFalse { + t.Errorf("Expected TriggerConditionBroker to be False, got %+v", cond) + } +} + +func TestReconcileKind_BrokerClassMismatch(t *testing.T) { + ctx := testContextWithRecorder(t) + brokerLister := newFakeBrokerLister() + js := newMockJetStreamContext() + + // Add broker with different class + broker := newTestBroker(testNamespace, testBrokerName, "DifferentBrokerClass") + brokerLister.addBroker(broker) + + r := newReconcilerForTest(brokerLister, js) + + trigger := newTriggerWithSubscriber(testNamespace, testTriggerName, testBrokerName, "subscriber.example.com") + + err := r.ReconcileKind(ctx, trigger) + + if err != nil { + t.Errorf("ReconcileKind() returned error = %v, want nil (soft failure)", err) + } + + // Check that the trigger status reflects the class mismatch + cond := trigger.Status.GetCondition(eventingv1.TriggerConditionBroker) + if cond == nil || cond.Status != corev1.ConditionFalse { + t.Errorf("Expected TriggerConditionBroker to be False, got %+v", cond) + } +} + +func TestReconcileKind_BrokerNotReady(t *testing.T) { + ctx := testContextWithRecorder(t) + brokerLister := newFakeBrokerLister() + js := newMockJetStreamContext() + + // Add broker that is not ready + broker := newTestBroker(testNamespace, testBrokerName, constants.BrokerClassName) + broker.Status.InitializeConditions() + // Not setting address - broker not ready + brokerLister.addBroker(broker) + + r := newReconcilerForTest(brokerLister, js) + + trigger := newTriggerWithSubscriber(testNamespace, testTriggerName, testBrokerName, "subscriber.example.com") + + err := r.ReconcileKind(ctx, trigger) + + if err != nil { + t.Errorf("ReconcileKind() returned error = %v, want nil (soft failure)", err) + } + + // Check that the trigger status reflects the broker not ready + cond := trigger.Status.GetCondition(eventingv1.TriggerConditionBroker) + if cond == nil || cond.Status != corev1.ConditionFalse { + t.Errorf("Expected TriggerConditionBroker to be False, got %+v", cond) + } +} + +func TestFinalizeKind_BrokerNotFound(t *testing.T) { + ctx := testContextWithRecorder(t) + brokerLister := newFakeBrokerLister() + js := newMockJetStreamContext() + + r := newReconcilerForTest(brokerLister, js) + + trigger := newTriggerWithSubscriber(testNamespace, testTriggerName, testBrokerName, "subscriber.example.com") + + err := r.FinalizeKind(ctx, trigger) + + if err != nil { + t.Errorf("FinalizeKind() returned error = %v, want nil when broker not found", err) + } +} + +func TestFinalizeKind_ConsumerDeleteError(t *testing.T) { + ctx := testContextWithRecorder(t) + brokerLister := newFakeBrokerLister() + js := newMockJetStreamContext() + + broker := newReadyBroker(testNamespace, testBrokerName) + brokerLister.addBroker(broker) + + // Set delete consumer to fail + js.setDeleteConsumerError(errors.New("delete failed")) + + r := newReconcilerForTest(brokerLister, js) + + trigger := newTriggerWithSubscriber(testNamespace, testTriggerName, testBrokerName, "subscriber.example.com") + + err := r.FinalizeKind(ctx, trigger) + + if err == nil { + t.Errorf("FinalizeKind() returned nil, want error when delete fails") + } +} + +func TestFinalizeKind_ConsumerNotFoundOK(t *testing.T) { + ctx := testContextWithRecorder(t) + brokerLister := newFakeBrokerLister() + js := newMockJetStreamContext() + + broker := newReadyBroker(testNamespace, testBrokerName) + brokerLister.addBroker(broker) + + // Consumer doesn't exist - should be OK + r := newReconcilerForTest(brokerLister, js) + + trigger := newTriggerWithSubscriber(testNamespace, testTriggerName, testBrokerName, "subscriber.example.com") + + err := r.FinalizeKind(ctx, trigger) + + if err != nil { + t.Errorf("FinalizeKind() returned error = %v, want nil when consumer not found", err) + } +} + +func TestBuildConsumerConfig(t *testing.T) { + tests := []struct { + name string + trigger *eventingv1.Trigger + broker *eventingv1.Broker + consumerName string + wantAckWait time.Duration + wantMaxDeliver int + wantAckPolicy nats.AckPolicy + wantFilterSubj string + }{ + { + name: "default delivery configuration", + trigger: newTriggerWithSubscriber(testNamespace, testTriggerName, testBrokerName, "subscriber.example.com"), + broker: newReadyBroker(testNamespace, testBrokerName), + consumerName: "test-consumer", + wantAckWait: 30 * time.Second, + wantMaxDeliver: 3, + wantAckPolicy: nats.AckExplicitPolicy, + wantFilterSubj: "test-namespace.test-broker._knative_broker.>", + }, + { + name: "custom retry count", + trigger: func() *eventingv1.Trigger { + retry := int32(5) + trigger := newTriggerWithSubscriber(testNamespace, testTriggerName, testBrokerName, "subscriber.example.com") + trigger.Spec.Delivery = &eventingduckv1.DeliverySpec{ + Retry: &retry, + } + return trigger + }(), + broker: newReadyBroker(testNamespace, testBrokerName), + consumerName: "test-consumer", + wantAckWait: 30 * time.Second, + wantMaxDeliver: 6, // retry + 1 + wantAckPolicy: nats.AckExplicitPolicy, + wantFilterSubj: "test-namespace.test-broker._knative_broker.>", + }, + { + name: "custom timeout", + trigger: func() *eventingv1.Trigger { + timeout := "PT1M" // 1 minute in ISO 8601 duration format + trigger := newTriggerWithSubscriber(testNamespace, testTriggerName, testBrokerName, "subscriber.example.com") + trigger.Spec.Delivery = &eventingduckv1.DeliverySpec{ + Timeout: &timeout, + } + return trigger + }(), + broker: newReadyBroker(testNamespace, testBrokerName), + consumerName: "test-consumer", + wantAckWait: 1 * time.Minute, + wantMaxDeliver: 3, + wantAckPolicy: nats.AckExplicitPolicy, + wantFilterSubj: "test-namespace.test-broker._knative_broker.>", + }, + { + name: "invalid timeout falls back to default", + trigger: func() *eventingv1.Trigger { + timeout := "invalid-duration" + trigger := newTriggerWithSubscriber(testNamespace, testTriggerName, testBrokerName, "subscriber.example.com") + trigger.Spec.Delivery = &eventingduckv1.DeliverySpec{ + Timeout: &timeout, + } + return trigger + }(), + broker: newReadyBroker(testNamespace, testBrokerName), + consumerName: "test-consumer", + wantAckWait: 30 * time.Second, // default + wantMaxDeliver: 3, + wantAckPolicy: nats.AckExplicitPolicy, + wantFilterSubj: "test-namespace.test-broker._knative_broker.>", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + r := &Reconciler{} + config := r.buildConsumerConfig(tc.trigger, tc.broker, tc.consumerName) + + if config.AckWait != tc.wantAckWait { + t.Errorf("AckWait = %v, want %v", config.AckWait, tc.wantAckWait) + } + if config.MaxDeliver != tc.wantMaxDeliver { + t.Errorf("MaxDeliver = %d, want %d", config.MaxDeliver, tc.wantMaxDeliver) + } + if config.AckPolicy != tc.wantAckPolicy { + t.Errorf("AckPolicy = %v, want %v", config.AckPolicy, tc.wantAckPolicy) + } + if config.FilterSubject != tc.wantFilterSubj { + t.Errorf("FilterSubject = %q, want %q", config.FilterSubject, tc.wantFilterSubj) + } + if config.Name != tc.consumerName { + t.Errorf("Name = %q, want %q", config.Name, tc.consumerName) + } + if config.Durable != tc.consumerName { + t.Errorf("Durable = %q, want %q", config.Durable, tc.consumerName) + } + if config.DeliverPolicy != nats.DeliverNewPolicy { + t.Errorf("DeliverPolicy = %v, want %v", config.DeliverPolicy, nats.DeliverNewPolicy) + } + if config.ReplayPolicy != nats.ReplayInstantPolicy { + t.Errorf("ReplayPolicy = %v, want %v", config.ReplayPolicy, nats.ReplayInstantPolicy) + } + }) + } +} + +func TestReconcileConsumer_CreateNew(t *testing.T) { + ctx := testContextWithRecorder(t) + brokerLister := newFakeBrokerLister() + js := newMockJetStreamContext() + + broker := newReadyBroker(testNamespace, testBrokerName) + brokerLister.addBroker(broker) + + r := newReconcilerForTest(brokerLister, js) + + trigger := newTriggerWithSubscriber(testNamespace, testTriggerName, testBrokerName, "subscriber.example.com") + + err := r.reconcileConsumer(ctx, trigger, broker) + + if err != nil { + t.Errorf("reconcileConsumer() returned error = %v, want nil", err) + } + + // Verify consumer was created + streamName := "KN_BROKER_TEST_NAMESPACE__TEST_BROKER" + consumerName := "KN_TRIGGER_TESTTRIGGERUID12345" + if js.consumers[streamName] == nil || js.consumers[streamName][consumerName] == nil { + t.Error("reconcileConsumer() did not create consumer") + } +} + +func TestReconcileConsumer_CreateFailed(t *testing.T) { + ctx := testContextWithRecorder(t) + brokerLister := newFakeBrokerLister() + js := newMockJetStreamContext() + js.setAddConsumerError(errors.New("failed to add consumer")) + + broker := newReadyBroker(testNamespace, testBrokerName) + brokerLister.addBroker(broker) + + r := newReconcilerForTest(brokerLister, js) + + trigger := newTriggerWithSubscriber(testNamespace, testTriggerName, testBrokerName, "subscriber.example.com") + + err := r.reconcileConsumer(ctx, trigger, broker) + + if err == nil { + t.Error("reconcileConsumer() returned nil, want error when create fails") + } +} + +func TestReconcileConsumer_UpdateExisting(t *testing.T) { + ctx := testContextWithRecorder(t) + brokerLister := newFakeBrokerLister() + js := newMockJetStreamContext() + + broker := newReadyBroker(testNamespace, testBrokerName) + brokerLister.addBroker(broker) + + // Pre-create the consumer + streamName := "KN_BROKER_TEST_NAMESPACE__TEST_BROKER" + consumerName := "KN_TRIGGER_TESTTRIGGERUID12345" + js.addConsumerInfo(streamName, consumerName) + + r := newReconcilerForTest(brokerLister, js) + + trigger := newTriggerWithSubscriber(testNamespace, testTriggerName, testBrokerName, "subscriber.example.com") + + err := r.reconcileConsumer(ctx, trigger, broker) + + if err != nil { + t.Errorf("reconcileConsumer() returned error = %v, want nil", err) + } +} + +func TestReconcileConsumer_ConsumerInfoError(t *testing.T) { + ctx := testContextWithRecorder(t) + brokerLister := newFakeBrokerLister() + js := newMockJetStreamContext() + js.setConsumerInfoError(errors.New("connection error")) + + broker := newReadyBroker(testNamespace, testBrokerName) + brokerLister.addBroker(broker) + + r := newReconcilerForTest(brokerLister, js) + + trigger := newTriggerWithSubscriber(testNamespace, testTriggerName, testBrokerName, "subscriber.example.com") + + err := r.reconcileConsumer(ctx, trigger, broker) + + if err == nil { + t.Error("reconcileConsumer() returned nil, want error when consumer info fails") + } +} + +func TestEventReasons(t *testing.T) { + tests := []struct { + constant string + want string + }{ + {ReasonConsumerCreated, "JetStreamConsumerCreated"}, + {ReasonConsumerUpdated, "JetStreamConsumerUpdated"}, + {ReasonConsumerFailed, "JetStreamConsumerFailed"}, + {ReasonConsumerDeleted, "JetStreamConsumerDeleted"}, + } + + for _, tc := range tests { + if tc.constant != tc.want { + t.Errorf("Constant = %q, want %q", tc.constant, tc.want) + } + } +} diff --git a/pkg/broker/utils/naming.go b/pkg/broker/utils/naming.go index 371e963bd..f8199ad3b 100644 --- a/pkg/broker/utils/naming.go +++ b/pkg/broker/utils/naming.go @@ -35,7 +35,11 @@ var ( // - "default/my-broker" => "KN_BROKER_DEFAULT__MY_BROKER" // - "knative-eventing/test-broker" => "KN_BROKER_KNATIVE_EVENTING__TEST_BROKER" func BrokerStreamName(b *eventingv1.Broker) string { - return strings.ToUpper(streamNameReplacer.Replace(fmt.Sprintf("KN_BROKER_%s__%s", b.Namespace, b.Name))) + return BrokerStreamNameByNsAndName(b.Namespace, b.Name) +} + +func BrokerStreamNameByNsAndName(ns string, name string) string { + return strings.ToUpper(streamNameReplacer.Replace(fmt.Sprintf("KN_BROKER_%s__%s", ns, name))) } // BrokerPublishSubjectName generates the subject name for publishing events to a broker's stream. diff --git a/pkg/channel/jetstream/controller/controller.go b/pkg/channel/jetstream/controller/controller.go index 957240b67..881815f02 100644 --- a/pkg/channel/jetstream/controller/controller.go +++ b/pkg/channel/jetstream/controller/controller.go @@ -36,7 +36,6 @@ import ( "k8s.io/client-go/tools/cache" kubeclient "knative.dev/pkg/client/injection/kube/client" deploymentinformer "knative.dev/pkg/client/injection/kube/informers/apps/v1/deployment" - endpointsinformer "knative.dev/pkg/client/injection/kube/informers/core/v1/endpoints" podinformer "knative.dev/pkg/client/injection/kube/informers/core/v1/pod" serviceinformer "knative.dev/pkg/client/injection/kube/informers/core/v1/service" serviceaccountinformer "knative.dev/pkg/client/injection/kube/informers/core/v1/serviceaccount" @@ -73,7 +72,6 @@ func NewController(ctx context.Context, _ configmap.Watcher) *controller.Impl { jsmInformer := natsjetstreamchannel.Get(ctx) deploymentInformer := deploymentinformer.Get(ctx) serviceInformer := serviceinformer.Get(ctx) - endpointsInformer := endpointsinformer.Get(ctx) serviceAccountInformer := serviceaccountinformer.Get(ctx) roleBindingInformer := rolebindinginformer.Get(ctx) podInformer := podinformer.Get(ctx) @@ -88,7 +86,6 @@ func NewController(ctx context.Context, _ configmap.Watcher) *controller.Impl { dispatcherServiceAccount: env.DispatcherServiceAccount, deploymentLister: deploymentInformer.Lister(), serviceLister: serviceInformer.Lister(), - endpointsLister: endpointsInformer.Lister(), serviceAccountLister: serviceAccountInformer.Lister(), roleBindingLister: roleBindingInformer.Lister(), jsmChannelLister: jsmChannelInformer.Lister(), @@ -117,10 +114,6 @@ func NewController(ctx context.Context, _ configmap.Watcher) *controller.Impl { FilterFunc: filterFunc, Handler: controller.HandleAll(grCh), }) - endpointsInformer.Informer().AddEventHandler(cache.FilteringResourceEventHandler{ - FilterFunc: filterFunc, - Handler: controller.HandleAll(grCh), - }) serviceAccountInformer.Informer().AddEventHandler(cache.FilteringResourceEventHandler{ FilterFunc: filterFunc, Handler: controller.HandleAll(grCh), diff --git a/pkg/channel/jetstream/controller/natsjetstreamchannel_test.go b/pkg/channel/jetstream/controller/natsjetstreamchannel_test.go index 29c26df51..fd9218fd5 100644 --- a/pkg/channel/jetstream/controller/natsjetstreamchannel_test.go +++ b/pkg/channel/jetstream/controller/natsjetstreamchannel_test.go @@ -23,10 +23,12 @@ import ( appsv1 "k8s.io/api/apps/v1" v1 "k8s.io/api/core/v1" + discoveryv1 "k8s.io/api/discovery/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" clientgotesting "k8s.io/client-go/testing" + "k8s.io/utils/ptr" kubeclient "knative.dev/pkg/client/injection/kube/client" "knative.dev/pkg/kmeta" "knative.dev/pkg/network" @@ -171,7 +173,7 @@ func TestAllCases(t *testing.T) { Objects: []runtime.Object{ makeReadyDispatcherDeployment(), makeDispatcherService(), - makeReadyEndpoints(), + makeReadyEndpointSlice(), reconciletesting.NewNatsJetStreamChannel(ncName, testNS), }, WantStatusUpdates: []clientgotesting.UpdateActionImpl{{ @@ -202,7 +204,7 @@ func TestAllCases(t *testing.T) { Objects: []runtime.Object{ makeReadyDispatcherDeployment(), makeDispatcherService(), - makeReadyEndpoints(), + makeReadyEndpointSlice(), reconciletesting.NewNatsJetStreamChannel(ncName, testNS), makeChannelService(reconciletesting.NewNatsJetStreamChannel(ncName, testNS)), }, @@ -265,7 +267,6 @@ func TestAllCases(t *testing.T) { dispatcherServiceAccount: dispatcherServiceAccount, deploymentLister: listers.GetDeploymentLister(), serviceLister: listers.GetServiceLister(), - endpointsLister: listers.GetEndpointsLister(), serviceAccountLister: listers.GetServiceAccountLister(), roleBindingLister: listers.GetRoleBindingLister(), jsmChannelLister: listers.GetNatsJetstreamChannelLister(), @@ -307,23 +308,34 @@ func makeDispatcherDeploymentWithSpec() *appsv1.Deployment { }).Build() } -func makeEmptyEndpoints() *v1.Endpoints { - return &v1.Endpoints{ +func makeEmptyEndpointSlice() *discoveryv1.EndpointSlice { + return &discoveryv1.EndpointSlice{ TypeMeta: metav1.TypeMeta{ - APIVersion: "v1", - Kind: "Endpoints", + APIVersion: "discovery.k8s.io/v1", + Kind: "EndpointSlice", }, ObjectMeta: metav1.ObjectMeta{ Namespace: testNS, - Name: dispatcherServiceName, + Name: dispatcherServiceName + "-abc", + Labels: map[string]string{ + discoveryv1.LabelServiceName: dispatcherServiceName, + }, }, + AddressType: discoveryv1.AddressTypeIPv4, } } -func makeReadyEndpoints() *v1.Endpoints { - e := makeEmptyEndpoints() - e.Subsets = []v1.EndpointSubset{{Addresses: []v1.EndpointAddress{{IP: "1.1.1.1"}}}} - return e +func makeReadyEndpointSlice() *discoveryv1.EndpointSlice { + es := makeEmptyEndpointSlice() + es.Endpoints = []discoveryv1.Endpoint{ + { + Addresses: []string{"1.1.1.1"}, + Conditions: discoveryv1.EndpointConditions{ + Ready: ptr.To(true), + }, + }, + } + return es } func makeReadyDispatcherDeployment() *appsv1.Deployment { diff --git a/pkg/channel/jetstream/controller/reconciler.go b/pkg/channel/jetstream/controller/reconciler.go index cf00f0ff9..1ffcea671 100644 --- a/pkg/channel/jetstream/controller/reconciler.go +++ b/pkg/channel/jetstream/controller/reconciler.go @@ -40,6 +40,7 @@ import ( "knative.dev/pkg/reconciler" corev1 "k8s.io/api/core/v1" + discoveryv1 "k8s.io/api/discovery/v1" apierrs "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" @@ -98,7 +99,6 @@ type Reconciler struct { deploymentLister appsv1listers.DeploymentLister serviceLister corev1listers.ServiceLister - endpointsLister corev1listers.EndpointsLister serviceAccountLister corev1listers.ServiceAccountLister roleBindingLister rbacv1listers.RoleBindingLister jsmChannelLister jetstreamv1alpha1listers.NatsJetStreamChannelLister @@ -138,22 +138,38 @@ func (r *Reconciler) ReconcileKind(ctx context.Context, nc *v1alpha1.NatsJetStre return err } - // Get the Dispatcher Service Endpoints and propagate the status to the Channel - // endpoints has the same name as the service, so not a bug. - e, err := r.endpointsLister.Endpoints(dispatcherNamespace).Get(jetstream.DispatcherName) + // Get the Dispatcher Service EndpointSlices and propagate the status to the Channel + endpointSliceList, err := r.kubeClientSet.DiscoveryV1().EndpointSlices(dispatcherNamespace).List(ctx, metav1.ListOptions{ + LabelSelector: discoveryv1.LabelServiceName + "=" + jetstream.DispatcherName, + }) if err != nil { - if apierrs.IsNotFound(err) { - nc.Status.MarkEndpointsFailed("DispatcherEndpointsDoesNotExist", "Dispatcher Endpoints does not exist") - return nil - } - - logger.Errorw("Unable to get the dispatcher endpoints", zap.Error(err)) + logger.Errorw("Unable to list the dispatcher endpoint slices", zap.Error(err)) nc.Status.MarkEndpointsFailed("DispatcherEndpointsGetFailed", "Failed to get dispatcher endpoints") return err } + endpointSlices := endpointSliceList.Items + + if len(endpointSlices) == 0 { + nc.Status.MarkEndpointsFailed("DispatcherEndpointsDoesNotExist", "Dispatcher Endpoints does not exist") + return nil + } + + // Check if any endpoint slice has ready endpoints + hasReadyEndpoints := false + for _, es := range endpointSlices { + for _, endpoint := range es.Endpoints { + if endpoint.Conditions.Ready != nil && *endpoint.Conditions.Ready { + hasReadyEndpoints = true + break + } + } + if hasReadyEndpoints { + break + } + } - if len(e.Subsets) == 0 { - logger.Infow("No endpoints found for Dispatcher service") + if !hasReadyEndpoints { + logger.Infow("No ready endpoints found for Dispatcher service") nc.Status.MarkEndpointsFailed("DispatcherEndpointsNotReady", "There are no endpoints ready for Dispatcher service") return nil } diff --git a/pkg/reconciler/testing/listers.go b/pkg/reconciler/testing/listers.go index eb614fcad..fc99bf848 100644 --- a/pkg/reconciler/testing/listers.go +++ b/pkg/reconciler/testing/listers.go @@ -19,11 +19,13 @@ package testing import ( appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" + discoveryv1 "k8s.io/api/discovery/v1" rbacv1 "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/runtime" fakekubeclientset "k8s.io/client-go/kubernetes/fake" appsv1listers "k8s.io/client-go/listers/apps/v1" corev1listers "k8s.io/client-go/listers/core/v1" + discoveryv1listers "k8s.io/client-go/listers/discovery/v1" rbacv1listers "k8s.io/client-go/listers/rbac/v1" "k8s.io/client-go/tools/cache" fakenatsslientset "knative.dev/eventing-natss/pkg/client/clientset/versioned/fake" @@ -96,8 +98,8 @@ func (l *Listers) GetServiceAccountLister() corev1listers.ServiceAccountLister { return corev1listers.NewServiceAccountLister(l.indexerFor(&corev1.ServiceAccount{})) } -func (l *Listers) GetEndpointsLister() corev1listers.EndpointsLister { - return corev1listers.NewEndpointsLister(l.indexerFor(&corev1.Endpoints{})) +func (l *Listers) GetEndpointSliceLister() discoveryv1listers.EndpointSliceLister { + return discoveryv1listers.NewEndpointSliceLister(l.indexerFor(&discoveryv1.EndpointSlice{})) } func (l *Listers) GetRoleBindingLister() rbacv1listers.RoleBindingLister { diff --git a/test/e2e/cmd/producer/main.go b/test/e2e/cmd/producer/main.go index cfc0c11e0..f2c0715aa 100644 --- a/test/e2e/cmd/producer/main.go +++ b/test/e2e/cmd/producer/main.go @@ -53,7 +53,7 @@ func main() { log.Print("sleeping") time.Sleep(10 * time.Second) - log.Print("done sleeping, sending events now") + log.Print("done sleeping, sending events now, target: ", env.Sink) send(cloudevents.ContextWithRetriesExponentialBackoff(ctx, 10*time.Millisecond, 10), c, env.Count) // Wait. @@ -71,8 +71,9 @@ func send(ctx context.Context, c cloudevents.Client, count int) { "message": "Hello, World!", }) + log.Print("Sending event: ", i) // Try to send with retry. - ctx := cloudevents.ContextWithRetriesExponentialBackoff(ctx, 10*time.Millisecond, 100) + ctx := cloudevents.ContextWithRetriesExponentialBackoff(ctx, 10*time.Millisecond, 10) if result := c.Send(ctx, e); cloudevents.IsUndelivered(result) { log.Print("Failed to send: ", result.Error()) @@ -80,6 +81,8 @@ func send(ctx context.Context, c cloudevents.Client, count int) { log.Print("Sent: ", i) } else if cloudevents.IsNACK(result) { log.Print("Sent but not accepted: ", result.Error()) + } else { + log.Print("Unknown result: ", result) } time.Sleep(50 * time.Millisecond) } diff --git a/vendor/knative.dev/eventing/pkg/client/injection/reconciler/eventing/v1/trigger/controller.go b/vendor/knative.dev/eventing/pkg/client/injection/reconciler/eventing/v1/trigger/controller.go new file mode 100644 index 000000000..5242083c8 --- /dev/null +++ b/vendor/knative.dev/eventing/pkg/client/injection/reconciler/eventing/v1/trigger/controller.go @@ -0,0 +1,178 @@ +/* +Copyright 2021 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by injection-gen. DO NOT EDIT. + +package trigger + +import ( + context "context" + fmt "fmt" + reflect "reflect" + strings "strings" + + zap "go.uber.org/zap" + corev1 "k8s.io/api/core/v1" + labels "k8s.io/apimachinery/pkg/labels" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + scheme "k8s.io/client-go/kubernetes/scheme" + v1 "k8s.io/client-go/kubernetes/typed/core/v1" + record "k8s.io/client-go/tools/record" + versionedscheme "knative.dev/eventing/pkg/client/clientset/versioned/scheme" + client "knative.dev/eventing/pkg/client/injection/client" + trigger "knative.dev/eventing/pkg/client/injection/informers/eventing/v1/trigger" + kubeclient "knative.dev/pkg/client/injection/kube/client" + controller "knative.dev/pkg/controller" + logging "knative.dev/pkg/logging" + logkey "knative.dev/pkg/logging/logkey" + reconciler "knative.dev/pkg/reconciler" +) + +const ( + defaultControllerAgentName = "trigger-controller" + defaultFinalizerName = "triggers.eventing.knative.dev" +) + +// NewImpl returns a controller.Impl that handles queuing and feeding work from +// the queue through an implementation of controller.Reconciler, delegating to +// the provided Interface and optional Finalizer methods. OptionsFn is used to return +// controller.ControllerOptions to be used by the internal reconciler. +func NewImpl(ctx context.Context, r Interface, optionsFns ...controller.OptionsFn) *controller.Impl { + logger := logging.FromContext(ctx) + + // Check the options function input. It should be 0 or 1. + if len(optionsFns) > 1 { + logger.Fatal("Up to one options function is supported, found: ", len(optionsFns)) + } + + triggerInformer := trigger.Get(ctx) + + lister := triggerInformer.Lister() + + var promoteFilterFunc func(obj interface{}) bool + var promoteFunc = func(bkt reconciler.Bucket) {} + + rec := &reconcilerImpl{ + LeaderAwareFuncs: reconciler.LeaderAwareFuncs{ + PromoteFunc: func(bkt reconciler.Bucket, enq func(reconciler.Bucket, types.NamespacedName)) error { + + // Signal promotion event + promoteFunc(bkt) + + all, err := lister.List(labels.Everything()) + if err != nil { + return err + } + for _, elt := range all { + if promoteFilterFunc != nil { + if ok := promoteFilterFunc(elt); !ok { + continue + } + } + enq(bkt, types.NamespacedName{ + Namespace: elt.GetNamespace(), + Name: elt.GetName(), + }) + } + return nil + }, + }, + Client: client.Get(ctx), + Lister: lister, + reconciler: r, + finalizerName: defaultFinalizerName, + } + + ctrType := reflect.TypeOf(r).Elem() + ctrTypeName := fmt.Sprintf("%s.%s", ctrType.PkgPath(), ctrType.Name()) + ctrTypeName = strings.ReplaceAll(ctrTypeName, "/", ".") + + logger = logger.With( + zap.String(logkey.ControllerType, ctrTypeName), + zap.String(logkey.Kind, "eventing.knative.dev.Trigger"), + ) + + impl := controller.NewContext(ctx, rec, controller.ControllerOptions{WorkQueueName: ctrTypeName, Logger: logger}) + agentName := defaultControllerAgentName + + // Pass impl to the options. Save any optional results. + for _, fn := range optionsFns { + opts := fn(impl) + if opts.ConfigStore != nil { + rec.configStore = opts.ConfigStore + } + if opts.FinalizerName != "" { + rec.finalizerName = opts.FinalizerName + } + if opts.AgentName != "" { + agentName = opts.AgentName + } + if opts.SkipStatusUpdates { + rec.skipStatusUpdates = true + } + if opts.DemoteFunc != nil { + rec.DemoteFunc = opts.DemoteFunc + } + if opts.PromoteFilterFunc != nil { + promoteFilterFunc = opts.PromoteFilterFunc + } + if opts.PromoteFunc != nil { + promoteFunc = opts.PromoteFunc + } + if opts.UseServerSideApplyForFinalizers { + if opts.FinalizerFieldManager == "" { + logger.Fatal("FinalizerFieldManager must be provided when UseServerSideApplyForFinalizers is enabled") + } + rec.useServerSideApplyForFinalizers = true + rec.finalizerFieldManager = opts.FinalizerFieldManager + rec.forceApplyFinalizers = opts.ForceApplyFinalizers + } + } + + rec.Recorder = createRecorder(ctx, agentName) + + return impl +} + +func createRecorder(ctx context.Context, agentName string) record.EventRecorder { + logger := logging.FromContext(ctx) + + recorder := controller.GetEventRecorder(ctx) + if recorder == nil { + // Create event broadcaster + logger.Debug("Creating event broadcaster") + eventBroadcaster := record.NewBroadcaster() + watches := []watch.Interface{ + eventBroadcaster.StartLogging(logger.Named("event-broadcaster").Infof), + eventBroadcaster.StartRecordingToSink( + &v1.EventSinkImpl{Interface: kubeclient.Get(ctx).CoreV1().Events("")}), + } + recorder = eventBroadcaster.NewRecorder(scheme.Scheme, corev1.EventSource{Component: agentName}) + go func() { + <-ctx.Done() + for _, w := range watches { + w.Stop() + } + }() + } + + return recorder +} + +func init() { + versionedscheme.AddToScheme(scheme.Scheme) +} diff --git a/vendor/knative.dev/eventing/pkg/client/injection/reconciler/eventing/v1/trigger/reconciler.go b/vendor/knative.dev/eventing/pkg/client/injection/reconciler/eventing/v1/trigger/reconciler.go new file mode 100644 index 000000000..f9ed704d5 --- /dev/null +++ b/vendor/knative.dev/eventing/pkg/client/injection/reconciler/eventing/v1/trigger/reconciler.go @@ -0,0 +1,561 @@ +/* +Copyright 2021 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by injection-gen. DO NOT EDIT. + +package trigger + +import ( + context "context" + json "encoding/json" + fmt "fmt" + + zap "go.uber.org/zap" + zapcore "go.uber.org/zap/zapcore" + corev1 "k8s.io/api/core/v1" + equality "k8s.io/apimachinery/pkg/api/equality" + errors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + types "k8s.io/apimachinery/pkg/types" + sets "k8s.io/apimachinery/pkg/util/sets" + scheme "k8s.io/client-go/kubernetes/scheme" + record "k8s.io/client-go/tools/record" + v1 "knative.dev/eventing/pkg/apis/eventing/v1" + versioned "knative.dev/eventing/pkg/client/clientset/versioned" + eventingv1 "knative.dev/eventing/pkg/client/listers/eventing/v1" + controller "knative.dev/pkg/controller" + kmp "knative.dev/pkg/kmp" + logging "knative.dev/pkg/logging" + reconciler "knative.dev/pkg/reconciler" +) + +// Interface defines the strongly typed interfaces to be implemented by a +// controller reconciling v1.Trigger. +type Interface interface { + // ReconcileKind implements custom logic to reconcile v1.Trigger. Any changes + // to the objects .Status or .Finalizers will be propagated to the stored + // object. It is recommended that implementors do not call any update calls + // for the Kind inside of ReconcileKind, it is the responsibility of the calling + // controller to propagate those properties. The resource passed to ReconcileKind + // will always have an empty deletion timestamp. + ReconcileKind(ctx context.Context, o *v1.Trigger) reconciler.Event +} + +// Finalizer defines the strongly typed interfaces to be implemented by a +// controller finalizing v1.Trigger. +type Finalizer interface { + // FinalizeKind implements custom logic to finalize v1.Trigger. Any changes + // to the objects .Status or .Finalizers will be ignored. Returning a nil or + // Normal type reconciler.Event will allow the finalizer to be deleted on + // the resource. The resource passed to FinalizeKind will always have a set + // deletion timestamp. + FinalizeKind(ctx context.Context, o *v1.Trigger) reconciler.Event +} + +// ReadOnlyInterface defines the strongly typed interfaces to be implemented by a +// controller reconciling v1.Trigger if they want to process resources for which +// they are not the leader. +type ReadOnlyInterface interface { + // ObserveKind implements logic to observe v1.Trigger. + // This method should not write to the API. + ObserveKind(ctx context.Context, o *v1.Trigger) reconciler.Event +} + +type doReconcile func(ctx context.Context, o *v1.Trigger) reconciler.Event + +// reconcilerImpl implements controller.Reconciler for v1.Trigger resources. +type reconcilerImpl struct { + // LeaderAwareFuncs is inlined to help us implement reconciler.LeaderAware. + reconciler.LeaderAwareFuncs + + // Client is used to write back status updates. + Client versioned.Interface + + // Listers index properties about resources. + Lister eventingv1.TriggerLister + + // Recorder is an event recorder for recording Event resources to the + // Kubernetes API. + Recorder record.EventRecorder + + // configStore allows for decorating a context with config maps. + // +optional + configStore reconciler.ConfigStore + + // reconciler is the implementation of the business logic of the resource. + reconciler Interface + + // finalizerName is the name of the finalizer to reconcile. + finalizerName string + + // useServerSideApplyForFinalizers configures whether to use server-side apply for finalizer management + useServerSideApplyForFinalizers bool + + // finalizerFieldManager is the field manager name for server-side apply of finalizers + finalizerFieldManager string + + // forceApplyFinalizers configures whether to force server-side apply for finalizers + forceApplyFinalizers bool + + // skipStatusUpdates configures whether or not this reconciler automatically updates + // the status of the reconciled resource. + skipStatusUpdates bool +} + +// Check that our Reconciler implements controller.Reconciler. +var _ controller.Reconciler = (*reconcilerImpl)(nil) + +// Check that our generated Reconciler is always LeaderAware. +var _ reconciler.LeaderAware = (*reconcilerImpl)(nil) + +func NewReconciler(ctx context.Context, logger *zap.SugaredLogger, client versioned.Interface, lister eventingv1.TriggerLister, recorder record.EventRecorder, r Interface, options ...controller.Options) controller.Reconciler { + // Check the options function input. It should be 0 or 1. + if len(options) > 1 { + logger.Fatal("Up to one options struct is supported, found: ", len(options)) + } + + // Fail fast when users inadvertently implement the other LeaderAware interface. + // For the typed reconcilers, Promote shouldn't take any arguments. + if _, ok := r.(reconciler.LeaderAware); ok { + logger.Fatalf("%T implements the incorrect LeaderAware interface. Promote() should not take an argument as genreconciler handles the enqueuing automatically.", r) + } + + rec := &reconcilerImpl{ + LeaderAwareFuncs: reconciler.LeaderAwareFuncs{ + PromoteFunc: func(bkt reconciler.Bucket, enq func(reconciler.Bucket, types.NamespacedName)) error { + all, err := lister.List(labels.Everything()) + if err != nil { + return err + } + for _, elt := range all { + // TODO: Consider letting users specify a filter in options. + enq(bkt, types.NamespacedName{ + Namespace: elt.GetNamespace(), + Name: elt.GetName(), + }) + } + return nil + }, + }, + Client: client, + Lister: lister, + Recorder: recorder, + reconciler: r, + finalizerName: defaultFinalizerName, + } + + for _, opts := range options { + if opts.ConfigStore != nil { + rec.configStore = opts.ConfigStore + } + if opts.FinalizerName != "" { + rec.finalizerName = opts.FinalizerName + } + if opts.SkipStatusUpdates { + rec.skipStatusUpdates = true + } + if opts.DemoteFunc != nil { + rec.DemoteFunc = opts.DemoteFunc + } + if opts.UseServerSideApplyForFinalizers { + if opts.FinalizerFieldManager == "" { + logger.Fatal("FinalizerFieldManager must be provided when UseServerSideApplyForFinalizers is enabled") + } + rec.useServerSideApplyForFinalizers = true + rec.finalizerFieldManager = opts.FinalizerFieldManager + rec.forceApplyFinalizers = opts.ForceApplyFinalizers + } + } + + return rec +} + +// Reconcile implements controller.Reconciler +func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error { + logger := logging.FromContext(ctx) + + // Initialize the reconciler state. This will convert the namespace/name + // string into a distinct namespace and name, determine if this instance of + // the reconciler is the leader, and any additional interfaces implemented + // by the reconciler. Returns an error is the resource key is invalid. + s, err := newState(key, r) + if err != nil { + logger.Error("Invalid resource key: ", key) + return nil + } + + // If we are not the leader, and we don't implement either ReadOnly + // observer interfaces, then take a fast-path out. + if s.isNotLeaderNorObserver() { + return controller.NewSkipKey(key) + } + + // If configStore is set, attach the frozen configuration to the context. + if r.configStore != nil { + ctx = r.configStore.ToContext(ctx) + } + + // Add the recorder to context. + ctx = controller.WithEventRecorder(ctx, r.Recorder) + + // Get the resource with this namespace/name. + + getter := r.Lister.Triggers(s.namespace) + + original, err := getter.Get(s.name) + + if errors.IsNotFound(err) { + // The resource may no longer exist, in which case we stop processing and call + // the ObserveDeletion handler if appropriate. + logger.Debugf("Resource %q no longer exists", key) + if del, ok := r.reconciler.(reconciler.OnDeletionInterface); ok { + return del.ObserveDeletion(ctx, types.NamespacedName{ + Namespace: s.namespace, + Name: s.name, + }) + } + return nil + } else if err != nil { + return err + } + + // Don't modify the informers copy. + resource := original.DeepCopy() + + var reconcileEvent reconciler.Event + + name, do := s.reconcileMethodFor(resource) + // Append the target method to the logger. + logger = logger.With(zap.String("targetMethod", name)) + switch name { + case reconciler.DoReconcileKind: + // Set and update the finalizer on resource if r.reconciler + // implements Finalizer. + if resource, err = r.setFinalizerIfFinalizer(ctx, resource); err != nil { + return fmt.Errorf("failed to set finalizers: %w", err) + } + + if !r.skipStatusUpdates { + reconciler.PreProcessReconcile(ctx, resource) + } + + // Reconcile this copy of the resource and then write back any status + // updates regardless of whether the reconciliation errored out. + reconcileEvent = do(ctx, resource) + + if !r.skipStatusUpdates { + reconciler.PostProcessReconcile(ctx, resource, original) + } + + case reconciler.DoFinalizeKind: + // For finalizing reconcilers, if this resource being marked for deletion + // and reconciled cleanly (nil or normal event), remove the finalizer. + reconcileEvent = do(ctx, resource) + + if resource, err = r.clearFinalizer(ctx, resource, reconcileEvent); err != nil { + return fmt.Errorf("failed to clear finalizers: %w", err) + } + + case reconciler.DoObserveKind: + // Observe any changes to this resource, since we are not the leader. + reconcileEvent = do(ctx, resource) + + } + + // Synchronize the status. + switch { + case r.skipStatusUpdates: + // This reconciler implementation is configured to skip resource updates. + // This may mean this reconciler does not observe spec, but reconciles external changes. + case equality.Semantic.DeepEqual(original.Status, resource.Status): + // If we didn't change anything then don't call updateStatus. + // This is important because the copy we loaded from the injectionInformer's + // cache may be stale and we don't want to overwrite a prior update + // to status with this stale state. + case !s.isLeader: + // High-availability reconcilers may have many replicas watching the resource, but only + // the elected leader is expected to write modifications. + logger.Warn("Saw status changes when we aren't the leader!") + default: + if err = r.updateStatus(ctx, logger, original, resource); err != nil { + logger.Warnw("Failed to update resource status", zap.Error(err)) + r.Recorder.Eventf(resource, corev1.EventTypeWarning, "UpdateFailed", + "Failed to update status for %q: %v", resource.Name, err) + return err + } + } + + // Report the reconciler event, if any. + if reconcileEvent != nil { + var event *reconciler.ReconcilerEvent + if reconciler.EventAs(reconcileEvent, &event) { + logger.Infow("Returned an event", zap.Any("event", reconcileEvent)) + r.Recorder.Event(resource, event.EventType, event.Reason, event.Error()) + + // the event was wrapped inside an error, consider the reconciliation as failed + if _, isEvent := reconcileEvent.(*reconciler.ReconcilerEvent); !isEvent { + return reconcileEvent + } + return nil + } + + if controller.IsSkipKey(reconcileEvent) { + // This is a wrapped error, don't emit an event. + } else if ok, _ := controller.IsRequeueKey(reconcileEvent); ok { + // This is a wrapped error, don't emit an event. + } else if errors.IsConflict(reconcileEvent) { + // Conflict errors are expected, don't emit an event. + } else { + logger.Errorw("Returned an error", zap.Error(reconcileEvent)) + r.Recorder.Event(resource, corev1.EventTypeWarning, "InternalError", reconcileEvent.Error()) + } + return reconcileEvent + } + + return nil +} + +func (r *reconcilerImpl) updateStatus(ctx context.Context, logger *zap.SugaredLogger, existing *v1.Trigger, desired *v1.Trigger) error { + existing = existing.DeepCopy() + return reconciler.RetryUpdateConflicts(func(attempts int) (err error) { + // The first iteration tries to use the injectionInformer's state, subsequent attempts fetch the latest state via API. + if attempts > 0 { + + getter := r.Client.EventingV1().Triggers(desired.Namespace) + + existing, err = getter.Get(ctx, desired.Name, metav1.GetOptions{}) + if err != nil { + return err + } + } + + // If there's nothing to update, just return. + if equality.Semantic.DeepEqual(existing.Status, desired.Status) { + return nil + } + + if logger.Desugar().Core().Enabled(zapcore.DebugLevel) { + if diff, err := kmp.SafeDiff(existing.Status, desired.Status); err == nil && diff != "" { + logger.Debug("Updating status with: ", diff) + } + } + + existing.Status = desired.Status + + updater := r.Client.EventingV1().Triggers(existing.Namespace) + + _, err = updater.UpdateStatus(ctx, existing, metav1.UpdateOptions{}) + return err + }) +} + +// updateFinalizersFiltered will update the Finalizers of the resource. +// TODO: this method could be generic and sync all finalizers. For now it only +// updates defaultFinalizerName or its override. +func (r *reconcilerImpl) updateFinalizersFiltered(ctx context.Context, resource *v1.Trigger, desiredFinalizers sets.Set[string]) (*v1.Trigger, error) { + if r.useServerSideApplyForFinalizers { + return r.updateFinalizersFilteredServerSideApply(ctx, resource, desiredFinalizers) + } + return r.updateFinalizersFilteredMergePatch(ctx, resource, desiredFinalizers) +} + +// updateFinalizersFilteredServerSideApply uses server-side apply to manage only this controller's finalizer. +func (r *reconcilerImpl) updateFinalizersFilteredServerSideApply(ctx context.Context, resource *v1.Trigger, desiredFinalizers sets.Set[string]) (*v1.Trigger, error) { + // Check if we need to do anything + existingFinalizers := sets.New[string](resource.Finalizers...) + + var finalizers []string + if desiredFinalizers.Has(r.finalizerName) { + if existingFinalizers.Has(r.finalizerName) { + // Nothing to do. + return resource, nil + } + // Apply configuration with only our finalizer to add it. + finalizers = []string{r.finalizerName} + } else { + if !existingFinalizers.Has(r.finalizerName) { + // Nothing to do. + return resource, nil + } + // For removal, we apply an empty configuration for our finalizer field manager. + // This effectively removes our finalizer while preserving others. + finalizers = []string{} // Empty array removes our managed finalizers + } + + // Determine GVK + gvks, _, err := scheme.Scheme.ObjectKinds(resource) + if err != nil || len(gvks) == 0 { + return resource, fmt.Errorf("failed to determine GVK for resource: %w", err) + } + gvk := gvks[0] + + // Create apply configuration + applyConfig := map[string]interface{}{ + "apiVersion": gvk.GroupVersion().String(), + "kind": gvk.Kind, + "metadata": map[string]interface{}{ + "name": resource.Name, + "uid": resource.UID, + "finalizers": finalizers, + }, + } + + applyConfig["metadata"].(map[string]interface{})["namespace"] = resource.Namespace + + patch, err := json.Marshal(applyConfig) + if err != nil { + return resource, err + } + + patcher := r.Client.EventingV1().Triggers(resource.Namespace) + + patchOpts := metav1.PatchOptions{ + FieldManager: r.finalizerFieldManager, + Force: &r.forceApplyFinalizers, + } + + updated, err := patcher.Patch(ctx, resource.Name, types.ApplyPatchType, patch, patchOpts) + if err != nil { + if !errors.IsConflict(err) { + r.Recorder.Eventf(resource, corev1.EventTypeWarning, "FinalizerUpdateFailed", + "Failed to update finalizers for %q via server-side apply: %v", resource.Name, err) + } + } else { + r.Recorder.Eventf(updated, corev1.EventTypeNormal, "FinalizerUpdate", + "Updated finalizers for %q via server-side apply", resource.GetName()) + } + return updated, err +} + +// updateFinalizersFilteredMergePatch uses merge patch to manage finalizers (legacy behavior). +func (r *reconcilerImpl) updateFinalizersFilteredMergePatch(ctx context.Context, resource *v1.Trigger, desiredFinalizers sets.Set[string]) (*v1.Trigger, error) { + // Don't modify the informers copy. + existing := resource.DeepCopy() + + var finalizers []string + + // If there's nothing to update, just return. + existingFinalizers := sets.New[string](existing.Finalizers...) + + if desiredFinalizers.Has(r.finalizerName) { + if existingFinalizers.Has(r.finalizerName) { + // Nothing to do. + return resource, nil + } + // Add the finalizer. + finalizers = append(existing.Finalizers, r.finalizerName) + } else { + if !existingFinalizers.Has(r.finalizerName) { + // Nothing to do. + return resource, nil + } + // Remove the finalizer. + existingFinalizers.Delete(r.finalizerName) + finalizers = sets.List(existingFinalizers) + } + + mergePatch := map[string]interface{}{ + "metadata": map[string]interface{}{ + "finalizers": finalizers, + "resourceVersion": existing.ResourceVersion, + }, + } + + patch, err := json.Marshal(mergePatch) + if err != nil { + return resource, err + } + + patcher := r.Client.EventingV1().Triggers(resource.Namespace) + + resourceName := resource.Name + updated, err := patcher.Patch(ctx, resourceName, types.MergePatchType, patch, metav1.PatchOptions{}) + if err != nil { + if !errors.IsConflict(err) { + r.Recorder.Eventf(existing, corev1.EventTypeWarning, "FinalizerUpdateFailed", + "Failed to update finalizers for %q: %v", resourceName, err) + } + } else { + r.Recorder.Eventf(updated, corev1.EventTypeNormal, "FinalizerUpdate", + "Updated %q finalizers", resource.GetName()) + } + return updated, err +} + +func (r *reconcilerImpl) setFinalizerIfFinalizer(ctx context.Context, resource *v1.Trigger) (*v1.Trigger, error) { + if _, ok := r.reconciler.(Finalizer); !ok { + return resource, nil + } + + finalizers := sets.New[string](resource.Finalizers...) + + // If this resource is not being deleted, mark the finalizer. + if resource.GetDeletionTimestamp().IsZero() { + finalizers.Insert(r.finalizerName) + } + + // Synchronize the finalizers filtered by r.finalizerName. + return r.updateFinalizersFiltered(ctx, resource, finalizers) +} + +func (r *reconcilerImpl) clearFinalizer(ctx context.Context, resource *v1.Trigger, reconcileEvent reconciler.Event) (*v1.Trigger, error) { + if _, ok := r.reconciler.(Finalizer); !ok { + return resource, nil + } + if resource.GetDeletionTimestamp().IsZero() { + return resource, nil + } + + finalizers := sets.New[string](resource.Finalizers...) + + if reconcileEvent != nil { + var event *reconciler.ReconcilerEvent + if reconciler.EventAs(reconcileEvent, &event) { + if event.EventType == corev1.EventTypeNormal { + finalizers.Delete(r.finalizerName) + } + } + } else { + finalizers.Delete(r.finalizerName) + } + + // Synchronize the finalizers filtered by r.finalizerName. + updated, err := r.updateFinalizersFiltered(ctx, resource, finalizers) + if err != nil { + // Check if the resource still exists by querying the API server to avoid logging errors + // when reconciling stale object from cache while the object is actually deleted. + logger := logging.FromContext(ctx) + + getter := r.Client.EventingV1().Triggers(resource.Namespace) + + _, getErr := getter.Get(ctx, resource.Name, metav1.GetOptions{}) + if errors.IsNotFound(getErr) { + // Resource no longer exists, which could happen during deletion + logger.Debugw("Resource no longer exists while clearing finalizers", + "resource", resource.GetName(), + "namespace", resource.GetNamespace(), + "originalError", err) + // Return the original resource since the finalizer clearing is effectively complete + return resource, nil + } + + // For other errors, return the original error + return updated, err + } + + return updated, nil +} diff --git a/vendor/knative.dev/eventing/pkg/client/injection/reconciler/eventing/v1/trigger/state.go b/vendor/knative.dev/eventing/pkg/client/injection/reconciler/eventing/v1/trigger/state.go new file mode 100644 index 000000000..9f261c441 --- /dev/null +++ b/vendor/knative.dev/eventing/pkg/client/injection/reconciler/eventing/v1/trigger/state.go @@ -0,0 +1,97 @@ +/* +Copyright 2021 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by injection-gen. DO NOT EDIT. + +package trigger + +import ( + fmt "fmt" + + types "k8s.io/apimachinery/pkg/types" + cache "k8s.io/client-go/tools/cache" + v1 "knative.dev/eventing/pkg/apis/eventing/v1" + reconciler "knative.dev/pkg/reconciler" +) + +// state is used to track the state of a reconciler in a single run. +type state struct { + // key is the original reconciliation key from the queue. + key string + // namespace is the namespace split from the reconciliation key. + namespace string + // name is the name split from the reconciliation key. + name string + // reconciler is the reconciler. + reconciler Interface + // roi is the read only interface cast of the reconciler. + roi ReadOnlyInterface + // isROI (Read Only Interface) the reconciler only observes reconciliation. + isROI bool + // isLeader the instance of the reconciler is the elected leader. + isLeader bool +} + +func newState(key string, r *reconcilerImpl) (*state, error) { + // Convert the namespace/name string into a distinct namespace and name. + namespace, name, err := cache.SplitMetaNamespaceKey(key) + if err != nil { + return nil, fmt.Errorf("invalid resource key: %s", key) + } + + roi, isROI := r.reconciler.(ReadOnlyInterface) + + isLeader := r.IsLeaderFor(types.NamespacedName{ + Namespace: namespace, + Name: name, + }) + + return &state{ + key: key, + namespace: namespace, + name: name, + reconciler: r.reconciler, + roi: roi, + isROI: isROI, + isLeader: isLeader, + }, nil +} + +// isNotLeaderNorObserver checks to see if this reconciler with the current +// state is enabled to do any work or not. +// isNotLeaderNorObserver returns true when there is no work possible for the +// reconciler. +func (s *state) isNotLeaderNorObserver() bool { + if !s.isLeader && !s.isROI { + // If we are not the leader, and we don't implement the ReadOnly + // interface, then take a fast-path out. + return true + } + return false +} + +func (s *state) reconcileMethodFor(o *v1.Trigger) (string, doReconcile) { + if o.GetDeletionTimestamp().IsZero() { + if s.isLeader { + return reconciler.DoReconcileKind, s.reconciler.ReconcileKind + } else if s.isROI { + return reconciler.DoObserveKind, s.roi.ObserveKind + } + } else if fin, ok := s.reconciler.(Finalizer); s.isLeader && ok { + return reconciler.DoFinalizeKind, fin.FinalizeKind + } + return "unknown", nil +} diff --git a/vendor/knative.dev/pkg/client/injection/kube/informers/core/v1/endpoints/endpoints.go b/vendor/knative.dev/pkg/client/injection/kube/informers/core/v1/endpoints/endpoints.go deleted file mode 100644 index 7c2b716d7..000000000 --- a/vendor/knative.dev/pkg/client/injection/kube/informers/core/v1/endpoints/endpoints.go +++ /dev/null @@ -1,52 +0,0 @@ -/* -Copyright 2022 The Knative Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// Code generated by injection-gen. DO NOT EDIT. - -package endpoints - -import ( - context "context" - - v1 "k8s.io/client-go/informers/core/v1" - factory "knative.dev/pkg/client/injection/kube/informers/factory" - controller "knative.dev/pkg/controller" - injection "knative.dev/pkg/injection" - logging "knative.dev/pkg/logging" -) - -func init() { - injection.Default.RegisterInformer(withInformer) -} - -// Key is used for associating the Informer inside the context.Context. -type Key struct{} - -func withInformer(ctx context.Context) (context.Context, controller.Informer) { - f := factory.Get(ctx) - inf := f.Core().V1().Endpoints() - return context.WithValue(ctx, Key{}, inf), inf.Informer() -} - -// Get extracts the typed informer from the context. -func Get(ctx context.Context) v1.EndpointsInformer { - untyped := ctx.Value(Key{}) - if untyped == nil { - logging.FromContext(ctx).Panic( - "Unable to fetch k8s.io/client-go/informers/core/v1.EndpointsInformer from context.") - } - return untyped.(v1.EndpointsInformer) -} diff --git a/vendor/modules.txt b/vendor/modules.txt index 043401f4d..d11c24406 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1277,6 +1277,7 @@ knative.dev/eventing/pkg/client/injection/informers/eventing/v1/broker knative.dev/eventing/pkg/client/injection/informers/eventing/v1/trigger knative.dev/eventing/pkg/client/injection/informers/factory knative.dev/eventing/pkg/client/injection/reconciler/eventing/v1/broker +knative.dev/eventing/pkg/client/injection/reconciler/eventing/v1/trigger knative.dev/eventing/pkg/client/listers/eventing/v1 knative.dev/eventing/pkg/client/listers/eventing/v1alpha1 knative.dev/eventing/pkg/client/listers/eventing/v1beta1 @@ -1324,7 +1325,6 @@ knative.dev/pkg/client/injection/kube/client/fake knative.dev/pkg/client/injection/kube/informers/admissionregistration/v1/mutatingwebhookconfiguration knative.dev/pkg/client/injection/kube/informers/admissionregistration/v1/validatingwebhookconfiguration knative.dev/pkg/client/injection/kube/informers/apps/v1/deployment -knative.dev/pkg/client/injection/kube/informers/core/v1/endpoints knative.dev/pkg/client/injection/kube/informers/core/v1/pod knative.dev/pkg/client/injection/kube/informers/core/v1/service knative.dev/pkg/client/injection/kube/informers/core/v1/serviceaccount From 93c36bfaef7f77fcbe5ee75ddd2bebc6ea4f4b2d Mon Sep 17 00:00:00 2001 From: Knative Automation Date: Wed, 28 Jan 2026 02:47:31 -0500 Subject: [PATCH 05/10] upgrade to latest dependencies (#726) bumping knative.dev/eventing cb840ef...d740aa4: > d740aa4 [main] Upgrade to latest dependencies (# 8863) Signed-off-by: Knative Automation --- go.mod | 2 +- go.sum | 4 ++-- vendor/modules.txt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index c5a8873ac..059adcdbe 100644 --- a/go.mod +++ b/go.mod @@ -29,7 +29,7 @@ require ( k8s.io/client-go v0.34.3 k8s.io/code-generator v0.34.3 k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 - knative.dev/eventing v0.47.1-0.20260126225531-cb840efa2fbb + knative.dev/eventing v0.48.0 knative.dev/hack v0.0.0-20260120115810-bf6758cba446 knative.dev/pkg v0.0.0-20260120122510-4a022ed9999a knative.dev/reconciler-test v0.0.0-20260120140419-4301404c03ce diff --git a/go.sum b/go.sum index 5404df11b..aebba5430 100644 --- a/go.sum +++ b/go.sum @@ -1071,8 +1071,8 @@ k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOP k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts= k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -knative.dev/eventing v0.47.1-0.20260126225531-cb840efa2fbb h1:cokLprP7InWi/f/BLtggVxuXQ3E789NwHyMwUTUxvpc= -knative.dev/eventing v0.47.1-0.20260126225531-cb840efa2fbb/go.mod h1:IFPgtLfE39zQAXTsgWTJdKjdCp3ehekqmosQu6CHS2A= +knative.dev/eventing v0.48.0 h1:jBwRXnhhfJ4S5EfBSgkhJzqtcfOIr8kfG1qoaXX56KA= +knative.dev/eventing v0.48.0/go.mod h1:QcPwZqLMwN1hyDXRdCCt7GCR2R+4cDULGoJf1LKuGEE= knative.dev/hack v0.0.0-20260120115810-bf6758cba446 h1:Y8raYHIuAL9/gUKGYD9/dD+EqUTmrpqVDowzfUVSlGs= knative.dev/hack v0.0.0-20260120115810-bf6758cba446/go.mod h1:L5RzHgbvam0u8QFHfzCX6MKxu/a/gIGEdaRBqNiVbl0= knative.dev/pkg v0.0.0-20260120122510-4a022ed9999a h1:9f29OTA7w/iVIX6PS6yveVVzNbcUS74eQfchVe8o2/4= diff --git a/vendor/modules.txt b/vendor/modules.txt index d11c24406..be5ccd853 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1198,7 +1198,7 @@ k8s.io/utils/net k8s.io/utils/pointer k8s.io/utils/ptr k8s.io/utils/trace -# knative.dev/eventing v0.47.1-0.20260126225531-cb840efa2fbb +# knative.dev/eventing v0.48.0 ## explicit; go 1.24.0 knative.dev/eventing/pkg/apis knative.dev/eventing/pkg/apis/common/integration/v1alpha1 From ca132c0e0a330a484ce5c12f7e8feae4520a6c06 Mon Sep 17 00:00:00 2001 From: Andrii Stelmashenko Date: Thu, 29 Jan 2026 16:13:33 +0200 Subject: [PATCH 06/10] custom broker ingress (#719) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * added broker ingress * upgrade to latest dependencies (#721) bumping knative.dev/reconciler-test b74774d...d1b946d: > d1b946d upgrade to latest dependencies (# 853) bumping knative.dev/hack ee8a1b2...bf6758c: > bf6758c bump GKE default version to 1.33 (# 458) bumping knative.dev/pkg af2d223...4a022ed: > 4a022ed upgrade to latest dependencies (# 3313) > 49cf63e bump K8s min version to 1.33 (# 3312) Signed-off-by: Knative Automation * upgrade to latest dependencies (#725) bumping knative.dev/eventing 59b517c...cb840ef: > cb840ef fix: Wait for full deployment rollout before marking IntegrationSink Ready (# 8858) > 86c43a6 Increase poll timings for IntegrationSource tests (# 8860) > 3226294 Prevent AuthZ test pollution by ensuring unready test runs last (# 8859) > ccf232a fix unused linter errors (# 8851) > 4303f77 Remove k8s 1.32 runs in KinD e2e tests (# 8857) > e169933 Run eventing office hours Slack reminder every other week (# 8855) > 71a6c9f fix: EventTransform not updating when expression changes (# 8848) > 45c284b [main] Upgrade to latest dependencies (# 8847) bumping knative.dev/reconciler-test d1b946d...4301404: > 4301404 upgrade to latest dependencies (# 854) Signed-off-by: Knative Automation * added trigger controller and reconciler (#716) * added trigger controller and reconciler * added unit tests * fixed ling and style errors * removed unused parameter * proceed deleting consumer even when broker not found * removed todo * removed empty lines * run go mod vendor * update-codegen * Update community files (#717) Signed-off-by: Knative Automation * upgrade to latest dependencies (#708) bumping k8s.io/gengo/v2 1244d31...85fd79d: > 85fd79d Merge pull request # 304 from jpbetz/full-parser > b22feca Merge pull request # 306 from carsontham/support-omitzero-in-LookupJSON > b37a58d Add utility functions to access args > cc0d9c4 Add named arg parser (# 299) > c990d91 added support for omitzero > db65aa6 Apply feedback > 3d52566 Merge pull request # 303 from thockin/master > 1460848 Switch to # for comments > 5d81b21 Merge pull request # 297 from jpbetz/tag-parser > ea49fb3 Treat embedded fields as inline > 44a1af5 Limit whitespace to after : and , for named args > 221fad0 Merge pull request # 296 from aaron-prindle/rawstring-tag-parsing > ae132b4 Apply feedback > dcc70af Simplify whitespace handling and collapse needless states > e3bc6f1 Merge pull request # 293 from shashankram/fix-ignore > fc15268 feat: add raw string support to gengo comment tag arg parsing > 5502674 Add JSON tag argument support > 53609f6 Rename TypedTag to Tag > 83e62b2 parser/test: fix bug in comparison > 271cbf8 Apply feedback > c926ad7 Add missing break > c375a88 Add dedicated scanner tests > 7d6753c Add godoc to TypeTag.String(), add a 4 level nesting test > 4842742 Add a simple scanner and move literal value parsing into micro-FSMs. > 461f54c Test parse string for extract, fix incomplete value bug > 2f7fe8a Add opt-in support for value parsing to ExtractFunctionStyleCommentTags and ExtractSingleBoolCommentTag > e0a2d5e Expand and clean up tests > 88534e7 Add chained tag parsing bumping go.opentelemetry.io/otel/metric 84e3f3a...6ce1429: > 6ce1429 Release v1.39.0 (# 7676) > 12e421a sdk/log: move Enabled method from FilterProcessor to Processor (# 7639) > 5982f16 fix(deps): update module golang.org/x/sys to v0.39.0 (# 7684) > 9288378 chore(deps): update module golang.org/x/sync to v0.19.0 (# 7683) > ee3dfef chore(deps): update github.com/securego/gosec/v2 digest to 41f28e2 (# 7682) > 9345d1f fix(deps): update module github.com/golangci/golangci-lint/v2 to v2.7.2 (# 7680) > d03b033 Check context prior to delaying retry in OTLP exporters (# 7678) > 61765e7 Fix flaky `TestClientInstrumentation` (# 7677) > a54721c chore(deps): update module github.com/go-git/go-billy/v5 to v5.7.0 (# 7679) > 746d086 chore(deps): update github/codeql-action action to v4.31.7 (# 7675) > 1bc9713 Regenerate `ErrorType` documentation in `semconv/v1.37.0` (# 7667) > 5a692cf Fix whitespace in `semconv/v1.33.0` (# 7665) > 4eff89b Fix whitespace in `semconv/v1.32.0` (# 7666) > d1825df Fix whitespace in `semconv/v1.36.0` (# 7663) > ddc307d Fix package name documentation and missing copyright in `semconv/v1.32.0` (# 7659) > 3e85447 Fix whitespace in `semconv/v1.34.0` (# 7664) > a64b9ec Fix package documentation name and return error in `semconv/v1.36.0` (# 7656) > be85ff8 Fix whitespace in `semconv/v1.37.0` (# 7660) > cddeb68 Fix package name documentation and missing copyright in `semconv/v1.34.0` (# 7657) > 3659648 Fix package name documentation and missing copyright in `semconv/v1.33.0` (# 7658) > e69beb8 Fix package documentation name and return err in `semconv/v1.37.0` (# 7655) > ddd0420 fix(deps): update module github.com/golangci/golangci-lint/v2 to v2.7.1 (# 7671) > 21e75b9 chore(deps): update module github.com/ldez/gomoddirectives to v0.8.0 (# 7669) > ca53078 Instrument the `SimpleLogProcessor` from sdk/log (# 7548) > df83919 chore(deps): update module github.com/spf13/cobra to v1.10.2 (# 7668) > 6af2f2f Generate semconv/v1.38.0 (# 7648) > 20fdce2 fix(deps): update module github.com/golangci/golangci-lint/v2 to v2.7.0 (# 7661) > 79144c5 chore(deps): update golang.org/x/telemetry digest to 8fff8a5 (# 7654) > d6aef9b chore(deps): update actions/checkout action to v6.0.1 (# 7651) > 98f784d fix(deps): update googleapis to ff82c1b (# 7652) > 5ea2a32 chore(deps): update actions/stale action to v10.1.1 (# 7653) > 85c1dfe chore(deps): update module github.com/godoc-lint/godoc-lint to v0.10.2 (# 7650) > 729bd6e fix(deps): update module go.opentelemetry.io/collector/pdata to v1.47.0 (# 7646) > d5faa3e chore(deps): update github/codeql-action action to v4.31.6 (# 7644) > 495a2c2 chore(deps): update golang.org/x/telemetry digest to abf20d0 (# 7643) > a72103c chore(deps): update module github.com/hashicorp/go-version to v1.8.0 (# 7641) > a0a0acd fix(deps): update golang.org/x to 87e1e73 (# 7636) > c152342 fix(deps): update googleapis to 79d6a2a (# 7634) > 6a16d13 chore(deps): update golang.org/x/telemetry digest to 55bbf37 (# 7633) > d3b4232 chore(deps): update module github.com/tomarrell/wrapcheck/v2 to v2.12.0 (# 7632) > cbcbb2d chore(deps): update github/codeql-action action to v4.31.5 (# 7631) > 414432d chore(deps): update module github.com/go-git/go-git/v5 to v5.16.4 (# 7629) > 7323fc7 chore(deps): update golang.org/x/telemetry digest to e487659 (# 7619) > d799e06 chore(deps): update module github.com/prometheus/common to v0.67.4 (# 7626) > c6e4cca chore(deps): update module dev.gaijin.team/go/golib to v0.8.0 (# 7627) > da01323 otlploghttp: support OTEL_EXPORTER_OTLP_LOGS_INSECURE and OTEL_EXPORTER_OTLP_INSECURE env vars (# 7608) > 4200d1e chore(deps): update actions/checkout action to v6 (# 7624) > 67d264a chore(deps): update module golang.org/x/crypto to v0.45.0 [security] (# 7622) > 7d39542 chore(deps): update module github.com/cyphar/filepath-securejoin to v0.6.1 (# 7618) > 9c98f52 chore(deps): update module go.uber.org/zap to v1.27.1 (# 7620) > 61649a4 chore(deps): update actions/setup-go action to v6.1.0 (# 7623) > 4929285 Replace fnv with xxhash (# 7497) > 98eb065 chore(deps): update github/codeql-action action to v4.31.4 (# 7615) > 5eea765 Upgrade macos tests to latest (# 7597) > dafdbf8 fix(deps): update module go.opentelemetry.io/collector/pdata to v1.46.0 (# 7611) > 205b584 chore(deps): update module github.com/prometheus/common to v0.67.3 (# 7613) > dfffbcf fix(deps): update module google.golang.org/grpc to v1.77.0 (# 7612) > e2f25cd chore(deps): update actions/checkout action to v5.0.1 (# 7609) > af4399f chore(deps): update module go.opentelemetry.io/collector/featuregate to v1.46.0 (# 7610) > 48eaa6c fix(deps): update module github.com/golangci/golangci-lint/v2 to v2.6.2 (# 7604) > e35220d chore(deps): update module github.com/mgechev/revive to v1.13.0 (# 7605) > d75bcb2 chore(deps): update github/codeql-action action to v4.31.3 (# 7603) > 6142eb6 fix(deps): update golang.org/x to e25ba8c (# 7602) > 8c2fb6f chore: exporters/prometheus/internal/x - Generate x package from x component template (# 7491) > cb5b1b6 fix(deps): update module golang.org/x/tools to v0.39.0 (# 7601) > bfb946a chore(deps): update golang.org/x/telemetry digest to 03ef243 (# 7600) > cbe16b6 fix(deps): update googleapis to 95abcf5 (# 7598) > 19a640a chore(deps): update golang.org/x (# 7599) > 1411938 fix(deps): update googleapis to 83f4791 (# 7594) > d1ebd7b fix(deps): update golang.org/x (# 7590) > bcf8234 chore(deps): update module github.com/catenacyber/perfsprint to v0.10.1 (# 7588) > 5c3ba32 chore(deps): update module github.com/maratori/testpackage to v1.1.2 (# 7586) > dff05f9 chore(deps): update module github.com/maratori/testableexamples to v1.0.1 (# 7585) > 093cdb6 chore(deps): update golang.org/x/telemetry digest to 5cc343d (# 7584) > fe47da3 sdk/log: update ExampleProcessor_eventName to use otel.event.name attribute (# 7568) > 13b122c chore(deps): update golang.org/x/telemetry digest to cbe4531 (# 7582) > ac5fbd7 chore(deps): update module github.com/mirrexone/unqueryvet to v1.3.0 (# 7583) > a80ee2d feat: instrument periodic reader from sdk/metric (# 7571) > 22b5704 chore(deps): update otel/weaver docker tag to v0.19.0 (# 7580) > 6120ee8 trace: add fuzz tests for TraceIDFromHex and SpanIDFromHex (# 7577) > ac5b181 fix(deps): update module github.com/golangci/golangci-lint/v2 to v2.6.1 (# 7579) > 389571b chore(deps): update golang.org/x/telemetry digest to ab4e49a (# 7578) > 69dfcc5 fix(deps): update module go.opentelemetry.io/collector/pdata to v1.45.0 (# 7575) > 38203b6 docs: sign artifacts before releasing (# 7567) > 635cf8d docs: remove demo repository update after release (# 7566) > 68190cc Instrument manual reader from sdk/metric (# 7524) > 4438ec3 fix(deps): update module go.opentelemetry.io/proto/otlp to v1.9.0 (# 7570) > 0e4d4ed fix(deps): update googleapis to f26f940 (# 7569) > d0483a7 chore(deps): update module github.com/cyphar/filepath-securejoin to v0.6.0 (# 7564) > 8a930a9 chore(deps): update module github.com/cyphar/filepath-securejoin to v0.5.1 (# 7563) > adbaa43 fix(deps): update build-tools to v0.29.0 (# 7561) > c1dc104 chore(deps): update module github.com/charmbracelet/x/term to v0.2.2 (# 7560) > e308db8 chore: handle Float64Histogram in log/observe errMeter (# 7555) > 5616ce4 chore(deps): update module github.com/go-critic/go-critic to v0.14.2 (# 7559) > d7ceecf fix(deps): update module github.com/golangci/golangci-lint/v2 to v2.6.0 (# 7554) > 893166a chore(deps): update module github.com/prometheus/procfs to v0.19.2 (# 7558) > 6fe90a7 chore(deps): update github/codeql-action action to v4.31.2 (# 7556) > 5ab687f chore(deps): update module github.com/go-critic/go-critic to v0.14.1 (# 7557) > c3eda34 Add disclaimer to span.SetAttributes (# 7550) > 8b61c33 chore(deps): update lycheeverse/lychee-action action to v2.7.0 (# 7552) > 9401e21 fix(deps): update googleapis to ab9386a (# 7553) > b209eca chore(deps): update module github.com/stbenjam/no-sprintf-host-port to v0.3.1 (# 7551) > 89da8a1 chore(deps): update module github.com/prometheus/common to v0.67.2 (# 7547) > ab21764 chore(deps): update golang.org/x/telemetry digest to d7a2859 (# 7546) > 82c8953 chore(deps): update module github.com/karamaru-alpha/copyloopvar to v1.2.2 (# 7545) > 0b0a4d3 chore(deps): update mvdan.cc/unparam digest to 5beb8c8 (# 7544) > c534ddd chore(deps): update module github.com/clipperhouse/uax29/v2 to v2.3.0 (# 7543) > 508eb2e chore(deps): update module github.com/prometheus/procfs to v0.19.1 (# 7542) > bd5db0f chore(deps): update module github.com/ashanbrown/forbidigo/v2 to v2.3.0 (# 7540) > c9b7ecf chore(deps): update module github.com/ashanbrown/makezero/v2 to v2.1.0 (# 7538) > af4f6ae chore(deps): update module github.com/prometheus/procfs to v0.19.0 (# 7539) > c434d43 chore(deps): update github/codeql-action action to v4.31.0 (# 7536) > 3ecd36a chore(deps): update github artifact actions (major) (# 7537) > e71de16 chore(deps): update module github.com/charithe/durationcheck to v0.0.11 (# 7534) > b15942f Added the `internal/observ` package to log (# 7532) > f1ba319 fix(deps): update golang.org/x to a4bb9ff (# 7533) > f0c2457 chore(deps): update golang.org/x/telemetry digest to 5be28d7 (# 7528) > 6f7ffc1 fix(deps): update googleapis to 3a174f9 (# 7529) > 060af76 chore(deps): update module github.com/prometheus/procfs to v0.18.0 (# 7530) > bc28867 Move scorpionknifes to emeritus (# 7526) > 7958d6f chore(deps): update module mvdan.cc/gofumpt to v0.9.2 (# 7527) > eadb423 Instrument the `otlploghttp` exporter (# 7512) > eb87c43 chore(deps): update module github.com/abirdcfly/dupword to v0.1.7 (# 7525) > 26ce126 fix(deps): update module go.opentelemetry.io/collector/pdata to v1.44.0 (# 7523) > 713012c chore(deps): update module go.opentelemetry.io/collector/featuregate to v1.44.0 (# 7522) > f0fefb9 fix(deps): update googleapis to 88f65dc (# 7521) > 56138d1 fix(deps): update golang.org/x (# 7482) > eb7ec3e Simulate failures for histogram creation paths without risking a nil-interface panic (# 7518) > f7d2882 feat: sdk/trace: span processed metric for simple span processor (# 7374) > 2e5fdd1 chore(deps): update github/codeql-action action to v4.30.9 (# 7515) > b5b6989 sdk/log: Fix AddAttributes, SetAttributes, SetBody on Record to not mutate input (# 7403) > f346dec chore: sdk/internal/x - generate x package from shared template (# 7495) > b37822b Prometheus exporter tests: iterate through all scopes rather than looking only at the first (# 7510) > 9dea78c chore(deps): update module github.com/go-critic/go-critic to v0.14.0 (# 7509) > ee0c203 fix(observ): correct rejected items and update comment style (# 7502) > 86e673c chore(deps): update module github.com/godoc-lint/godoc-lint to v0.10.1 (# 7508) > 0e18cf4 fix(deps): update googleapis to 4626949 (# 7506) > b78550d Added the `internal/observ` package to otlploghttp (# 7484) > b3129d3 docs: remove demo-accounting service from dependency list in releasing (# 7503) > 643e735 chore(deps): update module github.com/kunwardeep/paralleltest to v1.0.15 (# 7501) > 1935e60 Fix typos and linguistic errors in documentation / hacktoberfest (# 7494) > fa8e48b OTLP trace exporter include W3C trace flags (bits 0–7) in Span.Flags (# 7438) > 07a26be chore(deps): update module github.com/catenacyber/perfsprint to v0.10.0 (# 7496) > 73cbc69 chore(deps): update github/codeql-action action to v4.30.8 (# 7489) > f58f79b Instrument the `otlptracehttp` exporter (# 7486) > 5c78f7c chore(deps): update module github.com/gofrs/flock to v0.13.0 (# 7487) > 691638a Move sdk/internal/env to sdk/trace/internal/env (# 7437) > c8fc171 Add the `internal/observ` pkg to `otlptracehttp` (# 7480) > 874c4c3 feat: Improve error handling in prometheus exporter (# 7363) > a817caa Add a version const to otlptracehttp (# 7479) > 04ea40e Add the internal `x` package to `otlptracehttp` (# 7476) > 8e8c8c8 chore(deps): update module github.com/ldez/exptostd to v0.4.5 (# 7483) > f89d9f9 chore(deps): update module github.com/nunnatsa/ginkgolinter to v0.21.2 (# 7481) > dc8303b chore(deps): update golang.org/x (# 7477) > 0c97dfd fix(deps): update golang.org/x (# 7475) > 798133c chore(deps): update module github.com/skeema/knownhosts to v1.3.2 (# 7471) > 12bae3a chore(deps): update github/codeql-action action to v4 (# 7472) > 7375147 chore(deps): update module golang.org/x/net to v0.45.0 (# 7470) > 8f0e60d fix(deps): update google.golang.org/genproto/googleapis/rpc digest to 49b9836 (# 7469) > c692bc4 Instrument the `otlptracegrpc` exporter (# 7459) > ce38247 chore(deps): update google.golang.org/genproto/googleapis/api digest to 49b9836 (# 7468) > dd9576c chore(deps): update github/codeql-action action to v3.30.7 (# 7467) > 5f5f4af Document the ordering guarantees provided by the metrics SDK (# 7453) > b64883d chore(deps): update module github.com/prometheus/common to v0.67.1 (# 7465) > 78548fb chore(deps): update module github.com/stretchr/objx to v0.5.3 (# 7464) > c8e3897 Use sync.Map and atomics to improve sum performance (# 7427) > cfd8570 fix(deps): update module go.opentelemetry.io/collector/pdata to v1.43.0 (# 7462) > 681f607 fix(deps): update module google.golang.org/grpc to v1.76.0 (# 7463) > 94f243d chore(deps): update module go.opentelemetry.io/collector/featuregate to v1.43.0 (# 7461) > 11260cd fix(deps): update googleapis to 65f7160 (# 7460) > 14d6372 Add the `internal/observ` package to `otlptracegrpc` (# 7404) > a10652b sdk/trace: trace id high 64 bit tests (# 7212) > 5937fc8 chore(deps): update module github.com/bombsimon/wsl/v5 to v5.3.0 (# 7457) > a39337f chore(deps): update module github.com/go-git/go-git/v5 to v5.16.3 (# 7456) > 64d2bed fix(deps): update build-tools to v0.28.1 (# 7455) > c4bdd87 Support custom error type semantics (# 7442) > 931a5bd chore(deps): update actions/stale action to v10.1.0 (# 7452) > cf1f668 chore(deps): update module github.com/ghostiam/protogetter to v0.3.17 (# 7451) > bd1b3da Add exemplar reservoir parallel benchmarks (# 7441) > dc906d6 chore(deps): update module github.com/grpc-ecosystem/grpc-gateway/v2 to v2.27.3 (# 7450) > 3757239 fix(deps): update googleapis to 7c0ddcb (# 7449) > 7352831 fix(deps): update golang.org/x to 27f1f14 (# 7448) > 5dd35ce feat: logs SDK observability - otlploggrpc exporter metrics (# 7353) > 4b9e111 Skip link checking for acm.org which blocks the link checker (# 7444) > 48cbdac chore(deps): update github/codeql-action action to v3.30.6 (# 7446) > 3ea8606 fix(deps): update module google.golang.org/protobuf to v1.36.10 (# 7445) > dee11e6 Add temporality selector functions (# 7434) > ffeeee8 chore(deps): update peter-evans/create-issue-from-file action to v6 (# 7440) > 862a41a chore(deps): update golang.org/x/telemetry digest to 4eae98a (# 7439) > ea38204 Allow optimizing locking for built-in exemplar reservoirs (# 7423) > 6c54ef6 chore(deps): update ossf/scorecard-action action to v2.4.3 (# 7435) > 4d2b735 chore(deps): update golang.org/x/telemetry digest to 8e64475 (# 7431) > e54c038 chore(deps): update module github.com/charmbracelet/x/ansi to v0.10.2 (# 7432) > addcd63 fix(deps): update googleapis to 57b25ae (# 7429) > 45539cf Only enforce cardinality limits when the attribute set does not already exist (# 7422) > 59ac46c Prometheus exporter: change default translation strategy (# 7421) > 692e519 chore(deps): update module github.com/mattn/go-runewidth to v0.0.19 (# 7428) > 6cb0e90 Generate gRPC Client target parsing func (# 7424) > 81aeace chore(deps): update module go.augendre.info/fatcontext to v0.9.0 (# 7426) > 0db5ac7 sdk/trace/internal/x: generate x package from x component template # 7385 (# 7411) > fef6ee5 chore(deps): update github/codeql-action action to v3.30.5 (# 7425) > 22cfbce Add concurrent safe tests for metric aggregations (# 7379) > fc89784 chore(deps): update module github.com/cyphar/filepath-securejoin to v0.5.0 (# 7419) > bdd4881 chore(deps): update module github.com/mattn/go-runewidth to v0.0.17 (# 7418) > ac8d8e9 Optimize Observability return types in in Prometheus exporter (# 7410) > 88d3fed Optimize the return type of ExportSpans (# 7405) > 63ed041 chore(deps): update module github.com/quasilyte/go-ruleguard/dsl to v0.3.23 (# 7417) > 016b175 chore(deps): update module github.com/quasilyte/go-ruleguard to v0.4.5 (# 7416) > a883fa1 chore(deps): update github/codeql-action action to v3.30.4 (# 7414) > 97a78c1 Add measure benchmarks with exemplars recorded (# 7406) > 2e0b5b4 Add benchmark for synchronous gauge measurement (# 7407) > 97e2244 [chore]: Clean-up unused obsScopeName const (# 7408) > b85e2c1 chore(deps): update actions/cache action to v4.3.0 (# 7409) > 250a11e Add experimental `x` package to `otlptracegrpc` (# 7401) > 466f0cd chore(deps): update module dev.gaijin.team/go/golib to v0.7.0 (# 7402) > 3f05c91 chore(deps): update module github.com/ldez/gomoddirectives to v0.7.1 (# 7400) > 4c9c611 Link checker: ignore https localhost uris (# 7399) > 0cc2eb9 sdk/log: BenchmarkAddAttributes, BenchmarkSetAttributes, BenchmarkSetBody (# 7387) > 80cb909 refactor: replace `context.Background()` with `t.Context()`/`b.Context()` in tests (# 7352) > 2389f44 fix(deps): update module go.opentelemetry.io/collector/pdata to v1.42.0 (# 7397) > 5d3ce38 fix(deps): update googleapis to 9219d12 (# 7393) > dd938b2 fix(deps): update build-tools to v0.28.0 (# 7395) > 4e3152d chore(deps): update module go.opentelemetry.io/build-tools to v0.28.0 (# 7394) > 56498ab chore: sdk/log/internal/x - generate x package from x component template (# 7389) > a579a3e fix(deps): update module github.com/golangci/golangci-lint/v2 to v2.5.0 (# 7392) > de1563e chore(deps): update module github.com/tetafro/godot to v1.5.4 (# 7391) > b5d5bba chore(deps): update module github.com/sagikazarmark/locafero to v0.12.0 (# 7390) > a189c6b sdk/log: add TestRecordMethodsInputConcurrentSafe (# 7378) > 6180f83 Return partial OTLP export errors to the caller (# 7372) > 60f9f39 feat(prometheus): Add observability for prometheus exporter (# 7345) > d1dddde fix(deps): update github.com/opentracing-contrib/go-grpc/test digest to a6e64aa (# 7375) > e4bb37c chore(deps): update otel/weaver docker tag to v0.18.0 (# 7377) > be8e7b2 chore(deps): update module github.com/kulti/thelper to v0.7.1 (# 7376) > b866e36 chore(deps): update module github.com/ldez/grignotin to v0.10.1 (# 7373) > 9d52bde Use Set hash in Distinct (2nd attempt) (# 7175) > 666f95c Fix the typo in test names (# 7369) > 1298533 chore(deps): update module github.com/djarvur/go-err113 to v0.1.1 (# 7368) > 18b114b fix(deps): update module github.com/prometheus/otlptranslator to v1 (# 7358) > 7e4006a chore: generate feature flag files from shared (# 7361) > 5b808c6 Encapsulate SDK Tracer observability (# 7331) > e4ab314 Encapsulate SDK BatchSpanProcessor observability (# 7332) > 6243f21 fix(deps): update module go.opentelemetry.io/auto/sdk to v1.2.1 (# 7365) > 4fdd552 Track context containing span in `recordingSpan` (# 7354) > 59563f7 Do not use the user-defined empty set when comparing sets. (# 7357) > 01611d3 chore(deps): update module github.com/tetafro/godot to v1.5.2 (# 7360) > 305ec06 chore(deps): update module github.com/nunnatsa/ginkgolinter to v0.21.0 (# 7362) > 0d5aa14 chore(deps): update module github.com/antonboom/testifylint to v1.6.4 (# 7359) > 285cbe9 sdk/metric: add example for metricdatatest package (# 7323) > e2da30d trace,metric,log: change WithInstrumentationAttributes to not de-depuplicate the passed attributes in a closure (# 7266) > b168550 fix(deps): update golang.org/x to df92998 (# 7350) > 7fdebbe Rename Self-Observability as just Observability (# 7302) > b06d273 chore(deps): update github/codeql-action action to v3.30.3 (# 7348) > 38d7c39 chore(deps): update module go.yaml.in/yaml/v2 to v2.4.3 (# 7349) > 31629e2 fix(deps): update module google.golang.org/grpc to v1.75.1 (# 7344) > 6d1f9d0 fix(deps): update module golang.org/x/tools to v0.37.0 (# 7347) > df6058a chore(deps): update module golang.org/x/net to v0.44.0 (# 7341) > e26cebf chore(deps): update module github.com/antonboom/nilnil to v1.1.1 (# 7343) > 3baabce Do not allocate instrument options if possible in generated semconv packages (# 7328) > 9b6585a Encapsulate `stdouttrace.Exporter` instrumentation in internal package (# 7307) > a07b7e6 fix(deps): update module google.golang.org/protobuf to v1.36.9 (# 7340) > 71fda10 chore(deps): update golang.org/x (# 7326) > f2ea3f1 chore(deps): update github/codeql-action action to v3.30.2 (# 7339) > 6f50705 fix(deps): update googleapis to 9702482 (# 7335) > c4a6339 chore(deps): update module github.com/spf13/cast to v1.10.0 (# 7333) > 4368300 chore(deps): update module github.com/spf13/viper to v1.21.0 (# 7334) > d0b6f18 fix(deps): update module go.opentelemetry.io/collector/pdata to v1.41.0 (# 7337) > 81c1aca chore(deps): update module github.com/antonboom/errname to v1.1.1 (# 7338) > eb9fd73 chore(deps): update module github.com/lucasb-eyer/go-colorful to v1.3.0 (# 7327) > e759fbd chore(deps): update module github.com/sagikazarmark/locafero to v0.11.0 (# 7329) > cec9d59 chore(deps): update module github.com/spf13/afero to v1.15.0 (# 7330) > 07a91dd trace,metric,log: add WithInstrumentationAttributeSet option (# 7287) > b335c07 Encapsulate observability in Logs SDK (# 7315) > dcf14aa trace,metric,log: WithInstrumentationAttributes options to merge attributes (# 7300) > 63f1ee7 chore(deps): update module mvdan.cc/gofumpt to v0.9.1 (# 7322) > 6f04175 chore(deps): update golang.org/x (# 7324) > 567ef26 Add benchmark for exponential histogram measurements (# 7305) > 8ac554a fix(deps): update golang.org/x (# 7320) > b218e4b Don't track min and max when disabled (# 7306) > 810095e chore(deps): update benchmark-action/github-action-benchmark action to v1.20.7 (# 7319) > f8a9510 fix(deps): update module github.com/prometheus/client_golang to v1.23.2 (# 7314) > 8cea039 chore(deps): update golang.org/x/telemetry digest to af835b0 (# 7313) > 4a0606d chore(deps): update module github.com/pjbgf/sha1cd to v0.5.0 (# 7317) > 2de26d1 chore(deps): update github.com/grafana/regexp digest to f7b3be9 (# 7311) > 97c4e6c chore(deps): update github/codeql-action action to v3.30.1 (# 7312) > e2a4fb3 chore(deps): update golang.org/x/telemetry digest to 9b996f7 (# 7308) > de4b553 chore(deps): update module github.com/bombsimon/wsl/v5 to v5.2.0 (# 7309) > a5dcd68 Add Observability section to CONTRIBUTING doc (# 7272) > 4107421 chore(deps): update codecov/codecov-action action to v5.5.1 (# 7303) > d8d6e52 fix(deps): update module github.com/prometheus/client_golang to v1.23.1 (# 7304) > e54519a chore(deps): update actions/setup-go action to v6 (# 7298) > a0cc03c chore(deps): update actions/stale action to v10 (# 7299) > 8c8cd0a fix(deps): update module go.opentelemetry.io/proto/otlp to v1.8.0 (# 7296) > 83c041a chore(deps): update module mvdan.cc/gofumpt to v0.9.0 (# 7292) > 295fbdc chore(deps): update module github.com/golangci/go-printf-func-name to v0.1.1 (# 7290) > 09e5d69 chore(deps): update module github.com/ghostiam/protogetter to v0.3.16 (# 7289) > 7cb19f9 chore(deps): update benchmark-action/github-action-benchmark action to v1.20.5 (# 7293) > b8f00e3 chore(deps): update module github.com/spf13/pflag to v1.0.10 (# 7291) > 0174808 Fix schema urls (# 7288) > 5e3b939 Add tracetest example for testing instrumentation (# 7107) > 090e9ef chore(deps): update module github.com/spf13/cobra to v1.10.1 (# 7286) > a389393 chore(deps): update github/codeql-action action to v3.30.0 (# 7284) > 6ccc387 chore(deps): update module github.com/spf13/cobra to v1.10.0 (# 7285) > 774c740 Fix missing link in changelog (# 7273) > 5d1ec3a fix(deps): update github.com/opentracing-contrib/go-grpc/test digest to 0261db7 (# 7278) > f74ab34 chore(deps): update module github.com/spf13/pflag to v1.0.9 (# 7282) > b0903af chore(deps): update module github.com/rogpeppe/go-internal to v1.14.1 (# 7283) > 358fa01 fix(deps): update googleapis to ef028d9 (# 7279) > 68b1c4c fix(deps): update module github.com/opentracing-contrib/go-grpc to v0.1.2 (# 7281) > c8632bc fix(deps): update golang.org/x (# 7188) > 18ad4a1 fix(deps): update module github.com/golangci/golangci-lint/v2 to v2.4.0 (# 7277) > c2fea5f chore(deps): update module github.com/securego/gosec/v2 to v2.22.8 (# 7276) > 83403d3 fix(deps): update module go.opentelemetry.io/collector/pdata to v1.40.0 (# 7275) > 8ab8e42 Drop support for Go 1.23 (# 7274) bumping k8s.io/utils 24370be...4c0f3b2: > 4c0f3b2 Add generic buffer.TypedRingGrowing and shrinkable buffer.Ring > 0f33e8f Merge pull request # 326 from aojea/update_owners > 8884aa7 Merge pull request # 325 from aojea/fake_clock_waiters > 3f9e719 update owners > 1f6e0b7 Merge pull request # 321 from xigang/ring_growing > 755dc6a fake clock: expose the number of waiters > 336e707 Add Len and Cap methods for ring buffer bumping golang.org/x/net 2002a06...35e1306: > 35e1306 go.mod: update golang.org/x dependencies > 7c36036 http2, webdav, websocket: fix %q verb uses with wrong type > ec11ecc trace: fix data race in RenderEvents > bff14c5 http2: don't PING a responsive server when resetting a stream > 88a6421 dns/dnsmessage: avoid use of "strings" and "math" in dns/dnsmessage > 123d099 http2: support net/http.Transport.NewClientConn > 346cc61 webdav: relax test to check for any redirect status, not just 301 > 9a29643 go.mod: update golang.org/x dependencies > 07cefd8 context: deprecate > 5ac9dac publicsuffix: don't treat ip addresses as domain names > d1f64cc quic: use testing/synctest > fff0469 http2: document that RFC 7540 prioritization does not work with small payloads > f35e3a4 http2: fix weight overflow in RFC 7540 write scheduler > 89adc90 http2: fix typo referring to RFC 9218 as RFC 9128 instead > 8d76a2c quic: don't defer MAX_STREAMS frames indefinitely > 027f8b7 quic: fix expected ACK Delay in client's ACK after HANDSHAKE_DONE > dec9fe7 dns/dnsmessage: update SVCB packing to prohibit name compression > 9be1ff2 all: fix some comments > 6e243da quic: update Initial keys when handling Retry > 98daa2e quic: send ECN feedback to peers > c296faf net/http2: pool transport gzip readers > ef82ae8 dns/dnsmessage: return an error for too long SVCParam.Value > 3ba82d2 internal/quic/cmd/interop: test ChaCha20 on server > bb2055d dns/dnsmessage: add https svcb dns types > 63d1a51 http2: Allow reading frame header and body separately > 9f2f0b9 http2: avoid data race on DebugGoroutines in TestGoroutineLock > e7c005d http2: implement a more efficient writeQueue that avoids unnecessary copies. > b93acc2 all: use reflect.TypeFor instead of reflect.TypeOf bumping go.opentelemetry.io/otel/trace 84e3f3a...6ce1429: > 6ce1429 Release v1.39.0 (# 7676) > 12e421a sdk/log: move Enabled method from FilterProcessor to Processor (# 7639) > 5982f16 fix(deps): update module golang.org/x/sys to v0.39.0 (# 7684) > 9288378 chore(deps): update module golang.org/x/sync to v0.19.0 (# 7683) > ee3dfef chore(deps): update github.com/securego/gosec/v2 digest to 41f28e2 (# 7682) > 9345d1f fix(deps): update module github.com/golangci/golangci-lint/v2 to v2.7.2 (# 7680) > d03b033 Check context prior to delaying retry in OTLP exporters (# 7678) > 61765e7 Fix flaky `TestClientInstrumentation` (# 7677) > a54721c chore(deps): update module github.com/go-git/go-billy/v5 to v5.7.0 (# 7679) > 746d086 chore(deps): update github/codeql-action action to v4.31.7 (# 7675) > 1bc9713 Regenerate `ErrorType` documentation in `semconv/v1.37.0` (# 7667) > 5a692cf Fix whitespace in `semconv/v1.33.0` (# 7665) > 4eff89b Fix whitespace in `semconv/v1.32.0` (# 7666) > d1825df Fix whitespace in `semconv/v1.36.0` (# 7663) > ddc307d Fix package name documentation and missing copyright in `semconv/v1.32.0` (# 7659) > 3e85447 Fix whitespace in `semconv/v1.34.0` (# 7664) > a64b9ec Fix package documentation name and return error in `semconv/v1.36.0` (# 7656) > be85ff8 Fix whitespace in `semconv/v1.37.0` (# 7660) > cddeb68 Fix package name documentation and missing copyright in `semconv/v1.34.0` (# 7657) > 3659648 Fix package name documentation and missing copyright in `semconv/v1.33.0` (# 7658) > e69beb8 Fix package documentation name and return err in `semconv/v1.37.0` (# 7655) > ddd0420 fix(deps): update module github.com/golangci/golangci-lint/v2 to v2.7.1 (# 7671) > 21e75b9 chore(deps): update module github.com/ldez/gomoddirectives to v0.8.0 (# 7669) > ca53078 Instrument the `SimpleLogProcessor` from sdk/log (# 7548) > df83919 chore(deps): update module github.com/spf13/cobra to v1.10.2 (# 7668) > 6af2f2f Generate semconv/v1.38.0 (# 7648) > 20fdce2 fix(deps): update module github.com/golangci/golangci-lint/v2 to v2.7.0 (# 7661) > 79144c5 chore(deps): update golang.org/x/telemetry digest to 8fff8a5 (# 7654) > d6aef9b chore(deps): update actions/checkout action to v6.0.1 (# 7651) > 98f784d fix(deps): update googleapis to ff82c1b (# 7652) > 5ea2a32 chore(deps): update actions/stale action to v10.1.1 (# 7653) > 85c1dfe chore(deps): update module github.com/godoc-lint/godoc-lint to v0.10.2 (# 7650) > 729bd6e fix(deps): update module go.opentelemetry.io/collector/pdata to v1.47.0 (# 7646) > d5faa3e chore(deps): update github/codeql-action action to v4.31.6 (# 7644) > 495a2c2 chore(deps): update golang.org/x/telemetry digest to abf20d0 (# 7643) > a72103c chore(deps): update module github.com/hashicorp/go-version to v1.8.0 (# 7641) > a0a0acd fix(deps): update golang.org/x to 87e1e73 (# 7636) > c152342 fix(deps): update googleapis to 79d6a2a (# 7634) > 6a16d13 chore(deps): update golang.org/x/telemetry digest to 55bbf37 (# 7633) > d3b4232 chore(deps): update module github.com/tomarrell/wrapcheck/v2 to v2.12.0 (# 7632) > cbcbb2d chore(deps): update github/codeql-action action to v4.31.5 (# 7631) > 414432d chore(deps): update module github.com/go-git/go-git/v5 to v5.16.4 (# 7629) > 7323fc7 chore(deps): update golang.org/x/telemetry digest to e487659 (# 7619) > d799e06 chore(deps): update module github.com/prometheus/common to v0.67.4 (# 7626) > c6e4cca chore(deps): update module dev.gaijin.team/go/golib to v0.8.0 (# 7627) > da01323 otlploghttp: support OTEL_EXPORTER_OTLP_LOGS_INSECURE and OTEL_EXPORTER_OTLP_INSECURE env vars (# 7608) > 4200d1e chore(deps): update actions/checkout action to v6 (# 7624) > 67d264a chore(deps): update module golang.org/x/crypto to v0.45.0 [security] (# 7622) > 7d39542 chore(deps): update module github.com/cyphar/filepath-securejoin to v0.6.1 (# 7618) > 9c98f52 chore(deps): update module go.uber.org/zap to v1.27.1 (# 7620) > 61649a4 chore(deps): update actions/setup-go action to v6.1.0 (# 7623) > 4929285 Replace fnv with xxhash (# 7497) > 98eb065 chore(deps): update github/codeql-action action to v4.31.4 (# 7615) > 5eea765 Upgrade macos tests to latest (# 7597) > dafdbf8 fix(deps): update module go.opentelemetry.io/collector/pdata to v1.46.0 (# 7611) > 205b584 chore(deps): update module github.com/prometheus/common to v0.67.3 (# 7613) > dfffbcf fix(deps): update module google.golang.org/grpc to v1.77.0 (# 7612) > e2f25cd chore(deps): update actions/checkout action to v5.0.1 (# 7609) > af4399f chore(deps): update module go.opentelemetry.io/collector/featuregate to v1.46.0 (# 7610) > 48eaa6c fix(deps): update module github.com/golangci/golangci-lint/v2 to v2.6.2 (# 7604) > e35220d chore(deps): update module github.com/mgechev/revive to v1.13.0 (# 7605) > d75bcb2 chore(deps): update github/codeql-action action to v4.31.3 (# 7603) > 6142eb6 fix(deps): update golang.org/x to e25ba8c (# 7602) > 8c2fb6f chore: exporters/prometheus/internal/x - Generate x package from x component template (# 7491) > cb5b1b6 fix(deps): update module golang.org/x/tools to v0.39.0 (# 7601) > bfb946a chore(deps): update golang.org/x/telemetry digest to 03ef243 (# 7600) > cbe16b6 fix(deps): update googleapis to 95abcf5 (# 7598) > 19a640a chore(deps): update golang.org/x (# 7599) > 1411938 fix(deps): update googleapis to 83f4791 (# 7594) > d1ebd7b fix(deps): update golang.org/x (# 7590) > bcf8234 chore(deps): update module github.com/catenacyber/perfsprint to v0.10.1 (# 7588) > 5c3ba32 chore(deps): update module github.com/maratori/testpackage to v1.1.2 (# 7586) > dff05f9 chore(deps): update module github.com/maratori/testableexamples to v1.0.1 (# 7585) > 093cdb6 chore(deps): update golang.org/x/telemetry digest to 5cc343d (# 7584) > fe47da3 sdk/log: update ExampleProcessor_eventName to use otel.event.name attribute (# 7568) > 13b122c chore(deps): update golang.org/x/telemetry digest to cbe4531 (# 7582) > ac5fbd7 chore(deps): update module github.com/mirrexone/unqueryvet to v1.3.0 (# 7583) > a80ee2d feat: instrument periodic reader from sdk/metric (# 7571) > 22b5704 chore(deps): update otel/weaver docker tag to v0.19.0 (# 7580) > 6120ee8 trace: add fuzz tests for TraceIDFromHex and SpanIDFromHex (# 7577) > ac5b181 fix(deps): update module github.com/golangci/golangci-lint/v2 to v2.6.1 (# 7579) > 389571b chore(deps): update golang.org/x/telemetry digest to ab4e49a (# 7578) > 69dfcc5 fix(deps): update module go.opentelemetry.io/collector/pdata to v1.45.0 (# 7575) > 38203b6 docs: sign artifacts before releasing (# 7567) > 635cf8d docs: remove demo repository update after release (# 7566) > 68190cc Instrument manual reader from sdk/metric (# 7524) > 4438ec3 fix(deps): update module go.opentelemetry.io/proto/otlp to v1.9.0 (# 7570) > 0e4d4ed fix(deps): update googleapis to f26f940 (# 7569) > d0483a7 chore(deps): update module github.com/cyphar/filepath-securejoin to v0.6.0 (# 7564) > 8a930a9 chore(deps): update module github.com/cyphar/filepath-securejoin to v0.5.1 (# 7563) > adbaa43 fix(deps): update build-tools to v0.29.0 (# 7561) > c1dc104 chore(deps): update module github.com/charmbracelet/x/term to v0.2.2 (# 7560) > e308db8 chore: handle Float64Histogram in log/observe errMeter (# 7555) > 5616ce4 chore(deps): update module github.com/go-critic/go-critic to v0.14.2 (# 7559) > d7ceecf fix(deps): update module github.com/golangci/golangci-lint/v2 to v2.6.0 (# 7554) > 893166a chore(deps): update module github.com/prometheus/procfs to v0.19.2 (# 7558) > 6fe90a7 chore(deps): update github/codeql-action action to v4.31.2 (# 7556) > 5ab687f chore(deps): update module github.com/go-critic/go-critic to v0.14.1 (# 7557) > c3eda34 Add disclaimer to span.SetAttributes (# 7550) > 8b61c33 chore(deps): update lycheeverse/lychee-action action to v2.7.0 (# 7552) > 9401e21 fix(deps): update googleapis to ab9386a (# 7553) > b209eca chore(deps): update module github.com/stbenjam/no-sprintf-host-port to v0.3.1 (# 7551) > 89da8a1 chore(deps): update module github.com/prometheus/common to v0.67.2 (# 7547) > ab21764 chore(deps): update golang.org/x/telemetry digest to d7a2859 (# 7546) > 82c8953 chore(deps): update module github.com/karamaru-alpha/copyloopvar to v1.2.2 (# 7545) > 0b0a4d3 chore(deps): update mvdan.cc/unparam digest to 5beb8c8 (# 7544) > c534ddd chore(deps): update module github.com/clipperhouse/uax29/v2 to v2.3.0 (# 7543) > 508eb2e chore(deps): update module github.com/prometheus/procfs to v0.19.1 (# 7542) > bd5db0f chore(deps): update module github.com/ashanbrown/forbidigo/v2 to v2.3.0 (# 7540) > c9b7ecf chore(deps): update module github.com/ashanbrown/makezero/v2 to v2.1.0 (# 7538) > af4f6ae chore(deps): update module github.com/prometheus/procfs to v0.19.0 (# 7539) > c434d43 chore(deps): update github/codeql-action action to v4.31.0 (# 7536) > 3ecd36a chore(deps): update github artifact actions (major) (# 7537) > e71de16 chore(deps): update module github.com/charithe/durationcheck to v0.0.11 (# 7534) > b15942f Added the `internal/observ` package to log (# 7532) > f1ba319 fix(deps): update golang.org/x to a4bb9ff (# 7533) > f0c2457 chore(deps): update golang.org/x/telemetry digest to 5be28d7 (# 7528) > 6f7ffc1 fix(deps): update googleapis to 3a174f9 (# 7529) > 060af76 chore(deps): update module github.com/prometheus/procfs to v0.18.0 (# 7530) > bc28867 Move scorpionknifes to emeritus (# 7526) > 7958d6f chore(deps): update module mvdan.cc/gofumpt to v0.9.2 (# 7527) > eadb423 Instrument the `otlploghttp` exporter (# 7512) > eb87c43 chore(deps): update module github.com/abirdcfly/dupword to v0.1.7 (# 7525) > 26ce126 fix(deps): update module go.opentelemetry.io/collector/pdata to v1.44.0 (# 7523) > 713012c chore(deps): update module go.opentelemetry.io/collector/featuregate to v1.44.0 (# 7522) > f0fefb9 fix(deps): update googleapis to 88f65dc (# 7521) > 56138d1 fix(deps): update golang.org/x (# 7482) > eb7ec3e Simulate failures for histogram creation paths without risking a nil-interface panic (# 7518) > f7d2882 feat: sdk/trace: span processed metric for simple span processor (# 7374) > 2e5fdd1 chore(deps): update github/codeql-action action to v4.30.9 (# 7515) > b5b6989 sdk/log: Fix AddAttributes, SetAttributes, SetBody on Record to not mutate input (# 7403) > f346dec chore: sdk/internal/x - generate x package from shared template (# 7495) > b37822b Prometheus exporter tests: iterate through all scopes rather than looking only at the first (# 7510) > 9dea78c chore(deps): update module github.com/go-critic/go-critic to v0.14.0 (# 7509) > ee0c203 fix(observ): correct rejected items and update comment style (# 7502) > 86e673c chore(deps): update module github.com/godoc-lint/godoc-lint to v0.10.1 (# 7508) > 0e18cf4 fix(deps): update googleapis to 4626949 (# 7506) > b78550d Added the `internal/observ` package to otlploghttp (# 7484) > b3129d3 docs: remove demo-accounting service from dependency list in releasing (# 7503) > 643e735 chore(deps): update module github.com/kunwardeep/paralleltest to v1.0.15 (# 7501) > 1935e60 Fix typos and linguistic errors in documentation / hacktoberfest (# 7494) > fa8e48b OTLP trace exporter include W3C trace flags (bits 0–7) in Span.Flags (# 7438) > 07a26be chore(deps): update module github.com/catenacyber/perfsprint to v0.10.0 (# 7496) > 73cbc69 chore(deps): update github/codeql-action action to v4.30.8 (# 7489) > f58f79b Instrument the `otlptracehttp` exporter (# 7486) > 5c78f7c chore(deps): update module github.com/gofrs/flock to v0.13.0 (# 7487) > 691638a Move sdk/internal/env to sdk/trace/internal/env (# 7437) > c8fc171 Add the `internal/observ` pkg to `otlptracehttp` (# 7480) > 874c4c3 feat: Improve error handling in prometheus exporter (# 7363) > a817caa Add a version const to otlptracehttp (# 7479) > 04ea40e Add the internal `x` package to `otlptracehttp` (# 7476) > 8e8c8c8 chore(deps): update module github.com/ldez/exptostd to v0.4.5 (# 7483) > f89d9f9 chore(deps): update module github.com/nunnatsa/ginkgolinter to v0.21.2 (# 7481) > dc8303b chore(deps): update golang.org/x (# 7477) > 0c97dfd fix(deps): update golang.org/x (# 7475) > 798133c chore(deps): update module github.com/skeema/knownhosts to v1.3.2 (# 7471) > 12bae3a chore(deps): update github/codeql-action action to v4 (# 7472) > 7375147 chore(deps): update module golang.org/x/net to v0.45.0 (# 7470) > 8f0e60d fix(deps): update google.golang.org/genproto/googleapis/rpc digest to 49b9836 (# 7469) > c692bc4 Instrument the `otlptracegrpc` exporter (# 7459) > ce38247 chore(deps): update google.golang.org/genproto/googleapis/api digest to 49b9836 (# 7468) > dd9576c chore(deps): update github/codeql-action action to v3.30.7 (# 7467) > 5f5f4af Document the ordering guarantees provided by the metrics SDK (# 7453) > b64883d chore(deps): update module github.com/prometheus/common to v0.67.1 (# 7465) > 78548fb chore(deps): update module github.com/stretchr/objx to v0.5.3 (# 7464) > c8e3897 Use sync.Map and atomics to improve sum performance (# 7427) > cfd8570 fix(deps): update module go.opentelemetry.io/collector/pdata to v1.43.0 (# 7462) > 681f607 fix(deps): update module google.golang.org/grpc to v1.76.0 (# 7463) > 94f243d chore(deps): update module go.opentelemetry.io/collector/featuregate to v1.43.0 (# 7461) > 11260cd fix(deps): update googleapis to 65f7160 (# 7460) > 14d6372 Add the `internal/observ` package to `otlptracegrpc` (# 7404) > a10652b sdk/trace: trace id high 64 bit tests (# 7212) > 5937fc8 chore(deps): update module github.com/bombsimon/wsl/v5 to v5.3.0 (# 7457) > a39337f chore(deps): update module github.com/go-git/go-git/v5 to v5.16.3 (# 7456) > 64d2bed fix(deps): update build-tools to v0.28.1 (# 7455) > c4bdd87 Support custom error type semantics (# 7442) > 931a5bd chore(deps): update actions/stale action to v10.1.0 (# 7452) > cf1f668 chore(deps): update module github.com/ghostiam/protogetter to v0.3.17 (# 7451) > bd1b3da Add exemplar reservoir parallel benchmarks (# 7441) > dc906d6 chore(deps): update module github.com/grpc-ecosystem/grpc-gateway/v2 to v2.27.3 (# 7450) > 3757239 fix(deps): update googleapis to 7c0ddcb (# 7449) > 7352831 fix(deps): update golang.org/x to 27f1f14 (# 7448) > 5dd35ce feat: logs SDK observability - otlploggrpc exporter metrics (# 7353) > 4b9e111 Skip link checking for acm.org which blocks the link checker (# 7444) > 48cbdac chore(deps): update github/codeql-action action to v3.30.6 (# 7446) > 3ea8606 fix(deps): update module google.golang.org/protobuf to v1.36.10 (# 7445) > dee11e6 Add temporality selector functions (# 7434) > ffeeee8 chore(deps): update peter-evans/create-issue-from-file action to v6 (# 7440) > 862a41a chore(deps): update golang.org/x/telemetry digest to 4eae98a (# 7439) > ea38204 Allow optimizing locking for built-in exemplar reservoirs (# 7423) > 6c54ef6 chore(deps): update ossf/scorecard-action action to v2.4.3 (# 7435) > 4d2b735 chore(deps): update golang.org/x/telemetry digest to 8e64475 (# 7431) > e54c038 chore(deps): update module github.com/charmbracelet/x/ansi to v0.10.2 (# 7432) > addcd63 fix(deps): update googleapis to 57b25ae (# 7429) > 45539cf Only enforce cardinality limits when the attribute set does not already exist (# 7422) > 59ac46c Prometheus exporter: change default translation strategy (# 7421) > 692e519 chore(deps): update module github.com/mattn/go-runewidth to v0.0.19 (# 7428) > 6cb0e90 Generate gRPC Client target parsing func (# 7424) > 81aeace chore(deps): update module go.augendre.info/fatcontext to v0.9.0 (# 7426) > 0db5ac7 sdk/trace/internal/x: generate x package from x component template # 7385 (# 7411) > fef6ee5 chore(deps): update github/codeql-action action to v3.30.5 (# 7425) > 22cfbce Add concurrent safe tests for metric aggregations (# 7379) > fc89784 chore(deps): update module github.com/cyphar/filepath-securejoin to v0.5.0 (# 7419) > bdd4881 chore(deps): update module github.com/mattn/go-runewidth to v0.0.17 (# 7418) > ac8d8e9 Optimize Observability return types in in Prometheus exporter (# 7410) > 88d3fed Optimize the return type of ExportSpans (# 7405) > 63ed041 chore(deps): update module github.com/quasilyte/go-ruleguard/dsl to v0.3.23 (# 7417) > 016b175 chore(deps): update module github.com/quasilyte/go-ruleguard to v0.4.5 (# 7416) > a883fa1 chore(deps): update github/codeql-action action to v3.30.4 (# 7414) > 97a78c1 Add measure benchmarks with exemplars recorded (# 7406) > 2e0b5b4 Add benchmark for synchronous gauge measurement (# 7407) > 97e2244 [chore]: Clean-up unused obsScopeName const (# 7408) > b85e2c1 chore(deps): update actions/cache action to v4.3.0 (# 7409) > 250a11e Add experimental `x` package to `otlptracegrpc` (# 7401) > 466f0cd chore(deps): update module dev.gaijin.team/go/golib to v0.7.0 (# 7402) > 3f05c91 chore(deps): update module github.com/ldez/gomoddirectives to v0.7.1 (# 7400) > 4c9c611 Link checker: ignore https localhost uris (# 7399) > 0cc2eb9 sdk/log: BenchmarkAddAttributes, BenchmarkSetAttributes, BenchmarkSetBody (# 7387) > 80cb909 refactor: replace `context.Background()` with `t.Context()`/`b.Context()` in tests (# 7352) > 2389f44 fix(deps): update module go.opentelemetry.io/collector/pdata to v1.42.0 (# 7397) > 5d3ce38 fix(deps): update googleapis to 9219d12 (# 7393) > dd938b2 fix(deps): update build-tools to v0.28.0 (# 7395) > 4e3152d chore(deps): update module go.opentelemetry.io/build-tools to v0.28.0 (# 7394) > 56498ab chore: sdk/log/internal/x - generate x package from x component template (# 7389) > a579a3e fix(deps): update module github.com/golangci/golangci-lint/v2 to v2.5.0 (# 7392) > de1563e chore(deps): update module github.com/tetafro/godot to v1.5.4 (# 7391) > b5d5bba chore(deps): update module github.com/sagikazarmark/locafero to v0.12.0 (# 7390) > a189c6b sdk/log: add TestRecordMethodsInputConcurrentSafe (# 7378) > 6180f83 Return partial OTLP export errors to the caller (# 7372) > 60f9f39 feat(prometheus): Add observability for prometheus exporter (# 7345) > d1dddde fix(deps): update github.com/opentracing-contrib/go-grpc/test digest to a6e64aa (# 7375) > e4bb37c chore(deps): update otel/weaver docker tag to v0.18.0 (# 7377) > be8e7b2 chore(deps): update module github.com/kulti/thelper to v0.7.1 (# 7376) > b866e36 chore(deps): update module github.com/ldez/grignotin to v0.10.1 (# 7373) > 9d52bde Use Set hash in Distinct (2nd attempt) (# 7175) > 666f95c Fix the typo in test names (# 7369) > 1298533 chore(deps): update module github.com/djarvur/go-err113 to v0.1.1 (# 7368) > 18b114b fix(deps): update module github.com/prometheus/otlptranslator to v1 (# 7358) > 7e4006a chore: generate feature flag files from shared (# 7361) > 5b808c6 Encapsulate SDK Tracer observability (# 7331) > e4ab314 Encapsulate SDK BatchSpanProcessor observability (# 7332) > 6243f21 fix(deps): update module go.opentelemetry.io/auto/sdk to v1.2.1 (# 7365) > 4fdd552 Track context containing span in `recordingSpan` (# 7354) > 59563f7 Do not use the user-defined empty set when comparing sets. (# 7357) > 01611d3 chore(deps): update module github.com/tetafro/godot to v1.5.2 (# 7360) > 305ec06 chore(deps): update module github.com/nunnatsa/ginkgolinter to v0.21.0 (# 7362) > 0d5aa14 chore(deps): update module github.com/antonboom/testifylint to v1.6.4 (# 7359) > 285cbe9 sdk/metric: add example for metricdatatest package (# 7323) > e2da30d trace,metric,log: change WithInstrumentationAttributes to not de-depuplicate the passed attributes in a closure (# 7266) > b168550 fix(deps): update golang.org/x to df92998 (# 7350) > 7fdebbe Rename Self-Observability as just Observability (# 7302) > b06d273 chore(deps): update github/codeql-action action to v3.30.3 (# 7348) > 38d7c39 chore(deps): update module go.yaml.in/yaml/v2 to v2.4.3 (# 7349) > 31629e2 fix(deps): update module google.golang.org/grpc to v1.75.1 (# 7344) > 6d1f9d0 fix(deps): update module golang.org/x/tools to v0.37.0 (# 7347) > df6058a chore(deps): update module golang.org/x/net to v0.44.0 (# 7341) > e26cebf chore(deps): update module github.com/antonboom/nilnil to v1.1.1 (# 7343) > 3baabce Do not allocate instrument options if possible in generated semconv packages (# 7328) > 9b6585a Encapsulate `stdouttrace.Exporter` instrumentation in internal package (# 7307) > a07b7e6 fix(deps): update module google.golang.org/protobuf to v1.36.9 (# 7340) > 71fda10 chore(deps): update golang.org/x (# 7326) > f2ea3f1 chore(deps): update github/codeql-action action to v3.30.2 (# 7339) > 6f50705 fix(deps): update googleapis to 9702482 (# 7335) > c4a6339 chore(deps): update module github.com/spf13/cast to v1.10.0 (# 7333) > 4368300 chore(deps): update module github.com/spf13/viper to v1.21.0 (# 7334) > d0b6f18 fix(deps): update module go.opentelemetry.io/collector/pdata to v1.41.0 (# 7337) > 81c1aca chore(deps): update module github.com/antonboom/errname to v1.1.1 (# 7338) > eb9fd73 chore(deps): update module github.com/lucasb-eyer/go-colorful to v1.3.0 (# 7327) > e759fbd chore(deps): update module github.com/sagikazarmark/locafero to v0.11.0 (# 7329) > cec9d59 chore(deps): update module github.com/spf13/afero to v1.15.0 (# 7330) > 07a91dd trace,metric,log: add WithInstrumentationAttributeSet option (# 7287) > b335c07 Encapsulate observability in Logs SDK (# 7315) > dcf14aa trace,metric,log: WithInstrumentationAttributes options to merge attributes (# 7300) > 63f1ee7 chore(deps): update module mvdan.cc/gofumpt to v0.9.1 (# 7322) > 6f04175 chore(deps): update golang.org/x (# 7324) > 567ef26 Add benchmark for exponential histogram measurements (# 7305) > 8ac554a fix(deps): update golang.org/x (# 7320) > b218e4b Don't track min and max when disabled (# 7306) > 810095e chore(deps): update benchmark-action/github-action-benchmark action to v1.20.7 (# 7319) > f8a9510 fix(deps): update module github.com/prometheus/client_golang to v1.23.2 (# 7314) > 8cea039 chore(deps): update golang.org/x/telemetry digest to af835b0 (# 7313) > 4a0606d chore(deps): update module github.com/pjbgf/sha1cd to v0.5.0 (# 7317) > 2de26d1 chore(deps): update github.com/grafana/regexp digest to f7b3be9 (# 7311) > 97c4e6c chore(deps): update github/codeql-action action to v3.30.1 (# 7312) > e2a4fb3 chore(deps): update golang.org/x/telemetry digest to 9b996f7 (# 7308) > de4b553 chore(deps): update module github.com/bombsimon/wsl/v5 to v5.2.0 (# 7309) > a5dcd68 Add Observability section to CONTRIBUTING doc (# 7272) > 4107421 chore(deps): update codecov/codecov-action action to v5.5.1 (# 7303) > d8d6e52 fix(deps): update module github.com/prometheus/client_golang to v1.23.1 (# 7304) > e54519a chore(deps): update actions/setup-go action to v6 (# 7298) > a0cc03c chore(deps): update actions/stale action to v10 (# 7299) > 8c8cd0a fix(deps): update module go.opentelemetry.io/proto/otlp to v1.8.0 (# 7296) > 83c041a chore(deps): update module mvdan.cc/gofumpt to v0.9.0 (# 7292) > 295fbdc chore(deps): update module github.com/golangci/go-printf-func-name to v0.1.1 (# 7290) > 09e5d69 chore(deps): update module github.com/ghostiam/protogetter to v0.3.16 (# 7289) > 7cb19f9 chore(deps): update benchmark-action/github-action-benchmark action to v1.20.5 (# 7293) > b8f00e3 chore(deps): update module github.com/spf13/pflag to v1.0.10 (# 7291) > 0174808 Fix schema urls (# 7288) > 5e3b939 Add tracetest example for testing instrumentation (# 7107) > 090e9ef chore(deps): update module github.com/spf13/cobra to v1.10.1 (# 7286) > a389393 chore(deps): update github/codeql-action action to v3.30.0 (# 7284) > 6ccc387 chore(deps): update module github.com/spf13/cobra to v1.10.0 (# 7285) > 774c740 Fix missing link in changelog (# 7273) > 5d1ec3a fix(deps): update github.com/opentracing-contrib/go-grpc/test digest to 0261db7 (# 7278) > f74ab34 chore(deps): update module github.com/spf13/pflag to v1.0.9 (# 7282) > b0903af chore(deps): update module github.com/rogpeppe/go-internal to v1.14.1 (# 7283) > 358fa01 fix(deps): update googleapis to ef028d9 (# 7279) > 68b1c4c fix(deps): update module github.com/opentracing-contrib/go-grpc to v0.1.2 (# 7281) > c8632bc fix(deps): update golang.org/x (# 7188) > 18ad4a1 fix(deps): update module github.com/golangci/golangci-lint/v2 to v2.4.0 (# 7277) > c2fea5f chore(deps): update module github.com/securego/gosec/v2 to v2.22.8 (# 7276) > 83403d3 fix(deps): update module go.opentelemetry.io/collector/pdata to v1.40.0 (# 7275) > 8ab8e42 Drop support for Go 1.23 (# 7274) bumping k8s.io/apiextensions-apiserver d7702f9...a4ffeda: > a4ffeda Update dependencies to v0.34.3 tag > 4a9fea1 Merge pull request # 133901 from yongruilin/automated-cherry-pick-of-# 133896-upstream-release-1.34 > 3896d9f fix: Only warn for unrecognized formats on type=string > aada5e8 Merge remote-tracking branch 'origin/master' into release-1.34 > bad5b2a clarify that staging repos are automatically published > f498996 add pointer to CONTRIBUTING.md for more details on contributing, clarify read-only > f782221 link to what a staging repository is > 3625d64 docs: clarify that this is a staging repository and not for direct contributions > 71e26b6 Bump etcd sdk to v3.6.4 > 056a425 Merge pull request # 133180 from ylink-lfs/chore/ptr_cast_replace > 63d27f2 Merge pull request # 132942 from thockin/kyaml > f5ec1b6 chore: replace ptr caster with unified ptr.To > 6f8ce15 Allow white-spaced CABundle during webhook client creation and validation (# 132514) > 4a2cee4 Re-vendor sigs.k8s.io/yaml @ v1.6.0 > 69bd66b Merge pull request # 132935 from benluddy/cbor-bump-custom-marshalers > 243cae7 Merge pull request # 132837 from JoelSpeed/fix-max-elements-x-int-or-string > a5cddfa Bump to github.com/fxamacker/cbor/v2 v2.9.0. > 041e1fe Merge pull request # 133136 from yongruilin/crd-format-warning > d494e38 Add test case to prove MaxElements correctly set on IntOrString > 73c2e76 Merge pull request # 133104 from aman4433/chore/replace-float64ptr-with-ptrTo > 7f83928 feat: Implement warnings for unrecognized formats in CRDs > af2d308 chore: replace float64Ptr with ptr.To helper in validation and integration tests > 45ab34f feat: Add func to export the supportedVersionedFormats > ca04741 Merge pull request # 131700 from cici37/celList > 07fddd6 Add support for CEL list library. > bcc01b2 Merge pull request # 133010 from cici37/promote-Cel > 08239fc Update cel-go to v0.26.0 > 10bfaba Merge pull request # 133020 from pohly/apimachinery-list-map-keys > 79701ff Merge pull request # 131293 from skitt/typo-watchAction > 3876cff sigs.k8s.io/structured-merge-diff/v6 v6.3.0 > 9fbc252 Merge pull request # 132871 from dims/bump-k8s.io/kube-openapi-to-latest-SHA-f3f2b991d03b > 9990808 Typo fix: watchActcion > f6db3df Merge pull request # 132513 from xiaoweim/validation-cleanup-invalid > c072574 Bump k8s.io/kube-openapi to latest SHA (f3f2b991d03b) > a2eb7d0 address review comments > 890aa9b Cleanup: Remove field name from invalid field detail message > 83f842a Merge pull request # 132920 from ylink-lfs/chore/maxptr_removal > 3ca544d chore: maxPtr utility removal with ptr.To > 5505d2e Merge pull request # 132845 from ylink-lfs/chore/int64ptr_removal > d1cc299 chore: replace int64ptr with ptr.To > bdaffda Merge pull request # 132819 from ylink-lfs/chore/uint64ptr_usage_removal > 2149a4d chore: remove residual uint64ptr usage with ptr package > 6ffe60d Merge pull request # 132788 from ylink-lfs/chore/strptr_removal > 2923d2d chore: remove strPtr usage with ptr.To instead > 0366b97 Merge pull request # 132723 from PatrickLaabs/132086-apiextensions > baaa8ae Merge pull request # 132726 from PatrickLaabs/132086-apiext-apiserver-validation > f657946 chore: depr. pointer pkg replacement for apiextensions in general > 68ece1e Merge pull request # 132721 from PatrickLaabs/132086-apiext-integration > fe7f7a6 chore: depr. pointer pkg replacement for apiext. apiservers validations > e745655 Merge pull request # 132724 from PatrickLaabs/132086-apiext-controller > e1fdac0 chore: depr. pointer pkg replacement for apiext. integration > 0aa4758 Merge pull request # 132725 from PatrickLaabs/132086-apiext-registry > 5cdcaff chore: depr. pointer pkg replacement for apiext. pkg/cntroller > d867714 chore: depr. pointer pkg replacement for apiext. pkg/registry > 412ddb5 Merge pull request # 132314 from thockin/jp_nicer_api_errors > 9658f29 Merge pull request # 132675 from dims/bump-sigs-k8s-io-json-no-code-changes > 0ef7765 WIP: Fix tests > 18a002b Merge pull request # 132677 from dims/update-github.com/emicklei/go-restful/v3-to-v3.12.2 > 9f725bb Bump sigs.k8s.io/json to latest - no code changes > df1e55a Merge pull request # 132676 from dims/bump-go.yaml.in/yaml/v3-to-v3.0.4 > f882edd Update github.com/emicklei/go-restful/v3 to v3.12.2 > f7ec911 Bump go.yaml.in/yaml/v3 to v3.0.4 > fa53f68 Merge pull request # 132654 from Jefftree/b-openapi > 88058dd Update vendor > 2822a00 pin kube-openapi to v0.0.0-20250628140032-d90c4fd18f59 > 184ebbc Merge pull request # 132472 from xiaoweim/validation-cleanup > 0648b56 Merge pull request # 132584 from alvaroaleman/fix-asfafs > f0841a7 Cleanup: Remove redundant detail messages in field.Required > fbfcda5 Merge pull request # 132438 from dims/golangci-plugin-for-sorting-feature-gates > b38c31b Re-generate applyconfigurations > d045c7f Ensure all the files have the updated sorting > 16011e1 Merge pull request # 132357 from dims/drop-usage-of-forked-copies-of-goyaml.v2-and-goyaml.v3 > b0ff13f Merge pull request # 132517 from michaelasp/fixflake > cf24e04 switch to latest sigs.k8s.io/yaml v1.5.0 (run update-gofmt.sh as well) > d7e6677 Merge pull request # 132504 from jpbetz/name-formats > db34fc0 fix: Add wait for cache sync for customresourcediscovery tests > beaf7e4 Drop usage of forked copies of goyaml.v2 and goyaml.v3 > c895380 Merge pull request # 132467 from sdowell/ssa-terminating > 4035caa Add printer column validation tests > e732112 fix: prevent SSA from creating CR while CRD terminating > 01feeeb Introduce k8s-short-name and k8s-long-name to the OpenAPI formats supported by CRDs > 1c5db78 Merge pull request # 132194 from alvaroaleman/local0dev > 2ec638a Bump to latest kube-openapi > 35570bf Test that generated applyconfigs are a runtime.ApplyConfig > 46aa98f Re-Generate applyconfigs > b6ed36e Merge pull request # 132269 from dims/update-to-latest-github.com/modern-go/reflect2 > 404e9dd Update to latest github.com/modern-go/reflect2 > d7893ce Merge pull request # 132239 from dims/update-to-etcd-3.6.1-in-vendor > 1b90fee Update to etcd v3.6.1 in vendor/ > 0fe9bd2 Merge pull request # 132209 from dims/update-github.com/spf13/cobra-v1.9.1eksctl > d0531ef update github.com/spf13/cobra v1.9.1 > df69040 Merge pull request # 132103 from nojnhuh/typed-ring-buffer > 4b42467 Merge pull request # 132110 from jpbetz/gengo-bump > 3d9987c Update k8s.io/utils for new generic ring buffer > f0171f1 Bump gengo/v2 to latest > 1e69f9c Merge pull request # 131951 from dims/drop-usages-of-deprecated-otelgrpc-methods > de00027 Merge pull request # 131664 from jpbetz/subresources-enable-replicas > 66c0b02 Drop usages of deprecated otelgrpc methods > d928225 generate code > 80ea9f4 Merge pull request # 131838 from dims/bump-google.golang.org/grpc-to-google-v1.72.1 > 080d7cc Bump google.golang.org/grpc v1.72.1 > cf935fa Merge pull request # 128419 from liggitt/etcd-3.6 > f39952d bump etcd client to 3.6 > fc47812 Merge pull request # 131777 from BenTheElder/exit-codes > 6c2c4f7 verify scripts: preserve exit code > daf1c52 Merge pull request # 131616 from jpbetz/typeconverter-cleanup > b6bb912 Reorganize scheme type converter into apimachinery utils > be7b464 Merge pull request # 131477 from pohly/golangci-lint@v2 > f3d6de6 Merge pull request # 131595 from aojea/utils_fake_clock > 44000e7 chore: bump golangci-lint to v2 > 4b2d6d4 update k8s.io/utils to bring fakeClock.Waiters() > 83f8037 Merge pull request # 130989 from liggitt/creationTimestamp-omitzero > 7451720 Drop null creationTimestamp from test fixtures > 6207442 bump cbor to add omitzero support > f699de7 bump structured-merge-diff to add omitzero support > bf1974c Merge pull request # 131434 from pacoxu/fsnotify > 7396204 Merge pull request # 131444 from erdii/update-cel-go > e79d54b bump fsnotify v1.9.0 > d63635a chore: update github.com/google/cel-go dependency to v0.25.0 > 1e127a9 Merge pull request # 130995 from xigang/utils > f05aec0 Merge pull request # 131204 from dims/move-to-released-version-of-prometheus/client_golang-v1.22.0-from-rc.0 > 3f819e6 bump k8s.io/utils > 04f887e Move to released version of prometheus/client_golang v1.22.0 from rc.0 > ec6bea2 Merge pull request # 131103 from ahrtr/etcd_sdk_20250328 Signed-off-by: Knative Automation * upgrade to latest dependencies (#718) bumping golang.org/x/sys 08e5482...2f44229: > 2f44229 sys/cpu: add symbolic constants for remaining cpuid bits > e5770d2 sys/cpu: use symbolic names for masks > 714a44c sys/cpu: modify x86 port to match what internal/cpu does bumping golang.org/x/term 3863673...a7e5b04: > a7e5b04 go.mod: update golang.org/x dependencies > 943f25d x/term: handle transpose > 9b991dd x/term: handle delete key bumping golang.org/x/mod d271cf3...4c04067: > 4c04067 go.mod: update golang.org/x dependencies bumping golang.org/x/crypto 19acf81...506e022: > 506e022 go.mod: update golang.org/x dependencies > 7dacc38 chacha20poly1305: error out in fips140=only mode bumping golang.org/x/tools 00b22d9...2ad2b30: > 2ad2b30 go.mod: update golang.org/x dependencies > 5832cce internal/diff/lcs: introduce line diffs > 67c4257 gopls/internal/golang: Definition: fix Windows bug wrt //go:embed > 12c1f04 gopls/completion: check Selection invariant > 6d87185 internal/server: add vulncheck scanning after vulncheck prompt > 0c3a1fe go/ast/inspector: FindByPos returns the first innermost node > ca281cf go/analysis/passes/ctrlflow: add noreturn funcs from popular pkgs > 09c21a9 gopls/internal/analysis/unusedfunc: remove warnings for unused enum consts > 03cb455 internal/modindex: suppress missing modcacheindex message > 15d13e8 gopls/internal/util/typesutil: refine EnclosingSignature bug.Report > 02e1c6b gopls/internal/analysis/modernize: mapsloop: undefined loop-var > 41cca47 go/analysis/passes/modernize: fix stringsbuilder bug with in var(...) > cfae896 go/analysis/passes/modernize: stringsbuilder: avoid overlapping fixes > 0f94e53 go/{cfg,analysis/passes/{ctrlflow,buildssa}}: noreturn > 78e6aac go/analysis/passes/modernize: treat 'i += 1' as 'i++' > f12a0ae gopls/benchmark: skip unimported completion if not local > 5ca1b57 gopls/internal/server: disable TestVulncheckPreference for wasm > cee4451 go/analysis/passes/stdversion: suppress synctest warning > 267fc6b go/analysis/passes/modernize: disable BLoop analyzer > 761a007 gopls/mod: find references to required modules > 9136dd1 go/analysis/passes/modernize: document stringsbuilder/QF1012 synergy > eaaac7c gopls/internal/server: store vulncheck prompt preference > 24c2d6d go/analysis,gopls: update two modernizes' URLs > 61da5cd internal/astutil: return enclosing node when selection contains no nodes > 454a5c0 gopls/internal/server: disable TestCheckGoModDeps for js and wasm > acdd27b gopls/internal/analysis/modernize: fix URL > 95246c4 go/analysis/passes/modernize: rangeint: result vars are implicit uses > 6c613c8 gopls/internal/cache/parsego: construct File.Cursor lazily > 60782aa internal: fix unused errors reported by ineffassign > 3f45d3b internal/stdlib: update stdlib index for Go 1.26 R… * upgrade to latest dependencies (#726) bumping knative.dev/eventing cb840ef...d740aa4: > d740aa4 [main] Upgrade to latest dependencies (# 8863) Signed-off-by: Knative Automation * added timeout to shutdown handler. close nats conn after server shutdown * added read/write/idle timeouts * re-using handler logger * extracting msg id directly from event * using knative NS --------- Signed-off-by: Knative Automation Co-authored-by: Knative Automation --- cmd/broker/main.go | 8 +- cmd/ingress/main.go | 39 +++ pkg/broker/ingress/controller.go | 148 ++++++++++++ pkg/broker/ingress/handler.go | 176 ++++++++++++++ pkg/broker/ingress/handler_test.go | 374 +++++++++++++++++++++++++++++ 5 files changed, 739 insertions(+), 6 deletions(-) create mode 100644 cmd/ingress/main.go create mode 100644 pkg/broker/ingress/controller.go create mode 100644 pkg/broker/ingress/handler.go create mode 100644 pkg/broker/ingress/handler_test.go diff --git a/cmd/broker/main.go b/cmd/broker/main.go index d10a82bb5..97d0fc5a8 100644 --- a/cmd/broker/main.go +++ b/cmd/broker/main.go @@ -18,14 +18,13 @@ package main import ( // Import GCP auth plugin for authentication with GKE clusters - "os" - _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" "knative.dev/pkg/configmap" "knative.dev/pkg/injection" "knative.dev/pkg/injection/sharedmain" "knative.dev/pkg/signals" + "knative.dev/pkg/system" "knative.dev/eventing-natss/pkg/broker/controller" "knative.dev/eventing-natss/pkg/broker/trigger" @@ -37,10 +36,7 @@ func main() { // nats-config is volume mounted so initialize the fsloader ctx = fsloader.WithLoader(ctx, configmap.Load) - ns := os.Getenv("NAMESPACE") - if ns != "" { - ctx = injection.WithNamespaceScope(ctx, ns) - } + ctx = injection.WithNamespaceScope(ctx, system.Namespace()) sharedmain.MainWithContext(ctx, controller.ComponentName, controller.NewController, diff --git a/cmd/ingress/main.go b/cmd/ingress/main.go new file mode 100644 index 000000000..6c12b023b --- /dev/null +++ b/cmd/ingress/main.go @@ -0,0 +1,39 @@ +/* +Copyright 2026 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "os" + + "knative.dev/pkg/injection" + "knative.dev/pkg/injection/sharedmain" + "knative.dev/pkg/signals" + + "knative.dev/eventing-natss/pkg/broker/ingress" +) + +func main() { + component := "natsjs-broker-ingress" + + ctx := signals.NewContext() + ns := os.Getenv("NAMESPACE") + if ns != "" { + ctx = injection.WithNamespaceScope(ctx, ns) + } + + sharedmain.MainWithContext(ctx, component, ingress.NewController) +} diff --git a/pkg/broker/ingress/controller.go b/pkg/broker/ingress/controller.go new file mode 100644 index 000000000..4c1412773 --- /dev/null +++ b/pkg/broker/ingress/controller.go @@ -0,0 +1,148 @@ +/* +Copyright 2026 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package ingress + +import ( + "context" + "fmt" + "net/http" + "time" + + "github.com/kelseyhightower/envconfig" + "go.uber.org/zap" + + "knative.dev/pkg/configmap" + "knative.dev/pkg/controller" + "knative.dev/pkg/logging" + "knative.dev/pkg/reconciler" + + commonnats "knative.dev/eventing-natss/pkg/common/nats" +) + +// todo: Make these confgurable when migrate to shared ingress +const ( + // HTTP server timeouts + httpReadTimeout = 30 * time.Second + httpWriteTimeout = 30 * time.Second + httpIdleTimeout = 120 * time.Second +) + +type envConfig struct { + BrokerName string `envconfig:"BROKER_NAME" required:"true"` + Namespace string `envconfig:"BROKER_NAMESPACE" required:"true"` + StreamName string `envconfig:"STREAM_NAME" required:"true"` + NatsURL string `envconfig:"NATS_URL" required:"true"` + Port int `envconfig:"PORT" default:"8080"` +} + +// NewController creates a new ingress controller +func NewController(ctx context.Context, _ configmap.Watcher) *controller.Impl { + logger := logging.FromContext(ctx) + + // Load environment configuration + env := &envConfig{} + if err := envconfig.Process("", env); err != nil { + logger.Fatalw("Failed to process environment variables", zap.Error(err)) + } + + logger.Infow("Starting broker ingress", + zap.String("broker", env.BrokerName), + zap.String("namespace", env.Namespace), + zap.String("stream", env.StreamName), + zap.String("nats_url", env.NatsURL), + zap.Int("port", env.Port), + ) + + // Create NATS connection using URL from environment variable + natsConn, err := commonnats.NewNatsConnFromURL(ctx, env.NatsURL) + if err != nil { + logger.Fatalw("Failed to create NATS connection", zap.Error(err)) + } + + // Create JetStream context + js, err := natsConn.JetStream() + if err != nil { + logger.Fatalw("Failed to create JetStream context", zap.Error(err)) + } + + // Verify stream exists + _, err = js.StreamInfo(env.StreamName) + if err != nil { + logger.Fatalw("Stream does not exist", zap.Error(err), zap.String("stream", env.StreamName)) + } + + logger.Infow("Connected to JetStream", zap.String("stream", env.StreamName)) + + // Create the ingress handler + handler := NewHandler(HandlerConfig{ + Logger: logger, + JetStream: js, + BrokerName: env.BrokerName, + Namespace: env.Namespace, + StreamName: env.StreamName, + }) + + // Set up HTTP server with routes + mux := http.NewServeMux() + mux.Handle("/", handler) + mux.HandleFunc("/healthz", handler.LivenessChecker()) + mux.HandleFunc("/readyz", handler.ReadinessChecker()) + + server := &http.Server{ + Addr: fmt.Sprintf(":%d", env.Port), + Handler: mux, + ReadTimeout: httpReadTimeout, + WriteTimeout: httpWriteTimeout, + IdleTimeout: httpIdleTimeout, + } + + // Start HTTP server in background + go func() { + logger.Infow("Starting HTTP server", zap.Int("port", env.Port)) + if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { + logger.Fatalw("HTTP server error", zap.Error(err)) + } + }() + + // Handle graceful shutdown + go func() { + <-ctx.Done() + logger.Info("Shutting down HTTP server") + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + server.Shutdown(ctx) + natsConn.Close() + }() + + // Create a no-op controller impl since ingress doesn't reconcile any resources + impl := controller.NewContext(ctx, &noopReconciler{}, controller.ControllerOptions{ + WorkQueueName: "NatsJetStreamBrokerIngress", + Logger: logger, + }) + + logger.Info("Ingress controller initialized") + return impl +} + +// noopReconciler is a no-op reconciler since ingress doesn't reconcile any resources +type noopReconciler struct { + reconciler.LeaderAwareFuncs +} + +func (r *noopReconciler) Reconcile(ctx context.Context, key string) error { + return nil +} diff --git a/pkg/broker/ingress/handler.go b/pkg/broker/ingress/handler.go new file mode 100644 index 000000000..77f800dd2 --- /dev/null +++ b/pkg/broker/ingress/handler.go @@ -0,0 +1,176 @@ +/* +Copyright 2026 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package ingress + +import ( + "bytes" + "context" + "net/http" + + cejs "github.com/cloudevents/sdk-go/protocol/nats_jetstream/v2" + ce "github.com/cloudevents/sdk-go/v2" + "github.com/cloudevents/sdk-go/v2/binding" + cehttp "github.com/cloudevents/sdk-go/v2/protocol/http" + "github.com/nats-io/nats.go" + "go.uber.org/zap" + + brokerutils "knative.dev/eventing-natss/pkg/broker/utils" + commonce "knative.dev/eventing-natss/pkg/common/cloudevents" + "knative.dev/eventing-natss/pkg/tracing" +) + +// Handler handles incoming CloudEvents and publishes them to JetStream +type Handler struct { + logger *zap.SugaredLogger + js nats.JetStreamContext + brokerName string + namespace string + streamName string +} + +// HandlerConfig contains configuration for creating a Handler +type HandlerConfig struct { + Logger *zap.SugaredLogger + JetStream nats.JetStreamContext + BrokerName string + Namespace string + StreamName string +} + +// NewHandler creates a new ingress handler +func NewHandler(config HandlerConfig) *Handler { + return &Handler{ + logger: config.Logger, + js: config.JetStream, + brokerName: config.BrokerName, + namespace: config.Namespace, + streamName: config.StreamName, + } +} + +// ServeHTTP implements http.Handler +func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + logger := h.logger + + // Only accept POST requests + if r.Method != http.MethodPost { + logger.Warnw("Received non-POST request", zap.String("method", r.Method)) + w.WriteHeader(http.StatusMethodNotAllowed) + return + } + + // Convert HTTP request to CloudEvents message + message := cehttp.NewMessageFromHttpRequest(r) + defer message.Finish(nil) + + // Extract the event from the message + event, err := binding.ToEvent(ctx, message) + if err != nil { + logger.Warnw("Failed to extract event from request", zap.Error(err)) + w.WriteHeader(http.StatusBadRequest) + return + } + + // Validate the event + if err := event.Validate(); err != nil { + logger.Warnw("Invalid CloudEvent", zap.Error(err)) + w.WriteHeader(http.StatusBadRequest) + return + } + + logger.Debugw("Received CloudEvent", + zap.String("id", event.ID()), + zap.String("type", event.Type()), + zap.String("source", event.Source()), + ) + + // Publish to JetStream + if err := h.publishToJetStream(ctx, event); err != nil { + logger.Errorw("Failed to publish event to JetStream", zap.Error(err)) + w.WriteHeader(http.StatusInternalServerError) + return + } + + logger.Debugw("Successfully published event to JetStream", zap.String("id", event.ID())) + w.WriteHeader(http.StatusAccepted) +} + +// publishToJetStream publishes a CloudEvent to the broker's JetStream stream +func (h *Handler) publishToJetStream(ctx context.Context, event *ce.Event) error { + logger := h.logger.With(zap.String("msg_id", event.ID())) + + // Convert event to binding message + message := binding.ToMessage(event) + + // Extract event ID for deduplication (populated by Transform() during WriteMsg) + eventID := commonce.IDExtractorTransformer("") + transformers := append([]binding.Transformer{&eventID}, + tracing.SerializeTraceTransformers(ctx)..., + ) + + // Encode the message for JetStream + ctx = ce.WithEncodingStructured(ctx) + writer := new(bytes.Buffer) + if _, err := cejs.WriteMsg(ctx, message, writer, transformers...); err != nil { + logger.Errorw("Failed to encode CloudEvent for JetStream", zap.Error(err)) + return err + } + + // Build the subject name for publishing + // Add .events suffix to match the stream's subject pattern (publishSubject.>) + subject := brokerutils.BrokerPublishSubjectName(h.namespace, h.brokerName) + ".events" + + // Publish to JetStream with message ID for deduplication + _, err := h.js.Publish(subject, writer.Bytes(), nats.MsgId(string(eventID))) + if err != nil { + logger.Errorw("Failed to publish to JetStream", + zap.Error(err), + zap.String("subject", subject), + zap.String("event_id", string(eventID)), + ) + return err + } + + logger.Debugw("Published event to JetStream", + zap.String("subject", subject), + zap.String("event_id", string(eventID)), + ) + + return nil +} + +// ReadinessChecker returns an http.HandlerFunc for readiness checks +func (h *Handler) ReadinessChecker() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + // Check JetStream connection by getting stream info + _, err := h.js.StreamInfo(h.streamName) + if err != nil { + h.logger.Warnw("Readiness check failed: stream not available", zap.Error(err)) + w.WriteHeader(http.StatusServiceUnavailable) + return + } + w.WriteHeader(http.StatusOK) + } +} + +// LivenessChecker returns an http.HandlerFunc for liveness checks +func (h *Handler) LivenessChecker() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + } +} diff --git a/pkg/broker/ingress/handler_test.go b/pkg/broker/ingress/handler_test.go new file mode 100644 index 000000000..7d2d7722d --- /dev/null +++ b/pkg/broker/ingress/handler_test.go @@ -0,0 +1,374 @@ +/* +Copyright 2026 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package ingress + +import ( + "bytes" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + natsserver "github.com/nats-io/nats-server/v2/server" + natstest "github.com/nats-io/nats-server/v2/test" + "github.com/nats-io/nats.go" + "go.uber.org/zap" +) + +func runBasicJetStreamServer(t *testing.T) *natsserver.Server { + t.Helper() + opts := natstest.DefaultTestOptions + opts.Port = -1 + opts.JetStream = true + opts.StoreDir = t.TempDir() // Use temp dir to isolate test state + return natstest.RunServer(&opts) +} + +func TestNewHandler(t *testing.T) { + s := runBasicJetStreamServer(t) + defer s.Shutdown() + + nc, err := nats.Connect(s.ClientURL()) + if err != nil { + t.Fatalf("Failed to connect: %v", err) + } + defer nc.Close() + + js, err := nc.JetStream() + if err != nil { + t.Fatalf("Failed to get JetStream context: %v", err) + } + + logger := zap.NewNop().Sugar() + + handler := NewHandler(HandlerConfig{ + Logger: logger, + JetStream: js, + BrokerName: "test-broker", + Namespace: "test-namespace", + StreamName: "TEST_STREAM", + }) + + if handler == nil { + t.Fatal("NewHandler returned nil") + } + + if handler.brokerName != "test-broker" { + t.Errorf("brokerName = %v, want test-broker", handler.brokerName) + } + + if handler.namespace != "test-namespace" { + t.Errorf("namespace = %v, want test-namespace", handler.namespace) + } + + if handler.streamName != "TEST_STREAM" { + t.Errorf("streamName = %v, want TEST_STREAM", handler.streamName) + } +} + +func TestServeHTTP_MethodNotAllowed(t *testing.T) { + s := runBasicJetStreamServer(t) + defer s.Shutdown() + + nc, err := nats.Connect(s.ClientURL()) + if err != nil { + t.Fatalf("Failed to connect: %v", err) + } + defer nc.Close() + + js, err := nc.JetStream() + if err != nil { + t.Fatalf("Failed to get JetStream context: %v", err) + } + + logger := zap.NewNop().Sugar() + + handler := NewHandler(HandlerConfig{ + Logger: logger, + JetStream: js, + BrokerName: "test-broker", + Namespace: "test-namespace", + StreamName: "TEST_STREAM", + }) + + methods := []string{http.MethodGet, http.MethodPut, http.MethodDelete, http.MethodPatch} + + for _, method := range methods { + t.Run(method, func(t *testing.T) { + req := httptest.NewRequest(method, "/", nil) + w := httptest.NewRecorder() + + handler.ServeHTTP(w, req) + + if w.Code != http.StatusMethodNotAllowed { + t.Errorf("Status code = %v, want %v", w.Code, http.StatusMethodNotAllowed) + } + }) + } +} + +func TestServeHTTP_InvalidCloudEvent(t *testing.T) { + s := runBasicJetStreamServer(t) + defer s.Shutdown() + + nc, err := nats.Connect(s.ClientURL()) + if err != nil { + t.Fatalf("Failed to connect: %v", err) + } + defer nc.Close() + + js, err := nc.JetStream() + if err != nil { + t.Fatalf("Failed to get JetStream context: %v", err) + } + + logger := zap.NewNop().Sugar() + + handler := NewHandler(HandlerConfig{ + Logger: logger, + JetStream: js, + BrokerName: "test-broker", + Namespace: "test-namespace", + StreamName: "TEST_STREAM", + }) + + // Request without CloudEvents headers + req := httptest.NewRequest(http.MethodPost, "/", bytes.NewBufferString("not a cloud event")) + req.Header.Set("Content-Type", "text/plain") + w := httptest.NewRecorder() + + handler.ServeHTTP(w, req) + + if w.Code != http.StatusBadRequest { + t.Errorf("Status code = %v, want %v", w.Code, http.StatusBadRequest) + } +} + +func TestServeHTTP_ValidCloudEvent(t *testing.T) { + s := runBasicJetStreamServer(t) + defer s.Shutdown() + + nc, err := nats.Connect(s.ClientURL()) + if err != nil { + t.Fatalf("Failed to connect: %v", err) + } + defer nc.Close() + + js, err := nc.JetStream() + if err != nil { + t.Fatalf("Failed to get JetStream context: %v", err) + } + + // Create a stream to publish to + _, err = js.AddStream(&nats.StreamConfig{ + Name: "TEST_STREAM", + Subjects: []string{"test-namespace.test-broker._knative_broker.>"}, + }) + if err != nil { + t.Fatalf("Failed to create stream: %v", err) + } + + logger := zap.NewNop().Sugar() + + handler := NewHandler(HandlerConfig{ + Logger: logger, + JetStream: js, + BrokerName: "test-broker", + Namespace: "test-namespace", + StreamName: "TEST_STREAM", + }) + + // Create a valid CloudEvent in structured format + event := map[string]interface{}{ + "specversion": "1.0", + "type": "test.type", + "source": "test/source", + "id": "test-id-123", + "data": map[string]string{"key": "value"}, + } + + eventJSON, _ := json.Marshal(event) + req := httptest.NewRequest(http.MethodPost, "/", bytes.NewBuffer(eventJSON)) + req.Header.Set("Content-Type", "application/cloudevents+json") + w := httptest.NewRecorder() + + handler.ServeHTTP(w, req) + + if w.Code != http.StatusAccepted { + t.Errorf("Status code = %v, want %v", w.Code, http.StatusAccepted) + } + + // Verify the event was published to the stream + streamInfo, err := js.StreamInfo("TEST_STREAM") + if err != nil { + t.Fatalf("Failed to get stream info: %v", err) + } + + if streamInfo.State.Msgs != 1 { + t.Errorf("Stream message count = %v, want 1", streamInfo.State.Msgs) + } +} + +func TestServeHTTP_BinaryCloudEvent(t *testing.T) { + s := runBasicJetStreamServer(t) + defer s.Shutdown() + + nc, err := nats.Connect(s.ClientURL()) + if err != nil { + t.Fatalf("Failed to connect: %v", err) + } + defer nc.Close() + + js, err := nc.JetStream() + if err != nil { + t.Fatalf("Failed to get JetStream context: %v", err) + } + + // Create a stream to publish to + _, err = js.AddStream(&nats.StreamConfig{ + Name: "BINARY_STREAM", + Subjects: []string{"default.test-broker._knative_broker.>"}, + }) + if err != nil { + t.Fatalf("Failed to create stream: %v", err) + } + + logger := zap.NewNop().Sugar() + + handler := NewHandler(HandlerConfig{ + Logger: logger, + JetStream: js, + BrokerName: "test-broker", + Namespace: "default", + StreamName: "BINARY_STREAM", + }) + + // Create a valid CloudEvent in binary format + req := httptest.NewRequest(http.MethodPost, "/", bytes.NewBufferString(`{"key": "value"}`)) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("ce-specversion", "1.0") + req.Header.Set("ce-type", "test.type") + req.Header.Set("ce-source", "test/source") + req.Header.Set("ce-id", "binary-test-id") + w := httptest.NewRecorder() + + handler.ServeHTTP(w, req) + + if w.Code != http.StatusAccepted { + t.Errorf("Status code = %v, want %v", w.Code, http.StatusAccepted) + } +} + +func TestReadinessChecker(t *testing.T) { + s := runBasicJetStreamServer(t) + defer s.Shutdown() + + nc, err := nats.Connect(s.ClientURL()) + if err != nil { + t.Fatalf("Failed to connect: %v", err) + } + defer nc.Close() + + js, err := nc.JetStream() + if err != nil { + t.Fatalf("Failed to get JetStream context: %v", err) + } + + logger := zap.NewNop().Sugar() + + t.Run("stream available", func(t *testing.T) { + // Create stream first + _, err = js.AddStream(&nats.StreamConfig{ + Name: "READY_STREAM", + Subjects: []string{"ready.>"}, + }) + if err != nil { + t.Fatalf("Failed to create stream: %v", err) + } + + handler := NewHandler(HandlerConfig{ + Logger: logger, + JetStream: js, + BrokerName: "test-broker", + Namespace: "test-namespace", + StreamName: "READY_STREAM", + }) + + req := httptest.NewRequest(http.MethodGet, "/readyz", nil) + w := httptest.NewRecorder() + + handler.ReadinessChecker()(w, req) + + if w.Code != http.StatusOK { + t.Errorf("Status code = %v, want %v", w.Code, http.StatusOK) + } + }) + + t.Run("stream not available", func(t *testing.T) { + handler := NewHandler(HandlerConfig{ + Logger: logger, + JetStream: js, + BrokerName: "test-broker", + Namespace: "test-namespace", + StreamName: "NONEXISTENT_STREAM", + }) + + req := httptest.NewRequest(http.MethodGet, "/readyz", nil) + w := httptest.NewRecorder() + + handler.ReadinessChecker()(w, req) + + if w.Code != http.StatusServiceUnavailable { + t.Errorf("Status code = %v, want %v", w.Code, http.StatusServiceUnavailable) + } + }) +} + +func TestLivenessChecker(t *testing.T) { + s := runBasicJetStreamServer(t) + defer s.Shutdown() + + nc, err := nats.Connect(s.ClientURL()) + if err != nil { + t.Fatalf("Failed to connect: %v", err) + } + defer nc.Close() + + js, err := nc.JetStream() + if err != nil { + t.Fatalf("Failed to get JetStream context: %v", err) + } + + logger := zap.NewNop().Sugar() + + handler := NewHandler(HandlerConfig{ + Logger: logger, + JetStream: js, + BrokerName: "test-broker", + Namespace: "test-namespace", + StreamName: "TEST_STREAM", + }) + + req := httptest.NewRequest(http.MethodGet, "/healthz", nil) + w := httptest.NewRecorder() + + handler.LivenessChecker()(w, req) + + if w.Code != http.StatusOK { + t.Errorf("Status code = %v, want %v", w.Code, http.StatusOK) + } +} From 38fd5f6d76ac8c1773693649ba49bf308a530dfe Mon Sep 17 00:00:00 2001 From: Andrii Stelmashenko Date: Thu, 29 Jan 2026 16:28:53 +0200 Subject: [PATCH 07/10] changed error backoff --- pkg/broker/filter/consumer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/broker/filter/consumer.go b/pkg/broker/filter/consumer.go index 711b7b2f7..a0deffbe9 100644 --- a/pkg/broker/filter/consumer.go +++ b/pkg/broker/filter/consumer.go @@ -257,7 +257,7 @@ func (m *ConsumerManager) fetchLoop( } logger.Errorw("error fetching messages", zap.Error(err)) // Back off on errors - time.Sleep(time.Second) + time.Sleep(200 * time.Millisecond) continue } From f32947643e3e360ff6ea459202315751a5aa785c Mon Sep 17 00:00:00 2001 From: Andrii Stelmashenko Date: Thu, 29 Jan 2026 16:31:13 +0200 Subject: [PATCH 08/10] changed license note year --- pkg/broker/filter/consumer.go | 2 +- pkg/broker/filter/consumer_test.go | 2 +- pkg/broker/filter/controller.go | 2 +- pkg/broker/filter/handler.go | 2 +- pkg/broker/filter/handler_test.go | 2 +- pkg/broker/filter/reconciler.go | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/broker/filter/consumer.go b/pkg/broker/filter/consumer.go index a0deffbe9..c2983eaa3 100644 --- a/pkg/broker/filter/consumer.go +++ b/pkg/broker/filter/consumer.go @@ -1,5 +1,5 @@ /* -Copyright 2024 The Knative Authors +Copyright 2026 The Knative Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/broker/filter/consumer_test.go b/pkg/broker/filter/consumer_test.go index 866a8c5f0..72c458647 100644 --- a/pkg/broker/filter/consumer_test.go +++ b/pkg/broker/filter/consumer_test.go @@ -1,5 +1,5 @@ /* -Copyright 2024 The Knative Authors +Copyright 2026 The Knative Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/broker/filter/controller.go b/pkg/broker/filter/controller.go index 67cd57f34..c26644193 100644 --- a/pkg/broker/filter/controller.go +++ b/pkg/broker/filter/controller.go @@ -1,5 +1,5 @@ /* -Copyright 2024 The Knative Authors +Copyright 2026 The Knative Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/broker/filter/handler.go b/pkg/broker/filter/handler.go index a4ae4aff1..72cd2320d 100644 --- a/pkg/broker/filter/handler.go +++ b/pkg/broker/filter/handler.go @@ -1,5 +1,5 @@ /* -Copyright 2024 The Knative Authors +Copyright 2026 The Knative Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/broker/filter/handler_test.go b/pkg/broker/filter/handler_test.go index 33ac7fceb..66f8733e3 100644 --- a/pkg/broker/filter/handler_test.go +++ b/pkg/broker/filter/handler_test.go @@ -1,5 +1,5 @@ /* -Copyright 2024 The Knative Authors +Copyright 2026 The Knative Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/broker/filter/reconciler.go b/pkg/broker/filter/reconciler.go index cb6f98d21..90354ef84 100644 --- a/pkg/broker/filter/reconciler.go +++ b/pkg/broker/filter/reconciler.go @@ -1,5 +1,5 @@ /* -Copyright 2024 The Knative Authors +Copyright 2026 The Knative Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. From 514f1d5c5cb87a51a849b8a2c249b3c5c5eea262 Mon Sep 17 00:00:00 2001 From: Andrii Stelmashenko Date: Thu, 29 Jan 2026 21:08:48 +0200 Subject: [PATCH 09/10] added unit tests --- pkg/broker/filter/consumer_test.go | 50 ++++ pkg/broker/filter/controller_test.go | 88 +++++++ pkg/broker/filter/reconciler_test.go | 339 +++++++++++++++++++++++++++ 3 files changed, 477 insertions(+) create mode 100644 pkg/broker/filter/controller_test.go create mode 100644 pkg/broker/filter/reconciler_test.go diff --git a/pkg/broker/filter/consumer_test.go b/pkg/broker/filter/consumer_test.go index 72c458647..5408a49ec 100644 --- a/pkg/broker/filter/consumer_test.go +++ b/pkg/broker/filter/consumer_test.go @@ -17,8 +17,12 @@ limitations under the License. package filter import ( + "context" + "fmt" "testing" "time" + + "knative.dev/pkg/logging" ) func TestConsumerManagerConfigDefaults(t *testing.T) { @@ -115,3 +119,49 @@ func TestConsumerManagerConfig(t *testing.T) { }) } } + +func TestGetSubscriptionCount(t *testing.T) { + ctx := logging.WithLogger(context.Background(), logging.FromContext(context.TODO())) + + tests := []struct { + name string + count int + }{ + {"empty map", 0}, + {"one entry", 1}, + {"three entries", 3}, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + cm := &ConsumerManager{ + logger: logging.FromContext(ctx), + subscriptions: make(map[string]*TriggerSubscription), + } + for i := 0; i < tc.count; i++ { + uid := fmt.Sprintf("uid-%d", i) + cm.subscriptions[uid] = &TriggerSubscription{} + } + if got := cm.GetSubscriptionCount(); got != tc.count { + t.Errorf("GetSubscriptionCount() = %d, want %d", got, tc.count) + } + }) + } +} + +func TestHasSubscription(t *testing.T) { + ctx := logging.WithLogger(context.Background(), logging.FromContext(context.TODO())) + + cm := &ConsumerManager{ + logger: logging.FromContext(ctx), + subscriptions: make(map[string]*TriggerSubscription), + } + cm.subscriptions["existing-uid"] = &TriggerSubscription{} + + if !cm.HasSubscription("existing-uid") { + t.Error("HasSubscription() = false for existing UID, want true") + } + if cm.HasSubscription("missing-uid") { + t.Error("HasSubscription() = true for missing UID, want false") + } +} diff --git a/pkg/broker/filter/controller_test.go b/pkg/broker/filter/controller_test.go new file mode 100644 index 000000000..18e114a49 --- /dev/null +++ b/pkg/broker/filter/controller_test.go @@ -0,0 +1,88 @@ +/* +Copyright 2026 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package filter + +import ( + "testing" + + eventingv1 "knative.dev/eventing/pkg/apis/eventing/v1" + + "knative.dev/eventing-natss/pkg/broker/constants" +) + +func TestFilterTriggersByBrokerClass(t *testing.T) { + tests := []struct { + name string + broker *eventingv1.Broker + trigger *eventingv1.Trigger + wantFiltered bool + }{ + { + name: "trigger referencing NatsJetStreamBroker class broker", + broker: newTestBroker(testNamespace, testBrokerName, constants.BrokerClassName), + trigger: newTestTrigger(testNamespace, testTriggerName, testBrokerName), + wantFiltered: true, + }, + { + name: "trigger referencing different broker class", + broker: newTestBroker(testNamespace, testBrokerName, "OtherBrokerClass"), + trigger: newTestTrigger(testNamespace, testTriggerName, testBrokerName), + wantFiltered: false, + }, + { + name: "trigger referencing broker without class annotation", + broker: newTestBroker(testNamespace, testBrokerName, ""), + trigger: newTestTrigger(testNamespace, testTriggerName, testBrokerName), + wantFiltered: false, + }, + { + name: "trigger referencing non-existent broker passes to reconciler", + broker: nil, + trigger: newTestTrigger(testNamespace, testTriggerName, "non-existent-broker"), + wantFiltered: true, + }, + { + name: "non-trigger object", + broker: newTestBroker(testNamespace, testBrokerName, constants.BrokerClassName), + trigger: nil, + wantFiltered: false, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + brokerLister := newFakeBrokerLister() + if tc.broker != nil { + brokerLister.addBroker(tc.broker) + } + + filterFunc := filterTriggersByBrokerClass(brokerLister) + + var obj interface{} + if tc.trigger != nil { + obj = tc.trigger + } else { + obj = "not a trigger" + } + + got := filterFunc(obj) + if got != tc.wantFiltered { + t.Errorf("filterTriggersByBrokerClass() = %v, want %v", got, tc.wantFiltered) + } + }) + } +} diff --git a/pkg/broker/filter/reconciler_test.go b/pkg/broker/filter/reconciler_test.go new file mode 100644 index 000000000..8d929a8a5 --- /dev/null +++ b/pkg/broker/filter/reconciler_test.go @@ -0,0 +1,339 @@ +/* +Copyright 2026 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package filter + +import ( + "context" + "fmt" + "testing" + + corev1 "k8s.io/api/core/v1" + apierrs "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + + "knative.dev/pkg/apis" + "knative.dev/pkg/logging" + + eventingv1 "knative.dev/eventing/pkg/apis/eventing/v1" + eventinglisters "knative.dev/eventing/pkg/client/listers/eventing/v1" + + "knative.dev/eventing-natss/pkg/broker/constants" +) + +const ( + testNamespace = "test-namespace" + testBrokerName = "test-broker" + testTriggerName = "test-trigger" + testTriggerUID = "test-trigger-uid-12345" +) + +// newTestBroker creates a test broker with the given class annotation. +func newTestBroker(namespace, name, brokerClass string) *eventingv1.Broker { + annotations := make(map[string]string) + if brokerClass != "" { + annotations[eventingv1.BrokerClassAnnotationKey] = brokerClass + } + return &eventingv1.Broker{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: name, + Annotations: annotations, + }, + } +} + +// newReadyTestBroker creates a test broker that reports as ready. +func newReadyTestBroker(namespace, name, brokerClass string) *eventingv1.Broker { + b := newTestBroker(namespace, name, brokerClass) + // IsReady checks ObservedGeneration == Generation and all conditions happy. + b.Generation = 1 + b.Status.InitializeConditions() + b.Status.ObservedGeneration = 1 + // Mark all dependent conditions true so the top-level Ready becomes true. + manager := b.GetConditionSet().Manage(&b.Status) + manager.MarkTrue(eventingv1.BrokerConditionIngress) + manager.MarkTrue(eventingv1.BrokerConditionTriggerChannel) + manager.MarkTrue(eventingv1.BrokerConditionFilter) + manager.MarkTrue(eventingv1.BrokerConditionAddressable) + manager.MarkTrue(eventingv1.BrokerConditionDeadLetterSinkResolved) + manager.MarkTrue(eventingv1.BrokerConditionEventPoliciesReady) + return b +} + +// newTestTrigger creates a test trigger referencing a broker. +func newTestTrigger(namespace, name, brokerName string) *eventingv1.Trigger { + return &eventingv1.Trigger{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: name, + UID: types.UID(testTriggerUID), + }, + Spec: eventingv1.TriggerSpec{ + Broker: brokerName, + }, + } +} + +// newTestTriggerWithSubscriber creates a trigger with a resolved subscriber URI. +func newTestTriggerWithSubscriber(namespace, name, brokerName, subscriberURL string) *eventingv1.Trigger { + t := newTestTrigger(namespace, name, brokerName) + u, _ := apis.ParseURL(subscriberURL) + t.Status.SubscriberURI = u + return t +} + +// --- Fake listers --- + +type fakeBrokerLister struct { + brokers map[string]map[string]*eventingv1.Broker +} + +func newFakeBrokerLister() *fakeBrokerLister { + return &fakeBrokerLister{ + brokers: make(map[string]map[string]*eventingv1.Broker), + } +} + +func (f *fakeBrokerLister) addBroker(broker *eventingv1.Broker) { + if f.brokers[broker.Namespace] == nil { + f.brokers[broker.Namespace] = make(map[string]*eventingv1.Broker) + } + f.brokers[broker.Namespace][broker.Name] = broker +} + +func (f *fakeBrokerLister) List(selector labels.Selector) ([]*eventingv1.Broker, error) { + var result []*eventingv1.Broker + for _, ns := range f.brokers { + for _, broker := range ns { + result = append(result, broker) + } + } + return result, nil +} + +func (f *fakeBrokerLister) Brokers(namespace string) eventinglisters.BrokerNamespaceLister { + return &fakeBrokerNamespaceLister{ + namespace: namespace, + brokers: f.brokers[namespace], + } +} + +type fakeBrokerNamespaceLister struct { + namespace string + brokers map[string]*eventingv1.Broker +} + +func (f *fakeBrokerNamespaceLister) List(selector labels.Selector) ([]*eventingv1.Broker, error) { + result := make([]*eventingv1.Broker, 0, len(f.brokers)) + for _, broker := range f.brokers { + result = append(result, broker) + } + return result, nil +} + +func (f *fakeBrokerNamespaceLister) Get(name string) (*eventingv1.Broker, error) { + if broker, ok := f.brokers[name]; ok { + return broker, nil + } + return nil, apierrs.NewNotFound(schema.GroupResource{Group: "eventing.knative.dev", Resource: "brokers"}, name) +} + +func TestReconcileTrigger(t *testing.T) { + tests := []struct { + name string + broker *eventingv1.Broker + trigger *eventingv1.Trigger + wantErr bool + }{ + { + name: "broker not found returns nil", + broker: nil, + trigger: newTestTrigger(testNamespace, testTriggerName, testBrokerName), + wantErr: false, + }, + { + name: "broker wrong class returns nil", + broker: newTestBroker(testNamespace, testBrokerName, "OtherBrokerClass"), + trigger: newTestTrigger(testNamespace, testTriggerName, testBrokerName), + wantErr: false, + }, + { + name: "broker not ready returns nil", + broker: newTestBroker(testNamespace, testBrokerName, constants.BrokerClassName), + trigger: newTestTrigger(testNamespace, testTriggerName, testBrokerName), + wantErr: false, + }, + { + name: "subscriber URI not resolved returns nil", + broker: newReadyTestBroker(testNamespace, testBrokerName, constants.BrokerClassName), + trigger: newTestTrigger(testNamespace, testTriggerName, testBrokerName), + wantErr: false, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + brokerLister := newFakeBrokerLister() + if tc.broker != nil { + brokerLister.addBroker(tc.broker) + } + + ctx := logging.WithLogger(context.Background(), logging.FromContext(context.TODO())) + + r := &FilterReconciler{ + logger: logging.FromContext(ctx), + brokerLister: brokerLister, + } + + err := r.ReconcileTrigger(ctx, tc.trigger) + if tc.wantErr && err == nil { + t.Error("ReconcileTrigger() expected error, got nil") + } + if !tc.wantErr && err != nil { + t.Errorf("ReconcileTrigger() unexpected error: %v", err) + } + }) + } +} + +func TestReconcileTrigger_BrokerLookupError(t *testing.T) { + // Use a broker lister that returns an error other than NotFound. + brokerLister := &errorBrokerLister{ + err: fmt.Errorf("internal error"), + } + + ctx := logging.WithLogger(context.Background(), logging.FromContext(context.TODO())) + + r := &FilterReconciler{ + logger: logging.FromContext(ctx), + brokerLister: brokerLister, + } + + trigger := newTestTrigger(testNamespace, testTriggerName, testBrokerName) + err := r.ReconcileTrigger(ctx, trigger) + if err == nil { + t.Error("ReconcileTrigger() expected error for non-NotFound broker lookup error, got nil") + } +} + +// errorBrokerLister returns an arbitrary error for any lookup. +type errorBrokerLister struct { + err error +} + +func (e *errorBrokerLister) List(selector labels.Selector) ([]*eventingv1.Broker, error) { + return nil, e.err +} + +func (e *errorBrokerLister) Brokers(namespace string) eventinglisters.BrokerNamespaceLister { + return &errorBrokerNamespaceLister{err: e.err} +} + +type errorBrokerNamespaceLister struct { + err error +} + +func (e *errorBrokerNamespaceLister) List(selector labels.Selector) ([]*eventingv1.Broker, error) { + return nil, e.err +} + +func (e *errorBrokerNamespaceLister) Get(name string) (*eventingv1.Broker, error) { + return nil, e.err +} + +func TestDeleteTrigger(t *testing.T) { + ctx := logging.WithLogger(context.Background(), logging.FromContext(context.TODO())) + + cm := &ConsumerManager{ + logger: logging.FromContext(ctx), + subscriptions: make(map[string]*TriggerSubscription), + } + + r := &FilterReconciler{ + logger: logging.FromContext(ctx), + consumerManager: cm, + } + + // Deleting a non-existent trigger should return nil. + if err := r.DeleteTrigger("non-existent-uid"); err != nil { + t.Errorf("DeleteTrigger() unexpected error for non-existent trigger: %v", err) + } +} + +// Verify that IsReady helper produces truly ready brokers for use in other +// test cases. This guards against upstream condition-set changes breaking +// the helper silently. +func TestNewReadyTestBroker(t *testing.T) { + b := newReadyTestBroker(testNamespace, testBrokerName, constants.BrokerClassName) + if !b.IsReady() { + cond := b.Status.GetTopLevelCondition() + t.Errorf("newReadyTestBroker() produced broker that is not ready; top-level condition: %+v", cond) + for _, c := range b.Status.Conditions { + if c.Status != corev1.ConditionTrue { + t.Errorf(" condition %s = %s (%s)", c.Type, c.Status, c.Reason) + } + } + } +} + +// Verify subscriber URI detection works on both nil and non-nil cases. +func TestNewTestTriggerWithSubscriber(t *testing.T) { + t.Run("without subscriber", func(t *testing.T) { + trigger := newTestTrigger(testNamespace, testTriggerName, testBrokerName) + if trigger.Status.SubscriberURI != nil { + t.Errorf("expected nil SubscriberURI, got %v", trigger.Status.SubscriberURI) + } + }) + + t.Run("with subscriber", func(t *testing.T) { + trigger := newTestTriggerWithSubscriber(testNamespace, testTriggerName, testBrokerName, "http://example.com") + if trigger.Status.SubscriberURI == nil { + t.Error("expected non-nil SubscriberURI") + } + }) +} + +// TestReconcileTrigger_SkipReasons verifies that each early-return guard in +// ReconcileTrigger is independently effective. We run through a progression +// where exactly one guard is removed at each step to confirm each check. +func TestReconcileTrigger_SkipReasons(t *testing.T) { + ctx := logging.WithLogger(context.Background(), logging.FromContext(context.TODO())) + + // Ready broker + trigger with subscriber should NOT be skipped. + // We cannot proceed without a real JetStream, but we can confirm the + // early returns work by verifying the inverse: a ready broker with + // wrong class is still skipped. + broker := newReadyTestBroker(testNamespace, testBrokerName, "WrongClass") + trigger := newTestTriggerWithSubscriber(testNamespace, testTriggerName, testBrokerName, "http://subscriber.example.com") + + brokerLister := newFakeBrokerLister() + brokerLister.addBroker(broker) + + r := &FilterReconciler{ + logger: logging.FromContext(ctx), + brokerLister: brokerLister, + } + + // Even though trigger has a subscriber and broker is ready, the wrong + // class causes a skip (nil return). + if err := r.ReconcileTrigger(ctx, trigger); err != nil { + t.Errorf("ReconcileTrigger() with wrong broker class should skip, got error: %v", err) + } +} From d7c569657a16036d708a33f10b085853a0585643 Mon Sep 17 00:00:00 2001 From: Andrii Stelmashenko Date: Fri, 30 Jan 2026 08:57:10 +0200 Subject: [PATCH 10/10] added more tests --- pkg/broker/filter/consumer_test.go | 28 ++++ pkg/broker/filter/controller_test.go | 9 ++ pkg/broker/filter/handler_test.go | 219 +++++++++++++++++++++++++++ pkg/broker/filter/reconciler_test.go | 170 +++++++++++++++++++++ 4 files changed, 426 insertions(+) diff --git a/pkg/broker/filter/consumer_test.go b/pkg/broker/filter/consumer_test.go index 5408a49ec..c9a235103 100644 --- a/pkg/broker/filter/consumer_test.go +++ b/pkg/broker/filter/consumer_test.go @@ -165,3 +165,31 @@ func TestHasSubscription(t *testing.T) { t.Error("HasSubscription() = true for missing UID, want false") } } + +func TestConsumerManagerClose(t *testing.T) { + ctx := logging.WithLogger(context.Background(), logging.FromContext(context.TODO())) + + cm := &ConsumerManager{ + logger: logging.FromContext(ctx), + subscriptions: make(map[string]*TriggerSubscription), + } + + err := cm.Close() + if err != nil { + t.Errorf("Close() unexpected error on empty subscriptions: %v", err) + } +} + +func TestUnsubscribeTrigger_NotFound(t *testing.T) { + ctx := logging.WithLogger(context.Background(), logging.FromContext(context.TODO())) + + cm := &ConsumerManager{ + logger: logging.FromContext(ctx), + subscriptions: make(map[string]*TriggerSubscription), + } + + err := cm.UnsubscribeTrigger("non-existent-uid") + if err != nil { + t.Errorf("UnsubscribeTrigger() unexpected error for non-existent UID: %v", err) + } +} diff --git a/pkg/broker/filter/controller_test.go b/pkg/broker/filter/controller_test.go index 18e114a49..2865f6c46 100644 --- a/pkg/broker/filter/controller_test.go +++ b/pkg/broker/filter/controller_test.go @@ -17,6 +17,7 @@ limitations under the License. package filter import ( + "context" "testing" eventingv1 "knative.dev/eventing/pkg/apis/eventing/v1" @@ -86,3 +87,11 @@ func TestFilterTriggersByBrokerClass(t *testing.T) { }) } } + +func TestNoopReconciler(t *testing.T) { + r := &noopReconciler{} + err := r.Reconcile(context.Background(), "test-namespace/test-name") + if err != nil { + t.Errorf("noopReconciler.Reconcile() unexpected error: %v", err) + } +} diff --git a/pkg/broker/filter/handler_test.go b/pkg/broker/filter/handler_test.go index 66f8733e3..641f59673 100644 --- a/pkg/broker/filter/handler_test.go +++ b/pkg/broker/filter/handler_test.go @@ -23,11 +23,18 @@ import ( "testing" cloudevents "github.com/cloudevents/sdk-go/v2" + "github.com/cloudevents/sdk-go/v2/binding" "github.com/cloudevents/sdk-go/v2/protocol" "go.uber.org/zap" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" + "knative.dev/pkg/logging" + eventingv1 "knative.dev/eventing/pkg/apis/eventing/v1" "knative.dev/eventing/pkg/eventfilter" + "knative.dev/eventing/pkg/kncloudevents" ) func TestDetermineNatsResult(t *testing.T) { @@ -435,3 +442,215 @@ func TestBuildTriggerFilter(t *testing.T) { }) } } + +func TestBuildTriggerFilter_NilAttributes(t *testing.T) { + logger := zap.NewNop().Sugar() + + // Filter set but Attributes nil should fall through to default (nil filter). + trigger := &eventingv1.Trigger{ + ObjectMeta: metav1.ObjectMeta{Name: "t", Namespace: "ns"}, + Spec: eventingv1.TriggerSpec{ + Broker: "b", + Filter: &eventingv1.TriggerFilter{ + Attributes: nil, + }, + }, + } + f := buildTriggerFilter(logger, trigger) + if f != nil { + t.Error("expected nil filter when Filter is set but Attributes is nil") + } +} + +func TestBuildTriggerFilter_MultipleAttributes(t *testing.T) { + logger := zap.NewNop().Sugar() + + trigger := &eventingv1.Trigger{ + ObjectMeta: metav1.ObjectMeta{Name: "t", Namespace: "ns"}, + Spec: eventingv1.TriggerSpec{ + Broker: "b", + Filter: &eventingv1.TriggerFilter{ + Attributes: map[string]string{ + "type": "order.created", + "source": "shop", + }, + }, + }, + } + f := buildTriggerFilter(logger, trigger) + if f == nil { + t.Fatal("expected non-nil filter") + } + + t.Run("all attributes match", func(t *testing.T) { + e := cloudevents.NewEvent() + e.SetType("order.created") + e.SetSource("shop") + if f.Filter(context.Background(), e) == eventfilter.FailFilter { + t.Error("expected event to pass filter") + } + }) + + t.Run("partial match fails", func(t *testing.T) { + e := cloudevents.NewEvent() + e.SetType("order.created") + e.SetSource("other-shop") + if f.Filter(context.Background(), e) != eventfilter.FailFilter { + t.Error("expected event to fail filter") + } + }) +} + +func TestTypeExtractorTransformerTransform(t *testing.T) { + tests := []struct { + name string + eventType string + wantType string + }{ + { + name: "extracts type from event", + eventType: "com.example.event.v1", + wantType: "com.example.event.v1", + }, + { + name: "extracts empty type", + eventType: "", + wantType: "", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + e := cloudevents.NewEvent() + e.SetType(tc.eventType) + e.SetSource("test-source") + + em := binding.EventMessage(e) + + te := TypeExtractorTransformer("") + err := te.Transform(&em, nil) + if err != nil { + t.Fatalf("Transform() unexpected error: %v", err) + } + if string(te) != tc.wantType { + t.Errorf("TypeExtractorTransformer = %q, want %q", string(te), tc.wantType) + } + }) + } +} + +func TestNewTriggerHandler(t *testing.T) { + ctx := logging.WithLogger(context.Background(), zap.NewNop().Sugar()) + + trigger := &eventingv1.Trigger{ + ObjectMeta: metav1.ObjectMeta{Name: "test-trigger", Namespace: "test-ns"}, + Spec: eventingv1.TriggerSpec{Broker: "test-broker"}, + } + + subscriberURL, _ := apis.ParseURL("http://subscriber.example.com") + subscriber := duckv1.Addressable{URL: subscriberURL} + + handler, err := NewTriggerHandler(ctx, trigger, subscriber, nil, nil, nil, nil, nil) + if err != nil { + t.Fatalf("NewTriggerHandler() unexpected error: %v", err) + } + if handler == nil { + t.Fatal("NewTriggerHandler() returned nil handler") + } + if handler.trigger != trigger { + t.Error("handler.trigger not set correctly") + } + if handler.subscriber.URL.String() != subscriber.URL.String() { + t.Errorf("handler.subscriber URL = %v, want %v", handler.subscriber.URL, subscriber.URL) + } + if handler.filter != nil { + t.Error("handler.filter should be nil for trigger without filter") + } + if handler.brokerIngressURL != nil { + t.Error("handler.brokerIngressURL should be nil when not provided") + } + if handler.deadLetterSink != nil { + t.Error("handler.deadLetterSink should be nil when not provided") + } + if handler.retryConfig != nil { + t.Error("handler.retryConfig should be nil when not provided") + } +} + +func TestNewTriggerHandler_WithOptionalParams(t *testing.T) { + ctx := logging.WithLogger(context.Background(), zap.NewNop().Sugar()) + + trigger := &eventingv1.Trigger{ + ObjectMeta: metav1.ObjectMeta{Name: "test-trigger", Namespace: "test-ns"}, + Spec: eventingv1.TriggerSpec{ + Broker: "test-broker", + Filter: &eventingv1.TriggerFilter{ + Attributes: map[string]string{"type": "test.type"}, + }, + }, + } + + subscriberURL, _ := apis.ParseURL("http://subscriber.example.com") + subscriber := duckv1.Addressable{URL: subscriberURL} + + ingressURL, _ := apis.ParseURL("http://broker-ingress.example.com") + brokerIngress := &duckv1.Addressable{URL: ingressURL} + + dlsURL, _ := apis.ParseURL("http://dead-letter.example.com") + dls := &duckv1.Addressable{URL: dlsURL} + + retryConfig := &kncloudevents.RetryConfig{RetryMax: 3} + noRetryConfig := &kncloudevents.RetryConfig{RetryMax: 0} + + handler, err := NewTriggerHandler(ctx, trigger, subscriber, brokerIngress, dls, retryConfig, noRetryConfig, nil) + if err != nil { + t.Fatalf("NewTriggerHandler() unexpected error: %v", err) + } + if handler.filter == nil { + t.Error("handler.filter should not be nil for trigger with filter") + } + if handler.brokerIngressURL != brokerIngress { + t.Error("handler.brokerIngressURL not set correctly") + } + if handler.deadLetterSink != dls { + t.Error("handler.deadLetterSink not set correctly") + } + if handler.retryConfig != retryConfig { + t.Error("handler.retryConfig not set correctly") + } + if handler.noRetryConfig != noRetryConfig { + t.Error("handler.noRetryConfig not set correctly") + } +} + +func TestTriggerHandlerCleanup(t *testing.T) { + t.Run("nil filter", func(t *testing.T) { + h := &TriggerHandler{} + h.Cleanup() // should not panic + }) + + t.Run("with filter", func(t *testing.T) { + logger := zap.NewNop().Sugar() + trigger := &eventingv1.Trigger{ + ObjectMeta: metav1.ObjectMeta{Name: "t", Namespace: "ns"}, + Spec: eventingv1.TriggerSpec{ + Broker: "b", + Filter: &eventingv1.TriggerFilter{ + Attributes: map[string]string{"type": "test"}, + }, + }, + } + f := buildTriggerFilter(logger, trigger) + h := &TriggerHandler{filter: f} + h.Cleanup() // should call f.Cleanup() without error + }) +} + +func TestDefaultRetryConfigInitialized(t *testing.T) { + if defaultRetry.RetryMax != int(retryMax) { + t.Errorf("defaultRetry.RetryMax = %v, want %v", defaultRetry.RetryMax, retryMax) + } + if defaultRetry.Backoff == nil { + t.Error("defaultRetry.Backoff should not be nil") + } +} diff --git a/pkg/broker/filter/reconciler_test.go b/pkg/broker/filter/reconciler_test.go index 8d929a8a5..ce36ecd1a 100644 --- a/pkg/broker/filter/reconciler_test.go +++ b/pkg/broker/filter/reconciler_test.go @@ -19,8 +19,10 @@ package filter import ( "context" "fmt" + "strings" "testing" + "github.com/nats-io/nats.go" corev1 "k8s.io/api/core/v1" apierrs "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -29,8 +31,10 @@ import ( "k8s.io/apimachinery/pkg/types" "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" "knative.dev/pkg/logging" + eventingduckv1 "knative.dev/eventing/pkg/apis/duck/v1" eventingv1 "knative.dev/eventing/pkg/apis/eventing/v1" eventinglisters "knative.dev/eventing/pkg/client/listers/eventing/v1" @@ -337,3 +341,169 @@ func TestReconcileTrigger_SkipReasons(t *testing.T) { t.Errorf("ReconcileTrigger() with wrong broker class should skip, got error: %v", err) } } + +// --- Fake JetStream --- + +// fakeJetStream embeds the JetStreamContext interface and overrides only +// ConsumerInfo. Any other method will panic if called (embedded nil value), +// which is acceptable since our tests never reach those code paths. +type fakeJetStream struct { + nats.JetStreamContext + consumerInfoErr error +} + +func (f *fakeJetStream) ConsumerInfo(stream, name string, opts ...nats.JSOpt) (*nats.ConsumerInfo, error) { + return nil, f.consumerInfoErr +} + +func int32Ptr(v int32) *int32 { return &v } + +func TestNewFilterReconciler(t *testing.T) { + ctx := logging.WithLogger(context.Background(), logging.FromContext(context.TODO())) + + r := NewFilterReconciler(ctx, nil, nil, nil) + if r == nil { + t.Fatal("NewFilterReconciler() returned nil") + } + if r.logger == nil { + t.Error("reconciler.logger should not be nil") + } +} + +func TestReconcileTrigger_FullPath(t *testing.T) { + tests := []struct { + name string + brokerAddr bool + deadLetterURI string + deliveryRetry *int32 + jsErr error + wantErrContains string + }{ + { + name: "basic path through to SubscribeTrigger", + jsErr: fmt.Errorf("connection refused"), + wantErrContains: "failed to subscribe to trigger", + }, + { + name: "with broker ingress address", + brokerAddr: true, + jsErr: fmt.Errorf("connection refused"), + wantErrContains: "failed to subscribe to trigger", + }, + { + name: "with dead letter sink", + deadLetterURI: "http://dead-letter.example.com", + jsErr: fmt.Errorf("connection refused"), + wantErrContains: "failed to subscribe to trigger", + }, + { + name: "with delivery spec", + deliveryRetry: int32Ptr(3), + jsErr: fmt.Errorf("connection refused"), + wantErrContains: "failed to subscribe to trigger", + }, + { + name: "all optional fields set", + brokerAddr: true, + deadLetterURI: "http://dead-letter.example.com", + deliveryRetry: int32Ptr(5), + jsErr: fmt.Errorf("connection refused"), + wantErrContains: "failed to subscribe to trigger", + }, + { + name: "consumer not found error", + jsErr: nats.ErrConsumerNotFound, + wantErrContains: "not found", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + ctx := logging.WithLogger(context.Background(), logging.FromContext(context.TODO())) + + broker := newReadyTestBroker(testNamespace, testBrokerName, constants.BrokerClassName) + if tc.brokerAddr { + broker.Status.SetAddress(&duckv1.Addressable{ + URL: apis.HTTP("broker-ingress.example.com"), + }) + } + + trigger := newTestTriggerWithSubscriber(testNamespace, testTriggerName, testBrokerName, "http://subscriber.example.com") + if tc.deadLetterURI != "" { + u, _ := apis.ParseURL(tc.deadLetterURI) + trigger.Status.DeadLetterSinkURI = u + } + if tc.deliveryRetry != nil { + trigger.Spec.Delivery = &eventingduckv1.DeliverySpec{ + Retry: tc.deliveryRetry, + } + } + + brokerLister := newFakeBrokerLister() + brokerLister.addBroker(broker) + + cm := &ConsumerManager{ + logger: logging.FromContext(ctx), + ctx: ctx, + js: &fakeJetStream{consumerInfoErr: tc.jsErr}, + subscriptions: make(map[string]*TriggerSubscription), + } + + r := &FilterReconciler{ + logger: logging.FromContext(ctx), + brokerLister: brokerLister, + consumerManager: cm, + } + + err := r.ReconcileTrigger(ctx, trigger) + if err == nil { + t.Fatal("ReconcileTrigger() expected error, got nil") + } + if !strings.Contains(err.Error(), tc.wantErrContains) { + t.Errorf("error %q should contain %q", err.Error(), tc.wantErrContains) + } + }) + } +} + +func TestReconcileTrigger_ExistingSubscription(t *testing.T) { + ctx := logging.WithLogger(context.Background(), logging.FromContext(context.TODO())) + + broker := newReadyTestBroker(testNamespace, testBrokerName, constants.BrokerClassName) + subscriberURL := "http://subscriber.example.com" + trigger := newTestTriggerWithSubscriber(testNamespace, testTriggerName, testBrokerName, subscriberURL) + triggerUID := string(trigger.UID) + + brokerLister := newFakeBrokerLister() + brokerLister.addBroker(broker) + + // Pre-populate subscription with matching subscriber URL. + parsedURL, _ := apis.ParseURL(subscriberURL) + existingHandler := &TriggerHandler{ + subscriber: duckv1.Addressable{URL: parsedURL}, + } + + cm := &ConsumerManager{ + logger: logging.FromContext(ctx), + ctx: ctx, + js: &fakeJetStream{consumerInfoErr: fmt.Errorf("should not be called")}, + subscriptions: map[string]*TriggerSubscription{ + triggerUID: { + trigger: trigger, + handler: existingHandler, + }, + }, + } + + r := &FilterReconciler{ + logger: logging.FromContext(ctx), + brokerLister: brokerLister, + consumerManager: cm, + } + + // Should return nil because existing subscription has same URL. + err := r.ReconcileTrigger(ctx, trigger) + if err != nil { + t.Errorf("ReconcileTrigger() unexpected error for existing subscription with same URL: %v", err) + } +}