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

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


The following commit(s) were added to refs/heads/main by this push:
     new 226eef200 feat(traits): master refactoring
226eef200 is described below

commit 226eef20024b3e675972b2f1b22dc4228d0ec1db
Author: Pasquale Congiusti <[email protected]>
AuthorDate: Fri Mar 22 09:58:29 2024 +0100

    feat(traits): master refactoring
    
    Closes #4048
---
 addons/master/master.go      | 166 +++++++++++++++++++++++---------------
 addons/master/master_test.go | 186 +++++++++++++++++++++++++++++++++++++++++++
 pkg/trait/builder.go         |  10 ++-
 3 files changed, 297 insertions(+), 65 deletions(-)

diff --git a/addons/master/master.go b/addons/master/master.go
index 476f68bf8..692bfb2c4 100644
--- a/addons/master/master.go
+++ b/addons/master/master.go
@@ -26,6 +26,7 @@ import (
 
        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/client"
        "github.com/apache/camel-k/v2/pkg/metadata"
        "github.com/apache/camel-k/v2/pkg/resources"
        "github.com/apache/camel-k/v2/pkg/trait"
@@ -85,10 +86,13 @@ var (
 )
 
 func (t *masterTrait) Configure(e *trait.Environment) (bool, 
*trait.TraitCondition, error) {
-       if e.Integration == nil || !pointer.BoolDeref(t.Enabled, true) {
+       if e.Integration == nil {
                return false, nil, nil
        }
-       if !e.IntegrationInPhase(v1.IntegrationPhaseInitialization) && 
!e.IntegrationInRunningPhases() {
+       if !pointer.BoolDeref(t.Enabled, true) {
+               return false, 
trait.NewIntegrationConditionUserDisabled(masterComponent), nil
+       }
+       if !e.IntegrationInPhase(v1.IntegrationPhaseInitialization, 
v1.IntegrationPhaseBuildingKit) && !e.IntegrationInRunningPhases() {
                return false, nil, nil
        }
        if pointer.BoolDeref(t.Auto, true) {
@@ -110,9 +114,10 @@ func (t *masterTrait) Configure(e *trait.Environment) 
(bool, *trait.TraitConditi
                                        t.Enabled = &enabled
                                }
                        }
-               }
-               if !pointer.BoolDeref(t.Enabled, false) {
-                       return false, 
trait.NewIntegrationConditionUserDisabled("Master"), nil
+                       // No master component, can skip the trait execution
+                       if !pointer.BoolDeref(t.Enabled, false) {
+                               return false, nil, nil
+                       }
                }
                if t.IncludeDelegateDependencies == nil || 
*t.IncludeDelegateDependencies {
                        t.delegateDependencies = findAdditionalDependencies(e, 
meta)
@@ -145,83 +150,81 @@ func (t *masterTrait) Configure(e *trait.Environment) 
(bool, *trait.TraitConditi
                }
        }
 
-       return pointer.BoolDeref(t.Enabled, true), nil, nil
+       return pointer.BoolDeref(t.Enabled, false), nil, nil
 }
 
 func (t *masterTrait) Apply(e *trait.Environment) error {
        if e.IntegrationInPhase(v1.IntegrationPhaseInitialization) {
                util.StringSliceUniqueAdd(&e.Integration.Status.Capabilities, 
v1.CapabilityMaster)
-
                // Master sub endpoints need to be added to the list of 
dependencies
                for _, dep := range t.delegateDependencies {
                        
util.StringSliceUniqueAdd(&e.Integration.Status.Dependencies, dep)
                }
-
        } else if e.IntegrationInRunningPhases() {
-               serviceAccount := e.Integration.Spec.ServiceAccountName
-               if serviceAccount == "" {
-                       serviceAccount = "default"
-               }
-
-               templateData := struct {
-                       Namespace      string
-                       Name           string
-                       ServiceAccount string
-               }{
-                       Namespace:      e.Integration.Namespace,
-                       Name:           fmt.Sprintf("%s-master", 
e.Integration.Name),
-                       ServiceAccount: serviceAccount,
-               }
-
-               roleSuffix := leaseResourceType
-               if t.ResourceType != nil {
-                       roleSuffix = *t.ResourceType
-               }
-               roleSuffix = strings.ToLower(roleSuffix)
-
-               role, err := loadResource(e, fmt.Sprintf("master-role-%s.tmpl", 
roleSuffix), templateData)
+               // Master trait requires the ServiceAccount certain privileges
+               privileges, err := t.prepareRBAC(e.Client, 
e.Integration.Spec.ServiceAccountName, e.Integration.Name, 
e.Integration.Namespace)
                if err != nil {
                        return err
                }
-               roleBinding, err := loadResource(e, "master-role-binding.tmpl", 
templateData)
-               if err != nil {
-                       return err
+               // Add the RBAC privileges
+               e.Resources.AddAll(privileges)
+
+               if 
e.CamelCatalog.Runtime.Capabilities["master"].RuntimeProperties != nil {
+                       t.setCatalogConfiguration(e)
+               } else {
+                       t.setCustomizerConfiguration(e)
                }
+       }
 
-               e.Resources.Add(role)
-               e.Resources.Add(roleBinding)
+       return nil
+}
 
+// Deprecated: to be removed in future release in favor of func 
setCatalogConfiguration().
+func (t *masterTrait) setCustomizerConfiguration(e *trait.Environment) {
+       e.Integration.Status.Configuration = 
append(e.Integration.Status.Configuration,
+               v1.ConfigurationSpec{Type: "property", Value: 
"customizer.master.enabled=true"},
+       )
+       if t.ResourceName != nil {
+               resourceName := t.ResourceName
                e.Integration.Status.Configuration = 
append(e.Integration.Status.Configuration,
-                       v1.ConfigurationSpec{Type: "property", Value: 
"customizer.master.enabled=true"},
+                       v1.ConfigurationSpec{Type: "property", Value: 
fmt.Sprintf("customizer.master.kubernetesResourceName=%s", *resourceName)},
                )
+       }
+       if t.ResourceType != nil {
+               e.Integration.Status.Configuration = 
append(e.Integration.Status.Configuration,
+                       v1.ConfigurationSpec{Type: "property", Value: 
fmt.Sprintf("customizer.master.leaseResourceType=%s", *t.ResourceType)},
+               )
+       }
+       if t.LabelKey != nil {
+               e.Integration.Status.Configuration = 
append(e.Integration.Status.Configuration,
+                       v1.ConfigurationSpec{Type: "property", Value: 
fmt.Sprintf("customizer.master.labelKey=%s", *t.LabelKey)},
+               )
+       }
+       if t.LabelValue != nil {
+               e.Integration.Status.Configuration = 
append(e.Integration.Status.Configuration,
+                       v1.ConfigurationSpec{Type: "property", Value: 
fmt.Sprintf("customizer.master.labelValue=%s", *t.LabelValue)},
+               )
+       }
+}
 
-               if t.ResourceName != nil {
-                       resourceName := t.ResourceName
-                       e.Integration.Status.Configuration = 
append(e.Integration.Status.Configuration,
-                               v1.ConfigurationSpec{Type: "property", Value: 
fmt.Sprintf("customizer.master.kubernetesResourceName=%s", *resourceName)},
-                       )
-               }
-
-               if t.ResourceType != nil {
-                       e.Integration.Status.Configuration = 
append(e.Integration.Status.Configuration,
-                               v1.ConfigurationSpec{Type: "property", Value: 
fmt.Sprintf("customizer.master.leaseResourceType=%s", *t.ResourceType)},
-                       )
-               }
-
-               if t.LabelKey != nil {
-                       e.Integration.Status.Configuration = 
append(e.Integration.Status.Configuration,
-                               v1.ConfigurationSpec{Type: "property", Value: 
fmt.Sprintf("customizer.master.labelKey=%s", *t.LabelKey)},
-                       )
-               }
-
-               if t.LabelValue != nil {
-                       e.Integration.Status.Configuration = 
append(e.Integration.Status.Configuration,
-                               v1.ConfigurationSpec{Type: "property", Value: 
fmt.Sprintf("customizer.master.labelValue=%s", *t.LabelValue)},
-                       )
-               }
+func (t *masterTrait) setCatalogConfiguration(e *trait.Environment) {
+       if e.ApplicationProperties == nil {
+               e.ApplicationProperties = make(map[string]string)
+       }
+       if t.ResourceName != nil {
+               e.ApplicationProperties["camel.k.master.resourceName"] = 
*t.ResourceName
+       }
+       if t.ResourceType != nil {
+               e.ApplicationProperties["camel.k.master.resourceType"] = 
*t.ResourceType
+       }
+       if t.LabelKey != nil && t.LabelValue != nil {
+               e.ApplicationProperties["camel.k.master.labelKey"] = *t.LabelKey
+               e.ApplicationProperties["camel.k.master.labelValue"] = 
*t.LabelValue
        }
 
-       return nil
+       for _, cp := range 
e.CamelCatalog.Runtime.Capabilities["master"].RuntimeProperties {
+               e.ApplicationProperties[trait.CapabilityPropertyKey(cp.Key, 
e.ApplicationProperties)] = cp.Value
+       }
 }
 
 func (t *masterTrait) canUseLeases(e *trait.Environment) (bool, error) {
@@ -246,14 +249,49 @@ func findAdditionalDependencies(e *trait.Environment, 
meta metadata.IntegrationM
        return dependencies
 }
 
-func loadResource(e *trait.Environment, name string, params interface{}) 
(ctrl.Object, error) {
-       data, err := 
resources.TemplateResource(fmt.Sprintf("/resources/addons/master/%s", name), 
params)
+func loadResource(cli client.Client, name string, params interface{}) 
(ctrl.Object, error) {
+       data, err := 
resources.TemplateResource(fmt.Sprintf("resources/addons/master/%s", name), 
params)
        if err != nil {
                return nil, err
        }
-       obj, err := kubernetes.LoadResourceFromYaml(e.Client.GetScheme(), data)
+       obj, err := kubernetes.LoadResourceFromYaml(cli.GetScheme(), data)
        if err != nil {
                return nil, err
        }
        return obj, nil
 }
+
+func (t *masterTrait) prepareRBAC(cli client.Client, serviceAccount, itName, 
itNamespace string) ([]ctrl.Object, error) {
+       objs := make([]ctrl.Object, 0, 2)
+       if serviceAccount == "" {
+               serviceAccount = "default"
+       }
+
+       templateData := struct {
+               Namespace      string
+               Name           string
+               ServiceAccount string
+       }{
+               Namespace:      itNamespace,
+               Name:           fmt.Sprintf("%s-master", itName),
+               ServiceAccount: serviceAccount,
+       }
+
+       roleSuffix := leaseResourceType
+       if t.ResourceType != nil {
+               roleSuffix = *t.ResourceType
+       }
+       roleSuffix = strings.ToLower(roleSuffix)
+
+       role, err := loadResource(cli, fmt.Sprintf("master-role-%s.tmpl", 
roleSuffix), templateData)
+       if err != nil {
+               return nil, err
+       }
+       objs = append(objs, role)
+       roleBinding, err := loadResource(cli, "master-role-binding.tmpl", 
templateData)
+       if err != nil {
+               return nil, err
+       }
+       objs = append(objs, roleBinding)
+       return objs, nil
+}
diff --git a/addons/master/master_test.go b/addons/master/master_test.go
new file mode 100644
index 000000000..c396af1db
--- /dev/null
+++ b/addons/master/master_test.go
@@ -0,0 +1,186 @@
+/*
+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 master
+
+import (
+       "testing"
+
+       v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1"
+       "github.com/apache/camel-k/v2/pkg/trait"
+       "github.com/apache/camel-k/v2/pkg/util/camel"
+       "github.com/apache/camel-k/v2/pkg/util/kubernetes"
+       "github.com/apache/camel-k/v2/pkg/util/test"
+       "github.com/stretchr/testify/assert"
+       "github.com/stretchr/testify/require"
+       corev1 "k8s.io/api/core/v1"
+       metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+func TestMasterOn(t *testing.T) {
+       catalog, err := camel.DefaultCatalog()
+       require.NoError(t, err)
+
+       client, err := test.NewFakeClient()
+       require.NoError(t, err)
+       traitCatalog := trait.NewCatalog(nil)
+
+       environment := trait.Environment{
+               CamelCatalog: catalog,
+               Catalog:      traitCatalog,
+               Client:       client,
+               Integration: &v1.Integration{
+                       ObjectMeta: metav1.ObjectMeta{
+                               Name:      "test",
+                               Namespace: "ns",
+                       },
+                       Status: v1.IntegrationStatus{
+                               Phase: v1.IntegrationPhaseInitialization,
+                       },
+                       Spec: v1.IntegrationSpec{
+                               Profile: v1.TraitProfileKnative,
+                               Sources: []v1.SourceSpec{
+                                       {
+                                               DataSpec: v1.DataSpec{
+                                                       Name:    "Master.java",
+                                                       Content: 
`from("master:lock:timer:tick").to("log:test")`,
+                                               },
+                                               Language: v1.LanguageJavaSource,
+                                       },
+                               },
+                               Traits: v1.Traits{},
+                       },
+               },
+               Platform: &v1.IntegrationPlatform{
+                       Spec: v1.IntegrationPlatformSpec{
+                               Cluster: v1.IntegrationPlatformClusterOpenShift,
+                               Build: v1.IntegrationPlatformBuildSpec{
+                                       PublishStrategy: 
v1.IntegrationPlatformBuildPublishStrategyS2I,
+                                       Registry:        
v1.RegistrySpec{Address: "registry"},
+                                       RuntimeVersion:  
catalog.Runtime.Version,
+                               },
+                               Profile: v1.TraitProfileKnative,
+                       },
+                       Status: v1.IntegrationPlatformStatus{
+                               Phase: v1.IntegrationPlatformPhaseReady,
+                       },
+               },
+               EnvVars:        make([]corev1.EnvVar, 0),
+               ExecutedTraits: make([]trait.Trait, 0),
+               Resources:      kubernetes.NewCollection(),
+       }
+       environment.Platform.ResyncStatusFullConfig()
+
+       mt := NewMasterTrait()
+       mt.InjectClient(client)
+       // Initialization phase
+       configured, conditions, err := mt.Configure(&environment)
+       require.NoError(t, err)
+       assert.Empty(t, conditions)
+       assert.True(t, configured)
+       err = mt.Apply(&environment)
+       require.NoError(t, err)
+       assert.Len(t, environment.Integration.Status.Capabilities, 1)
+       assert.Equal(t, "master", 
environment.Integration.Status.Capabilities[0])
+       // Running phase
+       environment.Integration.Status.Phase = v1.IntegrationPhaseRunning
+       err = mt.Apply(&environment)
+       require.NoError(t, err)
+       assert.Equal(t, "test-lock", 
environment.ApplicationProperties["camel.k.master.resourceName"])
+       assert.Equal(t, "ConfigMap", 
environment.ApplicationProperties["camel.k.master.resourceType"])
+       assert.Equal(t, "camel.apache.org/integration", 
environment.ApplicationProperties["camel.k.master.labelKey"])
+       assert.Equal(t, "test", 
environment.ApplicationProperties["camel.k.master.labelValue"])
+       assert.Equal(t, "${camel.k.master.resourceName}", 
environment.ApplicationProperties["quarkus.camel.cluster.kubernetes.resource-name"])
+       assert.Equal(t, "${camel.k.master.resourceType}", 
environment.ApplicationProperties["quarkus.camel.cluster.kubernetes.resource-type"])
+       assert.Equal(t, "${camel.k.master.labelValue}", 
environment.ApplicationProperties["quarkus.camel.cluster.kubernetes.labels.\"camel.apache.org/integration\""])
+       roles := 0
+       roleBindings := 0
+       for _, obj := range environment.Resources.Items() {
+               // make sure it contains a Role and a RoleBinding
+               if obj.GetObjectKind().GroupVersionKind().Kind == "Role" {
+                       roles++
+               }
+               if obj.GetObjectKind().GroupVersionKind().Kind == "RoleBinding" 
{
+                       roleBindings++
+               }
+       }
+       assert.Equal(t, 1, roles)
+       assert.Equal(t, 1, roleBindings)
+}
+
+func TestMasterOff(t *testing.T) {
+       catalog, err := camel.DefaultCatalog()
+       require.NoError(t, err)
+
+       client, err := test.NewFakeClient()
+       require.NoError(t, err)
+       traitCatalog := trait.NewCatalog(nil)
+
+       environment := trait.Environment{
+               CamelCatalog: catalog,
+               Catalog:      traitCatalog,
+               Client:       client,
+               Integration: &v1.Integration{
+                       ObjectMeta: metav1.ObjectMeta{
+                               Name:      "test",
+                               Namespace: "ns",
+                       },
+                       Status: v1.IntegrationStatus{
+                               Phase: v1.IntegrationPhaseInitialization,
+                       },
+                       Spec: v1.IntegrationSpec{
+                               Profile: v1.TraitProfileKnative,
+                               Sources: []v1.SourceSpec{
+                                       {
+                                               DataSpec: v1.DataSpec{
+                                                       Name:    
"NonMaster.java",
+                                                       Content: 
`from("timer:tick").to("log:test")`,
+                                               },
+                                               Language: v1.LanguageJavaSource,
+                                       },
+                               },
+                               Traits: v1.Traits{},
+                       },
+               },
+               Platform: &v1.IntegrationPlatform{
+                       Spec: v1.IntegrationPlatformSpec{
+                               Cluster: v1.IntegrationPlatformClusterOpenShift,
+                               Build: v1.IntegrationPlatformBuildSpec{
+                                       PublishStrategy: 
v1.IntegrationPlatformBuildPublishStrategyS2I,
+                                       Registry:        
v1.RegistrySpec{Address: "registry"},
+                                       RuntimeVersion:  
catalog.Runtime.Version,
+                               },
+                               Profile: v1.TraitProfileKnative,
+                       },
+                       Status: v1.IntegrationPlatformStatus{
+                               Phase: v1.IntegrationPlatformPhaseReady,
+                       },
+               },
+               EnvVars:        make([]corev1.EnvVar, 0),
+               ExecutedTraits: make([]trait.Trait, 0),
+               Resources:      kubernetes.NewCollection(),
+       }
+       environment.Platform.ResyncStatusFullConfig()
+
+       mt := NewMasterTrait()
+       mt.InjectClient(client)
+       // Initialization phase
+       configured, conditions, err := mt.Configure(&environment)
+       require.NoError(t, err)
+       assert.Empty(t, conditions)
+       assert.False(t, configured)
+}
diff --git a/pkg/trait/builder.go b/pkg/trait/builder.go
index 48ad9dab0..b30fa0cbe 100644
--- a/pkg/trait/builder.go
+++ b/pkg/trait/builder.go
@@ -384,7 +384,7 @@ func (t *builderTrait) builderTask(e *Environment, taskConf 
*v1.BuildConfigurati
        if task.Maven.Properties == nil {
                task.Maven.Properties = make(map[string]string)
        }
-       // User provided Maven properties
+       // User provided build-time properties
        if t.Properties != nil {
                for _, v := range t.Properties {
                        key, value := property.SplitPropertyFileEntry(v)
@@ -396,6 +396,14 @@ func (t *builderTrait) builderTask(e *Environment, 
taskConf *v1.BuildConfigurati
                }
        }
 
+       // Build time property required by master capability
+       if e.IntegrationKit.HasCapability("master") && 
e.CamelCatalog.Runtime.Capabilities["master"].BuildTimeProperties != nil {
+               task.Maven.Properties["camel.k.master.enabled"] = "true"
+               for _, cp := range 
e.CamelCatalog.Runtime.Capabilities["master"].BuildTimeProperties {
+                       task.Maven.Properties[CapabilityPropertyKey(cp.Key, 
task.Maven.Properties)] = cp.Value
+               }
+       }
+
        if e.Platform.Status.Build.PublishStrategy == 
v1.IntegrationPlatformBuildPublishStrategyJib {
                profile, err := 
jib.JibMavenProfile(e.CamelCatalog.GetJibMavenPluginVersion(), 
e.CamelCatalog.GetJibLayerFilterExtensionMavenVersion())
                if err != nil {

Reply via email to