pkg/event/manager.go (256 lines of code) (raw):

/* Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to You 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 event import ( "context" "fmt" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/tools/record" ctrl "sigs.k8s.io/controller-runtime/pkg/client" v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1" "github.com/apache/camel-k/v2/pkg/client" "github.com/apache/camel-k/v2/pkg/util/kubernetes" "github.com/apache/camel-k/v2/pkg/util/log" ) const ( // ReasonIntegrationPhaseUpdated --. ReasonIntegrationPhaseUpdated = "IntegrationPhaseUpdated" // ReasonIntegrationConditionChanged --. ReasonIntegrationConditionChanged = "IntegrationConditionChanged" // ReasonIntegrationError --. ReasonIntegrationError = "IntegrationError" // ReasonIntegrationKitPhaseUpdated --. ReasonIntegrationKitPhaseUpdated = "IntegrationKitPhaseUpdated" // ReasonIntegrationKitConditionChanged --. ReasonIntegrationKitConditionChanged = "IntegrationKitConditionChanged" // ReasonIntegrationKitError --. ReasonIntegrationKitError = "IntegrationKitError" // ReasonIntegrationPlatformPhaseUpdated --. ReasonIntegrationPlatformPhaseUpdated = "IntegrationPlatformPhaseUpdated" // ReasonIntegrationPlatformConditionChanged --. ReasonIntegrationPlatformConditionChanged = "IntegrationPlatformConditionChanged" // ReasonIntegrationPlatformError --. ReasonIntegrationPlatformError = "IntegrationPlatformError" // ReasonIntegrationProfilePhaseUpdated --. ReasonIntegrationProfilePhaseUpdated = "IntegrationProfilePhaseUpdated" // ReasonIntegrationProfileConditionChanged --. ReasonIntegrationProfileConditionChanged = "IntegrationProfileConditionChanged" // ReasonIntegrationProfileError --. ReasonIntegrationProfileError = "IntegrationProfileError" // ReasonBuildPhaseUpdated --. ReasonBuildPhaseUpdated = "BuildPhaseUpdated" // ReasonBuildConditionChanged --. ReasonBuildConditionChanged = "BuildConditionChanged" // ReasonBuildError --. ReasonBuildError = "BuildError" // ReasonKameletError --. ReasonKameletError = "KameletError" // ReasonKameletConditionChanged --. ReasonKameletConditionChanged = "KameletConditionChanged" // ReasonKameletPhaseUpdated --. ReasonKameletPhaseUpdated = "KameletPhaseUpdated" // ReasonPipeError --. ReasonPipeError = "PipeError" // ReasonPipeConditionChanged --. ReasonPipeConditionChanged = "PipeConditionChanged" // ReasonPipePhaseUpdated --. ReasonPipePhaseUpdated = "PipePhaseUpdated" // ReasonRelatedObjectChanged --. ReasonRelatedObjectChanged = "ReasonRelatedObjectChanged" ) // NotifyIntegrationError automatically generates error events when the integration reconcile cycle phase has an error. func NotifyIntegrationError(ctx context.Context, c client.Client, recorder record.EventRecorder, old, newResource *v1.Integration, err error) { it := old if newResource != nil { it = newResource } if it == nil { return } recorder.Eventf(it, corev1.EventTypeWarning, ReasonIntegrationError, "Cannot reconcile Integration %s: %v", it.Name, err) } // NotifyIntegrationUpdated automatically generates events when the integration changes. func NotifyIntegrationUpdated(ctx context.Context, c client.Client, recorder record.EventRecorder, old, newResource *v1.Integration) { if newResource == nil { return } oldPhase := "" var oldConditions []v1.ResourceCondition if old != nil { oldPhase = string(old.Status.Phase) oldConditions = old.Status.GetConditions() } if newResource.Status.Phase != v1.IntegrationPhaseNone { notifyIfConditionUpdated(recorder, newResource, oldConditions, newResource.Status.GetConditions(), "Integration", newResource.Name, ReasonIntegrationConditionChanged) } notifyIfPhaseUpdated(ctx, c, recorder, newResource, oldPhase, string(newResource.Status.Phase), "Integration", newResource.Name, ReasonIntegrationPhaseUpdated, "") } // NotifyIntegrationKitUpdated automatically generates events when an integration kit changes. func NotifyIntegrationKitUpdated(ctx context.Context, c client.Client, recorder record.EventRecorder, old, newResource *v1.IntegrationKit) { if newResource == nil { return } oldPhase := "" var oldConditions []v1.ResourceCondition if old != nil { oldPhase = string(old.Status.Phase) oldConditions = old.Status.GetConditions() } if newResource.Status.Phase != v1.IntegrationKitPhaseNone { notifyIfConditionUpdated(recorder, newResource, oldConditions, newResource.Status.GetConditions(), "Integration Kit", newResource.Name, ReasonIntegrationKitConditionChanged) } notifyIfPhaseUpdated(ctx, c, recorder, newResource, oldPhase, string(newResource.Status.Phase), "Integration Kit", newResource.Name, ReasonIntegrationKitPhaseUpdated, "") } // NotifyIntegrationKitError automatically generates error events when the integration kit reconcile cycle phase has an error. func NotifyIntegrationKitError(ctx context.Context, c client.Client, recorder record.EventRecorder, old, newResource *v1.IntegrationKit, err error) { kit := old if newResource != nil { kit = newResource } if kit == nil { return } recorder.Eventf(kit, corev1.EventTypeWarning, ReasonIntegrationKitError, "Cannot reconcile Integration Kit %s: %v", kit.Name, err) } // NotifyIntegrationPlatformUpdated automatically generates events when an integration platform changes. func NotifyIntegrationPlatformUpdated(ctx context.Context, c client.Client, recorder record.EventRecorder, old, newResource *v1.IntegrationPlatform) { if newResource == nil { return } oldPhase := "" var oldConditions []v1.ResourceCondition if old != nil { oldPhase = string(old.Status.Phase) oldConditions = old.Status.GetConditions() } if newResource.Status.Phase != v1.IntegrationPlatformPhaseNone { notifyIfConditionUpdated(recorder, newResource, oldConditions, newResource.Status.GetConditions(), "Integration Platform", newResource.Name, ReasonIntegrationPlatformConditionChanged) } notifyIfPhaseUpdated(ctx, c, recorder, newResource, oldPhase, string(newResource.Status.Phase), "Integration Platform", newResource.Name, ReasonIntegrationPlatformPhaseUpdated, "") } // NotifyIntegrationPlatformError automatically generates error events when the integration Platform reconcile cycle phase has an error. func NotifyIntegrationPlatformError(ctx context.Context, c client.Client, recorder record.EventRecorder, old, newResource *v1.IntegrationPlatform, err error) { p := old if newResource != nil { p = newResource } if p == nil { return } recorder.Eventf(p, corev1.EventTypeWarning, ReasonIntegrationPlatformError, "Cannot reconcile Integration Platform %s: %v", p.Name, err) } // NotifyIntegrationProfileError automatically generates error events when the integration Platform reconcile cycle phase has an error. func NotifyIntegrationProfileError(ctx context.Context, c client.Client, recorder record.EventRecorder, old, newResource *v1.IntegrationProfile, err error) { p := old if newResource != nil { p = newResource } if p == nil { return } recorder.Eventf(p, corev1.EventTypeWarning, ReasonIntegrationProfileError, "Cannot reconcile Integration Profile %s: %v", p.Name, err) } // NotifyCamelCatalogUpdated automatically generates events when a CamelCatalog changes. func NotifyCamelCatalogUpdated(ctx context.Context, c client.Client, recorder record.EventRecorder, old, newResource *v1.CamelCatalog) { if newResource == nil { return } oldPhase := "" var oldConditions []v1.ResourceCondition if old != nil { oldPhase = string(old.Status.Phase) oldConditions = old.Status.GetConditions() } if newResource.Status.Phase != v1.CamelCatalogPhaseNone { notifyIfConditionUpdated(recorder, newResource, oldConditions, newResource.Status.GetConditions(), "CamelCatalog", newResource.Name, ReasonKameletConditionChanged) } notifyIfPhaseUpdated(ctx, c, recorder, newResource, oldPhase, string(newResource.Status.Phase), "CamelCatalog", newResource.Name, ReasonKameletPhaseUpdated, "") } // NotifyCamelCatalogError automatically generates error events when the CamelCatalog reconcile cycle phase has an error. func NotifyCamelCatalogError(ctx context.Context, c client.Client, recorder record.EventRecorder, old, newResource *v1.CamelCatalog, err error) { k := old if newResource != nil { k = newResource } if k == nil { return } recorder.Eventf(k, corev1.EventTypeWarning, ReasonKameletError, "Cannot reconcile CamelCatalog %s: %v", k.Name, err) } // NotifyPipeUpdated automatically generates events when a Pipe changes. func NotifyPipeUpdated(ctx context.Context, c client.Client, recorder record.EventRecorder, old, newResource *v1.Pipe) { if newResource == nil { return } oldPhase := "" var oldConditions []v1.ResourceCondition if old != nil { oldPhase = string(old.Status.Phase) oldConditions = old.Status.GetConditions() } if newResource.Status.Phase != v1.PipePhaseNone { notifyIfConditionUpdated(recorder, newResource, oldConditions, newResource.Status.GetConditions(), "Pipe", newResource.Name, ReasonPipeConditionChanged) } notifyIfPhaseUpdated(ctx, c, recorder, newResource, oldPhase, string(newResource.Status.Phase), "Pipe", newResource.Name, ReasonPipePhaseUpdated, "") } // NotifyPipeError automatically generates error events when the binding reconcile cycle phase has an error. func NotifyPipeError(ctx context.Context, c client.Client, recorder record.EventRecorder, old, newResource *v1.Pipe, err error) { k := old if newResource != nil { k = newResource } if k == nil { return } recorder.Eventf(k, corev1.EventTypeWarning, ReasonKameletError, "Cannot reconcile Pipe %s: %v", k.Name, err) } // NotifyBuildUpdated automatically generates events when a build changes. func NotifyBuildUpdated(ctx context.Context, c client.Client, recorder record.EventRecorder, old, newResource *v1.Build) { if newResource == nil { return } oldPhase := "" var oldConditions []v1.ResourceCondition if old != nil { oldPhase = string(old.Status.Phase) oldConditions = old.Status.GetConditions() } if newResource.Status.Phase != v1.BuildPhaseNone { notifyIfConditionUpdated(recorder, newResource, oldConditions, newResource.Status.GetConditions(), "Build", newResource.Name, ReasonBuildConditionChanged) } info := "" if newResource.Status.Failure != nil { attempt := newResource.Status.Failure.Recovery.Attempt attemptMax := newResource.Status.Failure.Recovery.AttemptMax info = fmt.Sprintf(" (recovery %d of %d)", attempt, attemptMax) } notifyIfPhaseUpdated(ctx, c, recorder, newResource, oldPhase, string(newResource.Status.Phase), "Build", newResource.Name, ReasonBuildPhaseUpdated, info) } // NotifyBuildError automatically generates error events when the build reconcile cycle phase has an error. func NotifyBuildError(ctx context.Context, c client.Client, recorder record.EventRecorder, old, newResource *v1.Build, err error) { p := old if newResource != nil { p = newResource } if p == nil { return } recorder.Eventf(p, corev1.EventTypeWarning, ReasonBuildError, "Cannot reconcile Build %s: %v", p.Name, err) } //nolint:lll func notifyIfPhaseUpdated(ctx context.Context, c client.Client, recorder record.EventRecorder, newResource ctrl.Object, oldPhase, newPhase string, resourceType, name, reason, info string) { if oldPhase == newPhase { return } // Update information about phase changes phase := newPhase if phase == "" { phase = "[none]" } recorder.Eventf(newResource, corev1.EventTypeNormal, reason, "%s %q in phase %q%s", resourceType, name, phase, info) if creatorRef, creator := getCreatorObject(ctx, c, newResource); creatorRef != nil && creator != nil { if namespace := newResource.GetNamespace(); namespace == creatorRef.Namespace { recorder.Eventf(creator, corev1.EventTypeNormal, ReasonRelatedObjectChanged, "%s %q, created by %s %q, changed phase to %q%s", resourceType, name, creatorRef.Kind, creatorRef.Name, phase, info) } else { recorder.Eventf(creator, corev1.EventTypeNormal, ReasonRelatedObjectChanged, "%s \"%s/%s\", created by %s %q, changed phase to %q%s", resourceType, namespace, name, creatorRef.Kind, creatorRef.Name, phase, info) } } } func notifyIfConditionUpdated(recorder record.EventRecorder, newResource runtime.Object, oldConditions, newConditions []v1.ResourceCondition, resourceType, name, reason string) { // Update information about changes in conditions for _, cond := range getCommonChangedConditions(oldConditions, newConditions) { tail := "" if cond.GetMessage() != "" { tail = fmt.Sprintf(": %s", cond.GetMessage()) } recorder.Eventf(newResource, corev1.EventTypeNormal, reason, "Condition %q is %q for %s %s%s", cond.GetType(), cond.GetStatus(), resourceType, name, tail) } } func getCommonChangedConditions(oldConditions, newConditions []v1.ResourceCondition) []v1.ResourceCondition { oldState := make(map[string]v1.ResourceCondition) for _, c := range oldConditions { oldState[c.GetType()] = c } var res []v1.ResourceCondition for _, newCond := range newConditions { oldCond := oldState[newCond.GetType()] if oldCond == nil || oldCond.GetStatus() != newCond.GetStatus() || oldCond.GetMessage() != newCond.GetMessage() { res = append(res, newCond) } } return res } func getCreatorObject(ctx context.Context, c client.Client, obj runtime.Object) (*corev1.ObjectReference, runtime.Object) { if ref := kubernetes.GetCamelCreator(obj); ref != nil { if ref.Kind == "Integration" { it := v1.NewIntegration(ref.Namespace, ref.Name) if err := c.Get(ctx, ctrl.ObjectKeyFromObject(&it), &it); err != nil { log.Infof("Cannot get information about the creator Integration %v: %v", ref, err) return nil, nil } return ref, &it } } return nil, nil }