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 != "" {
