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

commit b6e4d4e2d56aeddcf4390d47747fae09160deb55
Author: Pranjul Kalsi <[email protected]>
AuthorDate: Sat Dec 13 13:02:21 2025 +0530

    chore(trait): deprecate master trait in favour of manual RBAC configuration
---
 docs/modules/ROOT/partials/apis/camel-k-crds.adoc |   9 ++
 docs/modules/traits/pages/master.adoc             | 123 ++++++++++++++++++++++
 e2e/common/traits/master_test.go                  |  88 ++++++++++++++--
 pkg/apis/camel/v1/trait/master.go                 |  10 ++
 pkg/trait/master.go                               |  17 ++-
 pkg/trait/master_test.go                          |  88 ++++++++++++++--
 6 files changed, 318 insertions(+), 17 deletions(-)

diff --git a/docs/modules/ROOT/partials/apis/camel-k-crds.adoc 
b/docs/modules/ROOT/partials/apis/camel-k-crds.adoc
index 511522034..996c48276 100644
--- a/docs/modules/ROOT/partials/apis/camel-k-crds.adoc
+++ b/docs/modules/ROOT/partials/apis/camel-k-crds.adoc
@@ -8326,6 +8326,15 @@ It's activated automatically when using the master 
endpoint in a route, e.g. `fr
 NOTE: this trait adds special permissions to the integration service account 
in order to read/write configmaps and read pods.
 It's recommended to use a different service account than "default" when 
running the integration.
 
+WARNING: The Master trait is **deprecated** and will be removed in future 
release versions.
+This trait requires the operator to manage RBAC explicitly, which should be 
avoided for security
+and simplicity reasons. Users should manually create the required Role and 
RoleBinding, then configure
+Quarkus properties directly:
+
+       -p quarkus.camel.cluster.kubernetes.resource-name=<integration>-lock
+       -p quarkus.camel.cluster.kubernetes.resource-type=Lease
+       -p 
quarkus.camel.cluster.kubernetes.labels."camel.apache.org/integration"=<integration-name>
+
 
 [cols="2,2a",options="header"]
 |===
diff --git a/docs/modules/traits/pages/master.adoc 
b/docs/modules/traits/pages/master.adoc
index e120f47b2..ee82ddf27 100755
--- a/docs/modules/traits/pages/master.adoc
+++ b/docs/modules/traits/pages/master.adoc
@@ -1,6 +1,8 @@
 = Master Trait
 
 // Start of autogenerated code - DO NOT EDIT! (badges)
+[.badges]
+[.badge-key]##Deprecated since##[.badge-unsupported]##2.9.0##
 // End of autogenerated code - DO NOT EDIT! (badges)
 // Start of autogenerated code - DO NOT EDIT! (description)
 The Master trait allows to configure the integration to automatically leverage 
Kubernetes resources for doing
@@ -11,6 +13,15 @@ It's activated automatically when using the master endpoint 
in a route, e.g. `fr
 NOTE: this trait adds special permissions to the integration service account 
in order to read/write configmaps and read pods.
 It's recommended to use a different service account than "default" when 
running the integration.
 
+WARNING: The Master trait is **deprecated** and will be removed in future 
release versions.
+This trait requires the operator to manage RBAC explicitly, which should be 
avoided for security
+and simplicity reasons. Users should manually create the required Role and 
RoleBinding, then configure
+Quarkus properties directly:
+
+       -p quarkus.camel.cluster.kubernetes.resource-name=<integration>-lock
+       -p quarkus.camel.cluster.kubernetes.resource-type=Lease
+       -p 
quarkus.camel.cluster.kubernetes.labels."camel.apache.org/integration"=<integration-name>
+
 
 This trait is available in the following profiles: **Kubernetes, Knative, 
OpenShift**.
 
@@ -63,3 +74,115 @@ Name of the configmap/lease resource that will be used to 
store the lock. Defaul
 |===
 
 // End of autogenerated code - DO NOT EDIT! (configuration)
