| /* |
| 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 ( |
| "context" |
| "fmt" |
| "path" |
| "strconv" |
| "strings" |
| |
| "github.com/scylladb/go-set/strset" |
| |
| corev1 "k8s.io/api/core/v1" |
| metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" |
| controller "sigs.k8s.io/controller-runtime/pkg/client" |
| |
| "k8s.io/apimachinery/pkg/runtime" |
| |
| "github.com/apache/camel-k/pkg/apis/camel/v1alpha1" |
| "github.com/apache/camel-k/pkg/builder" |
| "github.com/apache/camel-k/pkg/client" |
| "github.com/apache/camel-k/pkg/metadata" |
| "github.com/apache/camel-k/pkg/platform" |
| "github.com/apache/camel-k/pkg/util/camel" |
| "github.com/apache/camel-k/pkg/util/kubernetes" |
| "github.com/apache/camel-k/pkg/util/log" |
| ) |
| |
| // True -- |
| const True = "true" |
| |
| // Identifiable represent an identifiable type |
| type Identifiable interface { |
| ID() ID |
| } |
| |
| // ID uniquely identifies a trait |
| type ID string |
| |
| // Trait is the interface of all traits |
| type Trait interface { |
| Identifiable |
| client.Injectable |
| |
| // InjectContext to inject a context |
| InjectContext(context.Context) |
| |
| // Configure the trait |
| Configure(environment *Environment) (bool, error) |
| |
| // Apply executes a customization of the Environment |
| Apply(environment *Environment) error |
| |
| // InfluencesKit determines if the trait has any influence on Integration Kits |
| InfluencesKit() bool |
| |
| // IsPlatformTrait marks all fundamental traits that allow the platform to work |
| IsPlatformTrait() bool |
| |
| // RequiresIntegrationPlatform indicates that the trait cannot work without an integration platform set |
| RequiresIntegrationPlatform() bool |
| } |
| |
| /* Base trait */ |
| |
| func newBaseTrait(id string) BaseTrait { |
| return BaseTrait{ |
| id: ID(id), |
| L: log.Log.WithName("traits").WithValues("trait", id), |
| } |
| } |
| |
| // BaseTrait is the root trait with noop implementations for hooks |
| type BaseTrait struct { |
| id ID |
| // Can be used to enable or disable a trait. All traits share this common property. |
| Enabled *bool `property:"enabled"` |
| client client.Client |
| ctx context.Context |
| L log.Logger |
| } |
| |
| // ID returns the identifier of the trait |
| func (trait *BaseTrait) ID() ID { |
| return trait.id |
| } |
| |
| // InjectClient implements client.ClientInject and allows to inject a client into the trait |
| func (trait *BaseTrait) InjectClient(c client.Client) { |
| trait.client = c |
| } |
| |
| // InjectContext allows to inject a context into the trait |
| func (trait *BaseTrait) InjectContext(ctx context.Context) { |
| trait.ctx = ctx |
| } |
| |
| // InfluencesKit determines if the trait has any influence on Integration Kits |
| func (trait *BaseTrait) InfluencesKit() bool { |
| return false |
| } |
| |
| // IsPlatformTrait marks all fundamental traits that allow the platform to work. |
| func (trait *BaseTrait) IsPlatformTrait() bool { |
| return false |
| } |
| |
| // RequiresIntegrationPlatform indicates that the trait cannot work without an integration platform set |
| func (trait *BaseTrait) RequiresIntegrationPlatform() bool { |
| // All traits require a platform by default |
| return true |
| } |
| |
| /* Environment */ |
| |
| // A Environment provides the context where the trait is executed |
| type Environment struct { |
| CamelCatalog *camel.RuntimeCatalog |
| RuntimeVersion string |
| Catalog *Catalog |
| C context.Context |
| Client client.Client |
| Platform *v1alpha1.IntegrationPlatform |
| IntegrationKit *v1alpha1.IntegrationKit |
| Integration *v1alpha1.Integration |
| Resources *kubernetes.Collection |
| PostActions []func(*Environment) error |
| PostProcessors []func(*Environment) error |
| Steps []builder.Step |
| BuildDir string |
| ExecutedTraits []Trait |
| EnvVars []corev1.EnvVar |
| Classpath *strset.Set |
| } |
| |
| // ControllerStrategy is used to determine the kind of controller that needs to be created for the integration |
| type ControllerStrategy string |
| |
| // List of controller strategies |
| const ( |
| ControllerStrategyDeployment = "deployment" |
| ControllerStrategyKnativeService = "knative-service" |
| ) |
| |
| // GetTrait -- |
| func (e *Environment) GetTrait(id ID) Trait { |
| for _, t := range e.ExecutedTraits { |
| if t.ID() == id { |
| return t |
| } |
| } |
| |
| return nil |
| } |
| |
| // IntegrationInPhase -- |
| func (e *Environment) IntegrationInPhase(phases ...v1alpha1.IntegrationPhase) bool { |
| if e.Integration == nil { |
| return false |
| } |
| |
| for _, phase := range phases { |
| if e.Integration.Status.Phase == phase { |
| return true |
| } |
| } |
| |
| return false |
| } |
| |
| // IntegrationKitInPhase -- |
| func (e *Environment) IntegrationKitInPhase(phases ...v1alpha1.IntegrationKitPhase) bool { |
| if e.IntegrationKit == nil { |
| return false |
| } |
| |
| for _, phase := range phases { |
| if e.IntegrationKit.Status.Phase == phase { |
| return true |
| } |
| } |
| |
| return false |
| } |
| |
| // InPhase -- |
| func (e *Environment) InPhase(c v1alpha1.IntegrationKitPhase, i v1alpha1.IntegrationPhase) bool { |
| return e.IntegrationKitInPhase(c) && e.IntegrationInPhase(i) |
| } |
| |
| // DetermineProfile determines the TraitProfile of the environment. |
| // First looking at the Integration.Spec for a Profile, |
| // next looking at the IntegrationKit.Spec |
| // and lastly the Platform Profile |
| func (e *Environment) DetermineProfile() v1alpha1.TraitProfile { |
| if e.Integration != nil && e.Integration.Spec.Profile != "" { |
| return e.Integration.Spec.Profile |
| } |
| |
| if e.IntegrationKit != nil && e.IntegrationKit.Spec.Profile != "" { |
| return e.IntegrationKit.Spec.Profile |
| } |
| |
| if e.Platform != nil { |
| return platform.GetProfile(e.Platform) |
| } |
| |
| return v1alpha1.DefaultTraitProfile |
| } |
| |
| // DetermineControllerStrategy determines the type of controller that should be used for the integration |
| func (e *Environment) DetermineControllerStrategy(ctx context.Context, c controller.Reader) (ControllerStrategy, error) { |
| if e.DetermineProfile() != v1alpha1.TraitProfileKnative { |
| return ControllerStrategyDeployment, nil |
| } |
| |
| trait := e.GetTrait("deployer") |
| if trait != nil { |
| deployerTrait := trait.(*deployerTrait) |
| if deployerTrait.Kind == ControllerStrategyDeployment { |
| return ControllerStrategyDeployment, nil |
| } else if deployerTrait.Kind == ControllerStrategyKnativeService { |
| return ControllerStrategyKnativeService, nil |
| } |
| } |
| |
| var sources []v1alpha1.SourceSpec |
| var err error |
| if sources, err = kubernetes.ResolveIntegrationSources(ctx, c, e.Integration, e.Resources); err != nil { |
| return "", err |
| } |
| |
| // In Knative profile: use knative service only if needed |
| meta := metadata.ExtractAll(e.CamelCatalog, sources) |
| if !meta.RequiresHTTPService { |
| return ControllerStrategyDeployment, nil |
| } |
| |
| return ControllerStrategyKnativeService, nil |
| } |
| |
| // DetermineNamespace -- |
| func (e *Environment) DetermineNamespace() string { |
| if e.Integration != nil && e.Integration.Namespace != "" { |
| return e.Integration.Namespace |
| } |
| if e.IntegrationKit != nil && e.IntegrationKit.Namespace != "" { |
| return e.IntegrationKit.Namespace |
| } |
| if e.Platform != nil && e.Platform.Namespace != "" { |
| return e.Platform.Namespace |
| } |
| |
| return "" |
| } |
| |
| // ComputeConfigMaps -- |
| func (e *Environment) ComputeConfigMaps() []runtime.Object { |
| sources := e.Integration.Sources() |
| maps := make([]runtime.Object, 0, len(sources)+1) |
| |
| // combine properties of integration with kit, integration |
| // properties have the priority |
| properties := "" |
| |
| for key, val := range e.CollectConfigurationPairs("property") { |
| properties += fmt.Sprintf("%s=%s\n", key, val) |
| } |
| |
| maps = append( |
| maps, |
| &corev1.ConfigMap{ |
| TypeMeta: metav1.TypeMeta{ |
| Kind: "ConfigMap", |
| APIVersion: "v1", |
| }, |
| ObjectMeta: metav1.ObjectMeta{ |
| Name: e.Integration.Name + "-properties", |
| Namespace: e.Integration.Namespace, |
| Labels: map[string]string{ |
| "camel.apache.org/integration": e.Integration.Name, |
| }, |
| }, |
| Data: map[string]string{ |
| "application.properties": properties, |
| }, |
| }, |
| ) |
| |
| for i, s := range sources { |
| if s.ContentRef != "" { |
| continue |
| } |
| |
| cm := corev1.ConfigMap{ |
| TypeMeta: metav1.TypeMeta{ |
| Kind: "ConfigMap", |
| APIVersion: "v1", |
| }, |
| ObjectMeta: metav1.ObjectMeta{ |
| Name: fmt.Sprintf("%s-source-%03d", e.Integration.Name, i), |
| Namespace: e.Integration.Namespace, |
| Labels: map[string]string{ |
| "camel.apache.org/integration": e.Integration.Name, |
| }, |
| Annotations: map[string]string{ |
| "camel.apache.org/source.language": string(s.InferLanguage()), |
| "camel.apache.org/source.loader": s.Loader, |
| "camel.apache.org/source.name": s.Name, |
| "camel.apache.org/source.compression": strconv.FormatBool(s.Compression), |
| }, |
| }, |
| Data: map[string]string{ |
| "content": s.Content, |
| }, |
| } |
| |
| maps = append(maps, &cm) |
| } |
| |
| for i, r := range e.Integration.Spec.Resources { |
| if r.Type != v1alpha1.ResourceTypeData { |
| continue |
| } |
| if r.ContentRef != "" { |
| continue |
| } |
| |
| cmKey := "content" |
| if r.ContentKey != "" { |
| cmKey = r.ContentKey |
| } |
| |
| cm := corev1.ConfigMap{ |
| TypeMeta: metav1.TypeMeta{ |
| Kind: "ConfigMap", |
| APIVersion: "v1", |
| }, |
| ObjectMeta: metav1.ObjectMeta{ |
| Name: fmt.Sprintf("%s-resource-%03d", e.Integration.Name, i), |
| Namespace: e.Integration.Namespace, |
| Labels: map[string]string{ |
| "camel.apache.org/integration": e.Integration.Name, |
| }, |
| Annotations: map[string]string{ |
| "camel.apache.org/resource.name": r.Name, |
| "camel.apache.org/resource.compression": strconv.FormatBool(r.Compression), |
| }, |
| }, |
| Data: map[string]string{ |
| cmKey: r.Content, |
| }, |
| } |
| |
| maps = append(maps, &cm) |
| } |
| |
| return maps |
| } |
| |
| // ComputeSourcesURI -- |
| func (e *Environment) ComputeSourcesURI() []string { |
| sources := e.Integration.Sources() |
| paths := make([]string, 0, len(sources)) |
| |
| for i, s := range sources { |
| root := "/etc/camel/sources" |
| root = path.Join(root, fmt.Sprintf("i-source-%03d", i)) |
| |
| srcName := strings.TrimPrefix(s.Name, "/") |
| src := path.Join(root, srcName) |
| src = "file:" + src |
| |
| params := make([]string, 0) |
| if s.InferLanguage() != "" { |
| params = append(params, "language="+string(s.InferLanguage())) |
| } |
| if s.Loader != "" { |
| params = append(params, "loader="+s.Loader) |
| } |
| if s.Compression { |
| params = append(params, "compression=true") |
| } |
| |
| if len(params) > 0 { |
| src = fmt.Sprintf("%s?%s", src, strings.Join(params, "&")) |
| } |
| |
| paths = append(paths, src) |
| } |
| |
| return paths |
| } |
| |
| // ConfigureVolumesAndMounts -- |
| func (e *Environment) ConfigureVolumesAndMounts(vols *[]corev1.Volume, mnts *[]corev1.VolumeMount) { |
| // |
| // Volumes :: Sources |
| // |
| |
| for i, s := range e.Integration.Sources() { |
| cmName := fmt.Sprintf("%s-source-%03d", e.Integration.Name, i) |
| refName := fmt.Sprintf("i-source-%03d", i) |
| resName := strings.TrimPrefix(s.Name, "/") |
| resPath := path.Join("/etc/camel/sources", refName) |
| |
| if s.ContentRef != "" { |
| cmName = s.ContentRef |
| } |
| |
| *vols = append(*vols, corev1.Volume{ |
| Name: refName, |
| VolumeSource: corev1.VolumeSource{ |
| ConfigMap: &corev1.ConfigMapVolumeSource{ |
| LocalObjectReference: corev1.LocalObjectReference{ |
| Name: cmName, |
| }, |
| Items: []corev1.KeyToPath{ |
| { |
| Key: "content", |
| Path: resName, |
| }, |
| }, |
| }, |
| }, |
| }) |
| |
| *mnts = append(*mnts, corev1.VolumeMount{ |
| Name: refName, |
| MountPath: resPath, |
| }) |
| } |
| |
| for i, r := range e.Integration.Spec.Resources { |
| if r.Type != v1alpha1.ResourceTypeData { |
| continue |
| } |
| |
| cmName := fmt.Sprintf("%s-resource-%03d", e.Integration.Name, i) |
| refName := fmt.Sprintf("i-resource-%03d", i) |
| resName := strings.TrimPrefix(r.Name, "/") |
| cmKey := "content" |
| resPath := path.Join("/etc/camel/resources", refName) |
| |
| if r.ContentRef != "" { |
| cmName = r.ContentRef |
| } |
| if r.ContentKey != "" { |
| cmKey = r.ContentKey |
| } |
| if r.MountPath != "" { |
| resPath = r.MountPath |
| } |
| |
| *vols = append(*vols, corev1.Volume{ |
| Name: refName, |
| VolumeSource: corev1.VolumeSource{ |
| ConfigMap: &corev1.ConfigMapVolumeSource{ |
| LocalObjectReference: corev1.LocalObjectReference{ |
| Name: cmName, |
| }, |
| Items: []corev1.KeyToPath{ |
| { |
| Key: cmKey, |
| Path: resName, |
| }, |
| }, |
| }, |
| }, |
| }) |
| |
| *mnts = append(*mnts, corev1.VolumeMount{ |
| Name: refName, |
| MountPath: resPath, |
| }) |
| } |
| |
| // |
| // Volumes :: Properties |
| // |
| |
| *vols = append(*vols, corev1.Volume{ |
| Name: "integration-properties", |
| VolumeSource: corev1.VolumeSource{ |
| ConfigMap: &corev1.ConfigMapVolumeSource{ |
| LocalObjectReference: corev1.LocalObjectReference{ |
| Name: e.Integration.Name + "-properties", |
| }, |
| Items: []corev1.KeyToPath{ |
| { |
| Key: "application.properties", |
| Path: "application.properties", |
| }, |
| }, |
| }, |
| }, |
| }) |
| |
| *mnts = append(*mnts, corev1.VolumeMount{ |
| Name: "integration-properties", |
| MountPath: "/etc/camel/conf", |
| }) |
| |
| // |
| // Volumes :: Additional ConfigMaps |
| // |
| |
| for _, cmName := range e.CollectConfigurationValues("configmap") { |
| refName := kubernetes.SanitizeLabel(cmName) |
| fileName := "integration-cm-" + strings.ToLower(cmName) |
| |
| *vols = append(*vols, corev1.Volume{ |
| Name: refName, |
| VolumeSource: corev1.VolumeSource{ |
| ConfigMap: &corev1.ConfigMapVolumeSource{ |
| LocalObjectReference: corev1.LocalObjectReference{ |
| Name: cmName, |
| }, |
| }, |
| }, |
| }) |
| |
| *mnts = append(*mnts, corev1.VolumeMount{ |
| Name: refName, |
| MountPath: path.Join("/etc/camel/conf.d", fileName), |
| }) |
| } |
| |
| // |
| // Volumes :: Additional Secrets |
| // |
| |
| for _, secretName := range e.CollectConfigurationValues("secret") { |
| refName := kubernetes.SanitizeLabel(secretName) |
| fileName := "integration-secret-" + strings.ToLower(secretName) |
| |
| *vols = append(*vols, corev1.Volume{ |
| Name: refName, |
| VolumeSource: corev1.VolumeSource{ |
| Secret: &corev1.SecretVolumeSource{ |
| SecretName: secretName, |
| }, |
| }, |
| }) |
| |
| *mnts = append(*mnts, corev1.VolumeMount{ |
| Name: refName, |
| MountPath: path.Join("/etc/camel/conf.d", fileName), |
| }) |
| } |
| |
| // |
| // Volumes :: Additional user provided volumes |
| // |
| for _, volumeConfig := range e.CollectConfigurationValues("volume") { |
| configParts := strings.Split(volumeConfig, ":") |
| |
| if len(configParts) != 2 { |
| continue |
| } |
| |
| pvcName := configParts[0] |
| mountPath := configParts[1] |
| volumeName := pvcName + "-data" |
| |
| *vols = append(*vols, corev1.Volume{ |
| Name: volumeName, |
| VolumeSource: corev1.VolumeSource{ |
| PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ |
| ClaimName: pvcName, |
| }, |
| }, |
| }) |
| |
| *mnts = append(*mnts, corev1.VolumeMount{ |
| Name: volumeName, |
| MountPath: mountPath, |
| }) |
| } |
| } |
| |
| // CollectConfigurationValues -- |
| func (e *Environment) CollectConfigurationValues(configurationType string) []string { |
| return CollectConfigurationValues(configurationType, e.Platform, e.IntegrationKit, e.Integration) |
| } |
| |
| // CollectConfigurationPairs -- |
| func (e *Environment) CollectConfigurationPairs(configurationType string) map[string]string { |
| return CollectConfigurationPairs(configurationType, e.Platform, e.IntegrationKit, e.Integration) |
| } |