This is an automated email from the ASF dual-hosted git repository.

astefanutti pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-k.git

commit 8fded214097f40819c872e71964f09b64b45c397
Author: Antonin Stefanutti <[email protected]>
AuthorDate: Thu Aug 26 16:28:01 2021 +0200

    feat(native): Add multi-kit support with prioritization
---
 pkg/apis/camel/v1/integrationkit_types.go         |   3 +
 pkg/apis/camel/v1/integrationkit_types_support.go |  18 ++
 pkg/controller/integration/build_kit.go           | 362 ++++++++++++----------
 pkg/controller/integration/build_kit_test.go      |  18 +-
 pkg/controller/integrationkit/build.go            |   2 +-
 pkg/trait/quarkus.go                              | 150 +++++++--
 pkg/trait/quarkus_test.go                         |   4 +-
 pkg/trait/trait_configure.go                      |   5 +-
 pkg/trait/trait_types.go                          |  45 +--
 9 files changed, 388 insertions(+), 219 deletions(-)

diff --git a/pkg/apis/camel/v1/integrationkit_types.go 
b/pkg/apis/camel/v1/integrationkit_types.go
index 0fae093..17203c5 100644
--- a/pkg/apis/camel/v1/integrationkit_types.go
+++ b/pkg/apis/camel/v1/integrationkit_types.go
@@ -105,6 +105,9 @@ const (
        // IntegrationKitLayoutNative labels a kit using the Quarkus native 
packaging
        IntegrationKitLayoutNative = "native"
 
+       // IntegrationKitPriorityLabel labels the kit priority
+       IntegrationKitPriorityLabel = "camel.apache.org/kit.priority"
+
        // IntegrationKitPhaseNone --
        IntegrationKitPhaseNone IntegrationKitPhase = ""
        // IntegrationKitPhaseInitialization --
diff --git a/pkg/apis/camel/v1/integrationkit_types_support.go 
b/pkg/apis/camel/v1/integrationkit_types_support.go
index a9c894e..408a884 100644
--- a/pkg/apis/camel/v1/integrationkit_types_support.go
+++ b/pkg/apis/camel/v1/integrationkit_types_support.go
@@ -18,6 +18,8 @@ limitations under the License.
 package v1
 
 import (
+       "strconv"
+
        corev1 "k8s.io/api/core/v1"
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
@@ -76,6 +78,22 @@ func (in *IntegrationKit) SetIntegrationPlatform(platform 
*IntegrationPlatform)
        in.Status.Platform = platform.Name
 }
 
+func (in *IntegrationKit) HasHigherPriorityThan(kit *IntegrationKit) bool {
+       p1 := 0
+       p2 := 0
+       if l, ok := in.Labels[IntegrationKitPriorityLabel]; ok {
+               if p, err := strconv.Atoi(l); err == nil {
+                       p1 = p
+               }
+       }
+       if l, ok := kit.Labels[IntegrationKitPriorityLabel]; ok {
+               if p, err := strconv.Atoi(l); err == nil {
+                       p2 = p
+               }
+       }
+       return p1 > p2
+}
+
 // GetCondition returns the condition with the provided type.
 func (in *IntegrationKitStatus) GetCondition(condType 
IntegrationKitConditionType) *IntegrationKitCondition {
        for i := range in.Conditions {
diff --git a/pkg/controller/integration/build_kit.go 
b/pkg/controller/integration/build_kit.go
index 348d3a5..3187512 100644
--- a/pkg/controller/integration/build_kit.go
+++ b/pkg/controller/integration/build_kit.go
@@ -20,10 +20,9 @@ package integration
 import (
        "context"
        "encoding/json"
-       "fmt"
+       "reflect"
 
        "github.com/pkg/errors"
-       "github.com/rs/xid"
 
        k8serrors "k8s.io/apimachinery/pkg/api/errors"
        "k8s.io/apimachinery/pkg/labels"
@@ -35,6 +34,7 @@ import (
        "github.com/apache/camel-k/pkg/platform"
        "github.com/apache/camel-k/pkg/trait"
        "github.com/apache/camel-k/pkg/util"
+       "github.com/apache/camel-k/pkg/util/defaults"
        "github.com/apache/camel-k/pkg/util/kubernetes"
 )
 
@@ -55,38 +55,32 @@ func (action *buildKitAction) CanHandle(integration 
*v1.Integration) bool {
 }
 
 func (action *buildKitAction) Handle(ctx context.Context, integration 
*v1.Integration) (*v1.Integration, error) {
-       kit, err := action.lookupKitForIntegration(ctx, action.client, 
integration)
-       if err != nil {
-               // TODO: we may need to add a wait strategy, i.e give up after 
some time
-               return nil, err
-       }
-
-       if kit != nil {
-               if kit.Labels[v1.IntegrationKitTypeLabel] == 
v1.IntegrationKitTypePlatform {
-                       // This is a platform kit and as it is auto generated 
it may get
-                       // out of sync if the integration that has generated 
it, has been
-                       // amended to add/remove dependencies
+       // TODO: we may need to add a timeout strategy, i.e give up after some 
time in case of an unrecoverable error.
 
-                       versionMatch := kit.Status.Version == 
integration.Status.Version
-
-                       // TODO: this is a very simple check, we may need to 
provide a deps comparison strategy
-                       dependenciesMatch := 
util.StringSliceContains(kit.Spec.Dependencies, integration.Status.Dependencies)
+       if integration.Status.IntegrationKit != nil {
+               kit, err := kubernetes.GetIntegrationKit(ctx, action.client, 
integration.Status.IntegrationKit.Name, 
integration.Status.IntegrationKit.Namespace)
+               if err != nil {
+                       return nil, errors.Wrapf(err, "unable to find 
integration kit %s/%s, %s", integration.Status.IntegrationKit.Namespace, 
integration.Status.IntegrationKit.Name, err)
+               }
 
-                       if !dependenciesMatch || !versionMatch {
-                               // We need to re-generate a kit or search for a 
new one that
-                               // satisfies integrations needs so let's remove 
the association
-                               // with a kit
+               if kit.Labels[v1.IntegrationKitTypeLabel] == 
v1.IntegrationKitTypePlatform {
+                       match, err := action.integrationMatches(integration, 
kit)
+                       if err != nil {
+                               return nil, err
+                       } else if !match {
+                               // We need to re-generate a kit, or search for 
a new one that
+                               // matches the integration, so let's remove the 
association
+                               // with the kit.
                                
integration.SetIntegrationKit(&v1.IntegrationKit{})
-
                                return integration, nil
                        }
+
                }
 
                if kit.Status.Phase == v1.IntegrationKitPhaseError {
                        integration.Status.Image = kit.Status.Image
                        integration.Status.Phase = v1.IntegrationPhaseError
                        integration.SetIntegrationKit(kit)
-
                        return integration, nil
                }
 
@@ -94,87 +88,59 @@ func (action *buildKitAction) Handle(ctx context.Context, 
integration *v1.Integr
                        integration.Status.Image = kit.Status.Image
                        integration.Status.Phase = v1.IntegrationPhaseDeploying
                        integration.SetIntegrationKit(kit)
-
-                       return integration, nil
-               }
-
-               if integration.Status.IntegrationKit == nil || 
integration.Status.IntegrationKit.Name == "" {
-                       integration.SetIntegrationKit(kit)
-
                        return integration, nil
                }
 
                return nil, nil
        }
 
-       pl, err := platform.GetCurrent(ctx, action.client, 
integration.Namespace)
-       if err != nil && !k8serrors.IsNotFound(err) {
+       existingKits, err := action.lookupKitsForIntegration(ctx, 
action.client, integration)
+       if err != nil {
                return nil, err
        }
 
-       kit = v1.NewIntegrationKit(integration.GetIntegrationKitNamespace(pl), 
fmt.Sprintf("kit-%s", xid.New()))
-
-       // Add some information for post-processing, this may need to be 
refactored
-       // to a proper data structure
-       kit.Labels = map[string]string{
-               v1.IntegrationKitTypeLabel:            
v1.IntegrationKitTypePlatform,
-               "camel.apache.org/runtime.version":    
integration.Status.RuntimeVersion,
-               "camel.apache.org/runtime.provider":   
string(integration.Status.RuntimeProvider),
-               kubernetes.CamelCreatorLabelKind:      v1.IntegrationKind,
-               kubernetes.CamelCreatorLabelName:      integration.Name,
-               kubernetes.CamelCreatorLabelNamespace: integration.Namespace,
-               kubernetes.CamelCreatorLabelVersion:   
integration.ResourceVersion,
-       }
-
-       // Set the kit to have the same characteristics as the integrations
-       kit.Spec = v1.IntegrationKitSpec{
-               Dependencies: integration.Status.Dependencies,
-               Repositories: integration.Spec.Repositories,
-               Traits:       action.filterKitTraits(ctx, 
integration.Spec.Traits),
-       }
-
-       if _, err := trait.Apply(ctx, action.client, integration, kit); err != 
nil {
+       env, err := trait.Apply(ctx, action.client, integration, nil)
+       if err != nil {
                return nil, err
        }
 
-       if err := action.client.Create(ctx, kit); err != nil {
-               return nil, err
+       var integrationKit *v1.IntegrationKit
+kits:
+       for i, kit := range env.IntegrationKits {
+               for j, k := range existingKits {
+                       match, err := action.kitMatches(&kit, &k)
+                       if err != nil {
+                               return nil, err
+                       }
+                       if match {
+                               if integrationKit == nil ||
+                                       integrationKit.Status.Phase != 
v1.IntegrationKitPhaseReady && k.Status.Phase == v1.IntegrationKitPhaseReady ||
+                                       integrationKit.Status.Phase == 
v1.IntegrationKitPhaseReady && k.Status.Phase == v1.IntegrationKitPhaseReady && 
k.HasHigherPriorityThan(integrationKit) {
+                                       integrationKit = &existingKits[j]
+                               }
+                               continue kits
+                       }
+               }
+               if err := action.client.Create(ctx, &kit); err != nil {
+                       return nil, err
+               }
+               if integrationKit == nil {
+                       integrationKit = &env.IntegrationKits[i]
+               }
        }
 
        // Set the kit name so the next handle loop, will fall through the
        // same path as integration with a user defined kit
-       integration.SetIntegrationKit(kit)
+       if integrationKit.Status.Phase == v1.IntegrationKitPhaseReady {
+               integration.Status.Image = integrationKit.Status.Image
+               integration.Status.Phase = v1.IntegrationPhaseDeploying
+       }
+       integration.SetIntegrationKit(integrationKit)
 
        return integration, nil
 }
 
-func (action *buildKitAction) filterKitTraits(ctx context.Context, in 
map[string]v1.TraitSpec) map[string]v1.TraitSpec {
-       if len(in) == 0 {
-               return in
-       }
-       catalog := trait.NewCatalog(ctx, action.client)
-       out := make(map[string]v1.TraitSpec)
-       for name, conf := range in {
-               t := catalog.GetTrait(name)
-               if t != nil && !t.InfluencesKit() {
-                       // We don't store the trait configuration if the trait 
cannot influence the kit behavior
-                       continue
-               }
-               out[name] = conf
-       }
-       return out
-}
-
-func (action *buildKitAction) lookupKitForIntegration(ctx context.Context, c 
ctrl.Reader, integration *v1.Integration) (*v1.IntegrationKit, error) {
-       if integration.Status.IntegrationKit != nil {
-               kit, err := kubernetes.GetIntegrationKit(ctx, c, 
integration.Status.IntegrationKit.Name, 
integration.Status.IntegrationKit.Namespace)
-               if err != nil {
-                       return nil, errors.Wrapf(err, "unable to find 
integration kit %s/%s, %s", integration.Status.IntegrationKit.Namespace, 
integration.Status.IntegrationKit.Name, err)
-               }
-
-               return kit, nil
-       }
-
+func (action *buildKitAction) lookupKitsForIntegration(ctx context.Context, c 
ctrl.Reader, integration *v1.Integration) ([]v1.IntegrationKit, error) {
        pl, err := platform.GetCurrent(ctx, c, integration.Namespace)
        if err != nil && !k8serrors.IsNotFound(err) {
                return nil, err
@@ -199,111 +165,181 @@ func (action *buildKitAction) 
lookupKitForIntegration(ctx context.Context, c ctr
                },
        }
 
-       kits := v1.NewIntegrationKitList()
-       if err := c.List(ctx, &kits, options...); err != nil {
+       list := v1.NewIntegrationKitList()
+       if err := c.List(ctx, &list, options...); err != nil {
                return nil, err
        }
 
-       for _, kit := range kits.Items {
-               kit := kit // pin
-
-               if kit.Status.Phase == v1.IntegrationKitPhaseError {
-                       continue
-               }
-
-               /*
-                       TODO: moved to label selector
-                       if kit.Status.RuntimeVersion != 
integration.Status.RuntimeVersion {
-                               continue
-                       }
-                       if kit.Status.RuntimeProvider != 
integration.Status.RuntimeProvider {
-                               continue
-                       }
-               */
-
-               if kit.Status.Version != integration.Status.Version {
-                       continue
-               }
-
-               ideps := len(integration.Status.Dependencies)
-               cdeps := len(kit.Spec.Dependencies)
-
-               if ideps != cdeps {
-                       continue
-               }
-
-               // When a platform kit 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 kits.
-               //
-               // 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 kit that does 
not have all the
-               // characteristics required by the integration.
-               //
-               // A kit can be used only if it contains a subset of the traits 
and related configurations
-               // declared on integration.
-               match, err := action.hasMatchingTraits(ctx, &kit, integration)
+       kits := make([]v1.IntegrationKit, 0)
+       for _, kit := range list.Items {
+               match, err := action.integrationMatches(integration, &kit)
                if err != nil {
                        return nil, err
-               }
-               if !match {
+               } else if !match {
                        continue
                }
-               if util.StringSliceContains(kit.Spec.Dependencies, 
integration.Status.Dependencies) {
-                       return &kit, nil
-               }
+               kits = append(kits, kit)
        }
 
-       return nil, nil
+       return kits, nil
 }
 
-// hasMatchingTraits compares traits defined on kit against those defined on 
integration
-func (action *buildKitAction) hasMatchingTraits(ctx context.Context, kit 
*v1.IntegrationKit, integration *v1.Integration) (bool, error) {
-       traits := action.filterKitTraits(ctx, integration.Spec.Traits)
+// integrationMatches returns whether the v1.IntegrationKit meets the 
requirements of the v1.Integration
+func (action *buildKitAction) integrationMatches(integration *v1.Integration, 
kit *v1.IntegrationKit) (bool, error) {
+       if kit.Status.Phase == v1.IntegrationKitPhaseError {
+               return false, nil
+       }
+       if kit.Status.Version != integration.Status.Version {
+               return false, nil
+       }
+       if len(integration.Status.Dependencies) != len(kit.Spec.Dependencies) {
+               return false, nil
+       }
+       // When a platform kit 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 
kits.
+       //
+       // 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 kit that does not 
have all the
+       // characteristics required by the integration.
+       //
+       // A kit can be used only if it contains a subset of the traits and 
related configurations
+       // declared on integration.
+       if match, err := action.hasMatchingTraits(integration, kit); !match || 
err != nil {
+               return false, err
+       }
+       if !util.StringSliceContains(kit.Spec.Dependencies, 
integration.Status.Dependencies) {
+               return false, nil
+       }
+       return true, nil
+}
 
-       // The kit has no trait, but the integration need some
-       if len(kit.Spec.Traits) == 0 && len(traits) > 0 {
+// kitMatches returns whether the v1.IntegrationKit match
+func (action *buildKitAction) kitMatches(k1 *v1.IntegrationKit, k2 
*v1.IntegrationKit) (bool, error) {
+       version := k1.Status.Version
+       if version == "" {
+               // Defaults with the version that is going to be set during the 
kit initialization
+               version = defaults.Version
+       }
+       if version != k2.Status.Version {
+               return false, nil
+       }
+       if len(k1.Spec.Dependencies) != len(k2.Spec.Dependencies) {
                return false, nil
        }
-       for name, kitTrait := range kit.Spec.Traits {
-               itTrait, ok := traits[name]
+       if len(k1.Spec.Traits) != len(k2.Spec.Traits) {
+               return false, nil
+       }
+       for name, kt1 := range k1.Spec.Traits {
+               kt2, ok := k2.Spec.Traits[name]
                if !ok {
-                       // skip it because trait configured on kit is not 
defined on integration
                        return false, nil
                }
-               data, err := json.Marshal(itTrait.Configuration)
-               if err != nil {
+               match, err := action.hasMatchingTrait(&kt1, &kt2)
+               if !match || err != nil {
                        return false, err
                }
-               itConf := make(map[string]interface{})
-               err = json.Unmarshal(data, &itConf)
-               if err != nil {
-                       return false, err
-               }
-               data, err = json.Marshal(kitTrait.Configuration)
-               if err != nil {
-                       return false, err
+       }
+       if !util.StringSliceContains(k1.Spec.Dependencies, 
k2.Spec.Dependencies) {
+               return false, nil
+       }
+       return true, nil
+}
+
+// hasMatchingTraits compares the traits defined on the v1.Integration with 
those defined on the v1.IntegrationKit
+func (action *buildKitAction) hasMatchingTraits(integration *v1.Integration, 
kit *v1.IntegrationKit) (bool, error) {
+       catalog := trait.NewCatalog(context.TODO(), action.client)
+
+       traitCount := 0
+       for name, itTrait := range integration.Spec.Traits {
+               t := catalog.GetTrait(name)
+               if t != nil && !t.InfluencesKit() {
+                       // We don't store the trait configuration if the trait 
cannot influence the kit behavior
+                       continue
                }
-               kitConf := make(map[string]interface{})
-               err = json.Unmarshal(data, &kitConf)
-               if err != nil {
-                       return false, err
+               traitCount++
+               kitTrait, ok := kit.Spec.Traits[name]
+               if !ok {
+                       // skip it because trait configured on integration is 
not defined on kit
+                       return false, nil
                }
-               for ck, cv := range kitConf {
-                       iv, ok := itConf[ck]
-                       if !ok {
-                               // skip it because trait configured on kit has 
a value that is not defined
-                               // in integration trait
-                               return false, nil
+               if ct, ok := t.(trait.ComparableTrait); ok {
+                       comparable, err := action.hasComparableTrait(ct, 
&itTrait, &kitTrait)
+                       if !comparable || err != nil {
+                               return false, err
                        }
-                       if !equal(iv, cv) {
-                               // skip it because trait configured on kit has 
a value that differs from
-                               // the one configured on integration
-                               return false, nil
+               } else {
+                       match, err := action.hasMatchingTrait(&itTrait, 
&kitTrait)
+                       if !match || err != nil {
+                               return false, err
                        }
                }
        }
 
+       // Check the number of influencing traits matches
+       if len(kit.Spec.Traits) != traitCount {
+               return false, nil
+       }
+
+       return true, nil
+}
+
+func (action *buildKitAction) hasComparableTrait(c trait.ComparableTrait, 
itTrait *v1.TraitSpec, kitTrait *v1.TraitSpec) (bool, error) {
+       it := reflect.New(reflect.TypeOf(c).Elem()).Interface()
+       data, err := json.Marshal(itTrait.Configuration)
+       if err != nil {
+               return false, err
+       }
+       err = json.Unmarshal(data, &it)
+       if err != nil {
+               return false, err
+       }
+
+       kt := reflect.New(reflect.TypeOf(c).Elem()).Interface()
+       data, err = json.Marshal(kitTrait.Configuration)
+       if err != nil {
+               return false, err
+       }
+       err = json.Unmarshal(data, &it)
+       if err != nil {
+               return false, err
+       }
+
+       return kt.(trait.ComparableTrait).Matches(it.(trait.Trait)), nil
+}
+
+func (action *buildKitAction) hasMatchingTrait(itTrait *v1.TraitSpec, kitTrait 
*v1.TraitSpec) (bool, error) {
+       data, err := json.Marshal(itTrait.Configuration)
+       if err != nil {
+               return false, err
+       }
+       itConf := make(map[string]interface{})
+       err = json.Unmarshal(data, &itConf)
+       if err != nil {
+               return false, err
+       }
+       data, err = json.Marshal(kitTrait.Configuration)
+       if err != nil {
+               return false, err
+       }
+       kitConf := make(map[string]interface{})
+       err = json.Unmarshal(data, &kitConf)
+       if err != nil {
+               return false, err
+       }
+       for ck, cv := range kitConf {
+               iv, ok := itConf[ck]
+               if !ok {
+                       // skip it because trait configured on kit has a value 
that is not defined
+                       // in integration trait
+                       return false, nil
+               }
+               if !equal(iv, cv) {
+                       // skip it because trait configured on kit has a value 
that differs from
+                       // the one configured on integration
+                       return false, nil
+               }
+       }
        return true, nil
 }
 
diff --git a/pkg/controller/integration/build_kit_test.go 
b/pkg/controller/integration/build_kit_test.go
index fa3df64..a1538e3 100644
--- a/pkg/controller/integration/build_kit_test.go
+++ b/pkg/controller/integration/build_kit_test.go
@@ -84,7 +84,7 @@ func TestLookupKitForIntegration_DiscardKitsInError(t 
*testing.T) {
        a.InjectLogger(log.Log)
        a.InjectClient(c)
 
-       i, err := a.lookupKitForIntegration(context.TODO(), c, &v1.Integration{
+       kits, err := a.lookupKitsForIntegration(context.TODO(), c, 
&v1.Integration{
                TypeMeta: metav1.TypeMeta{
                        APIVersion: v1.SchemeGroupVersion.String(),
                        Kind:       v1.IntegrationKind,
@@ -102,8 +102,9 @@ func TestLookupKitForIntegration_DiscardKitsInError(t 
*testing.T) {
        })
 
        assert.Nil(t, err)
-       assert.NotNil(t, i)
-       assert.Equal(t, "my-kit-2", i.Name)
+       assert.NotNil(t, kits)
+       assert.Len(t, kits, 1)
+       assert.Equal(t, "my-kit-2", kits[0].Name)
 }
 
 func TestLookupKitForIntegration_DiscardKitsWithIncompatibleTraits(t 
*testing.T) {
@@ -206,7 +207,7 @@ func 
TestLookupKitForIntegration_DiscardKitsWithIncompatibleTraits(t *testing.T)
        a.InjectLogger(log.Log)
        a.InjectClient(c)
 
-       i, err := a.lookupKitForIntegration(context.TODO(), c, &v1.Integration{
+       kits, err := a.lookupKitsForIntegration(context.TODO(), c, 
&v1.Integration{
                TypeMeta: metav1.TypeMeta{
                        APIVersion: v1.SchemeGroupVersion.String(),
                        Kind:       v1.IntegrationKind,
@@ -234,8 +235,9 @@ func 
TestLookupKitForIntegration_DiscardKitsWithIncompatibleTraits(t *testing.T)
        })
 
        assert.Nil(t, err)
-       assert.NotNil(t, i)
-       assert.Equal(t, "my-kit-3", i.Name)
+       assert.NotNil(t, kits)
+       assert.Len(t, kits, 1)
+       assert.Equal(t, "my-kit-3", kits[0].Name)
 }
 
 func TestHasMatchingTraits_KitNoTraitShouldNotBePicked(t *testing.T) {
@@ -274,7 +276,7 @@ func TestHasMatchingTraits_KitNoTraitShouldNotBePicked(t 
*testing.T) {
        a := buildKitAction{}
        a.InjectLogger(log.Log)
 
-       ok, err := a.hasMatchingTraits(context.TODO(), integrationKitSpec, 
integration)
+       ok, err := a.hasMatchingTraits(integration, integrationKitSpec)
        assert.Nil(t, err)
        assert.False(t, ok)
 }
@@ -325,7 +327,7 @@ func TestHasMatchingTraits_KitSameTraitShouldBePicked(t 
*testing.T) {
        a := buildKitAction{}
        a.InjectLogger(log.Log)
 
-       ok, err := a.hasMatchingTraits(context.TODO(), integrationKitSpec, 
integration)
+       ok, err := a.hasMatchingTraits(integration, integrationKitSpec)
        assert.Nil(t, err)
        assert.True(t, ok)
 }
diff --git a/pkg/controller/integrationkit/build.go 
b/pkg/controller/integrationkit/build.go
index 62b6820..9a8c390 100644
--- a/pkg/controller/integrationkit/build.go
+++ b/pkg/controller/integrationkit/build.go
@@ -44,7 +44,7 @@ type buildAction struct {
 }
 
 func (action *buildAction) Name() string {
-       return "build-submitted"
+       return "build"
 }
 
 func (action *buildAction) CanHandle(kit *v1.IntegrationKit) bool {
diff --git a/pkg/trait/quarkus.go b/pkg/trait/quarkus.go
index bb38039..2e47f5b 100644
--- a/pkg/trait/quarkus.go
+++ b/pkg/trait/quarkus.go
@@ -18,12 +18,16 @@ limitations under the License.
 package trait
 
 import (
+       "encoding/json"
        "fmt"
        "sort"
 
+       "github.com/rs/xid"
+
        v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
        "github.com/apache/camel-k/pkg/builder"
        "github.com/apache/camel-k/pkg/util/defaults"
+       "github.com/apache/camel-k/pkg/util/kubernetes"
 )
 
 type quarkusPackageType string
@@ -35,6 +39,11 @@ const (
        nativePackageType  quarkusPackageType = "native"
 )
 
+var kitPriority = map[quarkusPackageType]string{
+       fastJarPackageType: "1000",
+       nativePackageType:  "2000",
+}
+
 // The Quarkus trait configures the Quarkus runtime.
 //
 // It's enabled by default.
@@ -42,8 +51,13 @@ const (
 // +camel-k:trait=quarkus
 type quarkusTrait struct {
        BaseTrait `property:",squash"`
-       // The Quarkus package type, either `fast-jar` or `native` (default 
`fast-jar`)
-       PackageType *quarkusPackageType `property:"package-type" 
json:"packageType,omitempty"`
+       // The Quarkus package types, either `fast-jar` or `native` (default 
`fast-jar`).
+       // In case both `fast-jar` and `native` are specified, two 
IntegrationKits are created,
+       // with the `native` kit having precedence over the `fast-jar' one once 
ready.
+       // The order influences the resolution of the current IntegrationKit 
for the Integration.
+       // The IntegrationKit corresponding to the first package type will be 
assigned to the
+       // Integration in case no existing IntegrationKit that matches the 
Integration exists.
+       PackageTypes []quarkusPackageType `property:"package-type" 
json:"packageTypes,omitempty"`
 }
 
 func newQuarkusTrait() Trait {
@@ -52,30 +66,109 @@ func newQuarkusTrait() Trait {
        }
 }
 
+// IsPlatformTrait overrides base class method
+func (t *quarkusTrait) IsPlatformTrait() bool {
+       return true
+}
+
+// InfluencesKit overrides base class method
+func (t *quarkusTrait) InfluencesKit() bool {
+       return true
+}
+
+var _ ComparableTrait = &quarkusTrait{}
+
+func (t *quarkusTrait) Matches(trait Trait) bool {
+       qt, ok := trait.(*quarkusTrait)
+       if !ok {
+               return false
+       }
+
+       if IsNilOrTrue(t.Enabled) && IsFalse(qt.Enabled) {
+               return false
+       }
+
+types:
+       for _, p1 := range t.PackageTypes {
+               for _, p2 := range qt.PackageTypes {
+                       if p1 == p2 {
+                               continue types
+                       }
+               }
+               return false
+       }
+
+       return true
+}
+
 func (t *quarkusTrait) Configure(e *Environment) (bool, error) {
        if IsFalse(t.Enabled) {
                return false, nil
        }
 
-       if t.PackageType == nil {
-               packageType := fastJarPackageType
-               t.PackageType = &packageType
+       if len(t.PackageTypes) == 0 {
+               t.PackageTypes = []quarkusPackageType{fastJarPackageType}
        }
 
-       return e.IntegrationKitInPhase(v1.IntegrationKitPhaseNone, 
v1.IntegrationKitPhaseBuildSubmitted) ||
+       return e.IntegrationInPhase(v1.IntegrationPhaseBuildingKit) ||
+                       
e.IntegrationKitInPhase(v1.IntegrationKitPhaseBuildSubmitted) ||
                        e.InPhase(v1.IntegrationKitPhaseReady, 
v1.IntegrationPhaseDeploying) ||
                        e.InPhase(v1.IntegrationKitPhaseReady, 
v1.IntegrationPhaseRunning),
                nil
 }
 
 func (t *quarkusTrait) Apply(e *Environment) error {
-       switch e.IntegrationKit.Status.Phase {
+       if e.IntegrationInPhase(v1.IntegrationPhaseBuildingKit) {
+               integration := e.Integration
+
+               for _, packageType := range t.PackageTypes {
+                       kit := 
v1.NewIntegrationKit(integration.GetIntegrationKitNamespace(e.Platform), 
fmt.Sprintf("kit-%s", xid.New()))
+
+                       kit.Labels = map[string]string{
+                               v1.IntegrationKitTypeLabel:            
v1.IntegrationKitTypePlatform,
+                               "camel.apache.org/runtime.version":    
integration.Status.RuntimeVersion,
+                               "camel.apache.org/runtime.provider":   
string(integration.Status.RuntimeProvider),
+                               v1.IntegrationKitLayoutLabel:          
string(packageType),
+                               v1.IntegrationKitPriorityLabel:        
kitPriority[packageType],
+                               kubernetes.CamelCreatorLabelKind:      
v1.IntegrationKind,
+                               kubernetes.CamelCreatorLabelName:      
integration.Name,
+                               kubernetes.CamelCreatorLabelNamespace: 
integration.Namespace,
+                               kubernetes.CamelCreatorLabelVersion:   
integration.ResourceVersion,
+                       }
 
-       case v1.IntegrationKitPhaseNone:
-               if e.IntegrationKit.Labels == nil {
-                       e.IntegrationKit.Labels = make(map[string]string)
+                       traits := t.getKitTraits(e)
+                       data, err := 
json.Marshal(traits[quarkusTraitId].Configuration)
+                       if err != nil {
+                               return err
+                       }
+                       trait := quarkusTrait{}
+                       err = json.Unmarshal(data, &trait)
+                       if err != nil {
+                               return err
+                       }
+                       trait.PackageTypes = []quarkusPackageType{packageType}
+                       data, err = json.Marshal(trait)
+                       if err != nil {
+                               return err
+                       }
+                       traits[quarkusTraitId] = v1.TraitSpec{
+                               Configuration: v1.TraitConfiguration{
+                                       RawMessage: data,
+                               },
+                       }
+                       kit.Spec = v1.IntegrationKitSpec{
+                               Dependencies: e.Integration.Status.Dependencies,
+                               Repositories: e.Integration.Spec.Repositories,
+                               Traits:       traits,
+                       }
+
+                       e.IntegrationKits = append(e.IntegrationKits, *kit)
                }
-               e.IntegrationKit.Labels[v1.IntegrationKitLayoutLabel] = 
string(*t.PackageType)
+
+               return nil
+       }
+
+       switch e.IntegrationKit.Status.Phase {
 
        case v1.IntegrationKitPhaseBuildSubmitted:
                build := getBuilderTask(e.BuildTasks)
@@ -94,7 +187,7 @@ func (t *quarkusTrait) Apply(e *Environment) error {
 
                steps = append(steps, builder.Quarkus.CommonSteps...)
 
-               if t.isNativePackageType() {
+               if t.hasNativePackageType(e) {
                        build.Maven.Properties["quarkus.package.type"] = 
string(nativePackageType)
                        steps = append(steps, builder.Image.NativeImageContext)
                        // Spectrum does not rely on Dockerfile to assemble the 
image
@@ -118,7 +211,7 @@ func (t *quarkusTrait) Apply(e *Environment) error {
                build.Steps = builder.StepIDsFor(steps...)
 
        case v1.IntegrationKitPhaseReady:
-               if e.IntegrationInPhase(v1.IntegrationPhaseDeploying, 
v1.IntegrationPhaseRunning) && t.isNativePackageType() {
+               if e.IntegrationInPhase(v1.IntegrationPhaseDeploying, 
v1.IntegrationPhaseRunning) && t.hasNativePackageType(e) {
                        container := e.getIntegrationContainer()
                        if container == nil {
                                return fmt.Errorf("unable to find integration 
container: %s", e.Integration.Name)
@@ -132,18 +225,29 @@ func (t *quarkusTrait) Apply(e *Environment) error {
        return nil
 }
 
-// IsPlatformTrait overrides base class method
-func (t *quarkusTrait) IsPlatformTrait() bool {
-       return true
-}
-
-// InfluencesKit overrides base class method
-func (t *quarkusTrait) InfluencesKit() bool {
-       return true
+func (t *quarkusTrait) getKitTraits(e *Environment) map[string]v1.TraitSpec {
+       traits := make(map[string]v1.TraitSpec)
+       for name, spec := range e.Integration.Spec.Traits {
+               t := e.Catalog.GetTrait(name)
+               if t != nil && !t.InfluencesKit() {
+                       continue
+               }
+               traits[name] = spec
+       }
+       return traits
 }
 
-func (t *quarkusTrait) isNativePackageType() bool {
-       return t.PackageType != nil && *t.PackageType == nativePackageType
+func (t *quarkusTrait) hasNativePackageType(e *Environment) bool {
+       switch types := t.PackageTypes; len(types) {
+       case 0:
+               return false
+       case 1:
+               return types[0] == nativePackageType
+       default:
+               // The Integration has more than one package types.
+               // Let's rely on the current IntegrationKit to resolve it.
+               return e.IntegrationKit.Labels[v1.IntegrationKitLayoutLabel] == 
v1.IntegrationKitLayoutNative
+       }
 }
 
 func getBuilderTask(tasks []v1.Task) *v1.BuilderTask {
diff --git a/pkg/trait/quarkus_test.go b/pkg/trait/quarkus_test.go
index e09cc2b..f7c60d3 100644
--- a/pkg/trait/quarkus_test.go
+++ b/pkg/trait/quarkus_test.go
@@ -57,6 +57,7 @@ func TestConfigureDisabledQuarkusTraitShouldFail(t 
*testing.T) {
 
 func TestApplyQuarkusTraitDefaultKitLayout(t *testing.T) {
        quarkusTrait, environment := createNominalQuarkusTest()
+       environment.Integration.Status.Phase = v1.IntegrationPhaseBuildingKit
 
        configured, err := quarkusTrait.Configure(environment)
        assert.True(t, configured)
@@ -64,7 +65,8 @@ func TestApplyQuarkusTraitDefaultKitLayout(t *testing.T) {
 
        err = quarkusTrait.Apply(environment)
        assert.Nil(t, err)
-       assert.Equal(t, 
environment.IntegrationKit.Labels[v1.IntegrationKitLayoutLabel], 
v1.IntegrationKitLayoutFastJar)
+       assert.Len(t, environment.IntegrationKits, 1)
+       assert.Equal(t, 
environment.IntegrationKits[0].Labels[v1.IntegrationKitLayoutLabel], 
v1.IntegrationKitLayoutFastJar)
 }
 
 func createNominalQuarkusTest() (*quarkusTrait, *Environment) {
diff --git a/pkg/trait/trait_configure.go b/pkg/trait/trait_configure.go
index f255053..952a96c 100644
--- a/pkg/trait/trait_configure.go
+++ b/pkg/trait/trait_configure.go
@@ -23,9 +23,10 @@ import (
        "reflect"
        "strings"
 
-       v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
        "github.com/mitchellh/mapstructure"
        "github.com/pkg/errors"
+
+       v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
 )
 
 func (c *Catalog) configure(env *Environment) error {
@@ -149,7 +150,7 @@ func configureTrait(id string, config map[string]string, 
trait interface{}) erro
        )
 
        if err != nil {
-               return errors.Wrapf(err, "error while decoding trait 
configuration from annotations on trait %q", id)
+               return errors.Wrapf(err, "error while decoding trait 
configuration %q", id)
        }
 
        return decoder.Decode(config)
diff --git a/pkg/trait/trait_types.go b/pkg/trait/trait_types.go
index fc6d0f0..2ab9296 100644
--- a/pkg/trait/trait_types.go
+++ b/pkg/trait/trait_types.go
@@ -97,6 +97,15 @@ type Trait interface {
        Order() int
 }
 
+type Comparable interface {
+       Matches(Trait) bool
+}
+
+type ComparableTrait interface {
+       Trait
+       Comparable
+}
+
 // A list of named orders, useful for correctly binding addons
 const (
        // TraitOrderBeforeControllerCreation can be used to inject 
configuration such as properties and environment variables
@@ -110,9 +119,6 @@ const (
        TraitOrderPostProcessResources = 2450
 )
 
-/* Base trait */
-
-// NewBaseTrait --
 func NewBaseTrait(id string, order int) BaseTrait {
        return BaseTrait{
                TraitID:        ID(id),
@@ -173,8 +179,6 @@ func (trait *BaseTrait) Order() int {
        return trait.ExecutionOrder
 }
 
-/* ControllerStrategySelector */
-
 // ControllerStrategySelector is the interface for traits that can determine 
the kind of controller that will run the integration.
 type ControllerStrategySelector interface {
        // SelectControllerStrategy tells if the trait with current 
configuration can select a specific controller to use
@@ -183,18 +187,22 @@ type ControllerStrategySelector interface {
        ControllerStrategySelectorOrder() int
 }
 
-/* Environment */
-
-// A Environment provides the context where the trait is executed
+// An Environment provides the context for the execution of the traits
 type Environment struct {
-       CamelCatalog          *camel.RuntimeCatalog
-       RuntimeVersion        string
-       Catalog               *Catalog
-       C                     context.Context
-       Client                client.Client
-       Platform              *v1.IntegrationPlatform
-       IntegrationKit        *v1.IntegrationKit
-       Integration           *v1.Integration
+       CamelCatalog   *camel.RuntimeCatalog
+       RuntimeVersion string
+       Catalog        *Catalog
+       C              context.Context
+       Client         client.Client
+       // The active Platform
+       Platform *v1.IntegrationPlatform
+       // The current Integration
+       Integration *v1.Integration
+       // The IntegrationKit associated to the Integration
+       IntegrationKit *v1.IntegrationKit
+       // The IntegrationKits to be created for the Integration
+       IntegrationKits []v1.IntegrationKit
+       // The resources owned by the Integration that are applied to the API 
server
        Resources             *kubernetes.Collection
        PostActions           []func(*Environment) error
        PostStepProcessors    []func(*Environment) error
@@ -220,7 +228,6 @@ const (
        DefaultControllerStrategy = ControllerStrategyDeployment
 )
 
-// GetTrait --
 func (e *Environment) GetTrait(id ID) Trait {
        for _, t := range e.ExecutedTraits {
                if t.ID() == id {
@@ -231,7 +238,6 @@ func (e *Environment) GetTrait(id ID) Trait {
        return nil
 }
 
-// IntegrationInPhase --
 func (e *Environment) IntegrationInPhase(phases ...v1.IntegrationPhase) bool {
        if e.Integration == nil {
                return false
@@ -246,7 +252,6 @@ func (e *Environment) IntegrationInPhase(phases 
...v1.IntegrationPhase) bool {
        return false
 }
 
-// IntegrationKitInPhase --
 func (e *Environment) IntegrationKitInPhase(phases ...v1.IntegrationKitPhase) 
bool {
        if e.IntegrationKit == nil {
                return false
@@ -261,7 +266,6 @@ func (e *Environment) IntegrationKitInPhase(phases 
...v1.IntegrationKitPhase) bo
        return false
 }
 
-// InPhase --
 func (e *Environment) InPhase(c v1.IntegrationKitPhase, i v1.IntegrationPhase) 
bool {
        return e.IntegrationKitInPhase(c) && e.IntegrationInPhase(i)
 }
@@ -346,7 +350,6 @@ func (e *Environment) GetIntegrationPodSpec() 
*corev1.PodSpec {
        return nil
 }
 
-// DetermineCatalogNamespace --
 func (e *Environment) DetermineCatalogNamespace() string {
        // Catalog is expected to be together with the platform
        if e.Platform != nil && e.Platform.Namespace != "" {

Reply via email to