+
+== Migration Guide
+
+The Master trait is deprecated and will be removed in a future release. This 
trait requires the operator to manage RBAC explicitly, which should be avoided 
for security and simplicity reasons.
+
+=== Why Migrate?
+
+* **Reduced Operator Permissions**: The operator no longer needs permissions 
to create Role and RoleBinding resources
+* **Explicit RBAC Control**: Users have full control over the RBAC 
configuration
+* **Simplified Security Model**: Better separation of concerns between 
integration and infrastructure
+
+=== Migration Steps
+
+==== Step 1: Create RBAC Resources Manually
+
+Apply the following RBAC resources to your namespace:
+
+[source,yaml]
+----
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+  name: <integration-name>-master
+rules:
+  - apiGroups:
+      - coordination.k8s.io
+    resources:
+      - leases
+    verbs:
+      - create
+      - delete
+      - deletecollection
+      - get
+      - list
+      - patch
+      - update
+      - watch
+  - apiGroups:
+      - ""
+    resources:
+      - pods
+    verbs:
+      - get
+      - list
+      - watch
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+  name: <integration-name>-master
+subjects:
+  - kind: ServiceAccount
+    name: <service-account-name>
+roleRef:
+  kind: Role
+  name: <integration-name>-master
+  apiGroup: rbac.authorization.k8s.io
+----
+
+==== Step 2: Configure Quarkus Properties
+
+Instead of using the trait properties, configure the equivalent Quarkus 
properties directly:
+
+[cols="2m,2m,3a"]
+|===
+|Master Trait Property | Quarkus Property | Description
+
+| master.resource-name
+| quarkus.camel.cluster.kubernetes.resource-name
+| Name of the lock resource
+
+| master.resource-type
+| quarkus.camel.cluster.kubernetes.resource-type
+| Type of Kubernetes resource (Lease or ConfigMap)
+
+| master.label-key / master.label-value
+| quarkus.camel.cluster.kubernetes.labels."<key>"
+| Labels for pod identification
+
+|===
+
+==== Example Migration
+
+**Before (using deprecated trait):**
+[source,console]
+----
+$ kamel run MyRoute.java \
+  -t master.resource-name=my-lock \
+  -t master.resource-type=Lease \
+  -t master.label-key=leader-group \
+  -t master.label-value=my-group
+----
+
+**After (using Quarkus properties):**
+[source,console]
+----
+# First apply the RBAC resources
+$ kubectl apply -f master-rbac.yaml
+
+# Then run with Quarkus properties
+$ kamel run MyRoute.java \
+  --service-account my-integration-sa \
+  -p quarkus.camel.cluster.kubernetes.resource-name=my-lock \
+  -p quarkus.camel.cluster.kubernetes.resource-type=Lease \
+  -p quarkus.camel.cluster.kubernetes.labels."leader-group"=my-group
+----
+
+=== Notes
+
+* The `master:` component in Camel routes continues to work; only the 
automatic RBAC creation is removed
+* Ensure the service account used by the integration has the necessary 
permissions
+* For existing integrations, create the RBAC resources before removing the 
trait configuration
diff --git a/e2e/common/traits/master_test.go b/e2e/common/traits/master_test.go
index b545eba1f..731525b80 100644
--- a/e2e/common/traits/master_test.go
+++ b/e2e/common/traits/master_test.go
@@ -31,6 +31,8 @@ import (
        . "github.com/onsi/gomega"
 
        corev1 "k8s.io/api/core/v1"
+       rbacv1 "k8s.io/api/rbac/v1"
+       metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 
        . "github.com/apache/camel-k/v2/e2e/support"
        v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1"
@@ -39,25 +41,46 @@ import (
 func TestMasterTrait(t *testing.T) {
        t.Parallel()
        WithNewTestNamespace(t, func(ctx context.Context, g *WithT, ns string) {
-               t.Run("master works", func(t *testing.T) {
-                       g.Expect(KamelRun(t, ctx, ns, 
"files/Master.java").Execute()).To(Succeed())
-                       g.Eventually(IntegrationPodPhase(t, ctx, ns, "master"), 
TestTimeoutLong).Should(Equal(corev1.PodRunning))
-                       g.Eventually(IntegrationLogs(t, ctx, ns, "master"), 
TestTimeoutShort).Should(ContainSubstring("Magicstring!"))
+               t.Run("master works with properties", func(t *testing.T) {
+                       name := "master"
+                       // Create RBAC resources for the master component
+                       CreateMasterRBAC(t, ctx, g, ns, name, "default")
+
+                       // Run using Quarkus properties instead of deprecated 
trait
+                       g.Expect(KamelRun(t, ctx, ns, "files/Master.java",
+                               "-p", 
fmt.Sprintf("quarkus.camel.cluster.kubernetes.resource-name=%s-lock", name),
+                               "-p", 
"quarkus.camel.cluster.kubernetes.resource-type=Lease",
+                               "-p", 
fmt.Sprintf("quarkus.camel.cluster.kubernetes.labels.\"camel.apache.org/integration\"=%s",
 name),
+                       ).Execute()).To(Succeed())
+                       g.Eventually(IntegrationPodPhase(t, ctx, ns, name), 
TestTimeoutLong).Should(Equal(corev1.PodRunning))
+                       g.Eventually(IntegrationLogs(t, ctx, ns, name), 
TestTimeoutShort).Should(ContainSubstring("Magicstring!"))
                        g.Expect(Kamel(t, ctx, "delete", "--all", "-n", 
ns).Execute()).To(Succeed())
                })
 
-               t.Run("only one integration with master runs", func(t 
*testing.T) {
+               t.Run("only one integration with master runs using properties", 
func(t *testing.T) {
                        nameFirst := RandomizedSuffixName("first")
+                       nameSecond := RandomizedSuffixName("second")
+                       lockName := nameFirst + "-lock"
+
+                       CreateMasterRBAC(t, ctx, g, ns, nameFirst, "default")
+                       CreateMasterRBAC(t, ctx, g, ns, nameSecond, "default")
+
                        g.Expect(KamelRun(t, ctx, ns, "files/Master.java", 
"--name", nameFirst,
-                               "--label", "leader-group=same", "-t", 
"master.label-key=leader-group", "-t", "master.label-value=same", "-t", 
"owner.target-labels=leader-group",
+                               "--label", "leader-group=same",
+                               "-t", "owner.target-labels=leader-group",
+                               "-p", 
fmt.Sprintf("quarkus.camel.cluster.kubernetes.resource-name=%s", lockName),
+                               "-p", 
"quarkus.camel.cluster.kubernetes.resource-type=Lease",
+                               "-p", 
"quarkus.camel.cluster.kubernetes.labels.\"leader-group\"=same",
                        ).Execute()).To(Succeed())
                        g.Eventually(IntegrationConditionStatus(t, ctx, ns, 
nameFirst, v1.IntegrationConditionReady), 
TestTimeoutShort).Should(Equal(corev1.ConditionTrue))
                        g.Eventually(IntegrationLogs(t, ctx, ns, nameFirst), 
TestTimeoutShort).Should(ContainSubstring("Magicstring!"))
-                       // Start a second integration with the same lock (it 
should not start the route before 15 seconds)
-                       nameSecond := RandomizedSuffixName("second")
+
                        g.Expect(KamelRun(t, ctx, ns, "files/Master.java", 
"--name", nameSecond,
-                               "--label", "leader-group=same", "-t", 
"master.label-key=leader-group", "-t", "master.label-value=same", "-t", 
"owner.target-labels=leader-group",
-                               "-t", 
fmt.Sprintf("master.resource-name=%s-lock", nameFirst),
+                               "--label", "leader-group=same",
+                               "-t", "owner.target-labels=leader-group",
+                               "-p", 
fmt.Sprintf("quarkus.camel.cluster.kubernetes.resource-name=%s", lockName),
+                               "-p", 
"quarkus.camel.cluster.kubernetes.resource-type=Lease",
+                               "-p", 
"quarkus.camel.cluster.kubernetes.labels.\"leader-group\"=same",
                        ).Execute()).To(Succeed())
                        g.Eventually(IntegrationLogs(t, ctx, ns, nameSecond), 
TestTimeoutShort).Should(ContainSubstring("started in"))
                        g.Eventually(IntegrationLogs(t, ctx, ns, nameSecond), 
15*time.Second).ShouldNot(ContainSubstring("Magicstring!"))
@@ -65,3 +88,48 @@ func TestMasterTrait(t *testing.T) {
                })
        })
 }
+
+// CreateMasterRBAC creates the Role and RoleBinding
+func CreateMasterRBAC(t *testing.T, ctx context.Context, g *WithT, ns string, 
name string, serviceAccount string) {
+       t.Helper()
+
+       role := &rbacv1.Role{
+               ObjectMeta: metav1.ObjectMeta{
+                       Name:      name + "-master",
+                       Namespace: ns,
+               },
+               Rules: []rbacv1.PolicyRule{
+                       {
+                               APIGroups: []string{"coordination.k8s.io"},
+                               Resources: []string{"leases"},
+                               Verbs:     []string{"create", "delete", 
"deletecollection", "get", "list", "patch", "update", "watch"},
+                       },
+                       {
+                               APIGroups: []string{""},
+                               Resources: []string{"pods"},
+                               Verbs:     []string{"get", "list", "watch"},
+                       },
+               },
+       }
+       g.Expect(TestClient(t).Create(ctx, role)).To(Succeed())
+
+       roleBinding := &rbacv1.RoleBinding{
+               ObjectMeta: metav1.ObjectMeta{
+                       Name:      name + "-master",
+                       Namespace: ns,
+               },
+               Subjects: []rbacv1.Subject{
+                       {
+                               Kind:      "ServiceAccount",
+                               Name:      serviceAccount,
+                               Namespace: ns,
+                       },
+               },
+               RoleRef: rbacv1.RoleRef{
+                       APIGroup: "rbac.authorization.k8s.io",
+                       Kind:     "Role",
+                       Name:     name + "-master",
+               },
+       }
+       g.Expect(TestClient(t).Create(ctx, roleBinding)).To(Succeed())
+}
diff --git a/pkg/apis/camel/v1/trait/master.go 
b/pkg/apis/camel/v1/trait/master.go
index 9a08bb378..a7bf4bb02 100644
--- a/pkg/apis/camel/v1/trait/master.go
+++ b/pkg/apis/camel/v1/trait/master.go
@@ -25,7 +25,17 @@ package trait
 // NOTE: this trait adds special permissions to the integration service 
account in order to read/write configmaps and read pods.
 // It's recommended to use a different service account than "default" when 
running the integration.
 //
+// WARNING: The Master trait is **deprecated** and will be removed in future 
release versions.
+// This trait requires the operator to manage RBAC explicitly, which should be 
avoided for security
+// and simplicity reasons. Users should manually create the required Role and 
RoleBinding, then configure
+// Quarkus properties directly:
+//
+//     -p quarkus.camel.cluster.kubernetes.resource-name=<integration>-lock
+//     -p quarkus.camel.cluster.kubernetes.resource-type=Lease
+//     -p 
quarkus.camel.cluster.kubernetes.labels."camel.apache.org/integration"=<integration-name>
+//
 // +camel-k:trait=master.
+// +camel-k:deprecated=2.9.0.
 type MasterTrait struct {
        Trait `json:",inline" property:",squash"`
 
diff --git a/pkg/trait/master.go b/pkg/trait/master.go
index 7f7f5d7bd..9ac75e780 100644
--- a/pkg/trait/master.go
+++ b/pkg/trait/master.go
@@ -21,6 +21,7 @@ import (
        "fmt"
        "strings"
 
+       corev1 "k8s.io/api/core/v1"
        "k8s.io/utils/ptr"
        ctrl "sigs.k8s.io/controller-runtime/pkg/client"
 
@@ -100,7 +101,21 @@ func (t *masterTrait) Configure(e *Environment) (bool, 
*TraitCondition, error) {
                }
        }
 
-       return enabled, nil, nil
+       // Add deprecation warning condition when the trait is enabled
+       var condition *TraitCondition
+       if enabled {
+               condition = NewIntegrationCondition(
+                       "Master",
+                       v1.IntegrationConditionTraitInfo,
+                       corev1.ConditionTrue,
+                       TraitConfigurationReason,
+                       "The master trait is deprecated and will be removed in 
a future release. "+
+                               "Please manually create the required Role and 
RoleBinding, then configure Quarkus properties directly. "+
+                               "See documentation for migration guide.",
+               )
+       }
+
+       return enabled, condition, nil
 }
 
 func (t *masterTrait) Apply(e *Environment) error {
diff --git a/pkg/trait/master_test.go b/pkg/trait/master_test.go
index 9a9637272..f819f295f 100644
--- a/pkg/trait/master_test.go
+++ b/pkg/trait/master_test.go
@@ -89,9 +89,12 @@ func TestMasterOn(t *testing.T) {
        mt := NewMasterTrait()
        mt.InjectClient(client)
        // Initialization phase
-       configured, conditions, err := mt.Configure(&environment)
+       configured, condition, err := mt.Configure(&environment)
        require.NoError(t, err)
-       assert.Empty(t, conditions)
+       // Verify deprecation condition is present
+       assert.NotNil(t, condition)
+       assert.Equal(t, TraitConfigurationReason, condition.reason)
+       assert.Contains(t, condition.message, "deprecated")
        assert.True(t, configured)
        err = mt.Apply(&environment)
        require.NoError(t, err)
@@ -180,9 +183,10 @@ func TestMasterOff(t *testing.T) {
        mt := NewMasterTrait()
        mt.InjectClient(client)
        // Initialization phase
-       configured, conditions, err := mt.Configure(&environment)
+       configured, condition, err := mt.Configure(&environment)
        require.NoError(t, err)
-       assert.Empty(t, conditions)
+
+       assert.Nil(t, condition)
        assert.False(t, configured)
 }
 
@@ -243,9 +247,12 @@ func TestMasterAuto(t *testing.T) {
        mt.InjectClient(client)
        trait, _ := mt.(*masterTrait)
        // Initialization phase
-       configured, conditions, err := trait.Configure(&environment)
+       configured, condition, err := trait.Configure(&environment)
        require.NoError(t, err)
-       assert.Empty(t, conditions)
+       // Verify deprecation condition is present
+       assert.NotNil(t, condition)
+       assert.Equal(t, TraitConfigurationReason, condition.reason)
+       assert.Contains(t, condition.message, "deprecated")
        assert.True(t, configured)
        err = trait.Apply(&environment)
        require.NoError(t, err)
@@ -261,3 +268,72 @@ func TestMasterAuto(t *testing.T) {
        }
        assert.Equal(t, expectedTrait.Trait, trait.Trait)
 }
+
+func TestMasterTraitDeprecationWarning(t *testing.T) {
+       catalog, err := camel.DefaultCatalog()
+       require.NoError(t, err)
+
+       client, err := internal.NewFakeClient()
+       require.NoError(t, err)
+       traitCatalog := NewCatalog(nil)
+
+       environment := 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.IntegrationPlatformBuildPublishStrategyJib,
+                                       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, 0),
+               Resources:      kubernetes.NewCollection(),
+       }
+       environment.Platform.ResyncStatusFullConfig()
+
+       mt := NewMasterTrait()
+       mt.InjectClient(client)
+       configured, condition, err := mt.Configure(&environment)
+       require.NoError(t, err)
+       assert.True(t, configured)
+
+       require.NotNil(t, condition)
+       assert.Equal(t, TraitConfigurationReason, condition.reason)
+       assert.Contains(t, condition.message, "deprecated")
+       assert.Contains(t, condition.message, "removed")
+       assert.Contains(t, condition.message, "Role")
+       assert.Contains(t, condition.message, "RoleBinding")
+       assert.Contains(t, condition.message, "Quarkus properties")
+}

Reply via email to