pkg/trait/quarkus.go (402 lines of code) (raw):

/* Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package trait import ( "fmt" "sort" "github.com/rs/xid" corev1 "k8s.io/api/core/v1" "k8s.io/utils/ptr" 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/builder" "github.com/apache/camel-k/v2/pkg/util/boolean" "github.com/apache/camel-k/v2/pkg/util/defaults" "github.com/apache/camel-k/v2/pkg/util/kubernetes" "github.com/apache/camel-k/v2/pkg/util/log" ) const ( quarkusTraitID = "quarkus" quarkusTraitOrder = 1700 fastJarPackageType quarkusPackageType = "fast-jar" nativeSourcesPackageType quarkusPackageType = "native-sources" QuarkusNativeDefaultBaseImageName = "quay.io/quarkus/quarkus-micro-image:2.0" ) type quarkusPackageType string var kitPriority = map[quarkusPackageType]string{ fastJarPackageType: "1000", nativeSourcesPackageType: "2000", } type quarkusTrait struct { BasePlatformTrait traitv1.QuarkusTrait `property:",squash"` } type languageSettings struct { // indicates whether the language is supported deprecated bool // indicates whether the native mode is supported native bool // indicates whether the sources are required at build time for native compilation sourcesRequiredAtBuildTime bool } var ( // settings for an unknown language. defaultSettings = languageSettings{false, false, false} // settings for languages supporting native mode for old catalogs. nativeSupportSettings = languageSettings{false, true, false} ) // Retrieves the settings of the given language from the Camel catalog. func getLanguageSettings(e *Environment, language v1.Language) languageSettings { if loader, ok := e.CamelCatalog.Loaders[string(language)]; ok { native, nExists := loader.Metadata["native"] if !nExists { return getLegacyLanguageSettings(language) } sourcesRequiredAtBuildTime, sExists := loader.Metadata["sources-required-at-build-time"] deprecated, dpExists := loader.Metadata["deprecated"] return languageSettings{ native: native == boolean.TrueString, sourcesRequiredAtBuildTime: sExists && sourcesRequiredAtBuildTime == boolean.TrueString, deprecated: dpExists && deprecated == boolean.TrueString, } } return getLegacyLanguageSettings(language) } // Provides the legacy settings of a given language. func getLegacyLanguageSettings(language v1.Language) languageSettings { switch language { case v1.LanguageXML, v1.LanguageYaml, v1.LanguageKamelet: return nativeSupportSettings default: return defaultSettings } } func newQuarkusTrait() Trait { return &quarkusTrait{ BasePlatformTrait: NewBasePlatformTrait(quarkusTraitID, quarkusTraitOrder), } } // InfluencesKit overrides base class method. func (t *quarkusTrait) InfluencesKit() bool { return true } func (t *quarkusTrait) Matches(trait Trait) bool { qt, ok := trait.(*quarkusTrait) if !ok { return false } if len(t.Modes) == 0 && len(qt.Modes) != 0 && !qt.containsMode(traitv1.JvmQuarkusMode) { return false } for _, md := range t.Modes { if md == traitv1.JvmQuarkusMode && len(qt.Modes) == 0 { continue } if qt.containsMode(md) { continue } return false } // We need to check if the native base image used is the same thisNativeBaseImage := t.NativeBaseImage if thisNativeBaseImage == "" { thisNativeBaseImage = QuarkusNativeDefaultBaseImageName } otherNativeBaseImage := qt.NativeBaseImage if otherNativeBaseImage == "" { otherNativeBaseImage = QuarkusNativeDefaultBaseImageName } return thisNativeBaseImage == otherNativeBaseImage } func (t *quarkusTrait) Configure(e *Environment) (bool, *TraitCondition, error) { condition := t.adaptDeprecatedFields() if t.languageSettingDeprecated(e) { message := "The sources contains some language marked as deprecated. This Integration may not be supported in future release." if condition == nil { condition = NewIntegrationCondition( "Quarkus", v1.IntegrationConditionTraitInfo, corev1.ConditionTrue, TraitConfigurationReason, message) } else { condition.message += message } } if t.containsMode(traitv1.NativeQuarkusMode) && e.IntegrationInPhase(v1.IntegrationPhaseBuildingKit) { // Native compilation is only supported for a subset of languages, // so let's check for compatibility, and fail-fast the Integration, // to save compute resources and user time. if err := t.validateNativeSupport(e); err != nil { return false, nil, err } } return e.IntegrationInPhase(v1.IntegrationPhaseBuildingKit) || e.IntegrationKitInPhase(v1.IntegrationKitPhaseBuildSubmitted) || e.IntegrationKitInPhase(v1.IntegrationKitPhaseReady) && e.IntegrationInRunningPhases(), condition, nil } func (t *quarkusTrait) adaptDeprecatedFields() *TraitCondition { if t.PackageTypes != nil { message := "The package-type parameter is deprecated and may be removed in future releases. Make sure to use mode parameter instead." t.L.Info(message) for _, pt := range t.PackageTypes { if pt == traitv1.NativePackageType { t.Modes = append(t.Modes, traitv1.NativeQuarkusMode) continue } if pt == traitv1.FastJarPackageType { t.Modes = append(t.Modes, traitv1.JvmQuarkusMode) } } return NewIntegrationCondition("Quarkus", v1.IntegrationConditionTraitInfo, corev1.ConditionTrue, TraitConfigurationReason, message) } return nil } func (t *quarkusTrait) languageSettingDeprecated(e *Environment) bool { if e.Integration == nil { return false } for _, source := range e.Integration.AllSources() { if language := source.InferLanguage(); getLanguageSettings(e, language).deprecated { return true } } return false } func (t *quarkusTrait) validateNativeSupport(e *Environment) error { for _, source := range e.Integration.AllSources() { if language := source.InferLanguage(); !getLanguageSettings(e, language).native { return fmt.Errorf("invalid native support: Integration %s/%s contains a %s source that cannot be compiled to native executable", e.Integration.Namespace, e.Integration.Name, language) } } return nil } func (t *quarkusTrait) Apply(e *Environment) error { if e.IntegrationInPhase(v1.IntegrationPhaseBuildingKit) { t.applyWhileBuildingKit(e) return nil } switch e.IntegrationKit.Status.Phase { case v1.IntegrationKitPhaseBuildSubmitted: if err := t.applyWhenBuildSubmitted(e); err != nil { return err } case v1.IntegrationKitPhaseReady: if err := t.applyWhenKitReady(e); err != nil { return err } } return nil } func (t *quarkusTrait) applyWhileBuildingKit(e *Environment) { switch len(t.Modes) { case 0: // Default behavior kit := t.newIntegrationKit(e, fastJarPackageType) e.IntegrationKits = append(e.IntegrationKits, *kit) case 1: kit := t.newIntegrationKit(e, packageType(t.Modes[0])) e.IntegrationKits = append(e.IntegrationKits, *kit) default: // execute jvm mode before native mode sort.Slice(t.Modes, func(i, j int) bool { return t.Modes[i] != traitv1.NativeQuarkusMode }) for _, md := range t.Modes { kit := t.newIntegrationKit(e, packageType(md)) if kit.Spec.Traits.Quarkus == nil { kit.Spec.Traits.Quarkus = &traitv1.QuarkusTrait{} } kit.Spec.Traits.Quarkus.Modes = []traitv1.QuarkusMode{md} e.IntegrationKits = append(e.IntegrationKits, *kit) } } } func (t *quarkusTrait) newIntegrationKit(e *Environment, packageType quarkusPackageType) *v1.IntegrationKit { integration := e.Integration kit := v1.NewIntegrationKit(integration.GetIntegrationKitNamespace(e.Platform), fmt.Sprintf("kit-%s", xid.New())) kit.Labels = map[string]string{ v1.IntegrationKitTypeLabel: v1.IntegrationKitTypePlatform, kubernetes.CamelLabelRuntimeVersion: integration.Status.RuntimeVersion, kubernetes.CamelLabelRuntimeProvider: 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, } if v, ok := integration.Annotations[v1.PlatformSelectorAnnotation]; ok { v1.SetAnnotation(&kit.ObjectMeta, v1.PlatformSelectorAnnotation, v) } if v, ok := integration.Annotations[v1.IntegrationProfileAnnotation]; ok { v1.SetAnnotation(&kit.ObjectMeta, v1.IntegrationProfileAnnotation, v) if v, ok := e.Integration.Annotations[v1.IntegrationProfileNamespaceAnnotation]; ok { v1.SetAnnotation(&kit.ObjectMeta, v1.IntegrationProfileNamespaceAnnotation, v) } else { // set integration profile namespace to the integration namespace. // this is because the kit may live in another namespace and needs to resolve the integration profile from the integration namespace. v1.SetAnnotation(&kit.ObjectMeta, v1.IntegrationProfileNamespaceAnnotation, e.Integration.Namespace) } } operatorID := defaults.OperatorID() if operatorID != "" { kit.SetOperatorID(operatorID) } kit.Spec = v1.IntegrationKitSpec{ Dependencies: e.Integration.Status.Dependencies, Repositories: e.Integration.Spec.Repositories, Traits: propagateKitTraits(e), } if packageType == nativeSourcesPackageType { kit.Spec.Sources = propagateSourcesRequiredAtBuildTime(e) } if e.Integration.Status.Capabilities != nil { kit.Spec.Capabilities = e.Integration.Status.Capabilities } return kit } func propagateKitTraits(e *Environment) v1.IntegrationKitTraits { kitTraits := v1.IntegrationKitTraits{} if e.Platform != nil { propagate(fmt.Sprintf("platform %q", e.Platform.Name), e.Platform.Status.Traits, &kitTraits, e) } if e.IntegrationProfile != nil { propagate(fmt.Sprintf("integration profile %q", e.IntegrationProfile.Name), e.IntegrationProfile.Spec.Traits, &kitTraits, e) } propagate(fmt.Sprintf("integration %q", e.Integration.Name), e.Integration.Spec.Traits, &kitTraits, e) return kitTraits } func propagate(traitSource string, traits v1.Traits, kitTraits *v1.IntegrationKitTraits, e *Environment) { ikt := v1.IntegrationKitTraits{ Builder: traits.Builder.DeepCopy(), Camel: traits.Camel.DeepCopy(), Quarkus: traits.Quarkus.DeepCopy(), } if err := kitTraits.Merge(ikt); err != nil { log.Errorf(err, "Unable to propagate traits from %s to the integration kit", traitSource) } // propagate addons that influence kits too if len(traits.Addons) > 0 { if kitTraits.Addons == nil { kitTraits.Addons = make(map[string]v1.AddonTrait) } for id, addon := range traits.Addons { if t := e.Catalog.GetTrait(id); t != nil && t.InfluencesKit() { kitTraits.Addons[id] = *addon.DeepCopy() } } } } func (t *quarkusTrait) applyWhenBuildSubmitted(e *Environment) error { buildTask := getBuilderTask(e.Pipeline) if buildTask == nil { return fmt.Errorf("unable to find builder task: %s", e.Integration.Name) } packageTask := getPackageTask(e.Pipeline) if packageTask == nil { return fmt.Errorf("unable to find package task: %s", e.Integration.Name) } buildSteps, err := builder.StepsFrom(buildTask.Steps...) if err != nil { return err } buildSteps = append(buildSteps, builder.Quarkus.CommonSteps...) packageSteps, err := builder.StepsFrom(packageTask.Steps...) if err != nil { return err } if buildTask.Maven.Properties == nil { buildTask.Maven.Properties = make(map[string]string) } native, err := t.isNativeKit(e) if err != nil { return err } // The LoadCamelQuarkusCatalog is required to have catalog information available by the builder packageSteps = append(packageSteps, builder.Quarkus.LoadCamelQuarkusCatalog) //nolint:nestif if native { buildTask.Maven.Properties["quarkus.native.enabled"] = "true" if nativePackageType := builder.QuarkusRuntimeSupport(e.CamelCatalog.GetCamelQuarkusVersion()).NativeMavenProperty(); nativePackageType != "" { buildTask.Maven.Properties[nativePackageType] = "true" } if t.NativeBaseImage == "" { packageTask.BaseImage = QuarkusNativeDefaultBaseImageName } else { packageTask.BaseImage = t.NativeBaseImage } if len(e.IntegrationKit.Spec.Sources) > 0 { buildTask.Sources = e.IntegrationKit.Spec.Sources buildSteps = append(buildSteps, builder.Quarkus.PrepareProjectWithSources) } packageSteps = append(packageSteps, builder.Image.NativeImageContext) // Create the dockerfile, regardless it's later used or not by the publish strategy packageSteps = append(packageSteps, builder.Image.ExecutableDockerfile) } else { // Default, if nothing is specified buildTask.Maven.Properties["quarkus.package.jar.type"] = string(fastJarPackageType) packageSteps = append(packageSteps, builder.Quarkus.ComputeQuarkusDependencies) if t.isIncrementalImageBuild(e) { packageSteps = append(packageSteps, builder.Image.IncrementalImageContext) } else { packageSteps = append(packageSteps, builder.Image.StandardImageContext) } // Create the dockerfile, regardless it's later used or not by the publish strategy packageSteps = append(packageSteps, builder.Image.JvmDockerfile) } // Sort steps by phase sort.SliceStable(buildSteps, func(i, j int) bool { return buildSteps[i].Phase() < buildSteps[j].Phase() }) sort.SliceStable(packageSteps, func(i, j int) bool { return packageSteps[i].Phase() < packageSteps[j].Phase() }) buildTask.Steps = builder.StepIDsFor(buildSteps...) packageTask.Steps = builder.StepIDsFor(packageSteps...) return nil } func (t *quarkusTrait) isNativeKit(e *Environment) (bool, error) { switch modes := t.Modes; len(modes) { case 0: return false, nil case 1: return modes[0] == traitv1.NativeQuarkusMode, nil default: return false, fmt.Errorf("kit %q has more than one package type", e.IntegrationKit.Name) } } func (t *quarkusTrait) isIncrementalImageBuild(e *Environment) bool { // We need to get this information from the builder trait if trait := e.Catalog.GetTrait(builderTraitID); trait != nil { builder, ok := trait.(*builderTrait) return ok && ptr.Deref(builder.IncrementalImageBuild, true) } // Default always to true for performance reasons return true } func (t *quarkusTrait) applyWhenKitReady(e *Environment) error { if e.IntegrationInRunningPhases() && t.isNativeIntegration(e) { container := e.GetIntegrationContainer() if container == nil { return fmt.Errorf("unable to find integration container: %s", e.Integration.Name) } container.Command = []string{"./camel-k-integration-" + defaults.Version + "-runner"} container.WorkingDir = builder.DeploymentDir } return nil } func (t *quarkusTrait) isNativeIntegration(e *Environment) bool { // The current IntegrationKit determines the Integration runtime type return e.IntegrationKit != nil && e.IntegrationKit.Labels[v1.IntegrationKitLayoutLabel] == v1.IntegrationKitLayoutNativeSources } // Indicates whether the given source code is embedded into the final binary. func (t *quarkusTrait) isEmbedded(e *Environment, source v1.SourceSpec) bool { if e.IntegrationInRunningPhases() { return e.IntegrationKit != nil && t.isNativeIntegration(e) && sourcesRequiredAtBuildTime(e, source) } else if e.IntegrationKitInPhase(v1.IntegrationKitPhaseBuildSubmitted) { native, _ := t.isNativeKit(e) return native && sourcesRequiredAtBuildTime(e, source) } return false } func (t *quarkusTrait) containsMode(m traitv1.QuarkusMode) bool { for _, mode := range t.Modes { if mode == m { return true } } return false } func packageType(mode traitv1.QuarkusMode) quarkusPackageType { if mode == traitv1.NativeQuarkusMode { return nativeSourcesPackageType } if mode == traitv1.JvmQuarkusMode { return fastJarPackageType } return "" } // Indicates whether the given source file is required at build time for native compilation. func sourcesRequiredAtBuildTime(e *Environment, source v1.SourceSpec) bool { settings := getLanguageSettings(e, source.InferLanguage()) return settings.native && settings.sourcesRequiredAtBuildTime } // Propagates the user defined sources that are required at build time for native compilation. func propagateSourcesRequiredAtBuildTime(e *Environment) []v1.SourceSpec { array := make([]v1.SourceSpec, 0) for _, source := range e.Integration.OriginalSources() { if sourcesRequiredAtBuildTime(e, source) { array = append(array, source) } } return array }