diff --git a/pkg/controller/integration/util.go b/pkg/controller/integration/util.go index da62a87ea7..6c3b23fdef 100644 --- a/pkg/controller/integration/util.go +++ b/pkg/controller/integration/util.go @@ -20,11 +20,11 @@ package integration import ( "context" + "github.com/apache/camel-k/pkg/util" + k8sclient "sigs.k8s.io/controller-runtime/pkg/client" "github.com/apache/camel-k/pkg/apis/camel/v1alpha1" - "github.com/apache/camel-k/pkg/util" - "github.com/pkg/errors" ) @@ -75,6 +75,22 @@ func LookupContextForIntegration(ctx context.Context, c k8sclient.Reader, integr continue } + // + // When a platform context is created it inherits the traits from the integrations and as + // some traits may influence the build thus the artifacts present on the container image, + // we need to take traits into account when looking up for compatible contexts. + // + // It could also happen that an integration is updated and a trait is modified, if we do + // not include traits in the lookup, we may use a context that does not have all the + // characteristics required by the integration. + // + // An context be used only if it contains a subset of the traits and related configurations + // declared on integration. + // + if !HasMatchingTraits(&ctx, integration) { + continue + } + if util.StringSliceContains(ctx.Spec.Dependencies, integration.Status.Dependencies) { return &ctx, nil } @@ -83,3 +99,31 @@ func LookupContextForIntegration(ctx context.Context, c k8sclient.Reader, integr return nil, nil } + +// HasMatchingTraits compare traits defined on context against those defined on integration. +func HasMatchingTraits(ctx *v1alpha1.IntegrationContext, integration *v1alpha1.Integration) bool { + for ctxTraitName, ctxTraitConf := range ctx.Spec.Traits { + iTraitConf, ok := integration.Spec.Traits[ctxTraitName] + if !ok { + // skip it because trait configured on context is not defined on integration. + return false + } + + for ck, cv := range ctxTraitConf.Configuration { + iv, ok := iTraitConf.Configuration[ck] + + if !ok { + // skip it because trait configured on context has a value that is not defined + // in integration trait + return false + } + if iv != cv { + // skip it because trait configured on context has a value that differs from + // the one configured on integration + return false + } + } + } + + return true +} diff --git a/pkg/controller/integration/util_test.go b/pkg/controller/integration/util_test.go index 98bed6d1d0..37f1fb6fc1 100644 --- a/pkg/controller/integration/util_test.go +++ b/pkg/controller/integration/util_test.go @@ -29,7 +29,7 @@ import ( "github.com/stretchr/testify/assert" ) -func TestLookupContextForIntegration(t *testing.T) { +func TestLookupContextForIntegration_DiscardContextsInError(t *testing.T) { c, err := test.NewFakeClient( &v1alpha1.IntegrationContext{ TypeMeta: metav1.TypeMeta{ @@ -100,3 +100,192 @@ func TestLookupContextForIntegration(t *testing.T) { assert.NotNil(t, i) assert.Equal(t, "my-context-2", i.Name) } + +func TestLookupContextForIntegration_DiscardContextsWithIncompatibleTraits(t *testing.T) { + c, err := test.NewFakeClient( + // + // Should be discarded because it contains both of the required traits but one + // contains a different configuration value + // + &v1alpha1.IntegrationContext{ + TypeMeta: metav1.TypeMeta{ + APIVersion: v1alpha1.SchemeGroupVersion.String(), + Kind: v1alpha1.IntegrationContextKind, + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns", + Name: "my-context-1", + Labels: map[string]string{ + "camel.apache.org/context.type": v1alpha1.IntegrationContextTypePlatform, + }, + }, + Spec: v1alpha1.IntegrationContextSpec{ + Dependencies: []string{ + "camel-core", + "camel-irc", + }, + Traits: map[string]v1alpha1.TraitSpec{ + "knative": { + Configuration: map[string]string{ + "enabled": "true", + }, + }, + "knative-service": { + Configuration: map[string]string{ + "enabled": "false", + }, + }, + }, + }, + Status: v1alpha1.IntegrationContextStatus{ + Phase: v1alpha1.IntegrationContextPhaseReady, + }, + }, + // + // Should be discarded because it contains a subset of the required traits but + // with different configuration value + // + &v1alpha1.IntegrationContext{ + TypeMeta: metav1.TypeMeta{ + APIVersion: v1alpha1.SchemeGroupVersion.String(), + Kind: v1alpha1.IntegrationContextKind, + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns", + Name: "my-context-2", + Labels: map[string]string{ + "camel.apache.org/context.type": v1alpha1.IntegrationContextTypePlatform, + }, + }, + Spec: v1alpha1.IntegrationContextSpec{ + Dependencies: []string{ + "camel-core", + "camel-irc", + }, + Traits: map[string]v1alpha1.TraitSpec{ + "knative": { + Configuration: map[string]string{ + "enabled": "false", + }, + }, + }, + }, + Status: v1alpha1.IntegrationContextStatus{ + Phase: v1alpha1.IntegrationContextPhaseReady, + }, + }, + // + // Should be discarded because it contains both of the required traits but + // also an additional one + // + &v1alpha1.IntegrationContext{ + TypeMeta: metav1.TypeMeta{ + APIVersion: v1alpha1.SchemeGroupVersion.String(), + Kind: v1alpha1.IntegrationContextKind, + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns", + Name: "my-context-3", + Labels: map[string]string{ + "camel.apache.org/context.type": v1alpha1.IntegrationContextTypePlatform, + }, + }, + Spec: v1alpha1.IntegrationContextSpec{ + Dependencies: []string{ + "camel-core", + "camel-irc", + }, + Traits: map[string]v1alpha1.TraitSpec{ + "knative": { + Configuration: map[string]string{ + "enabled": "true", + }, + }, + "knative-service": { + Configuration: map[string]string{ + "enabled": "true", + }, + }, + "gc": { + Configuration: map[string]string{ + "enabled": "true", + }, + }, + }, + }, + Status: v1alpha1.IntegrationContextStatus{ + Phase: v1alpha1.IntegrationContextPhaseReady, + }, + }, + // + // Should be discarded because it contains a subset of the required traits and + // same configuration values + // + &v1alpha1.IntegrationContext{ + TypeMeta: metav1.TypeMeta{ + APIVersion: v1alpha1.SchemeGroupVersion.String(), + Kind: v1alpha1.IntegrationContextKind, + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns", + Name: "my-context-4", + Labels: map[string]string{ + "camel.apache.org/context.type": v1alpha1.IntegrationContextTypePlatform, + }, + }, + Spec: v1alpha1.IntegrationContextSpec{ + Dependencies: []string{ + "camel-core", + "camel-irc", + }, + Traits: map[string]v1alpha1.TraitSpec{ + "knative": { + Configuration: map[string]string{ + "enabled": "true", + }, + }, + }, + }, + Status: v1alpha1.IntegrationContextStatus{ + Phase: v1alpha1.IntegrationContextPhaseReady, + }, + }, + ) + + assert.Nil(t, err) + + i, err := LookupContextForIntegration(context.TODO(), c, &v1alpha1.Integration{ + TypeMeta: metav1.TypeMeta{ + APIVersion: v1alpha1.SchemeGroupVersion.String(), + Kind: v1alpha1.IntegrationKind, + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns", + Name: "my-integration", + }, + Spec: v1alpha1.IntegrationSpec{ + Traits: map[string]v1alpha1.TraitSpec{ + "knative": { + Configuration: map[string]string{ + "enabled": "true", + }, + }, + "knative-service": { + Configuration: map[string]string{ + "enabled": "true", + }, + }, + }, + }, + Status: v1alpha1.IntegrationStatus{ + Dependencies: []string{ + "camel-core", + "camel-irc", + }, + }, + }) + + assert.Nil(t, err) + assert.NotNil(t, i) + assert.Equal(t, "my-context-4", i.Name) +}