diff --git a/addons/addons_test.go b/addons/addons_test.go index 600b80c522..3adda05d21 100644 --- a/addons/addons_test.go +++ b/addons/addons_test.go @@ -20,8 +20,6 @@ package addons import ( "testing" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "github.com/apache/camel-k/v2/addons/master" "github.com/apache/camel-k/v2/addons/telemetry" v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1" @@ -68,37 +66,3 @@ func TestTraitConfiguration(t *testing.T) { require.True(t, ok) assert.True(t, *telemetry.Enabled) } - -func TestTraitConfigurationFromAnnotations(t *testing.T) { - env := trait.Environment{ - Integration: &v1.Integration{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - "trait.camel.apache.org/master.enabled": "true", - "trait.camel.apache.org/master.resource-name": "test-lock", - "trait.camel.apache.org/master.label-key": "test-label", - "trait.camel.apache.org/master.label-value": "test-value", - "trait.camel.apache.org/telemetry.enabled": "true", - }, - }, - Spec: v1.IntegrationSpec{ - Profile: v1.TraitProfileKubernetes, - }, - }, - } - c := trait.NewCatalog(nil) - require.NoError(t, c.Configure(&env)) - - require.NotNil(t, c.GetTrait("master")) - master, ok := c.GetTrait("master").(*master.TestMasterTrait) - require.True(t, ok) - assert.True(t, *master.Enabled) - assert.Equal(t, "test-lock", *master.ResourceName) - assert.Equal(t, "test-label", *master.LabelKey) - assert.Equal(t, "test-value", *master.LabelValue) - - require.NotNil(t, c.GetTrait("telemetry")) - telemetry, ok := c.GetTrait("telemetry").(*telemetry.TestTelemetryTrait) - require.True(t, ok) - assert.True(t, *telemetry.Enabled) -} diff --git a/e2e/common/misc/pipe_with_image_test.go b/e2e/common/misc/pipe_with_image_test.go index 0cb9b53b86..ff68b9b068 100644 --- a/e2e/common/misc/pipe_with_image_test.go +++ b/e2e/common/misc/pipe_with_image_test.go @@ -55,10 +55,9 @@ func TestPipeWithImage(t *testing.T) { g.Eventually(IntegrationGeneration(t, ctx, ns, bindingID)). Should(gstruct.PointTo(BeNumerically("==", 1))) - g.Eventually(Integration(t, ctx, ns, bindingID)).Should(WithTransform(Annotations, And( + g.Eventually(Integration(t, ctx, ns, bindingID)).Should(WithTransform(Annotations, HaveKeyWithValue("test", "1"), - HaveKeyWithValue("trait.camel.apache.org/container.image", expectedImage), - ))) + )) g.Eventually(IntegrationStatusImage(t, ctx, ns, bindingID)). Should(Equal(expectedImage)) g.Eventually(IntegrationPodPhase(t, ctx, ns, bindingID), TestTimeoutShort). @@ -73,10 +72,9 @@ func TestPipeWithImage(t *testing.T) { g.Expect(KamelBindWithID(t, ctx, operatorID, ns, "my-own-timer-source", "my-own-log-sink", "--annotation", "trait.camel.apache.org/container.image="+expectedImage, "--annotation", "trait.camel.apache.org/jvm.enabled=false", "--annotation", "trait.camel.apache.org/kamelets.enabled=false", "--annotation", "trait.camel.apache.org/dependencies.enabled=false", "--annotation", "test=2", "--name", bindingID).Execute()).To(Succeed()) g.Eventually(IntegrationGeneration(t, ctx, ns, bindingID)). Should(gstruct.PointTo(BeNumerically("==", 1))) - g.Eventually(Integration(t, ctx, ns, bindingID)).Should(WithTransform(Annotations, And( + g.Eventually(Integration(t, ctx, ns, bindingID)).Should(WithTransform(Annotations, HaveKeyWithValue("test", "2"), - HaveKeyWithValue("trait.camel.apache.org/container.image", expectedImage), - ))) + )) g.Eventually(IntegrationStatusImage(t, ctx, ns, bindingID)). Should(Equal(expectedImage)) g.Eventually(IntegrationPodPhase(t, ctx, ns, bindingID), TestTimeoutShort). diff --git a/pkg/cmd/bind.go b/pkg/cmd/bind.go index d8b66be139..2f629adcb5 100644 --- a/pkg/cmd/bind.go +++ b/pkg/cmd/bind.go @@ -175,7 +175,7 @@ func (o *bindCmdOptions) validate(cmd *cobra.Command, args []string) error { } catalog := trait.NewCatalog(client) - return validateTraits(catalog, extractTraitNames(o.Traits)) + return trait.ValidateTraits(catalog, extractTraitNames(o.Traits)) } func (o *bindCmdOptions) run(cmd *cobra.Command, args []string) error { @@ -236,7 +236,7 @@ func (o *bindCmdOptions) run(cmd *cobra.Command, args []string) error { binding.Spec.Integration = &v1.IntegrationSpec{} } catalog := trait.NewCatalog(client) - if err := configureTraits(o.Traits, &binding.Spec.Integration.Traits, catalog); err != nil { + if err := trait.ConfigureTraits(o.Traits, &binding.Spec.Integration.Traits, catalog); err != nil { return err } } diff --git a/pkg/cmd/kit_create.go b/pkg/cmd/kit_create.go index f488c7ee46..fa03c0718e 100644 --- a/pkg/cmd/kit_create.go +++ b/pkg/cmd/kit_create.go @@ -153,7 +153,7 @@ func (command *kitCreateCommandOptions) run(cmd *cobra.Command, args []string) e if err := command.parseAndConvertToTrait(command.Secrets, "mount.config"); err != nil { return err } - if err := configureTraits(command.Traits, &kit.Spec.Traits, catalog); err != nil { + if err := trait.ConfigureTraits(command.Traits, &kit.Spec.Traits, catalog); err != nil { return err } existed := false diff --git a/pkg/cmd/run.go b/pkg/cmd/run.go index 4eb1d5d7b9..0afbb1d19e 100644 --- a/pkg/cmd/run.go +++ b/pkg/cmd/run.go @@ -297,7 +297,7 @@ func (o *runCmdOptions) validate(cmd *cobra.Command) error { } catalog := trait.NewCatalog(client) - return validateTraits(catalog, extractTraitNames(o.Traits)) + return trait.ValidateTraits(catalog, extractTraitNames(o.Traits)) } func filterBuildPropertyFiles(maybePropertyFiles []string) []string { @@ -561,7 +561,7 @@ func (o *runCmdOptions) createOrUpdateIntegration(cmd *cobra.Command, c client.C if len(o.Traits) > 0 { catalog := trait.NewCatalog(c) - if err := configureTraits(o.Traits, &integration.Spec.Traits, catalog); err != nil { + if err := trait.ConfigureTraits(o.Traits, &integration.Spec.Traits, catalog); err != nil { return nil, err } } diff --git a/pkg/cmd/run_test.go b/pkg/cmd/run_test.go index 8ba5399b8f..3d3a19a5f4 100644 --- a/pkg/cmd/run_test.go +++ b/pkg/cmd/run_test.go @@ -449,7 +449,7 @@ func TestConfigureTraits(t *testing.T) { catalog := trait.NewCatalog(client) traits := v1.Traits{} - err = configureTraits(runCmdOptions.Traits, &traits, catalog) + err = trait.ConfigureTraits(runCmdOptions.Traits, &traits, catalog) require.NoError(t, err) traitMap, err := trait.ToTraitMap(traits) diff --git a/pkg/controller/integration/build_kit.go b/pkg/controller/integration/build_kit.go index acbc01966a..af2d164ae2 100644 --- a/pkg/controller/integration/build_kit.go +++ b/pkg/controller/integration/build_kit.go @@ -99,7 +99,7 @@ kits: k := &existingKits[i] action.L.Debug("Comparing existing kit with environment", "env kit", kit.Name, "existing kit", k.Name) - match, err := kitMatches(&kit, k) + match, err := kitMatches(action.client, &kit, k) if err != nil { return nil, fmt.Errorf("error occurred matches integration kits with environment for integration %s/%s: %w", integration.Namespace, integration.Name, err) diff --git a/pkg/controller/integration/integration_controller.go b/pkg/controller/integration/integration_controller.go index 4fbdc39b4d..a3c895c2f9 100644 --- a/pkg/controller/integration/integration_controller.go +++ b/pkg/controller/integration/integration_controller.go @@ -85,7 +85,7 @@ func newReconciler(mgr manager.Manager, c client.Client) reconcile.Reconciler { ) } -func integrationUpdateFunc(old *v1.Integration, it *v1.Integration) bool { +func integrationUpdateFunc(c client.Client, old *v1.Integration, it *v1.Integration) bool { // Observe the time to first readiness metric previous := old.Status.GetCondition(v1.IntegrationConditionReady) next := it.Status.GetCondition(v1.IntegrationConditionReady) @@ -99,7 +99,7 @@ func integrationUpdateFunc(old *v1.Integration, it *v1.Integration) bool { updateIntegrationPhase(it.Name, string(it.Status.Phase)) // If traits have changed, the reconciliation loop must kick in as // traits may have impact - sameTraits, err := trait.IntegrationsHaveSameTraits(old, it) + sameTraits, err := trait.IntegrationsHaveSameTraits(c, old, it) if err != nil { Log.ForIntegration(it).Error( err, @@ -324,7 +324,7 @@ func add(ctx context.Context, mgr manager.Manager, c client.Client, r reconcile. return false } - return integrationUpdateFunc(old, it) + return integrationUpdateFunc(c, old, it) }, DeleteFunc: func(e event.DeleteEvent) bool { // Evaluates to false if the object has been confirmed deleted diff --git a/pkg/controller/integration/kits.go b/pkg/controller/integration/kits.go index 22adf0f039..9832139fa7 100644 --- a/pkg/controller/integration/kits.go +++ b/pkg/controller/integration/kits.go @@ -121,11 +121,11 @@ func integrationMatches(ctx context.Context, c client.Client, integration *v1.In return false, err } - itc, err := trait.NewSpecTraitsOptionsForIntegrationAndPlatform(integration, pl) + itc, err := trait.NewSpecTraitsOptionsForIntegrationAndPlatform(c, integration, pl) if err != nil { return false, err } - ikc, err := trait.NewSpecTraitsOptionsForIntegrationKit(kit) + ikc, err := trait.NewSpecTraitsOptionsForIntegrationKit(c, kit) if err != nil { return false, err } @@ -170,7 +170,7 @@ func statusMatches(integration *v1.Integration, kit *v1.IntegrationKit, ilog *lo } // kitMatches returns whether the kit matches with the existing target kit. -func kitMatches(kit *v1.IntegrationKit, target *v1.IntegrationKit) (bool, error) { +func kitMatches(c client.Client, kit *v1.IntegrationKit, target *v1.IntegrationKit) (bool, error) { version := kit.Status.Version if version == "" { // Defaults with the version that is going to be set during the kit initialization @@ -184,11 +184,11 @@ func kitMatches(kit *v1.IntegrationKit, target *v1.IntegrationKit) (bool, error) } // We cannot have yet the status set - c1, err := trait.NewSpecTraitsOptionsForIntegrationKit(kit) + c1, err := trait.NewSpecTraitsOptionsForIntegrationKit(c, kit) if err != nil { return false, err } - c2, err := trait.NewSpecTraitsOptionsForIntegrationKit(target) + c2, err := trait.NewSpecTraitsOptionsForIntegrationKit(c, target) if err != nil { return false, err } diff --git a/pkg/controller/integration/kits_test.go b/pkg/controller/integration/kits_test.go index 1f946aa350..9f10855263 100644 --- a/pkg/controller/integration/kits_test.go +++ b/pkg/controller/integration/kits_test.go @@ -25,6 +25,7 @@ import ( v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1" traitv1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1/trait" + "github.com/apache/camel-k/v2/pkg/client" "github.com/apache/camel-k/v2/pkg/trait" "github.com/apache/camel-k/v2/pkg/util/test" @@ -279,7 +280,10 @@ func TestHasMatchingTraits_KitNoTraitShouldNotBePicked(t *testing.T) { }, } - ok, err := integrationAndKitHaveSameTraits(integration, kit) + c, err := test.NewFakeClient(integration, kit) + require.NoError(t, err) + + ok, err := integrationAndKitHaveSameTraits(c, integration, kit) require.NoError(t, err) assert.False(t, ok) } @@ -326,8 +330,9 @@ func TestHasMatchingTraits_KitSameTraitShouldBePicked(t *testing.T) { }, }, } - - ok, err := integrationAndKitHaveSameTraits(integration, kit) + c, err := test.NewFakeClient(integration, kit) + require.NoError(t, err) + ok, err := integrationAndKitHaveSameTraits(c, integration, kit) require.NoError(t, err) assert.True(t, ok) } @@ -429,12 +434,12 @@ func TestHasNotMatchingSources(t *testing.T) { assert.False(t, hsm2) } -func integrationAndKitHaveSameTraits(i1 *v1.Integration, i2 *v1.IntegrationKit) (bool, error) { - itOpts, err := trait.NewSpecTraitsOptionsForIntegration(i1) +func integrationAndKitHaveSameTraits(c client.Client, i1 *v1.Integration, i2 *v1.IntegrationKit) (bool, error) { + itOpts, err := trait.NewSpecTraitsOptionsForIntegration(c, i1) if err != nil { return false, err } - ikOpts, err := trait.NewSpecTraitsOptionsForIntegrationKit(i2) + ikOpts, err := trait.NewSpecTraitsOptionsForIntegrationKit(c, i2) if err != nil { return false, err } diff --git a/pkg/controller/integration/monitor.go b/pkg/controller/integration/monitor.go index 47bb9829a3..5d908cca6d 100644 --- a/pkg/controller/integration/monitor.go +++ b/pkg/controller/integration/monitor.go @@ -23,6 +23,7 @@ import ( "fmt" "reflect" "strconv" + "strings" appsv1 "k8s.io/api/apps/v1" batchv1 "k8s.io/api/batch/v1" @@ -131,9 +132,32 @@ func (action *monitorAction) Handle(ctx context.Context, integration *v1.Integra return integration, err } + action.checkTraitAnnotationsDeprecatedNotice(integration) + return action.monitorPods(ctx, environment, integration) } +// Deprecated: to be removed in future versions, when we won't support any longer trait annotations into Integrations. +func (action *monitorAction) checkTraitAnnotationsDeprecatedNotice(integration *v1.Integration) { + if integration.Annotations != nil { + for k := range integration.Annotations { + if strings.HasPrefix(k, v1.TraitAnnotationPrefix) { + integration.Status.SetCondition( + v1.IntegrationConditionType("AnnotationTraitsDeprecated"), + corev1.ConditionTrue, + "DeprecationNotice", + "Annotation traits configuration is deprecated and will be removed soon. Use .spec.traits configuration instead.", + ) + action.L.Infof( + "WARN: annotation traits configuration is deprecated and will be removed soon. Use .spec.traits configuration for %s integration instead.", + integration.Name, + ) + return + } + } + } +} + func (action *monitorAction) monitorPods(ctx context.Context, environment *trait.Environment, integration *v1.Integration) (*v1.Integration, error) { controller, err := action.newController(environment, integration) if err != nil { diff --git a/pkg/controller/integrationkit/monitor.go b/pkg/controller/integrationkit/monitor.go index 5b72ef1530..7a71d9a76c 100644 --- a/pkg/controller/integrationkit/monitor.go +++ b/pkg/controller/integrationkit/monitor.go @@ -19,9 +19,11 @@ package integrationkit import ( "context" + "strings" v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1" "github.com/apache/camel-k/v2/pkg/util/digest" + corev1 "k8s.io/api/core/v1" ) // NewMonitorAction creates a new monitoring handling action for the kit. @@ -64,5 +66,29 @@ func (action *monitorAction) Handle(ctx context.Context, kit *v1.IntegrationKit) return kit, nil } + action.checkTraitAnnotationsDeprecatedNotice(kit) + return nil, nil } + +// Deprecated: to be removed in future versions, when we won't support any longer trait annotations into IntegrationKits. +func (action *monitorAction) checkTraitAnnotationsDeprecatedNotice(integrationKit *v1.IntegrationKit) { + if integrationKit.Annotations != nil { + for k := range integrationKit.Annotations { + if strings.HasPrefix(k, v1.TraitAnnotationPrefix) { + integrationKit.Status.SetCondition( + v1.IntegrationKitConditionType("AnnotationTraitsDeprecated"), + corev1.ConditionTrue, + "DeprecationNotice", + "Annotation traits configuration is deprecated and will be removed soon. Use .spec.traits configuration instead.", + ) + + action.L.Infof( + "WARN: annotation traits configuration is deprecated and will be removed soon. Use .spec.traits configuration for %s integration kit instead.", + integrationKit.Name, + ) + return + } + } + } +} diff --git a/pkg/controller/integrationplatform/monitor.go b/pkg/controller/integrationplatform/monitor.go index 1156a80f96..3a3b3c25e6 100644 --- a/pkg/controller/integrationplatform/monitor.go +++ b/pkg/controller/integrationplatform/monitor.go @@ -20,6 +20,7 @@ package integrationplatform import ( "context" "fmt" + "strings" v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1" platformutil "github.com/apache/camel-k/v2/pkg/platform" @@ -124,6 +125,28 @@ func (action *monitorAction) Handle(ctx context.Context, platform *v1.Integratio } platform.Status.Phase = platformPhase + action.checkTraitAnnotationsDeprecatedNotice(platform) return platform, nil } + +// Deprecated: to be removed in future versions, when we won't support any longer trait annotations into IntegrationPlatforms. +func (action *monitorAction) checkTraitAnnotationsDeprecatedNotice(platform *v1.IntegrationPlatform) { + if platform.Annotations != nil { + for k := range platform.Annotations { + if strings.HasPrefix(k, v1.TraitAnnotationPrefix) { + platform.Status.SetCondition( + v1.IntegrationPlatformConditionType("AnnotationTraitsDeprecated"), + corev1.ConditionTrue, + "DeprecationNotice", + "Annotation traits configuration is deprecated and will be removed soon. Use .spec.traits configuration instead.", + ) + action.L.Infof( + "WARN: annotation traits configuration is deprecated and will be removed soon. Use .spec.traits configuration for %s platform instead.", + platform.Name, + ) + return + } + } + } +} diff --git a/pkg/controller/kameletbinding/kameletbinding_controller.go b/pkg/controller/kameletbinding/kameletbinding_controller.go index 2c6313a199..2da902b221 100644 --- a/pkg/controller/kameletbinding/kameletbinding_controller.go +++ b/pkg/controller/kameletbinding/kameletbinding_controller.go @@ -47,7 +47,7 @@ import ( // Add creates a new KameletBinding Controller and adds it to the Manager. The Manager will set fields on the Controller // and Start it when the Manager is Started. func Add(ctx context.Context, mgr manager.Manager, c client.Client) error { - return add(mgr, newReconciler(mgr, c)) + return add(mgr, newReconciler(mgr, c), c) } func newReconciler(mgr manager.Manager, c client.Client) reconcile.Reconciler { @@ -65,14 +65,14 @@ func newReconciler(mgr manager.Manager, c client.Client) reconcile.Reconciler { ) } -func add(mgr manager.Manager, r reconcile.Reconciler) error { - c, err := controller.New("kamelet-binding-controller", mgr, controller.Options{Reconciler: r}) +func add(mgr manager.Manager, r reconcile.Reconciler, c client.Client) error { + ctrl, err := controller.New("kamelet-binding-controller", mgr, controller.Options{Reconciler: r}) if err != nil { return err } // Watch for changes to primary resource KameletBinding - err = c.Watch(source.Kind(mgr.GetCache(), &v1alpha1.KameletBinding{}), + err = ctrl.Watch(source.Kind(mgr.GetCache(), &v1alpha1.KameletBinding{}), &handler.EnqueueRequestForObject{}, platform.FilteringFuncs{ UpdateFunc: func(e event.UpdateEvent) bool { @@ -87,7 +87,7 @@ func add(mgr manager.Manager, r reconcile.Reconciler) error { // If traits have changed, the reconciliation loop must kick in as // traits may have impact - sameTraits, err := trait.KameletBindingsHaveSameTraits(oldKameletBinding, newKameletBinding) + sameTraits, err := trait.KameletBindingsHaveSameTraits(c, oldKameletBinding, newKameletBinding) if err != nil { Log.ForKameletBinding(newKameletBinding).Error( err, @@ -114,7 +114,7 @@ func add(mgr manager.Manager, r reconcile.Reconciler) error { } // Watch Integration to propagate changes downstream - err = c.Watch(source.Kind(mgr.GetCache(), &v1.Integration{}), + err = ctrl.Watch(source.Kind(mgr.GetCache(), &v1.Integration{}), handler.EnqueueRequestForOwner( mgr.GetScheme(), mgr.GetRESTMapper(), diff --git a/pkg/controller/kameletbinding/monitor.go b/pkg/controller/kameletbinding/monitor.go index 2c6d409f89..8c0187fcf1 100644 --- a/pkg/controller/kameletbinding/monitor.go +++ b/pkg/controller/kameletbinding/monitor.go @@ -82,7 +82,7 @@ func (action *monitorAction) Handle(ctx context.Context, binding *v1alpha1.Kamel integrationProfileNamespaceChanged := v1.GetIntegrationProfileNamespaceAnnotation(binding) != "" && (v1.GetIntegrationProfileNamespaceAnnotation(binding) != v1.GetIntegrationProfileNamespaceAnnotation(&it)) - sameTraits, err := trait.IntegrationAndKameletBindingSameTraits(&it, binding) + sameTraits, err := trait.IntegrationAndKameletBindingSameTraits(action.client, &it, binding) if err != nil { return nil, err } diff --git a/pkg/controller/pipe/integration.go b/pkg/controller/pipe/integration.go index b281f7b95a..15f5a35562 100644 --- a/pkg/controller/pipe/integration.go +++ b/pkg/controller/pipe/integration.go @@ -27,6 +27,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1" + "github.com/apache/camel-k/v2/pkg/trait" "github.com/apache/camel-k/v2/pkg/client" "github.com/apache/camel-k/v2/pkg/platform" @@ -49,6 +50,10 @@ func CreateIntegrationFor(ctx context.Context, c client.Client, binding *v1.Pipe annotations := util.CopyMap(binding.Annotations) // avoid propagating the icon to the integration as it's heavyweight and not needed delete(annotations, v1.AnnotationIcon) + traits, err := extractAndDeleteTraits(c, annotations) + if err != nil { + return nil, fmt.Errorf("could not marshal trait annotations %w", err) + } it := v1.Integration{ ObjectMeta: metav1.ObjectMeta{ @@ -82,6 +87,10 @@ func CreateIntegrationFor(ctx context.Context, c client.Client, binding *v1.Pipe it.Spec = *binding.Spec.Integration.DeepCopy() } + if traits != nil { + it.Spec.Traits = *traits + } + // Set replicas (or override podspecable value) if present if binding.Spec.Replicas != nil { replicas := *binding.Spec.Replicas @@ -210,6 +219,11 @@ func CreateIntegrationFor(ctx context.Context, c client.Client, binding *v1.Pipe return &it, nil } +// extractAndDeleteTraits will extract the annotation traits into v1.Traits struct, removing from the value from the input map. +func extractAndDeleteTraits(c client.Client, annotations map[string]string) (*v1.Traits, error) { + return trait.ExtractAndMaybeDeleteTraits(c, annotations, true) +} + func configureBinding(integration *v1.Integration, bindings ...*bindings.Binding) error { for _, b := range bindings { if b == nil { diff --git a/pkg/controller/pipe/integration_test.go b/pkg/controller/pipe/integration_test.go index 30af047067..9a6c692297 100644 --- a/pkg/controller/pipe/integration_test.go +++ b/pkg/controller/pipe/integration_test.go @@ -27,6 +27,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" + "k8s.io/utils/pointer" ) func TestCreateIntegrationForPipe(t *testing.T) { @@ -187,3 +188,73 @@ func expectedNominalRouteWithDataType(name string) string { id: binding ` } + +func TestExtractTraitAnnotations(t *testing.T) { + client, err := test.NewFakeClient() + require.NoError(t, err) + annotations := map[string]string{ + "my-personal-annotation": "hello", + v1.TraitAnnotationPrefix + "service.enabled": "true", + v1.TraitAnnotationPrefix + "container.image-pull-policy": "Never", + v1.TraitAnnotationPrefix + "camel.runtime-version": "1.2.3", + v1.TraitAnnotationPrefix + "camel.properties": `["prop1=1", "prop2=2"]`, + v1.TraitAnnotationPrefix + "environment.vars": `["env1=1"]`, + } + traits, err := extractAndDeleteTraits(client, annotations) + require.NoError(t, err) + assert.Equal(t, pointer.Bool(true), traits.Service.Enabled) + assert.Equal(t, corev1.PullNever, traits.Container.ImagePullPolicy) + assert.Equal(t, "1.2.3", traits.Camel.RuntimeVersion) + assert.Equal(t, []string{"prop1=1", "prop2=2"}, traits.Camel.Properties) + assert.Equal(t, []string{"env1=1"}, traits.Environment.Vars) + assert.Len(t, annotations, 1) + assert.Empty(t, annotations[v1.TraitAnnotationPrefix+"service.enabled"]) + assert.Empty(t, annotations[v1.TraitAnnotationPrefix+"container.image-pull-policy"]) + assert.Empty(t, annotations[v1.TraitAnnotationPrefix+"camel.runtime-version"]) + assert.Empty(t, annotations[v1.TraitAnnotationPrefix+"camel.properties"]) + assert.Empty(t, annotations[v1.TraitAnnotationPrefix+"environment.vars"]) + assert.Equal(t, "hello", annotations["my-personal-annotation"]) +} + +func TestExtractTraitAnnotationsError(t *testing.T) { + client, err := test.NewFakeClient() + require.NoError(t, err) + annotations := map[string]string{ + "my-personal-annotation": "hello", + v1.TraitAnnotationPrefix + "servicefake.bogus": "true", + } + traits, err := extractAndDeleteTraits(client, annotations) + require.Error(t, err) + assert.Equal(t, "trait servicefake does not exist in catalog", err.Error()) + assert.Nil(t, traits) + assert.Len(t, annotations, 2) +} + +func TestExtractTraitAnnotationsEmpty(t *testing.T) { + client, err := test.NewFakeClient() + require.NoError(t, err) + annotations := map[string]string{ + "my-personal-annotation": "hello", + } + traits, err := extractAndDeleteTraits(client, annotations) + require.NoError(t, err) + assert.Nil(t, traits) + assert.Len(t, annotations, 1) +} + +func TestCreateIntegrationTraitsForPipeWithTraitAnnotations(t *testing.T) { + client, err := test.NewFakeClient() + require.NoError(t, err) + + pipe := nominalPipe("my-pipe") + pipe.Annotations[v1.TraitAnnotationPrefix+"service.enabled"] = "true" + + it, err := CreateIntegrationFor(context.TODO(), client, &pipe) + require.NoError(t, err) + assert.Equal(t, "my-pipe", it.Name) + assert.Equal(t, "default", it.Namespace) + assert.Equal(t, map[string]string{ + "my-annotation": "my-annotation-val", + }, it.Annotations) + assert.Equal(t, pointer.Bool(true), it.Spec.Traits.Service.Enabled) +} diff --git a/pkg/controller/pipe/monitor.go b/pkg/controller/pipe/monitor.go index d8f2739aed..b7e109f3e9 100644 --- a/pkg/controller/pipe/monitor.go +++ b/pkg/controller/pipe/monitor.go @@ -72,7 +72,7 @@ func (action *monitorAction) Handle(ctx context.Context, pipe *v1.Pipe) (*v1.Pip integrationProfileNamespaceChanged := v1.GetIntegrationProfileNamespaceAnnotation(pipe) != "" && (v1.GetIntegrationProfileNamespaceAnnotation(pipe) != v1.GetIntegrationProfileNamespaceAnnotation(&it)) - sameTraits, err := trait.IntegrationAndPipeSameTraits(&it, pipe) + sameTraits, err := trait.IntegrationAndPipeSameTraits(action.client, &it, pipe) if err != nil { return nil, err } diff --git a/pkg/controller/pipe/monitor_test.go b/pkg/controller/pipe/monitor_test.go index a05f6e4dd5..a911599d69 100644 --- a/pkg/controller/pipe/monitor_test.go +++ b/pkg/controller/pipe/monitor_test.go @@ -312,8 +312,56 @@ func TestPipeIntegrationCreatingFromPipeCreatingPhase(t *testing.T) { // We calculate the integration the same way it does the operator // as we don't expect it to change in this test. it, err := CreateIntegrationFor(context.TODO(), c, pipe) + require.NoError(t, err) it.Status.Phase = v1.IntegrationPhaseBuildingKit + c, err = test.NewFakeClient(pipe, it) + require.NoError(t, err) + + a := NewMonitorAction() + a.InjectLogger(log.Log) + a.InjectClient(c) + assert.Equal(t, "monitor", a.Name()) + assert.True(t, a.CanHandle(pipe)) + handledPipe, err := a.Handle(context.TODO(), pipe) require.NoError(t, err) + assert.Equal(t, v1.PipePhaseCreating, handledPipe.Status.Phase) + assert.Equal(t, corev1.ConditionFalse, handledPipe.Status.GetCondition(v1.PipeConditionReady).Status) + assert.Equal(t, "Integration \"my-pipe\" is in \"Creating\" phase", handledPipe.Status.GetCondition(v1.PipeConditionReady).Message) +} + +func TestPipeIntegrationPipeTraitAnnotations(t *testing.T) { + pipe := &v1.Pipe{ + TypeMeta: metav1.TypeMeta{ + APIVersion: v1.SchemeGroupVersion.String(), + Kind: v1.PipeKind, + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns", + Name: "my-pipe", + Annotations: map[string]string{ + "trait.camel.apache.org/camel.runtime-version": "1.2.3", + }, + }, + Spec: v1.PipeSpec{ + Source: v1.Endpoint{ + URI: pointer.String("timer:tick"), + }, + Sink: v1.Endpoint{ + URI: pointer.String("log:info"), + }, + }, + Status: v1.PipeStatus{ + Phase: v1.PipePhaseCreating, + }, + } + + c, err := test.NewFakeClient(pipe) + require.NoError(t, err) + // We calculate the integration the same way it does the operator + // as we don't expect it to change in this test. + it, err := CreateIntegrationFor(context.TODO(), c, pipe) + require.NoError(t, err) + it.Status.Phase = v1.IntegrationPhaseBuildingKit c, err = test.NewFakeClient(pipe, it) require.NoError(t, err) diff --git a/pkg/controller/pipe/pipe_controller.go b/pkg/controller/pipe/pipe_controller.go index 3060cefc5b..9ad801b8da 100644 --- a/pkg/controller/pipe/pipe_controller.go +++ b/pkg/controller/pipe/pipe_controller.go @@ -47,7 +47,7 @@ import ( // Add creates a new Pipe Controller and adds it to the Manager. The Manager will set fields on the Controller // and Start it when the Manager is Started. func Add(ctx context.Context, mgr manager.Manager, c client.Client) error { - return add(mgr, newReconciler(mgr, c)) + return add(mgr, newReconciler(mgr, c), c) } func newReconciler(mgr manager.Manager, c client.Client) reconcile.Reconciler { @@ -65,14 +65,14 @@ func newReconciler(mgr manager.Manager, c client.Client) reconcile.Reconciler { ) } -func add(mgr manager.Manager, r reconcile.Reconciler) error { - c, err := controller.New("pipe-controller", mgr, controller.Options{Reconciler: r}) +func add(mgr manager.Manager, r reconcile.Reconciler, c client.Client) error { + ctrl, err := controller.New("pipe-controller", mgr, controller.Options{Reconciler: r}) if err != nil { return err } // Watch for changes to primary resource Pipe - err = c.Watch(source.Kind(mgr.GetCache(), &v1.Pipe{}), + err = ctrl.Watch(source.Kind(mgr.GetCache(), &v1.Pipe{}), &handler.EnqueueRequestForObject{}, platform.FilteringFuncs{ UpdateFunc: func(e event.UpdateEvent) bool { @@ -87,7 +87,7 @@ func add(mgr manager.Manager, r reconcile.Reconciler) error { // If traits have changed, the reconciliation loop must kick in as // traits may have impact - sameTraits, err := trait.PipesHaveSameTraits(oldPipe, newPipe) + sameTraits, err := trait.PipesHaveSameTraits(c, oldPipe, newPipe) if err != nil { Log.ForPipe(newPipe).Error( err, @@ -114,7 +114,7 @@ func add(mgr manager.Manager, r reconcile.Reconciler) error { } // Watch Integration to propagate changes downstream - err = c.Watch(source.Kind(mgr.GetCache(), &v1.Integration{}), + err = ctrl.Watch(source.Kind(mgr.GetCache(), &v1.Integration{}), handler.EnqueueRequestForOwner( mgr.GetScheme(), mgr.GetRESTMapper(), diff --git a/pkg/trait/quarkus.go b/pkg/trait/quarkus.go index 08dd0aafa7..8d47446141 100644 --- a/pkg/trait/quarkus.go +++ b/pkg/trait/quarkus.go @@ -20,7 +20,6 @@ package trait import ( "fmt" "sort" - "strings" "github.com/rs/xid" @@ -277,11 +276,6 @@ func (t *quarkusTrait) newIntegrationKit(e *Environment, packageType quarkusPack v1.SetAnnotation(&kit.ObjectMeta, v1.IntegrationProfileNamespaceAnnotation, e.Integration.Namespace) } } - for k, v := range integration.Annotations { - if strings.HasPrefix(k, v1.TraitAnnotationPrefix) { - v1.SetAnnotation(&kit.ObjectMeta, k, v) - } - } operatorID := defaults.OperatorID() if operatorID != "" { kit.SetOperatorID(operatorID) diff --git a/pkg/trait/quarkus_test.go b/pkg/trait/quarkus_test.go index 473da5c449..6c0ae919d5 100644 --- a/pkg/trait/quarkus_test.go +++ b/pkg/trait/quarkus_test.go @@ -70,25 +70,6 @@ func TestApplyQuarkusTraitDefaultKitLayout(t *testing.T) { assert.Equal(t, environment.IntegrationKits[0].Labels[v1.IntegrationKitLayoutLabel], v1.IntegrationKitLayoutFastJar) } -func TestApplyQuarkusTraitAnnotationKitConfiguration(t *testing.T) { - quarkusTrait, environment := createNominalQuarkusTest() - environment.Integration.Status.Phase = v1.IntegrationPhaseBuildingKit - - v1.SetAnnotation(&environment.Integration.ObjectMeta, v1.TraitAnnotationPrefix+"quarkus.foo", "camel-k") - - configured, condition, err := quarkusTrait.Configure(environment) - assert.True(t, configured) - require.NoError(t, err) - assert.Nil(t, condition) - - err = quarkusTrait.Apply(environment) - require.NoError(t, err) - assert.Len(t, environment.IntegrationKits, 1) - assert.Equal(t, v1.IntegrationKitLayoutFastJar, environment.IntegrationKits[0].Labels[v1.IntegrationKitLayoutLabel]) - assert.Equal(t, "camel-k", environment.IntegrationKits[0].Annotations[v1.TraitAnnotationPrefix+"quarkus.foo"]) - -} - func TestQuarkusTraitBuildModeOrder(t *testing.T) { quarkusTrait, environment := createNominalQuarkusTest() quarkusTrait.Modes = []traitv1.QuarkusMode{traitv1.NativeQuarkusMode, traitv1.JvmQuarkusMode} diff --git a/pkg/trait/trait_configure.go b/pkg/trait/trait_configure.go index 8efb45a1cb..b14d40b774 100644 --- a/pkg/trait/trait_configure.go +++ b/pkg/trait/trait_configure.go @@ -36,6 +36,7 @@ func (c *Catalog) Configure(env *Environment) error { if err := c.configureTraits(env.Platform.Status.Traits); err != nil { return err } + // Deprecated: to be removed in future version if err := c.configureTraitsFromAnnotations(env.Platform.Annotations); err != nil { return err } @@ -49,6 +50,7 @@ func (c *Catalog) Configure(env *Environment) error { if err := c.configureTraits(env.IntegrationKit.Spec.Traits); err != nil { return err } + // Deprecated: to be removed in future version if err := c.configureTraitsFromAnnotations(env.IntegrationKit.Annotations); err != nil { return err } @@ -57,6 +59,7 @@ func (c *Catalog) Configure(env *Environment) error { if err := c.configureTraits(env.Integration.Spec.Traits); err != nil { return err } + // Deprecated: to be removed in future version if err := c.configureTraitsFromAnnotations(env.Integration.Annotations); err != nil { return err } @@ -117,6 +120,7 @@ func decodeTrait(in map[string]interface{}, target Trait) error { return json.Unmarshal(data, target) } +// Deprecated: to be removed in future versions. func (c *Catalog) configureTraitsFromAnnotations(annotations map[string]string) error { options := make(map[string]map[string]interface{}, len(annotations)) for k, v := range annotations { @@ -154,6 +158,7 @@ func (c *Catalog) configureTraitsFromAnnotations(annotations map[string]string) return c.configureFromOptions(options) } +// Deprecated: to be removed in future versions. func (c *Catalog) configureFromOptions(traits map[string]map[string]interface{}) error { for id, config := range traits { t := c.GetTrait(id) @@ -167,6 +172,7 @@ func (c *Catalog) configureFromOptions(traits map[string]map[string]interface{}) return nil } +// Deprecated: to be removed in future versions. func configureTrait(id string, config map[string]interface{}, trait interface{}) error { md := mapstructure.Metadata{} diff --git a/pkg/cmd/trait_support.go b/pkg/trait/trait_support.go similarity index 92% rename from pkg/cmd/trait_support.go rename to pkg/trait/trait_support.go index 5ae94a1a49..eca4ba63f9 100644 --- a/pkg/cmd/trait_support.go +++ b/pkg/trait/trait_support.go @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package cmd +package trait import ( "encoding/json" @@ -26,7 +26,6 @@ import ( "strings" v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1" - "github.com/apache/camel-k/v2/pkg/trait" "github.com/apache/camel-k/v2/pkg/util" "github.com/mitchellh/mapstructure" ) @@ -38,18 +37,26 @@ var knownAddons = []string{"keda", "master", "strimzi", "3scale", "tracing"} var traitConfigRegexp = regexp.MustCompile(`^([a-z0-9-]+)((?:\.[a-z0-9-]+)(?:\[[0-9]+\]|\..+)*)=(.*)$`) -func validateTraits(catalog *trait.Catalog, traits []string) error { +func ValidateTrait(catalog *Catalog, trait string) error { + tr := catalog.GetTrait(trait) + if tr == nil { + return fmt.Errorf("trait %s does not exist in catalog", trait) + } + + return nil +} + +func ValidateTraits(catalog *Catalog, traits []string) error { for _, t := range traits { - tr := catalog.GetTrait(t) - if tr == nil { - return fmt.Errorf("trait %s does not exist in catalog", t) + if err := ValidateTrait(catalog, t); err != nil { + return err } } return nil } -func configureTraits(options []string, traits interface{}, catalog trait.Finder) error { +func ConfigureTraits(options []string, traits interface{}, catalog Finder) error { config, err := optionsToMap(options) if err != nil { return err @@ -144,7 +151,7 @@ func optionsToMap(options []string) (optionMap, error) { return optionMap, nil } -func configureAddons(config optionMap, traits interface{}, catalog trait.Finder) error { +func configureAddons(config optionMap, traits interface{}, catalog Finder) error { // Addon traits require raw message mapping addons := make(map[string]v1.AddonTrait) for id, props := range config { diff --git a/pkg/trait/util.go b/pkg/trait/util.go index 374d90e48b..94581dad47 100644 --- a/pkg/trait/util.go +++ b/pkg/trait/util.go @@ -27,8 +27,6 @@ import ( "sort" "strings" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - ctrl "sigs.k8s.io/controller-runtime/pkg/client" v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1" @@ -348,12 +346,12 @@ func Equals(i1 Options, i2 Options) bool { } // IntegrationsHaveSameTraits return if traits are the same. -func IntegrationsHaveSameTraits(i1 *v1.Integration, i2 *v1.Integration) (bool, error) { - c1, err := NewSpecTraitsOptionsForIntegration(i1) +func IntegrationsHaveSameTraits(c client.Client, i1 *v1.Integration, i2 *v1.Integration) (bool, error) { + c1, err := NewSpecTraitsOptionsForIntegration(c, i1) if err != nil { return false, err } - c2, err := NewSpecTraitsOptionsForIntegration(i2) + c2, err := NewSpecTraitsOptionsForIntegration(c, i2) if err != nil { return false, err } @@ -362,12 +360,12 @@ func IntegrationsHaveSameTraits(i1 *v1.Integration, i2 *v1.Integration) (bool, e } // PipesHaveSameTraits return if traits are the same. -func PipesHaveSameTraits(i1 *v1.Pipe, i2 *v1.Pipe) (bool, error) { - c1, err := NewTraitsOptionsForPipe(i1) +func PipesHaveSameTraits(c client.Client, i1 *v1.Pipe, i2 *v1.Pipe) (bool, error) { + c1, err := NewTraitsOptionsForPipe(c, i1) if err != nil { return false, err } - c2, err := NewTraitsOptionsForPipe(i2) + c2, err := NewTraitsOptionsForPipe(c, i2) if err != nil { return false, err } @@ -377,12 +375,12 @@ func PipesHaveSameTraits(i1 *v1.Pipe, i2 *v1.Pipe) (bool, error) { // KameletBindingsHaveSameTraits return if traits are the same. // Deprecated. -func KameletBindingsHaveSameTraits(i1 *v1alpha1.KameletBinding, i2 *v1alpha1.KameletBinding) (bool, error) { - c1, err := NewTraitsOptionsForKameletBinding(i1) +func KameletBindingsHaveSameTraits(c client.Client, i1 *v1alpha1.KameletBinding, i2 *v1alpha1.KameletBinding) (bool, error) { + c1, err := NewTraitsOptionsForKameletBinding(c, i1) if err != nil { return false, err } - c2, err := NewTraitsOptionsForKameletBinding(i2) + c2, err := NewTraitsOptionsForKameletBinding(c, i2) if err != nil { return false, err } @@ -393,16 +391,15 @@ func KameletBindingsHaveSameTraits(i1 *v1alpha1.KameletBinding, i2 *v1alpha1.Kam // IntegrationAndPipeSameTraits return if traits are the same. // The comparison is done for the subset of traits defines on the binding as during the trait processing, // some traits may be added to the Integration i.e. knative configuration in case of sink binding. -func IntegrationAndPipeSameTraits(i1 *v1.Integration, i2 *v1.Pipe) (bool, error) { - itOpts, err := NewSpecTraitsOptionsForIntegration(i1) +func IntegrationAndPipeSameTraits(c client.Client, i1 *v1.Integration, i2 *v1.Pipe) (bool, error) { + itOpts, err := NewSpecTraitsOptionsForIntegration(c, i1) if err != nil { return false, err } - klbOpts, err := NewTraitsOptionsForPipe(i2) + klbOpts, err := NewTraitsOptionsForPipe(c, i2) if err != nil { return false, err } - toCompare := make(Options) for k := range klbOpts { if v, ok := itOpts[k]; ok { @@ -417,12 +414,12 @@ func IntegrationAndPipeSameTraits(i1 *v1.Integration, i2 *v1.Pipe) (bool, error) // The comparison is done for the subset of traits defines on the binding as during the trait processing, // some traits may be added to the Integration i.e. knative configuration in case of sink binding. // Deprecated. -func IntegrationAndKameletBindingSameTraits(i1 *v1.Integration, i2 *v1alpha1.KameletBinding) (bool, error) { - itOpts, err := NewSpecTraitsOptionsForIntegration(i1) +func IntegrationAndKameletBindingSameTraits(c client.Client, i1 *v1.Integration, i2 *v1alpha1.KameletBinding) (bool, error) { + itOpts, err := NewSpecTraitsOptionsForIntegration(c, i1) if err != nil { return false, err } - klbOpts, err := NewTraitsOptionsForKameletBinding(i2) + klbOpts, err := NewTraitsOptionsForKameletBinding(c, i2) if err != nil { return false, err } @@ -437,8 +434,17 @@ func IntegrationAndKameletBindingSameTraits(i1 *v1.Integration, i2 *v1alpha1.Kam return Equals(klbOpts, toCompare), nil } -func newTraitsOptions(opts Options, objectMeta *metav1.ObjectMeta) (Options, error) { - m2, err := FromAnnotations(objectMeta) +// newTraitsOptions will merge the traits annotations with the traits spec using the same format. +func newTraitsOptions(c client.Client, opts Options, annotations map[string]string) (Options, error) { + annotationTraits, err := ExtractAndMaybeDeleteTraits(c, annotations, false) + if err != nil { + return nil, err + } + if annotationTraits == nil { + return opts, nil + } + + m2, err := ToTraitMap(*annotationTraits) if err != nil { return nil, err } @@ -450,7 +456,59 @@ func newTraitsOptions(opts Options, objectMeta *metav1.ObjectMeta) (Options, err return opts, nil } -func NewSpecTraitsOptionsForIntegrationAndPlatform(i *v1.Integration, pl *v1.IntegrationPlatform) (Options, error) { +// ExtractAndDeleteTraits will extract the annotation traits into v1.Traits struct, removing from the value from the input map. +func ExtractAndMaybeDeleteTraits(c client.Client, annotations map[string]string, del bool) (*v1.Traits, error) { + // structure that will be marshalled into a v1.Traits as it was a kamel run command + catalog := NewCatalog(c) + traitsPlainParams := []string{} + for k, v := range annotations { + if strings.HasPrefix(k, v1.TraitAnnotationPrefix) { + key := strings.ReplaceAll(k, v1.TraitAnnotationPrefix, "") + traitID := strings.Split(key, ".")[0] + if err := ValidateTrait(catalog, traitID); err != nil { + return nil, err + } + traitArrayParams := extractAsArray(v) + for _, param := range traitArrayParams { + traitsPlainParams = append(traitsPlainParams, fmt.Sprintf("%s=%s", key, param)) + } + if del { + delete(annotations, k) + } + } + } + if len(traitsPlainParams) == 0 { + return nil, nil + } + var traits v1.Traits + if err := ConfigureTraits(traitsPlainParams, &traits, catalog); err != nil { + return nil, err + } + + return &traits, nil +} + +// extractTraitValue can detect if the value is an array representation as ["prop1=1", "prop2=2"] and +// return an array with the values or with the single value passed as a parameter. +func extractAsArray(value string) []string { + if strings.HasPrefix(value, "[") && strings.HasSuffix(value, "]") { + arrayValue := []string{} + data := value[1 : len(value)-1] + vals := strings.Split(data, ",") + for _, v := range vals { + prop := strings.Trim(v, " ") + if strings.HasPrefix(prop, `"`) && strings.HasSuffix(prop, `"`) { + prop = prop[1 : len(prop)-1] + } + arrayValue = append(arrayValue, prop) + } + return arrayValue + } + + return []string{value} +} + +func NewSpecTraitsOptionsForIntegrationAndPlatform(c client.Client, i *v1.Integration, pl *v1.IntegrationPlatform) (Options, error) { var options Options var err error if pl != nil { @@ -471,32 +529,44 @@ func NewSpecTraitsOptionsForIntegrationAndPlatform(i *v1.Integration, pl *v1.Int options[k] = v } - return newTraitsOptions(options, &i.ObjectMeta) + // Deprecated: to remove when we remove support for traits annotations. + // IMPORTANT: when we remove this we'll need to remove the cli from the func, + // which will bring to more cascade removal. It had to be introduced to support the deprecated feature + // in a properly manner (ie, comparing the spec.traits with annotations in a proper way). + return newTraitsOptions(c, options, i.ObjectMeta.Annotations) } -func NewSpecTraitsOptionsForIntegration(i *v1.Integration) (Options, error) { +func NewSpecTraitsOptionsForIntegration(c client.Client, i *v1.Integration) (Options, error) { m1, err := ToTraitMap(i.Spec.Traits) if err != nil { return nil, err } - return newTraitsOptions(m1, &i.ObjectMeta) + // Deprecated: to remove when we remove support for traits annotations. + // IMPORTANT: when we remove this we'll need to remove the cli from the func, + // which will bring to more cascade removal. It had to be introduced to support the deprecated feature + // in a properly manner (ie, comparing the spec.traits with annotations in a proper way). + return newTraitsOptions(c, m1, i.ObjectMeta.Annotations) } -func newTraitsOptionsForIntegrationKit(i *v1.IntegrationKit, traits v1.IntegrationKitTraits) (Options, error) { +func newTraitsOptionsForIntegrationKit(c client.Client, i *v1.IntegrationKit, traits v1.IntegrationKitTraits) (Options, error) { m1, err := ToTraitMap(traits) if err != nil { return nil, err } - return newTraitsOptions(m1, &i.ObjectMeta) + // Deprecated: to remove when we remove support for traits annotations. + // IMPORTANT: when we remove this we'll need to remove the cli from the func, + // which will bring to more cascade removal. It had to be introduced to support the deprecated feature + // in a properly manner (ie, comparing the spec.traits with annotations in a proper way). + return newTraitsOptions(c, m1, i.ObjectMeta.Annotations) } -func NewSpecTraitsOptionsForIntegrationKit(i *v1.IntegrationKit) (Options, error) { - return newTraitsOptionsForIntegrationKit(i, i.Spec.Traits) +func NewSpecTraitsOptionsForIntegrationKit(c client.Client, i *v1.IntegrationKit) (Options, error) { + return newTraitsOptionsForIntegrationKit(c, i, i.Spec.Traits) } -func NewTraitsOptionsForPipe(pipe *v1.Pipe) (Options, error) { +func NewTraitsOptionsForPipe(c client.Client, pipe *v1.Pipe) (Options, error) { options := Options{} if pipe.Spec.Integration != nil { @@ -510,11 +580,11 @@ func NewTraitsOptionsForPipe(pipe *v1.Pipe) (Options, error) { } } - return newTraitsOptions(options, &pipe.ObjectMeta) + return newTraitsOptions(c, options, pipe.ObjectMeta.Annotations) } // Deprecated. -func NewTraitsOptionsForKameletBinding(kb *v1alpha1.KameletBinding) (Options, error) { +func NewTraitsOptionsForKameletBinding(c client.Client, kb *v1alpha1.KameletBinding) (Options, error) { options := Options{} if kb.Spec.Integration != nil { @@ -528,47 +598,7 @@ func NewTraitsOptionsForKameletBinding(kb *v1alpha1.KameletBinding) (Options, er } } - return newTraitsOptions(options, &kb.ObjectMeta) -} - -func FromAnnotations(meta *metav1.ObjectMeta) (Options, error) { - options := make(Options) - for k, v := range meta.Annotations { - if strings.HasPrefix(k, v1.TraitAnnotationPrefix) { - configKey := strings.TrimPrefix(k, v1.TraitAnnotationPrefix) - if strings.Contains(configKey, ".") { - parts := strings.SplitN(configKey, ".", 2) - id := parts[0] - prop := parts[1] - if _, ok := options[id]; !ok { - options[id] = make(map[string]interface{}) - } - options[id][prop] = stringOrSlice(v) - } else { - return options, fmt.Errorf("wrong format for trait annotation %q: missing trait ID", k) - } - } - } - - return options, nil -} - -// stringOrSlice returns either a string or a slice with trimmed values when the input is -// represented as an array style (ie, [a,b,c]). -func stringOrSlice(val string) interface{} { - if val == "[]" { - // empty array - return []string{} - } - if strings.HasPrefix(val, "[") && strings.HasSuffix(val, "]") { - slice := strings.Split(val[1:len(val)-1], ",") - for i := range slice { - slice[i] = strings.Trim(slice[i], " ") - } - return slice - } else { - return val - } + return newTraitsOptions(c, options, kb.ObjectMeta.Annotations) } // verify if the integration in the Environment contains an endpoint. diff --git a/pkg/trait/util_test.go b/pkg/trait/util_test.go index d89262bbf9..6e56b15a11 100644 --- a/pkg/trait/util_test.go +++ b/pkg/trait/util_test.go @@ -25,6 +25,7 @@ import ( v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1" traitv1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1/trait" + "github.com/apache/camel-k/v2/pkg/util/test" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -174,6 +175,8 @@ func TestToTrait(t *testing.T) { } func TestSameTraits(t *testing.T) { + c, err := test.NewFakeClient() + require.NoError(t, err) t.Run("empty traits", func(t *testing.T) { oldKlb := &v1.Pipe{ Spec: v1.PipeSpec{ @@ -190,7 +193,7 @@ func TestSameTraits(t *testing.T) { }, } - ok, err := PipesHaveSameTraits(oldKlb, newKlb) + ok, err := PipesHaveSameTraits(c, oldKlb, newKlb) require.NoError(t, err) assert.True(t, ok) }) @@ -219,7 +222,7 @@ func TestSameTraits(t *testing.T) { }, } - ok, err := PipesHaveSameTraits(oldKlb, newKlb) + ok, err := PipesHaveSameTraits(c, oldKlb, newKlb) require.NoError(t, err) assert.True(t, ok) }) @@ -248,7 +251,7 @@ func TestSameTraits(t *testing.T) { }, } - ok, err := PipesHaveSameTraits(oldKlb, newKlb) + ok, err := PipesHaveSameTraits(c, oldKlb, newKlb) require.NoError(t, err) assert.False(t, ok) }) @@ -273,7 +276,7 @@ func TestSameTraits(t *testing.T) { }, } - ok, err := PipesHaveSameTraits(oldKlb, newKlb) + ok, err := PipesHaveSameTraits(c, oldKlb, newKlb) require.NoError(t, err) assert.True(t, ok) }) @@ -294,7 +297,7 @@ func TestSameTraits(t *testing.T) { }, } - ok, err := PipesHaveSameTraits(oldKlb, newKlb) + ok, err := PipesHaveSameTraits(c, oldKlb, newKlb) require.NoError(t, err) assert.True(t, ok) }) @@ -319,7 +322,7 @@ func TestSameTraits(t *testing.T) { }, } - ok, err := PipesHaveSameTraits(oldKlb, newKlb) + ok, err := PipesHaveSameTraits(c, oldKlb, newKlb) require.NoError(t, err) assert.False(t, ok) }) @@ -340,7 +343,7 @@ func TestSameTraits(t *testing.T) { }, } - ok, err := PipesHaveSameTraits(oldKlb, newKlb) + ok, err := PipesHaveSameTraits(c, oldKlb, newKlb) require.NoError(t, err) assert.False(t, ok) }) @@ -392,46 +395,28 @@ func TestHasMathchingTraitsMissing(t *testing.T) { assert.True(t, b1) } -func TestFromAnnotationsPlain(t *testing.T) { - meta := metav1.ObjectMeta{ - Annotations: map[string]string{ - "trait.camel.apache.org/trait.prop1": "hello1", - "trait.camel.apache.org/trait.prop2": "hello2", +func TestIntegrationAndPipeSameTraits(t *testing.T) { + pipe := &v1.Pipe{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + "trait.camel.apache.org/camel.runtime-version": "1.2.3", + }, }, } - opt, err := FromAnnotations(&meta) - require.NoError(t, err) - tt, ok := opt.Get("trait") - assert.True(t, ok) - assert.Equal(t, "hello1", tt["prop1"]) - assert.Equal(t, "hello2", tt["prop2"]) -} -func TestFromAnnotationsArray(t *testing.T) { - meta := metav1.ObjectMeta{ - Annotations: map[string]string{ - "trait.camel.apache.org/trait.prop1": "[hello,world]", - // The func should trim empty spaces as well - "trait.camel.apache.org/trait.prop2": "[\"hello=1\", \"world=2\"]", + integration := &v1.Integration{ + Spec: v1.IntegrationSpec{ + Traits: v1.Traits{ + Camel: &traitv1.CamelTrait{ + RuntimeVersion: "1.2.3", + }, + }, }, } - opt, err := FromAnnotations(&meta) + c, err := test.NewFakeClient(pipe, integration) require.NoError(t, err) - tt, ok := opt.Get("trait") - assert.True(t, ok) - assert.Equal(t, []string{"hello", "world"}, tt["prop1"]) - assert.Equal(t, []string{"\"hello=1\"", "\"world=2\""}, tt["prop2"]) -} -func TestFromAnnotationsArrayEmpty(t *testing.T) { - meta := metav1.ObjectMeta{ - Annotations: map[string]string{ - "trait.camel.apache.org/trait.prop": "[]", - }, - } - opt, err := FromAnnotations(&meta) + result, err := IntegrationAndPipeSameTraits(c, integration, pipe) require.NoError(t, err) - tt, ok := opt.Get("trait") - assert.True(t, ok) - assert.Equal(t, []string{}, tt["prop"]) + assert.True(t, result) } diff --git a/pkg/util/digest/digest.go b/pkg/util/digest/digest.go index 946b399f15..9630467db8 100644 --- a/pkg/util/digest/digest.go +++ b/pkg/util/digest/digest.go @@ -336,6 +336,7 @@ func sortedTraitsMapKeys(m map[string]map[string]interface{}) []string { return res } +// Deprecated: to be removed in future versions. func sortedTraitAnnotationsKeys(it *v1.Integration) []string { res := make([]string, 0, len(it.Annotations)) for k := range it.Annotations {